Skip to content

Commit 2f115ab

Browse files
committed
Rename project concepts to environment (revisions, APIs)
Rename/rework control-plane and spec artifacts from "project" to "environment" for runtime deployment semantics. Key changes: - Replace sys_project_revision with sys_environment_revision (object schema, DB usages, indexes, and migrations) and update all cloud/service-cloud route handlers to read/update/delete the new table. - Update cloud artifact helpers, multi-project plugin, branch routes, public/cloud/storage routes to use environment IDs/fields and storage_key semantics. - Rename spec artifacts: project-package.zod -> environment-package.zod and introduce environment.zod; adjust related zod schemas and request/response types to refer to environments. - Update tenant objects: rename sys-project-revision.object -> sys-environment-revision.object, remove sys-project-branch.object, and adjust objects index and comments. - Change provisioning service, tests and APIs to use Environment types/fields (projectId -> environmentId, ProvisionEnvironmentRequest/Response, and return shapes), and update related tests. - Update UI components to import EnvironmentType where previously ProjectType/Status were used. Rationale: align deployment history and package installation with ADR-0006 v3 where runtime container is sys_environment (environments are the correct control-plane entity for revisions and installations).
1 parent 535c868 commit 2f115ab

21 files changed

Lines changed: 869 additions & 644 deletions

