Skip to content

Commit cfc3fe8

Browse files
committed
refactor(architecture): remove legacy migration paths
Collapse the database to one verified current-schema baseline. Delete historical SQL and obsolete reconciliation contracts. Enforce exact runtime state and keep CI scoped to affected surfaces.
1 parent fd2fc48 commit cfc3fe8

237 files changed

Lines changed: 9234 additions & 97479 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.env.migrate.example

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,3 @@ SUPABASE_MIGRATION_EXPECTED_HOST=db.snqtclnmhcaupqynjyux.supabase.co
66
SUPABASE_MIGRATION_EXPECTED_DATABASE=postgres
77
SUPABASE_MIGRATION_EXPECTED_ROLE=postgres
88
SUPABASE_MIGRATION_EXPECTED_SYSTEM_IDENTIFIER=replace_with_pg_control_system_identifier
9-
10-
# Optional one-time protected migration attestation envelope.
11-
CHEATCODE_MIGRATION_ATTESTATIONS=

.github/workflows/static-checks.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ name: Static Checks
22

33
on:
44
pull_request:
5+
push:
6+
branches:
7+
- main
58

69
permissions:
710
contents: read

AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ The deleted `plan.md` is not authoritative and must not be restored. Base change
234234

235235
Specifically check before changing:
236236
- `pnpm-workspace.yaml` and the lockfile for exact dependency versions
237-
- `packages/db/src/schema`, `packages/db/drizzle`, and `infra/supabase/migrations` for Postgres behavior
237+
- `packages/db/src/schema` and `packages/db/drizzle` for Postgres behavior
238238
- Worker `wrangler.jsonc`, `apps/web/vercel.json`, and `.github/workflows` for deployment topology
239239
- `biome.jsonc` and TypeScript configs for code-quality rules
240240

README.md

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -202,23 +202,21 @@ pnpm deadcode
202202

203203
## Database migrations
204204

205-
`scripts/migrate.ts` owns migration planning and execution. Every mutation
206-
requires `--apply` and an explicit phase; `--phase=all` is read-only.
205+
`scripts/migrate.ts` owns migration planning and execution. The repository keeps
206+
one current-schema baseline plus future forward migrations in the Drizzle
207+
journal; it does not retain the pre-launch migration archive.
207208

208209
```bash
209-
pnpm db:migrate -- --dry-run --phase=all
210-
pnpm db:migrate -- --apply --phase=pre-deploy
211-
pnpm db:migrate -- --apply --phase=post-deploy
212-
pnpm db:migrate -- --apply --phase=release-finalization
210+
pnpm db:migrate -- --dry-run
211+
pnpm db:migrate -- --apply
213212
```
214213

215214
The migration command loads `.env.migrate` on an authorized operator
216215
workstation, validates the administrative connection target and pinned database
217216
identity before applying changes, and accepts protected process environment
218217
values in automation. Migration credentials are never loaded by the app or
219-
bound to a Worker. Apply expand
220-
migrations before code that depends on them, and apply contractions only after
221-
all deployed code is compatible with the contracted schema.
218+
bound to a Worker. The runner verifies the exact source journal and final
219+
production contract through the same pinned administrative session.
222220

223221
Configure the three Worker Hyperdrive bindings with the existing guarded helper:
224222

@@ -260,11 +258,9 @@ deployed last. The independent preview proxy deploys only when its dependency
260258
closure changed. If Cloudflare release metadata is unavailable or inconsistent,
261259
the command safely redeploys the relevant set instead of guessing.
262260

263-
There is no second release orchestrator, compatibility deploy command, or hidden
264-
workspace-reconciliation command. Schema migrations, Worker deployment, and
265-
Vercel deployment are explicit operations; operators must sequence them using
266-
the expand/contract rule above and verify Worker health and the production web
267-
revision before applying destructive migrations.
261+
Schema migrations, Worker deployment, and Vercel deployment are explicit
262+
operations. Verify Worker health and the production web revision whenever a
263+
release moves more than one surface.
268264

269265
Publish a new immutable Daytona snapshot after changing
270266
`infra/containers/sandbox/` by dispatching the protected workflow from `main`:

