Skip to content

Commit a2826a1

Browse files
feat(objectql)!: a flow, a webhook, a connector and an email template hold one name per package (bare-name phase 1, 3/4) (#31093)
<!-- claude-projects-attribution: {"github_login":"os-project-manager"} --> _Requested by **Leehom** · [project thread](https://claude.ai/code/project/chan_01TenzsRopZj4jMbvjX8uz1X?thread=cmsg_01TenzsRopZj4jMbvjX8uz1XAtn2B9TgkenAcW63kQpRgC)_ Refs #30803 (PR 3 of 4 — flow, webhook, connector, email_template) Opened by `domain:server#1` (session_017xRBcTJTE7kSbcMoJQ4Txf); taken over by `domain:server#5` (claim 6713676993, card-thread session_01FEkQ5pr7Ua2kPRCMZNupZg) on triage's hand-over 6713649116. PR 1 was #30836 (the data family), PR 2 #31006 (the request family); the last PR of the series carries the card's closing line. 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 flow, webhook, connector or email template 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 flow, webhook, connector or email template 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.8). ⚠️ **Tier H**: this diff touches `docs/adr/0048-cross-package-metadata-collision.md` (header, §3.4 note, P1.5, P1.6 and the new P1.8). The seat routes the approver. ## 维护者速读 - 改了什么:裁决 C 第一阶段的第三族。冷启动结束后,flow、webhook、connector、email template 这四类名字,一个部署只认一个包:安装门和注册口拒绝第二个包,422 `NAMESPACE_CONFLICT`,点出两个持有包和改名建议(与 #30836、#31006 同一机制,不另起一套)。 - 为什么改:实测两包同名——自动化引擎只武装一个 flow(包 id 字典序靠前者,有告警),`/meta/flow/NAME` 却给先注册包的声明;webhook 派发只订阅先注册包的声明,另一个永不触发,无告警;`/meta/connector/NAME`(设计器的连接器选择器读的门)给先注册包的描述;邮件发送 `template: NAME` 解析先注册包的模板。启动后再装一个同名包,今天照常 200 并被静默遮蔽。 - 风险与回滚:只收窄接受集,不改任何已存数据;平台内置邮件模板包(`com.objectstack.service.email`)按既有契约(ADR-0131 D6,应用同名模板覆盖内置)被排除为"可覆盖基线",既不持有名字也不被拒。基线常量只在引擎内部,不进 `@objectstack/objectql` 的公开导出。回滚即 revert 本 PR。 - 先看:`packages/objectql/src/bare-name-one-holder.ts`(四类加入列表、`OVERRIDABLE_BASELINE_PACKAGES`、`declaredBareNames` 按门的包 id 读基线)、`registry.ts` 的 `bareNamePackageHolders` / `sealBareNameHolders`(邮件模板 `pkg:name@locale` 键的成员剥离,是最有风险的 hunk)、ADR-0048 P1.8。 - 两分钟验证:`pnpm --filter @objectstack/objectql exec vitest run src/registry-bare-name-one-holder-flow-family.test.ts` ——预期 54 passed;或看 dogfood 文件 `bare-name-one-holder-flow-family.dogfood.test.ts` 的四个用例。 - 需要你做的:ADR 改动是 Tier H,需要审批人批准后由席位落地。 ## What changes - **`packages/objectql/src/bare-name-one-holder.ts`** — the covered types grow from six to ten: the registry types `flow`, `webhook`, `connector` and `email_template`, listed after the request family (`BARE_NAME_ONE_HOLDER_TYPES` is now the one ordered list, `BARE_NAME_REGISTRY_TYPES` derived from it); their collection keys, labels (`email template`), consumer clauses and nouns, so a refusal's reason names what each consumer resolves. New **`OVERRIDABLE_BASELINE_PACKAGES`** / `isOverridableBaseline(type, packageId)` (module-level, not on the package index): per type, the packages whose items a consumer serves any other package's same-named item over by contract — today the email service's built-in templates (`com.objectstack.service.email`, `EmailServicePlugin.init`). `declaredBareNames` reads the manifest's package id the way the package door does (`artifactPackageId`: `id`, else `name`) and declares nothing of a type the package is the baseline of. Module doc: the four measurements, the baseline, what is not judged. - **`packages/objectql/src/registry.ts`** — `bareNamePackageHolders` and `sealBareNameHolders` read a bundled type's key through `bundleBaseKey` (`pkg:name@locale` holds the NAME), and the holder list drops a type's baseline package; the item seam skips a baseline package's own registration. Comments name the ten types. - **`packages/objectql/src/index.ts`** — unchanged: `@objectstack/objectql`'s published surface does not widen. The booted pin holds the baseline's spelling by behaviour instead (see Verification). - **`packages/objectql/src/plugin.ts`** — the seal's comment names the ten types; no code change. - **`docs/adr/0048-cross-package-metadata-collision.md`** (Tier H) — the header's Addenda entry and §3.4's 2026-11-06 note name the ten types and the baseline; P1.5 keeps `agent` and `skill` as the last family; P1.6 names the baseline; new **P1.8** — the measurement, what is refused (the bundle, the connector kinds), the overridable baseline, what is not judged (the ADR-0146 declared override, ADR-0097's duplicate at boot, the kernel `metadata` service), the cold boot. - **`scripts/adr-anchors/packages__objectql__src__bare-name-one-holder.ts.json`** — the invariant names the third family and its served holder, the bundle reading and the baseline exclusion. Same ADR citations. - **`.changeset/30803-bare-name-flow-family.md`** — `@objectstack/objectql` `minor`, both declaration lines, BREAKING Before / Now / The overridable baseline / The cold boot / Who moves / Unchanged, `adr-0087: not-required (no-migration-prescription)`, measured producers. ## Why (measured) Booted showcase + two `AppPlugin` packages (`com.qa.bare.alpha`, `com.qa.bare.beta`) sharing one name per type, in both registration orders, on `24bc2593`; then `POST /marketplace/install-local` of a third package (`gamma`) declaring all four held names: | Type | Consumer | Served | Warned | Post-boot third package today | |:--|:--|:--|:--|:--| | `flow` | automation engine (`getFlow`, the bare-name flow map via `resolveFlowPrecedence`) | `alpha` in BOTH orders (lexicographically-first package id) | yes, naming both | `200`, shadowed | | `flow` | `GET /meta/flow/NAME` | first-registered (`alpha` in alpha→beta, `beta` in beta→alpha) — so in one order the designer's body and the running body come from two packages | no | — | | `webhook` | `declaredWebhooks.declaredWebhookNamed` (the dispatcher's reader) and `GET /meta/webhook/NAME` | first-registered; the other is never subscribed | no | `200`, never subscribed | | `connector` (descriptor) | `GET /meta/connector/NAME` (the picker's read); `listItems` returns both | first-registered | no | `200`, shadowed | | `connector` (provider-bound instance) | automation service reconcile | the boot is REFUSED: `duplicate declarative connector instance name 'qa_shared_conn_live' — connector names must be unique (ADR-0097)` | loud (fatal) | after the boot: logged at `error`, skipped (code reading) | | `email_template` | `email.renderTemplate({ template })` and `GET /meta/email_template/NAME` | first-registered | no | `200`, shadowed | The showcase boot also showed the built-in `auth.password_reset` bundle registered under `com.objectstack.service.email` only (four locales) — the showcase itself declares no override; the email plugin's loader serves any other package's same-named member over the built-in by contract, which is why that package is the overridable baseline rather than a holder. ## Verification Stamped on head `c05d912a` = `e01df46c` (this PR's two original commits) + merge of `origin/main` `b3bd75d8` (`6bbbb4a7`, 93 commits, no conflict; the only main change in these files is `registry.ts`'s ADR-0146 pair lookup, `pairOf` → `pairsOf`, no hunk shared) + `c05d912a` (should-fix 1 and the nit of contract review 6703490135). - `packages/objectql/src/registry-bare-name-one-holder-flow-family.test.ts` (54 cells, real `ObjectKernel` + `ObjectQLPlugin`): for each of the four types 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). Family-specific: the type list and its order; `declaredBareNames` over the four collections and a nested plugin, a bundle read once; a descriptor and a provider-bound connector instance hold the name alike; an email template's bundle is one holder and another package's member of that name is one conflict on the name; the item seam judges every member of a bundle; a holder is read from its member key alone (no claim) and the cold boot reads a bundled clash from the member keys; the baseline vocabulary, now also read by the door's package id (a manifest with no `id` whose `name` is the baseline declares nothing; one named for an app declares its template); CONTROLs — an app overriding a built-in template after the boot installs and a second app is refused naming the first app only, the baseline package is refused nothing at either door, the cold boot of the baseline beside an app's override is no clash, two apps overriding one built-in name warn naming the two apps and never the baseline; the reason names only the types it carries; a built-in flow name is refused at the package door. With the data- and request-family pins and `hook-binder.test.ts`: **4 files / 154 passed** on `c05d912a`. - `packages/qa/dogfood/test/bare-name-one-holder-flow-family.dogfood.test.ts` (booted showcase + two `AppPlugin` packages + `MarketplaceInstallLocalPlugin`): the cold boot boots and logs ONE `WARN` line naming the flow, the webhook, the connector and the email template, both packages and the renames, never the built-in bundle or its package, while the automation engine's own flow-collision warning still arms `alpha`; `POST /marketplace/install-local` of a third package declaring the held flow name is `422 PLUGIN_REGISTER_FAILED` naming both holders and `gamma_…`, the package list unchanged and the armed flow untouched; a package declaring the held webhook name plus the showcase's own flow and email template names is refused naming the showcase; CONTROL: fresh names install, and so does a package overriding the built-in `auth.password_reset` — the send then renders the app's subject, the built-in bundle still registered beside it. **The spelling pin, without the export:** the package the email plugin registered the built-ins under is read back from the registry, and the override's install is what holds the engine's spelling of it — were the vocabulary to spell that package any other way, the package would hold `auth.password_reset` and the install would be refused naming it (ablation below). On `c05d912a`: this file and the data and request families' dogfood files plus `email-template-materialization.dogfood.test.ts` → **4 files / 16 passed**. - Ablations on `c05d912a`, each through `ablation-replace.mjs` or a trapped restore, restored with blob == HEAD and `git diff HEAD` empty: | Leg | Mutation | Result | |:--|:--|:--| | src (new, the nit) | `declaredBareNames` reads the baseline by `manifest.id` again | unit file 1 of 54 red: the vocabulary cell, `expected [ { type: 'email_template', … } ] to deeply equal []` | | dist (new, should-fix 1) | `OVERRIDABLE_BASELINE_PACKAGES.email_template` misspelt (marker in 4 built files), objectql rebuilt | dogfood pin 1 of 4 red: the CONTROL, `PLUGIN_REGISTER_FAILED … declares the email template "auth.password_reset"` naming `com.objectstack.service.email`; restored, rebuilt, marker absent from all 14 built files | | dist (re-run, the dogfood file moved) | objectql's `bare-name-one-holder.ts`, `registry.ts`, `plugin.ts` at the merge base `b3bd75d8`, rebuilt (head marker absent from all 14 built files) | dogfood pin 3 of 4 red (no warning line; `gamma` and `delta` installed), the CONTROL green — the CONTROL no longer imports anything from objectql, so on the base it is a control; rebuilt from HEAD | | src (round 1 at `e01df46c`, code unmoved) | the four types removed from `BARE_NAME_ONE_HOLDER_TYPES` / the baseline exclusion removed / the bundle member not stripped | 39 / 3 / 2 of 54 red — not re-run: the merge and `c05d912a` leave those hunks byte-identical | - `pnpm --filter @objectstack/objectql typecheck` exit 0 (incl. `check:test-typecheck`: 39 files / 233 errors / 64 pinned signatures held, unchanged); `pnpm --filter @objectstack/dogfood exec tsc --noEmit` exit 0; eslint (`--no-inline-config`) over `packages/objectql/src` and the dogfood file exit 0. - `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands` at `c05d912a` (merge base `b3bd75d8`, 8 paths): 84 derived families, every one run with its exit code captured before any pipe; `--ran`: "84 derived famil(ies) accounted for — 84 run, 0 NOT-MEASURED" (exit 0). One rerun after a prerequisite: `pnpm check:dual-build-cjs-loads` exit 3 PREREQUISITE NOT MET (`@objectstack/scenarios` had no `dist/` on this worktree; the seven packages round 1 named were built first) → built, rerun exit 0 (122 entry points / 72 packages load). `check:type-check-debt` exit 0 on the first run. - Full dogfood battery (`pnpm --filter @objectstack/dogfood test`, under `os-verify-lock`) at `c05d912a`: `VERDICT command-exit 0` · Test Files 436 passed | 3 skipped (439) · Tests 3614 passed | 41 skipped (3655) · no failures (held the lock 1567s on a shared box). Ran because the title carries `!` and `Clause-②` carries the `(narrowing)` arm. - Not re-run on the merged head (round 1 at `e01df46c`, `VERDICT command-exit 0` both): the full `@objectstack/objectql` suite (565 files / 11476 passed) and `@objectstack/runtime` (553 files / 8882 passed, 19 skipped); CI's Test Core runs both on this head. - CI on `c05d912a`, read once after the push: 35 success, 13 skipped, 2 failure — `Test Core (3/8)` and its aggregate `Test Core`, one cell: `@objectstack/scenarios` `test/runner/external-driver.test.ts` › "a full crm run through the command…" **timed out at 5044 ms** against vitest's 5000 ms default (no assertion failed; 572 of 573 passed). Not this diff: the test arrived with #31265 and drives a scripted CRM run through a spawned command; on this head it passed 4 of 4 locally (2973 ms when timed). Left for the seat to judge; nothing pushed to re-run it. ## Kept - Same-name actions on different objects (`objectName:name`), jobs keyed `appId:jobName`, the unmanaged import's all-type refusal, the environment catalog (ADR-0005 overlay) and the ADR-0146 declared override of a base flow (a stored row); the automation service's own ADR-0097 duplicate refusal at boot; every data- and request-family pin green unchanged (`registry-bare-name-one-holder.test.ts`, `…-request-family.test.ts`, `hook-binder.test.ts` — 100 cells). ## Acceptance notes - The measurement per type is the table above; the card's audit row for `flow` ("one armed, warning logged") holds, and the second consumer (`/meta/flow/NAME`) adds a silent split in one order. - Not judged (stated in the module doc and P1.8): the kernel `metadata` service's `(type, name)` overwrite — on the composed runtime none of the four is read through it (the webhook reader falls back to it only with no registry). - `GET /connectors` answered `404` on the showcase composition during the measurement (the route is not mounted there); the connector reading used `/meta/connector/NAME` and the registry list instead. Observation only. - Round 2 (take-over): contract review 6703490135's should-fix 1 is closed by its route (b) — `OVERRIDABLE_BASELINE_PACKAGES` stays off `packages/objectql/src/index.ts`, so the file leaves this PR's diff and the changeset no longer calls the constant exported; `Clause-②: no (narrowing)` now reads true as written. Its nit (`declaredBareNames` read the baseline by `manifest.id` while the door judges `artifactPackageId`) is fixed in the same reader line. The ADR-0131 D6/D10 citation nit needed no change (the review said so). ## Series and out of scope - PR4 (`agent`, `skill`, the card's closing line) follows this one; ADR-0048 P1.5 names it. - A second app overriding a built-in template beside a first app's override is refused by this PR; whether the first app's override should itself be reported at boot (today: no warning, by the loader's contract) is left as the email plugin's own question. ## Human Minimum - [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic. (not touched) - [x] 2. Every name I added uses the casing for its kind. - [x] 3. No formatter ran; `pnpm lint` — see Verification. - [x] 4. One issue, one branch off an up-to-date `main`, one pull request (third of a series by type family, per the seat). - [x] 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset. - [x] 6. `packages/spec` touched: n/a. - [x] 7. No hand edits to `content/docs/references/`, `packages/*/CHANGELOG.md` or `content/docs/releases/`. - [x] 8. Title, body and comments are in English (the maintainer brief is in Chinese by the seat's convention). - [x] The *Before Submitting* list in CONTRIBUTING.md is done. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01FEkQ5pr7Ua2kPRCMZNupZg <!-- signed: domain:server#5 · os-project-manager · session_01FEkQ5o8PE7ZtVjcor51wBZ --> --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent f5e23e2 commit a2826a1

8 files changed

Lines changed: 986 additions & 65 deletions
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
---
2+
'@objectstack/objectql': minor
3+
---
4+
5+
feat(objectql)!: a flow, a webhook, a connector and an email template hold one name per package — once the cold boot has sealed the registry, a package declaring one under a name another installed package holds is refused at install and registration, naming both holders and the rename; a boot that already holds such a clash warns
6+
7+
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 flow, webhook, connector or email template bears a name another installed package holds. Nothing is widened.
8+
Metadata-protocol: narrowed
9+
Skills-docs: none — `skills/**` does not teach that two packages may share a flow, webhook, connector or email template 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.8).
10+
11+
<!-- adr-0087: not-required (no-migration-prescription) the refusal removes no key, export or field and changes the shape of no stored body; what an author does about a refused name is pick a different one, which no conversion can choose for them -->
12+
13+
**BREAKING** accept-set narrowing at the package registration door, the third family of maintainer ruling C on #30798, phase 1 (the first was picklists, mappings and datasets; the second APIs, object-less actions and functions): managed install (`install-local`, a post-start `manifest.register`, a hot reload) and package registration (`ObjectQL.registerApp` and every package-bound `registerItem`), for flows, webhooks, connectors and email templates. ADR-0048 §3.4 narrowed (addendum 2026-11-06, P1.8).
14+
15+
- **Why.** Measured on a booted showcase with two packages sharing one bare name per type, in both registration orders: the automation engine armed ONE flow per name — the lexicographically-first package's, whichever registered first, with a warning — while `/meta/flow/NAME` served the first-registered package's declaration; the webhook dispatcher's declared reader kept the first-registered package's webhook and never subscribed the other; `/meta/connector/NAME` (the connector picker's read) served the first-registered package's descriptor, and two provider-bound instances under one name already refused the boot (ADR-0097); a send's `template: NAME` resolved the first-registered package's email template. Each answer flipped with registration order; only the flow precedence warned, and it armed one of them all the same. A third package installed after the boot under any of those names installed (`200`) and was shadowed.
16+
- **Before.** Two installed packages could each ship a flow, webhook, connector or email template under one name, at boot or by a later install; every caller was served one of them, chosen by registration order.
17+
- **Now.** After the cold boot (the engine plugin seals the registry at `kernel:bootstrapped`, once every package of the boot has registered), a package registration declaring a flow, webhook, connector or email template under a name another installed package holds is refused ahead of every mutation, so it leaves no record behind. The envelope is the first family's: `code: 'NAMESPACE_CONFLICT'`, `status: 422`, `install-local` answering `422 PLUGIN_REGISTER_FAILED`, every conflicting name in one refusal, both holders named, the rename (the incoming package's namespace prefixed) carried, `conflicts[]` structured. An email template's name holds its whole `(name, locale)` bundle: two members of one package's bundle are one holder, and another package's member of that name — another locale included — is refused on the name.
18+
- **The overridable baseline.** The platform's built-in auth templates are declared under the email service's own package (`com.objectstack.service.email`), and an app's same-named template overrides them by contract (ADR-0131 D6). That package is an overridable baseline for `email_template` (`OVERRIDABLE_BASELINE_PACKAGES`, private to the engine): it neither holds a template name nor is refused one, so an app overriding `auth.password_reset` still installs; the first OTHER package to take a built-in name holds it, and a second is refused naming the first.
19+
- **The cold boot.** A deployment whose installed packages already share such a name still boots: the one warning line names every such name, both holders and the rename, and changes nothing about which holder each caller is served; the flow precedence warning keeps naming both packages and arming one. The maintainer ruled this on 2026-11-06: the boot warns, and only a new holder is refused (`BARE_NAME_COLD_BOOT_MODE` stays `'warn'`).
20+
- **Who moves.** An author whose package declares a flow, webhook, connector or email template under a name another package installed beside it already ships, at the next install or upgrade into a running deployment. **The one-line fix: rename the item in one of the two packages — the refusal names the namespace-prefixed name — or uninstall one of them.** A flow, trigger, picker binding, activation row or send that named the old name must name the new one; nothing rewrites stored references.
21+
- **Unchanged.** The same package registering its own name again. An environment item under a package's name (never judged, never a holder: the ADR-0005 overlay), a paired `.custom` package's declared override of a base flow among them (ADR-0146 D9: a stored row). The email service's built-in templates (above). The automation service's own duplicate refusal of two provider-bound connector instances in one boot (ADR-0097). A job keyed `appId:jobName`, an action bound to an object, every type the rule does not list (ADR-0048 §3.4 as written). `OS_METADATA_COLLISION=warn` does not downgrade the refusal.
22+
23+
**Measured producers.** On objectstack `24bc2593`, the importable examples carry 33 flows (`app-showcase` 32, `app-crm` 1), 1 webhook, 4 connectors and 1 email template (all `app-showcase`; `app-multi-package` none), and no name has more than one holder; `app-todo`'s three flows are `todo_`-prefixed. The platform's own packages declare one email template bundle each of the four built-in auth templates under the email service's package — the baseline — and no flow, webhook or connector. HotCRM is not checked out in this container and is NOT MEASURED here; its five packages share one namespace family, so a same-name clash among them would be a Who-moves case above. Deployed and marketplace packages NOT MEASURED.

‎docs/adr/0048-cross-package-metadata-collision.md‎

Lines changed: 91 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
**Builds on**: [ADR-0003](./0003-package-as-first-class-citizen.md) (package as first-class citizen), [ADR-0005](./0005-metadata-customization-overlay.md) (artifact vs runtime overlay precedence), [ADR-0008](./0008-metadata-repository-and-change-log.md) (metadata repository, `MetaRef` identity), [ADR-0010](./0010-metadata-protection-model.md) (package provenance / `_packageId` stamping)
66
**Consumers**: `@objectstack/objectql` (`SchemaRegistry.registerItem`, `ObjectQL.registerApp`, install path), `@objectstack/console` / `objectui` (routing + metadata resolution), package authors, CLI/CI install path, the app marketplace registry.
77
**Surfaced by**: ADR-0046 review (doc naming) → generalised to all bare-named metadata → re-examined through the app-marketplace install lens.
8-
**Addenda**: [2026-08-08 — publish-time / marketplace namespace exclusivity](#addendum-2026-08-08-publish-time--marketplace-namespace-exclusivity) (**Accepted** 2026-08-08 — maintainer ruling on #1825, [acceptance comment 5226411687](https://github.com/objectstack-ai/objectstack/issues/1825#issuecomment-5226411687), landed by PR #6671; additive to §3.2, supersedes nothing); [2026-11-06 — bare names resolved without a package hold one package](#addendum-2026-11-06-bare-names-resolved-without-a-package-hold-one-package--34-narrowed) (**Accepted**, ruling C on #30798, phase 1; narrows §3.4 for picklists, mappings, datasets, APIs, object-less actions and engine functions, supersedes nothing).
8+
**Addenda**: [2026-08-08 — publish-time / marketplace namespace exclusivity](#addendum-2026-08-08-publish-time--marketplace-namespace-exclusivity) (**Accepted** 2026-08-08 — maintainer ruling on #1825, [acceptance comment 5226411687](https://github.com/objectstack-ai/objectstack/issues/1825#issuecomment-5226411687), landed by PR #6671; additive to §3.2, supersedes nothing); [2026-11-06 — bare names resolved without a package hold one package](#addendum-2026-11-06-bare-names-resolved-without-a-package-hold-one-package--34-narrowed) (**Accepted**, ruling C on #30798, phase 1; narrows §3.4 for picklists, mappings, datasets, APIs, object-less actions, engine functions, flows, webhooks, connectors and email templates, supersedes nothing).
99

1010
---
1111

@@ -267,22 +267,28 @@ package — stays covered by the `naming/namespace-prefix` lint in `os lint`.
267267
> [Addendum (2026-10-08)](#addendum-2026-10-08-the-security-catalog-holds-one-name-per-deployment--34-narrowed).
268268
269269
> **Narrowed (2026-11-06) — picklists, mappings, datasets, APIs, object-less
270-
> actions and engine functions are out of §3.4.** Their consumers resolve the
271-
> name with no package — a field's `picklist`, an import's `mappingName`, a
272-
> dashboard's `datasetName`, the endpoint matcher's one API per name,
273-
> `/actions/global/NAME`, a flow `script` node's function — so with two holders
274-
> every caller is served one of them, chosen by registration order, measured on a
275-
> booted two-package deployment (#30803). Once the cold boot has sealed the
276-
> registry, a package whose picklist, mapping, dataset, API or object-less action
277-
> bears a name another installed package holds is refused at managed install and
278-
> at package registration (a built-in's name, at managed install), and a package
279-
> whose handlers bring a function another package holds is refused at the hook
280-
> binder's bind, each naming both holders and the rename. ⛔ The environment
281-
> catalog is never a holder: an environment item under a package's name is the
282-
> ADR-0005 overlay. The other types the measurement found resolving without a
283-
> package context — `email_template`, `webhook`, `connector`, `flow`, `agent` and
284-
> `skill` — are to follow, each family in its own change. Every other type,
285-
> an action bound to an object among them, keeps §3.4 as written. See
270+
> actions, engine functions, flows, webhooks, connectors and email templates are
271+
> out of §3.4.** Their consumers resolve the name with no package — a field's
272+
> `picklist`, an import's `mappingName`, a dashboard's `datasetName`, the
273+
> endpoint matcher's one API per name, `/actions/global/NAME`, a flow `script`
274+
> node's function, the automation engine's one armed flow per name, the
275+
> dispatcher's one declared webhook per name, the connector picker's one
276+
> connector per name, a send's `template` — so with two holders every caller is
277+
> served one of them, chosen by registration order, measured on a booted
278+
> two-package deployment (#30803). Once the cold boot has sealed the registry, a
279+
> package whose picklist, mapping, dataset, API, object-less action, flow,
280+
> webhook, connector or email template bears a name another installed package
281+
> holds is refused at managed install and at package registration (a built-in's
282+
> name, at managed install), and a package whose handlers bring a function
283+
> another package holds is refused at the hook binder's bind, each naming both
284+
> holders and the rename. ⛔ The environment catalog is never a holder: an
285+
> environment item under a package's name is the ADR-0005 overlay. ⛔ Nor is the
286+
> email service's built-in template bundle: an app's same-named template
287+
> overrides it by contract (ADR-0131 D6), so that package holds no template name
288+
> and is refused none. The remaining types the measurement found resolving
289+
> without a package context — `agent` and `skill` — are to follow in the series'
290+
> last change. Every other type, an action bound to an object among them, keeps
291+
> §3.4 as written. See
286292
> [Addendum (2026-11-06)](#addendum-2026-11-06-bare-names-resolved-without-a-package-hold-one-package--34-narrowed).
287293
288294
### 3.5 Namespace rename-on-install is deferred (non-goal)
@@ -950,8 +956,8 @@ first phase.
950956

951957
> **Status of this addendum: Accepted** — it records phase 1 of a maintainer
952958
> ruling. It **narrows §3.4 for more metadata types, one family per change (P1.2,
953-
> P1.7), and supersedes no other text**: §§1–6, the 2026-08-08 addendum and the
954-
> 2026-10-08 addendum stand verbatim.
959+
> P1.7, P1.8), and supersedes no other text**: §§1–6, the 2026-08-08 addendum and
960+
> the 2026-10-08 addendum stand verbatim.
955961
>
956962
> **Ruling this addendum records** — #30798, answered by the maintainer
957963
> 「同意 c」 (ruling record 6676670839, 2026-11-06). The ruled option's text,
@@ -1059,18 +1065,17 @@ the doors never refuse.
10591065

10601066
### P1.5 To follow
10611067

1062-
The same measurement found these types resolving without a package context too:
1063-
`email_template`, `webhook` (the second package's webhook is never subscribed),
1064-
`connector`, `flow`, `agent` and `skill`. Each family joins P1.2's list in its own
1065-
change under #30803 — `flow`, `webhook`, `connector` and `email_template`; then
1066-
`agent` and `skill` — with its own pins, narrowing §3.4 the same way (the
1067-
maintainer kept all of them in #30803 on 2026-11-06). `tool` and `rag_pipeline`
1068-
share a reader with `agent` and were not measured. The request family that was
1069-
listed here is P1.7.
1068+
The same measurement found `agent` and `skill` resolving without a package
1069+
context too. They join P1.2's list in the series' last change under #30803, with
1070+
their own pins, narrowing §3.4 the same way (the maintainer kept all of them in
1071+
#30803 on 2026-11-06). `tool` and `rag_pipeline` share a reader with `agent` and
1072+
were not measured. The request family that was listed here is P1.7; the flow
1073+
family — `flow`, `webhook`, `connector` and `email_template` — is P1.8.
10701074

10711075
### P1.6 Where it is implemented
10721076

1073-
`@objectstack/objectql`: the rule, the holders and the switch in
1077+
`@objectstack/objectql`: the rule, the holders, the overridable baseline
1078+
(`OVERRIDABLE_BASELINE_PACKAGES`, P1.8) and the switch in
10741079
`bare-name-one-holder.ts`; the package door in `SchemaRegistry.installPackage` and
10751080
the item seam in `SchemaRegistry.registerItem`; the function door in
10761081
`bindHooksToEngine`, through `SchemaRegistry.claimBareNameFunctions`; the seal in
@@ -1115,3 +1120,61 @@ ambiguous: the binder resolves it among its own package's functions only.
11151120
**The cold boot** reads the bind claims with the registry's holders, under P1.3's
11161121
switch: two packages of one boot binding one function name are named in the same
11171122
warning line, as packages, never as their handler owners.
1123+
1124+
### P1.8 The flow family: `flow`, `webhook`, `connector`, `email_template`
1125+
1126+
Measured with P1.1's two packages, in both orders (#30803, PR 3):
1127+
1128+
- **`flow`** — the automation engine keys its flow map by the bare name and
1129+
arms ONE flow per name: the lexicographically-first package's
1130+
(`resolveFlowPrecedence`), whichever registered first, with a warning naming
1131+
both — the one type of the nine that warns; `/meta/flow/NAME` serves the
1132+
FIRST-registered package's declaration, so in one order the body the designer
1133+
opens and the body that runs come from two packages. A third package installed
1134+
after the boot under that name installs (`200`) and is shadowed.
1135+
- **`webhook`** — the dispatcher's declared-webhook reader keeps the
1136+
FIRST-registered package's declaration per name and never subscribes the
1137+
other, silently; `/meta/webhook/NAME` serves the same one.
1138+
- **`connector`** — `/meta/connector/NAME` (the by-name door the designer's
1139+
connector picker reads) serves the FIRST-registered package's descriptor,
1140+
silently. Two provider-bound instances under one name at boot are already
1141+
refused by the automation service (ADR-0097's duplicate, fatal at boot); after
1142+
the boot the second is logged at `error` and skipped, its install answering
1143+
`200`.
1144+
- **`email_template`** — a send's `template: NAME` resolves the
1145+
FIRST-registered package's bundle, silently; `/meta/email_template/NAME` serves
1146+
the same one.
1147+
1148+
**What is refused.** All four join P1.2's list: the same two doors, the same
1149+
holders, the same envelope and rename. A provider-bound connector instance and a
1150+
catalog descriptor hold the name alike — it is the connector's identity in the
1151+
engine's registry and the picker both. An email template's NAME holds its
1152+
bundle: the registry keys the type by `(name, locale)` (`pkg:name@locale`), the
1153+
holder reading strips the member, two members of one package's bundle are one
1154+
holder, and another package's member of that name — another locale included — is
1155+
one conflict, on the name.
1156+
1157+
**The overridable baseline.** The platform's built-in auth templates are
1158+
code-declared under the email service's own package
1159+
(`EmailServicePlugin.init`, `@objectstack/plugin-email`), and the template loader
1160+
serves any OTHER package's `(name, locale)` member over them by contract
1161+
(ADR-0131 D6/D10: an app declaring `auth.password_reset` overrides the
1162+
platform's copy). So that package is an overridable baseline for
1163+
`email_template` (`OVERRIDABLE_BASELINE_PACKAGES`): it neither holds a template
1164+
name nor is refused one, at the package door, the item seam and the cold boot
1165+
alike. The first other package to take a built-in name holds it; a second is
1166+
refused naming the first, never the baseline. No other type has a baseline today.
1167+
1168+
**Not judged.** A paired `.custom` package's declared override of a base
1169+
package's flow (ADR-0146 D9 item 1) is a stored row bound to its pair, hydrated
1170+
into the bare slot — the environment catalog, never a package holder, so the
1171+
override path stays open. The automation service's own duplicate refusal of two
1172+
provider-bound instances in ONE boot (ADR-0097) runs before the seal and stands;
1173+
the doors here judge the install that follows a boot. The kernel `metadata`
1174+
service's `(type, name)` overwrite is not a consumer of these four on a
1175+
composed runtime (the webhook reader falls back to it only with no registry),
1176+
and is not judged.
1177+
1178+
**The cold boot** reads the four with the registry's other holders, under
1179+
P1.3's switch: the flow precedence warning keeps naming both packages and arming
1180+
one, as before; the bare-name warning names every clash once with its rename.

0 commit comments

Comments
 (0)