Goal
Consolidate stem's already-decent boundary validation into a structured layer matching seed + niac.
Context
Stem is in the best shape of the three sibling repos but has gaps. Audited 2026-05-24:
- ✅
decodeJSONStrict() helper in internal/api/server.go (good — used by auth/license/recovery/settings handlers)
- ❌ Post-decode validation is inline per-handler and inconsistent (e.g.,
testType == "" defaults, interface existence checks)
- ❌ TS side relies on hand-rolled type guards:
isStemRole(), isModeUpdateResponse() in ui/src/contexts/RoleContext.tsx
- ❌ SSE consumer has no per-frame validation
- ❌ No schema generation; TS types hand-written and silently drift from Go structs
Children
Phasing
| Phase |
Children |
Goal |
| 2. Go validators |
#268 |
Declarative tags replace inline checks |
| 3. Schema gen pipeline |
#269, #270, #271 |
One source of truth: Go struct → JSON Schema → TS types |
| 4. Trust boundary parsers |
#272 |
Validate SSE frames + outbound API responses |
Tech choices
- Go validator:
github.com/go-playground/validator/v10
- Schema generator:
github.com/invopop/jsonschema (mirrors NIAC's reference impl)
- TS codegen:
json-schema-to-typescript
- TS runtime parser:
valibot
Cross-repo
Goal
Consolidate stem's already-decent boundary validation into a structured layer matching seed + niac.
Context
Stem is in the best shape of the three sibling repos but has gaps. Audited 2026-05-24:
decodeJSONStrict()helper ininternal/api/server.go(good — used by auth/license/recovery/settings handlers)testType == ""defaults, interface existence checks)isStemRole(),isModeUpdateResponse()inui/src/contexts/RoleContext.tsxChildren
go-playground/validator/v10+ struct tags; consolidate scattered post-decode checksinvopop/jsonschemagenerator pattern →docs/schemas/*.jsonRoleContext.tsxPhasing
Tech choices
github.com/go-playground/validator/v10github.com/invopop/jsonschema(mirrors NIAC's reference impl)json-schema-to-typescriptvalibotCross-repo