fix(plugin-email,plugin-security): durability SUMMARY reports must print against an error-less sink - #10396
fix(plugin-email,plugin-security): durability SUMMARY reports must print against an error-less sink#10396os-zhuang wants to merge 1 commit into
error-less sink#10396Conversation
… must print against an `error`-less sink `SweepLogger.error` and `ProjectionLogger.error` are declared OPTIONAL, and both batch summaries were spelled `logger?.error?.(…)` — an optional call that emits NOTHING when the method is absent. #9657 repaired the six per-row reports of this shape; it could not reach these two, because the gate only judges a call inside a `catch` and a summary sits after the loop. That made the split WORSE, not better: against a `{ info, warn }` sink the per-row detail now landed at `warn` while the count of failures stayed silent, so the detail and the total reported through different channels. Both summaries now reach for `error` and fall back to `warn`, never to silence. Also extends `check:durability-log-level` with a SUMMARY limb so the class cannot regress: a report keyed on the counter a durability-critical catch accumulated into is judged on SPELLING alone — the limb never second-guesses a chosen level. Measured before it was proposed: 2 judged (both sites here), 1 discovered and deliberately not judged (`objectql/plugin.ts`, author-chosen `info`), 3 dropped as boolean latches rather than counters. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DdCnBGcHeufjrq7drTD3wt
📓 Docs Drift Check2 anchor(s) derived from 2 changed package(s); no hand-written page names any of them. ✅ What this run could not see
Coarse fallback — 16 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 4f318de6644dc35ad759e3af51a0ff961d95ae9f && git checkout 4f318de6644dc35ad759e3af51a0ff961d95ae9f
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 900e48935638a328b8aaa32ad5ddc29efa0f8dc3 31f2ebced107a633411690c0638835d0ba6ea46e && git checkout -B drift-repro 900e48935638a328b8aaa32ad5ddc29efa0f8dc3 && git merge --no-ff 31f2ebced107a633411690c0638835d0ba6ea46e
node scripts/docs-audit/affected-docs.mjs --json 900e48935638a328b8aaa32ad5ddc29efa0f8dc3 |
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 32406085715 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
跨 PR 相同签名(24h,按失败测试文件聚合):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
Fixes #9748
Two durability summary reports were spelled
logger?.error?.(…)— an optional call that emits nothing when the sink has noerrormethod, anderroris declared optional on both sinks. #9657 repaired the six per-row reports of this shape; it could not reach these two, becausecheck:durability-log-levelonly judges a call inside acatchand a summary sits after the loop.The premise was re-verified against
origin/mainbefore any code was written (801296050): both sites still carried the unguarded spelling, and the gate was still blind to them.The harm, reproduced before it was fixed
Both new tests were written first and run against unmodified source. Both failed, identically — the summary emitted nothing at all:
⭐ The sharp part: both summaries count failures whose per-row line #9657 did repair. So against an
error-less sink the repair made the split worse, not better — the detail arrived atwarnwhile the total vanished, and the counts and the detail reported through different channels. Inpermission-set-projection.tsit is worse still: theelsebranch carrying theinfo"reconciled" line is skipped too, so such a sink heard neither.After the fix, both files are green (
18/18and52/52; full suites420/420and1305/1305).Which option, and why
The card recorded two directions and deliberately did not rule between them. I took both — repair the two sites and extend the gate — because the population turned out to be real and bounded. The measurement came first:
error/fatalinfo/warnobjectql/plugin.ts"Schema sync complete")The naive version of the criterion — "any branch guarded by a counter a durability catch mutated, that logs, must be loud" — reddens 4 correct sites (
database-loader.ts×2,protocol.ts,objectql/plugin.ts). Every one of them is a recovery or success line that is correctlyinfo. Baselining those would have parked correct code in a shrink-only ledger whose header calls every line debt — the exact anti-pattern #5241 removed. Two narrowings, both measured, both load-bearing:flag = truein a catch is a say-it-once latch, not a failure count. Keying on++/+=drops all three latch sites.loggerLevelcannot see the(logger.error ?? logger.warn)(…)fallback, so a loud catch reads assilent-swallow— and the spelling it CAN see prints nothing #9657's question — "you reached forerror; against a sink with noerror, does anything print?" — so it is a narrowing of the question applied to a wider population, not a new semantic judgement. A branch that reports atinfo/warnis discovered, counted, and not judged.The only way to trip the limb is
logger.error?.(…)with no fallback, which is never correct. #9657's rule is unchanged and not weakened, and no baseline was widened —durability-degradation.baseline.jsonis stillentries: [].The reject side, asserted positively
A matcher that quietly stopped matching also prints zero findings. So the gate now prints its census on every run, and the self-test pins the skip count rather than merely observing no findings:
Eight new self-test cases pin both directions plus the reject side (
expectSummaryJudged/expectSummarySkipped), including the two controls that a broken matcher would pass: a boolean latch must yield 0 judged, and an unrelated condition next to the same catch must yield 0 — while theinfobranch must yield exactly 1 skipped. Self-test:63 case(s) passed.Ablation — the limb is shown going red
The gate is a pure source-text AST scanner (
distis in itsSKIP_DIRS), so no build sits in this path and no rebuild leg applies. Mutation and restore were each confirmed on disk by grepping both the injected and the removed text, never by an editor's exit code.?.error?.(), confirmed on disk: injected 1+1, removed 0+0) → gate exit 1,✗ 2 durability report(s) degrade quietly … 0 inside a catch, 2 in a SUMMARY the catch feeds (#9748). Direction as predicted: red. The skip count held at 1 through both legs, so the matcher did not collapse.git diff --exit-code= 0 (byte-identical), gate exit 0, census back to0 conditional.Verification
All gates below were run on the final commit
31f2ebced(git rev-parse --short HEAD), derived withnode scripts/pm/dispatch-gates.mjswith no path arguments — 6 paths, 12 path-matched families plus 6 convention-triggered ones. Exit codes were captured before any pipe. Full detail in the report comment on #9748.Clause ② declared: not reachable — the path limb is
packages/spec/src/**and this change touches none of it.Changeset — judged by publish surface, not by habit:
@objectstack/plugin-emailand@objectstack/plugin-securityare both published (private=false), and this changes their runtime logging behaviour on a path a host can observe. A changeset is owed; this is notskip-changeset, and no such label was applied.⛔ Left in draft, not merged, auto-merge not enabled — landing is the maintainer's.
Generated by Claude Code