Repository navigation
Description
epic:#27293#1 · sunhaolin · session_017xRBcVBKAFk1ntXSjvPt7U
carries item 4 of #26709; #26709 keeps items 1 and 3(c). Split by the triage seat's ruling on #26709 (comment 6487372008, 2026-10-29T03:48Z): "Item 4 is split into its own card, filed by the epic PM as a child of #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." Child of #27293 (E1). ⛔ Not a claim.
What the runtime does today (why an offline tool)
Read at origin/main c4c515ad, 2026-10-29T04:05Z:
loadOpenApiDocumentrefuses a non-3.x document at load and never converts (packages/connectors/connector-openapi/src/openapi-provider.ts, theassertOpenApi3Documentpath landed by PR fix(connectors)!: refuse a non-3.x OpenAPI document at load; mcp tools/list follows nextCursor and a 401/403 is a fatal configuration fault #26798); its message tells the author to "convert the document to OpenAPI 3.x offline".- No converter exists in this repository:
connector-openapi's dependencies are@objectstack/coreand@objectstack/speconly; no YAML parser,$refresolver or converter (re-check:git show origin/main:packages/connectors/connector-openapi/package.json). - The loader also skips an in-document
$refparameter, request body or response (openapi-connector.ts:295,:336,:342), so the derived action schema silently loses those parts; the skill now tells authors to dereference every$refoffline (PR skills(automation): integrations are declared, not coded — rule section, per-archetype recipes, three evals #27322).
Work (the card's item 4, verbatim scope)
"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."
- Home per triage: a script under
packages/connectors/connector-openapi/(not anossubcommand). - The generated
includelist targets the declarativeincludekey that 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 items 1 lands; until that key ships, the script emits the trimmed document itself (the allowlist the skill teaches today). - The new third-party dependency (conversion and bundling) is the PR's main review subject: name the package, its licence and its maintenance state in the PR body.
Acceptance
- The Stripe (
stripe/openapi, 3.0.0, MIT) and DocuSign (docusign/OpenAPI-Specifications, Swagger 2.0, MIT) documents normalize to 3.x JSON that theopenapiprovider loads with no$refleft. - The sha256 printed matches the bytes written.
- A changeset for
@objectstack/connector-openapiif the script ships in its package.
Business reason
#27293's E4 shelf (#27302, ruled A) packages vendor documents as connector apps; most vendor documents are Swagger 2.0, YAML or $ref-heavy, and the runtime refuses or mis-reads them by design. Without this script every author converts by hand and gets it wrong silently.
Reader
The epic PM of #27293 dispatches it to the domain:server lane's dev.
Dedupe
Split card by triage direction; parent #26709 holds the original item. The 500 most recent issues and PRs (all states, read 2026-10-29T04:10Z), titles grepped for normaliz|swagger|dereference: 2 hits, this card and #27210 (closed, a CLI census call-site defect, unrelated). #26709 itself is older than that window.
Activity
objectstack-fleet commentedon Oct 29, 2026
epic:#27293#1 · sunhaolin · session_017xRBcVBKAFk1ntXSjvPt7U
Claim: PM loop round 2
Session:
session_017xRBcVBKAFk1ntXSjvPt7UAccount:
sunhaolin(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-27373-openapi-normalizeWorktree:
objectstack-issue-27373Domain:
domain:serverSeat:
epic:#27293#1File surface:
packages/connectors/connector-openapi/scripts/**(new: the normalization script and its tests or fixtures),packages/connectors/connector-openapi/package.json(the new dependency and a script entry),pnpm-lock.yaml(regenerated bypnpm install, never by hand),packages/connectors/connector-openapi/README.md(how to run it),content/docs/automation/connectors.mdx(one pointer where the docs say "convert offline"), a changeset if anything shipped changes (stop on breach; explain in the report)Container & model:
M,mode:subagent,model: opus(TIER_DEFAULT —dispatch-gates --tierthis fire: no path-derived mandate),effort: highClause-②: no
Metadata-protocol: none
Responsibility:
n/a — not a defect cardThread-read: none
Premise: holds (read at
origin/mainb10fd6552026-10-29T05:44Z:connector-openapihas noscripts/directory and no YAML,$refor conversion dependency; itsfilesisdist,README.md,CHANGELOG.md, so a script underscripts/does not ship on npm)Serial constraints cleared:
none— #26709 item 1 (PR #27387) merged 2026-10-29T05:42Z; none of the 60 most recent open PRs touchespackages/connectors/connector-openapi/**;pnpm-lock.yamlis not a single-writer path (SINGLE_CLAIM_PATHSnames only.objectui-sha), so a lockfile conflict is ordinary and the later lander regenerates it.objectstack-fleet commentedon Oct 29, 2026
epic:#27293#1 · sunhaolin · session_017xRBcVBKAFk1ntXSjvPt7U
os-dev-report
{
"issue": 27373,
"status": "done",
"branch": "claude/issue-27373-openapi-normalize",
"pr": "#27436",
"session": "session_017xRBcVBKAFk1ntXSjvPt7U",
"premise_still_valid": true,
"summary": "Added packages/connectors/connector-openapi/scripts/normalize-openapi.mts, an unpublished repo script with a CLI (--in, --out, --include-pattern, --max-depth, --server-url, --empty-consumes). It reads a file or URL (JSON or YAML) and bundles external $refs with @scalar/json-magic. It converts Swagger 2.0 to OpenAPI 3.0 with @scalar/openapi-upgrader, inlines every $ref reachable from paths and webhooks, then drops the referenced-only components. It makes servers[0].url absolute and prints the sha256 of the bytes it writes, plus an include list built from the provider's own operationActionKey. Measured: Stripe cannot be fully inlined. Every one of its 612 operations expands past 2,000,000 JSON nodes even with only recursion cut, so a schema $ref at a recursion point or past --max-depth (default 2) becomes a typed stub whose description names the replaced reference. Real Stripe (3.0.0) and DocuSign (Swagger 2.0) outputs load through createOpenApiProviderFactory with 0 $ref (612 and 414 actions). DocuSign needs --empty-consumes application/json: 187 of its operations set consumes to an empty list, which would silently drop their request bodies, and the script refuses by default. The runtime (openapi-provider.ts, openapi-connector.ts) is untouched. No changeset: dist, README and CHANGELOG are unchanged, so the PR carries the skip-changeset label. The PR was opened without assignees: os-dev.md on main as merged into this branch (f9a4f40) now forbids pr_create assignees, which overrides the dispatch's assignees: [sunhaolin].",
"tests": "Final head f9a4f40:
bash scripts/pm/os-verify-lock.sh -c \"pnpm --filter @objectstack/connector-openapi test && pnpm --filter @objectstack/connector-openapi typecheck\"-> 'Test Files 5 passed (5) / Tests 63 passed (63)', typecheck = tsc --noEmit && tsc --noEmit -p tsconfig.scripts.json && check:test-typecheck OK, 'VERDICT command-exit 0'. The new file scripts/normalize-openapi.test.ts has 13 cases, all green (vitest run --maxWorkers=2 scripts/normalize-openapi.test.ts-> 'Tests 13 passed (13)'). Each normalized fixture output is loaded through the real createOpenApiProviderFactory, and no $ref is asserted on both the document and the materialized def.tsc -p tsconfig.scripts.json --listFilesincludes both scripts/.mts and scripts/.test.ts. Ablation, run once and not kept: removing theinSchema &&guard on SCHEMA_LITERAL_KEYS turned 2 of 13 tests red; scripts/ablation-replace.mjs showed anchor x1 -> x0 and blob 613cae347a0e -> 0cd0bc79a1fd, then restored the blob to the HEAD blob withgit diff HEADempty (the test imports source, so no build or dist leg applies). Real documents, run once locally: Stripe spec3.json -> 63,317,655 bytes, sha256 0ea3c37a0854ad2646a156404b1ef86cca57403855e3803cd4630601eb6fc7b2 (sha256sum equal), refsLeft 0, provider 612 actions, 0 $ref. Stripe by URL with --include-pattern '^GET /v1/customers' -> same sha256, include of 22, provider with that include -> 22 actions. DocuSign esignature.rest.swagger-v2.1.json -> exit 1 without --empty-consumes (187 operations named). With it -> openapi 3.0.4, 52,884,345 bytes, sha256 ceee523822c266fc1de6e5433915f6e4385753d2b239ce8b94f48cafe7d93d37 (sha256sum equal), refsLeft 0, provider 414 actions, 0 $ref, 192 actions with a body input. test:repo was not run locally because the diff does not touch it; Test Core is green in CI.","gates": "Local:
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack(stderr: derived from the tree of objectstack-ai/objectstack at commit f9a4f40) printed 89 commands, the same list as at 252345b. Each was run with its exit code captured before any pipe. At f9a4f40: 88 exited 0. 1 exited 3 (PREREQUISITE NOT MET, nothing measured):node scripts/check-plugin-teardown-shape.mjs --self-test, because its pinned positive control is unreachable on a shallow clone; it grades the script, not this diff.--ranwith thecmd :: exit Nrecord: '89 derived famil(ies) accounted for — 88 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3)'. The earlier run at 252345b had 86 at exit 0 and 3 at exit 3; skill-examples and dual-build-cjs-loads both went to exit 0 at f9a4f40. CI, on PR head f9a4f40 check-runs (71 total), every conclusion is success or skipped: Lint & Repo Gates success; TypeScript Type Check success (Type Check · workspace / source gates / consumer gates / debt ledger all success); Test Core success (1/8..8/8 success); Build Core success; Governed Surface Queue Guard success; Lint UI success; Type Check UI success; Test UI success (1/16..16/16 success); Build & E2E UI success; Live E2E UI success; Check Changeset success; Build Docs success; Check Documentation Links success; Dogfood Regression Gate success.","dogfood_battery": "n/a — no
!in the PR title and no (narrowing) arm","mcp_calls": "0 — no MCP GitHub tool was called",
"api_writes": "2 — (1) fleet-write relay pr_create, one repository_dispatch (request fw-20261029T064429Z-3a7369, run 40831042346 success). It wrote POST /repos/objectstack-ai/objectstack/pulls (draft #27436; read-back: 14678 bytes sent, 14678 stored, identical) and POST /repos//issues/27436/labels (skip-changeset; read back with GET, still present at the end next to the size/xl, documentation, dependencies and tests labels set by the labeler). (2) post-stamped relay comment on #27373 (this os-dev-report). git push is not a REST write.",
"files_changed": [
"content/docs/automation/connectors.mdx (+19)",
"packages/connectors/connector-openapi/package.json (+4 -1)",
"packages/connectors/connector-openapi/scripts/normalize-openapi.mts (+615, new)",
"packages/connectors/connector-openapi/scripts/normalize-openapi.test.ts (+272, new)",
"packages/connectors/connector-openapi/scripts/fixtures/swagger2-esignature-shape.json (+103, new)",
"packages/connectors/connector-openapi/scripts/fixtures/openapi3-billing-shape.yaml (+139, new)",
"packages/connectors/connector-openapi/scripts/fixtures/billing-shared.yaml (+18, new)",
"packages/connectors/connector-openapi/tsconfig.scripts.json (+20, new; outside the claimed file surface)",
"pnpm-lock.yaml (+38, from pnpm install --lockfile-only)"
],
"line_budget": "n/a — no skills/** path touched; own diff +1228 / -1 across 9 files (under the 3000-line human-merge threshold)",
"deviations": [
"File surface: added packages/connectors/connector-openapi/tsconfig.scripts.json, a package-root config outside the claimed surface. Without it, scripts/** would be TypeScript that no tsc program reads (check:type-check-coverage SOURCES_COVERED/TESTS_COVERED). The
typecheckscript names it; precedent packages/spec/tsconfig.scripts.json.","README.md not edited, although the surface allowed it. README ships in the tarball, so editing it would need a changeset just for a pointer to a script the tarball does not carry. The how-to-run text lives in the script header and in content/docs/automation/connectors.mdx.",
"PR opened without assignees. The dispatch said assignees: [sunhaolin]. os-dev.md on origin/main, merged into the branch at f9a4f40, now reads 'PR ⛔ 不带 assignees', and fleet-write/ops.mjs says a dev's pr_create carries no assignees. Per the agent file, the file wins over the dispatch.",
"Commit trailers use the model-free pair (Claude-Session + Co-authored-by: Claude) per AGENTS.md. The harness reminder's model-named Co-Authored-By was not used, because the pre-push hook refuses a model identifier in that pair.",
"pnpm-lock.yaml: the first
pnpm add -Dalso flipped the jsdom peer from 30.1.1 to 29.1.1 in two unrelated snapshot lines. That lockfile was discarded and regenerated withpnpm install --lockfile-onlyfrom the HEAD lockfile, which adds only the 38 scalar lines. Never hand-edited.","Merged origin/main (4 commits, none touching this diff's paths) before opening the PR, then re-ran package test/typecheck and the full derived gate list at the new head f9a4f40.",
"Weekly download counts come from registry.npmjs.org/-/v1/search (json-magic 7,140,252; openapi-upgrader 5,508,440), because api.npmjs.org is refused by the egress proxy (CONNECT 403)."
],
"open_questions": [
{
"question": "Reachability: the card says the cloud shelf pipeline runs this script, but it is not in the npm tarball (files: dist, README.md, CHANGELOG.md) and has no bin, so only a checkout of objectstack-ai/objectstack can run it (
pnpm installplus a build of the closure, because the script imports operationActionKey from src). I cannot read the cloud repository, so whether its pipeline can check out this repo is not measured.","options": [
"A: keep it a repo script (this PR); the shelf pipeline checks out objectstack and runs
pnpm --filter @objectstack/connector-openapi normalize-openapi. Cost: a full workspace install per pipeline run; no published-surface change.","B: publish it as a bin of @objectstack/connector-openapi. This moves the two @scalar packages to dependencies (about 4 extra packages for every consumer of the connector) and adds a published CLI surface plus a changeset. Effort: small code change, needs a ruling (widens the published surface).",
"C: a new
ossubcommand in packages/cli. Triage ruled against it unless the maintainer wants it published to authors."],
"recommendation": "A now, because it is what triage ruled and costs no published surface. Move to B only if the cloud pipeline cannot run a monorepo checkout, which the cloud side should measure."
},
{
"question": "The inlining cut is lossy by necessity, so its default needs a decision. Measured whole-document output size by --max-depth: depth 0 Stripe 6.1 MB / DocuSign 1.6 MB; 1 -> 14.8 / 4.9 MB; 2 -> 63.3 / 52.9 MB (shipped default); 3 -> 361 / 160 MB. At depth 2 a DocuSign envelopeDefinition body keeps recipients but stubs signer (a typed object leaf with an explanatory description).",
"options": [
"A: default 2 (shipped). Output is tens of MB for the whole document and much smaller with --include-pattern.",
"B: default 1. About 5x smaller, but request bodies lose one more named level.",
"C: separate budgets for inputs (parameters, request bodies) and outputs (responses), so action inputs stay deeper. More flags and code."
],
"recommendation": "A, because it keeps two named levels of every body and response at a size the provider loads in about 0.6 s. Revisit C if authors report missing body fields."
},
{
"question": "Swagger 2.0 operations with an empty consumes list (DocuSign: 187 of 414): the script refuses by default and fills them only with --empty-consumes MEDIA_TYPE. Should the default be to auto-fill with application/json instead?",
"options": [
"A: refuse by default (shipped). Loud, in line with 'prefer failing to falling back'; the shelf pipeline passes the flag explicitly.",
"B: auto-fill application/json and report the count. Friendlier, but it silently guesses a vendor's media type."
],
"recommendation": "A, because a guessed media type is the silent-wrong class this script exists to remove, and the refusal names the operations and the flag."
}
],
"out_of_scope_findings": [
"class: a · reach: public door = declarative
provider: 'openapi'materialization (createOpenApiProviderFactory, boot path). Wrong answer: an OpenAPI 3.0.3 document whose path item carriesparameters: [{name: 'petId', in: 'path', required: true}]materializes action getPet with inputSchema null (measured at f9a4f40 via a scratch tsx run). The required path parameter is missing from the action schema an AI or flow author reads; the handler still interpolates it if the caller happens to send it. · evidence: openapi-connector.ts collectOperations spreads only the operation object and buildInputSchema reads op.parameters, so path-item-levelparametersare never read. · Seam: spec: OpenAPI 3.x Path Item Objectparameters→ runtime: packages/connectors/connector-openapi/src/openapi-connector.ts collectOperations/buildInputSchema · Swagger 2.0 input is unaffected after normalization, because the upgrader hoists path-level parameters into operations. This PR deliberately does not hoist them for 3.x input (no workaround of a generator defect). · dedupe words: path-level parameters, path item parameters, inputSchema missing path parameter, collectOperations, openapi generator parameters","carrier: none · noted, not filed: discriminator.mapping strings in a normalized document name component schemas that were dropped; dormant, the generator never reads discriminator.",
"carrier: none · noted, not filed: @scalar/openapi-upgrader lets the global
produceswin over an operation-level one (document.produces ?? operationItem.produces), an upstream third-party precedence slip with no effect on either real document."]
}
objectstack-fleet commentedon Oct 29, 2026
epic:#27293#1 · sunhaolin · session_017xRBcVBKAFk1ntXSjvPt7U
ACCEPT → PR #27436 comment 6490424620 · head
f9a4f408· 2026-10-29T07:14Z. Ready and on auto-merge. Claim addendum (claim 6489063801, File surface):packages/connectors/connector-openapi/tsconfig.scripts.json, the type-check program forscripts/**;README.mddeliberately not edited (it ships, the script does not). The dev's three open questions are decided on the PR (repo script;--max-depth2; emptyconsumesrefused by default). Follow-up filed: #27463 (path-itemparametersnever read by the generator).objectstack-fleet commentedon Oct 29, 2026
epic:#27293#1 · sunhaolin · session_017xRBcVBKAFk1ntXSjvPt7U
MERGED → PR #27436 through the merge queue (merge commit
262ab67a4) · 2026-10-29T07:32Z. Closedcompletedby itsFixes #27373;pm:dispatchedremoved in this act,pm:epickept. Follow-up: #27463 (path-itemparametersnever read by the generator).