@hachej/boring-agent 0.1.17 → 0.1.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/{sandbox-handle-store-hK76cTjn.d.ts → agentPluginEvents-zyIvVjsA.d.ts} +28 -32
  2. package/dist/{chunk-F3CE5CNW.js → chunk-B5JECXMG.js} +5 -7
  3. package/dist/front/index.d.ts +23 -7
  4. package/dist/front/index.js +764 -421
  5. package/dist/front/styles.css +61 -0
  6. package/dist/{tool-ui-DSmWuqGe.d.ts → harness-DRrTn_5T.d.ts} +43 -24
  7. package/dist/server/index.d.ts +79 -10
  8. package/dist/server/index.js +243 -54
  9. package/dist/shared/index.d.ts +5 -3
  10. package/dist/shared/index.js +3 -1
  11. package/dist/tool-ui-DIFNGwYd.d.ts +20 -0
  12. package/docs/ACCESSIBILITY.md +55 -0
  13. package/docs/API.md +64 -0
  14. package/docs/CSP.md +40 -0
  15. package/docs/ERROR_CODES.md +54 -0
  16. package/docs/KNOWN_LIMITATIONS.md +100 -0
  17. package/docs/MIGRATION.md +50 -0
  18. package/docs/PERFORMANCE.md +68 -0
  19. package/docs/PLUGINS.md +108 -0
  20. package/docs/README.md +17 -0
  21. package/docs/RISKS-MULTI-TAB.md +46 -0
  22. package/docs/STYLING.md +71 -0
  23. package/docs/UI-SHADCN.md +56 -0
  24. package/docs/VERCEL_COSTS.md +103 -0
  25. package/docs/plans/AGENT_EVAL_FRAMEWORK.md +466 -0
  26. package/docs/plans/agent-package-spec.md +1777 -0
  27. package/docs/plans/harness-followup-capabilities.md +440 -0
  28. package/docs/plans/harness-tool-ui-capabilities.md +144 -0
  29. package/docs/plans/pi-followup-history-projection.md +229 -0
  30. package/docs/plans/pi-tools-migration.md +1061 -0
  31. package/docs/plans/reviews/pi-followup-history-codex-review.md +11 -0
  32. package/docs/plans/reviews/pi-followup-history-gpt-review.md +64 -0
  33. package/docs/plans/reviews/pi-followup-history-opus-review.md +43 -0
  34. package/docs/plans/reviews/pi-followup-history-xai-review.md +43 -0
  35. package/docs/plans/vercel-base-snapshot-template-plan.md +527 -0
  36. package/docs/plans/vercel-persistent-sandbox-adapter.md +553 -0
  37. package/docs/runtime.md +56 -0
  38. package/docs/tools.md +75 -0
  39. package/package.json +4 -3
