Skip to content

feat(connector-openapi): declarative providerConfig.include operation allowlist - #27387

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-26709-include-and-tool-snapshot
Oct 29, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-26709-include-and-tool-snapshot

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Part of #26709 (Work item 1 only). Item 3(c) is held for a contract decision, reported on the card; item 4 lives on #27373.

Clause-②: yes (widening)
Metadata-protocol: added
Skills-docs: follow-up owed — skills/objectstack-automation/references/integration-recipes.md, skills/objectstack-automation/evals/integrations-declared.json

维护者速读

  • 改了什么:声明式 openapi 连接器实例新增 providerConfig.include(操作白名单),只有列出的操作成为 action;条目写 operationId,无 operationId 的操作写生成的 method_path 键。
  • 为什么改:大型 API(GitHub 1,242 个操作、Graph 约 1.8 万)今天只能整份暴露或手工裁剪文档;条目写错或上游改名时启动直接失败并列出文档里的键,不会静默少一个 action。
  • 风险与回滚:纯新增键,不写 include 的实例行为不变;回滚即还原本 PR,已写 include 的实例会重新暴露全部操作。
  • 先看:packages/connectors/connector-openapi/src/openapi-provider.ts(未知条目拒收与形状校验两段),openapi-connector.ts(抽出唯一的 action 键推导 operationActionKey),openapi-provider.test.ts。
  • 两分钟验证:pnpm --filter @objectstack/connector-openapi exec vitest run --maxWorkers=2 src/openapi-provider.test.ts,预期 38 passed,其中 providerConfig.include 组 9 个用例全绿。
  • 需要你做的:无,席位落地。

