Skip to content

[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

Open

Description

@objectstack-fleet

epic:#27293#1 · sunhaolin · session_017xRBcVBKAFk1ntXSjvPt7U

Ruled: 6545635244 · letter B · 2026-10-31T15:53Z
Ruling-ref: 6545635244

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.

维护者速读

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)

  1. One aggregate hash cannot attribute a mismatch to a tool. (Arithmetic; no re-check.)
  2. 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 at origin/main.
  3. A degraded channel exists to report a mismatch: the provider throws ConnectorUpstreamUnavailableError and the materializer registers a degraded husk retried with backoff (degradeConnectorInstance, plugin.ts:2172). Its log wording is "upstream unavailable … retrying with backoff", which misdescribes a pin mismatch.
  4. include on mcp is 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 — declarative include and a loud Swagger 2.0 refusal for connector-openapi; tools/list pagination, 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

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

  1. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    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.


    Generated by Claude Code

  2. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    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/main ded67bfb, 2026-10-29T10:17Z):

    • Premises re-run, all hold: an unchanged MCP instance is not reconnected on reconcile (plugin.ts L1844–1847), so a running process holds no earlier good snapshot; the degraded channel is degradeConnectorInstance (plugin.ts L2172, called L2077) and its wording describes an unavailable upstream; toolsSnapshot has 0 hits under packages/ (nothing shipped yet); item 1 (openapi include, 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, $ref bundled and dereferenced, absolute servers[0].url, sha256, and a generated include list #27373 (the offline normalisation tool the card names as a pin emitter) is closed.
    • Prior rulings: triage 6487372008 graded the item (protocol addition, p1, domain:server) and ruled nothing on the shape; check-prior-rulings.mjs --card 27401 names 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.
    • Facets: agree with the card on all four. ① per-tool integrity, keyed by tool name like 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.
    • Seat's letter: B — providerConfig.toolsSnapshot, a record keyed by tool name whose value is the sha256 of that tool's canonical name + description + inputSchema; a mismatch degrades the instance with a message naming the tool (and a pinned tool that is gone); the aggregate toolsSnapshotSha256 spelling 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).
    • Workload of B: domain:server · 1 PR · S–M · Metadata-protocol: added, Clause-②: yes (widening) ⇒ contract review before landing · no precedent.
    • After the ruling: the [Epic] Marketplace and connector auto-shelf — E1 platform prerequisites (openapi/mcp hardening, outbound OAuth, request signing), E2 administrator credential hand-off, E3 author recipes and handbook, E4 the first shelf of official connector apps #27293 epic PM dispatches it to domain:server with Ruling-ref:; the card leaves the box under pm:epic (its stroke).

    Prior rulings read: toolsSnapshot, tool snapshot, rug-pull, include → triage 6487372008 (grade only); thread: 6489246137; repo: objectstack-ai/objectstack

  3. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    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.toolsSnapshot on mcp connector instances — a record keyed by tool name, each value the sha256 of that tool's canonical JSON of name + description + inputSchema. On every materialization the selected tools are hashed and compared per tool; a mismatch marks the instance degraded with 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 by include, which the pin composes with (same key: the tool name). The aggregate spelling toolsSnapshotSha256 in #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 (include keeps one meaning across openapi and mcp). Premises re-read at origin/main ded67bfb (brief 6494012088).

    Workload of the letter: domain:server · 1 PR · S–M · Metadata-protocol: added (a new author-written providerConfig key on mcp), Clause-②: yes (widening) ⇒ contract review at CONTRACT_REVIEW_TIER before landing · no precedent.

    Execution: the #27293 epic PM dispatches this card to domain:server with Ruling-ref: naming this comment; needs-user-decision → pm:dispatched is the epic PM's stroke (pm:epic stays). #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 不新增第二种钥匙形状

  4. objectstack-fleet commented on Oct 31, 2026

    @objectstack-fleet
    ContributorAuthor

    epic:#27293#1 · sunhaolin · session_017xRBcVBKAFk1ntXSjvPt7U

    Claim: PM loop round 3
    Session: session_017xRBcVBKAFk1ntXSjvPt7U
    Account: sunhaolin (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-27401-mcp-tool-snapshot
    Worktree: objectstack-issue-27401
    Domain: domain:server
    Seat: epic:#27293#1
    File surface: packages/connectors/connector-mcp/src/mcp-provider.ts (+ its tests): the providerConfig.toolsSnapshot record and its per-tool check in one helper; the degraded channel's wording for a pin mismatch, where it is raised (packages/services/service-automation/src/plugin.ts only if the mismatch needs its own reason or retry policy there); content/docs/automation/connectors.mdx (the key); a @objectstack/connector-mcp changeset (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: opus (TIER_DEFAULT build — dispatch-gates --tier this fire: no path-derived mandate; the ruling owes a CONTRACT_REVIEW_TIER review before landing), effort: xhigh (p1)
    Clause-②: yes (widening)
    Metadata-protocol: added
    Responsibility: n/a — not a defect card
    Thread-read: 6506797088
    Ruling-ref: 6506797088
    Premise: holds (read at origin/main c5bf56b8 2026-10-31T02:02Z: toolsSnapshot 0 hits in connector-mcp/src, control include 17 in mcp-provider.ts; the include refusals landed in 46943657 (#27402), so the pin composes with a loud allowlist)
    Serial constraints cleared: none — none of the 60 most recent open PRs touches packages/connectors/connector-mcp/** or service-automation/src/plugin.ts (files read 2026-10-31T02:02Z).

  5. objectstack-fleet commented on Oct 31, 2026

    @objectstack-fleet
    ContributorAuthor

    epic:#27293#1 · sunhaolin · session_017xRBcVBKAFk1ntXSjvPt7U
    os-dev-report

    {
    "issue": 27401,
    "status": "needs_decision",
    "branch": "claude/issue-27401-mcp-tool-snapshot",
    "pr": null,
    "session": "session_017xRBcVBKAFk1ntXSjvPt7U",
    "premise_still_valid": true,
    "resume_from": "claude/issue-27401-mcp-tool-snapshot@68d8770",
    "summary": "I built the ruled letter-B shape and pushed it to the branch. No PR is open. providerConfig.toolsSnapshot on mcp instances is a record of tool name → sha256 of the canonical JSON (RFC 8785 form) of name + description + inputSchema. Two shapes are refused before connecting as plain configuration faults: a malformed record, and a pin on a tool that include leaves out. After tools/list, each pinned tool is compared on its own. A pinned tool that changed, or that the server no longer serves, degrades the instance with one message naming each such tool and the digest it now serves. Docs, a minor changeset, and the exports computeMcpToolsSnapshot / mcpToolDigest are included. I stopped at the named channel question, as the dispatch trigger requires. On today's channel (ConnectorUpstreamUnavailableError) the husk reason and the connector_action refusal name the tool, and the retry backoff re-checks the pin. But on every attempt the materializer's ERROR line still says 'upstream unavailable … retrying with backoff' and the engine's WARN line says 'until its upstream is reachable'. Making those two lines truthful needs a marker the materializer can tell apart from an outage, which means a new error type in packages/spec. So the choice goes back to the maintainer (open_questions[0]). Readings of the ruling I took and tested: (1) a pinned tool that include also lists is reported by the pin (degraded) when the server drops it, not by include's typo refusal; (2) a selected tool the pin does not name is not checked, because tools the server adds are include's job; (3) a digest must be exactly 64 lowercase hex characters; (4) an empty record is accepted and pins nothing, like include: []. Under option A the branch can become a PR as it stands. Under B, connector-mcp changes only inside toolsSnapshotDriftError; the spec marker and a service-automation branch are added.",
    "tests": "Package tests at 68d8770: pnpm --filter @objectstack/connector-mcp test gave 'Test Files 6 passed (6) · Tests 80 passed (80)', lock VERDICT command-exit 0. The new src/tool-snapshot.test.ts has 24 tests, all passing. They cover: the digest equals an independently written sha256 string; two key orders hash equal at every depth; matching pins come up ready; one changed description degrades naming only that tool and closes the client; a changed inputSchema degrades too; a pinned tool that is no longer listed degrades naming it; a pinned tool that include also lists and the server drops is reported by the pin, not include; an unpinned include typo stays include's configuration fault; all drifts are named in one message; a partial pin works; 9 malformed records are refused before connecting; a pin on a tool outside include is refused before connecting; the control without toolsSnapshot is unchanged; a boot through AutomationServicePlugin registers the instance degraded, its degradedReason naming the tool, with no action. Typecheck: pnpm --filter @objectstack/connector-mcp typecheck exit 0. The test layer reports 'compiles … 1 file(s) / 5 error(s) / 1 pinned signature(s) held', the ledger unchanged from before. tsc --listFiles -p tsconfig.test.json counts 6 of 6 test files, tool-snapshot.test.ts among them. Ablation (run once, on the per-tool compare): node scripts/ablation-replace.mjs on src/tool-snapshot.ts replaced the anchor 'if (digest !== pinned) changed.push({ name, served: digest });' with a no-op. Its readings: 'ok mutation landed: anchor 1 → 0, blob 088fea03bab9 → 3532d1d24ba9'; then 'Tests 4 failed | 20 passed (24)'. The 4 failures are exactly the changed-description, changed-inputSchema, all-drifts-named and through-materializer cases. Then 'ok restored: blob == HEAD (088fea03bab9) and git diff HEAD is empty'. The subject is imported relatively from src, so there is no dist leg. Measurement of today's channel (a temporary test, deleted): the instance is state degraded with degradedHold undefined. The server was contacted 1 time at boot, 2 times by +5s, 3 times by +15s. Each attempt logs ERROR "[Automation] connector instance 'vendor_mcp' (provider 'mcp') upstream unavailable — instance registered degraded (no actions); retrying with backoff, attempt N." and WARN "Connector registered DEGRADED: vendor_mcp (origin: declarative) — no actions and no handlers until its upstream is reachable; …". The tool's name appears only in the records' meta.",
    "gates": "Derived at 68d8770 with node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, without paths as base os-dev.md says: 87 commands. I ran all 87, recording each exit code before any pipe. 84 exited 0 on the first run. 3 exited 3, which is PREREQUISITE NOT MET, not a finding: (a) check-plugin-teardown-shape.mjs --self-test, whose pinned commit 621a487 is not in the shallow clone. I fetched that one commit and the rerun exited 0 ('48 cases pass'). (b) spec check:skill-examples, which needs client and client-react declarations. I built that closure and the rerun exited 0 ('278 prose examples type-check across 3 surface(s)', which includes the new docs os:check block). (c) check:dual-build-cjs-loads, which needs every package's dist: NOT MEASURED locally. Declared narrowing: connector-mcp dist/index.js and dist/index.mjs both load and export computeMcpToolsSnapshot and mcpToolDigest, and the digest matches an independent sha256. --ran with the exit codes recorded: 'Run reconciliation — 87 derived, 86 run, 1 NOT-MEASURED, 0 UNRUN', exit 0. There is no PR, so there are no check-runs to cite. pnpm lint, test:repo and the rest of the full CI suite were not run locally (CI-owned).",
    "dogfood_battery": "n/a — no PR title (so no !), and Clause-② carries the (widening) arm, not (narrowing)",
    "mcp_calls": "0 — no MCP GitHub calls",
    "api_writes": "1 — POST /repos//issues/27401/comments (this os-dev-report, through scripts/pm/post-stamped.mjs); also 3 git pushes to the branch (not REST)",
    "line_budget": "n/a — no skills/** file and no line-ratcheted ledger touched",
    "files_changed": [
    ".changeset/27401-mcp-tools-snapshot.md",
    "content/docs/automation/connectors.mdx",
    "packages/connectors/connector-mcp/src/index.ts",
    "packages/connectors/connector-mcp/src/mcp-connector.ts",
    "packages/connectors/connector-mcp/src/mcp-provider.ts",
    "packages/connectors/connector-mcp/src/tool-snapshot.test.ts",
    "packages/connectors/connector-mcp/src/tool-snapshot.ts"
    ],
    "skills_docs": "Skills-docs: follow-up owed — skills/** not edited here (Tier H); whether a skill should teach providerConfig.toolsSnapshot is for the seat that owns skills/**",
    "deviations": [
    "No PR and no labels: status needs_decision, per the dispatch trigger 'If it needs a new error type in packages/spec, stop and report needs_decision'. The branch is pushed as the resume point.",
    "dispatch-gates was derived without paths. The dispatch passed paths; base .claude/agents/os-dev.md derives from the merge base and wins on conflict.",
    "My system prompt (a copy of os-dev) says pr_create carries assignees and the report comment opens with the marker. Base os-dev.md says no PR assignees and a seat-signature line first, via post-stamped.mjs. I followed base.",
    "The harness attribution reminder asks for a model-named Co-Authored-By trailer. AGENTS.md requires the model-free pair, so the commits carry that.",
    "To make check-plugin-teardown-shape --self-test runnable, I fetched one commit (621a487, --depth=1 --no-write-fetch-head) into the shared object store.",
    "The adding PR does not exist yet, so its body (Clause-②: yes (widening), Metadata-protocol: added, Skills-docs line, the 维护者速读 block) is unwritten. The changeset already carries the two declaration lines."
    ],
    "open_questions": [
    {
    "question": "The ruling says a toolsSnapshot mismatch degrades the instance. Today's ADR-0097 contract has exactly one marker that degrades without failing boot: CONNECTOR_UPSTREAM_UNAVAILABLE, which packages/spec declares as 'the upstream … is temporarily unreachable'. Thrown for a mismatch (as on the branch), it gives: a degradedReason that names the tool (GET /connectors and the connector_action refusal); re-checks on the 5s → 5min backoff; and on every attempt an ERROR 'upstream unavailable … retrying with backoff' plus a WARN 'until its upstream is reachable', with the tool named only in meta. A plain throw would fail the boot, and CONNECTOR_CREDENTIAL_REJECTED would mark a credential rejected. Which channel should a pin mismatch use?",
    "options": [
    "A — Ship as on the branch: reuse CONNECTOR_UPSTREAM_UNAVAILABLE. The instance is degraded with a reason naming each tool and the served digest. It is re-checked on the backoff: a server that restores the pinned definitions recovers it, and an edited pin re-materializes it at once. The materializer's ERROR and the engine's WARN keep the outage wording. Cost: nothing more; 1 PR (connector-mcp only). Long-term cost: a spec marker thrown outside its declared meaning, and an operator reading logs sees 'upstream unavailable' during a tool rewrite.",
    "B — Add a spec marker (for example CONNECTOR_UPSTREAM_CHANGED) to @objectstack/spec/integration connector-provider-errors.ts: a class, a structural guard, and an error-code ledger row. service-automation gets a materializer branch that degrades with its own wording ('serves a tool definition that differs from its toolsSnapshot; re-checking with backoff', and the engine record to match), on the same backoff. connector-mcp changes only its one throw. Cost: S, one PR across spec + service-automation + connector-mcp (spec lane). It adds one error code and no gate.",
    "C — Widen CONNECTOR_UPSTREAM_UNAVAILABLE's declared meaning in the spec TSDoc to cover 'serves a definition its pin does not match', and make the two log lines cause-neutral for every provider. Cost: XS in lines, but a semantic change that rewrites the outage wording every provider gets, and the code's name still says 'unavailable'."
    ],
    "recommendation": "B. Holding (no retry) is not offered: the server can restore the pinned definition where this process cannot see it, which is the same reason an environment or vault credential refusal is retried on the backoff. A hold would also need B's marker. Four axes: ① Actual business need: nobody on the board names a pin, and the item sits in the maintainer's directed scope. Measured: A and B show the administrator the same reason; they differ only in the two log lines an operator reads during an incident. ② Long-term soundness: B keeps every marker's declared meaning true (contract-first). A makes 'temporarily unreachable' false for one of its throws. C changes the meaning for every provider. ③ Preventing AI mistakes: under A, an agent reading the ERROR record goes after the network, and a provider author learns the marker means 'anything degradable'. Under B the record names the pin. ④ Startup-stage scope: A adds nothing. B adds one error code and one materializer branch (S, no gate). C is smallest in lines but semantic. Trade-off: ② and ③ favour B, ④ favours A. B is a cross-lane spec change before this item ships; A ships now and keeps two misleading log lines."
    }
    ],
    "out_of_scope_findings": []
    }

  6. objectstack-fleet commented on Oct 31, 2026

    @objectstack-fleet
    ContributorAuthor

    epic:#27293#1 · sunhaolin · session_017xRBcVBKAFk1ntXSjvPt7U

    Release: claim 6537542245 (session_017xRBcVBKAFk1ntXSjvPt7U, epic:#27293#1) · premise falsified, back to the decision box · 2026-10-31T02:55Z
    Resume-from: claude/issue-27401-mcp-tool-snapshot@68d8770f

    维护者速读

    • 改了什么:不改代码。[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(你裁的 B,按工具逐个钉快照)已在分支上做完(24 个测试,删掉逐工具比对时 4 个转红),但没开 PR:「快照对不上时实例进入降级」这一步,现在只能借用「上游暂时不可达」这个错误码,日志会写成「上游不可用、退避重试」。
    • 为什么改:裁决原文假设「重连修不好钉子」(since no reconnect fixes a pin)。dev 实测这个前提不成立:服务端把工具定义改回来后,退避重试会让实例自动恢复。前提被证伪,按规则回箱,不由席位代答。
    • 风险与回滚:卡挂着期间分支停在 68d8770f 不动;MCP 侧仍没有快照校验(现状),include 照常挡住新增工具。
    • 先看:下面「选项」表。
    • 两分钟验证:git show origin/main:packages/services/service-automation/src/plugin.ts | sed -n 2655,2659p(每次重试打的那句 ERROR)。
    • 需要你做的:选 A / B / C。

    Background

    Ruling 6506797088 (letter B), verbatim: "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." The dev's report: 6538058257. Read at origin/main c5bf56b8:

    • The only marker that degrades an instance without failing boot is CONNECTOR_UPSTREAM_UNAVAILABLE (packages/spec/src/integration/connector-provider-errors.ts, ledger row packages/spec/src/api/error-code-ledger.zod.ts:1620), declared as "the upstream … is temporarily unreachable".
    • Thrown for a pin mismatch (as on the branch): degradedReason and the connector_action refusal name the changed tool; the instance is re-checked on the 5 s → 5 min backoff; every attempt logs ERROR "… upstream unavailable — instance registered degraded (no actions); retrying with backoff, attempt N." (service-automation/src/plugin.ts:2658) and WARN "… until its upstream is reachable" (plugin-extension-api.ts:388), the tool named only in the records' meta.
    • Measured by the dev: a server that restores the pinned definitions recovers the instance on the backoff; an edited pin re-materializes it at once. So a reconnect can fix a pin, which is the premise the ruling's "no reconnect fixes a pin" rested on.

    Governing text: ruling 6506797088 (above); AGENTS.md Prime Directive #12 (contract-first; a declared marker means what it declares); ERROR_CODE_LEDGER (a new code is Clause-②: yes, spec lane).

    Options

    What it does What the operator sees Effort
    A Ship the branch as is: reuse CONNECTOR_UPSTREAM_UNAVAILABLE Admin page names the tool; logs say "upstream unavailable, retrying" during a tool rewrite connector-mcp only, 1 PR, ready now
    B Add one spec marker (e.g. CONNECTOR_UPSTREAM_CHANGED: class, guard, ledger row) and a materializer branch with its own wording ("serves a tool definition that differs from its toolsSnapshot; re-checking with backoff"), same backoff Admin page and logs both say the pin did not match, naming the tool S, 1 PR across spec + service-automation + connector-mcp (spec lane review); the branch's connector-mcp part stays
    C Widen CONNECTOR_UPSTREAM_UNAVAILABLE's declared meaning to cover a pin mismatch and make both log lines cause-neutral for every provider Neutral wording everywhere XS lines, but a semantic change to a published marker every provider throws

    Business meaning: A puts a "network down" sign on a tampered package; B gives tampering its own sign; C makes every sign say "something is wrong".

    四维分析

    os-decision-facets

    • ① 项目长远合理性:两年后每个错误码都该只表达它声明的那件事(契约先行);B 让「上游不可达」继续只表示不可达,新码只表示「被改写」;A 让一个已发布错误码在一处说假话;C 改写所有 provider 共用的含义。
    • ② 实际业务拉动:板上无客户点名;该项在你 10-27 指示的范围内。A 与 B 给管理员看到的原因相同(都点名工具),差别只在事故时运维读到的两行日志。
    • ③ 防 AI 犯错:A 下 agent 读到 ERROR 会去查网络,写 provider 的 AI 会学到「这个码=任何可降级的情况」;B 的记录直接点名钉子不符,响亮且可定位。
    • ④ 创业阶段不扩散:A 什么都不加;B 加一个错误码和一个物化分支(S,无新门禁);C 行数最少但改语义。
      Prior rulings read: toolsSnapshot, upstream unavailable, degraded channel → ruling 6506797088 (shape B; channel left to the dev as a named question); thread: [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; repo: objectstack-ai/objectstack

    Recommendation

    B, keeping the backoff re-check (the dev measured that it recovers a restored server, the same reason a vault or env credential refusal is retried). Not A: a published marker would say something false in every incident log. Not C: it rewrites the meaning every provider relies on. Confidence gap: B crosses into the spec lane (a new error code is Clause-②: yes), so its PR owes a CONTRACT_REVIEW_TIER review.

    Self-check: 只看①选 B;②③④ 是否翻转:否(④ 偏 A,但只影响工作量,不翻字母)。

    After the ruling

    • A: re-claim with Resume-from above; the dev opens the PR from 68d8770f as it stands.
    • B: re-claim with Resume-from; the same branch adds the spec marker and the materializer branch; contract review at CONTRACT_REVIEW_TIER before landing.
    • C: re-claim; spec TSDoc and both log lines change in the same PR.
  7. objectstack-fleet commented on Oct 31, 2026

    @objectstack-fleet
    ContributorAuthor

    director#2 · os-tesla · session_01L9qibxdiybU8ehPKBroYE3

    Brief-claimed-by: session_01L9qibxdiybU8ehPKBroYE3 · batch #433 · 2026-10-31T15:17Z

    Director seat (the maintainer's decisions thread, 2026-10-31). This card is presented to the maintainer in batch #433 (five cards: #27884, #27401, #27949, #28117, #28220 — the in-flight-blocked ones first) with the latest four-facet letter on this card as the seat's reading; the seat's increment and the maintainer's letter follow in the ruling comment.

  8. objectstack-fleet commented on Oct 31, 2026

    @objectstack-fleet
    ContributorAuthor

    director#2 · os-tesla · session_01L9qibxdiybU8ehPKBroYE3

    Ruling: batch #433 item 2 · letter B · maintainer 「同意」 2026-10-31T15:49Z

    Director seat (the maintainer's decisions thread). Presented in batch #433 (five in-flight-blocked cards, detailed letters in the project's decision doc; brief-claimed on this card at 15:16Z–15:18Z). The maintainer answered the whole batch at 2026-10-31T15:47Z, verbatim: 「同意」 = every item at the seat's recommendation.

    What the ruling binds (letter B): a toolsSnapshot pin mismatch degrades the instance through a marker of its own — one new spec error marker (for example CONNECTOR_UPSTREAM_CHANGED: class, structural guard, error-code ledger row in @objectstack/spec) and one materializer branch in service-automation with its own wording ("serves a tool definition that differs from its toolsSnapshot; re-checking with backoff"), on the same 5 s → 5 min backoff the dev measured to recover a restored server. CONNECTOR_UPSTREAM_UNAVAILABLE keeps its declared meaning. The branch's connector-mcp half (68d8770) stands; only its one throw changes. Not taken: A (ship as on the branch, the published marker says "unreachable" in every incident log), C (widen the marker's meaning for every provider). The shape ruling 6506797088 (letter B, per-tool record) is unchanged.

    Workload of the letter: domain:server with a spec-lane edit · 1 PR across spec + service-automation + connector-mcp · S · Metadata-protocol: added (the toolsSnapshot key, as already ruled) plus a new error code ⇒ Clause-②: yes · contract review at CONTRACT_REVIEW_TIER before landing.

    Execution: the #27293 epic PM re-claims with Resume-from: claude/issue-27401-mcp-tool-snapshot@68d8770f and Ruling-ref: naming this comment; needs-user-decision → pm:dispatched is the epic PM's stroke (pm:epic stays). Analysis read: 6538081826.

  9. objectstack-fleet commented on Nov 1, 2026

    @objectstack-fleet
    ContributorAuthor

    epic:#27293#1 · sunhaolin · session_017xRBcVBKAFk1ntXSjvPt7U

    Claim: PM loop round 4 (re-claim after the ruling)
    Session: session_017xRBcVBKAFk1ntXSjvPt7U
    Account: sunhaolin (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-27401-mcp-tool-snapshot
    Worktree: objectstack-issue-27401
    Domain: domain:server
    Seat: epic:#27293#1
    File surface: packages/spec/src/integration/connector-provider-errors.ts (the new marker class and guard), packages/spec/src/api/error-code-ledger.zod.ts (its ledger row) and whatever the error-code and route gates require beside them; packages/services/service-automation/src/plugin.ts (one materializer branch with its own wording, same backoff); packages/connectors/connector-mcp/src/** (the branch's toolsSnapshot work, its throw switched to the new marker); content/docs/automation/connectors.mdx; changesets for the touched packages (stop on breach; explain in the report)
    Container & model: M, mode:cloud (a packages/spec/src/** surface plus other packages is build-heavy; one card, one container), model: opus (TIER_DEFAULT build; the ruling owes a CONTRACT_REVIEW_TIER review before landing), effort: xhigh (p1)
    Clause-②: yes (widening)
    Metadata-protocol: added
    Responsibility: n/a — not a defect card
    Thread-read: 6545635244
    Ruling-ref: 6545635244
    Resume-from: claude/issue-27401-mcp-tool-snapshot@68d8770f
    Premise: holds (read at origin/main ea74ebf9 2026-11-01T03:06Z: the branch head is 68d8770f; no marker other than CONNECTOR_UPSTREAM_UNAVAILABLE degrades without failing boot)
    Serial constraints cleared: open PR #28357 (#28311, this seat) edits connector-mcp/src/mcp-provider.ts and the TSDoc of connector-provider-errors.ts — it lands first or this branch merges it; open PR #28500 (another seat) edits service-automation/src/plugin.ts — ordinary concurrency, the later lander merges origin/main.

  10. objectstack-fleet commented on Nov 1, 2026

    @objectstack-fleet
    ContributorAuthor

    epic:#27293#1 · sunhaolin · session_017xRBcVBKAFk1ntXSjvPt7U

    Dispatched (mode:cloud) · 2026-11-01T03:07Z · claim 6552681469

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

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions