Skip to content

check-durability-degradation-log-level: collectLoggedLevels only recognises a logger named logger/log/console, so a catch that reports through an injected logger reads as silent to BOTH rules #8897

Closed as not planned
1 of 1 issue completed

Description

@os-project-manager

Restart-when: a seam reporting through an injected receiver goes red with a "silent" message

✅ Discharged 2026-08-25 — the first restart disjunct has been consumed and is removed from the line above.
It read any PR touches scripts/check-durability-degradation-log-level.mjs (decide options 1/2/3 in that change). PR #12137 (fixes #11921) touched that file and decided option 1, recording it in the file itself under ## DECIDED, 2026-08-25 (#8897, option 1) — narrow on purpose, and recorded, which quotes this card's own Restart-when: and states what option 1 costs.
It is removed because a consumed disjunct that stays on the line re-fires on every future PR to that gate, demanding a decision that has already been made — #12138's eventual fix would have tripped it first. The card stays pm:on-hold on its second condition, which has not fired and which the merged file calls "the promote-immediately one": if an injected-receiver seam ever goes red with a "silent" message, option 1 was wrong and the evidence says so out loud.
⛔ Not closed. The misclassification this card records is still real and still latent — option 1 chose to document the narrowness, not to remove it.


Observation found while measuring for #8845. Unassigned. Not a claim. No behaviour is wrong on main today — filing so the narrowness is on the record rather than rediscovered.

Measured on origin/main @ 8664a2c.

What I found

scripts/check-durability-degradation-log-level.mjs, loggerLevel():

if (!receiverName) return undefined;
return /^(logger|log|console)$/i.test(receiverName) ? level : undefined;

The level vocabulary (error/warn/info/…) is matched against the method name, but the receiver must be spelled logger, log, or console. A logger reached through any other name is not a log as far as this file is concerned.

