Skip to content

fix(plugin-audit): key the lost auth-event row report per CAUSE, and name the real cause in its first line - #18246

Merged
claude[bot] merged 3 commits into
mainfrom
claude/issue-17452-auth-event-audit-second-copy
Sep 15, 2026
Merged

claude[bot] merged 3 commits into
mainfrom
claude/issue-17452-auth-event-audit-second-copy

Conversation

@claude

@claude claude Bot commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Fixes #17452

Clause-②: no

What this is

packages/plugins/plugin-audit/src/auth-event-audit.ts — the writer behind the login / logout rows of the compliance ledger — carried a second, independent copy of both defects that #15166 removed from audit-writers.ts:

  1. its own process-level failureReported boolean, so after the first failure of ANY cause, every later failure of every OTHER cause degraded to debug for the life of the process;
  2. its own fixed message literal, printing the ADR-0057 §3.6 / OS_TELEMETRY_DB datasource guidance unconditionally, regardless of what actually failed.

persistAuthEventAuditRow is registered in DURABILITY_CRITICAL_CALLEES (scripts/check-durability-degradation-log-level.mjs:361), whose entire purpose is that durability loss is reported at error. That register is the declaration this restores — ⛔ it is not edited here.

Separation re-verified before choosing the approach

The dispatch asked whether the two writers have converged since #15166. Measured on origin/main at b3b43b6ea (this branch's base), they had not:

audit-writers.ts auth-event-audit.ts (before)
reporter reportAuditWriteFailure reportAuthEventWriteFailure
dedupe state reportedAuditFailureCauses (a Set) failureReported (a boolean)
message cause-led, conditional remedy one fixed literal
logger (engine as any).logger injected AuthEventAuditLogger, error OPTIONAL
tables written sys_audit_log + sys_activity sys_audit_log only

So the fix lands in place rather than merging two structurally different reporters. What is shared is the part the card called a port: the cause-key helpers.

What changed

  • audit-writers.ts — auditFailureCauseKey and auditFailureCauseSummary become module exports so the auth-event sink can use them. ⛔ Not added to src/index.ts: the sharing is internal to the package. This mirrors the existing createFieldPresenceProbe import that already crosses these two files.
  • auth-event-audit.ts — the boolean becomes reportedAuthEventFailureCauses, a Set keyed by auditFailureCauseKey(SESSION_OBJECT, err). The first error line leads with auditFailureCauseSummary(err, detail) — the code and message that were already computed one line above the branch and passed only into the debug payload. The datasource guidance is kept and made conditional on isMissingTableError(err, 'sys_audit_log'), asked for the one table this writer writes.
  • auth-event-audit.test.ts — 7 new cases (see below).
  • A patch changeset.

⛔ Importing the helpers rather than re-spelling them is deliberate: a second copy of this key is how the defect reached this file, so a third spelling would be the same mistake again.

Evidence

Reproduce first — the new block against the unfixed source

Commit 3985dd2bb is the pin block alone, on top of unmodified source. pnpm --filter @objectstack/plugin-audit exec vitest run src/auth-event-audit.test.ts:

Tests  4 failed | 10 passed (14)

FAIL  reports a SECOND, DIFFERENT cause at error — a new cause is a new degradation
  AssertionError: expected [ { level: 'error', …(2) } ] to have a length of 2 but got 1

FAIL  prints the datasource remedy for the cause it is the remedy FOR, and not for others
  AssertionError: expected 'Auth-event audit write FAILED — the c…' not to match /OS_TELEMETRY_DB/
  Received: "… Fix: confirm `sys_audit_log` is reachable … Set `OS_TELEMETRY_DB=0` …"
  (the cause driven through the reporter was ERR_SYSTEM_WRITE_ORGANIZATION_REQUIRED)

Both defects the card names, reproduced through the real reporter: a second cause silenced to debug, and the datasource hint printed for a failure it is not the remedy for.

After the fix

Tests 14 passed (14). Whole package: Test Files 23 passed (23) · Tests 341 passed (341); pnpm --filter @objectstack/plugin-audit typecheck exit 0 (check:test-typecheck: OK — 0 file(s) / 0 error(s)).

Reverse verification — three legs, mutated on disk, each restored byte-identically

Every leg proves the mutation landed (grep -c on the anchor text and on the injected text, plus the on-disk blob hash moving), runs the suite, then restores with git checkout HEAD -- path and proves git hash-object equals the HEAD blob and git diff HEAD is empty. HEAD blob ba07853195ca57faf56734d32e005a836cd0aae2 before and after all three. No build/dist leg is owed: the suite imports ./auth-event-audit.js relatively and vitest.config.ts aliases @objectstack/types to source, so nothing on the tested path resolves through dist.

