fix(blueprint): stop §10 rejecting a cycle §4.2 permits - #48
Merged
Conversation
§10 said "A cycle is rejected with ERR_CONNECTION_CYCLE" while §4.2 of the same document says the graph MAY contain a cycle and an implementation MUST NOT reject a composition for that reason alone. The corpus sides with §4.2 — semantic/002-connection-cycle and semantic/011-three-node-cycle both declare expected: pass — so §10 was the outlier, and a disagreement between two normative artifacts blocks a release here rather than leaving an implementation to choose. It is not residue from the acyclicity reversal, which matters for what the fix should say. §4.2's permission landed in af2dec0, the ADR 0005 commit that withdrew ERR_DEPENDENCY_CYCLE, and §10 was written afterwards in e840e6d, whose own message states the intent: cycles MUST be detected rather than bounded by a recursion limit, because a stack overflow is a crash and not a diagnostic. The rejection clause reinstated a withdrawn rule under a new code name one commit later. ERR_CONNECTION_CYCLE was in no registry and occurred nowhere else in the repository. So the traversal requirement is kept, and now cites §4.2 rather than contradicting it. It also says what detection does not mean: a walk that meets a cycle terminates, it does not report. Deleting the clause outright would have left a reader who arrives at §10 first able to re-infer rejection from silence. conformance/README.md's phases table still named "dependency cycles" as something the semantic phase enforces — the same withdrawn rule surviving in a second document. That phase enforces cross-document agreement. No fixture: nothing about validation changes, and the surviving rule is already pinned by the two cycle cases. No ADR: this corrects a slip inside ADR 0005 §2 rather than deciding policy, and that section reads true afterwards. Refs #46 Signed-off-by: Justin Merrell <merrelljustin@gmail.com>
…registry Two directions were checked and a third was not. checkCaseShape asks whether every code a fixture declares is defined by the prose; checkCoverage asks whether every code the prose defines is exercised by a fixture. Neither looks at a code named in a sentence, because DIAGNOSTIC_ROW is anchored to ^| and so sees only the registry tables. That gap is how blueprint §10 shipped naming ERR_CONNECTION_CYCLE — a code no table has ever defined, contradicting §4.2 in the same document — with every check green. A code named in normative prose is the same promise to an implementer as a row in the registry, and an implementer who looks this one up finds nothing. checkProseCodes closes it. Every backticked ERR_ in the three spec.md files must be declared by some registry, or recorded in HYPOTHETICAL with a written reason, in the shape UNCOVERED and UNPINNED already use. The set compared against is the union of all three registries rather than registryFor's reachable set. Component §3 and §10 legitimately name blueprint's codes and listing §4 names ERR_UNREFERENCED_COMPONENT; those are citations, not declarations, and the question being asked is whether the code exists at all. One entry in HYPOTHETICAL. Component §3 names ERR_SCHEMA_TOO_OLD as a code that deliberately does not exist, to explain why a field from a newer release is reported as ERR_UNKNOWN_FIELD — a validator holding neither definition cannot tell that case from a misspelling. Scoped to spec.md. An ADR is immutable and records withdrawn codes as history, so ERR_DEPENDENCY_CYCLE in ADR 0005 is correctly a code that no longer exists; a named code is a promise only in a normative document. Confirmed to fail closed three ways before being committed: the reinstated §10 wording is reported at blueprint spec.md:751, an emptied HYPOTHETICAL is reported at component spec.md:100, and an invented code anywhere in a spec.md is reported. Refs #46 Signed-off-by: Justin Merrell <merrelljustin@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changes
Blueprint §10's "Graph traversal" paragraph said a cycle is rejected with
ERR_CONNECTION_CYCLE, contradicting §4.2 of the same document — which says thegraph MAY contain a cycle and an implementation MUST NOT reject a composition
for that reason alone — and naming a code no diagnostics table has ever
declared. The traversal requirement around it is kept and now cites §4.2 rather
than contradicting it. Two adjacent artifacts of the same withdrawn rule go with
it:
conformance/README.md's phases table, and the absence of any gate thatwould have caught a phantom code.
Why
Closes #46.
The corpus already sides with §4.2 —
semantic/002-connection-cycleandsemantic/011-three-node-cycleboth declareexpected: "pass"— so §10 was theoutlier, and a disagreement between two normative artifacts blocks a release
here rather than leaving an implementation to choose.
One correction to the issue's diagnosis, and it changes what the fix should
say. This is not text left behind by the acyclicity reversal.
git log -Sputs §4.2's permission in
af2dec0(#41, the ADR 0005 commit that withdrewERR_DEPENDENCY_CYCLE) and the whole of §10 — this paragraph included — ine840e6d(#42), afterwards. That commit's own message states the intentplainly:
So the rejection clause reinstated a withdrawn rule under a new code name one
commit after it was withdrawn, while the sentence around it is intended and
correct.
That is why this is not the suggested one-line deletion. A reader who arrives at
§10 first must not be able to re-infer rejection from silence, so the paragraph
now names §4.2's permission and says what detection does not mean — a walk
that meets a cycle terminates, it does not report:
conformance/README.md's phases table still named "dependency cycles" assomething the
semanticphase enforces — the same withdrawn rule surviving in asecond document. That phase enforces cross-document agreement.
No ADR: this corrects a slip inside ADR 0005 §2's decision rather than deciding
policy, and that section reads true afterwards. Same reasoning as #45.
The gate that was missing
Worth more than the clause itself.
tools/src/conformance.tschecked twodirections and not a third:
checkCaseShapediagnostics.jsondeclares must existcheckCoverageDIAGNOSTIC_ROWis anchored to^\|, so an inline`ERR_CONNECTION_CYCLE`was invisible to every gate — which is exactly why this reached
mainwith CIgreen. A code named in normative prose is the same promise to an implementer as
a row in the registry, and an implementer who looked this one up found nothing.
checkProseCodescloses it: every backtickedERR_in the threespec.mdfiles must be declared by some registry, or recorded in
HYPOTHETICALwith awritten reason, in the shape
UNCOVEREDandUNPINNEDalready use.Two design points the diff does not make obvious:
registryFor's reachable set. Component §3 and §10 legitimately nameblueprint's
ERR_UNKNOWN_COMPONENT,ERR_COMPONENT_NOT_PUBLISHEDandERR_UNKNOWN_COMPUTE_PROFILE, and listing §4 namesERR_UNREFERENCED_COMPONENT. Those are citations, and the question is whetherthe code exists at all.
spec.md. An ADR is immutable and records withdrawn codesas history, so
ERR_DEPENDENCY_CYCLEin ADR 0005 is correctly a code thatno longer exists. A named code is a promise only in a normative document.
HYPOTHETICALhas exactly one entry. Component §3 namesERR_SCHEMA_TOO_OLDasa code that deliberately does not exist, to explain why a field from a newer
release is reported as
ERR_UNKNOWN_FIELD. Run against the whole corpus, thosetwo were the only unresolved mentions in the repository.
Compatibility
No schema change and no validation behaviour change. The rule §10 named was
never implemented by this repository's validator, never had a registry entry,
and never had a fixture; §4.2 is unchanged and is what the corpus has always
enforced.
Verification
task checkgreen — all 16 checks, includingcheck:standards(6 schemasmeta-validated) and
check:parity(133 subjects, 0 Ajv/Blaze disagreements);neither self-skipped. 145 conformance cases pass, profile
offline, 33/37requirements pinned — unchanged from
main.The new gate was confirmed to fail closed three ways before being committed,
each restored afterwards:
ERR_CONNECTION_CYCLEwordingspecifications/blueprint/v1/spec.md:751— the exact line the issue namesHYPOTHETICALspecifications/component/v1/spec.md:100— proving the allowlist is load-bearing, not decorative`ERR_MADE_UP`into aspec.mdgrep -rn ERR_CONNECTION_CYCLEnow returns nothing outside.git.No fixture, deliberately
Nothing about validation changes, and the surviving rule is already pinned by
the two cycle cases the issue cites. The traversal requirement stays without a
stable ID by #42's own boundary: rules about what an implementation does don't
get one, because a document cannot violate them. The new gate is verified by
fail-closed demonstration, the way #42 verified its four lint rules.
docs/traceability.mdis unchanged — no requirement IDs move.Checklist
task checkpasses locallyschemas/dist/regenerated withtask bundleand committed (never edited by hand) — n/a, no schema changeclause— n/a, see abovespec.md— schemadescriptions are explanatory, not normativegit commit -s)🤖 Generated with Claude Code