Repository navigation
docs(deployment): the install modes page; fix(runtime): the read-only package refusals stop prescribing the retired org overlay - #23108
Conversation
…hat works, not the retired org overlay WRITABLE_PACKAGE_REQUIRED and DUPLICATE_SOURCE_NOT_A_BASE prescribed "an ADR-0005 org overlay". ADR-0131 D6 retired that axis: a write naming an organization answers 403 NOT_OVERRIDABLE. Both now name customising beside the package as environment-level state, one clause shared by the two doors; the duplicate refusal also names the unmanaged install for an editable copy. Text only: no code, status, condition or order changes. Claude-Session: https://claude.ai/code/session_019G5ZnRrTMiwcHG6iZM19zf Co-authored-by: Claude <[email protected]>
…ed install keeps, and what an unmanaged copy keeps and drops A new authoring page beside Publish, Versioning & Preview: how a package declares installModes, how the installer picks a mode, what a managed install keeps (its locks bind), and what an unmanaged copy keeps and drops (no lock in either spelling; strippedLocks and the CLI's Locks removed lines name each one). Plus the runtime changeset for the refusal text, and the regenerated hand-written docs list. Claude-Session: https://claude.ai/code/session_019G5ZnRrTMiwcHG6iZM19zf Co-authored-by: Claude <[email protected]>
📓 Docs Drift CheckThis PR changes 1 package(s): 1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 1 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 26 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 99889f878e5867e5050b30d4e36b371ae4c3259b && git checkout 99889f878e5867e5050b30d4e36b371ae4c3259b
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 2808438e9dad471383140b286f8d970afa59559d 338dc51bfd73f4e8ece5d3d3b2bedeaded33a6cb && git checkout -B drift-repro 2808438e9dad471383140b286f8d970afa59559d && git merge --no-ff 338dc51bfd73f4e8ece5d3d3b2bedeaded33a6cb
node scripts/docs-audit/affected-docs.mjs --json 2808438e9dad471383140b286f8d970afa59559d
|
Part of #15213
Clause-②: no
Stage S3 of #15213: the authoring docs page for a package's install modes, and the
packages.tsrefusal text that still prescribed the retired "ADR-0005 org overlay". S4 (the objectui marketplace choice) remains on the card, so #15213 stays open.Clause-② is
nobecause only text moves. No accept set, schema, published shape, refusalcode, status,details, condition or order changes.What changes
content/docs/deployment/install-modes.mdx(new) and its nav entry incontent/docs/deployment/meta.json, right after Publish, Versioning & Preview. Packaging and publishing pages live in that tree, and that page covers the install step this one extends.scripts/docs-audit/handwritten-docs.jsonwas regenerated (check-audit-scope.mjs --write, +1 line).packages/runtime/src/domains/packages.ts: the two read-only refusals name the customisation that works today. One shared clause,CUSTOMISE_BESIDE_REMEDY, serves both doors. The duplicate refusal also names the unmanaged install. The:389docblock and the duplicate docblock's two remedy sentences were reworded to match.packages/runtime/src/domains/packages-readonly-gate.test.ts: the three pins that readADR-0005now readADR-0131 D6, and they assert thatADR-0005is absent. The duplicate pin also assertsinstallMode 'unmanaged'..changeset/15213-readonly-package-remedy.md:@objectstack/runtimepatch.A1 holds. At
2d21b9c891,git grep -il 'installModes\|unmanaged'overcontent/docs(excludingreleases/andreferences/) matched 0 files. The control wordos package installmatched 7 files on the same tree. No existing page covered install modes, so this is a new page rather than an extension of one.The page, sentence by sentence, with the code that backs it
Each reading was taken on
mainbefore the sentence was written. The suites named here were run at base2d21b9c89and again at head338dc51bf(verification record below).The two modes (the intro table)
ManifestSchema.installModesandINSTALL_MODESinspec/src/kernel/manifest.zod.ts.package_id= the base andorganization_idNULL, are served_provenance: 'org', andPUTof every copied item answers 200.findConflictanswers'marketplace'(allow upgrade). Dogfood P1: no ledger file, andgetPackage(source)is undefined across a restart.groupandisolated. Backing: dogfood P2. Onisolatedthe unmanaged install answers 409UNMANAGED_INSTALL_WALLED_POSTUREwith zero writes, and the managed control answers 200.Declare the modes
defineStackexample withinstallModes: ['managed', 'unmanaged']. Backing: the key is a live manifest key, andmanifest.test.tsparses it.['managed'], with no parse-time default, so nothing is stamped. Backing:resolveInstallModesandDEFAULT_INSTALL_MODES.manifest.test.tspins that the parsed output has noinstallModesproperty.managedorunmanaged. Backing:.min(1)andz.enum(INSTALL_MODES).manifest.test.tspins[]and['template'].unmanagedalone is legal, and a walled deployment cannot install such a package. Backing:judgeInstallMode. A managed request gets 422INSTALL_MODE_NOT_PERMITTED, and an unmanaged one gets the walled refusal.defineStack,os validateandos buildrefuse a package that permitsunmanagedwhile carrying certain keys, withSTACK_UNMANAGED_INSTALL_UNCOPYABLEand one finding per key. Backing:validateUnmanagedInstallModeinstack.zod.tsandstack-unmanaged-install-mode.test.ts. The keys are:RUNTIME_UNCREATABLE_TYPES, read offallowRuntimeCreate);code);uncopyable:objectExtensions,picklistExtensions,navigationContributions,contributes);packages(envelope).unmanaged, or to move what cannot be copied. Backing: the validator docblock's own remedy.UNMANAGED_INSTALL_KEY_DISPOSITIONSinkernel/unmanaged-install.ts, read by bothdefineStackand the import planner.Pick the mode at install time
--install-modesetsinstallModeonPOST /api/v1/marketplace/install-local. Without the flag no field is sent, and the runtime installs managed. Backing: CLIinstall.ts:201–:202. The CLI test pins that noinstallModeis sent without the flag.INSTALL_MODE_NOT_PERMITTED, naming what is permitted; walled gets 409, naming the posture; managed while a copy holds the namespace gets 409RESOURCE_CONFLICT, naming the base. Each is judged before any write. Backing:judgeInstallModeand step 1f. The cloud-connection install-modes suite covers all three (21 tests), and dogfood P2 and P3 cover them end to end.install.ts:297, pinned by the CLI test "asked for unmanaged, answered without it".What a managed install keeps
kernel:readyrehydrate, andfindConflict.view,dashboard,report,translationoremail_templateis overlaid by saving one under the same name, and removing the overlay restores the package's own. Backing: theallowEnvironmentOverlay: trueregistry rows inmetadata-plugin.zod.ts. Dogfoodmanaged-content-sealed"control (regime O)":PUT200,DELETE200, "reset to artifact default".managedItemSealedSentenceandpackagedBaseRegimeSentenceinpackaged-base-regime.ts, and the same suite's 403 cases.protectionblock is translated into_lockwith_lockSource: 'package', the item is served locked with the reason, and the write doors answer 403ITEM_LOCKEDper the lock matrix. Backing: dogfood "lock control" (the dashboard serveslock: 'full',editable: false,deletable: false; the dataset serves_lock: 'no-overlay'and_lockSource: 'package', with noprotection). The matrix isobjectql/src/protocol-lock-enforcement.test.ts(7 tests).What an unmanaged copy keeps and drops
PACKAGE_ID-copy, which keeps the namespace. Backing: the door passes notargetPackageId, soplanUnmanagedImportuses its default. Dogfood P1 answerstargetPackageId: 'com.example.crm-copy'andnamespace: 'crm'.importUnmanagedPackagecallssaveMetaItemfor each item, and dogfood P1.NAMESPACE_LOCALEitems (crm_zh_cn). Backing: thetranslations: 'converted'disposition, the unit test, and dogfood P1.seeded.mode: 'skipped'when the package declares any. Backing:installUnmanaged(declaresSeeds) and dogfood P1.importUnmanagedPackagesteps 1–8 androllBackUnmanagedImport. Unit tests cover the environment-row collision, the managed-name collision and the mid-copy failure.The copy keeps no lock, in either spelling (the seat's carry
6128999134, restated in6132465308)_lock的打包项时:去掉锁(A)、拒绝这类包(B),还是原样保留锁(C)?(ADR-0131 D6 × ADR-0010 保护模型) #22971 (6119681934): "a package permitsunmanagedexplicitly throughinstallModes, so the vendor has already chosen to let environments take editable copies"._lock,_lockReason,_lockSourceand_lockDocsUrl, and without itsprotectionblock on a type whose schema declares one. Backing:stripUnmanagedImportLock. The block's declaration is asked ofoverlaySchemaDeclaredKey. Dogfood P1 finds no copied row carrying any of the five keys.INVALID_METADATAwith a rollback. Aprotectionblock onaction,hookorpageis left in place and refused the same way. Backing: unit tests "a lock family the spec refuses is not stripped" and "aprotectionblock on a type that declares none is still refused by the save door".The install lists each lock it drops
strippedLockshas one entry per binding lock, in write order, shaped{ type, name, lock, reason? }.reasonis verbatim, or absent when the package gave none. Both spellings on one item produce one entry with the block's lock.noneis not listed. The list is always present. Backing:ImportUnmanagedPackageResponseSchema.strippedLocksand the unit tests for both spellings,none, and a package that locks nothing. Dogfood P1 checks the exact list.Locks removed, then oneTYPE.NAME: LOCK — REASONline per lock, leaving off the dash and reason when there is no reason. Backing: CLIinstall.ts:275–:283. The CLI test pinsLocks removed:\s+2and both line forms. The printed block mirrorsprintKV's layout.The remedy text, FROM → TO
WRITABLE_PACKAGE_REQUIRED(DELETE,PATCH /disable,POST /publish,POST /reverton a read-only package)FROM:
TO:
DUPLICATE_SOURCE_NOT_A_BASE(POST /packages/:id/duplicateon a read-only package)FROM:
TO:
Why each remedy, measured on
main:NOT_OVERRIDABLEbyorganizationScopedWriteRefusal.protocol.org-scoped-write-refused.test.tspins it for every registry type, on the save, the publish and the package publish. Together with the unmanaged-install suite it passes 364 tests at head.managedItemSealedSentence.ITEM_LOCKED, per the matrix test.INSTALL_MODE_NOT_PERMITTED;RESOURCE_CONFLICTwith "Uninstall that package first, or keep the managed install" (unit test "refuses an owned namespace — a managed install of the same package included").2d21b9c891(:389,:538,:641) and 0 at head. No refusal is left without a working remedy, so there is no open question.Verification record
At head
338dc51bf, the merge oforigin/mainabf86554cthroughscripts/pm/os-regen-merge.sh. The merge left no further changes to commit, and the branch touches no generated artifact that needs regeneration. The dogfood closure was rebuilt on the merged tree:turbo run build --filter=@objectstack/dogfood^..., 63/63, throughos-verify-lock.sh. Every run below went through the lock, and each VERDICT line reads exit 0. The reading-suite chain is joined with&&after one leadingexport, so its last exit covers every suite in it. The reverse-verification script exits 0 by design, and the red it measured is quoted below.@objectstack/runtime, the affected package. Fullvitest run --project local: 361 files, 5163 passed, 19 skipped.typecheckexit 0.The reading suites, at head:
package-unmanaged-install.test.ts+protocol.org-scoped-write-refused.test.ts: 364 passed;protocol-lock-enforcement.test.ts: 7;--project unitpackage-install-mode.test.ts: 10;marketplace-install-local-install-modes.test.ts: 21;stack-unmanaged-install-mode.test.ts+kernel/unmanaged-install.test.ts+kernel/manifest.test.ts: 103;install-local-unmanaged-mode+managed-content-sealed: 30.The same suites, minus the org-scope and lock-matrix files, passed at base
2d21b9c89before the page was written.Reverse verification, from the committed state:
packages.tswas restored from base2d21b9c891(blobaad47d51d5, landing proven by hash equality; "ADR-0005 org overlay" ×3,CUSTOMISE_BESIDE_REMEDY×0).packages-readonly-gate.test.tsthen went red as predicted: 3 failed, 22 passed. The three failures were the duplicate envelope pin and the DELETE and disable remedy pins.git checkout HEAD --under an EXIT/INT/TERM trap. Afterwards the blob equals HEAD (fd665ab7e8) andgit diff HEADis empty.Gates:
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackwas derived at338dc51bf. Its stderr line 1 namesobjectstack-ai/objectstackat338dc51bf, and it derived 100 commands. One pass ran at338dc51bf, with HEAD recorded before and after and stdin closed:pnpm check:type-check-debt: skipped, NOT MEASURED by the dispatch.pnpm --filter @objectstack/spec run check:skill-examplesandpnpm check:dual-build-cjs-loads: exit 3, PREREQUISITE NOT MET. They needdist/of packages this diff does not touch:client-react,studio,embedder-openai,knowledge-memory,knowledge-ragflow,organizations,service-cluster-redisandservice-knowledge. Three tries to build them through the lock each hit queue-timeout (exit 99, about 27 minutes, while another run held the lock). This is a declared narrowing: NOT MEASURED here, and CI runs both.--ranreconciliation: 100 derived, 97 run, 2 NOT-MEASURED, 1 UNRUN (check:type-check-debt, as dispatched).check:doc-authoring;check:doc-anchors(491 fragment links resolve);check-affected-docs;check:docs-audit-scope(203 hand-written docs);check-doc-frontmatter(414 pages parse);check:docs-single-h1;@objectstack/spec check:docs.check:nul-bytesexit 0 too.check:generatedis not derived for this diff, which touches no spec file.packages.tsand its test. The.mdx, the.mdand the two.jsonfiles answer "File ignored because no matching configuration".eslint --no-inline-config --format jsonon the 2 files reports 2 files, 0 errors and 0 warnings. The config enables no type-aware linting (noparserOptions.project, noprojectService; 6 rules resolve forpackages.ts), so this diff cannot move a verdict on an untouched file.affected-docs.mjs origin/mainnamescontent/docs/api/metadata-api.mdx(re-read: it quotes neither refusal's text, so nothing changes) and one release-owned page.Size: 322 changed lines (+299 / −23), 6 files, against merge base
abf86554c. Under the 3,000 human-merge line.Acceptance notes
package-lifecycle.zod.ts:233(A3) is not applied here. PR feat(spec, metadata-protocol): the draft list and the commit timeline tell two languages of one email template apart — each entry carries member (#23073) #23099 (claude/issue-23073-list-member, another seat) edits that file and is open and not merged, so this stage does not touch it.main. It says "No route serves the verb yet; the install door that reads aninstallModecalls it". Since S2 (feat(spec,cloud-connection,cli): the installModes manifest key and the install door's unmanaged mode (ADR-0131 D6, stage S2) #22916),POST /api/v1/marketplace/install-localwithinstallMode: 'unmanaged'calls it:installUnmanagedcallsprotocol.importUnmanagedPackage.ImportUnmanagedPackageRequestSchema(:233–:234): "The install doorPOST /api/v1/marketplace/install-localcalls it forinstallMode: 'unmanaged', with the package body flattened and notargetPackageId, so the base takes the default id." It is TSDoc only, with no.describe()change.--install-mode.content/docs/deployment/cli.mdx, theos package installoptions table, has no row for the flag S2 shipped. Neither Publish, Versioning & Preview (step 3) nor the deployment index table ("The pages for shipping an app") links the new page. All three are outside this claim's file surface (one new page plus its nav entry). Carrier: none.installModesTSDoc names the wrong code for the authoring refusal.spec/src/kernel/manifest.zod.ts(:628) saysUNMANAGED_INSTALL_UNCOPYABLE.defineStackraisesSTACK_UNMANAGED_INSTALL_UNCOPYABLE; the shorter code is the import's. It is TSDoc only and does not reach the generated reference. Carrier: none.packaged-base-regime.tssays the install mode is not modelled yet. Its header (the "A managed package" section) reads "The install mode is not modelled yet (the manifest declaration of permitted modes is later work)", which has been stale since S2. Its conclusion still holds: only a managed install registers items through the artifact loader. Carrier: none.template. S2's ACCEPT routed this to S3. It is Tier H and outside this claim's file surface, so it is not edited. Carrier: none.groupandisolatedfor the walled refusal. The verb reads an unrecognisedOS_TENANCY_POSTUREas walled, but S2 observed the door's admission answering 401 before that point in a stub harness. That is unmeasured here, so the page does not state it.installModesyet.git grep -l installModes -- examplesmatches 0 files at head. The controlmanifest:matchesexamples/app-crmandexamples/app-multi-packageon the same tree. The page's guidance is prospective, and only the dogfood fixture adds the key.Generated by Claude Code