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
Triage: first grade —
enhancement·priority:p2·domain:server·area:api·pm:queue· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-27T14:16Z⛔ Not a claim, ⛔ not a dispatch.
includeand a loud Swagger 2.0 refusal; connector-mcp gainstools/listpagination, a fatal 401 and a tool-snapshot hash. Ifincludeis a new declared key, it is a protocol addition (p1) and comes back to triage before it is built.Generated by Claude Code
objectstack-fleet commentedon Oct 27, 2026
Claim: PM loop round 1 (partial: Work items 2 and 3(a)(b) only)
Session:
session_01QSeKYmQXM3rHJcPnrEPk4BAccount:
os-justinBranch:
claude/issue-26709-connector-load-faultsWorktree:
objectstack-issue-26709Domain:
domain:serverSeat:
domain:server#3File surface:
packages/connectors/connector-openapi/src/**(item 2: refuse a non-3.x document at load),packages/connectors/connector-mcp/src/**(item 3(a)tools/listpagination, 3(b) 401/403 as a fatal configuration fault), their tests, one changeset per package (stop on breach; explain in the report). ⛔ Nopackages/spec/**.Container & model:
L(two packages, three behaviours),mode:cloud,model: opus(dispatch-gates --tier: "no path-derived mandate … default opus"),effort: xhighClause-②: no (narrowing)
Metadata-protocol: narrowed
Responsibility: n/a — not a defect card
Thread-read: 6444363371
Premise: holds (seat re-check on
origin/main74db43ef:git grep -i swagger packages/connectors/connector-openapi/src→ 0 hits;mcp-connector.tscallslistToolswith nonextCursor;mcp-provider.ts:215still carries "A credential the …")Serial constraints cleared: none (no open
pm:dispatchedclaim declarespackages/connectors/**)Scope held back, and why: item 1 (declarative
include) and item 3(c) (toolsSnapshotSha256) each add aproviderConfigkey, and triage's grade (6444363371) says such a key comes back to triage before it is built. Item 4 (normalization tool) has no home yet ("home per triage"). The PR carriesRefs #26709 (items 2, 3a, 3b), neverFixes; this card is released back topm:queuewhen it lands, with the remaining items named.objectstack-fleet commentedon Oct 27, 2026
Dispatched (
mode:cloud) ·domain:serverseat 3 · 2026-10-27T15:20Zsession_01QSeKYmG4MEJUzuz837U8Uq(title "⚡ server 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 connector load faults"), outcome branchclaude/issue-26709-connector-load-faults, per claim6445373075(items 2, 3(a), 3(b) only).os-dev-reportcomment.objectstack-fleet commentedon Oct 27, 2026
os-dev-report
{
"issue": 26709,
"status": "done",
"branch": "claude/issue-26709-connector-load-faults",
"pr": "#26798",
"session": "session_01QSeKYmG4MEJUzuz837U8Uq",
"premise_still_valid": true,
"summary": "Items 2, 3(a), 3(b) only (claim 6445373075), PR body 'Refs #26709 (items 2, 3a, 3b)', no closing keyword. Item 2: connector-openapi loadOpenApiDocument passes every spec form (inline, URL, file) through assertOpenApi3Document; an absent or non-3.x openapi field (swagger 2.0 included) throws plain (the provider's existing fatal path), naming the source and the offline conversion; no runtime conversion. Item 3(a): the SDK-backed client's listTools follows nextCursor until exhausted, bounded at MCP_TOOLS_LIST_MAX_PAGES = 100; hitting the bound throws an error naming the bound (never a truncated list), which the provider classifies like any failed discovery (degraded + retried, see open_questions[0]). Item 3(b): the mcp provider's catch tells a credential rejection apart by the SDK's own StreamableHTTPError (401/403) or UnauthorizedError anywhere in the cause chain and throws plain (fatal at boot, never retried); McpError(401) and other statuses stay ConnectorUpstreamUnavailableError; the 'A credential the …' comment now states the new rule and its reason. No new error taxonomy, nothing under packages/spec. Premise re-check on BASE a485b5a: git grep -i swagger connector-openapi/src exit 1 (0 hits); nextCursor in mcp-connector.ts exit 1 (0 hits); 'A credential the' at mcp-provider.ts:215. Card assignee untouched (os-justin, set by the PM).",
"tests": "Head 2177e83. pnpm --filter connector-openapi --filter connector-mcp test under os-verify-lock: connector-mcp Test Files 5 passed (5), Tests 46 passed (46); connector-openapi Test Files 4 passed (4), Tests 41 passed (41); typecheck both OK (check:test-typecheck OK, 1 file / 5 errors / 1 pinned signature held, unchanged ledger); VERDICT command-exit 0. eslint on the 6 changed .ts files exit 0. New pins: openapi-provider.test.ts 'refuses a document that is not OpenAPI 3.x' (file/URL/inline swagger 2.0, missing field, 4.0.0; each asserts Error + NOT upstream-unavailable + message names the source and 'Convert the document to OpenAPI 3.x offline'; control 3.0.3/3.1.0/3.1 load); mcp-http-discovery-faults.test.ts (real Streamable-HTTP fixture via the real SDK client: two pages → alpha,beta,gamma with cursors [undefined,'page-2']; endless pager rejects with the bound message after exactly 100 tools/list calls; 401 and 403 → plain Error, message 'was refused by its MCP server (HTTP n)' + 'is not retried', server saw exactly one request; declarative boot via AutomationServicePlugin + ConnectorMcpPlugin against 401 → bootstrap rejects, no degraded reason, still one request after 300 ms); mcp-provider.test.ts 'a rejected credential is a configuration fault' (SDK error classes; controls StreamableHTTPError(503) and McpError(401) stay upstream-unavailable). Ablations via node scripts/ablation-replace.mjs (src imported directly, no dist hop; each restore proven blob == HEAD, git diff HEAD empty): A1 version gate → if (true): 4 failed | 25 passed, 'expected createOpenApiConnector: no base URL … to contain connector pets providerConfig.spe…'; A2 pagination → first page only: 2 failed, 'expected [alpha, beta] to deeply equal [alpha, beta, gamma]' (first A2 attempt was refused by the tool as a no-op, replacement already present; rerun with a distinct marker); A3 bound → silent truncation: 1 failed | 4 passed, 'promise resolved … instead of rejecting'; A4 credential rule → if (false): 7 failed | 22 passed, 'expected true to be false'.",
"gates": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (tree 1629349 for the derivation; change set 9 paths, 513 lines): 87 commands, run sequentially with exit captured before any pipe; 84 exit 0; 3 exit 3 PREREQUISITE NOT MET = NOT MEASURED: node scripts/check-plugin-teardown-shape.mjs --self-test (needs git fetch --unshallow; checker-health only), pnpm --filter @objectstack/spec run check:skill-examples (reads packages/client-react/dist, not built), pnpm check:dual-build-cjs-loads (reads every package dist, not built; Build Core owns it). The first 9 commands ran before the last commit and were rerun on 2177e83: all exit 0. --ran reconciliation with exit codes: '87 derived famil(ies) accounted for — 84 run, 3 NOT-MEASURED (3 DERIVED from a recorded exit 3)'. Also pnpm check:adr-0087-registration exit 0 (2 declared-breaking changesets, each not-required (no-migration-prescription)). PR check-runs at report time on 2177e83: all in_progress (Check Changeset, Lint · ESLint, Lint · repo & tooling gates, Type Check · workspace, Governed Surface Queue Guard, PR-body guards, …); 18.x upgrade harness and Packed-tarball smoke skipped (opt-in). Not waited on, per os-dev.",
"dogfood_battery": "2177e83a · os-verify-lock: VERDICT command-exit 0 (held 1412s) · pnpm --filter '@objectstack/dogfood^...' build && pnpm --filter @objectstack/dogfood test, no OS_TEST_SHARD, no path · Test Files 302 passed | 3 skipped (305) · Tests 2505 passed | 20 skipped (2525) · no failures",
"mcp_calls": "0 — no MCP GitHub tool called; all reads were REST (gh api GET: issue, comments, pull 26798, check-runs)",
"api_writes": "2 — (1) relay pr_create: POST /repos/objectstack-ai/objectstack/dispatches (fleet-write run 40538888684, success; created PR #26798 draft + POST issues/26798/assignees os-justin; read-back 13892 bytes sent = stored, identical); (2) this os-dev-report comment via scripts/pm/post-stamped.mjs: POST /repos//issues/26709/comments (relayed). git push not counted.",
"line_budget": "9 files, +498 / -15 (513 changed lines; under the 3000 human-merge threshold); src: openapi-provider.ts +45/-6 net, mcp-connector.ts, mcp-provider.ts; rest tests, 2 changesets, 1 docs page",
"deviations": "(1) Worktree: the shared checkout /home/user/objectstack was moved to a detached HEAD so the platform-designated branch could be checked out in the dedicated worktree ../objectstack-issue-26709 (a branch cannot be checked out twice); no empty-branch probe push (platform branch). (2) The harness attribution reminder asked for a Co-Authored-By trailer naming a model; per os-dev and AGENTS.md the commits carry the model-free pair (Claude-Session + Co-authored-by: Claude). (3) Package-level ablations ran on src/ (tests import src relatively), so no dist rebuild or ablation-dist-preflight leg applies.",
"files_changed": [
".changeset/26709-mcp-tools-list-pages-fatal-401.md",
".changeset/26709-openapi-refuse-swagger2.md",
"content/docs/automation/connectors.mdx",
"packages/connectors/connector-mcp/src/mcp-connector.ts",
"packages/connectors/connector-mcp/src/mcp-http-discovery-faults.test.ts",
"packages/connectors/connector-mcp/src/mcp-provider.test.ts",
"packages/connectors/connector-mcp/src/mcp-provider.ts",
"packages/connectors/connector-openapi/src/openapi-provider.test.ts",
"packages/connectors/connector-openapi/src/openapi-provider.ts"
],
"open_questions": [
{
"question": "Implementation trade-off (decided, recorded): what happens when tools/list pages past the 100-page bound on a declarative instance?",
"options": [
"A (shipped) — createMcpConnector throws a plain error naming the bound; the mcp provider's existing classification wraps it as ConnectorUpstreamUnavailableError, so the instance is degraded with that reason in GET /connectors and retried on backoff; never a truncated list",
"B — classify it fatal (plain) like the 401: boot fails"
],
"recommendation": "A, because the author has no config key that fixes an over-paginating server (it is upstream behaviour, the remedy is the server's), the degraded reason is loud and names the bound, and a fatal classification would take down a whole app boot for one upstream's behaviour; B is a one-line change in the provider's catch if the seat prefers it."
},
{
"question": "ADR-0097 §'Upstream availability' says 'The mcp provider classifies connect / tools/list failures as unavailable'; this PR carves out a rejected credential (401/403) as a configuration fault, consistent with the same section's split (unresolvable credentialRef fatal; openapi spec fetch 401 fatal). docs/adr/** is governed (Tier H) and outside this claim's file surface, so the ADR text is not edited. Who amends it?",
"options": [
"A — the seat (or maintainer) lands a one-line amendment to ADR-0097 naming the 401/403 exception, citing this PR",
"B — leave the ADR sentence as is"
],
"recommendation": "A, because AGENTS.md Prime Directive #13 wants a reversal of recorded behaviour written into the ADR; the code comment already cites ADR-0097's split, so the amendment is one sentence."
}
],
"out_of_scope_findings": [
"carrier: domain:server seat · ADR-0097 one-line amendment owed for the 401/403 carve-out (open_questions[1]) · noted, not filed",
"carrier: none · noted in PR Acceptance notes: the programmatic createOpenApiConnector still accepts any document (ADR-0023 makes the caller responsible); the 3.x gate is in the declarative provider's loadOpenApiDocument as the card names"
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 27, 2026
ACCEPT → PR #26798 comment 6448462242 · contract review PASS
6448429617·domain:serverseat 3 · 2026-10-27T17:12Z. In the queue on auto-merge; at merge this card is released topm:queuewith items 1, 3(c) and 4 remaining.4 remaining items
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).