What changes

  • connector-openapi provider: providerConfig.include (optional) is an array of action keys: an operation's operationId, or, when it has none, the generated method_path key (delete /customers/{id} is delete_customers_id). It is handed to the generator's existing include predicate (createOpenApiConnector's include?: (op: OperationInfo) => boolean), which the provider did not forward before.
  • Unknown entry = fatal configuration fault. An entry the document does not carry throws a plain Error after the bundle is generated: fatal at boot, a skipped entry on reload, never ConnectorUpstreamUnavailableError. The message names the entry and lists up to 20 of the document's keys.
  • Shape checked before the document loads. include that is not an array of non-empty strings throws plain before any spec fetch, so a malformed allowlist never exposes the whole document.
  • One key derivation. operationActionKey (exported from openapi-connector.ts, not from the package index) is the single spelling of "operationId, else the method_path slug". The generator keys its actions with it, and the provider matches include entries against it, so the entry and the action it admits can never be spelled two ways.
  • Docs: content/docs/automation/connectors.mdx documents the key in the openapi section. One changeset (@objectstack/connector-openapi: minor).

Same allowlist meaning as the mcp provider's include (mcp-provider.ts:48, :223-226): a list of names, and only listed names become actions. The two providers now differ on bad entries. The card text makes an unknown openapi entry fatal, but the mcp provider still ignores an unknown tool name (zero actions) and drops a non-array include without a word (every tool exposed). This PR does not touch connector-mcp; the divergence is reported on the card for a decision.

Tests

Head 3d7773a5. New describe('openapi provider — providerConfig.include') in openapi-provider.test.ts, 9 cases:

  • control: no include gives every operation (listCustomers, createCustomer, delete_customers_id);
  • include: ['listCustomers'] gives exactly that action and that handler;
  • an operation without operationId is named by its method_path key; document order is kept;
  • include: ['listCustomers', 'nope'] is refused with a plain Error (not upstream-unavailable). The message names 'nope' only and lists the document's keys;
  • an operation that has an operationId is not named by its method_path key;
  • 'listCustomers', ['listCustomers', 7], [''], an object and null are each refused by shape, and the spec URL is fetched 0 times;
  • include: [] admits nothing, the same as an empty mcp include;
  • real boot (LiteKernel + AutomationServicePlugin + ConnectorOpenApiPlugin, declared through the registry): the instance is ready with exactly ['listCustomers'] in getConnectorDescriptors(); with ['nope'] the bootstrap rejects naming it, and no degraded reason is recorded.

These are provider-level plain-Error faults. No HTTP envelope is involved, so the assertions check the error class, the upstream-unavailable marker and the named entry, the same way this file's existing Swagger 2.0 refusals do.

Package test + typecheck (os-verify-lock, VERDICT command-exit 0)
pnpm --filter @objectstack/connector-openapi test && pnpm --filter @objectstack/connector-openapi typecheck
 Test Files  4 passed (4)
      Tests  50 passed (50)
check:test-typecheck: OK — @objectstack/connector-openapi's test layer compiles under packages/connectors/connector-openapi/tsconfig.test.json; 1 file(s) / 5 error(s) / 1 pinned signature(s) held in test-typecheck-debt.json
os-verify-lock: VERDICT command-exit 0

eslint --no-inline-config on the three changed .ts files: exit 0.

Ablations (node scripts/ablation-replace.mjs; tests import src directly, no dist hop; each restore proven blob == HEAD 9609ea65091a, git diff HEAD empty)
  • A1, the provider stops forwarding the predicate (include, to include: undefined,): exit 1, 5 failed. The four allowlist cases fail, and so does the boot case that expects exactly ['listCustomers'].
  • A2, the unknown-entry refusal is disabled (the unknown.length comparison flipped so it is never true): exit 1, 3 failed | 35 passed ('nope', the method_path-for-operationId case, the boot refusal).
  • A3, the shape check is disabled: exit 1, 1 failed | 37 passed (the shape case).

Acceptance notes

  • The card's acceptance line for item 1 (include: ['listCustomers'] exposes exactly that action; include: ['nope'] refuses at materialization with the operation name in the message) is pinned at both the factory and the real-boot layer.
  • A key the generator de-duplicates (two operations sharing one operationId, which yields x and x_2) is admitted by its base key, which admits both. The _2 spelling is not an include entry. Noted, not filed: no producer measured.
  • skills/objectstack-automation/references/integration-recipes.md (line 63, "The openapi provider has no include key — the trimmed document is the allowlist.") and the integrations-declared.json eval's expected outputs become false when this lands. skills/** is Tier H and outside this PR, so the follow-up is owed and named in the Skills-docs: line.
  • File surface: openapi-connector.ts sits outside the claim's declared surface. It was edited because the provider needs the generator's own key derivation. The other way was a second copy of the slug, i.e. two spellings of one key. The edit is the helper plus its one call site.

Verification

Derived gate families (node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, tree 3d7773a, 5 paths)

88 commands, run sequentially, each exit captured before any pipe: 85 exit 0; 3 exit 3 PREREQUISITE NOT MET = NOT MEASURED:

  • node scripts/check-plugin-teardown-shape.mjs --self-test (asks for git fetch --unshallow);
  • pnpm --filter @objectstack/spec run check:skill-examples (reads packages/client-react/dist, not built here);
  • pnpm check:dual-build-cjs-loads (reads every package's dist; Build Core owns it).

--ran reconciliation: "88 derived famil(ies) accounted for — 85 run, 3 NOT-MEASURED (3 DERIVED from a recorded exit 3)", exit 0.

Not run locally, CI-owned: test:repo, the repo-wide lint, and the full families of the required checks. connector-mcp is untouched, so it owes no test here. There is no dogfood run: the title has no ! and Clause-② carries no (narrowing) arm.


Generated by Claude Code

… allowlist

An openapi instance lists the operations it exposes as action keys
(operationId, else the generated method_path key), mapped onto the
generator's existing include predicate. An entry the document does not
carry, or an include that is not a string array, is a plain configuration
fault at materialization. The action-key derivation is one exported
helper the generator and the provider share.

Claude-Session: https://claude.ai/code/session_017xRBcVBKAFk1ntXSjvPt7U
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface labels Oct 29, 2026
@github-actions

github-actions Bot commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/connector-openapi, touching 8 documentable anchor(s).

2 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/api/declarative-endpoints.mdx (via operationId (literal, a string literal in operationActionKey))
  • content/docs/automation/connectors.mdx (via operationId (literal, a string literal in operationActionKey))
What this run could not see
  • 1 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 58 of 210 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 2 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 7e200b4a68c242afc9f7caf0289e985eff3fcac6 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 085dc8eaa183d32ad30db66adb82b5be9b54cd0a — the merge of head b9b9e22304c05f33728bb263ed55d4fcd6ce3ce1 into base 7e200b4a68c242afc9f7caf0289e985eff3fcac6, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 085dc8eaa183d32ad30db66adb82b5be9b54cd0a && git checkout 085dc8eaa183d32ad30db66adb82b5be9b54cd0a
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 7e200b4a68c242afc9f7caf0289e985eff3fcac6 b9b9e22304c05f33728bb263ed55d4fcd6ce3ce1 && git checkout -B drift-repro 7e200b4a68c242afc9f7caf0289e985eff3fcac6 && git merge --no-ff b9b9e22304c05f33728bb263ed55d4fcd6ce3ce1

node scripts/docs-audit/affected-docs.mjs --json 7e200b4a68c242afc9f7caf0289e985eff3fcac6

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 7e200b4a68c242afc9f7caf0289e985eff3fcac6 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

epic:#27293#1 · sunhaolin · session_017xRBcVBKAFk1ntXSjvPt7U

Contract review

Served-tier: TIER_DEFAULT
Head-sha: b9b9e22304c05f33728bb263ed55d4fcd6ce3ce1
Local-runs: none

Face: the Clause-②: yes (widening) declaration alone — no packages/spec/src/** path and no governed path in the committed tree (check-governed-merges --pr 27387: NOT governed). Read the net diff, the card #26709 with its triage ruling 6487372008, and the head's check-runs (50: 37 success, 13 skipped — every skip on main's roster per check-expected-skips, none red).

① Derived judgments

  • Accept-set widening, provider: 'openapi' providerConfig.include: a new optional author-written key, an array of action keys. Right: it is the card's item 1 text verbatim, and triage graded it a protocol addition (p1, lane domain:server, provider-validated). Absent include keeps today's behaviour (the control test).
  • Matching key: entries are compared against operationActionKey(op) — the generator's own derivation, now exported from openapi-connector.ts and used at its one call site — so an allowlist entry and the action it admits share one spelling. Right; the predicate runs on every operation before the filter (openapi-connector.ts:208), so the unknown-entry check sees the full key set. Known edge, accepted and noted by the dev: a collision-suffixed key (x_2) is not an entry; the base key admits both.
  • Refusals: a non-array or non-empty-string shape is refused before the document is fetched (test: 0 fetches over five bad shapes); an unknown entry is refused after generation with the entry named and up to 20 document keys listed. Both throw plain Error — fatal at boot, a skipped entry on reload — never ConnectorUpstreamUnavailableError. Right per Prime Directive Add comprehensive test suite for Zod schema validation #12 and the card's "loud, not ignored".
  • One meaning with mcp: the allowlist meaning matches mcp-provider.ts:223-226 (a name allowlist; [] exposes nothing in both). The loudness differs — mcp stays silent on a bad entry — and that is filed as connector-mcp: a declarative providerConfig.include that is not an array is dropped and every tool is exposed, and an unknown tool name is ignored — refuse both at materialization, as openapi does (one meaning for include) #27402 to bring mcp to this PR's behaviour, so this PR does not need to change. The docs sentence "the same allowlist meaning as the mcp provider's include" stays true of the meaning.
  • Public surface: operationActionKey is exported from its module but not added to the package index; no published entry moves.

② Semver level

@objectstack/connector-openapi minor, Clause-②: yes (widening), Metadata-protocol: added on the changeset and the PR body. Right: a new optional accepted key widens the authorable surface. Check Changeset success on this head. Skills-docs: the published recipe line and eval answer that say openapi has no include become false at merge — owed as follow-up #27331 (request 6488631558), outside this PR because skills/** is Tier H.

③ Boundary flags

Implemented-by: claude/issue-26709-include-and-tool-snapshot
Reviewed-by: session_017xRBcVBKAFk1ntXSjvPt7U

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

epic:#27293#1 · sunhaolin · session_017xRBcVBKAFk1ntXSjvPt7U

席位核对

ACCEPT at head b9b9e223 · Implemented-by: claude/issue-26709-include-and-tool-snapshot · Reviewed-by: this seat · contract review PASS 6488674286 (same head).

Check Result
Shape draft, base main, first line Part of #26709 (Work item 1 only); no closing keyword in the body — the card stays open for #27401
Scope 5 files, +265/−5; no content/docs/releases/**; @objectstack/connector-openapi minor changeset present; one surface addendum (openapi-connector.ts, claim comment 6488645780)
Gates 50 check-runs on the head: 37 success, 13 skipped (all on main's roster, check-expected-skips OK); Lint & Repo Gates, TypeScript Type Check, Test Core 8/8 success; mergeable clean
Evidence 9 new provider tests including a real boot (LiteKernel + AutomationServicePlugin + ConnectorOpenApiPlugin) and three ablations, each red when its guard is removed
Docs, checked line by line connectors.mdx: the include key's meaning, the method_path example (delete /customers/{id} → delete_customers_id, matched by the test), "an operation that has an operationId is named only by it" (test), and "fails the boot … naming the entry and listing the document's keys" (the refusal text; on a Studio reload the entry is skipped with the same error)
Out of scope #27401 (item 3(c) shape, maintainer) · #27402 (mcp include divergence) · Skills-docs: follow-up #27331

Landing: not governed, +270 lines: the seat lands it now (pr_ready + automerge_enable).

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 29, 2026 05:21
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 29, 2026 05:21
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 29, 2026
Merged via the queue into main with commit 7f17135 Oct 29, 2026
51 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-26709-include-and-tool-snapshot branch October 29, 2026 05:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants