Spec Studio

Validation re-runs while an author edits carriers

spec:consumers.validate-watchUse Case / Behavior · storyready floor ready

Packs: pack:adopter-speed-v1, pack:design-management-v1, pack:self-hosting-v1

Design: no design entries · no pinned declarations · no decisions

Findings: no errors, no warnings

specs/consumers/validate-watch.sdp.md

No open questions and no blocking questions; implementation binding present, verifier binding present, and expected-outcome oracle none.

Intent
problem
An author editing a Spec re-runs sdp validate by hand after every save.
outcome
Let sdp validate --watch re-run the one validation path on every carrier change and stay alive after findings, as a loop for authoring and never a second validation path.
Behavior
rule line 19 · specs/consumers/validate-watch.sdp.md:19
sdp validate --watch [root] installs its watcher first, then runs the same validate path sdp validate runs, from scratch, once at start and again after every carrier create, change, delete or rename under the root.
rule line 20 · specs/consumers/validate-watch.sdp.md:20
A carrier ends in .sdp.md, .sdp.gherkin or .sdp.ts. The watcher ignores the directories discovery skips, the configured exclusion prefixes and every non-carrier path other than the rules files and parameter files --rules names, which it watches wherever they live, as spec:validation.corpus-rules#rule.watchReruns and spec:validation.corpus-rules#rule.watchedParams state; a removed watched directory and an event with no filename each schedule a run because their affected carriers cannot be named.
rule line 21 · specs/consumers/validate-watch.sdp.md:21
Events that arrive while a run is in progress coalesce into one pending run.
rule.operatorStop line 22 · specs/consumers/validate-watch.sdp.md:22
Findings print as sdp validate prints them and the process stays alive; operator SIGINT terminates the process with exit 0 even while a rules body is active, without waiting for it. No further rules file runs or output is written after the stop.

no example exercises it

rule line 23 · specs/consumers/validate-watch.sdp.md:23
For sdp validate, --watch cannot combine with --check-clean, and it re-runs on carrier edits and on edits to the files --rules names, and never on source edits, so an author re-runs validation after editing an anchor. This rule restricts no other verb's --watch, which its own Spec states.

4 unkeyed rules.

Verification

