Proposed (STA-821). It amends
0212 open question 1 (a failed
read audit never blocks the read). Linear STA-821.
Context
AccessLogService always failed open: a read was served when its access record could not be written. That is right
for most personal data, but a financial or health read served unlogged breaks the evidence a regulator asks for. The
read audits also stayed in each service’s own table, so the audit service never saw them.
Decision
- Fail closed by class, as configuration.
AccessLogConfig.failClosedlists the data classes whose read is refused (access_log_write_failed, retryable) when its record cannot be written; the default isfinancialandhealth(the regulated special category). A market pack or deployment changes the list in runway. - A privileged surface fails closed whatever its class:
@AccessLogged({ failClosed: true })on break-glass, impersonation or staff export reads. - The record decides before the value leaves: an async method resolves only after its record is written. A synchronous method cannot be held back; a fail-closed read is written async.
- An
outboxsink.OutboxAccessLogSinkDriversends each record asAccessRecorded(access_record.recorded) through the outbox; the audit service chains and stores it. - One canonical hash. The chain hash is
@stackra/support’scanonicalHash(ADR 0416), byte for byte the earlier form, so stored chains still verify.
Consequences
- A sink outage now refuses financial and health reads; operators alert on
access_log_write_failed. - Services that report to the audit service set
sink: outbox.
Enforcement
packages/security/access-log/src/core/services/__tests__/access-log.service.test.ts,
packages/security/access-log/src/core/drivers/__tests__/outbox-access-log-sink.driver.test.ts,
packages/security/access-log/src/core/functions/__tests__/hash-access-link.function.test.ts (pinned hash).