Repository navigation
fix(plugin-audit): read-audit reports once per CAUSE, not once per process - #18595
Conversation
…ocess `reportReadAuditWriteFailure` carried its own process-level `failureReported` boolean and its own fixed message literal — the third independent copy of the pair #15166 fixed in `audit-writers.ts` and #17452 fixed in `auth-event-audit.ts`, on a seam already registered in `DURABILITY_CRITICAL_CALLEES`. The dedupe key is now `auditFailureCauseKey`, imported rather than re-spelled (a second copy of the key is how this defect reached the second file), and the ADR-0057 §3.6 / `OS_TELEMETRY_DB` guidance is printed for the missing-table cause it is the remedy for, asked through the shared `isMissingTableError` predicate. Claude-Session: https://claude.ai/code/session_01WmBwEiWPff9JZPd5BSGNeH Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 7 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 4 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 9 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 5bfd82d87d854fd6607d7c7ba09bc61062893f20 && git checkout 5bfd82d87d854fd6607d7c7ba09bc61062893f20
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin e0d05538c0d0275728d25acfafbab259e23d0710 6195b0000124648378e738018ae74704acde21ed && git checkout -B drift-repro e0d05538c0d0275728d25acfafbab259e23d0710 && git merge --no-ff 6195b0000124648378e738018ae74704acde21ed
node scripts/docs-audit/affected-docs.mjs --json e0d05538c0d0275728d25acfafbab259e23d0710
|
Fixes #18247
reportReadAuditWriteFailure(packages/plugins/plugin-audit/src/read-audit.ts) carried the THIRD independent copy of one defect pair — the pair #15166 fixed inaudit-writers.tsand #17452 fixed inauth-event-audit.ts. This PR removes the duplicate; it does not write a fourth implementation. The two helpers #18246 exported for exactly this purpose are imported.Clause-②: no — no export is added, no error code is added, and nothing is relaxed. Two already-exported helpers are imported and one existing message literal becomes conditional;
git diffadds noexportinpackages/.The two defects, both live on a seam the repo already declared durability-critical
failureReportedboolean. The first failure of ANY cause silenced every later failure of every OTHER cause for the life of the process. Record-view rows are written from a BUFFER off the request path, so there is no in-flight request left to notice, and the shippedrecord_viewslist view answers "who viewed this record" with a confident, wrong, SHORT list.OS_TELEMETRY_DBdatasource guidance unconditionally — so a fault with nothing to do with datasource routing (anERR_SYSTEM_WRITE_ORGANIZATION_REQUIREDrefusal, say) sent the operator to check something that was working.Its callee
persistReadAuditRowsis registered inDURABILITY_CRITICAL_CALLEES(scripts/check-durability-degradation-log-level.mjs), so the repo has already declared this write durability-critical. A reporter that switches itself off after one cause is exactly the failure that declaration cannot afford.What changed
auditFailureCauseKey, imported fromaudit-writers.tsrather than re-spelled — a second copy of the key is how this defect reached the second file. The counting unit is a DEGRADATION, and a second cause is a second degradation.sys_audit_log), not the viewed object. One flush is ONE write carrying rows about MANY audited objects, so there is no single viewed object to name, and picking whichever landed first in the batch would make the key depend on traffic — the one propertyauditFailureCauseKeyexists to deny. The key therefore reduces to the driver's code vocabulary: bounded by construction. (audit-writers.tspassesctx.objectandauth-event-audit.tsits constantsys_sessionbecause on those two seams the audited object IS single-valued per write.)auditFailureCauseSummary(err, detail), so the driver's code and message reach the operator instead of being computed and dropped.isMissingTableErrorpredicate and printed for exactly the missing-table cause it was written for.persistReadAuditRowswrites ONE table, so the question is asked about that one — unlikepersistAuditTrailRow, which writes the ledger row and itssys_activitymirror and asks about both. Every other cause now gets the driver's own verdict plus the fix that matches it.What deliberately did NOT change
debug, and AGENTS.md names "log every failure aterror" as this rule's falsifier.error-then-warnsink fallback (check-durability-degradation-log-level:loggerLevelcannot see the(logger.error ?? logger.warn)(…)fallback, so a loud catch reads assilent-swallow— and the spelling it CAN see prints nothing #9657) — and its dedupe is per-cause too, so a host that injected a logger withouterrorhears the second fault as well.audit-writers.tsis untouched: it is the source imported FROM.Evidence
Tests — 7 new pins,
packages/plugins/plugin-audit/src/read-audit.test.tsFour pin the defects, three are the discriminating controls that must NOT move:
Ablation — the pins are capable of failing
Both defects put back (
git showof the pre-fix blob onto the path, proven on disk:reportedReadAuditFailureCauses3 to 0,missingTable2 to 0,let failureReported = false;0 to 1,SHORT answer. Fix: confirm0 to 1; mutated blobf202954dvs HEAD blobae1e1b9a), then:The three controls stayed GREEN on both sides, which is the half that matters: "still degrades a REPEAT of an already-reported cause to debug", "keys on the error CODE, never its message" (200 batches, 200 distinct messages, one code, one line) and "folds a fault carrying NO code into ONE bucket". Deleting the dedupe outright would redden those three.
Restore leg:
git checkout HEAD -- PATH(naming HEAD, never a bare checkout), blob back toae1e1b9a,git diff HEADfor the path empty,git status --porcelainclean. No ablation artifact is left in the tree.Gates —
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands, reconciled with--ran63 derived families, all run with the exit code captured to disk before reading; reconciliation reports
63 derived, 60 run, 3 NOT-MEASURED, 0 UNRUN.check:dual-build-cjs-loads,check:i18n,check:type-check-debtall exited 3, the code these gates use for PREREQUISITE NOT MET: each needs a fullpnpm build(57 packages have nodist/in this worktree, which built only plugin-audit's dependency closure). CI builds, so CI measures them. Not read as green and not as red.pnpm check:durability-log-level— run although this card's derivation does not name it, because the diff sits in acatchguarding a registered durability-critical callee. Green:36 durability-critical catch seam(s), all loud, rethrowing or propagating to the caller. The gate has no objection to this change.pnpm check:cross-package-test-inputsexited 1, and the finding is not this diff's. It namespackages/cli/test/init-created-files-summary.e2e.test.tsdescendingpackages/spec/dist/— no path of mine. Mechanism:coversDirectory/the walked-root radius answer withreaddirSyncagainst the real filesystem, andpackages/spec/dist/is a gitignored build artifact that exists here only because the dependency-closure build created it. Control: the same gate on a checkout with nopackages/spec/distexits 0 (OK: 29 package(s) read outside themselves, all declared). Reported below rather than ridden in.Lint — the whole population, not a narrowing
Run at
6195b00(the final commit), on a clean tree.Acceptance notes
reportOverflowin the same file was examined and is NOT this class. Its report has no cause dimension at all — the buffer overflowing is one condition, it takes noerr, and its remedy text is already cause-agnostic. A cause key there would key on nothing. Noted, not filed; successor: whoever next touches this batcher.packages/services/service-settings/src/config-change-audit.ts:157stays out, and my reading agrees with the card's. Its callee is a bareeng.insertthat no register names, its first line already carriesCause:plus the real detail, and its remedy text is already cause-agnostic. An observation, not a contract violation.check:cross-package-test-inputsreverses its verdict on a gitignored build artifact (see Gates above) — reported to the PM with dedupe words for the filing seat, not filed from here and not fixed here.Generated by Claude Code