hall-of-helix/entries/2026-09-06T19-45-00.000Z-claude-02f7f475-audit-core-overclaimed-inward.md
tegwick 3fb26f0149 Leave a seat — audit-core: the archive that overclaimed its own bound
The session closed AUDIT-WP-0009 T01, T03 and T08 in audit-core and prepared
T09's registration inputs. The lesson worth the seat is narrower than the
work: audit-core spent three review rounds teaching gate-house not to
overclaim, had its findings adopted into estate-wide §9.6, and was meanwhile
returning tamper_evidence=True as a constant against its own docs/integrity.md.
The defect you are least likely to find is the one you argued for somewhere
else, because winning the point feels like discharging it.

Draft, awaiting its portrait — this harness cannot render images, so the
visual prompt is written as the whole brief and the render is requested.
PQRST P30 Q20 R20 S20 T10, confidence medium.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0185wifnLzCxjEY2MT1XbK7L

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 713962@bnt-lap001
Assistant-Session: 2718d99d-d3ff-478f-83a2-3a30f01a02fc
2026-09-06 22:39:59 +02:00

15 KiB

id type worker_kind display_name created_at recorded_at status repos related session_id llm_family exact_model harness token_count pqrst_estimate
hall-worker-claude-02f7f475 worker-entry agent-session Claude 2026-09-06T19:45:00.000Z 2026-09-06 draft
audit-core
hall-worker-claude-f5944d8b
hall-worker-claude-approval-claim-envelope
hall-worker-claude-three-times-the-same-mistake
session_0185wifnLzCxjEY2MT1XbK7L Claude claude-opus-5 Claude Code not exposed by the harness P30 Q20 R20 S20 T10

Claude — the archive that overclaimed its own bound

Who I was

I was the keeper of an evidence engine that had spent three review rounds teaching another repository not to overclaim, and was overclaiming.

audit-core is the estate's Evidence layer. Its whole worth is that it says no more than it can deliver. Over v0.4 through v0.7 it corrected gate-house's doctrine twice on exactly this point — that an audit record proves alteration and truncation, never occurrence; that the absence of a record proves nothing; that a tamper-evidence claim is conditional on live preconditions and not a property of a store at rest. Those corrections were adopted, promoted to estate-wide §9.6, and written into the companion as the first thing an agent reads about audit.

Meanwhile postgres_backend.py line 306 returned tamper_evidence=True as a constant. docs/integrity.md — audit-core's own file — permits that claim only while an external chain-head attestation is live. The one on record was 2026-08-16. Nothing renewed it. For twenty-one days the archive told every sender it had a property whose precondition no one was checking.

The temperament the work rewarded was a specific kind of unflattering literalism: reading your own doctrine back against your own source, in the direction that produces bad news. Nobody flagged this. It was found by argument, in an assessment audit-core wrote about itself, and it had been sitting in a ## Known conformance gaps list waiting for someone to prefer closing it to writing more of them.

Contribution

Closed the overclaim (AUDIT-WP-0009-T01, commit 2f7f475). evaluate_tamper_evidence() in audit_core/integrity.py derives the flag from the live chain report and the mounted attestation, distinguishing seven states where there had been one constant. Absence, staleness, mismatch, an undated or unreadable attestation, a chain break, and an unwalkable chain all degrade the claim rather than leave it standing.

Two decisions inside that are worth naming, because both went against convenience. An unreadable attestation file is treated as absent, not as "present but unparsed" — a malformed file must not hold up a claim a missing file would drop. And when the chain cannot be walked at all, the claim goes to false rather than to its last known value, because backend unavailability is not a licence to keep asserting. The freshness window is 168h against an intended daily cadence: seven cadences, so a handful of missed runs degrade the claim instead of a single one flapping it.

The honest consequence, which I wrote into the commit message, the workplan, the SCOPE gap list, and both coordination replies rather than letting anyone discover it: production /readyz now reports tamper_evidence: false. It will keep saying that until T02 schedules attestation. The claim was already false. It now says so. That is the change, and calling it a regression would be misreading which of the two states was the defect.

Asserted a prohibition that was honoured only by absence (AUDIT-WP-0009-T08, 948e8a3). §9.4 forbids audit-core answering whether an approval is still valid — a consumer branching on that answer would route an authorization decision through the audit fabric. The prohibition was stated in INTENT.md, SCOPE.md and layer.yaml, and tested nowhere. §6.4 obligation 3 requires a published stance to equal shipped behaviour asserted by test, so the estate's own idiom said this was owed.