leg mutation anchor→inject result
A cause key collapsed to one constant bucket (= the old boolean) 1→0 / 0→1, hash 31b8e4c… 2 failed — reports a SECOND, DIFFERENT cause at error, [#9657] the warn fallback is per-cause too
B missingTable forced to true (= the old unconditional hint) 1→0 / 0→1, hash 13edd5b… 1 failed — prints the datasource remedy for the cause it is the remedy FOR, and not for others
C dedupe removed entirely (the named falsifier) 1→0 / 0→1, hash 7d998a1… 4 failed — the three anti-noise controls, plus the pre-existing #8144 reported at ERROR, once case

The discriminating control for a per-cause dedupe

Leg C is the point. "A different cause now reports" is satisfied by simply deleting the boolean, which is the outcome AGENTS.md names as this rule's falsifier. Three cases hold the other half, and they stay green under leg A (12 of 14 passed there, and none of the three is in that leg's FAIL list) while going red under leg C:

  • still degrades a REPEAT of an already-reported cause to debug — 5 sign-ins, same cause ⇒ 1 error + 4 debug, and the debug lines carry the same cause key.
  • keys on the error CODE, never its message, so a per-row fault cannot flood error — 200 sign-ins, 200 distinct per-row messages, one code ⇒ 1 error.
  • folds a fault carrying NO code into ONE bucket rather than growing one — 200 sign-ins, no code at all ⇒ 1 error.

⇒ the delivered behaviour is cause-keyed dedupe, not no dedupe. The premise #15166's ruling hung on — that cause-keying does not reintroduce #4420's unreadable flood — holds on this seam too, and for a stronger reason: the object dimension is constant here (sys_session), so the key reduces to the driver's own closed code vocabulary.

Clause-② re-derived from the DELIVERED diff

src/index.ts is untouched (git diff origin/main --stat on it is empty), and tsup builds the single entry src/index.ts.

  • .d.ts export list of the published entry (dist/index.d.ts): 27 names, neither auditFailureCauseKey nor auditFailureCauseSummary among them.
  • runtime probe, name in await import('dist/index.mjs') (the path exports["."] names):
auditFailureCauseKey          not reachable
auditFailureCauseSummary      not reachable
createAuthEventAuditSink      REACHABLE       [POSITIVE CONTROL]
installAuditWriters           REACHABLE       [POSITIVE CONTROL]
thisSymbolDoesNotExist        not reachable   [NEGATIVE CONTROL]
total runtime exports: 11

⇒ Clause-②: no, derived by reachability rather than by the word export. The changeset is graded patch.

Gate denominator

node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 65 families at HEAD d20bd8765. All 65 were run with exit codes recorded before any pipe, and reconciled:

Run reconciliation — 65 derived, 65 run, 0 NOT-MEASURED, 0 UNRUN.
  EXIT CODES — all 65 accounted famil(ies) carry one, so the NOT-MEASURED count above is DERIVED from them.
✓ dispatch-gates --ran: 65 derived famil(ies) accounted for — 65 run, 0 NOT-MEASURED
  (a DERIVED zero — all 65 recorded an exit code and none of them is 3).

Three first answered exit 3, PREREQUISITE NOT MET — check:i18n, check:dual-build-cjs-loads, check:type-check-debt. ⛔ Not read as green: their prerequisite closures were built (turbo run build over the i18n gate's named closure, then over ./packages/* ./packages/*/*) and all three re-run to exit 0 — check-i18n-bundles: OK (9 package(s) — all bundles in sync…) and check-type-check-coverage --re-measure: OK — 5 ledger entr(ies) re-measured, 55 raw tsc error(s) total, none above its recorded number.

Outside the derived set and run anyway because it is the declaration this card restores: pnpm check:durability-log-level exit 0 — ✓ durability-degradation log levels: 36 durability-critical catch seam(s), all loud….

eslint — a declared narrowing, with its three readings

Repo-wide pnpm lint is CI's run. Narrowed here to the three source files this diff touches, at HEAD d20bd8765:

① Population read from eslint's own eslint.config.mjs, not guessed: the base block is files: ['**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}'] with further packages/**/*.{ts,tsx,mts,cts} blocks — all three files are inside both.
② Count read from --format json: 3 files linted, 0 errors, 0 warnings, exit 0.
③ Invariance over untouched files: eslint.config.mjs states at its own line 328 that this repo "runs one eslint.config.mjs, which never enables type-aware linting (no parserOptions.project, no typed @typescript-eslint rules) for ANY file" — measured there with a positive control. With no cross-file type program, this diff cannot move the verdict on any file it does not touch.

Acceptance notes

Out of scope for this PR, ⛔ not fixed here, and ⛔ not filed either — the dispatch reserved filing to the PM:

Notes for review


Generated by Claude Code

…roduction, currently RED

Drives two failures of DIFFERENT causes through `reportAuthEventWriteFailure`
and shows the second degrades to `debug`, and shows the ADR-0057 §3.6 /
`OS_TELEMETRY_DB` datasource hint printing for an organization refusal it is
not the remedy for.

