Skip to content

feat(objectql)!: an API, an object-less action and an engine function hold one name per package (bare-name phase 1, 2/4) - #31006

Merged
os-zhuang merged 4 commits into
mainfrom
claude/project-thread-crfe5o
Nov 7, 2026
Merged

os-zhuang merged 4 commits into
mainfrom
claude/project-thread-crfe5o

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

Requested by Elon · project thread

domain:server#3 · os-elon-musk · session_016WV4d1EuHiCqvh8VEz81QD

Refs #30803 (PR 2 of 4 — the request family; PR 1 was #30836; the last PR of the series carries Fixes)

Clause-②: no (narrowing) — the engine's package door (SchemaRegistry.installPackage), item seam (SchemaRegistry.registerItem) and hook binder (bindHooksToEngine) refuse, after the cold boot, a package registration they accepted before: one whose API or object-less action, or one of whose functions, bears a name another installed package holds. Nothing is widened.
Metadata-protocol: narrowed
Skills-docs: none — skills/** does not teach that two packages may share an API, object-less action or function name, and the naming rule itself is phase 2's (#30805); the prescription ships in the refusal and warning text and in ADR-0048's 2026-11-06 addendum (P1.7).

⚠️ Tier H: this diff touches docs/adr/0048-cross-package-metadata-collision.md (§3.4 narrowing note, P1.3, P1.5, P1.6 and the new P1.7). The seat routes the approver.

维护者速读

  • 改了什么:裁决 C 第一阶段的第二族。冷启动结束后,API、无对象 action、引擎函数这三类名字,一个部署只认一个包。API 与无对象 action 在安装门和注册口被拒(与 feat(objectql)!: a picklist, mapping or dataset name holds one package (bare-name phase 1, 1/4) #30836 的 picklist 相同);函数在 hook-binder 绑定处被拒。拒绝都是 422 NAMESPACE_CONFLICT,点出两个持有包和改名建议。
  • 为什么:实测两包同名时,端点匹配只留后注册的 API,另一个包的路径直接 404;/actions/global/NAME 跑后注册包的代码,/meta 却给先注册包的声明;函数注册表只留后注册的函数,flow 的 script 节点与 action 的 target 都按名取、不带包。
  • 冷启动:按你 2026-11-06 的裁决 B,已同名的部署照常启动,一条告警点名两个包和改名,开关保持 'warn'。告警里原来写“继续服务先注册的那个”,对 API、action 代码与函数不对(它们服务的是后注册的),改成“谁被服务保持现状”。
  • 不变的:绑在对象上的 action(objectName:name 键)既不算持有者也不被拒;hook 的 handler 本来就只在自己包里找函数;同一包重装或热重载;环境覆盖层;直接调用 ObjectQL.registerFunction。
  • 先看:bare-name-one-holder.ts(类型、holdsBareName 的无对象过滤、handlerOwnerPackageId、按类型生成的拒绝理由)、registry.ts 的 claimBareNameFunctions、hook-binder.ts 的认领调用、ADR-0048 新增 P1.7。
  • 需要你做的:ADR 改动是 Tier H,需要审批人批准。

What changes

  • packages/objectql/src/bare-name-one-holder.ts — the covered types grow from three to six: the registry types api and action (BARE_NAME_REGISTRY_TYPES, isBareNameRegistryType) and function, which no registry stores. holdsBareName(type, item) is the one place an action is read as covered: object-less only, through the engine's own owner-key ladder (standaloneActionOwnerKey / isObjectLessActionKey); declaredBareNames and the registry ask it. handlerOwnerPackageId('app:<id>') reads the runtime's handler owner as the package. bareNameReason(types) builds the refusal's reason from the consumers of the types the refusal carries, so a picklist refusal still names a field's picklist and an API refusal names the endpoint matcher.
  • packages/objectql/src/registry.ts —
    • the holders (bareNamePackageHolders) skip a composite slot whose item is not covered (a bound action), and read function claims alone for function;
    • claimBareNameFunctions(owner, names) (public): before the seal it records each name's claim; once sealed it refuses, ahead of recording anything, a bind bringing a function another package holds — every conflict in one BareNameConflictError, the rename carried — and lets a package re-bind its own;
    • the item seam judges isBareNameRegistryType(type) && holdsBareName(type, item);
    • the seal reads function claims with the registry's slots; its reading names the first-registered holder as the existing one (unchanged for the data family, whose first-registered holder is the served one);
    • the cold-boot warning says the boot "changes nothing about which holder each caller is served" instead of "keeps serving the first-registered holder", which is false for an API, an object-less action's handler and a function (served last-registered).
  • packages/objectql/src/hook-binder.ts — bindHooksToEngine asks registry.claimBareNameFunctions(opts.packageId, …) with the callable entries of opts.functions before its registration loop, so a refused bind registers and binds nothing. @throws added to its docblock.
  • packages/objectql/src/plugin.ts — two comments name the six types and the binder's door; no code change.
  • docs/adr/0048-cross-package-metadata-collision.md (Tier H) — §3.4's 2026-11-06 note now names the six types and the binder; P1.3 records the maintainer's cold-boot ruling (warn; only a new holder refused) and the warning's corrected sentence; P1.5 keeps the four-plus-two still to follow and their order; P1.6 names the function door; new P1.7 — the measurement, what is refused (bound actions excluded, why), the function door and what it does not judge, the cold boot. The header's Addenda entry names the six types. P1.1's heading names the family it covers ("the data family: picklist, mapping, dataset"), beside P1.7's request family.
  • .changeset/30803-bare-name-request-family.md — @objectstack/objectql minor, both declaration lines, BREAKING Before / Now / Who moves / Unchanged, adr-0087: not-required (no-migration-prescription), measured producers.
  • scripts/adr-anchors/packages__objectql__src__bare-name-one-holder.ts.json — the anchor's invariant now names both families and their served holder (the data family, first-registered; the request family — the endpoint matcher's API, an object-less action's handler, the engine function registry's function — last-registered) and three doors: the package door, the item seam, and the hook binder's bind (bindHooksToEngine, through SchemaRegistry.claimBareNameFunctions). Same ADR citations (ADR-0048, ADR-0005).
  • .changeset/30803-bare-name-one-package-holder.md (PR 1's release note, still pending) — one sentence corrected: a DELIBERATE CORRECTION, below.

DELIBERATE CORRECTION

This PR changes a changeset it did not add: .changeset/30803-bare-name-one-package-holder.md, PR 1's note (#30836), which no release has consumed yet. Check Changeset (check-empty-changeset) refuses that by design and stays red; the dispatching seat confirms the correction by its own record. One sentence of the note's The cold boot paragraph changes:

  • Before: "Whether that boot should be refused instead is the maintainer's open call; the switch (BARE_NAME_COLD_BOOT_MODE, 'warn') sits in bare-name-one-holder.ts."
  • After: "The maintainer ruled this on 2026-11-06: the boot warns, and only a new holder is refused; the switch (BARE_NAME_COLD_BOOT_MODE, 'warn') sits in bare-name-one-holder.ts."

Why: the maintainer ruled the question on 2026-11-06 (ruling B, record 6687840782 on the card), and this PR's own changeset records that ruling; both notes ship in one CHANGELOG release, so the older one would publish a question as still open. The note's sentence "the first-registered holder keeps being served" stays as it is: it is true for its family (picklist, mapping, dataset).

Why (measured, stage 1)

Booted showcase + two AppPlugin packages sharing one name per type, both registration orders, on c17719dd (stage1-measurement.md in the seat's project folder):

Type Consumer Served Warned
api endpoint matcher (kernel metadata service, one API per name) last-registered: its path 200, the other package's path 404 no
object-less action POST /actions/global/NAME vs GET /meta/action/NAME handler: last-registered; declaration: first-registered the handler bind ("replaces"), naming neither package
function engine function registry (resolveFunction), read by flow script nodes and action target refs last-registered no
CONTROL object-bound action POST /actions/OBJ/NAME on each package's object each package's own, both orders —
CONTROL hook naming a function beforeInsert hook handler: NAME own package, both orders (bind-time resolution) —

Verification

  • packages/objectql/src/registry-bare-name-one-holder-request-family.test.ts (new, 34 cells, real ObjectKernel + ObjectQLPlugin): for api and the object-less action the data family's matrix — package door (both holders, rename, no record left), ABLATION before the seal, item seam, nested plugin, same-package reload (CONTROL), uninstall releases, environment registration never judged and never a holder (CONTROL), the cold boot (warns once naming both and the rename; reload keeps both, a third refused naming both; 'refuse' refuses with the doors' envelope). action only: CONTROLs — same-named bound actions on two packages' own objects coexist; a bound action neither holds an object-less action's name nor is refused over one, while a second object-less one is; the item seam judges object-less and lets bound through. function: the handler-owner reading; after the seal a bind bringing a held function is refused naming both and the rename and registers neither of its functions (the served one untouched); ABLATION before the seal (both claims coexist, the seal reads them); CONTROLs — the same package re-binding, an owner-less bind, an entry with no callable handler; uninstall releases; the cold boot warns once naming both PACKAGES (never app:…) while the last-registered function stays served, and a third package is refused naming both. The reason names only the types the refusal carries; a built-in API name is refused at the package door.
  • packages/qa/dogfood/test/bare-name-one-holder-request-family.dogfood.test.ts (new, booted showcase + two AppPlugin packages + MarketplaceInstallLocalPlugin): the cold boot boots and logs ONE WARN line naming the API, the object-less action and the function, both packages (not app:com.qa…) and the renames; POST /marketplace/install-local of a third package declaring the held API name is 422 PLUGIN_REGISTER_FAILED naming both holders and gamma_…, the package list unchanged and its path unserved (404); a package declaring the held object-less action and the showcase's API name is refused naming both; CONTROL: fresh names and an action bound to its own object under the held action's name install. 4 passed; the data family's dogfood file beside it 4 passed.
  • Ablations (src): the engine plugin's seal call removed → 17 of the 34 unit cells red, 17 green; the binder's claim call removed → the 3 binder-path function cells red; holdsBareName forced to true → the 3 bound-action CONTROLs red. Each restored.
  • @objectstack/objectql full suite: 561 files / 11239 tests passed; @objectstack/runtime full suite: 553 files / 10160 passed, 19 skipped (the binder's callers); data-family pins and hook-binder.test.ts 100 passed beside the new file. pnpm --filter @objectstack/objectql typecheck exit 0, including check:test-typecheck (the new file compiles under tsconfig.test.json, not ledgered: its function fixtures are typed HookHandler, 0990e5d); @objectstack/dogfood tsc --noEmit 0 errors.
  • node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands: 74 derived families, all run, every exit 0; --ran reconciliation "74 derived famil(ies) accounted for — 74 run, 0 NOT-MEASURED". None needed a rerun: the two prerequisites PR 1 met (check-plugin-teardown-shape --self-test's pinned fixture commit on the shallow clone, check:dual-build-cjs-loads's seven dists) were in place before the run.
  • Ablation (dist): the new dogfood pin against objectql built from the merge base's four sources → 3 of 4 red (install-local answered 200 and installed gamma / delta; no warning line), the fresh-names CONTROL green; 4 of 4 green after rebuilding the PR's dist.
  • Full dogfood battery (pnpm turbo run test --filter=@objectstack/dogfood, under os-verify-lock) at 0990e5d: VERDICT command-exit 0 · Test Files 428 passed | 3 skipped (431) · Tests 3560 passed | 41 skipped (3601).
  • Merged origin/main (c77dd17, 14 commits, none touching this diff's files; dispatch-gates --commands derives the same 74) as bb78644, rebuilt the dogfood graph, and reran the four bare-name files: objectql 3 files / 100 passed, dogfood 2 files / 8 passed. The full suites, the 74 gates and the battery above ran at 0990e5d, before that merge.
  • Delta at 247853de (contract review 6690893976: should-fix 1 and 3, plus nit 1; prose only, 3 files, +3 / −3, no code): node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands derives 82 families for the PR; the 39 that read the three changed files (31 matched by path, 8 whole-tree) ran, each with its exit code recorded: 38 exit 0 — among them check:adr-anchors (OK, 111 anchored files), check-adr-links (991 links resolve), check:adr-index, check:doc-authoring, check-adr-0087-registration --base origin/main, check-changeset-no-major, check:closed-ceremony-text, check-closing-keyword-parity, check:doc-formula-expressions (after the @objectstack/lint closure build) — and check-empty-changeset --base origin/main exit 1, the DELIBERATE CORRECTION refusal above. --ran reconciliation: 82 derived, 39 run, 0 NOT-MEASURED, 43 UNRUN; the 43 read only the code and test paths this delta leaves untouched, ran at 0990e5dd (74 of 74 exit 0), and CI is green at bb786448. No suite was rerun.

Kept

  • Object-bound actions keyed objectName:name, jobs keyed appId:jobName, hooks resolving their own package's functions; every data-family pin (registry-bare-name-one-holder.test.ts, its dogfood file) green unchanged.

Series and out of scope

Deviations

  • A function's door is the hook binder, not the registry's two doors: functions are code a runtime module brings, never a manifest collection, so install-local (no runtime module) can bring none, and its post-boot door is pinned in-process only. A refused bind is logged by AppPlugin (Failed to bind declarative hooks, error) and leaves that package's other registrations as they are.
  • The runtime's handler owner app:<packageId> is read inside objectql (handlerOwnerPackageId) so a refusal names the package; the dogfood pin asserts the warning names com.qa.bare.* and never app:, which pins the two spellings together across the package boundary.

Human Minimum

  • 1. Schemas start as Zod; packages/spec holds no runtime logic. (not touched)
  • 2. Every name I added uses the casing for its kind.
  • 3. No formatter ran.
  • 4. One issue, one branch off an up-to-date main, one pull request (second of a series by type family, per the seat).
  • 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset.
  • 6. packages/spec touched: n/a.
  • 7. No hand edits to content/docs/references/, packages/*/CHANGELOG.md or content/docs/releases/.
  • 8. Title, body and comments are in English (the maintainer brief is in Chinese by the seat's convention).
  • The Before Submitting list in CONTRIBUTING.md is done.

… hold one name per package

The request family of ruling C's phase 1 on the bare-name rule the data
family introduced. Once the cold boot has sealed the registry:

- an API or an object-less action whose name another installed package
  holds is refused at the package door and the item seam, as a picklist
  is; an action bound to an object (keyed objectName:name) neither holds
  a name nor is refused over one;
- a hook-binder bind bringing a function another package holds is
  refused ahead of every registration and bind, the holders being the
  claims each package's bind records (the runtime's handler owner
  app:<packageId> is read as the package);