apps/agent-worker/README.md

Lines changed: 19 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -196,14 +196,15 @@ failed cleanup instead of silently leaking storage. Filesystem operations with a
196196
path remain concurrent across unrelated projects, but arbitrary code, shell execution, and process
197197
launch always take a non-exclusive global lease because path parsing cannot prove their runtime
198198
filesystem scope. Project cleanup fences and drains that lease, terminates every managed and
199-
same-user untracked sandbox process, and only then removes the folder. Account deletion destroys shared
200-
sandbox state once and removes run Durable Objects in bounded pages. The account cleanup
201-
RPC synchronously fences new sandbox work, drains operations that already started, records
202-
final sandbox usage, clears the user's Daytona volume subpath, and deletes every validated
203-
sandbox. A temporary durable tombstone makes an interrupted cleanup resume behind the same
204-
fence. Once external cleanup succeeds, the configured 2026-07-15 Workers compatibility contract
205-
lets one atomic `deleteAll()` remove that tombstone, owner keys, workspace SQLite schema, and
206-
alarm so the object ceases to occupy storage.
199+
same-user untracked sandbox process, and only then removes the folder. Account
200+
deletion destroys shared sandbox state once and removes run Durable Objects in
201+
bounded pages. The account cleanup RPC synchronously fences new sandbox work,
202+
drains operations that already started, records final sandbox usage, clears the
203+
user's Daytona volume subpath, and deletes every validated sandbox. A temporary
204+
durable tombstone makes an interrupted cleanup resume behind the same fence.
205+
Once external cleanup succeeds, one atomic `deleteAll()` removes the tombstone,
206+
owner keys, workspace SQLite schema, and alarm so the object ceases to occupy
207+
storage.
207208

208209
Constructors inspect existing identity and SQLite metadata without materializing an empty store.
209210
An object with no registered owner absorbs late lease/alarm cleanup and rejects every other
@@ -221,38 +222,20 @@ project/thread soft-delete generation and verifies that every requested run belo
221222
scope. The 30-second signature window is therefore safe to retry and cannot authorize stale or
222223
cross-tenant destruction; no shared key or legacy signature fallback exists.
223224

224-
Workspace and sandbox releases use a separate signed internal RPC. For one exact
225-
release SHA, the closed release gate and an in-memory mutation lease reject concurrent
226-
workspace operations. Preparation stops affected processes, collision-checks and renames
227-
Daytona folders, reconciles process and port state, and records only temporary KV evidence for
228-
the canonical folders that existed. Finalization reloads the already-canonical Postgres
229-
inventory and requires the same physical evidence before snapshot work begins. The release
230-
workflow drains all AgentRuns before this phase, so no stale run can recreate a replaced path.
231-
Generic Durable Object reconciliation deliberately runs first: it contracts the permanent
232-
SQLite schema to the project tombstone table and removes the one-time transition and retired-slug
233-
tables; prepare and finalize do not depend on either table. An owner with no materialized sandbox
234-
state uses only the in-memory maintenance lease plus the temporary evidence key, so successful
235-
reconciliation does not leave an empty SQLite store behind.
236-
237-
Finalization also reconciles the user's existing Daytona sandbox to the exact configured
238-
snapshot. Volume-backed replacements mount the same isolated subpath and compare complete tree
239-
digests. The one-time adoption of a local-disk sandbox creates a deterministic archive and copies
240-
it through durable 8 MiB chunks; there is no total workspace-size cap. A candidate never carries
241-
the canonical label while the source does. After digest verification the source is retired, the
242-
candidate receives the full canonical label set, the Durable Object atomically adopts its exact
243-
ID, and only then is the old sandbox deleted. Every boundary is retryable by the temporary
244-
upgrade phase and deterministic candidate identity. Once final verification succeeds, both the
245-
workspace-transition evidence and snapshot-upgrade state are deleted; an ambiguous response can
246-
therefore retry against the canonical physical state without leaving cutover residue. Account
247-
deletion clears the user's shared-volume subpath before deleting all exact owned sandboxes, so
248-
persistent volume data does not outlive the account.
225+
Every ProjectSandbox uses the one configured immutable Daytona snapshot and the
226+
one configured shared workspace volume. Existing sandbox identity is accepted
227+
only when its owner, canonical labels, snapshot, volume, and mount contract all
228+
match. Mismatches fail closed instead of running a hidden migration. New
229+
sandboxes mount the user's isolated volume subpath directly at `/workspace`.
230+
Account deletion clears that subpath before deleting all exactly owned
231+
sandboxes, so persistent volume data does not outlive the account.
249232