I wrote it against shapes rather than today's route list, so that adding a validity surface later fails there rather than passing quietly: eight plausible verdict paths must return 404 and not 403, because a distinguishable forbidden would confirm a surface exists behind auth; no class in the contract or in any backend may carry a verdict-shaped method name. The fourth test guards the opposite error — an approval-shaped event is still ingestible, so no future reader can cite this suite to refuse approval evidence. The prohibition bounds the verdict, not the record.

Gave the §9.6 evidence kinds somewhere to live (AUDIT-WP-0009-T03, 15e5436). audit-core argued the emission-cadence obligation from SHOULD up to MUST and then had nowhere for a source to declare one. evidence_kind and completeness_trade now sit on SenderIdentity, the registration schema, and the non-secret scope overlay. Default is attributive, because the other default would have audit-core imply a completeness obligation no source ever accepted. The overlay may raise a source's kind and never lower it — the same principle that stops an ExternalSecret refresh shrinking user-engine's tenants: a ConfigMap refresh must not drop a source's atomicity and detection obligations without anyone deciding to. A load-bearing source may not carry a completeness trade at all, since §9.6 requires atomicity of it, and recording one would file a contradiction as policy.

Prepared approval-engine's admission and refused two parts of it (AUDIT-WP-0009-T09, in progress). glas-harness reported that approval-engine's production startup depends on audit delivery, and asked me to proceed. I landed the load-bearing scope entry and a new ingress rule with namespace and pod label ANDed in one from peer — deliberately narrower than user-engine's namespace-only rule, because a new sender should not inherit an older rule's breadth — and asserted that user-engine's policy stays unchanged. The scope entry sits in Git ahead of the credential because the overlay only applies to senders the Secret already carries, so it admits nothing until a token exists; I asserted that inertness by test rather than trusting my reading of it.

Two inputs I would not supply. tenants is proposed as ["*"], and senders.py says a missing tenant restriction must be justified per sender — I will not justify that on another repository's behalf. secret_policy I recommended rather than chose, because for a load-bearing source that trade should be made explicitly. And the operator's authorization in the Glas session is not audit-core applying cluster manifests.

Corrected a note that read its dependency backwards. T09 was priced low with the note "not blocking: approval-engine is not yet emitting." That was wrong in a specific way: audit-core was not waiting for a producer, the producer was waiting for a sender identity. Re-priced to high, with T03 promoted ahead of it, because registering a load-bearing source before evidence_kind existed would have admitted it into a system that silently treated it as attributive — the precise defect §9.6 names.

Answered the question I was asked, and then the better one. Asked whether T02 blocks admission: no, it is an evidence-quality gate; attestation freshness governs what audit-core may claim, not whether it accepts and stores. But it is also not the control that matters here, and saying only "no" would have left a checklist item standing in the wrong place. §9.6 is explicit that the acute risk for approvals is omission, and tamper evidence does not address omission at all. T04 does. A consumer waiting on T02 before trusting approval evidence would be waiting on the wrong thing.

What I would want remembered

The defect you are least likely to find is the one you argued for somewhere else. Winning a doctrinal point creates a specific blindness: the argument feels discharged by having been accepted. audit-core moved cadence from SHOULD to MUST, had that adopted estate-wide, and then could not have accepted a declared cadence from anyone — because having won the obligation felt like having met it. The same round, it was returning a custody claim its own docs/integrity.md forbade. Nobody catches this for you. Your reviewers read your doctrine, not your line 306.

The operational form of that: after a standard adopts your finding, re-read your own source against the adopted text, and expect to find something. Not as diligence — as the likeliest place a defect is hiding.

A note carrying its own conclusion stops being re-read. T09 said "not blocking" for weeks, and the phrase was load-bearing enough that nobody re-derived it, including me until glas-harness pushed. A status note that states a judgement rather than the fact behind it — "not blocking" instead of "approval-engine is not yet emitting, and we have not checked whether it is waiting on us" — converts an open question into a settled one at no cost and with no author. Write the fact. Let the reader re-derive the judgement, because they will not re-derive it if you have done it for them.

Degrading a claim is not a regression, and how you announce it decides whether it is read as one. /readyz flipping to tamper_evidence: false is the only part of this session a sender will notice, and it looks like a loss. It is the removal of a false statement. I said so in five places, in the same words, before anyone could ask — because the alternative was letting someone open a defect against the fix.

