Skip to content

feat(objectql)!: a picklist, mapping or dataset name holds one package (bare-name phase 1, 1/4) - #30836

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

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

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Elon · project thread

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

Refs #30803 (PR 1 of 4; the last PR of the series carries Fixes)

Clause-②: no (narrowing) — the engine's package door (SchemaRegistry.installPackage) and item seam (SchemaRegistry.registerItem) refuse, after the cold boot, a package registration they accepted before: one whose picklist, mapping or dataset 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 a picklist, mapping or dataset name, and the naming rule is phase 2's (#30805); the prescription ships in the refusal and warning text and in ADR-0048's 2026-11-06 addendum.

⚠️ Tier H: this diff touches docs/adr/0048-cross-package-metadata-collision.md (§3.4 narrowing note + addendum). The seat routes the approver.

维护者速读

  • 改了什么:按裁决 C 第一阶段,picklist、mapping、dataset 三类名字一个部署只能有一个包持有。冷启动结束(kernel:bootstrapped)后,第二个包再声明同名项,在安装入口和注册入口都被拒(422 NAMESPACE_CONFLICT),提示里点出两个持有包和改名建议(命名空间前缀,如 beta_status)。
  • 为什么:实测两包同名时,字段的 picklist、导入的 mappingName、仪表盘的 datasetName 都不带包上下文,谁先注册就给谁,另一个包被默默错配(picklist 甚至拒绝自己包的取值)。
  • 冷启动:已经同名的部署照常启动,只打一条告警,点名两个持有包和改名;开关 BARE_NAME_COLD_BOOT_MODE 默认 'warn',等你裁决是否改成拒绝启动。
  • 不变的:同一包重装或热重载;环境里同名覆盖(ADR-0005 覆盖层,从不算持有者);其它类型(page、按对象的 action、job 等)继续共存;OS_METADATA_COLLISION=warn 不降级。
  • 先看:packages/objectql/src/bare-name-one-holder.ts(规则与开关)、registry.ts 的 refuseBareNameConflicts / sealBareNameHolders、plugin.ts 的 kernel:bootstrapped 封印、ADR-0048 新附录。
  • 需要你做的:ADR 改动是 Tier H,需要审批人批准;冷启动拒绝还是告警,等你一句话。

What changes

  • packages/objectql/src/bare-name-one-holder.ts (new) — the rule and its vocabulary, the security catalog's sibling (security-catalog-namespace.ts) generalised: the covered types (picklist, mapping, dataset), the declared names a manifest carries (declaredBareNames, the engine's own collection sources, nested plugins[] one level), the holders (a package; a built-in, none today), the rename (suggestedBareNameRename: the namespace prefixed), and the one cold-boot switch BARE_NAME_COLD_BOOT_MODE ('warn').
  • packages/objectql/src/registry.ts —
    • BareNameConflictError: the ADR-0112 envelope (code: 'NAMESPACE_CONFLICT', status: 422), every conflict in one refusal, conflicts[] of { type, name, incomingPackageId, existingHolder, rename }.
    • The package door: installPackage asks refuseBareNameConflicts ahead of every mutation (after the security catalog's and the default record page's refusals), records the package's claims, and uninstallPackage forgets them. No co-owner exemption, no collisionPolicy downgrade.
    • The item seam: registerItem refuses a package-bound picklist, mapping or dataset over another package's name.
    • One predicate for both doors (bareNameHoldersOtherThan): empty until sealed, empty when the incoming package already holds the name; holders are composite <packageId>:<name> slots in serving order plus install claims. ⛔ The bare slot (the environment catalog) is never read.
    • sealBareNameHolders() (the seal + every name the boot left held twice, sorted) and the module-level sealBareNameHoldersAtColdBoot(registry, mode, logger) (warn once, or refuse with door: 'cold-boot').
  • packages/objectql/src/plugin.ts — the engine plugin seals at kernel:bootstrapped (after the kernel:ready install-local rehydrate), under BARE_NAME_COLD_BOOT_MODE.
  • packages/objectql/src/security-catalog-namespace.ts — one header sentence ("for every other type §3.4's coexistence stands") points at the sibling.
  • docs/adr/0048-cross-package-metadata-collision.md (Tier H) — a "Narrowed (2026-11-06)" note under §3.4, an Addenda-line entry, and the addendum: ruling C's text and phasing verbatim (record 6676670839), the measurement, what is refused, the cold boot and its switch, what stays as §3.4 has it, the types to follow, where it is implemented. It states that after phase 3 the refusal remains only as the backstop for namespace-less packages, the shared base/system/sys namespaces and third-party packages in the deprecation window.
  • scripts/adr-anchors/packages__objectql__src__bare-name-one-holder.ts.json — the anchor.
  • .changeset/30803-bare-name-one-package-holder.md — @objectstack/objectql minor, both declaration lines, BREAKING Before / Now / Who moves / Unchanged, adr-0087: not-required (no-migration-prescription), measured producers.