250233
Production binds `CHEATCODE_RELEASE_GATE` explicitly. `draining` rejects public
251234
run, sandbox, preview, download, and deletion admission while allowing already
252235
admitted AgentRun Workflow/DO callbacks, sandbox operations, and persistence to
253236
finish. `closed` additionally fences those continuation paths and serves only
254-
`/health` plus the exact signed canonical-workspace reconciliation RPC. Stable
255-
drain proofs run at both gates before DDL.
237+
`/health` plus the signed database-readiness RPC. Stable drain proofs run at
238+
both gates before DDL.
256239

257240
Project ZIP generation and streaming share the exact
258241
`PROJECT_ARCHIVE_MAX_OUTPUT_BYTES` contract from `@cheatcode/types` (640 MiB). The
@@ -276,7 +259,7 @@ pnpm --filter @cheatcode/agent-worker typecheck
276259

277260
- `CHEATCODE_ENVIRONMENT` (`production` in committed Wrangler config; local generated config overrides it)
278261
- `CHEATCODE_RELEASE_SHA` (required for production deployments)
279-
- `CHEATCODE_RELEASE_GATE` (`open` in source; coordinated releases inject `draining` and then `closed` until migration/reconciliation complete)
262+
- `CHEATCODE_RELEASE_GATE` (`open` in source; coordinated releases inject `draining` and then `closed` until migration and database-readiness checks complete)
280263
- `CF_VERSION_METADATA`
281264
- `AGENT_RUN`
282265
- `AGENT_RUN_WORKFLOW`

apps/agent-worker/src/agent-api-system-routes.ts