4 of 14 fail on the unfixed source. The three that pass — repeat-of-same-cause
dedupe, code-keyed (never message-keyed) bounding, and the no-code fold — are
the discriminating controls: a process-wide boolean satisfies them too, so
they constrain the fix rather than demonstrate the defect.

Claude-Session: https://claude.ai/code/session_01URLHobLUJB9K1ABV6ofdjj
Co-authored-by: Claude <noreply@anthropic.com>
…name the real cause in the first line

`auth-event-audit.ts` carried a second, independent copy of both defects
#15166 fixed in `audit-writers.ts`: its own process-level `failureReported`
boolean, so after the first failure of ANY cause every later failure of every
OTHER cause degraded to `debug` for the life of the process; and its own fixed
message literal, printing the ADR-0057 §3.6 / `OS_TELEMETRY_DB` datasource
guidance unconditionally, regardless of what actually failed.

The dedupe key is now the failure's identity — the error `code` (or its
absence) plus the object — and the helpers are `audit-writers.ts`'s, imported
rather than re-spelled: a second copy of the key is how the defect reached
this file, so a third spelling would be the same mistake again. A repeat of an
already-reported cause still degrades to `debug`; a new cause reports at
`error`, once.

The datasource guidance is kept and made conditional on the missing-table
cause it is the remedy for, asked through the shared `isMissingTableError`
predicate for the one table `persistAuthEventAuditRow` writes.

Claude-Session: https://claude.ai/code/session_01URLHobLUJB9K1ABV6ofdjj
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/plugin-audit, touching 4 documentable anchor(s).

6 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/deployment/production-readiness.mdx (via sys_audit_log (literal, a string literal in createAuthEventAuditSink))
  • content/docs/kernel/runtime-services/audit-service.mdx (via sys_audit_log (literal, a string literal in createAuthEventAuditSink))
  • content/docs/permissions/record-view-auditing.mdx (via sys_audit_log (literal, a string literal in createAuthEventAuditSink))
  • content/docs/plugins/packages.mdx (via sys_audit_log (literal, a string literal in createAuthEventAuditSink))
  • content/docs/protocol/kernel/config-resolution.mdx (via sys_audit_log (literal, a string literal in createAuthEventAuditSink))
  • content/docs/ui/setup-app.mdx (via sys_audit_log (literal, a string literal in createAuthEventAuditSink))

⛔ 4 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/index.mdx (via sys_audit_log (literal, a string literal in createAuthEventAuditSink))
  • content/docs/releases/v14.mdx (via sys_audit_log (literal, a string literal in createAuthEventAuditSink))
  • content/docs/releases/v17/17-0.mdx (via sys_audit_log (literal, a string literal in createAuthEventAuditSink))
  • content/docs/releases/v17/17-1.mdx (via sys_audit_log (literal, a string literal in createAuthEventAuditSink))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • the SDK route bridge reached 60 of 215 client-bound route-ledger rows — the other 155 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 155: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 100 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 9 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json b3b43b6ea0c30f35d861c9cc0fedc9c085027b7b → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 0c7232a313ed46bc8a53ceb304d4eb29bd966f74 — the merge of head d20bd87650185735e45342e52a63686bf7f94118 into base b3b43b6ea0c30f35d861c9cc0fedc9c085027b7b, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 0c7232a313ed46bc8a53ceb304d4eb29bd966f74 && git checkout 0c7232a313ed46bc8a53ceb304d4eb29bd966f74
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin b3b43b6ea0c30f35d861c9cc0fedc9c085027b7b d20bd87650185735e45342e52a63686bf7f94118 && git checkout -B drift-repro b3b43b6ea0c30f35d861c9cc0fedc9c085027b7b && git merge --no-ff d20bd87650185735e45342e52a63686bf7f94118

node scripts/docs-audit/affected-docs.mjs --json b3b43b6ea0c30f35d861c9cc0fedc9c085027b7b

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs b3b43b6ea0c30f35d861c9cc0fedc9c085027b7b → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Sep 15, 2026
@claude
claude Bot marked this pull request as ready for review September 15, 2026 03:32
@claude
claude Bot enabled auto-merge September 15, 2026 03:33
@claude
claude Bot added this pull request to the merge queue Sep 15, 2026
Merged via the queue into main with commit cb648cb Sep 15, 2026
36 checks passed
@claude
claude Bot deleted the claude/issue-17452-auth-event-audit-second-copy branch September 15, 2026 04:03
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 28, 2026
…ocess (objectstack-ai#18595)

Fixes objectstack-ai#18247

`reportReadAuditWriteFailure`
(`packages/plugins/plugin-audit/src/read-audit.ts`) carried the THIRD
independent copy of one defect pair — the pair objectstack-ai#15166 fixed in
`audit-writers.ts` and objectstack-ai#17452 fixed in `auth-event-audit.ts`. This PR
**removes the duplicate**; it does not write a fourth implementation.
The two helpers objectstack-ai#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 (objectstack-ai#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 [objectstack-ai#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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

1 participant