Repository navigation
feat(objectql)!: a flow, a webhook, a connector and an email template hold one name per package (bare-name phase 1, 3/4) - #31093
Conversation
… hold one name per package (bare-name phase 1, 3/4) The flow family of maintainer ruling C on #30798, phase 1: `flow`, `webhook`, `connector` and `email_template` join the bare-name registry types. Measured on a booted showcase with two packages sharing one name per type, in both orders: the automation engine arms one flow per name (lexicographically-first package, with a warning) while `/meta/flow/NAME` serves the first-registered declaration; the webhook dispatcher's declared reader keeps the first-registered webhook and never subscribes the other; `/meta/connector/NAME` serves the first-registered descriptor (two provider-bound instances already refuse the boot, ADR-0097); a send's `template` resolves the first-registered email template. A third package installed after the boot under any of those names installed (200) and was shadowed. - `bare-name-one-holder.ts`: the four types, in family order, with their collection keys, labels, consumer clauses and nouns; the holder reading excludes a type's OVERRIDABLE BASELINE (the email service's built-in templates, which an app's same-named template overrides by contract, ADR-0131 D6): that package neither holds a template name nor is refused one. - `registry.ts`: the holder reading and the cold-boot seal strip a bundled type's registry-key member (`pkg:name@locale`), so an email template's NAME holds its bundle; the item seam skips the baseline. - New `registry-bare-name-one-holder-flow-family.test.ts` (52 cells) and the booted dogfood pin `bare-name-one-holder-flow-family.dogfood.test.ts`. - ADR-0048: header, §3.4 note, P1.5, P1.6 and the new P1.8; the anchor's invariant names the third family and the baseline; one changeset. Claude-Session: https://claude.ai/code/session_017xRBcTJTE7kSbcMoJQ4Txf Co-authored-by: Claude <noreply@anthropic.com>
… showcase and the bundled holder reading - `OVERRIDABLE_BASELINE_PACKAGES` is exported from `@objectstack/objectql`, so the dogfood pin can hold its spelling of the email service's package equal to the package the email plugin registers the built-in templates under on this boot (read back from the registry: the two spellings are pinned together across the package boundary without either package importing the other). - Two unit cells read an email template's holder from its member key alone: a package registering a bundle member through the registry directly (no install claim) holds the name, and the cold boot reads such a clash from the member keys as one conflict on the name. Claude-Session: https://claude.ai/code/session_017xRBcTJTE7kSbcMoJQ4Txf Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 22 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 9 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 21 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 7227596b2ae4417c0f4d100b2a87d821c1502ac6 && git checkout 7227596b2ae4417c0f4d100b2a87d821c1502ac6
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 7c523aa108650e48d19419ae9b76924c64707741 212583ddbc47ce4c7e4bee0862cd8a7e78dc474a && git checkout -B drift-repro 7c523aa108650e48d19419ae9b76924c64707741 && git merge --no-ff 212583ddbc47ce4c7e4bee0862cd8a7e78dc474a
node scripts/docs-audit/affected-docs.mjs --json 7c523aa108650e48d19419ae9b76924c64707741
|
|
domain:server#1 · sunhaolin · session_017xRBcTJTE7kSbcMoJQ4Txf Contract reviewServed-tier: Written 2026-11-07T06:41Z. Inputs: card #30803 (body + all 15 comments, rulings 6687840782 and 6688041914 and the dev report 6703300597 included), PR #31093 (body, 9-file list, ① Derived judgmentsAccept-set and public-surface changes the diff implies, each judged against the code it reaches:
Half-right (nit, no change owed): ② Semver level
③ Boundary flags
④ Design judgmentEach scoped file against the code it cites and the scenario it is accepted against; cited code READ, never reviewed.
Observation routed to the seat (outside this card): must-fix: none. should-fix: 1 (the Implemented-by: VERDICT: PASS |
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FEkQ5pr7Ua2kPRCMZNupZg
… read it by the door's package id The booted flow-family pin now holds the baseline's spelling by behaviour: it reads the package the email plugin registered the built-in templates under from the registry, and an app overriding a built-in template must install, which it cannot if the engine's vocabulary spells that package any other way. OVERRIDABLE_BASELINE_PACKAGES leaves the objectql index, so the package's published surface does not widen, and the changeset no longer says it is exported. declaredBareNames reads the baseline by artifactPackageId (id, else name), the identity the package door judges, so the door and the seam agree on a manifest with no id. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FEkQ5pr7Ua2kPRCMZNupZg
|
domain:server#5 · os-project-manager · session_01FEkQ5o8PE7ZtVjcor51wBZ Contract-review-claim: session_01FEkQ5o8PE7ZtVjcor51wBZ · head c05d912 · 2026-11-07T23:35Z |
|
domain:server#5 · os-project-manager · session_01FEkQ5o8PE7ZtVjcor51wBZ Contract reviewServed-tier: Written 2026-11-07, outside the dev's session. Inputs: card #30803 (body and all 18 comments — rulings 6687840782 and 6688041914, the claims, os-dev-reports 6703300597 and 6714512451, the earlier records); PR #31093 (body, 8-file list, 4 commits, ① Derived judgmentsEvery accept-set and public-surface change the diff implies, judged against the code it reaches:
② Semver level
③ Boundary flags
④ Design judgmentEach scoped file against the code it cites and the scenario it is accepted against; cited code READ, never reviewed.
The narrowing itself against the published spec on The package door ( Install-local — tried to break it, one finding. The inline branch (the dogfood's door,
Out-of-scope observation (not this PR's, for the seat): on must-fix: none. should-fix: 1 (install-local's cloud branch swallows the bare-name refusal into a next-boot clash — the series' door in cloud-connection, filed as its own card, outside this diff). Observations: id impersonation under the baseline package id (pre-existing); the exported-pair bundle through Implemented-by: VERDICT: PASS Generated by Claude Code |
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FEkQ5pr7Ua2kPRCMZNupZg
|
domain:server#5 · os-project-manager · session_01FEkQ5o8PE7ZtVjcor51wBZ Contract reviewServed-tier: Delta record on the new head, 2026-11-07T23:56Z. It stands on its own and carries forward record 6714650391 (PASS on ① Derived judgmentsThree confirmations, each a reading, then the carried judgments.
So every judgment of 6714650391 ① carries forward unchanged: the package door and item seam narrow for the four types (right); ② Semver levelUnchanged from 6714650391: ③ Boundary flags
④ Design judgmentEach scoped file against the code it cites and the scenario it is accepted against; cited code READ, never reviewed — and, on this head, unmoved: the eight blobs and every cited file compare equal to what 6714650391 judged, so each judgment there holds here verbatim. In brief:
The narrowing against the published spec on Install-local — should-fix 1, carried forward, not a FAIL of this PR. The inline branch refuses The two test files and the anchor — same blobs: 54 unit cells on a real kernel, every refusal by envelope with both holders and the rename, ABLATION cells before the seal, the vocabulary cell pinning both Observations carried (not this PR's): id impersonation under the baseline package id; the exported pair bundle through must-fix: none. should-fix: 1 (carried forward from 6714650391 — install-local's cloud branch, the series' door in cloud-connection, filed as its own card, outside this diff). The merge itself adds nothing to judge: a clean merge of Implemented-by: VERDICT: PASS Generated by Claude Code |
|
domain:server#5 · os-project-manager · session_01FEkQ5o8PE7ZtVjcor51wBZ 维护者速读
席位意见:建议批准。四类的拒绝与前两步同一机制,ADR 与代码、四个消费方一致;上一轮复审的发布面问题已按方案 (b) 关闭( 席位核对 — ACCEPT ·
|
Requested by Leehom · project thread
Refs #30803 (PR 3 of 4 — flow, webhook, connector, email_template)
Opened by
domain:server#1(session_017xRBcTJTE7kSbcMoJQ4Txf); taken over bydomain: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).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.维护者速读
NAMESPACE_CONFLICT,点出两个持有包和改名建议(与 feat(objectql)!: a picklist, mapping or dataset name holds one package (bare-name phase 1, 1/4) #30836、feat(objectql)!: an API, an object-less action and an engine function hold one name per package (bare-name phase 1, 2/4) #31006 同一机制,不另起一套)。/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的四个用例。What changes
packages/objectql/src/bare-name-one-holder.ts— the covered types grow from six to ten: the registry typesflow,webhook,connectorandemail_template, listed after the request family (BARE_NAME_ONE_HOLDER_TYPESis now the one ordered list,BARE_NAME_REGISTRY_TYPESderived from it); their collection keys, labels (email template), consumer clauses and nouns, so a refusal's reason names what each consumer resolves. NewOVERRIDABLE_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).declaredBareNamesreads the manifest's package id the way the package door does (artifactPackageId:id, elsename) 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—bareNamePackageHoldersandsealBareNameHoldersread a bundled type's key throughbundleBaseKey(pkg:name@localeholds 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 keepsagentandskillas 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 kernelmetadataservice), 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/objectqlminor, 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
AppPluginpackages (com.qa.bare.alpha,com.qa.bare.beta) sharing one name per type, in both registration orders, on24bc2593; thenPOST /marketplace/install-localof a third package (gamma) declaring all four held names:flowgetFlow, the bare-name flow map viaresolveFlowPrecedence)alphain BOTH orders (lexicographically-first package id)200, shadowedflowGET /meta/flow/NAMEalphain alpha→beta,betain beta→alpha) — so in one order the designer's body and the running body come from two packageswebhookdeclaredWebhooks.declaredWebhookNamed(the dispatcher's reader) andGET /meta/webhook/NAME200, never subscribedconnector(descriptor)GET /meta/connector/NAME(the picker's read);listItemsreturns both200, shadowedconnector(provider-bound instance)duplicate declarative connector instance name 'qa_shared_conn_live' — connector names must be unique (ADR-0097)error, skipped (code reading)email_templateemail.renderTemplate({ template })andGET /meta/email_template/NAME200, shadowedThe showcase boot also showed the built-in
auth.password_resetbundle registered undercom.objectstack.service.emailonly (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 oforigin/mainb3bd75d8(6bbbb4a7, 93 commits, no conflict; the only main change in these files isregistry.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, realObjectKernel+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;declaredBareNamesover 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 noidwhosenameis 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 andhook-binder.test.ts: 4 files / 154 passed onc05d912a.packages/qa/dogfood/test/bare-name-one-holder-flow-family.dogfood.test.ts(booted showcase + twoAppPluginpackages +MarketplaceInstallLocalPlugin): the cold boot boots and logs ONEWARNline 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 armsalpha;POST /marketplace/install-localof a third package declaring the held flow name is422 PLUGIN_REGISTER_FAILEDnaming both holders andgamma_…, 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-inauth.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 holdauth.password_resetand the install would be refused naming it (ablation below). Onc05d912a: this file and the data and request families' dogfood files plusemail-template-materialization.dogfood.test.ts→ 4 files / 16 passed.Ablations on
c05d912a, each throughablation-replace.mjsor a trapped restore, restored with blob == HEAD andgit diff HEADempty:declaredBareNamesreads the baseline bymanifest.idagainexpected [ { type: 'email_template', … } ] to deeply equal []OVERRIDABLE_BASELINE_PACKAGES.email_templatemisspelt (marker in 4 built files), objectql rebuiltPLUGIN_REGISTER_FAILED … declares the email template "auth.password_reset"namingcom.objectstack.service.email; restored, rebuilt, marker absent from all 14 built filesbare-name-one-holder.ts,registry.ts,plugin.tsat the merge baseb3bd75d8, rebuilt (head marker absent from all 14 built files)gammaanddeltainstalled), the CONTROL green — the CONTROL no longer imports anything from objectql, so on the base it is a control; rebuilt from HEADe01df46c, code unmoved)BARE_NAME_ONE_HOLDER_TYPES/ the baseline exclusion removed / the bundle member not strippedc05d912aleave those hunks byte-identicalpnpm --filter @objectstack/objectql typecheckexit 0 (incl.check:test-typecheck: 39 files / 233 errors / 64 pinned signatures held, unchanged);pnpm --filter @objectstack/dogfood exec tsc --noEmitexit 0; eslint (--no-inline-config) overpackages/objectql/srcand the dogfood file exit 0.node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commandsatc05d912a(merge baseb3bd75d8, 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-loadsexit 3 PREREQUISITE NOT MET (@objectstack/scenarioshad nodist/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-debtexit 0 on the first run.Full dogfood battery (
pnpm --filter @objectstack/dogfood test, underos-verify-lock) atc05d912a: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!andClause-②carries the(narrowing)arm.Not re-run on the merged head (round 1 at
e01df46c,VERDICT command-exit 0both): the full@objectstack/objectqlsuite (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 aggregateTest Core, one cell:@objectstack/scenariostest/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 feat(scenarios): run-scenarios --driver codex and --driver external:COMMAND, with the driver on each gallery app page #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
objectName:name), jobs keyedappId: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
flow("one armed, warning logged") holds, and the second consumer (/meta/flow/NAME) adds a silent split in one order.metadataservice'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 /connectorsanswered404on the showcase composition during the measurement (the route is not mounted there); the connector reading used/meta/connector/NAMEand the registry list instead. Observation only.OVERRIDABLE_BASELINE_PACKAGESstays offpackages/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 (declaredBareNamesread the baseline bymanifest.idwhile the door judgesartifactPackageId) 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
agent,skill, the card's closing line) follows this one; ADR-0048 P1.5 names it.Human Minimum
packages/specholds no runtime logic. (not touched)pnpm lint— see Verification.main, one pull request (third of a series by type family, per the seat).packages/spectouched: n/a.content/docs/references/,packages/*/CHANGELOG.mdorcontent/docs/releases/.🤖 Generated with Claude Code
https://claude.ai/code/session_01FEkQ5pr7Ua2kPRCMZNupZg