Skip to content
Merged
1 change: 1 addition & 0 deletions docs/adr/0019-app-as-consumer-unit.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
**Deciders**: ObjectStack Protocol Architects
**Builds on**: ADR-0003 (Package as First-Class Citizen), ADR-0006 (Three-Layer Tenancy), ADR-0016 (Studio Package Authoring & Publish)
**Consumers**: `@objectstack/spec/kernel` (`plugin.zod.ts`), `@objectstack/spec/cloud` (`marketplace.zod.ts`), the Console `marketplace` UI, the Studio publish flow, `docs/design/marketplace-publishing.md`
**Related**: [ADR-0141](./0141-inter-app-dependency-declared-checked-before-install.md) (2026-10-25) — the separate, gated decision §Out of scope asks for on "Shared/published data model + inter-App dependencies", taken for the dependency half only (a declared `manifest.dependencies` entry plus a pre-install check; the shared data model half stays out of scope). Additive cross-reference under ADR-0130 D2's recording convention; this record's text is unchanged.

> **Revision history**
> - **v1/v2** explored a much larger scheme: a metadata/code *plane* split,
Expand Down
53 changes: 37 additions & 16 deletions docs/adr/0028-metadata-naming-and-namespace-isolation.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# ADR-0028: Metadata Naming & Namespace Isolation — Derived Physical Names, Namespace-Scoped Identity, and a Single Kernel Contract

**Status**: Deferred (2026-07-16; proposed 2026-06-01) — the target model (namespace as identity dimension, short authored names, derived physical names, `namingMode` dual-read, `sys` reservation, namespaced transport segments) is entirely unbuilt, and adopting it now would be a platform-wide migration (physical table derivation, transport routes, dual-read compatibility) with no present pain to justify it: the literal-prefix current-state contract (`spec/kernel/namespace-prefix.ts`) is enforced, collision-proof (ADR-0028/0048), and working. **Re-open trigger**: real multi-package ecosystem naming pain — e.g. a marketplace package whose natural short names are unusable under literal prefixing, or cross-environment portability requiring namespace remapping. Until then this stays a recorded design, not planned work.
**Status**: Deferred (2026-07-16; proposed 2026-06-01) — the target model (namespace as identity dimension, short authored names, derived physical names, `namingMode` dual-read, `sys` reservation, namespaced transport segments) is entirely unbuilt, and adopting it now would be a platform-wide migration (physical table derivation, transport routes, dual-read compatibility) with no present pain to justify it: the literal-prefix current-state contract (`spec/kernel/namespace-prefix.ts`) is enforced, collision-proof (ADR-0028/0048), and working. **Re-open trigger**: real multi-package ecosystem naming pain — e.g. a marketplace package whose natural short names are unusable under literal prefixing, or cross-environment portability requiring namespace remapping. Until then this stays a recorded design, not planned work. · **Amended** (2026-10-25, [ADR-0141](./0141-inter-app-dependency-declared-checked-before-install.md) D6, rulings [`6369704387`](https://github.com/objectstack-ai/objectstack/issues/25403#issuecomment-6369704387) and [`6377678758`](https://github.com/objectstack-ai/objectstack/issues/25403#issuecomment-6377678758) on #25403): D4's sentence is rewritten from "Apps are sandboxed: no cross-app references" to "No cross-app reference without a declared dependency", and the three sentences that restate it (TL;DR item 4, D6's author-time bullet, leak-point 3) follow; nothing else moves and the record stays Deferred.
**Deciders**: ObjectStack Protocol Architects
**Supersedes**: the hand-written object-namespace-prefix authoring rule documented in `packages/spec/src/kernel/manifest.zod.ts` (the `namespace` field) and enforced by `validateNamespacePrefix()` in `packages/spec/src/stack.zod.ts` — that rule's standalone record is [ADR-0129](./0129-object-name-is-the-canonical-id.md) (2026-08-29; it governs until this ADR's re-open trigger fires).
**Builds on**: [ADR-0005](./0005-metadata-customization-overlay.md) (one Zod source per type, org overlay), [ADR-0008](./0008-metadata-repository-and-change-log.md) (Repository · ChangeLog · Cache · Registry; `MetaRef = org/type/name`), [ADR-0010](./0010-metadata-protection-model.md) (protection model), [ADR-0019](./0019-app-as-consumer-unit.md) (app as the consumer-installable unit), [ADR-0025](./0025-plugin-package-distribution.md) (package distribution), [ADR-0029](./0029-kernel-object-ownership-and-platform-objects-decomposition.md) (**prerequisite** — kernel object ownership; D5/D6 below assume the kernel is properly owned per ADR-0029)
Expand Down Expand Up @@ -33,9 +33,12 @@ metadata platform actually uses, and extends collision-freedom to **all** kinds:
identifiers inject the namespace at generation. Uniqueness is enforced where
it physically matters (storage tables, route table, tool registry), not on
the authored name.
4. **Apps are sandboxed: no cross-app references.** The only legal
cross-boundary reference is **app → kernel**. This doubles as a security
boundary.
4. **No cross-app reference without a declared dependency** (rewritten
2026-10-25 by ADR-0141 D6; first proposed as "Apps are sandboxed: no
cross-app references", app → kernel only). The legal cross-boundary
references are **app → kernel** and **app → an app its manifest declares in
`dependencies`**; every other cross-app reference is refused. The closed set
is the security boundary.
5. **The kernel is one unified, reserved namespace (`sys`) — one contract, but
ownership distributed across first-party plugins.** It is the platform's
public contract and the sole well-known import target (`sys.user`). Each
Expand Down Expand Up @@ -152,13 +155,28 @@ schema), resolved back to `(ns, name)` at runtime.
**Resolution pipeline:** `route /data/todo/task` → handler resolves `(todo, task)`
→ driver derives physical `todo_task` → response payloads use short names.

### D4 — Apps are sandboxed: no cross-app references (security boundary)

A `type: app` package may reference its own metadata (short names) and the
kernel (qualified), but **may not reference metadata owned by another app**.
This collapses the only remaining "two writing styles" risk to a single,
allow-listed case (kernel imports) and simultaneously enforces a tenant-style
isolation boundary between marketplace apps.
### D4 — No cross-app reference without a declared dependency (security boundary)

> **Rewritten 2026-10-25 by [ADR-0141](./0141-inter-app-dependency-declared-checked-before-install.md) D6**
> (rulings [`6369704387`](https://github.com/objectstack-ai/objectstack/issues/25403#issuecomment-6369704387) and
> [`6377678758`](https://github.com/objectstack-ai/objectstack/issues/25403#issuecomment-6377678758) on #25403). As
> proposed on 2026-06-01 this decision read *"Apps are sandboxed: no cross-app references"* — a `type: app` package
> *"may not reference metadata owned by another app"*, the only legal cross-boundary reference being app → kernel. The
> isolation stays; its boundary is now the set of apps the manifest declares. The rest of this record is unchanged and
> stays Deferred.

A `type: app` package may reference its own metadata (short names), the
kernel (qualified), and the metadata of the apps its manifest declares in
`dependencies` (qualified); it **may not reference metadata owned by any other
app**. The "two writing styles" risk stays collapsed to allow-listed,
declaration-driven cases — a qualified form appears only for a cross-boundary
reference, and the set of legal targets is closed and written in the manifest —
and the closed set keeps the tenant-style isolation boundary between
marketplace apps: an app reaches only what it declared. On the enforced
present-state model (ADR-0129), the runtime save door already judges a package
write against that closure, and ADR-0141 D2 decides that installing an app
whose declared dependency is absent or out of range is refused — a decision
its runtime card implements; nothing enforces it until that card lands.

### D5 — The kernel is one unified, reserved namespace (`sys`) with object ownership distributed across first-party plugins

Expand Down Expand Up @@ -232,8 +250,9 @@ reference target. It is:
- **Author time** (`defineStack`, `os validate`): generalize
`validateNamespacePrefix` into a *namespace-scope* validator over **all**
collections — names must be bare short names (no `ns_` prefix, no `__`),
references resolve within the package namespace or to a qualified
`sys.x` / (forbidden) cross-app ref. Early failure with the exact fix string.
references resolve within the package namespace, to a qualified `sys.x`, or
to a qualified reference into a declared dependency (D4); any other cross-app
ref is refused. Early failure with the exact fix string.
- **Install time** (package registry): register every
`(namespace, type, name)` plus every derived transport key (route,
connector id, tool name, webhook). The only true conflict left is **two
Expand All @@ -257,9 +276,11 @@ the author/AI as a second style:
name needs a `tableName`/`physicalName` override — derivation is the
*default*, not mandatory (mirrors `columnName`).
3. **Cross-package references.** Within a package: short name. To the kernel:
qualified `sys.x`. Cross-app: forbidden (D4). This is the one place a
qualified form appears, and it is a single deterministic rule (like an
`import`), not an arbitrary second style.
qualified `sys.x`. To an app the manifest declares in `dependencies`:
qualified (D4). Any other app: forbidden (D4). A qualified form appears
only at a cross-boundary reference, and it is a single deterministic rule
(like an `import` whose targets are the declared dependencies), not an
arbitrary second style.
4. **Diagnostics.** Driver errors/logs will show `todo_task`; this is read-only
and not authored, so it does not reintroduce ambiguity (cosmetic mapping only).

Expand Down
66 changes: 66 additions & 0 deletions docs/adr/0130-release-artifact-as-co-ownership-boundary.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@
([#14512](https://github.com/objectstack-ai/objectstack/issues/14512)): a multi-package artifact
carries its metadata **once**, in `packages[]` — the flattened copy is no longer emitted beside
it. D4's read-both rule and D7 are unchanged, so every artifact already on disk still loads.
**D8's deferral end point amended by the 2026-10-25 addendum**
([ADR-0141](./0141-inter-app-dependency-declared-checked-before-install.md) D5,
[#25403](https://github.com/objectstack-ai/objectstack/issues/25403)): a package that declares
`dependencies` on an installed package and shares its namespace is that package's co-owner at the
install gate, across artifacts (and its declared siblings' — ADR-0141 D5), so the owner field is deferred until **marketplace publishing**
rather than until a module's first standalone artifact. The same addendum records that the
2026-09-02 addendum's #14488 line is stale. D1–D7 are unchanged.
**Deciders**: ObjectStack maintainer, 2026-09-01, live PM chat, verbatim and untranslated:
「立 ADR 起草卡派发」and 「14122 作为epic 任务集中跟踪」— approving the proposal in
[#14122](https://github.com/objectstack-ai/objectstack/issues/14122) into the ADR-drafting lane
Expand Down Expand Up @@ -743,3 +750,62 @@ fallback bolted onto the emitter.

Every one of those shapes stays readable by D4's read-both rule, and none is refused: a
composition that was legal before this change stays legal.

---

## Addendum (2026-10-25, #25403) — D8's deferral ends at marketplace publishing; the #14488 line is stale

**Provenance.** Two maintainer rulings on
[#25403](https://github.com/objectstack-ai/objectstack/issues/25403), recorded by
[ADR-0141](./0141-inter-app-dependency-declared-checked-before-install.md): letter B′
([`6369704387`](https://github.com/objectstack-ai/objectstack/issues/25403#issuecomment-6369704387),
「同意」 2026-10-23T16:28:30Z — an app may depend on another installed app; one pre-install check;
the rest deferred by name) and letter A
([`6377678758`](https://github.com/objectstack-ai/objectstack/issues/25403#issuecomment-6377678758),
「同意 #8」 2026-10-24T00:40:55Z — 「声明依赖即命名空间共有,单向,D3 对象名唯一检查跨发布物同行」). ⛔ This
section edits no text above: D1–D8, §1–§8 and both earlier addenda read as written; the two points
below say how two of their sentences now resolve.

### D8's deferral now ends at marketplace publishing

D6 and D8 above say the owner field becomes necessary *"the day a module ships as its own
artifact"*. ADR-0141 D5 answers that day without the field: a package that declares `dependencies`
on an installed package X and uses X's namespace is X's co-owner at the install gate — one-way (X
declares nothing and gains nothing), read from either end at registration, direct declarations
only, plus siblings under one declared parent that holds the namespace, as ADR-0141 D5 defines —
and D3's per-object-name uniqueness check spans those co-owners across artifacts exactly as
it spans co-owners inside one artifact, because the registry reads one co-owner set for both halves
of the pair. Inside one tenant the proof is the declaration plus the administrator's install act;
no manifest field is added.

So D8's deferral now ends at **marketplace publishing**: when a module is published for other
tenants and must assert *"I have the same publisher as HotCRM"* across vendors, the owner /
publisher field lands, aligned to the `publisherOrgId` reservation of ADR-0048's 2026-08-08
addendum (still Proposed; its A.5 precondition is the opening of third-party publishing), as D8
already requires. That field is the later superset of ADR-0141 D5 and needs no undo of it. D6's
cost statement stands for modules inside one artifact; a module that becomes its own artifact
trades that cost for the separate-version cost ADR-0141's Consequences state, bounded by the
declared range and judged once, at install.

### The #14488 line is stale

The 2026-09-02 addendum's *What was NOT decided* section lists
[#14488](https://github.com/objectstack-ai/objectstack/issues/14488) (per-package composition of
grants) as *"filed, not decided"*. It is decided: #14488 was closed `not_planned` on 2026-09-12 by
ruling [`5645322259`](https://github.com/objectstack-ai/objectstack/issues/14488#issuecomment-5645322259)
— an artifact installs whole, the app package's permission sets cover every module's objects,
nothing composes grants into another package's set, and a module carved out as its own artifact
ships its own permission sets under ADR-0086 D4; the card is ⛔ not reopened for that case. The three
inherited inputs listed under that line remain accurate as history; none is open work.

One sentence of that ruling is read beside B′ here, so a reader of both records is not left to
reconcile them: its premise 「目前的要求是发布物是完整安装的,没有设计单独安装其中 一个模块的需求。」 has
two halves. The first — an artifact installs whole — is unchanged: D2 above stands, and a module
shipped on its own under ADR-0141 is another artifact, not a partial install of the first. The
second — that no requirement to install one module alone existed — names the need the maintainer's
B′ ruling of 2026-10-23 (「而每个包可以独立安装」) now states, met by another artifact: the path the
ruling itself named for that case, a module carved out as its own artifact shipping its own
permission sets under ADR-0086 D4, never a partial install. The ruling's disposition is unchanged:
a standalone module's permission sets are the module author's work, not a platform blocker
(ADR-0141 D4, D6). What record-level access across a dependency means is ADR-0141 D4's open
section, decided with the first consumer.
Loading
Loading