Skip to content

feat(connector-openapi): normalize-openapi --trim writes only the operations --include-pattern matches - #28331

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-28309-normalizer-trim
Oct 31, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-28309-normalizer-trim

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #28309
Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:离线脚本 normalize-openapi 新增 --trim 开关,配合 --include-pattern 只写出被匹配的操作;两处文档同步。
  • 为什么改:大型厂商文档(Stripe 612 个操作、63 MB)原样打包不现实;Stripe 连接器应用要求打包文件就是脚本输出并记录其 sha256。
  • 风险与回滚:只改仓库脚本(不随 npm 发布)与文档;不加 --trim 时输出逐字节不变;回滚即 revert 本 PR。
  • 先看:packages/connectors/connector-openapi/scripts/normalize-openapi.mts 的 trimPaths 与第 4 步前的裁剪调用;其测试文件的 --trim 一节;content/docs/automation/connectors.mdx 新段落。
  • 两分钟验证:pnpm --filter @objectstack/connector-openapi exec vitest run scripts/normalize-openapi.test.ts,预期 20 个测试全部通过。
  • 需要你做的:无,席位落地。

What changes

  • scripts/normalize-openapi.mts gains --trim (option trim on normalizeOpenApi). With it, every path operation no --include-pattern matches, every path item left with no operation, and webhooks are dropped before dereferencing, so only the kept operations are walked and nothing reachable only from a dropped operation reaches the output. A $ref path item is resolved (not walked) before its operations are selected. The include list is computed exactly as before (same patterns, same imported operationActionKey), and the printed sha256 is still the hash of the bytes written, which are now the trimmed bytes. The summary gains trimmedFrom (operations in the source) when trimming. --trim with no --include-pattern is refused (exit 1); a pattern matching nothing is refused as before.
  • securitySchemes stay, as in the untrimmed path (they are referenced by name, never by $ref, and the provider does not read them); every other component was already dropped after inlining, and under --trim it is never reached from a dropped operation.
  • Docs: content/docs/automation/connectors.mdx (the script's paragraph: the example command now carries --trim, and a new paragraph says what trimming writes and when to leave it off); content/docs/deployment/marketplace-handbook.mdx, the one README-section sentence that still described the normalizer as forthcoming now says how a declarative OpenAPI app produces its packaged document and which sha256 to record.
  • The runtime (src/**) does not change; the script stays a repository script (not in files[], no bin), so this carries skip-changeset.

Why a separate --trim flag, not trimming whenever --include-pattern is given

  • An implied trim changes what the one documented command writes (its bytes and its hash) with no change to the command line. With a flag, every existing invocation writes exactly what it wrote before: the untrimmed Stripe run below reproduces the connector apps, E4 batch 1 (static key): GitHub over its remote MCP server and Stripe read over its OpenAPI document, as open-edition metadata packages validated by this repository's CI #28276 reading byte for byte.
  • The two outputs serve two different authors. Untrimmed plus include lets an app widen include later by editing config alone; trimmed means widening requires re-running the script. Which one an app wants is the author's call, and the command line that produced the packaged bytes should say which one it was.
  • Today's callers: the only documented call is the example in connectors.mdx (the shelf pipeline is not built yet), so the flag costs nobody a migration; the example now uses --trim, which is what recipe 1 teaches (trim to the operations the app calls).

Tests

scripts/normalize-openapi.test.ts, on the existing fixtures, a new --trim block and one new CLI case:

  • the trimmed output holds exactly the matched operations (GetCustomers, GetCustomersCustomer, get_v1_balance), drops the unmatched POST from a path item whose GET is kept, has the same include list as the untrimmed run, loads through createOpenApiProviderFactory with exactly those actions and no $ref in the document or the materialized def;
  • trimming to balance alone drops the customer path items and leaves no text reachable only from them (A customer., Error response., the shared limit parameter, subscription), with zero cuts counted (only the kept operation was walked);
  • a Swagger 2.0 document is trimmed after conversion, keyed by operationId;
  • a $ref path item is resolved before selection (its sibling summary kept) and webhooks are dropped;
  • refusals: --trim with no pattern; a pattern matching nothing;
  • control: without --trim the whole document is written, as before;
  • CLI: with --trim, bytes and sha256 equal the written file's length and sha256 of its bytes, operations is 2 and trimmedFrom is 4; --trim alone exits 1 and writes nothing.
Package test and typecheck (head 08bf282)
pnpm --filter @objectstack/connector-openapi test && pnpm --filter @objectstack/connector-openapi typecheck
 Test Files  6 passed (6)
      Tests  82 passed (82)
check:test-typecheck: OK — @objectstack/connector-openapi's test layer compiles under packages/connectors/connector-openapi/tsconfig.test.json
os-verify-lock: VERDICT command-exit 0
scripts/normalize-openapi.test.ts alone: Tests 20 passed (20) (13 before this PR, 7 new)
Ablation (one-off, committed fix first, restored)

node scripts/ablation-replace.mjs on normalize-openapi.mts, anchor if (matches(patterns, { key: operationActionKey(... (hits 1 to 0, blob a2190cd6ea17 to 70610c5bb588) replaced by a condition that keeps every operation. The test file then reads Tests 5 failed | 15 passed (20): the four trim selection cases and the CLI --trim case went red; the refusal and control cases stayed green, as expected. Restored: blob equals HEAD (a2190cd6ea17) and git diff HEAD is empty. No build or dist/ leg: the test imports the script from source.

Acceptance notes

Measured once against the real Stripe document, stripe/openapi openapi/spec3.json at 51610f5f (MIT; 8,317,513 bytes, sha256 7cff4cc46d0654101a36a3302f7544640f5f73e4f3cad6f264f0e23c19fa1776), with the pattern ^(GetCustomers|PostCustomers|GetCustomersCustomer|GetCharges|PostPaymentIntents|GetPaymentIntentsIntent)$ at the default --max-depth 2:

run bytes operations sha256
without --trim (control) 63,317,655 612 0ea3c37a0854ad2646a156404b1ef86cca57403855e3803cd4630601eb6fc7b2
with --trim 1,934,435 6 (trimmedFrom 612) 08d6e40bccec707a0bfc355b1cbf936df29018c22e7be658d616683e501b13d4

Verification

Derived gates (dispatch-gates --commands --repo objectstack-ai/objectstack, head 08bf282)

78 commands derived from the 4 changed paths; each run with its exit code captured before any pipe. --ran reconciliation: 78 derived, 78 run, 0 NOT-MEASURED (a derived zero). On the first run (head e6e7833) check:skill-examples and check:dual-build-cjs-loads answered exit 3 PREREQUISITE NOT MET (unbuilt sibling packages); after building the @objectstack/client-react closure both measured exit 0. Re-run in full on the final head 08bf282 (after merging origin/main): all 78 exit 0.

Not run locally, by rule: test:repo, pnpm lint and the other repo-wide scans (CI).


Generated by Claude Code

…ations --include-pattern matches

Claude-Session: https://claude.ai/code/session_017xRBcVBKAFk1ntXSjvPt7U
Co-authored-by: Claude <noreply@anthropic.com>
…ormalizer sentence reads as landed

Claude-Session: https://claude.ai/code/session_017xRBcVBKAFk1ntXSjvPt7U
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Oct 31, 2026
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests labels Oct 31, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs.

What this run could not see

Coarse fallback — 0 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 501c864dd5918a7bab1f3a59e1246b8a0ddff48c → packageMentionDocs.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

epic:#27293#1 · sunhaolin · session_017xRBcVBKAFk1ntXSjvPt7U

席位核对

ACCEPT at head 08bf2826 · Implemented-by: claude/issue-28309-normalizer-trim · Reviewed-by: this seat. No contract-review record is owed: Clause-②: no, no packages/spec/src/** path, no governed path (check-governed-merges --pr 28331: NOT governed).

Check Result
Shape draft, base main, first line Fixes #28309; the only closing keyword
Scope 4 files, +283/−29, inside the claim's surface; the runtime src/** untouched; the script ships in no files[], so skip-changeset is right
Gates 55 check-runs on the head: 38 success, 17 skipped (all on main's roster, check-expected-skips OK); mergeable clean
Evidence 20 script tests (7 new), each trimmed output loaded through the real provider with no $ref; ablation of the selection turns 5 red; real Stripe document: 63,317,655 bytes / 612 operations untrimmed (byte-identical to #28276's reading) → 1,934,435 bytes / 6 operations trimmed, sha256 equal to sha256sum
Design a separate --trim flag, refused without --include-pattern; selection runs before dereferencing, so nothing reachable only from a dropped operation is written; the untrimmed path is unchanged (control)
Docs, read line by line connectors.mdx: the example now passes --trim, and the paragraph's four claims (only matched operations; webhooks and empty path items dropped; the printed sha256 is of the trimmed bytes; refused without a pattern) match the script; marketplace-handbook.mdx: the README-section sentence reads as landed and links the provider: 'openapi' section; #28308's sections untouched

Out of scope: the skill's recipe 1 says "trim" without naming --trim → noted, not filed (skills/** is Tier H; #27331 is that lane's open card for this file family) · #28276's Stripe half is unblocked by this merge.

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

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 skip-changeset PR has no user-facing published change; bypasses the changeset gate tests

Projects

None yet

1 participant