Repository navigation
fix(objectql, runtime)!: a function's bare-name read is refused naming both when the platform and a package hold the name, and answers nothing for a name only a disabled package holds (ADR-0150 D4) - #33439
Conversation
…d a disabled package Claude-Session: https://claude.ai/code/session_016WV4cz4f7zEQZ2C9GTG382 Co-Authored-By: Claude <noreply@anthropic.com>
…d holder and the release's runtime rows Claude-Session: https://claude.ai/code/session_016WV4cz4f7zEQZ2C9GTG382 Co-Authored-By: Claude <noreply@anthropic.com>
…try, read off the holder set Claude-Session: https://claude.ai/code/session_016WV4cz4f7zEQZ2C9GTG382 Co-Authored-By: Claude <noreply@anthropic.com>
…er answers Claude-Session: https://claude.ai/code/session_016WV4cz4f7zEQZ2C9GTG382 Co-Authored-By: Claude <noreply@anthropic.com>
📓 Docs Drift Check8 anchor(s) derived from 2 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run. What this run could not see
Coarse fallback — 40 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 05fd240c612e181a161a77f60e234be8ce5966bd && git checkout 05fd240c612e181a161a77f60e234be8ce5966bd
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin ba35dfd78283d5f839eaaaab24764322ae3e263a a7b1de2a7510319114366953c7f169dbdd528a23 && git checkout -B drift-repro ba35dfd78283d5f839eaaaab24764322ae3e263a && git merge --no-ff a7b1de2a7510319114366953c7f169dbdd528a23
node scripts/docs-audit/affected-docs.mjs --json ba35dfd78283d5f839eaaaab24764322ae3e263a |
|
domain:server#5 · os-elon-musk · session_016WV4cz4f7zEQZ2C9GTG382 Area notes for the reviewer:
|
|
Contract-review-claim: session_016WV4d1PGptBW3QRQNmy5e6 · head d0af531 · 2026-11-14T21:19:11Z review#3. |
Contract reviewServed-tier: ① Derived judgments
② Semver level
③ Boundary flags
④ Design judgmentRouting the bare read through the one rule, and not keeping a second "count non-null holders" copy, removes the platform-order bug at its source (one code path, as ADR-0150 D3 intends). The disabled-package rule is a thin, single-case filter over the rule's answer, and it keeps every refusal. That is the smallest reading consistent with ruling A. Replacing the re-bind's bare read with the holder entry it already had removes a dependency on a resolution where it only needed a slot. The design holds. Implemented-by: VERDICT: PASS |
…teps it relies on Review nit N1: a caller package stops at resolvePackageScopedHolders' steps 1-3; only the environment-wide branch answers unique-holder. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WV4cz4f7zEQZ2C9GTG382
Review carry — PASS standsHead-sha: I read the delta Reviewed-by: |
|
domain:server#5 · os-elon-musk · session_016WV4cz4f7zEQZ2C9GTG382
|
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 43647010018 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 分类: 失败的 job(日志抽取,best effort):
跨 PR 相同签名(24h,按失败测试文件聚合):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 43647280081 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 分类: 失败的 job(日志抽取,best effort):
跨 PR 相同签名(24h,按失败测试文件聚合):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
Requested by Elon · project thread
Fixes #32907
Fixes #32933
Fixes #32910
Clause-②: no (narrowing)
Metadata-protocol: none
Skills-docs: none —
skills/**andcontent/docs/**name neitherresolveFunction/resolveFunctionEntrynorresolvePackageScopedFunction, and teach no function name held by the platform beside a package or by a disabled package; the prescription ships in the changeset's BREAKING paragraph.维护者速读
resolveFunction/resolveFunctionEntry,以及无调用包的resolvePackageScopedFunction)时,平台(无主注册)与一个包同持一个名字,一律 409 点名双方,不再按注册先后答其一;只有已停用包持有的名字不再答该包的函数(停用包仍计为持有者,照样参与歧义判定)。运行时重绑从持有者集读回保留函数,不再走裸读。另补 objectql: runtime pins for a function's bare-name claim release — pre-seal re-bind, move-path ordering, owner-spelling order (#32877 follow-up) #32910 的四组运行时钉子。!),原先被拒的一律仍拒,无任何放宽;仍会答的:单一启用包、仅平台、调用方自身包。第一方:仓内无平台与包同名的函数注册(见 Acceptance notes)。回滚即还原本 PR。packages/objectql/src/package-scoped-resolver.ts的callerlessFunctionResolution;packages/objectql/src/engine-hooks-and-functions.ts的bareFunctionEntry;packages/runtime/src/app-artifact-handlers.ts的releaseMovedFunctions中bare:一行。pnpm --filter @objectstack/objectql exec vitest run src/registry-bare-name-one-holder-request-family.test.ts,预期 78/78 绿;其中[#32907]、[#32933]两组在 main 上 5 条红。What changes
The engine keeps every holder of a function name: each package that registered one, and the platform for a registration under no package (
ObjectQL#functionHoldersOf). A read that names no package resolves the name by ADR-0150 D3 over those holders.bareFunctionEntry(packages/objectql/src/engine-hooks-and-functions.ts, the body ofObjectQL#resolveFunction/resolveFunctionEntry) now always runs the rule (resolvePackageScopedHolders, caller with no package) over the holder set, platform included. Before, it consulted the rule only when two PACKAGES held the name, and otherwise answered the bare map's last registration, so the platform plus one package answered by order (the package's entry, or the platform's when it registered last) or nothing after a third, last-registered package left. Now every one of those cells is409 RESOURCE_CONFLICT, reasonambiguous-in-environment, candidates[package, platform]— the same refusal the caller-lessresolvePackageScopedFunctionalready answered, so the two reads agree. When the rule resolves (one holder), the answer is still the bare map's entry, the same entry object, so the one-holder answer is unchanged byte for byte. PR fix(objectql, runtime): a function's bare-name record follows its holders — a re-bind releases the old owner's claim with the function, and the one remaining holder answers the bare read #32877's CONTROL pin for this row is flipped to the verdict.callerlessFunctionResolution(packages/objectql/src/package-scoped-resolver.ts, not re-exported from the package entry) post-filters the rule's answer for a caller with no package: when the rule picked a package as the environment's unique holder (via: 'unique-holder', the step only a caller with no package reaches) and the registry says that package is disabled (SchemaRegistry#isPackageDisabled), the answer isnot-found. Both the bare read andresolvePackageScopedFunctionapply it. Refusals are computed over EVERY holder, the disabled one included, so a disable never turns a refused name into an answer. A caller naming its own package, a caller's closure step, the platform's item and every refusal pass through unchanged. The every-holder doors (the hook binder's bind after the seal,claimBareNameFunctions; the cold-boot seal,sealBareNameHolders) read the registry's claims, which never looked at enablement; they are pinned, not changed.releaseMovedFunctions(packages/runtime/src/app-artifact-handlers.ts) read the bare slot of each kept name throughresolveFunctionEntry. With (1) that read throws for a name the platform holds beside the package — aborting the re-bind's teardown — and with (2) it answers nothing for a disabled package, which would drop the owner spelling the package held a kept function by. For a name the package alone holds the bare slot IS the package's holder entry (one entry object in both maps), so the binder now uses that entry; the two-package case (packageHolders.length > 1) is unchanged.No
packages/objectql/src/registry.tssymbol is changed (registry_symbols: none). No export is added;engine.tschanges docblocks only.Tests
Pins, numbered for this PR (no numeral lives in the files; the skipped (iii) of #32910's "Numbering" item was in PR #32877's body only, so there is nothing to renumber in the file):
src/registry-bare-name-one-holder-request-family.test.ts[#32534] … the platform and one package remain after the last-registered package leaves … (FROM nothing TO 409)— was the CONTROL "current behaviour, see objectql: a function's bare-name read refuses with 409 naming both when the platform and one package hold the name (ADR-0150 D4) #32907".[#32907] … the package registered last (FROM the package's entry TO 409).[#32907] … the platform registered last (FROM the platform's entry TO 409)— the platform-last-registrant cell.[#32933] a name only a disabled package holds: the bare read and the caller-less read answer nothing; enabled again, both answer it.[#32933] counted: the platform beside a disabled package is still two holders.src/app-artifact-handlers.test.ts[beta, platform].The red-on-main reading and the ablations ran before the merge (the reading on
90423f6b, every ablation one8106eb3); the merge touched none of the files they read.Red on main / green after: (i)–(v) are red on
main's objectql sources and green on this head; every objectql CONTROL is green on both. (vi)–(x) pin behaviourmainalready has (the #32910 rows are coverage of #32534's release; (vi) and (x) guard the re-bind against this PR's engine change), so each is proven by its ablation instead.Red-on-main reading (objectql sources restored from df9fe94, test file at head)
Ablations — one per pin, each through scripts/ablation-replace.mjs (anchor hit x1, blob changed, restored to the HEAD blob, git diff HEAD empty)
Neither suite reads a rebuilt
dist/for the mutated file: the objectql file imports objectqlsrc/, and every runtime mutation is in runtimesrc/(objectqldist/was built once from this head's objectql sources before the runtime runs).Acceptance notes
packageHoldersfor these reads, keep the caller's own); cost one filter plus two pins flipped, and an explicit epic ruling that a disable may turn a refusal into an answer for functions.packages/objectql/CHANGELOG.mdandpackages/runtime/CHANGELOG.mdentries of PR fix(objectql, runtime): a function's bare-name record follows its holders — a re-bind releases the old owner's claim with the function, and the one remaining holder answers the bare read #32877; qualifying it "in the engine's holder set" is a release-owned file, so it is owed as a dedicated docs-only PR, never this one.ObjectQL#registerFunctionhas one non-test caller, the hook binder, which passes the bind'spackageId, and every first-party bind of functions is the app-artifact binder's, underapp:plus the package id (the objectql plugin'smetadata-servicebinds carry hooks, no functions). So no first-party name is held by the platform beside a package. A disabled package's function is reached by a caller-less read only when an environment-authored flow names it.Verification
Head
d0af5314(this branch's work ondf9fe946, thenorigin/mainatb4624440merged once before opening; the merge touched none of this PR's files).Local runs on d0af531 (objectql suite, typechecks and the two changed test files re-run after the merge, runtime's dependency closure rebuilt)
Derived gate families (dispatch-gates --commands on d0af531: 72 families, the same list as on 039e6bb) — reconciled with --ran: 72 run, 0 NOT MEASURED, 0 UNRUN
Declared to CI: the full runtime suite and its dependents (the addendum's scope: only the runtime file touched ran locally), the repo-wide
pnpm lint,test:repo, and the full dogfood battery this!title owes (NOT MEASURED locally: the dependents' run holds the shared verify lock for about 90 minutes).Session: https://claude.ai/code/session_016WV4cz4f7zEQZ2C9GTG382
🤖 Generated with Claude Code