@@ -0,0 +1,527 @@
1
+ # Vercel Base Snapshot Template Plan
2
+
3
+ Status: draft for review
4
+ Date: 2026-04-30
5
+ Owner package: `@boring/agent`
6
+ Consumers: `apps/full-app`, future `@boring/cloud`
7
+
8
+ ## Problem
9
+
10
+ New Vercel sandboxes currently pay cold-start setup cost every time they need
11
+ runtime dependencies, template files, or package installs. We also need a clear
12
+ policy for what happens when the app changes its default sandbox image while
13
+ existing workspaces already have user files.
14
+
15
+ The product invariant stays:
16
+
17
+ > One workspace maps to one durable sandbox identity.
18
+
19
+ A reusable base snapshot can improve first boot, but it must not become the
20
+ source of truth for an existing workspace. Once a workspace has user mutations,
21
+ the workspace's own persistent sandbox state or workspace snapshot wins.
22
+
23
+ ## Validated Provider Facts
24
+
25
+ Official Vercel docs checked on 2026-04-30:
26
+
27
+ - `Sandbox.create()` accepts `source: { type: "snapshot", snapshotId }`.
28
+ - `sandbox.snapshot()` captures filesystem and installed packages, then stops
29
+ the source sandbox automatically.
30
+ - Snapshots expire after 30 days by default; `expiration: 0` disables expiry.
31
+ - Non-persistent sandboxes lose filesystem data when stopped unless a snapshot
32
+ exists.
33
+ - Beta persistent sandboxes use a durable `name`; stopping auto-saves state,
34
+ and resuming creates a new compute session from that saved state.
35
+ - Beta persistence is enabled by default and can be disabled with
36
+ `persistent: false`.
37
+
38
+ Implementation note:
39
+
40
+ - Keep a code comment next to `snapshot({ expiration: 0 })` that cites this
41
+ provider behavior. If Vercel changes the zero-expiry contract, bake tooling
42
+ must fail validation instead of creating snapshots that expire unexpectedly.
43
+
44
+ Sources:
45
+
46
+ - https://vercel.com/docs/vercel-sandbox/sdk-reference
47
+ - https://vercel.com/docs/vercel-sandbox/concepts/snapshots
48
+ - https://vercel.com/docs/vercel-sandbox/concepts
49
+ - https://vercel.com/changelog/vercel-sandbox-persistent-sandboxes-beta
50
+
51
+ ## Goals
52
+
53
+ - Define an app-level base snapshot that all new Vercel workspaces can start
54
+ from.
55
+ - Keep base snapshot state separate from workspace state.
56
+ - Make invalidation explicit: bake a new base snapshot, rotate config, keep old
57
+ snapshots while any workspace still references them.
58
+ - Support both stable snapshot-based sandboxes and beta persistent named
59
+ sandboxes.
60
+ - Keep `@boring/agent` core-free and app-agnostic.
61
+ - Keep full-app wiring thin: it supplies config and stores, but does not own
62
+ sandbox lifecycle behavior.
63
+ - Keep the DB model provider-neutral so Fly, Modal, local volumes, or future
64
+ providers can use equivalent metadata.
65
+
66
+ ## Non-Goals
67
+
68
+ - Do not reset existing workspaces to the new base snapshot automatically.
69
+ - Do not put secrets, user files, provider tokens, or per-user auth material in
70
+ the base snapshot.
71
+ - Do not delete old provider snapshots during rollout.
72
+ - Do not replace the persistent sandbox adapter plan.
73
+ - Do not require `@boring/core` to import `@boring/agent`.
74
+
75
+ ## Mental Model
76
+
77
+ There are two snapshot classes:
78
+
79
+ 1. Base snapshot
80
+ - App/deployment-level template.
81
+ - Contains shared tools and dependencies.
82
+ - Used only when provisioning a workspace that has no runtime resource yet.
83
+ - Rotated by operators or CI when the manifest changes.
84
+
85
+ 2. Workspace snapshot or persistent state
86
+ - Workspace-level durable state.
87
+ - Contains user files and installed packages created inside that workspace.
88
+ - Used to recover a stopped or expired workspace.
89
+ - Always wins over the base snapshot after first provisioning.
90
+
91
+ Restore priority:
92
+
93
+ 1. Existing persistent sandbox name, when using Vercel persistent beta.
94
+ 2. Existing active stable sandbox id.
95
+ 3. Existing workspace snapshot id.
96
+ 4. App-level base snapshot id, only for first provisioning.
97
+ 5. Empty sandbox or template tarball fallback.
98
+
99
+ ## App-Level Configuration
100
+
101
+ Add config fields read by the agent package and provided by the app shell:
102
+
103
+ ```text
104
+ BORING_AGENT_VERCEL_BASE_SNAPSHOT_ID=snap_...
105
+ BORING_AGENT_VERCEL_BASE_SNAPSHOT_VERSION=boring-node24-2026-04-30-a1b2c3d4
106
+ BORING_AGENT_VERCEL_BASE_SNAPSHOT_RUNTIME=node24
107
+ BORING_AGENT_VERCEL_BASE_SNAPSHOT_MANIFEST_PATH=./sandbox-template.manifest.json
108
+ BORING_AGENT_VERCEL_BASE_SNAPSHOT_CACHE_PATH=~/.config/boring-agent/base-snapshots.json
109
+ ```
110
+
111
+ Rules:
112
+
113
+ - `BASE_SNAPSHOT_ID` is optional. Without it, current behavior remains.
114
+ - `BASE_SNAPSHOT_VERSION` is required when `BASE_SNAPSHOT_ID` is set.
115
+ - `BASE_SNAPSHOT_RUNTIME` should default to the adapter runtime, not to the
116
+ current bake helper's `python3.13` default.
117
+ - Version is a human-readable label plus manifest hash.
118
+ - Full app stores these as environment variables or deployment secrets.
119
+ - Future cloud can store the same fields in deployment/app runtime template
120
+ metadata.
121
+
122
+ ## Provider-Neutral DB Model
123
+
124
+ The workspace runtime store should track runtime resources generically, not as
125
+ Vercel-only columns.
126
+
127
+ Recommended logical fields for a workspace runtime resource:
128
+
129
+ ```ts
130
+ type WorkspaceRuntimeResource = {
131
+ workspaceId: string
132
+ provider: "vercel" | "local" | "fly" | "modal" | string
133
+ resourceKind: "sandbox" | "volume" | "snapshot" | string
134
+ handleKind: "session" | "persistent-name" | "volume-path" | string
135
+ handle: string
136
+ currentSessionId?: string
137
+ currentSnapshotId?: string
138
+ baseSnapshotId?: string
139
+ baseSnapshotVersion?: string
140
+ templateVersion?: string
141
+ runtime?: string
142
+ state: "pending" | "provisioning" | "ready" | "stopped" | "error"
143
+ status?: string
144
+ expiresAt?: string
145
+ provisionLockToken?: string
146
+ metadata?: Record<string, unknown>
147
+ createdAt: string
148
+ updatedAt: string
149
+ lastUsedAt?: string
150
+ lastSeenAt?: string
151
+ }
152
+ ```
153
+
154
+ For Vercel stable:
155
+
156
+ - `handleKind = "session"`
157
+ - `handle = sandboxId`
158
+ - `currentSnapshotId = latest workspace snapshot id`
159
+ - `baseSnapshotId/baseSnapshotVersion = provenance only`
160
+
161
+ For Vercel persistent beta:
162
+
163
+ - `handleKind = "persistent-name"`
164
+ - `handle = deterministic sandbox name`
165
+ - `currentSessionId = latest session id, metadata only`
166
+ - `currentSnapshotId = provider-reported persisted snapshot when available`
167
+ - `baseSnapshotId/baseSnapshotVersion = provenance only`
168
+
169
+ Important rule:
170
+
171
+ - `baseSnapshotId` is never the recovery source after user mutations unless
172
+ there is no workspace state yet.
173
+ - `templateVersion` is first-class because it drives workspace upgrade
174
+ decisions. Do not bury it only in generic metadata.
175
+ - `expiresAt` is first-class because stable workspace snapshots can expire and
176
+ cleanup/resume code must not assume stored snapshot ids are valid forever.
177
+
178
+ ## Base Snapshot Registry
179
+
180
+ The app or future cloud layer must keep an app-level registry for base
181
+ snapshots. Environment variables are enough to select the active pointer, but
182
+ not enough for audit, rollback, or cleanup.
183
+
184
+ Recommended logical fields:
185
+
186
+ ```ts
187
+ type WorkspaceBaseSnapshotRegistryEntry = {
188
+ provider: "vercel" | string
189
+ snapshotId: string
190
+ version: string
191
+ manifestHash: string
192
+ runtime: string
193
+ status: "baking" | "ready" | "failed" | "retired"
194
+ active: boolean
195
+ previousSnapshotId?: string
196
+ sourceSandboxId?: string
197
+ manifest: Record<string, unknown>
198
+ createdAt: string
199
+ readyAt?: string
200
+ retiredAt?: string
201
+ expiresAt?: string
202
+ }
203
+ ```
204
+
205
+ Registry rules:
206
+
207
+ - One active base snapshot per app/runtime/template lane.
208
+ - Old base snapshots remain registered after rotation.
209
+ - Cleanup can delete only snapshots that are not active, not previous rollback
210
+ candidates, and not referenced by any workspace runtime resource.
211
+ - Keep old base snapshots for a minimum retention window, initially 14 days.
212
+
213
+ ## Current Code Touchpoints
214
+
215
+ Existing useful pieces:
216
+
217
+ - `createDefaultVercelClient()` in
218
+ `packages/agent/src/server/runtime/modes/vercel-sandbox.ts` already supports
219
+ creating from `source: { type: "snapshot", snapshotId }`.
220
+ - `resolveSandboxHandle()` in
221
+ `packages/agent/src/server/sandbox/vercel-sandbox/resolveSandboxHandle.ts`
222
+ already recreates from a stored snapshot id.
223
+ - `bakeSnapshotIfNeeded()` in
224
+ `packages/agent/src/server/sandbox/vercel-sandbox/bake.ts` already has a
225
+ local cache and package hash, but it is not wired into the mode adapter.
226
+
227
+ Gaps:
228
+
229
+ - `bakeSnapshotIfNeeded()` hashes only package lists. It should hash the full
230
+ base manifest.
231
+ - `bakeSnapshotIfNeeded()` defaults to `python3.13`; full-app likely wants
232
+ `node24` or the adapter runtime.
233
+ - Stable `Sandbox.create()` source is one of snapshot or tarball. If a
234
+ workspace also needs app template files, apply the template overlay after
235
+ creation from the base snapshot.
236
+ - Current handle records do not distinguish base snapshot provenance from
237
+ workspace snapshot recovery.
238
+
239
+ ## Base Manifest
240
+
241
+ Create a manifest that describes the base image inputs:
242
+
243
+ ```json
244
+ {
245
+ "schemaVersion": 1,
246
+ "name": "boring-full-app-node",
247
+ "runtime": "node24",
248
+ "systemPackages": ["git", "jq", "ripgrep", "tar", "gzip"],
249
+ "commands": [
250
+ "corepack enable",
251
+ "corepack prepare pnpm@latest --activate"
252
+ ],
253
+ "markerPath": "/vercel/sandbox/.boring/base-snapshot.json"
254
+ }
255
+ ```
256
+
257
+ Hash inputs:
258
+
259
+ - manifest schema version
260
+ - runtime
261
+ - system packages
262
+ - package manager versions
263
+ - setup commands
264
+ - copied template files, if any
265
+ - agent package version or template version
266
+
267
+ Marker file written into the seed sandbox:
268
+
269
+ ```json
270
+ {
271
+ "kind": "boring-base-snapshot",
272
+ "version": "boring-node24-2026-04-30-a1b2c3d4",
273
+ "runtime": "node24",
274
+ "hash": "a1b2c3d4...",
275
+ "createdAt": "2026-04-30T00:00:00.000Z"
276
+ }
277
+ ```
278
+
279
+ ## Bake Flow
280
+
281
+ 1. Load and validate the base manifest.
282
+ 2. Compute the manifest hash.
283
+ 3. Acquire a distributed bake lock for the manifest hash.
284
+ 4. Check registry or cache for an existing snapshot with that hash.
285
+ 5. If cache hits and `Snapshot.get()` reports a usable terminal status, reuse
286
+ it.
287
+ 6. Create a seed sandbox with the manifest runtime.
288
+ 7. Run setup commands with a restricted environment. Pass only explicitly
289
+ allowlisted variables; never inherit the app server environment.
290
+ 8. Install system packages and runtime dependencies.
291
+ 9. Write the marker file.
292
+ 10. Run smoke checks:
293
+ - runtime version
294
+ - `git --version`
295
+ - `rg --version`
296
+ - package manager version
297
+ - marker file content
298
+ 11. Snapshot with `expiration: 0` for managed app templates.
299
+ 12. Poll `Snapshot.get()` until the snapshot is usable or failed.
300
+ 13. Record snapshot id, version, hash, runtime, source sandbox id, expiry, and
301
+ status.
302
+ 14. Release the distributed bake lock.
303
+
304
+ Operational note:
305
+
306
+ - `sandbox.snapshot()` stops the seed sandbox. Do not run more setup commands
307
+ after snapshotting.
308
+ - If a second bake process sees the same manifest hash while the first one owns
309
+ the lock, it should wait and reuse the winner's snapshot instead of baking a
310
+ duplicate.
311
+
312
+ ## Runtime Provisioning Flow
313
+
314
+ For a new workspace:
315
+
316
+ 1. Resolve workspace id and deterministic provider handle.
317
+ 2. Attempt to create or claim a `provisioning` runtime resource row with a
318
+ unique `provisionLockToken`.
319
+ 3. If another request already owns provisioning, poll the row until it reaches
320
+ `ready` or `error`.
321
+ 4. If a ready resource exists, restore from that resource and ignore the base
322
+ snapshot.
323
+ 5. If no ready resource exists and `BASE_SNAPSHOT_ID` is configured, create the
324
+ sandbox from that snapshot.
325
+ 6. Persist the provider handle and base snapshot provenance on the locked row.
326
+ 7. Apply app template overlay if needed.
327
+ 8. On overlay failure, mark the runtime resource `error`, keep enough metadata
328
+ to debug the provider handle, and do not mark the workspace `ready`.
329
+ 9. For persistent beta, rely on provider persistence. Do not call
330
+ `sandbox.snapshot()` after provisioning.
331
+ 10. For stable sandboxes, do not call `sandbox.snapshot()` during first
332
+ provisioning because snapshotting stops the sandbox. Schedule workspace
333
+ snapshots only on explicit save, graceful stop, or idle maintenance with a
334
+ clear reconnect path.
335
+ 11. Mark workspace runtime `ready`.
336
+
337
+ For an existing workspace:
338
+
339
+ 1. Resume by persistent name or sandbox id.
340
+ 2. If stable session is gone and a workspace snapshot exists, recreate from the
341
+ workspace snapshot.
342
+ 3. If stable session is gone and no workspace snapshot exists, show a conflict
343
+ error and require explicit reset. Do not fall back to the base snapshot.
344
+
345
+ Persistent beta validation gate:
346
+
347
+ - Before implementation, run a live provider probe for
348
+ `Sandbox.create({ name, persistent: true, source: { type: "snapshot" } })`.
349
+ - If Vercel rejects that combination, first provisioning must either create an
350
+ empty named persistent sandbox and run setup commands, or apply a small
351
+ post-create template overlay. The fallback must be measured before rollout.
352
+
353
+ ## Invalidation Policy
354
+
355
+ Snapshots are immutable. Invalidation means rotating the pointer.
356
+
357
+ Process:
358
+
359
+ 1. Change the base manifest or dependency versions.
360
+ 2. Bake a new snapshot.
361
+ 3. Smoke test a new sandbox created from the new snapshot.
362
+ 4. Update `BORING_AGENT_VERCEL_BASE_SNAPSHOT_ID`.
363
+ 5. Update `BORING_AGENT_VERCEL_BASE_SNAPSHOT_VERSION`.
364
+ 6. Deploy the app.
365
+ 7. New workspaces use the new base.
366
+ 8. Existing workspaces continue using their own workspace resource.
367
+ 9. Keep old base snapshots until no workspace records reference them.
368
+ 10. Delete old snapshots only through a separate explicit cleanup task.
369
+
370
+ Existing workspace upgrades:
371
+
372
+ - Do not recreate existing workspaces from the new base.
373
+ - Run an explicit workspace migration command inside each workspace.
374
+ - Verify files and dependency state.
375
+ - Save or wait for persistent state to be saved.
376
+ - Update the workspace resource metadata with the applied template version.
377
+
378
+ ## Implementation Phases
379
+
380
+ ### Phase 1 - Contracts and Config
381
+
382
+ - Add base snapshot config schema.
383
+ - Add provider-neutral runtime resource fields or metadata mapping.
384
+ - Add `provisioning`, `templateVersion`, `expiresAt`, and
385
+ `provisionLockToken` to the runtime resource model.
386
+ - Add app-level base snapshot registry storage.
387
+ - Extend `SandboxHandleRecord` to carry:
388
+ - `currentSnapshotId`
389
+ - `baseSnapshotId`
390
+ - `baseSnapshotVersion`
391
+ - `runtime`
392
+ - `handleKind`
393
+ - Keep backward compatibility for existing records.
394
+ - Confirm and lock down `expiration: 0` semantics in a provider smoke test.
395
+
396
+ ### Phase 2 - Resolver Behavior
397
+
398
+ - Teach Vercel stable resolver to use base snapshot only when no workspace
399
+ resource exists.
400
+ - Keep workspace snapshot as the only stable recovery source after first
401
+ provisioning.
402
+ - Preserve the full-app policy where unavailable workspace sandboxes return a
403
+ clear conflict instead of silent replacement.
404
+ - Add the provisioning claim/lock path so concurrent requests cannot create two
405
+ sandboxes for the same workspace.
406
+ - Define the template overlay strategy:
407
+ - preferred: bake template files into the base snapshot when shared by all
408
+ workspaces
409
+ - fallback: keep overlays small and fail provisioning if the overlay exceeds
410
+ a configured size limit
411
+
412
+ ### Phase 3 - Bake Command
413
+
414
+ - Replace package-list hash with manifest hash.
415
+ - Add runtime support with a `node24` full-app default.
416
+ - Add `expiration` support.
417
+ - Add `Snapshot.get()` validation before cache reuse.
418
+ - Poll snapshot creation until a usable terminal status before publishing the
419
+ snapshot id.
420
+ - Add a distributed bake lock around manifest hash.
421
+ - Run bake setup commands with an explicitly allowlisted environment.
422
+ - Add smoke checks and marker writing.
423
+ - Define CI or release ownership for bake automation:
424
+ - trigger on manifest change, package/runtime version change, or scheduled
425
+ security refresh
426
+ - run in CI or a controlled release job with Vercel credentials
427
+ - publish the resulting snapshot id/version through deployment config without
428
+ manual copy-paste
429
+
430
+ ### Phase 4 - Persistent Beta Alignment
431
+
432
+ - If persistent beta is enabled, create named sandbox from base snapshot on
433
+ first provisioning.
434
+ - Store sandbox name as durable handle.
435
+ - Treat session id as metadata.
436
+ - Verify file operations and `runCommand` auto-resume stopped named sandboxes.
437
+ - Validate that Vercel supports `name` plus `source: snapshot` in the same
438
+ create request.
439
+ - Do not call `sandbox.snapshot()` as part of persistent beta recovery unless
440
+ Vercel exposes it as provider metadata. Provider auto-save is the recovery
441
+ path.
442
+
443
+ ### Phase 5 - Tests
444
+
445
+ - Unit test resolver priority:
446
+ - existing workspace snapshot beats base snapshot
447
+ - base snapshot used only for first provisioning
448
+ - missing workspace snapshot returns conflict in full-app policy
449
+ - Unit test manifest hashing and cache reuse.
450
+ - Unit test no secret keys are accepted in base manifest env sections.
451
+ - Unit test bake subprocesses receive only an allowlisted environment.
452
+ - Unit test concurrent provisioning for one workspace creates one provider
453
+ handle.
454
+ - Unit test duplicate bake calls with one manifest hash create one snapshot.
455
+ - Unit test provisioning failure between sandbox create and ready state moves
456
+ the runtime to `error`.
457
+ - Unit test workspace snapshot expiry returns conflict instead of panic or base
458
+ fallback.
459
+ - Unit test template overlay failure does not mark workspace `ready`.
460
+ - E2E test workspace creation starts from base and shows expected tools.
461
+ - E2E test workspace switching preserves file tree and workbench state.
462
+ - E2E test stopped sandbox resumes and user-created files remain visible.
463
+ - E2E test base invalidation affects new workspaces only.
464
+
465
+ ### Phase 6 - Rollout
466
+
467
+ - Bake a dev base snapshot.
468
+ - Enable in local full-app environment.
469
+ - Run workspace lifecycle E2E.
470
+ - Deploy to Fly with base snapshot env vars.
471
+ - Create one new workspace and verify first boot.
472
+ - Stop/resume the workspace and verify files persist.
473
+ - Bake a second base snapshot and verify only new workspaces use it.
474
+ - Keep the previous base snapshot id in rollback config until the new snapshot
475
+ is stable.
476
+ - Add an alert/log signal for workspace provisioning failures, initially
477
+ workspace creation failure rate above 5 percent over 5 minutes.
478
+ - Do not retire old base snapshots before the minimum retention window.
479
+
480
+ ## Risks
481
+
482
+ - Beta persistent SDK behavior can change before GA.
483
+ - Snapshot storage costs and retention limits need an operator budget.
484
+ - Old workspaces with no workspace snapshot cannot be recovered after stable
485
+ sandbox stop.
486
+ - Using `expiration: 0` avoids surprise expiry but requires cleanup discipline.
487
+ - Template overlay after snapshot creation can reintroduce cold-start cost if it
488
+ grows large.
489
+ - The base snapshot can accidentally capture secrets if bake commands read app
490
+ env. Bake must run with a restricted environment.
491
+ - A broken base snapshot can break all new workspace creation until the pointer
492
+ is rolled back.
493
+ - Concurrent provisioning or concurrent baking can create orphaned provider
494
+ resources unless guarded by locks.
495
+
496
+ ## Open Questions
497
+
498
+ - What exact Vercel snapshot statuses should block cache reuse?
499
+ - Should a base snapshot be per app, per tenant, or per workspace template?
500
+ - Should workspace reset create a new deterministic sandbox name generation, or
501
+ delete/reuse the old persistent name?
502
+ - What cleanup command should own old snapshot deletion?
503
+ - What is the maximum acceptable template overlay size before it must be baked
504
+ into the base snapshot?
505
+ - Should bake run in CI, a release job, or an admin-only app command?
506
+
507
+ Questions that must be closed before Phase 2 implementation starts:
508
+
509
+ 1. Live validation of persistent `name` plus `source: snapshot`.
510
+ 2. Vercel snapshot statuses considered usable for cache and restore.
511
+ 3. Template overlay strategy and size limit.
512
+ 4. Bake automation owner and credential boundary.
513
+
514
+ ## Review Log
515
+
516
+ - Claude Code: `revise`.
517
+ - Main feedback: add provisioning state and lock, poll snapshot status after
518
+ bake, promote expiry/template version into the model, add bake automation
519
+ ownership, add rollback signals, and split persistent beta recovery from
520
+ stable snapshot recovery.
521
+ - Incorporated into this plan.
522
+ - Gemini: `revise`.
523
+ - Main feedback: do not call `sandbox.snapshot()` during initial provisioning
524
+ because it stops the sandbox, validate named persistent sandbox creation
525
+ from a snapshot, add provisioning failure handling, add a registry for
526
+ cleanup, and strip bake environment variables.
527
+ - Incorporated into this plan.