Repository navigation
Commit c7d6037
feat(spec,cloud-connection,cli): the installModes manifest key and the install door's unmanaged mode (ADR-0131 D6, stage S2) (#22916)
Part of #15213: stage S2 of the ADR-0131 D6 execution card. The
`installModes` manifest key lands with the install door that honours it,
the `installMode` body key and `os package install --install-mode`,
spelled `unmanaged` per ruling B (`6108509158`). Still on the card: S3
(the authoring docs page, which now also carries the `packages.ts`
remedy text, see "Scope and size") and S4 (the objectui marketplace
choice).
Clause-②: yes (widening: the optional manifest key `installModes`; the
install-local body field `installMode`; the CLI option `--install-mode`;
new exports in `@objectstack/spec/kernel` and
`@objectstack/metadata-protocol`; the error codes
`STACK_UNMANAGED_INSTALL_UNCOPYABLE` and `INSTALL_MODE_NOT_PERMITTED`)
> ~~Held out of the merge queue by #22949.~~ Released 2026-10-12: #22949
is no longer open. `main` has since been merged twice: at `6326fbe084`,
with the net diff unchanged, and in the merge round below (`5a7ed7ca6`),
whose one conflict keeps both sides. A fresh contract review of the
merged head is owed before the queue.
Claim `6112998649`. S1 (`importUnmanagedPackage`, PR #22879 →
`72b26ed4f5`) is the verb this door calls. Changeset:
`.changeset/15213-install-modes.md` (`minor` on `@objectstack/spec`,
`@objectstack/cloud-connection`, `@objectstack/cli`,
`@objectstack/metadata-protocol`). Nothing existing is removed. One
existing answer is stricter: step 1f (below) refuses a managed install
whose namespace a writable base holds with `409 RESOURCE_CONFLICT`,
where a catalog install of it answered `200`.
## Merge round (seat order `6121590923`; fourth contract review
`6121541034`)
Head `8a713cd9e`: the merge `5a7ed7ca6` of `origin/main` at `e1deaefa8`
through `scripts/pm/os-regen-merge.sh`, then one text-and-comments
commit, `8a713cd9e`. No force-push, no rebase.
- **Conflicts: one, not three.** The review read three `main` commits as
moving paths this PR edits. Measured on `e1deaefa8` against
`6326fbe084`, a driver-less `merge-tree` in a throwaway bare clone
(GitHub's condition) and the driver-on `merge-tree` both name one
conflicted path, `packages/cloud-connection/vitest.config.ts`.
`packages/spec/src/stack.zod.ts`,
`packages/cloud-connection/src/marketplace-install-local-plugin.ts` and
`packages/metadata-protocol/src/protocol.ts` (main's #22954) merged
without a conflict.
- `vitest.config.ts`: both sides inserted an anchored source alias at
the same spot. Both are kept: this PR's `@objectstack/metadata-protocol`
alias first, then main's `@objectstack/objectql` alias.
- `stack.zod.ts`: both collectors are in the merged file. Main's
`collectDuplicateFunctionNameErrors` sits inside the cross-reference
aggregate (`STACK_CROSS_REFERENCE_INVALID`, no new code), beside this
PR's `validateUnmanagedInstallMode`.
- The door: main's D3 `refuseWalledCatalogPackage` stays where main put
it (see the order below).
- **Generated artifacts.** The script's step 2 took no path from main,
because no `merge=os-regen` path moved on both sides. After a fresh spec
build, `check:generated` reads 14 of 14 artifacts current on the merged
tree, so no regeneration commit is owed.
- **The net diff is still this PR's own.** It covers the same 40 paths.
For 39 of them, the per-file `git patch-id --stable` of the diff against
main is the same before the merge (`6a40a09ed9..6326fbe`) and after
it (`e1deaefa8..5a7ed7c`). The one that differs is `vitest.config.ts`,
whose context changed. Nothing published changes, so the `Clause-②` line
stands as written.
- **The door's order on the merged head.** Every step comes before any
write:
0. `installMode` is parsed against `InstallModeSchema` (`400
INVALID_REQUEST`), before the cloud fetch;
1. 1a, the payload check (`PLUGIN_MANIFEST_INVALID`);
2. 1b, the package id (`PLUGIN_MANIFEST_INVALID`, 400 or 502);
3. ADR-0131 D3, from main: under a wall, a caller below the operator
rung gets `403 FORBIDDEN` when the incoming or installed version carries
a position, a permission set or a capability;
4. 1c, the protocol handshake (`422 OS_PROTOCOL_INCOMPATIBLE`);
5. 1e, the install mode. A mode the package does not permit gets `422
INSTALL_MODE_NOT_PERMITTED`, a malformed `installModes` gets
`PLUGIN_MANIFEST_INVALID`, and `unmanaged` on a walled requested posture
gets `409 UNMANAGED_INSTALL_WALLED_POSTURE`;
6. 1d, code this door cannot run (`422 VALIDATION_ERROR`);
7. the `unmanaged` branch returns here;
8. 1f, the managed-after-unmanaged refusal (`409 RESOURCE_CONFLICT`),
then the managed steps.
One answer changes as a result. A tenant admin who asks for `unmanaged`
under a wall, on a package that carries a catalog item, now gets D3's
`403` instead of the `409`. Nothing else about the door's behaviour
changes. The door's header docblock and the 1e comment now list D3 among
the checks that run before the mode check.
- **Dogfood P2 on the merged door: green.** P2's caller is the seeded
platform admin, whom D3 admits. The CRM artifact it installs declares
positions and permission sets, so P2 really passes through D3's
admission. It still gets `409 UNMANAGED_INSTALL_WALLED_POSTURE` with
zero writes, and the managed positive control still answers 200.
- **The `_lock` status clause** (ruling A on #22971, `6119681934`). Five
texts now carry the same clause: the lock is kept as written today, and
a follow-up on #15213 strips it per #22971's ruling A. The strip itself
is not in this PR.
1. The `INSTALL_MODES` TSDoc (`manifest.zod.ts`, shipped in the
`.d.ts`): "keeps that lock on the copy (an open question on #15213)" now
reads "keeps that lock on the copy as written today, and a follow-up on
#15213 strips it per #22971's ruling A".
2. The changeset entry: "keeps that lock on the copy, and its handling
is an open question on #15213." now reads "keeps that lock on the copy
as written today, and a follow-up on #15213 strips it per #22971's
ruling A."
3. The door's header docblock: "keeps that lock on the copy)" now reads
"keeps that lock on the copy as written today, and a follow-up on #15213
strips it per #22971's ruling A)".
4. The verb's module header (`package-unmanaged-install.ts`): "keeps
that lock)" now reads "keeps that lock as written today, and a follow-up
on #15213 strips it per #22971's ruling A)".
5. `importUnmanagedPackage`'s TSDoc (`protocol.ts`): the same change as
4.
Only texts 1 and 2 said "open question". Texts 3 to 5 said only that the
lock is kept.
- **Verified at `8a713cd9e`:**
- spec built, and `check:generated` reads 14 of 14 current;
- spec, nine suites (the seven this PR touches, plus main's
`stack-duplicate-function-name` and `stack-cross-reference-envelope`): 9
files / 235 tests passed, and the census still counts eight sites;
- the dogfood dependency closure built (63 of 63 turbo tasks);
- dogfood `install-local-unmanaged-mode`: 17 of 17, P2's four cases
included; main's `wall-package-refusal`: 19 of 19;
- `@objectstack/cloud-connection`, full suite: 44 files / 536 tests;
- `@objectstack/metadata-protocol`, five suites: 123 tests;
- `@objectstack/cli`, `--project unit`, the four `package-install-*`
suites: 23 tests;
- `typecheck` exits 0 on spec, cloud-connection, metadata-protocol, cli
and dogfood.
- **Gates** (`dispatch-gates --commands --repo
objectstack-ai/objectstack`, no paths, at `8a713cd9e`). 124 commands
were derived. 122 of them ran, and 121 of those exit 0.
- `check:platform-checklist` exits 1. A detached control on `main` at
`e1deaefa8` shows the same 10 problems, line for line. `main`'s
`794177d9b` (#22995) changed that checker after this merge; CI reads the
merge ref.
- `check:skill-examples` first answered exit 3, `PREREQUISITE NOT MET`.
After `@objectstack/client-react` was built, it exits 0.
- NOT MEASURED: `check:type-check-debt`, which the seat order excludes,
and `check:dual-build-cjs-loads`, which needs a whole-workspace build
(CI's `Build Core` runs it). `--ran` reconciles 124 derived, 122 run and
2 UNRUN, those two.
- Artifact rosters: 44 of 45 exit 0, including the three PR-context
guards, run with this PR's number, head ref and body.
`check:published-readme-exports` exits 3, `PREREQUISITE NOT MET`,
because it reads packages outside the built closure: NOT MEASURED.
- **After the merge**, `origin/main` moved on to `794177d9b`
(`67baccc55`, `794177d9b`). Neither commit touches a path of this PR.
## Patch round 2: the editability and stricter-answer wording (seat
comment `6117204840`, contract review `6117191273`)
The review at `618824f3bf` failed on two sentences of text; no source
logic changed. Commit `57b4f4fa7f`:
- **The changeset, sentence 1.** "every item is editable" is gone. It
now reads: "the copy's items are this environment's own metadata, the
next save edits them, and it follows no upgrade. One exception is
carried: an item the package ships with an authored protection lock
(`_lock`) keeps that lock on the copy, and its handling is an open
question on #15213." The merge round replaced that last clause.
- **The changeset's close.** "behaves exactly as before" is gone. It now
reads: "an install that does not name a mode is managed, as before, with
one stricter answer". It then names that answer: the 1f refusal, `409
RESOURCE_CONFLICT` before anything is written, for a managed install of
a package whose namespace a writable base holds, where a catalog install
of it answered `200`. This body's opening sentence carves out the same
answer.
- **The same claim at runtime.**
- The install-local door's unmanaged answer `note` now reads "The
package's metadata is now this environment's own, in the base 'BASE',
and it follows no upgrade. Refresh the console to see it."
- The CLI's unmanaged success line now reads "Package copied into the
environment (unmanaged) — its items are this environment's own metadata,
and it follows no upgrade".
- No test asserted the removed wording. The CLI test matches the line's
unchanged prefix, so no test changed.
- **Also corrected in this body** (the review's boundary flag): the door
asks the walled-posture refusal after the payload, id and handshake
checks (1a to 1c, none of which writes), not "before anything else about
the package".
- **No sync.** GitHub reads the PR `mergeable: true`, state `blocked`
(draft), neither dirty nor behind. `origin/main` has moved 4 commits
past the merge base, and none touches spec source or a generated
reference.
- **Verified at `57b4f4fa7f`:**
- the dependency closures of `@objectstack/cloud-connection` and
`@objectstack/cli` were built (exit 0);
- `marketplace-install-local-install-modes.test.ts`: 19 of 19;
- `@objectstack/cloud-connection` `typecheck` exit 0;
- the CLI's `package-install-mode.test.ts` (`--project unit`): 8 of 8;
- `@objectstack/cli` `typecheck` exit 0;
- `check-empty-changeset --base origin/main` exit 0.
- **Gates for this round's paths.** The three files `57b4f4fa7f` changes
were passed as paths to `dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack`, as the seat ordered; the whole-PR
derivation at this head still names the same 124. That gives 61
commands, run at `57b4f4fa7f` (recorded before and after the pass): **60
of 61 exit 0**. `check:dual-build-cjs-loads` is NOT MEASURED (a
whole-workspace build, excluded by the dispatch). The four `check:i18n*`
/ `check:lean-entry-closure` gates ran after the CLI and the four
examples were built. `--ran` reconciles 61 derived, 60 run, the one
UNRUN being `dual-build-cjs-loads`. The three PR-context guards
(`check-closing-target-claim`, `check-partof-closing-keyword`,
`check-single-claim-paths`) ran against this body with the PR's context,
all exit 0.
- **Round 3, comments only (seat comment `6117487130`), commit
`8e9d1bfce5`.** The `INSTALL_MODES` TSDoc (`manifest.zod.ts`, shipped in
the `.d.ts`) and the door's header docblock now say the copy is the
environment's own metadata, which the next save edits, and name the
`_lock` exception in one clause. The door's comments place the walled
refusal after the payload check and steps 1b–1c, before any write.
Verified at `8e9d1bfce5`: `check:generated` 14 of 14; spec and
cloud-connection `typecheck` exit 0; of the 73 families derived for the
two paths, 72 exit 0 and `dual-build-cjs-loads` is NOT MEASURED.
- **Round 4, text and comments only (seat comment `6118016989`, contract
review `6117921319`), commit `3059dee3f6`.** "Editable" with no
exception beside it is gone from every shipped text the review named:
the 1f refusal message, `InstallModeSchema`'s `.describe()` and the
regenerated `kernel/manifest.mdx`, the CLI flag description and example
line, and the changeset heading. It is also gone from the five flagged
comments and from `importUnmanagedPackage`'s TSDoc in `protocol.ts`,
which the sweep found. The supersede paragraph now names S1's "editable
from then on" and "is fully editable"; S1's entry stays byte-equal.
Verified at `3059dee3f6`, all exit 0: `check:generated` 14 of 14,
`check:docs`, `check-empty-changeset`, and the spec, cloud-connection,
CLI and metadata-protocol `typecheck`. Of the 110 families derived for
the round's 8 paths, 109 exit 0 and `dual-build-cjs-loads` is NOT
MEASURED.
## Patch round: the sync and the supersede (seat comment `6116200445`)
- **The sync.** `origin/main` at `7dbb89dc5d` is merged through
`scripts/pm/os-regen-merge.sh` (merge `29cfd19fd6`).
`content/docs/references/index.mdx` is generated and not text-merged, so
it was rendered again from the merged spec (`gen:schema` then
`gen:docs`) and committed on its own (`d9add8817a`). It now reads
Automation 71 schemas (main's `FlowCollectionSlot`), Kernel 158 (this
PR's `InstallMode`), 1,524 in total. This was the red `check:docs` on
`Type Check · source gates`. After it, `check:generated` reads 14 of 14
artifacts current, and `FlowCollectionSlot` is in the same 5 generated
files on `origin/main` and on this head.
- **The supersede** (`618824f3bf`). S1's pending entry
`.changeset/15213-unmanaged-import-verb.md` is restored byte-equal to
the merge base (blob `af966c215b` on both sides). The correction now
lives in this PR's own `.changeset/15213-install-modes.md`. That entry
names the earlier one, quotes its two now-false sentences, states what
holds now and says it supersedes them. The sentences are "no door calls
it yet, and no manifest key declares the mode yet", and seeds and
translations "left to the install door, which applies them after the
copy as it does for a managed install". This is the mechanism of the
#22673 changeset and of `19939-flow-collection-value-slots.md`. `node
scripts/check-empty-changeset.mjs --base origin/main` exits 0: no
changeset from the merge base is modified or deleted.
- **Q2 (`installedFrom`) and Q3 (seed data):** A, as built, per the
seat. **Q1** (an authored `_lock` survives the copy): no edit in this
round; the seat carries it to the card.
## What lands
- **`installModes`** (`packages/spec/src/kernel/manifest.zod.ts`). An
optional, non-empty list of `'managed'` / `'unmanaged'`
(`InstallModeSchema`, `INSTALL_MODES`). There is **no parse-time
default** (Q9): absence survives a parse, and
`resolveInstallModes(manifest)` is the one reader of what it means
(`DEFAULT_INSTALL_MODES`, `['managed']`). The liveness row is `live`;
its evidence is the door and the authoring refusal.
- **Refused at authoring (Q5).** `defineStack`, and so `os validate` and
`os build`, refuses a package that permits `unmanaged` while it carries
what the copy cannot write:
- a non-empty collection of a type the registry declares
runtime-uncreatable (`allowRuntimeCreate: false`, read off
`DEFAULT_METADATA_TYPE_REGISTRY`, never a list);
- code;
- an extension of another package's items.
The code is `STACK_UNMANAGED_INSTALL_UNCOPYABLE` (422), the eighth site
of the `StackRefusalError` family, with one `issues` entry per key. The
key classification the import verb used to keep privately moved to spec
as `UNMANAGED_INSTALL_KEY_DISPOSITIONS` (`kernel/unmanaged-install.ts`).
The authoring refusal and the door's import now read **one** table, held
total against `ManifestSchema` and the stack definition by a spec test.
So the PR that adds a schema key is the PR that reddens.
- **The door**
(`packages/cloud-connection/src/marketplace-install-local-plugin.ts`,
`POST /api/v1/marketplace/install-local`):
- `installMode` is parsed against `InstallModeSchema`. Absent means
`managed`, so every existing caller is unchanged; a value outside the
enum answers `400 INVALID_REQUEST` before the cloud fetch.
- Step 1e, on both sources, before any write:
- a mode the package's `installModes` does not permit answers `422
INSTALL_MODE_NOT_PERMITTED` with `details.permitted`;
- a malformed `installModes` answers `PLUGIN_MANIFEST_INVALID`, with the
id gate's 400/502 split;
- `unmanaged` on a walled REQUESTED posture answers `409
UNMANAGED_INSTALL_WALLED_POSTURE` (Q2). This is the import verb's own
refusal, `unmanagedInstallPostureRefusal`, now exported. The door asks
it after the payload check, step 1b (the id), main's ADR-0131 D3
walled-catalog refusal and step 1c (the handshake), none of which
writes, and before any other judgement of the package; the verb still
asks it for every other caller. The full merged order is under "Merge
round".
- The `unmanaged` branch hands the verb ONE flat package body. A
compiled artifact's nested `manifest:` is flattened, the collections
winning, as `AssembledPackageBodySchema` records. The branch runs none
of the managed steps: no conflict classification, no D5e gate, no
`manifest.register`, no ledger entry, no `syncSchemas`, no binder, no
`applySideEffects`. The answer carries `installMode`, `targetPackageId`,
`copiedCount`, `copied` and `installedFrom`; `seeded: { mode: 'skipped'
}` appears when the package declares seed data. The verb's refusals are
relayed with their own code, status, message and `details`; a 5xx
message is withheld and logged.
- Step 1f (Q6), the managed-after-unmanaged refusal, on BOTH sources
before any write. A managed install of a package whose namespace
(declared, or derived from its id) is held by a **writable** base
answers `409 RESOURCE_CONFLICT`, naming the base in the message and
`details`. A namespace held by another code package keeps the lenient
branch's existing behaviour, whose fate is a separate decision.
- **`os package install --install-mode managed|unmanaged`**
(`packages/cli/src/commands/package/install.ts`):
- Without the option, nothing is sent.
- An unmanaged answer prints the base, the item count and
`installedFrom`, and none of the managed lines (hot-load; "re-registers
on every boot").
- Asking for `unmanaged` and getting an answer without it means the
runtime predates the mode and installed MANAGED. The command then exits
1 and says so.
- The command's prose says two *sources* (catalog, air-gapped), so
"mode" has one meaning.
- **The import verb**
(`packages/metadata-protocol/src/package-unmanaged-install.ts`), as S1's
carry list asks:
- `translations` are now copied, each locale of each bundle as one
`translation` item `NAMESPACE_LOCALE` (`crm_zh_cn`), the bundles merged
in order;
- `installModes` is read and never written onto the base;
- an empty code container (`functions: {}`) is no code.
- The `MetadataWriteFace` docblock is corrected: the verb forwards
`cause.issues`, but its one door relays message + code + `details` only,
so the full-prose default on the 422 stays right.
- **Carried prose.**
- The `specVersion` guidance names
`marketplace/template-manifest.zod.ts`; the retired `cloud/` path is
pinned absent.
- `18.meta-doors-organization-scope-retired.ts:30` reads "an unmanaged
install".
- S1's pending changeset is left byte-equal to the base. This PR's
changeset supersedes the two sentences in it that this stage made false:
"no door calls it yet", and seeds/translations "left to the install
door". See "Patch round".
## Zone 2 readings (measured on `origin/main` at the base `45e206e498`)
1. **File list.** Every stage-0 S2 site is where the plan put it:
- `ManifestSchema` at `kernel/manifest.zod.ts:401`, with the retired
path at `:418`;
- `defineStack`'s validators in `stack.zod.ts`;
- `liveness/manifest.json`, which had 0 `installModes` rows;
- the door's `handleInstall` at `:930`;
- `install.ts:7/10/15/58/98` ("mode" prose);
- `packages.ts:384/421/524` (the "ADR-0005 org overlay" remedies);
- `18.meta-doors…:30`.
Beyond it, necessarily: S1's `package-unmanaged-install.ts` (its private
key table moved to spec; without a row, `installModes` would have been
refused by the import as an unrecognised key);
`metadata-protocol/src/index.ts` (one export); the two `defineStack`
refusal censuses; and S1's pending changeset.
2. **The door** (base `45e206e498`):
- catalog versus air-gapped branch at `if (inlineManifest)` `:969`;
- step 1d at `:1149`;
- the managed branch's `findConflict` at `:1195`, the D5e gate at
`:1222`, `manifest.register` at `:1272`, the ledger write at `:1310` and
`applySideEffects` at `:1343`.
The `unmanaged` branch returns before `:1195`, after 1d, and none of
these run (pinned: no register, no sync, no ledger file). The import's
three registry reads (`protocol.ts:24472` `getPackage` /
`manifests.has`, `:24479` `getNamespaceOwners`) were measured on the
REAL registry by the dogfood suite. See P3.
3. **Translations and `installed_from`.**
- Translations travel in the copied items. Measured:
`translation/crm_zh_cn` is served after P1 and again after the restart.
- `installed_from` is recorded on the door's answer (`installedFrom: {
packageId, version }`) and on the operator log line at `info`. **No
durable carrier**: it needs a contract decision (the install record or
`sys_packages.metadata`), which is left to the seat. See Acceptance
notes.
- The keys S1 left to the door: `translations`, now copied by the verb;
and `data`, not loaded, and the answer says so.
4. **The acceptance on `examples/app-crm`.** The fixture is the CRM's
built artifact with `installModes: ['managed', 'unmanaged']` on its
manifest. Suite:
`packages/qa/dogfood/test/install-local-unmanaged-mode.dogfood.test.ts`,
17 cases, green on the merged tree `799c30127b` and again at
`677d7fad45`.
- **P1** (`single`, one database file across a restart):
- 200, answering `targetPackageId: 'com.example.crm-copy'` and
`installedFrom: { packageId: 'com.example.crm', version: '4.0.0' }`;
- every copied item served by `GET /meta`, never with a non-`org`
provenance;
- every copied item saved by `PUT` with its own served body,
`defineView` containers included (`view/crm_lead`);
- every row has `package_id` = the base and `organization_id` NULL;
- no ledger file and no registry package under `com.example.crm`;
- after the restart, every item is still served, the source is still
unregistered, and the base still holds the `crm` namespace.
- **P2** (`isolated`):
- `409 UNMANAGED_INSTALL_WALLED_POSTURE`, `details: { posture:
'isolated' }`;
- zero writes: no base, no `sys_metadata` row, no ledger file;
- positive control: the managed install of the same artifact on the same
boot answers 200.
- **P3**:
- after the restart, a managed install of `4.0.1` answers `409
RESOURCE_CONFLICT` with `details: { namespace: 'crm', base:
'com.example.crm-copy' }`;
- every copied row's `checksum|metadata` is byte-identical before and
after;
- a second unmanaged install answers 409 "'com.example.crm-copy' already
exists" (`getPackage` on the real registry);
- a target that is the booted code package `com.example.showcase`
answers 409 (`manifests.has`);
- a second target over the owned namespace answers 409 with `details: {
namespace: 'crm', owners: ['com.example.crm-copy'] }`
(`getNamespaceOwners`).
- **S1's open notes:**
- the container expansion is measured clean (above);
- the authored protection lock is measured as a **gap**, under
Acceptance notes.
5. **Size.** See "Scope and size".
## Ablation
Dropping the requested-posture refusal turns P2 red.
- **Mutation.** `unmanagedInstallPostureRefusal` always answers `null`,
via `scripts/ablation-replace.mjs` in wrap mode (anchor ×1 → ×0, blob
`87618e8d12` → `c3f5b8c7b9`). `@objectstack/metadata-protocol` was
rebuilt, and `ablation-dist-preflight` found the marker in 2 built
files. The build's `.d.ts` step refused the always-true condition
(TS2872), but the JS the dogfood consumes was emitted.
- **Mutated P2:** 3 failed, 1 passed (the posture precondition), as
predicted:
- the refusal answered 200;
- writes happened;
- the positive control then met the managed-after-unmanaged refusal.
- **Restore:** blob `87618e8d12` == HEAD, `git diff HEAD` empty, rebuild
exit 0, `ablation-dist-preflight --absent` marker absent from all 24
built files, tree clean. P2 green again (4 passed).
## Tests (each line names the commit it ran at)
- **This round, at `618824f3bf` (after the sync merge `29cfd19fd6`),
`@objectstack/spec`:**
- the seven suites this PR touches: 7 files / 205 tests passed
(`kernel/manifest`, `kernel/manifest-unknown-keys`,
`kernel/unmanaged-install`, `stack-unmanaged-install-mode`,
`stack-refusal-envelopes`, `stack-conversions-record`,
`type-alias-convention.pin`);
- `gen:migration-registry` current (423 semantic), then `--project repo`
(`test:repo`): 55 files / 971 tests passed;
- `typecheck` exit 0.
- The merge brought no change to `cloud-connection` or
`metadata-protocol` source. The lines below are the earlier round's,
after the merge `682620e7dc`.
- **`@objectstack/spec`**:
- at `bbe1b2be2b` (the merged tree): `--project local` 650 files /
19,405 tests passed (1 todo); `--project repo` 55 / 971; `typecheck`
exit 0;
- at `677d7fad45`, after the ADR-0122 pin: the four touched suites 106 /
106 (`type-alias-convention.pin`, `kernel/manifest`,
`kernel/unmanaged-install`, `stack-unmanaged-install-mode`); `typecheck`
exit 0.
- The defineStack refusal censuses (`stack-refusal-envelopes`,
`stack-conversions-record`) now count eight sites.
- **`@objectstack/cloud-connection`**, full suite at `677d7fad45`: 44
files / 536 tests. The new
`marketplace-install-local-install-modes.test.ts` has 19 cases.
`typecheck` exit 0.
- **`@objectstack/metadata-protocol`** at `bbe1b2be2b`: 5 files / 123
tests (`package-unmanaged-install`, both face inventories, the
`lookupArtifactItem` call-site census, `tenant-authored-write`).
`typecheck` exit 0.
- **`@objectstack/cli`**, `--project unit`, the four `package-install-*`
suites at `bbe1b2be2b`: 4 files / 23 tests,
`package-install-mode.test.ts` 8 of them. `typecheck` exit 0 at
`677d7fad45`. The `integration` tier is declared to CI: no spawn entry
and no integration file is touched.
- **`@objectstack/runtime`**, `src/domains/` at `bbe1b2be2b`: 108 files
/ 2,712 tests; `typecheck` exit 0. The runtime diff is empty after the
move to S3.
- **Dogfood:**
- the new suite plus the six existing install-local suites at
`799c30127b` (merged tree, closure rebuilt): 7 files / 56 tests;
- the new suite again at `677d7fad45` (cloud-connection rebuilt): 17 /
17;
- `typecheck` exit 0.
## Gates (`node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack`, no paths, derived again and run at
`618824f3bf`)
The derivation at `618824f3bf` names the same 124 commands as at
`677d7fad45`. Union at `618824f3bf` (`git rev-parse --short HEAD`,
recorded before and after the pass): **122 of 124 exit 0**. Reconciled
with `--ran`: 123 of 124 derived families accounted with an exit code;
the one UNRUN is `dual-build-cjs-loads` (below).
- In the first pass, 8 commands answered exit 3 `PREREQUISITE NOT MET`,
which measures nothing: the worktree was re-created for this round, so
the built outputs they read were absent. The 8 are
`check:doc-formula-expressions`, `check:doc-security-posture`,
`check:skill-examples`, `check:docs-transcript-drift`, `check:i18n`,
`check:i18n-coverage`, `check:i18n-walk-parity` and
`check:lean-entry-closure`. The packages their own fix lines name were
built with their dependencies (69 packages, exit 0, tree clean after),
and all 8 were run again at the same head: exit 0. The record above
carries the second run.
- `pnpm check:dual-build-cjs-loads`: NOT MEASURED. It needs a
whole-workspace build, which the dispatch excludes.
- `node scripts/check-empty-changeset.mjs --base origin/main`: **exit
0**. "No empty-frontmatter changeset introduced by this diff (1
declaring changeset(s) added)" and "No changeset from the merge base
modified or deleted by this diff". `check-changeset-no-major` exit 0.
- `pnpm check:platform-checklist`: **red on `origin/main` too, not this
PR.** The same 8 problems (7 `ABSENT SYMBOL` anchors in
`docs/qa/platform-checklist/areas/*.json` naming `protocol.ts` /
`attachment-access-hooks.ts` symbols, plus one anchor floor) are
identical, line for line, on a detached `origin/main` at `eab7236ff7`,
measured this round. This PR touches no checklist file and no named
symbol.
- Named by the seat, all exit 0 at `618824f3bf`: `check:generated`,
`check:docs` ("226 generated files in sync"), `check:api-surface`
("public API surface + factory signatures unchanged"),
`check:authorable-surface` and `check-empty-changeset`. Also exit 0:
`check:liveness`, `check:route-envelope`, `check:error-code-casing` and
`check:nul-bytes`.
- Fixed in this PR by the first round's own findings:
`check:slot-lookup`, `check:spec-parsed-alias`,
`check:test-source-alias` and `check:empty-state`.
- Artifact rosters (the block the no-path run prints). 42 of its 45
commands ran at `618824f3bf`, all exit 0; 14 of those are checker-health
`--self-test` runs, which cannot fail for this diff. The three
PR-context guards ran against this body with the PR's context, all exit
0: `check-closing-target-claim`, `check-partof-closing-keyword` and
`check-single-claim-paths`.
- After the sync, `origin/main` moved one commit, `eab7236ff7` (#22907).
It touches no spec source, no generated reference and no file of this
PR, so the reference index stays current against the merge ref. CI reads
the current merge ref.
## Scope and size
After the merge round, the change set is 2,623 changed lines (+2,451 /
−172, the same 40 files), generated included, against the merge base
`e1deaefa8`, under the 3,000 human-merge line. Before that round it was
2,602 changed lines (+2,433 / −169) against the merge base `7dbb89dc5d`.
It read 2,604 / 41 files before this round, which counted S1's changeset
edit that is now restored. It measured 2,667 with the `packages.ts`
remedies, past the stage plan's ~2.6k mark. As the plan prescribes,
**the `packages.ts` "ADR-0005 org overlay" remedy text (`:421`, `:524`,
docblock `:384`) moves to S3**, untouched here. S3 therefore does not
fold in.
## Acceptance notes
- **Gap, measured, and since ruled (Q1); the changeset names it as the
one carried exception to editability.** An item a package ships with an
authored protection lock (`_lock: 'full'`, ADR-0010) is copied as
written. The next `PUT /api/v1/meta/view/NAME` then answers `403
ITEM_LOCKED`, so that copied item is not editable, against D6's
"editable in Studio like anything authored there". Measured on the real
showcase stack: the import is called directly; the install-local door
calls the same verb. The remedy is now ruled: A on #22971
(`6119681934`), the import strips `_lock` / `_lockReason` /
`_lockSource` and names each stripped lock. A follow-up on #15213
carries the strip. This PR keeps the lock as written today, and every
shipped text says so (see "Merge round").
- `installed_from` has no durable carrier (see Zone 2 item 3). The seat
ruled this A, as built (Q2).
- Seed data is not loaded by an unmanaged install. The reseed and purge
doors act on ledger entries this install never writes, so seeded rows
would be rows nothing here could clear. The answer says so. The seat
ruled this A, as built (Q3).
- ADR-0126's status line
(`docs/adr/0126-packaged-metadata-customization-model.md:3`, "template =
copied once…") still spells the old word. It is a governed Tier H
surface, so it is not edited here.
- Observation, unmeasured: a managed install of a compiled artifact
through this door hands `registerApp` a body whose `namespace` sits only
on the nested `manifest:`, so the registry's namespace gate may not see
it. The managed-after-unmanaged refusal reads the declared namespace
itself, so it does not depend on that.
- An unrecognised `OS_TENANCY_POSTURE` is refused by the door's
install-capability admission (401 in the unit harness) before step 1e.
The import's own suite pins that value as walled.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: objectstack-fleet[bot] <332303061+objectstack-fleet[bot]@users.noreply.github.com>1 parent 2acff87 commit c7d6037
40 files changed
Lines changed: 2451 additions & 172 deletions
File tree
- .changeset
- content/docs/references
- api
- kernel
- packages
- cli
- src/commands/package
- test
- cloud-connection
- src
- metadata-protocol/src
- qa/dogfood/test
- spec
- api-surface
- authorable-surface
- declaration-map
- export-origins
- json-schema.manifest
- liveness
- state-counts
- src
- api
- kernel
- migrations/entries/semantic
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
28 | 28 | | |
29 | 29 | | |
30 | 30 | | |
31 | | - | |
| 31 | + | |
32 | 32 | | |
33 | 33 | | |
34 | 34 | | |
| |||
199 | 199 | | |
200 | 200 | | |
201 | 201 | | |
| 202 | + | |
202 | 203 | | |
203 | 204 | | |
204 | 205 | | |
| |||
339 | 340 | | |
340 | 341 | | |
341 | 342 | | |
| 343 | + | |
342 | 344 | | |
343 | 345 | | |
344 | 346 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
366 | 366 | | |
367 | 367 | | |
368 | 368 | | |
| 369 | + | |
369 | 370 | | |
370 | 371 | | |
371 | 372 | | |
| |||
506 | 507 | | |
507 | 508 | | |
508 | 509 | | |
| 510 | + | |
509 | 511 | | |
510 | 512 | | |
511 | 513 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
89 | 89 | | |
90 | 90 | | |
91 | 91 | | |
| 92 | + | |
92 | 93 | | |
93 | 94 | | |
94 | 95 | | |
| |||
270 | 271 | | |
271 | 272 | | |
272 | 273 | | |
| 274 | + | |
273 | 275 | | |
274 | 276 | | |
275 | 277 | | |
| |||
333 | 335 | | |
334 | 336 | | |
335 | 337 | | |
| 338 | + | |
336 | 339 | | |
337 | 340 | | |
338 | 341 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
146 | 146 | | |
147 | 147 | | |
148 | 148 | | |
| 149 | + | |
149 | 150 | | |
150 | 151 | | |
151 | 152 | | |
| |||
190 | 191 | | |
191 | 192 | | |
192 | 193 | | |
| 194 | + | |
193 | 195 | | |
194 | 196 | | |
195 | 197 | | |
| |||
307 | 309 | | |
308 | 310 | | |
309 | 311 | | |
| 312 | + | |
310 | 313 | | |
311 | 314 | | |
312 | 315 | | |
| |||
440 | 443 | | |
441 | 444 | | |
442 | 445 | | |
| 446 | + | |
443 | 447 | | |
444 | 448 | | |
445 | 449 | | |
| |||
535 | 539 | | |
536 | 540 | | |
537 | 541 | | |
| 542 | + | |
538 | 543 | | |
539 | 544 | | |
540 | 545 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1950 | 1950 | | |
1951 | 1951 | | |
1952 | 1952 | | |
| 1953 | + | |
1953 | 1954 | | |
1954 | 1955 | | |
1955 | 1956 | | |
| |||
0 commit comments