Skip to content

feat(objectql)!: an agent and a skill hold one name per package (bare-name phase 1, 4/4) - #31375

Merged
os-zhuang merged 3 commits into
mainfrom
claude/project-thread-373joq
Nov 8, 2026
Merged

os-zhuang merged 3 commits into
mainfrom
claude/project-thread-373joq

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Leehom · project thread

Fixes #30803

PR 4 of 4, the last of phase 1's series by type family: PR 1 #30836 (the data family), PR 2 #31006 (the request family), PR 3 #31093 (the flow family), this one the AI family (agent, skill), per ruling 6688041914. Claim 6716532253 (domain:server#5, card-thread session_01FEkQ5pr7Ua2kPRCMZNupZg).

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 agent or skill bears a name another installed package holds. Nothing is widened.
Metadata-protocol: narrowed
Skills-docs: none — skills/** does not teach that two packages may share an agent or skill 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.9).

⚠️ Tier H: this diff touches docs/adr/0048-cross-package-metadata-collision.md (header, §3.4 note, the addendum's status line, P1.5 and the new P1.9). The seat routes the approver.

维护者速读

What changes

  • packages/objectql/src/bare-name-one-holder.ts — the covered types grow from ten to twelve: agent and skill join BARE_NAME_ONE_HOLDER_TYPES last, as the AI family (BARE_NAME_REGISTRY_TYPES stays derived); their collection keys (agents, skills), consumer clauses and nouns, empty built-in and overridable-baseline sets. Module doc: the two measurements, the family closing the series.
  • packages/objectql/src/registry.ts, packages/objectql/src/plugin.ts — comments name the twelve types; no code change. Both doors and the cold-boot seal are generic over the list.
  • packages/objectql/src/registry-bare-name-one-holder-flow-family.test.ts — its type-list cell now pins its own family's slot (slice(0, 10)); the whole list is pinned by the AI family's file.
  • docs/adr/0048-cross-package-metadata-collision.md (Tier H) — header Addenda entry and §3.4's 2026-11-06 note name the twelve types and drop "to follow"; the addendum's status line names P1.9 as the last change; P1.5 becomes the series' record ("The series, complete"); new P1.9 — the measurement, what is refused, what is not in the list (tool, rag_pipeline), the cold boot, the series' close.
  • scripts/adr-anchors/packages__objectql__src__bare-name-one-holder.ts.json — the invariant names the fourth family and its served holder.
  • .changeset/30803-bare-name-ai-family.md — @objectstack/objectql minor, both declaration lines, BREAKING Why / Before / Now / 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 30e0bd16; then POST /marketplace/install-local of a third package (gamma) declaring all held names (a temporary dogfood file, deleted):

Type Consumer Served (alpha→beta / beta→alpha) Warned Third package after the boot
agent GET /meta/agent/NAME alpha / beta — first-registered no 200, shadowed
agent kernel metadata service (get('agent', NAME), the MCP agent_prompt read) nothing in either order: the service lists no package agent on the composed runtime (ADR-0063 §2) — —
skill MCP prompts/get (listSkillPrompts over the merged GET /meta/skill list) alpha / beta — first-registered; the other row dropped no 200, shadowed
skill GET /meta/skill/NAME alpha / beta — first-registered no —
tool (not in the ruling) GET /meta/tool/NAME alpha / beta — first-registered no 200, shadowed
rag_pipeline (not in the ruling) — not declarable: defineStack refuses the ragPipelines key; nothing registered — —

The agent runtime that resolves an agent or a tool by name for chat lives in objectstack-ai/cloud and is NOT MEASURED here. Both ruled types resolve without package context, so both join the list.

Verification

At head 15ed46b6 (feat 55c70231 cut on 30e0bd16, then origin/main merged once at e7354255: six commits, none in this PR's files, no conflict). The unit and dogfood pins, the gates and the battery ran on 15ed46b6; the ablation, the typechecks and the full objectql suite ran on 55c70231:

  • packages/objectql/src/registry-bare-name-one-holder-ai-family.test.ts (new, 26 cells, real ObjectKernel + ObjectQLPlugin): the type list (all twelve, in family order) and the two labels; declaredBareNames over agents and skills and a nested plugin, a tool declaring nothing; for each of the two 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); CONTROLs — two packages' tools under one name both install after the seal, one package's agent and another's skill under one name never meet; the reason names only the types it carries; a built-in skill name is refused at the package door. With the data, request and flow families' pins and hook-binder.test.ts: 5 files / 180 passed.
  • packages/qa/dogfood/test/bare-name-one-holder-ai-family.dogfood.test.ts (new, booted showcase + two AppPlugin packages + MarketplaceInstallLocalPlugin): the cold boot boots and logs ONE WARN line naming the agent and the skill, both packages and the renames, never the shared tool; /meta/agent/NAME, /meta/skill/NAME and the MCP skill prompt keep serving alpha; POST /marketplace/install-local of gamma declaring the held agent name and of delta declaring the held skill name are each 422 PLUGIN_REGISTER_FAILED naming both holders and the rename, the package list unchanged; CONTROL: epsilon with fresh agent and skill names plus the shared tool name installs, and its agent and skill prompt serve. With the three earlier families' dogfood files: 4 files / 16 passed.
  • Ablation (committed first, through ablation-replace.mjs, restored with blob == HEAD and git diff HEAD empty): agent and skill filtered out of BARE_NAME_ONE_HOLDER_TYPES (marker ABL4) → the AI family's unit file 18 of 26 red (the list and declaredBareNames cells, every refusal at both doors, each ABLATION cell's after-seal half, the cold-boot warn / reload / refuse cells, the reason and built-in cells; the 8 green are, per type, the same-package reload, the uninstall release and the environment CONTROL, plus the tool and agent-versus-skill CONTROLs); objectql rebuilt with the mutation (ablation-dist-preflight: marker in 4 built files; the DTS step failed on the mutated literal's type, TS2322, after the JS bundles were written) → the dogfood pin 3 of 4 red (no warning line; gamma and delta installed), the CONTROL green. Restored, rebuilt (marker absent from all 14 built files, tree clean) → 4 of 4 green.
  • 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, no findings.
  • @objectstack/objectql full suite under os-verify-lock: 568 files / 11516 tests passed (VERDICT command-exit 0).
  • node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands at 15ed46b6 (merge base e7354255, 9 paths): 90 derived families, every one run with its exit code captured before any pipe, all 90 exit 0 on the first run (the check:dual-build-cjs-loads prerequisites were already built on this worktree); --ran: "90 derived famil(ies) accounted for — 90 run, 0 NOT-MEASURED" (exit 0). The gates first ran on 55c70231; main moved during that run, so it was stopped, main merged and the whole derived list re-run on 15ed46b6 (the derivation was the same 90).
  • Full dogfood battery (pnpm --filter @objectstack/dogfood test, under os-verify-lock) at 15ed46b6: VERDICT command-exit 0 · Test Files 438 passed | 3 skipped (441) · Tests 3622 passed | 41 skipped (3663) · no failures (held the lock 1801s on a shared box). Ran because the title carries ! and Clause-② carries the (narrowing) arm.

Kept

  • Same-package re-registration, the environment catalog (ADR-0005 overlay), every data-, request- and flow-family pin green unchanged; tool and rag_pipeline keep §3.4 as written; cold boot stays ruling B (BARE_NAME_COLD_BOOT_MODE = 'warn', untouched).

Acceptance notes

  • agent: the open framework's one by-name read of a package agent is /meta/agent/NAME. The kernel metadata service (what MCP agent_prompt reads) lists no package agent at all on the composed runtime; agents are platform-owned (ADR-0063 §2), and the cloud runtime that resolves one by name for chat was not measured. The ruling kept agent in this card and the /meta door is a by-name read without package context, so it joins.
  • tool: same first-registered answer at /meta/tool/NAME; not in the ruling's nine, so not added (reported to the seat).
  • rag_pipeline: defineStack refuses ragPipelines as an unrecognised stack key, though the engine's collection loop and the manifest spelling table still list it; nothing measured beyond that (reported to the seat).

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; lint — see Verification.
  • 4. One issue, one branch off an up-to-date main, one pull request (the fourth of a series by type family, per the ruling).
  • 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 with Claude Code

https://claude.ai/code/session_01FEkQ5pr7Ua2kPRCMZNupZg

…-name phase 1, 4/4)

Measured first on a booted showcase with two packages sharing one agent
and one skill name, both orders: /meta/agent/NAME serves the
first-registered package's agent, an MCP client's prompts/get of a skill
resolves the first-registered package's skill (listSkillPrompts keeps
the first row per name), silently; a third package installed after the
boot answers 200 and is shadowed.

agent and skill join BARE_NAME_ONE_HOLDER_TYPES as the AI family, the
last: after the cold boot's seal the package door and the item seam
refuse a second holder with the first family's envelope; the cold boot
warns. A tool is not covered. ADR-0048 records the outcome as P1.9 and
closes the series; the anchor invariant names the fourth family.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FEkQ5pr7Ua2kPRCMZNupZg
@github-actions github-actions Bot added size/l 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 8, 2026
@github-actions

github-actions Bot commented Nov 8, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/objectql, touching 4 documentable anchor(s). ⚠️ 1 changed file(s) yielded no anchor (packages/objectql/src/plugin.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

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

  • content/docs/automation/email-templates.mdx (via email_template (literal, a string literal in BareNameRegistryType))
  • content/docs/concepts/metadata-lifecycle.mdx (via email_template (literal, a string literal in BareNameRegistryType))
  • content/docs/deployment/environment-variables.mdx (via email_template (literal, a string literal in BareNameRegistryType))
  • content/docs/deployment/validating-metadata.mdx (via email_template (literal, a string literal in BareNameRegistryType))

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

  • content/docs/releases/v18/18-2.mdx (via email_template (literal, a string literal in BareNameRegistryType))
  • content/docs/releases/v18/18-3.mdx (via email_template (literal, a string literal in BareNameRegistryType))

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

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/objectql/src/plugin.ts) — pages documenting those are invisible to this run
  • 2 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 64 of 227 client-bound route-ledger rows — the other 163 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 163: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 59 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 7189fb2a2f765b84c26c47e63f13adbb81f4d3da → packageMentionDocs.

Which tree this was computed on

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

node scripts/docs-audit/affected-docs.mjs --json 7189fb2a2f765b84c26c47e63f13adbb81f4d3da

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

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#5 · os-project-manager · session_01FEkQ5o8PE7ZtVjcor51wBZ

Contract-review-claim: session_01FEkQ5o8PE7ZtVjcor51wBZ · head 15ed46b · 2026-11-08T04:57Z

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#5 · os-project-manager · session_01FEkQ5o8PE7ZtVjcor51wBZ

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 15ed46b6c2bd7d72e3747cf65ea5210eff96c9f1
Local-runs: none
Scope: docs/adr/0048-cross-package-metadata-collision.md + packages/objectql/src/bare-name-one-holder.ts, packages/objectql/src/registry.ts, packages/objectql/src/plugin.ts, packages/objectql/src/registry-bare-name-one-holder-ai-family.test.ts, packages/objectql/src/registry-bare-name-one-holder-flow-family.test.ts, packages/qa/dogfood/test/bare-name-one-holder-ai-family.dogfood.test.ts, scripts/adr-anchors/packages__objectql__src__bare-name-one-holder.ts.json (the changeset .changeset/30803-bare-name-ai-family.md is a prose face, read for ② only)

Two faces at once: the rules face (docs/adr/0048-*.md is the reviewFace hit) and the narrowing face (Clause-②: no (narrowing) on the PR body and the changeset). Inputs: card #30803 (body, every comment, the rulings 6687840782 and 6688041914, the claim 6716532253, the os-dev-report 6717468349), PR #31375 (body, 9 files, commits 55c70231 + merge 15ed46b6, net diff against merge base e7354255), PRs #30836 / #31006 / #31093 (merged), the check-runs on 15ed46b6. Cited code read, never reviewed: registry.ts doors and seal, engine-top-level.ts collection loop, spec/.../manifest-collection-spelling.ts, metadata-plugin.zod.ts, mcp/src/skill-prompts.ts, mcp/src/mcp-server-runtime.ts (agent_prompt, skill bridge), runtime/src/domains/mcp.ts and domains/ai.ts, rest /meta/:type/:name through SchemaRegistry.getItem, cloud-connection/src/marketplace-install-local-plugin.ts, core/src/artifact-packages.ts, ADR-0063 §2, hotcrm at 6961ab1 (read-only grep, no run).

Check-runs on 15ed46b6 at review time (the gate verdicts): completed success — Lint & Repo Gates (every check:* gate), Build Core, Governed Surface Queue Guard, Dogfood Regression Gate, Check Changeset, Lint · ESLint, Lint · docs & code guards, Lint · CI/release & engine gates, Lint · repo & tooling gates, Lint · PM dispatch-gates, Lint · scoped (all three), Type Check · source gates / consumer gates / debt ledger, Test UI, Live E2E UI, Spec property liveness, PR-body guards, Check PR Size, Check Documentation Links, Plan Test Core shards. Still in progress — Test Core 1–8 and Type Check · workspace (the required TypeScript Type Check and Test Core contexts). No failure. Two of the eleven required contexts are not complete, so this record is a contract verdict, not a green claim: the seat reads them to completion before landing.

① Derived judgments

  • The accept set: BARE_NAME_ONE_HOLDER_TYPES grows 10 → 12 (agent, skill appended as the AI family), so the derived BARE_NAME_REGISTRY_TYPES grows 9 → 11 and isBareNameOneHolderType / isBareNameRegistryType answer true for both. Every door is generic over the list, so with no code change in registry.ts or plugin.ts (comments only, as the PR says — verified on the diff) three accept sets narrow after the seal: the package door (refuseBareNameConflicts over declaredBareNames, which now reads agents and skills on the manifest and each nested plugins[] entry one level deep), the item seam (registerItem with a packageId, type agent or skill), and the cold boot's seal (sealBareNameHolders iterates the list). Right — and reachable: the engine's collection loop registers agents, tools, skills (METADATA_ARRAY_KEYS, engine-top-level.ts:4078) through registerItem under the package's composite PACKAGE:NAME slot, and the spec's PLURAL_TO_SINGULAR folds agents → agent, skills → skill, so COLLECTION_KEY (agents, skills) names the keys the seams actually read.
  • Not bundled: ITEM_KEY_DISCRIMINATORS holds only email_template, so bundleBaseKey is the identity for both types and the holder reading needs no member stripping. Right, and P1.9's "neither is bundled" is true.
  • Empty built-in and baseline sets for both: grep over packages/** and examples/** finds no platform or example package declaring agents: or skills: (packages/skills is the SKILL.md catalog, not skill metadata; the open runtime answers GET /ai/agents with { agents: [] }). Right for this repository. The Record type over BareNameOneHolderType makes both additions mandatory — the ablation's TS2322 confirms the type pins the list.
  • Refusal prose (CONSUMER_CLAUSE, TYPE_NOUN, default bareNameTypeLabel): carries no tracker number, names the two consumers the measurement found. Right.
  • Public surface of @objectstack/objectql: nothing new on the index. packages/objectql/src/index.ts re-exports nothing from bare-name-one-holder.ts (the declaredSecurityCatalogNames export at line ~99 is the security catalog's, not this module's); the widened BareNameRegistryType union is module-internal. The published change is behavioural only: the doors' accept set. Right, and the minor level is for that.
  • Why the flow-family file moved: its list cell pinned the WHOLE list with toEqual, which went red the moment a family appended; it now pins its own prefix (slice(0, 10)) and the AI family's file pins all twelve in order with toEqual. A pin that moves when the list grows is exactly what a whole-list pin in a non-last family is; the prefix pin still fails on a reorder or an insertion before the flow family, and the cell's second assertion (BARE_NAME_REGISTRY_TYPES = whole list minus function) still runs on the whole list. Right; no pin was weakened.
  • Dogfood door: MarketplaceInstallLocalPlugin inline branch maps any manifest.register throw to 422 PLUGIN_REGISTER_FAILED with the registry's sentence, and the registry refused ahead of every mutation, so "package list unchanged" is the right pin. Right (see ④ for the other branch).
  • Anchor JSON: adrs unchanged, invariant restated with the fourth family and its served holder; check-adr-anchors needs a non-empty adrs and invariant and resolves the letter citations — Lint · docs & code guards is green on this head. Right.
  • Merge commit: git diff 55c70231 15ed46b6 over the PR's nine paths is empty, so the readings the report took on 55c70231 (full objectql suite, typechecks, ablation) describe this head's files; CI on 15ed46b6 covers the rest.

② Semver level

  • .changeset/30803-bare-name-ai-family.md: '@objectstack/objectql': minor, title feat(objectql)!: — the same level and banner as PR 1–3's changesets (30803-bare-name-one-package-holder.md, 30803-bare-name-request-family.md, 30803-bare-name-flow-family.md, all still pending in .changeset/). Only @objectstack/objectql publishes from this diff (@objectstack/dogfood is private: true; scripts/, docs/ and the test files publish nothing). AGENTS.md's rule holds: a narrowed kind takes at least minor; the (narrowing) arm is BREAKING and the body carries the BREAKING banner, Why / Before / Now / The cold boot / Who moves / Unchanged, and exactly one ADR-0087 marker (not-required (no-migration-prescription), PR 3's wording — right: no key, export or field is removed, no stored body changes shape; a refused name is renamed by its author). Matches what the diff publishes.
  • Clause-②: no (narrowing) — right: both doors refuse, after the seal, a registration they accepted on main (a second package's same-named agent or skill); no input is newly accepted anywhere in the diff.
  • Metadata-protocol: narrowed — right: a package author's declarable set shrinks (a name another installed package holds is refused at install); the same declaration both PR 2 and PR 3 made for their families.
  • Skills-docs: none — right: skills/** is untouched, and a grep of skills/** and content/docs/** finds no text teaching that two packages may ship a same-named skill or agent, nor any "unique within the package" guidance for either type. The spec's own words are "Skill unique identifier" / "Agent unique identifier" (skill.zod.ts:298, agent.zod.ts:332). The prescription lives in the refusal text and P1.9, as the series has done.
  • Changeset "Measured producers": "HotCRM is not checked out in this container and is NOT MEASURED." It is checked out beside this repo: at hotcrm 6961ab1 there are six *.skill.ts (customer_360, email_drafting, lead_qualification, live_data, revenue_forecasting under app.objectstack.hotcrm; case_triage under app.objectstack.hotcrm.service) and no agent; all six names are distinct across its packages, so HotCRM holds no two-package clash and boots unwarned under this PR. The changeset's NOT MEASURED is honest, not wrong — nit: the seat may add this reading to the changeset or the PR when it next edits either; nothing blocks on it.

③ Boundary flags

Every deviation in report 6717468349, answered:

  1. Effort below the order's xhigh floor — recorded on claim 6716532253; the seat's, not the tree's.
  2. Anchor JSON outside the claim's file surface — the order names the anchor update; the invariant edit is one line and correct (①). Accepted.
  3. Flow-family list pin → slice(0, 10) — judged in ①: right.
  4. Ablation DTS leg failed TS2322 after the JS bundles were written — the expected consequence of filtering a literal out of a Record-pinned list; the marker was in 4 built files so the dogfood reading (3 of 4 red, CONTROL green) is of the mutated build. Accepted as a valid ablation.
  5. Gates re-run on 15ed46b6, full suite / typecheck / ablation left on 55c70231 — the merge touched none of the nine paths (①); CI on the head carries the typecheck and test families. Accepted.
  6. No labels on pr_create — the auto-labeller has since set documentation, size/l, tests, tooling. Nothing owed.

Out-of-scope findings in the report, answered or escalated:

  • tool serves first-registered at /meta/tool/NAME, no warning, a third package installs 200 and is shadowed. The ruling 6688041914 lists nine types and tool is not one; not adding it here is the right scope discipline, and the CONTROL pins (two packages' tools under one name both install after the seal) make the boundary explicit. But the measurement is a reproducible silent mis-resolution of the same class as the nine — Prime Directive 10 files such a thing, it does not leave it "noted". Escalated to the seat: file it as a card or put it on the maintainer's desk with phase 3 (Bare-name phase 3: defineStack and the save door refuse a bare package-authored name; ADR-0129 and ADR-0048 amended; migration of stored and external references (ruling C on #30798) #30806); the ruling's list is the maintainer's to extend. Not this PR's.
  • ragPipelines is not a defineStack key (StackSchema refuses it) while the spec's PLURAL_TO_SINGULAR (manifest-collection-spelling.ts:93) and the engine's METADATA_ARRAY_KEYS (engine-top-level.ts:4078) both list it — a collection declared in two registries that no author can reach. That is "declared ≠ enforced" (Prime Directive 10). Escalated to the seat: a card against packages/spec / packages/objectql, outside the ruling and this PR. P1.9's statement of it is accurate.
  • The kernel metadata service (get('agent', NAME), the MCP agent_prompt read) lists no package agent on the composed runtime while /meta/agent/NAME serves one — two by-name readers of one type disagree. Under ADR-0063 §2 agents are platform-owned and *.agent.ts is closed to third parties, so the metadata service's answer is the ADR's and the open /meta door is the generous one; the StackSchema.agents key (described "platform-internal") still registers a package's agents. Pre-existing, outside this PR; the narrowing is consistent with the ADR's direction either way. Escalated to the seat as a note for Bare-name phase 3: defineStack and the save door refuse a bare package-authored name; ADR-0129 and ADR-0048 amended; migration of stored and external references (ruling C on #30798) #30806 / the AI lane, not a blocker.
  • The cloud agent runtime, deployed and marketplace packages: NOT MEASURED — stands. HotCRM, which the changeset also lists as unmeasured, is measured above (②): no clash.

Tier H. docs/adr/0048-cross-package-metadata-collision.md is a Tier H register row. GOVERNED_APPROVERS is os-zhuang, hotlong; the PR carries zero reviews at review time and is a draft. This record is a contract review, not that approval: an authorized APPROVED review from one of the two must exist, undismissed, before the owning seat lands it through the queue (the maintainer's merge or enqueue is the other landing). Governed Surface Queue Guard is green, which is the guard's own reading at PR time, not a substitute for the approval.

④ Design judgment

The ADR-0048 changes, against their first consumers and the rulings.

  • Header Addenda line — adds "agents and skills — phase 1 complete"; the anchor link is unchanged and still resolves to the addendum heading. Holds.
  • §3.4 note (2026-11-06) — adds /meta/agent/NAME and "an MCP client's skill prompt" to the consumer list, "agent or skill" to the refused list, drops the "to follow" sentence and adds "a tool among them" to what keeps §3.4. Each clause holds against the code read: /meta/:type/:name ends in SchemaRegistry.getItem(type, name) with no package, which answers the bare slot, then the first composite slot in insertion order (registry.ts:8123–8150) — the first-registered package; listSkillPrompts keeps the first row per name and drops the rest (skill-prompts.ts), over the merged skill listing both the HTTP (runtime/domains/mcp.ts, readMergedSkillRows) and stdio (mcp-server-runtime.ts, mergedDiagnosedList) bridges read. Should-fix (prose, nit-grade on the contract): "Every other type, a tool among them, an action bound to an object among them, keeps §3.4 as written" stacks two "among them" clauses in one sentence; one of them wants to go ("Every other type — a tool, an action bound to an object — keeps §3.4 as written"). No effect on the rule; fix in the next ADR touch or now, the seat's call.
  • Status line — "P1.2, P1.7, P1.8, P1.9 — the last". Holds.
  • P1.5 "The series, complete" — records the four families and their sections; "the data family (P1.1)" points at the measurement section rather than the refusal (P1.2) — harmless, the heading says "complete" and the body names P1.9 as the close. Holds against the ruling 6688041914 (PR order; all nine kept in Bare-name phase 1: refuse a second package holding a non-object name whose consumers resolve without package context (ruling C on #30798) #30803).
  • P1.9 — the measurement (both orders, a third package after the boot), what is refused ("the same two doors, the same holders, the same envelope and rename"; "neither is bundled", no baseline or built-in; agent and skill never meet), what is not in the list (tool with its measurement; rag_pipeline undeclarable), the cold boot under P1.3's 'warn', and the close. Every claim matches bare-name-one-holder.ts after the diff, the registry seams, and the AI-family pins; the first consumer (the list and the anchor invariant) agrees with the text. P1.6 "Where it is implemented" stays true unchanged. Holds.
  • Cold boot B — BARE_NAME_COLD_BOOT_MODE untouched at 'warn'; the seal still fires at kernel:bootstrapped after the install-local rehydrate at kernel:ready (plugin.ts:1229). The unit cells pin one warning line naming both holders and the rename and the "changes nothing about which holder each caller is served" sentence; the dogfood pins the WARN level on a booted showcase. Ruling 6687840782 honoured.

The narrowing itself, against the published spec on main. It makes the spec's own refusal real; it takes no declared capability away. On main ADR-0048 P1.5 already names agent and skill as "to follow in the series' last change", the ruling kept both in the card, and the spec calls each name a "unique identifier"; the two consumers that exist in the open framework are name-keyed with no package. Two packages shipping one agent or skill name was never a declared capability — it was a silent mis-serve of one of them. Break attempts:

  • Both orders: holders are a set over the item store's composite slots plus install claims, read per (type, name); the refusal lists every holder (the cold-boot test pins alpha then beta as existingHolder rows). Order does not matter to the refusal. Holds.
  • A third package after the boot: refused at the package door naming both, nothing recorded (unit); install-local inline → 422 PLUGIN_REGISTER_FAILED, package list unchanged, readers untouched (dogfood). Holds.
  • The package door vs the item seam: a nested plugin's collections and a direct package-bound registerItem both hit the seam; pinned. The environment slot (no packageId) is never judged — and for these two types the protocol door already closes an environment overlay of a package-shipped item (allowEnvironmentOverlay: false for agent, skill, tool in DEFAULT_METADATA_TYPE_REGISTRY), so the registry-level CONTROL is correct and, at the protocol door, moot. Holds.
  • Cold-boot warn vs post-seal refuse: warn at boot, refuse after; 'refuse' refuses the boot with the doors' envelope; a reload keeps both holders. Pinned. Holds.
  • Built-in or baseline items shipped by the platform: none in this repository (①). In cloud, service-ai resolves the two platform agents (ask, build, ADR-0063 §2); if it registers them as agent items under its own package id, an app shipping an agent named ask is refused after the seal — which is what ADR-0063 wants (agents are closed to third parties), not a wrong refusal; at a cold boot it would warn, as today's /meta/agent/ask already serves the first-registered. NOT MEASURED (cloud), noted, not a finding against this tree.
  • An agent and a skill sharing one name: separate type maps, separate claim maps; CONTROL pin. Holds.
  • Install-local — the cloud-fetched branch. Should-fix, out of this diff, series-wide, carrier: the seat as a card. marketplace-install-local-plugin.ts has two branches around manifest.register: the inline-manifest branch refuses every throw as 422 PLUGIN_REGISTER_FAILED (what the dogfood pins, for all four families); the { packageId, versionId } branch (manifest fetched from the marketplace) stays lenient by design (install-local: a cloud install whose hot-register fails answers 200 with hotLoaded: true (the lenient path), so a console says the app "should now appear" over a kernel that does not have it #22695) and refuses only a NAMESPACE_CONFLICT whose conflicts[] names an environment holder (refusesEnvironmentHeldCatalogName, fix(cloud-connection): a cloud-fetched marketplace install of a package shipping a catalog name an environment-authored item holds writes its ledger entry, and the next cold boot refuses; refuse it before the first write #25366) — any other throw, a BareNameConflictError naming a package holder included, is logged as "hot-register failed (will load on next restart)", the ledger entry is written, the answer is a success with hotLoaded: false, and at the next boot the package rehydrates BEFORE the seal, where the clash becomes a cold-boot WARN and both packages hold the name. On that branch "refused at managed install" is not real for any of the eleven registry types — this PR's two included — and the "will load on next restart" sentence is literally true and semantically the bypass. This is not this PR's hunk: the branch predates the series, PR 1–3 shipped the same door and the same claim, and the fix is one predicate in cloud-connection (treat a NAMESPACE_CONFLICT with conflicts[] of any holder kind as the pre-write refusal it is — a restart heals a package-held clash no more than an environment-held one; it only downgrades it to a warning), not a change to the list. The PR's own hunks are correct and the inline door, the item seam and the cold boot are all real, so this does not fail the tree; it does need a card before anyone reads P1.2/P1.9's "managed install" as covering both branches. Prime Directive 10 names the remedy (file it; keep the claim as narrow as the enforcement).
  • Spec-level naming — a SkillSchema/AgentSchema name regex of ^[a-z_][a-z0-9_]*$ admits an unprefixed name; the namespace rule is phase 2/3 (Bare-name phase 2: the published authoring skills teach one rule, every metadata name carries the namespace (ruling C on #30798) #30805, Bare-name phase 3: defineStack and the save door refuse a bare package-authored name; ADR-0129 and ADR-0048 amended; migration of stored and external references (ruling C on #30798) #30806). Out of scope here by the ruling's phasing; the refusal's rename (NAMESPACE_NAME) points at it.

Series convention: a fourth per-family test file and dogfood file rather than a describe in the data family's harness file — AGENTS.md (ruling record 6583487846) prefers the shared harness, but PR 2 and PR 3 established one file per family under contract review; nit, consistent with the series.

Findings, graded. Must-fix: none. Should-fix: (a) the cloud-fetched install-local branch swallows a package-held BareNameConflictError into the lenient restart path — card against marketplace-install-local-plugin.ts, series-wide, not this PR's hunk; (b) the §3.4 note's doubled "among them" clause — prose. Nits: HotCRM reading for the changeset's producers line; the per-family test file against the shared-harness rule. Out-of-scope, escalated to the seat: tool (same defect class, outside the ruling — maintainer's list), ragPipelines (declared in two tables, reachable from neither), the agent_prompt / /meta/agent reader disagreement.

Implemented-by: claude/project-thread-373joq
Reviewed-by: session_01FEkQ5o8PE7ZtVjcor51wBZ

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#5 · os-project-manager · session_01FEkQ5o8PE7ZtVjcor51wBZ

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: a9d8e39701956668d5480cd984e9eb8de78d5093
Local-runs: none
Scope: docs/adr/0048-cross-package-metadata-collision.md + packages/objectql/src/bare-name-one-holder.ts, packages/objectql/src/registry.ts, packages/objectql/src/plugin.ts, packages/objectql/src/registry-bare-name-one-holder-ai-family.test.ts, packages/objectql/src/registry-bare-name-one-holder-flow-family.test.ts, packages/qa/dogfood/test/bare-name-one-holder-ai-family.dogfood.test.ts, scripts/adr-anchors/packages__objectql__src__bare-name-one-holder.ts.json (the changeset .changeset/30803-bare-name-ai-family.md is a prose face, read for ② only)

A delta on record 6717571232 (the PASS on 15ed46b6c2bd7d72e3747cf65ea5210eff96c9f1). The author pushed one commit on top, a9d8e397 ("docs(adr-0048): untangle the doubled clause in the §3.4 bare-name note"). What moved, read off the tree, not the push's account of it: git diff 15ed46b6 a9d8e397 is ONE file, docs/adr/0048-cross-package-metadata-collision.md, +2/−2, inside the 2026-11-06 §3.4 note; every other path between the two heads is byte-identical (the diff outside the ADR is empty). The net diff against main is still the same nine files at merge base e7354255 (origin/main has moved to 7189fb2a, the base has not), so ①, ② and ③ of record 6717571232 stand unchanged on this head and are restated below only where the delta touches them. Same two faces (rules: the ADR is the reviewFace hit; narrowing: Clause-②: no (narrowing) on the PR body and changeset), same inputs, plus card #31296.

Check-runs on a9d8e397 at review time: completed success — Governed Surface Queue Guard, Check Documentation Links, Check PR Size, PR-body guards, Flag docs affected, Spec property liveness, Auto Label; in progress — Check Changeset, every Lint · leg (CI/release & engine, ESLint, PM dispatch-gates, docs & code guards, repo & tooling, the three scoped), every Type Check · leg (workspace, source, consumer, debt ledger); queued — Build Core, Dogfood Regression Gate, Plan Test Core shards, Test UI, Live E2E UI; Test Core and Lint & Repo Gates not yet reported. No failure. Most of the eleven required contexts are not complete, so this record is a contract verdict, not a green claim; on 15ed46b6, whose code and tests this head carries byte-identical, the same families were green or still running green (record 6717571232). The seat reads this head's runs to completion before landing.

① Derived judgments

Nothing moved. The delta adds no hunk outside the ADR's prose: BARE_NAME_ONE_HOLDER_TYPES is still the twelve in family order, the derived BARE_NAME_REGISTRY_TYPES the eleven, COLLECTION_KEY / BUILT_IN_BARE_NAMES / OVERRIDABLE_BASELINE_PACKAGES / CONSUMER_CLAUSE / TYPE_NOUN unchanged from the reviewed head; registry.ts and plugin.ts are still comment-only edits; the flow-family prefix pin (slice(0, 10)) and the AI family's whole-list pin are unchanged; the anchor invariant is unchanged; nothing new on @objectstack/objectql's index. The accept-set judgments of record 6717571232 carry over as right.

② Semver level

Nothing moved. The changeset is byte-identical to the reviewed head: '@objectstack/objectql': minor, feat(objectql)!: banner, Clause-②: no (narrowing), Metadata-protocol: narrowed, Skills-docs: none, the one ADR-0087 marker. The delta publishes nothing (an ADR edit). The delta's wording does not teach a second same-named skill or agent anywhere — it names what KEEPS §3.4 (a tool, an object-bound action), which is what the CONTROL pins hold. Matches what the diff publishes; the HotCRM nit from the prior record (six distinct skill names across two packages at hotcrm 6961ab1, no agent, no clash) stands as a nit, nothing owed.

③ Boundary flags

④ Design judgment

The delta against the rule it states. The old sentence stacked two "among them" clauses; the new one — "Every other type keeps §3.4 as written, among them a tool and an action bound to an object" — is the same claim, read once. Against the code it cites on this head: tool is absent from BARE_NAME_ONE_HOLDER_TYPES and declaredBareNames reads no tools collection, so a tool neither holds nor is refused a name (CONTROL pin, unit and dogfood); an action bound to an object is excluded at holdsBareName by the engine's own owner-key ladder (isObjectLessActionKey(standaloneActionOwnerKey(item))), unchanged since PR 2. The sentence holds against its first consumer, and the series' close P1.5 / P1.9, the header Addenda line and the status line are byte-identical to the reviewed head, so ④'s judgments of record 6717571232 carry over: the ADR changes hold against their first consumers and the rulings (cold boot B, all nine kept, PR order), and the narrowing makes the spec's own refusal real rather than taking a declared capability away.

Break attempts, re-run on the delta. None of the scenarios can move on a two-line ADR prose edit with no code hunk: both orders, a third package after the boot, the package door and the item seam, cold-boot warn vs post-seal refuse, built-in or baseline items (none in this repository), an agent and a skill under one name — the pins are the same files at the same blobs as on 15ed46b6. The one path that was not real — the cloud-fetched install-local branch — is unchanged and carried by #31296.

Findings, graded. Must-fix: none. Should-fix: none newly owed on this head — (b) closed by the delta; (a) carried by card #31296. Nits (standing, nothing owed): the HotCRM reading for the changeset's producers line; a fourth per-family test file against AGENTS.md's shared-harness preference (series precedent, PR 2/3). Out-of-scope (escalated to the seat, unchanged): tool, ragPipelines, the agent_prompt / /meta/agent reader disagreement.

Implemented-by: claude/project-thread-373joq
Reviewed-by: session_01FEkQ5o8PE7ZtVjcor51wBZ

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#5 · os-project-manager · session_01FEkQ5o8PE7ZtVjcor51wBZ

维护者速读

  • 改了什么:同名「一个包只能持有一个」规则(Bare-name phase 1: refuse a second package holding a non-object name whose consumers resolve without package context (ruling C on #30798) #30803 第 4/4 步,收尾)扩到 AI 智能体(agent)和技能(skill)。启动封印后,第二个包再注册已被持有的这两类名字会被拒,返回 422 NAMESPACE_CONFLICT,点名两个持有者并给出改名;冷启动遇到已有冲突只告警(裁决 B)。ADR-0048 的 P1.5 改成实测结论,新增 P1.9。
  • 为什么改:实测这两类按名字解析、不带包上下文(/meta/agent、/meta/skill、MCP 技能提示都静默取先注册者),第二个同名包会被遮蔽。
  • 风险与回滚:@objectstack/objectql minor,标题带 !。新会被拒的只有「封印后第二个包声明已被持有的智能体或技能名」。HotCRM 现有 6 个技能名互不相同,0 个智能体,不受影响。回滚即 revert。
  • 先看:packages/objectql/src/bare-name-one-holder.ts(那张有序表新增两类);docs/adr/0048-cross-package-metadata-collision.md 的 P1.9。
  • 两分钟验证:pnpm --filter @objectstack/objectql exec vitest run src/registry-bare-name-one-holder-ai-family.test.ts,预期 26 passed。
  • 需要你做的:批准或直接合并本 PR(Tier H,docs/adr/**)。合并即关闭 Bare-name phase 1: refuse a second package holding a non-object name whose consumers resolve without package context (ruling C on #30798) #30803。

席位意见:建议批准。机制与前三步相同,ADR 与代码、消费方一致;复审提出的 ADR 文字问题已在本头修正。

席位核对 — ACCEPT · domain:server#5 · Head-sha a9d8e39701956668d5480cd984e9eb8de78d5093 · 2026-11-08T05:40Z


Generated by Claude Code

@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 8, 2026
@os-zhuang
os-zhuang marked this pull request as ready for review November 8, 2026 05:42
@os-zhuang
os-zhuang added this pull request to the merge queue Nov 8, 2026
Merged via the queue into main with commit 4a3b186 Nov 8, 2026
56 checks passed
@os-zhuang
os-zhuang deleted the claude/project-thread-373joq branch November 8, 2026 06:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation pm:awaiting-merge PR only: landing waits on the maintainer's own review and merge (SKILL.md 复核 ②); never on an issue size/l 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.

Bare-name phase 1: refuse a second package holding a non-object name whose consumers resolve without package context (ruling C on #30798)

2 participants