Lines changed: 0 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,14 @@ import {
33
findGeneratedOutput,
44
getProject,
55
isAgentStateDeletionAuthorized,
6-
loadWorkspaceTransitionOwner,
76
withUserContext,
87
} from "@cheatcode/db";
98
import { resolveWorkerSecret, type WorkerSecret } from "@cheatcode/env";
109
import { APIError, readBoundedRequestText } from "@cheatcode/observability";
1110
import {
1211
InternalAgentStateDeleteBodySchema,
1312
InternalStateDeleteResponseSchema,
14-
InternalWorkspaceReconciliationBodySchema,
15-
InternalWorkspaceReconciliationResponseSchema,
1613
internalUserStateDeletePath,
17-
internalUserWorkspaceReconciliationPath,
1814
OutputIdSchema,
1915
ProjectId,
2016
UserId,
@@ -47,108 +43,11 @@ type AgentContext = Context<{ Bindings: AgentEnv }>;
4743

4844
export function registerAgentSystemHttpRoutes(app: Hono<{ Bindings: AgentEnv }>): void {
4945
app.post("/internal/users/:userId/delete-state", deleteInternalUserState);
50-
app.post("/internal/users/:userId/reconcile-workspaces", reconcileInternalUserWorkspaces);
5146
app.post("/v1/outputs/:outputId/download-url", mintOutputDownloadUrl);
5247
app.get("/v1/outputs/:outputId/download", downloadOutput);
5348
app.post("/v1/projects/:projectId/download", downloadProjectArchive);
5449
}
5550

56-
async function reconcileInternalUserWorkspaces(c: AgentContext): Promise<Response> {
57-
if (c.env.CHEATCODE_RELEASE_GATE !== "closed") {
58-
throw new APIError(
59-
409,
60-
"conflict_state_invalid",
61-
"Workspace reconciliation requires the closed release gate",
62-
{ retriable: false },
63-
);
64-
}
65-
assertAgentInternalHostname(c.req.raw);
66-
assertAgentLifecycleCapability(c.req.raw);
67-
const userId = UserId(GatewayUserIdSchema.parse(c.req.param("userId")));
68-
const rawBody = await readBoundedRequestText(
69-
c.req.raw,
70-
MAX_INTERNAL_MAINTENANCE_BODY_BYTES,
71-
"Internal workspace reconciliation",
72-
);
73-
await verifyAgentLifecycleRequest({
74-
expectedPathname: internalUserWorkspaceReconciliationPath(userId),
75-
rawBody,
76-
request: c.req.raw,
77-
secrets: c.env,
78-
});
79-
const body = InternalWorkspaceReconciliationBodySchema.parse(
80-
parseInternalMaintenanceJson(rawBody),
81-
);
82-
if (c.env.CHEATCODE_RELEASE_SHA !== body.releaseSha) {
83-
throw new APIError(409, "conflict_state_invalid", "Agent release does not match transition", {
84-
details: { actualReleaseSha: c.env.CHEATCODE_RELEASE_SHA ?? null },
85-
retriable: false,
86-
});
87-
}
88-
await assertWorkspaceTransitionInventory(c.env, userId, body);
89-
const sandbox = await sandboxStubForUser(c.env, userId);
90-
const result =
91-
body.phase === "prepare"
92-
? await sandbox.prepareWorkspaceTransition(body)
93-
: await sandbox.finalizeWorkspaceTransition(body);
94-
return c.json(InternalWorkspaceReconciliationResponseSchema.parse(result));
95-
}
96-
97-
async function assertWorkspaceTransitionInventory(
98-
env: AgentEnv,
99-
userId: UserId,
100-
body: z.infer<typeof InternalWorkspaceReconciliationBodySchema>,
101-
): Promise<void> {
102-
const { db, close } = createDb(env.HYPERDRIVE, {
103-
audience: "app_agent",
104-
signingSecret: env.DATABASE_CONTEXT_SIGNING_SECRET_AGENT,
105-
});
106-
try {
107-
const owner = await withUserContext(db, userId, (transaction) =>
108-
loadWorkspaceTransitionOwner(transaction, userId),
109-
);
110-
if (!owner || !workspaceInventoryMatches(owner.projects, body.projects, body.phase)) {
111-
throw new APIError(
112-
409,
113-
"conflict_state_invalid",
114-
"Postgres workspace inventory does not match transition",
115-
{ retriable: false },
116-
);
117-
}
118-
} finally {
119-
await close();
120-
}
121-
}
122-
123-
function workspaceInventoryMatches(
124-
actual: Array<{
125-
canonicalWorkspaceSlug: string;
126-
currentWorkspaceSlug: string;
127-
projectId: string;
128-
}>,
129-
requested: Array<{
130-
canonicalWorkspaceSlug: string;
131-
currentWorkspaceSlug: string;
132-
projectId: string;
133-
}>,
134-
phase: "finalize" | "prepare",
135-
): boolean {
136-
if (actual.length !== requested.length) {
137-
return false;
138-
}
139-
const requestedById = new Map(requested.map((project) => [project.projectId, project]));
140-
return actual.every((project) => {
141-
const request = requestedById.get(project.projectId);
142-
return (
143-
request?.canonicalWorkspaceSlug === project.canonicalWorkspaceSlug &&
144-
(phase === "finalize"
145-
? project.currentWorkspaceSlug === request.canonicalWorkspaceSlug
146-
: project.currentWorkspaceSlug === request.currentWorkspaceSlug ||
147-
project.currentWorkspaceSlug === request.canonicalWorkspaceSlug)
148-
);
149-
});
150-
}
151-
15251
async function deleteInternalUserState(c: AgentContext): Promise<Response> {
15352
assertAgentInternalHostname(c.req.raw);
15453
assertAgentLifecycleCapability(c.req.raw);

apps/agent-worker/src/durable-object-storage.ts

Lines changed: 0 additions & 71 deletions
This file was deleted.

0 commit comments

Comments
 (0)