problemDerived runnable modules freeze the registrar interface
spec:extraction.runnable-modulesPacks: pack:adopter-speed-v1, pack:self-hosting-v1
Design: 7 design entries · 5 pinned declarations · 4 decisions
Findings: no errors, no warnings
specs/extraction/runnable-modules.sdp.md
No open questions and no blocking questions; implementation binding present, verifier binding present, and expected-outcome oracle none.
Intent
- A bound example test would otherwise repeat registration, step dispatch and oracle comparison that the graph and contracts already determine.
outcome- Give each bindable example a generated registrar, so its authored test keeps semantics derivation cannot supply.
value- A Spec and a small authored semantics module express the test while the engine neither loads nor executes adopter code.
Behavior
ruleline 28 ·specs/extraction/runnable-modules.sdp.md:28- Code generation emits a registrar called with
createWorld,invoke,observe,expectedand optionalassertions. It owns describe/it registration, step dispatch, the three-way comparator and failure rendering. The authored test keeps its top-levelspecTestanchor and one activation call beside its semantics. The authored file imports the generated registrar; the generated module imports no authored code. Normal runner discovery loads the authored file and activates registration. Generated modules never self-register; the graph records binding, never liveness. ruleline 29 ·specs/extraction/runnable-modules.sdp.md:29- Generated sibling path — for each bindable example, codegen emits exactly one generated registration module as a sibling
*.generated.tsof the authored.test.ts(deterministic filename keyed by Spec ID, not by the authored suite filename). One authored suite may verify many examples without their registrar map keys or output paths colliding. Exactly one generated registration per example; a second registration path for the same example is refused. Generated siblings are disposable build output: an adopted registrar is committed because authored code imports it, while unadopted registrars remain ignored, regenerable output and confer no migration claim. ruleline 30 ·specs/extraction/runnable-modules.sdp.md:30generated/registrars.jsonis a versioned, sorted manifest of every extraction-root-relative registrar sibling owed by the build. Publication reconciles the manifest and removes siblings no longer owed under the build-pipeline law.--check-cleancompares the manifest and every sibling byte with fresh generation. Preflight independently compares every tracked adopted registrar in the worktree and Git index with regenerated bytes.ruleline 31 ·specs/extraction/runnable-modules.sdp.md:31- Registration-path loudness — multiple anchors in one authored suite still locate one registrar sibling; when one example resolves to more than one distinct authored suite path, generation emits a named finding and withholds that registrar rather than selecting a path silently.
ruleline 32 ·specs/extraction/runnable-modules.sdp.md:32- Generated registrars import shared runtime helpers from
@libar-dev/software-delivery-protocol/testingand types through public package subpaths. Public exports are the root for model, anchors and public types,./anchorsfor zero-dependency anchor builders,./runnerfor the framework-neutral plan/run core,./vitestforbindExample, and./testingfor the registrar runtime. Authored suites may import anchor builders through the root or./anchors. Authored and generated modules never import privatesrcpaths. The supported optional Vitest peer range is>=2, as declared by the package. ruleline 33 ·specs/extraction/runnable-modules.sdp.md:33- Step identity is the step's contract key: its step key under
spec:carrier.step-keys, or its skeleton text when it has none, and generation introduces no ordinal or slug identity. A directly authoredbindExamplehandler map fails the typecheck when one of its keys goes stale. An adopted registrar holds no authored per-step handler: a stale committed registrar fails the regeneration preflight, and its internal handlers change with the contract when it is regenerated. The contract key of a Then step of the example space also names an Outcomekind, so changing it fails the typecheck of an authored oracle that names the old kind. ruleline 34 ·specs/extraction/runnable-modules.sdp.md:34- Handler resolution — the generated registrar's internal dispatch object is an exhaustive mapped type over the contract's step keys: removing or staling one of those generated callbacks is a
tscerror naming that step. The adopted authored surface retains no per-step callbacks (and therefore no step keys); its five adapters are required keys (assertionsoptional), so a missing or stale authored callback is atscerror naming that adapter. These are the two exhaustive mapped-type checks; demanding an authored error name a step while simultaneously retaining no authored per-step surface is refused as contradictory. Codegen cannot statically see authored handlers, so no generation-time handler refusal is promised — the type system carries it. Existing generation-time refusals (unbindable example, incompatible vocabulary, colliding path) are reused unchanged. ruleline 35 ·specs/extraction/runnable-modules.sdp.md:35- Scenario-level invocation — the five-adapter interface carries one product
invokeper example, so the firstWhenoccurrence invokes it and laterWhenoccurrences in the same lawful scenario do not repeat side effects. ruleline 36 ·specs/extraction/runnable-modules.sdp.md:36- At the first Then, the comparator refuses an incomplete point before calling the oracle and otherwise evaluates and caches
expected(point). It finds the authored Then whose contract key equalsexpected.kind, compares that step's authored parameters with the oracle payload, readsobserve(world), compares observed and oracle outcomes, and runs additional assertions separately. Actual-equals-oracle alone is insufficient: a Spec parameter mutation must fail. The oracle acceptsPartial<Conditions>; the registrar never casts a partial point to Conditions. ruleline 37 ·specs/extraction/runnable-modules.sdp.md:37- Oracle payload versus Spec Then parameters and observed versus oracle outcomes use deep structural equality on the authored shape. Diagnostics name missing, extra or changed values by path and show expected and actual values. The comparator quotes authored Then parameters and skeleton text without rephrasing them.
ruleline 38 ·specs/extraction/runnable-modules.sdp.md:38- Failure rendering reuses the contract's matching Then step plus the existing
renderContractSteppath. When the oracle selected no authored Then, emit a scenario-level diagnostic quoting Spec ID, oracle kind, and the contract keys of the available Then steps; preserve the original assertion ascause. ruleline 39 ·specs/extraction/runnable-modules.sdp.md:39- Bound unspecified outcome —
unspecifiednames a coverage gap in the example space, but a bound executable example selecting it reddens because no authored Then may silently witness that gap. ruleline 40 ·specs/extraction/runnable-modules.sdp.md:40- The authored top-level
specTestcarries a non-emptyverifiestarget naming the example. It remains the source ofhas-verifier; generated execution confers no delivery fact and records no pass state. ruleline 41 ·specs/extraction/runnable-modules.sdp.md:41- Additive assertions — optional
assertionsmay add domain checks the Outcome union cannot express; they never replace steps (3) or (5) of the three-way comparator and never silence a Spec-param or oracle mismatch. ruleline 42 ·specs/extraction/runnable-modules.sdp.md:42- Case mapping and outlines — outline rows map one deterministic case per row in carrier order when a multi-row table sugar expands statically into sibling examples (point-per-example). Scenario Outlines and inline Examples tables stay refused as executable carrier constructs inside a single example; one example remains one point and earns exactly one generated registration.
ruleline 43 ·specs/extraction/runnable-modules.sdp.md:43- Registrars use no environment, global or module side channel, mutable global registry or singleton world shared across examples. Generated siblings export the registrar and never self-register. Generation derives no domain observation; the engine never loads or executes adopter code. Registrars do not become generated test harnesses. Scenario Outlines stay refused. The interface changes neither the claim taxonomy, anchor bindings nor the contract-key identity of steps.
16 unkeyed rules.
Design
registrarline 46 ·specs/extraction/runnable-modules.sdp.md:46export declare function registerExample<World>(adapters: RunnableExampleAdapters<World, Conditions, Outcome>): void;is the signature template; substitute the generated function name and the parent's generated Conditions and Outcome names.no citation recorded
registrarNameline 47 ·specs/extraction/runnable-modules.sdp.md:47- Prefix
registerto the example's final dot-delimited id segment in PascalCase after removing the first occurrence of the parent's final segment in PascalCase; if removal leaves an empty name, use the example's PascalCase segment. PascalCase splits that segment at non-alphanumeric characters, capitalizes each part, and prefixesXwhen the result starts with a digit. adaptersline 48 ·specs/extraction/runnable-modules.sdp.md:48interface RunnableExampleAdapters<W, C, O extends { readonly kind: string }> { readonly createWorld: (point: Partial<C>) => W; readonly invoke: (world: W, point: Partial<C>) => void | Promise<void>; readonly observe: (world: W) => O; readonly expected: (point: Partial<C>) => O; readonly assertions?: (world: W, observed: O) => void | Promise<void>; }no citation recorded
registrarPathline 49 ·specs/extraction/runnable-modules.sdp.md:49- The authored suite directory plus the example id without the
spec:prefix and the suffix.test.generated.ts, retaining the id's dots. registrarManifestline 50 ·specs/extraction/runnable-modules.sdp.md:50generated/registrars.jsoncarries the versioned, sorted extraction-root-relative registrar paths owed by the build.no citation recorded
thenBindingline 51 ·specs/extraction/runnable-modules.sdp.md:51compareContractOutcome(execution, { kind: "then", key, text, params })is the generated binding of a Then step; it compares only the step whosekeyequals the oracle'skind.no citation recorded
noMatchingThenline 52 ·specs/extraction/runnable-modules.sdp.md:52scenario <spec>: oracle selected "<kind>"; available Then steps: "<key>", …is the diagnostic when no authored Then matches, listing each Then step's contract key in contract order, with the original assertion ascause.no citation recorded
Verification
Verifier bindings
- test:protocol.runnable-modules · verifies generated runnable-module registration and runtime semantics · anchored · test anchor binding present ·
test/testing.test.ts:58External run evidence from
test/testing.test.tsTest anchors:
test:protocol.runnable-modules. Specs verified by those bindings: spec:extraction.runnable-modules.Run 1: external run evidence from vitest 4.1.10
Build: CI 380 · Branch: feature/roadmap-and-slices-v0 · Commit: 34909918bf0be0e849959644401797191cae379f · Start: 2026-10-22T17:40:31.456Z · Stop: 2026-10-22T17:42:16.171Z
Name Suite Result Duration compares Spec Then params, oracle payload, and the observed outcome before assertions generated registrar testing runtime passed 1.86 ms invokes the scenario-level product call only once across repeated When steps generated registrar testing runtime passed 0.86 ms renders the matching contract step for a Spec/oracle payload mismatch generated registrar testing runtime passed 0.9 ms stops before domain assertions when observation disagrees with the oracle generated registrar testing runtime passed 1.11 ms emits a scenario diagnostic when the oracle selects no authored Then generated registrar testing runtime passed 0.33 ms refuses incomplete-point comparison at Then without calling the oracle generated registrar testing runtime passed 0.47 ms - test:protocol.runnable-modules-generation · checks registrar generation, sibling paths and competing suite paths · anchored · test anchor binding present ·
test/codegen.test.ts:22External run evidence from
test/codegen.test.tsTest anchors:
test:protocol.runnable-modules-generation. Specs verified by those bindings: spec:extraction.runnable-modules.Run 1: external run evidence from vitest 4.1.10
Build: CI 380 · Branch: feature/roadmap-and-slices-v0 · Commit: 34909918bf0be0e849959644401797191cae379f · Start: 2026-10-22T17:40:31.456Z · Stop: 2026-10-22T17:42:16.171Z
Name Suite Result Duration emits a space contract per example-space parent and a step contract per bindable example the contracts codegen stage passed 5.81 ms emits an exhaustive sibling registrar for a bindable anchored example the contracts codegen stage passed 1.41 ms keys sibling registrar paths by Spec ID when one authored suite verifies multiple examples the contracts codegen stage passed 4.35 ms refuses multiple distinct registrar paths for one example the contracts codegen stage passed 1.32 ms is deterministic: two generations over one graph are byte-identical the contracts codegen stage passed 0.99 ms refuses a prose-only example and an unbound-slot example — refusing is the honest behavior the contracts codegen stage passed 0.6 ms emits a step contract with inferred param types when no parent vocabulary matches the contracts codegen stage passed 0.42 ms withholds a child contract when its steps drift outside an existing parent vocabulary the contracts codegen stage passed 1.48 ms withholds a child contract when multiple parent spaces type one skeleton incompatibly the contracts codegen stage passed 1 ms keeps a partial point honest: only the bound dimensions enter the point the contracts codegen stage passed 0.69 ms keeps incomplete contract points in reader and recipe coverage the contracts codegen stage passed 6.43 ms counts the review probe's small binding with an empty Then in recipe 44 the contracts codegen stage passed 1.89 ms warns and drops an undeclared slot from the point — loud, never silent (L2) the contracts codegen stage passed 0.67 ms warns and drops an off-dimension value; the step contract widens the slot so it still compiles the contracts codegen stage passed 0.76 ms warns on a conflicting re-binding of one skeleton within one example — first wins (MD-17) the contracts codegen stage passed 0.71 ms warns on a conflicting dimension declaration across vocabulary steps — first wins the contracts codegen stage passed 0.34 ms warns on conflicting declarations of one dimension within a vocabulary step — first wins the contracts codegen stage passed 0.38 ms refuses to guess the single-quoted-literal vocabulary form — deferred to the grammar session, loudly the contracts codegen stage passed 0.47 ms warns on a vocabulary slot that declares no usable type — an authored slot never falls out silently (L2) the contracts codegen stage passed 0.43 ms binding a Then-vocabulary slot is ordinary (the outcome point) — no undeclared-slot warning the contracts codegen stage passed 0.69 ms emits nothing over a graph with no example spaces and no bindable examples the contracts codegen stage passed 0.19 ms derives contract AND point from the first complete entry, and names extra entries (MD-17 made loud) the contracts codegen stage passed 0.59 ms warns on a conflicting re-binding of one slot WITHIN a single step text — first wins the contracts codegen stage passed 0.52 ms withholds the contracts tree WHOLE on a case-colliding path — all-or-nothing, never a second gate the contracts codegen stage passed 0.81 ms - test:protocol.runnable-modules-types · checks registrar skeletons and required authored adapters · anchored · test anchor binding present ·
test/runnable-modules.typecheck.ts:5
Design view
Requirement drawing
- Declared verifying examples: no declared verifying example.
- E9: verifies · anchored
The diagram as text
- spec:extraction.runnable-modules: Derived runnable modules freeze the registrar interface · declared Spec
- spec:decisions.adopted-registrars-committed: Adopted runnable registrars are committed · declared Spec
- spec:extraction.example-runner: A bound example runs its contract steps against a fresh world · declared Spec
- component:protocol.codegen: contracts and registrar output · anchored code ·
src/codegen/contracts.ts:633 - component:protocol.testing: the generated registrar runtime · anchored code ·
src/testing/index.ts:38 - impl:protocol.example-testing-helpers: adapter-lifecycle and oracle-comparison helpers for bound examples · anchored code ·
src/testing/index.ts:50 - impl:protocol.runnable-modules: derives and publishes runnable registrar modules from the graph · anchored code ·
src/codegen/contracts.ts:658 - test:protocol.runnable-modules: verifies generated runnable-module registration and runtime semantics · anchored test binding · binding present ·
test/testing.test.ts:58 - test:protocol.runnable-modules-generation: checks registrar generation, sibling paths and competing suite paths · anchored test binding · binding present ·
test/codegen.test.ts:22 - test:protocol.runnable-modules-types: checks registrar skeletons and required authored adapters · anchored test binding · binding present ·
test/runnable-modules.typecheck.ts:5 - spec:decisions.adopted-registrars-committed refines spec:extraction.runnable-modules · declared
- spec:extraction.runnable-modules refines spec:extraction.example-runner · declared
- component:protocol.codegen satisfies spec:extraction.runnable-modules · anchored
- component:protocol.testing satisfies spec:extraction.runnable-modules · anchored
- impl:protocol.example-testing-helpers satisfies spec:extraction.runnable-modules · anchored
- impl:protocol.runnable-modules satisfies spec:extraction.runnable-modules · anchored
- test:protocol.runnable-modules verifies spec:extraction.runnable-modules · anchored
- test:protocol.runnable-modules-generation verifies spec:extraction.runnable-modules · anchored
- test:protocol.runnable-modules-types verifies spec:extraction.runnable-modules · anchored
Mermaid source
Copyable source; the Studio draws static SVG.
Preferred and chosen syntax: requirementDiagram; exact refines, satisfies and verifies, with claim words in nodes and per-edge comments.
Decisions
- Adopted runnable registrars are committed
A registrar becomes adopted when tracked authored code imports it. Adopted registrars are committed and byte-checked against fresh generation; unadopted registrars remain ignored, regenerable output. A deterministic generated manifest owns reconciliation for both groups without turning registrar content into authored truth.
- The comment form is a second anchor representation
An anchor has two extracted representations that feed one closed envelope, the constant form as ruled and a comment form. The comment form is a top-level
/** … */block in a.tsor.tsxfile carrying reserved camelCase, TSDoc-compatible tags;@sdpAnchor <id>opens it, and the id's namespace selects the flavor exactly as the constant builders do. Comment prose still authors nothing: only the reserved tags are read, and a comment that does not open with/**is never read. Trust is by reserved grammar, not by builder import, so no import is required. The same id in two forms is a duplicate id. The package also ships a zero-dependency subpath@libar-dev/software-delivery-protocol/anchorsexporting the id builders and the three anchor builders and nothing else, for runtimes that may import but must not loadnode:*modules or ts-morph; the extractor trusts that specifier as a Protocol builder module. - Bindings state existence, not liveness
Delivery facts record bindings and enabled verifier existence; coverage gaps and human readiness practice remain explicit without becoming graph facts.
- Each example binds one point
An example binds exactly one point; table syntax may expand statically into sibling examples and renderers may project siblings as a table.
Runtime
component:protocol.codegen· contracts and registrar output ·src/codegen/contracts.ts:633· layer application · context protocol Uses
component:protocol.graph· the one graph and its semantic decisions ·src/graph/schema.ts:34· layer domain · context protocol component:protocol.notation· the leaf step codec ·src/notation/slots.ts:261· layer domain · context protocol component:protocol.reader· the typed graph contract and body evaluator ·src/reader/reader.ts:671· layer application · context protocol component:protocol.validate· one finding currency and ordered checks ·src/validate/validators.ts:72· layer domain · context protocol
component:protocol.testing· the generated registrar runtime ·src/testing/index.ts:38· layer adapter · context protocol Uses
component:protocol.adapters· external runtime and source adapters ·src/adapters/vitest.ts:23· layer adapter · context protocol component:protocol.runner· provider-neutral example execution ·src/runner/index.ts:99· layer application · context protocol
impl:protocol.example-testing-helpers· adapter-lifecycle and oracle-comparison helpers for bound examples ·src/testing/index.ts:50Component:
component:protocol.testing· the generated registrar runtime ·src/testing/index.ts:38· layer adapter · context protocol Uses
None.
impl:protocol.runnable-modules· derives and publishes runnable registrar modules from the graph ·src/codegen/contracts.ts:658Component:
component:protocol.codegen· contracts and registrar output ·src/codegen/contracts.ts:633· layer application · context protocol Uses
None.
Runtime observation: not tracked.
Evidence
Runtime observation: not tracked.
Build attestations: not tracked.
Deployments: not tracked.
Bills of materials: not tracked.
Impact
- E9: dependsOn · declared
The diagram as text
- spec:carrier.step-keys: An example step may carry a key that survives rewording
- spec:decisions.adopted-registrars-committed: Adopted runnable registrars are committed
- spec:decisions.anchor-comment-form: The comment form is a second anchor representation
- spec:decisions.binding-not-liveness: Bindings state existence, not liveness
- spec:decisions.point-per-example: Each example binds one point
- spec:extraction.build-pipeline: The build pipeline has one ordered flow
- spec:extraction.example-runner: A bound example runs its contract steps against a fresh world
- spec:extraction.executable-contracts: The build derives step and space contracts from graph examples
- spec:extraction.runnable-modules: Derived runnable modules freeze the registrar interface
- spec:extraction.runnable-modules decidedBy spec:decisions.adopted-registrars-committed · declared
- spec:extraction.runnable-modules decidedBy spec:decisions.anchor-comment-form · declared
- spec:extraction.runnable-modules decidedBy spec:decisions.binding-not-liveness · declared
- spec:extraction.runnable-modules decidedBy spec:decisions.point-per-example · declared
- spec:extraction.runnable-modules dependsOn spec:carrier.step-keys · declared — the registrar binds each step under its contract key
- spec:extraction.runnable-modules dependsOn spec:extraction.build-pipeline · declared — the build publishes the registrars and their manifest under its staged-publication law
- spec:extraction.runnable-modules dependsOn spec:extraction.executable-contracts · declared
- spec:extraction.runnable-modules refines spec:extraction.example-runner · declared
- spec:decisions.adopted-registrars-committed refines spec:extraction.runnable-modules · declared
Mermaid source
Copyable source; the Studio draws static SVG.
Preferred syntax: flowchart. Chosen syntax: labelled flowchart, preserving exact relations, claims and folds.
Rests on
- refines · spec:extraction.example-runner · stated ready, floor ready
- dependsOn · spec:carrier.step-keys · stated ready, floor ready
- dependsOn · spec:extraction.build-pipeline · stated ready, floor ready
- dependsOn · spec:extraction.executable-contracts · stated ready, floor ready
- decidedBy · spec:decisions.adopted-registrars-committed · stated ready, floor ready
- decidedBy · spec:decisions.anchor-comment-form · stated ready, floor ready
- decidedBy · spec:decisions.binding-not-liveness · stated ready, floor ready
- decidedBy · spec:decisions.point-per-example · stated ready, floor ready
Rested on by
Distance 1
- spec:decisions.adopted-registrars-committed
Implementing units
None.
Referencing units
None.
Enabled verifiers: None
Packs: pack:self-hosting-v1
Relations
| Relation and direction | Other end | Claim | Reason | Exercised keys |
|---|---|---|---|---|
| decidedBy · outgoing | Adopted runnable registrars are committedspec:decisions.adopted-registrars-committed · resolves | declared | None | None |
| decidedBy · outgoing | The comment form is a second anchor representationspec:decisions.anchor-comment-form · resolves | declared | None | None |
| decidedBy · outgoing | Bindings state existence, not livenessspec:decisions.binding-not-liveness · resolves | declared | None | None |
| decidedBy · outgoing | Each example binds one pointspec:decisions.point-per-example · resolves | declared | None | None |
| dependsOn · outgoing | An example step may carry a key that survives rewordingspec:carrier.step-keys · resolves | declared | the registrar binds each step under its contract key | None |
| dependsOn · outgoing | The build pipeline has one ordered flowspec:extraction.build-pipeline · resolves | declared | the build publishes the registrars and their manifest under its staged-publication law | None |
| dependsOn · outgoing | The build derives step and space contracts from graph examplesspec:extraction.executable-contracts · resolves | declared | None | None |
| refines · outgoing | A bound example runs its contract steps against a fresh worldspec:extraction.example-runner · resolves | declared | None | None |
| refines · incoming | Adopted runnable registrars are committedspec:decisions.adopted-registrars-committed · resolves | declared | None | None |
Bindings
Implementation binding: present
component:protocol.codegen· contracts and registrar output ·src/codegen/contracts.ts:633· role projection · layer application · context protocol component:protocol.testing· the generated registrar runtime ·src/testing/index.ts:38· role adapter · layer adapter · context protocol impl:protocol.example-testing-helpers· adapter-lifecycle and oracle-comparison helpers for bound examples ·src/testing/index.ts:50· role adapter · component component:protocol.testing· layer adapter · context protocolimpl:protocol.runnable-modules· derives and publishes runnable registrar modules from the graph ·src/codegen/contracts.ts:658· role projection · component component:protocol.codegen· layer application · context protocol
Design reference: none
Verifier binding: present
- test:protocol.runnable-modules · verifies generated runnable-module registration and runtime semantics · anchored ·
test/testing.test.ts:58· test anchor - test:protocol.runnable-modules-generation · checks registrar generation, sibling paths and competing suite paths · anchored ·
test/codegen.test.ts:22· test anchor - test:protocol.runnable-modules-types · checks registrar skeletons and required authored adapters · anchored ·
test/runnable-modules.typecheck.ts:5· test anchor
Expected-outcome oracle: none
Runtime observation: not tracked.
Run results: not recorded in the graph.
Findings
None.
Composing (0)
Composing needs the script.