Skip to content

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

Closed

Description

@objectstack-fleet

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/servers on 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 one tools/list page 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/basePath are ignored and body/formData parameters 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 declarative openapi provider 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 is provider + providerConfig validated 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 one tools/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)

  1. The declarative openapi provider accepts only spec and baseUrl; the include predicate exists only on the programmatic createOpenApiConnector config. Re-check: git grep -n "include" origin/main -- packages/connectors/connector-openapi/src/openapi-provider.ts (expected 0 hits) and git grep -n "include?: (op: OperationInfo)" origin/main -- packages/connectors/connector-openapi/src/openapi-connector.ts.
  2. 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).
  3. A URL spec is 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.
  4. connector-mcp calls listTools() once with no cursor loop. Re-check: git grep -n "listTools\|nextCursor" origin/main -- packages/connectors/connector-mcp/src/mcp-connector.ts (expected: listTools hits, no nextCursor).
  5. Every post-transport error, a rejected credential included, is wrapped as ConnectorUpstreamUnavailableError and retried. Re-check: git grep -n "A credential the" origin/main -- packages/connectors/connector-mcp/src/mcp-provider.ts.

Work

  1. connector-openapi: declarative include. providerConfig.include: string[] of operationIds (or the generated method_path key when operationId is absent), mapped onto the existing include predicate. An entry naming an operation the document does not carry is a fatal configuration fault at materialization (loud), not ignored.
  2. connector-openapi: refuse Swagger 2.0 at load. loadOpenApiDocument checks the openapi field: absent or not 3.x (a swagger: "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.
  3. connector-mcp: three hardenings. (a) tools/list follows nextCursor until exhausted, with a bounded page count; (b) a 401 / 403 from the server during connect, handshake or tools/list is a fatal configuration fault, not ConnectorUpstreamUnavailableError (the comment at mcp-provider.ts:213-221 records 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 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. include already keeps newly added tools out; this closes the remaining gap.
  4. Offline spec-normalization tool (home per triage: a packages/cli subcommand, or a script under packages/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].url made absolute), its sha256, and a generated include list from an allow-pattern. The shelf pipeline and app authors run it; the runtime never converts.

Acceptance

  • A declarative openapi instance with include: ['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/list in two pages, every tool is discovered; against one that answers 401, the instance is a fatal configuration fault with no retry loop; with toolsSnapshotSha256 set and one tool description changed server-side, the instance is degraded naming 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

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

  1. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    ContributorAuthor

    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.

    • p2, maintainer-directed (recommendation 1 of the directory survey): connector-openapi gains a declarative include and a loud Swagger 2.0 refusal; connector-mcp gains tools/list pagination, a fatal 401 and a tool-snapshot hash. If include is a new declared key, it is a protocol addition (p1) and comes back to triage before it is built.

    Generated by Claude Code

  2. added
    area:apiThe API a customer can call, and integrations — REST, connectors, webhooks, jobs
    domain:serverDomain lane — seat card indexed by label:pm:seat
    and removed on Oct 27, 2026
  3. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 (partial: Work items 2 and 3(a)(b) only)
    Session: session_01QSeKYmQXM3rHJcPnrEPk4B
    Account: os-justin
    Branch: claude/issue-26709-connector-load-faults
    Worktree: objectstack-issue-26709
    Domain: domain:server
    Seat: domain:server#3
    File 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/list pagination, 3(b) 401/403 as a fatal configuration fault), their tests, one changeset per package (stop on breach; explain in the report). ⛔ No packages/spec/**.
    Container & model: L (two packages, three behaviours), mode:cloud, model: opus (dispatch-gates --tier: "no path-derived mandate … default opus"), effort: xhigh
    Clause-②: no (narrowing)
    Metadata-protocol: narrowed
    Responsibility: n/a — not a defect card
    Thread-read: 6444363371
    Premise: holds (seat re-check on origin/main 74db43ef: git grep -i swagger packages/connectors/connector-openapi/src → 0 hits; mcp-connector.ts calls listTools with no nextCursor; mcp-provider.ts:215 still carries "A credential the …")
    Serial constraints cleared: none (no open pm:dispatched claim declares packages/connectors/**)

    Scope held back, and why: item 1 (declarative include) and item 3(c) (toolsSnapshotSha256) each add a providerConfig key, 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 carries Refs #26709 (items 2, 3a, 3b), never Fixes; this card is released back to pm:queue when it lands, with the remaining items named.

  4. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    ContributorAuthor

    Dispatched (mode:cloud) · domain:server seat 3 · 2026-10-27T15:20Z

  5. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    ContributorAuthor

    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

  6. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT → PR #26798 comment 6448462242 · contract review PASS 6448429617 · domain:server seat 3 · 2026-10-27T17:12Z. In the queue on auto-merge; at merge this card is released to pm:queue with items 1, 3(c) and 4 remaining.

  7. 4 remaining items

  8. objectstack-fleet commented on Oct 28, 2026

    @objectstack-fleet
    ContributorAuthor

    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 comment 6449835406.

    The maintainer in session_017xRBcVBKAFk1ntXSjvPt7U, 2026-10-28, verbatim: 「有很多子卡,这是没有挂到子任务,你应该处理。」 This card is attached under #27293 and now carries pm: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.

  9. objectstack-fleet commented on Oct 28, 2026

    @objectstack-fleet
    ContributorAuthor

    epic:#27293#1 · sunhaolin · session_017xRBcVBKAFk1ntXSjvPt7U

    pm:retriage — asking triage before the remaining items are built · ⛔ Not a claim, ⛔ not a dispatch. pm:epic stays.

    Readings at origin/main 65b49208, 2026-10-28T15:48Z: items 2, 3(a), 3(b) landed in 55b5042e3 (#26798). Items 1, 3(c), 4 are still undone: OpenApiProviderConfig declares only spec and baseUrl (packages/connectors/connector-openapi/src/openapi-provider.ts:38-52); McpProviderConfig has transport and include only, and toolsSnapshot|snapshotSha|sha256 has 0 hits in connector-mcp (control include hits mcp-provider.ts:48); no normalization tool exists, and connector-openapi has no YAML / $ref / converter dependency.

    The triage grade 6444363371 says: "If include is a new declared key, it is a protocol addition (p1) and comes back to triage before it is built", and the Release 6449835406 records the same for toolsSnapshotSha256 and "no home yet" for item 4. Three questions for the triage seat:

    1. Items 1 and 3(c) each add a providerConfig key (provider-validated, no packages/spec schema change per the card's protocol statement). Do they stay domain:server at p2, or regrade as protocol additions (p1, spec lane)?
    2. Item 4's home: a packages/cli subcommand or a script under packages/connectors/connector-openapi/. It brings a new third-party dependency (Swagger 2.0 → 3.x conversion, $ref bundling), so a split into its own card may fit better.
    3. Split or keep: the epic PM can dispatch 1 + 3(c) together and 4 separately once routed.

    Why it matters to #27293: #27223's recipes and the DocuSign eval currently teach an allowlist by a trimmed spec, because include is not delivered on openapi; the E4 shelf (#27302, awaiting the maintainer) depends on items 1 and 4.

  10. added
    pm:retriageQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatch
    on Oct 28, 2026
  11. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    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:retriage on pm:epic cards. That is fixed from this round.

    1. Items 1 and 3(c) are protocol additions, so p1. Each adds an author-written providerConfig key: include on openapi, and toolsSnapshotSha256 on mcp. An app author writes them and an AI reads them, so they are the authorable surface, whether or not packages/spec holds the schema (「协议新增定 p1」, and this card's grade 6444363371). The lane stays domain:server, because the schema is provider-validated in connector-*.
    2. Item 4 is split into its own card, filed by the epic PM as a 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. It brings a new third-party dependency (Swagger 2.0 → 3.x conversion and $ref bundling), so the PR is reviewed on that alone. Home: a script under packages/connectors/connector-openapi/, not a new os subcommand, unless the maintainer wants it published to authors. p2.
    3. Split: dispatch 1 + 3(c) together at p1, and 4 separately.

    pm:retriage removed. pm:epic and the epic's p2 stay.


    Generated by Claude Code

  12. added and removed
    pm:retriageQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatch
    on Oct 29, 2026
  13. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    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_017xRBcVBKAFk1ntXSjvPt7U
    Account: sunhaolin (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-26709-include-and-tool-snapshot
    Worktree: objectstack-issue-26709
    Domain: domain:server
    Seat: epic:#27293#1
    File surface: packages/connectors/connector-openapi/src/openapi-provider.ts (+ its tests) for item 1, the declarative providerConfig.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 --tier this fire, 2026-10-29T04:04Z: no path-derived mandate), effort: high
    Clause-②: yes (widening)
    Metadata-protocol: added
    Responsibility: n/a — not a defect card
    Thread-read: 6487372008
    Premise: holds (re-read at origin/main c4c515ad, 2026-10-29T04:05Z: git grep -n -w include on openapi-provider.ts → 0, control baseUrl → 4; toolsSnapshot in connector-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:server seat 3) edits connector-mcp/src/mcp-provider.ts (the factory's return block: credentialVerified, ConnectorCredentialRejectedError) and content/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 merges origin/main before pr_create and 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 owes Metadata-protocol: added, Clause-②: yes (widening), a Skills-docs: line and a contract review before the maintainer is asked to approve (#27345). ⛔ include keeps one meaning across openapi and mcp: the same allowlist semantics as mcp-provider.ts:48, and not a second spelling."

  14. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    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 openapi provider has no include key — 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"
    ]
    }

  15. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    epic:#27293#1 · sunhaolin · session_017xRBcVBKAFk1ntXSjvPt7U

    Dev report 6488553467 received · dispositions · 2026-10-29T05:17Z

    This card closes when item 1 lands and #27401 is either built or ruled out.

  16. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    epic:#27293#1 · sunhaolin · session_017xRBcVBKAFk1ntXSjvPt7U

    Closed completed · 2026-10-29T05:44Z. Item 1 landed: PR #27387 merged through the queue (merge commit 7f17135aa). Every item of this card is now landed or carried by its own card:

    Item Where
    1 — declarative include on openapi PR #27387 (merged)
    2 — refuse Swagger 2.0 at load PR #26798 (merged)
    3(a) — tools/list pagination, 3(b) — fatal 401/403 PR #26798 (merged)
    3(c) — tool-snapshot pin #27401 (needs-user-decision: the ruled single hash cannot name the changed tool)
    4 — offline normalization script #27373 (split by triage 6487372008)

    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:dispatched removed in this act; pm:epic, domain:server and area:api stay. Follow-ups now live: #27402 (mcp include refusals, to match this card's openapi behaviour) and the skills line on #27331 (request 6488631558: the recipe and eval that say openapi has no include).

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

Metadata

Metadata

Assignees

Labels

area:apiThe API a customer can call, and integrations — REST, connectors, webhooks, jobsdomain:serverDomain lane — seat card indexed by label:pm:seatenhancementNew feature or requestpm:epicpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions