Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion docs/adr/0070-package-first-authoring.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# ADR-0070: Package-first authoring — every runtime-authored item lives in a writable package (base); no orphans

**Status**: Accepted (2026-06-24) — P1–P3 implemented, merged & live-verified (see *Implementation status*); D4–D6 remaining · **Superseded in part by [ADR-0146](./0146-studio-designs-setup-administers-one-editor-per-item-paired-customize-package.md)** (2026-10-31) — D3's prompt to create a base, for the paired case only: a customer's first customization of an installed base lands in the auto-created `<base>.custom` package without a prompt (ADR-0146 D3); D5's destination for a package-less overlay row of a packaged item: the base's `<base>.custom`, as a declared override, after which the package-less write closes (ADR-0146 D11). D1, D2, D4 and D6 stand; D3's prompt stands everywhere else.
**Status**: Accepted (2026-06-24) — P1–P3 implemented, merged & live-verified (see *Implementation status*); D4–D6 remaining · **Superseded in part by [ADR-0146](./0146-studio-designs-setup-administers-one-editor-per-item-paired-customize-package.md)** (2026-10-31) — D3's prompt to create a base, for the paired case only: a customer's first customization of an installed base lands in the auto-created `<base>.custom` package without a prompt (ADR-0146 D3); D5's destination for a package-less overlay row of a packaged item: the base's `<base>.custom`, as a declared override, after which the package-less write closes (ADR-0146 D11). D1, D2, D4 and D6 stand; D3's prompt stands everywhere else. · **Amended** (2026-10-31, [#28117](https://github.com/objectstack-ai/objectstack/issues/28117) — the maintainer's ruling of 2026-10-30, 「大包 + 设置里启用模块」, then 「同意」: a `type: module` sub-package of an installed app may be **disabled**, not authored into, by an admin, on the tie PR [#28308](https://github.com/objectstack-ai/objectstack/pull/28308) shipped; see the amendment note under D2. D2's two kinds and its writability predicate stand.)
**Deciders**: ObjectStack Protocol Architects
**Builds on**: [ADR-0005](./0005-metadata-customization-overlay.md) (`allowOrgOverride` / runtime overlay model; one Zod source per type), [ADR-0048](./0048-cross-package-metadata-collision.md) (one app per package; package id is the addressing unit), [ADR-0033](./0033-ai-assisted-metadata-authoring.md) (AI authors metadata as drafts), [ADR-0067](./0067-commit-history-and-rollback-for-ai-authoring.md) (commit history & rollback for AI authoring)
**Consumers**: `@objectstack/objectql` (`protocol.saveMetaItem` write path), `@objectstack/spec` (metadata-type registry), `../objectui` (Studio create flow, package scope selector, `PackagesPage`), `../cloud` (`service-ai-studio` — `create_metadata` / `resolvePackageId` / `apply_blueprint`)
Expand Down Expand Up @@ -63,6 +63,8 @@ A runtime **create** (`intent = runtime-only`, i.e. not an overlay of an artifac

The kernel exposes a single predicate — *is this package writable?* — reused by all surfaces (cloud's `resolvePackageId:319` read-only check is the seed of this).

> **Amended 2026-10-31 ([#28117](https://github.com/objectstack-ai/objectstack/issues/28117), the maintainer's ruling of 2026-10-30; shipped in PR [#28308](https://github.com/objectstack-ai/objectstack/pull/28308), `7abe151b`) — a `type: module` sub-package of an installed app may be disabled (not authored into) by an admin.** The maintainer, verbatim: 「大包 + 设置里启用模块」, then 「同意」 — a suite ships as one artifact, an `app` package with `module` packages beside it; the customer's own admin switches its modules off and on, per environment; and "disabled" has exactly one meaning: hidden, refused on the data plane, data kept. *Read-only* above means *may not be authored into*; it does not mean *may not be switched off*. So `PATCH /api/v1/packages/:id/disable` reads one exemption before the writability check, `packages/runtime/src/domains/packages.ts#isDisableableAppModule`, and the tie it reads is the one both install paths leave on the registry row — a code boot and a marketplace install alike, since neither keeps the artifact's own package list past registration: the row's `manifest.type` is `'module'`, its `manifest.dependencies` directly names a package installed here whose `manifest.type` is `'app'` (ADR-0141 D1), and its own `manifest.scope` is not `system` / `cloud`. Everything else answers the door as before. The predicate above does not move: `packages/metadata-protocol/src/package-writability.ts#isWritablePackage` is unchanged, `writable` keeps meaning "may author into" and stays `false` for a module, so edit, publish, revert, delete and overwrite still answer `422 WRITABLE_PACKAGE_REQUIRED`; the door's own verdict is served beside it as `disableable` (`packages/spec/src/api/package-api-assembled.zod.ts#SERVED_ROW_VERDICTS`), computed by the predicate the door asks, so no client re-derives the rule. While the module is disabled, its objects answer `404 OBJECT_PACKAGE_DISABLED` on the data plane and the by-name reads, as any disabled package's do; the navigation fold drops its contributions (`packages/objectql/src/registry.ts#applyNavContributions`); and its record-change flows are withheld for in-process writes too (`packages/triggers/trigger-record-change/src/record-change-trigger.ts#RecordChangeTrigger`). Enable restores all three; the data was never touched. Not decided here: whether an app's own in-process access to a disabled module's objects (a hook's `ctx.api`) should meet the data plane's refusal — today it is served, pinned and documented, and the question is with the maintainer as decision letter `6538936519` on #28117.

### D3 — Create-flow contract: select-or-create a base first, enforced everywhere [new]

Every authoring surface follows the same contract; none silently defaults into a catch-all:
Expand Down
Loading