- the cold boot reads all three under the same switch, which stays
  'warn' as ruled.

The refusal's reason now names the consumers of the types it lists, and
the boot warning no longer says the first-registered holder is served:
an API, an object-less action's handler and a function are served
last-registered. ADR-0048's 2026-11-06 addendum gains P1.7.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WV4d1EuHiCqvh8VEz81QD
The bind fixtures handed `bindHooks` functions returning a string, which
its `Record<string, HookHandler>` option does not accept, and the bound
action fixture lost its index signature in the spread; the test layer's
typecheck (tsconfig.test.json) refused the file. Each fixture is now a
distinct `HookHandler` closure (the cells compare identity only), and the
bound action is typed as a record.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WV4d1EuHiCqvh8VEz81QD
@github-actions github-actions Bot added size/xl documentation Improvements or additions to documentation tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface labels Nov 6, 2026
@github-actions

github-actions Bot commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/objectql, touching 27 documentable anchor(s).

5 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/concepts/metadata-lifecycle.mdx (via SchemaRegistry (symbol, a top-level class))
  • content/docs/deployment/environment-variables.mdx (via SchemaRegistry (symbol, a top-level class))
  • content/docs/kernel/services-checklist.mdx (via SchemaRegistry (symbol, a top-level class))
  • content/docs/plugins/adding-a-metadata-type.mdx (via SchemaRegistry (symbol, a top-level class))
  • content/docs/protocol/kernel/plugin-spec.mdx (via SchemaRegistry (symbol, a top-level class))

