docs(capture): make Q4 audit-sink-down posture classification-aware (#49)
CI / Lint / Test / Vet (push) Successful in 12s
CI / Mirror to GitHub (push) Successful in 3s

Reconsidered Q4: instead of one global degrade-and-warn, the posture now
inherits from effective classification (Q1):
- confidential + audit-sink-down -> hard-refuse (no buffer; removes the
  buffer-integrity question for confidential data)
- internal/public + audit-sink-down -> degrade-and-warn + durable local
  buffer + ntfy + reconcile-on-recovery
- floor (all tiers): refuse if nothing can record the audit
Updated the I5 Gherkin scenarios + obligations row to match. Couples Q4
to the Q1 classification spine -> one coherent sensitivity model.
This commit is contained in:
mathias
2026-06-22 20:20:40 +00:00
parent b7a2cc5fdf
commit 2a595b5a92
+37 -18
View File
@@ -47,7 +47,7 @@ beyond the I5 audit log.
| **I2 deliberate acceptance** | The *distributed-library* form opens no new acceptance. IF a central relay node is deployed, its cross-harness reach MUST be entered in `infra/docs/security-baseline.md` with Why-accepted / Revisit-if before it ships. | | **I2 deliberate acceptance** | The *distributed-library* form opens no new acceptance. IF a central relay node is deployed, its cross-harness reach MUST be entered in `infra/docs/security-baseline.md` with Why-accepted / Revisit-if before it ships. |
| **I3 GitOps reconcilability** | IF `capture` runs as a deployed service, its manifest lives under `infra/k3s/apps/**` (sovereign source, Flux-reconciled). No untracked runtime. | | **I3 GitOps reconcilability** | IF `capture` runs as a deployed service, its manifest lives under `infra/k3s/apps/**` (sovereign source, Flux-reconciled). No untracked runtime. |
| **I4 decisions captured** | The distributed-vs-central decision and the intent-named-verb pattern are recorded (ADR + brain). | | **I4 decisions captured** | The distributed-vs-central decision and the intent-named-verb pattern are recorded (ADR + brain). |
| **I5 auditability** | Every capture emits a request-level audit record to the alloy/loki substrate: actor/principal, harness, items written, timestamp. | | **I5 auditability** | Every capture emits a request-level audit record (actor/principal, harness, items written, timestamp) to the alloy/loki substrate. **Classification-aware degradation** (§4.4): confidential + sink-down → hard-refuse; internal/public + sink-down → durable local buffer + ntfy + reconcile. Floor: refuse if nothing can record the audit. |
--- ---
@@ -147,24 +147,33 @@ Feature: Capture session value uniformly across harnesses
Then the would-be receipt is returned Then the would-be receipt is returned
And nothing is written anywhere And nothing is written anywhere
# --- I5: auditability is non-optional, but degrades rather than blocks --- # --- I5: auditability is classification-aware (confidential fails closed) ---
Scenario: Capture proceeds under a durable local audit buffer when the central sink is down Scenario: Confidential capture hard-refuses when the central audit sink is down
Given the central audit substrate (loki) cannot be written to Given the effective classification is "confidential"
And the central audit substrate (loki) cannot be written to
When capture is invoked
Then the capture is refused before any write
And the reason names the auditability invariant
# Confidential work must be centrally auditable at write time — no buffered exception.
Scenario: Internal capture degrades to a durable local buffer when the sink is down
Given the effective classification is "internal" or "public"
And the central audit substrate (loki) cannot be written to
When capture is invoked When capture is invoked
Then the capture proceeds Then the capture proceeds
And the audit record is written to a durable LOCAL fallback buffer And the audit record is written to a durable LOCAL fallback buffer
And an ntfy alert is emitted naming the degraded audit state And an ntfy alert is emitted naming the degraded audit state
And the receipt flags that audit was buffered locally, not centrally recorded And the receipt flags that audit was buffered locally, not centrally recorded
# Never UN-audited: the write is buffered durably and reconciled on recovery.
Scenario: Locally buffered audit records reconcile to the central sink on recovery Scenario: Locally buffered audit records reconcile to the central sink on recovery
Given audit records were buffered locally during a sink outage Given internal-tier audit records were buffered locally during a sink outage
When the central audit substrate becomes reachable again When the central audit substrate becomes reachable again
Then the buffered records are replayed to the central sink Then the buffered records are replayed to the central sink
And the local buffer is cleared only after confirmed central write And the local buffer is cleared only after confirmed central write
Scenario: Capture refuses if even the local audit buffer cannot be written Scenario: Even internal capture refuses if neither sink nor local buffer can be written
Given neither the central sink nor the local fallback buffer can be written Given the effective classification is "internal" or "public"
And neither the central sink nor the local fallback buffer can be written
When capture is invoked When capture is invoked
Then the capture is refused Then the capture is refused
And the reason names the auditability invariant And the reason names the auditability invariant
@@ -211,13 +220,23 @@ binding design decisions for the build.
reach **must be entered in `infra/docs/security-baseline.md`** with Why-accepted / Revisit-if reach **must be entered in `infra/docs/security-baseline.md`** with Why-accepted / Revisit-if
**before it ships** (I2). That ledger entry is v1 work, not a follow-up. **before it ships** (I2). That ledger entry is v1 work, not a follow-up.
4. **Audit-sink-down — degrade-and-warn, with a durable local buffer + reconcile-on-recovery.** 4. **Audit-sink-down — classification-aware: confidential fails closed, internal/public degrades.**
If the central audit sink (loki) is unreachable, capture **proceeds** rather than blocking The posture inherits from the effective classification (decision 1), so there is one coherent
(availability), BUT the audit record is written to a **durable local fallback buffer** and an sensitivity model rather than a separate availability policy:
**ntfy alert** fires. Buffered records **reconcile to the central sink on recovery**; the local - **Confidential + central audit sink unreachable → hard-refuse.** No buffer, no proceed.
buffer clears only after confirmed central write. **Floor:** if *neither* the central sink nor the Confidential work must be centrally auditable *at write time*; "buffer and reconcile later"
local buffer can be written, capture **refuses** — degrade-and-warn never means *un*-audited. introduces a buffer-integrity question (can a write tamper with its own pending audit record?)
- Rationale: keeps capture available during an observability outage while preserving I5 — the that must not exist for confidential data. The simplicity of "refuse" is itself the assurance
write is always recorded *somewhere durable*, just not centrally until reconciliation. This is asset — trivially true, nothing to poke holes in.
the difference between an I5 violation and an I5-compliant degraded mode, and it must be - **Internal / public + central sink unreachable → degrade-and-warn** with a durable local buffer
presentable as such to a due-diligence client. + ntfy alert + reconcile-on-recovery (the earlier Q4 design, now scoped to lower tiers). Keeps
capture available for your own homelab work during an observability outage; negligible risk
since the buffered record is still durable and the data isn't client-confidential.
- **Floor (all tiers):** if *nothing* — neither central sink nor (for internal/public) the local
buffer — can record the audit, capture **refuses**. No tier writes wholly un-audited.
- Rationale: matches assurance cost to data sensitivity, exactly as the I1/sovereignty model
does for placement. Presentable to a due-diligence client as "audit posture is
classification-aware: confidential fails closed, internal degrades gracefully" — which
demonstrates the judgment, not just a binary. Couples Q4 to Q1's classification machinery
(being built anyway) and removes the buffer-integrity rabbit hole for the only tier where it
mattered.