Skip to content

fix(cli): os build carries a package-relative OpenAPI connector spec into the artifact - #28533

Merged
objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-28505-app-package-file-install
Nov 1, 2026
Merged

objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-28505-app-package-file-install

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

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

维护者速读

  • 改了什么:os build 现在把 provider: 'openapi' 连接器以包内相对路径引用的 OpenAPI 文档读出来,直接内联写进 dist/objectstack.json;路径无效时构建失败。
  • 为什么改:装进别的运行时(os package install 或市场安装)只带过去那份 JSON,路径会按宿主自己的目录解析,找不到文件,连接器静默不出现;Stripe 应用装上即用不了。
  • 风险与回滚:产物里 providerConfig.spec 由路径变成对象,体积随文档增大(Stripe 由约 4.7 KB 到 59.8 KB);读产物的云端流水线需接受内联形态(spec 本就允许)。回滚即还原本 PR,源码不需要改。
  • 先看:packages/cli/src/utils/bundle-connector-spec-files.ts(只识别 openapi 的 spec 字符串且非 http(s) 的那一种);packages/cli/src/commands/compile.ts 新增的 4a-bis 段;packages/qa/dogfood/test/install-local-connector-spec-file.dogfood.test.ts。
  • 两分钟验证:pnpm --filter @objectstack/connector-app-stripe build,预期输出一行 Bundling connector spec files... 1 document (46.0 KB),且 dist/objectstack.json 里 connectors[0].providerConfig.spec.openapi 为 3.0.0。
  • 需要你做的:无,席位落地。

What changes

  • os build carries a package-relative OpenAPI spec into the artifact. New packages/cli/src/utils/bundle-connector-spec-files.ts, wired in compile.ts right after the seed-file bundling, split the same way (collect = the gate, place = the assembly). For every connector entry (top level, manifest, and each packages[i].manifest body) with provider: 'openapi' whose providerConfig.spec is a non-empty string that is not an http(s) URL, the file is read relative to the directory holding objectstack.config.ts, confined to it by the runtime loader's own rule, parsed as JSON, and written in place of the path. Every other provider, key and form is written as before.
  • The build refuses a reference it cannot carry: absolute, climbing out of the root, naming no file, or naming a file that is not a JSON object. Located at the entry (connectors[0].providerConfig.spec), in the text face and in --json.
  • Docs: one paragraph in content/docs/automation/connectors.mdx (the provider: 'openapi' section) says the build carries the file.
  • Changeset: @objectstack/cli patch.

What this changes for readers of the artifact (the cloud ingestion pipeline among them): an artifact built by this CLI carries providerConfig.spec as the inline document object, never the path, and is larger by the document's size. The inline form is one the spec schema (providerConfig: z.record(z.string(), z.unknown())) and the openapi provider already accept; no packages/spec change.

The measurement (before any code), at 23d1303d

Scratch app, provider: 'openapi', providerConfig.spec: './openapi/fixture.openapi.json', auth: bearer with the credential set:

  1. os build wrote dist/objectstack.json (0.7 KB) with providerConfig: { spec: './openapi/fixture.openapi.json' }, no document.
  2. Installed into the showcase (a different root) through POST /api/v1/marketplace/install-local, body exactly as packages/cli/src/commands/package/install.ts builds it for an artifact file: 200, hotLoaded: true, credentialRefs[0].status: set.
  3. The reconcile then logged, and GET /api/v1/automation/connectors did not list the instance at all (not even degraded):
ERROR [Automation] failed to materialize connector instance 'qa28505_fixture' via provider 'openapi' (ADR-0097). {"error":"connector-openapi provider: connector 'qa28505_fixture' failed to read providerConfig.spec './openapi/fixture.openapi.json': package file ref './openapi/fixture.openapi.json' could not be read (resolved to '/home/user/objectstack-issue-28505/examples/app-showcase/openapi/fixture.openapi.json'): ENOENT ..."}

The install door caches the artifact JSON alone (STORAGE_DIR/SAFE_ID.json, marketplace-install-local-plugin.ts header), and service-automation hands every instance createPackageFileLoader(this.options.packageRoot), the host's root. An installed package has no directory on disk, so "resolve against the installed package's own root" (route b) has nothing to resolve against without the artifact carrying the file anyway; route (a) needs no contract change, so it is the route taken. ADR-0097 (§ Deliberate scope boundaries) describes the file form as resolved against "the declaring stack/package root (packageRoot, CLI default: the objectstack.config.ts directory)" and does not anticipate an installed package having a root of its own.

After the fix, the real Stripe app (packages/apps/connector-stripe, now on main) builds to 59.8 KB with the 46.0 KB document inline, and installed into the showcase the same way it materializes ready with its eight actions.