⛔ 1 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v17/17-4.mdx (via SchemaRegistry (symbol, a top-level class))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 7 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 63 of 222 client-bound route-ledger rows — the other 159 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 159: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 104 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 21 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 7569fb57fa15d20ff6a93bd9ad224fe40b0995d4 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 38f83413112d4575751f0a61919eefe74dd0c276 — the merge of head 247853de2279045496cd6fc1b351e5a1500ecf15 into base 7569fb57fa15d20ff6a93bd9ad224fe40b0995d4, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 38f83413112d4575751f0a61919eefe74dd0c276 && git checkout 38f83413112d4575751f0a61919eefe74dd0c276
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 7569fb57fa15d20ff6a93bd9ad224fe40b0995d4 247853de2279045496cd6fc1b351e5a1500ecf15 && git checkout -B drift-repro 7569fb57fa15d20ff6a93bd9ad224fe40b0995d4 && git merge --no-ff 247853de2279045496cd6fc1b351e5a1500ecf15

node scripts/docs-audit/affected-docs.mjs --json 7569fb57fa15d20ff6a93bd9ad224fe40b0995d4

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 7569fb57fa15d20ff6a93bd9ad224fe40b0995d4 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#2 · os-tesla · session_01L9qibyvxbrLP4jm4PargoH

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: bb7864488bdfe2b5a7eb5c8d913975c6943619b1
Local-runs: none
Scope: docs/adr/0048-cross-package-metadata-collision.md (Tier H), packages/objectql/src/bare-name-one-holder.ts, packages/objectql/src/hook-binder.ts, packages/objectql/src/plugin.ts, packages/objectql/src/registry.ts, packages/objectql/src/registry-bare-name-one-holder-request-family.test.ts, packages/qa/dogfood/test/bare-name-one-holder-request-family.dogfood.test.ts (the narrowing face's floor: every path of the diff outside the prose faces; .changeset/30803-bare-name-request-family.md read as the ② input) + cited on origin/main: packages/objectql/src/registry.ts whole bare-name region at the head (bareNameClaims, bareNamePackageHolders, bareNameRenameFor, bareNameHoldersOtherThan, refuseBareNameConflicts, recordBareNameClaims, forgetBareNameClaims, claimBareNameFunctions, sealBareNameHolders, the registerItem seam, the installPackage and uninstallPackage call sites at 8644 / 8733 / 8902); packages/objectql/src/hook-binder.ts 150–260 (the function pre-load, its place ahead of unregisterHooksByPackage and every registerFunction); packages/objectql/src/engine.ts 664, 843–875 and engine-hooks-and-functions.ts 197–235 (registerFunction, the per-entry packageId stamp, unregisterFunctionsByPackage); packages/objectql/src/engine.ts 888–907 (bindHooks → bindHooksToEngine); packages/objectql/src/plugin.ts 3660–3685 and 4330–4360 (the two metadata-service binds, hooks only, no functions), 1216–1225 (the kernel:bootstrapped seal); packages/objectql/src/action-governance.ts 82–124 (isObjectLessActionKey, standaloneActionOwnerKey, standaloneActionOwnsRoute); packages/runtime/src/app-artifact-handlers.ts 160–330 (appArtifactHandlerOwner, bindAppArtifactHandlers: owner app: + appId, functions from collectBundleFunctionEntries, the try/catch that logs Failed to bind declarative hooks at error); packages/runtime/src/app-plugin.ts 1610–1665 (the one binder call, in start, after runtime.onEnable); packages/core/src/kernel.ts 348–352 (use() refused once bootstrap has started) and 526–632 (Phase 1 init → Phase 2 start → kernel:ready → kernel:bootstrapped); packages/core/src/kernel-base.ts 213–230, 332–345; packages/objectql/src/registry-bare-name-one-holder.test.ts 218–250 (the data family's cold-boot pin); scripts/adr-anchors/packages__objectql__src__bare-name-one-holder.ts.json; scripts/check-adr-anchors.mjs header (what the anchor check reads); .changeset/30803-bare-name-one-package-holder.md (PR 1's, still unconsumed at the head); examples/app-showcase/src/system/apis/index.ts (showcase_task_feed); packages/objectql/package.json and packages/qa/dogfood/package.json (private).

Inputs read: card #30803 body and all nine comments (triage 6677272063, seat 3's claim 6677636083, PR 1's os-dev report 6679284054, progress 6684125224, ruling B 6687840782, ruling "all nine types" 6688041914, PR 2's os-dev report 6690087672, the parked note 6690280341, seat 2's take-over claim 6690638720); PR #30836's body, merge 2a6a28db, and its two contract-review records 6679538573 (PASS) and 6679688246 (delta PASS); PR #31006's body, 8-file list (983+ / 100−), its three commits over merge-base c77dd17f (3f834fb7, the test-typing commit 0990e5dd, the main merge bb786448), the net diff origin/main...bb786448; the docs-drift comment 6690094030; the check-runs on bb786448, read at 2026-11-06T17:17Z: 50 total, 37 completed/success, 13 completed/skipped, 0 failed, 0 in progress (Check Changeset, Governed Surface Queue Guard, Dogfood Regression Gate, PR-body guards, Spec property liveness, Build Core, Lint & Repo Gates with every Lint · leg, Type Check · workspace / consumer gates / source gates / debt ledger, TypeScript Type Check, Test Core 1–8 and aggregate, Test UI, Live E2E UI). Both carriers read by eye: PR body and changeset each carry Clause-②: no (narrowing) and Metadata-protocol: narrowed as line-initial keys, one each, plus Skills-docs: none — …. The PR is a draft, mergeable: true, author objectstack-fleet[bot].

① Derived judgments

  • The covered set grows by exactly three, in two shapes. BARE_NAME_REGISTRY_TYPES = picklist, mapping, dataset, api, action; BARE_NAME_ONE_HOLDER_TYPES = those + function. The registry doors (refuseBareNameConflicts via declaredBareNames, and the registerItem seam) ask isBareNameRegistryType, never function; sealBareNameHolders iterates all six and reads the item store only for registry types, claims for all. BUILT_IN_BARE_NAMES keyed by all six, all empty. TRUE; matches the ruling of 6688041914 (PR 2 = api + object-less action + function registry).
  • "Object-less" at the door is the engine's own reading. holdsBareName('action', item) = isObjectLessActionKey(standaloneActionOwnerKey(item)): objectName → collector object → 'global'; object-less when empty, 'global' or '*'. That is the same ladder the router's rung 2 (standaloneActionOwnsRoute) and the runtime's handler key (app-artifact-handlers.ts 318–322) use, so "bound" means one thing from declaration to dispatch. It is applied in all four readers: declaredBareNames (the package door's input), the registerItem seam, bareNamePackageHolders (so a bound action's composite slot PKG:name is skipped when the holders of an object-less name are read) and, through that, the seal. Pinned by three CONTROLs (same-named bound actions on two packages' own objects coexist; a bound action neither holds nor is refused over an object-less name while a second object-less one is; the seam lets a bound one through) and the dev's forced-true ablation (3 red). TRUE.
  • The function door is claimBareNameFunctions, asked once per bind, ahead of everything. In bindHooksToEngine the claim call sits inside the opts.functions pre-load, before the per-function registerFunction loop and before the hot-reload unregisterHooksByPackage, with the callable entries only (normalizeFlowFunctionEntry(entry) !== undefined, the same predicate the loop skips on). Sealed: every conflicting name collected, BareNameConflictError thrown before any claim is recorded, so a refused bind registers and binds nothing and the previous set stays bound. Unsealed: claims recorded for the seal. packages.includes(incomingPackageId) makes a package's own re-bind no new holder; owner-less binds return early. Uninstall releases: forgetBareNameClaims walks every type's map, functions included. Pinned: refusal naming both + rename with resolveFunction unchanged and beta_only_fn unregistered; ABLATION pre-seal coexistence read by the seal; CONTROLs re-bind / owner-less / no-callable; uninstall; the cold boot's single line naming packages not owners. TRUE.
  • The owner is read as the package. handlerOwnerPackageId('app:ID') → ID; anything else is its own name. The runtime's appArtifactHandlerOwner(appId) is literally 'app:' + appId, and bindAppArtifactHandlers hands that owner and collectBundleFunctionEntries(bundle) to ql.bindHooks — the one call in the repo that passes functions with an owner. The two metadata-service binds in plugin.ts pass hooks only, so no pseudo-package ever claims. The dogfood pins the two spellings together across the package boundary (com.qa.bare.* named, app:com.qa absent). TRUE.
  • Boot order makes the seal safe for functions. ObjectKernel.bootstrap(): Phase 1 init for every plugin (AppPlugin registers its manifest), Phase 2 start for every plugin (AppPlugin's one bindAppArtifactHandlers call, after runtime.onEnable), then kernel:ready, then kernel:bootstrapped (the seal). So every boot package's functions are claimed before the seal and a boot never refuses itself; plugin.ts's amended comment ("their functions in start()") is right. use() is refused once bootstrap has started, so a post-seal function bind reaches the door only through a hot reload or a host's own bindHooks (see ③ / should-fix 2). TRUE.
  • The refusal reason is built per type and the message shape is PR 1's. bareNameReason(types) filters the present types in canonical order (all six when none) and joins TYPE_NOUN + CONSUMER_CLAUSE; bareNameReasonFor(conflicts) feeds both the doors' message and the cold-boot warning; bareNameTypeLabel names API / object-less action / function. Envelope unchanged: NAMESPACE_CONFLICT, 422, conflicts[], both holders, the rename via bareNameRenameFor (stands down for no namespace or a shareable one). Pinned (reason names only the types carried; built-in API refused at the package door naming the platform). TRUE.
  • The cold-boot warning sentence (dev question 2). bareNameColdBootWarning now reads "The boot continues and changes nothing about which holder each caller is served". For picklist/mapping/dataset the served holder is still the first-registered one (the registry's by-name getItem, unchanged by this diff) — the new sentence is true of them, since it states that no holder changes. For api (endpoint matcher), the object-less action's handler and function the served one is the last-registered, and for the action the declaration and the handler come from different packages, so "keeps serving the first-registered holder" was false for the new family and a family-neutral sentence is the only true one for a shared line. The seal's existingHolder keeps naming the first-registered holder ([first, ...others], the docblock now says so), which is a reporting order, not a serving claim. Nothing pins the old sentence: at the merge-base it occurs only in registry.ts (the source), ADR P1.3, PR 1's changeset and a comment at registry-bare-name-one-holder.test.ts:235 whose assertion is getItem(type, name).label contains alpha — the served holder, not the sentence — unchanged here and green (dev: 3 files / 100 passed; Test Core green on this head). The request-family test pins the new sentence. TRUE; one leftover is should-fix 3.
  • The function door's boundary (dev question 1). On origin/main, .registerFunction( in non-test source: packages/objectql/src/hook-binder.ts:216 (the binder) and packages/formula/src/stdlib.ts ×35 — the latter is CEL's env.registerFunction, a different registry; zero in examples/**, apps/**, HotCRM src/; four test files. Every package's functions reach the engine through AppPlugin.start → bindAppArtifactHandlers → ql.bindHooks({ packageId: 'app:' + appId, functions }); install-local carries no runtime module and so brings no functions (its binder call is the same function with withholdHooksWithoutBody). So the binder is the one door a package's functions pass, and a direct ObjectQL.registerFunction is host code outside any package — which the diff declares as not judged in all three places (module doc "What the rule does not judge", ADR P1.7, changeset "Unchanged"). Declared = enforced; no second door for a package's function. Residual, stated: the registry cannot see a host-registered function, so neither the doors nor the cold boot can name a clash between a host function and a package's. The engine facet already stamps packageId on every entry (engine-hooks-and-functions.ts:201), so a seam at registerFunction would be small once refactor: move shared state out of the protocol.ts and engine.ts facades into state-owning facets, so the hot bodies left on the class can become delegations (ruling 6624345766) #30020's facade work lands — a follow-up note, not owed here.
  • Nothing widened on the published face. index.ts's export list is untouched; claimBareNameFunctions is a new public method on the already-exported SchemaRegistry, the same shape PR 1's sealBareNameHolders took and record 6679538573 accepted under no (narrowing); BareNameOneHolderType widens by three literals inside the module, which index.ts does not export. No spec file, no error code, no status moves. no (narrowing) is the right spelling.
  • Kept behaviours hold. Object-bound actions (objectName:name), jobs (appId:jobName), a hook's handler (resolved in its own package by bindHooksToEngine, untouched), same-package reload, environment overlay never judged and never a holder (the seam is still inside if (packageId && …); bareNamePackageHolders still skips the bare slot), OS_METADATA_COLLISION=warn not consulted. Every data-family pin is untouched by the diff and green.
  • PR body vs diff. Every claim in "What changes" is in the diff: the six types and the two predicates, holdsBareName, handlerOwnerPackageId, bareNameReason, the holders' bound-action skip, claimBareNameFunctions, the seam's holdsBareName, the seal's claim read, the warning sentence, the binder's claim call and @throws, plugin.ts comments only (+7/−5, no code), the ADR hunks (header Addenda line, §3.4 note, status line, P1.3, P1.5, P1.6, new P1.7), the changeset's two carriers, BREAKING sections and adr-0087 marker. Ablations, suites and the dogfood battery are the dev's readings, not re-run here (Local-runs: none); CI on the head is read above.

② Semver level

Both carriers read Clause-②: no (narrowing) and Metadata-protocol: narrowed, line-initial, one each; Skills-docs: none — … present with the reason PR 1 shipped (phase 2's rule is #30805's). narrowed is right: a package registration (and a function bind) that succeeded is refused on a declared value. The level follows the kind: minor on @objectstack/objectql, the one published package moved (@objectstack/dogfood is private: true; it carries a test only). ! on the PR title and the changeset's first line; BREAKING section Why / Before / Now / The cold boot / Who moves (the one-line fix in bold) / Unchanged / Measured producers; exactly one adr-0087: not-required (no-migration-prescription) marker, the #22197 / PR 1 text. Check Changeset green on this head. Consistent with PR 1's accepted changeset in every field. The "Measured producers" figures (examples: 3 APIs — showcase_task_feed, showcase_inquiry_purge_api, showcase_recalc_estimates_api confirmed in app-showcase/src/system/apis/index.ts; HotCRM: no API / function / object-less action) are prose face; the showcase API count is confirmed, the rest is the lane seat's ACCEPT.

Check-runs on bb786448: 37 success / 13 skipped / 0 failed / 0 in progress (list above). Draft; docs/adr/** ⇒ Tier H, the body says so, the seat routes the approver.

③ Boundary flags

  • open_questions: none in 6690087672. The two questions the parked note 6690280341 raised are answered in ① (function door boundary: acceptable; warning sentence: correct for every family, nothing pins the old one).
  • Out-of-scope notes of the dev, read: (a) direct ObjectQL.registerFunction — answered in ①, a follow-up note; (b) the router's rung-2 read for an object-less action of one package beside a bound action of another with the same name — standaloneActionOwnsRoute makes an object-less declaration own every object's route by design (its docblock calls the asymmetry deliberate), so that is a pre-existing routing property, not a holder question; the seat's note stands as filed.
  • Cold boot: ruling B (6687840782) is what ships — BARE_NAME_COLD_BOOT_MODE = 'warn', only a new holder refused; recorded in ADR P1.3, the module doc, the changeset. Type list: ruling 6688041914, all nine in this card, PR order api+action+function → flow/webhook/connector/email_template → agent/skill — ADR P1.5 states that order.
  • File surface: all eight files inside seat 3's claimed surface; engine.ts / protocol.ts (held by refactor: move shared state out of the protocol.ts and engine.ts facades into state-owning facets, so the hot bodies left on the class can become delegations (ruling 6624345766) #30020) untouched; no spec, lint, template, skill or ui file. The claim's surface also named the ADR's scripts/adr-anchors/ anchor, which this diff leaves unchanged (should-fix 1). packages/runtime (the binder's caller) untouched — the function door's loudness at the runtime is should-fix 2.
  • The docs-drift bot (6690094030) lists five hand-written docs naming SchemaRegistry; none contradicts the rule as far as the anchors it names go (lane seat's ACCEPT reads them).
  • Author objectstack-fleet[bot]; branch claude/project-thread-crfe5o; Refs #30803 (PR 2 of 4; the last carries Fixes), as the series allows. PR 3 and PR 4 not started (parked note).
  • Independence: rendered by an isolated contract-review subagent fed the card and its rulings, PR 1's body and two records, the PR body, the net diff, the cited code on origin/main, the kernel and runtime call sites, the anchor and the check-runs — not the seat's or the dev's conclusions; adopted by the seat named in Reviewed-by:.

④ Design judgment

  • The narrowing against ADR-0048 on main. §3.4's coexistence rests on every caller carrying its package id. Stage 1 measured, on a booted two-package showcase in both orders, that the endpoint matcher keeps one API per name (the other package's path 404), /actions/global/NAME runs the last-registered handler while /meta serves the first-registered declaration, and the function registry keeps one function per name for flow script nodes and action target refs — none with a package. Refusing a second holder where the package registers (or binds) takes away only a coexistence no consumer honoured, and the bound action, which the key objectName:name does honour, is left in §3.4 explicitly. The four-axis reading: declared ⇒ enforced holds (the three doors refuse what the changeset says they refuse, nothing more is claimed); contract tightening over leniency holds at the registry (loud 422 at the package door and the seam, a thrown BareNameConflictError at the binder) — the one place leniency survives is the runtime's catch around the bind (should-fix 2), which the diff states rather than hides. Holds.
  • registry.ts. One predicate (bareNameHoldersOtherThan) for all three doors, empty before the seal and for a package that already holds the name; the bound-action skip in bareNamePackageHolders is the right place (every reader of holders goes through it, so the seal, both doors and the function door agree). claimBareNameFunctions reuses the claim map and the uninstall release with no new state. The seal's candidate read is unchanged for registry types and claim-only for function. Holds against installPackage's refusal order (8644, before every mutation) and registerItem's stamp-and-store order.
  • hook-binder.ts. The claim sits before the registration loop and before the hot-reload unregister, so a refusal is all-or-nothing for the bind and leaves the previous set bound — the "ahead of every registration and bind it would make" the ADR states. The duck-typed registry?.claimBareNameFunctions keeps the binder usable against an engine without the registry (the file's existing style for registerFunction). Holds.
  • ADR-0048. §3.4 note, the addendum status line, P1.3, P1.5, P1.6 and P1.7 match the code and the two rulings; P1.7 names the first consumer (the runtime's app: bind) and what is not judged; P1.5 carries the execution plan in the ruled order. The 2026-10-08 addendum and §§1–6 untouched. One drift a later card would inherit is outside the ADR, in its anchor (should-fix 1); one heading reads stale (nit 1). Holds.
  • The pins. The data family's matrix repeated per type × door for api and action, the action-specific CONTROLs, the function door's own matrix (refusal, pre-seal ablation, three CONTROLs, uninstall, cold boot in both readings), the reason and built-in cells; the dogfood drives the cold boot and the install-local door on a booted showcase with the showcase's own API as a holder. Three ablations reported (seal removed 17/34 red; binder claim removed 3 red; holdsBareName forced 3 red) and the dist ablation (3/4 red before rebuild). Holds.

Findings (none must-fix):

  1. should-fix — scripts/adr-anchors/packages__objectql__src__bare-name-one-holder.ts.json, invariant: still names only a field's picklist, an import's mappingName and a dashboard's datasetName, "served the first-registered one", and two doors ("the package door and the item seam"). The module it anchors now covers six types and three doors, and the seat's claim named this anchor in the file surface. check-adr-anchors.mjs reads only the ADR-NNNN citations, so nothing is red, but PR 3 and PR 4 extend from this text. Fix: one sentence naming the request family (API, object-less action, function; served by registration order) and the binder's bind as the third door, or "each family in its own change under Bare-name phase 1: refuse a second package holding a non-object name whose consumers resolve without package context (ruling C on #30798) #30803". Anchor only; a delta review by this reviewer suffices.
  2. should-fix (follow-up card, runtime surface) — packages/runtime/src/app-artifact-handlers.ts, bindAppArtifactHandlers, the catch after ql.bindHooks: a post-seal BareNameConflictError from the binder is logged at error and swallowed, so a hot reload (or a host's own bindAppArtifactHandlers call) that brings a function another package holds leaves the package installed with none of its hooks and functions bound, while the same clash in an API or object-less action refuses the whole registration. The kernel refuses use() after bootstrap, so this is reachable only by reload or host code, and the changeset and PR body state it ("logged by AppPlugin at error"). Not a must-fix: the collision is prevented, the log names both holders and the rename, and the runtime is outside this PR's surface. Fix: a card for the runtime lane to rethrow BareNameConflictError (or surface it on AppArtifactHandlerBinding) so the function door is as loud as the other two; not a reason to hold this head.
  3. should-fix (prose) — .changeset/30803-bare-name-one-package-holder.md (PR 1's, unconsumed at this head), "The cold boot": "Whether that boot should be refused instead is the maintainer's open call" — ruled B on 2026-11-06 (6687840782), and this PR's changeset records the ruling; both entries will sit in one CHANGELOG release. Its "the first-registered holder keeps being served" sentence stays true for its family and needs no change. Fix: replace the "open call" sentence with the ruling (warn; only a new holder refused). One line; a delta review suffices.

Nits:

  • nit — ADR P1.1's heading "Why §3.4's premise does not hold for these three types" now sits beside P1.7's three more; "for the data family" (or "these first three types") reads right. The body of P1.1 is correctly scoped.
  • nit — handlerOwnerPackageId reads any owner without the app: prefix as a package id; today no caller passes functions under such an owner (the metadata-service binds pass hooks only) and the unit test pins the spelling, so nothing is wrong — note it for PR 3 / PR 4's authors, since a future owner-tagged bind of functions would claim under its tag.
  • nit — a function claim is keyed by the runtime's appId (manifest id, falling back to name), while the registry's doors judge by manifest.id and skip a manifest without one; a package with no id would claim functions under its name while its items are never judged. No shipped package is id-less; worth one sentence in the module doc when the file is next touched.
  • nit — "Measured producers": the HotCRM figures (five packages, 40 bound actions, no API / function) were not re-measured here; the showcase's three APIs were. Prose face, the lane seat's ACCEPT.

Implemented-by: claude/project-thread-crfe5o
Reviewed-by: session_01L9qibyvxbrLP4jm4PargoH

VERDICT: PASS

Record written 2026-11-06T17:17Z.

… binder's door; PR 1's changeset records the cold-boot ruling

The ADR-0048 anchor on bare-name-one-holder.ts named only the data family
(picklist, mapping, dataset; served first-registered) and two doors. It now
names the request family too (API, object-less action, engine function;
served last-registered) and the hook binder's bind
(bindHooksToEngine through SchemaRegistry.claimBareNameFunctions) as the
third door. Same ADR citations.

PR 1's still-pending changeset called the cold-boot question the
maintainer's open call; it was ruled on 2026-11-06 (the boot warns, only a
new holder is refused), so the note now says that.

ADR-0048 P1.1's heading names the data family it covers, beside P1.7's
request family.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L9qibxXA6Z1QEsocQJhugE
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#2 · os-tesla · session_01L9qibyvxbrLP4jm4PargoH

Contract review · delta

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 247853de2279045496cd6fc1b351e5a1500ecf15
Local-runs: none
Scope: the one commit 247853de over the reviewed head bb7864488bdfe2b5a7eb5c8d913975c6943619b1 (record 6690893976): scripts/adr-anchors/packages__objectql__src__bare-name-one-holder.ts.json (+1 −1), .changeset/30803-bare-name-one-package-holder.md (+1 −1), docs/adr/0048-cross-package-metadata-collision.md (+1 −1, Tier H). git diff --stat bb786448 247853de lists exactly these three paths, 3 insertions / 3 deletions; no code, test, pin or fixture moved, so every judgment of 6690893976 on the code stands as read. The commit answers should-fix 1, should-fix 3 and nit 1 of that record; should-fix 2 (the runtime's catch around the bind) is a follow-up card by design and is not touched.

Inputs read: the commit message of 247853de; the full diff bb786448..247853de; the anchor file at 247853de; scripts/check-adr-anchors.mjs's header (what it checks: every cited ADR-NNNN resolves to a record under docs/adr/, and a cited Dk names a decision of that ADR); docs/adr/0005-metadata-customization-overlay.md and 0048-cross-package-metadata-collision.md present at the head; ruling B 6687840782; the code the rewritten sentences describe, as read for 6690893976 (registry.ts claimBareNameFunctions / sealBareNameHolders / the seam, hook-binder.ts 201–216, bare-name-one-holder.ts BARE_NAME_COLD_BOOT_MODE = 'warn').

① Derived judgments — one line per rewritten sentence

  • Anchor invariant (should-fix 1). Before: "a field's picklist, an import's mappingName and a dashboard's datasetName do not, so with two holders every caller is served the first-registered one. … narrows §3.4 for these types … refused at the package door and the item seam, naming both holders and the namespace-prefixed rename." After: "Two families of consumers carry none, so with two holders every caller is served one package's item, picked by registration order: the data family — a field's picklist, an import's mappingName and a dashboard's datasetName — serves the first-registered one; the request family — the endpoint matcher's API, an object-less action's handler and the engine function registry's function, read by a flow script node or an action's target — serves the last-registered one. … (each family in its own change under Bare-name phase 1: refuse a second package holding a non-object name whose consumers resolve without package context (ruling C on #30798) #30803) … refused at three doors … the package door (SchemaRegistry.installPackage) and the item seam (SchemaRegistry.registerItem) for the registry types, and the hook binder's bind (bindHooksToEngine in hook-binder.ts, through SchemaRegistry.claimBareNameFunctions) for a function, ahead of every registration it would make." TRUE against the head: the data family is served by the registry's by-name read (first-registered); the API, the object-less action's handler and the function are served last-registered (P1.7's measurement; the sentence names the handler, not the declaration, which is the one that serves first); the three doors and their symbols are the ones in registry.ts and hook-binder.ts; "ahead of every registration it would make" is the claim call's place before the registerFunction loop and the hot-reload unregister. The four ⛔ clauses are unchanged. check:adr-anchors holds: the adrs array still reads ADR-0048, ADR-0005; the invariant cites ADR-0048 and ADR-0005, both resolving to records under docs/adr/; it cites no Dk, so the decision-index check has nothing to resolve.
  • PR 1's changeset, "The cold boot" (should-fix 3). Before: "Whether that boot should be refused instead is the maintainer's open call; the switch (BARE_NAME_COLD_BOOT_MODE, 'warn') sits in bare-name-one-holder.ts." After: "The maintainer ruled this on 2026-11-06: the boot warns, and only a new holder is refused; the switch (BARE_NAME_COLD_BOOT_MODE, 'warn') sits in bare-name-one-holder.ts." TRUE against ruling B (6687840782: warn at boot, refuse new installs; 'warn' is what PR 1 shipped, no follow-up PR owed) and against the code at the head (BARE_NAME_COLD_BOOT_MODE = 'warn', the doors refuse a new holder after the seal). The sentence before it, "the first-registered holder keeps being served", is kept and stays true for the data family. The two changesets now tell one story in the release notes.
  • ADR-0048 P1.1 heading (nit 1). Before: "### P1.1 Why §3.4's premise does not hold for these three types". After: "### P1.1 Why §3.4's premise does not hold for the data family: picklist, mapping, dataset". TRUE: the body's "These three consumers" now has its antecedent in the heading, the family vocabulary matches the addendum's status line ("one family per change (P1.2, P1.7)") and P1.7's "The request family". No anchor link to P1.1 exists in the ADR or elsewhere in the tracked tree that the heading change could break (the #addendum-2026-11-06-… anchors used by the header and §3.4 are the addendum's own, untouched).

② Semver level

Unchanged. Neither carrier moved: .changeset/30803-bare-name-request-family.md is untouched, and PR 1's changeset keeps its front matter ('@objectstack/objectql': minor), its ! first line, its two declaration lines and its adr-0087 marker; only one prose sentence moved. Check Changeset (check-empty-changeset) is expected red on this head by design: the commit edits a changeset that is PR 1's, pending release, as a deliberate correction of its ruling note — the "red by design" case the SKILL.md provides for, confirmed by the seat's ACCEPT; the landing precondition reads that red with its signature as designed, not as a failure of this diff.

③ Boundary flags

  • Tier H stands: docs/adr/** is touched again, by one heading; the seat routes the approver as before.
  • The anchor is a governed-register file (scripts/adr-anchors/), named in seat 3's claimed surface; it moves here as should-fix 1 asked.
  • Should-fix 2 of 6690893976 remains a follow-up card on the runtime surface, as that record said; not owed on this head.
  • No other path moved; main is not merged again by this commit (one commit on bb786448).

④ Design judgment

The three edits make the text say what the code at the head does and what the ruling says, and nothing more: the anchor's invariant now states the two serving orders the measurement found and the three doors by symbol, so PR 3 and PR 4 extend from a true sentence; PR 1's release note records the ruling the two PRs now share; the ADR heading names the family it describes. Holds.

Findings: none.

Implemented-by: claude/project-thread-crfe5o
Reviewed-by: session_01L9qibyvxbrLP4jm4PargoH

VERDICT: PASS

2026-11-06T17:44Z

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#2 · os-tesla · session_01L9qibyvxbrLP4jm4PargoH

维护者速读

  • 改了什么:Bare-name phase 1: refuse a second package holding a non-object name whose consumers resolve without package context (ruling C on #30798) #30803 第二步(ADR-0048 裁决 C 第一阶段第二族)。冷启动后 API、无对象 action、引擎函数三类名字一个部署只认一个包:前两类在安装门与注册口拒,函数在 hook-binder 绑定处拒;都是 422 NAMESPACE_CONFLICT,点名两个持有包并给改名建议。ADR-0048 补 P1.7,锚点与 PR 1 的 release note 同步到你的裁决 B。
  • 为什么改:实测两包同名时,端点只留后注册的 API(另一个包的路径 404),/actions/global/NAME 跑后注册包的代码而 /meta 给先注册包的声明,函数注册表只留后注册的函数;调用方都不带包名,所以只能一名一包。
  • 风险与回滚:已同名的现有部署照常启动(裁决 B:一条告警,开关 'warn'),只拒新增的第二个持有包;绑在对象上的 action、同包重装/热重载、环境覆盖层不受影响。已知缺口:热重载带来别包已持有的函数时,运行时把拒绝记为 error 日志而不是整体拒绝(另立卡跟进)。回滚即撤 PR。
  • 先看:packages/objectql/src/bare-name-one-holder.ts(holdsBareName 的无对象过滤、按类型的拒绝理由)、registry.ts 的 claimBareNameFunctions、hook-binder.ts 的认领调用位置、ADR-0048 P1.7。
  • 两分钟验证:pnpm --filter @objectstack/objectql exec vitest run src/registry-bare-name-one-holder-request-family.test.ts,全过;CI Check Changeset 的红是有意的(改了 PR 1 尚未发布的 release note 一句,见下方第 5 条)。
  • 需要你做的:ADR 改动属 Tier H,请以审批人身份批准;批准后由席位落地。

席位意见:可以落地。

席位核对

ACCEPT · seat domain:server#2 · head 247853de · 2026-11-06T17:47Z

  1. Shape: Refs #30803 (PR 2 of 4), draft, base main, 9 files, Tier H (docs/adr/0048-cross-package-metadata-collision.md). Clause-②: no (narrowing) and Metadata-protocol: narrowed on both carriers; minor on @objectstack/objectql. Take-over claim 6690638720, amendment 6690942072.
  2. Premise: ruling C on [decision] A non-object metadata name shared by two installed packages often resolves to one package silently: which naming contract replaces ADR-0048 §3.4? #30798 (phase 1) and the maintainer's rulings on Bare-name phase 1: refuse a second package holding a non-object name whose consumers resolve without package context (ruling C on #30798) #30803: cold boot B (6687840782) and "all nine types" (6688041914, PR 2 = API + object-less action + function). Stage 1 measurement in the PR body: no request-family consumer carries a package.
  3. Review: CONTRACT_REVIEW_TIER contract review 6690893976 (PASS on bb786448, 0 must-fix, 3 should-fix) and delta review 6691463846 (PASS on this head 247853de, no findings): should-fix 1 (ADR anchor invariant now names both families and the three doors) and should-fix 3 (PR 1's changeset) are done, plus nit 1 (P1.1 heading). Should-fix 2 (the runtime's bindAppArtifactHandlers logs a post-seal BareNameConflictError at error instead of refusing) is outside this PR's surface; the seat files it as its own domain:server card.
  4. Gates / check-runs: code unchanged since bb786448 (37 success, 13 skipped, 0 failed there). On 247853de the prose delta reruns CI; Check Changeset is red by design (item 5); the dev's derived gates on the delta: 38 of 39 exit 0, check:adr-anchors OK.
  5. DELIBERATE CORRECTION confirmed (check-empty-changeset; landing-operations: release-text-only diff ⇒ the dispatching seat's ACCEPT confirms, quoting each sentence). One note is changed, .changeset/30803-bare-name-one-package-holder.md (PR 1's feat(objectql)!: a picklist, mapping or dataset name holds one package (bare-name phase 1, 1/4) #30836, unconsumed), paragraph "The cold boot", one sentence:
    • Before: "Whether that boot should be refused instead is the maintainer's open call; the switch (BARE_NAME_COLD_BOOT_MODE, 'warn') sits in bare-name-one-holder.ts."
    • After: "The maintainer ruled this on 2026-11-06: the boot warns, and only a new holder is refused; the switch (BARE_NAME_COLD_BOOT_MODE, 'warn') sits in bare-name-one-holder.ts."
      The old sentence became false when ruling B landed; restoring it would put a false sentence into the release. Its "first-registered holder keeps being served" sentence stays true for its family and is untouched. The PR body declares the correction.
  6. Landing: Tier H ⇒ the PR stays draft, pm:awaiting-merge, review requested from the governed approvers. On approval the seat runs the pre-landing checks, readies it and arms auto-merge with Check Changeset red as confirmed here. PR 3 and PR 4 follow after this merges; their hand-off is on the card.

@objectstack-fleet objectstack-fleet Bot added the pm:awaiting-merge PR only: landing waits on the maintainer's own review and merge (SKILL.md 复核 ②); never on an issue label Nov 6, 2026
@os-zhuang
os-zhuang marked this pull request as ready for review November 7, 2026 03:05
@os-zhuang
os-zhuang added this pull request to the merge queue Nov 7, 2026
Merged via the queue into main with commit 902f162 Nov 7, 2026
57 of 60 checks passed
@os-zhuang
os-zhuang deleted the claude/project-thread-crfe5o branch November 7, 2026 03:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation pm:awaiting-merge PR only: landing waits on the maintainer's own review and merge (SKILL.md 复核 ②); never on an issue size/xl tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants