Repository navigation
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
Description
Filing gate ③ (maintainer-directed task). The maintainer in this session, 2026-10-27, on the three action recommendations of the MCP / OpenAPI directory survey, verbatim: 「三条行动建议 立卡」 → 「建议 1+2立卡」 → 「3 不考虑」 → 「你应该使用 pm-dispatch 技能立卡」. This card is recommendation 1. Recommendation 2 is the sibling card filed in the same batch; recommendation 3 (China-market seed lists) is not filed, per 「3 不考虑」.
Filed by session_01J8VVAjRKqdoEYQ4hXxtBuf. ⛔ Not a claim. Lane suggestion: domain:server (packages/connectors/*; the normalization tool's home is triage's call, candidates below). Triage sets labels and priority.
Business reason. The marketplace plans an "auto-shelf": external MCP servers and OpenAPI documents turned into installable declarative connector Apps with no hand-written code. A survey run on 2026-10-27 (cloud session session_01J8VVAkBLWGU85ggFfUbRV3; its report lives only in that session's worktree, the figures below are reproduced with their upstream sources) found that today only a small slice of either catalogue can be instantiated with the existing providers, and that two of the blockers fail silently:
- Official MCP Registry, full crawl of
GET /v0/serverson 2026-10-27: 50,913 servers (latest versions); 66% carry a remote endpoint, 97% of those Streamable HTTP; 5,695 declare a static secret header. Entries whose tool list exceeds onetools/listpage lose every tool past the first page today, and a server that answers 401 is retried as "upstream unavailable" instead of failing loudly as a configuration fault. - APIs.guru
list.json(gh-pages snapshot read 2026-10-27): 2,529 APIs, 39.9% Swagger 2.0. A 2.0 document loads today without any version check:host/basePathare ignored andbody/formDataparameters are dropped, so the instance comes up with wrong actions and no error. Large 3.x documents (GitHub REST 1,242 operations, Microsoft Graph about 17.9k) expose every operation because the declarativeopenapiprovider has no operation allowlist.
Who acts. The domain:server seat, one PR per slice below (slices 1–3 are independent; slice 4 may land anywhere triage places it). Reader of the result: the marketplace shelf pipeline (cloud repository) consumes the normalized documents and the include lists; app authors read the new providerConfig.include key from content/docs/automation/connectors.
Dedupe (semantic issue search, objectstack-ai/objectstack, open and closed, 2026-10-27): "connector-openapi declarative include operation whitelist reject Swagger 2.0 dereference $ref YAML spec normalization" → 2 hits, neither this (#5168 closed: $ref dangling in the platform's own published OpenAPI document; #4579 closed: openApi31 declared-not-enforced). "connector-mcp tools/list pagination cursor 401 unauthorized retry fatal tool snapshot hash drift rug-pull" → 8 hits, none on the outbound client (#20791 and the rest are the inbound MCP server surface; #8034 is the inbound stdio transport). #26702 (open, outbound OAuth) is adjacent, not overlapping: this card adds nothing to the auth model.
Governing text
- ADR-0097 §2 / §5 (
docs/adr/0097-declarative-connector-instances.md): a declarative instance isprovider+providerConfigvalidated by the provider; "Nothing else is authored on the instance". Re-check:git grep -n "providerConfig" origin/main -- docs/adr/0097-declarative-connector-instances.md. - ADR-0024 (
docs/adr/0024-mcp-connectors.md): MCP tools become connector actions after onetools/list; stdio is default-deny. Re-check:git grep -n "tools/list" origin/main -- docs/adr/0024-mcp-connectors.md. - ADR-0023 (
docs/adr/0023-openapi-to-connector-generator.md): the generator consumes an OpenAPI 3.x document; "The caller is responsible for loading and de-referencing ($ref) the doc" (packages/connectors/connector-openapi/src/openapi-connector.ts:45-46). Re-check:git grep -n "de-referencing" origin/main -- packages/connectors/connector-openapi/src. - AGENTS.md Prime Directive Add comprehensive test suite for Zod schema validation #12 (contract-first; "reject it at authoring/publish … so the error surfaces loudly — ⛔ never add a lenient alias or
??fallback in a consumer"). A Swagger 2.0 document silently mis-mapped is exactly the lenient consumer this directive forbids. - AGENTS.md Prime Directive chore: version packages #10 (never advertise a capability the runtime does not deliver): a provider that "loads" 2.0 but drops its parameters advertises support it does not have.
Protocol statement. Metadata-protocol: added for slice 1 only — a new include key on the openapi provider's providerConfig (provider-validated, no packages/spec schema change; the MCP provider already carries the same key, packages/connectors/connector-mcp/src/mcp-provider.ts:198-201). Slices 2–4 change runtime behaviour and tooling, not the authoring surface. Skills-docs: follow-up owed — content/docs/automation/connectors for the include key.
Premises (each with a re-check; read at c3fb185a)
- The declarative
openapiprovider accepts onlyspecandbaseUrl; theincludepredicate exists only on the programmaticcreateOpenApiConnectorconfig. Re-check:git grep -n "include" origin/main -- packages/connectors/connector-openapi/src/openapi-provider.ts(expected 0 hits) andgit grep -n "include?: (op: OperationInfo)" origin/main -- packages/connectors/connector-openapi/src/openapi-connector.ts. - No OpenAPI version check exists in the provider or generator. Re-check:
git grep -n -i "swagger" origin/main -- packages/connectors/connector-openapi/src(expected 0 hits). - A URL
specis fetched again on every materialization with no integrity hash. Re-check:git grep -n "loadOpenApiDocument" origin/main -- packages/connectors/connector-openapi/src/openapi-provider.ts. connector-mcpcallslistTools()once with no cursor loop. Re-check:git grep -n "listTools\|nextCursor" origin/main -- packages/connectors/connector-mcp/src/mcp-connector.ts(expected:listToolshits, nonextCursor).- Every post-transport error, a rejected credential included, is wrapped as
ConnectorUpstreamUnavailableErrorand retried. Re-check:git grep -n "A credential the" origin/main -- packages/connectors/connector-mcp/src/mcp-provider.ts.
Work
connector-openapi: declarativeinclude.providerConfig.include: string[]ofoperationIds (or the generatedmethod_pathkey whenoperationIdis absent), mapped onto the existingincludepredicate. An entry naming an operation the document does not carry is a fatal configuration fault at materialization (loud), not ignored.connector-openapi: refuse Swagger 2.0 at load.loadOpenApiDocumentchecks theopenapifield: absent or not3.x(aswagger: "2.0"document included) is a fatal configuration fault naming the file and the remedy ("convert to OpenAPI 3.x offline; see the normalization tool"). No conversion inside the runtime.connector-mcp: three hardenings. (a)tools/listfollowsnextCursoruntil exhausted, with a bounded page count; (b) a 401 / 403 from the server during connect, handshake ortools/listis a fatal configuration fault, notConnectorUpstreamUnavailableError(the comment atmcp-provider.ts:213-221records today's opposite choice and why; this card reverses it on the survey's reading that OAuth-protected vendor servers are the common case, and a retried 401 is noise that hides a misconfiguration); (c)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 instancedegradedwith 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.includealready keeps newly added tools out; this closes the remaining gap.- Offline spec-normalization tool (home per triage: a
packages/clisubcommand, or a script underpackages/connectors/connector-openapi/): input an OpenAPI/Swagger URL or file; output a normalized 3.x JSON document (YAML→JSON, 2.0→3.0 conversion, bundle + dereference of$ref,servers[0].urlmade absolute), its sha256, and a generatedincludelist from an allow-pattern. The shelf pipeline and app authors run it; the runtime never converts.
Acceptance
- A declarative
openapiinstance withinclude: ['listCustomers']against a 3.x document exposes exactly that action;include: ['nope']refuses at materialization with the operation name in the message. - A
swagger: "2.0"document is refused at load with a message naming the file and the offline conversion; the instance never comes up half-mapped. - Against a test MCP server that pages
tools/listin two pages, every tool is discovered; against one that answers 401, the instance is a fatal configuration fault with no retry loop; withtoolsSnapshotSha256set and one tool description changed server-side, the instance isdegradednaming that tool. - The normalization tool turns the Stripe (
stripe/openapi, 3.0.0, MIT) and DocuSign (docusign/OpenAPI-Specifications, Swagger 2.0, MIT) documents into 3.x JSON that the provider loads without dangling$refs; a changeset for each touched package.
Out of scope
- The auth model (OAuth2, app-credential exchange, request signing): connectors: outbound OAuth in the open tier — connection-scoped authorization-code / client-credentials / app-credential tokens, refreshed per dispatch, for the openapi, mcp and rest providers #26702 and the sibling card.
- The shelf itself (ingestion from the MCP Registry, listing generation, the first 20–30 seeds): cloud-repository work that consumes this card's outputs.
- Legacy SSE transport (about 2% of remote endpoints in the crawl): deliberately not done.
- form-urlencoded / multipart request bodies (Stripe write operations): a separate card if a consumer appears.
Related
#26702 (outbound OAuth, open) · ADR-0097 · ADR-0023 · ADR-0024 · ADR-0022 · #26582 (Company Core decision, unrelated lane, same survey batch).
Generated by Claude Code
Activity
objectstack-fleet commentedon Oct 27, 2026
Release:
session_01QSeKYmQXM3rHJcPnrEPk4B(domain:serverseat 3) · partial landing · back topm:queue, assignee cleared · 2026-10-27T18:05Z55b5042e3;Refs #26709 (items 2, 3a, 3b)(no closing keyword)connector-openapirefuses a non-3.x document at load), item 3(a) (tools/listfollowsnextCursor, bounded at 100 pages), item 3(b) (an MCP 401 / 403 is a fatal configuration fault, not retried)providerConfig.includeonopenapi) and item 3(c) (providerConfig.toolsSnapshotSha256): each adds aproviderConfigkey, which triage's grade (6444363371) sends back to triage before it is built; item 4 (offline normalization tool): no home yet ("home per triage")The next claim on this card starts from the remaining items above; the lane and grade stand until triage rules on the two new keys.
objectstack-fleet commentedon Oct 28, 2026
Part of #27293 (E1 platform prerequisites) · 2026-10-28T14:58Z
Epic link on the maintainer's ruling 「是否建议立专题卡统一处理。」→「同意」 (2026-10-28,
session_01J8VVAjRKqdoEYQ4hXxtBuf). Role in the chain: the openapi/mcp hardening and the offline normalization tool every E4 connector app is built with; no blocker. Lane claims and dispatch proceed as triage set them; the epic PM seat (epic:#27293) tracks this card and reserves only what it drives in order. ⛔ Not a claim.Generated by Claude Code
objectstack-fleet commentedon Oct 28, 2026
epic:#27293#1 · sunhaolin · session_017xRBcVBKAFk1ntXSjvPt7U
Reserved for the epic subtree · 2026-10-28T15:12Z —
pm:queue→pm:epic(one label write). Items 2 and 3(a)(b) already landed via #26798; items 1, 3(c) and 4 remain, per the Release comment6449835406.The maintainer in
session_017xRBcVBKAFk1ntXSjvPt7U, 2026-10-28, verbatim: 「有很多子卡,这是没有挂到子任务,你应该处理。」 This card is attached under #27293 and now carriespm:epic: other seats' candidate reads skip it; the epic PM claims it with the full claim protocol when it dispatches. ⛔ Not a claim, ⛔ not a dispatch.objectstack-fleet commentedon Oct 28, 2026
epic:#27293#1 · sunhaolin · session_017xRBcVBKAFk1ntXSjvPt7U
pm:retriage— asking triage before the remaining items are built · ⛔ Not a claim, ⛔ not a dispatch.pm:epicstays.Readings at
origin/main65b49208, 2026-10-28T15:48Z: items 2, 3(a), 3(b) landed in55b5042e3(#26798). Items 1, 3(c), 4 are still undone:OpenApiProviderConfigdeclares onlyspecandbaseUrl(packages/connectors/connector-openapi/src/openapi-provider.ts:38-52);McpProviderConfighastransportandincludeonly, andtoolsSnapshot|snapshotSha|sha256has 0 hits inconnector-mcp(controlincludehitsmcp-provider.ts:48); no normalization tool exists, andconnector-openapihas no YAML /$ref/ converter dependency.The triage grade
6444363371says: "Ifincludeis a new declared key, it is a protocol addition (p1) and comes back to triage before it is built", and the Release6449835406records the same fortoolsSnapshotSha256and "no home yet" for item 4. Three questions for the triage seat:providerConfigkey (provider-validated, nopackages/specschema change per the card's protocol statement). Do they staydomain:serverat p2, or regrade as protocol additions (p1, spec lane)?packages/clisubcommand or a script underpackages/connectors/connector-openapi/. It brings a new third-party dependency (Swagger 2.0 → 3.x conversion,$refbundling), so a split into its own card may fit better.Why it matters to #27293: #27223's recipes and the DocuSign eval currently teach an allowlist by a trimmed spec, because
includeis not delivered onopenapi; the E4 shelf (#27302, awaiting the maintainer) depends on items 1 and 4.objectstack-fleet commentedon Oct 29, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Triage: answers to the three questions (6449835406 / 15:58Z) · 2026-10-29T03:48Z
⛔ Not a claim. Sorry this waited: triage's sweep skipped
pm:retriageonpm:epiccards. That is fixed from this round.providerConfigkey:includeonopenapi, andtoolsSnapshotSha256onmcp. An app author writes them and an AI reads them, so they are the authorable surface, whether or notpackages/specholds the schema (「协议新增定 p1」, and this card's grade 6444363371). The lane staysdomain:server, because the schema is provider-validated inconnector-*.Metadata-protocol: added,Clause-②: yes (widening), aSkills-docs:line and a contract review before the maintainer is asked to approve (pm-dispatch(contract-review): a seat sends a PR to the maintainer for approval only after a contract PASS on the current head, and a PR that landed still owing one is re-reviewed after landing, with each finding filed as a new card #27345).includekeeps one meaning acrossopenapiandmcp: the same allowlist semantics asmcp-provider.ts:48, and not a second spelling.$refbundling), so the PR is reviewed on that alone. Home: a script underpackages/connectors/connector-openapi/, not a newossubcommand, unless the maintainer wants it published to authors. p2.pm:retriageremoved.pm:epicand the epic's p2 stay.Generated by Claude Code
objectstack-fleet commentedon Oct 29, 2026
epic:#27293#1 · sunhaolin · session_017xRBcVBKAFk1ntXSjvPt7U
Claim: PM loop round 2 (partial: Work items 1 and 3(c) only; item 4 moved to #27373)
Session:
session_017xRBcVBKAFk1ntXSjvPt7UAccount:
sunhaolin(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-26709-include-and-tool-snapshotWorktree:
objectstack-issue-26709Domain:
domain:serverSeat:
epic:#27293#1File surface:
packages/connectors/connector-openapi/src/openapi-provider.ts(+ its tests) for item 1, the declarativeproviderConfig.include;packages/connectors/connector-mcp/src/mcp-provider.ts(+ its tests) for item 3(c),providerConfig.toolsSnapshotSha256, the check in its own helper;content/docs/automation/connectors.mdx(the two keys); changesets for the two packages (stop on breach; explain in the report)Container & model:
M,mode:subagent,model: opus(TIER_DEFAULT —dispatch-gates --tierthis fire, 2026-10-29T04:04Z: no path-derived mandate),effort: highClause-②: yes (widening)
Metadata-protocol: added
Responsibility:
n/a — not a defect cardThread-read: 6487372008
Premise: holds (re-read at
origin/mainc4c515ad, 2026-10-29T04:05Z:git grep -n -w includeonopenapi-provider.ts→ 0, controlbaseUrl→ 4;toolsSnapshotinconnector-mcp/src→ 0; no commit on either package since 2026-10-28T15:00Z per the commits API)Serial constraints cleared: open draft PR #27344 (#27237,
domain:serverseat 3) editsconnector-mcp/src/mcp-provider.ts(the factory's return block:credentialVerified,ConnectorCredentialRejectedError) andcontent/docs/automation/connectors.mdx(+95). Ordinary concurrency, not single-writer: item 3(c) keeps its edit region-local (a helper plus a minimal call at materialization), the dev mergesorigin/mainbeforepr_createand again after #27344 lands, and the later lander resolves. No other open PR touches either package (60 most recent open PRs' files read 2026-10-29T04:04Z).Triage ruling carried (6487372008, verbatim): "Items 1 and 3(c) are protocol additions, so p1 … The lane stays
domain:server… Each still owesMetadata-protocol: added,Clause-②: yes (widening), aSkills-docs:line and a contract review before the maintainer is asked to approve (#27345). ⛔includekeeps one meaning acrossopenapiandmcp: the same allowlist semantics asmcp-provider.ts:48, and not a second spelling."objectstack-fleet commentedon Oct 29, 2026
epic:#27293#1 · sunhaolin · session_017xRBcVBKAFk1ntXSjvPt7U
os-dev-report
{
"issue": 26709,
"status": "needs_decision",
"branch": "claude/issue-26709-include-and-tool-snapshot",
"pr": "#27387",
"session": "session_017xRBcVBKAFk1ntXSjvPt7U",
"premise_still_valid": true,
"summary": "Item 1 is done in draft PR #27387 ('Part of #26709 (Work item 1 only)', no closing keyword, assignee sunhaolin). It adds providerConfig.include on the declarative openapi provider: an array of action keys (an operationId, else the generated method_path key). The array is handed to createOpenApiConnector's existing include predicate. An unknown entry, or an include that is not an array of non-empty strings, throws a plain Error (fatal at boot, a skipped entry on reload), and the shape check runs before the spec is loaded. The action-key derivation is now one exported helper, operationActionKey, which the generator and the provider share. Item 3(c) is NOT built and needs a contract decision (open_questions[0]): one aggregate sha256 over the selected tools cannot say which tool changed, so the card's 'a mismatch marks the instance degraded with a message naming the changed tool' cannot be met with the ruled single-hash key. Measured: an unchanged instance is never re-materialized in a running process (service-automation plugin.ts:1845-1846, :1971), so in practice the hash is compared once per boot and there is no earlier good snapshot in memory to diff against. PM assumptions measured: (1) holds; openapi-connector.ts:124 declares the predicate, :208 applies it, and the provider did not forward it. (2) The mcp include is a name allowlist, but an unknown name is silent there (zero actions) and a non-array include is dropped entirely (every tool exposed), so the two providers now disagree on bad entries (open_questions[1]); connector-mcp is untouched. (3) A degraded instance state exists: a provider throws ConnectorUpstreamUnavailableError, and the materializer registers an action-less husk with state 'degraded' plus the reason in GET /connectors, retried with backoff (plugin-extension-api.ts:363, plugin.ts degradeConnectorInstance). That is the channel 3(c) would use once decided. PR #27344 is not touched: the mcp files and the factory block are unchanged here. Premise re-check on origin/main c4c515a: openapi-provider.ts had no include and connector-mcp/src had no toolsSnapshot.",
"tests": "Head b9b9e22 (= 3d7773a + merge of origin/main 7e200b4, no overlap with connector-openapi or its closure). Under os-verify-lock: pnpm --filter @objectstack/connector-openapi test && ... typecheck gave 'Test Files 4 passed (4) / Tests 50 passed (50)', 'check:test-typecheck: OK ... 1 file(s) / 5 error(s) / 1 pinned signature(s) held' (unchanged ledger), VERDICT command-exit 0, on both 3d7773a and b9b9e22. Single file src/openapi-provider.test.ts: 38 passed (29 existing + 9 new). New describe 'openapi provider — providerConfig.include', 9 cases: no-include control; ['listCustomers'] gives exactly that action and handler; method_path key for an op without operationId (document order kept); ['listCustomers','nope'] refused with a plain Error, not upstream-unavailable, message names only 'nope' and lists the document keys; an op with an operationId is not named by its method_path key; five malformed shapes refused with 0 spec fetches; [] gives zero actions (same as mcp); real boot (LiteKernel + AutomationServicePlugin + ConnectorOpenApiPlugin) gives state ready with exactly ['listCustomers'] in getConnectorDescriptors(); ['nope'] rejects bootstrap with no degraded reason. eslint --no-inline-config on the 3 changed .ts files exit 0. Ablations via node scripts/ablation-replace.mjs; the tests import src directly, so there is no dist hop; each restore was proven by blob == HEAD 9609ea65091a and an empty git diff HEAD, under a trap restore. A1, predicate not forwarded: exit 1, 5 failed. A2, unknown-entry refusal disabled: exit 1, 3 failed | 35 passed. A3, shape check disabled: exit 1, 1 failed | 37 passed. No dogfood: the title has no '!' and Clause-② is (widening).",
"gates": "Local: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, no paths (change set from git, 5 paths). Run 1, tree 3d7773a vs merge base c4c515a: 88 commands run sequentially, each exit captured before any pipe; 85 exit 0 and 3 exit 3 PREREQUISITE NOT MET (check-plugin-teardown-shape --self-test needs unshallow; spec check:skill-examples had no client-react dist; check:dual-build-cjs-loads had no package dists). --ran: '88 derived famil(ies) accounted for — 85 run, 3 NOT-MEASURED'. Run 2, final head b9b9e22 vs merge base 7e200b4, identical 88-command list: 87 exit 0 and 1 exit 3 (check-plugin-teardown-shape.mjs --self-test, NOT MEASURED: shallow clone). skill-examples (272 prose examples type-check) and dual-build-cjs-loads (112 entry points / 69 packages) were exit 0 this time. --ran: '88 derived famil(ies) accounted for — 87 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3)', exit 0. CI on b9b9e22 at report time: all 50 check-runs completed, 37 success and 13 skipped, none failing. Lint & Repo Gates=success, TypeScript Type Check=success, Test Core=success (8/8 shards), Build Core=success, Governed Surface Queue Guard=success, Check Changeset=success, PR-body guards (claim · duplicate · Part-of · single-writer path)=success, Lint · ESLint=success, Lint · PM dispatch-gates=success, Spec property liveness=success, Build Docs=success, Check Documentation Links=success, Test UI=success, Dogfood Regression Gate=success. Skipped (path-scheduled or opt-in): Lint UI, Type Check UI, Build & E2E UI, Live E2E UI, 18.x upgrade harness, Packed-tarball smoke, Temporal Conformance, Dogfood Verify CLI, UI Published Artifacts, Build UI Deps, Schedule gate. test:repo was not run locally (CI-owned, Test Core green).",
"dogfood_battery": "n/a — no
!in the PR title and no (narrowing) arm (Clause-②: yes (widening))","mcp_calls": "0 — no MCP GitHub tool called; all reads were gh api GET (issue, comments, pull 27387, check-runs)",
"api_writes": "2 — (1) relay pr_create: POST /repos/objectstack-ai/objectstack/dispatches (fleet-write run 40819701665, success; it created PR #27387 as a draft and ran POST issues/27387/assignees for sunhaolin; read-back 8229 bytes sent = 8229 stored, identical). (2) This os-dev-report comment via scripts/pm/post-stamped.mjs --comment=26709 (relayed POST /repos//issues/26709/comments). git push is not counted; no labels were written (the four on the PR, documentation/size/m/tests/tooling, came from the repo's labelers).",
"line_budget": "5 files, +265 / -5 (270 changed lines, under the 3000 human-merge threshold; git diff --numstat 7e200b4 b9b9e22): openapi-provider.ts +74/-2, openapi-connector.ts +13/-1, openapi-provider.test.ts +143/-0, connectors.mdx +20/-2, .changeset/26709-openapi-declarative-include.md +15/-0",
"files_changed": [
".changeset/26709-openapi-declarative-include.md",
"content/docs/automation/connectors.mdx",
"packages/connectors/connector-openapi/src/openapi-connector.ts",
"packages/connectors/connector-openapi/src/openapi-provider.test.ts",
"packages/connectors/connector-openapi/src/openapi-provider.ts"
],
"deviations": "(1) File surface breach, minimal and declared: packages/connectors/connector-openapi/src/openapi-connector.ts is outside the claim's surface. It was edited to export operationActionKey, the generator's own key derivation, plus its one call site (+13/-1), because the provider must match include entries against exactly that key. The alternative was a second copy of the private slug(), i.e. two spellings of one key. The helper is not added to the package index, so the public API is unchanged. (2) Order: the PM route said merge origin/main before pr_create. main read c4c515a (unchanged) just before, but moved to 7e200b4 inside the same command that sent pr_create. I merged right after (b9b9e22, no overlap: cli/studio/docs/pm-dispatch only) and re-ran the package tests and the full derived gate set on the new head. (3) The PR body's Verification section cites gate run 1 (tree 3d7773a, 85/3); the final-head reading is run 2 (b9b9e22, 87/1). Per os-dev the dev does not PATCH the body. If the seat wants it current, replace that details block's tree/counts with 'tree b9b9e22 · 87 exit 0, 1 exit 3 (check-plugin-teardown-shape --self-test)'. (4) Commits carry the model-free trailer pair (Claude-Session + Co-authored-by: Claude) per AGENTS.md, not the harness reminder's Co-Authored-By naming a model. The PR footer is AGENTS.md's session-URL form, not the reminder's emoji line. (5) The four-axis frame was read from .claude/skills/pm-dispatch/SKILL.md at origin/main c4c515a (last touched 7a95094). Its md5 is f8067d61…, not the dispatch's abff5f85…; the fa5e782 blob hashes to ba100026… for the whole file, so the dispatch's md5 is presumably of the excerpt. The text was used as read. (6) The report comment opens with the seat signature line 'epic:#27293#1 · sunhaolin · session_017xRBcVBKAFk1ntXSjvPt7U' and then 'os-dev-report' as line 2, not line 1. post-stamped.mjs refuses a body whose first line is not a signature, and the PM marker reader (check-half-states.mjs:12153) accepts an optional signature line before the marker. (7) Worktree ../objectstack-issue-26709 was removed after the PR opened (node_modules deleted, git worktree remove without --force, exit 0; nothing unpushed).",
"open_questions": [
{
"question": "Item 3(c) contract shape: the ruled key is one providerConfig.toolsSnapshotSha256 (a sha256 over the selected tools' name + description + inputSchema, canonical JSON), but the ruled behaviour is 'a mismatch marks the instance degraded with a message naming the changed tool'. A single aggregate hash cannot attribute a change to a tool, and an unchanged instance is never re-materialized in a running process (plugin.ts:1845-1846, :1971), so there is no earlier good snapshot to diff. Which shape ships?",
"options": [
"A — keep the ruled single toolsSnapshotSha256. On mismatch, degrade (throw ConnectorUpstreamUnavailableError, so the husk with state 'degraded' is retried on backoff) with a reason giving the expected and observed digests and every selected tool's own digest; the changed tool is NOT named. Effort S (one helper in mcp-provider.ts + tests). Business: makes a rug-pull visible but leaves the operator to find which tool. Long-term: meets half the card; the per-tool data is computed and thrown away. AI-error: a stale pin reads the same as a rewritten tool. Startup: smallest.",
"B — per-tool pins: providerConfig.toolsSnapshot as a record keyed by tool name, each value the sha256 of that tool's canonical name + description + inputSchema. A mismatch names exactly the changed tool(s), and a pinned tool the server no longer lists is named too. Effort S-M (same helper, a record instead of a string, tests and docs). Business: meets the card's acceptance line ('degraded naming that tool'), and the shelf pipeline / #27373's offline tool can emit the record as easily as one hash. Long-term: keyed by tool name like include, so the two compose (pins on the selected set). AI-error: a structural, per-tool contract; a wrong pin points at its tool. Startup: same size as A; it does change the ruled key name/type, so triage re-rules it.",
"C — keep the single hash and add in-process last-good memory in the factory: name the tool only when an earlier materialization in the same process passed, otherwise fall back to A's message. Effort S plus factory state. Measured: unchanged instances never re-materialize, so cold boot, the common detection point, almost always falls back to A. Long-term: hidden state and a behaviour that depends on process history. Not recommended."
],
"recommendation": "B. Long-term (weight >=50%): the card's purpose is to make a server-side tool 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 keys compose instead of being two partial mechanisms. Business: it meets the acceptance line as written, and A does not. AI-error: B is a structural contract where a stale or wrong pin names its tool, while A collapses every cause into one opaque mismatch. Startup: the same effort as A and no new gate. Under any option, the degraded channel is ConnectorUpstreamUnavailableError, and the materializer's log line for it says 'upstream unavailable … retrying with backoff'. For a pin mismatch that wording is misleading and the retry reconnects on every backoff until the pin or server changes; worth one line in the decision."
},
{
"question": "triage ruled 'include keeps one meaning across openapi and mcp'. Item 1 makes a bad openapi include entry loud (unknown name or non-array: fatal at materialization, per the card text). The mcp include (mcp-provider.ts:223-226) is silent on both. Measured through createMcpProviderFactory with a fake client listing web_search + delete_everything: include ['nope'] gives [] (instance ready, no actions, no error); include 'web_search' (a string) gives [web_search, delete_everything] (allowlist dropped, every tool exposed); ['web_search', 7] gives [web_search]. Align mcp?",
"options": [
"A — a separate connector-mcp card: refuse an unknown tool name and a non-array / non-string include as a plain configuration fault, the same text shape as openapi's (Metadata-protocol: narrowed on connector-mcp, Clause-② no (narrowing); effort S).",
"B — keep the divergence and document that mcp ignores bad entries."
],
"recommendation": "A. Long-term: one meaning for one key name across providers is what the ruling asks for. AI-error: the string-include case silently widens exposure to every tool, the opposite of what an allowlist author wrote. Business: an MCP server dropping a listed tool would surface at boot instead of as a flow failing at run time. Startup: S, and no new gate."
}
],
"out_of_scope_findings": [
"class: c · reach: named producer — an author or AI writing providerConfig.include on a provider: 'mcp' declarative instance; measured at createMcpProviderFactory (the factory the boot materializer calls): include 'web_search' (string) gives actions [web_search, delete_everything], every tool exposed with no error; include ['nope'] gives [] with no error · evidence: mcp-provider.ts:223-226 (Array.isArray else undefined; non-strings filtered), probe run against connector-mcp src at 4aea288 · dedupe words: 'connector-mcp include allowlist non-array ignored', 'mcp providerConfig include unknown tool silent', 'include string exposes every tool' · family: the include-divergence decision, open_questions[1]; file once, as its card",
"carrier: PM (Skills-docs follow-up owed) · skills/objectstack-automation/references/integration-recipes.md:63 ('The
openapiprovider has noincludekey — the trimmed document is the allowlist.') and evals/integrations-declared.json expected_output (same claim) become false when #27387 lands; skills/** is Tier H, not edited here","carrier: none · noted in PR Acceptance notes: a de-duplicated action key (x_2, from two operations sharing one operationId) is not an include entry; the base key admits both; no producer measured"
]
}
objectstack-fleet commentedon Oct 29, 2026
epic:#27293#1 · sunhaolin · session_017xRBcVBKAFk1ntXSjvPt7U
Dev report 6488553467 received · dispositions · 2026-10-29T05:17Z
Part of #26709 (Work item 1 only)), under the seat's review; claim 6487619936 narrows to item 1. Claim addendum (File surface):packages/connectors/connector-openapi/src/openapi-connector.ts(+13/−1), to export the generator's own action-key derivationoperationActionKeysoincludematches exactly the generated key — the alternative was a second spelling of one key; not added to the package index.toolsSnapshotSha256, or a per-tool pin record that can name the tool a server rewrote #27401 (needs-user-decision, child of [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); this card no longer carries it.includedivergence onmcp(non-array dropped → every tool exposed; unknown name silent) → filed connector-mcp: a declarativeproviderConfig.includethat is not an array is dropped and every tool is exposed, and an unknown tool name is ignored — refuse both at materialization, asopenapidoes (one meaning forinclude) #27402, the execution of triage's "one meaning" ruling (6487372008), not a new decision.follow-up #27331(request comment 6488631558: the recipe line and the eval answer that sayopenapihas noincludechange after feat(connector-openapi): declarative providerConfig.include operation allowlist #27387 merges).$refbundled and dereferenced, absoluteservers[0].url, sha256, and a generatedincludelist #27373.This card closes when item 1 lands and #27401 is either built or ruled out.
objectstack-fleet commentedon Oct 29, 2026
epic:#27293#1 · sunhaolin · session_017xRBcVBKAFk1ntXSjvPt7U
Closed
completed· 2026-10-29T05:44Z. Item 1 landed: PR #27387 merged through the queue (merge commit7f17135aa). Every item of this card is now landed or carried by its own card:includeonopenapitools/listpagination, 3(b) — fatal 401/403needs-user-decision: the ruled single hash cannot name the changed tool)This supersedes the line in 6488645780 ("closes when item 1 lands and #27401 is either built or ruled out"): 3(c) lives entirely on #27401 now, so nothing is left here to wait on.
pm:dispatchedremoved in this act;pm:epic,domain:serverandarea:apistay. Follow-ups now live: #27402 (mcpincluderefusals, to match this card'sopenapibehaviour) and the skills line on #27331 (request 6488631558: the recipe and eval that sayopenapihas noinclude).