Repository navigation
Description
domain:devx#1 · os-warren · session_01JQwxbYeYDjwmf7geACxC5Z
18.3.0 pre-cut re-check, wave 5, card W5-EXPORT: a targeted check of the whole-deployment export and its re-install (#28914 / PR #29755, #29342 / PR #29746, ADR-0146 D8 item 3). Sweep anchor: #27377. Wave-4 comparison: none (new surface, no ledger item covers it).
Environment fingerprint
- Framework:
objectstack-ai/objectstackmain@589049b023d82c94508bf9fce752ea19069279e5, checked out detached (ui/the same commit; no console build: every leg is api/cli). - Subject CI: push run 41600797636 re-read at 08:58Z:
completed/success. - Build:
pnpm exec turbo run build --concurrency=2underscripts/pm/os-verify-lock.sh, exit 0, 21m32s (08:27:34Z to 08:49:06Z);check-dev-prereqsgreen (106 artifacts). - Boots, each from its own scratch copy of
examples/app-showcase(node_modules symlinked), ownOS_HOME, upload root and file DB:OS_PORT=P node packages/cli/bin/run.js dev --ui --seed-admin --no-watch -p P -d file:RUN/data.db- source: port 41510, booted 08:49Z;
- target 1 (re-install by
os package install): port 41520, fresh DB; - target 2 (re-install by
POST /marketplace/install-local, plus probes): port 41530, fresh DB.
- Personas: seeded admin
admin@objectos.ai; a member minted byPOST /auth/admin/create-user(+change-password), holding neitherstudio.accessnorsetup.access; anonymous. - install-local ledgers landed in each scratch copy's
.objectstack/installed-packages/; the tree'sexamples/app-showcase/.objectstack/does not exist after the run. - Run executed sequentially by one runner (no subagents: one targeted check, one ontology).
Scope
Selector release-18.3.0-w5-export: one targeted check, no ledger item (so no revision to pin). Surfaces: GET /api/v1/packages/export, client.packages.exportAll(), os package export [--out DIR], withheldMaps per entry, POST /api/v1/marketplace/install-local naming incomplete maps, ADR-0146 D8 (a pair's export is GET /packages/X.custom/export).
Ontology built on the source boot:
| package | how | content |
|---|---|---|
com.qa.w5export |
POST /packages (source authored), ns w5export |
object w5export_item (title, amount), flow w5export_probe, connector w5export_erp with providerConfig.headers {X-W5-Tenant: …}; all drafted with ?package= and published (publish-drafts 3 published / 0 failed) |
com.example.showcase.custom |
POST /packages/com.example.showcase/ensure-customization (201, ns showcase_custom) |
object_extension showcase_task adding w5_region__c, published |
com.qa.w5local |
POST /marketplace/install-local (source marketplace) |
object w5local_thing, flow w5local_probe |
com.qa.w5off |
POST /packages, dependencies: {com.qa.w5export: ^0.1.0} |
object w5off_note, published, then PATCH /packages/com.qa.w5off/disable (200, enabled: false) |
Per-clause verdicts
| # | clause | verdict | evidence (text oracle) |
|---|---|---|---|
| E1 | as admin, GET /packages/export answers every non-code package once, the disabled one included, code-registered excluded |
pass | 200; order com.qa.w5export, com.example.showcase.custom, com.qa.w5local, com.qa.w5off; GET /packages holds 33 rows, exactly those 4 are not source: code; no id repeated; com.example.showcase and the 28 platform packages absent |
| E2 | dependencies first; a pair after its base | pass | com.qa.w5off (depends on com.qa.w5export) after it. The pair's base com.example.showcase is code, so the pair is exported alone, as ADR-0146 D8 item 3 (PR #29830 wording) and the bundle docblock say ("A pair of a code base is still exported") |
| E3 | each entry byte-equal to GET /packages/:id/export |
pass | JSON.stringify of each bundle entry equals the per-package data for all 4 |
| E4 | withheldMaps names the connector's map, and no header name or value travels |
pass | com.qa.w5export.withheldMaps = [{type: connector, name: w5export_erp, path: providerConfig.headers}]; connector exported as providerConfig: {baseUrl}; the bundle text contains neither X-W5-Tenant nor its value; no other entry carries the key |
| E5 | the disabled package's entry carries its content | fail | com.qa.w5off entry is {id, name, version, namespace, dependencies} with no objects, while its sys_metadata row object/w5off_note is active. Re-enable: the entry carries objects: [w5off_note]; disable again: empty again. See repro R1 |
| E6 | any query parameter is refused 400 VALIDATION_ERROR naming it | pass | ?foo=1, ?package=…, ?version=1 each 400 VALIDATION_ERROR, The "foo" query parameter is not supported by this endpoint… (each names its own parameter) |
| E7 | a member without studio.access/setup.access → 403; anonymous → 401 |
pass | member 403 PERMISSION_DENIED "Reading packages requires the studio.access or setup.access capability." (per-package export the same); anonymous 401 UNAUTHENTICATED; admin 200 (control) |
| E8 | client.packages.exportAll() answers the bundle |
pass | built @objectstack/client dist, admin token: same 4 ids, JSON.stringify equal to the REST data; an anonymous client throws httpStatus 401, code UNAUTHENTICATED |
| E9 | os package export --out DIR: one numbered directory per package, in dependency order |
pass | exit 0, 01-com.qa.w5export … 04-com.qa.w5off, each objectstack.json equal to bundle entry N; the same --out again refused exit 1 "already holds files" |
| E10 | fresh boot: installing each directory in order lands objects, fields, flows and the pair's extension, served | pass | target 1, os package install DIR/NN/objectstack.json ×4, all exit 0: w5export_item meta 200 + POST /data 201 + list 200; flows w5export_probe, w5local_probe 200; connector 200 (providerConfig: {baseUrl}); showcase_task serves w5_region__c "W5 Region"; w5local_thing 200. (w5off_note 404: it was never in the export, E5) |
| E11 | the install answer names each withheld map as incomplete, and logs a warn | pass | target 2, POST /marketplace/install-local {manifest: entry}: com.qa.w5export answers 200 with withheldMaps: [{connector, w5export_erp, providerConfig.headers}], the other three none; boot log WARN [MarketplaceInstallLocal] installed com.qa.w5export managed with 1 connector map(s) the package export withheld whole, each now absent: connector 'w5export_erp' providerConfig.headers… (target 1 logged the same line). The CLI's own output names nothing: see the probe line |
| E12 | re-reading GET /packages/export on the new boot matches the first, modulo withheld values |
fail | target 1: same 4 ids in the same order; com.qa.w5local and com.qa.w5off byte-equal; com.qa.w5export differs only by withheldMaps (expected: the map is absent on the target); com.example.showcase.custom lost objectExtensions although the target still serves w5_region__c and GET /packages/com.example.showcase/customizations still lists it. See repro R2 |
Reproduction rules
R1 (E5): a disabled package exports as an empty shell. Fresh showcase boot, admin bearer token.
POST /api/v1/packages {"manifest":{"id":"com.qa.w5off","name":"W5 disabled","version":"0.1.0","type":"app","namespace":"w5off"}}→ 201.PUT /api/v1/meta/object/w5off_note?mode=draft&package=com.qa.w5off {"name":"w5off_note","label":"W5 Note","sharingModel":"private","fields":{"body":{"type":"text","label":"Body"}}}→ 200;POST /api/v1/packages/com.qa.w5off/publish-drafts {}→ 200published1.GET /api/v1/packages/com.qa.w5off/export→ 200 withobjects: [w5off_note](control).PATCH /api/v1/packages/com.qa.w5off/disable→ 200enabled: false.GET /api/v1/packages/com.qa.w5off/exportandGET /api/v1/packages/export.
- Expected: the entry still carries
objects: [w5off_note](the bundle declaration: "Enabled or not, every such package is exported … an install of the bundle installs every entry enabled"). - Actual: 200
{"id":"com.qa.w5off","name":"W5 disabled","version":"0.1.0","namespace":"w5off"}, noobjects; thesys_metadatarow staysstate: active. Re-enable → content back. Reproduced three times on the source boot. Installing that entry elsewhere installs an empty package, so the object is lost from the deployment copy with no error.
R2 (E12): an export omits the objectExtensions of a package installed through install-local. Fresh showcase boot, admin bearer token.
POST /api/v1/marketplace/install-local {"manifest":{"id":"com.qa.r2base","namespace":"r2base","name":"r2 base","version":"1.0.0","type":"app","objects":[{"name":"r2base_account","label":"Account","sharingModel":"private","fields":{"title":{"type":"text","label":"Title"}}}]}}→ 200.POST /api/v1/marketplace/install-local {"manifest":{"id":"com.qa.r2ext","namespace":"r2ext","name":"r2 ext","version":"1.0.0","type":"app","dependencies":{"com.qa.r2base":"^1"},"objectExtensions":[{"extend":"r2base_account","fields":{"r2ext_code":{"type":"text","label":"Billing code"}}}]}}→ 200.GET /api/v1/meta/object/r2base_account→fields.r2ext_code.label= "Billing code" (the extension is served).GET /api/v1/packages/com.qa.r2ext/export.
- Expected: the entry carries
objectExtensions: [{extend: r2base_account, fields: {r2ext_code: …}}], as the export of a pair whoseobject_extensionwas authored does (the source pair's export carriedobjectExtensions: [showcase_task + w5_region__c]). - Actual: 200
{"id":"com.qa.r2ext","name":"r2 ext","version":"1.0.0","namespace":"r2ext","dependencies":{"com.qa.r2base":"^1"}}, noobjectExtensions. Reproduced on target 2 (this shape), and on the source boot and target 1 with a second package extendingw5local_thing. In the round trip: the source paircom.example.showcase.customexported its extension; installed on target 1 byos package install, it servesw5_region__c, but its re-export has noobjectExtensions, so a second export → install hop drops the customization with no error.
Derived verdict
Targeted check: fail (E5, E12 fail; E1–E4, E6–E11 pass).
Off-clause probes (RUNNER rule 8)
About 12 minutes, admin and member personas, on the source and target 2 boots. Boundary classes: missing dependency, out-of-order install, malformed input, failure inside assembly, the CLI install path.
- Ordering with a missing dependency:
POST /packagesdeclaring a dependency on a not-yet-created package → 422DEPENDENCY_MISSING, nothing installed; installing the dependent export entry (04-com.qa.w5off) before its dependency through install-local → 422DEPENDENCY_MISSINGnamingcom.qa.w5export ^0.1.0; installing in the served order then succeeds. Holds. - Malformed
withheldMaps(path: providerConfig.secret) on install-local → 400PLUGIN_MANIFEST_INVALIDnaming the allowed positions. Holds. - A package whose assembly throws → whole bundle refused: not reached live. Deleting the stored connector row of a published authored package under a running boot left the served read 200 (registry-held), so the export did not throw. The branch is pinned only at the dispatcher-over-stub layer:
packages/runtime/src/domains/packages-export-bundle.test.ts10/10 passed at the subject (incl. both whole-or-absent cases),packages/cli/test/package-export.test.ts6/6. The per-type silent skip inside assembly is 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 (fix PR fix(runtime)!: a package export refuses when the read of a type the runtime serves fails, naming the package and the type #29824 open). - Found:
os package install DIR/NN/objectstack.json, the commandos package exportprints for each directory, says nothing about an incomplete connector map: for01-com.qa.w5exportit printed "Package installed into the running kernel" and the cache path, while the install-local answer it received carriedwithheldMaps(seen on target 2 through the same door). Extracted, see the close-out.
Fixture gaps / notes for #27377
Reported in the runner's final report for the anchor (proposed ledger item, and three observations), not here.
Activity
objectstack-fleet commentedon Nov 3, 2026
domain:devx#1 · os-warren · session_01JQwxbYeYDjwmf7geACxC5Z
Close-out. Both FAILs are product defects (the export breaks its own declared contract), each extracted; the off-clause probe finding is extracted too. No security finding. Checklist-accuracy notes and the proposed ledger item go to #27377 through the runner's final report.