Skip to content

objectql: two packages' tools under one name, /meta/tool/NAME silently serves the first-registered one; measure cloud's by-name tool read, then decide whether tool joins the bare-name one-holder list #31414

Closed

Description

@objectstack-fleet

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

Filing gate: ② the maintainer's ruling. His 「以上都同意」 (2026-11-08T06:38:35Z, project message cmsg_01TenzsRopZj4jMbvjX8uz1XN1Vwc2QdcfhdqcbPa2zcGj) accepts the recommendation to open a separate p2 card for tool, out of PR #31375's measurement (#30803, the AI family). ⛔ Not a claim.
Reader: the seat that owns packages/objectql/src/bare-name-one-holder.ts (the phase 1 one-holder list, ADR-0048 §3.4 / P1.9).
Dedupe (open AND closed): bare-name tool one holder, /meta/tool shadow → only #30803 / PR #31375, which recorded tool as "not in the ruling".
reach: two installed packages declaring a tool under one name. GET /meta/tool/NAME serves the first-registered package's tool in either order, with no warning. A third package declaring the same name installs after the cold boot (200, shadowed). Measured by the #30803 PR 4 dev on 30e0bd16 (PR #31375 body, "Why (measured)" table).
class: a · type: Bug
Grade: p2, per the ruling.

维护者速读

两个包声明同名 tool 时,/meta/tool/NAME 静默返回先注册包的那个,第三个同名包照装不误。tool 是 MCP 暴露给 AI 的面,被遮蔽时 AI 可能调用到错的工具。#30803 第一阶段的裁决列表不含 tool,所以 PR #31375 没加。按裁决,先测 cloud 侧按名取 tool 的读取路径,再决定 tool 是否补进第一阶段。

What to do

  1. Measure first: how the agent runtime in objectstack-ai/cloud resolves a tool by name for chat and MCP. Does it read with package context, or is it a bare-name read like /meta/tool/NAME? PR feat(objectql)!: an agent and a skill hold one name per package (bare-name phase 1, 4/4) #31375 marked this NOT MEASURED. The measurement needs cloud read access.
  2. Then decide. If the by-name read has no package context, tool joins BARE_NAME_ONE_HOLDER_TYPES the way agent / skill did in feat(objectql)!: an agent and a skill hold one name per package (bare-name phase 1, 4/4) #31375 (both doors, the cold-boot warn, ADR-0048 addendum: Tier H, Clause-② narrowing, BREAKING changeset). If every consumer reads with package context, record that in ADR-0048 §3.4 and close.

Refs #30803, PR #31375

Activity

  1. added
    bugSomething isn't working
    pm:queueReady for the PM dispatch loop
    domain:serverDomain lane — seat card indexed by label:pm:seat
    on Nov 8, 2026
  2. objectstack-fleet commented on Nov 8, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: audit · p2 · domain:server · confirmed · 2026-11-08T06:54Z

    ⛔ Not a claim.

    • When two packages declare a tool under one name, /meta/tool/NAME silently serves the first-registered one, and a third same-named package still installs. tool is the surface MCP exposes to agents, so a shadowed tool can be called in place of the right one. Split out by the maintainer's ruling (「以上都同意」).
    • p2 per the ruling; lane domain:server (bare-name-one-holder.ts). pm:queue.

    Generated by Claude Code

  3. objectstack-fleet commented on Nov 8, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: lane stays domain:server · 2026-11-08T16:05Z

    ⛔ Not a claim.


    Generated by Claude Code

  4. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Nov 8, 2026
  5. objectstack-fleet commented on Nov 8, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#1 · os-steve · session_01ADHkmPF1skfu2kx89BEXZi

    Claim: PM loop round 1 · #31414 · 2026-11-08T18:00Z · bug (p2; lane kept domain:server by triage 6723775120)
    Session: session_01ADHkmPF1skfu2kx89BEXZi
    Account: os-steve
    Branch: claude/project-thread-7w4hop (the card thread's harness-pinned branch; platform exception to claude/issue-<n>-<slug>)
    Worktree: objectstack-issue-31414
    Domain: domain:server
    Seat: domain:server#1
    File surface: packages/objectql/src/bare-name-one-holder.ts and its save-door / install-door callers in packages/objectql/src/, their tests, docs/adr/0048-cross-package-metadata-collision.md (a dated note under the 2026-11-06 addendum only), a changeset (stop on breach; explain in the report)
    Container & model: M, mode:thread, model: default tier (the ADR-0048 note is Tier H: the seat's review of record runs at CONTRACT_REVIEW_TIER), effort: high
    Clause-②: yes (narrowing)
    Metadata-protocol: narrowed
    Responsibility: to be measured by the dev (which change let a second same-named tool install: tool was left out of PR #31375's list)
    Thread-read: 6723775120
    Premise: holds (read at 69244406: BARE_NAME_ONE_HOLDER_TYPES ends at 'agent', 'skill'; no 'tool')
    Serial constraints cleared: draft PR #31586 (ADR-0150, epic #31527) edits ADR-0048's Status line only. The dev ⛔ does not touch that line and rebases on whichever lands first. No open PR touches bare-name-one-holder.ts. If step 1 finds every consumer reads with package context, the dev reports and the seat re-states this claim as Clause-②: no / Metadata-protocol: none (docs-only close).
    Order: standard order + server-seat1/31414-delta.md (project files).

  6. objectstack-fleet commented on Nov 8, 2026

    @objectstack-fleet
    ContributorAuthor

    repo:cloud#3 · os-zhuang · session_01QUs3BvRcDHXqRU4TSVE4u6

    Answer: #31414 step 1 (cloud read, for domain:server#1) · 2026-11-08T18:04Z

    Read at objectstack-ai/cloud main ba6f0fa. These are code readings; nothing was run.

    Short answer: cloud's agent runtime resolves a tool by bare name, with no package context, for both chat and MCP. But it resolves against its in-process ToolRegistry, which plugins fill in code. It never reads a metadata tool item or /meta/tool/NAME to pick what runs.

    Path Resolution file#symbol
    Registry A Map<string, …> keyed by definition.name. register is set, so a second register under the same name silently replaces the first (last wins). packages/service-ai/src/tools/tool-registry.ts#ToolRegistry.register
    Who fills it Plugins in code, on the ai:ready hook. The open runtime registers none; @objectstack/service-ai-studio registers the shipped families. packages/service-ai/src/plugin.ts (the ctx.trigger('ai:ready', …) block)
    Chat (skill.tools) Each claimed name is looked up by bare name in a map built from the full registry; a trailing * matches by prefix. packages/service-ai/src/skill-registry.ts#SkillRegistry.flattenToTools, called from packages/service-ai/src/agent-runtime.ts (availableTools)
    Direct execute POST /api/v1/ai/tools/:toolName/execute does toolRegistry.has(toolName) and then execute. packages/service-ai/src/routes/tool-routes.ts
    MCP (cloud's build door) Its source is the same aiService.toolRegistry, so tools/list and tools/call see the same bare-name map. packages/service-ai-studio/src/plugin.ts#mcpDoorSource → createMcpDoorRegistry
    Metadata tool items Written as a mirror of what code registered, for Studio visibility (register('tool', name, …, { notify: false }) when absent). The runtime only probes exists('tool', '__probe__') for store reachability. packages/service-ai-studio/src/plugin.ts (the selectToolDefinitions loop); packages/service-ai/src/plugin.ts (the probe)

    What this means for step 2. No cloud consumer reads a tool with package context. No cloud consumer executes from the metadata tool item either: a package-declared tool metadata item reaches no handler in cloud's AI runtime. In cloud, the /meta/tool/NAME shadowing changes what Studio shows, not what an agent calls. The live same-name hazard on the cloud side is the ToolRegistry's silent last-wins set. That is cloud's own code, outside this card's fix. Seat 3 can file it as a cloud card if domain:server wants it recorded. The framework's own /mcp tools/list and tools/call are objectstack code and are measured on your side.

  7. objectstack-fleet commented on Nov 8, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#1 · os-steve · session_01ADHkmS1DVx9CH1xvXikQBW
    os-dev-report

    {
      "issue": 31414,
      "status": "done",
      "branch": "claude/project-thread-7w4hop",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/31661",
      "session": "session_01ADHkmS1DVx9CH1xvXikQBW",
      "premise_still_valid": true,
      "summary": "Step 1: every reader of a tool names it with no package — in this repo /meta/tool/NAME, SkillSchema.tools (bare snake_case only), AIToolDefinition/ToolCallPart, the MCP bridge (registerTool(tool.name); the SDK holds one tool per name), agent guardrails.blockedTopics; in cloud (repo:cloud#3 answer 6725094995) the chat and MCP doors read one bare-name ToolRegistry. Step 2, joins path: tool joins BARE_NAME_ONE_HOLDER_TYPES last (both doors and the cold-boot warn are generic over the list), the AI family's unit and dogfood pins carry tool through the same matrix, ADR-0048 gains dated note P1.10 under the 2026-11-06 addendum (Status line untouched), BREAKING objectql minor changeset. Responsibility: no regression; tool was never in phase 1's ruled list (PR #31375 / 4a3b186c pinned its coexistence as a CONTROL). Needs nothing from child R (#31575).",
      "tests": "At cbd8c5b2 (origin/main merged once, #31614 touched registry.ts): bare-name unit pins 5 files / 192 passed (AI family file 38, was 26); objectql full 571 files / 11593 passed; mcp 60 / 915; objectql typecheck and dogfood tsc --noEmit exit 0. At 57fd18f8: same pins 192/192; four bare-name dogfood files 17/17 (ff31f6f9); metadata-protocol 327 files (4 skipped) / 22593 passed; rest 394 / 7546 passed (332 skipped); runtime local 562 / 9020 passed (19 skipped). Ablation on 57fd18f8 (ablation-replace.mjs, marker ABL31414, tool filtered out of the list): unit 10 of 38 red; objectql rebuilt with it (preflight: marker in 4 built files; DTS TS2322 after the JS was written) -> dogfood AI file 2 of 5 red (cold-boot line lacks the tool; zeta installs 200); restored blob == HEAD, git diff HEAD empty, rebuilt, --absent: 0 of 14 built files, tree clean. eslint --no-inline-config over packages/objectql/src + the dogfood file exit 0.",
      "gates": "dispatch-gates --commands at cbd8c5b2: 83 derived, all run with exit captured before any pipe; 82 exit 0 first pass, check:type-check-debt exit 3 (lock wait behind this card's dogfood run) then exit 0 on rerun; --ran: 83 run, 0 NOT-MEASURED (exit 0). Teardown self-test fixture fetched first (shallow clone). pnpm lint: neither touched package has a lint script; package-scoped eslint exit 0. CI check-runs: in_progress at report time.",
      "dogfood_battery": "cbd8c5b2 · os-verify-lock VERDICT command-exit 0 (held 1943s) · Test Files 440 passed | 3 skipped (443) · Tests 3645 passed | 41 skipped (3686) · no failures (also green at 57fd18f8: 440|3 files, 3642|41 tests)",
      "mcp_calls": "8 — reads only: issue_read get x2, issue_read get_comments x4, pull_request_read get x2 (#31375, #31661)",
      "api_writes": "2 — fleet-write relay pr_create (#31661, read-back 12725 bytes sent == stored); this os-dev-report comment via post-stamped.mjs. Reads outside MCP: gh api search/code x2, gh api issues/31414/comments x1.",
      "open_questions": [
        { "question": "The claim spells `Clause-②: yes (narrowing)`; the delta asked for it and the PR body and changeset copy it verbatim. #31375, a pure narrowing of the same list, used `no (narrowing)` (yes reads as widen-and-narrow). Keep yes?", "options": ["A keep yes (narrowing) as claimed", "B seat restates the claim as no (narrowing) and patches the body + changeset line"], "recommendation": "B, because nothing is widened; same as #31375" },
        { "question": "ADR-0048's header Addenda line, the §3.4 2026-11-06 note, P1.5 and P1.9 still list the types without tool; the claim limited the ADR edit to a dated note (P1.10 says to read them with it). Fold tool into those lines?", "options": ["A leave as P1.10 cross-reference", "B seat widens the file surface and the dev folds tool into the four spots in a follow-up push"], "recommendation": "B after #31586 lands (it edits the Status line beside the header), so the ADR reads whole" },
        { "question": "scripts/adr-anchors/packages__objectql__src__bare-name-one-holder.ts.json's invariant names the families without tool (outside the claim's surface). Update it?", "options": ["A leave", "B add one clause naming tool in the same follow-up push as the ADR fold"], "recommendation": "B, it is the anchor a later reader of the list checks" }
      ],
      "security_findings": "none",
      "out_of_scope_findings": [
        "class: a · reach: named producer (cloud packages/service-ai/src/tools/tool-registry.ts#ToolRegistry.register, a Map.set: a second code-registered tool under one name silently replaces the first, per repo:cloud#3 reading 6725094995) · carrier: cloud (the cloud seat offered to file it) · dedupe words: ToolRegistry last-wins, duplicate tool register cloud",
        "carrier: none · noted in PR Acceptance notes: cloud mirrors code tools into `tool` metadata only when absent; a package-bound mirror after the seal would now be refused on a second same-named plugin — NOT MEASURED on a running cloud build"
      ],
      "files_changed": [
        ".changeset/31414-bare-name-tool.md",
        "docs/adr/0048-cross-package-metadata-collision.md",
        "packages/objectql/src/bare-name-one-holder.ts",
        "packages/objectql/src/plugin.ts",
        "packages/objectql/src/registry.ts",
        "packages/objectql/src/registry-bare-name-one-holder-ai-family.test.ts",
        "packages/qa/dogfood/test/bare-name-one-holder-ai-family.dogfood.test.ts"
      ],
      "line_budget": "+147 / -46 over 7 files (source 19+2+2 lines; tests 58 + 53; ADR +37; changeset +22)",
      "deviations": [
        "packages/qa/dogfood/test/bare-name-one-holder-ai-family.dogfood.test.ts is outside `packages/objectql/src/` but is the booted pin of the same doors; its tool CONTROL went red by design, so it is a forced edit (named in the PR body)",
        "Verification spans two heads: metadata-protocol, rest and runtime ran on 57fd18f8 only, not re-run after merging origin/main (cbd8c5b2), which touched objectql registry.ts but no reader of the list; objectql, mcp, pins, typechecks, gates and the dogfood battery ran on cbd8c5b2",
        "A first ablation attempt used a comment marker that would not survive to dist; it was restored (git diff HEAD empty) before any run and re-planted with a string marker",
        "PR assignee and reviewer not set: standard order forbids MCP writes and os-dev forbids PR assignees"
      ]
    }

    Stamped 2026-11-08T20:26Z.


    Generated by Claude Code

  8. objectstack-fleet commented on Nov 8, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#1 · os-steve · session_01ADHkmPF1skfu2kx89BEXZi

    Claim: PM loop round 1 · #31414 · 2026-11-08T20:28Z · bug (p2) · re-stated after the dev's report 6726685498; supersedes 6725057016 on the declaration and file-surface lines only
    Session: session_01ADHkmPF1skfu2kx89BEXZi
    Account: os-steve
    Branch: claude/project-thread-7w4hop
    Worktree: objectstack-issue-31414
    Domain: domain:server
    Seat: domain:server#1
    File surface: packages/objectql/src/bare-name-one-holder.ts and its comment mentions in plugin.ts / registry.ts, their tests, the booted pin packages/qa/dogfood/test/bare-name-one-holder-ai-family.dogfood.test.ts, docs/adr/0048-cross-package-metadata-collision.md (P1.10, plus tool folded into the header's Addenda line, the §3.4 2026-11-06 note, P1.5 and P1.9; ⛔ never the Status line, which draft PR #31586 edits), scripts/adr-anchors/packages__objectql__src__bare-name-one-holder.ts.json (one clause naming tool), a changeset
    Container & model: M, mode:thread, model: default tier (Tier H ADR + narrowing: the review of record runs at CONTRACT_REVIEW_TIER), effort: high
    Clause-②: no (narrowing)
    Metadata-protocol: narrowed
    Responsibility: to be measured by the dev (which change let a second same-named tool install: tool was left out of PR #31375's list)
    Thread-read: 6726685498
    Premise: holds (see 6725057016); step 1 measured every reader bare-name in this repo and in cloud (6725094995).
    Serial constraints cleared: unchanged from 6725057016. Clause-② corrected: nothing is widened, so the criterion (does the card widen the accept set or the public surface?) answers no; the narrowing arm stays, as #31375 declared it.

  9. objectstack-fleet commented on Nov 8, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#1 · os-steve · session_01ADHkmPF1skfu2kx89BEXZi

    PR #31661 head 30b6c84f: contract review PASS (6727183831, CONTRACT_REVIEW_TIER) adopted, seat ACCEPT 6727202320. Tier H (ADR-0048): stays DRAFT, review requested from os-zhuang (GOVERNED_APPROVERS).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingdomain:serverDomain lane — seat card indexed by label:pm:seatpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions