Uh oh!
There was an error while loading. Please reload this page.
refactor(plugin-sharing): collapse the two byte-identical MinimalLogger shapes - #11068
Conversation
…er shapes plugin-sharing/src declared seven module-local interfaces all named MinimalLogger. The duplication was not the defect; divergence under one name was — when #10556 made bulk-recompute.ts's `warn` non-optional, tsc reported "Two different types with this name exist, but they are unrelated." bu-tree-recompute.ts and primary-bu-projection.ts were byte-identical, so they now share one declaration, OptionalSharingLogger in logger-shapes.ts. The name is deliberately different: the next forwarding edge between it and a module still declaring its own MinimalLogger names two types instead of one twice. The other five are left alone with a stated reason each, recorded in logger-shapes.ts. Notably record-orphan-cleanup.ts's bare `Function` members cannot be tightened here — `Function` is not assignable to any concrete signature, and both loggers handed to it are themselves spelled `Function`. check:optional-error-sink (#9754) population is unchanged: 37 declaring `error`, 2 permit silence, 2 baselined, verified before and after. The shared shape declares no `error` and must not grow one. Part of #10692 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PnJHU45vPJj5UQrxe946Bx
📓 Docs Drift Check5 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. ✅ What this run could not see
Coarse fallback — 8 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 837b34dbcc48334e1a78448f7328c96ff0b1f2b8 && git checkout 837b34dbcc48334e1a78448f7328c96ff0b1f2b8
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 3e26359a7d87fbe0e03c7236346166c71a821ec0 5eb6636f2b7780b9eb9e390be4e634ebdbf74255 && git checkout -B drift-repro 3e26359a7d87fbe0e03c7236346166c71a821ec0 && git merge --no-ff 5eb6636f2b7780b9eb9e390be4e634ebdbf74255
node scripts/docs-audit/affected-docs.mjs --json 3e26359a7d87fbe0e03c7236346166c71a821ec0 |
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 32581078922 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
跨 PR 相同签名(24h,按失败测试文件聚合):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
Uh oh!
There was an error while loading. Please reload this page.
Part of #10692
Collapses the two genuinely byte-identical
MinimalLoggerdeclarations inplugin-sharingonto one shared internal type. Internal types only — none of theseven local
MinimalLoggerinterfaces was exported, so no published surface and noruntime behaviour changes.
This is
Part of, not a closing keyword, and deliberately so: the divergence the cardis about is not fully closed by this subset. Five declarations remain, and two of
them turn out to sit behind a contract decision that is the PM's to make, not a
refactor. The measurement is below.
The count: it is SEVEN, not six
Derived by enclosing declaration, not by grep hit: every
interface MinimalLogger/
type MinimalLogger =in the package, then each one's members read off thedeclaration itself.
packages/plugins/plugin-sharing/srcdeclares seven interfaces namedMinimalLogger, one per file — all seven spell the same name. The card's title sayssix and its body says "Seven rows, six of which spell the same name"; both are off by
one against the tree at
072d072d2. Nineteen textualMinimalLoggerhits resolve to7 declarations + 12 type annotations.
Normalising away doc comments, there are 5 distinct structural shapes:
check:optional-error-sinkpopulation?{ info?, warn, error? }— all(msg: any, ...rest: any[]) => voidbulk-recompute.ts{ info?, warn }— same signaturesrule-hooks.ts,record-share-cascade.tserrorabsent){ info?, warn? }— same signaturesbu-tree-recompute.ts,primary-bu-projection.ts{ info?, warn? }— but a string message + Record meta signature (see below)sharing-rule-provenance.ts{ info?: Function, warn?: Function }record-orphan-cleanup.tsThe card groups three files as
{ info?, warn? }. That grouping normalises away themember signatures, and they differ:
sharing-rule-provenance.tsspells its membersa string message plus a Record meta object, while
bu-tree-recompute.tsandprimary-bu-projection.tsspell theirs with ananymessage and a rest parameter:Only the latter two are byte-identical, so only those two are unified here. Folding
sharing-rule-provenance.tsin would go one of two ways, and both change meaning,so neither is a de-duplication:
anyspelling → deletes real checking at its call sites;What is unified
bu-tree-recompute.tsandprimary-bu-projection.tsnow shareOptionalSharingLoggerin the newsrc/logger-shapes.ts.The shared type is given a different name on purpose. The card's complaint is the
diagnostic
Two different types with this name exist, but they are unrelated.A futureforwarding edge between this shape and a module still declaring its own
MinimalLoggernow names two different types instead of the same name twice. (
OptionalLoggerwasnot reused —
plugin-webhooks/src/auto-enqueuer.tsalready declares a different typeunder that name, which is the same hazard.)
⛔
record-orphan-cleanup.tswas left alone — it is blocked, not skippedThe card asks for its bare
Functionmembers to be replaced with real signatures.That is not possible without a contract decision, and the reason is measured, not
argued:
Functionis assignable to no concrete signature. The two loggers actually handed tosweepOrphanedRowsByRecordExistenceareSharingServiceOptions['logger'](
sharing-service.ts:1381) andShareLinkServiceOptions['logger'](
share-link-service.ts:704), and both are themselves spelled with bareFunction— uncast, unlike every other logger entry point in this package, whicharrives as
ctx.logger as any. So tightening the consumer requires tightening thosetwo producers first.
I measured what that costs, then reverted it byte-identically:
errorTightening them costs 0 compile errors — and enrols two new RED sinks:
Both shapes are latently red today: they declare
error?beside an optionalwarn,which is exactly what #9754 forbids. They escape the gate only because bare
Functionis not a
FunctionTypeNode, so the gate's structural matcher never sees them. And thegate's own prescribed repair — dropping the
?fromwarn— lands onSharingServiceOptionsandShareLinkServiceOptions, both publicly exported fromsrc/index.ts, so it is a breaking change for any host passing{ info, error }.That is a contract decision for the #10556 family. Recorded on #10692 for the PM rather
than done quietly here.
check:optional-error-sink— the load-bearing check, before and afterPopulation unchanged, quoting the gate's own verdict lines:
Before
After — the two enforced numbers and the verdict line are identical:
One informational number did move, and it is not a membership change: the
narrowings:line's tally of pure sinks declaring noerrorwent56 → 54. Ipredicted
55and was wrong by one; the miss is explained rather than waved at. Twodeclarations were removed and one added, so
-2 +1 = -1was the prediction. The newdeclaration is not counted at all, because the gate prefilters files by text before
parsing:
logger-shapes.tsnever spellserrorfollowed by:or(, so it is skipped beforethe parser sees it. The two files it replaced matched that regex only because their call
sites pass
{ error: err?.message }— a literalerror:. Verified directly: deletinglogger-shapes.tsleaves the tally at 54, unchanged.This is sound for what the gate enforces (a sink declaring
errornecessarilymatches the regex), but it means the advertised "cost of the narrowing" tally
undercounts. Filed separately as #11069; no behaviour here depends on it.
Proof that
tscwould have failedA type change has no runtime behaviour to assert, so the compiler is the instrument —
and an instrument not shown to be live is not a measurement. Signature predicted
before running:
Predicted — mutate the shared type's
warnto(msg: string, code: number) => void;expect 6 ×
TS2345"not assignable to parameter of type 'number'", 3 in each file,exit 2; errors in both files being the load-bearing part, since that is what proves
both modules really consume the single shared declaration.
Observed — exactly that:
6 errors, 3 per file, exit 2 — prediction and observation agree on count, code, shape
and distribution.
Restore proved byte-identical by
git hash-object(
2533d6dad5348f3728a6db38edb0eafad3b56a61before the mutation and after the restore),and the restore leg re-run to a real verdict rather than trusted on the hash:
tscexit 0, 0 errors,
git status --porcelainempty.The two call sites the card names still compile and their
warnrequirement isunchanged —
rule-hooks.tsandrecord-share-cascade.tsare untouched by this PR.Gates
Union derived on the final commit
5eb6636f2, clean tree,node scripts/pm/dispatch-gates.mjswith no path arguments (it takes the change setfrom the merge base itself: 4 paths vs
072d072d2). It named 11 families plus oneconvention-triggered. It did not name
check:optional-error-sink— that gatecomputes its own population and scores
silentfor every card — which is exactly whythe card mandated it by hand.
All verdicts below are the gates' own printed lines; exit codes were captured before any
pipe.
check:optional-error-sink✓ optional-error sink contract … (2 baselined, shrink-only)plugin-sharing typecheckplugin-sharing testTest Files 25 passed (25)·Tests 624 passed (624)check:changeset-gate-self-tests✓ check-empty-changeset --self-test: 118 assertions over real temp git reposcheck:objectui-changeset✓ objectui-changeset-digest: 70058c167c14..a90fbe836c37 walks completelycheck:slot-lookup✓ slot-lookup ratchet holds: 107 unswept site(s) in 25 file(s), none newcheck:test-source-aliascheck-test-source-alias OK — 72 packages with tests scannedcheck:type-source-resolutioncheck-type-source-resolution OK — 77 packages with a tsconfig.json scannedcheck-adr-0087-registration✓ this PR adds no declared-breaking changeset (1 non-breaking changeset(s) seen)check-changeset-no-major✓ This diff introduces no \major` bump.`check-ci-filter-parityOK: all 83 declared cross-package glob(s) (72 unique) are coveredcheck-empty-changeset✓ No empty-frontmatter changeset introduced by this diffcheck-plugin-teardown-shape✓ 63 Plugin implementation(s) across 4446 source(s); baseline fully burned downcheck-affected-docs✓ affected-docs self-test: 339 cases pass.check:nul-bytes✓ check-nul-bytes --self-test: 75 assertions over a temp git repocheck:i18ncheck-i18n-bundles: OK (9 package(s) — all bundles in sync)·plugins/plugin-sharing in sync (4 bundle(s))check:i18nfirst returnedPREREQUISITE NOT MET — the workspace CLI is not built … Nothing was checkedat exit 1. That is not measured, never a pass, so the CLI wasbuilt (
turbo run build --filter=@objectstack/cli) and the gate re-run to the realverdict quoted above.
Open question for the PM — not decided here
What should this package's logger contract be? Three candidates, with cost:
forwarding edge re-opens the same diagnostic.
{ info?, warn? }contract with a precise signature(a string message plus a Record meta object), absorbing
sharing-rule-provenance.ts,bu-tree-recompute.tsandprimary-bu-projection.ts. Cost: 0 at every caller —all three receive
ctx.logger as anyorundefined, so no caller constrains them —and it gains arity/type checking at ~12 in-module call sites, which is precisely
what the card says bare
Functionfails to provide. This is my recommendation forthe
{ info?, warn? }family, but it tightens two modules, so it is a contract call,not a refactor.
error. ⛔ Not recommended. Itenrols four modules into
check:optional-error-sink's population — a ledger beingpaid down deliberately (Pay down the optional-error sink ledger — 13 paid, 2 remain and both are DESIGN CALLS (was: "15 sink types") #10556: 15 → 3 → 2, shrink-only) — and the repair it then
demands is a breaking change to two exported option types.
The
record-orphan-cleanup.tsproducer question above is the sharper one, and it islive whether or not this card proceeds: two shapes are red under #9754's rule today and
are hidden only by a spelling.
Generated by Claude Code