Skip to content

test(triggers,approvals): pin expected read-refusal noise instead of muting it - #11570

Merged
os-steve merged 1 commit into
mainfrom
claude/issue-11081-expected-noise-capture-pin
Aug 24, 2026
Merged

test(triggers,approvals): pin expected read-refusal noise instead of muting it#11570
os-steve merged 1 commit into
mainfrom
claude/issue-11081-expected-noise-capture-pin

Conversation

@claude

@claudeclaudeBot commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Part of #11081

Verified at e3c28458.

What landed

The two SqlDriver-backed fixtures of the five now carry @objectstack/runtime's
expected-noise capture pin (#10629 / #10630) instead of relying on a blanket
mute:

  • packages/triggers/trigger-record-change/src/record-change-integration.test.ts
  • packages/plugins/plugin-approvals/src/status-mirror-cascade.integration.test.ts

logger: { level: 'silent' }stays (#10984 is not reverted) — it is the key
ObjectKernelConfig actually reads, and it is what keeps ~1000 INFO/WARN frames
out of the shared shard log. The capture is layered on top: it withholds only a
line naming a declared table and carrying that same table's no such table
reason, forwards every other driver fault to the real console, counts what it
withheld, and asserts in afterAll that each channel fired.

⚠️ The card's premise is falsified — this was a LIVE defect, not future observability

The card's central non-claim was "Nothing unexpected was silenced on the day",
resting on a frame table where both these fixtures go to 0 frames after
#10984. Measured on today's origin/main, as-is, with no mutation:

fixture[sql-driver] DATABASE_ERROR … refused a read on on origin/maincard saidwith this PR
record-change-integration.test.ts8400
status-mirror-cascade.integration.test.ts2500

SqlDriver owns its own logger (a protected field with a console default),
and the kernel's logger: { level: 'silent' } never reaches it. So the driver
channel was never silenced at all — 109 refusal envelopes have been printing
into the shared shard log on main this whole time. Only the engine channel
(ERROR Find operation failed) was silenced, and that half of the card's table
is correct.

Those 109 lines are the exact shape the helper's own header records as having
been "lifted VERBATIM into a p1 flake signature (#10293) and sent a whole
dispatch cycle at the wrong mechanism".

The pin works in BOTH directions (both demonstrated)

Every leg ran under trap restore EXIT INT TERM, with the mutation proven on
disk by grepping the injected anchor and comparing git hash-object before and
after, and every restore proven byte-identical.

Half 2 — an expected refusal that stops being emitted REDs the test.
Suppressed exactly one recognised refusal (sys_organization) before the
capture could see it. Result: Test Files 1 failed (1), failing in the
afterAll pin, with the diff naming both channels:

+ "the driver's read refusal for 'sys_organization' was never emitted",
+ "the engine's 'Find operation failed' frame for 'sys_organization' was never emitted",

Half 1 — an unexpected ERROR frame reaches the log. Injected a fault on a
declared table (sys_user) carrying a reason that is notno such table,
so the predicate must refuse to withhold it. Measured on the driver channel:

armtotal driver fault linesof which the injected UNEXPECTED one
origin/main (blanket mute)939
this PR (pin)99

The unexpected frame surfaces in both — but on main it arrives buried in 84
expected lines, and with the pin it is the only thing on the channel.

Stated honestly: the first attempt at this control was invalid — it
injected beforeregisterDriver, where the driver's logger is still its own
console default, so the marker appeared in both arms and the comparison proved
nothing. Both arms above inject afterregisterDriver. Finding that is what
turned up the falsified premise above.

Residual gap, not fixed here: on the engine channel an unexpected ERROR
is still invisible. captureEngine forwards unrecognised frames to the engine's
own logger, which the kernel's silent level drops. The pin's engine half buys
the afterAll assertion, not loudness.

Why only two of the five

Re-measured per fixture at level: 'info'. The other three drive memory/fake
drivers
, not SqlDriver, so they emit no refused a read on line and no
Find operation failed frame — captureExpectedReadRefusals recognises
nothing in them:

fixturedriverERROR frames todayrecognised by the helper
record-change-integrationSqlDriver11084 + 1 asserted negative path
status-mirror-cascadeSqlDriver3225 + 1 asserted negative path
bulk-write-per-row-contextmemory150
formula-contextmemory30
multilookup-contextmemory30

Their entire ERROR surface is one boot-without-a-datasource trio (sys_metadata could NOT be read at boot, [wait] suspended wait-timer re-arm ABORTED,
[Automation] sys_automation_run could not be read at startup), which neither
of the helper's two predicates covers. Pinning it needs a new predicate on a
different seam — those frames come from the kernel logger, which is built in the
constructor and captured by reference by the plugin loader and the service
context, so a post-construction swap propagates only partially. A capture that
silently misses frames is the phantom-check failure mode, so it is left for a
decision rather than guessed at.

Declared, not left for CI to discover

Importing the helper across the package boundary is a cross-package test input.
Measured both options:

  • Declaring — 2 entries in CROSS_PACKAGE_TEST_INPUTS + 2 #testinputs
    entries in turbo.json. Precedent already in the repo: @objectstack/plugin-auth#test
    and @objectstack/dogfood#test both declare packages/runtime/src/**.
  • Relocating — move the helper, rewrite 16 runtime test imports, add a
    shared home plus devDeps in 3 packages.

Took declaring. The radius is the one file rather than
packages/runtime/src/**, because the helper imports nothing — that single path
is the whole escaping read.

Two consequences worth review:

  1. Once these packages gained any declaration the gate demanded their full
    roster, including three paths that pre-existing prose already named
    (scripts/slot-lookup-baseline.json, packages/core/src/kernel.ts, and this
    gate's own path). They were invisible only because a package with no escaping
    test is never rostered. Declared, per the gate's own doctrine that declaring a
    mentioned file beats rewording prose to dodge the scanner.
  2. trigger-record-change's test program needed rootDir widened to
    packages/ (TS6059 — enforced over every program file even under --noEmit).
    Same consequence-of-an-import that plugin-approvals already records for
    Promote resolveRecordOrganizationField to the shared platform-row resolver (approvals + automation runs), per the ruled cloud#1395 Option A #10101. Emit is unaffected: that config asserts noEmit, and tsup builds from
    tsconfig.json whose entry is src/index.ts alone. The config's own note was
    rewritten rather than just its value — its old text argued rootDir should stay
    ./src on a measurement this PR moved.

Verification

All at e3c28458.

  • Controls identical — nothing weakened.trigger-record-change 78 → 78,
    plugin-approvals 585 → 585, runtime 2732 → 2732.
  • Card's numbers had drifted (they were from 08-22): plugin-approvals
    536 → 585, runtime 2692 → 2732, trigger-record-change 78 unchanged.
    record-change-integration's own frame total moved 672 → 987.
  • Derived gate union green (scripts/pm/dispatch-gates.mjs --repo, exit
    codes captured before any pipe): cross-package-test-inputs ("OK: 16
    package(s) read outside themselves, all declared, and turbo.json hashes every
    declared glob"), entry-guard, parse-guard, pnpm-filter-targets,
    published-files, slot-lookup, test-source-alias, type-source-resolution,
    ci-filter-parity, plugin-teardown-shape, affected-docs,
    query-options-erasure, type-check-coverage, engine-double-contract,
    where-matcher, nul-bytes, i18n.
  • check:i18n first returned PREREQUISITE NOT MET (CLI not built) — recorded
    as NOT MEASURED, not green; built @objectstack/cli and re-ran to a real
    "OK (9 package(s) — all bundles in sync)".
  • check:type-check-debt --re-measure on the fully built closure: "32 ledger
    entr(ies) re-measured, 1897 raw tsc error(s) total, none above its recorded
    number."
  • pnpm lint (full repo, eslint . --no-inline-config): exit 0. No narrowing.

skip-changeset: verified against scripts/pr-labels.mjs's own rule —
"skip-changeset is the exemption for a PR that publishes nothing". This diff
is two test files, a noEmit test config, and repo tooling; no published dist
changes.

Follow-ups filed

Generated by Claude Code


Generated by Claude Code

…muting it
The two SqlDriver-backed fixtures of #11081 blanket-silenced their kernel, so
the 109 expected authz/organization read refusals they provoke were suppressed
along with every unexpected frame, and nothing asserted the expected reads
still happen. Both now carry `@objectstack/runtime`'s shared expected-noise
capture (#10629 / #10630): it withholds only a line naming a declared table AND
carrying that same table's `no such table` reason, forwards every other driver
fault to the real console, and counts what it withheld so `afterAll` can assert
each channel fired.
`logger: { level: 'silent' }` stays (#10984) -- it is the key ObjectKernelConfig
reads, and it is what keeps ~1000 INFO/WARN frames out of the shared shard log.
The import escapes the package on purpose, so it is DECLARED rather than left
for CI to find: one file (the helper imports nothing) in
CROSS_PACKAGE_TEST_INPUTS plus a matching `#test` inputs entry in turbo.json for
each package. trigger-record-change's test program needs `rootDir` widened as a
consequence, the same shape plugin-approvals already records for #10101.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015ahemw8RcTgqtxrj15PEZx
@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 24, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs.

@os-steveClaude

Copy link
Copy Markdown
Collaborator

CI triage — the one red is not this PR's, and it is not simply "a flake" either

Dispatching PM seat (session_015ahemw8RcTgqtxrj15PEZx). Recording the diagnosis so the next reader does not redo it.

Temporal Conformance (live PG + MySQL) failed. One test of 2543:

FAIL packages/drivers/driver-sql/src/sql-driver-11224-update-stamp-precision.test.ts
#11224 — the UPDATE door stamps at the audit column's precision (sqlite)
§3 keeps sub-second resolution, so same-second updates are ordered
AssertionError: every update in this second stamped the SAME instant: expected 1 to be greater than 1
Test Files 1 failed | 125 passed (126)
Tests 1 failed | 2542 passed (2543)

Why "not this PR's" is a measurement and not a shrug

This diff touches two test fixtures, one noEmit test config, turbo.json and CROSS_PACKAGE_TEST_INPUTS. No file under packages/drivers/ is in it. That alone is the standard "names a service the diff does not touch" case.

⚠️ But the symptom is ALSO the defect this test guards against, so it needed the extra step

new Set(stamps).size === 1 is exactly what a truncating updatedAtStamp() produces — every update inside one second collapsing onto one value. Calling this a flake without ruling that out would have been the mistake.

The test file's own docblock supplies the discriminator:

Restoring main's updatedAtStamp() body turns §1, §2 and §3 red

Only §3 failed. §1 and §2 both assert sub-second digits directly —

:164 'no `created_at` in this run carried sub-second digits, …' ).toBeGreaterThan(0)
:192 expect(rows.filter((r) => r.createdAt % 1000 !== 0).length).toBeGreaterThan(0)

— and both passed. If truncation had regressed, all three would be red. So the stamping is intact and §3's collision is a timing artifact of the run.

What §3 actually is

for(leti=0;i<ROUNDS;i++){awaitdriver.update(MANAGED,id,{title: `t${i}`},OPTS);stamps.push((awaitreadAudit(driver,MANAGED,id)).updatedAt);}expect(span,'…').toBeLessThan(1_000);expect(newSet(stamps).size,'every update in this second stamped the SAME instant').toBeGreaterThan(1);

A probabilistic guard: it needs the wall clock to tick at least once across ROUNDS sqlite round-trips. On a fast enough runner every stamp lands in the same millisecond and it fails with correct code. Same class as the vitest-console-teardown-race case this seat hit earlier tonight, where the repo's own queue-triage bot put it as "the instrument stopped reproducing".

Filed as #11572 — this is not a defect in the diff and not a fix for this PR to carry.

Disposition

⛔ No re-queue and no re-run spent yet: 4 checks are still in progress and this PR is a draft, so there is nothing to protect. When the rest of CI settles, one re-run is the sanctioned discriminator for exactly this case — and only one. ⛔ The test will not be skipped, disabled, or quarantined, and no empty commit will be pushed.


Generated by Claude Code

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/mskip-changesetPR has no user-facing published change; bypasses the changeset gatetests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@os-steve@claude