Skip to content

docs(runtime): state the expected-read-refusal capture's real per-channel pass-through loudness - #12295

Merged
os-trump merged 4 commits into
mainfrom
claude/issue-11569-engine-noise-doc-asymmetry
Aug 26, 2026
Merged

docs(runtime): state the expected-read-refusal capture's real per-channel pass-through loudness#12295
os-trump merged 4 commits into
mainfrom
claude/issue-11569-engine-noise-doc-asymmetry

Conversation

@os-trump

@os-trumpos-trump commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator

Fixes#11569

Documentation-only correction to packages/runtime/src/expected-read-refusal-noise.ts,
plus one new pin that measures what the corrected prose claims. No sink, predicate,
count or assertion changes. No fixture becomes loud. None of the 19 consumers of
captureExpectedReadRefusals is touched.

Scope is the triage re-ruling of 2026-08-25 15:56Z, which overturned the 03:29Z grading:
narrow the documented claim to the measured truth. A loud-channel mechanism
(caller-supplied sink / level floor / console sink) is explicitly out of scope and is not
here.

What the prose got wrong

The module header claimed of BOTH channels that anything the capture does not recognise is
"forwarded to the real console untouched", and the engine-gate paragraph said an
unrecognised DATABASE_ERROR "reaches the log with both halves intact". True on the
driver channel; half-false on the engine channel:

  • captureDriver's non-matching branch calls console.warn / console.errordirectly;
  • captureEngine's non-matching branch calls target.error(...), where target is the
    engine's own logger — the ObjectLogger the kernel built from its logger config
    and handed to the engine by reference. ObjectLogger.write returns early unless
    LEVEL_ORDER.error >= LEVEL_ORDER[config.level], so the frame is dropped whenever the
    configured level ranks above error.

The measurement the new prose is written from