Tests

  • packages/cli/src/utils/bundle-connector-spec-files.test.ts (unit tier): carried with its other keys kept; manifest and packages[] bodies; inline document, http(s) URL, another provider and a descriptor left as written; five refusals, each located and naming the reference, nothing changed.
  • packages/qa/dogfood/test/install-local-connector-spec-file.dogfood.test.ts: spawns the BUILT os build on a one-connector app, asserts the artifact carries the document, installs it into the showcase through install-local and asserts GET /automation/connectors lists the instance ready with the document's operation. Declares a measured test-budget: 20 (class: spawns the real producer once per single-cause fixture).
Red before, green after, one ablation (dogfood file, reads the CLI's dist)
  • Red, CLI dist built from main: Tests 2 failed (2) — expected './openapi/fixture.openapi.json' to deeply equal { openapi: '3.0.3', …(3) }; the listing held only the showcase's five connectors.
  • Green after pnpm --filter @objectstack/cli build: Tests 2 passed (2).
  • Ablation, committed head 9b81f435: node scripts/ablation-replace.mjs --file packages/cli/src/commands/compile.ts --anchor 'placeConnectorSpecFiles(connectorSpecs);' --replacement 'void placeConnectorSpecFiles; /* ABLATED_SPEC_PLACE */' -- … → anchor 1 -> 0, blob daba252de551 -> 0994c37b6d13; rebuild; ablation-dist-preflight @objectstack/cli ABLATED_SPEC_PLACE → marker present in 1 built file; Tests 2 failed (2); restored blob == HEAD (daba252de551) and git diff HEAD is empty. Restore leg: rebuild; preflight --absent → marker absent from all 772 built files, working tree clean against HEAD; Tests 2 passed (2).
Final head f6ca481 (after merging origin/main)
  • pnpm --filter @objectstack/cli exec vitest run --project unit --maxWorkers=2 src/utils/bundle-connector-spec-files.test.ts src/utils/bundle-seed-files.test.ts → Tests 16 passed (16).
  • pnpm --filter @objectstack/dogfood exec vitest run --maxWorkers=2 test/install-local-connector-spec-file.dogfood.test.ts → Tests 2 passed (2).
  • pnpm --filter @objectstack/connector-app-stripe test → Tests 16 passed (16).
  • pnpm --filter @objectstack/cli typecheck and pnpm --filter @objectstack/dogfood typecheck → exit 0 (tsc --listFiles: the dogfood program holds the new test; the CLI's tsconfig.json program covers src/** tests).
  • CLI integration tier: declared to CI (no integration-tier file or spawn entry touched).

Acceptance notes

  • os validate does not run this gate; only os build does (seed files run in both). A missing spec file is therefore refused at build, not at validate.
  • os dev's watcher watches objectstack.config.ts and src/; a spec file outside src/ (the Stripe app's ./openapi/) edited during os dev reaches the running artifact at the next recompile, not on its own.
  • A non-OpenAPI-3 document carried inline is refused at materialization naming "(the inline document)" rather than the file path.
  • The runtime half is untouched: loadPackageFile / resolvePackagePath still resolve an installed package's file ref against the host root. Measured: an installed package naming ./src/system/connectors/status-openapi.json materialized ready with the showcase host's own document. Reported for a separate card, not fixed here.
  • ADR-0097's scope-boundary paragraph does not mention build-time carrying; an amendment note is the maintainer's call (governed surface, not edited here). skills/objectstack-automation/references/integration-recipes.md recipe 1 stays correct ("ships in the package").

Verification

Derived gate families, local, head f6ca481

node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack → 90 commands; all 90 run, exit 0 each (two first exited 3, PREREQUISITE NOT MET for unbuilt packages outside this diff — check:skill-examples needed @objectstack/client-react dist, check:dual-build-cjs-loads seven package dists; built, re-run: 278 prose examples type-check across 3 surface(s) and 113 published require entry point(s) across 70 package(s) load). --ran reconciliation: 90 derived famil(ies) accounted for — 90 run, 0 NOT-MEASURED. Repo-wide scans (pnpm lint) and test:repo are CI's.


Generated by Claude Code

@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 Nov 1, 2026
@github-actions

github-actions Bot commented Nov 1, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/cli, touching 10 documentable anchor(s).

21 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json 37184244b82b9e4234581b653cdc7554e74e6136.

⛔ 6 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 1 anchor(s) matched too much of the corpus to be a work list: os validate (command, 81 pages)
  • 6 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 60 of 212 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 — 35 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 37184244b82b9e4234581b653cdc7554e74e6136 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 9a77ea3ed31a58755d68bda5dba16c9b9afb1563 — the merge of head 842ba0f7cb1392adc5de5d98af4f2cef1902864d into base 37184244b82b9e4234581b653cdc7554e74e6136, 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 9a77ea3ed31a58755d68bda5dba16c9b9afb1563 && git checkout 9a77ea3ed31a58755d68bda5dba16c9b9afb1563
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 37184244b82b9e4234581b653cdc7554e74e6136 842ba0f7cb1392adc5de5d98af4f2cef1902864d && git checkout -B drift-repro 37184244b82b9e4234581b653cdc7554e74e6136 && git merge --no-ff 842ba0f7cb1392adc5de5d98af4f2cef1902864d

node scripts/docs-audit/affected-docs.mjs --json 37184244b82b9e4234581b653cdc7554e74e6136

⚠️ 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 37184244b82b9e4234581b653cdc7554e74e6136 → 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

席位核对

ACCEPT at head f6ca481e · Implemented-by: claude/issue-28505-app-package-file-install · Reviewed-by: this seat (lane ACCEPT at TIER_DEFAULT). Clause-②: no. No packages/spec file is touched, so no CONTRACT_REVIEW_TIER record is owed: the inline spec object is a form the openapi provider already accepts.

Check Result
Shape Draft, base main. First line is Fixes #28505, the only closing keyword.
Scope 6 files, +476/−0 against the merge base. Not governed (check-governed-merges --pr 28533). One changeset for @objectstack/cli. Inside the claimed surface: build route (a).
Measurement first Done as the card asked, at 23d1303d. The install-local of a built artifact answered 200 hotLoaded:true, then the reconcile logged ENOENT on the host path, and the instance was absent from GET /automation/connectors. The install caches the JSON alone, so route (b) has no root to resolve against.
Evidence 2 dogfood cases go red before (the path survives in the artifact; the listing holds only the host's connectors) and green after. One ablation of placeConnectorSpecFiles turns exactly those red. 14 unit cases cover the refusals: absolute, escaping, missing, non-JSON, non-object. The Stripe app on main builds to 59.8 KB with its 46.0 KB document inline and materializes ready with 8 actions once installed.
Gates Local: 90 derived, 90 run, 0 unrun. CI on the head is still running: 30 success, 12 skipped, 7 in progress, none red so far.
Artifact readers providerConfig.spec of an openapi entry is now the document, never a path, and the artifact grows by the document's size. The cloud pipeline (#28278) reads these artifacts: it gets a note on its card.

Out of scope, filed: #28534 is the runtime half of the defect: the materializer resolves an installed package's file ref against the host's root, and the dev measured it binding the host's own document. It is a child of #27293 and carries the dev's options A/B/C. #28535 is a separate defect: os package install ./dist/objectstack.json leaves engines nested, so install-local skips the ADR-0087 check. It is not epic scope and goes to triage. The dev's "noted, not filed" items are not filed: each is either a convenience gap or governed ADR wording.

Landing: not governed. The seat lands it (pr_ready + automerge_enable) once CI on this head is green.

…h new sites in the parity ledgers

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017xRBcVBKAFk1ntXSjvPt7U
@github-actions github-actions Bot added size/l and removed size/m labels Nov 1, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

epic:#27293#1 · sunhaolin · session_017xRBcVBKAFk1ntXSjvPt7U

席位核对

ACCEPT at head 842ba0f7 (delta over the ACCEPT 6553798451 on f6ca481e) · Implemented-by: claude/issue-28505-app-package-file-install · Reviewed-by: this seat (lane ACCEPT at TIER_DEFAULT). Clause-②: no. No packages/spec file is touched.

Check Result
The red on f6ca481e Test Core (6/8): 3 tests in 2 @objectstack/cli ledger files, this PR's own.
Delta (842ba0f7, 4 files, +152/−1) collectConnectorSpecFiles is now wired into os validate (step 3d-ter, same --json errors envelope, exit 1) and listed in SHARED_NON_REGISTRY_GATES. placeConnectorSpecFiles sits in the NOT_A_GATE "Artifact assembly" row, beside placeSeedFiles. The top-level stack.connectors read is classified, with the same shape and reason as the bundle-seed-files.ts :: stack.data row. A new e2e runs the real CLI from source: os build carries the document and keeps the other keys, and both commands refuse a missing file at connectors[0].providerConfig.spec.
Tests The whole @objectstack/cli suite, not file-selected: Test Files 544 passed and 1 skipped (545); Tests 6743 passed and 9 skipped.
Gates CI on 842ba0f7: 50 check-runs, 38 success, 12 skipped, none red. check-expected-skips --pr 28533: OK. Mergeable clean. Not governed.
Stale PR-body line (the dev writes the body once) The "Acceptance notes" bullet saying os validate does not run the new gate is out of date as of 842ba0f7: it now does.

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/l tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

1 participant