Why (measured, stage 1)

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

Type Consumer Served Warned
picklist field picklist: NAME, /meta/object options + write validator first-registered list for BOTH objects; the second package's own values refused VALIDATION_FAILED no
mapping POST /data/OBJ/import { mappingName } first-registered; the other package's import 400 MAPPING_TARGET_MISMATCH no
dataset POST /analytics/dataset/query { datasetName } first-registered no

Verification

  • packages/objectql/src/registry-bare-name-one-holder.test.ts (new, 40 cells, real ObjectKernel + ObjectQLPlugin, the manifest service door): per type × door — package door (both holders, rename, no record left), item seam, nested plugin, uninstall releases, same-package reload (CONTROL), environment registration never judged and never a holder (CONTROL); per type at the cold boot — boots and warns once naming both holders and the rename; both holders keep their name across a reload while a third is refused naming both; 'refuse' refuses with the doors' envelope; the cold boot's reading (served holder as existing, sorted, one warning); no-clash boot is silent; every conflict in one refusal; no namespace, no rename; a built-in name; CONTROLs: a non-covered type (page) coexists, a picklist extension is no holder, collisionPolicy: 'warn' does not downgrade.
  • packages/qa/dogfood/test/bare-name-one-holder.dogfood.test.ts (new, booted showcase + two AppPlugin packages + MarketplaceInstallLocalPlugin): the cold boot boots and logs ONE WARN line naming all three names, both holders and the renames; POST /marketplace/install-local of a third package declaring the held picklist name is 422 PLUGIN_REGISTER_FAILED naming both holders and gamma_…, package list unchanged; a package redeclaring the showcase's mapping and dataset names is refused naming com.example.showcase; CONTROL: fresh names install. 4 passed.
  • Ablation (src): the engine plugin's seal call removed → 19 of the 40 unit cells red (every door refusal and the cold-boot warning), the 21 ablation/control cells stay green; restored. Ablation (dist): the dogfood file run against the pre-change @objectstack/objectql dist (dogfood imports it through dist) → 3 of 4 red (no warning line; both install-local cases 200), the fresh-names CONTROL green; green after rebuilding the dist.
  • @objectstack/objectql full suite: 556 files / 11108 tests passed; pnpm --filter @objectstack/objectql typecheck (incl. the test-typecheck ratchet) exit 0.
  • Gates (dispatch-gates --commands, 81 derived + 5 roster families, reconciled with --ran) and the full dogfood battery at this head (75d1339f, origin/main merged): both were running when this PR opened; their results are in the os-dev report 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.

