Commit a6a1de4
fix(plugin-audit): read-audit reports once per CAUSE, not once per process (#18595)
Fixes #18247
`reportReadAuditWriteFailure`
(`packages/plugins/plugin-audit/src/read-audit.ts`) carried the THIRD
independent copy of one defect pair — the pair #15166 fixed in
`audit-writers.ts` and #17452 fixed in `auth-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 diff` adds no `export` in
`packages/`.
## The two defects, both live on a seam the repo already declared
durability-critical
1. **Its own process-level `failureReported` boolean.** 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 shipped `record_views` list view answers "who viewed this
record" with a confident, wrong, SHORT list.
2. **Its own fixed message literal**, printing the ADR-0057 §3.6 /
`OS_TELEMETRY_DB` datasource guidance unconditionally — so a fault with
nothing to do with datasource routing (an
`ERR_SYSTEM_WRITE_ORGANIZATION_REQUIRED` refusal, say) sent the operator
to check something that was working.
Its callee `persistReadAuditRows` is registered in
`DURABILITY_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
- The dedupe key is now `auditFailureCauseKey`, **imported** from
`audit-writers.ts` rather 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.
- The object dimension of the shared key is the **ledger**
(`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 property `auditFailureCauseKey`
exists to deny. The key therefore reduces to the driver's code
vocabulary: bounded by construction. (`audit-writers.ts` passes
`ctx.object` and `auth-event-audit.ts` its constant `sys_session`
because on those two seams the audited object IS single-valued per
write.)
- The first line now leads with `auditFailureCauseSummary(err, detail)`,
so the driver's code and message reach the operator instead of being
computed and dropped.
- The ADR-0057 §3.6 remedy is asked for through the shared
`isMissingTableError` predicate and printed for exactly the
missing-table cause it was written for. `persistReadAuditRows` writes
ONE table, so the question is asked about that one — unlike
`persistAuditTrailRow`, which writes the ledger row and its
`sys_activity` mirror 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
- The once-per-degradation anti-noise rule itself. A repeat of an
already-reported cause still degrades to `debug`, and AGENTS.md names
"log every failure at `error`" as this rule's falsifier.
- The `error`-then-`warn` sink fallback (#9657) — and its dedupe is
per-cause too, so a host that injected a logger without `error` hears
the second fault as well.
- `audit-writers.ts` is untouched: it is the source imported FROM.
- The rule that an audit failure never reaches the read.
## Evidence
### Tests — 7 new pins,
`packages/plugins/plugin-audit/src/read-audit.test.ts`
Four pin the defects, three are the discriminating controls that must
NOT move:
```
pnpm --filter @objectstack/plugin-audit test -> exit 0 24 files / 353 tests passed
pnpm --filter @objectstack/plugin-audit typecheck -> exit 0 (tsc, tsconfig.scripts.json, check:test-typecheck: 0 files / 0 errors)
```
### Ablation — the pins are capable of failing
Both defects put back (`git show` of the pre-fix blob onto the path,
proven on disk: `reportedReadAuditFailureCauses` 3 to 0, `missingTable`
2 to 0, `let failureReported = false;` 0 to 1, `SHORT answer. Fix:
confirm` 0 to 1; mutated blob `f202954d` vs HEAD blob `ae1e1b9a`), then:
```
Tests 4 failed | 29 passed (33)
x reports a SECOND, DIFFERENT cause at error - a new cause is a new degradation
AssertionError: expected [ { level: 'error', ... } ] to have a length of 2 but got 1
x carries the underlying code and message in the first line it prints
AssertionError: expected 'Read-audit write FAILED - 1 record-vi...' to match /ERR_SYSTEM_WRITE_ORGANIZATION_REQUIRED/
x prints the datasource remedy for the cause it is the remedy FOR, and not for others
AssertionError: expected 'Read-audit write FAILED - 1 record-vi...' not to match /OS_TELEMETRY_DB/
x [#9657] the `warn` fallback is per-cause too - a sink with no `error` hears the second fault
AssertionError: expected [ { level: 'warn', ... } ] to have a length of 2 but got 1
```
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 to `ae1e1b9a`, `git diff HEAD` for the path empty,
`git status --porcelain` clean. No ablation artifact is left in the
tree.
### Gates — `node scripts/pm/dispatch-gates.mjs --repo
objectstack-ai/objectstack --commands`, reconciled with `--ran`
63 derived families, all run with the exit code captured to disk before
reading; reconciliation reports `63 derived, 60 run, 3 NOT-MEASURED, 0
UNRUN`.
- **60 green.**
- **3 NOT MEASURED** — `check:dual-build-cjs-loads`, `check:i18n`,
`check:type-check-debt` all exited **3**, the code these gates use for
PREREQUISITE NOT MET: each needs a full `pnpm build` (57 packages have
no `dist/` 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 a `catch`
guarding 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-inputs` exited 1, and the finding is
not this diff's.** It names
`packages/cli/test/init-created-files-summary.e2e.test.ts` descending
`packages/spec/dist/` — no path of mine. Mechanism:
`coversDirectory`/the walked-root radius answer with `readdirSync`
against the real filesystem, and `packages/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 no
`packages/spec/dist` exits 0 (`OK: 29 package(s) read outside
themselves, all declared`). Reported below rather than ridden in.
### Lint — the whole population, not a narrowing
```
node --stack-size=4000 node_modules/eslint/bin/eslint.js . --no-inline-config --format json -> exit 0
files in eslint population: 6803 files with findings: 0
```
Run at `6195b00` (the final commit), on a clean tree.
## Acceptance notes
- **`reportOverflow` in 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 no `err`, 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:157`
stays out, and my reading agrees with the card's.** Its callee is a bare
`eng.insert` that no register names, its first line already carries
`Cause: ` plus the real detail, and its remedy text is already
cause-agnostic. An observation, not a contract violation.
- **`check:cross-package-test-inputs` reverses 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](https://claude.ai/code/session_01WmBwEiWPff9JZPd5BSGNeH)_
Co-authored-by: Claude <noreply@anthropic.com>1 parent ad067ad commit a6a1de4
3 files changed
Lines changed: 305 additions & 17 deletions
File tree
- .changeset
- packages/plugins/plugin-audit/src
Lines changed: 16 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
597 | 597 | | |
598 | 598 | | |
599 | 599 | | |
| 600 | + | |
| 601 | + | |
| 602 | + | |
| 603 | + | |
| 604 | + | |
| 605 | + | |
| 606 | + | |
| 607 | + | |
| 608 | + | |
| 609 | + | |
| 610 | + | |
| 611 | + | |
| 612 | + | |
| 613 | + | |
| 614 | + | |
| 615 | + | |
| 616 | + | |
| 617 | + | |
| 618 | + | |
| 619 | + | |
| 620 | + | |
| 621 | + | |
| 622 | + | |
| 623 | + | |
| 624 | + | |
| 625 | + | |
| 626 | + | |
| 627 | + | |
| 628 | + | |
| 629 | + | |
| 630 | + | |
| 631 | + | |
| 632 | + | |
| 633 | + | |
| 634 | + | |
| 635 | + | |
| 636 | + | |
| 637 | + | |
| 638 | + | |
| 639 | + | |
| 640 | + | |
| 641 | + | |
| 642 | + | |
| 643 | + | |
| 644 | + | |
| 645 | + | |
| 646 | + | |
| 647 | + | |
| 648 | + | |
| 649 | + | |
| 650 | + | |
| 651 | + | |
| 652 | + | |
| 653 | + | |
| 654 | + | |
| 655 | + | |
| 656 | + | |
| 657 | + | |
| 658 | + | |
| 659 | + | |
| 660 | + | |
| 661 | + | |
| 662 | + | |
| 663 | + | |
| 664 | + | |
| 665 | + | |
| 666 | + | |
| 667 | + | |
| 668 | + | |
| 669 | + | |
| 670 | + | |
| 671 | + | |
| 672 | + | |
| 673 | + | |
| 674 | + | |
| 675 | + | |
| 676 | + | |
| 677 | + | |
| 678 | + | |
| 679 | + | |
| 680 | + | |
| 681 | + | |
| 682 | + | |
| 683 | + | |
| 684 | + | |
| 685 | + | |
| 686 | + | |
| 687 | + | |
| 688 | + | |
| 689 | + | |
| 690 | + | |
| 691 | + | |
| 692 | + | |
| 693 | + | |
| 694 | + | |
| 695 | + | |
| 696 | + | |
| 697 | + | |
| 698 | + | |
| 699 | + | |
| 700 | + | |
| 701 | + | |
| 702 | + | |
| 703 | + | |
| 704 | + | |
| 705 | + | |
| 706 | + | |
| 707 | + | |
| 708 | + | |
| 709 | + | |
| 710 | + | |
| 711 | + | |
| 712 | + | |
| 713 | + | |
| 714 | + | |
| 715 | + | |
| 716 | + | |
| 717 | + | |
| 718 | + | |
| 719 | + | |
| 720 | + | |
| 721 | + | |
| 722 | + | |
| 723 | + | |
| 724 | + | |
| 725 | + | |
| 726 | + | |
| 727 | + | |
| 728 | + | |
| 729 | + | |
| 730 | + | |
| 731 | + | |
| 732 | + | |
| 733 | + | |
| 734 | + | |
| 735 | + | |
| 736 | + | |
| 737 | + | |
| 738 | + | |
| 739 | + | |
| 740 | + | |
| 741 | + | |
| 742 | + | |
| 743 | + | |
| 744 | + | |
| 745 | + | |
| 746 | + | |
| 747 | + | |
| 748 | + | |
| 749 | + | |
| 750 | + | |
| 751 | + | |
| 752 | + | |
| 753 | + | |
| 754 | + | |
| 755 | + | |
| 756 | + | |
| 757 | + | |
| 758 | + | |
| 759 | + | |
| 760 | + | |
| 761 | + | |
| 762 | + | |
| 763 | + | |
| 764 | + | |
| 765 | + | |
| 766 | + | |
| 767 | + | |
| 768 | + | |
| 769 | + | |
| 770 | + | |
| 771 | + | |
| 772 | + | |
| 773 | + | |
| 774 | + | |
| 775 | + | |
| 776 | + | |
| 777 | + | |
| 778 | + | |
| 779 | + | |
| 780 | + | |
| 781 | + | |
| 782 | + | |
| 783 | + | |
| 784 | + | |
| 785 | + | |
| 786 | + | |
| 787 | + | |
| 788 | + | |
| 789 | + | |
| 790 | + | |
| 791 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
88 | 88 | | |
89 | 89 | | |
90 | 90 | | |
| 91 | + | |
91 | 92 | | |
92 | 93 | | |
93 | 94 | | |
94 | 95 | | |
95 | 96 | | |
96 | | - | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
97 | 111 | | |
98 | 112 | | |
99 | 113 | | |
| |||
481 | 495 | | |
482 | 496 | | |
483 | 497 | | |
484 | | - | |
| 498 | + | |
| 499 | + | |
| 500 | + | |
| 501 | + | |
| 502 | + | |
| 503 | + | |
| 504 | + | |
| 505 | + | |
| 506 | + | |
| 507 | + | |
| 508 | + | |
| 509 | + | |
| 510 | + | |
| 511 | + | |
| 512 | + | |
| 513 | + | |
| 514 | + | |
| 515 | + | |
| 516 | + | |
| 517 | + | |
| 518 | + | |
| 519 | + | |
| 520 | + | |
| 521 | + | |
| 522 | + | |
| 523 | + | |
| 524 | + | |
| 525 | + | |
| 526 | + | |
| 527 | + | |
| 528 | + | |
| 529 | + | |
| 530 | + | |
| 531 | + | |
| 532 | + | |
| 533 | + | |
| 534 | + | |
| 535 | + | |
| 536 | + | |
| 537 | + | |
| 538 | + | |
| 539 | + | |
| 540 | + | |
| 541 | + | |
| 542 | + | |
485 | 543 | | |
486 | 544 | | |
487 | 545 | | |
488 | | - | |
489 | | - | |
| 546 | + | |
| 547 | + | |
| 548 | + | |
| 549 | + | |
| 550 | + | |
| 551 | + | |
| 552 | + | |
| 553 | + | |
| 554 | + | |
| 555 | + | |
| 556 | + | |
| 557 | + | |
| 558 | + | |
| 559 | + | |
| 560 | + | |
490 | 561 | | |
491 | 562 | | |
492 | | - | |
| 563 | + | |
| 564 | + | |
| 565 | + | |
| 566 | + | |
| 567 | + | |
493 | 568 | | |
494 | | - | |
495 | | - | |
496 | | - | |
497 | | - | |
498 | | - | |
499 | | - | |
500 | | - | |
501 | | - | |
502 | | - | |
503 | | - | |
504 | | - | |
505 | | - | |
| 569 | + | |
| 570 | + | |
| 571 | + | |
| 572 | + | |
| 573 | + | |
| 574 | + | |
| 575 | + | |
| 576 | + | |
| 577 | + | |
| 578 | + | |
| 579 | + | |
| 580 | + | |
| 581 | + | |
| 582 | + | |
| 583 | + | |
| 584 | + | |
| 585 | + | |
506 | 586 | | |
507 | 587 | | |
508 | 588 | | |
| |||
0 commit comments