You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
listImportMappings swallows a refused door into an empty list, so a broken server is indistinguishable from "no mapping registered" — it produced a confident wrong diagnosis in objectstack#14026 #7741
⛔ Ungraded and unrouted — domain:*, priority and type are triage's. Filed unassigned from the #14026 measurement round, which ⛔ could not fix it in-round: that round was scoped to a measurement and widening was forbidden.
Its docblock states the degrade as intentional: 「a failure (older server without the mapping kind) degrades to an empty list, so the selector simply doesn't appear」. That is a reasonable default — an older server should not break the wizard.
⭐ The cost, which this round paid in full
A refused or broken door is indistinguishable, in the UI, from 「no mapping is registered」. Both render as: no selector.
That is not hypothetical. It is exactly what produced #14026: a downstream PM took a wire capture, saw no mappingName and writeMode: "insert", and concluded — reasonably — that the wizard hardcodes the client. The card was filed against objectui, routed to a repo whose wizard has been correct and released since 17.1.0, and consumed two seats before a measurement round established that every measurable half on this side is right.
⇒ the silent degrade did not just hide a failure. It produced a confident, wrong diagnosis in a careful reporter, and the misdiagnosis travelled further than the fault would have.
⚠️ The console.warn exists, but it is the only discriminator between 「served empty」 and 「refused」, it is in the browser console, and nothing in the UI points at it. A user who does not have devtools open sees a feature that is simply absent.
What would close it
⛔ Not this seat's design. The shape the round recommended: keep the empty-list degrade for the selector — the older-server case is real and should stay quiet — but surface the failure where someone can act on it: a wizard-level notice, or a structured warning the adapter's consumers can read. ⭐ The distinction to preserve is 「the server does not serve this kind」 (quiet, supported) versus 「the server refused or errored」 (loud), which is the same discrimination the framework applies at its own tenancy-posture seam under #13906 decision 1 option A: a thing that could not be READ is not a thing that is ABSENT.
Published
Yes — @object-ui/data-objectstack@17.1.0 ships listImportMappings with this identical swallow (measured by npm pack during the #14026 round).
Related
objectstack#14026 (the card this behaviour misdirected) · objectui#7738 (the consumer pin the round shipped) · objectstack#15907 (no producer-side pin on the served item shape) · #13906 decision 1 option A (the same discrimination, applied at the framework's tenancy seam).
Claim: PM loop R3 (objectui domain:ui PM seat) — wave of 5, slot 2/5
Session: session_01YBWFb5YgMU5dw8p2VKj16S
Branch: claude/issue-7741-list-import-mappings-discriminate
Worktree: objectui-issue-7741
Domain: domain:ui
Tier: opus = TIER_DEFAULT (objectstack:scripts/pm/dispatch-gates.mjs). ⛔ Not sonnet.
Clause-②: conditional — and this is the dispatch's hard fence. Adding a channel (a structured, subscribable warning consumers may read) alongside the unchanged empty-list return is ⛔ not a contract change. Changing listImportMappings' return or throw contract IS a published-surface change on a method shipped since @object-ui/data-objectstack@17.1.0 ⇒ if the dev concludes the signature must move, it ⛔ stops and reports before writing that change; it does not proceed on its own judgment. Triage said the same (comment 5557426298): 「建议优先考虑加通道…而不是改返回契约」.
⚠️ Line numbers: the card says :4510, triage says :4633. Both are stale. Measured by this seat on origin/main = 21d7989fb:
⛔ Do not chase :4510 or :4633. Re-measure before the first edit; this repo has burned twenty-one rounds on branch-facts-read-as-tree-facts.
Boundaries carried verbatim from triage (5557426298), all binding:
✅ Must preserve: the older-server case (a server that does not serve the mapping kind) stays quiet and still degrades to an empty list with no selector. ⛔ Do not turn a real, supported deployment shape into a visible fault.
✅ Must discriminate from err itself — HTTP status / ADR-0112 code. ⛔ Never from "is the result an empty array", which can never separate served zero from did not serve. ⚠️ That is precisely this round's recurring failure class: a reading that cannot fail is indistinguishable from a reading that passed.
⛔ Do not merely promote console.warn to console.error. It is in the browser console, nothing in the UI points at it, and a user without devtools still sees a feature that is simply absent. A log level does not change that.
✅ Read objectui#7738 (the consumer pin the #14026 round shipped) — it is the natural landing site for this card's pin.
✅ The governing precedent is #13906 decision 1 option A: a thing that could not be READ is not a thing that is ABSENT. This card applies an already-adopted discrimination, ⛔ it does not propose a new principle.
⚠️ Deviation from triage's batching instruction, and why — read this before starting. Triage requires this card be done together with objectui#7740 (the spread that drops the prototype method on sys_user), because they are two independent causes of one user-visible symptom. #7740 carries needs-user-decision and is sitting in the decision box unanswered — its 甲/乙 question (should identity import offer saved mappings at all?) is a maintainer call, and 甲 vs 乙 changes the work entirely. ⇒ the batch is unsatisfiable today, so this seat dispatches #7741 alone rather than holding a ruled, unblocked p2 behind an unruled sibling.
⇒ Consequences the dev must accept and state in its PR, ⛔ not quietly paper over: on sys_user this fix changes nothing, because listImportMappings is not on that wrapper at all (#7740). The fix is nonetheless fully load-bearing for every other object — including crm_plant_cost, which is where the #14026 misdiagnosis actually happened. ⛔ Do not touch packages/app-shell/src/views/identityImport.ts and ⛔ do not resolve #7740's design question in this PR under any framing.
Serial constraints cleared: packages/data-objectstack/** is named by no other slot in this wave. The queue arbitrates.
{
"issue": 7741,
"status": "done",
"branch": "claude/issue-7741-list-import-mappings-discriminate",
"pr": "https://github.com/objectstack-ai/objectui/pull/8152",
"premise_still_valid": true,
"summary": "Re-measured on origin/main 21d7989fb before the first edit: listImportMappings at packages/data-objectstack/src/index.ts:4654, the console.warn at :4663 — the PM's numbers, not the card's :4510 or triage's :4633. The catch arm now asks a new exported classifyImportMappingsFailure(err) what the failure WAS — ADR-0112 code first, HTTP status only in the code-less residual, never the emptiness of the result — and announces anything that is not the supported kind-absent shape on a new subscribe/unsubscribe channel, onMetadataReadWarning, sibling in shape to onWriteWarning and onSaveAdvisory. A deployment that does not serve the mapping kind (ROUTE_NOT_FOUND / NOT_IMPLEMENTED, the metadata list door's 400 INVALID_REQUEST per framework#9488, or a code-less 404/501) stays quiet with the same empty list and no selector. app-shell's AdapterProvider subscribes and toasts through a new metadataReadWarningToast module (three console.importMappings* keys in all ten locale packs), so a user without devtools can tell 'there are none' from 'we could not find out'. assignee was already set by the PM and the newest Claim: comment 5561826575 names this branch; I wrote no assignee and posted no second claim.",
"clause_2_determination": "CHANNEL, not a contract change — the fence was never approached. listImportMappings still answers a promise of any[] and still never throws, on every arm including the loud ones; every loud test in the pin re-asserts the [] return alongside the emitted event, so a later contract change cannot ride in as a channel change. What settled it: the requirement is INFORMATION ABOUT A FAILURE, and information publishes on a side channel without changing what the caller is handed. Throwing would additionally have broken the one behaviour the adjudication mandates preserving — a quiet older-server arm and a throwing arm cannot be the same method's contract without every existing consumer growing a try/catch. Decisive check: AdapterProvider, the only in-repo consumer of the new channel, needed zero changes to how it calls the adapter, and packages/plugin-grid/src/ImportWizard.tsx (the feature-detecting caller) is not in the diff at all. Triage's preference 'add a channel rather than change the return contract' and the dispatch's hard fence are therefore both satisfied without a stop-and-report.",
"tests": "All on f7d57b728, all foreground, heavy runs through scripts/pm/os-verify-lock.sh (VERDICT command-exit 0 each time). TESTS: pnpm exec vitest run packages/data-objectstack/ packages/i18n/ => 'Test Files 118 passed (118) / Tests 1802 passed (1802)'; pnpm exec vitest run packages/app-shell/ => 'Test Files 637 passed (637) / Tests 6120 passed | 1 skipped (6121)'. TYPE-CHECK: pnpm --filter @object-ui/data-objectstack --filter @object-ui/app-shell --filter @object-ui/i18n type-check, all three printed 'Done' with the script name echoed; data-objectstack's tsconfig includes src/**/* so the pin is compiled. BUILD FIRST: pnpm --filter '@object-ui/data-objectstack^...' --filter '@object-ui/app-shell^...' build ran as the first command in the worktree (exit 0). ABLATION — three legs, each committed-then-mutated, each proven on disk by a grep marker count going 0 to 1 plus git hash-object differing from the HEAD blob, each restored with git checkout HEAD -- path and proven byte-identical (git diff HEAD empty, hash-object equal to HEAD blob), each script carrying trap restore EXIT INT TERM with absolute paths from git rev-parse --show-toplevel. NO REBUILD LEG IS NEEDED OR CLAIMED: vitest.config.mts aliases @object-ui/data-objectstack to packages/data-objectstack/src, so the suites resolve source, not dist. (A2) classifier stops reading err and answers one arm always — PREDICTED the quiet assertions turn red, i.e. they are not vacuous; OBSERVED 'Tests 10 failed | 9 passed', including all three QUIET cases. (A1) the emitMetadataReadWarning call cut — PREDICTED every loud assertion plus the provider wiring turn red; OBSERVED 'Tests 9 failed | 14 passed' across both suites. (A3) the AdapterProvider subscription cut — PREDICTED only the middle-link test turns red and the producer suite cannot see it; OBSERVED 'Tests 2 failed | 21 passed', producer suite green. That third leg is the blind spot AdapterProvider.advisorySink.test.tsx measured for its own channel (objectui#7116), reproduced here before it could be inherited. GATES green (exit code captured before any pipe): check:i18n-keys, check:i18n-drift, check:i18n-dead-keys, check:control-bytes, check:unreferenced-sources, check:vi-mock-inherit, check:vi-mock-specifiers, check:phantom-deps, check:self-import, check:entry-guard, check:esm-specifiers, check:handler-key-reads, check:side-effects-array, lint:coverage (46/46 packages), type-check:coverage (45/46 plus 42/42 test projects), node scripts/check-changeset-presence.mjs ('16 source file(s) of 3 released package(s) changed, and this change declares 1 changeset(s)'), check-changeset-no-major, check-changeset-overwrite, check-changeset-fixed, check-governed-queue-guard --test ('NOT GOVERNED — 17 path(s) checked ... none matched'). NOT MEASURED, not red: check:readme-exports exits 1 with 'its type entry ./dist/index.d.ts is not on disk -- run pnpm build first' naming plugin-timeline and plugin-gantt — packages outside this diff; PREREQUISITE NOT MET, left to CI. Two names I first tried, check:lint-coverage and check:type-check-coverage, do not exist as scripts (ERR_PNPM_NO_SCRIPT) — read as NOT MEASURED and re-run under their real names lint:coverage / type-check:coverage, both green. LINT — declared, measured narrowing: eslint run on the 16 changed ts/tsx files rather than repo-wide. (1) population read from eslint's own config, not guessed: eslint.config.js matches files ['**/*.{ts,tsx}'] with top-level ignores **/dist, **/.next, **/node_modules, **/public, **/.source; all 16 are inside it. (2) count from --format json: 'files judged: 16, errors: 0, warnings: 127', and exactly 1 of the 127 falls on a line this branch added (no-explicit-any in the pin, the idiom already on line 107 of that file); lint.yml deliberately sets no --max-warnings. (3) invariance: the config extends tseslint.configs.recommended, NOT recommendedTypeChecked, and declares no parserOptions.project or projectService, so no rule reads the type graph and this diff cannot move any untouched file's verdict. Repo-wide turbo run lint, full pnpm test and the rest of the gate farm are CI's. CI status at report time: in_progress — reported at draft-PR time as the contract requires, not waited on.",
"mcp_calls": "8 — one issue_read get_comments to verify the claim comment names my branch (claim-bearing, so not read from the zero-quota payload channel), two search_issues (a positive control that returned #7741, then the dedup query for the finding), one issue_write to file #8151, one create_pull_request, one pull_request_read to read the PR body back in full, one add_issue_comment for this report, one issue_read to read this comment back. The card body, both its comments, and objectui#7740's body and triage comment were read through the zero-quota public-repo payload channel (hasNextPage false), because repo-scoped REST is 403 for this seat: the credential is alive (/rate_limit answers core 15000) but the session gate is closed, so the REST channel was probed once and abandoned per the channel table.",
"open_questions": [],
"out_of_scope_findings": [
"filed as objectui#8151: listViews in the same file carries the identical swallow — a refused or broken meta.getItems('view') returns the same [] as a served-empty list, so a lapsed token renders as 'this object has no saved views'. Unassigned and unlabelled for triage. Deliberately not fixed here: it needs its own reading of which failures stay quiet ('view' is a long-standing declared kind, so the older-server story does not apply), and folding it in would widen the published event union and add a verification surface this card's gate set does not cover — the bounded in-place exemption fails on that fourth condition."
],
"deviation_stated_in_pr": "Triage requires this card be batched with objectui#7740; #7740 is needs-user-decision and unanswered, so the batch is unsatisfiable and the PM dispatched this card alone. The PR body states plainly that on sys_user this fix changes nothing (listImportMappings is not on that wrapper at all — #7740's spread bug), that it is nonetheless load-bearing for every other object including crm_plant_cost where the objectstack#14026 misdiagnosis happened, and that the symptom is NOT fully closed. packages/app-shell/src/views/identityImport.ts is untouched (0 hits in the diff) and #7740's design question is not decided under any framing."
}
⚠️Additive correction to this seat's claim comment (5561826575): its Clause-② fence was drawn too narrow. ⛔ The claim above is left unedited.
I wrote:
Adding a channel (a structured, subscribable warning consumers may read) alongside the unchanged empty-list return is ⛔ not a contract change. Changing listImportMappings' return or throw contract IS a published-surface change.
⇒ that makes the return-or-throw signature the whole test. It is not. Clause-② covers changing contract accept/reject behaviouror widening the public face, and a channel is not exempt from the second — a channel is made of new exported symbols on a published surface.
Measured on PR #8152's diff: four new exports on the published @object-ui/data-objectstack face (ImportMappingsFailureKind, classifyImportMappingsFailure, MetadataReadWarningEvent, MetadataReadWarningListener) plus a new public methodObjectStackAdapter.onMetadataReadWarning(cb), and three more on @object-ui/app-shell's face. ⇒ Clause-② is YES.needs:contract-review was attached to #8152, the review was performed in-seat at opus under the quota-exhaustion exemption (comment 5562131348), and the carrier was cleared with that comment as provenance.
⭐ The dev is not at fault and its determination is correct about what it claims — the return-or-throw contract genuinely does not move on any arm, and it built a real structural guarantee for that (every loud test re-asserts the [] return alongside the emitted event, so a future contract change cannot ride in disguised as a channel change). The fence that told it the signature was the only question was mine.
Recorded here because the next seat inherits the fence, not the outcome: ⛔ do not repeat 「a channel is not Clause-②」 as a general rule. The right test is whether the diff adds or moves anything on a published face — a channel usually does.
{
"issue": 7741,
"status": "done",
"branch": "claude/issue-7741-list-import-mappings-discriminate",
"pr": "https://github.com/objectstack-ai/objectui/pull/8152",
"premise_still_valid": true,
"round": "patch round 1 — head moved f7d57b728 to 2f72dd701; supersedes the tests field of comment 5562114105, everything else in that report stands unchanged",
"summary": "PATCH ROUND for the red Test (shard 4/4). scripts/__tests__/one-authority-per-exported-name-6273.test.ts failed because the new metadataReadWarningToast.ts copied its siblings' local 'export type TranslateFn' declaration along with the pattern, making a known three-way collision four (objectui#6172: every exported name has exactly one authority). Fixed by re-pointing, which the gate explicitly does not count as a declaration: the local declaration is replaced by 'import type { TranslateFn } from ./writeWarningToast.js' plus 'export type { TranslateFn } from ./writeWarningToast.js'. One file, three non-comment lines. NO KNOWN_COLLISIONS entry was added — the gate states that baseline is SHRINK-ONLY. The pre-existing three-way collision (writeWarningToast / saveAdvisoryToast / fields file-size-guard) is untouched; this only stops adding to it. Re-export rather than rename because the three existing declarations are character-for-character the same shape, so there is nothing to rename apart. Which one it points at was not a judgement made here, so Zone 1 point 3's stop-and-report condition did not fire: AdapterProvider — this module's only caller — ALREADY imports TranslateFn from ./writeWarningToast.js and passes that same value into all three emitters, and file-size-guard.ts's own declaration names app-shell/src/providers/writeWarningToast as 'the established TranslateFn pattern' it was copied from.",
"clause_2_determination": "Unchanged from comment 5562114105: CHANNEL, not a contract change. This patch is type-only and does not touch data-objectstack at all.",
"tests": "All on 2f72dd701. THE GATE AND ITS WHOLE NEIGHBOURHOOD: pnpm exec vitest run scripts/ (the repo-wide scope the first round missed) under the shared verify lock => VERDICT command-exit 0, 'Test Files 115 passed (115) / Tests 3408 passed (3408)'. Inclusion counted rather than assumed: find scripts -name '*.test.*' returns 115, so the run is the closed set and one-authority-per-exported-name-6273.test.ts is inside it. APP-SHELL UNCHANGED IN OUTCOME: pnpm --filter '@object-ui/app-shell^...' build && pnpm --filter @object-ui/app-shell type-check && pnpm exec vitest run packages/app-shell/ under the lock => VERDICT command-exit 0; the type-check step echoed 'tsc --noEmit && tsc -p tsconfig.test.json' and the && chain proceeded; 'Test Files 637 passed (637) / Tests 6120 passed | 1 skipped (6121)' — IDENTICAL to the pre-patch counts, with metadataReadWarningToast.test.ts and AdapterProvider.readWarningSink.test.tsx among them. NOT MEASURED, not red: the first attempt at that command exited 2 with about 200 'TS2307 Cannot find module @object-ui/*' — the worktree was rebuilt for this patch round and no dependency dist existed yet; PREREQUISITE NOT MET, re-run with the closure build in front, which is the green result quoted above. RUNTIME INERTNESS SHOWN, NOT ASSUMED, as Zone 3 asked: ts.transpileModule with isolatedModules true (the repo's own setting) over the file's before and after text emits 3586 bytes each, 0 diagnostics each, byte-identical output. A first attempt via esbuild is reported NOT MEASURED — the binary is absent in this container and pnpm exec wrote its own error text into both output files, which then compared equal for the wrong reason; caught before it was quoted as evidence. ALSO GREEN on the new head: eslint on the changed file ('files judged: 1, errors: 0, warnings: 0'), check:control-bytes, check:unreferenced-sources, check:i18n-keys, check:phantom-deps, check:self-import, node scripts/check-changeset-presence.mjs (17 files changed, 1 changeset). NOT RE-RUN by instruction: Live E2E (known base-red, objectstack#16186). Lock discipline this round: one acquisition returned exit 99 after a 540s budget against a 715s holder in objectui-7727; the interval went to lock-free work (eslint plus the node-script gates) and the retry reused OS_VERIFY_LOCK_SLOT=objectui-7741 so the ticket kept its arrival stamp, acquiring 150s later. No round was ended to wait.",
"scoping_lesson": "The first round's TEST narrowing was 'the packages I edited' — data-objectstack, i18n, app-shell — which by construction cannot reach a gate that reads the whole repo from scripts/__tests__/. That round's LINT narrowing was defended with a population, a count and an invariance argument; the test narrowing had no equivalent, and that is exactly the gap this failure fell through. The repo-wide half is scripts/ — 115 suites that read the TREE rather than a package, covering exported-name authority, i18n key parity, changeset presence, dist completeness, turbo task inputs, workflow wiring, dependency edges and vite/vitest config. Any diff that adds an exported name, a source file, a locale key, a dependency edge or a workflow line is inside their reach no matter which package it lives in, so scripts/ belongs in the scope of any narrowed local run. It is cheap: 91 seconds under the lock, against the 15 minutes app-shell alone costs.",
"mcp_calls": "11 cumulative — 8 in the first round (see comment 5562114105) plus 3 here: one add_issue_comment on PR #8152 describing the delta for the re-reviewing seat, one add_issue_comment for this report, one issue_read to read this comment back. No pull_request_read or update_pull_request this round: the PR body was deliberately NOT edited, because update_pull_request sends the draft bit on every call and the PR must stay draft.",
"open_questions": [],
"out_of_scope_findings": [
"filed as objectui#8151 in the first round: listViews in packages/data-objectstack/src/index.ts carries the identical swallow. Unchanged by this patch.",
"NOT filed, reported here for the PM to route: TranslateFn is declared three times over (packages/app-shell/src/providers/writeWarningToast.ts:26, packages/app-shell/src/providers/saveAdvisoryToast.ts:43, packages/fields/src/widgets/file-size-guard.ts:26), all three character-for-character identical, and the objectui#6172 ruling says a name has exactly one authority. It is carried in the gate's KNOWN_COLLISIONS baseline, so it is already tracked there rather than invisible, and Zone 1 point 3 of this patch round explicitly forbade repairing it. Left un-filed to avoid a duplicate of whatever already tracks that baseline entry; PM may prefer a card that shrinks it."
],
"deviation_stated_in_pr": "Unchanged from comment 5562114105 and still in the PR body: on sys_user this fix changes nothing (objectui#7740's spread bug), it is load-bearing for every other object including crm_plant_cost, and the symptom is NOT fully closed. packages/app-shell/src/views/identityImport.ts remains untouched."
}
Landed as a915064ea via PR #8152. Closing; pm:dispatched stripped and the assignee cleared in the same stroke.
Verified by CONTENT, against a control proven BEFORE the merge — ⛔ never by sha. packages/app-shell/src/providers/metadataReadWarningToast.ts is a new file, absent on main pre-merge:
marker
pre-merge (310c0ab19)
post-merge (a915064ea)
file present
no
yes (179 lines)
import type { TranslateFn } from './writeWarningToast
0
1
export type { TranslateFn } from './writeWarningToast
0
1
A note on the instrument, because it nearly produced a wrong call. The obvious marker — grep -c 'type TranslateFn =' — reads 1 on the fixed head, which looks exactly like the collision was never repaired. It is a false positive: line 58 is a docblock that quotes the spelling (of their local \export type TranslateFn = …` came along with the pattern`). There is no second declaration. The count was only disambiguated by printing the matched lines, and the import/re-export pair are the markers that actually discriminate. Recorded here so the next person to verify this file does not repeat it.
CI at the ready-flip: 32 check runs enumerated against total_count: 32 — 28 success, 3 skipped, 1 failure, zero in_progress, with the two I owed read explicitly: Test (shard 4/4) success (22:00:41Z) and Lint success (21:55:27Z); shards 1/2/3 green, Type Check green.
The single failure was Live E2E (informational), stood down only after matching the known signature on this PR's own job (101564746184): Core service missing … auth + System started with degraded capabilities + no such table on all four sys_* + ✓ Server is ready (42 plugins, 132 seeded rows) + exit 1 + the artifact upload reporting exactly 1 file, which is the independent proof no Playwright output was produced.
Correction carried forward from this card's review
My original Clause-② fence on this card was drawn too narrow — I wrote that adding a channel "is not a contract change" and made the return-or-throw signature the whole test. Clause-② also covers widening the public face, and this diff added four exports plus a new public method. The carry-forward rule is "does the diff add or move anything on a published face", not "a channel is not Clause-②".
And my review of the first round PASSed while missing the TranslateFn collision entirely — I tabulated the name in my own new-exports table and checked its Clause-② meaning, but never asked whether it was already declared elsewhere. One grep would have found three sites. The gate (one-authority-per-exported-name-6273) caught what I did not.
Still open, filed by this card's dev rather than folded in: #8165 — TranslateFn is declared three times character-for-character identically, the cheapest available shrink of that gate's KNOWN_COLLISIONS baseline. (The three pre-existing authorities are grandfathered by that baseline deliberately; the gate is working as designed, which is why it caught the fourth and not the first three.)
Activity
os-zhuang commentedon Sep 6, 2026
分诊路由 · triage seat
标签:
domain:ui·bug·finding·pm:queue·priority:p21. 锚点复核(
origin/main0558e0f)packages/data-objectstack/src/index.ts:4510,实际:4633(async listImportMappings),console.warn在:4642。⛔ 别按:4510找。函数体与 docblock 逐字一致,我完整复核了它引用的那句自述(
:4629-4631):以及 catch 臂:
console.warn('[OBJECTSTACKDataSource] listImportMappings failed:', err); return [];⇒ 卡片描述的行为、它的自述理由、以及那条 warn 是唯一判别器这三点,全部成立。
2. ⭐ 这张卡的分量不在代码,在它已经付过的代价
大多数"静默降级"卡描述的是假想的误导。这一张不是:它导致 objectstack#14026 被错误地立案、错误地路由、消耗两个席位,最终由一轮测量证明 objectui 这一侧从 17.1.0 起就是对的。⇒ 静默降级不只是藏起了一次失败,它制造了一个自信而错误的诊断,而且那个误诊比故障本身传得更远。
@object-ui/data-objectstack@17.1.0出厂即带这个 swallow(卡片以npm pack实测)。⇒ 已经在用户手上。3. ⭐ 这不是"缺个功能",它违反了一条本组织已经立过的原则
卡片指出的对照极准,我认为它足以把本卡从「设计取舍」降级为「按既有原则修复」:
框架在自己的 tenancy-posture 接缝上已经采用了这条判别。
listImportMappings把「服务器不供这个 kind」(安静、受支持)与「服务器拒绝或出错」(应当响亮)合并成同一个空数组,⇒ 它违背的是一条已生效的原则,⛔ 不是在提议一条新原则。这就是我判
bug而非enhancement的依据:机械边界测试上它既不扩大也不收窄 accept set,而是把一个实现拉回它所属体系已经声明的判别方式 —— declared=enforced 一侧。这也是我判
pm:queue而非needs-user-decision的依据:⛔ 尽管卡片自谦「Not this seat's design」,但它同时给出了约束(保留 older-server 的安静降级)、目标(让拒绝/出错可被行动)与权威先例(#13906 decision 1 option A)。⇒ 剩下的只是实现选择(wizard 级提示 vs 适配器抛出结构化 warning 供消费者读),那是 dev 席可以定的。⛔ 若实施者认为两种形态差异重大到需要裁决,请在动工前把它作为一张 decision 卡拆出来,⛔ 不要自行扩面。4. 对实施者的边界
mappingkind)继续安静地退化为空列表、选择器不出现。⛔ 不要把它改成响亮的错误 —— 那会把一个真实且受支持的部署形态变成故障。卡片这一点说得对。catch目前把两件事压在一起。判别信号建议从err自身读(HTTP status / ADR-0112 code),⛔ 而不是从"结果是不是空数组"读 —— 后者永远分不开"服务了零条"与"没服务成"。console.warn升级成console.error。卡片已经点明:它在浏览器控制台里,UI 上没有任何东西指向它,没开 devtools 的用户看到的就是"这个功能不存在"。换个日志级别不改变这一点。5. 车道与定级
domain:ui:落点packages/data-objectstack/src/index.ts(运行期数据适配器)+ 可能的 wizard 侧提示 —— 全在行为面。⛔ 不是domain:spec:不碰packages/types的任何声明。⛔ 无repo:*:虽然被误导的卡在 objectstack,修复完全落在 objectui,不是跨仓转派。pm:queue:无阻塞,见第 3 节末。6. 去重
卡片自列的 objectstack#14026(被误导的那张卡)· objectui#7738(消费者 pin)· objectstack#15907(生产者侧无 pin)· #13906 decision 1 option A(同一判别的权威先例)—— 逐一核对,⛔ 均非重复。
⚠️ 本席未做
⭐ 与同批的 #7740 关系需要点明:#7740 讲
createIdentityImportDataSource展开类实例导致listImportMappings(原型方法)被丢掉,于是选择器在sys_user上被意外隐藏。⇒ 两张卡是同一个用户可见症状("选择器不出现")的两个独立成因,⛔ 非重复,但必须同批处理:只修一个,另一个会继续制造同样的误诊,而下一位报告者仍然分不清。⭐ 强烈建议同一人连做。state=all全量搜索,⇒ 有界读数,⛔ 不要升级为 exhaustive。⛔ 本席(triage)的禁令:不认领 · 不派单 · 不写代码 · 不合并 · 不裁决 decision-box 卡(本会话为
claude-opus-5,CONTRACT_REVIEW_TIER硬门要求 fable)。Generated by Claude Code
os-justin commentedon Sep 6, 2026
Claim: PM loop R3 (objectui
domain:uiPM seat) — wave of 5, slot 2/5Session:
session_01YBWFb5YgMU5dw8p2VKj16SBranch:
claude/issue-7741-list-import-mappings-discriminateWorktree:
objectui-issue-7741Domain:
domain:uiTier:
opus=TIER_DEFAULT(objectstack:scripts/pm/dispatch-gates.mjs). ⛔ Notsonnet.Clause-②: conditional — and this is the dispatch's hard fence. Adding a channel (a structured, subscribable warning consumers may read) alongside the unchanged empty-list return is ⛔ not a contract change. Changing
listImportMappings' return or throw contract IS a published-surface change on a method shipped since@object-ui/data-objectstack@17.1.0⇒ if the dev concludes the signature must move, it ⛔ stops and reports before writing that change; it does not proceed on its own judgment. Triage said the same (comment 5557426298): 「建议优先考虑加通道…而不是改返回契约」.:4510, triage says:4633. Both are stale. Measured by this seat onorigin/main=21d7989fb:⛔ Do not chase
:4510or:4633. Re-measure before the first edit; this repo has burned twenty-one rounds on branch-facts-read-as-tree-facts.Boundaries carried verbatim from triage (5557426298), all binding:
mappingkind) stays quiet and still degrades to an empty list with no selector. ⛔ Do not turn a real, supported deployment shape into a visible fault.erritself — HTTP status / ADR-0112 code. ⛔ Never from "is the result an empty array", which can never separate served zero from did not serve.console.warntoconsole.error. It is in the browser console, nothing in the UI points at it, and a user without devtools still sees a feature that is simply absent. A log level does not change that.sys_user), because they are two independent causes of one user-visible symptom. #7740 carriesneeds-user-decisionand is sitting in the decision box unanswered — its 甲/乙 question (should identity import offer saved mappings at all?) is a maintainer call, and 甲 vs 乙 changes the work entirely. ⇒ the batch is unsatisfiable today, so this seat dispatches #7741 alone rather than holding a ruled, unblocked p2 behind an unruled sibling.⇒ Consequences the dev must accept and state in its PR, ⛔ not quietly paper over: on
sys_userthis fix changes nothing, becauselistImportMappingsis not on that wrapper at all (#7740). The fix is nonetheless fully load-bearing for every other object — includingcrm_plant_cost, which is where the #14026 misdiagnosis actually happened. ⛔ Do not touchpackages/app-shell/src/views/identityImport.tsand ⛔ do not resolve #7740's design question in this PR under any framing.Serial constraints cleared:
packages/data-objectstack/**is named by no other slot in this wave. The queue arbitrates.Generated by Claude Code
os-justin commentedon Sep 6, 2026
os-dev-report
{ "issue": 7741, "status": "done", "branch": "claude/issue-7741-list-import-mappings-discriminate", "pr": "https://github.com/objectstack-ai/objectui/pull/8152", "premise_still_valid": true, "summary": "Re-measured on origin/main 21d7989fb before the first edit: listImportMappings at packages/data-objectstack/src/index.ts:4654, the console.warn at :4663 — the PM's numbers, not the card's :4510 or triage's :4633. The catch arm now asks a new exported classifyImportMappingsFailure(err) what the failure WAS — ADR-0112 code first, HTTP status only in the code-less residual, never the emptiness of the result — and announces anything that is not the supported kind-absent shape on a new subscribe/unsubscribe channel, onMetadataReadWarning, sibling in shape to onWriteWarning and onSaveAdvisory. A deployment that does not serve the mapping kind (ROUTE_NOT_FOUND / NOT_IMPLEMENTED, the metadata list door's 400 INVALID_REQUEST per framework#9488, or a code-less 404/501) stays quiet with the same empty list and no selector. app-shell's AdapterProvider subscribes and toasts through a new metadataReadWarningToast module (three console.importMappings* keys in all ten locale packs), so a user without devtools can tell 'there are none' from 'we could not find out'. assignee was already set by the PM and the newest Claim: comment 5561826575 names this branch; I wrote no assignee and posted no second claim.", "clause_2_determination": "CHANNEL, not a contract change — the fence was never approached. listImportMappings still answers a promise of any[] and still never throws, on every arm including the loud ones; every loud test in the pin re-asserts the [] return alongside the emitted event, so a later contract change cannot ride in as a channel change. What settled it: the requirement is INFORMATION ABOUT A FAILURE, and information publishes on a side channel without changing what the caller is handed. Throwing would additionally have broken the one behaviour the adjudication mandates preserving — a quiet older-server arm and a throwing arm cannot be the same method's contract without every existing consumer growing a try/catch. Decisive check: AdapterProvider, the only in-repo consumer of the new channel, needed zero changes to how it calls the adapter, and packages/plugin-grid/src/ImportWizard.tsx (the feature-detecting caller) is not in the diff at all. Triage's preference 'add a channel rather than change the return contract' and the dispatch's hard fence are therefore both satisfied without a stop-and-report.", "tests": "All on f7d57b728, all foreground, heavy runs through scripts/pm/os-verify-lock.sh (VERDICT command-exit 0 each time). TESTS: pnpm exec vitest run packages/data-objectstack/ packages/i18n/ => 'Test Files 118 passed (118) / Tests 1802 passed (1802)'; pnpm exec vitest run packages/app-shell/ => 'Test Files 637 passed (637) / Tests 6120 passed | 1 skipped (6121)'. TYPE-CHECK: pnpm --filter @object-ui/data-objectstack --filter @object-ui/app-shell --filter @object-ui/i18n type-check, all three printed 'Done' with the script name echoed; data-objectstack's tsconfig includes src/**/* so the pin is compiled. BUILD FIRST: pnpm --filter '@object-ui/data-objectstack^...' --filter '@object-ui/app-shell^...' build ran as the first command in the worktree (exit 0). ABLATION — three legs, each committed-then-mutated, each proven on disk by a grep marker count going 0 to 1 plus git hash-object differing from the HEAD blob, each restored with git checkout HEAD -- path and proven byte-identical (git diff HEAD empty, hash-object equal to HEAD blob), each script carrying trap restore EXIT INT TERM with absolute paths from git rev-parse --show-toplevel. NO REBUILD LEG IS NEEDED OR CLAIMED: vitest.config.mts aliases @object-ui/data-objectstack to packages/data-objectstack/src, so the suites resolve source, not dist. (A2) classifier stops reading err and answers one arm always — PREDICTED the quiet assertions turn red, i.e. they are not vacuous; OBSERVED 'Tests 10 failed | 9 passed', including all three QUIET cases. (A1) the emitMetadataReadWarning call cut — PREDICTED every loud assertion plus the provider wiring turn red; OBSERVED 'Tests 9 failed | 14 passed' across both suites. (A3) the AdapterProvider subscription cut — PREDICTED only the middle-link test turns red and the producer suite cannot see it; OBSERVED 'Tests 2 failed | 21 passed', producer suite green. That third leg is the blind spot AdapterProvider.advisorySink.test.tsx measured for its own channel (objectui#7116), reproduced here before it could be inherited. GATES green (exit code captured before any pipe): check:i18n-keys, check:i18n-drift, check:i18n-dead-keys, check:control-bytes, check:unreferenced-sources, check:vi-mock-inherit, check:vi-mock-specifiers, check:phantom-deps, check:self-import, check:entry-guard, check:esm-specifiers, check:handler-key-reads, check:side-effects-array, lint:coverage (46/46 packages), type-check:coverage (45/46 plus 42/42 test projects), node scripts/check-changeset-presence.mjs ('16 source file(s) of 3 released package(s) changed, and this change declares 1 changeset(s)'), check-changeset-no-major, check-changeset-overwrite, check-changeset-fixed, check-governed-queue-guard --test ('NOT GOVERNED — 17 path(s) checked ... none matched'). NOT MEASURED, not red: check:readme-exports exits 1 with 'its type entry ./dist/index.d.ts is not on disk -- run pnpm build first' naming plugin-timeline and plugin-gantt — packages outside this diff; PREREQUISITE NOT MET, left to CI. Two names I first tried, check:lint-coverage and check:type-check-coverage, do not exist as scripts (ERR_PNPM_NO_SCRIPT) — read as NOT MEASURED and re-run under their real names lint:coverage / type-check:coverage, both green. LINT — declared, measured narrowing: eslint run on the 16 changed ts/tsx files rather than repo-wide. (1) population read from eslint's own config, not guessed: eslint.config.js matches files ['**/*.{ts,tsx}'] with top-level ignores **/dist, **/.next, **/node_modules, **/public, **/.source; all 16 are inside it. (2) count from --format json: 'files judged: 16, errors: 0, warnings: 127', and exactly 1 of the 127 falls on a line this branch added (no-explicit-any in the pin, the idiom already on line 107 of that file); lint.yml deliberately sets no --max-warnings. (3) invariance: the config extends tseslint.configs.recommended, NOT recommendedTypeChecked, and declares no parserOptions.project or projectService, so no rule reads the type graph and this diff cannot move any untouched file's verdict. Repo-wide turbo run lint, full pnpm test and the rest of the gate farm are CI's. CI status at report time: in_progress — reported at draft-PR time as the contract requires, not waited on.", "mcp_calls": "8 — one issue_read get_comments to verify the claim comment names my branch (claim-bearing, so not read from the zero-quota payload channel), two search_issues (a positive control that returned #7741, then the dedup query for the finding), one issue_write to file #8151, one create_pull_request, one pull_request_read to read the PR body back in full, one add_issue_comment for this report, one issue_read to read this comment back. The card body, both its comments, and objectui#7740's body and triage comment were read through the zero-quota public-repo payload channel (hasNextPage false), because repo-scoped REST is 403 for this seat: the credential is alive (/rate_limit answers core 15000) but the session gate is closed, so the REST channel was probed once and abandoned per the channel table.", "open_questions": [], "out_of_scope_findings": [ "filed as objectui#8151: listViews in the same file carries the identical swallow — a refused or broken meta.getItems('view') returns the same [] as a served-empty list, so a lapsed token renders as 'this object has no saved views'. Unassigned and unlabelled for triage. Deliberately not fixed here: it needs its own reading of which failures stay quiet ('view' is a long-standing declared kind, so the older-server story does not apply), and folding it in would widen the published event union and add a verification surface this card's gate set does not cover — the bounded in-place exemption fails on that fourth condition." ], "deviation_stated_in_pr": "Triage requires this card be batched with objectui#7740; #7740 is needs-user-decision and unanswered, so the batch is unsatisfiable and the PM dispatched this card alone. The PR body states plainly that on sys_user this fix changes nothing (listImportMappings is not on that wrapper at all — #7740's spread bug), that it is nonetheless load-bearing for every other object including crm_plant_cost where the objectstack#14026 misdiagnosis happened, and that the symptom is NOT fully closed. packages/app-shell/src/views/identityImport.ts is untouched (0 hits in the diff) and #7740's design question is not decided under any framing." }Generated by Claude Code
os-justin commentedon Sep 6, 2026
I wrote:
⇒ that makes the return-or-throw signature the whole test. It is not. Clause-② covers changing contract accept/reject behaviour or widening the public face, and a channel is not exempt from the second — a channel is made of new exported symbols on a published surface.
Measured on PR #8152's diff: four new exports on the published
@object-ui/data-objectstackface (ImportMappingsFailureKind,classifyImportMappingsFailure,MetadataReadWarningEvent,MetadataReadWarningListener) plus a new public methodObjectStackAdapter.onMetadataReadWarning(cb), and three more on@object-ui/app-shell's face. ⇒ Clause-② is YES.needs:contract-reviewwas attached to #8152, the review was performed in-seat atopusunder the quota-exhaustion exemption (comment 5562131348), and the carrier was cleared with that comment as provenance.⭐ The dev is not at fault and its determination is correct about what it claims — the return-or-throw contract genuinely does not move on any arm, and it built a real structural guarantee for that (every loud test re-asserts the
[]return alongside the emitted event, so a future contract change cannot ride in disguised as a channel change). The fence that told it the signature was the only question was mine.Recorded here because the next seat inherits the fence, not the outcome: ⛔ do not repeat 「a channel is not Clause-②」 as a general rule. The right test is whether the diff adds or moves anything on a published face — a channel usually does.
Generated by Claude Code
os-justin commentedon Sep 6, 2026
os-dev-report
{ "issue": 7741, "status": "done", "branch": "claude/issue-7741-list-import-mappings-discriminate", "pr": "https://github.com/objectstack-ai/objectui/pull/8152", "premise_still_valid": true, "round": "patch round 1 — head moved f7d57b728 to 2f72dd701; supersedes the tests field of comment 5562114105, everything else in that report stands unchanged", "summary": "PATCH ROUND for the red Test (shard 4/4). scripts/__tests__/one-authority-per-exported-name-6273.test.ts failed because the new metadataReadWarningToast.ts copied its siblings' local 'export type TranslateFn' declaration along with the pattern, making a known three-way collision four (objectui#6172: every exported name has exactly one authority). Fixed by re-pointing, which the gate explicitly does not count as a declaration: the local declaration is replaced by 'import type { TranslateFn } from ./writeWarningToast.js' plus 'export type { TranslateFn } from ./writeWarningToast.js'. One file, three non-comment lines. NO KNOWN_COLLISIONS entry was added — the gate states that baseline is SHRINK-ONLY. The pre-existing three-way collision (writeWarningToast / saveAdvisoryToast / fields file-size-guard) is untouched; this only stops adding to it. Re-export rather than rename because the three existing declarations are character-for-character the same shape, so there is nothing to rename apart. Which one it points at was not a judgement made here, so Zone 1 point 3's stop-and-report condition did not fire: AdapterProvider — this module's only caller — ALREADY imports TranslateFn from ./writeWarningToast.js and passes that same value into all three emitters, and file-size-guard.ts's own declaration names app-shell/src/providers/writeWarningToast as 'the established TranslateFn pattern' it was copied from.", "clause_2_determination": "Unchanged from comment 5562114105: CHANNEL, not a contract change. This patch is type-only and does not touch data-objectstack at all.", "tests": "All on 2f72dd701. THE GATE AND ITS WHOLE NEIGHBOURHOOD: pnpm exec vitest run scripts/ (the repo-wide scope the first round missed) under the shared verify lock => VERDICT command-exit 0, 'Test Files 115 passed (115) / Tests 3408 passed (3408)'. Inclusion counted rather than assumed: find scripts -name '*.test.*' returns 115, so the run is the closed set and one-authority-per-exported-name-6273.test.ts is inside it. APP-SHELL UNCHANGED IN OUTCOME: pnpm --filter '@object-ui/app-shell^...' build && pnpm --filter @object-ui/app-shell type-check && pnpm exec vitest run packages/app-shell/ under the lock => VERDICT command-exit 0; the type-check step echoed 'tsc --noEmit && tsc -p tsconfig.test.json' and the && chain proceeded; 'Test Files 637 passed (637) / Tests 6120 passed | 1 skipped (6121)' — IDENTICAL to the pre-patch counts, with metadataReadWarningToast.test.ts and AdapterProvider.readWarningSink.test.tsx among them. NOT MEASURED, not red: the first attempt at that command exited 2 with about 200 'TS2307 Cannot find module @object-ui/*' — the worktree was rebuilt for this patch round and no dependency dist existed yet; PREREQUISITE NOT MET, re-run with the closure build in front, which is the green result quoted above. RUNTIME INERTNESS SHOWN, NOT ASSUMED, as Zone 3 asked: ts.transpileModule with isolatedModules true (the repo's own setting) over the file's before and after text emits 3586 bytes each, 0 diagnostics each, byte-identical output. A first attempt via esbuild is reported NOT MEASURED — the binary is absent in this container and pnpm exec wrote its own error text into both output files, which then compared equal for the wrong reason; caught before it was quoted as evidence. ALSO GREEN on the new head: eslint on the changed file ('files judged: 1, errors: 0, warnings: 0'), check:control-bytes, check:unreferenced-sources, check:i18n-keys, check:phantom-deps, check:self-import, node scripts/check-changeset-presence.mjs (17 files changed, 1 changeset). NOT RE-RUN by instruction: Live E2E (known base-red, objectstack#16186). Lock discipline this round: one acquisition returned exit 99 after a 540s budget against a 715s holder in objectui-7727; the interval went to lock-free work (eslint plus the node-script gates) and the retry reused OS_VERIFY_LOCK_SLOT=objectui-7741 so the ticket kept its arrival stamp, acquiring 150s later. No round was ended to wait.", "scoping_lesson": "The first round's TEST narrowing was 'the packages I edited' — data-objectstack, i18n, app-shell — which by construction cannot reach a gate that reads the whole repo from scripts/__tests__/. That round's LINT narrowing was defended with a population, a count and an invariance argument; the test narrowing had no equivalent, and that is exactly the gap this failure fell through. The repo-wide half is scripts/ — 115 suites that read the TREE rather than a package, covering exported-name authority, i18n key parity, changeset presence, dist completeness, turbo task inputs, workflow wiring, dependency edges and vite/vitest config. Any diff that adds an exported name, a source file, a locale key, a dependency edge or a workflow line is inside their reach no matter which package it lives in, so scripts/ belongs in the scope of any narrowed local run. It is cheap: 91 seconds under the lock, against the 15 minutes app-shell alone costs.", "mcp_calls": "11 cumulative — 8 in the first round (see comment 5562114105) plus 3 here: one add_issue_comment on PR #8152 describing the delta for the re-reviewing seat, one add_issue_comment for this report, one issue_read to read this comment back. No pull_request_read or update_pull_request this round: the PR body was deliberately NOT edited, because update_pull_request sends the draft bit on every call and the PR must stay draft.", "open_questions": [], "out_of_scope_findings": [ "filed as objectui#8151 in the first round: listViews in packages/data-objectstack/src/index.ts carries the identical swallow. Unchanged by this patch.", "NOT filed, reported here for the PM to route: TranslateFn is declared three times over (packages/app-shell/src/providers/writeWarningToast.ts:26, packages/app-shell/src/providers/saveAdvisoryToast.ts:43, packages/fields/src/widgets/file-size-guard.ts:26), all three character-for-character identical, and the objectui#6172 ruling says a name has exactly one authority. It is carried in the gate's KNOWN_COLLISIONS baseline, so it is already tracked there rather than invisible, and Zone 1 point 3 of this patch round explicitly forbade repairing it. Left un-filed to avoid a duplicate of whatever already tracks that baseline entry; PM may prefer a card that shrinks it." ], "deviation_stated_in_pr": "Unchanged from comment 5562114105 and still in the PR body: on sys_user this fix changes nothing (objectui#7740's spread bug), it is load-bearing for every other object including crm_plant_cost, and the symptom is NOT fully closed. packages/app-shell/src/views/identityImport.ts remains untouched." }Generated by Claude Code
os-justin commentedon Sep 6, 2026
Landed as
a915064eavia PR #8152. Closing;pm:dispatchedstripped and the assignee cleared in the same stroke.Verified by CONTENT, against a control proven BEFORE the merge — ⛔ never by sha.
packages/app-shell/src/providers/metadataReadWarningToast.tsis a new file, absent on main pre-merge:310c0ab19)a915064ea)import type { TranslateFn } from './writeWarningToastexport type { TranslateFn } from './writeWarningToastA note on the instrument, because it nearly produced a wrong call. The obvious marker —
grep -c 'type TranslateFn ='— reads 1 on the fixed head, which looks exactly like the collision was never repaired. It is a false positive: line 58 is a docblock that quotes the spelling (of their local \export type TranslateFn = …` came along with the pattern`). There is no second declaration. The count was only disambiguated by printing the matched lines, and the import/re-export pair are the markers that actually discriminate. Recorded here so the next person to verify this file does not repeat it.CI at the ready-flip: 32 check runs enumerated against
total_count: 32— 28 success, 3 skipped, 1 failure, zeroin_progress, with the two I owed read explicitly:Test (shard 4/4)success (22:00:41Z) andLintsuccess (21:55:27Z); shards 1/2/3 green,Type Checkgreen.The single failure was
Live E2E (informational), stood down only after matching the known signature on this PR's own job (101564746184):Core service missing … auth+System started with degraded capabilities+no such tableon all foursys_*+✓ Server is ready(42 plugins, 132 seeded rows) + exit 1 + the artifact upload reporting exactly 1 file, which is the independent proof no Playwright output was produced.Correction carried forward from this card's review
My original Clause-② fence on this card was drawn too narrow — I wrote that adding a channel "is not a contract change" and made the return-or-throw signature the whole test. Clause-② also covers widening the public face, and this diff added four exports plus a new public method. The carry-forward rule is "does the diff add or move anything on a published face", not "a channel is not Clause-②".
And my review of the first round PASSed while missing the
TranslateFncollision entirely — I tabulated the name in my own new-exports table and checked its Clause-② meaning, but never asked whether it was already declared elsewhere. One grep would have found three sites. The gate (one-authority-per-exported-name-6273) caught what I did not.Still open, filed by this card's dev rather than folded in: #8165 —
TranslateFnis declared three times character-for-character identically, the cheapest available shrink of that gate'sKNOWN_COLLISIONSbaseline. (The three pre-existing authorities are grandfathered by that baseline deliberately; the gate is working as designed, which is why it caught the fourth and not the first three.)Generated by Claude Code