fix: repair chained imports and track blocked evidence dependencies
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s

Assistant: codex
Assistant-Model: gpt-6.1-sol
Assistant-Session: 01a1046e-465c-7d43-8c4c-d10f0a1feadd
This commit is contained in:
tegwick 2026-10-04 11:36:06 +02:00
parent 28b567e588
commit 146f4e1dd3
21 changed files with 869 additions and 95 deletions

View file

@ -0,0 +1,166 @@
# Cross-repo evidence admission requirements
Reviewed 2026-10-04 against audit-core source, intakes, inbox and owner files.
Work: AUDIT-WP-0013 (senders), AUDIT-WP-0014 (execution receipts), and
AUDIT-WP-0015 (independent evidence). These are source and metadata commitments;
activation requires the relevant owner delivery and approved live change.
## Flex-auth decision records — AUDIT-WP-0013-T02/T03
Owner file: access-engine/workplans/FLEX-WP-0031-decision-record-emission.md,
reviewed at `257a83e997d7d30d3ace2b4524fab47cdfb7a6de`. T03's durable outbox
is done. Do not keep registration waiting on an outbox that already exists.
Audit-core accepts custody admission per AUDIT-IN-0006; it does not decide how
authorization effects are released.
Flex-auth must supply six final non-secret registration payloads and exact
namespace/pod ingress selectors: ops-warden, tenant-engine, user-engine,
secrets-engine, informed-decision-t03 and informed-decision-sitting. Each
identity permits its exact `flex-auth.<pin>` source, write true/read false,
`tenants: ["*"]` with the existing deny-before-tenant-creation justification,
load-bearing and explicitly selected `secret_policy: redact` in the protected
registry. No wildcard source match and no credential in the payload.
The emitter actually uses `flex-auth.decision.<effect>` (ClassPrefix in
internal/emission/outbox.go). Final heartbeat keys must therefore be
`flex-auth.decision.deny`, `.redact`, `.not_applicable`, `.audit_only`, each at
86400 seconds; shorthand effect names would monitor the wrong stored class.
Heartbeat wire type is `audit-core.heartbeat`, with `data.class` set to the
exact vouched-for class, independent non-empty correlation/id and offset-aware
occurred_at. Each per-pin canonical `emission_cadence` must name the exact
source and classes; flex-auth supplies allow's expected rate, window and
threshold. Audit-core will not choose them for the source. Counts compare
committed outbox entries, not all released decisions, by identical class/window.
The remaining custody owner is railiance-platform; ops-warden must supply a usable sender-mint routing entry.
The current audit-core-senders entry exists but is draft, unresolvable and
non-executable, names ops-mason, and describes an old in-cluster generation
path; it is not the reviewed attended per-pin mint lane. Correct/promote the
existing routing record or return a concrete owner lane, without creating a
second competing registry. Use `warden route` before any credential
request. An attended seed/delivery needs the founder's terminal and per-change
go. Then audit-core reviews the payloads, prepares exact scope/ingress,
and verifies accepted/duplicate writes, wrong source/tenant/read refusals,
reconciliation, per-class missing heartbeats and formerly-valid bearer
revocation. Source and owner revisions plus sanitized receipts are required.
Gate House GH-WP-0004-T12 owes the final decision for FLEX-DEC-2026-018.
Message `715cf1f2-55dd-4d2d-9b7b-c5dffa0181e5` is explicitly a reading, not
that decision: current text admits no load-bearing failure-path exception.
Audit-core retains the non-atomic/non-conformant description for the restrictive
classes released uncommitted. It will not repurpose attributive
`completeness_trade`, add a permissive schema field ahead of the ruling, or
claim atomicity. The final ruling blocks an exception/conformance declaration;
it does not block preparing transport custody for honestly described evidence.
## Qonto collector mapping — AUDIT-WP-0013-T04/T05
Accepted bounded receiver admission/mapping commitment for QONTO-WP-0006-T02.
Qonto source reviewed at `bf8a9ede6704523d4744d63bf481ca8b195d86f4`.
rapp-qonto owns the collector runtime; its finished RAPP-QONTO-WP-0003 is
historical rollout evidence, not current collector acceptance. qonto-assistant
owns the source declaration, class inventory, scope and process counters.
| Source class | Proposed receiver type | Required preserved metadata |
| --- | --- | --- |
| audit.allow | audit.allow | stream_id, stream_instance_id, stream_sequence, event_class and sanitized identity/egress context |
| audit.deny | audit.deny | same metadata; rare load-bearing, no expected-rate monitor |
| audit.heartbeat | audit-core.heartbeat | original event_class audit.heartbeat, timestamp/reason, instance and sequence, both source/window transition counts and window bounds |
The adapter must supply every field of docs/event-envelope.md. Stable event id
and Idempotency-Key must derive deterministically from the original instance
and sequence so retries cannot mint new accepted records. Tenant and subject
must come from an owner-approved binding, never inferred from a pod name or a
convenience tenant. Preserve the original heartbeat event_class in data; do
not rename Qonto's emitted class in its documentation.
A heartbeat can vouch for the quiet deny class through `data.class=audit.deny`.
Its assertion is no deny transitions in the window, not proof that there were
no allow transitions. qonto-assistant must approve that mapping and the exact
per-class heartbeat registration with its canonical declaration and actual
configured interval (default 86400). If another vouched-for class is needed,
review fan-out and sequence/count accounting explicitly. Sender-level
`evidence_kind` has one value: use the source owner's explicit load-bearing
admission for this mixed stream, retaining attributive allow and load-bearing
deny/heartbeat in the per-class canonical inventory. No blanket atomicity or
completeness claim follows from admission.
Audit-core now implements `GET /v1/reconciliation`'s optional
`stream_instance_id`, selecting exact stored `data.stream_instance_id` within
an authorized source/tenant and acceptance-time window. Duplicates count once;
restarts never share counts accidentally. This is source-tested and requires a
reviewed receiver release before live use. It does not turn source-time windows
into acceptance-time windows. The collector/observer must define bounded lag,
watermark/sequence boundaries and the comparable accepted-count derivation;
it must distinguish late arrival from lost evidence. Sender own counts are
available with read false. Independent kings-guard archive/sequence inspection
needs its separately scoped read identity/feed, not a writer upgraded to read
all tenants. No observer access scope is invented here.
Unblock inputs from rapp-qonto: collector source/image/config/Deployment pins,
metadata-only sample envelopes, exact namespace/pod selectors, authoritative
tenant binding, stable instance/restart/scale-to-zero semantics, retry/drop and
lag behavior, redacted success/failure receipts and actual accepted receiver
records. From qonto-assistant: reviewed mapping and per-class cadence,
classification, retention/access/export/secret policy. Audit-core then prepares
registration and checks acceptance, retries, retained instance/sequence,
wrong-scope refusals and accepted-count derivation. King's Guard KG-WP-0005-T03
subsequently owns independent missing-heartbeat, sequence-gap and count-divergence
acceptance. T02's sender proof never waits on Qonto T03 observer acceptance.
No bank keys or bank API traffic are needed for these receipts.
## Execution-attribution receipts — AUDIT-WP-0014
Proposal reviewed in net-kingdom/canon/standards/playbook-capability-contract_v0.2.md
at `45a189a39167eef62709df0e857309f7ddc59029` (NK-WP-0040-T02).
Audit-core accepts the evidence-holder role; Railiance is the proposed emitter
and must confirm its exact emitting component and sender source. NetKingdom
owns the canonical receipt schema and validator. Audit-core checks shape and
credential scope, stores the event and chains it. Custody does not authenticate
the historical actor chain or decide whether decision_refs authorize execution.
Before implementation, NetKingdom must return a versioned schema, artifact
path/digest, unknown/absent semantics and positive/negative fixtures. All listed
fields must be represented: initiator issuer/subject, optional delegation,
executing workload, runtime principal, tenant, environment, run id, source
commit, artifact digest, target, action, decision refs and time interval with
clock source and optional established precision bound. Unknown attribution stays
explicit `unknown`; delegation is absent when none; a time bound is absent when
not established. No bearer or role response may be embedded.
Railiance must supply the exact wire event class and sender, authoritative
scope, stable retry/id binding to run_id, classification, emission guarantee,
retention/access/export and secret policy, plus emitter revision and metadata
fixtures. Audit-core will wrap the receipt in its existing eight-field envelope,
validate the canonical data schema before accept, enforce declared tenant/source,
refuse malformed/secret content, retain original unknowns, and prove duplicate
and conflict behavior. Cross-check receipt tenant with envelope tenant; require
ordered offset-aware start/end if that is the canonical rule. No independent
local receipt schema is shipped ahead of the owner schema, and generic JSON
acceptance is not advertised as receipt-schema validation.
## Independent chain-head custody — AUDIT-WP-0015-T01
RESOURCE-WP-0002-T06 is done for encrypted logical database copies. That
terminal task is not an unfinished chain-head copy commitment. The 2026-09-15
AUDIT-WP-0009-T12 receipt proves the in-cluster attestor only.
resource-control and rapp-postgres must name the current offsite operator/owner
record, return a destination outside cluster and Barman failure domains on the
existing Nextcloud + age lane, and supply metadata-only upload and independent
readback receipts for the same chain-head digest, event id/count and observed_at.
State cadence, freshness/failure handling, access separation and retrieval test.
No Nextcloud credential belongs in audit-core. Without this return, the control
remains a database-owner boundary and carries no cluster-owner survival claim.
## Refreshed E2 and asm-t08 — AUDIT-WP-0015-T02
Whitehat commit `7b5bc1e444cf57ae5d9ba1dac0c0189c0833e278` and message
`81bebdd8-5ca7-47b3-aafe-1d00ff8be122` leave WHITEHAT-WP-0006-T03 wait.
The 2026-08-22 E2 run is historical and its -01/-02/-03 engagement ids are
terminal. Whitehat is not requesting a run. A future run needs a distinct
engagement, plane admission and rules-of-engagement approvals; audit-core
creates none of those during this review. The alternative is access-engine's
confirmation of a flex-auth tenant-A identity with no tenant-B authority.
asm-t08's surface owner owes component revision, identities, window and technique.
These remain owner inputs; no field or authorization is fabricated. Original
AUDIT-WP-0008-T05 remains done for its historical accepted scope.

