canon: release-validation-gate + contract-governs-handoff-drift + bootstrap hook (P1.3.3 root-cause fix)#126
Merged
klappy merged 3 commits intoApr 20, 2026
Conversation
…off-drift + bootstrap hook
Root-cause fix for the P1.3.3 process failure that shipped 0.21.0 with
two Bugbot-detectable bugs and zero independent validation. The fix is
not 'remember to wait for Bugbot next time' (orchestrators don't
persist memory) but mechanical canon that future sessions cannot avoid
encountering.
Three artifacts:
1. NEW canon/constraints/release-validation-gate.md (tier 1, 168 lines)
Three binding rules for any oddkit ship:
- Rule 1: no merge with active reviews still in_progress
- Rule 2: no promotion without independent fresh-context validation
when the PR touches load-bearing surface (orchestrate.ts, matchers,
governance reads, response envelope, action behavior)
- Rule 3: canon outranks any session-scoped recommendation that says
otherwise. Same-session smoke + live self-call do NOT satisfy R2.
Names the failure modes explicitly (Stall-as-skip, Findings-as-noise,
Option-A-justification) so future sessions can't re-litigate them as
judgment calls. Captures O-open P11: oddkit_gate enforces this at
completion transitions (mechanical enforcement, not just orchestrator
obligation).
2. NEW canon/principles/contract-governs-handoff-drift.md (tier 2, 128 lines)
Graduates a candidate principle on its third deciding-argument
recurrence: P1.3.1 implicit, P1.3.2 explicit (three load-bearing
applications), P1.3.3 explicit-via-failure (the principle's absence
shipped two prod bugs). Names the conflict resolution sequence:
identify, default-to-canon, surface deviation, propose amendment if
the session's judgment was right. Names the three pressure points
where it bites hardest: time pressure, authority pressure (respect
for prior orchestrator's ledger), scope-justified shortcuts.
3. AMEND canon/bootstrap/model-operating-contract.md (+4 lines, +1 section)
New 'Before Shipping Code' section in Tool Rhythm. Discoverability
hook: future sessions read the bootstrap on first turn and now
encounter explicit references to release-validation-gate and
contract-governs-handoff-drift before any ship work. The bootstrap
loads on session start; canon search loads when claimed. Putting the
pointer in bootstrap means the rule reaches the orchestrator before
the orchestrator decides whether to search.
Provenance:
- P1.3.3 process failure: orchestrator treated Cursor Bugbot in_progress
as non-blocking, merged PRs #120 and #121 before Bugbot completed
review. Bugbot subsequently posted findings (one medium-severity prod
regression, one low-severity DRY violation). The medium one broke the
strictly-additive invariant the PR description claimed.
- 0.21.1 fix-forward branch is open at klappy/oddkit#fix/0.21.1-bugbot-stopword-and-dry
applying both Bugbot fixes. That PR will be the FIRST application of
this canon (independent validator dispatch is mandatory before its
promotion merges).
- Full session post-mortem to land in P1.3.3 closeout ledger after the
0.21.1 sequence completes under the new canon rules.
This canon binds the orchestrator that wrote it. The 0.21.1 promotion
PR cannot ship until a Sonnet 4.6 read-only validator is dispatched
and its findings are folded into the closeout ledger. That is the test
of whether the canon works: it works if it stops the same author from
making the same mistake twice.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Bootstrap hook restates full constraint, creating DRY drift risk
- Replaced the dense paragraph that restated all three rules, load-bearing surface list, and handoff-drift procedure with a one-line pointer matching the convention of every other Tool Rhythm subsection, so rules live only in the constraint doc.
Preview (95fc3c4835)
diff --git a/canon/bootstrap/model-operating-contract.md b/canon/bootstrap/model-operating-contract.md
--- a/canon/bootstrap/model-operating-contract.md
+++ b/canon/bootstrap/model-operating-contract.md
@@ -84,6 +84,10 @@
- **`oddkit_challenge`** — Pressure-test claims, assumptions, and proposals during exploration and planning — not during execution, where challenge's prompts are not questions to hand back to the operator.
- **`oddkit_validate`** — Before declaring any task complete. NEEDS_ARTIFACTS means produce the artifacts, not ask the operator whether they are required.
+### Before Shipping Code
+
+- **`klappy://canon/constraints/release-validation-gate`** — Binding on every PR merge and prod promotion in `klappy/oddkit` and any oddkit-pattern MCP server. Fetch and obey before the ship; rules, triggers, and dispositions live in the constraint, not here.
+
### For Durable Records
- **`oddkit_encode`** — Structure decisions, insights, and boundaries as OLDC+H artifacts. Does not persist — the caller saves to file. Encode continuously at natural breakpoints.
diff --git a/canon/constraints/release-validation-gate.md b/canon/constraints/release-validation-gate.md
new file mode 100644
--- /dev/null
+++ b/canon/constraints/release-validation-gate.md
@@ -1,0 +1,168 @@
+---
+uri: klappy://canon/constraints/release-validation-gate
+title: "Release Validation Gate — Mechanical Rules That Bind Every Ship"
+audience: canon
+exposure: nav
+tier: 1
+voice: neutral
+stability: stable
+tags: ["canon", "constraint", "release", "validation", "bugbot", "sonnet-validator", "promotion-gate", "fresh-context", "convention-requires-an-enforcer", "ship-safety"]
+epoch: E0008.3
+date: 2026-04-20
+derives_from: "canon/principles/verification-requires-fresh-context.md, canon/principles/vodka-architecture.md, docs/appendices/convention-requires-an-enforcer.md, canon/values/axioms.md, odd/ledger/2026-04-20-p1-3-3-challenge-canon-parity-landed.md"
+complements: "canon/constraints/oddkit-prompt-pattern.md, canon/principles/dry-canon-says-it-once.md"
+governs: "Every PR merged to oddkit main and every promotion to oddkit prod. Also applies to klappy.dev canon PRs that change governance documents the worker reads at runtime. Binding on every orchestrator and every Managed Agent that ships code in this program."
+status: active
+---
+
+# Release Validation Gate — Mechanical Rules That Bind Every Ship
+
+> Convention is optional; convention plus an enforcer is binding. Smoke tests catch mechanical bugs; independent review catches PRD-vs-PR intent drift; canon-vs-ledger conflict resolution is settled by canon. None of these are recommendations — they are the gate. An orchestrator that decides "this scope is small enough to skip validation" has substituted personal judgment for canon, which is exactly the failure mode this constraint exists to prevent. The Cursor Bugbot stall pattern, the "Option A is fine for this one" justification, and the in-session self-validation are all named here so future sessions cannot re-litigate them as judgment calls. They are not judgment calls. They are the gate.
+
+---
+
+## Summary — What Must Be True Before Any Ship
+
+Three rules govern the release pipeline for oddkit and any oddkit-pattern MCP server in this program. They are non-negotiable and apply regardless of scope, urgency, or operator pressure.
+
+**First, no PR merges to main with active reviews still in progress.** Cursor Bugbot — and any successor automated reviewer — is a real reviewer, not a CI ornament. An `in_progress` Bugbot status is an active review the orchestrator must wait for. A `completed/neutral` Bugbot status with findings is a review that must be read, dispositioned, and either fixed forward in the same PR or explicitly waived in the PR body with reasoning. Treating an in-progress reviewer as non-blocking because it stalls is an inversion of what review is for. Reviewers are slow precisely because they are reading.
+
+**Second, no main → prod promotion ships without independent fresh-context validation when the PR touches load-bearing surface.** "Load-bearing surface" includes anything in `workers/src/orchestrate.ts`, anything that adds or removes governance file reads, anything that changes the worker's response envelope, and anything that changes the matcher algorithm. For these PRs, the orchestrator must dispatch an independent validator session — Sonnet 4.6 read-only via Managed Agents, or any equivalent fresh-context reviewer — and fold the validator's findings into the closeout ledger before merging the promotion PR. Same-session self-validation does not satisfy this. Smoke runs do not satisfy this. The validator's job is to find what the orchestrator missed, which structurally requires a different agent in a different context window.
+
+**Third, when a session ledger or handoff recommends skipping a step in this constraint, canon wins.** This is the contract-governs-handoff-drift principle in operation: a recommendation in a session-scoped artifact does not override a tier-1 canon constraint. Specifically, the P1.3.2 ledger's recommendation that "Option A is fine for P1.3.3" was a session-scoped judgment that turned out wrong precisely because Bugbot caught two real bugs P1.3.3 shipped to prod. Future ledgers may make similar recommendations; canon outranks them.
+
+The enforcement is partly mechanical (the gate checks below) and partly orchestrator-bound (search canon before claiming, per the bootstrap). When the mechanical enforcement is incomplete, the orchestrator's obligation is heavier, not lighter.
+
+---
+
+## Rule 1 — No Merge With Active Reviews
+
+For any PR to `klappy/oddkit` main:
+
+**Wait conditions (mechanical):**
+- Every check-run reported by the GitHub Checks API for the head SHA must be in `status: completed`. No exceptions for "Bugbot is non-blocking historically" or "this scope is small."
+- For each completed check, the conclusion must be one of: `success`, `neutral`, or `skipped`. `failure`, `cancelled`, `timed_out`, `action_required`, or `stale` block.
+- Cursor Bugbot specifically: if conclusion is `neutral` AND review comments exist, every comment must be dispositioned (fix-forward in this PR, waive in PR body with rationale, or open a tracking issue). If conclusion is `neutral` with NO findings, the PR proceeds.
+- `Cursor Bugbot Autofix` may remain `in_progress` indefinitely without blocking, BUT only after the parent `Cursor Bugbot` check has reached `completed`. Autofix runs after the review; the review is the gate, not the autofix.
+
+**The two failure modes this prevents:**
+- *Stall-as-skip:* The orchestrator polls, sees Bugbot stuck in `in_progress` past some informal threshold, and decides Bugbot is non-blocking. This is wrong every time. If Bugbot is genuinely broken, the disposition is to wait longer or to surface the broken-tooling problem, not to ship past it.
+- *Findings-as-noise:* Bugbot finds things and the orchestrator dismisses them as "low severity" or "out of scope" without recording the disposition. Every finding gets a disposition; no finding is ignored.
+
+**Verification the orchestrator must perform before clicking merge:**
+1. Pull the GitHub `/check-runs` endpoint for the PR head SHA. Confirm every entry has `status: completed`.
+2. Pull the PR `/reviews` endpoint. For any `cursor[bot]` review with body text, read the body. For any review-comment thread on a file/line, read the thread.
+3. For every Bugbot finding, record disposition (fix-forward / waive-with-reason / tracking-issue) in the PR body or the closeout ledger.
+4. Only then merge.
+
+---
+
+## Rule 2 — No Promotion Without Independent Validation
+
+For any `main → prod` promotion PR on `klappy/oddkit` (and any oddkit-pattern MCP server in this program):
+
+**Trigger condition:** the merged PR(s) being promoted include any change to load-bearing surface:
+- `workers/src/orchestrate.ts`
+- `workers/src/bm25.ts` or any other matcher/parser module
+- Addition or removal of a governance file fetch (anything calling `fetcher.getFile` or `fetcher.getIndex`)
+- Response envelope changes (any new or removed `result.*` field)
+- New or modified `oddkit_*` action behavior
+
+If any of those apply, **independent validator dispatch is mandatory before the promotion PR merges.**
+
+**The validator must:**
+- Run in a fresh session, not the orchestrator's session. Per `klappy://canon/principles/verification-requires-fresh-context`: same-session self-validation produces structural blindness because creation context bridges flaws.
+- Use the Managed Agents API per `/mnt/skills/user/managed-agents/SKILL.md`, or an equivalent fresh-context reviewer.
+- Be model-family-permissive: Sonnet 4.6 is the default per the skill ("literal, flag-happy, catches more"); Opus is acceptable; the constraint is fresh context, not a specific model.
+- Be read-only: validator does not push code, open PRs, or modify any repo. Findings are reported back; the orchestrator decides disposition.
+- Perform the 5-corroboration pattern from P1.3.1: PRD-vs-shipped diff drift, bytes-on-main verification, live preview curl of new shapes, canon retrievability and content, independent smoke against the preview URL × 3 consecutive runs.
+
+**The orchestrator must:**
+- Dispatch the validator after the feat PR is open and the preview deploy is live, but before merging the promotion PR.
+- Wait for the validator session to reach `idle` status. Do not proceed past timeout without explicit acknowledgment of timeout in the closeout ledger.
+- Read every validator finding. Disposition each one in the closeout ledger before promotion merges.
+- If the validator surfaces a `FAIL` or `PARTIAL` verdict on any corroboration, treat that as a blocker until either the issue is fixed-forward or the disposition is recorded with explicit reasoning.
+
+**Same-session self-validation does not satisfy this.** Specifically, the following do NOT count as independent validation:
+- The orchestrator running smoke tests three times in their own session.
+- The orchestrator calling the production tool from their own MCP connection (a "live self-call").
+- The orchestrator writing a closeout ledger that asserts the work is correct.
+
+These are useful artifacts. They are not validation. Validation requires the context break.
+
+---
+
+## Rule 3 — Canon Wins Over Session Artifacts
+
+When a session ledger, handoff, or PRD recommends skipping or downgrading a rule in this constraint, **canon wins**. The session artifact is a record of one session's judgment; canon is the program's binding contract.
+
+This rule is the inverse of the trap that produced this constraint's existence. The P1.3.2 ledger recommended Option A (smoke-heavy, no validator dispatch) for P1.3.3 with the explicit caveat: *"escalate to Option B if any smoke failure surfaces something that requires judgment-call resolution."* The P1.3.3 orchestrator read both halves and picked the convenient half. Canon now binds the choice: when this constraint says "validator dispatch is mandatory," that mandate cannot be downgraded by a session-scoped recommendation, even one written by a thoughtful prior session ledger.
+
+**Specifically:**
+- A handoff or ledger MAY recommend a particular validator pattern (Sonnet 4.6 vs Opus, 5-corroboration vs lighter touch). It MAY NOT recommend skipping validator dispatch entirely.
+- A handoff or ledger MAY recommend deferring a Bugbot finding to a follow-up PR. It MAY NOT recommend treating Bugbot as non-blocking.
+- A handoff or ledger MAY argue that a particular release is exempt from this constraint. The orchestrator MUST NOT act on that argument without writing canon that names the exemption first. If the exemption is real, the canon needs amending; if it isn't, the recommendation is wrong.
+
+This is the contract-governs-handoff-drift principle (`klappy://canon/principles/contract-governs-handoff-drift`) applied to release safety specifically.
+
+---
+
+## What Counts As "Load-Bearing Surface"
+
+The Rule 2 trigger is "any change to load-bearing surface." Concretely:
+
+**Always load-bearing:**
+- `workers/src/orchestrate.ts` (any change)
+- `workers/src/bm25.ts` (any change to tokenize/stem/buildBM25Index/searchBM25)
+- New or removed `mcp_server.tool(...)` registrations
+- New or removed governance file reads (`fetcher.getFile` calls)
+- Response envelope changes (`result.*` field added/removed/renamed)
+- New or removed `oddkit_*` action
+
+**Not load-bearing (Rule 2 not triggered):**
+- CHANGELOG-only PRs
+- Documentation-only PRs (`docs/`, `README`, etc.)
+- CI workflow PRs that don't change worker behavior
+- Dependency bumps that don't change behavior (lockfile-only)
+- Test-only PRs that don't change `workers/src/`
+
+**Ambiguous cases default to load-bearing.** When in doubt, dispatch the validator. The 3–5 minutes of agent-time is cheap insurance.
+
+---
+
+## Mechanical Enforcement Roadmap
+
+This constraint is currently enforced by orchestrator obligation (search canon before claiming, follow what's found). Mechanical enforcement via the `oddkit_gate` tool's `execution → completion` transition is the obvious next code beat:
+
+When the gate detects a `completion` transition with input mentioning `merge`, `promote`, `ship to prod`, or similar, the gate should:
+1. Pull the GitHub Checks API for the PR being merged (orchestrator passes the PR URL or number in `context`).
+2. Verify all check-runs reach `completed` with acceptable conclusions.
+3. Verify Bugbot findings are dispositioned.
+4. For load-bearing PRs, verify a validator session ID is referenced in `context`.
+5. Return `NOT_READY` with specific unmet prerequisites if any rule is violated.
+
+Captured as carry-forward O-open: **P11 — `oddkit_gate` enforces release-validation-gate at completion transitions.** Until P11 ships, the orchestrator's manual obligation is heavier.
+
+---
+
+## How This Constraint Got Written
+
+This constraint exists because P1.3.3 (oddkit 0.21.0, shipped 2026-04-20) violated all three rules:
+- **Rule 1:** orchestrator treated `Cursor Bugbot` `in_progress` as non-blocking, merged PR #120 and PR #121 before Bugbot completed its review. Bugbot subsequently posted two findings (one medium-severity, one low-severity); the medium one was a real prod regression breaking the strictly-additive invariant the PR description claimed.
+- **Rule 2:** orchestrator skipped Sonnet 4.6 validator dispatch despite the P1.3.2 ledger's explicit warning that "smoke-only should not become the default." Same-session smoke + a live self-call were treated as substitutes for fresh-context validation. They weren't.
+- **Rule 3:** orchestrator followed the P1.3.2 ledger's session-scoped recommendation ("Option A is fine for P1.3.3") over the bootstrap contract's tier-1 rule ("validate before declaring done, with context break"). Canon should have won; it didn't, because it didn't exist yet.
+
+The fix-forward was 0.21.1 (klappy/oddkit#122 or similar — the PR opened to apply Bugbot's autofix and graduate the missing canon). This constraint is the canon that makes the next session's correct path mechanical, not optional.
+
+The full session post-mortem is at `klappy://odd/ledger/2026-04-20-p1-3-3-challenge-canon-parity-landed`.
+
+---
+
+## Related Canon
+
+- **[Verification Requires Fresh Context](klappy://canon/principles/verification-requires-fresh-context)** — the principle this constraint operationalizes for the release pipeline specifically.
+- **[Contract Governs Handoff Drift](klappy://canon/principles/contract-governs-handoff-drift)** — the principle Rule 3 is a direct application of.
+- **[Vodka Architecture](klappy://canon/principles/vodka-architecture)** — server stays thin; canon enforces. This constraint is the canon side of an enforcement that the server does not yet implement.
+- **[Convention Requires an Enforcer](klappy://docs/appendices/convention-requires-an-enforcer)** — why a recommendation in a ledger is insufficient; conventions become real only when something mechanical refuses to proceed without them.
+- **[Cache Fetches and Parses](klappy://canon/principles/cache-fetches-and-parses)** — the principle that DID graduate cleanly in P1.3.3; demonstrates the pattern this constraint codifies as a release-pipeline rule.
+- **[Managed Agents Skill](file:///mnt/skills/user/managed-agents/SKILL.md)** — the operational mechanism for satisfying Rule 2's validator dispatch.
diff --git a/canon/principles/contract-governs-handoff-drift.md b/canon/principles/contract-governs-handoff-drift.md
new file mode 100644
--- /dev/null
+++ b/canon/principles/contract-governs-handoff-drift.md
@@ -1,0 +1,128 @@
+---
+uri: klappy://canon/principles/contract-governs-handoff-drift
+title: "Contract Governs Handoff Drift — Canon Wins When Session Artifacts Disagree"
+audience: canon
+exposure: nav
+tier: 2
+voice: neutral
+stability: semi_stable
+tags: ["canon", "principle", "governance", "drift", "handoff", "ledger", "canon-authority", "source-of-truth", "contract", "vodka-architecture"]
+epoch: E0008.3
+date: 2026-04-20
+derives_from: "canon/principles/vodka-architecture.md, canon/principles/dry-canon-says-it-once.md, canon/principles/prompt-over-code.md, canon/values/axioms.md, odd/ledger/2026-04-20-p1-3-1-challenge-canary-landed.md, odd/ledger/2026-04-20-p1-3-2-gate-canary-landed.md, odd/ledger/2026-04-20-p1-3-3-challenge-canon-parity-landed.md"
+complements: "canon/constraints/release-validation-gate.md, canon/constraints/oddkit-prompt-pattern.md, canon/bootstrap/model-operating-contract.md"
+governs: "All conflicts between session-scoped artifacts (handoffs, ledgers, PRDs, scratch notes) and tier-1 / tier-2 canon. Binding on every orchestrator: when a session artifact recommends a path that contradicts canon, canon wins, full stop."
+status: active
+---
+
+# Contract Governs Handoff Drift — Canon Wins When Session Artifacts Disagree
+
+> Session ledgers and handoffs are records of one session's judgment under one session's pressures. Canon is the program's binding contract, refined across many sessions and many incidents. When the two disagree, canon wins. The orchestrator that follows a session-scoped recommendation over a canon-level rule has substituted personal judgment for the program's accumulated wisdom — which is exactly what canon exists to prevent. The graduation test for this principle was satisfied painfully: P1.3.1 named it implicitly, P1.3.2 named it explicitly with three documented recurrences, and P1.3.3 demonstrated its absence costs by shipping two prod bugs precisely because a session ledger's recommendation was followed over the bootstrap contract's tier-1 rule.
+
+---
+
+## Summary — Canon Outranks Every Session Artifact Without Exception
+
+Session artifacts — handoffs, ledgers, PRDs, scratch notes, post-mortems — are valuable. They capture context, surface lessons, and let one session hand state to the next. They are not, however, sources of binding governance. When a session artifact recommends a path that contradicts canon, the orchestrator follows canon and surfaces the contradiction so canon can be amended if the session's judgment was actually right.
+
+This principle inverts a common temptation: when a session ledger written by a thoughtful prior orchestrator recommends a particular path ("Option A is fine for this scope," "Bugbot is non-blocking," "this release is small enough to skip the validator"), the next session feels social pressure to honor that recommendation. The pressure is misplaced. The prior session's recommendation is one data point under that session's pressures; canon is the contract refined across many. Canon wins.
+
+The principle is not anti-ledger. Ledgers are essential — they are how learnings propagate, how rationale persists, how patterns become visible. The principle is about what a ledger CAN and CANNOT do. A ledger can describe, recommend, observe, and propose. A ledger cannot override canon. If the ledger's recommendation is genuinely better than canon's rule, the disposition is to amend canon, not to ignore canon. Canon amendment is a bounded, reviewable act; canon ignorance is unbounded and corrosive.
+
+---
+
+## What Canon Means In This Program
+
+For purposes of this principle, "canon" is the set of documents in `klappy/klappy.dev` under:
+- `canon/values/` (tier-1 axiomatic)
+- `canon/principles/` (tier-1 or tier-2 principles)
+- `canon/constraints/` (tier-1 binding rules)
+- `canon/bootstrap/` (the operating contract fetched on session start)
+
+These documents have governance authority. They are versioned, peer-reviewed (or self-reviewed under the writing-canon gate), and stable across sessions. Changes go through canon PRs.
+
+"Session artifacts" are everything else that captures session work:
+- `odd/handoffs/` (forward-looking transitions)
+- `odd/ledger/` (retrospective records)
+- Working-directory PRDs (ephemeral planning artifacts)
+- Inline reasoning in chat (transient)
+- Sonnet validator reports (single-session findings)
+
+These artifacts have observational authority — they describe what happened and recommend what to do next. They do not have governance authority.
+
+The line between the two is enforced by location, frontmatter (tier field), and ownership. A document under `odd/ledger/` cannot grant itself canon-level authority by claiming so in its body. A document under `canon/constraints/` carries that authority by virtue of its location and tier-1 frontmatter.
+
+---
+
+## The Three Recurrences That Earned Graduation
+
+The graduation test for a candidate canon principle is three deciding-argument recurrences — three decisions where the principle is the load-bearing reason, not three appearances of the pattern.
+
+**Recurrence 1 — P1.3.1 (oddkit 0.19.0, implicit).** The P1.3.1 closeout ledger named the principle in passing as part of the carry-list: "ground framing in code + canon, not the latest handoff." The decision the principle drove was minor (whether to trust a handoff's claim about what shipped or to grep main directly), but the framing was correct: source-of-truth hierarchy puts code/canon above handoff narrative. Implicit recurrence; the principle was named but not yet load-bearing for a major decision.
+
+**Recurrence 2 — P1.3.2 (oddkit 0.20.0, explicit).** The P1.3.2 closeout ledger documented three explicit appeals to the principle in a single session: (a) trusting parser-test bytes over handoff function-name claims when the handoff said `discover*` and the code said `fetch*`; (b) trusting current code state over handoff line-number claims when the line numbers had drifted; (c) using the principle as the standing rule that grounded the orchestrator's decision to git-pull main first before reading any handoff content. First explicit recurrence; principle was load-bearing for three decisions across one session.
+
+**Recurrence 3 — P1.3.3 (oddkit 0.21.0, explicit-via-failure).** The P1.3.3 orchestrator violated the principle and shipped two prod bugs. The P1.3.2 closeout ledger had recommended Option A (smoke-heavy attestation, no Sonnet 4.6 validator dispatch) for P1.3.3 with the explicit caveat that smoke-only should not become the default. The bootstrap contract said validate before declaring done, with context break. The session-scoped recommendation contradicted the canon-level rule. The orchestrator picked the recommendation and shipped 0.21.0 to prod. Cursor Bugbot subsequently caught two real findings (one medium-severity prod regression breaking the strictly-additive invariant) that the orchestrator's same-session smoke and self-call had not surfaced. The structural fix was 0.21.1 plus the writing of this principle and its companion `klappy://canon/constraints/release-validation-gate`. Recurrence three is satisfied by demonstrating that the principle's absence costs.
+
+The third recurrence was painful but it was the right kind of painful — the kind that produces durable canon rather than ephemeral lesson. P1.3.3's process failure is documented in full at `klappy://odd/ledger/2026-04-20-p1-3-3-challenge-canon-parity-landed`.
+
+---
+
+## How Conflicts Get Resolved
+
+When the orchestrator notices a conflict between a session artifact and canon — or between a recommendation and a tier-1 rule — the resolution sequence is:
+
+**Step 1 — Identify the conflict explicitly.** Name what the session artifact recommends. Name what canon binds. Quote both. Do not let the conflict stay implicit; implicit conflicts get resolved by convenience.
+
+**Step 2 — Default to canon.** Until canon is amended, canon binds. The orchestrator follows canon and proceeds. The session artifact's recommendation is recorded as a deviation, not acted on.
+
+**Step 3 — Surface the deviation.** In the closeout ledger or in a follow-up handoff, name the conflict explicitly: *"The P1.3.2 ledger recommended X; canon binds Y; this session followed canon Y and surfaces the conflict for canon-level review."* This produces an audit trail and gives the next session a clear handoff.
+
+**Step 4 — Propose canon amendment if the session's judgment was right.** If the session-scoped recommendation was genuinely better than canon's rule, propose a canon amendment in a separate PR. The amendment is a bounded, reviewable act. Canon authority lives where canon authority can be reviewed; sneaking changes via session ledgers bypasses review.
+
+**Step 5 — If the amendment is rejected, the canon's rule was right.** The orchestrator's correct path was to follow canon, even when the prior session's recommendation said otherwise. The deviation record from step 3 becomes the evidence that canon held.
+
+This is not rigid. It is structured. Rigidity would be "never deviate from canon under any circumstances." Structure is "deviate only via amendment, not via ignorance." The difference is auditability.
+
+---
+
+## What This Principle Does NOT Say
+
+**It does not say session artifacts are unimportant.** Ledgers, handoffs, PRDs, and validator reports are essential to how this program works. They capture context that canon cannot. They surface lessons in the moment. They let sessions hand state to each other. The principle is not anti-ledger; it is anti-ledger-as-binding-governance.
+
+**It does not say canon is always right.** Canon evolves. Canon has bugs. Canon can be wrong. The principle says: when canon is wrong, the disposition is to amend canon, not to ignore canon. Amendment is reviewable; ignorance is invisible.
+
+**It does not say recommendations are bad.** A session ledger that says "Option A is fine for next time" is a useful data point and may genuinely save the next session time. The principle says: the next session's orchestrator must verify the recommendation against canon before acting on it. If canon binds the orchestrator to Option B, Option B is what ships, even when Option A would have been faster.
+
+**It does not say the orchestrator must love this rule.** Following canon when a thoughtful prior session's ledger says otherwise feels like ignoring expertise. It is not. It is preserving the audit trail that lets canon evolve under review rather than via drift.
+
+---
+
+## Where This Principle Bites Hardest
+
+This principle is uncomfortable in three predictable situations:
+
+**Time pressure.** A session under wall-clock pressure feels the pull to follow whichever path is faster. When the faster path is the session-scoped recommendation and the canon-bound path is slower (e.g., dispatching a Sonnet validator that takes 5 minutes vs. running a smoke that takes 30 seconds), the principle binds against time. The orchestrator's preferences do not get to override canon because canon is inconvenient.
+
+**Authority pressure.** A handoff or ledger written by a thoughtful prior orchestrator carries social weight. Following its recommendation feels respectful; deviating feels like second-guessing. The principle says: respect for the prior orchestrator looks like protecting the audit trail, not like rubber-stamping their judgment. If their judgment was canon-worthy, it goes in canon. If it didn't make it into canon, it's a recommendation, and recommendations don't bind.
+
+**Scope-justified shortcuts.** "This release is small enough to skip the validator." "This change is too minor to bother Bugbot." "We've shipped 12 of these patterns; the 13th doesn't need re-review." The principle binds against scope-justified shortcuts. Canon does not have a scope clause unless canon explicitly writes one. If canon binds for "any PR to oddkit main," the rule binds for the smallest PR as much as for the largest. Scope-based exceptions go through canon amendment, not through orchestrator judgment.
+
+---
+
+## Related Canon
+
+- **[Vodka Architecture](klappy://canon/principles/vodka-architecture)** — the server stays thin; canon enforces. This principle protects the canon-as-source-of-truth invariant by refusing to let session artifacts compete for governance authority.
+- **[DRY — The Canon Says It Once](klappy://canon/principles/dry-canon-says-it-once)** — if governance lives in two places (canon AND a session ledger), the two can drift. The principle here is the disposition rule when they have already drifted: canon wins; ledger gets reconciled or amended.
+- **[Prompt Over Code](klappy://canon/principles/prompt-over-code)** — governance is fetched at runtime, never baked in. This principle extends the rule: governance is fetched from canon, not from session artifacts that happen to be in the orchestrator's context window.
+- **[Release Validation Gate](klappy://canon/constraints/release-validation-gate)** — the constraint Rule 3 of which is a direct application of this principle to the release pipeline. The two were written together because P1.3.3's process failure required both to prevent recurrence.
+- **[Verification Requires Fresh Context](klappy://canon/principles/verification-requires-fresh-context)** — same author, same session, accumulated context degrades judgment. This principle is the meta-rule for when session-context-degraded judgment (recorded in a ledger) tries to override fresher canon.
+- **[Model Operating Contract — Bootstrap](klappy://canon/bootstrap/model-operating-contract)** — fetched on every session's first turn. The bootstrap names "search canon before claiming" as non-negotiable; this principle is what to do when canon search returns a result that contradicts a session artifact already in context.
+
+---
+
+## See Also — The Three Deciding-Argument Recurrences
+
+- `klappy://odd/ledger/2026-04-20-p1-3-1-challenge-canary-landed` — implicit recurrence; principle named in carry-list.
+- `klappy://odd/ledger/2026-04-20-p1-3-2-gate-canary-landed` — first explicit recurrence; three load-bearing applications in one session.
+- `klappy://odd/ledger/2026-04-20-p1-3-3-challenge-canon-parity-landed` — third recurrence via demonstrated cost: the principle's absence let a session ledger's recommendation override canon, shipping two prod bugs that an independent validator would have caught.You can send follow-ups to the cloud agent here.
Reviewed by Cursor Bugbot for commit 4d2bc88. Configure here.
… PR #126) Bugbot caught a within-canon DRY violation in the bootstrap hook: the 'Before Shipping Code' section restated all three rules from release-validation-gate.md plus the principle from contract-governs-handoff-drift.md in a single dense paragraph. Every other subsection in Tool Rhythm uses one-line description + pointer. Bugbot's finding is correct per dry-canon-says-it-once.md: 'Two documents saying the same thing in different words... creates the same drift risk, just between documents.' Fix: collapse to two one-line bullets matching the convention. Full rules live in release-validation-gate.md; the bootstrap is the discoverability hook, not a duplicate. Meta-instructive: this is the first application of release-validation-gate's own discipline. The canon being merged caught me in another canon violation (DRY) before I could merge it. Working as designed.
klappy
added a commit
that referenced
this pull request
Apr 20, 2026
…flip predecessor handoff to superseded
Honest closeout for P1.3.3.
Two halves, both load-bearing:
1. Technical work that shipped:
- D5 stemmed prereq matcher (replace per-prereq regex with stemmed
set intersection + 4 structural-test side-paths)
- D9 cache removal (drop cachedChallengeTypeIndex; inline
buildBM25Index per cache-fetches-and-parses)
- cache-fetches-and-parses graduated tier-2 canon (klappy.dev#125,
merged 3726073)
- All landed in oddkit 0.21.0 (PR #120 merged 33ca5bf,
PR #121 merged 25ad719)
2. Process failure that demonstrated why release-validation-gate
needed to exist:
- Orchestrator merged PRs #120 and #121 with Cursor Bugbot still
in_progress, treating it as non-blocking
- Skipped Sonnet 4.6 validator dispatch despite P1.3.2 ledger
warning that smoke-only should not become the default
- Bugbot subsequently posted 2 findings: medium-severity prod
regression breaking the strictly-additive invariant the PR
description claimed (stop-word filter dropping 'from' from
source-named vocab), low-severity DRY violation in
BasePrerequisite (re-listed PrereqMatchVocab fields manually
instead of intersection)
- Approximate prod-regression window: 04:11Z to 05:09Z + warmup
≈ 1h 39m on 'from'-keyword source-named matches
3. Structural fix:
- canon/constraints/release-validation-gate.md (tier 1, 168 lines)
— three binding rules: no merge with active reviews in_progress,
no promotion without independent fresh-context validation when
PR touches load-bearing surface, canon outranks any
session-scoped recommendation
- canon/principles/contract-governs-handoff-drift.md (tier 2,
128 lines) — graduated on third deciding-argument recurrence
(P1.3.1 implicit, P1.3.2 explicit, P1.3.3 explicit-via-failure)
- canon/bootstrap/model-operating-contract.md (+4 lines) —
'Before Shipping Code' section as discoverability hook
- All in klappy.dev#126, merged ee9aee4
- Captures O-open P11: oddkit_gate enforces release-validation-gate
mechanically at execution → completion transitions
4. Fix-forward applied the new canon end-to-end:
- oddkit#122 merged d17bc0c (0.21.1 fix branch, both Bugbot
findings addressed, +2 regression smoke assertions)
- Bugbot wait respected on PR #122 (completed/success)
- Sonnet 4.6 validator dispatched (sesn_011CaERPjHi1CV4TrvKW68jB)
against fix branch + 0.21.0 prod state + canon PR #126
- Validator verdict: CONDITIONAL PASS (conditional only on canon
PR #126 merging first; resolved before promotion)
- oddkit#123 merged 2c5d652b (promotion to prod)
- Bugbot wait respected on PR #123 (~225s, completed/success)
- Prod 0.21.1 verified live; live self-call confirms 'from' fix
Meta-instructive moment: Bugbot caught a within-canon DRY violation
on the bootstrap hook in PR #126 itself. The first application of
release-validation-gate's discipline caught me in another canon
violation (dry-canon-says-it-once) before I could merge it. Working
as designed.
Predecessor handoff (odd/handoffs/2026-04-20-p1-3-3-challenge-revisit.md)
flipped to status: superseded with explicit supersession_note naming
the 'Option A is fine for P1.3.3' line in its Validation Plan section
as the recommendation that produced the incident. Future sessions
reading the superseded handoff will be redirected here.
Carry-forward O-opens updated:
- P11 NEW: oddkit_gate mechanical enforcement of release-validation-gate
- P12 NEW: tokenize() audit pass (any caller using canon vocab/input
should explicitly pass stop-word set)
- P2/P3/P5/P6/P8/P9/P10 carried from prior sweeps unchanged
klappy
added a commit
that referenced
this pull request
Apr 20, 2026
…flip predecessor handoff to superseded (#127) Honest closeout for P1.3.3. Two halves, both load-bearing: 1. Technical work that shipped: - D5 stemmed prereq matcher (replace per-prereq regex with stemmed set intersection + 4 structural-test side-paths) - D9 cache removal (drop cachedChallengeTypeIndex; inline buildBM25Index per cache-fetches-and-parses) - cache-fetches-and-parses graduated tier-2 canon (klappy.dev#125, merged 3726073) - All landed in oddkit 0.21.0 (PR #120 merged 33ca5bf, PR #121 merged 25ad719) 2. Process failure that demonstrated why release-validation-gate needed to exist: - Orchestrator merged PRs #120 and #121 with Cursor Bugbot still in_progress, treating it as non-blocking - Skipped Sonnet 4.6 validator dispatch despite P1.3.2 ledger warning that smoke-only should not become the default - Bugbot subsequently posted 2 findings: medium-severity prod regression breaking the strictly-additive invariant the PR description claimed (stop-word filter dropping 'from' from source-named vocab), low-severity DRY violation in BasePrerequisite (re-listed PrereqMatchVocab fields manually instead of intersection) - Approximate prod-regression window: 04:11Z to 05:09Z + warmup ≈ 1h 39m on 'from'-keyword source-named matches 3. Structural fix: - canon/constraints/release-validation-gate.md (tier 1, 168 lines) — three binding rules: no merge with active reviews in_progress, no promotion without independent fresh-context validation when PR touches load-bearing surface, canon outranks any session-scoped recommendation - canon/principles/contract-governs-handoff-drift.md (tier 2, 128 lines) — graduated on third deciding-argument recurrence (P1.3.1 implicit, P1.3.2 explicit, P1.3.3 explicit-via-failure) - canon/bootstrap/model-operating-contract.md (+4 lines) — 'Before Shipping Code' section as discoverability hook - All in klappy.dev#126, merged ee9aee4 - Captures O-open P11: oddkit_gate enforces release-validation-gate mechanically at execution → completion transitions 4. Fix-forward applied the new canon end-to-end: - oddkit#122 merged d17bc0c (0.21.1 fix branch, both Bugbot findings addressed, +2 regression smoke assertions) - Bugbot wait respected on PR #122 (completed/success) - Sonnet 4.6 validator dispatched (sesn_011CaERPjHi1CV4TrvKW68jB) against fix branch + 0.21.0 prod state + canon PR #126 - Validator verdict: CONDITIONAL PASS (conditional only on canon PR #126 merging first; resolved before promotion) - oddkit#123 merged 2c5d652b (promotion to prod) - Bugbot wait respected on PR #123 (~225s, completed/success) - Prod 0.21.1 verified live; live self-call confirms 'from' fix Meta-instructive moment: Bugbot caught a within-canon DRY violation on the bootstrap hook in PR #126 itself. The first application of release-validation-gate's discipline caught me in another canon violation (dry-canon-says-it-once) before I could merge it. Working as designed. Predecessor handoff (odd/handoffs/2026-04-20-p1-3-3-challenge-revisit.md) flipped to status: superseded with explicit supersession_note naming the 'Option A is fine for P1.3.3' line in its Validation Plan section as the recommendation that produced the incident. Future sessions reading the superseded handoff will be redirected here. Carry-forward O-opens updated: - P11 NEW: oddkit_gate mechanical enforcement of release-validation-gate - P12 NEW: tokenize() audit pass (any caller using canon vocab/input should explicitly pass stop-word set) - P2/P3/P5/P6/P8/P9/P10 carried from prior sweeps unchanged
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.

Root-cause structural fix for the P1.3.3 process failure.
P1.3.3 shipped oddkit 0.21.0 to prod with two Cursor Bugbot-detectable bugs (one medium-severity prod regression breaking the strictly-additive invariant the PR description claimed, one low-severity DRY violation). The orchestrator treated Bugbot's
in_progressstate as non-blocking and skipped Sonnet 4.6 validator dispatch, despite the P1.3.2 ledger explicitly warning that smoke-only should not become the default.The fix is not "remember to wait for Bugbot next time" — orchestrators don't persist memory across sessions, and the next session would inherit the same trap. The fix is mechanical canon that future sessions cannot avoid encountering.
Three artifacts
NEW
canon/constraints/release-validation-gate.md(tier 1, 168 lines)Three binding rules for every oddkit ship:
in_progressmeans waiting;completed/neutralwith findings means dispositioned, not ignored. Names the failure modes explicitly (Stall-as-skip, Findings-as-noise) so they cannot be re-litigated as judgment calls.workers/src/orchestrate.ts, matcher modules, governance reads, response envelope, action behavior. Same-session smoke and live self-calls are useful artifacts but do NOT satisfy this — independent validation requires a different agent in a different context window (Sonnet 4.6 read-only via Managed Agents is the default mechanism).Captures O-open P11 as the next code beat:
oddkit_gateenforces this constraint atexecution → completiontransitions by checking GitHub Checks API for the relevant PR and refusing to PASS until Bugbot iscompletedand (for load-bearing PRs) a validator session ID is referenced incontext.NEW
canon/principles/contract-governs-handoff-drift.md(tier 2, 128 lines)Graduates a candidate principle on its third deciding-argument recurrence:
Names the conflict-resolution sequence (identify, default-to-canon, surface deviation, propose amendment if right). Names the three pressure points where the principle bites hardest: time pressure, authority pressure, scope-justified shortcuts.
AMEND
canon/bootstrap/model-operating-contract.md(+4 lines, +1 section)New "Before Shipping Code" section under Tool Rhythm. Discoverability hook: the bootstrap loads on every session's first turn, so future sessions encounter explicit references to
release-validation-gateandcontract-governs-handoff-driftbefore any ship work. Putting the pointer in the bootstrap means the rule reaches the orchestrator before the orchestrator decides whether to search for it.Why this comes BEFORE 0.21.1 promotion
The 0.21.1 fix-forward branch (
klappy/oddkit#fix/0.21.1-bugbot-stopword-and-dry, head9d09fe4) is open with the two Bugbot fixes. That PR will be the first application of this canon — independent Sonnet 4.6 validator dispatch is mandatory before its promotion merges, per Rule 2. This canon must merge first so the constraint is live in canon when the orchestrator searches it during the 0.21.1 ship.The test of whether the canon works is whether it stops the same author from making the same mistake twice in the same session. If the orchestrator dispatches the validator, waits for Bugbot completion, and folds findings into the ledger before promoting 0.21.1 — the canon worked. If not, the orchestrator should be called out, again.
Writing canon gate (per
klappy://canon/meta/writing-canon)For each new doc:
Provenance
fromfrom canon vocab; low-severity DRY violation inBasePrerequisitefix/0.21.1-bugbot-stopword-and-dry(open, awaits this canon's merge)Note
Medium Risk
Medium risk because it introduces new tier-1/tier-2 governance that changes required merge/promotion process and may block releases if interpreted or followed incorrectly, though it does not modify runtime code.
Overview
Adds a new tier-1 canon constraint,
klappy://canon/constraints/release-validation-gate, that makes release safety rules binding: don’t merge PRs with any active/in-progress reviews (including Cursor Bugbot), require independent fresh-context validation beforemain → prodpromotions that touch defined “load-bearing” surfaces, and explicitly state that canon overrides any session ledger/handoff recommending shortcuts.Adds a new tier-2 principle,
klappy://canon/principles/contract-governs-handoff-drift, formalizing how to resolve conflicts between canon and session-scoped artifacts (default-to-canon + surface deviation + amend canon if needed).Updates the bootstrap
model-operating-contracttool rhythm with a “Before Shipping Code” section that points sessions to these new documents so the rules are discoverable at session start.Reviewed by Cursor Bugbot for commit da93f63. Bugbot is set up for automated code reviews on this repo. Configure here.