A real lean kernel (ObjectKernel + DriverPlugin(SqliteWasmDriver ':memory:') +
ObjectQLPlugin), a real capture on both channels, and one real failing read of a table
the capture was not told about — so the pair is unrecognised and both channels take
their pass-through path. The driver channel is counted at console.warn; the engine
channel at process.stderr (ObjectLogger prefers the process streams under
environment: 'node', so console is unreachable there — the same finding this module's
[#11571] block already records).

kernel logger.leveldriver pass-throughengine pass-through (Find operation failed)
info11
error11
fatal10
silent10

Sample of the engine frame at error (present) — the same read, at silent, produced
zero lines on process.stderr:

2026-08-25T16:43:15.938Z ERROR Find operation failed {"object":"probe_11569_undeclared",
"error":{"message":"The database refused to run this query for object
'probe_11569_undeclared'. …

Three things that measurement settles:

  1. The negative is real, not a broken probe. The identical read on the identical
    composition does reach the log at error and at info. The positive control is a
    case in the pin, not a footnote.
  2. The condition is the LEVEL THRESHOLD, not the word silent.fatal drops the
    frame too. The prose says "ranks above error (fatal, silent)" for that reason.
  3. The surviving half is untouched. A recognised pair at silent still withholds on
    both channels, counts 1 refusal + 1 engine frame, and silentChannels() is [].

⭐ All four pin cases are green both before and after this PR's comment edits — a
comment edit cannot redden a test. They are regression guards on the behaviour the new
prose describes, never red-before evidence for it.

A correction to the card's own premise, measured

The card states "Every fixture that carries this capture also constructs its kernel with
logger: { level: 'silent' }". That is not what the tree says. Grepping the 19
consumers for a kernel actually constructed that way (code, not prose) returns three:

packages/plugins/plugin-approvals/src/status-mirror-cascade.integration.test.ts:186
packages/runtime/src/notifications.hono.integration.test.ts:112
packages/triggers/trigger-record-change/src/record-change-integration.test.ts:300,345,373

The other sixteen leave the kernel at createLogger's default info, where the info
row above says the unrecognised engine frame is logged. The asymmetry the card
reports is real and the three silent fixtures are blind to it exactly as described — but
it is a per-fixture property, not a property of the capture's whole consumer set. The new
prose says that explicitly, so the next reader does not inherit the over-broad reading.

What changed in the comments

  • Header, "What this is NOT is a mute" — the second bullet's "forwarded to the real
    console untouched" became "forwarded untouched to the sink that would have received it",
    followed by a new ⚠️ [#11569] block stating the per-channel loudness and its exact
    condition, a line saying the engine channel's loudness is the caller's and is not
    uniform across consumers, and a line reading the guarantee per channel (driver:
    pinned both directions; engine: pinned one direction only).
  • The engine-gate paragraph — "it reaches the log with both halves intact" became
    "this capture never swallows it", split into the DRIVER half (reaches the log intact on
    the direct console sink) and the ENGINE half (handed back to the engine's own logger;
    dropped under logger: { level: 'silent' }).
  • captureEngine, at the implementation — a new note naming where a non-matching
    frame actually goes, why it is deliberately not repaired into a console sink, and what
    that does not weaken. Plus a two-line pointer on the interface member.
  • "This is the console side-effect only" → "This is the LOG side-effect only", with
    what "the log" means per channel.

One correction beyond the three the re-ruling names

Called out here because an unnamed drive-by is unreviewable. The [#11571] block at the
foot of the same file — added by #12145, merged 15:06Z, fifty minutes before the 15:56Z
re-ruling — cross-referenced #11569 as a defect meaning "the two mechanisms here want
repairing before a third is stacked on them". The re-ruling makes that clause false: the
pass-through's level-dependence is now a documented limit, not a pending repair. The
clause was narrowed to say exactly that, in the same file, in the same defect class. No
other text in that block moved, and the block's own conclusion (no third mechanism) is
unchanged.

Scope, measured

  • git diff --name-only origin/main...HEAD lists exactly two paths, both under
    packages/runtime/src/.
  • git grep -ln "captureExpectedReadRefusals" -- packages returns 20 files on main
    the definition plus 19 consumers. The intersection of that list with this PR's diff is
    empty, so neither the 16 packages/runtime/** fixtures nor the two domain:services
    surfaces (plugin-approvals, trigger-record-change) is touched.

Verification

Gate family derived, not recalled: node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack, re-derived after the change set was final. Every exit code
captured before any pipe; each verdict below is the gate's own line.

Re-run in full at the final commit f811e755a6:

  • check:nul-bytesOK (scanned 6800 text file(s) … no raw ASCII control bytes)
  • check:test-source-aliasOK — 72 packages with tests scanned; 61 registered as still resolving a workspace dep through dist/
  • check:cross-package-test-inputsOK: 16 package(s) read outside themselves, all declared
  • check:engine-double-contractOK — 409 pinned, 133 in the DEBT ledger, 2 exempt
  • check:where-matcher✓ where-matcher conformance holds: 299 matcher(s) discovered … none new
  • check:published-files, check:slot-lookup, check:type-source-resolution — exit 0
  • check:query-options-erasure✓ ratchet holds: 67 unswept non-test site(s) in 17 file(s), none new
  • check:type-check-coverage — exit 0
  • check-plugin-teardown-shape✓ 63 Plugin implementation(s) across 4714 source(s) … baseline fully burned down
  • check-ci-filter-parityOK: all 96 declared cross-package glob(s) … covered
  • check-affected-docs — exit 0

Tests and types at f811e755a6, under the shared verify lock
(VERDICT command-exit 0 · held the lock 16s):

Test Files 4 passed (4)
Tests 24 passed (24)

— the new pin plus three consumers of the touched module
(federated-boot-binding, notifications.hono.integration,
cross-field-refusal-operand-withhold). pnpm --filter @objectstack/runtime typecheck
exit 0.

Two declared narrowings, so they are not read as full coverage:

  1. pnpm test for @objectstack/runtime was narrowed to the pin plus those three
    consumers rather than the whole package suite. The edit outside the new file is
    comments only, so no other fixture's behaviour can move; CI runs the full suite
    regardless.
  2. The repo-wide pnpm lint was narrowed to a targeted eslint --no-inline-config --format json over the two changed files: 2 files linted, 0 errors, 0 warnings
    (count read from eslint's own JSON, not asserted). The narrowing is sound because
    eslint.config.mjsnever enables type-aware linting (no parserOptions.project,
    no typed @typescript-eslint rules) for any file — stated in the config itself, with
    its own measurement — so a diff confined to these two files cannot move a verdict on
    any untouched file.

check:type-check-debt --re-measure was not run locally: it requires the whole
workspace closure built and refuses outright otherwise. Not measured here; CI runs it.

No changeset — comments plus one test file, in a module that is not exported from
src/index.ts and therefore not bundled, so this PR publishes nothing. The
skip-changeset label carries that declaration (same route as #12145 on this same file).

Generated by Claude Code

…rough asymmetry
The module header claimed, of both channels, that anything the capture
does not recognise is forwarded to the real console. That holds for
`captureDriver` (its non-matching branch calls `console` directly) and
not for `captureEngine` (its non-matching branch calls the engine's own
kernel-derived logger, which drops an `error` frame whenever the
configured level ranks above `error` — `fatal` or `silent`).
Documentation only: no sink, predicate, count or assertion changes, and
no consuming fixture becomes loud. A new pin measures both directions of
the asymmetry on a real kernel so the prose cannot rot unnoticed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HbG3rGVLjZStHQxHDtzJdJ
…ss-through loudness
Boots a real lean kernel (ObjectQL + sqlite-wasm) at four logger levels and
counts what each channel's pass-through puts in front of a reader:
level driver pass-through engine pass-through
info 1 1
error 1 1
fatal 1 0
silent 1 0
plus a recognised-pair case proving the withholding/counting half is
unchanged. Comment-only edits elsewhere in the module; this is what keeps
those sentences from rotting unmeasured.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HbG3rGVLjZStHQxHDtzJdJ
@github-actions

github-actionsBot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

2 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to listnot a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run.

What this run could not see
  • the SDK route bridge reached 45 of 222 client-bound route-ledger rows — the other 177 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 177: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 107 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 — 23 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 8cdd696592b0c3c7bb7c3caa776f91aa6eb8cb68packageMentionDocs.

Which tree this was computed on

This run read content/docs from ce669dc3d610c506625a2be13bc48a9811b84bb5 — the merge of head f811e755a6258e968041f50a8c2f72015a689e35 into base 8cdd696592b0c3c7bb7c3caa776f91aa6eb8cb68, 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 ce669dc3d610c506625a2be13bc48a9811b84bb5 && git checkout ce669dc3d610c506625a2be13bc48a9811b84bb5
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 8cdd696592b0c3c7bb7c3caa776f91aa6eb8cb68 f811e755a6258e968041f50a8c2f72015a689e35 && git checkout -B drift-repro 8cdd696592b0c3c7bb7c3caa776f91aa6eb8cb68 && git merge --no-ff f811e755a6258e968041f50a8c2f72015a689e35
node scripts/docs-audit/affected-docs.mjs --json 8cdd696592b0c3c7bb7c3caa776f91aa6eb8cb68

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

… not uniform
Measured on origin/main: only 3 of the 19 fixtures that carry
captureExpectedReadRefusals construct their kernel with
`logger: { level: 'silent' }`; the other 16 leave it at the default
`info`, where an unrecognised engine frame IS logged. The header now says
so, so the silent-fixture case is not read as the rule for all of them.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HbG3rGVLjZStHQxHDtzJdJ
@os-trumpos-trump added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 25, 2026 — with Claude
@os-trump
os-trump marked this pull request as ready for review August 25, 2026 23:47
@os-trump
os-trump added this pull request to the merge queueAug 25, 2026
Merged via the queue into main with commit 9edd79fAug 26, 2026
40 checks passed
@os-trump
os-trump deleted the claude/issue-11569-engine-noise-doc-asymmetry branch August 26, 2026 00:20
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

2 participants

@os-trump@claude