apps/studio/src/components/production-guard.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ import {
4141
type ReactNode,
4242
} from 'react';
4343
import { AlertTriangle } from 'lucide-react';
44-
import type { ProjectType } from '@objectstack/spec/cloud';
44+
import type { EnvironmentType as ProjectType } from '@objectstack/spec/cloud';
4545
import {
4646
Dialog,
4747
DialogContent,

apps/studio/src/components/project-badge.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
*/
1616

1717
import { cn } from '@/lib/utils';
18-
import type { ProjectType } from '@objectstack/spec/cloud';
18+
import type { EnvironmentType as ProjectType } from '@objectstack/spec/cloud';
1919

2020
const VARIANT: Record<ProjectType, string> = {
2121
production:

apps/studio/src/components/project-status-badge.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
import { Loader2, AlertTriangle, CheckCircle2, PauseCircle, Archive, MoveRight } from 'lucide-react';
1717
import { cn } from '@/lib/utils';
18-
import type { ProjectStatus } from '@objectstack/spec/cloud';
18+
import type { EnvironmentStatus as ProjectStatus } from '@objectstack/spec/cloud';
1919

2020
const VARIANT: Record<ProjectStatus, string> = {
2121
provisioning:

packages/services/service-cloud/src/cloud-artifact-api-plugin.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* registration to the `routes/cloud.ts` and `routes/public.ts` modules.
88
*
99
* P0 — Pluggable storage via {@link IStorageService} (fallback to local FS).
10-
* P1 — Version history via `sys_project_revision`, commit-aware GET, rollback.
10+
* P1 — Version history via `sys_environment_revision`, commit-aware GET, rollback.
1111
*
1212
* Routes registered:
1313
* GET /cloud/resolve-hostname?host=...

packages/services/service-cloud/src/cloud-artifact-helpers.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -146,12 +146,12 @@ export async function readProjectCredentials(driver: IDataDriver, projectId: str
146146
// ---------------------------------------------------------------------------
147147
// Publish helper — shared by POST /cloud/projects/:id/metadata and the
148148
// MultiProjectPlugin template seeder. Uploads the artifact bundle to the
149-
// configured storage adapter and inserts/refreshes a sys_project_revision
149+
// configured storage adapter and inserts/refreshes a sys_environment_revision
150150
// row so the next GET /cloud/projects/:id/artifact resolves it.
151151
// ---------------------------------------------------------------------------
152152

153153
export interface PublishProjectRevisionParams {
154-
/** Control-plane data driver (sys_project / sys_project_revision). */
154+
/** Control-plane data driver (sys_environment / sys_environment_revision). */
155155
driver: IDataDriver;
156156
/** Storage adapter (R2, S3, local FS) — must implement upload/exists. */
157157
storage: { upload: (key: string, data: Buffer) => Promise<void>; exists: (key: string) => Promise<boolean> };
@@ -204,34 +204,34 @@ export async function publishProjectRevision(
204204

205205
let created = false;
206206
let revisionId: string;
207-
const existing = await (driver.findOne as any)('sys_project_revision_DEPRECATED', {
207+
const existing = await (driver.findOne as any)('sys_environment_revision', {
208208
where: { environment_id: project.id, commit_id: commitId },
209209
});
210210
if (existing) {
211211
revisionId = existing.id;
212212
if (!existing.is_current) {
213213
try {
214-
const oldCurrent = await (driver.findOne as any)('sys_project_revision_DEPRECATED', {
214+
const oldCurrent = await (driver.findOne as any)('sys_environment_revision', {
215215
where: { environment_id: project.id, is_current: true },
216216
});
217217
if (oldCurrent && oldCurrent.id !== existing.id) {
218-
await (driver.update as any)('sys_project_revision_DEPRECATED', oldCurrent.id, { is_current: false });
218+
await (driver.update as any)('sys_environment_revision', oldCurrent.id, { is_current: false });
219219
}
220220
} catch { /* table may not exist yet */ }
221-
await (driver.update as any)('sys_project_revision_DEPRECATED', existing.id, { is_current: true });
221+
await (driver.update as any)('sys_environment_revision', existing.id, { is_current: true });
222222
}
223223
} else {
224224
try {
225-
const oldCurrent = await (driver.findOne as any)('sys_project_revision_DEPRECATED', {
225+
const oldCurrent = await (driver.findOne as any)('sys_environment_revision', {
226226
where: { environment_id: project.id, is_current: true },
227227
});
228228
if (oldCurrent) {
229-
await (driver.update as any)('sys_project_revision_DEPRECATED', oldCurrent.id, { is_current: false });
229+
await (driver.update as any)('sys_environment_revision', oldCurrent.id, { is_current: false });
230230
}
231231
} catch { /* ok */ }
232232
const { randomUUID } = await import('node:crypto');
233233
revisionId = randomUUID();
234-
await (driver.create as any)('sys_project_revision_DEPRECATED', {
234+
await (driver.create as any)('sys_environment_revision', {
235235
id: revisionId,
236236
project_id: project.id,
237237
commit_id: commitId,

packages/services/service-cloud/src/multi-project-plugin.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ export interface MultiProjectPluginConfig {
5353
ttlMs?: number;
5454
/** Direct storage adapter for artifact publishing. Bypasses kernel.getService('file-storage'). */
5555
storage?: any;
56-
/** Adapter name (e.g. 's3', 'local') — used when persisting sys_project_revision rows. */
56+
/** Adapter name (e.g. 's3', 'local') — used when persisting sys_environment_revision rows. */
5757
storageAdapterName?: string;
5858
}
5959

@@ -273,7 +273,7 @@ function createTemplateSeeder(
273273
try { await flushable.flush(); } catch { /* best effort */ }
274274
}
275275

276-
// Publish the seeded bundle as a sys_project_revision so the
276+
// Publish the seeded bundle as a sys_environment_revision so the
277277
// artifact API serves it to remote runtimes (objectos in cloud
278278
// mode reads `GET /cloud/projects/:id/artifact`). Without this
279279
// step, the seeded data only lives in this project's metadata

packages/services/service-cloud/src/routes/branches.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
22

33
/**
4-
* Branch endpoints — git-style logical branches over `sys_project_revision`.
4+
* Branch endpoints — git-style logical branches over `sys_environment_revision`.
55
*
66
* GET /cloud/projects/:id/branches
77
* POST /cloud/projects/:id/branches/:name/rename
@@ -82,16 +82,16 @@ export async function setBranchHead(
8282
revisionId: string,
8383
): Promise<void> {
8484
try {
85-
const heads = (await driver.find('sys_project_revision_DEPRECATED', {
85+
const heads = (await driver.find('sys_environment_revision', {
8686
where: { environment_id: projectId, branch, is_branch_head: true },
8787
limit: 100,
8888
})) as BranchHeadRow[];
8989
for (const h of heads) {
9090
if (h.id !== revisionId) {
91-
await driver.update('sys_project_revision_DEPRECATED', h.id, { is_branch_head: false });
91+
await driver.update('sys_environment_revision', h.id, { is_branch_head: false });
9292
}
9393
}
94-
await driver.update('sys_project_revision_DEPRECATED', revisionId, {
94+
await driver.update('sys_environment_revision', revisionId, {
9595
branch,
9696
is_branch_head: true,
9797
});
@@ -175,7 +175,7 @@ export function registerBranchRoutes(server: IHttpServer, deps: RouteDeps): void
175175
if (!driver) return controlPlaneUnavailable(res);
176176

177177
try {
178-
const rows = (await (driver.find as any)('sys_project_revision_DEPRECATED', {
178+
const rows = (await (driver.find as any)('sys_environment_revision', {
179179
where: { environment_id: projectId },
180180
orderBy: [{ field: 'published_at', direction: 'desc' }],
181181
limit: 5000,
@@ -215,20 +215,20 @@ export function registerBranchRoutes(server: IHttpServer, deps: RouteDeps): void
215215
if (!driver) return controlPlaneUnavailable(res);
216216

217217
try {
218-
const collisions = (await (driver.find as any)('sys_project_revision_DEPRECATED', {
218+
const collisions = (await (driver.find as any)('sys_environment_revision', {
219219
where: { environment_id: projectId, branch: normalizedNew },
220220
limit: 1,
221221
})) as any[];
222222
if (Array.isArray(collisions) && collisions.length > 0) {
223223
return res.status(409).json(fail(`Branch '${normalizedNew}' already exists`, 409));
224224
}
225225

226-
const rows = (await (driver.find as any)('sys_project_revision_DEPRECATED', {
226+
const rows = (await (driver.find as any)('sys_environment_revision', {
227227
where: { environment_id: projectId, branch: oldName },
228228
limit: 5000,
229229
})) as BranchHeadRow[];
230230
for (const r of rows) {
231-
await (driver.update as any)('sys_project_revision_DEPRECATED', r.id, { branch: normalizedNew });
231+
await (driver.update as any)('sys_environment_revision', r.id, { branch: normalizedNew });
232232
}
233233
return res.json(ok({ projectId, from: oldName, to: normalizedNew, renamed: rows.length }));
234234
} catch (err: any) {
@@ -259,7 +259,7 @@ export function registerBranchRoutes(server: IHttpServer, deps: RouteDeps): void
259259
if (!driver) return controlPlaneUnavailable(res);
260260

261261
try {
262-
const rows = (await (driver.find as any)('sys_project_revision_DEPRECATED', {
262+
const rows = (await (driver.find as any)('sys_environment_revision', {
263263
where: { environment_id: projectId, branch: name },
264264
limit: 5000,
265265
})) as BranchHeadRow[];
@@ -275,7 +275,7 @@ export function registerBranchRoutes(server: IHttpServer, deps: RouteDeps): void
275275
}
276276
for (const r of rows) {
277277
if (r.is_branch_head) {
278-
await (driver.update as any)('sys_project_revision_DEPRECATED', r.id, { is_branch_head: false });
278+
await (driver.update as any)('sys_environment_revision', r.id, { is_branch_head: false });
279279
}
280280
}
281281
return res.json(ok({ projectId, branch: name, demoted: rows.filter((r) => r.is_branch_head).length, totalRevisions: rows.length }));

packages/services/service-cloud/src/routes/cloud.ts

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -134,12 +134,12 @@ export function registerCloudRoutes(server: IHttpServer, deps: RouteDeps): void
134134
try {
135135
let rev: any = null;
136136
if (requestedCommit) {
137-
rev = await (driver.findOne as any)('sys_project_revision_DEPRECATED', {
137+
rev = await (driver.findOne as any)('sys_environment_revision', {
138138
where: { environment_id: projectId, commit_id: requestedCommit },
139139
});
140140
if (!rev) return res.status(404).json(fail(`Revision '${requestedCommit}' not found for project '${projectId}'`, 404));
141141
} else {
142-
rev = await (driver.findOne as any)('sys_project_revision_DEPRECATED', {
142+
rev = await (driver.findOne as any)('sys_environment_revision', {
143143
where: { environment_id: projectId, is_current: true },
144144
});
145145
}
@@ -307,22 +307,22 @@ export function registerCloudRoutes(server: IHttpServer, deps: RouteDeps): void
307307
let revisionCreated = false;
308308
let revisionId: string | null = null;
309309
try {
310-
const existing = await (driver.findOne as any)('sys_project_revision_DEPRECATED', {
310+
const existing = await (driver.findOne as any)('sys_environment_revision', {
311311
where: { environment_id: projectId, commit_id: commitId },
312312
});
313313

314314
if (!existing) {
315315
try {
316-
const oldCurrent = await (driver.findOne as any)('sys_project_revision_DEPRECATED', {
316+
const oldCurrent = await (driver.findOne as any)('sys_environment_revision', {
317317
where: { environment_id: projectId, is_current: true },
318318
});
319319
if (oldCurrent) {
320-
await (driver.update as any)('sys_project_revision_DEPRECATED', oldCurrent.id, { is_current: false });
320+
await (driver.update as any)('sys_environment_revision', oldCurrent.id, { is_current: false });
321321
}
322322
} catch { /* table may not exist yet */ }
323323

324324
revisionId = randomUUID();
325-
await (driver.create as any)('sys_project_revision_DEPRECATED', {
325+
await (driver.create as any)('sys_environment_revision', {
326326
id: revisionId,
327327
project_id: projectId,
328328
commit_id: commitId,
@@ -344,14 +344,14 @@ export function registerCloudRoutes(server: IHttpServer, deps: RouteDeps): void
344344
// Re-publish same commit: ensure it's current AND that branch head reflects this push
345345
if (!existing.is_current) {
346346
try {
347-
const oldCurrent = await (driver.findOne as any)('sys_project_revision_DEPRECATED', {
347+
const oldCurrent = await (driver.findOne as any)('sys_environment_revision', {
348348
where: { environment_id: projectId, is_current: true },
349349
});
350350
if (oldCurrent && oldCurrent.id !== existing.id) {
351-
await (driver.update as any)('sys_project_revision_DEPRECATED', oldCurrent.id, { is_current: false });
351+
await (driver.update as any)('sys_environment_revision', oldCurrent.id, { is_current: false });
352352
}
353353
} catch { /* ok */ }
354-
await (driver.update as any)('sys_project_revision_DEPRECATED', existing.id, { is_current: true });
354+
await (driver.update as any)('sys_environment_revision', existing.id, { is_current: true });
355355
}
356356
}
357357

@@ -424,7 +424,7 @@ export function registerCloudRoutes(server: IHttpServer, deps: RouteDeps): void
424424
query.where.branch = branchFilter;
425425
}
426426
}
427-
const rows = await (driver.find as any)('sys_project_revision_DEPRECATED', query);
427+
const rows = await (driver.find as any)('sys_environment_revision', query);
428428
const hasMore = rows.length > limit;
429429
const items = hasMore ? rows.slice(0, limit) : rows;
430430
const nextCursor = hasMore ? items[items.length - 1]?.published_at : undefined;
@@ -468,11 +468,11 @@ export function registerCloudRoutes(server: IHttpServer, deps: RouteDeps): void
468468
try {
469469
// Accept full commit id or a 8+ char prefix (matches the
470470
// 12-char display in the Studio recent-revisions list).
471-
let target = await (driver.findOne as any)('sys_project_revision_DEPRECATED', {
471+
let target = await (driver.findOne as any)('sys_environment_revision', {
472472
where: { environment_id: projectId, commit_id: commitId },
473473
});
474474
if (!target && commitId.length >= 8) {
475-
const candidates = await (driver.find as any)('sys_project_revision_DEPRECATED', {
475+
const candidates = await (driver.find as any)('sys_environment_revision', {
476476
where: { environment_id: projectId, commit_id: { $like: `${commitId}%` } },
477477
limit: 2,
478478
});
@@ -484,14 +484,14 @@ export function registerCloudRoutes(server: IHttpServer, deps: RouteDeps): void
484484
}
485485
if (!target) return res.status(404).json(fail(`Revision '${commitId}' not found`, 404));
486486

487-
const oldCurrent = await (driver.findOne as any)('sys_project_revision_DEPRECATED', {
487+
const oldCurrent = await (driver.findOne as any)('sys_environment_revision', {
488488
where: { environment_id: projectId, is_current: true },
489489
});
490490
if (oldCurrent && oldCurrent.id !== target.id) {
491-
await (driver.update as any)('sys_project_revision_DEPRECATED', oldCurrent.id, { is_current: false });
491+
await (driver.update as any)('sys_environment_revision', oldCurrent.id, { is_current: false });
492492
}
493493

494-
await (driver.update as any)('sys_project_revision_DEPRECATED', target.id, { is_current: true });
494+
await (driver.update as any)('sys_environment_revision', target.id, { is_current: true });
495495

496496
const project = await (driver.findOne as any)('sys_environment', { where: { id: projectId } });
497497
if (project) {
@@ -536,7 +536,7 @@ export function registerCloudRoutes(server: IHttpServer, deps: RouteDeps): void
536536
: null;
537537

538538
try {
539-
const all = (await (driver.find as any)('sys_project_revision_DEPRECATED', {
539+
const all = (await (driver.find as any)('sys_environment_revision', {
540540
where: { environment_id: projectId },
541541
orderBy: [{ field: 'published_at', direction: 'desc' }],
542542
limit: 10_000,
@@ -572,7 +572,7 @@ export function registerCloudRoutes(server: IHttpServer, deps: RouteDeps): void
572572
}
573573
}
574574
try {
575-
await (driver.delete as any)('sys_project_revision_DEPRECATED', r.id);
575+
await (driver.delete as any)('sys_environment_revision', r.id);
576576
deletedRows++;
577577
} catch (delErr: any) {
578578
console.warn('[CloudArtifactAPI] Failed to delete revision row', r.id, delErr?.message);

packages/services/service-cloud/src/routes/project-lifecycle.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -190,8 +190,8 @@ export function registerProjectLifecycleRoutes(server: IHttpServer, deps: Packag
190190
// NOT abort the env-create flow.
191191
try {
192192
const baseSecret = (process.env.OS_AUTH_SECRET ?? process.env.AUTH_SECRET ?? '').trim();
193-
const newProjectId = (result.project as AnyRow)?.id;
194-
const newHostname = (result.project as AnyRow)?.hostname;
193+
const newProjectId = (result.environment as AnyRow)?.id;
194+
const newHostname = (result.environment as AnyRow)?.hostname;
195195
if (baseSecret && newProjectId) {
196196
const driver = await getDriver();
197197
if (driver) {
@@ -230,7 +230,7 @@ export function registerProjectLifecycleRoutes(server: IHttpServer, deps: Packag
230230
}
231231

232232
return res.status(201).json(ok({
233-
project: result.project,
233+
project: result.environment,
234234
warnings: result.warnings,
235235
durationMs: result.durationMs,
236236
}));

packages/services/service-cloud/src/routes/public.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ export function registerPublicRoutes(server: IHttpServer, deps: RouteDeps): void
5757
return res.status(404).json(fail('not found', 404));
5858
}
5959

60-
const current = await (driver.findOne as any)('sys_project_revision_DEPRECATED', {
60+
const current = await (driver.findOne as any)('sys_environment_revision', {
6161
where: { environment_id: projectId, is_current: true },
6262
});
6363

@@ -93,11 +93,11 @@ export function registerPublicRoutes(server: IHttpServer, deps: RouteDeps): void
9393
let rev: any = null;
9494
try {
9595
if (requestedCommit) {
96-
rev = await (driver.findOne as any)('sys_project_revision_DEPRECATED', {
96+
rev = await (driver.findOne as any)('sys_environment_revision', {
9797
where: { environment_id: projectId, commit_id: requestedCommit },
9898
});
9999
} else {
100-
rev = await (driver.findOne as any)('sys_project_revision_DEPRECATED', {
100+
rev = await (driver.findOne as any)('sys_environment_revision', {
101101
where: { environment_id: projectId, is_current: true },
102102
});
103103
}
@@ -173,7 +173,7 @@ export function registerPublicRoutes(server: IHttpServer, deps: RouteDeps): void
173173
const limit = Math.min(Math.max(Number(req.query?.limit ?? 20), 1), 100);
174174
let rows: any[] = [];
175175
try {
176-
rows = (await (driver.find as any)('sys_project_revision_DEPRECATED', {
176+
rows = (await (driver.find as any)('sys_environment_revision', {
177177
where: { environment_id: projectId },
178178
orderBy: [{ field: 'published_at', direction: 'desc' }],
179179
limit,

0 commit comments

Comments
 (0)