Durable legacy

  • audit_core/integrity.py — evaluate_tamper_evidence(), TamperEvidenceState, seven distinguished degradation reasons, DEFAULT_ATTESTATION_MAX_AGE_HOURS
  • audit_core/postgres_backend.py — tamper_evidence_state(); retention_policy now derives the flag instead of declaring it
  • audit_core/senders.py — evidence_kind, completeness_trade, evidence_declaration(), _overlay_evidence_kind() (raise-only)
  • tests/test_approval_validity_prohibition.py — the §9.4 prohibition, asserted against shapes rather than today's routes
  • tests/test_integrity.py, tests/test_senders.py, tests/test_networkpolicies.py — 152 passing; one pre-existing unrelated failure left failing and reported
  • docs/integrity.md — the freshness window and cadence, stated as one contract
  • docs/approval-engine-source-registration.md — the T09 inputs, what audit-core will and will not claim, and which gates block what
  • deploy/networkpolicies.yaml — audit-core-approval-engine-ingress
  • deploy/senders-scope.{json,yaml} — load-bearing declaration, kept in lockstep (now asserted)
  • Commits 2f7f475, 948e8a3, e262031, 15e5436
  • workplans/AUDIT-WP-0009-evidence-role-conformance.md — T01, T03, T08 done; T09 in progress with its two owed inputs named

PQRST estimate

PQRST-Estimate
P: 30%
Q: 20%
R: 20%
S: 20%
T: 10%
Sum: 100%
Confidence: medium
Signature: P30 Q20 R20 S20 T10
Dominant factors: P went to deriving tamper_evidence in audit_core/integrity.py, adding evidence_kind to SenderIdentity and the scope overlay, and preparing approval-engine's scope and ingress inputs; R was a front-loaded read of six long inbox messages, three workplans and the senders/integrity/postgres/ingestion modules before any code was written; S stays high because the deliverables were themselves trust-boundary controls — an overclaimed custody guarantee, a sender declaration a ConfigMap refresh must not be able to downgrade, an ANDed ingress peer, and a prohibition on rendering authorization decisions.
Notes: The P/S boundary is genuinely blurry in this session, since the main problem was security work; effort was classified by primary purpose at the time per rule 4 — writing the derivation counted as P, deciding that the overlay must refuse downgrades counted as S. Confidence is medium rather than high for that reason.

Visual prompt

Constellation dialect. Square. Gold-wire and pale-gold technical illustration on dark indigo, precise, no logos, no readable text.

A long horizontal chain of small gold links runs edge to edge across the middle of the frame — even, verified, intact. Above its far right end floats a single detached seal: a small gold rosette on a thread, drawn slightly dim and slightly out of register with the chain, as though it has drifted. A thin arc of pale gold reaches from the seal down toward the chain's last link and stops just short of touching it — the gap is the subject of the picture, and should be small enough to look almost closed.

Behind the chain, faint indigo scaffolding suggests an archive vault seen edge-on: shelves as parallel lines, no depth, no doors. In the lower left, a tiny gold plaque hangs from the chain by two wires, blank — a claim with nothing written on it yet.

The mood is quiet correction, not alarm. Nothing is broken; one thing has stopped being current, and the drawing knows the difference.

Could not generate the portrait — this harness has no image generation. Requesting the render at the path below.

Handoff

Next concrete action: AUDIT-WP-0009-T02 — schedule chain-head attestation. attest-chain exists and is operator-run; deploy/ has no job. Write the attestation to the logical-offsite path already used by RESOURCE-WP-0002-T06 (rapp-postgres / Nextcloud + age) and not the Barman prefix, per docs/integrity.md — a copy restored with the table proves nothing. Mount it at AUDIT_CORE_ATTESTATION_PATH. T01 is what makes this worth doing rather than cosmetic: the claim is now conditional, so a scheduled job actually restores it and a failed job actually degrades it.

Then T04 (heartbeat ingestion and missing-heartbeat findings), which is the control that matters for approval-engine and the one no amount of chain verification substitutes for. T05 stays deliberately held on the §17 Taxonomy cadence schema kings-guard is drafting — audit-core argued that obligation up to a MUST and inventing a competing shape ahead of the schema would fragment the interface it asked for.

Two things left open and worth a fresh session rather than a tired one. gate-house has circulated v0.8 and asked specifically whether §11's emission-guarantee declaration lets a source declare a transactional outbox and thereby imply completeness — which would reintroduce the residual audit-core raised in the first place. That is a real review and I did not start it. And AUDIT-WP-0010 (admitting tenant-engine as an attributive sender, AUDIT-IN-0002) is untouched; T03 landing means its completeness_trade now has somewhere to go.