Repository navigation
Description
epic:#3600#1 · hotlong · session_01Q4EjjPXwX1YfHyKJUh4agu
Filing-gate category: ② decision the maintainer directed. Maintainer, verbatim (Claude Code session, 2026-11-06), on the seat's bare-name audit: 「裸名的问题立决策卡」. Origin: the scenario-library epic objectstack-ai/cloud#3600 (gallery apps installed side by side in one environment).
Maintainer brief
- The problem in one sentence: when two installed apps each ship, say, an automation or a button with the same name, the platform often runs only one of them — usually silently, and which one depends on install order.
- What you decide: how a non-object metadata name stays unambiguous across installed packages. Options A, B-lite, B, C, D below; the seat recommends C, phased.
- If nothing is decided: every new marketplace or gallery app that reuses a common name (
send_email,approve,sales_rep) can silently disable part of another app; nobody sees an error.
Background
Only object names must carry the package namespace (<ns>_<short>, ADR-0129). Every other metadata type — flows, actions, hooks, picklists, pages, dashboards, reports, apps, datasets, api items, email templates, agents, tools, skills, jobs, connectors, webhooks, … — has a bare name. ADR-0048 decided that two packages MAY ship the same bare name: storage keeps both (unique on type, name, package_id, bundle_member), §3.3 says "each caller resolves to its own package's item", and §3.4 says two distinct packages "coexist on the same bare name by construction". The 2026-10-08 narrowing of §3.4 states its premise, "§3.4 rests on every caller carrying its package id", and took the security catalog out because assignments carry no package context. For action the ADR never claimed package scoping (§6 lists it as intentionally global; same-name actions on different objects are keyed objectName:name and do not collide).
Governing text
- ADR-0048 (
docs/adr/0048-cross-package-metadata-collision.md): §3.3 (lines 217-256) package-scoped resolution, and a context-free read "is best-effort: it returns the first match and the caller is expected to pass the package id when it cares"; §3.4 (261-266) coexistence "by construction", narrowed 2026-10-08 for positions, permission sets and capabilities (feat(objectql)!: positions, permission sets and capabilities hold one name per deployment — a second holder is refused at registration, naming both #22197 / [decision] cold boot admits a package whose permission set or position name the environment catalog already holds (package registration runs before sys_metadata hydration), while a hot install of the same package is refused #22307); §5's "[done] lint warns on non-prefixed app/page/dashboard/flow/action/report/dataset" (333-335) is out of date (below). Superseded in part by ADR-0146 D2/D8 for a base's dependency closure only. os lintnaming/namespace-prefix(packages/cli/src/commands/lint.ts:319-326): today flags intra-package duplicates only, and its message says "Distinct packages may reuse the same name freely; the namespace prefix is an optional convention, not a collision-avoidance requirement".- ADR-0129 (Accepted): the literal object prefix gives the AI author "exactly one writing style".
- ADR-0028 (Deferred): package-scoped identity with qualified cross references; the seat's ruling record on objectstack-ai/cloud#3600 (comment 6676044775) records that this direction makes AI authors err more.
- Security catalog rule (ruling Q4 = A on feat(core,objectql,plugin-security,plugin-sharing): the catalog is read from the registry; assignment tables reference it by name (ADR-0131 D2/D3/D4) #15196; the ADR-0048 2026-10-08 addendum).
- Unmanaged import (
unmanagedImportCollisions,packages/metadata-protocol/src/protocol-unmanaged-import.ts:46-90): already refuses every same-name clash, all types, 409.
Analysis re-read: an isolated reviewer at the contract-review tier re-verified every cited line and answered PASS WITH FIXES; all ten fixes are applied in this text.
Protocol statement: this card changes ADR-0048 §3.4's scope (every option but D) and, under C, ADR-0129's rule. No fleet protocol change.
Premise (re-check commands)
The seat's audit (read-only, 2026-11-06, origin/main 802c98c; re-verified by an isolated reviewer at 04f36b6) finds §3.4's premise false for most behaviour types: their runtime consumers resolve by bare name with no package context. Re-check:
grep -n "register(" packages/metadata/src/metadata-manager.ts | head # kernel 'metadata' service keyed (type, name); second register overwrites (~853-858); list dedups by name (~1407-1412)
sed -n 340,400p packages/services/service-automation/src/flow-precedence.ts # one flow armed per name, ranked by package id alphabetically, warning logged
sed -n 4970,4990p packages/runtime/src/action-execution.ts # action dispatch: by-name read with no package
sed -n 415,425p ../cloud/packages/service-ai/src/tools/action-tools.ts # AI tool name action_<bare name>; second skipped (~1014-1017)
sed -n 250,260p ../cloud/packages/service-ai/src/tools/tool-registry.ts # ToolRegistry.register overwrites silently
sed -n 100,110p packages/objectql/src/engine-hooks-and-functions.ts # function registry keyed by bare name, last wins
sed -n 7900,7945p packages/objectql/src/registry.ts # managed install refuses only namespace / object / security-catalog / default-page clashes
Measured outcome per type (non-security types only):
| Runtime behaviour on a second same-name package item | Types |
|---|---|
| Both served correctly | job (a second holder is scheduled under appId:jobName, #21602), assignment rules (both evaluated) |
| One armed, warning logged, deterministic | flow |
| One served, silently | everything read through the kernel metadata service (agents, skills, external catalogs, api items via listForIndex), dispatch of an object-less action, AI tools (action_<name>, ToolRegistry), function handlers behind hooks, picklists, mappings, datasets/cubes, email templates, webhooks, two declared connectors |
| Package-scoped when the caller names the package, first match otherwise (reads only: a package-less save or delete of an ambiguous name already answers 409) | app, page, dashboard, report, standalone view, doc/book |
A security-relevant subset of the audit is with the maintainer separately and is not described here.
The specific question
§3.4 promised coexistence; most execution-time consumers cannot honour it. Which contract replaces it?
Options
| Option | What it does | What a customer sees | Workload |
|---|---|---|---|
| A. One name, one holder, everywhere | Extend the security catalog's refusal to every type whose consumers resolve without package context: install and package registration refuse a held name, naming both holders | The second app's install is refused with "rename X or uninstall Y"; nothing breaks silently, but two unrelated vendors' apps that both use send_email cannot coexist |
domain:engine · 1–2 PRs · precedent #22197 (+1080/−103) and #22365 (+705/−43) for three types |
| B-lite. Package-scope only the execution-time consumers | Flow engine, object-less action dispatch, function registry and AI tool names resolve by (package, name); everything else stays as is | Fewer silent cases, but a cross-package trigger or action must still name a package, and every new consumer must remember to | several lanes (automation, runtime, ai) · ~4 PRs · no precedent |
| B. Make every consumer package-aware | Honour §3.4: each runtime consumer resolves by (package, name); cross-package references use the qualified <packageId>:<name> form |
Same-name apps coexist; authors and AI must choose between a bare and a qualified reference at every cross-package site | many lanes (automation, runtime, ai, mcp, metadata, analytics, email, webhooks) · ~10+ PRs · no precedent |
| C. Every name carries the namespace | Extend ADR-0129's literal prefix to every package-authored top-level metadata name (crm_send_email), except names already derived from a prefixed object (fields, object extensions, container views) or already namespaced (translations <ns>_<locale>); enforced at defineStack/os validate and the save door; same-name clashes become impossible by construction |
Names read crm_send_email; two apps never collide; one naming rule for everything |
domain:engine + domain:devx · 3–4 PRs (rule + ADR amendment, load-time conversion or migration of first-party packages/templates/examples, lint) · no measured precedent for a cross-type naming rule; nearest landed: #22197 (+1080/−103) and #22365 (+705/−43), three types |
| D. Status quo, fix consumers one by one | Keep §3.4; file each silent consumer as found | Silent partial breakage continues until each is found | open-ended; no precedent |
Business meaning, one line each:
- A = "first come, first served" for names — like a shared drive that refuses a second file of the same name.
- B-lite = folders only for the files that run, links into them must still say which folder.
- B = every app gets its own folder, but every link must say which folder — the ADR-0028 shape.
- C = every app's files start with the app's code. Salesforce managed packages show every custom component as
ns__Name; ServiceNow scoped apps prefix tables, fields, properties and script-include API namesx_<co>_<app>_and scope the rest by application scope. Both derive the prefix; ObjectStack authors it literally (ADR-0129, for the one-writing-style reason). - D = keep the bug, chase symptoms.
Four axes (business stance)
- ① Long-term soundness. C collapses two rules (objects prefixed; everything else bare, plus a growing per-type refusal list) into one, and matches how Salesforce names packaged components. A adds types to a refusal list, and that list grows with every new type. B and B-lite multiply package-qualified sites at consumers. D grows the bug surface.
- ② Real pull today. The gallery installs several verified apps side by side and the maintainer has ruled that gallery apps prefix every name (2026-11-06); the marketplace ships multiple packages per environment. Measured incidents: [finding] Two install-local packages that declare the same job name: the second install silently replaces the first package's job, which stops running, and the door says nothing #21602 (jobs, fixed), meta: with two package holders of one dashboard name, GET /meta/dashboard/X/layers silently returns one holder's overlay while package-less save and delete answer 409 #28337 (dashboard overlay, fixed). No customer report of a flow or action clash yet.
- ③ AI errors. C gives the AI one rule ("every name starts with your namespace"), and the error is loud at authoring. A is loud, but only at install, after the work is done. B and B-lite add a second spelling, the qualified form — the failure ADR-0129 exists to prevent — and a wrong guess resolves silently to another package. D is silent.
- ④ Startup non-sprawl. A and C add no new key; C is a rule change plus a one-time migration. B and B-lite add a qualified-reference form to maintain forever.
Recommendation
C, phased. End state, two years out: every package-authored name carries its namespace, as every custom component in a Salesforce managed package does; a collision cannot be authored by a namespaced package, and the per-type refusal list is a backstop only, not the contract.
Phasing (② and workload adjust the order only; the letter does not change):
- Now: extend the existing registration refusal (A's mechanism) to the silent types. It closes silent mis-resolution for packages already in the field. After phase 3 it remains only as the backstop for the cases C cannot reach: packages that declare no namespace, the shared
base/system/sysnamespaces, and third-party packages inside the deprecation window. - Next: the existing
naming/namespace-prefixlint rule (today intra-package duplicates only, its message calling the prefix optional) is re-widened to warn on every bare non-object name, and ADR-0048 §5's lint bullet is corrected in the same PR; scenario/gallery cards and first-party templates adopt the prefix (already ruled for the gallery; live card Scenario cards carry an app code; the runner and gallery use the demo<code> namespace and refuse a bare non-object item name #30795). - Then:
defineStack/os validateand the save door refuse a bare name in a package that declares a namespace, with the rename in the message; first-party packages migrate in the same release, and third-party packages get a load-time conversion or a deprecation window (the lane's design call).
Confidence gaps: the per-type list is an audit reading, not a booted two-package measurement; some names may be referenced by external callers (inbound webhook ids, connector ids), where a rename is a breaking change — phase 3 must list them. The migration size of first-party packages (hotcrm, templates, examples) is not measured.
Not chosen: A alone keeps two naming rules and a list that grows with each new type. B-lite leaves every new execution-time consumer to remember package scoping, and adds the qualified form. B is the ADR-0028 direction the maintainer rejected for AI-error reasons. D leaves silent breakage in place.
Self-check: on ① alone C; do ② ③ ④ flip it: no.
Execution after the ruling
- C: the seat files three
domain:engine/domain:devxcards (phase 1 refusal backstop; phase 2 lint + templates; phase 3 rule + ADR-0129 / ADR-0048 §3.4 amendment + migration), serial in that order, and records the ruling on objectstack-ai/cloud#3600. - A: one
domain:enginecard (the phase-1 card alone) plus the ADR-0048 §3.4 narrowing. - B or D: the seat files per-consumer cards from the table above.
os-decision-facets
- ① Long-term: C shrinks special cases (one naming rule, the refusal list a backstop only); A grows a list; B and B-lite multiply qualified-reference sites.
- ② Pull: gallery side-by-side installs (ruled 2026-11-06) and multi-package marketplace environments; incidents [finding] Two install-local packages that declare the same job name: the second install silently replaces the first package's job, which stops running, and the door says nothing #21602, meta: with two package holders of one dashboard name, GET /meta/dashboard/X/layers silently returns one holder's overlay while package-less save and delete answer 409 #28337 fixed; no flow/action report yet.
- ③ AI errors: C is one rule, loud at authoring; A loud at install; B and B-lite add a second spelling that fails silently; D silent.
- ④ Non-sprawl: A and C add no key; B and B-lite add a permanent qualified form.
Prior rulings read: "one name, one holder" → 10 hits (security catalog, #28337); "name collision" package metadata → 193 hits (relevant: #21602, #28337, #22307 A); "needs-user-decision prefix name" → #29594 (a different question); ADR-0048 §3.3–§3.5, §5, §6 + 2026-10-08 addendum, ADR-0129 D2–D4, ADR-0028 (Deferred), ADR-0146 D2/D8; thread: none
Related
- objectstack-ai/cloud#3600 (epic; gallery namespace rulings 2026-11-06)
- feat(objectql)!: positions, permission sets and capabilities hold one name per deployment — a second holder is refused at registration, naming both #22197, feat(objectql)!: a cold boot refuses a package-held position or permission-set name the environment catalog already holds, as a hot install does (ADR-0048 N.3) #22365, [decision] cold boot admits a package whose permission set or position name the environment catalog already holds (package registration runs before sys_metadata hydration), while a hot install of the same package is refused #22307 (security catalog one-name rule), [finding] Two install-local packages that declare the same job name: the second install silently replaces the first package's job, which stops running, and the door says nothing #21602 (jobs), meta: with two package holders of one dashboard name, GET /meta/dashboard/X/layers silently returns one holder's overlay while package-less save and delete answer 409 #28337 (dashboards)
- ADR-0048 §3.5 (rename-on-install, recorded as deferred) is amended separately by the install-time rename card the maintainer approved 2026-11-06.
Generated by Claude Code
Activity
objectstack-fleet commentedon Nov 6, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Triage: audit ·
domain:engine→domain:server· p1 kept ·needs-user-decision· 2026-11-06T03:53Z⛔ Not a claim.
domain:engineis a retired alias ofdomain:server. A decision card on the naming contract that replaces ADR-0048 for a non-object name shared by two installed packages. It waits for the maintainer.Generated by Claude Code
objectstack-fleet commentedon Nov 6, 2026
epic:#3600#1 · hotlong · session_01Q4EjjPXwX1YfHyKJUh4agu
Ruled: C
Maintainer, verbatim (Claude Code session, 2026-11-06): 「同意 c」, after the seat's multi-dimension analysis in session. Every package-authored metadata name carries its namespace, phased.
Execution, all filed and handed to lanes (maintainer, verbatim: 「能交给车队开发的你不要自己开发」):
domain:engine): measure on a booted two-package environment, then refuse a second holder of a name whose consumers resolve without package context, at install, registration and cold boot.domain:devx): the lint warns on every bare non-object name; ADR-0048 §5 corrected; first-party templates and examples prefixed, with the bare-name baseline recorded.domain:skills): the published authoring skills teach the one rule.domain:engine+domain:spec, blocked by Bare-name phase 1: refuse a second package holding a non-object name whose consumers resolve without package context (ruling C on #30798) #30803 and Bare-name phase 2: the namespace-prefix lint warns on every bare non-object name; first-party templates and examples adopt the prefix (ruling C on #30798) #30804):defineStackand the save door refuse bare names; ADR-0129 and ADR-0048 §3.4 amended; a migration plan for stored references (permission-set assignments), external references (webhook, connector and api ids), third-party packages, Studio auto-prefixing and length, before any code.The security-relevant subset stays with the security seat. Closing this decision card as completed; the phase cards carry the work.