Repository navigation
feat(connector-openapi): declarative providerConfig.include operation allowlist - #27387
Conversation
Claude-Session: https://claude.ai/code/session_017xRBcVBKAFk1ntXSjvPt7U Co-authored-by: Claude <noreply@anthropic.com>
… 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>
…clude-and-tool-snapshot
📓 Docs Drift CheckThis PR changes 1 package(s): 2 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
What this run could not see
Coarse fallback — 2 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # 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
|
|
epic:#27293#1 · sunhaolin · session_017xRBcVBKAFk1ntXSjvPt7U Contract reviewServed-tier: Face: the ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS |
|
epic:#27293#1 · sunhaolin · session_017xRBcVBKAFk1ntXSjvPt7U 席位核对ACCEPT at head
Landing: not governed, +270 lines: the seat lands it now ( |
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键。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-openapiprovider:providerConfig.include(optional) is an array of action keys: an operation'soperationId, or, when it has none, the generatedmethod_pathkey (delete /customers/{id}isdelete_customers_id). It is handed to the generator's existingincludepredicate (createOpenApiConnector'sinclude?: (op: OperationInfo) => boolean), which the provider did not forward before.Errorafter the bundle is generated: fatal at boot, a skipped entry on reload, neverConnectorUpstreamUnavailableError. The message names the entry and lists up to 20 of the document's keys.includethat is not an array of non-empty strings throws plain before any spec fetch, so a malformed allowlist never exposes the whole document.operationActionKey(exported fromopenapi-connector.ts, not from the package index) is the single spelling of "operationId, else themethod_pathslug". The generator keys its actions with it, and the provider matchesincludeentries against it, so the entry and the action it admits can never be spelled two ways.content/docs/automation/connectors.mdxdocuments the key in theopenapisection. One changeset (@objectstack/connector-openapi: minor).Same allowlist meaning as the
mcpprovider'sinclude(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 unknownopenapientry fatal, but themcpprovider still ignores an unknown tool name (zero actions) and drops a non-arrayincludewithout a word (every tool exposed). This PR does not touchconnector-mcp; the divergence is reported on the card for a decision.Tests
Head
3d7773a5. Newdescribe('openapi provider — providerConfig.include')inopenapi-provider.test.ts, 9 cases:includegives every operation (listCustomers,createCustomer,delete_customers_id);include: ['listCustomers']gives exactly that action and that handler;operationIdis named by itsmethod_pathkey; document order is kept;include: ['listCustomers', 'nope']is refused with a plainError(not upstream-unavailable). The message names'nope'only and lists the document's keys;operationIdis not named by itsmethod_pathkey;'listCustomers',['listCustomers', 7],[''], an object andnullare each refused by shape, and the spec URL is fetched 0 times;include: []admits nothing, the same as an emptymcpinclude;LiteKernel+AutomationServicePlugin+ConnectorOpenApiPlugin, declared through the registry): the instance isreadywith exactly['listCustomers']ingetConnectorDescriptors(); with['nope']the bootstrap rejects naming it, and no degraded reason is recorded.These are provider-level plain-
Errorfaults. 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)
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)
include,toinclude: undefined,): exit 1, 5 failed. The four allowlist cases fail, and so does the boot case that expects exactly['listCustomers'].unknown.lengthcomparison flipped so it is never true): exit 1, 3 failed | 35 passed ('nope', the method_path-for-operationId case, the boot refusal).Acceptance notes
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.operationId, which yieldsxandx_2) is admitted by its base key, which admits both. The_2spelling is not anincludeentry. Noted, not filed: no producer measured.skills/objectstack-automation/references/integration-recipes.md(line 63, "Theopenapiprovider has noincludekey — the trimmed document is the allowlist.") and theintegrations-declared.jsoneval'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 theSkills-docs:line.openapi-connector.tssits 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 forgit fetch --unshallow);pnpm --filter @objectstack/spec run check:skill-examples(readspackages/client-react/dist, not built here);pnpm check:dual-build-cjs-loads(reads every package'sdist; Build Core owns it).--ranreconciliation: "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-mcpis 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