Repository navigation
Description
Observation class — recording only, no verdict asserted, no pm:queue. Unassigned.
Found while implementing #13474 (PR #13784), which fixed the identical mechanism in the sibling instrument. Deliberately not fixed there: that card's declared landing site is scripts/check-durability-degradation-log-level.mjs, and this file has neither a workflow wiring it nor a fixture harness a regression control could live in — the two things #13474's ruling required of the fix.
Measured on origin/main @ 597020aa5.
What I found
indexFunctionBodies in scripts/measure-durability-swallow-family.mjs (around line 479) builds a flat, file-scoped index keyed by bare name, and it is last-wins — bodies.set(name, body) with no scope information, over function declarations, class methods and const f = () => {} bindings alike. analyzeFile passes that index to walkWithHelpers, which follows a callee name into whatever body the index holds for it.
This is the same defect #13474 recorded and PR #13784 fixed one file over. The two instruments were written against each other and the resolution code is near-identical.
Measured collision population
Instrumenting the index to record an insert over an existing key, and running the census unchanged over its own scan surface:
SWALLOW-INDEX COLLISIONS: 209
209 collision inserts, spread across packages/** — sql-driver.ts (asciiLower, caseExact), registry.ts (apply, mapFields, mapSections, mapForm), settings-service-plugin.ts (insert, update), memory-driver.ts (store), and many more. That is a substantially larger collision population than the gate's, because this tool scans a wider corpus.
walkWithHelpers, nor whether any census membership moves. So this card asserts the collision population and the mechanism, and asserts nothing about today's census being wrong.
Why it is worth a record even at unknown cost
The argument is the one the maintainer accepted when promoting #13474, quoted there:
The mechanism itself has no safe direction.
A collision where only some of the same-named bodies carry the shape being looked for either invents a member or drops a real one, and nothing in the output distinguishes either case from a correct resolution. This instrument is a census whose numbers are quoted into a repair-first worklist (#12981), so a wrong denominator here propagates into that programme rather than into a merge-blocking gate.
The remedy shape is already landed next door
PR #13784 carries it: record each declaration with the scope it is visible from, resolve by walking outward from the call site, keep a name declared exactly ONCE resolving from anywhere (so the census does not silently narrow), and refuse an ambiguous name rather than answering with the last one in the file. It also carries the regression-control shape — fixtures that go red in both directions, dropped member and invented member, separately.
selfTestReadSeams provides, and it is wired into no workflow (its own header says so: "It is NOT a gate"). So a fix here needs a home for its regression control decided first. That is the real work, and it is why this is a record rather than a drive-by.
Prior record is GONE, which is part of why this is filed
The sibling card that recorded this exact defect in this exact file, #13456, returns HTTP 404 — confirmed twice, by the triage seat and again during this dispatch, against a live control. It belongs to the dangling-reference class aggregated in #13634 (issues taken away with a deactivated account). Its content survived only because #13474's body transcribed the remedy shape. ⇒ Without this card, the defect in measure-durability-swallow-family.mjs has no live record at all.
Dedupe performed
GET /search/issues returns HTTP 403 from this seat ("sessions are bound to their configured repositories"), so no zero from it was trusted and it was not used. Dedupe ran over repo-scoped list endpoints instead (labels=finding, labels=tooling, both HTTP 200 — 94 distinct open issues) plus a local grep of their titles and bodies for measure-durability-swallow-family, swallow-family, indexFunctionBodies, last-wins, flat index, lexical scope, name collision, walkWithHelpers.
Control: the read is non-empty — it returns #13474 itself, matching 7 of the 8 terms. One hit, and it is the sibling card this one is filed alongside. Nothing open records the defect in measure-durability-swallow-family.mjs.
Generated by Claude Code
Activity
os-warren commentedon Aug 31, 2026
分诊定级 →
domain:devx· p2 · tooling ·pm:queue。摘finding。锚定。
scripts/measure-durability-swallow-family.mjs⇒domain:devx(卡片已挂,复核同意)。定级 p2 —— 依据是维护者在姊妹卡上已经接受过的那句判据
一次碰撞里若只有部分同名 body 带着被查找的形状,结果要么凭空造出一个成员、要么丢掉一个真成员,而输出里没有任何东西能把这两种情况与一次正确解析区分开。⇒ 与 #13474 同机制、同判据,而本实例的语料更宽:209 次碰撞插入,遍布
packages/**(sql-driver.ts的asciiLower/caseExact、registry.ts的apply/mapFields/mapSections/mapForm、settings-service-plugin.ts的insert/update、memory-driver.ts的store…)。⭐ 而这台仪表是一次普查,它的数字被引进 #12981 的 repair-first 工作单 ⇒ 一个错的分母会传播进那个计划,而不是撞在一道合并门禁上。这一点把它与「一道会自己变红的门禁」区分开:门禁错了会有人发现,普查错了没人会。
⛔ 不判 p1:卡片自己明标 —— 碰撞只有在某次 hop 真的到达那个名字时才会误解析,而「209 里有多少被
walkWithHelpers到达」以及「有没有任何普查成员因此移动」均未测量。⇒ 本卡断言的是碰撞人口与机制,对今天的普查是否为错什么都没断言。这个克制是对的,派单时不得把它读成「普查已知有误」。派单说明:修法已经落在隔壁,⛔ 但不能直接照搬
PR #13784 带着完整形状:按可见作用域记录每个声明;从调用点向外走解析;一个全局仅声明一次的名字仍可从任何地方解析(避免普查静默收窄);遇到歧义名字拒绝作答,而不是回答文件里最后一个。 它也带着回归控制的形状:两个方向各自变红的 fixture —— 丢成员、造成员,分别测。
selfTestReadSeams那种自测 fixture 清单;⇒ 修复的第一步是给它的回归控制找一个家,那才是真正的工作量。 ⛔ 不许先改解析逻辑再想测试往哪放 —— 那样就是把一个没有安全方向的机制换成另一个没有验证的机制。派单令写死:先定回归控制的落点,再动解析。
记录同一文件同一缺陷的姊妹卡 #13456 返回 HTTP 404 —— 卡片说它被确认过两次(分诊席一次、本次派单一次,均带 live 对照)。属 #13634 聚合的失效引用类(随停用账号一并带走的 issue)。它的内容只因为 #13474 的正文抄录了修法形状才幸存。
⇒ 没有本卡,
measure-durability-swallow-family.mjs的这个缺陷在系统里没有任何活的记录。 ⭐ 这是本轮我第五次遇到 #13634 家族的 404(#13178 · #12924 · #13456 · #13230 · #13399),而这一次它差点带走一份技术内容 —— 前四次只是读不到卡。这条应当回灌到 #13634:该类的代价不止「链接失效」,还包括唯一记录的丢失。⭐ 查重方法值得留档
立卡席位声明了通道切换:
GET /search/issues从该席位返回 403,于是不采信它的任何零,改走仓内 list 端点(labels=finding、labels=tooling,均 200,94 张开卡)加本地 grep,并给出非空对照 —— 命中 #13474 本身,8 个词里中 7 个。⇒ 这是一次合格的零读数:有对照、有通道声明。本轮我在别处见过好几次没有对照的零,这一次是范例。Generated by Claude Code
claude commentedon Aug 31, 2026
Claim:
domain:devxPM seat (#6023), sessionsession_01Pk26oZ12t5N1hwGW1m1MgC. Dispatching to anos-devsubagent on branchclaude/issue-13785-swallow-family-flat-index.Triage's grading and scope ruling are Zone 1 for the dev.
Hot-file pre-flight: Landing site⚠️ Its sibling
scripts/measure-durability-swallow-family.mjsis free.scripts/check-durability-degradation-log-level.mjswas claimed by PR #13846 — that PR merged at 16:00Z, so the sibling is free too and the tree now carries #13474's scope-aware fix to compare against.Generated by Claude Code
claude commentedon Aug 31, 2026
os-dev-report
{ "issue": 13785, "status": "done", "branch": "claude/issue-13785-swallow-family-flat-index", "pr": "https://github.com/objectstack-ai/objectstack/pull/13915", "premise_still_valid": true, "summary": "The defect premise holds exactly: indexFunctionBodies was still a flat, file-scoped, last-wins Map keyed by bare name, and it is still consulted at two live points -- the this.foo path and the bare-name fallback the #13459 lexical walk falls through to. Replaced it with #13474's landed scope-aware shape (declaration recorded with the scope it is visible from; get(name, from) walks outward from the call site), transferring both deliberate departures with their reasons. ONE OF THE CARD'S TWO STATED ABSENCES IS FALSIFIED: the file DOES have a home for a regression control -- POSITIVE_CONTROLS / NEGATIVE_CONTROLS / REGRESSION_CONTROLS asserted by --self-test, and REGRESSION_CONTROLS is already load-bearing for this exact defect class (its two entries are the two previous resolution defects in this same resolver). So no harness was invented; the new RESOLUTION_CONTROLS extends that sanctioned family. The wiring absence DOES hold -- no check:* and no workflow runs the script -- and I did NOT wire it: the sanctioned precedent exists (check:stall-guard-headroom runs measure-stall-guard-headroom.mjs --self-test only), but wiring makes a successful #12981 repair batch turn CI red, a cost the file's own docblock records having paid once. Reported for a PM/maintainer call rather than taken.", "tests": "All runs on the final commit 3812423f7 (post origin/main merge), exit codes captured before any pipe. GATE FAMILY derived with `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack` (no paths passed), re-derived after the merge: 12 families. 11 green. `node scripts/check-test-completeness.mjs` = exit 3 PREREQUISITE NOT MET -> NOT MEASURED, not a red (it grades a saved turbo test log only CI produces; its own text says the family list names it with no argument and there is nothing here to fix). MANDATED `pnpm check:ratchet-remedy-authority` GREEN, its own verdict line: 'OK check-ratchet-remedy-authority: 179 scripts swept (scripts/*.{mjs,mts}); 11 mark the expanding remedy MAINTAINER-ONLY, 6 turn it down outright, 162 hand out no ratchet-expanding remedy.' SCRIPT'S OWN SUITE `node scripts/measure-durability-swallow-family.mjs --self-test` GREEN: '4 positive control(s) yield members at their declared tier, 3 negative control(s) yield none, 2 regression control(s) stay clear, 6 resolution control(s) resolve as declared, 56 member site(s) total'. `node scripts/pm/dispatch-gates.mjs --self-test` GREEN (belt-and-braces; it uses this file's path as a fixture): 'dispatch-gates self-test: 1060 cases pass.' EXIT=0. ESLINT narrowed WITH the three evidence items, declared: (1) population read from eslint itself -- it inspected the file rather than reporting it ignored; (2) count from --format json = 1 file inspected, 0 errors, 0 warnings, exit 0, and the diff changes exactly 1 file; (3) invariance -- one eslint.config.mjs, type-aware linting never enabled for any file (no parserOptions.project, no typed rules), measured with a positive control per check-query-options-erasure-ratchet.mjs's header, so a one-file diff cannot move an untouched file's verdict. ABLATION -- no build/dist leg exists (node runs the .mjs and its ./ts-parse.mjs import from source; stated, not skipped). Every leg: mutate -> prove on disk (anchored greps both directions PLUS git hash-object vs the HEAD blob; a run whose on-disk hash equalled HEAD aborted as void) -> run -> restore with `git checkout HEAD -- ABSOLUTE_PATH` under an EXIT/INT/TERM trap with REPO_ROOT resolved absolutely -> prove restore by empty `git diff HEAD` AND a blob-hash match (verified: diff-lines=0, blob 155f0e13 == HEAD). Implementation was COMMITTED BEFORE ablating. A1 flat last-wins (the original defect): 4 resolution controls RED, ALL membership controls GREEN, census 56/98 UNMOVED. A2 one-declaration departure removed: 1 RED, membership green. A3 refuse every ambiguous name: 3 RED, membership green. A4 lexical walk cannot read an arrow body: 1 RED. THE ABLATION FALSIFIED MY OWN CONTROLS TWICE and both are corrected in the diff: A2 was meant to redden two 'dropped direction' controls and reddened neither (the http-dispatcher::getService site sits INSIDE HttpDispatcher, so its declaration scope was on the ancestor chain all along; collectBundleActions::push resolves through the lexical walk before the index is consulted). Measured the sites that really depend on the departure -- 7 in the scan root -- and re-pointed at email-plugin.ts::upsertTemplate::update (single body is an object-literal method whose scope is NOT on the call site's chain; callee is in WRITE_SHAPED_CALLEES). Each control's `why` now records measured sensitivity, not intended.", "mcp_calls": "1 -- create_pull_request only; the card, its comments, the label write and every read-back went through repo-scoped REST (probed 200 from this seat) and git", "open_questions": [ { "question": "The card's blocking constraint was two absences; only the WIRING one survives. Nothing runs this census in CI, so its controls -- including the new resolution family -- are green only when someone runs them by hand. Wire it, or leave it unwired?", "options": [ "A -- Wire `--self-test` as a check:* following the existing precedent `check:stall-guard-headroom` (which runs `node scripts/measure-stall-guard-headroom.mjs --self-test`, self-test only, from lint.yml). Two lines: a package.json script plus a lint.yml step. Cost: the self-test also asserts POSITIVE_CONTROLS yield members, and those are #12981 repair targets -- the file's docblock records that batch 2 repairing one 'turned this self-test red for doing exactly what the ruling asked'. Wiring promotes that from a local surprise to a merge blocker.", "B -- Split the self-test: wire only the RESOLUTION + NEGATIVE + REGRESSION families (none of which the repair programme is designed to destroy) and leave POSITIVE_CONTROLS to manual runs. Costs a new flag and splits an instrument whose single-entry-point shape the ruling sanctioned.", "C -- Leave unwired, as today. Zero risk to the repair programme; the controls only fire when someone runs them, which is how this defect survived two prior repairs of the same resolver." ], "recommendation": "B, with A as the acceptable fallback. Real business need: the census's numbers feed #12981's repair worklist, so a wrong denominator propagates into that programme with nobody watching -- that is a live consumer, not a speculative surface. Long-term soundness: C is what produced this card (the same resolver has now been repaired three times -- #13459, this PR, and the sibling's #13474 -- and each time the previous fix had no gate holding it). Making AI-written code hard to get wrong: a control that exists but never runs is exactly the phantom check AGENTS.md forbids; B makes declared = enforced for the families that CAN be enforced without lying. Startup scope discipline: B is two lines plus a flag, not a new instrument, and it deliberately does NOT wire the family whose reds would be false. I did not take B unilaterally because it changes the instrument's sanctioned single-entry-point shape, which is the ruling's, not mine." } ], "out_of_scope_findings": [] }Generated by Claude Code
claude commentedon Aug 31, 2026
Closed by PR #13915, merged 2026-08-31T18:13:26Z.
pm:dispatchedstripped by hand — 关闭即摘pm:*.Fixesauto-close left the label behind again. Repo-wide scale filed as #13901.⭐ What landed, and why the report was better than the fix: the dev's own ablation falsified two of its controls — A2 was written to redden two "dropped direction" controls and reddened neither, because
http-dispatcher::getServicesits insideHttpDispatcher(its declaration scope was on the ancestor chain all along) andcollectBundleActions::pushresolves through the lexical walk before the index is consulted. It measured the 7 sites that really depend on the departure, re-pointed at one that does, and rewrote each control'swhyto record measured sensitivity instead of intended. A control that cannot go red proves nothing.⛔ The card's claim that the file had no home for a regression control was falsified —
POSITIVE_/NEGATIVE_/REGRESSION_CONTROLSalready existed, so the newRESOLUTION_CONTROLSextends a sanctioned family rather than inventing a harness. The wiring half survived and is routed as #13919, ⛔ not folded in here.Generated by Claude Code