Skip to content

canon(constraints): bug-class lessons from resolver PR (Phase 2 PR-2.3b)#146

Merged
klappy merged 2 commits intomainfrom
canon/bug-class-lessons-from-resolver-pr
Apr 27, 2026
Merged

canon(constraints): bug-class lessons from resolver PR (Phase 2 PR-2.3b)#146
klappy merged 2 commits intomainfrom
canon/bug-class-lessons-from-resolver-pr

Conversation

@klappy
Copy link
Copy Markdown
Owner

@klappy klappy commented Apr 26, 2026

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.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's initial commit; without the catch, the resolver would have shipped Unknown action: resolve errors 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.md

Tools that read superseded_by must normalize across three shapes:

  • Full canonical URI: klappy://canon/x/y
  • Path with .md: canon/x/y.md
  • Path without: canon/x/y

Cycle detection keys on canonical URI, not raw superseded_by string. Caught by the independent Sonnet 4.6 validator dispatched per release-validation-gate on PR-2.1 — klappy://docs/oddkit/proactive/dolche-vocabulary had superseded_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.md

Bash silently concatenates two assignments when the newline between them is dropped:

RAW=$(curl -X POST -d '...')RESULT=$(extract_json "$RAW")  # the bug

Parses, runs, produces wrong values. After any closing ) of a command substitution, the next character must be a newline. bash -n does 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 a superseded_by chain, 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


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 and VALID_ACTIONS, and (3) superseded_by consumers must normalize three allowed value shapes and key cycle detection on canonical URIs.

Updates the oddkit_audit spec to clarify/narrow the default scope.paths to [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.

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.
@klappy klappy merged commit dbc690c into main Apr 27, 2026
1 check passed
@klappy klappy deleted the canon/bug-class-lessons-from-resolver-pr branch April 27, 2026 00:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant