Skip to content

Commit d4eed33

Browse files
committed
ADR-0006 v4: drop dev-workspace Project
Implement ADR-0006 v4: remove the dev-workspace `Project` concept and consolidate deploy/versioning on the Package model. Deleted `packages/spec/src/cloud/project.zod.ts` and removed its export; trimmed Project-related tests from `packages/spec/src/cloud/environment.test.ts`; updated `environment.zod.ts` docstring to reference v4. Marked `sys_environment_revision` as `@deprecated transitional` (kept for compatibility) and added explanatory comments/export in `packages/services/service-tenant/src/objects/index.ts`. Added the new ADR document `docs/adr/0006-project-environment-split.v4.md` and updated the changelog and v3 ADR header to reflect v4 superseding v3.
1 parent 2f115ab commit d4eed33

9 files changed

Lines changed: 316 additions & 398 deletions

File tree

CHANGELOG.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,43 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Changed — ADR-0006 v4: drop dev-workspace Project, unify deploy on Package
11+
12+
Removed the dev-workspace `sys_project` / `sys_project_branch` /
13+
`sys_project_revision` concept introduced in v3. After scoping Phase 5 we
14+
confirmed every Project responsibility is already covered by
15+
`sys_package` + `sys_package_version` + `sys_package_installation`
16+
(ADR-0003), so maintaining a parallel tree would create two competing
17+
version-management spines.
18+
19+
CLI `objectstack publish` will be rewired to create a `sys_package_version`
20+
and upsert a `sys_package_installation` row (Phase B of v4). The local dev
21+
workspace is now just "local files + git" — no server-side row.
22+
23+
**Spec / schema side**
24+
- Deleted `packages/spec/src/cloud/project.zod.ts` and dropped its export
25+
from `packages/spec/src/cloud/index.ts`. `ProjectSchema`,
26+
`ProjectBranchSchema`, `ProjectRevisionSchema` and their helpers are no
27+
longer exported.
28+
- Trimmed the corresponding `describe` blocks from
29+
`packages/spec/src/cloud/environment.test.ts`.
30+
- `packages/spec/src/cloud/environment.zod.ts` docstring updated to point
31+
at ADR-0006 v4 and stop referencing the deleted `project.zod.ts`.
32+
- `packages/services/service-tenant/src/objects/sys-environment-revision.object.ts`
33+
marked `@deprecated transitional`; the file remains so the existing CLI
34+
publish HTTP path keeps compiling, and the index re-exports it with a
35+
comment explaining it will be removed in Phase D.
36+
- `packages/services/service-tenant/src/objects/sys-project-revision.object.ts`
37+
and `sys-project-branch.object.ts` (dormant Phase 5 placeholders) were
38+
previously deleted in the same cleanup pass.
39+
40+
**Docs**
41+
- Added `docs/adr/0006-project-environment-split.v4.md` describing the
42+
unified Package model and the four-phase rollout (A protocol cleanup
43+
now → B CLI rewire → C command split → D table removal).
44+
- Marked v3 (`docs/adr/0006-project-environment-split.md`) as
45+
*Superseded by v4*.
46+
1047
### Added — M10.34 first-class custom action: "Invite User" on `sys_user`
1148

1249
`sys_user` is `managedBy: 'better-auth'`, so generic CRUD is correctly suppressed (no New/Edit/Delete) — but Setup admins still need a way to add a user. Instead of routing them to a soon-to-be-deprecated organization-members page, we declare a proper schema action that opens a real inline modal and POSTs to the better-auth invite endpoint.

docs/adr/0006-project-environment-split.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
# ADR-0006: Environment & Project as Independent Siblings — v3
22

3-
**Status**: Accepted (v3supersedes v2)
3+
**Status**: Superseded by v4 (`0006-project-environment-split.v4.md`)2026-05-20
44
**Date**: 2026-05-20 (v3)
55
**Deciders**: ObjectStack Protocol Architects
66
**Supersedes**: v1 (strict tree), v2 (siblings + sys_deployment join)
7+
**Superseded by**: v4 — drops the dev-workspace Project concept entirely; user code unifies on `sys_package` / `sys_package_version` / `sys_package_installation`.
78
**Builds on**: ADR-0002 (Environment-Per-Database Isolation), ADR-0003 (Package as First-Class Citizen)
89
**Consumers**: `@objectstack/service-tenant`, `@objectstack/service-cloud`, `@objectstack/spec/cloud`, `apps/cloud`, the Console `cloud_control` app
910

Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
# ADR-0006: Environment & Project — v4 (drop dev-workspace Project, unify on Package)
2+
3+
**Status**: Accepted (v4 — supersedes v3)
4+
**Date**: 2026-05-20 (v4)
5+
**Deciders**: ObjectStack Protocol Architects
6+
**Supersedes**: v1 (strict tree), v2 (siblings + sys_deployment join), v3 (siblings + deferred dev-workspace `sys_project`)
7+
**Builds on**: ADR-0002 (Environment-Per-Database Isolation), ADR-0003 (Package as First-Class Citizen)
8+
**Consumers**: `@objectstack/service-tenant`, `@objectstack/service-cloud`, `@objectstack/spec/cloud`, `@objectstack/cli`, the Console `cloud_control` app
9+
10+
> **v4 revision note** — v3 kept `sys_project` reserved as a future
11+
> *dev-workspace* concept (Phase 5 Builder UX). When we started drafting the
12+
> Phase 5 schema we realised that **the responsibilities we were about to
13+
> hand to `sys_project` are already covered by `sys_package` /
14+
> `sys_package_version` / `sys_package_installation`** (ADR-0003).
15+
> Introducing a parallel "Project + Branch + Revision" stack would create
16+
> two competing version-management trees in the control plane, exactly the
17+
> drift this ADR family exists to eliminate.
18+
>
19+
> v4 therefore deletes the dev-workspace Project concept entirely. There is
20+
> no `sys_project*` table. There is no `sys_environment_revision` table in
21+
> the target state — both are subsumed by the existing package three-tier
22+
> (identity / immutable version / per-env installation).
23+
>
24+
> The local dev workspace continues to exist — but as **local files +
25+
> git**, not as a server-side table. A `objectstack publish` from any
26+
> working copy is just "create a new `sys_package_version` for the implicit
27+
> package that represents this code base".
28+
29+
---
30+
31+
## Context
32+
33+
After v3 we attempted to scope the Phase 5 dev-workspace schema and
34+
immediately hit overlap:
35+
36+
| Need | v3 plan (`sys_project*`) | What already exists (`sys_package*`) |
37+
|:---|:---|:---|
38+
| Code identity + namespace | `sys_project` (slug, owner_org) | `sys_package` (manifest_id, owner_org, visibility) |
39+
| Immutable version snapshots | `sys_project_revision` (commit_id, checksum, storage_key) | `sys_package_version` (semver, checksum, storage_key, status) |
40+
| Per-env deployment | (would have needed `sys_deployment`) | `sys_package_installation` (env × package × version) |
41+
| Cross-env promotion | (would have needed a join) | `INSERT INTO sys_package_installation … SELECT … FROM staging-env` |
42+
| Marketplace distribution | n/a | `visibility = 'marketplace'`, `is_starter` |
43+
| Dependency declaration | n/a | `sys_package_version.manifest_json` deps |
44+
45+
Every Project responsibility maps 1:1 onto an existing Package row. The
46+
only "missing" capability — git-style **branches** (parallel main / staging
47+
/ feature heads) — can be expressed today with semver prereleases
48+
(`1.2.3-staging.4`) and elevated to a real `sys_package_channel` mechanism
49+
later when CI demands it.
50+
51+
Meanwhile the CLI publish path currently writes a **third** revision table
52+
(`sys_environment_revision`, introduced transitionally during v3 cleanup).
53+
That table holds nothing `sys_package_version` couldn't hold, but it
54+
locks the runtime to a single-package-per-env assumption and prevents
55+
Marketplace and user code from sharing one mental model.
56+
57+
The platform is still pre-launch; the same one-shot-wipe window v3 used
58+
remains open.
59+
60+
---
61+
62+
## Decision
63+
64+
1. **Drop the dev-workspace `sys_project` concept.** There is no Project
65+
table, no Project Branch table, no Project Revision table in the
66+
target state. The local working copy on a developer's machine is the
67+
"workspace" — versioned by git, not by a control-plane row.
68+
69+
2. **All code distribution flows through Package.** Whether the source is
70+
a user-authored repo, a starter template, or a third-party marketplace
71+
submission, it ends up as a `sys_package` (identity), one or more
72+
`sys_package_version` rows (immutable snapshots), and zero-or-more
73+
`sys_package_installation` rows (per-env activation).
74+
75+
3. **`sys_environment_revision` is transitional.** Today the CLI publish
76+
path writes this table to keep things compiling. It will be removed
77+
once the CLI publish path is rewritten to call the package version
78+
create + installation upsert endpoints (Phase B below).
79+
80+
4. **Persona model collapses to one path.** "Consumer" and "Builder"
81+
personas in v3 both ultimately install a `sys_package_version` into a
82+
`sys_environment`. The difference is *which package* they install
83+
(marketplace vs. their own), not which schema they touch.
84+
85+
```
86+
Organization (account root — billing, members, SSO realm)
87+
├── Environment (1..N — runtime container)
88+
│ ├── hostname, database_url, plan, quota, status
89+
│ └── installations → sys_package_installation
90+
│ (env × package × version)
91+
92+
└── Package (0..N — code identity, Marketplace or private)
93+
├── manifest_id, owner_org, visibility, is_starter
94+
└── versions → sys_package_version
95+
(immutable artifact snapshots, semver)
96+
```
97+
98+
**No FK between Environment and Package.** They meet only at
99+
`sys_package_installation`.
100+
101+
### Two flows, one schema
102+
103+
| Flow | What the user does | What writes to the DB |
104+
|:---|:---|:---|
105+
| **Install from Marketplace** | Browse → pick package → choose env | `INSERT sys_package_installation (env, package, version)` |
106+
| **`objectstack publish` (CLI)** | Local code → `build``publish` | (a) ensure `sys_package` for this code base; (b) `INSERT sys_package_version` with new artifact; (c) upsert `sys_package_installation` pointing the env at the new version |
107+
| **Promote staging → prod** | `objectstack promote --from staging --to prod` | `INSERT sys_package_installation` for prod using staging's `package_version_id` (zero re-upload) |
108+
| **Rollback** | `objectstack rollback --env prod --to <ver>` | `UPDATE sys_package_installation … SET package_version_id = <old>` |
109+
110+
### Implicit `sys_package` for user code
111+
112+
A user's repo is associated with a package the first time `publish` runs
113+
against a given control plane. The package's `manifest_id` defaults to
114+
`local.<org_slug>.<project_slug>` (overridable in `objectstack.config.ts`).
115+
Subsequent publishes from the same repo create new `sys_package_version`
116+
rows under that package.
117+
118+
This means user code, starter templates, and Marketplace apps are
119+
indistinguishable to the runtime — they all resolve to a
120+
`sys_package_installation` row at load time.
121+
122+
### Branching strategy (interim)
123+
124+
Today: encode the channel in the semver prerelease tag.
125+
126+
| Channel | Example version | Resolves on env via |
127+
|:---|:---|:---|
128+
| stable | `1.4.0` | `package_version_id` pin |
129+
| staging | `1.4.0-staging.7` | installation upgrade |
130+
| PR preview | `1.4.0-pr.123.2` | ephemeral env + installation |
131+
132+
Later: introduce `sys_package_channel` (`{package_id, name, head_version_id}`)
133+
plus `sys_package_installation.tracking` (`pinned` | `channel_head`) to
134+
get CI-style "always run staging branch HEAD" without a Project table.
135+
136+
### Hostname routing
137+
138+
Unchanged from v3. The Cloudflare Worker resolves
139+
`<slug>.objectos.app → sys_environment.id`. Runtime loads each
140+
installed package's current version via `sys_package_installation`.
141+
142+
### Console UX
143+
144+
Unchanged surface area, simpler mental model:
145+
146+
```
147+
Environments ← runtime targets
148+
Packages ← code you own + Marketplace browser (one tab, filtered)
149+
Members
150+
Billing
151+
```
152+
153+
There is no separate "Projects" tab. A power user inspecting "what's
154+
installed where" goes through Packages → version history → installations.
155+
156+
---
157+
158+
## Consequences
159+
160+
### Positive
161+
162+
1. **One version-management spine.** Marketplace apps, starter templates,
163+
and user code share the same three tables. Operators learn one model.
164+
2. **Smaller schema.** No `sys_project`, no `sys_project_branch`, no
165+
`sys_project_revision`, no `sys_environment_revision`, no
166+
`sys_deployment`. The control plane keeps `sys_environment` +
167+
`sys_package*` and nothing else for the deploy story.
168+
3. **Promote / rollback are SQL on `sys_package_installation`.** Same
169+
property v3 advertised, now also true for user code.
170+
4. **CLI publish and Marketplace install converge.** A single set of
171+
permissions, audit events, and Studio screens covers both paths.
172+
5. **Dev workspace = local files + git.** We don't compete with git for
173+
branch / revision UX in the control plane.
174+
175+
### Negative / Costs
176+
177+
1. **Two paths today don't converge yet.** The CLI still writes
178+
`sys_environment_revision`; until Phase B lands, "what version is
179+
running in prod" requires looking at two tables. Transitional.
180+
2. **No server-side branches today.** Teams that want CI-driven
181+
`staging``prod` channels must encode it in semver prereleases
182+
until `sys_package_channel` ships.
183+
3. **`@objectstack/spec/cloud` API surface change.** `ProjectSchema`,
184+
`ProjectBranchSchema`, `ProjectRevisionSchema` are removed. No
185+
downstream consumers in production yet.
186+
187+
### Neutral
188+
189+
1. `sys_package_version.published_from_project_id` (introduced in v3) is
190+
removed as a field — provenance lives in `sys_package_version.metadata`
191+
if needed.
192+
2. `ProjectArtifactSchema` (`packages/spec/src/cloud/project-artifact.zod.ts`)
193+
is the envelope returned by `GET /cloud/projects/:id/artifact`. The
194+
route name is kept for BC; the response shape continues to wrap the
195+
compiled `ObjectStackDefinitionSchema`. The "Project" in the schema
196+
name is historical and will be renamed to `EnvironmentArtifact` in a
197+
follow-up.
198+
199+
---
200+
201+
## Phasing
202+
203+
| Phase | Scope | Status |
204+
|:---|:---|:---|
205+
| **A — Drop Project from the protocol** | Remove `packages/spec/src/cloud/project.zod.ts`; update `index.ts`; trim Project tests from `environment.test.ts`; mark `sys_environment_revision` as `@deprecated transitional` | ✅ This commit |
206+
| **B — Rewire CLI publish onto Package** | `objectstack publish` resolves implicit `sys_package`; calls `POST /cloud/packages/:id/versions`; upserts `sys_package_installation`. Old `/cloud/projects/:envId/metadata` becomes a thin BC shim that internally walks the new path. | Next |
207+
| **C — Split CLI commands** | `objectstack push` (version only) + `objectstack deploy` (installation upsert) + `objectstack promote` + `objectstack rollback`. `publish` stays as a `push && deploy` alias. | Next+1 |
208+
| **D — Remove transitional revision table** | After Phase B is shipping and verified: drop `sys_environment_revision` schema, delete `_DEPRECATED` route handlers, wipe table from any seeded control planes. | After C |
209+
| **E — `sys_package_channel` (optional)** | Only if real CI need surfaces. Adds named channels + tracking mode to installations. | Deferred |
210+
211+
There is no "Phase 5 Builder UX" anymore — Builder is just "you own
212+
private packages now", and the existing Packages UI covers it.
213+
214+
---
215+
216+
## Migration
217+
218+
Pre-launch: drop tables, rebuild. Same one-shot wipe v3 used.
219+
220+
Post-launch (forward-looking, if v4 lands after launch):
221+
- Read each `sys_environment_revision` row.
222+
- Ensure an implicit `sys_package` exists for the env's owner org.
223+
- For each revision: insert `sys_package_version` (semver = `0.<idx>.0`,
224+
storage_key copied verbatim, checksum copied verbatim).
225+
- Insert `sys_package_installation` for the env's *current* revision.
226+
- Drop `sys_environment_revision`.
227+
228+
The script lives in `@objectstack/service-tenant`'s migration folder
229+
when needed.
230+
231+
---
232+
233+
## Open questions
234+
235+
1. **`manifest_id` default for local code.** `local.<org_slug>.<project_slug>`
236+
collides if two projects share a slug. Resolution: include a 6-hex
237+
suffix on first publish, persist it in `objectstack.config.ts`.
238+
2. **Visibility default for CLI-created packages.** `private` (owner org
239+
only) — consistent with `sys_package.visibility` default. Explicit
240+
`objectstack package publish --marketplace` flow to escalate.
241+
3. **Multi-package envs in Studio.** The current "Environment detail" page
242+
assumes one artifact. Phase B+ updates it to list installations and
243+
show per-package versions.
244+
245+
---
246+
247+
## References
248+
249+
- ADR-0002 — Environment-Per-Database Isolation
250+
- ADR-0003 — Package as First-Class Citizen
251+
- v3 (archived as the immediate predecessor): `0006-project-environment-split.md` (this file's prior version, retained in git history)
252+
- Power Platform: Solution → Environment model (same shape, different names)
253+
- Salesforce: Unlocked Package → Org model (no "Project" table either)
254+
- npm + lockfile: identity + immutable version + installation pointer

packages/services/service-tenant/src/objects/index.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,12 @@
77
export * from './sys-environment.object.js';
88
export * from './sys-environment-member.object.js';
99
export * from './sys-environment-credential.object.js';
10+
// ADR-0006 v4: dev-workspace `sys_project*` concept dropped. User code
11+
// is now modelled as an implicit `sys_package` per org, and version
12+
// management goes through sys_package_version + sys_package_installation
13+
// (same path Marketplace uses). The transitional sys_environment_revision
14+
// schema below will be removed once the CLI publish path is rewired.
1015
export * from './sys-environment-revision.object.js';
11-
// Note: dev-workspace `sys_project` / `sys_project_branch` schemas are NOT
12-
// registered here. The future Phase 5 dev-workspace concept will introduce
13-
// them under a separate package; this directory only ships runtime
14-
// Environment metadata.
1516
export * from './sys-app.object.js';
1617
export * from './sys-package.object.js';
1718
export * from './sys-package-version.object.js';

packages/services/service-tenant/src/objects/sys-environment-revision.object.ts

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,22 @@
33
import { ObjectSchema, Field } from '@objectstack/spec/data';
44

55
/**
6-
* sys_environment_revision — Per-Environment Artifact Revision History
6+
* sys_environment_revision — **TRANSITIONAL** Per-Environment Deploy Log
77
*
8-
* One row per `objectstack publish` (a.k.a. CLI deploy). Each row records a
9-
* content-addressable pointer to the compiled artifact stored in
10-
* IStorageService (S3, R2, local fs, …) plus enough provenance to support
11-
* rollback, audit, and "preview at commit" UX in Studio.
8+
* @deprecated Transitional. ADR-0006 v4 dropped the standalone "deploy
9+
* revision" concept: a CLI `publish` should produce a `sys_package_version`
10+
* row (immutable artifact snapshot) and a `sys_package_installation` upsert
11+
* (env × package × version pointer). The same three-table model already
12+
* powers Marketplace installs — there is no reason to maintain a parallel
13+
* revision table for "user code".
1214
*
13-
* Per ADR-0006 v3, the runtime container is `sys_environment`, so the
14-
* deployment history lives next to it as `sys_environment_revision`. The
15-
* dormant `sys_project_revision` schema from the rename was a placeholder
16-
* for the future dev-workspace Project (Phase 5) and is **not** the
17-
* deployment table — that one is this row.
15+
* This row remains so the existing `objectstack publish` HTTP path keeps
16+
* working until the CLI rewrite lands. New callers MUST NOT bind to this
17+
* shape. See ADR-0006 v4 → "Phase B: Unify deploy on sys_package_version".
1818
*
19-
* Lifecycle:
20-
* - `is_current = true` for at most one row per environment. Activating a
21-
* historical revision flips the flag (atomic UPDATE in the cloud-
22-
* artifact plugin's POST /activate handler).
23-
* - Rows are immutable apart from `is_current`, `is_branch_head`,
24-
* `branch` (during rename), and `note`.
25-
* - `storage_key` is content-addressable
26-
* (`artifacts/orgs/<org_id>/projects/<env_id>/<commit_id>.json` by
27-
* default), so re-publishing identical content is a no-op upload.
19+
* Current behaviour: one row per `objectstack publish`. Each row records a
20+
* content-addressable pointer to the compiled artifact stored in
21+
* IStorageService (S3, R2, local fs, …).
2822
*
2923
* Lives in the control plane only.
3024
*

0 commit comments

Comments
 (0)