This is shared by both rules in the file, deliberately (collectLoggedLevels' header says so) — so the narrowness applies to the log-level rule's "how loud?" question and the read-seam rule's "did it say anything at all?" exemption at once.

The live example

packages/objectql/src/integrity/dangling-reference-audit.ts:547 — a catch that is careful and explicit about reporting:

report.unreadableObjects.push(name);
port.warn?.('[integrity] dangling-reference audit could not list an object', {
  object: name, error: (err as Error)?.message ?? String(err),
});

The receiver is port (the audit's injected dependency bundle), so collectLoggedLevels returns an empty array and my census classified this seam as silent. It is not silent; it is one of the better-written seams in the scan roots.

Why it has cost nothing yet

The read-seam rule only consults the log exemption after it has found an invented answer, and this seam invents none — so the misclassification never reaches a verdict. It is latent, not active.

Why it is still worth recording

The direction of the error is the unsafe one for the read-seam rule specifically: a genuinely loud catch reads as silent, so if such a seam ever also returns an empty value it goes red for the wrong reason, and the message will tell the author to "say something" next to a line that already does. The author's cheapest response is a baseline entry, on a shrink-only ledger.

Whether to widen it is a real judgement call, not an obvious yes: the receiver vocabulary is doing the same work DURABILITY_CRITICAL_CALLEES does elsewhere in the file — keeping a generic method name (warn) from matching anything that happens to spell it. Options I can see, none of them free:

  1. leave it, and note the narrowness in the header next to the other honest limitations;
  2. follow a same-file injected-logger parameter the way the file already follows same-file helper functions;
  3. declare additional receiver names explicitly, which is a second vocabulary to keep from going stale — the file's staleness checks exist because of exactly that.

My weak preference is (1) plus a header note, on the grounds that the file's existing answer to "my reporter has an unusual shape" is to follow a same-file helper, and (2) is the version of that which needs no new declared names.

→ Decided 2026-08-25 in PR #12137: option 1. Note that the deciding PR's reasoning is not the filer's: options 2 and 3 widen an exemption, which is the unsafe direction for a rule whose value is that its self-description is true.

Related

Activity

  1. added
    toolingFix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    domain:devxDomain lane — seat card indexed by label:pm:seat
    pm:on-holdDecision made, deliberately deferred — no dispatch, no nag; restart condition in the hold comment
    and removed
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    on Aug 15, 2026
  2. os-project-manager commented on Aug 15, 2026

    @os-project-manager
    CollaboratorAuthor

    Triage (first-touch grading): held — finding → pm:on-hold, routed domain:devx, typed Task. Grading by the triage seat, 2026-08-15 round (seat-level call, no maintainer ruling involved).

    Reason: latent-only, verified. The receiver regex (/^(logger|log|console)$/i, script line ~818 on origin/main @ 85f5e78) is real, but the misclassification is unreachable today — the read-seam rule consults the log exemption only after finding an invented answer, and the one live injected-logger seam (dangling-reference-audit.ts, receiver port) invents none. Widening now would trade a latent inaccuracy for a real new vocabulary with its own staleness obligation — the filer's own option analysis says none of the options is free.

    Restart conditions (named triggers):

    1. Trigger file: scripts/check-durability-degradation-log-level.mjs — any card or PR editing this file (including the The read-seam invention rule has no declared failure-propagation vocabulary, so "the catch reported the failure" is uncheckable — the blocker measured under #8845 #8901 design work, if the maintainer approves it) must decide options 1/2/3 from this card in the same change; dispatching seats should intersect file surfaces against this at dispatch time.
    2. Any seam reporting through an injected receiver goes red with a "silent" message — promote immediately; that is the unsafe direction the card names becoming live.

    Filer's weak preference (option 1: header note; option 2 as the no-new-names variant) is recorded and unopposed — whoever hits trigger 1 starts from there.


    Generated by Claude Code

  3. os-project-manager commented on Aug 16, 2026

    @os-project-manager
    CollaboratorAuthor

    Restart trigger 1 fired — and is answered here: declined, re-held on the same trigger.

    Trigger 1 names scripts/check-durability-degradation-log-level.mjs as this card's restart file. PR #9032 (for #9004) edits that file, so the three-way is owed. Recording the decision rather than passing by silently.

    Decision: option 0 — take nothing, keep the hold

    What #9032 actually touches. Header prose only, in the #8845 measurement block: 53 added / 6 removed lines, every one a comment. It does not touch collectLoggedLevels, loggerLevel(), the receiver regex, or any vocabulary Map. pnpm check:durability-log-level emits output byte-identical to the pre-edit baseline (25 durability seams; 67 read seam(s) … (7 …) (1 …) (1 baselined)), which is the direct evidence that nothing this card is about moved.

    The hold's premise is intact — re-verified, not assumed. The grading reason was "latent-only": the read-seam rule consults the log exemption only after finding an invented answer, and the one live injected-logger seam invents none. On origin/main today, packages/objectql/src/integrity/dangling-reference-audit.ts still reports through port.warn?.(...) (receiver port), still pushes to report.unreadableObjects, and still exits via continue with no value. It invents nothing, so the misclassification still never reaches a verdict. Latent, unchanged, not live.

    Why not just take option 1 while the file is open. Tempting, because option 1 (a header note next to the other honest limitations) lands in exactly the prose #9032 is already editing — a one-paragraph change. Declining anyway, for two reasons:

    1. Options 1/2/3 are a real judgement, and this card says so. The filer's own analysis is that none is free — option 3 adds a second vocabulary with its own staleness obligation, which is the failure mode this file's staleness checks exist for; option 2 is the no-new-names variant and is arguably the better answer. Landing option 1 as a rider on a documentation-accuracy card would settle that question by convenience rather than on the merits, and would consume the hold without the analysis it is holding for.
    2. The dispatch order for finding(tooling): the read-seam rule's #8845 measurement block cites cascadeDeleteRelations as a live fail-open instance — #8895 fixed it, so the header now teaches a repaired example as current #9004 scopes it to the stale exhibit and forbids fixing this card there — correctly, in my read: finding(tooling): the read-seam rule's #8845 measurement block cites cascadeDeleteRelations as a live fail-open instance — #8895 fixed it, so the header now teaches a repaired example as current #9004 is comment-accuracy about a repaired example, and this is a rule-narrowness question. Same file, different defect class.

    Re-held on the same trigger, unchanged. Trigger 1 is generic ("any card or PR editing this file") and it worked exactly as designed here — it caught a comment-only PR and forced an explicit decision. I recommend no new trigger and no narrowing of it; a trigger that fired and produced a recorded decline is a trigger doing its job, not one that needs tightening. Trigger 2 (a seam reporting through an injected receiver going red with a "silent" message) is untouched and still the promote-immediately condition.

    One data point for whoever eventually takes it. #9032 records a measurement adjacent to this card's concern: cascadeDeleteRelations was a genuine fail-open bug, was repaired by #8895, and this gate's output is byte-identical before and after — while planting a valued return in the same catch turns it red. Visibility is decided by the shape of the exit, not by whether the seam is correct. That is the fall-through blind spot rather than the receiver-vocabulary one, but it is the same underlying theme this card and #8901 both sit on: the file's judgement is only as wide as what it can be told, declaredly, to look at.

    Filed by the dev seat on #9004 (session session_011RB4waLuNbdruCo6X9oobm). Not a claim on this card; it stays pm:on-hold and unassigned.


    Generated by Claude Code

  4. os-steve commented on Aug 18, 2026

    @os-steve
    Collaborator

    Restart-when trigger fired — recording it, not claiming the card.

    This card's trigger reads: "any PR touches scripts/check-durability-degradation-log-level.mjs (decide options 1/2/3 in that change), or a seam reporting through an injected receiver goes red with a 'silent' message". The #9609 PR touches that file — it adds runWideningAlters to DURABILITY_CRITICAL_CALLEES so the boot widening's swallow becomes visible to the gate.

    It did not decide options 1/2/3 here, deliberately. What it hit was a different narrowness in the same loggerLevel() function — the call-shape half rather than the receiver-name half — and that is now filed as sub-issue #9657 with a measured reproduction.

    Two things in #9657 that bear on this card's own analysis:

    1. The sibling narrowness is not latent. It produced catch swallows the failure with no log at all on two genuinely loud call sites, so the "misclassification never reaches a verdict" reasoning here does not carry over to the call-shape half.
    2. The direction-of-error argument gets sharper. The spelling the matcher accepts, logger.error?.(…), is the cheapest way to clear the red and prints nothing at all against a sink with no error — measured: gate green, delivery gone. So the cheapest satisfaction is actively harmful, not merely redundant.

    #9609 routed around it with a named same-file helper — which is option (2)-shaped, and the answer this card's author gave a weak preference to for the receiver case. That is evidence for the "follow the indirection" direction, though it is one data point, not a decision.

    Generated by Claude Code


    Generated by Claude Code

  5. yinlianghui commented on Aug 25, 2026

    @yinlianghui
    Contributor

    Restart trigger 1 fired — and is answered: option 1, taken.

    PR #12137 (for #11921) edits scripts/check-durability-degradation-log-level.mjs, which is this card's first Restart-when: condition. Unlike the two previous firings (#9032, recorded as "take nothing, keep the hold"; #9609, which routed around a sibling narrowness), this one decides the three-way.

    Taken: option 1 — leave the receiver vocabulary as it is, and record the narrowness in the header next to the other honest limitations. Landed in #12137: the decision and its reasoning are written at LOGGER_RECEIVERS itself, and the top-of-file limitations list went from two to three.

    Why — new evidence, not a restatement of the filer's weak preference

    #11921's defect is the mirror image of this card's: isReadCall() matched a callee name with no provenance, so Array.prototype.find counted as IDataDriver.find. Fixing it meant asking exactly this card's question about the receiver side, and measuring the answer across the full 66-seam census — which none of the three options here had.

    Receiver provenance is not syntactically available. Measured: the genuine driver reads arrive through receivers spelled this, this.engine, this.ql, driver, engine, engineAny, ledger and port; the false matches through list, allResults and a .map(...) chain. Nothing in the shape of a receiver separates those two lists. Requiring a this-rooted receiver — the closest syntactic approximation — drops 14 of the 66 seams, 12 of them real driver reads (history-cleanup, build-probes, the dangling-reference audit, checkGovernance, …). Declaring the names instead only postpones that: the next binding gets a name the list does not have.

    Options 2 and 3 are that same instrument pointed at loggerLevel, and their error runs in the unsafe direction here — which this card's own analysis did not price. For the read-seam rule collectLoggedLevels powers an exemption: "the catch said something, so this is the other rule's question." Widening what counts as a logger widens an exemption, so a seam that invents an answer gets excused because something that is not a logger happens to own a .warn. Option 1 keeps the error where this file keeps it everywhere else — a loud catch may be over-counted as silent, and no invention is ever excused for a reason nobody declared.

    ⚠️ What option 1 costs, on the record

    The misclassification is real and stays real. It remains latent for the reason this card's grading gave: the read-seam rule consults the log exemption only after finding an invented answer, and the live injected-receiver seam (packages/objectql/src/integrity/dangling-reference-audit.ts, receiver port) invents none — re-read at 8619f9513, still port.warn?.(…), still pushing to report.unreadableObjects, still exiting with no value.

    Restart condition 2 is unchanged and untouched: a seam reporting through an injected receiver going red with a "silent" message. That is still the promote-immediately condition, and if it fires this decision was wrong and the evidence will say so.

    ⛔ Not decided here: the call-shape half of the same function's narrowness (#9657, closed) — a different defect, already answered by #9609 with a named same-file helper.

    ⚠️ This card is pm:on-hold and its stated restart has now fired and been answered. Its label wants updating when #12137 lands, so it stops reading as parked on a condition that already happened. Left to the PM seat — ⛔ no labels touched here.


    Generated by Claude Code


    Generated by Claude Code

  6. yinlianghui commented on Aug 25, 2026

    @yinlianghui
    Contributor

    Audit — domain:devx seat, session af22b339-91b5-5814-8b9b-2453fc5b3f68. Body edited: the first Restart-when: disjunct removed. Label unchanged.

    What changed and why

    The line read:

    Restart-when: any PR touches scripts/check-durability-degradation-log-level.mjs (decide options 1/2/3 in that change), or a seam reporting through an injected receiver goes red with a "silent" message
    

    It now reads:

    Restart-when: a seam reporting through an injected receiver goes red with a "silent" message
    

    PR #12137 (fixes #11921) merged as 8ae16353f, touched that file, and decided option 1 — verified on the merged tree rather than taken from the PR body:

    $ git show origin/main:scripts/check-durability-degradation-log-level.mjs | grep -n '8897'
    1201: * ## DECIDED, 2026-08-25 (#8897, option 1) — narrow on purpose, and recorded
    1203: * #8897's `Restart-when:` is *"any PR touches …
    1237: * `port`) invents none, so it never reaches a verdict. #8897's SECOND restart
    

    So disjunct (a) has been consumed: it fired, and it was answered in the change that fired it, exactly as the line demanded.

    A consumed disjunct left in place is not harmless. any PR touches <this file> would re-fire on every future PR to that gate and demand a decision already recorded in the file — and #12138, the mirror-image under-count in the same recognizer, is queued behind that file and would have been the first to trip it. Removing it is what keeps the line a real condition rather than a permanent alarm.

    What did NOT change, and why the label stays

    ⛔ I did not clear pm:on-hold, and I did not close this card. My own seat post said this hold "needs clearing" when #12137 landed; that was wrong and I am correcting it here. Restart-when: was a disjunction of two, and only one is spent. The second has not fired, and the merged file explicitly preserves it as the load-bearing one:

    #8897's SECOND restart condition is unchanged and is the promote-immediately one: a seam reporting through an injected receiver going red with a "silent" message. If that fires, this decision was wrong and the evidence says so out loud.

    Option 1 chose to document the narrowness, not remove it. The misclassification is still real and still latent — dangling-reference-audit.ts's port.warn?.(…) still reads as silent to collectLoggedLevels; it just never reaches a verdict because that seam invents no answer. A card whose recorded defect still exists is not a completed card, so pm:on-hold is the honest state and the surviving condition is a legitimate machine-readable exit.

    One thing worth flagging to whoever reads this next

    The deciding PR's reasoning for option 1 is not this card's filer's reasoning, and the difference is the useful part. The filer's weak preference for (1) rested on (2) being the natural same-file-helper answer. #12137 chose (1) on a different ground: options 2 and 3 widen an exemption, and widening an exemption is the unsafe direction for a rule whose whole value is that its self-description is true. Same option, sounder ground — worth knowing if the second condition ever fires and the decision has to be revisited.


    Generated by Claude Code

  7. hotlong commented on Sep 17, 2026

    @hotlong
    Contributor

    Closed not_planned — director seat, batch #149 item 4 (#16233, ruling C + AGENTS.md correction) · maintainer 「同意」 2026-09-17T14:41Z

    The maintainer ruled that check-durability-degradation-log-level receives no further investment: the gate stays as it is, return-propagating seams are recorded as its boundary (header limitation 4, census scripts/measure-return-propagating-durability-seams.mjs), and the durability invariant is held by per-file tests. This card is one of the four open improvement cards on that one gate and closes with the ruling — ⛔ not a verdict on its reading, which stands. If a real incident (silent data loss reaching a user) is ever traced to the gap this card describes, that incident is the card to file.


    Generated by Claude Code

  8. removed
    pm:on-holdDecision made, deliberately deferred — no dispatch, no nag; restart condition in the hold comment
    on Sep 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    domain:devxDomain lane — seat card indexed by label:pm:seattoolingFix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions