Skip to content

Watch the logger receiver-detach class repo-wide, on an anchored receiver vocabulary - #13132

Merged
os-elon merged 2 commits into
mainfrom
claude/issue-12820-receiver-detach-gate
Aug 29, 2026
Merged

Watch the logger receiver-detach class repo-wide, on an anchored receiver vocabulary#13132
os-elon merged 2 commits into
mainfrom
claude/issue-12820-receiver-detach-gate

Conversation

@os-elon

Copy link
Copy Markdown
Collaborator

Fixes#12820

The first deliverable this card asked for is the false-positive surface, not a gate. It is measured below, on today's main, and it turned out to be cheaply decidable — so the gate is here too. Every number is reproducible with node scripts/check-logger-receiver-detach.mjs --census.

1. Today's classified census, re-run

origin/main has moved from the card's 23843d3f4 to adf70f763b; the tracked .ts population grew from 5,096 to 5,169. Classified, non-test source, same four shapes:

shapecard @ 23843d3f4today @ adf70f763bverdict
live receiver-detach defects00unchanged
parenthesized-callee on a bound/options receiver22 (same two files)legitimate
two-step options-callback sites2516 call sites in 10 fileslegitimate
destructured channels00
channels passed as a bare callback argument00

⚠️ The two-step row is a decomposition difference, not a shrink, and I could not reproduce the card's 25 exactly — my instrument counts call sites of a detached local. All six files the card names are still present, and three more appeared that the card's list does not carry (cli/commands/serve.ts, core/src/logger.ts, driver-mongodb/src/test-mongod.ts). I am reporting my own decomposition rather than asserting the two numbers are the same measurement.

Triage restart condition #1 has NOT fired. There is still no fourth instance; this stays preventive, p2, Task.

2. The A2.2 return path, re-measured with a proved-non-zero control

Still 0 in-repo callers write warn: someLogger.warn into an options bag. But the re-measurement found something the card could not have seen from one file: the naive instrument for that shape returns 36 hits repo-wide, and every one of them is a data-field copyerror: result.error, error: outcome.error, error: prep.error. error is the most common result-envelope field name in the tree. So the return-path shape is unusable without a receiver filter, and with one it is exactly zero. That zero is now a ratchet, shape ⑤ of the gate.

3. ⭐ The criterion (ruling 1), and why it cannot be the channel name

