ADR-085
spec/workflow.schema.json invoke.tool oneOf · interim hand-derived bridge to engine schemars codegen
accepted · 2026-05-27 · L0 · cites 7
0ADR-085 · spec workflow.schema.json oneOf · bridge to schemars codegen #
Context #
nika/spec/schemas/workflow.schema.json ships the structural contract
for nika: v1 workflow documents · consumed by JSON-Schema-aware tools
(IDE plugins · yaml-language-server · CI validators · 3rd-party
authoring tooling).
Pre-this-ADR · the invoke.tool field was a permissive regex pattern ·
"tool": { "type": "string", "pattern": "^(nika|mcp):[a-z][a-z0-9_]*(/[a-z][a-z0-9_]*)*$"}This permits ANY nika:<name> string. yaml-language-server (the canonical
LSP backbone for VS Code · IntelliJ · Neovim) cannot autocomplete from
regex patterns · only from closed enum arrays per JSON-Schema 2020-12
spec. The cookbook author writes tool: nika: and gets ZERO completion
suggestions · friction the new-author surface absorbs disproportionately.
The G1 LSP-readiness research (per nika-lsp-readiness-design-v0.1 ·
D-2026-05-27-N2) established the central theorem · « one obvious way »
(D-2026-05-22-N6 stdlib-collapse 42→26) IS « LSP-perfect » (closed
finite enumerable set) · they're the same property at different layers.
The catalog reconciliation (ADR-084) closed the engine-side · this ADR
closes the spec-side.
Decision #
nika/spec/schemas/workflow.schema.json invoke.tool field splits from
the single regex pattern into a oneOf ·
- nika branch · CLOSED enum of 26 canonical builtins (sorted
alphabetically from
nika/spec/stdlib/builtins-v0.1.md·nika:assert·nika:cost· ... ·nika:write). Enables yaml-language-server LSP-perfect autocomplete for any cookbook author. - mcp branch · KEEPS the open regex pattern (
^mcp:[a-z][a-z0-9_]* (/[a-z][a-z0-9_]*)*$) · MCP tool surface is external + plugin-dynamic · closed-enum is structurally impossible (each MCP server publishes its own dynamic catalog at runtime).
Engine companion · nika-schema::codegen::nika_builtin_tool_enum_schema()
emits the same enum derived from nika_catalog::ALL_BUILTINS at
call time · the no-drift gate (spec hand-derived → engine catalog
canonical) is enforced by nika-schema/src/codegen.rs::tests:: enum_matches_catalog_one_to_one.
Why interim hand-derived (NOT engine-codegen output today) #
Per LOCK-031 trigger-gate discipline (no infra behind a locked gate)
- scope-shrink (canon NOW · code DEFER per consumer signal) ·
- LSP value is operator-immediate · the hand-derived oneOf gives
yaml-language-server consumers 100% of the LSP-completion value
TODAY · zero engine-ship dependency · zero schemars adoption ·
zero
nika-schema-codegenxtask required. - Schemars adoption requires nika-schema parser maturation · the
full G1 ratchet (
WorkflowDocumentRust type with#[derive(JsonSchema)]+ `#[schemars(schema_with = ...)]` on the `tool` field) requires the WIP nika-schema parser scaffolding to reach a complete `WorkflowDocument` shape · today nika-schema ships parser + raw + types modules but the canonical document-level type is still maturing. - The bridge is the right shape · per the schema file header comment
(« INTERIM hand-derived schema · prose spec is the single source of
truth · regenerate + diff against engine
nika-schemaat GA ») · the decision was always to hand-derive then codegen · this ADR locks the transition mechanic.
Future ratchet (trigger-gated) #
The full schemars-adoption G1 codegen (the « next ADR-086 » slot) ships when ALL of these trigger gates clear ·
- nika-schema parser maturity ·
WorkflowDocumentis the canonical document-level Rust type · all sub-types (Task·Verb·Invoke·Agent· etc.) admitted into the public surface. - Cookbook `$schema` modeline adoption · ≥1 example in
nika/spec/examples/carries# yaml-language-server: $schema=...pointing toworkflow.schema.json· proving the LSP-consumer path is live. - LSP UX feedback signal · ≥1 explicit operator-side incident OR feature request citing « completion missed builtin X » OR « workflow schema autocomplete is the killer feature ».
- schemars 1.0 in workspace.dependencies · today absent · added
only when the full
WorkflowDocumentderive is shippable.
When all 4 clear · the future ADR-086 (or ADR-085 extension) authors ·
crates/nika-schema-codegen/xtask binary (orcargo run --bin nika-schema-codegen) that emitsworkflow.schema.jsonfromschema_for!(WorkflowDocument).nika-schema::codegen::nika_builtin_tool_enum_schemabecomes theschema_withadapter consumed by schemars during the derive.- The hand-derived
nika/spec/schemas/workflow.schema.jsonbecomes a CHECK-IN CODEGEN OUTPUT (regenerable + diff-able · per Diamond Rule 7 hallucination forbidden + Self-Evolving Olympus PILLAR 1 projection- by-default). - Hygiene vector
check-spec-schema-codegen-parity(P1/fail) verifies spec/schemas/workflow.schema.json matches the engine codegen output at commit time · structural no-drift gate.
Consequences #
Positive #
- LSP value lands now · yaml-language-server autocompletes the 26 builtins · zero engine dependency · operator wins immediately.
- No-drift property structural at engine layer ·
nika_builtin_tool_enum_schema()readsALL_BUILTINSat call time · catalog mutations propagate automatically · the 8 conformance tests incodegen.rsenforce parity (26 entries · alphabetically sorted · prefixednika:· matches catalog · includes canonical anchors · excludes legacy post-D-N6). - Bridge is publicly documented · the schema file header + this ADR+ `nika-schema::codegen` module doc all cross-reference the future ratchet · no surprise transition when ADR-086 ships.
- Symbol seam established ·
pub fn nika_builtin_tool_enum_schemais the seam future schemars adoption plugs into (schema_withattribute target) · the migration is mechanical when triggered.
Negative #
- Spec ↔ engine sync is manual today · if
ALL_BUILTINSmutates (e.g. spec adds 27th builtin via a future D-lock) · the spec schema's hand-derived enum must be regenerated manually · the engine no-drift test catches the catalog side but NOT the schema-file side until the future hygiene vector ships.- Mitigation · spec changes to builtins are extremely rare (governed by D-locks per `nika/spec/stdlib/builtins-v0.1.md` § Promotion criteria · the canon is « one obvious way » per D-N6). - Mitigation · `nika-schema::codegen::nika_builtin_tool_enum_schema` PRINTS the canonical JSON-Schema fragment a maintainer can paste into the spec schema (zero invention · just copy). - Schemars dep deferred · the full G1 codegen ratchet ships only when nika-schema parser matures + LSP UX signal · this is intentional scope-shrink, not weakness.
Neutral #
- L0 layer constraint preserved ·
codegen.rsis pure-Rust · zero I/O · zero async ·serde_jsonalready in nika-schema deps · no new crate · no L0 violation. - Tests · +8 net new in
nika-schema/src/codegen.rs::tests· enum_has_26_entries · enum_alphabetically_sorted · enum_all_prefixed_with_nika · enum_matches_catalog_one_to_one · schema_is_string_type · schema_has_description · enum_includes_canonical_anchors · enum_excludes_legacy_post_d_n6.
Alternatives considered #
A · Generate workflow.schema.json from schemars NOW #
REJECTED · requires WorkflowDocument Rust type maturation (nika-schema
WIP) · scope creep + premature surface lock · LSP value lands without
this · scope-shrink discipline applies.
B · Keep regex pattern · accept zero LSP completion #
REJECTED · the LSP-perfection theorem (D-2026-05-27-N2) established that « one obvious way » + closed-enum-schema = SAME PROPERTY · this ADR materializes the property at the spec surface.
C · Generate the schema fragment in a build.rs at engine build time #
REJECTED · engine build artifacts don't propagate to the spec submodule · the spec is a separate distribution · build.rs would be invisible to external schema consumers. The future codegen xtask (ADR-086) writes the file at the spec submodule path · explicit + checked-in.
Gate verification #
This is a refactor of admitted-crate data + types (not a new crate admission) · the ADR-003 12-gate ceremony reduces to applicable gates ·
- ✅ Gate 2 TDD · 8 new conformance tests in
codegen.rs· the no-drift gate (enum_matches_catalog_one_to_one) is the canonical seam. - ✅ Gate 3 IMPL ·
cargo check --workspaceGREEN. - ✅ Gate 4 CLIPPY ·
cargo clippy --workspace --all-targets -- -D warnings0 warnings. - ✅ Gate 8 DOCS ·
cargo doc --no-deps -p nika-schema0 warnings. - ✅ Gate 12 ATOMIC · single engine commit.
cargo fmt --checkGREEN.cargo deny checkadvisories+bans+licenses+sources ok.- Gates 1 / 5 / 6 / 7 / 9 / 10 / 11 · N/A for data refactor.
Companion artefacts #
nika-schema/src/codegen.rs·pub fn nika_builtin_tool_enum_schema+ 8 conformance tests · the engine-side no-drift gate.nika-schema/src/lib.rs· module registration + re-export.nika/spec/schemas/workflow.schema.json(separate submodule commit · invoke.tool oneOf · 26-builtin closed enum).- Internal G1 design + closure artefacts in private monorepo (cited by D-id only · audit trail preserved at the source-of-truth surface).
References #
- ADR-084 · nika-catalog reconciliation to spec 26 (prerequisite)
- ADR-082 · envelope
nika: v1single-version-marker nika/spec/stdlib/builtins-v0.1.md· the canonical 26 source-of-truth- Internal · D-2026-05-27-N2 LSP-perfection theorem (private blueprint)
- Internal · LOCK-031 trigger-gate discipline (private doctrine)
- Internal · scope-shrink discipline (canon NOW · code DEFER)
- Internal · cross-source-validation §2.6 Signal 7 (verify-the-auditor)
🦋
read at v0.107.0 · the decision record ships with the engine