Repository navigation
[Decision] connector-mcp tool-snapshot pin (#26709 item 3(c)): one aggregate toolsSnapshotSha256, or a per-tool pin record that can name the tool a server rewrote #27401
Description
epic:#27293#1 · sunhaolin · session_017xRBcVBKAFk1ntXSjvPt7U
Ruled: 6506797088 · letter B · 2026-10-29T19:52Z
carries item 3(c) of #26709; #26709 keeps item 1 (PR #27387). Filing gate: ② a contract-shape decision the maintainer takes. Raised by the os-dev on #26709 as needs_decision (report comment 6488553467); the seat's own readings below. Child of #27293 (E1). ⛔ Not a claim; item 3(c) is not dispatched while this card is open.
维护者速读
- 改了什么:不改代码。connectors: auto-shelf readiness — declarative
includeand a loud Swagger 2.0 refusal for connector-openapi;tools/listpagination, fatal 401 and a tool-snapshot hash for connector-mcp; an offline spec-normalization tool #26709 第 3(c) 项(MCP 工具快照校验)在实现时发现卡上自相矛盾,拆出来请你定形状。 - 为什么改:卡上定的键是「一个总 hash」(
toolsSnapshotSha256),验收却要求「不一致时点名是哪个工具被改了」;一个总 hash 只能说「变了」,说不出是哪个,而运行中也没有「上一次正常的快照」可比。 - 风险与回滚:卡挂着期间第 1 项(openapi
include)照常落地;MCP 侧暂无快照校验,现状不变(include已挡住新增工具,挡不住已有工具被改写描述或参数)。 - 先看:下面「选项」表。
- 两分钟验证:
git show origin/main:packages/services/service-automation/src/plugin.ts | sed -n 1844,1847p(未变化的实例不重连)。 - 需要你做的:选 A / B / C / D。
Background
#26709 item 3(c), verbatim: "providerConfig.toolsSnapshotSha256 (optional): when set, the hash of the selected tools' name + description + inputSchema (canonical JSON) is compared on every materialization; a mismatch marks the instance degraded with a message naming the changed tool, so a server-side rewrite of a tool's description or schema (the "rug-pull" class) is visible instead of silently accepted." Triage (6487372008) graded it a protocol addition, p1, domain:server.
Governing text: the card's own item text (above) and its acceptance line; AGENTS.md Prime Directive #12 (contract-first: refuse loudly at authoring or load, never tolerate silently); triage 6487372008 「include keeps one meaning across openapi and mcp」 (the pin should compose with include, keyed the same way).
Protocol statement: Metadata-protocol: added either way (a new author-written providerConfig key on mcp); Clause-②: yes (widening).
Premises (each with its re-check)
- One aggregate hash cannot attribute a mismatch to a tool. (Arithmetic; no re-check.)
- An unchanged instance is never re-materialized in a running process, so there is no earlier good snapshot in memory:
git show origin/main:packages/services/service-automation/src/plugin.ts | sed -n 1844,1847p("unchanged entries are left alone (an unchanged MCP instance is NOT reconnected)"), and the signature check at ~:1970. Read 2026-10-29T05:10Z atorigin/main. - A degraded channel exists to report a mismatch: the provider throws
ConnectorUpstreamUnavailableErrorand the materializer registers adegradedhusk retried with backoff (degradeConnectorInstance,plugin.ts:2172). Its log wording is "upstream unavailable … retrying with backoff", which misdescribes a pin mismatch. includeonmcpis a name allowlist (mcp-provider.ts:223-226).
The question
When a remote MCP server silently rewrites one of the tools an app depends on, what should the administrator see: "something changed", or "tool X changed"?
Options
| What it does | What the administrator sees | Effort | |
|---|---|---|---|
| A | Keep the ruled single toolsSnapshotSha256; on mismatch degrade the instance with expected and observed digests |
"The tool set changed" — no tool named; they diff the server by hand | domain:server S, 1 PR; no precedent |
| B | Replace it with providerConfig.toolsSnapshot: a record keyed by tool name, each value the sha256 of that tool's canonical name + description + inputSchema |
"Tool create_issue changed" (and "pinned tool x is gone"); meets the card's acceptance line |
domain:server S–M, 1 PR; #27373's offline tool can emit the record |
| C | Single hash plus in-process last-good memory | Names the tool only if the same process saw a good snapshot first — at cold boot (the common detection point) falls back to A | S + hidden state; not recommended |
| D | Drop item 3(c) for now (close not planned); include already keeps new tools out |
No rewrite detection | zero |
Business meaning: A is a seal on the whole box; B is a seal on each item in the box; D is no seal yet.
四维分析
os-decision-facets
- ① 项目长远合理性:两年后的样子是「每个被依赖的远程工具都能被钉住,变了能精确报出是谁」(参照 npm/pnpm 锁文件按包逐条记 integrity,而不是给整棵依赖树一个总 hash);B 与
include同样按工具名为键,两把钥匙能叠用;A 只能报「变了」,逐工具的数据算出来又扔掉;C 引入依赖进程历史的隐藏状态;D 不增生但把这类攻击面留着。 - ② 实际业务拉动:板上没有客户点名要这个功能;拉动来自你 2026-10-27 对目录调研的指示(「建议 1+2立卡」),3(c) 在那张卡的范围内。零客户拉动按规则会荐 D,但该项已在你指示的范围内,所以本卡只问形状,D 留作你收回的出口。
- ③ 防 AI 犯错:B 是结构化契约,一条钉错的 pin 会指向它那个工具,响亮且可定位;A 把「钉的值过时」与「工具被改写」压成同一条模糊报错,AI 和管理员都只能猜。
- ④ 创业阶段不扩散:A 与 B 工作量相当(都是一个 helper + 测试),都不新增门禁;D 最省。
Prior rulings read: toolsSnapshot, tool snapshot hash, rug-pull → the card's own item text and triage 6487372008 (grade and lane only, not the shape); thread: connectors: auto-shelf readiness — declarativeincludeand a loud Swagger 2.0 refusal for connector-openapi;tools/listpagination, fatal 401 and a tool-snapshot hash for connector-mcp; an offline spec-normalization tool #26709; repo: objectstack-ai/objectstack
Recommendation
B. The card's purpose is to make a server-side rewrite visible and attributable, and only per-tool data can name the tool; B keys the pin by tool name like include, so the two compose. Not A: it misses the card's own acceptance line. Not C: hidden, history-dependent behaviour. Not D: the item is inside the scope you directed; D stays the exit if you no longer want it. Confidence gap: whether the degraded channel's "upstream unavailable … retrying with backoff" wording and retry loop should be specialised for a pin mismatch (it reconnects on every backoff until the pin or the server changes); under A or B the dev should name that in the PR.
Self-check: 只看①选 B;②③④ 是否翻转:否(② 零客户拉动指向 D,但该项在你已指示的范围内,只影响是否保留出口,不翻字母)。
After the ruling
- A or B: the epic PM dispatches it to
domain:server(same lane as feat(connector-openapi): declarative providerConfig.include operation allowlist #27387),Metadata-protocol: added,Clause-②: yes (widening), with a contract review before landing; under B the key istoolsSnapshot(record), and the card text'stoolsSnapshotSha256is retired before it ever ships. - D: this card closes not planned; connectors: auto-shelf readiness — declarative
includeand a loud Swagger 2.0 refusal for connector-openapi;tools/listpagination, fatal 401 and a tool-snapshot hash for connector-mcp; an offline spec-normalization tool #26709 closes when item 1 lands.
Related
#26709 (parent item) · PR #27387 (item 1) · #27373 (offline tool that could emit pins) · #27293.
Dedupe
Split by the dev's needs_decision; the parent item is #26709. The 500 most recent issues and PRs, titles grepped for snapshot|rug-pull|toolsSnapshot (read 2026-10-29T04:10Z): 0 hits; control connector on the same set: 10 hits.
Activity
objectstack-fleet commentedon Oct 29, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: 可对外的 API | none | P②
Triage: p1 ·
domain:server· decision stays with the maintainer · 2026-10-29T06:00Z⛔ Not a claim.
includeand a loud Swagger 2.0 refusal for connector-openapi;tools/listpagination, fatal 401 and a tool-snapshot hash for connector-mcp; an offline spec-normalization tool #26709 item 3(c). The card asks for one aggregatetoolsSnapshotSha256, but its acceptance also asks to name the tool that changed. One hash cannot name a tool, so the maintainer picks the shape (A to D).openapiinclude, PR feat(connector-openapi): declarative providerConfig.include operation allowlist #27387) is not held by it.Generated by Claude Code
objectstack-fleet commentedon Oct 29, 2026
director#1 · os-bill · session_01Utp6CbQLcy8yg6vhJVXXi8
Brief-claimed-by: session_01Utp6CbQLcy8yg6vhJVXXi8 · batch #428 · 2026-10-29T10:17Z
Director seat, summon #54 continuation 1 (ledger objectstack#12708, opening mark 6493929869). Presented to the maintainer in batch #428 as item 3 of 4 — #26709 item 3(c), split by the dev's
needs_decision(6488553467), epic #27293 E1.Seat increment (re-read at
origin/mainded67bfb, 2026-10-29T10:17Z):degradeConnectorInstance(plugin.ts L2172, called L2077) and its wording describes an unavailable upstream;toolsSnapshothas 0 hits underpackages/(nothing shipped yet); item 1 (openapiinclude, PR feat(connector-openapi): declarative providerConfig.include operation allowlist #27387) is merged and not held by this card; connectors: an offline OpenAPI normalization script for connector-openapi — Swagger 2.0 → 3.x, YAML → JSON,$refbundled and dereferenced, absoluteservers[0].url, sha256, and a generatedincludelist #27373 (the offline normalisation tool the card names as a pin emitter) is closed.domain:server) and ruled nothing on the shape;check-prior-rulings.mjs --card 27401names ADR-0129 D3, ADR-0097 §6, ADR-0109 §4, ADR-0032 — term matches, none decides a snapshot pin's granularity. The item's own acceptance line ("a message naming the changed tool") is the governing text, and it contradicts the item's key shape (one aggregate hash); the maintainer picks which half gives way.include, is the lockfile shape (npm / pnpm record integrity per package, not one hash per tree); the two keys compose. ② no customer named it; the item is inside the scope the maintainer directed on 2026-10-27 (「建议 1+2立卡」), so D is the exit if that scope is withdrawn, not the default. ③ B names the tool and distinguishes "pin stale" from "tool rewritten"; A compresses both into one undiagnosable message; C hides state in process history. ④ A and B cost the same (one helper + tests); neither adds a gate.providerConfig.toolsSnapshot, a record keyed by tool name whose value is the sha256 of that tool's canonicalname+description+inputSchema; a mismatch degrades the instance with a message naming the tool (and a pinned tool that is gone); the aggregatetoolsSnapshotSha256spelling is retired before it ever ships. Carry the card's confidence gap into the dispatch: the degraded channel's "upstream unavailable … retrying with backoff" wording and retry loop should be specialised for a pin mismatch (no reconnect loop fixes a pin).domain:server· 1 PR · S–M ·Metadata-protocol: added,Clause-②: yes (widening)⇒ contract review before landing · no precedent.domain:serverwithRuling-ref:; the card leaves the box underpm:epic(its stroke).Prior rulings read: toolsSnapshot, tool snapshot, rug-pull, include → triage 6487372008 (grade only); thread: 6489246137; repo: objectstack-ai/objectstack
objectstack-fleet commentedon Oct 29, 2026
director#1 · os-bill · session_01Utp6CbQLcy8yg6vhJVXXi8
Ruling: batch #428 item 3 · letter B · maintainer 「27401 27328 同意」 2026-10-29T19:50Z
Director seat, summon #54 continuation 1 (ledger objectstack#12708, opening mark 6493929869, brief 6494012088). The maintainer answered batch #428 in the summon chat, verbatim: 「27401 27328 同意,另外两个我记得其他总监处理过了」 — 同意 = the seat's letter on this card: B.
What the ruling binds (letter B): #26709 item 3(c) ships as
providerConfig.toolsSnapshotonmcpconnector instances — a record keyed by tool name, each value the sha256 of that tool's canonical JSON ofname+description+inputSchema. On every materialization the selected tools are hashed and compared per tool; a mismatch marks the instancedegradedwith a message that names the changed tool (and a pinned tool that is no longer served); a tool the server adds is already kept out byinclude, which the pin composes with (same key: the tool name). The aggregate spellingtoolsSnapshotSha256in #26709's item text is retired before anything ships; no in-process last-good memory (C) and no aggregate digest (A). Carried into the dispatch as a named question for the dev: the degraded channel's wording ("upstream unavailable … retrying with backoff") and its reconnect loop are specialised for a pin mismatch, since no reconnect fixes a pin — the dev names what it does in the PR body.Governing text: #26709 item 3(c)'s acceptance line ("a message naming the changed tool"), AGENTS.md Prime Directive #12 (refuse loudly at load), triage 6487372008 (
includekeeps one meaning acrossopenapiandmcp). Premises re-read atorigin/mainded67bfb(brief 6494012088).Workload of the letter:
domain:server· 1 PR · S–M ·Metadata-protocol: added(a new author-writtenproviderConfigkey onmcp),Clause-②: yes (widening)⇒ contract review atCONTRACT_REVIEW_TIERbefore landing · no precedent.Execution: the #27293 epic PM dispatches this card to
domain:serverwithRuling-ref:naming this comment;needs-user-decision→pm:dispatchedis the epic PM's stroke (pm:epicstays). #26709 closes when item 1 (merged, PR #27387) and this item have landed.os-decision-facets
① 长远:逐工具 integrity 是 lockfile 的形状,与
include同以工具名为键,两把钥匙可叠用;总 hash 把算出的逐工具数据扔掉② 拉动:板上无客户点名;项在维护者 10-27「建议 1+2 立卡」的范围内
③ 防错:一条钉错的 pin 指向它那个工具,响亮可定位;A 把「钉的值过时」与「工具被改写」压成一条模糊报错
④ 不扩散:A 与 B 同量,皆不加门禁;B 不新增第二种钥匙形状