Measured over property reads whose NAME is a log channel, in call position, non-test source:

  • deps.error(...)117 sites — an error-envelope constructor, packages/runtime/src/domains/**
  • this.error(...)91 sites — oclif's Command.error, which throws
  • Math.log(...)2 sites — arithmetic

210 sites that are not logging at all, before detachment is even asked about. The name decides nothing.

The criterion is an anchored RECEIVER vocabulary, matched on the final name segment of the receiver expression — the same bargain DURABILITY_CRITICAL_CALLEES makes next door, with each entry naming why. logger / this.logger / ctx.logger / opts.logger / deps.logger / kernel.logger / (driver as any).logger are one entry; opts and options are not in the vocabulary at all, so the options-callback population is never reached. A second map, DECLINED_RECEIVERS, records what was considered and refused with the measurement (console: bound in Node and browsers; opts/options: caller-supplied plain functions; deps/this/math: not sinks), because the next author's first instinct is to add one of them.

Honest limitations are in the file header: it is a ratchet, not a proof (a sink spelled a way the vocabulary lacks goes unwatched — the safe direction), it reads non-test source only, and it judges spelling, not reachability.

4. ⭐ A false-positive population the card could not measure

Lifted repo-wide as-is, the prototype's bare-argument shape produces 351 findings in test files329 of them expect(someLogger.warn).toHaveBeenCalled() and 6vi.mocked(...). Worse: the two pin tests that document this defect class (logger-receiver-detach.test.ts in driver-sql and plugin-auth) detach a channel deliberately, as their control sample. A gate that reds the tests proving the defect exists is the gate that gets deleted. Hence the non-test narrowing, declared with those numbers rather than as a style choice.

5. The prototype was widened, not reinvented

driver-sql's logger-receiver-detach.test.ts §3 is the judgement; this gate keeps its four shapes and its two-sided control literally (including the fallback split across lines and the docblock quoting the shape in prose), adds the receiver vocabulary and shape ⑤, and takes the reading surface from one file to 2,280. The package-local pin is untouched and still guards its own file.

6. ⛔ Empty sweep refuses

This gate's success condition and its total-failure condition print the same green, so every run — not only --self-test — scans a two-sided control corpus first and refuses unless the MUST-FIRE half produces exactly its expected findings and the MUST-NOT-FIRE half (the real console / options-callback / .bind / .call / guarded spellings from this tree) produces none. An empty walk is also refused.

7. Ablations — predicted before running, with the miss reported

No build is involved: this is a .mjs reading .ts source text, nothing resolves through dist/. Each mutation was proved on disk by counting the deleted and injected text separately, and each restore was proved by comparing the working blob to its HEAD blob.

#mutationpredictedmeasured
Are-introduce the #12792 shape in driver-turso/src/turso-driver.ts (drop the .call(receiver, …))exit 1, exactly 1 parenthesized-callee on logger✅ exact
Bdelete logger from RECEIVER_SENSITIVE_SINKSexit 1 with 5 CONTROL must-fire problems, ⛔ not a green✅ exact — repo findings stayed 0, which is the point
Cadd console to RECEIVER_SENSITIVE_SINKSexit 1; must-not-fire control fires; ≥12 repo findings including both card-named console sites⚠️ direction and both named sites ✅, magnitude wrong: 6, not ≥12

⚠️ The C miss is worth recording. I predicted ≥12 by reading the card's "options-callback shape … with a console default" as meaning a ?? console.warn leg sits in the same expression. It does not — the options sites read opts.warn with no console operand in the expression at all, so adding console reaches only the 6 genuinely console-rooted sites. The card's phrase describes the semantic default, not the syntax. My prediction was wrong; the measurement corrected it.

Ablation A's output is what the gate says when it fires:

packages/drivers/driver-turso/src/turso-driver.ts:396 [parenthesized-callee] receiver `logger`, channel `error/warn`
(this.logger.error ?? this.logger.warn)(message),
Fix: call it through the property access, or restore the receiver explicitly.

8. Import-free where it counts, and the bare-root obligation

The gate does not import scripts/pm/dispatch-gates.mjs (#12855's precedent): the import specifier would itself be a path literal in this file's source and would hand the derivation that module's population as this gate's own. Its self-test therefore holds ROOT_DIR_WATCH_HINTS to the walk with a local matcher, in both directions — every walked file is named by a hint, and no hint is dead. It does import typescript via import-prerequisite, unavoidably: this card exists because two regex instruments were weaker than the AST one.

scripts/pm/bare-root-worklist.mjs --self-test fired FRESH on the new gate's SCAN_ROOTS. Three DECLARED-NARROWER verdicts are recorded, each with its measured ratio (packages 4968/5485 = 90.6%, examples 204/241 = 84.6%, apps 28/40 = 70.0%).

9. Side note — both halves or neither

The ADR-0120 D4 note at driver-sql/src/sql-driver.ts records that check-durability-degradation-log-level.mjs cannot see a call on a parenthesized expression. The matcher blind spot is real and still unrecorded as fixed, AND #12792 measured that it costs zero findings today — none of its nine sites sat inside a catch guarding a DURABILITY_CRITICAL_CALLEES operation, and the audit output was byte-identical before and after. Stating either half alone misleads. That file is untouched here; #12981 remains open on another of its blind spots, and that is a separate PR.

Verification, at be6fb46a82

  • pnpm check:logger-receiver-detach — green: self-test, then 2,280 files, 0 detaches, control fired in the same run.
  • pnpm lint (eslint . --no-inline-config, whole repo) — exit 0, 68s. No narrowing claimed.
  • The derived family (node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack) — 41 rows, 37 green. Re-derived after rebasing onto adf70f763b: the family list is byte-identical to the pre-rebase derivation (measured, not assumed).
  • The 4 non-green rows are all NOT MEASURED rather than red: check-test-completeness needs a turbo test log argument; check-half-states exits 3 PREREQUISITE NOT MET (no GitHub credential in this container); check:dual-build-cjs-loads and check:type-check-debt exit 3/1 needing a built package closure — their own --self-test legs passed, and this diff touches no package source.
  • pnpm check:nul-bytes, check:watch-hint-literal, check:entry-guard, check:parse-guard, check:pm-dispatch-gates, bare-root-worklist --self-test, dispatch-gates --self-test (872 cases) — all green.

No changeset:check:published-files reports every publishable package's files whitelist as dist + README.md + CHANGELOG.md; the root package is private. This diff is scripts/**, root package.json and .github/workflows/lint.yml — nothing publishes. skip-changeset applies.

Session: https://claude.ai/code/session_01CPrUz21stTFhJRUirdc4yw

Generated by Claude Code


Generated by Claude Code

os-elonand others added 2 commits August 29, 2026 05:42
Anchors an explicit RECEIVER vocabulary -- never the channel name -- so the
two legitimate populations #12820 measured (bound `console` methods and
caller-supplied options callbacks) stay green.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CPrUz21stTFhJRUirdc4yw
@github-actionsgithub-actionsBot added size/l ci/cd dependencies Pull requests that update a dependency file labels Aug 29, 2026
@os-elonos-elon added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 29, 2026 — with Claude
@os-elon
os-elon marked this pull request as ready for review August 29, 2026 06:15
@os-elon
os-elon enabled auto-merge August 29, 2026 06:15
@os-elon
os-elon added this pull request to the merge queueAug 29, 2026
Merged via the queue into main with commit 8b6bf51Aug 29, 2026
37 checks passed
@os-elon
os-elon deleted the claude/issue-12820-receiver-detach-gate branch August 29, 2026 07:06
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cddependenciesPull requests that update a dependency filesize/lskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[finding] detach-then-call on a logger channel has now been fixed card-by-card three times, and no gate watches the class

1 participant

@os-elon