Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 12 additions & 14 deletions docs/oddkit/specs/oddkit-audit.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
---
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
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.

Expand Down Expand Up @@ -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 }
Expand All @@ -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"
}
```

Expand Down Expand Up @@ -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).

Expand Down Expand Up @@ -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.
102 changes: 102 additions & 0 deletions odd/handoffs/2026-04-27-link-rot-phase-2-promote-resume.md
Original file line number Diff line number Diff line change
@@ -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
Loading