canon(constraints): bug-class lessons from resolver PR (Phase 2 PR-2.3b)#146
Merged
canon(constraints): bug-class lessons from resolver PR (Phase 2 PR-2.3b)#146
Conversation
Phase 2 PR-2.3b of the link-rot-elimination campaign. Sibling to the oddkit_audit implementation in klappy/oddkit#143. Encodes three specific bugs that surfaced during PR-2.1 review and validation, so future-self gets the lesson without rediscovering it. Three new tier-2 constraints (additive, no regressions): 1. canon/constraints/oddkit-action-registration-completeness.md New oddkit MCP actions must register in BOTH the dispatch switch AND the VALID_ACTIONS array. Caught by Cursor Bugbot on PR-2.1 initial commit; would have shipped Unknown action: <n> errors to every caller. Process control until typecheck-time invariant exists. 2. canon/constraints/superseded-by-shape-normalization.md Tools that read superseded_by must normalize across three shapes: full klappy:// URI, repo-relative path with .md, repo-relative path without. Cycle detection keys on canonical URI, not raw superseded_by string. Caught by independent Sonnet 4.6 validator on PR-2.1; mixed-shape supersession was a real production-blocking bug. The Vodka rule applies: resolver normalizes, consumers stay simple. 3. canon/constraints/bash-test-rig-assignment-chain-discipline.md Bash silently concatenates two assignments separated by missing whitespace. RAW=$(curl)RESULT=$(...) parses, runs, produces wrong values. After any closing ) of a command substitution, next character must be a newline. Caught by post-merge CI on PR-2.1; one-character fix, non-trivial diagnostic time. Per operator decision: bundle these into PR-2.3 of the link-rot campaign as supporting canon. Adding them as canon (not just commit messages) makes the lessons searchable and audit-checkable. Refs: - klappy/oddkit#140 (PR-2.1 — where all three bugs surfaced) - klappy/oddkit#143 (PR-2.3a — sibling, audit implementation) - klappy://docs/planning/link-rot-elimination-campaign - klappy://canon/constraints/release-validation-gate (the gate that caught two of three bugs) - klappy://canon/principles/vodka-architecture (the principle the superseded_by-normalization constraint operationalizes)
klappy
pushed a commit
to klappy/oddkit
that referenced
this pull request
Apr 26, 2026
…surface CF Preview test 14j (default-scope audit) timed out at 120s on the prior default of [writings/, canon/, odd/, docs/]. Cold-cache fetching ~560 files through the worker's zip-extract path exceeded the curl budget. v1 default scope is writings/ only. Reasons it's honest, not a hack: - PR-2.2's actual cleanup was writings-only; the campaign motivation was reader complaints about broken links in published essays. - April-9 reference-integrity audit classified the 49 unfixed refs as intentional (template placeholders, site routes, historical archive, .cursor/plans) — none in writings/. - writings/ is where authors write klappy:// URIs as body links most often; canon/odd/docs use frontmatter cross-refs which the resolver governs separately. canon/, odd/, docs/ become explicit opt-in via scope.paths. Reversal is one line if a real consumer demonstrates wider need (or if parallelized fetching graduates from the deferred-concerns ledger). Spec amendment to klappy://docs/oddkit/specs/oddkit-audit (v2.1) lands in the sibling canon PR (klappy/klappy.dev#146) so the spec self- documents the deviation rather than the code silently diverging. Refs: - klappy://docs/oddkit/specs/oddkit-audit (DRAFT v2.1 — to be amended) - klappy://docs/planning/link-rot-deferred-concerns (parallelized fetching is a candidate for the deferred ledger)
…ped behavior Spec previously said default scope was full repo excluding docs/archive/. Real shipped behavior in oddkit v0.26.0 is writings/ only — cold-cache fetching ~560 files exceeded the 120s CF Preview curl budget. Smaller default is honest, not a hack: - PR-2.2 cleanup was writings-only - April-9 audit classified non-writings broken refs as intentional - writings/ is where klappy:// URIs appear as body links most often Other paths become explicit opt-in via scope.paths. Reversal is one-line if a real consumer demonstrates wider need. Bundles cleanly into PR-2.3b since both the canon constraints and this spec amendment are in the same canon repo. Per Vodka: spec follows shipped reality, not the other way around.
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.
Summary
Phase 2 PR-2.3b of the link-rot-elimination campaign. Encodes three bug-class lessons from PR-2.1 (the resolver implementation) as tier-2 canon constraints. Sibling to klappy/oddkit#143 (the audit implementation).
Per operator decision in PR-2.2: bundle the three bug-class lessons from the resolver PR's review and validation into PR-2.3 as supporting canon. Making them canon (not just commit messages) makes them searchable and audit-checkable.
Three new tier-2 constraints (additive)
1.
canon/constraints/oddkit-action-registration-completeness.mdNew oddkit MCP actions must register in both the dispatch switch and the
VALID_ACTIONSarray. Caught by Cursor Bugbot on PR-2.1's initial commit; without the catch, the resolver would have shippedUnknown action: resolveerrors to every caller because the validator rejects names not in the allowlist before dispatch is reached. Process control until a typecheck-time invariant exists (a possible future fix is captured in the constraint's "Future Work" section).2.
canon/constraints/superseded-by-shape-normalization.mdTools that read
superseded_bymust normalize across three shapes:klappy://canon/x/y.md:canon/x/y.mdcanon/x/yCycle detection keys on canonical URI, not raw
superseded_bystring. Caught by the independent Sonnet 4.6 validator dispatched per release-validation-gate on PR-2.1 —klappy://docs/oddkit/proactive/dolche-vocabularyhadsuperseded_by: "canon/definitions/dolcheo-vocabulary.md"and the resolver's strict-URI lookup couldn't follow it. The Vodka rule applies: the resolver normalizes; consumers and authors don't.3.
canon/constraints/bash-test-rig-assignment-chain-discipline.mdBash silently concatenates two assignments when the newline between them is dropped:
Parses, runs, produces wrong values. After any closing
)of a command substitution, the next character must be a newline.bash -ndoes NOT catch this. Caught by post-merge CI on PR-2.1's third commit; one-character fix, non-trivial diagnostic time. Authoring discipline + visual blank lines as preventive; CI lint as durable fix when ritual breaks down.Why land these as canon and not just commit messages
Commit messages don't show up in
oddkit_search. Canon constraints do. The next time someone (or some agent) is about to add an oddkit action, mishandle asuperseded_bychain, or paste a bash test block, they can find the constraint by searching for the symptom — not just by remembering "didn't this come up in some PR a few months ago?"What this PR does NOT do
Refs
klappy://docs/planning/link-rot-elimination-campaignklappy://canon/constraints/release-validation-gate(the gate that caught two of three bugs)klappy://canon/principles/vodka-architecture(the principle thesuperseded_by-normalization constraint operationalizes)Note
Low Risk
Documentation-only changes: adds new canon constraint pages and a small spec clarification, with no runtime code impact.
Overview
Adds three new tier-2 canon constraints capturing recurring failure modes from the resolver work: (1) bash test rigs must not accidentally join
$(...)assignments across missing newlines, (2) new oddkit actions must be registered in both the dispatch switch andVALID_ACTIONS, and (3)superseded_byconsumers must normalize three allowed value shapes and key cycle detection on canonical URIs.Updates the
oddkit_auditspec to clarify/narrow the defaultscope.pathsto[writings/](with rationale tied to CI timeout/cold-cache behavior).Reviewed by Cursor Bugbot for commit 7d6e61e. Bugbot is set up for automated code reviews on this repo. Configure here.