Stated readiness by floor reachedRows: stated readiness, idea to ready. Columns: floor reached, none to ready. Each mark counts one member; above 100 members a cell is solid. Floor none is distinct from idea.
Outlined diagonal: matches. Right of diagonal: floor above statement. Left of diagonal: statement above floor.
Maturity map as text
Counts by stated readiness and floor reached. To the right the floor exceeds the statement; to the left the statement exceeds the floor.
Preferred syntax: block. Fixed categorical cells; membership appears in comments.
Framing
This Pack groups what a corpus needs to check its own policy on the one validation path, in reading order. Corpus rules are the corpus's own graph bodies that sdp validate --rules runs and reports apart from the Protocol's findings. The architecture walk is a catalog recipe a corpus runs as one of those rules, over the layers, contexts and uses its anchors state. Component designs state the components a design adds before their code exists, and a catalog recipe compares them with the components the anchors later realize. Each Spec is followed by the examples that will verify it.
Members
1. Corpus-authored rules run on the one validation pathbehavior · readyfloor ready · 0 open, 0 blocking questions · implementation binding present · verifier binding present
impl:protocol.corpus-rule-runner · runs selected corpus rules and separates their failures · src/cli/corpus-rules.ts:65 · role service · component component:protocol.cli · layer edge · context protocol
impl:protocol.corpus-rules · runs copied rule bodies after Protocol validation and composes the separate corpus report, output and exit · src/cli/validate-view-command.ts:79 · role service · component component:protocol.cli · layer edge · context protocol
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
2. An error from a corpus rule fails validation and leaves the Protocol's line aloneexample · readyfloor ready · 0 open, 0 blocking questions · implementation binding none · verifier binding present
1 verifier, 1 enabled · no verifying examples, 0 enabled
8. A parameter file that stops being JSON fails only its own rules file, and the watcher runs onexample · readyfloor ready · 0 open, 0 blocking questions · implementation binding none · verifier binding present
10. A domain member that uses an infrastructure member breaks a forbidden pairexample · readyfloor ready · 0 open, 0 blocking questions · implementation binding none · verifier binding present
1 verifier, 1 enabled · no verifying examples, 0 enabled
11. The walk named as a rules file fails validation on an isolation breachexample · readyfloor ready · 0 open, 0 blocking questions · implementation binding none · verifier binding present
impl:protocol.planned-components-recipe · asserts realization of the shipped planned against realized components recipe · test/architecture-recipes.test.ts:16
Design reference: present
impl:protocol.curation-recipe · asserts the curation report over external inferred imports · test/curation-recipes.test.ts:32
No open questions and no blocking questions; implementation binding present, verifier binding present, and expected-outcome oracle present.
The original design let a team add its own rules beside the built-in validators, each with an id, a severity and the whole graph query API, run in the same pass and reported in the same output (docs/lineage/v0-design/06-extraction-and-validation.md §6.5). The Protocol has two check families, conformance and honesty, and every validator belongs to one of them. The architectural annotation decision sends a corpus's own rules to the corpus's own gate.
The first adopter's gate runs sdp validate and parses its summary line, then checks its own context isolation, entry addresses, dependency cycles, question keys, prose mentions, cited laws and Pack pages. It also parses carrier frontmatter to read kind, readiness and relations, a second read model that a graph-based corpus-rule runner would remove.
A rules file holds the body sdp q evaluates, so a rule tried through sdp q runs unchanged under sdp validate, and a catalog recipe whose answer carries findings, such as the architecture walk, is a rules file as shipped. A rules file that needs a shipped recipe's answer calls it through recipes and keeps no copy of its code. The runner reads only the answer's findings, so the recipe keeps the rest of its answer for a reader. Corpus findings print after the Protocol's and are tallied on a line of their own, so a gate that reads the Protocol's summary line reads it unchanged; any error, the Protocol's or the corpus's, exits 1, and the two lines say which. Each rules file receives its own copy of what it reads, so a careless rule cannot change another rule's input and the order of the files changes only the order of the output. Its parameters are its own too: a catalog recipe answers a parameter name it does not take with that name and no findings, so parameters shared by every file would fail the walk on the names another file reads.
Intent
problem
A corpus with rules of its own checks them off the one validation path, reading the carriers a second way and reporting in a form no Protocol surface reads.
outcome
Let a corpus author its own graph rules and have their findings join the one validation report, so no corpus re-parses its carriers to check itself.
A corpus rule is a graph body the corpus authors, given g, graph, report, params and recipes as a recipe body is, and returning findings that each carry a rule id, a subject, a message and a severity.
A corpus rule reads the one derived graph, the Protocol's report and the params the operator supplies for its file, directly or through the shipped recipes it calls, and no carrier, source file or network; that is the rule author's obligation, because the runner evaluates the body with the trust sdp q asks and claims no sandbox.
Validation runs corpus rules only from a file the operator names with sdp validate --rules PATH, which may repeat, and never from a file it finds in the corpus or in a configuration; it asks the trust sdp q already asks, to run code the operator chose.
Rules files run one at a time in the order named, each to completion, after the Protocol's validators; every file receives the same Protocol report and none sees another file's findings, so the order changes only the order of the output.
Each rules file receives its own copy of the graph and the report, made after the graph is written, and its own params, so a body that changes what it receives changes no node, edge, delivery fact or Protocol finding, and nothing another rules file receives.
Each rules file receives as params only the parameters the operator names after its --rules and before the next --rules, or {} when none follow it, so no file receives a parameter meant for another.
Each corpus finding names its rule and its rules file as its source. The Protocol states none of these rules: a shipped recipe that a corpus names as a rules file checks the rule data the corpus passes it.
Corpus findings stand outside the conformance and honesty families, in a section of the output labelled as the corpus's own and tallied on a summary line of their own, so the two families keep their meaning, the guardrail that checks never judge content quality or workflow keeps binding the Protocol's own validators, and no corpus finding adds to, suppresses or changes a Protocol finding or the exit reason it sets.
A corpus finding carries severity error or warning. When the operator names --rules, an error corpus finding makes sdp validate exit nonzero and a warning stays informative; without --rules no corpus rule runs and only the Protocol's validators set the exit code.
A corpus finding moves no readiness floor and reaches no other surface: not the report sdp q hands a body, not the reader, not the written graph, and not the Design Review or another projection.
A rules file that cannot be read or does not parse, a body that throws, or an answer that is not an object with a findings array of well-formed findings is a rule execution failure; sdp validate reports it in the corpus section and exits nonzero, so a broken rule never passes as a clean run. A failed file reports none of its findings, and the other files still run.
sdp validate --watch with --rules reads and runs every named rules file afresh in each cycle, after the Protocol's validators, and a create, change, delete or rename of a named rules file starts a cycle as a carrier edit does, wherever the file lives, under a directory discovery skips such as node_modules included.
Under --watch, each cycle after the first reads again every file a --params @PATH or KEY=@PATH after a --rules names, and an edit to such a file starts a cycle, wherever the file lives; a file that cannot be read or parsed then fails only the rules file it belongs to, for that cycle, while the first cycle's read refuses the command as paramsFlag states.
Address resolution (recipe 25) and dependency cycles (recipe 26) answer with findings beside the rest of their answers, as the architecture walk (recipe 42), external registers (recipe 50) and entry citations (recipe 51) do, so a corpus names any of those shipped files as a rules file, or calls it through recipes from a rules file of its own.
Given a probe corpus in which {corpus: "two Specs depend on each other through dependsOn"}
Given the caller passes the addresses {addresses: "none"}
When the shipped {recipe: "dependency cycles"} file runs {invocation: "as a rules file named by sdp validate --rules"}
Then the command exits {exitCode: 1}
Then the answer carries {findings: 1} findings with the rule ids {ruleIds: "dependency-cycle"}
Given a probe corpus in which {corpus: "one Spec depends on another"}
Given the caller passes the addresses {addresses: "spec:probe.one#design.absent"}
When the shipped {recipe: "address resolution"} file runs {invocation: "as a rules file named by sdp validate --rules"}
Then the command exits {exitCode: 1}
Then the answer carries {findings: 1} findings with the rule ids {ruleIds: "unresolved-address"}
Given a probe corpus the Protocol's validators find clean
Given a first rules file that {first: "empties the graph it receives"}
Given a second rules file that returns one warning when the graph it receives holds no node
When the probe corpus is validated with the rules files named by --rules in that order
Then the command exits {exitCode: 0}
Then the Protocol summary line reads {protocolSummary: "validate: 0 errors · 0 warnings (conformance + honesty over the one graph)"}
Then the corpus summary line reads {corpusSummary: "corpus rules: 0 errors · 0 warnings · 0 of 2 files failed (the corpus's own rules)"}
Given a probe corpus the Protocol's validators find clean
Given a first rules file that {first: "returns one error finding"}
When the probe corpus is validated with the rules files named by --rules in that order
Then the command exits {exitCode: 1}
Then the Protocol summary line reads {protocolSummary: "validate: 0 errors · 0 warnings (conformance + honesty over the one graph)"}
Then the corpus summary line reads {corpusSummary: "corpus rules: 1 errors · 0 warnings · 0 of 1 files failed (the corpus's own rules)"}
Given a probe corpus the Protocol's validators find clean
Given a first rules file that {first: "returns a finding with no subject"}
When the probe corpus is validated with the rules files named by --rules in that order
Then the command exits {exitCode: 1}
Then the Protocol summary line reads {protocolSummary: "validate: 0 errors · 0 warnings (conformance + honesty over the one graph)"}
Then the corpus summary line reads {corpusSummary: "corpus rules: 0 errors · 0 warnings · 1 of 1 files failed (the corpus's own rules)"}
Given a probe corpus the Protocol's validators find clean
Given a first rules file that {first: "reads a parameter file that gains a key after the first cycle under --watch"}
Given a second rules file that returns one warning when the graph it receives holds no node
When the probe corpus is validated with the rules files named by --rules in that order
Then the second cycle's corpus summary line reads {corpusSummary: "corpus rules: 0 errors · 2 warnings · 0 of 2 files failed (the corpus's own rules)"}
Given a probe corpus the Protocol's validators find clean
Given a first rules file that {first: "is rewritten to return one error finding after the first cycle under --watch"}
Given a second rules file that returns one warning when the graph it receives holds no node
When the probe corpus is validated with the rules files named by --rules in that order
Then the second cycle's corpus summary line reads {corpusSummary: "corpus rules: 1 errors · 0 warnings · 0 of 2 files failed (the corpus's own rules)"}
Given a probe corpus the Protocol's validators find clean
Given a first rules file that {first: "reads a parameter file that stops being JSON after the first cycle under --watch"}
Given a second rules file that returns one warning when the graph it receives holds no node
When the probe corpus is validated with the rules files named by --rules in that order
Then the second cycle's corpus summary line reads {corpusSummary: "corpus rules: 0 errors · 0 warnings · 1 of 2 files failed (the corpus's own rules)"}
Then the watcher is still running after the second cycle: {running: true}
Given a probe corpus the Protocol's validators find clean
Given a first rules file that {first: "returns one warning finding"}
When the probe corpus is validated with the rules files named by --rules in that order
Then the command exits {exitCode: 0}
Then the Protocol summary line reads {protocolSummary: "validate: 0 errors · 0 warnings (conformance + honesty over the one graph)"}
Then the corpus summary line reads {corpusSummary: "corpus rules: 0 errors · 1 warnings · 0 of 1 files failed (the corpus's own rules)"}
Given a probe corpus the Protocol's validators find clean
Given a first rules file that {first:"returns one error finding"|"returns one warning finding"|"returns a finding with no subject"|"empties the graph it receives"|"is rewritten to return one error finding after the first cycle under --watch"|"reads a parameter file that gains a key after the first cycle under --watch"|"reads a parameter file that stops being JSON after the first cycle under --watch"}
Given a second rules file that returns one warning when the graph it receives holds no node
When the probe corpus is validated with the rules files named by --rules in that order
Then the command exits {exitCode:number}
Then the Protocol summary line reads {protocolSummary:string}
Then the corpus summary line reads {corpusSummary:string}
Then the second cycle's corpus summary line reads {corpusSummary:string}
Then the watcher is still running after the second cycle: {running:boolean}
sdp validate [root] [--exclude PATH]... [--check-clean | --watch] [--rules PATH [--params JSON | --params @PATH | --params KEY=@PATH]...]...; --rules and --params are options of validate alone, a --params before the first --rules is refused, one file named twice is refused, including through a symlink, and each PATH resolves from the working directory, as a --params @PATH file does. Diagnostics retain the path the operator named.
--params on validate takes the three forms of sdp q --params with the same refusals, before the graph derives; each --params belongs to the nearest --rules before it in the argument list, whatever other options stand between them; one file's parameters merge as sdp q merges them, a key given twice for one file is refused, two files may each take the same key, and a file with no --params after it receives {}.
a rules file is UTF-8 text holding one plain JavaScript async function body, the form sdp q evaluates, with the bindings g, graph, report, params and recipes; the runner reads the text and never imports it as a module, so no file extension is required and a catalog recipe file under dist/recipes/ is a lawful rules file.
{ findings: [] }: the body returns an object whose findings is an array, possibly empty; the runner reads findings and nothing else, so the answer's other keys stay the body's own data and are never printed.
{ validatorId, subjectId, severity, message, relatedId?, path?, file?, line? }, where validatorId is the rule id, one token matching ^[a-z][a-z0-9-]*$, so it never reads as a Protocol validator id; subjectId and message are non-empty strings; severity is "error" or "warning"; relatedId and path are strings; file is a non-empty root-relative POSIX path, with absolute paths, backslashes and traversal above the root refused, and line a positive integer that requires file; the runner ignores a finding's other keys.
{ rulesFile, validatorId, subjectId, severity, message, relatedId?, path?, file?, line? } is the corpus branch of the finding currency, where rulesFile is the path as the operator named it; it carries no family, and the Protocol's findings keep their shape.
the report a run with --rules builds keeps the Protocol's findings unchanged in findings and adds corpusFindings, the corpus findings of each file that did not fail, in the order the files are named and then in the order returned, and corpusFailures, one { rulesFile, reason } for each failed file in the order named, never a finding; the report a rules file receives, like the one sdp q hands a body, carries neither.
on standard error, after every Protocol finding, each rules file in the order named prints the line corpus rules <path> and then its findings in the order returned, each in the one composition of location, severity in brackets, rule id and message; a failed file prints corpus rules <path> failed: <reason> in place of its findings.
corpus rules: <errors> errors · <warnings> warnings · <failed> of <files> files failed (the corpus's own rules), written to standard output directly after the Protocol's summary line, which stays as spec:validation.diagnostic-rendering pins it; without --rules no such line prints; when the graph does not derive, no rules file runs and no Protocol summary line prints, and the corpus line, the last line on standard output, reads corpus rules: not run, the graph did not derive.
with --rules, sdp validate exits 1 when a Protocol finding is an error, a corpus finding is an error, a rules file fails or the graph does not derive, and 0 otherwise; the two summary lines say which.
<reason> is cannot be read (<cause>), does not parse at line <L>, column <C>: <cause>, threw: <message on one line>, returned no findings array, followed by ; diagnostics: <the answer's diagnostics as JSON> when the answer carries diagnostics or by ; unknownParams: <as JSON>; accepts: <as JSON> when it carries unknownParams, finding <N> has no lawful <field>, with N counted from 1, or, under --watch after the first cycle, its parameters cannot be read (<cause>) when a parameter file named after its --rules cannot be read or parsed.
no citation recorded
Verification
Verifier bindings
spec:consumers.agent-surface.address-and-cycle-recipes.cycle-fails-a-rules-run · The shipped dependency-cycles file fails a rules run on a cycle · declared · example enabled · specs/consumers/agent-surface.address-and-cycle-recipes.cycle-fails-a-rules-run.sdp.md
spec:consumers.agent-surface.address-and-cycle-recipes.unresolved-address-fails-a-rules-run · The shipped address-resolution file fails a rules run on an address that names no entry · declared · example enabled · specs/consumers/agent-surface.address-and-cycle-recipes.unresolved-address-fails-a-rules-run.sdp.md
spec:validation.corpus-rules.copies-contain-changes · A rules file that empties its graph leaves the next file's graph whole · declared · example enabled · specs/validation/corpus-rules.copies-contain-changes.sdp.md
spec:validation.corpus-rules.error-finding-fails · An error from a corpus rule fails validation and leaves the Protocol's line alone · declared · example enabled · specs/validation/corpus-rules.error-finding-fails.sdp.md
spec:validation.corpus-rules.params-edit-starts-a-cycle · Editing a parameter file starts a cycle that reads the new parameters · declared · example enabled · specs/validation/corpus-rules.params-edit-starts-a-cycle.sdp.md
spec:validation.corpus-rules.rules-edit-starts-a-cycle · Rewriting a rules file under node_modules starts a cycle that runs the new body · declared · example enabled · specs/validation/corpus-rules.rules-edit-starts-a-cycle.sdp.md
spec:validation.corpus-rules.unreadable-params-fail-one-file · A parameter file that stops being JSON fails only its own rules file, and the watcher runs on · declared · example enabled · specs/validation/corpus-rules.unreadable-params-fail-one-file.sdp.md
first · "returns one error finding" | "returns one warning finding" | "returns a finding with no subject" | "empties the graph it receives" | "is rewritten to return one error finding after the first cycle under --watch" | "reads a parameter file that gains a key after the first cycle under --watch" | "reads a parameter file that stops being JSON after the first cycle under --watch" · declared by a first rules file that {first}
Requirement drawing for spec:validation.corpus-rules
E6: refines (7), verifies (9) · declared
The diagram as text
spec:validation.corpus-rules: Corpus-authored rules run on the one validation path · declared Spec
spec:validation.architecture-constraints: Declared architecture rules are checked against anchored structure · declared Spec
spec:validation.corpus-rules.copies-contain-changes: A rules file that empties its graph leaves the next file's graph whole · declared Spec
spec:validation.corpus-rules.error-finding-fails: An error from a corpus rule fails validation and leaves the Protocol's line alone · declared Spec
spec:validation.corpus-rules.malformed-finding-fails: A finding with no subject fails its rules file · declared Spec
spec:validation.corpus-rules.params-edit-starts-a-cycle: Editing a parameter file starts a cycle that reads the new parameters · declared Spec
spec:validation.corpus-rules.rules-edit-starts-a-cycle: Rewriting a rules file under node_modules starts a cycle that runs the new body · declared Spec
spec:validation.corpus-rules.unreadable-params-fail-one-file: A parameter file that stops being JSON fails only its own rules file, and the watcher runs on · declared Spec
spec:validation.corpus-rules.warning-finding-informs: A warning from a corpus rule informs and passes · declared Spec
spec:validation.external-registers: A corpus checks the register tokens its prose cites · declared Spec
spec:consumers.agent-surface.address-and-cycle-recipes.cycle-fails-a-rules-run: The shipped dependency-cycles file fails a rules run on a cycle · declared Spec
spec:consumers.agent-surface.address-and-cycle-recipes.unresolved-address-fails-a-rules-run: The shipped address-resolution file fails a rules run on an address that names no entry · declared Spec
spec:validation.architecture-constraints.forbidden-layer-pair: A domain member that uses an infrastructure member breaks a forbidden pair · declared Spec
spec:validation.architecture-constraints.isolated-context-as-rule: The walk named as a rules file fails validation on an isolation breach · declared Spec
spec:validation.architecture-constraints.unstated-layer-unclassified: A component with no layer leaves its edge unclassified · declared Spec
spec:validation.external-registers.uncited-on-request: A held token no Spec cites fails a rules run only when the caller asks · declared Spec
spec:validation.external-registers.unknown-and-retired: A cited token the register lacks or retires fails a rules run · declared Spec
spec:consumers.agent-surface: Agents script a visible typed graph · declared Spec
impl:protocol.architecture-walk-recipe: asserts realization of the shipped architecture walk recipe · anchored code · test/architecture-recipes.test.ts:10
impl:protocol.corpus-rule-runner: runs selected corpus rules and separates their failures · anchored code · src/cli/corpus-rules.ts:65
impl:protocol.corpus-rules: runs copied rule bodies after Protocol validation and composes the separate corpus report, output and exit · anchored code · src/cli/validate-view-command.ts:79
impl:protocol.external-registers-recipe: asserts realization of the shipped external-registers recipe · anchored code · test/external-registers.test.ts:9
impl:protocol.validate-watch: watches carrier directories and coalesces events into a pending validation rerun · anchored code · src/cli/validate-watch.ts:591
spec:validation.architecture-constraints refines spec:validation.corpus-rules · declared — the architecture walk is a corpus-selected rule over anchored structure
spec:validation.corpus-rules refines spec:consumers.agent-surface · declared — a corpus scripts the graph through the evaluation sink to report its own findings
Preferred syntax: requirementDiagram. Chosen syntax: flowchart TB because models, folds require exact labels unavailable in requirement syntax.
Decisions
spec:decisions.architectural-annotation
Architectural significance is annotated on the anchor of the unit that realizes it. A code anchor may carry role, the architectural pattern the unit plays; a component: anchor may additionally carry layer, one of edge, application, domain, adapter, or infrastructure, and context, its bounded context. The component: anchor is anchored architecture: it is the declared component, and there is no separate architecture file, because the anchor sits where the component is realized. It is not the original design's declared Component that could exist before code; an unrealized component lives in Specs and never as an invented CodeNode. A role is a free, corpus-owned vocabulary whose taxonomy the census renders with counts from the graph; service, decider, projection, read-model, codec, contract, barrel, and utility are the lineage set, not a closed list. layer is a closed set, and a value outside it is an envelope error; layer or context on a non-component anchor is an envelope error. All three attributes are optional, and omission is lawful.
impl:protocol.corpus-rules · runs copied rule bodies after Protocol validation and composes the separate corpus report, output and exit · src/cli/validate-view-command.ts:79
Shown as text: this drawing exceeds the height budget of 2000 pixels after folding.
The diagram as text
spec:consumers.agent-surface: Agents script a visible typed graph
spec:consumers.agent-surface.address-and-cycle-recipes.cycle-fails-a-rules-run: The shipped dependency-cycles file fails a rules run on a cycle
spec:consumers.agent-surface.address-and-cycle-recipes.unresolved-address-fails-a-rules-run: The shipped address-resolution file fails a rules run on an address that names no entry
spec:consumers.agent-surface.evaluation-sink: The evaluation sink runs one body and prints its return
spec:consumers.agent-surface.recipe-parameters: Recipes take parameters as data and ship as runnable files
spec:consumers.validate-watch: Validation re-runs while an author edits carriers
spec:decisions.architectural-annotation: Architectural significance is annotated where it is realized
spec:validation.architecture-constraints: Declared architecture rules are checked against anchored structure
spec:validation.corpus-rules: Corpus-authored rules run on the one validation path
spec:validation.corpus-rules.copies-contain-changes: A rules file that empties its graph leaves the next file's graph whole
spec:validation.corpus-rules.error-finding-fails: An error from a corpus rule fails validation and leaves the Protocol's line alone
spec:validation.corpus-rules.malformed-finding-fails: A finding with no subject fails its rules file
spec:validation.corpus-rules.params-edit-starts-a-cycle: Editing a parameter file starts a cycle that reads the new parameters
spec:validation.corpus-rules.rules-edit-starts-a-cycle: Rewriting a rules file under node_modules starts a cycle that runs the new body
spec:validation.corpus-rules.unreadable-params-fail-one-file: A parameter file that stops being JSON fails only its own rules file, and the watcher runs on
spec:validation.corpus-rules.warning-finding-informs: A warning from a corpus rule informs and passes
spec:validation.diagnostic-rendering: One diagnostic currency, its location composed from structured fields
spec:validation.external-registers: A corpus checks the register tokens its prose cites
spec:validation.two-check-families: Validation separates well-formedness from non-pretending
spec:validation.corpus-rules dependsOn spec:consumers.agent-surface.evaluation-sink · declared — a rules file holds the body the sink evaluates, with the sink's bindings and trust
spec:validation.corpus-rules dependsOn spec:consumers.agent-surface.recipe-parameters · declared — validate takes `--params` in the sink's grammar
spec:validation.corpus-rules dependsOn spec:consumers.validate-watch · declared — the watch loop reruns the named rules in each cycle
spec:validation.corpus-rules dependsOn spec:validation.diagnostic-rendering · declared — corpus findings use the shared diagnostic currency
spec:validation.corpus-rules refines spec:consumers.agent-surface · declared — a corpus scripts the graph through the evaluation sink to report its own findings
spec:validation.external-registers dependsOn spec:validation.corpus-rules · declared — the recipe answers with findings and runs as a rules file
spec:validation.architecture-constraints refines spec:validation.corpus-rules · declared — the architecture walk is a corpus-selected rule over anchored structure
impl:protocol.corpus-rule-runner · runs selected corpus rules and separates their failures · src/cli/corpus-rules.ts:65 · role service · component component:protocol.cli · layer edge · context protocol
impl:protocol.corpus-rules · runs copied rule bodies after Protocol validation and composes the separate corpus report, output and exit · src/cli/validate-view-command.ts:79 · role service · component component:protocol.cli · layer edge · context protocol
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
spec:consumers.agent-surface.address-and-cycle-recipes.cycle-fails-a-rules-run · The shipped dependency-cycles file fails a rules run on a cycle · declared · specs/consumers/agent-surface.address-and-cycle-recipes.cycle-fails-a-rules-run.sdp.md · example enabled
spec:consumers.agent-surface.address-and-cycle-recipes.unresolved-address-fails-a-rules-run · The shipped address-resolution file fails a rules run on an address that names no entry · declared · specs/consumers/agent-surface.address-and-cycle-recipes.unresolved-address-fails-a-rules-run.sdp.md · example enabled
spec:validation.corpus-rules.copies-contain-changes · A rules file that empties its graph leaves the next file's graph whole · declared · specs/validation/corpus-rules.copies-contain-changes.sdp.md · example enabled
spec:validation.corpus-rules.error-finding-fails · An error from a corpus rule fails validation and leaves the Protocol's line alone · declared · specs/validation/corpus-rules.error-finding-fails.sdp.md · example enabled
spec:validation.corpus-rules.params-edit-starts-a-cycle · Editing a parameter file starts a cycle that reads the new parameters · declared · specs/validation/corpus-rules.params-edit-starts-a-cycle.sdp.md · example enabled
spec:validation.corpus-rules.rules-edit-starts-a-cycle · Rewriting a rules file under node_modules starts a cycle that runs the new body · declared · specs/validation/corpus-rules.rules-edit-starts-a-cycle.sdp.md · example enabled
spec:validation.corpus-rules.unreadable-params-fail-one-file · A parameter file that stops being JSON fails only its own rules file, and the watcher runs on · declared · specs/validation/corpus-rules.unreadable-params-fail-one-file.sdp.md · example enabled
No open questions and no blocking questions; implementation binding none, verifier binding present, and expected-outcome oracle none.
Intent
outcome
Execute the exit rule on one rules file that returns one error finding over a corpus the Protocol finds clean.
assumptions
The world writes a probe corpus of two story-altitude behavior Specs stating idea, each with an Intent outcome, the second declaring refines on the first, so the Protocol's validators report no finding. The rules file's body returns { findings: [{ validatorId: "probe-rule", subjectId: "probe-subject", severity: "error", message: "the probe rule reports its subject" }] }. The world runs sdp validate on the probe root with that file named by --rules and reads the exit code and standard output.
Behavior
Examples
Given a probe corpus the Protocol's validators find clean
Given a first rules file that {first: "returns one error finding"}
When the probe corpus is validated with the rules files named by --rules in that order
Then the command exits {exitCode: 1}
Then the Protocol summary line reads {protocolSummary: "validate: 0 errors · 0 warnings (conformance + honesty over the one graph)"}
Then the corpus summary line reads {corpusSummary: "corpus rules: 1 errors · 0 warnings · 0 of 1 files failed (the corpus's own rules)"}
Verification
Verifier bindings
test:protocol.validation.corpus-rules.error-finding-fails · anchored · test anchor binding present · test/self-hosting-corpus-checks.test.ts:325
Design view
Requirement drawing
Satisfying or referencing units: no recorded binding.
Requirement drawing for spec:validation.corpus-rules.error-finding-failsThe diagram as text
spec:validation.corpus-rules.error-finding-fails: An error from a corpus rule fails validation and leaves the Protocol's line alone · declared Spec
spec:validation.corpus-rules: Corpus-authored rules run on the one validation path · declared Spec
No open questions and no blocking questions; implementation binding none, verifier binding present, and expected-outcome oracle none.
Intent
outcome
Execute the exit rule on one rules file that returns one warning finding over a corpus the Protocol finds clean.
assumptions
The world writes the probe corpus of the error example. The rules file's body returns { findings: [{ validatorId: "probe-rule", subjectId: "probe-subject", severity: "warning", message: "the probe rule reports its subject" }] }. The world runs sdp validate on the probe root with that file named by --rules and reads the exit code and standard output.
Behavior
Examples
Given a probe corpus the Protocol's validators find clean
Given a first rules file that {first: "returns one warning finding"}
When the probe corpus is validated with the rules files named by --rules in that order
Then the command exits {exitCode: 0}
Then the Protocol summary line reads {protocolSummary: "validate: 0 errors · 0 warnings (conformance + honesty over the one graph)"}
Then the corpus summary line reads {corpusSummary: "corpus rules: 0 errors · 1 warnings · 0 of 1 files failed (the corpus's own rules)"}
Verification
Verifier bindings
test:protocol.validation.corpus-rules.warning-finding-informs · anchored · test anchor binding present · test/self-hosting-corpus-checks.test.ts:339
Design view
Requirement drawing
Satisfying or referencing units: no recorded binding.
Requirement drawing for spec:validation.corpus-rules.warning-finding-informsThe diagram as text
spec:validation.corpus-rules.warning-finding-informs: A warning from a corpus rule informs and passes · declared Spec
spec:validation.corpus-rules: Corpus-authored rules run on the one validation path · declared Spec
No open questions and no blocking questions; implementation binding none, verifier binding present, and expected-outcome oracle none.
Intent
outcome
Execute the broken-rule rule on one rules file whose only finding carries no subject.
assumptions
The world writes the probe corpus of the error example. The rules file's body returns { findings: [{ validatorId: "probe-rule", severity: "warning", message: "the probe rule forgets its subject" }] }, a warning that would pass if it were well formed. The world runs sdp validate on the probe root with that file named by --rules, reads the exit code and standard output, and reads on standard error the failure line naming the first finding's subjectId.
Behavior
Examples
Given a probe corpus the Protocol's validators find clean
Given a first rules file that {first: "returns a finding with no subject"}
When the probe corpus is validated with the rules files named by --rules in that order
Then the command exits {exitCode: 1}
Then the Protocol summary line reads {protocolSummary: "validate: 0 errors · 0 warnings (conformance + honesty over the one graph)"}
Then the corpus summary line reads {corpusSummary: "corpus rules: 0 errors · 0 warnings · 1 of 1 files failed (the corpus's own rules)"}
Verification
Verifier bindings
test:protocol.validation.corpus-rules.malformed-finding-fails · anchored · test anchor binding present · test/self-hosting-corpus-checks.test.ts:332
Design view
Requirement drawing
Satisfying or referencing units: no recorded binding.
Requirement drawing for spec:validation.corpus-rules.malformed-finding-failsThe diagram as text
spec:validation.corpus-rules.malformed-finding-fails: A finding with no subject fails its rules file · declared Spec
spec:validation.corpus-rules: Corpus-authored rules run on the one validation path · declared Spec
No open questions and no blocking questions; implementation binding none, verifier binding present, and expected-outcome oracle none.
Intent
outcome
Execute the copy rule on two rules files, the first changing the graph it receives and the second reading its own.
assumptions
The world writes the probe corpus of the error example. The first rules file's body sets the length of graph.nodes and of graph.edges to zero and returns { findings: [] }. The second rules file's body returns one warning finding when graph.nodes is empty and { findings: [] } otherwise. The world runs sdp validate on the probe root with the first file, then the second, named by --rules, and reads the exit code and standard output.
Behavior
Examples
Given a probe corpus the Protocol's validators find clean
Given a first rules file that {first: "empties the graph it receives"}
Given a second rules file that returns one warning when the graph it receives holds no node
When the probe corpus is validated with the rules files named by --rules in that order
Then the command exits {exitCode: 0}
Then the Protocol summary line reads {protocolSummary: "validate: 0 errors · 0 warnings (conformance + honesty over the one graph)"}
Then the corpus summary line reads {corpusSummary: "corpus rules: 0 errors · 0 warnings · 0 of 2 files failed (the corpus's own rules)"}
Verification
Verifier bindings
test:protocol.validation.corpus-rules.copies-contain-changes · anchored · test anchor binding present · test/self-hosting-corpus-checks.test.ts:318
Design view
Requirement drawing
Satisfying or referencing units: no recorded binding.
Requirement drawing for spec:validation.corpus-rules.copies-contain-changesThe diagram as text
spec:validation.corpus-rules.copies-contain-changes: A rules file that empties its graph leaves the next file's graph whole · declared Spec
spec:validation.corpus-rules: Corpus-authored rules run on the one validation path · declared Spec
No open questions and no blocking questions; implementation binding none, verifier binding present, and expected-outcome oracle none.
Intent
outcome
Execute the watch rule on a rules file that lives under a directory discovery skips and is rewritten after the first cycle.
assumptions
The world writes the probe corpus of the error example, a first rules file under the probe root's node_modules/probe/ whose body returns one warning finding, and the second rules file of the copy example. It starts sdp validate --watch on the probe root with the two files named by --rules in that order, waits for the first cycle, rewrites the first file to return one error finding in place of the warning, waits for the second cycle, reads its corpus summary line and stops the watcher.
Behavior
Examples
Given a probe corpus the Protocol's validators find clean
Given a first rules file that {first: "is rewritten to return one error finding after the first cycle under --watch"}
Given a second rules file that returns one warning when the graph it receives holds no node
When the probe corpus is validated with the rules files named by --rules in that order
Then the second cycle's corpus summary line reads {corpusSummary: "corpus rules: 1 errors · 0 warnings · 0 of 2 files failed (the corpus's own rules)"}
Verification
Verifier bindings
test:protocol.validation.corpus-rules.rules-edit-starts-a-cycle · anchored · test anchor binding present · test/reach-examples.test.ts:448
Design view
Requirement drawing
Satisfying or referencing units: no recorded binding.
Requirement drawing for spec:validation.corpus-rules.rules-edit-starts-a-cycleThe diagram as text
spec:validation.corpus-rules.rules-edit-starts-a-cycle: Rewriting a rules file under node_modules starts a cycle that runs the new body · declared Spec
spec:validation.corpus-rules: Corpus-authored rules run on the one validation path · declared Spec
No open questions and no blocking questions; implementation binding none, verifier binding present, and expected-outcome oracle none.
Intent
outcome
Execute the watched-parameters rule on a parameter file, outside the probe root, that gains a key after the first cycle.
assumptions
The world writes the probe corpus of the error example, a parameter file outside the probe root holding { "one": 1 }, a first rules file whose body returns one warning finding for each key of its params, named by --rules and followed by --params @PATH naming that file, and the second rules file of the copy example. It starts the watcher as the rules-edit example does and, after the first cycle, writes { "one": 1, "two": 2 } to the parameter file, then reads the second cycle's corpus summary line and stops the watcher.
Behavior
Examples
Given a probe corpus the Protocol's validators find clean
Given a first rules file that {first: "reads a parameter file that gains a key after the first cycle under --watch"}
Given a second rules file that returns one warning when the graph it receives holds no node
When the probe corpus is validated with the rules files named by --rules in that order
Then the second cycle's corpus summary line reads {corpusSummary: "corpus rules: 0 errors · 2 warnings · 0 of 2 files failed (the corpus's own rules)"}
Verification
Verifier bindings
test:protocol.validation.corpus-rules.params-edit-starts-a-cycle · anchored · test anchor binding present · test/reach-examples.test.ts:454
Design view
Requirement drawing
Satisfying or referencing units: no recorded binding.
Requirement drawing for spec:validation.corpus-rules.params-edit-starts-a-cycleThe diagram as text
spec:validation.corpus-rules.params-edit-starts-a-cycle: Editing a parameter file starts a cycle that reads the new parameters · declared Spec
spec:validation.corpus-rules: Corpus-authored rules run on the one validation path · declared Spec
No open questions and no blocking questions; implementation binding none, verifier binding present, and expected-outcome oracle none.
Intent
outcome
Execute the watched-parameters rule on a parameter file that holds text that is not JSON after the first cycle.
assumptions
The world writes the probe corpus, parameter file and rules files of the parameter-edit example and starts the watcher as that example does. After the first cycle it writes text that is not JSON to the parameter file, waits for the second cycle, reads its corpus summary line and, on standard error, the line corpus rules <path> failed: its parameters cannot be read (<cause>) for the first file, then checks that the process is still running before it stops the watcher.
Behavior
Examples
Given a probe corpus the Protocol's validators find clean
Given a first rules file that {first: "reads a parameter file that stops being JSON after the first cycle under --watch"}
Given a second rules file that returns one warning when the graph it receives holds no node
When the probe corpus is validated with the rules files named by --rules in that order
Then the second cycle's corpus summary line reads {corpusSummary: "corpus rules: 0 errors · 0 warnings · 1 of 2 files failed (the corpus's own rules)"}
Then the watcher is still running after the second cycle: {running: true}
Verification
Verifier bindings
test:protocol.validation.corpus-rules.unreadable-params-fail-one-file · anchored · test anchor binding present · test/reach-examples.test.ts:460
Design view
Requirement drawing
Satisfying or referencing units: no recorded binding.
Requirement drawing for spec:validation.corpus-rules.unreadable-params-fail-one-fileThe diagram as text
spec:validation.corpus-rules.unreadable-params-fail-one-file: A parameter file that stops being JSON fails only its own rules file, and the watcher runs on · declared Spec
spec:validation.corpus-rules: Corpus-authored rules run on the one validation path · declared Spec
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
No implementation neighborhood recorded.
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:validation.corpus-rules.unreadable-params-fail-one-file
E2: verifies · declared
The diagram as text
spec:validation.corpus-rules: Corpus-authored rules run on the one validation path
spec:validation.corpus-rules.unreadable-params-fail-one-file: A parameter file that stops being JSON fails only its own rules file, and the watcher runs on
No open questions and no blocking questions; implementation binding present, verifier binding present, and expected-outcome oracle none.
The original design gave architecture a validation tier of its own. Its rules said that only adapter components depend on external systems, that domain code imports no infrastructure and that layers depend in one direction; dependency-cruiser, architecture tests and lint rules ran them, and their findings joined the one report (docs/lineage/v0-design/06-extraction-and-validation.md §6.4). Since the architectural annotation decision, the data those rules read is in the graph with no inference: a component anchor states its layer and its context, and uses and memberOf edges are anchored. The same decision keeps the check out of the Protocol. A negative architecture constraint stays declared intent in a Spec, and a corpus that wants it enforced checks its declared uses, layer and context in its own gate.
The first adopter checks that a component in its platform context has no uses edge to a component in another context, resolving each end through memberOf. Architecture crossings, recipe 32, reports differences in context or layer and refuses nothing. The architectural annotation decision keeps architecture checks in the corpus's own gate, and the Protocol supplies the reusable graph walk.
The walk returns the edges a rule forbids as corpus findings, so one body answers a reader through sdp q and fails a gate when the corpus names it as a rules file; the first adopter's isolation check becomes the walk named by sdp validate --rules with the isolated context platform, and none of its own code. An edge the walk cannot decide, because a component states no layer or context or an end belongs to no component, is listed apart: a breach the anchors cannot show is a curation gap, not a finding.
Intent
problem
A corpus that states an architecture rule in a Spec cannot have it checked by the Protocol, so each adopter writes the same walk over uses, memberOf, layer and context in its own gate.
outcome
Check a corpus's declared architecture rules, such as a forbidden uses direction or context isolation, against the layers, contexts and uses its anchors state.
The Protocol's validators never judge a corpus's architecture; an architecture rule runs as one of the corpus's own rules, the corpus's gate decides what fails, and that gate may be the corpus's own invocation of sdp validate --rules with the severity the rule states.
A corpus states each architecture rule as intent in a constraint Spec that says what is forbidden and why, and keeps the rule's machine form, its forbidden layer pairs and isolated contexts, as data its corpus rule passes to the walk.
The Protocol supplies the walk as a catalog recipe, recipe 42, that takes forbidden layer pairs and isolated contexts as recipe parameters, resolves each end of every anchored uses edge to its component, directly or through memberOf, and returns each edge a rule forbids with both units, both components and the rule.
The walk returns the edges it forbids as corpus findings in its answer's findings, so the shipped recipe file runs unchanged as a rules file under sdp validate --rules, at the severity the corpus passes it.
The walk checks two forms, forbidden layer pairs and isolated contexts. It requires no component to state a layer or a context and checks no port, route or other fact the anchors do not carry; a corpus that wants such a check writes a rule of its own.
A component that states no layer or context falls outside a rule that names one, and the walk lists the edge as unclassified rather than as a breach, as it lists an edge with an end that belongs to no component.
The corpus binds a discovered TypeScript check site to its constraint Spec through a test anchor; that check invokes the corpus rule, and has-verifier states that the check exists while its result stays in the corpus's run.
Given a member of a component at the layer {fromLayer: "domain"} in the context {fromContext: "orders"} that uses a member of a component at the layer {toLayer: "infrastructure"} in the context {toContext: "orders"}
When the architecture walk runs {invocation: "through sdp q"} with the forbidden pair domain to infrastructure and the isolated context platform
Then the walk reports {findings: 1} findings with the rule ids {ruleIds: "forbidden-layer"}
Then the walk lists {unclassified: 0} unclassified edges
Then the command exits {exitCode: 0}
Given a member of a component at the layer {fromLayer: "application"} in the context {fromContext: "platform"} that uses a member of a component at the layer {toLayer: "adapter"} in the context {toContext: "orders"}
When the architecture walk runs {invocation: "as a rules file named by sdp validate --rules"} with the forbidden pair domain to infrastructure and the isolated context platform
Then the walk reports {findings: 1} findings with the rule ids {ruleIds: "isolated-context"}
Then the command exits {exitCode: 1}
Given a member of a component at the layer {fromLayer: "domain"} in the context {fromContext: "platform"} that uses a member of a component at the layer {toLayer: "unstated"} in the context {toContext: "platform"}
When the architecture walk runs {invocation: "through sdp q"} with the forbidden pair domain to infrastructure and the isolated context platform
Then the walk reports {findings: 0} findings with the rule ids {ruleIds: ""}
Then the walk lists {unclassified: 1} unclassified edges
Then the command exits {exitCode: 0}
Given a member of a component at the layer {fromLayer:"domain"|"application"} in the context {fromContext:"platform"|"orders"} that uses a member of a component at the layer {toLayer:"infrastructure"|"adapter"|"unstated"} in the context {toContext:"platform"|"orders"}
When the architecture walk runs {invocation:"through sdp q"|"as a rules file named by sdp validate --rules"} with the forbidden pair domain to infrastructure and the isolated context platform
Then the walk reports {findings:number} findings with the rule ids {ruleIds:string}
Then the walk lists {unclassified:number} unclassified edges
Then the command exits {exitCode:number}
recipe 42, the architecture walk, takes params.forbidden, a list of { from, to } pairs of layers from the closed set; params.isolated, a list of context tokens; and params.severity, "error" by default or "warning"; either list may be empty, and a pair or context given twice counts once; a parameter name outside these three returns { unknownParams, accepts } alone, before any other check, as every catalog recipe answers a name it does not take; neither list given, a layer outside the set, a malformed token or another severity returns { diagnostics } with no findings; and either answer fails the file when the walk runs as a rules file.
{ input: { forbidden, isolated, severity }, totals: { usesEdges, resolved, findings, unclassified }, findings, unclassified } is the answer when every parameter name is known and every value lawful, where resolved counts the uses edges whose two ends both resolve to a component; findings sort by from, then to, then rule id, and unclassified rows by from, then to; recipe 32's answer stays as it is.
an anchored uses edge whose ends resolve to two distinct components breaks the pair { from, to } when the using component states layer from and the used one states layer to, and breaks the isolation of a context when the using component states that context and the used one states another; two units of one component never break a rule, as recipe 32 never counts them as crossing.
{ validatorId, subjectId, relatedId, severity, message, from, to, fromComponent, toComponent, rule }, a corpus finding in the shape spec:validation.corpus-rules#design.ruleFinding pins, where subjectId is the using unit from, relatedId the used unit to, both components are { id, layer, context } with null for an unstated attribute, and validatorId is forbidden-layer with rule: { from, to } the pair it breaks, or isolated-context with rule: { context } the isolated context it leaves.
a forbidden-layer message reads <from> uses <to>: layer <from layer> uses layer <to layer> (<fromComponent id> to <toComponent id>), a forbidden pair, and an isolated-context message reads <from> uses <to>: <fromComponent id> in the isolated context <context> uses <toComponent id> in the context <other context>.
unclassified rows { from, to, fromComponent, toComponent, missing } list each anchored uses edge the walk cannot decide while a list is non-empty: an edge with an end that belongs to no component, that end's component null and missing: ["component"], and an edge between two distinct components where either leaves unstated an attribute a non-empty list reads, layer for forbidden and context for isolated, named in missing.
fromLayer · "domain" | "application" · declared by a member of a component at the layer {fromLayer} in the context {fromContext} that uses a member of a component at the layer {toLayer} in the context {toContext}
fromContext · "platform" | "orders" · declared by a member of a component at the layer {fromLayer} in the context {fromContext} that uses a member of a component at the layer {toLayer} in the context {toContext}
toLayer · "infrastructure" | "adapter" | "unstated" · declared by a member of a component at the layer {fromLayer} in the context {fromContext} that uses a member of a component at the layer {toLayer} in the context {toContext}
toContext · "platform" | "orders" · declared by a member of a component at the layer {fromLayer} in the context {fromContext} that uses a member of a component at the layer {toLayer} in the context {toContext}
invocation · "through sdp q" | "as a rules file named by sdp validate --rules" · declared by the architecture walk runs {invocation} with the forbidden pair domain to infrastructure and the isolated context platform
Requirement drawing for spec:validation.architecture-constraintsThe diagram as text
spec:validation.architecture-constraints: Declared architecture rules are checked against anchored structure · declared Spec
spec:validation.architecture-constraints.forbidden-layer-pair: A domain member that uses an infrastructure member breaks a forbidden pair · declared Spec
spec:validation.architecture-constraints.isolated-context-as-rule: The walk named as a rules file fails validation on an isolation breach · declared Spec
spec:validation.architecture-constraints.unstated-layer-unclassified: A component with no layer leaves its edge unclassified · declared Spec
spec:validation.corpus-rules: Corpus-authored rules run on the one validation path · declared Spec
impl:protocol.architecture-walk-recipe: asserts realization of the shipped architecture walk recipe · anchored code · test/architecture-recipes.test.ts:10
spec:validation.architecture-constraints refines spec:validation.corpus-rules · declared — the architecture walk is a corpus-selected rule over anchored structure
Preferred and chosen syntax: requirementDiagram; exact refines, satisfies and verifies, with claim words in nodes and per-edge comments.
Decisions
spec:decisions.architectural-annotation
Architectural significance is annotated on the anchor of the unit that realizes it. A code anchor may carry role, the architectural pattern the unit plays; a component: anchor may additionally carry layer, one of edge, application, domain, adapter, or infrastructure, and context, its bounded context. The component: anchor is anchored architecture: it is the declared component, and there is no separate architecture file, because the anchor sits where the component is realized. It is not the original design's declared Component that could exist before code; an unrealized component lives in Specs and never as an invented CodeNode. A role is a free, corpus-owned vocabulary whose taxonomy the census renders with counts from the graph; service, decider, projection, read-model, codec, contract, barrel, and utility are the lineage set, not a closed list. layer is a closed set, and a value outside it is an envelope error; layer or context on a non-component anchor is an envelope error. All three attributes are optional, and omission is lawful.
Runtime
impl:protocol.architecture-walk-recipe · asserts realization of the shipped architecture walk recipe · test/architecture-recipes.test.ts:10
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:validation.architecture-constraintsThe diagram as text
spec:decisions.architectural-annotation: Architectural significance is annotated where it is realized
spec:model.anchors: Source anchors bind code without carrying intent
spec:validation.architecture-constraints: Declared architecture rules are checked against anchored structure
spec:validation.architecture-constraints.forbidden-layer-pair: A domain member that uses an infrastructure member breaks a forbidden pair
spec:validation.architecture-constraints.isolated-context-as-rule: The walk named as a rules file fails validation on an isolation breach
spec:validation.architecture-constraints.unstated-layer-unclassified: A component with no layer leaves its edge unclassified
spec:validation.corpus-rules: Corpus-authored rules run on the one validation path
spec:validation.architecture-constraints refines spec:validation.corpus-rules · declared — the architecture walk is a corpus-selected rule over anchored structure
No open questions and no blocking questions; implementation binding none, verifier binding present, and expected-outcome oracle none.
Intent
outcome
Execute the walk through sdp q on one uses edge between members of two components whose layers form a forbidden pair.
assumptions
The world writes a probe root with no Spec and two comment-form component: anchors, each with one impl: member; the using member's anchor uses the used member. Each component states the layer and context its slots give, and the Protocol's validators find the root clean. The world runs the walk's shipped recipe file through sdp q --json with params{ "forbidden": [{ "from": "domain", "to": "infrastructure" }], "isolated": ["platform"] }; the rule ids are the findings' validatorId values joined by a comma and a space, in answer order.
Behavior
Examples
Given a member of a component at the layer {fromLayer: "domain"} in the context {fromContext: "orders"} that uses a member of a component at the layer {toLayer: "infrastructure"} in the context {toContext: "orders"}
When the architecture walk runs {invocation: "through sdp q"} with the forbidden pair domain to infrastructure and the isolated context platform
Then the walk reports {findings: 1} findings with the rule ids {ruleIds: "forbidden-layer"}
Then the walk lists {unclassified: 0} unclassified edges
Then the command exits {exitCode: 0}
Verification
Verifier bindings
test:protocol.validation.architecture-constraints.forbidden-layer-pair · anchored · test anchor binding present · test/self-hosting-corpus-checks.test.ts:346
Design view
Requirement drawing
Satisfying or referencing units: no recorded binding.
Requirement drawing for spec:validation.architecture-constraints.forbidden-layer-pairThe diagram as text
spec:validation.architecture-constraints.forbidden-layer-pair: A domain member that uses an infrastructure member breaks a forbidden pair · declared Spec
spec:validation.architecture-constraints: Declared architecture rules are checked against anchored structure · declared Spec
No open questions and no blocking questions; implementation binding none, verifier binding present, and expected-outcome oracle none.
Intent
outcome
Execute the walk's shipped recipe file, unchanged, as a rules file under sdp validate --rules on one uses edge that leaves an isolated context.
assumptions
The world writes the probe root of the forbidden-pair example, with the layers and contexts the slots give. It runs sdp validate on that root with the walk's shipped recipe file named by --rules and followed by --params with the parameters of that example. The findings are the lines in that file's corpus section, and the rule ids are their rule ids joined by a comma and a space, in output order.
Behavior
Examples
Given a member of a component at the layer {fromLayer: "application"} in the context {fromContext: "platform"} that uses a member of a component at the layer {toLayer: "adapter"} in the context {toContext: "orders"}
When the architecture walk runs {invocation: "as a rules file named by sdp validate --rules"} with the forbidden pair domain to infrastructure and the isolated context platform
Then the walk reports {findings: 1} findings with the rule ids {ruleIds: "isolated-context"}
Then the command exits {exitCode: 1}
Verification
Verifier bindings
test:protocol.validation.architecture-constraints.isolated-context-as-rule · anchored · test anchor binding present · test/self-hosting-corpus-checks.test.ts:353
Design view
Requirement drawing
Satisfying or referencing units: no recorded binding.
Requirement drawing for spec:validation.architecture-constraints.isolated-context-as-ruleThe diagram as text
spec:validation.architecture-constraints.isolated-context-as-rule: The walk named as a rules file fails validation on an isolation breach · declared Spec
spec:validation.architecture-constraints: Declared architecture rules are checked against anchored structure · declared Spec
No open questions and no blocking questions; implementation binding none, verifier binding present, and expected-outcome oracle none.
Intent
outcome
Execute the walk through sdp q on one uses edge whose used component states no layer.
assumptions
The world writes the probe root of the forbidden-pair example, except that the used component's anchor states no layer, which the unstated value names. It runs the walk as that example does; an empty rule-id string names no finding.
Behavior
Examples
Given a member of a component at the layer {fromLayer: "domain"} in the context {fromContext: "platform"} that uses a member of a component at the layer {toLayer: "unstated"} in the context {toContext: "platform"}
When the architecture walk runs {invocation: "through sdp q"} with the forbidden pair domain to infrastructure and the isolated context platform
Then the walk reports {findings: 0} findings with the rule ids {ruleIds: ""}
Then the walk lists {unclassified: 1} unclassified edges
Then the command exits {exitCode: 0}
Verification
Verifier bindings
test:protocol.validation.architecture-constraints.unstated-layer-unclassified · anchored · test anchor binding present · test/self-hosting-corpus-checks.test.ts:360
Design view
Requirement drawing
Satisfying or referencing units: no recorded binding.
Requirement drawing for spec:validation.architecture-constraints.unstated-layer-unclassifiedThe diagram as text
spec:validation.architecture-constraints.unstated-layer-unclassified: A component with no layer leaves its edge unclassified · declared Spec
spec:validation.architecture-constraints: Declared architecture rules are checked against anchored structure · declared Spec
No open questions and no blocking questions; implementation binding present, verifier binding present, and expected-outcome oracle none.
The original design declared a component before its code, as a Component node with a layer, a bounded context and a capability, kept in an architecture folder and reconciled with its code anchors and the Specs (docs/lineage/v0-design/03-graph-metamodel.md §2.5; 04-authoring-surfaces.md §7). Its runtime chapter separated what a unit requires from the provider that supplies it, the provider a composition root selects and the one a test puts in its place, and gave one mechanism the ownership of runtime composition (05-runtime-anchors.md §1, §3.3, §7).
The Protocol records the architecture the code realizes. A component: anchor states its layer and context, and anchored memberOf and uses edges join the code units. Under the architectural annotation decision, a component no code realizes is a Spec, and the graph holds no node for it until an anchor exists. The first adopter states its component list and its context rule in prose (libar-platform/design/PLAN.md §2.1) and checks context isolation over anchored uses in its own script (libar-platform/design/tools/check.py). The owner ruled that the architectural annotation decision stands: a component designed before its code is a component design Spec, and the component: anchor that later realizes it satisfies that Spec, which is the join a comparison reads.
Choosing a provider stays prose. The original's distinctions between the interface a unit requires, the providers that supply it, the one a composition root selects and the one a test substitutes help a reader, and none of them needs graph structure beyond uses to be compared. Keeping one composition mechanism per application is a corpus's own architecture rule when the corpus wants it, stated in a constraint Spec and checked by its own corpus rule. A component design names its component with the keys a component anchor uses, so the comparison reads the same words on both sides, and a key the design leaves out is not compared, so a design states only what it has decided; a design that has decided its component uses nothing says none, which a left-out key cannot say.
Intent
problem
A design ahead of its code cannot state the components it adds, their layer and context and what they use, in a form the graph compares with the components the code later anchors.
outcome
Let a design name the components it will add, with their layer, context and uses, and compare that design with the anchored components once code exists.
risks
A planned component kept apart from its realization is a second place for the component to drift, which is why the architectural annotation decision refused a separate architecture file and why the design lives in a Spec the realizing anchor satisfies.
Given a component design for {componentId: "component:probe.history"} that plans the layer {plannedLayer: "domain"} and uses {plannedUses: "component:probe.kernel"}
Given the component anchor for that design {anchor: "satisfies the design"}
Given that anchor states the layer {realizedLayer: "application"} and its member uses {realizedUses: "component:probe.audit"}
When the realization comparison runs
Then the comparison lists {unrealized: 0} unrealized designs, {matching: 0} matching designs and {differing: 2} differing rows
Then the differing fields are {fields: "layer, uses"}
Given a component design for {componentId: "component:probe.history"} that plans the layer {plannedLayer: "domain"} and uses {plannedUses: "component:probe.kernel"}
Given the component anchor for that design {anchor: "satisfies the design"}
Given that anchor states the layer {realizedLayer: "domain"} and its member uses {realizedUses: "component:probe.kernel"}
When the realization comparison runs
Then the comparison lists {unrealized: 0} unrealized designs, {matching: 1} matching designs and {differing: 0} differing rows
Then the differing fields are {fields: ""}
Given a component design for {componentId: "component:probe.history"} that plans the layer {plannedLayer: "domain"} and uses {plannedUses: "component:probe.kernel"}
Given the component anchor for that design {anchor: "is absent"}
Given that anchor states the layer {realizedLayer: "absent"} and its member uses {realizedUses: ""}
When the realization comparison runs
Then the comparison lists {unrealized: 1} unrealized designs, {matching: 0} matching designs and {differing: 0} differing rows
Then the differing fields are {fields: ""}
Given a component design for {componentId:string} that plans the layer {plannedLayer:"domain"|"application"} and uses {plannedUses:string}
Given the component anchor for that design {anchor:"is absent"|"satisfies the design"}
Given that anchor states the layer {realizedLayer:"domain"|"application"|"absent"} and its member uses {realizedUses:string}
When the realization comparison runs
Then the comparison lists {unrealized:number} unrealized designs, {matching:number} matching designs and {differing:number} differing rows
Then the differing fields are {fields:string}
A Spec that states a component the code does not yet realize and what it is for. Its Design entries are keyed as the anchor's attributes are named: id for the planned component: anchor's own id, layer, context, uses for the components it will use, and optionally role. The keys are a convention recipes read; the Protocol closes no Design shape, and the Spec confers nothing and mints no code unit.
The component a component design names. It stays intent until a component: anchor realizes it, and that anchor satisfies the component design; the graph holds no component node before the anchor exists.
Recipe 43, planned against realized components, which reads each component design beside the component: anchor that satisfies it, resolving members through memberOf as recipe 32 does, and lists a design no anchor satisfies yet, a layer, context or role that differs, and a uses named on one side only. It reports and refuses nothing.
A code unit that supplies an interface another unit requires; a composition selects one provider for each requirement, and a test may select another. A component design names its providers and that selection in prose, and none of them earns graph structure beyond uses.
a Spec is a component design when its Design id value, bare or in the code span that opens it, is a component: id; the curation report and the realization comparison select designs this way, and no validator reads the convention.
the comparison reads five Design keys, id, layer, context, role and uses, and no other; layer takes one of the five layers, context and role one token on the role grammar, and uses either none for the empty list or component: ids separated by commas, each trimmed, read as the set of distinct ids; each value is read bare when it is the whole value, or from the code span that opens it, with prose after the span free to say why.
recipe 43, planned against realized components, takes no parameter, so any parameter name returns { unknownParams, accepts: [] } alone, as every catalog recipe answers a name it does not take; otherwise it returns { totals, unrealized, differing, unreadable }, where totals is { designs, realized, matching, unrealized, differing, unreadable }, matching counts the realized designs with neither a differing row nor an unreadable row, and differing counts rows.
a design is realized when its component: CodeNode is anchored and has an anchored satisfies edge to the design, as the curation report reads it; a design whose component exists without that edge stays unrealized, with anchorExists: true.
for a realized design, each of layer, context, role and uses the design states is compared with the anchored component, and a key the design leaves out is not compared; the realized uses are the components other than its own that an anchored uses edge from the component or one of its members reaches, each end resolved to its component as recipe 32 resolves it.
unrealized rows are { spec, componentId, anchorExists, layer, context, role, uses } with the planned values, null when unstated, and uses an empty list when the design states none; differing rows are { spec, componentId, field, planned, realized }, one for each differing field, where layer, context and role carry the two values or null and uses carries the two sorted lists of components named on that side only; unreadable rows are { spec, key, value } for a value outside its key's form, and that key is not compared; rows sort by spec, then by field or key.
no citation recorded
Verification
Verifier bindings
spec:model.planned-architecture.differing-layer-and-uses · A realized component that differs from its design gives one row per field · declared · example enabled · specs/model/planned-architecture.differing-layer-and-uses.sdp.md
spec:model.planned-architecture.matching-design · A realized component that matches its design gives no row · declared · example enabled · specs/model/planned-architecture.matching-design.sdp.md
spec:model.planned-architecture.unrealized-design · A component design no anchor satisfies is listed as unrealized · declared · example enabled · specs/model/planned-architecture.unrealized-design.sdp.md
Example space
componentId · string · declared by a component design for {componentId} that plans the layer {plannedLayer} and uses {plannedUses}
plannedLayer · "domain" | "application" · declared by a component design for {componentId} that plans the layer {plannedLayer} and uses {plannedUses}
plannedUses · string · declared by a component design for {componentId} that plans the layer {plannedLayer} and uses {plannedUses}
anchor · "is absent" | "satisfies the design" · declared by the component anchor for that design {anchor}
realizedLayer · "domain" | "application" | "absent" · declared by that anchor states the layer {realizedLayer} and its member uses {realizedUses}
realizedUses · string · declared by that anchor states the layer {realizedLayer} and its member uses {realizedUses}
Preferred syntax: requirementDiagram. Chosen syntax: flowchart TB because references require exact labels unavailable in requirement syntax.
Decisions
spec:decisions.architectural-annotation
Architectural significance is annotated on the anchor of the unit that realizes it. A code anchor may carry role, the architectural pattern the unit plays; a component: anchor may additionally carry layer, one of edge, application, domain, adapter, or infrastructure, and context, its bounded context. The component: anchor is anchored architecture: it is the declared component, and there is no separate architecture file, because the anchor sits where the component is realized. It is not the original design's declared Component that could exist before code; an unrealized component lives in Specs and never as an invented CodeNode. A role is a free, corpus-owned vocabulary whose taxonomy the census renders with counts from the graph; service, decider, projection, read-model, codec, contract, barrel, and utility are the lineage set, not a closed list. layer is a closed set, and a value outside it is an envelope error; layer or context on a non-component anchor is an envelope error. All three attributes are optional, and omission is lawful.
Runtime
impl:protocol.planned-components-recipe · asserts realization of the shipped planned against realized components recipe · test/architecture-recipes.test.ts:16
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:model.planned-architecture
E6: verifies · declared (3 unit edges)
The diagram as text
spec:consumers.design-slice: A slice reads a Pack as one design, with the architecture it touches and what each member needs next
spec:consumers.impact-graph.curation-assist: File-level imports show where curation is missing
spec:consumers.spec-studio.diagram-kinds.scoped-architecture: A Spec set draws the architecture it touches
spec:consumers.spec-studio.lenses.architecture: The architecture lens draws components by context and layer, the planned beside the built
spec:consumers.upgrade-kit.capability-adoption: Capability adoption counts where each capability is used and where it could apply
spec:decisions.architectural-annotation: Architectural significance is annotated where it is realized
spec:model.anchors: Source anchors bind code without carrying intent
spec:model.core-model: The Protocol models delivery with one enrichable Spec
spec:model.planned-architecture: Components are designed ahead of their code
spec:model.planned-architecture.differing-layer-and-uses: A realized component that differs from its design gives one row per field
spec:model.planned-architecture.matching-design: A realized component that matches its design gives no row
spec:model.planned-architecture.unrealized-design: A component design no anchor satisfies is listed as unrealized
spec:model.planned-architecture dependsOn spec:model.anchors · declared — a planned component is realized by a component anchor that satisfies its design
spec:model.planned-architecture refines spec:model.core-model · declared — planned components enrich Specs before code binds them
spec:consumers.design-slice dependsOn spec:model.planned-architecture · declared — planned components and differing designs come from the realization comparison
spec:consumers.impact-graph.curation-assist dependsOn spec:model.planned-architecture · declared — the report uses the component-design convention to identify unrealized planned components
spec:consumers.spec-studio.lenses.architecture dependsOn spec:model.planned-architecture · declared — planned components come from the component designs recipe 43 reads
spec:consumers.upgrade-kit.capability-adoption dependsOn spec:model.planned-architecture · declared — component designs are selected by the convention that Spec states
impl:protocol.planned-components-recipe · asserts realization of the shipped planned against realized components recipe · test/architecture-recipes.test.ts:16
Design reference: present
impl:protocol.curation-recipe · asserts the curation report over external inferred imports · test/curation-recipes.test.ts:32
Verifier binding: present
spec:model.planned-architecture.differing-layer-and-uses · A realized component that differs from its design gives one row per field · declared · specs/model/planned-architecture.differing-layer-and-uses.sdp.md · example enabled
spec:model.planned-architecture.matching-design · A realized component that matches its design gives no row · declared · specs/model/planned-architecture.matching-design.sdp.md · example enabled
spec:model.planned-architecture.unrealized-design · A component design no anchor satisfies is listed as unrealized · declared · specs/model/planned-architecture.unrealized-design.sdp.md · example enabled
Expected-outcome oracle: none
Runtime observation: not tracked.
Run results: not recorded in the graph.
Findings
None.
14. A component design no anchor satisfies is listed as unrealized
spec:model.planned-architecture.unrealized-design
A component design no anchor satisfies is listed as unrealized
No open questions and no blocking questions; implementation binding none, verifier binding present, and expected-outcome oracle none.
Intent
outcome
Execute the realization comparison on one component design whose component no anchor realizes yet.
assumptions
The world writes a probe root holding one story-altitude model Spec stating idea with an Intent outcome, one Model term and a Design section whose id, layer and uses entries carry the slots' planned values, bare. Beside it the root holds comment-form anchors for component:probe.kernel and component:probe.audit, each with one impl: member. The world runs the comparison's shipped recipe file through sdp q --json; the differing fields are the field values of the differing rows joined by a comma and a space, in answer order, and an empty string names none.
Behavior
Examples
Given a component design for {componentId: "component:probe.history"} that plans the layer {plannedLayer: "domain"} and uses {plannedUses: "component:probe.kernel"}
Given the component anchor for that design {anchor: "is absent"}
Given that anchor states the layer {realizedLayer: "absent"} and its member uses {realizedUses: ""}
When the realization comparison runs
Then the comparison lists {unrealized: 1} unrealized designs, {matching: 0} matching designs and {differing: 0} differing rows
Then the differing fields are {fields: ""}
Verification
Verifier bindings
test:protocol.model.planned-architecture.unrealized-design · anchored · test anchor binding present · test/self-hosting-corpus-checks.test.ts:381
Design view
Requirement drawing
Satisfying or referencing units: no recorded binding.
Requirement drawing for spec:model.planned-architecture.unrealized-designThe diagram as text
spec:model.planned-architecture.unrealized-design: A component design no anchor satisfies is listed as unrealized · declared Spec
spec:model.planned-architecture: Components are designed ahead of their code · declared Spec
No open questions and no blocking questions; implementation binding none, verifier binding present, and expected-outcome oracle none.
Intent
outcome
Execute the realization comparison on one realized component whose layer and uses differ from its design.
assumptions
The world writes the probe root of the unrealized-design example and adds a comment-form anchor for the planned component that satisfies the design Spec and states the realized layer, with one impl: member whose anchor uses the member of the component the realized-uses slot names.
Behavior
Examples
Given a component design for {componentId: "component:probe.history"} that plans the layer {plannedLayer: "domain"} and uses {plannedUses: "component:probe.kernel"}
Given the component anchor for that design {anchor: "satisfies the design"}
Given that anchor states the layer {realizedLayer: "application"} and its member uses {realizedUses: "component:probe.audit"}
When the realization comparison runs
Then the comparison lists {unrealized: 0} unrealized designs, {matching: 0} matching designs and {differing: 2} differing rows
Then the differing fields are {fields: "layer, uses"}
Verification
Verifier bindings
test:protocol.model.planned-architecture.differing-layer-and-uses · anchored · test anchor binding present · test/self-hosting-corpus-checks.test.ts:367
Design view
Requirement drawing
Satisfying or referencing units: no recorded binding.
Requirement drawing for spec:model.planned-architecture.differing-layer-and-usesThe diagram as text
spec:model.planned-architecture.differing-layer-and-uses: A realized component that differs from its design gives one row per field · declared Spec
spec:model.planned-architecture: Components are designed ahead of their code · declared Spec
No open questions and no blocking questions; implementation binding none, verifier binding present, and expected-outcome oracle none.
Intent
outcome
Execute the realization comparison on one realized component whose layer and uses match its design.
assumptions
The world writes the probe root of the differing example, with the realized layer and uses the slots give.
Behavior
Examples
Given a component design for {componentId: "component:probe.history"} that plans the layer {plannedLayer: "domain"} and uses {plannedUses: "component:probe.kernel"}
Given the component anchor for that design {anchor: "satisfies the design"}
Given that anchor states the layer {realizedLayer: "domain"} and its member uses {realizedUses: "component:probe.kernel"}
When the realization comparison runs
Then the comparison lists {unrealized: 0} unrealized designs, {matching: 1} matching designs and {differing: 0} differing rows
Then the differing fields are {fields: ""}
Verification
Verifier bindings
test:protocol.model.planned-architecture.matching-design · anchored · test anchor binding present · test/self-hosting-corpus-checks.test.ts:374
Design view
Requirement drawing
Satisfying or referencing units: no recorded binding.
Requirement drawing for spec:model.planned-architecture.matching-designThe diagram as text
spec:model.planned-architecture.matching-design: A realized component that matches its design gives no row · declared Spec
spec:model.planned-architecture: Components are designed ahead of their code · declared Spec