From 0d960ce987f159d13464a83e55cf02b587db6dd7 Mon Sep 17 00:00:00 2001 From: Chris Klapp Date: Sun, 26 Apr 2026 23:08:30 -0400 Subject: [PATCH 1/3] canon: persist link-rot phase 2 resume handoff (RV-gate cleared, promote shipped) --- ...6-04-27-link-rot-phase-2-promote-resume.md | 102 ++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 odd/handoffs/2026-04-27-link-rot-phase-2-promote-resume.md diff --git a/odd/handoffs/2026-04-27-link-rot-phase-2-promote-resume.md b/odd/handoffs/2026-04-27-link-rot-phase-2-promote-resume.md new file mode 100644 index 00000000..9ec470dc --- /dev/null +++ b/odd/handoffs/2026-04-27-link-rot-phase-2-promote-resume.md @@ -0,0 +1,102 @@ +--- +uri: klappy://odd/handoffs/2026-04-27-link-rot-phase-2-promote-resume +title: "Handoff — Link-Rot Phase 2 Promote Resume (RV-Gate Cleared, Awaiting Final Merge)" +audience: odd +exposure: nav +tier: 2 +voice: neutral +stability: stable +tags: ["handoff", "link-rot", "phase-2", "promote", "rv-gate", "resume", "epoch-8"] +epoch: E0008 +date: 2026-04-27 +derives_from: + - "odd/handoffs/2026-04-27-link-rot-phase-2-promote-and-phase-3.md" + - "canon/constraints/release-validation-gate.md" +governs: "Resumption of v0.26.0 promote after RV-gate validator pass + F-1 fix-forward" +--- + +# Handoff — Link-Rot Phase 2 Promote Resume + +> RV-gate validator was dispatched on PR #146 per canon (overriding the prior handoff's defer). Validator found one MEDIUM bug (F-1: misleading `oddkit_audit` tool description). Fix-forward shipped as PR #147, squash-merged to main at SHA `428b6769`. PR #146 is still open and now needs (a) CI re-run on the new main HEAD, (b) body updated with validator findings + dispositions, (c) merge. Same-session toolset regressed again before final steps could ship. + +## Verified State (2026-04-27T02:39 UTC) + +- ✅ Prod oddkit `v0.25.0` (`oddkit_version` confirms — promote NOT shipped) +- ✅ Canon constraints from PR-2.3b live (`oddkit-action-registration-completeness` resolves, hash `pcc8q2`) +- ✅ Per compaction summary: main HEAD = `428b6769ea5dcb7041f8af924c129dee78a9c74f` (PR #147 squash merge) +- ✅ Per compaction summary: PR #146 OPEN, body still says "DO NOT MERGE until validator findings appended" + +## RV-Gate Validator Outcome (PR #143 → promote PR #146) + +Dispatched via Managed Agents API, claude-sonnet-4-6, read-only fresh-context. +- AGENT_ID: `agent_011CaTUhej22aHQ8yxuH5cKF` +- SESSION_ID: `sesn_011CaTUjLzivzqVVboKnfUjw` +- Report saved at agent's `/home/user/ledger/rv-gate-pr146-v0.26.0-oddkit-audit.md` + +| ID | Severity | Finding | Disposition | +|----|----------|---------|-------------| +| F-1 | MEDIUM | `workers/src/index.ts` tool description claimed default scope was `writings/, canon/, odd/, docs/`; actual `DEFAULT_AUDIT_PATHS = ["writings/"]` | **Fixed** — PR #147 (squash `428b6769`), merged to main pre-promote | +| F-2 | LOW | PR #143 body table stale post-merge | Doc-debt, no live impact, no action | +| F-3 | MEDIUM | Spec mandated `index_state: {warm_count, warming_count}`; shipped omits it | Intentional — CHANGELOG documents drift; queue follow-up canon PR on klappy.dev to amend spec (precedent: spec was already amended once at v2.1) | +| F-4 | LOW | `suppressed_findings` returns full objects; spec said count only | Additive, not harmful, no action | +| F-5 | INFO | `PARTIAL_INDEX` status absent | Intentional; spec disagrees — fold into F-3 spec amendment | +| F-6 | INFO | `since_commit` accepted but ignored | Documented in CHANGELOG, no action | +| F-7 | env | Validator's CF Workers container hit DNS overflow on POST `/mcp` | Not a code defect — CI's "Test CF Preview" green on same surface (after re-run) satisfies live-behavior corroboration | + +C2 (bytes-on-main) and C4 (canon retrievability) both PASSED. Verdict: **fix-forward required → COMPLETE; cleared to merge once body updated.** + +## Pending — Resume Here + +1. **Wait for PR #146 CI on new main HEAD** (`428b6769`). Pattern observed: "Test CF Preview" check fails ~22-26s on first run of any new commit (deploy race — the wait-for-preview step doesn't actually wait long enough). If it fires, immediately `POST /actions/runs/{id}/rerun-failed-jobs`. Twice in a row = real regression. + +2. **Update PR #146 body** via `PATCH /repos/klappy/oddkit/pulls/146`. Replace the trailing line `⏳ Validator findings: pending — DO NOT MERGE until appended.` with the dispositions table above, then `✅ Validator findings appended; cleared to merge.` + +3. **Merge PR #146** with `merge_method: "merge"` (regular merge — promote PR convention; check #142/#145 for the exact body). + +4. **Wait ~3 min for CF auto-deploy** to prod from new prod HEAD. + +5. **Post-deploy verification**: + - `oddkit_version` → `0.26.0` + - Call unified `oddkit` action `audit` (default scope) → expect `{status, summary, findings, scope: { paths: ["writings/"] }}` + - Call standalone `oddkit_audit` with `{scope: {paths: ["writings/", "canon/"]}}` → expect wider scope honored + - Confirm action enum has 12 values (not 11) + +6. **Phase 3 PR-3.1**: Open `.github/workflows/canon-quality.yml` on klappy/klappy.dev (soft-block mode). Re-draft from `klappy://docs/oddkit/specs/oddkit-audit` (~225-line workflow). Triggers on PR + push, calls `oddkit_audit` against prod via curl, sticky comment via `marocchino/sticky-pull-request-comment@v2`, respects `vars.AUDIT_ENFORCEMENT_MODE` (soft default). + +7. **Observation cycle**: 3-5 PRs through soft-block gate. + +8. **Phase 3 PR-3.2**: Flip `vars.AUDIT_ENFORCEMENT_MODE` to `hard`. + +9. **Save BOTH handoffs to canon** (small canon PR on klappy.dev): + - `odd/handoffs/2026-04-27-link-rot-phase-2-promote-and-phase-3.md` (the original, attached at start of this session — never landed) + - `odd/handoffs/2026-04-27-link-rot-phase-2-promote-resume.md` (this document) + +10. **Spec amendment PR** for F-3/F-5 on klappy.dev: amend `docs/oddkit/specs/oddkit-audit` to v2.2 — drop `index_state` from spec OR mark it as planned-future; document `PARTIAL_INDEX` status as deferred. + +## Key Resources + +- GitHub PAT (do NOT commit): in project instructions +- Anthropic API key: in project instructions +- Managed-agents env: `env_016RffZyqSdHeb5s3Z6UABw8`, header `managed-agents-2026-04-01` +- Validator transcript: previous session container `/home/claude/work/events_final.json` (lost when container resets — full text was in compacted transcript at `/mnt/transcripts/2026-04-27-02-37-54-oddkit-audit-promote-rv-gate.txt`) + +## Continuation Test + +First three actions for the next session: +1. `oddkit_version` — sanity check (should still be `0.25.0` until step 3 completes). +2. `GET /repos/klappy/oddkit/pulls/146` — confirm still open, check check-status on head. +3. If checks green: PATCH body → merge. If checks red: rerun failed jobs first. + +If the next session starts re-dispatching the validator, re-deriving validator findings, or arguing about scope — the handoff failed. + +## Persistence Note (added when this handoff was persisted to canon) + +This handoff was authored during the prior session (compacted at 2026-04-27T02:39 UTC) and persisted to canon by the next session that successfully executed the Continuation Test. The "original" handoff referenced in item 9 (`odd/handoffs/2026-04-27-link-rot-phase-2-promote-and-phase-3.md`) was not persistable — it was attached only to the prior session's chat context, was never written to canon, and was lost when that session compacted. This document is comprehensive enough to stand alone for forensic purposes; the original handoff's contribution is preserved indirectly via this document's references and the session ledger linked below. + +## See Also + +- `klappy://canon/constraints/release-validation-gate` (Rule 2 — independent validator dispatch on load-bearing surface) +- `klappy://canon/principles/contract-governs-handoff-drift` +- `klappy://docs/planning/link-rot-elimination-campaign` (sequencing v2.1) +- `klappy://docs/oddkit/specs/oddkit-audit` (v2.2 — amended in the same PR that persisted this handoff) +- `klappy://odd/ledger/2026-04-27-link-rot-phase-2-shipped` — session ledger that closes out the promote From fbcea47cba0caa2ae56f7cf5911dc218e430a03a Mon Sep 17 00:00:00 2001 From: Chris Klapp Date: Sun, 26 Apr 2026 23:08:31 -0400 Subject: [PATCH 2/3] canon: session ledger for v0.26.0 promote (link-rot Phase 2 shipped) --- .../2026-04-27-link-rot-phase-2-shipped.md | 130 ++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 odd/ledger/2026-04-27-link-rot-phase-2-shipped.md diff --git a/odd/ledger/2026-04-27-link-rot-phase-2-shipped.md b/odd/ledger/2026-04-27-link-rot-phase-2-shipped.md new file mode 100644 index 00000000..9c38d1f9 --- /dev/null +++ b/odd/ledger/2026-04-27-link-rot-phase-2-shipped.md @@ -0,0 +1,130 @@ +--- +uri: klappy://odd/ledger/2026-04-27-link-rot-phase-2-shipped +title: "Session Ledger — Link-Rot Phase 2 Shipped (oddkit v0.26.0 in prod)" +audience: odd +exposure: nav +tier: 2 +voice: neutral +stability: stable +tags: ["ledger", "session-ledger", "link-rot", "phase-2", "promote", "v0.26.0", "oddkit-audit", "epoch-8", "rv-gate"] +epoch: E0008 +date: 2026-04-27 +derives_from: + - "odd/handoffs/2026-04-27-link-rot-phase-2-promote-resume.md" + - "canon/constraints/release-validation-gate.md" +governs: "Closeout for the v0.26.0 promote — what shipped, what was verified, what remains for Phase 3" +--- + +# Session Ledger — Link-Rot Phase 2 Shipped + +> oddkit v0.26.0 promoted to prod at 2026-04-27T02:51:55 UTC. CF auto-deploy completed by 02:55:26 UTC. All four post-deploy verifications passed against the live `https://oddkit.klappy.dev/mcp` endpoint. The new `oddkit_audit` action and unified-router `action: "audit"` are live; default scope `["writings/"]` and wider scope `["writings/", "canon/"]` both honored. One additional Cursor Bugbot finding surfaced on the promote PR head (low-severity dead `lineSeen` field) — waived in PR body with rationale and tracked at klappy/oddkit#148. Validator findings F-3 and F-5 (intentional spec drift) addressed in this same canon PR via the spec v2.2 amendment. + +## Summary — What Shipped + +- **Action**: `oddkit_audit` standalone tool + unified `oddkit` `action: "audit"` enum value +- **Version**: oddkit `0.25.0 → 0.26.0` (strictly additive +1 action; enum count `12 → 13`) +- **Default scope**: `["writings/"]`; opt-in wider via `scope.paths` +- **Surface**: `https://oddkit.klappy.dev/mcp` (and any oddkit-pattern MCP server using this Worker codebase) +- **Promote PR**: klappy/oddkit#146 — merged 2026-04-27T02:51:55Z via regular merge (`merge_method: "merge"`, promote convention) +- **First real findings**: 3 dead-reference findings surfaced in default-scope smoke; 17 in `["writings/", "canon/"]` smoke. Real link-rot in canon — useful Phase 3 input. + +## Decisions Made This Session + +### D — Bugbot lineSeen finding waived rather than fix-forwarded + +The Cursor Bugbot review on PR #146 head SHA `428b6769` posted one Low-severity finding: the `lineSeen` property on the `pendingSuppress` object in `workers/src/orchestrate.ts:1947,1959` is assigned but never read. Independently verified via `grep -n lineSeen` — only two writes, no reads. + +**Disposition**: waived in PR body with rationale + tracked at klappy/oddkit#148. **Rationale**: fix-forward would re-touch `orchestrate.ts` (load-bearing surface per `klappy://canon/constraints/release-validation-gate` Rule 2), which would force another full validator dispatch for what is a cosmetic-only cleanup with no behavioral surface. The release-validation-gate canon explicitly enumerates "waive in PR body with rationale" as one of three valid dispositions for `completed/neutral` Bugbot reviews with comments. The tracking issue keeps the dead code visible for the next PR that legitimately touches the audit code path. + +### D — Spec amended to v2.2 in this same canon PR + +Validator findings F-3 (`index_state` field shipped omitted but spec mandated it) and F-5 (`PARTIAL_INDEX` status absent but spec listed it) are intentional drift documented in CHANGELOG. The spec is the authoritative future-target; rather than carry permanent drift, the spec graduates to v2.2 in this canon PR with both fields marked deferred. F-4 (`suppressed_findings` returns full objects vs spec's "count only") was dispositioned "no action" by the validator and remains unchanged in v2.2 — additive, not harmful, and any future amendment can be a separate small PR if alignment becomes useful. + +### D — Original handoff persisted only via reference, not reconstruction + +Item 9 of the resume handoff said BOTH handoffs needed persisting: +- `odd/handoffs/2026-04-27-link-rot-phase-2-promote-and-phase-3.md` — the "original," attached at start of the prior session +- `odd/handoffs/2026-04-27-link-rot-phase-2-promote-resume.md` — the resume + +This session has the resume in full text but does not have the original. The original was attached to the prior session's chat context only; it was never persisted to canon and the prior session's container/transcript was no longer accessible. **Per axiom 4 ("You Cannot Verify What You Did Not Observe"), this session refuses to reconstruct the original from inference.** The resume is comprehensive enough to stand alone for forensic purposes — it summarizes the validator dispatch, the F-1 fix-forward, the open promote-PR state, and the full continuation test. The original's loss is recorded honestly in the resume's "Persistence Note" appendix. + +## Observations + +### O — Containers reset DNS resolution mid-session + +POST `/mcp` from `curl` in the working container hit "DNS cache overflow" reproducibly after enough other network calls — the same environmental issue the validator's container hit (recorded as F-7 in the validator findings). Switching to Python `urllib.request` with an explicit `User-Agent` header bypassed it cleanly. This is a CF Workers Containers / Anthropic execution-environment quirk, not an oddkit defect. Worth carrying forward: post-deploy verification scripts should default to urllib over curl when running inside this environment. + +### O — Cursor Bugbot Autofix is correctly non-blocking per canon Rule 1 + +The promote PR's `Cursor Bugbot Autofix` check sat in `in_progress` while the parent `Cursor Bugbot` check completed `neutral`. The `mergeable_state` was `unstable` as a result. Canon (`klappy://canon/constraints/release-validation-gate` Rule 1) explicitly addresses this: "`Cursor Bugbot Autofix` may remain `in_progress` indefinitely without blocking, BUT only after the parent `Cursor Bugbot` check has reached `completed`." This session followed canon and merged anyway. Confirming the canon's specific carve-out is real and operational. + +### O — Real link-rot exists in `writings/` + +Default-scope audit returned 3 dead-reference findings on first prod run: +- `writings/choosing-faith-not-fear.md:203` → `klappy://writings/four-questions-that-change-everything` +- (two more — visible in any subsequent default-scope audit call) + +Phase 3 enforcement will surface these on the next PR that touches a writings file. The campaign was always "wire the gate, then fix what it surfaces" — this is the input data for the fix sweep. + +## Learnings + +### L — Validator findings split cleanly into "fix" vs "amend spec" vs "no action" + +The 7-finding validator report disposed cleanly: +- F-1: real bug → fix-forward (PR #147) +- F-2, F-4, F-6: doc-debt or additive — no action +- F-3, F-5: intentional implementation drift from spec → amend spec (this PR) +- F-7: environmental — not a code defect + +This is the pattern the release-validation-gate is meant to produce. Each finding gets a category, each category has a standard disposition. No finding gets ignored; no finding's disposition is left ambiguous. The review is genuinely a contract, not a ceremony. + +### L — Continuation Test as written-in-handoff is a strong checkpoint mechanism + +The resume handoff's three-action Continuation Test (`oddkit_version` → `GET /repos/.../pulls/146` → patch+merge or rerun) gave this session an unambiguous starting line. The handoff's failure clause ("If the next session starts re-dispatching the validator, re-deriving validator findings, or arguing about scope — the handoff failed") gave a clear failure signal. Sessions that lose state benefit enormously from explicit Continuation Tests in the prior handoff. + +## Constraints Held + +### C — Release-Validation-Gate Rules 1, 2, 3 all honored + +- **Rule 1**: every check-run reached `completed` with acceptable conclusion (success / neutral) before merge. The one Bugbot review comment was dispositioned (waive + tracking issue) per the rule's three valid options. +- **Rule 2**: the original PR #143 had been validated by an independent fresh-context Sonnet 4.6 agent. F-1 was fixed-forward in PR #147 before promote merged. The validator did its job — this session honored its findings rather than re-litigating them. +- **Rule 3**: the resume handoff explicitly noted the prior session's compaction; this session did not treat any session-scoped recommendation as overriding canon. The Bugbot finding got a canon-prescribed disposition. + +## Handoff — What Remains + +### Item 6 from the resume handoff: Phase 3 PR-3.1 + +`.github/workflows/canon-quality.yml` on klappy/klappy.dev. Triggers on PR + push, calls `oddkit_audit` against prod via `curl` (or `python -c "import urllib..."` if curl fails — see the DNS observation above), sticky comment via `marocchino/sticky-pull-request-comment@v2`, respects `vars.AUDIT_ENFORCEMENT_MODE` (soft default). + +Spec is at `klappy://docs/oddkit/specs/oddkit-audit` (v2.2 after this PR). The workflow is a separate execution beat — substantial enough to warrant its own gate transition. + +### Item 7: Observation cycle + +3-5 PRs through the soft-block gate, observing finding rate and noise patterns. Cannot run inside a single session. + +### Item 8: PR-3.2 — flip enforcement to hard mode + +Single-line change to `vars.AUDIT_ENFORCEMENT_MODE`. Trivial mechanically; gated on item 7. + +### Item 10 (resolved in this PR): spec amendment v2.2 + +This PR contains the spec amendment. After merge, F-3 and F-5 close cleanly. + +## Continuation Test — for the next session + +If picking up Phase 3 PR-3.1: + +1. `oddkit_version` — confirm prod is still `0.26.0`. +2. `oddkit_get` on `klappy://docs/oddkit/specs/oddkit-audit` — confirm v2.2 amendment landed (Origin section shows v2.2 entry). +3. `GET /repos/klappy/klappy.dev/contents/.github/workflows/canon-quality.yml` — confirm workflow does NOT yet exist (404 expected). +4. Begin PR-3.1: branch, write workflow per spec, open draft PR, smoke locally if possible, ungate. + +## See Also + +- `klappy://odd/handoffs/2026-04-27-link-rot-phase-2-promote-resume` — the handoff this session executed +- `klappy://canon/constraints/release-validation-gate` — Rules 1/2/3, all honored +- `klappy://docs/oddkit/specs/oddkit-audit` — the spec, now at v2.2 +- `klappy://docs/planning/link-rot-elimination-campaign` — the multi-phase campaign this PR completes Phase 2 of +- klappy/oddkit#146 — the promote PR +- klappy/oddkit#147 — the F-1 fix-forward +- klappy/oddkit#148 — tracking issue for the dead `lineSeen` field From 7908310d5f98d66b044c787e2cc1d8dfe8708a63 Mon Sep 17 00:00:00 2001 From: Chris Klapp Date: Sun, 26 Apr 2026 23:08:32 -0400 Subject: [PATCH 3/3] =?UTF-8?q?spec:=20oddkit-audit=20v2.2=20=E2=80=94=20d?= =?UTF-8?q?efer=20index=5Fstate=20and=20PARTIAL=5FINDEX=20(validator=20F-3?= =?UTF-8?q?,=20F-5)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/oddkit/specs/oddkit-audit.md | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/docs/oddkit/specs/oddkit-audit.md b/docs/oddkit/specs/oddkit-audit.md index f56fcfdd..e8e498b6 100644 --- a/docs/oddkit/specs/oddkit-audit.md +++ b/docs/oddkit/specs/oddkit-audit.md @@ -1,6 +1,6 @@ --- uri: klappy://docs/oddkit/specs/oddkit-audit -title: "oddkit_audit — Action Specification (DRAFT v2 — KISS)" +title: "oddkit_audit — Action Specification (DRAFT v2.2 — KISS)" audience: docs exposure: nav tier: 2 @@ -8,17 +8,17 @@ voice: neutral stability: draft tags: ["spec", "oddkit", "audit", "dead-references", "ci-gate", "vodka", "kiss"] epoch: E0008 -date: 2026-04-26 +date: 2026-04-27 derives_from: - "canon/methods/reference-integrity-audit.md" - "canon/principles/partial-data-with-transparency-and-background-warm.md" - "canon/principles/ritual-is-a-smell.md" - "docs/oddkit/specs/oddkit-resolve.md" governs: "Mechanical detection of dead klappy:// references at PR time" -supersedes: "DRAFT v1 (2026-04-26, four-check version)" +supersedes: "DRAFT v2.1 (2026-04-26, default-scope narrowed); DRAFT v1 (2026-04-26, four-check version)" --- -# oddkit_audit — Action Specification (DRAFT v2 — KISS) +# oddkit_audit — Action Specification (DRAFT v2.2 — KISS) > Walk every `klappy://` URI in canon. Call `oddkit_resolve` on each. Report the ones that don't resolve. That's the entire job. @@ -68,7 +68,7 @@ No `checks` field. There's one check; it always runs. No `severity_floor`. Workf { "action": "audit", "result": { - "status": "OK" | "FINDINGS" | "PARTIAL_INDEX", + "status": "OK" | "FINDINGS", "summary": { "total_findings": 12, "by_severity": { "error": 11, "warning": 1 } @@ -88,13 +88,9 @@ No `checks` field. There's one check; it always runs. No `severity_floor`. Workf "occurrence": "/page/writings/some-slug", "message": "Use a klappy:// URI instead of /page/ path" } - ], - "index_state": { - "warm_count": 552, - "warming_count": 0 - } + ] }, - "server_time": "2026-04-26T02:50:00.000Z" + "server_time": "2026-04-27T02:55:26.000Z" } ``` @@ -135,13 +131,13 @@ Per the partial-data principle: 1. User-blocking path bounded by cache lookups. 2. Background warm via `ctx.waitUntil`. -3. Concrete disclosure via `index_state`. +3. Concrete disclosure via `index_state` — **DEFERRED in v2.2 (see Origin)**. -When `status: PARTIAL_INDEX`, findings are best-effort. CI workflow handles this by treating partial-index runs as non-blocking (warning, retry on next push). +The `PARTIAL_INDEX` status and the `index_state.warming_count` mechanism are deferred. The shipped v0.26.0 implementation does not emit them: the worker's audit path hits the resolver synchronously per URI and treats unresolvable URIs as `dead-reference` regardless of warm-state. If a real consumer demonstrates the need to distinguish "URI is dead" from "URI couldn't be checked because the index wasn't warm yet," the deferred mechanism graduates from this section back into the Output schema. Until then the Use Only What Hurts principle keeps the envelope minimal. ## Disconfirmers — What Would Falsify This -1. **The resolver has bugs that produce false `NOT_FOUND` responses.** Audit findings would be false positives. Mitigation: workflow respects `index_state.warming_count`; release-validation-gate on the resolver catches this before audit ships. +1. **The resolver has bugs that produce false `NOT_FOUND` responses.** Audit findings would be false positives. Mitigation: release-validation-gate on the resolver catches this before audit ships. The `index_state.warming_count` mitigation referenced in v2.0 is deferred (see Partial-Data Compliance and Origin v2.2 amendment). 2. **Findings volume on first run is so high authors disable the gate.** Mitigation: workflow ships in soft-block mode; one observation cycle to assess before hard-block. 3. **The line-level allowlist proves insufficient (e.g., a template file legitimately has 30 placeholder URIs).** Triggers reconsideration of file-level allowlist (the deferred frontmatter field). @@ -178,3 +174,5 @@ Net-new action. No existing callers. Drafted on 2026-04-26 alongside `oddkit_resolve` (DRAFT v4). v1 of this spec proposed four checks (dead-reference + terminological-drift + projection-staleness + epoch-gaps) plus a deprecated-terms registry, epoch-completeness rules, and an `audit_allow:` frontmatter field. v2 (this revision) cut to one check and one allowlist mechanism per the operator's Vodka discipline. The other three checks and supporting registries moved to the deferred-concerns ledger with explicit revisit triggers. **v2.1 amendment (2026-04-26, end of PR-2.3a)**: default scope narrowed from "full repo excluding `docs/archive/`" to `["writings/"]`. Surfaced when the v0.26.0 implementation's CF Preview test 14j (default-scope audit) timed out at 120s — cold-cache fetching ~560 files via the worker's zip-extract path exceeded the curl budget. Real reasons the smaller default is honest: PR-2.2's actual cleanup was writings-only; the April-9 audit classified non-writings broken refs as intentional templates/site-routes/historical-archive (Classes A–E); writings is where authors write `klappy://` URIs as body links most often. Wider scope is one explicit `scope.paths` arg away. If a real consumer demonstrates wider need, the default broadens (or parallelized fetching graduates from the deferred-concerns ledger). Reversal is one-line. + +**v2.2 amendment (2026-04-27, post-promote of v0.26.0)**: `index_state` field and `PARTIAL_INDEX` status removed from the documented Output schema. Surfaced when the post-promote validator (klappy/oddkit#146 RV-gate) flagged F-3 (spec mandated `index_state: {warm_count, warming_count}`; shipped omits it) and F-5 (spec listed `PARTIAL_INDEX` in the status enum; shipped omits it). Both were intentional drifts in the v0.26.0 implementation: the worker's audit path is synchronous-per-URI against the resolver and does not currently distinguish warming from terminal states. Rather than carry permanent spec-vs-shipped drift, the spec graduates to v2.2 with both fields marked deferred and the Use Only What Hurts principle invoked. If a real consumer demonstrates need to distinguish "URI is dead" from "URI couldn't be checked because the index wasn't warm yet," the deferred mechanism graduates back into the schema. F-4 (`suppressed_findings` returns full objects vs spec's "count only") was dispositioned by the validator as `no action — additive, not harmful` and remains unchanged in v2.2; if alignment becomes useful, a separate small amendment can land.