View file

@ -22,8 +22,13 @@ duplicate accept must not mint a second link. A conflict must not
advance the head. Concurrent first-accepts of different events take an
advisory lock (`CHAIN_LOCK_KEY`) so they cannot fork.
Verify walks `ORDER BY accepted_at, event_id`, recomputes every link,
and reports the first event id whose stored hashes do not match. A
PostgreSQL verification follows `chain_prev` from genesis, recomputes every
link, and retains orphaned or forked rows as breaks. The next append selects
the unique unreferenced head under the same advisory lock. This separates
chain insertion order from historical `accepted_at`: importing older SQLite
events extends the existing chain without rewriting timestamps or links.
SQLite's development chain still walks `ORDER BY accepted_at, event_id`.
Verification reports the first event id whose stored hashes do not match. A
break is a **custody defect**, not a sender error. Retrying the event
will not repair it.

View file

@ -290,3 +290,27 @@ The historical SQLite importer has five PostgreSQL test failures tracked by
AUDIT-IN-0004. It is outside the live receiver path; do not use it as a recovery
procedure until that record closes. The release test result must state this
exclusion explicitly, never describe the entire suite as green.
### Historical SQLite import
`python -m audit_core migrate-store --from-sqlite <file> --source-id <stable-id>`
uses PostgreSQL custody with schema migration `0007-import-receipts`. Run this
with the migration owner's database authority, not a runtime writer identity.
Keep `source-id` unchanged across file relocation and retries; its default is
the absolute file path. Replacing a source at that path requires a new identity.
No source content is copied into the receipt table: it holds hashed source and
row identities, content fingerprints and previously imported finding counts.
Import reads one SQLite snapshot and writes under the native append advisory
lock. Historical events extend the stored link chain with original IDs, hashes,
records and exact acceptance timestamps. Existing differing evidence is reported
as a conflict and retained; no trigger is disabled and no existing chain link
is rewritten. Retries skip imported dead letters and add only the incremental
source finding count. Decreasing counts or changed historical dead letters are
conflicts. Verification checks events and the resulting chain independently.
The new PostgreSQL receiver and attestor follow links rather than sorting by
historical timestamps. Deploy that code to all PostgreSQL append/verify workers
before importing historical rows into a populated destination; older workers
still use timestamp order. Source tests do not authorize a production import.

View file

@ -114,6 +114,19 @@ The response carries a `means` field stating that agreement proves neither
completeness nor that any event occurred — because this is the number most
likely to be quoted out of context in someone else's conformance argument.
The optional `stream_instance_id` query parameter restricts counts to the exact
string stored at `details.data.stream_instance_id`. The response echoes that
filter; no filter preserves the existing aggregate response. Empty or repeated
instance parameters are refused. Source and tenant authorization still apply,
and duplicate deliveries count once. Both SQLite and PostgreSQL implement it.
These are **acceptance-time** windows (`since` inclusive, `until` exclusive),
not the producer's occurrence-time or sequence windows. A collector/observer
must account for drain lag before comparing source-window counters, retain
`stream_sequence`, and never compare a restarted process's reset counters with
another instance or a lifetime total. Instance isolation alone does not detect
sequence gaps or determine whether an instance is active.
## Declared emission cadence
`AUDIT-WP-0009-T05`. The contract is info-tech-canon's EmissionCadence