feat(shared): roster-assistant proposal contract (#1210) #1217
No reviewers
Labels
No labels
area:agents
area:dashboard
area:database
area:design
area:design-review
area:flows
area:infra
area:meta
area:security
area:sessions
area:webhook
area:workdir
security
type:bug
type:chore
type:meta
type:user-story
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
charles/agent-hooks!1217
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "code-lead/1210"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
First ticket of the Agents Roster AI Assistant feature — lands the typed shape both halves of the assistant agree on so the rest of the feature can build against a single contract. Closes #1210.
packages/shared/src/roster-proposal.ts:ProposalEnvelope,ProposedAgent,ProposedToolPermission,ProposedMcpServer,CatalogSnapshottypes.MODEL_TIERS,TOOL_PERMISSION_MODES,DEFAULT_REFINEMENTS_SOFT_CAPconsts.PROPOSAL_ENVELOPE_JSON_SCHEMA(draft 2020-12) — the same object the server hands to Claude asresponse_formatand re-validates the response against (defence in depth).name→agent_type.name + agents.name,system_prompt→agent_type.system_prompt_template,skills[]→agent_skill,mcp_servers[]→mcp_server, …) so the next ticket walks oneProposedAgentand writes straight to the existing roster store with zero translation.nameregex on the JSON schema mirrorshandleAgentsCreate's^[a-z][a-z0-9-]*$so the assistant fails fast in the structured-output validator instead of the acceptor 400-ing after a refinement loop.roster-proposal.test.ts:parent_proposal_id, optionalrationale/setup_notes).additionalProperties: false), emptyagents, invalid tier / mode, name regex violations, non-integer / negativeversion, malformedcreated_at, wrong-typeparent_proposal_id, missing required fields on tool/mcp entries.createAgent()and downstream stores so a future field rename can't silently widen the surface.ajv+ajv-formatsadded as devDeps on@claude-hooks/shared(validator only used in tests; the schema literal stays runtime-dep-free).Test plan
bun test packages/shared/src/roster-proposal.test.ts— 30/30 pass.just qa— typecheck + lint + format + sql-layer-check + paraglide + i18n + flow-schema all green; 3362-test server suite still passes.🤖 Generated with Claude Code
Review —
feat(shared): roster-assistant proposal contract(#1210)CI: ✅ green · Mergeable: ✅
Overview
Clean contract-only PR. Lands the typed shape (
ProposalEnvelope,ProposedAgent,CatalogSnapshot), the draft 2020-12 JSON Schema, and 30 contract tests covering round-trip, schema accept/reject, and write-path alignment. Every deliverable from issue #1210 is present and the destination map comment is accurate against the live schema.Verified against:
apps/server/src/main.tshandleAgentsCreate— name regex/^[a-z][a-z0-9-]*$/matches the schemapatternexactly ✅infrastructure/database/schema/agents.ts—name,type,prompt_appendix,match_labels,notes,lifecycle,idle_stop_secondsall align withwriteArgsin the write-path test ✅infrastructure/database/schema/agent-type.ts—system_prompt_templatedirection correct;rolecolumn is separate from the proserolefield and the destination map calls this out correctly ✅infrastructure/database/schema/agent-skill.ts—skills[]maps toagent_skill.name(the natural key), consistent with the proposal's string-array shape ✅Findings
1. Missing
format: "uuid"on the three ID fields — suggest adding before follow-on tickets build on thisproposal_id,parent_proposal_id(the string branch), andsession_idare documented as UUIDs (issue text: "Stable id (uuid)") but the schema only validatesminLength: 1.ajv-formatsis already a devDep and the tests already spin up an AJV instance withaddFormats. Addingformat: "uuid"to all three would:Not a blocker for this ticket since the schema is defence-in-depth over the structured-output gate, but worth landing before the propose-endpoint ticket starts consuming this schema in production.
2.
defaultinrefinements_soft_capschema property has no runtime effect as configuredThe
defaultis useful documentation for the Claude structured-output call (the model sees it in the schema) but the AJV validator inbuildValidator()doesn't setuseDefaults: true, so a response that omits the field would fail therequiredcheck rather than get the default applied. This is actually the correct behaviour (the field is inrequired), but the server-side code that eventually calls AJV with this schema should be aware thedefaultis schema-documentation-only, not an AJV coercion trigger. Worth a one-line comment on the constant when the propose-endpoint ticket lands.3. Validator instantiation inconsistency — minor
The accept-path describe block creates a fresh
buildValidator()per test; the reject-path describe block shares a single instance at describe scope. AJV compiled validators are reentrant so there's no bug, but the inconsistency is mildly surprising. Either pattern is fine — just worth making uniform if this file grows.Summary
All three ACs from #1210 are fully met, the destination map is accurate, the name regex matches the live handler, and the 30 tests exercise both the happy-path and the rejection surface. Finding #1 is the only one worth acting on before the propose-endpoint ticket ships; #2 and #3 are informational.
APPROVED. Landing this unblocks the rest of the feature.