Repository navigation
feat(spec,cloud-connection,cli): the installModes manifest key and the install door's unmanaged mode (ADR-0131 D6, stage S2) - #22916
Conversation
…nstall door's mode, translations copied Work in progress for the S2 stage of the unmanaged install mode. Claude-Session: https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF Co-authored-by: Claude <[email protected]>
…ged-mode dogfood suite, changeset Claude-Session: https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF Co-authored-by: Claude <[email protected]>
…JSON Schema form Claude-Session: https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF Co-authored-by: Claude <[email protected]>
…map, reference and liveness-count artifacts Claude-Session: https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF Co-authored-by: Claude <[email protected]>
…p the posture value the admission refuses first Claude-Session: https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF Co-authored-by: Claude <[email protected]>
Claude-Session: https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF Co-authored-by: Claude <[email protected]>
…surement leaves the suite The change set measured past the stage plan's 2.6k-line mark, so the retired-overlay remedies in runtime/src/domains/packages.ts go to S3, as the plan prescribes. The locked-item probe measured a real gap (a copied item that shipped `_lock: 'full'` answers 403 ITEM_LOCKED to the next save); its remedy is not ruled, so it is reported rather than pinned here. Claude-Session: https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF Co-authored-by: Claude <[email protected]>
…iner among them Claude-Session: https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF Co-authored-by: Claude <[email protected]>
Claude-Session: https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF Co-authored-by: Claude <[email protected]>
… a permissive-looking clause Claude-Session: https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF Co-authored-by: Claude <[email protected]>
…), pinned isomorphic; the door's objectql lookup carries its contract; the protocol is aliased to source in the door's suites Claude-Session: https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF Co-authored-by: Claude <[email protected]>
📓 Docs Drift CheckThis PR changes 4 package(s): 37 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 10 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 147 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin c1c7ef8ced81ecf8f46e985b34c9780604797f17 && git checkout c1c7ef8ced81ecf8f46e985b34c9780604797f17
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 67baccc5546c795c5b3b77b881c0581fa802b975 8a713cd9e66ceb5744467f978d88dc07b99d1c1d && git checkout -B drift-repro 67baccc5546c795c5b3b77b881c0581fa802b975 && git merge --no-ff 8a713cd9e66ceb5744467f978d88dc07b99d1c1d
node scripts/docs-audit/affected-docs.mjs --json 67baccc5546c795c5b3b77b881c0581fa802b975
|
…tree The merge of origin/main (7dbb89d) brought FlowCollectionSlot (#22883); content/docs/references/index.mdx is generated and not text-merged, so it is rendered again from the merged spec: Automation 71 schemas, total 1524. Kernel keeps the branch's 158 (InstallMode). Claude-Session: https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF Co-authored-by: Claude <[email protected]>
…PR's entry supersedes its two now-false sentences .changeset/15213-unmanaged-import-verb.md is S1's pending entry on the base, so it goes back byte-equal to the merge base (7dbb89d). The correction moves into .changeset/15213-install-modes.md as a supersede: it names that entry, quotes "no door calls it yet, and no manifest key declares the mode yet" and the seeds-and-translations clause "left to the install door, which applies them after the copy as it does for a managed install", and states what holds now. Same mechanism as #22673's changeset and 19939-flow-collection-value-slots.md. Claude-Session: https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF Co-authored-by: Claude <[email protected]>
Contract reviewServed-tier: Read-only, adversarial, on PR #22916 ( ① Derived judgments1. The accept set at every door, against the D6 rulings — holds.
2. The 3. The changesets — one sentence FAILS, one is imprecise.
4. The sync hop 5. Reach — holds. 6. The open note on 7. CI on the head — green or rostered skips. 42 check-runs: 38 ② Semver level
③ Boundary flags
What lifts the FAIL: one patch round on Implemented-by: VERDICT: FAIL Generated by Claude Code |
…nment's own metadata, a shipped _lock carried; the one stricter managed answer is named The contract review on 618824f failed on two sentences, and the seat's order adds the same claim at runtime: - .changeset/15213-install-modes.md: "every item is editable" becomes what holds — the copy's items are this environment's own metadata and the next save edits them — and the carried exception is named: an item shipped with an authored protection lock (`_lock`) keeps it on the copy, an open question on the card. The close no longer says an install that names no mode "behaves exactly as before": it is managed as before, with one stricter answer, the 409 RESOURCE_CONFLICT for a managed install whose namespace a writable base holds. - The install-local door's unmanaged answer `note` and the CLI's unmanaged success line drop "every item is editable". Text only; no test asserted the removed wording (the CLI test matches the success line's unchanged prefix). Claude-Session: https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF Co-authored-by: Claude <[email protected]>
… — the copy is the environment's own metadata, a shipped _lock carried; the walled refusal follows 1b-1c Comments only, per the seat's round-3 order before the second contract review: - packages/spec/src/kernel/manifest.zod.ts, the INSTALL_MODES TSDoc (it ships in the .d.ts): "editable like anything authored there" becomes the environment's own metadata that the next save edits, with the _lock exception named in one clause, as the changeset says. - marketplace-install-local-plugin.ts, the header docblock: the same edit for "editable and followed by no upgrade", and the walled refusal's order stated as the door has it — after the payload check and steps 1b-1c, none of which writes, before any write. The import comment and the 1e step comment carried the same "before anything else about the package" claim and say the same now. Claude-Session: https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF Co-authored-by: Claude <[email protected]>
Contract reviewServed-tier: Second contract review of PR #22916 ( ① Derived judgments1. The first record's two FAIL items are lifted, truly — and the same claim survives in five shipped texts the rounds did not reach: FAIL.
2. The hop 3. The rest of the first record holds on this head. Re-read here, not carried: the door's order on both sources (item 1 above, by line); the S1 entry 4. CI on the head: one red, judged not this PR's; everything else green or rostered. 42 check-runs: 37 ② Semver level
③ Boundary flags
What lifts the FAIL: one text round on the five texts in item 1 (the 1f message, Implemented-by: VERDICT: FAIL Generated by Claude Code |
…lls every copied item editable; the S1 entry's two editability phrases are superseded The second contract review (at 8e9d1bf) found the same per-item universal, "editable" with no exception beside it, in shipped text the earlier rounds did not reach. Each now says what holds (the environment's own metadata, which the next save edits), names the `_lock` exception, or drops the universal: - the 1f refusal message (runtime): "whose items are this environment's own metadata"; - InstallModeSchema's .describe(), and the generated content/docs/references/kernel/manifest.mdx rendered from it; - the CLI's --install-mode description and its example line; - the changeset's heading; - comments: the CLI command docblock, the door's installUnmanaged TSDoc, package-unmanaged-install.ts's module docblock and translations note, the `converted` disposition's TSDoc, and importUnmanagedPackage's TSDoc in protocol.ts (found by the sweep, same claim); - the changeset's supersede paragraph names S1's "editable from then on" and "is fully editable"; S1's entry stays byte-equal to the merge base. Text and comments only. Claude-Session: https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF Co-authored-by: Claude <[email protected]>
Contract reviewServed-tier: Third contract review of PR #22916 ( ① Derived judgments1. The second record's FAIL is lifted, verified in the hop, and the hop moves no logic — right. 2. The accept set at 3. The authoring refusal 4. One key table, and the removal it replaces — right, nothing published removed. 5. The install door — right (Q2 A, Q9 A). Step 0 parses 6. Step 1f — a stricter answer on one existing input; judged NOT a narrowing of a published accept set, so the 7. The verb's carry items — right. 8. The CLI — right. 9. Reach — right. The two codes are in the closed 10. CI on the head — 37 11. Governed surfaces, form, size — right. None of the 40 paths is under ② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
Contract reviewServed-tier: Fourth contract review of PR #22916 ( ① Derived judgments1. The accept set at 2. One key table, and the removal it replaces — right; nothing published removed. 3. The authoring refusal 4. The install door — right (Q2 A, Q9 A). Step 0 parses 5. Step 1f — a stricter answer on one existing input; judged NOT a narrowing of a published accept set, so the 6. The verb's carry items — right. 7. The CLI — right. 8. Reach — right. The two codes are in the closed 9. The shipped texts — right on this head. The five texts the second record failed, and the five the seat swept, carry no per-item universal: where a text speaks of editability it says the copy is the environment's own metadata and the next save edits it, with the 10. CI on the head — all green or rostered skips, read twice. At the first read one shard ( 11. Governed surfaces, form, size, fork — right. None of the 40 paths is under ② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS |
…-install-modes One textual conflict, packages/cloud-connection/vitest.config.ts: both sides inserted an anchored source alias at the same spot. Both kept — the metadata-protocol alias (this branch, ADR-0131 D6) and the objectql alias (main, ADR-0131 D3 W2). packages/spec/src/stack.zod.ts, the install-local door and packages/metadata-protocol/src/protocol.ts merged without a conflict: both duplicate-name and unmanaged-install collectors are present, and the D3 walled-catalog refusal sits where main put it, after step 1b and before step 1c, so ahead of step 1e. Claude-Session: https://claude.ai/code/session_019G5ZnRrTMiwcHG6iZM19zf Co-authored-by: Claude <[email protected]>
…atus clause is ruled, not open; the door's comments name D3 in the merged order Five shipped texts now say one thing about a lock an item ships with: the copy keeps it as written today, and a follow-up on #15213 strips it per #22971's ruling A. Two of them said its handling was an open question; three said only that the lock is kept. The INSTALL_MODES TSDoc, the changeset entry, the install-local door's header docblock, the verb's module header and importUnmanagedPackage's TSDoc. The merge placed main's ADR-0131 D3 walled-catalog refusal after step 1b and before step 1c, so ahead of step 1e. The door's header docblock and the 1e comment now list it among the checks that precede the mode judgement. No behaviour changes. Claude-Session: https://claude.ai/code/session_019G5ZnRrTMiwcHG6iZM19zf Co-authored-by: Claude <[email protected]>
Contract reviewServed-tier: Fifth contract review of PR #22916 ( ① Derived judgments1. The accept set at 2. One key table, and the removal it replaces — right; nothing published is removed. 3. The authoring refusal 4. The install door on the merged head — right (Q2 A, Q9 A), and the order is read off the head's source, not the body. Step 0 parses 5. Step 1f — a stricter answer on one existing input; judged NOT a narrowing of a published accept set, so the 6. The verb's carry items — right. 7. The CLI — right. 8. Reach — right. The two codes are in the closed 9. The shipped texts after the merge round — right. No added line in the diff carries "open question" (swept). The five texts the seat order 10. The merge round itself — verified as the PR body describes it, on the diff. The two door comments the dev edited beyond the five ordered texts — the header docblock's ordering sentence and the 1e comment — now name D3 among the checks that precede the mode check, which item 4 reads as true on this head; both are comments with no behaviour beside them. The one textual conflict, 11. CI on the head — all green or rostered skips, read twice. At the second read, 42 check-runs: 38 12. Governed surfaces, form, size, fork — right. None of the 40 paths is under ② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
Part of #15213: stage S2 of the ADR-0131 D6 execution card. The
installModesmanifest key lands with the install door that honours it, theinstallModebody key andos package install --install-mode, spelledunmanagedper ruling B (6108509158). Still on the card: S3 (the authoring docs page, which now also carries thepackages.tsremedy text, see "Scope and size") and S4 (the objectui marketplace choice).Clause-②: yes (widening: the optional manifest key
installModes; the install-local body fieldinstallMode; the CLI option--install-mode; new exports in@objectstack/spec/kerneland@objectstack/metadata-protocol; the error codesSTACK_UNMANAGED_INSTALL_UNCOPYABLEandINSTALL_MODE_NOT_PERMITTED)Claim
6112998649. S1 (importUnmanagedPackage, PR #22879 →72b26ed4f5) is the verb this door calls. Changeset:.changeset/15213-install-modes.md(minoron@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 with409 RESOURCE_CONFLICT, where a catalog install of it answered200.Merge round (seat order
6121590923; fourth contract review6121541034)Head
8a713cd9e: the merge5a7ed7ca6oforigin/mainate1deaefa8throughscripts/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
maincommits as moving paths this PR edits. Measured one1deaefa8against6326fbe084, a driver-lessmerge-treein a throwaway bare clone (GitHub's condition) and the driver-onmerge-treeboth name one conflicted path,packages/cloud-connection/vitest.config.ts.packages/spec/src/stack.zod.ts,packages/cloud-connection/src/marketplace-install-local-plugin.tsandpackages/metadata-protocol/src/protocol.ts(main's fix(metadata-protocol): two locales of one email template survive a restart — sys_metadata overlay rows keyed by bundle member (#22877, stage 1) #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-protocolalias first, then main's@objectstack/objectqlalias.stack.zod.ts: both collectors are in the merged file. Main'scollectDuplicateFunctionNameErrorssits inside the cross-reference aggregate (STACK_CROSS_REFERENCE_INVALID, no new code), beside this PR'svalidateUnmanagedInstallMode.refuseWalledCatalogPackagestays where main put it (see the order below).Generated artifacts. The script's step 2 took no path from main, because no
merge=os-regenpath moved on both sides. After a fresh spec build,check:generatedreads 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 --stableof the diff against main is the same before the merge (6a40a09ed9..6326fbe084) and after it (e1deaefa8..5a7ed7ca6). The one that differs isvitest.config.ts, whose context changed. Nothing published changes, so theClause-②line stands as written.The door's order on the merged head. Every step comes before any write:
0.
installModeis parsed againstInstallModeSchema(400 INVALID_REQUEST), before the cloud fetch;PLUGIN_MANIFEST_INVALID);PLUGIN_MANIFEST_INVALID, 400 or 502);403 FORBIDDENwhen the incoming or installed version carries a position, a permission set or a capability;422 OS_PROTOCOL_INCOMPATIBLE);422 INSTALL_MODE_NOT_PERMITTED, a malformedinstallModesgetsPLUGIN_MANIFEST_INVALID, andunmanagedon a walled requested posture gets409 UNMANAGED_INSTALL_WALLED_POSTURE;422 VALIDATION_ERROR);unmanagedbranch returns here;409 RESOURCE_CONFLICT), then the managed steps.One answer changes as a result. A tenant admin who asks for
unmanagedunder a wall, on a package that carries a catalog item, now gets D3's403instead of the409. 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_POSTUREwith zero writes, and the managed positive control still answers 200.The
_lockstatus clause (ruling A on [Decision] 「非托管」安装复制带_lock的打包项时:去掉锁(A)、拒绝这类包(B),还是原样保留锁(C)?(ADR-0131 D6 × ADR-0010 保护模型) #22971,6119681934). Five texts now carry the same clause: the lock is kept as written today, and a follow-up on feat(spec,objectql,cli): the template install mode — a package copied once into the environment ledger, fully editable, refused on shared-database multi-tenant postures (ADR-0131 D6) #15213 strips it per [Decision] 「非托管」安装复制带_lock的打包项时:去掉锁(A)、拒绝这类包(B),还是原样保留锁(C)?(ADR-0131 D6 × ADR-0010 保护模型) #22971's ruling A. The strip itself is not in this PR.INSTALL_MODESTSDoc (manifest.zod.ts, shipped in the.d.ts): "keeps that lock on the copy (an open question on feat(spec,objectql,cli): the template install mode — a package copied once into the environment ledger, fully editable, refused on shared-database multi-tenant postures (ADR-0131 D6) #15213)" now reads "keeps that lock on the copy as written today, and a follow-up on feat(spec,objectql,cli): the template install mode — a package copied once into the environment ledger, fully editable, refused on shared-database multi-tenant postures (ADR-0131 D6) #15213 strips it per [Decision] 「非托管」安装复制带_lock的打包项时:去掉锁(A)、拒绝这类包(B),还是原样保留锁(C)?(ADR-0131 D6 × ADR-0010 保护模型) #22971's ruling A"._lock的打包项时:去掉锁(A)、拒绝这类包(B),还是原样保留锁(C)?(ADR-0131 D6 × ADR-0010 保护模型) #22971's ruling A."_lock的打包项时:去掉锁(A)、拒绝这类包(B),还是原样保留锁(C)?(ADR-0131 D6 × ADR-0010 保护模型) #22971's ruling A)".package-unmanaged-install.ts): "keeps that lock)" now reads "keeps that lock as written today, and a follow-up on feat(spec,objectql,cli): the template install mode — a package copied once into the environment ledger, fully editable, refused on shared-database multi-tenant postures (ADR-0131 D6) #15213 strips it per [Decision] 「非托管」安装复制带_lock的打包项时:去掉锁(A)、拒绝这类包(B),还是原样保留锁(C)?(ADR-0131 D6 × ADR-0010 保护模型) #22971's ruling A)".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:check:generatedreads 14 of 14 current;stack-duplicate-function-nameandstack-cross-reference-envelope): 9 files / 235 tests passed, and the census still counts eight sites;install-local-unmanaged-mode: 17 of 17, P2's four cases included; main'swall-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 fourpackage-install-*suites: 23 tests;typecheckexits 0 on spec, cloud-connection, metadata-protocol, cli and dogfood.Gates (
dispatch-gates --commands --repo objectstack-ai/objectstack, no paths, at8a713cd9e). 124 commands were derived. 122 of them ran, and 121 of those exit 0.check:platform-checklistexits 1. A detached control onmainate1deaefa8shows the same 10 problems, line for line.main's794177d9b(fix(qa): check-platform-checklist judgesretiredby its own rule, not the active one #22995) changed that checker after this merge; CI reads the merge ref.check:skill-examplesfirst answered exit 3,PREREQUISITE NOT MET. After@objectstack/client-reactwas built, it exits 0.check:type-check-debt, which the seat order excludes, andcheck:dual-build-cjs-loads, which needs a whole-workspace build (CI'sBuild Coreruns it).--ranreconciles 124 derived, 122 run and 2 UNRUN, those two.check:published-readme-exportsexits 3,PREREQUISITE NOT MET, because it reads packages outside the built closure: NOT MEASURED.After the merge,
origin/mainmoved on to794177d9b(67baccc55,794177d9b). Neither commit touches a path of this PR.Patch round 2: the editability and stricter-answer wording (seat comment
6117204840, contract review6117191273)The review at
618824f3bffailed on two sentences of text; no source logic changed. Commit57b4f4fa7f:_lock) keeps that lock on the copy, and its handling is an open question on feat(spec,objectql,cli): the template install mode — a package copied once into the environment ledger, fully editable, refused on shared-database multi-tenant postures (ADR-0131 D6) #15213." The merge round replaced that last clause.409 RESOURCE_CONFLICTbefore anything is written, for a managed install of a package whose namespace a writable base holds, where a catalog install of it answered200. This body's opening sentence carves out the same answer.notenow 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."mergeable: true, stateblocked(draft), neither dirty nor behind.origin/mainhas moved 4 commits past the merge base, and none touches spec source or a generated reference.57b4f4fa7f:@objectstack/cloud-connectionand@objectstack/cliwere built (exit 0);marketplace-install-local-install-modes.test.ts: 19 of 19;@objectstack/cloud-connectiontypecheckexit 0;package-install-mode.test.ts(--project unit): 8 of 8;@objectstack/clitypecheckexit 0;check-empty-changeset --base origin/mainexit 0.57b4f4fa7fchanges were passed as paths todispatch-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 at57b4f4fa7f(recorded before and after the pass): 60 of 61 exit 0.check:dual-build-cjs-loadsis NOT MEASURED (a whole-workspace build, excluded by the dispatch). The fourcheck:i18n*/check:lean-entry-closuregates ran after the CLI and the four examples were built.--ranreconciles 61 derived, 60 run, the one UNRUN beingdual-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.6117487130), commit8e9d1bfce5. TheINSTALL_MODESTSDoc (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_lockexception in one clause. The door's comments place the walled refusal after the payload check and steps 1b–1c, before any write. Verified at8e9d1bfce5:check:generated14 of 14; spec and cloud-connectiontypecheckexit 0; of the 73 families derived for the two paths, 72 exit 0 anddual-build-cjs-loadsis NOT MEASURED.6118016989, contract review6117921319), commit3059dee3f6. "Editable" with no exception beside it is gone from every shipped text the review named: the 1f refusal message,InstallModeSchema's.describe()and the regeneratedkernel/manifest.mdx, the CLI flag description and example line, and the changeset heading. It is also gone from the five flagged comments and fromimportUnmanagedPackage's TSDoc inprotocol.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 at3059dee3f6, all exit 0:check:generated14 of 14,check:docs,check-empty-changeset, and the spec, cloud-connection, CLI and metadata-protocoltypecheck. Of the 110 families derived for the round's 8 paths, 109 exit 0 anddual-build-cjs-loadsis NOT MEASURED.Patch round: the sync and the supersede (seat comment
6116200445)origin/mainat7dbb89dc5dis merged throughscripts/pm/os-regen-merge.sh(merge29cfd19fd6).content/docs/references/index.mdxis generated and not text-merged, so it was rendered again from the merged spec (gen:schemathengen:docs) and committed on its own (d9add8817a). It now reads Automation 71 schemas (main'sFlowCollectionSlot), Kernel 158 (this PR'sInstallMode), 1,524 in total. This was the redcheck:docsonType Check · source gates. After it,check:generatedreads 14 of 14 artifacts current, andFlowCollectionSlotis in the same 5 generated files onorigin/mainand on this head.618824f3bf). S1's pending entry.changeset/15213-unmanaged-import-verb.mdis restored byte-equal to the merge base (blobaf966c215bon 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 fix(spec)!: a single-brace token in a translated flow screen title is refused, like the description beside it (#22627) #22673 changeset and of19939-flow-collection-value-slots.md.node scripts/check-empty-changeset.mjs --base origin/mainexits 0: no changeset from the merge base is modified or deleted.installedFrom) and Q3 (seed data): A, as built, per the seat. Q1 (an authored_locksurvives 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, andresolveInstallModes(manifest)is the one reader of what it means (DEFAULT_INSTALL_MODES,['managed']). The liveness row islive; its evidence is the door and the authoring refusal.Refused at authoring (Q5).
defineStack, and soos validateandos build, refuses a package that permitsunmanagedwhile it carries what the copy cannot write:allowRuntimeCreate: false, read offDEFAULT_METADATA_TYPE_REGISTRY, never a list);The code is
STACK_UNMANAGED_INSTALL_UNCOPYABLE(422), the eighth site of theStackRefusalErrorfamily, with oneissuesentry per key. The key classification the import verb used to keep privately moved to spec asUNMANAGED_INSTALL_KEY_DISPOSITIONS(kernel/unmanaged-install.ts). The authoring refusal and the door's import now read one table, held total againstManifestSchemaand 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):installModeis parsed againstInstallModeSchema. Absent meansmanaged, so every existing caller is unchanged; a value outside the enum answers400 INVALID_REQUESTbefore the cloud fetch.installModesdoes not permit answers422 INSTALL_MODE_NOT_PERMITTEDwithdetails.permitted;installModesanswersPLUGIN_MANIFEST_INVALID, with the id gate's 400/502 split;unmanagedon a walled REQUESTED posture answers409 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".unmanagedbranch hands the verb ONE flat package body. A compiled artifact's nestedmanifest:is flattened, the collections winning, asAssembledPackageBodySchemarecords. The branch runs none of the managed steps: no conflict classification, no D5e gate, nomanifest.register, no ledger entry, nosyncSchemas, no binder, noapplySideEffects. The answer carriesinstallMode,targetPackageId,copiedCount,copiedandinstalledFrom;seeded: { mode: 'skipped' }appears when the package declares seed data. The verb's refusals are relayed with their own code, status, message anddetails; a 5xx message is withheld and logged.409 RESOURCE_CONFLICT, naming the base in the message anddetails. 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):installedFrom, and none of the managed lines (hot-load; "re-registers on every boot").unmanagedand getting an answer without it means the runtime predates the mode and installed MANAGED. The command then exits 1 and says so.The import verb (
packages/metadata-protocol/src/package-unmanaged-install.ts), as S1's carry list asks:translationsare now copied, each locale of each bundle as onetranslationitemNAMESPACE_LOCALE(crm_zh_cn), the bundles merged in order;installModesis read and never written onto the base;functions: {}) is no code.MetadataWriteFacedocblock is corrected: the verb forwardscause.issues, but its one door relays message + code +detailsonly, so the full-prose default on the 422 stays right.Carried prose.
specVersionguidance namesmarketplace/template-manifest.zod.ts; the retiredcloud/path is pinned absent.18.meta-doors-organization-scope-retired.ts:30reads "an unmanaged install".Zone 2 readings (measured on
origin/mainat the base45e206e498)File list. Every stage-0 S2 site is where the plan put it:
ManifestSchemaatkernel/manifest.zod.ts:401, with the retired path at:418;defineStack's validators instack.zod.ts;liveness/manifest.json, which had 0installModesrows;handleInstallat: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,installModeswould have been refused by the import as an unrecognised key);metadata-protocol/src/index.ts(one export); the twodefineStackrefusal censuses; and S1's pending changeset.The door (base
45e206e498):if (inlineManifest):969;:1149;findConflictat:1195, the D5e gate at:1222,manifest.registerat:1272, the ledger write at:1310andapplySideEffectsat:1343.The
unmanagedbranch 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:24472getPackage/manifests.has,:24479getNamespaceOwners) were measured on the REAL registry by the dogfood suite. See P3.Translations and
installed_from.translation/crm_zh_cnis served after P1 and again after the restart.installed_fromis recorded on the door's answer (installedFrom: { packageId, version }) and on the operator log line atinfo. No durable carrier: it needs a contract decision (the install record orsys_packages.metadata), which is left to the seat. See Acceptance notes.translations, now copied by the verb; anddata, not loaded, and the answer says so.The acceptance on
examples/app-crm. The fixture is the CRM's built artifact withinstallModes: ['managed', 'unmanaged']on its manifest. Suite:packages/qa/dogfood/test/install-local-unmanaged-mode.dogfood.test.ts, 17 cases, green on the merged tree799c30127band again at677d7fad45.single, one database file across a restart):targetPackageId: 'com.example.crm-copy'andinstalledFrom: { packageId: 'com.example.crm', version: '4.0.0' };GET /meta, never with a non-orgprovenance;PUTwith its own served body,defineViewcontainers included (view/crm_lead);package_id= the base andorganization_idNULL;com.example.crm;crmnamespace.isolated):409 UNMANAGED_INSTALL_WALLED_POSTURE,details: { posture: 'isolated' };sys_metadatarow, no ledger file;4.0.1answers409 RESOURCE_CONFLICTwithdetails: { namespace: 'crm', base: 'com.example.crm-copy' };checksum|metadatais byte-identical before and after;getPackageon the real registry);com.example.showcaseanswers 409 (manifests.has);details: { namespace: 'crm', owners: ['com.example.crm-copy'] }(getNamespaceOwners).Size. See "Scope and size".
Ablation
Dropping the requested-posture refusal turns P2 red.
unmanagedInstallPostureRefusalalways answersnull, viascripts/ablation-replace.mjsin wrap mode (anchor ×1 → ×0, blob87618e8d12→c3f5b8c7b9).@objectstack/metadata-protocolwas rebuilt, andablation-dist-preflightfound the marker in 2 built files. The build's.d.tsstep refused the always-true condition (TS2872), but the JS the dogfood consumes was emitted.87618e8d12== HEAD,git diff HEADempty, rebuild exit 0,ablation-dist-preflight --absentmarker absent from all 24 built files, tree clean. P2 green again (4 passed).Tests (each line names the commit it ran at)
618824f3bf(after the sync merge29cfd19fd6),@objectstack/spec: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-registrycurrent (423 semantic), then--project repo(test:repo): 55 files / 971 tests passed;typecheckexit 0.cloud-connectionormetadata-protocolsource. The lines below are the earlier round's, after the merge682620e7dc.@objectstack/spec:bbe1b2be2b(the merged tree):--project local650 files / 19,405 tests passed (1 todo);--project repo55 / 971;typecheckexit 0;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);typecheckexit 0.stack-refusal-envelopes,stack-conversions-record) now count eight sites.@objectstack/cloud-connection, full suite at677d7fad45: 44 files / 536 tests. The newmarketplace-install-local-install-modes.test.tshas 19 cases.typecheckexit 0.@objectstack/metadata-protocolatbbe1b2be2b: 5 files / 123 tests (package-unmanaged-install, both face inventories, thelookupArtifactItemcall-site census,tenant-authored-write).typecheckexit 0.@objectstack/cli,--project unit, the fourpackage-install-*suites atbbe1b2be2b: 4 files / 23 tests,package-install-mode.test.ts8 of them.typecheckexit 0 at677d7fad45. Theintegrationtier is declared to CI: no spawn entry and no integration file is touched.@objectstack/runtime,src/domains/atbbe1b2be2b: 108 files / 2,712 tests;typecheckexit 0. The runtime diff is empty after the move to S3.799c30127b(merged tree, closure rebuilt): 7 files / 56 tests;677d7fad45(cloud-connection rebuilt): 17 / 17;typecheckexit 0.Gates (
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, no paths, derived again and run at618824f3bf)The derivation at
618824f3bfnames the same 124 commands as at677d7fad45. Union at618824f3bf(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 isdual-build-cjs-loads(below).PREREQUISITE NOT MET, which measures nothing: the worktree was re-created for this round, so the built outputs they read were absent. The 8 arecheck:doc-formula-expressions,check:doc-security-posture,check:skill-examples,check:docs-transcript-drift,check:i18n,check:i18n-coverage,check:i18n-walk-parityandcheck: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-majorexit 0.pnpm check:platform-checklist: red onorigin/maintoo, not this PR. The same 8 problems (7ABSENT SYMBOLanchors indocs/qa/platform-checklist/areas/*.jsonnamingprotocol.ts/attachment-access-hooks.tssymbols, plus one anchor floor) are identical, line for line, on a detachedorigin/mainateab7236ff7, measured this round. This PR touches no checklist file and no named symbol.618824f3bf:check:generated,check:docs("226 generated files in sync"),check:api-surface("public API surface + factory signatures unchanged"),check:authorable-surfaceandcheck-empty-changeset. Also exit 0:check:liveness,check:route-envelope,check:error-code-casingandcheck:nul-bytes.check:slot-lookup,check:spec-parsed-alias,check:test-source-aliasandcheck:empty-state.618824f3bf, all exit 0; 14 of those are checker-health--self-testruns, 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-keywordandcheck-single-claim-paths.origin/mainmoved one commit,eab7236ff7(feat(verify): the reference-value shape family, runnable as os verify --reference-shapes (#15953 slice 1) #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 base7dbb89dc5d. It read 2,604 / 41 files before this round, which counted S1's changeset edit that is now restored. It measured 2,667 with thepackages.tsremedies, past the stage plan's ~2.6k mark. As the plan prescribes, thepackages.ts"ADR-0005 org overlay" remedy text (:421,:524, docblock:384) moves to S3, untouched here. S3 therefore does not fold in.Acceptance notes
_lock: 'full', ADR-0010) is copied as written. The nextPUT /api/v1/meta/view/NAMEthen answers403 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 [Decision] 「非托管」安装复制带_lock的打包项时:去掉锁(A)、拒绝这类包(B),还是原样保留锁(C)?(ADR-0131 D6 × ADR-0010 保护模型) #22971 (6119681934), the import strips_lock/_lockReason/_lockSourceand names each stripped lock. A follow-up on feat(spec,objectql,cli): the template install mode — a package copied once into the environment ledger, fully editable, refused on shared-database multi-tenant postures (ADR-0131 D6) #15213 carries the strip. This PR keeps the lock as written today, and every shipped text says so (see "Merge round").installed_fromhas no durable carrier (see Zone 2 item 3). The seat ruled this A, as built (Q2).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.registerAppa body whosenamespacesits only on the nestedmanifest:, 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.OS_TENANCY_POSTUREis 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