Kept (the card's three)

  • Same-name actions on different objects stay keyed objectName:name; jobs stay keyed appId:jobName (app-artifact-handlers.jobs.test.ts); the unmanaged import's all-type refusal is untouched. Every existing security-catalog pin is unchanged (registry-security-catalog-namespace.test.ts and its siblings green in the full suite).

Series and out of scope

  • PR2–PR4 (same mechanism, own pins, §3.4's "to follow" list): api, object-less action, engine function registry; email_template, webhook, connector, flow; agent, skill. A type the security seat claims lands there instead (asked of the maintainer by the seat).
  • Candidates sharing a reader, not measured: tool and rag_pipeline register by name like agent at the artifact door; included only on the seat's word.
  • Cloud carrier note (not filed): the AI runtime consumers of agent/skill/tool names (service-ai: agents, skills, action_NAME tool names, ToolRegistry) live in objectstack-ai/cloud, not in this repo, and were NOT MEASURED here.
  • Not refused: analytics_cube (a non-host package's cube never reaches the analytics service, 404 CUBE_NOT_FOUND both orders: a separate reach gap).

Deviations

  • The seal lives in the engine plugin's kernel:bootstrapped hook, not the registry's own doors: during a boot every package registers again, so the doors judge nothing until every package of the boot has registered. A kernel with bootPhaseHooks: false never seals (its doors never refuse); stated in the module doc and the ADR.
  • Unlike the security catalog, the environment catalog is never a holder here: an environment item under a package's name is the ADR-0005 overlay (the seat's ruling 1).

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 (first 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.

Generated by Claude Code

Once the cold boot has sealed the registry (the engine plugin, at
kernel:bootstrapped), a package whose picklist, mapping or dataset bears
a name another installed package (or a built-in) already holds is refused
at the package door (installPackage, ahead of every mutation) and at the
item seam (registerItem), with the NAMESPACE_CONFLICT 422 envelope naming
both holders and the namespace-prefixed rename. These consumers resolve
the name with no package, so with two holders every caller was served
the first-registered one (measured, stage 1).

During a boot the doors judge nothing; a deployment whose packages
already share such a name boots and warns once (BARE_NAME_COLD_BOOT_MODE
'warn', the switch the maintainer rules on). A package already holding a
name is never a new holder. The environment catalog is never a holder
(ADR-0005 overlay). ADR-0048 §3.4 narrowed (addendum 2026-11-06).

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 42 documentable anchor(s). ⚠️ 1 changed file(s) yielded no anchor (packages/objectql/src/security-catalog-namespace.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

26 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json 7d9682ffd7115b89a7f4c5c8dfe14691afc1ce7b.

⛔ 9 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/objectql/src/security-catalog-namespace.ts) — pages documenting those are invisible to this run
  • 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 7d9682ffd7115b89a7f4c5c8dfe14691afc1ce7b → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 64d63e745efdf0c17f48205969c04ad459542594 — the merge of head 417ed9b9c5afe55d27978b4738c76f1a20714f83 into base 7d9682ffd7115b89a7f4c5c8dfe14691afc1ce7b, 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 64d63e745efdf0c17f48205969c04ad459542594 && git checkout 64d63e745efdf0c17f48205969c04ad459542594
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 7d9682ffd7115b89a7f4c5c8dfe14691afc1ce7b 417ed9b9c5afe55d27978b4738c76f1a20714f83 && git checkout -B drift-repro 7d9682ffd7115b89a7f4c5c8dfe14691afc1ce7b && git merge --no-ff 417ed9b9c5afe55d27978b4738c76f1a20714f83

node scripts/docs-audit/affected-docs.mjs --json 7d9682ffd7115b89a7f4c5c8dfe14691afc1ce7b

⚠️ 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 7d9682ffd7115b89a7f4c5c8dfe14691afc1ce7b → pass the list as
args.docs, on the commit named under Which tree this was computed on.

…der each package's namespace

The five fixture packages, installed into one kernel, all declared the
mapping `orders_pull`; a mapping name now holds one package per
deployment, so the control's install was refused as a second holder.
Each package now declares `<namespace>_orders_pull` (and the typo cases
name `<namespace>_orders_pul`), so each case still measures only its
own pull binding.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WV4d1EuHiCqvh8VEz81QD
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

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

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: d154c6228ee725091c082a65b5f2949e4087c4bc
Local-runs: none
Scope: packages/objectql/src/bare-name-one-holder.ts, packages/objectql/src/registry.ts, packages/objectql/src/plugin.ts, packages/objectql/src/security-catalog-namespace.ts, packages/objectql/src/registry-bare-name-one-holder.test.ts, packages/qa/dogfood/test/bare-name-one-holder.dogfood.test.ts, packages/cli/test/package-install-local-jobs-pull.integration.test.ts, scripts/adr-anchors/packages__objectql__src__bare-name-one-holder.ts.json, docs/adr/0048-cross-package-metadata-collision.md (Tier H) + cited on main: security-catalog-namespace.ts whole (the precedent's rule, holders, cold boot, exclusions); registry.ts (registerItem's security-catalog seam, baseName, withDisc and the composite packageId:name slot vs the bare slot; installPackage's refusal order: namespace gate, co-owned object names, security catalog, default record page; isShareableNamespace; uninstallPackage → unregisterItemsByPackage; the ADR-0146 D3 uninstall refusal); plugin.ts (the existing kernel:bootstrapped hook, loadMetadataFromService's package-bound registerItem, the residual seam at 3190–3235); packages/core/src/kernel-base.ts (dispatchesBootPhaseHooks, bootPhaseHooks); packages/metadata-core/src/item-key-discriminators.ts (email_template only); packages/metadata-protocol/src/protocol-unmanaged-import.ts (unmanagedImportCollisions), protocol-registry-hydration.ts and protocol.ts 9876 (hydration and write-through register with no package), protocol-write-gates.ts (isArtifactBacked's note), served-override.ts, protocol-customization-package.ts; packages/cloud-connection/src/marketplace-install-local-customization-pair.ts (header: a pair is never installed as code) and marketplace-install-local-plugin.ts (kernel:ready rehydrate); packages/runtime/src/app-plugin.ts (registers in init()); packages/objectql/src/engine-package-registration.ts (registerPlugin, one level); ADR-0146 header, D2 ("Which types"), D3, D8; ADR-0048 §3.4 and the 2026-10-08 note on main; scripts/pm/clause2-line.mjs; scripts/check-adr-0087-registration.mjs (the categories); scripts/adr-anchors/packages__objectql__src__security-catalog-namespace.ts.json; packages/objectql/CHANGELOG.md (the #22197 entry's declarations and marker; the _packageId stamp refusal entry); packages/objectql/src/index.ts (the registry export list); .changeset/30803-bare-name-one-package-holder.md as the ② input; the picklist, mapping and dataset names of examples/app-crm, app-showcase, app-todo, app-multi-package, packages/create-objectstack's blank template and HotCRM 6961ab1's five packages.

Inputs read: card #30803 body and its three comments (triage 6677272063, seat claim 6677636083, os-dev-report 6679284054); decision card #30798 body and ruling C (6676670839); the dispatch order dispatch-30803.md, 30803/stage1-measurement.md and 30803/pr1-body.md on the project mount; PR #30836 body, 10-file list, three commits (d9c82841, the main merge 75d1339f, d154c622), the net diff vs main (merge-base b2a2b76d, 3 ahead, main 14 ahead touching none of the 10 paths); the docs-drift comment 6678325553; the check-runs on this head, read twice (07:08Z and 07:19Z). Both carriers through clause2-line.mjs: Clause-② value: no, arm: narrowing, Metadata-protocol value: narrowed, one line-initial key each.

① Derived judgments

  • Package against package and built-ins, never the environment catalog (seat ruling 1). bareNamePackageHolders walks the type's store, skips the bare slot (key === name) and reads only composite packageId:name slots (the stamp when present, else the key prefix) plus the install claims; bareNameHoldersOtherThan adds a built-in at the package door only. A registration with no packageId is never judged (the seam is inside if (packageId && isBareNameOneHolderType(type))), and the cold boot's sealBareNameHolders reads the same two sources. Pinned per type: an environment registration over a package's name coexists, an environment-only name is no holder, and an environment item under a package's name is no clash at the cold boot. TRUE, and the three places that state it (module doc, ADR P1.2, the anchor) agree.
  • Cold boot is one switch, shipping 'warn' (seat ruling 2). BARE_NAME_COLD_BOOT_MODE: BareNameColdBootMode = 'warn' in bare-name-one-holder.ts is read at exactly one call site, the engine plugin's kernel:bootstrapped hook, which hands it to sealBareNameHoldersAtColdBoot(registry, mode, logger); 'refuse' throws BareNameConflictError with door: 'cold-boot' from inside the hook, and the kernel's boot-path dispatch propagates (hook-dispatch.ts). Flipping it is the one constant plus the two pins that read the default (it('the switch is "warn" ...') and the dogfood boot case, which expects a boot and a WARN line); the 'refuse' arm is pinned on its own through the exported function. A single point to flip. TRUE.
  • Family split (seat ruling 3). The diff refuses picklist, mapping, dataset and nothing else (BARE_NAME_ONE_HOLDER_TYPES, pinned by the page CONTROL); the ADR §3.4 note and P1.5 name the to-follow list (api, object-less action, function registry, email_template, webhook, connector, flow, agent, skill) and the security-seat carve-out. The three types match stage 1's silent rows; analytics_cube is left out for the measured reason (no reach). TRUE.
  • The refusal names both holders and the rename. bareNameConflictLine carries the incoming package, the type, the name, describeBareNameHolder(existing) and the rename; the error's conflicts[] carries them structured; the cold-boot warning uses the same line. Install-local surfaces it as 422 PLUGIN_REGISTER_FAILED with the message (dogfood pins alpha, beta, "gamma_qa_shared_status"); a third package is refused naming both earlier holders (pinned). The rename stands down for no namespace or a shareable one (isShareableNamespace) and when the name already carries the prefix. TRUE.
  • ADR-0146 .custom overrides unaffected. A declared override (D2, dataset and mapping are overridable types) is a stored sys_metadata row bound to the pair; the pair is "never installed as code" (marketplace-install-local-customization-pair.ts header: its install writes each item through the save door), the ensure door installs an empty manifest through installPackage (claims nothing), and every hydration and write-through registers with no package (protocol-registry-hydration.ts 127/487/1302, protocol.ts 9876, isArtifactBacked's note). The served override is read from rows (isServedOverrideRowOf), not the registry. So no path puts a pair's dataset or mapping into a composite slot or a claim, and the new seam never sees it. The one package-bound hydration site (loadMetadataFromService) covers object, view, app, flow, hook only. TRUE; the ADR addendum does not say this (nit below).
  • Unmanaged import unaffected. unmanagedImportCollisions is untouched and reads rows plus isArtifactBacked; the import writes rows, which hydrate into the bare slot and are never judged. TRUE.
  • Kept behaviours. Object-bound actions and jobs are outside the type list; same-package re-registration is excluded by packages.includes(incomingPackageId); picklistExtensions declare no name; collisionPolicy: 'warn' is not consulted; every security-catalog path is untouched except one header sentence. Pinned as CONTROLs.
  • The fixture rename hides no consumer break (seat-accepted deviation). The five com.example.pull* packages are the test's own and ship nowhere; the assertions still pin the same verdicts (declares no mapping, declares no connectorSource) against the renamed names. Across shipped first-party sets: the four examples hold 7 names with no duplicate; the blank template declares none; HotCRM's five packages hold 5 picklists, 3 mappings, 10 datasets with no duplicate inside the artifact. Across the two sets two dataset names coincide: task_metrics (HotCRM src/sales/datasets/task.dataset.ts, examples/app-todo/src/datasets/task.dataset.ts) and opportunity_metrics (HotCRM src/sales/datasets/opportunity.dataset.ts, examples/app-crm/src/datasets/opportunity.dataset.ts). No shipped composition installs HotCRM beside an example (dogfood boots the showcase alone; HotCRM's smoke boots HotCRM alone; app-multi-package declares none of the three types), so nothing shipped breaks at install; the coincidence is a should-fix on the changeset's measured-producers paragraph, below.
  • Precedent fit with security-catalog-namespace.ts. Same vocabulary and shape (*_TYPES, is*Type, COLLECTION_KEY, BUILT_IN_*, the holder union, describe*Holder, *HolderKey, declared*Names reading the same sources as the engine seam, nested plugins[] one level as registerPlugin does), same door order in installPackage (after the default-record-page refusal, ahead of every mutation, no co-owner exemption, no downgrade), same item-seam placement (before stamp or store), claims noted on the restore point, forgotten at uninstall. The two differences are the ruled ones and each is stated in the module doc, the ADR and the precedent's amended header: the environment catalog is not a holder, and the cold boot is a seal plus a switch rather than a post-hydration boot check (claims are a Set because a boot registers every package before anything is judged). Sound.
  • ADR text matches the code, with one breadth: §3.4's note and P1.2 say a built-in holder is refused "at managed install and at package registration", while the item seam asks builtIns: false (the module doc says "package door only", as the precedent's note did). Nit. P1.3's seal moment, P1.4's kept list, P1.5's to-follow list and P1.6's symbols match registry.ts and plugin.ts; the anchor names the file, ADR-0048 and ADR-0005 with the invariant's four ⛔s matching the module doc.
  • Published face. index.ts exports a named list from registry.js; BareNameConflictError, sealBareNameHoldersAtColdBoot, bareNameColdBootWarning and the new module are not added to it, and NAMESPACE_CONFLICT_CODE was already exported. No .describe() text, no spec file, no error code or status moves. Nothing widened: no (narrowing) is the right spelling, matching the _packageId stamp refusal at the same door and the feat(objectql)!: positions, permission sets and capabilities hold one name per deployment — a second holder is refused at registration, naming both #22197 entry.

② Semver level

Both carriers read Clause-②: no (narrowing) and Metadata-protocol: narrowed, and the dispatch's warning against yes (narrowing) is honoured. narrowed is right by AGENTS.md step 3: a package registration that succeeded is now refused on a declared value, "a value refused". The level follows the kind: minor on @objectstack/objectql, the one package the diff moves (packages/qa/dogfood and packages/cli move tests only). The ! title is on the PR and the changeset's first line; the BREAKING section reads Why / Before / Now / The cold boot / Who moves (with the one-line fix in bold) / Unchanged / Measured producers; one adr-0087: not-required (no-migration-prescription) marker, its text verbatim the #22197 entry's, which shipped the same rule for the security catalog; Check Changeset is green on this head. Disposition: honest and precedented — no key, export or stored shape moves, and the name an author picks is not a conversion's to choose; the changeset carries no FROM-to-TO rewrite, only the remedy. Ledgers: narrowed moves none here (no spec file, no migration entry; Spec property liveness green). Skills-docs is honest (phase 2's rule is #30805's), spelled none in the body and no move in the changeset (nit).

Check-runs on d154c622 at 07:19Z: 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. The PR is a draft; docs/adr/** makes this Tier H, the body says so, the seat routes the approver.

③ Boundary flags

④ Design judgment

  • The narrowing against ADR-0048 on main: §3.4 rests on every caller carrying its package id; stage 1 measured, on a booted two-package showcase in both orders, that a field's picklist, an import's mappingName and a dashboard's datasetName carry none and are served the first-registered holder silently (the picklist refusing a package's own values). Refusing a second holder where the package is registered is ruling C's phase-1 mechanism and takes away only a coexistence no consumer honoured. Holds.
  • registry.ts: one predicate for both doors, empty until sealed and empty for a package that already holds the name, so a boot never refuses itself and a reload, re-install or upgrade keeps its names while a third package is refused naming both. The seal is placed where every package of the boot has registered (AppPlugin.init, the kernel:ready rehydrate), and a kernel that withholds the boot-phase hooks never seals, which the module doc and ADR state. Holds against installPackage's refusal order and registerItem's stamp-and-store order.
  • The pins cover the matrix the dispatch asked for: per type × door (package door, item seam, nested plugin, uninstall, same-package CONTROL, environment CONTROL), the cold boot in both modes, the reading's order and single warning, every conflict in one refusal, no-namespace, built-in, three CONTROLs for the kept behaviours; two ablations (seal removed → 19 red; dist pre-change → 3 of 4 red) reported. Holds.

Findings (none must-fix):

  • should-fix — changeset, "Measured producers": the paragraph measures the four examples and HotCRM each alone and says "no name has more than one holder"; across the two sets task_metrics and opportunity_metrics are each declared by HotCRM (src/sales/datasets/) and by an example (app-todo, app-crm). No shipped composition installs them together, so nothing shipped breaks, but the sentence should name the coincidence (or say the sets were measured separately) so a demo environment that installs HotCRM beside an example reads its own Who-moves here. Prose only; a delta review by this reviewer suffices.
  • nit — ADR-0048 §3.4 note and P1.2: "or a built-in holds is refused at managed install and at package registration" reads both doors; the item seam does not ask built-ins (builtIns: false, the precedent's shape, stated in the module doc). One clause: "a built-in, at the package door".
  • nit — ADR P1.4 could add one sentence that a paired .custom package's declared override (ADR-0146 D2) is a stored row bound to the pair, never a package holder, so the refusal does not close the override path; the code already behaves so.
  • nit — Skills-docs: is none in the PR body and no move in the changeset; both fleet-used spellings, one would do.

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

VERDICT: PASS

…door only and keeps the .custom override path

Contract-review follow-up on the bare-name phase 1 change:

- the §3.4 narrowing note and P1.2 say a built-in's name is judged at
  managed install only, as the item seam asks no built-in;
- P1.4 states that a paired `.custom` package's declared override is a
  stored row bound to its pair, never a package holder;
- the changeset's measured producers name the two dataset names HotCRM
  and the examples share (`task_metrics`, `opportunity_metrics`), and
  its `Skills-docs:` line uses the claim template's `none` spelling.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WV4d1EuHiCqvh8VEz81QD
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

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

Contract review — delta on record 6679538573

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 0b9c39542d7acd18eb568c4703adb2c67f02856f
Local-runs: none
Scope: the one commit 0b9c3954 over the reviewed head d154c6228ee725091c082a65b5f2949e4087c4bc: .changeset/30803-bare-name-one-package-holder.md (+2 −2) and docs/adr/0048-cross-package-metadata-collision.md (+8 −4, Tier H). No other path; git diff --name-only d154c622 0b9c3954 lists exactly these two, so no code, test, anchor or fixture moved and every judgment of record 6679538573 on the code stands as read.

Inputs read: the commit message of 0b9c3954; the full diff d154c622..0b9c3954; the PR body at this head (byte-identical to the body read for 6679538573); the changeset at this head; both carriers through clause2-line.mjs; the check-runs on 0b9c3954, read at 07:25Z and again when the changeset and docs gates settled.

① Derived judgments

  • Prose only. The changeset's two hunks change Skills-docs: no move — to Skills-docs: none — and append three sentences to "Measured producers"; the ADR's three hunks rephrase the §3.4 note's built-in clause, add half a sentence to P1.2's built-in bullet and add one P1.4 bullet. No code path, pin, anchor or fixture is touched. TRUE.
  • The should-fix is answered. "Measured producers" now says the two sets were measured separately, names task_metrics (HotCRM src/sales/datasets/ and app-todo) and opportunity_metrics (HotCRM and app-crm), states that no shipped composition installs HotCRM beside an example so nothing shipped is refused, and makes HotCRM beside app-todo or app-crm a Who-moves case. That is what the record asked for and what the code does on such an install. TRUE.
  • Nit 1 answered. §3.4's note now reads "a name another installed package holds is refused at managed install and at package registration (a built-in's name, at managed install)", and P1.2's built-in bullet adds "as there, only the package door asks it" — matching refuseBareNameConflicts (builtIns: true) and the item seam (builtIns: false). TRUE.
  • Nit 2 answered. P1.4 gains "A paired .custom package's declared override (ADR-0146 D2) is a stored row bound to its pair, never a package holder, so the refusal does not close the override path" — the fact record 6679538573 verified against marketplace-install-local-customization-pair.ts, the hydration seams and served-override.ts. TRUE.
  • Nit 3 answered. Both carriers now spell Skills-docs: none —; the PR body already did, so only the changeset moved. TRUE.

② Semver level

Unchanged. clause2-line.mjs reads the PR body and the changeset at 0b9c3954 as Clause-② value: no, arm: narrowing and Metadata-protocol value: narrowed, one line-initial key each; the changeset keeps '@objectstack/objectql': minor, the ! first line, the BREAKING section and exactly one adr-0087: not-required (no-migration-prescription) marker, none of which the commit touched.

Check-runs on 0b9c3954 at 07:30Z: 47 so far — 21 completed/success, among them Check Changeset, Governed Surface Queue Guard, Dogfood Regression Gate, PR-body guards, Spec property liveness, Lint · docs & code guards, Lint · repo & tooling gates, Lint · ESLint, Lint · CI, release & engine gates, Lint · PM dispatch-gates, Lint · scoped (two of three), Type Check · source gates, Type Check · debt ledger, Plan Test Core shards, Flag docs affected by code changes, Test UI; 13 completed/skipped; 13 in_progress (Build Core, Test Core 1 to 8 of 8, Type Check · consumer gates, Type Check · workspace, Lint · scoped · query-options erasure, Live E2E UI); none failed. The gates that read the two touched files have settled green; the remaining runs exercise code this commit did not touch, and the all-green landing precondition is the seat's to read on this head.

③ Boundary flags

  • Tier H stands: docs/adr/** is touched again, by prose that narrows the ADR's own claim to what the code does; the seat routes the approver as before.
  • The commit carries the dev session's Claude-Session trailer and no Refs trailer; the repository squash-merges, so no branch message reaches main.
  • Nothing else in the dispatch's surface or outside it moved.

④ Design judgment

The three ADR edits make the text say less, not more: a built-in is judged where the code judges it, and the one override path the narrowing could be misread as closing is named as open. The changeset's paragraph now carries the one cross-set coincidence a Who-moves reader needs. Holds.

Findings (none must-fix):

  • nit — "Measured producers" still counts HotCRM 6961ab1 at "2 datasets"; its tree holds nine *.dataset.ts files (account, contact, event, forecast, lead, opportunity, task under sales/, product under revenue/, case under service/), all with distinct names, so the sentence's claim (no name held twice inside the artifact) holds and only the count is off. Carrier the seat, whenever the changeset is next touched; not a reason to hold this head.

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

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

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

ACCEPT · #30803 (PR 1 of 4) · 2026-11-06T07:47Z · Head-sha: 0b9c39542d7acd18eb568c4703adb2c67f02856f

维护者速读(席位校对)

  • 改了什么:按裁决 C 第一阶段,picklist / mapping / dataset 三类名字一个部署只能由一个包持有;冷启动结束后第二个包再声明同名项,安装入口和注册入口都拒绝(422 NAMESPACE_CONFLICT),点名两个持有包和改名建议。
  • 为什么:实测这三类的消费方都不带包上下文,先注册者胜,另一个包被静默错配(picklist 连自己包的取值都被拒)。
  • 冷启动:已存在同名的部署照常启动,只告警一次;开关 BARE_NAME_COLD_BOOT_MODE 默认 'warn',是唯一翻转点,等维护者裁决(席位线程决策卡)。
  • 不变:同包重装/热重载、环境覆盖层(ADR-0005)、其它类型共存、OS_METADATA_COLLISION=warn 不降级。
  • 先看:bare-name-one-holder.ts、registry.ts 的 refuseBareNameConflicts / sealBareNameHolders、plugin.ts 的 kernel:bootstrapped 封印、ADR-0048 附录。
  • 需要你做的:ADR-0048 改动是 Tier H,请 os-zhuang 或 hotlong 批准(契约复核 PASS 6679538573 + 增量 PASS 6679688246,Head-sha 0b9c3954);另有冷启动拒绝/告警一问待 Elon。

席位意见:同意落地。代码面按安全目录先例(#22197)泛化,范围严格限于席位裁定的三类;其余八类待维护者确认安全重叠后进 PR2–PR4。

席位核对

  1. Shape: 10 files, +1,366/−17 (bare-name-one-holder.ts new; registry.ts package door + item seam + seal; plugin.ts seal at kernel:bootstrapped; one header sentence in security-catalog-namespace.ts; ADR-0048 §3.4 note + 2026-11-06 addendum + anchor; 40-cell unit file; 4-cell dogfood file; one cli fixture rename; changeset). Diff read at the reviewed head.
  2. Premise, measured: stage-1 booted measurement (seat project file 30803/stage1-measurement.md) → seat rulings in the card thread (package-vs-package only, the ADR-0005 overlay never a holder; four PRs by family; PR1 = picklist / mapping / dataset). Contract review 6679538573 (CONTRACT_REVIEW_TIER, d154c622): PASS, no must-fix; delta review 6679688246 (this head, prose only): PASS, the should-fix and all three nits answered. Clause-②: no (narrowing), Metadata-protocol: narrowed on both carriers (clause2-line.mjs); minor on @objectstack/objectql; ADR-0087 not-required (no-migration-prescription), the feat(objectql)!: positions, permission sets and capabilities hold one name per deployment — a second holder is refused at registration, naming both #22197 precedent's disposition.
  3. Gates: dispatch-gates 82 derived, 82 run, all exit 0, --ran 0 NOT-MEASURED (at d154c622); full dogfood battery 424 files / 3537 tests green (title carries !); ablations 19/40 unit red, 3/4 dist red. At this head the changeset gates, check:adr-anchors and the ADR / docs gates exit 0. CI on this head: 37 success, 13 skipped, 0 failed.
  4. Accepted deviations: packages/cli/test/package-install-local-jobs-pull.integration.test.ts (outside the dispatch surface): its five test-owned fixture packages shared the mapping name orders_pull, now <namespace>_orders_pull; the reviewer confirmed no shipped composition breaks (the HotCRM / example coincidences task_metrics, opportunity_metrics are never installed together, and the changeset names them as a Who-moves case). The cold-boot switch ships 'warn' pending the maintainer's ruling; 'refuse' is pinned separately and the flip is one constant plus two default pins. One reviewer nit left for the next touch of the changeset: the HotCRM dataset count reads 2, the tree holds 9 distinct names.
  5. Out of scope: PR2–PR4 (api, object-less action, function registry, webhook, connector, flow, email_template, agent, skill) wait for the maintainer's answer on the security-relevant subset of [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; tool / rag_pipeline named as candidates; the cloud service-ai consumers are a carrier note in the body (not filed); docs candidate (import-mappings page) not filed. Skills-docs: none holds (the naming rule is Bare-name phase 2: the published authoring skills teach one rule, every metadata name carries the namespace (ruling C on #30798) #30805's).
  6. Landing: check-governed-merges --pr 30836: governed, Tier H (docs/adr/** ×1), 1,383 lines. pm:awaiting-merge added; reviewers os-zhuang and hotlong requested in this stroke. The seat lands after an authorized APPROVED review and the maintainer's cold-boot ruling (flipped first if he rules refuse).

@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 6, 2026 09:52
@os-zhuang
os-zhuang enabled auto-merge November 6, 2026 09:52
…-crfe5o

# Conflicts:
#	docs/adr/0048-cross-package-metadata-collision.md
@os-zhuang
os-zhuang added this pull request to the merge queue Nov 6, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

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

ACCEPT addendum · #30803 (PR 1 of 4) · 2026-11-06T11:22Z · Head-sha: 417ed9b9c5afe55d27978b4738c76f1a20714f83

Carries ACCEPT 6679952314 (on 0b9c3954) to this head. The delta is one merge of origin/main at 7d9682ff, made by a merge commit (no rebase):

  • Conflict: one line, docs/adr/0048-cross-package-metadata-collision.md:8 (the header Addenda entry). Main's docs(adr): record ADR-0048 Addendum A as accepted #30833 moved Addendum A from Proposed to Accepted; this PR had appended its 2026-11-06 entry to the same line. Resolution keeps main's text verbatim and appends this PR's entry; no decision text moved.
  • Auto-merged: packages/objectql/src/plugin.ts (main's feat(objectql,platform-objects): keep a by-id delete's rows in the recycle bin (ADR-0148) #30780 recycle-bin import, option, field and retention read in init(), none near the kernel:bootstrapped seal); registry.ts unchanged on main.
  • Re-run at this head: @objectstack/objectql full suite 560 files / 11,205 tests passed, typecheck exit 0 (test-typecheck ratchet OK); the 15 ADR / changeset gates exit 0; dispatch-gates' one new family (scripts/ci/snapshot-next.mjs --self-test) exit 0. CI on this head: 37 success, 13 skipped, 0 failed.

The contract review's judgments (6679538573, delta 6679688246) stand: no code line of this PR moved. check-governed-merges: Tier H; in the merge queue since 11:22Z on os-zhuang's arming. Cold-boot mode ships 'warn'; the maintainer's ruling is still open, and a refuse ruling lands as a one-constant follow-up.

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 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