Verifier bindings

  • test:protocol.validate-watch · watch-loop checks verify the validate --watch authoring loop · anchored · test anchor binding present · test/cli.test.ts:1670

    External run evidence from test/cli.test.ts

    Test anchors: test:protocol.engine-provenance, test:protocol.extraction-determinism, test:protocol.projections-model.publication, test:protocol.regenerability, test:protocol.validate-watch. Specs verified by those bindings: spec:consumers.engine-provenance, spec:consumers.projections-model, spec:consumers.validate-watch, spec:extraction.determinism, spec:extraction.regenerability.

    Run 2: external run evidence from vitest 4.1.10

    Build: CI 380 · Branch: feature/roadmap-and-slices-v0 · Commit: 34909918bf0be0e849959644401797191cae379f · Start: 2026-10-22T17:42:16.533Z · Stop: 2026-10-22T17:42:27.785Z

    NameSuiteResultDuration
    prints the exact help text for no argssdp clipassed1.39 ms
    points operators to the shipped on-ramps and Protocol corpussdp clipassed1.4 ms
    describes both anchor forms and non-failing warnings in helpsdp clipassed0.33 ms
    prints the exact help text for --helpsdp clipassed0.14 ms
    builds the checkout-v1 example: writes graph, contracts, and registrar manifest atomicallysdp clipassed92.85 ms
    invalidates an existing Design Review when build replaces its source graphsdp clipassed51.1 ms
    views the self-hosting corpus from the default repository rootsdp clipassed8406.8 ms
    renders contracts warnings through the one formatter and counts them in the summarysdp clipassed6.91 ms
    withholds case-colliding contract paths with a warning: graph kept, nothing gatessdp clipassed3.45 ms
    passes --check-clean on the example (determinism self-check through the CLI)sdp clipassed28.36 ms
    end-to-end determinism self-check: delete generated/, rebuild, byte-identicalsdp clipassed106.14 ms
    fails clean on a root that is not a directory: one line, exit 1, never a stack tracesdp clipassed0.32 ms
    rejects an unknown option: one line, exit 1, nothing runssdp clipassed0.1 ms
    accepts repeatable --exclude paths for buildsdp clipassed3.25 ms
    accepts repeatable --exclude paths for validatesdp clipassed3.57 ms
    accepts repeatable --exclude paths for viewsdp clipassed4.61 ms
    uses the identical normalized exclusions in both --check-clean extractionssdp clipassed4.99 ms
    refuses invalid --exclude path "" before build writessdp clipassed0.75 ms
    refuses invalid --exclude path "." before build writessdp clipassed0.21 ms
    refuses invalid --exclude path "./explorations" before build writessdp clipassed0.27 ms
    refuses invalid --exclude path "explorations/" before build writessdp clipassed0.16 ms
    refuses invalid --exclude path "/explorations" before build writessdp clipassed0.19 ms
    refuses invalid --exclude path "../x" before build writessdp clipassed0.18 ms
    refuses invalid --exclude path "a/../b" before build writessdp clipassed0.13 ms
    refuses invalid --exclude path "a//b" before build writessdp clipassed0.12 ms
    refuses invalid --exclude path "a\\b" before build writessdp clipassed0.21 ms
    refuses a missing --exclude operand before build writessdp clipassed0.28 ms
    names a flag supplied as an --exclude operand before build writessdp clipassed0.19 ms
    documents repeatable --exclude paths in helpsdp clipassed0.06 ms
    rejects a second root argument: one line, exit 1, nothing runssdp clipassed0.16 ms
    notes an empty authored model: zero spec files still builds and exits 0, but says where it lookedsdp clipassed0.59 ms
    renders a finding's location exactly once, from the structured fields: file:line — [severity]sdp clipassed2.19 ms
    renders a Gherkin parser failure through the public CLIsdp clipassed1.24 ms
    exits 1, writes nothing, and removes a stale graph.json on a hard-error corpussdp clipassed1.87 ms
    fails build on an integer-like design key holding a static valuesdp clipassed1.25 ms
    fails build on an integer-like design key holding a non-static valuesdp clipassed1.05 ms
    fails build on an integer-like design key holding a methodsdp clipassed1.09 ms
    fails build on an integer-like design key holding an accessorsdp clipassed1.22 ms
    fails --check-clean on a diverging second extraction: exit 1, the stale graph.json removedsdp clipassed7.21 ms
    fails --check-clean on a diverging second contract generation: exit 1, no artifact survivessdp clipassed18.15 ms
    owns the contracts tree wholesale: with nothing to generate, a stale contracts dir is removedsdp clipassed2.71 ms
    reconciles a registrar sibling that a later generation no longer owessdp clipassed19.57 ms
    fails --check-clean on registrar-byte drift and removes the uncertified known setsdp clipassed26.76 ms
    passes --check-clean without a prior manifest when no registrars are owedsdp clipassed3.91 ms
    fails --check-clean on registrar manifest whitespace driftsdp clipassed26 ms
    fails --check-clean on registrar manifest ignored property driftsdp clipassed26.54 ms
    removes graph, contracts, manifest, and registrar siblings after a staged write failuresdp clipassed19.02 ms
    fails clean when extraction throws past discovery: one line, exit 1, the stale graph.json removedsdp clipassed0.6 ms
    fails clean when generated exists as a file: one line each for build and view, never a stack tracesdp clipassed5.38 ms
    names the survivor when a recovery-path removal is denied: a stale artifact never silently reads as currentsdp clipassed0.56 ms
    stops a build when an existing Design Review cannot be invalidatedsdp clipassed1.62 ms
    fails clean when the checks throw: one line, exit 1, the built graph.json stayssdp clipassed2.52 ms
    suppresses the empty-model note when spec files were found but none reified — a failed file is not an absent onesdp clipassed1.52 ms
    treats a root of only ordinary .feature files as an empty authored model, not a missing-identity errorsdp clipassed0.65 ms
    suppresses the empty-model note when a failed .sdp.gherkin finding.file matches the carrier suffix predicatesdp clipassed1.01 ms
    builds a mixed root with one .sdp.gherkin carrier and ignores the ordinary .feature siblingsdp clipassed0.88 ms
    removes the temp twin on a failed build and a failed view — no partial .tmp artifact survivessdp clipassed8.95 ms
    recognizes the published-bin path: a .bin-style symlink resolves to the entry modulesdp clipassed0.43 ms
    exits 0 quietly on a stdout EPIPE and stays fatal for any other stream errorsdp clipassed0.53 ms
    validates the example: exit 0, the artifact written, and no warningsdp clipassed28.78 ms
    gates on the checks, not the build: a clean-building broken link validates to exit 1 with the artifact keptsdp clipassed2.67 ms
    short-circuits the checks on extraction hard errors: validate keeps build semanticssdp clipassed1.64 ms
    prints help plus an unknown-command errorsdp clipassed0.13 ms
    views the example: validate + the Design Review written, with no warningsdp clipassed33.87 ms
    owns the view directory wholesale: a stale page does not survive a re-rendersdp clipassed15.8 ms
    regenerates the view byte-identically: delete generated/, re-view, same bytessdp clipassed24.79 ms
    writes the view even when checks fail: findings render in it, the exit code is validate'ssdp clipassed2.06 ms
    keeps build semantics on extraction hard errors: no graph, no view, stale view removedsdp clipassed1.87 ms
    fails view --check-clean on a diverging second render: exit 1, the stale view removedsdp clipassed6.53 ms
    fails clean when the render throws: one line on stderr, exit 1, the stale view removedsdp clipassed2.71 ms
    closes the compiled watch process with exit 0 after operator SIGINTsdp validate --watchpassed299.82 ms
    documents --watch as a validate-only authoring loopsdp validate --watchpassed0.19 ms
    rejects --watch on build with a named error and no watchersdp validate --watchpassed0.18 ms
    rejects --watch on view with a named error and no watchersdp validate --watchpassed0.06 ms
    rejects --watch on census with a named error and no watchersdp validate --watchpassed0.05 ms
    rejects --watch on mermaid with a named error and no watchersdp validate --watchpassed0.05 ms
    rejects --watch on gherkin with a named error and no watchersdp validate --watchpassed0.04 ms
    rejects --watch on import with a named error and no watchersdp validate --watchpassed0.17 ms
    rejects --watch on new with a named error and no watchersdp validate --watchpassed0.2 ms
    rejects --watch on q with a named error and no watchersdp validate --watchpassed0.18 ms
    rejects --watch --check-clean with a named error and no watchersdp validate --watchpassed0.12 ms
    installs the watcher before the first validation cyclesdp validate --watchpassed4.45 ms
    catches an edit during the first cycle with exactly one coalesced rerunsdp validate --watchpassed5.23 ms
    coalesces a burst during an active cycle into exactly one rerunsdp validate --watchpassed4.92 ms
    prints validation findings and keeps watching until abort exits 0sdp validate --watchpassed2.68 ms
    ignores generated, dist, node_modules, coverage, dot directories, and configured excludessdp validate --watchpassed5.24 ms
    coalesces production-shaped events from later turns into one pending rerunsdp validate --watchpassed7.83 ms
    does not natively subscribe to excluded or generated subtreessdp validate --watchpassed69.25 ms
    routes a native watcher error through a typed channel and closes oncesdp validate --watchpassed3.1 ms
    clean-repo determinism: the full pipeline at a different absolute path is byte-identicalnot recordedpassed67.67 ms
    prints one line naming the package version and the recorded commit, and exits 0sdp --versionpassed0.49 ms
    names the commit unknown when the build recorded none or recorded something elsesdp --versionpassed0.33 ms
    reads the version from the package manifest two directories above the CLI modulesdp --versionpassed0.38 ms
    documents --version and --params in the usage textsdp --versionpassed0.14 ms
    records the checked-out commit at build time, or unknown when git cannot name onesdp --versionpassed11.15 ms
    publishes the commit the build step reads from git beside the CLIsdp --versionpassed0.9 ms
    builds commit metadata from a substituted git archive extracted without .gitsdp --versionpassed64.59 ms
    uses only a full archive commit when git gives none, keeping git firstsdp --versionpassed0.68 ms
    answers from the compiled CLI with the commit its build recorded beside itsdp --versionpassed327.1 ms
    runs recipe 4 as shipped, its changed files passed with --params @PATHa shipped recipe file through the compiled CLIpassed363.4 ms
  • test:protocol.validate-watch-rules · process checks verify operator stop during active corpus rules · anchored · test anchor binding present · test/corpus-rules.test.ts:343

    External run evidence from test/corpus-rules.test.ts

    Test anchors: test:protocol.validate-watch-rules. Specs verified by those bindings: spec:consumers.validate-watch.

    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

    NameSuiteResultDuration
    documents repeatable rules, per-file parameters and check-cleancorpus rule argumentspassed1.64 ms
    refuses "--params" before derivingcorpus rule argumentspassed0.95 ms
    refuses "--params" before derivingcorpus rule argumentspassed0.2 ms
    refuses "--rules" before derivingcorpus rule argumentspassed0.17 ms
    refuses "--rules" before derivingcorpus rule argumentspassed0.51 ms
    refuses "--rules" before derivingcorpus rule argumentspassed0.62 ms
    refuses "--rules" before derivingcorpus rule argumentspassed0.31 ms
    refuses "--rules" before derivingcorpus rule argumentspassed0.28 ms
    refuses "--rules" before derivingcorpus rule argumentspassed0.19 ms
    binds parameters to the preceding file across other optionscorpus rule argumentspassed0.99 ms
    refuses rules on buildcorpus rule argumentspassed0.32 ms
    refuses rules on viewcorpus rule argumentspassed0.12 ms
    refuses rules on censuscorpus rule argumentspassed0.08 ms
    refuses rules on mermaidcorpus rule argumentspassed0.17 ms
    refuses rules on gherkincorpus rule argumentspassed0.21 ms
    refuses rules on qcorpus rule argumentspassed0.22 ms
    refuses the same rules file through a symlink before derivingcorpus rules on validatepassed1.44 ms
    refuses a finding file outside root-relative POSIX paths: /etc/passwdcorpus rules on validatepassed17.39 ms
    refuses a finding file outside root-relative POSIX paths: ../outside.tscorpus rules on validatepassed3.81 ms
    refuses a finding file outside root-relative POSIX paths: src/../../outside.tscorpus rules on validatepassed3.33 ms
    refuses a finding file outside root-relative POSIX paths: src\\probe.tscorpus rules on validatepassed2.78 ms
    refuses a finding file outside root-relative POSIX paths: C:/outside.tscorpus rules on validatepassed2.73 ms
    reports a root-relative finding path: src/probe.tscorpus rules on validatepassed2.49 ms
    reports a root-relative finding path: src/../probe.tscorpus rules on validatepassed5.91 ms
    reports a error finding apart from Protocol findingscorpus rules on validatepassed3.89 ms
    reports a warning finding apart from Protocol findingscorpus rules on validatepassed6.12 ms
    fails a broken file and continues: throw new Error('two\nlines')corpus rules on validatepassed3.14 ms
    fails a broken file and continues: return {corpus rules on validatepassed9.22 ms
    fails a broken file and continues: return { diagnostics: ['bad'] }corpus rules on validatepassed5.85 ms
    fails a broken file and continues: return { unknownParams: ['typo'], accepts: [] }corpus rules on validatepassed4.67 ms
    fails a broken file and continues: return { findings: [{ validatorId: 'a', severity: 'warning', message: 'x' }] }corpus rules on validatepassed3.95 ms
    fails a broken file and continues: return { findings: [{ ...({ validatorId: "probe-rule", subjectId: "probe-subject", severity: "error", message: "probe finding" }), severity: 'info' }] }corpus rules on validatepassed2.81 ms
    fails a broken file and continues: return { findings: [{ ...({ validatorId: "probe-rule", subjectId: "probe-subject", severity: "error", message: "probe finding" }), validatorId: 'a/b' }] }corpus rules on validatepassed2.24 ms
    fails a broken file and continues: return { findings: [{ ...({ validatorId: "probe-rule", subjectId: "probe-subject", severity: "error", message: "probe finding" }), line: 1 }] }corpus rules on validatepassed2.98 ms
    fails a broken file and continues: return { findings: [{ ...({ validatorId: "probe-rule", subjectId: "probe-subject", severity: "error", message: "probe finding" }), relatedId: 1 }] }corpus rules on validatepassed2.42 ms
    fails a broken file and continues: return { findings: [{ ...({ validatorId: "probe-rule", subjectId: "probe-subject", severity: "error", message: "probe finding" }), path: 1 }] }corpus rules on validatepassed2.2 ms
    fails a broken file and continues: return { findings: [{ ...({ validatorId: "probe-rule", subjectId: "probe-subject", severity: "error", message: "probe finding" }), file: '' }] }corpus rules on validatepassed2.1 ms
    fails an unreadable filecorpus rules on validatepassed2.49 ms
    discards every finding of a malformed answercorpus rules on validatepassed2.18 ms
    clones the graph and report separately for each file and leaves written bytes wholecorpus rules on validatepassed2.98 ms
    gives each file only its own parameters, including JSON files and keyed valuescorpus rules on validatepassed2.5 ms
    keeps the ordinary validate output unchanged and q report free of corpus datacorpus rules on validatepassed3.32 ms
    does not run rules when the graph does not derivecorpus rules on validatepassed1.86 ms
    exposes corpus data only on the aggregate validate outcomenot recordedpassed1.96 ms
    awaits bodies in file order and keeps a Protocol error fatalnot recordedpassed9 ms
    SIGINT exits 0 during a live rules body, with no later output or rules, even if it can settle: falsenot recordedpassed407.24 ms
    SIGINT exits 0 during a live rules body, with no later output or rules, even if it can settle: truenot recordedpassed484.28 ms
    rereads rules when its file changesnot recordedpassed6.75 ms
    gives each watch cycle fresh parameters after a body mutates themnot recordedpassed6.89 ms
Design view

Requirement drawing

  • Declared verifying examples: no declared verifying example.
Requirement drawing for spec:consumers.validate-watch
The diagram as text
  • spec:consumers.validate-watch: Validation re-runs while an author edits carriers · declared Spec
  • spec:consumers.authoring-on-ramp: Authors move one Spec from intent to reviewed evidence · declared Spec
  • impl:protocol.validate-watch: watches carrier directories and coalesces events into a pending validation rerun · anchored code · src/cli/validate-watch.ts:591
  • test:protocol.validate-watch: watch-loop checks verify the validate --watch authoring loop · anchored test binding · binding present · test/cli.test.ts:1670
  • test:protocol.validate-watch-rules: process checks verify operator stop during active corpus rules · anchored test binding · binding present · test/corpus-rules.test.ts:343
  • spec:consumers.validate-watch refines spec:consumers.authoring-on-ramp · declared
  • impl:protocol.validate-watch satisfies spec:consumers.validate-watch · anchored
  • test:protocol.validate-watch verifies spec:consumers.validate-watch · anchored
  • test:protocol.validate-watch-rules verifies spec:consumers.validate-watch · 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

No decision record named.

Runtime
  • component:protocol.cli · entry points, shared build and publication · src/cli/build-command.ts:32 · layer edge · context protocol

    Uses

    • component:protocol.adapters · external runtime and source adapters · src/adapters/vitest.ts:23 · layer adapter · context protocol
    • component:protocol.codegen · contracts and registrar output · src/codegen/contracts.ts:633 · layer application · context protocol
    • component:protocol.extract · the carrier and source pipeline · src/extract/index.ts:192 · layer application · context protocol
    • component:protocol.graph · the one graph and its semantic decisions · src/graph/schema.ts:34 · layer domain · context protocol
    • component:protocol.import · canonical conversion with a fidelity boundary · src/import/import.ts:45 · layer application · context protocol
    • component:protocol.model · the authored contracts and stable ids · src/model/anchors.ts:100 · layer domain · context protocol
    • component:protocol.projections · read-only output, layout and owned publication · src/projections/design-review.ts:29 · layer edge · 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
  • impl:protocol.validate-watch · watches carrier directories and coalesces events into a pending validation rerun · src/cli/validate-watch.ts:591

    Component: component:protocol.cli · entry points, shared build and publication · src/cli/build-command.ts:32 · layer edge · 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
Relations of spec:consumers.validate-watch
The diagram as text
  • spec:consumers.authoring-on-ramp: Authors move one Spec from intent to reviewed evidence
  • spec:consumers.obsidian-vault.iteration: A design pass is followed and answered from the vault
  • spec:consumers.spec-studio.publisher.watch: sdp studio --watch rebuilds the page set on each carrier change
  • spec:consumers.validate-watch: Validation re-runs while an author edits carriers
  • spec:decisions.one-validation-path: Validation follows one graph
  • spec:extraction.excludes: Extraction exclusions are strict consumer input
  • spec:validation.corpus-rules: Corpus-authored rules run on the one validation path
  • spec:consumers.validate-watch dependsOn spec:decisions.one-validation-path · declared
  • spec:consumers.validate-watch dependsOn spec:extraction.excludes · declared
  • spec:consumers.validate-watch refines spec:consumers.authoring-on-ramp · declared
  • spec:consumers.obsidian-vault.iteration dependsOn spec:consumers.validate-watch · declared — the vault's watch runs the same loop over carriers and anchored source files
  • spec:consumers.spec-studio.publisher.watch dependsOn spec:consumers.validate-watch · declared — the watch reruns on the carrier events validate's watch follows
  • spec:validation.corpus-rules dependsOn spec:consumers.validate-watch · declared — the watch loop reruns the named rules in each cycle
Mermaid source

Copyable source; the Studio draws static SVG.

Preferred syntax: flowchart. Chosen syntax: labelled flowchart, preserving exact relations, claims and folds.

Rests on

Rested on by

Distance 1

Distance 2

Distance 3

Relations

Relation and directionOther endClaimReasonExercised keys
dependsOn · outgoingValidation follows one graph
spec:decisions.one-validation-path · resolves
declaredNoneNone
dependsOn · outgoingExtraction exclusions are strict consumer input
spec:extraction.excludes · resolves
declaredNoneNone
refines · outgoingAuthors move one Spec from intent to reviewed evidence
spec:consumers.authoring-on-ramp · resolves
declaredNoneNone
dependsOn · incomingA design pass is followed and answered from the vault
spec:consumers.obsidian-vault.iteration · resolves
declaredthe vault's watch runs the same loop over carriers and anchored source filesNone
dependsOn · incomingsdp studio --watch rebuilds the page set on each carrier change
spec:consumers.spec-studio.publisher.watch · resolves
declaredthe watch reruns on the carrier events validate's watch followsNone
dependsOn · incomingCorpus-authored rules run on the one validation path
spec:validation.corpus-rules · resolves
declaredthe watch loop reruns the named rules in each cycleNone

Bindings

Implementation binding: present

  • impl:protocol.validate-watch · watches carrier directories and coalesces events into a pending validation rerun · src/cli/validate-watch.ts:591 · role service · component component:protocol.cli · layer edge · context protocol

Design reference: none

Verifier binding: present

  • test:protocol.validate-watch · watch-loop checks verify the validate --watch authoring loop · anchored · test/cli.test.ts:1670 · test anchor
  • test:protocol.validate-watch-rules · process checks verify operator stop during active corpus rules · anchored · test/corpus-rules.test.ts:343 · 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.