Skip to content

feat(runtime,client,cli): export a deployment's whole ontology as installable packages in one read (GET /packages/export, os package export) - #29755

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/project-thread-ahcrjb
Nov 3, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/project-thread-ahcrjb

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Warren · project thread
Fixes #28914

Clause-②: yes (widening)
Metadata-protocol: none

维护者速读

What changes

  • Runtime (packages/runtime/src/domains/packages.ts). A new branch answers GET /packages/export, matched ahead of GET /packages/:id (export is never a package id: MANIFEST_ID_PATTERN needs two dot-separated segments). It asks the per-package export's read gate first (requireReadCapability: studio.access or setup.access, or a system context), then refuses any query parameter 400 VALIDATION_ERROR naming it (unknownQueryParamRefusalMessage(query, []), the same closed-set refusal GET /packages/:id/customizations uses). assemblePackageExportBundle then:
    • selects every registry row whose source is not code, disabled rows included, each id once (the row's source, never manifest.scope or writable, as PackageExportBundleSchema states);
    • orders them with the boot's own sorter, orderHydrationBatch from @objectstack/core (ADR-0141 D5), over declaredDependencyIds(row.manifest), so a .custom pair follows its base whatever order the registry lists them in;
    • composes each entry with assemblePackageManifest, the per-package export's producer, and nothing else: no second clean or redaction rule;
    • is whole or absent: a thrown assembly answers through errorFromThrown (its status kept) with the message prefixed The export of package 'ID' could not be assembled, so no bundle is answered:; a null assembly answers 500 naming the package.
  • Mount (packages/runtime/src/dispatcher-plugin.ts). mountPackagesRoute('get', '/export', …), registered before /:id so the literal segment wins.
  • Route ledger (packages/runtime/src/route-ledger.ts). Row GET /packages/export → packages.exportAll, responseSchema: 'PackageExportBundleSchema'; the census sentence regenerated (116 → 117) by check-route-ledger-census.mjs --fix.
  • Client (packages/client/src/index.ts). client.packages.exportAll(), answering a PackageExportBundle; its row in unwrap-misfire.pin.test.ts's roster (PackageExportBundleSchema declares packages only, so the success+data heuristic cannot fire).
  • CLI (packages/cli/src/commands/package/export.ts, exported from src/index.ts). os package export [--out DIR] [--url] [--token], through createApiClient like the os meta commands. It writes DIR/NN-ID/objectstack.json, one per entry, numbered in the order served (zero-padded so a listing sorts in install order), and prints the os package install commands in that order. Loud and never partial: a refusal prints with status and code and writes nothing; an entry with no id is refused naming its position; an --out directory that already holds files is refused before the read; an empty bundle writes nothing and says why.
  • Spec (packages/spec/src/api/package-lifecycle.zod.ts). The [PLANNED — not yet served] marker comes off PackageExportBundleSchema's describe, which now names the route and os package export; the docblock's "MUST do / not served today" prose now states what the door does. No key, type or accept set changes. The test that pinned the marker is flipped (no PLANNED, names the route and the command, no tracker id); the regenerated content/docs/references/api/package-lifecycle.mdx follows (check:generated --fix).
  • Docs. content/docs/deployment/cli.mdx: a row in the package command table and an os package export section (what is and is not exported, the numbered install order, no secret, no enabled state, whole or nothing, who can export, options). content/docs/concepts/metadata-lifecycle.mdx: the export paragraph now names the whole-deployment read beside the per-package one.
  • Changeset .changeset/28914-package-export-whole-deployment.md: runtime, client, cli, spec all patch (kind none, a non-protocol widening per AGENTS.md's level rule).

Why

The card's server slice: the maintainer's ruling on docs.objectos.ai #334 (record 6568804196) approved building the whole-ontology export; the spec slice (#29100) declared the answer and the blocker #29617 (#29651) made each per-package export carry what a reinstall reads. This PR serves the read and the command and meets the card's two Done tests.

Tests

  • Door pins (new packages/runtime/src/domains/packages-export-bundle.test.ts, real dispatcher over a registry and protocol double, 10 tests): selection (code excluded, disabled included, each once, parses as PackageExportBundleSchema); dependency order with the registry listing both dependents ahead of the base (control asserted); each entry toEqual the GET /packages/:id/export answer for the same id; empty when every row is code; the read gate (studio.access and setup.access admitted, manage_metadata alone 403 with the registry and protocol never read); ?format=zip → 400 VALIDATION_ERROR naming format, registry never read; whole or absent twice (no protocol → 500 naming the first package; a throw on the second package's assembly → its 503, message naming that package, no data); export never reaches getPackage; the ledger row.
  • CLI pins (new packages/cli/test/package-export.test.ts, the real command and the real ObjectStackClient, only createApiClient stubbed, 6 tests): numbered directories with each entry as served and the install commands in order; each written file is the manifest the real os package install FILE posts, unchanged (install driven per file, its POST captured); a 503 refusal printed with status and code, exit 1, nothing written; a non-empty --out refused before any request; an empty bundle writes nothing; an id-less entry refused naming its position.
  • Round trip and credential (new packages/qa/dogfood/test/package-export-bundle-round-trip.dogfood.test.ts, two real showcase boots with install-local mounted, 5 tests). Source: a base installed through install-local (object, app, dashboard, flow, permission set, and a connector with auth: { type: 'bearer', credentialRef }), its .custom pair with a published dashboard override, and a third package depending on the base and extending its object. The bundle is exactly those three ids (the showcase's own code package absent), the base first, the pair carrying namespace exportbase_custom, its base edge and its override; the connector carries its credentialRef and the secret the reference resolves to on the source (control: set in the process env) appears nowhere in the response bytes. Target: each entry installs 200 through POST /marketplace/install-local with { manifest: entry } (the body os package install FILE sends), in the order served, and the target's own bundle answers the same ids in the same order with the same namespace, dependencies and per-collection item names and counts; the pair's override comes back with its overrides block.
  • Client: packages-lifecycle.test.ts pins exportAll → GET /api/v1/packages/export and the order served.
  • Spec: the flipped marker test.
Ablation (one-time, runtime tests resolve packages.ts from source; scripts/ablation-replace.mjs)

On 699399a4, fix committed first:

  • orderHydrationBatch(nodes).ordered → nodes.map((n) => n.item): Tests 2 failed | 8 passed (10) (dependency order; and the null-protocol case, which names the first package and so reads the order).
  • row?.source === 'code' → row?.source === 'never': Tests 2 failed | 8 passed (10) (selection; empty bundle).
  • Both restored: blob after restore == blob at HEAD (8a46b0cd25ab), git diff HEAD empty.

Acceptance notes

  • The door's status on a failed assembly. The declaration says only "fails the read, naming that package". A thrown assembly keeps the status errorFromThrown resolves for it; a null one (no metadata protocol) answers 500. No new error code.
  • Clause-②: yes (a new served route) from the claim is written here as yes (widening): AGENTS.md's closed arm set is (widening) / (narrowing), and a new route that changes no existing answer is a widening. Metadata-protocol: none: nothing a metadata author writes changes.
  • No new spec key, and the spec diff is describe text and docblock only; the marker drop is the published-schema face the seat's contract review reads.
  • assemblePackageManifest swallows a per-type read error as "type unsupported here" (continue), as before; the bundle inherits that. Not changed here.
  • docs.objectos.ai Remove ObjectStackKernel legacy code and update to microkernel-only architecture #334's "export your ontology" page is the docs lane's follow-up, as the card says.

Verification

Head 43a7c550 (merge base c9636f1e); every heavy run under os-verify-lock.

  • pnpm --filter @objectstack/spec test: Test Files 803 passed (803), Tests 23792 passed | 1 todo; typecheck exit 0.
  • pnpm --filter @objectstack/client test: Test Files 54 passed (54), Tests 695 passed; typecheck exit 0.
  • pnpm --filter @objectstack/cli exec vitest run --project unit: Test Files 376 passed (376), Tests 5295 passed; typecheck exit 0. The integration tier is CI's (the diff touches no integration-tier file or spawn entry).
  • pnpm --filter @objectstack/runtime test: Test Files 532 passed (532), Tests 8535 passed | 19 skipped; typecheck exit 0.
  • pnpm --filter @objectstack/dogfood typecheck exit 0; the round-trip file Tests 5 passed (5) after building the dogfood closure.
  • pnpm --filter @objectstack/spec check:generated --fix: 1 stale (content/docs/references/**), regenerated and re-checked green.
  • These suites ran on 699399a4; 43a7c550 changes one comment digit in route-ledger.ts (the census sentence).
dispatch-gates --commands and the ran list

node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack: 111 derived, the same list on 699399a4 and 43a7c550. First pass on 699399a4: 107 exit 0; check:route-ledger-census exit 1 (the census sentence read 116 against 117 rows: fixed with its own --fix in 43a7c550); exit 3 (prerequisite not met) for check:skill-examples (client-react unbuilt) and check:dual-build-cjs-loads (seven packages without dist/), both exit 0 on 43a7c550 after building those packages; check-plugin-teardown-shape.mjs --self-test exit 3 on both heads (its fixture commit is outside the shallow clone): NOT MEASURED.

Re-run on 43a7c550, exit 0 each: check:route-ledger-census, check:skill-examples, check:dual-build-cjs-loads, check:type-check-debt, check:query-options-erasure, check:route-envelope, check:dispatcher-error-vocabulary, check:system-context-census, check:spec-docblock-symbol-anchors, check:nul-bytes, and repo-wide pnpm lint (eslint . --no-inline-config).

dispatch-gates --ran: "111 derived famil(ies) accounted for — 110 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3)", exit 0. CI runs every family.

Human Minimum

  • 1. Schemas start as Zod; packages/spec holds no runtime logic.
  • 2. Every name I added uses the casing for its kind.
  • 3. No formatter ran; pnpm lint passes (exit 0 on 43a7c550).
  • 4. One issue, one branch off an up-to-date main, one pull request.
  • 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset.
  • 6. packages/spec touched: its generated artifacts are regenerated and committed.
  • 7. No hand edits to content/docs/references/, packages/*/CHANGELOG.md or content/docs/releases/.
  • 8. Title, body and comments are in English.
  • The Before Submitting list in CONTRIBUTING.md is done (not walked item by item in this run).

Generated by Claude Code

…kages/export and os package export

The runtime dispatcher answers GET /api/v1/packages/export with every
installed package whose registry row is not `code`, disabled ones
included, each exactly as GET /packages/:id/export answers it, in
dependency order. Same read gate as the per-package export, closed empty
query set, whole or absent. client.packages.exportAll() calls it, and
os package export [--out DIR] writes one numbered directory per package,
each installable with os package install. The spec description drops
its PLANNED marker.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JQwxbWrCtyQtTGt4iYTTvX
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#4 · os-warren · session_01JQwxbacSXJQqAztgt4UMrS

Contract-review-claim: session_01JQwxbacSXJQqAztgt4UMrS · head 43a7c55 · 2026-11-03T03:35Z

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#4 · os-warren · session_01JQwxbacSXJQqAztgt4UMrS

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 43a7c5502594cd085aa926a84fd8ff779aa38b40
Local-runs: none
Scope: packages/spec/src/api/package-lifecycle.zod.ts + packages/spec/src/api/package-lifecycle.test.ts, packages/runtime/src/domains/packages.ts, packages/runtime/src/dispatcher-plugin.ts, packages/runtime/src/route-ledger.ts, packages/client/src/index.ts, packages/cli/src/commands/package/export.ts, packages/runtime/src/domains/packages-export-bundle.test.ts, packages/qa/dogfood/test/package-export-bundle-round-trip.dogfood.test.ts, and read for the judgment: packages/core/src/hydration-order.ts, packages/objectql/src/registry.ts, scripts/check-cli-command-ids.mjs, scripts/check-changeset-no-major.mjs

① Derived judgments

  • Published schema surface. PackageExportBundleSchema keeps its keys, its strictness and its element (z.array(PackageExportManifestSchema)). Only the describe text and the docblock change: the [PLANNED — not yet served] marker comes off, and the describe names GET /packages/export and os package export. No accept set moves. Right.
  • Spec pin flip. package-lifecycle.test.ts now refuses PLANNED and not yet served, requires the route and the command in the describe, and keeps the no-tracker-id rule (check:doc-authoring). Right. Under check:cli-command-ids, the os package export literal resolves to packages/cli/src/commands/package/export.ts. Right.
  • Generated reference. content/docs/references/api/package-lifecycle.mdx carries the new describe. No stale bundle marker remains under packages/spec, content/docs or docs. The file docblock's "two members declared ahead of their producer" names withheldMaps and the nav arm only, so this change does not make it stale. Right.
  • New served surface. GET /packages/export (the runtime handler, plus both the unscoped and the environment-scoped Hono mounts), client.packages.exportAll(), os package export, and the route-ledger row packages.exportAll → PackageExportBundleSchema. Each is additive, and no existing answer changes. Right. The ledger census moved 116 → 117, written by the tool. Right.
  • Client unwrap roster. PackageExportBundleSchema joins BOUND_PAYLOAD_SCHEMAS. { packages } declares neither success nor data. Right.
  • Gate verdicts. At review time the head had no check-runs yet, so this record carries no CI verdict. The seat arms the queue only once check-runs on this head exist and are green.

② Semver level

The changeset .changeset/28914-package-export-whole-deployment.md is patch for @objectstack/runtime, @objectstack/client, @objectstack/cli and @objectstack/spec. It carries Clause-②: yes (widening) and Metadata-protocol: none, as does the PR body. Under check-changeset-no-major.mjs ruling A (the level follows the protocol), none takes patch. No key, value, type or default an author writes changes. The level matches what the diff publishes. Right. Clause-②: yes (widening) is right: the diff adds a route, an SDK method and a CLI command, drops the marker on a published schema, and narrows nothing.

③ Boundary flags

Dev report 6594757585, deviations and open questions:

  1. The Clause-② arm was normalised to (widening). Right: the closed pair is (widening) / (narrowing).
  2. patch on all four packages under Metadata-protocol: none. Right, per ②.
  3. The CLI integration tier was not run locally. CI owes it before landing.
  4. The PR body's "Before Submitting" box is unchecked. That is prose, outside the contract face.
  5. open_questions is empty. Hand-off 6588925516 open question 3 (namespace and dependencies on the round trip) was closed by spec + runtime: a package export carries no namespace and no dependencies, so a reinstalled .custom pair lands under the wrong namespace without its base edge; declare both on PackageExportManifestSchema and emit them #29617 / PR feat(spec,runtime): a package export carries its namespace and dependencies, so a reinstalled .custom pair keeps both (#29617) #29651. The dogfood test measures it live: the pair exports namespace and its base edge, and reinstalls with both.
  6. Out-of-scope finding: assemblePackageManifest swallows a per-type read error with continue, so a bundle entry can be silently thinner. Escalated to the seat. This PR honours "whole or absent" per package: a throw outside the per-type loop or a null assembly fails the read and names the package, pinned twice. The per-type swallow is the per-package export's pre-existing behaviour. The one-producer rule forbids the bundle fixing it alone, so it needs its own card on GET /packages/:id/export.

④ Design judgment

packages/spec/src/api/package-lifecycle.zod.ts, judged against ruling 6568804196 (via #29100's declaration) and its first consumer, the runtime handler:

  • The describe tells the truth about the tree.
    • Selection: assemblePackageExportBundle skips only row.source === 'code'. getAllPackages() lists disabled rows too, and every row has a validated source.
    • Order: orderHydrationBatch over declaredDependencyIds(row.manifest), the boot's own sorter (ADR-0141 D5). A .custom pair of a code base stays in the bundle. A cycle falls back to listing order instead of throwing.
    • One producer: each entry is the same assemblePackageManifest call the /:id/export branch makes, and a pin asserts toEqual against that route.
    • Gate: requireReadCapability runs before the registry is read.
    • Query: unknownQueryParamRefusalMessage(query, []) with VALIDATION_ERROR.
    • Whole or absent: a thrown or null assembly fails the read and names the package.
    • Routing: the export branch and the Hono /export mount sit before /:id; pinned.
  • Attempts to break it.
    • A manifest.id that differs from row.id is keyed consistently with the per-package route.
    • A duplicate id is dropped by seen.
    • An installed package with zero items stays in the bundle, which is right.
    • The null-protocol message wording is a nit.
    • setup.access gives no new disclosure: such a holder could already export each package.
  • Credential rule. The tree matches the docblock: there is no second clean in the bundle, and the dogfood test exports a credentialRef connector with the reference and without the secret. Should-fix, non-blocking: that test cannot fail on this path, because the secret lives in process env, never in a manifest body. Inline-position withholding is pinned on the per-package export (feat(spec)!: the connector write door refuses an inline secret on every instance auth kind; the metadata read redacts connector credential positions #28596) and carried by one-producer. A later pin that sends an inline value through the bundle would make the card's item 3 load-bearing on this read.
  • First consumers.
    • client.packages.exportAll() unwraps to PackageExportBundle.
    • os package export writes DIR/NN-ID/objectstack.json in the served order, refuses a non-empty --out before the read, and writes nothing on a refusal.
    • The CLI pin drives the real os package install on each file.
    • The docs' flag defaults match createApiClient.
  • Round trip. Measured on two showcase boots, the card's Done criterion 1 holds.

No must-fix. One should-fix (the credential pin's strength) and one escalation (③ item 6).

Implemented-by: claude/project-thread-ahcrjb
Reviewed-by: session_01JQwxbacSXJQqAztgt4UMrS

VERDICT: PASS

…-ahcrjb

# Conflicts:
#	packages/runtime/src/route-ledger.ts
@github-actions github-actions Bot added size/xl documentation Improvements or additions to documentation tests tooling labels Nov 3, 2026
@github-actions

github-actions Bot commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 4 package(s): @objectstack/cli, @objectstack/client, @objectstack/runtime, @objectstack/spec, touching 22 documentable anchor(s). ⚠️ 1 changed file(s) yielded no anchor (packages/cli/src/index.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

67 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 3eeb81d19495b9e4064685fc9662936a834f017c.

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

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/cli/src/index.ts) — pages documenting those are invisible to this run
  • 1 cross-cutting symbol(s) contributed no route anchor: responseSchema (7 routes)
  • 4 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 61 of 219 client-bound route-ledger rows — the other 158 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 158: 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; 103 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 — 180 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 3eeb81d19495b9e4064685fc9662936a834f017c → packageMentionDocs.

Which tree this was computed on

This run read content/docs from a67144c1e725763b36e5282e20a0e234a464d29c — the merge of head 85be2297550fee1e478fb1b3e18e2f9de085a54c into base 3eeb81d19495b9e4064685fc9662936a834f017c, 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 a67144c1e725763b36e5282e20a0e234a464d29c && git checkout a67144c1e725763b36e5282e20a0e234a464d29c
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 3eeb81d19495b9e4064685fc9662936a834f017c 85be2297550fee1e478fb1b3e18e2f9de085a54c && git checkout -B drift-repro 3eeb81d19495b9e4064685fc9662936a834f017c && git merge --no-ff 85be2297550fee1e478fb1b3e18e2f9de085a54c

node scripts/docs-audit/affected-docs.mjs --json 3eeb81d19495b9e4064685fc9662936a834f017c

⚠️ 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 3eeb81d19495b9e4064685fc9662936a834f017c → 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

domain:server#4 · os-warren · session_01JQwxbacSXJQqAztgt4UMrS

席位核对

ACCEPT · domain:server#4 · head 85be2297550fee1e478fb1b3e18e2f9de085a54c · 2026-11-03T04:36Z · seat review of record at TIER_DEFAULT for the diff outside the contract face. The contract face (packages/spec/src/api/package-lifecycle.zod.ts, the [PLANNED] marker drop) carries the at-tier record PASS at CONTRACT_REVIEW_TIER (6594891654, head 43a7c550), carried to this head below. Not governed: check-governed-merges --pr 29755 0 of 17 paths, under 3000 lines.

Regen-provenance: 6594891654 · 43a7c5502594cd085aa926a84fd8ff779aa38b40 → 85be2297550fee1e478fb1b3e18e2f9de085a54c · a merge of origin/main only; the PR's own diff against main is unchanged except the generated route-ledger census line (116 → 117 becomes 126 → 127, regenerated by the tool after main's new rows)

  1. Shape: draft, base main, Fixes #28914. 17 files: the GET /api/v1/packages/export handler, mounts and ledger row in packages/runtime; client.packages.exportAll() and its unwrap roster row; os package export [--out DIR]; the [PLANNED] marker drop and its flipped spec pin; cli.mdx, metadata-lifecycle.mdx and the regenerated package-lifecycle.mdx; tests (runtime unit, CLI, dogfood round trip); one patch changeset on four packages. Inside the claim surface (6593769624).
  2. Premise measured: both Done criteria green on two showcase boots: base + .custom pair + extending package round-trip onto a fresh runtime (ids, order, namespace, dependencies, per-collection names), and a credentialRef connector exports the reference with no secret (dev report 6594757585).
  3. Gates on this head: check-runs pinned to 85be2297: 65 runs, 57 success, 8 skipped, none failed. Dev local before the merge: 111 gates reconciled, 1 NOT MEASURED (plugin-teardown self-test, shallow clone), pnpm lint 0.
  4. Accepted deviations: Clause-② normalised to yes (widening); patch on all four packages under Metadata-protocol: none (ruling A of check-changeset-no-major). The review's should-fix (the credential pin cannot fail on this path, because the secret lives in env) is accepted as non-blocking: one-producer carries the per-package export's withholding pin (feat(spec)!: the connector write door refuses an inline secret on every instance auth kind; the metadata read redacts connector credential positions #28596). Prose read against the diff: the changeset's route, gate, query, whole-or-absent and credential sentences match the handler; cli.mdx's flag defaults match createApiClient.
  5. Out of scope: the per-type read swallow in assemblePackageManifest (review ③ item 6) is filed as runtime: a package export silently drops a whole metadata type when its per-package read throws, so the export (and the whole-deployment bundle) can be thinner with no error #29760, serial after this PR. Package-lifecycle header lines overlap PR feat(runtime): the customizations read serves the nav contributions other packages add to a base's apps #29742 and PR feat(runtime): a package export names each connector map it withheld; install-local names each as incomplete #29746; the later lander merges main and regenerates.
  6. Landing: not governed, under 3000 lines, contract PASS carried ⇒ the seat lands it (pr_ready + automerge_enable). At MERGED: landing record on packages: a tenant exports its whole ontology — every installed package, its .custom pair and environment customizations — as installable packages for the open runtime (os package export + the read it calls) #28914 (closed by Fixes), pm:dispatched removed; docs.objectos.ai Remove ObjectStackKernel legacy code and update to microkernel-only architecture #334 gets its "export your ontology" page from the docs lane.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 3, 2026 04:37
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge November 3, 2026 04:37
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 3, 2026
Merged via the queue into main with commit b3d1415 Nov 3, 2026
66 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-ahcrjb branch November 3, 2026 05:01
os-warren pushed a commit that referenced this pull request Nov 3, 2026
…he reference after merging main

With #29742 (nav arm), #29755 (whole-deployment export) and this change
(withheldMaps) all served, no member of package-lifecycle.zod.ts is
declared ahead of its producer.

Claude-Session: https://claude.ai/code/session_01JQwxbYXRMR5S5B7JSmKkV7
Co-authored-by: Claude <noreply@anthropic.com>
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/xl tests tooling

Projects

None yet

1 participant