antpath 0.1.1 → 0.2.1

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.
@@ -20,9 +20,9 @@ This supersedes the earlier SDK-only MVP boundary.
20
20
  - Minimal tenant-scoped dashboard metadata for monitoring run execution.
21
21
  - Durable worker recovery after restarts, deploys, crashes, or missed notifications.
22
22
  - Horizontal worker scaling without duplicate run ownership.
23
- - Provider cleanup by default after terminal states, with optional retention when configured per-request or per-deployment.
24
- - BYO provider key custody with encrypted storage.
25
- - Private output capture with quota enforcement.
23
+ - Provider cleanup by default after terminal states, with optional retention of Claude-side resources when configured per-request or per-deployment.
24
+ - Per-session BYO provider key custody: keys arrive inline with each run, are encrypted in Supabase Vault for the lifetime of that single run, and are deleted at cleanup. Persistent workspace-level provider key custody is backlog.
25
+ - Unconditional capture of every session-scoped Claude artifact into private Supabase Storage, bounded only by the workspace storage cap.
26
26
  - Programmatic SDK access for submitting and observing platform runs.
27
27
  - Test-driven implementation across SDK, dashboard, worker, database, storage, and provider adapters.
28
28
 
@@ -34,6 +34,8 @@ This supersedes the earlier SDK-only MVP boundary.
34
34
  - No direct browser access to provider APIs.
35
35
  - No direct browser Supabase data access.
36
36
  - No raw prompt, raw model output, raw provider event payload, MCP credential, provider key, or output file content in normal application tables.
37
+ - No persistent workspace-level provider connections (no "saved Anthropic keys"). Every run carries its own secrets bundle; nothing about the user's Anthropic key persists past run cleanup. Persistent BYO custody is backlog.
38
+ - No user-configurable output capture: every artifact Claude exposes on the run's session is captured. There are no globs, no `capture: false`, no per-file or per-run user-visible caps.
37
39
  - No provider Agent/Environment caching in MVP.
38
40
  - No provider webhooks in MVP.
39
41
  - No Supabase Realtime in MVP.
@@ -44,7 +46,7 @@ This supersedes the earlier SDK-only MVP boundary.
44
46
  | Area | Decision |
45
47
  | --- | --- |
46
48
  | Product surface | Platform plus SDK. |
47
- | Repository | Convert the repository to an npm TypeScript workspace. |
49
+ | Repository | Convert the repository to a pnpm TypeScript workspace. |
48
50
  | SDK location | Move the existing SDK package to `packages/sdk`. |
49
51
  | SDK role | The SDK submits runs and observes status, metadata, outputs, cancellation, and deletion through the platform API. |
50
52
  | Dashboard | Build a first-party minimal dashboard. |
@@ -59,8 +61,8 @@ This supersedes the earlier SDK-only MVP boundary.
59
61
  | Service credentials | Supabase service-role credentials are server/worker only. |
60
62
  | SDK auth | SDK uses hashed, workspace-scoped API tokens. Dashboard uses Auth.js sessions. |
61
63
  | API token attribution | Token-authenticated runs are attributed to the token creator at submission time unless a future service-account model is introduced. |
62
- | Provider key custody | Workspace BYO Anthropic key is stored encrypted through Supabase Vault. |
63
- | Secret lifetime | Worker resolves provider keys per claimed lifecycle step, keeps them only in memory for that step, and drops them before lease release. |
64
+ | Provider key custody | BYO Anthropic key arrives inline with every run submission. The BFF stores the secrets bundle in Supabase Vault, attaches the secret id to the run row, and deletes the Vault entry as part of cleanup. Persistent workspace-level provider connections are backlog. |
65
+ | Secret lifetime | Worker resolves the per-run secrets bundle once per claimed lifecycle step, keeps the decoded payload only in memory for that step, and drops it before lease release. The Vault entry itself is destroyed when the run reaches terminal cleanup. |
64
66
  | Provider resources | Create provider Agent, Environment, Vault/Credential, Session, and file resources per run in MVP. |
65
67
  | Provider resource caching | Backlog; later cache Agent/Environment by Template/config hash. |
66
68
  | Worker wakeup | Poll due rows from the runs table. Postgres `NOTIFY` is a latency optimization only. |
@@ -72,12 +74,13 @@ This supersedes the earlier SDK-only MVP boundary.
72
74
  | MCP approvals | Disallow approval-required tools. Worker must not approve tools or return custom tool results. |
73
75
  | Template boundary | Templates are code-first, secret-free snapshots with stable hashes. |
74
76
  | Idempotency | Run submission idempotency is scoped by `(workspace_id, idempotency_key)` and request hash. Same key and same hash returns the existing run; same key and different hash returns conflict. |
75
- | Quotas | Enforce workspace plan quotas with per-user attribution. |
76
- | Run caps | Plan-based caps cover duration, concurrency, storage, polling, retries, and output capture. |
77
+ | Quotas | Enforce workspace storage and concurrency quotas. Storage is the only user-visible cap on output capture. |
78
+ | Run caps | Plan-based caps cover duration, concurrency, polling, retries, and workspace storage. There are no user-visible per-file or per-run output caps. |
77
79
  | Operational config | Exact cap/tier values are environment-configurable defaults with conservative fallbacks and run-level snapshots. |
78
- | Output capture | Enabled by default when configured by the run/template and within quota. |
80
+ | Boot-time env validation | Worker and dashboard validate required environment variables at startup through a shared role-scoped helper and exit/abort with an aggregated error when any are missing. No silent defaults for identity, credentials, or required URLs. |
81
+ | Output capture | Unconditional. Every file Claude exposes on the run's session is captured into private Supabase Storage. The workspace storage cap is the only user-visible quota; capture failures (quota exceeded, download failed) are persisted as `output_capture_failures` rows and surfaced in the dashboard alongside successful outputs. |
79
82
  | Output access | BFF returns signed links only after workspace authorization. |
80
- | Cleanup | Clean up Claude provider resources by default after terminal state and output capture; explicit run policy or worker default can retain them for inspection. |
83
+ | Cleanup | Clean up Claude provider resources by default after terminal state and output capture; explicit run policy or worker default can retain Claude-side resources for inspection. The per-run Vault secret is always deleted at cleanup regardless of the Claude-side retention knob. |
81
84
  | Delete semantics | User delete is soft/pending while execution, cleanup, or storage deletion is active. Hard purge happens only after cleanup/storage deletion succeeds. |
82
85
  | Retention | Run metadata and stored outputs remain until user deletion in MVP. |
83
86
  | Realtime | Defer from MVP; use BFF-mediated refresh/polling first. Revisit with custom Supabase JWT/RLS or a server-mediated realtime bridge. |
@@ -125,35 +128,44 @@ packages/
125
128
 
126
129
  ### Identity and tenancy
127
130
 
128
- - `users`
129
- - Auth.js user identity mirror or Auth.js adapter user table reference.
131
+ - Auth.js adapter tables: `users`, `accounts`, `sessions`, and `verification_token`
132
+ - Auth.js owns OAuth account linking and email magic-link verification persistence in the same Supabase Postgres database.
133
+ - Active application sessions use Auth.js JWT strategy; the `sessions` table exists for adapter compatibility but is not the active session store.
134
+ - Auth.js OAuth tokens remain confined to Auth.js adapter tables and never become antpath provider credentials.
135
+ - `app_users`
136
+ - Antpath-owned user identity row linked to Auth.js `users.id` through `auth_user_id`.
137
+ - Workspace membership, run attribution, API-token ownership, disable state, and JWT token-version checks use `app_users.id`.
138
+ - `disabled_at` and `token_version` are checked by privileged BFF routes to reject disabled or stale JWT users.
130
139
  - No provider secrets.
131
140
  - `workspaces`
132
141
  - Tenant boundary.
133
142
  - Plan, quota, status, and retention settings.
134
143
  - `workspace_memberships`
135
- - Workspace, user, role, and membership status.
144
+ - Workspace, `app_user_id`, role, and membership status.
136
145
  - Every dashboard/API operation must prove membership/scope.
137
146
  - `api_tokens`
138
147
  - Workspace-scoped SDK credentials.
139
- - Store hashed token material only, plus scopes, creator, last-used timestamp, and revoked timestamp.
148
+ - Store hashed token material only, plus scopes, `creator_app_user_id`, last-used timestamp, and revoked timestamp.
140
149
  - API-token submissions freeze the attributed user on the run row at creation time.
141
150
 
142
- ### Provider connections
151
+ ### Per-run secrets
143
152
 
144
- - `provider_connections`
145
- - Workspace-owned provider configuration.
146
- - Provider type, display name, validation status, rotation/revocation status, and encrypted secret reference.
147
- - Secret values live in Supabase Vault.
148
- - Only trusted server/worker code can resolve decrypted provider keys.
153
+ There is no `provider_connections` table in MVP. Every run carries its own secrets.
154
+
155
+ - The submission contract has a top-level `secrets` block: `{ anthropic: { apiKey }, mcpServers?: [{ name, url, headers? }], skills?: [...] }`. The shared parser enforces that **no other field** carries secret-bearing keys; the `secrets` block is the single allowlisted carrier and never serializes into normal metadata, logs, events, audit rows, or stored snapshots.
156
+ - The dashboard BFF stores the JSON-encoded bundle in Supabase Vault at submission time and writes the resulting `vault_secret_id` into `runs.execution_secret_id`. The bundle is the only durable trace of the user's Anthropic key, MCP credentials, and skill references for that run.
157
+ - Worker code reads `runs.execution_secret_id` once per claimed step, decodes the bundle in memory, builds the Claude provider, and drops the decoded values before lease release.
158
+ - At terminal cleanup the worker deletes the Vault entry and clears `runs.execution_secret_id`. Cleanup of the Vault secret happens regardless of the Claude-side retention knob.
159
+ - If the user revokes (rotates) the Anthropic key on Anthropic's side mid-run, antpath has no knowledge of this until the next provider call fails. Active runs surface a `tenant_permanent` failure with a redacted reason; cleanup proceeds with the previously vaulted key while it remains valid and gives up gracefully if it does not.
149
160
 
150
161
  ### Runs
151
162
 
152
163
  - `runs`
153
164
  - User-visible logical run.
154
- - Workspace, creator/attributed user, template snapshot/hash, status, lifecycle phase, plan caps, timestamps.
165
+ - Workspace, `creator_app_user_id`, template snapshot/hash, status, lifecycle phase, plan caps, timestamps.
155
166
  - Submission idempotency key and request hash.
156
167
  - Unique constraint: `(workspace_id, idempotency_key)`.
168
+ - `execution_secret_id` — Supabase Vault secret id for the per-run secrets bundle (Anthropic key + MCP credentials + skill references). Cleared at cleanup.
157
169
  - Lease fields: `lease_owner`, `lease_token`, `lease_expires_at`, `attempt_count`, `next_check_at`, `priority`.
158
170
  - Cancellation/deletion fields: `cancel_requested_at`, `pending_delete_at`, `deleted_at`.
159
171
  - Provider observation cursor/watermark where available.
@@ -174,24 +186,34 @@ packages/
174
186
 
175
187
  - `output_objects`
176
188
  - Workspace/run owner, storage bucket/path, size, checksum if available, content type, provider file id.
189
+ - One row per artifact successfully captured from the run's session into private Supabase Storage.
177
190
  - Used for quota accounting and signed-link generation.
191
+ - `output_capture_failures`
192
+ - Workspace/run owner, provider file id, filename, byte size as reported by the provider, reason (`workspace_quota_exceeded`, `download_failed`, etc.), redacted error message.
193
+ - Surfaced in the dashboard alongside `output_objects` so the user can see exactly which session artifacts didn't make it to storage and why.
178
194
  - `cleanup_attempts`
179
195
  - Per-resource cleanup action, status, retry count, redacted error code/message, timestamps.
180
196
  - `usage_ledger`
181
197
  - Token/cost/storage attribution by workspace and attributed user.
182
198
  - Written transactionally with source event/output rows to avoid drift.
199
+ - `audit_logs`
200
+ - Safe security audit trail for sign-in user upsert, API-token changes, run cancellation/deletion, signed-link creation, and cleanup failures.
201
+ - Stores safe identifiers, action names, redacted errors, and non-secret metadata only.
202
+ - `rate_limit_buckets`
203
+ - Durable token-bucket-style counters for abuse-sensitive BFF/API operations.
204
+ - Used before side effects for run submission, run cancellation/deletion, signed-link creation, API-token creation/revocation, and email-auth throttling where practical.
183
205
 
184
206
  ## Run lifecycle
185
207
 
186
- 1. SDK/dashboard submits a run to the BFF.
208
+ 1. SDK/dashboard submits a run to the BFF. The submission carries a top-level `secrets` bundle (Anthropic key, optional MCP credentials, optional skill references) plus the non-secret template/variables/cleanup payload.
187
209
  2. BFF validates Auth.js session or SDK API token.
188
210
  3. BFF resolves active workspace membership/scope.
189
- 4. BFF validates Template/request shape, plan limits, and idempotency key/request hash.
190
- 5. BFF writes a `runs` row with execution-affecting cap values snapshotted from plan/env defaults.
211
+ 4. BFF validates Template/request shape, plan limits, and idempotency key/request hash. The shared parser rejects every secret-bearing key outside the allowlisted `secrets` block.
212
+ 5. BFF stores the JSON-encoded `secrets` bundle in Supabase Vault and writes a `runs` row with execution-affecting cap values snapshotted from plan/env defaults plus `execution_secret_id` pointing at the Vault entry.
191
213
  6. BFF emits Postgres `NOTIFY` for fast wakeup.
192
214
  7. Worker claims due runs with row locking, `SKIP LOCKED`, lease token, and lease expiry.
193
- 8. Worker resolves the provider key from Supabase Vault for the claimed step.
194
- 9. Worker validates platform constraints: no approval-required tools, no custom antpath-executed tools, known output policy.
215
+ 8. Worker resolves the per-run secrets bundle from Supabase Vault for the claimed step.
216
+ 9. Worker validates platform constraints: no approval-required tools, no custom antpath-executed tools.
195
217
  10. Worker pre-journals intended provider resources.
196
218
  11. Worker creates provider resources and records provider IDs immediately after successful calls.
197
219
  12. Worker creates provider session and sends the initial user event.
@@ -199,8 +221,8 @@ packages/
199
221
  14. Worker later reclaims the run and polls provider session status plus event list.
200
222
  15. Worker stores only metadata events and usage, deduped by provider event id.
201
223
  16. Before every side effect, worker checks lease token and cancellation/deletion requests.
202
- 17. On terminal provider state or antpath timeout, worker captures configured outputs within per-file, per-run, and workspace quota caps.
203
- 18. Worker cleans up provider resources by default, or records them as retained when the run/deployment policy asks to retain them.
224
+ 17. On terminal provider state or antpath timeout, worker lists session-scoped files via the Claude Files API and unconditionally captures every file the workspace storage quota still has room for, persisting `output_capture_failures` rows for any file that would exceed the cap.
225
+ 18. Worker cleans up Claude-side provider resources by default, or records them as retained when the run/deployment policy asks to retain them. The per-run Vault entry is deleted regardless.
204
226
  19. Worker marks the run terminal and releases the lease.
205
227
  20. Dashboard/SDK read tenant-scoped metadata and signed output links through BFF APIs.
206
228
 
@@ -259,51 +281,59 @@ BFF/server actions must:
259
281
  - return only metadata allowed by the user's role/scope;
260
282
  - keep Supabase service-role credentials out of browser bundles.
261
283
 
284
+ Auth.js and antpath authorization stay separate in the database. Auth.js owns `users`, `accounts`, `sessions`, and `verification_token`; antpath owns `app_users` and links each row to Auth.js `users.id`. JWT callbacks expose only safe identity fields such as `app_user_id` and token version. BFF routes must load the current `app_users` row and reject disabled users or stale token versions before privileged actions.
285
+
286
+ All platform and Auth.js tables have RLS enabled and `anon`/`authenticated` table privileges revoked. This is a defense-in-depth guard against accidental Supabase Data API exposure; direct `pg` repository code remains the primary BFF/worker authorization boundary for this phase.
287
+
262
288
  Supabase Realtime is backlog until antpath either mints short-lived Supabase-compatible JWTs with RLS policies or exposes a server-mediated realtime bridge.
263
289
 
264
290
  ## Secret handling
265
291
 
266
- - Provider keys are workspace BYOK and stored via Supabase Vault.
267
- - MCP credentials follow the same no-log/no-table-secret rule.
268
- - Normal app tables store only secret references and validation/rotation status.
269
- - Worker resolves decrypted provider keys only for a claimed lifecycle step.
270
- - Secret values should use explicit redacted wrappers in code so they cannot serialize into logs, metrics, errors, events, or fixtures.
292
+ - Provider keys arrive inline with each submission and are stored in Supabase Vault tied to a single run via `runs.execution_secret_id`.
293
+ - MCP credentials and any skill payloads referenced by the run live alongside the provider key inside the same per-run Vault bundle.
294
+ - Normal app tables store only the Vault secret id, not decoded provider keys, MCP credentials, or skill payloads.
295
+ - Worker resolves the decoded bundle only for a claimed lifecycle step and drops the decoded values before lease release.
296
+ - The shared submission parser keeps an allowlist of exactly one carrier (`secrets`) and rejects every other field that looks like a secret-bearing key (`apiKey`, `accessToken`, `password`, `mcpCredentials`, etc.).
297
+ - Secret values use explicit redacted wrappers in code so they cannot serialize into logs, metrics, errors, events, or fixtures.
271
298
  - Secret redaction applies to logs, errors, run metadata, provider events, tests, fixtures, and docs.
272
- - If a provider key is revoked/rotated mid-run, active runs are failed or cancelled with a tenant-permanent error unless cleanup can still authenticate.
273
- - Revoked keys do not get a hidden grace cache in MVP. If cleanup cannot authenticate after revocation, the run/resource moves to `cleanup_failed` with an actionable tenant error.
299
+ - The per-run Vault entry is deleted as part of run cleanup. Cleanup of the Vault entry happens regardless of the Claude-side retention knob.
300
+ - If a provider key is revoked or rotated on the provider side mid-run, the next provider call fails with a `tenant_permanent` error. The worker fails the affected run, attempts cleanup with whatever credentials are still valid, and surfaces a redacted reason on the dashboard.
301
+ - There is no platform-side rotation/revocation flow because there is no persistent provider connection. To "rotate" the user simply submits the next run with a different key.
274
302
 
275
303
  ## Output storage and quotas
276
304
 
305
+ Output capture is unconditional. Every artifact the user's Claude session exposes is downloaded to private Supabase Storage. There are no user-facing knobs to opt out, no globs, and no per-file or per-run user-visible caps.
306
+
277
307
  Output flow:
278
308
 
279
- 1. Worker lists provider session files.
280
- 2. Worker checks provider file metadata, output policy, and remaining quota.
281
- 3. Worker downloads selected files with streaming hard caps.
282
- 4. If provider size is unknown or exceeds caps, worker aborts capture for that object and records a quota/cap warning.
283
- 5. Worker uploads accepted files to private Supabase Storage.
284
- 6. Worker records `output_objects` rows.
285
- 7. Worker cleans up provider resources by default, or retains them when explicitly configured.
286
- 8. Dashboard/SDK request signed links through the BFF.
309
+ 1. Worker lists files scoped to the run's Claude session: `GET /v1/files?scope_id=<session_id>`. Each entry includes `id`, `filename`, and `size_bytes`.
310
+ 2. For each file, before downloading, the worker checks `(workspace.storage_used_bytes + size_bytes)` against the workspace storage cap.
311
+ 3. If the file would exceed the cap, the worker writes an `output_capture_failures` row (`reason = "workspace_quota_exceeded"`, with filename and reported byte size) and moves on without downloading.
312
+ 4. Otherwise the worker downloads via `GET /v1/files/{file_id}/content` with a hard streaming safety cap so a malformed listing response cannot OOM the worker, uploads to private Supabase Storage at a workspace-scoped path, and writes an `output_objects` row.
313
+ 5. Other download/upload failures (network, storage, unexpected size) are recorded as `output_capture_failures` with the redacted reason.
314
+ 6. Dashboard/SDK request signed links through the BFF for any `output_objects` row.
287
315
 
288
316
  Quota rules:
289
317
 
290
- - Workspace plan quota is the hard enforcement boundary.
291
- - Per-file and per-run caps protect the worker before workspace quota is consumed.
292
- - Per-user attribution is frozen from the run row at submission time.
293
- - Free first X users is a plan/billing rule on top of workspace-level enforcement.
318
+ - Workspace storage cap is the only hard enforcement boundary. Default is generous (configurable via env, see [`environment-variables.md`](./environment-variables.md)).
319
+ - There is no per-user quota. Per-user attribution still freezes on the run row at submission time for billing/audit purposes, but it is not a quota dimension.
320
+ - There are no user-visible per-file or per-run caps. A worker-internal streaming safety cap protects against malformed listings.
321
+ - Capture failures are first-class data: the dashboard renders `<filename> (<size>) skipped: workspace quota exceeded` next to successful outputs so the user can see exactly what didn't make it.
294
322
 
295
323
  ## Cleanup and reconciliation
296
324
 
297
- Claude provider-resource cleanup is mandatory by default after terminal provider state and output capture so antpath does not leave behind provider state. Retention is opt-in and policy-driven through `cleanup.claudeSession = "retain"` or `ANTPATH_WORKER_CLAUDE_SESSION_CLEANUP_DEFAULT=retain`. Retained resources are recorded in `provider_resources.cleanup_status = retained` and remain reachable via the provider API.
325
+ Claude provider-resource cleanup is mandatory by default after terminal provider state and output capture so antpath does not leave behind provider state. Retention is opt-in for **Claude-side resources only** through `cleanup.claudeSession = "retain"` or `ANTPATH_WORKER_CLAUDE_SESSION_CLEANUP_DEFAULT=retain`. Retained resources are recorded in `provider_resources.cleanup_status = retained` and remain reachable via the provider API. Antpath-side metadata, output objects, and the per-run Vault entry are not affected by this knob — Vault entries are always deleted at cleanup; metadata and outputs persist until the user explicitly deletes the run.
298
326
 
299
327
  Explicit cleanup order:
300
328
 
301
- 1. Provider credentials/vaults.
302
- 2. Session files/session where supported.
303
- 3. Agent/archive.
304
- 4. Environment/archive or delete where allowed.
305
- 5. Uploaded provider file resources.
306
- 6. Local output metadata/storage only when user deletes a run.
329
+ 1. Capture session-scoped files into private Supabase Storage (with quota pre-check; failures persisted).
330
+ 2. Provider session files / session where supported.
331
+ 3. Agent / archive.
332
+ 4. Environment / archive or delete where allowed.
333
+ 5. Skills uploaded for this session and any other ephemeral provider resources created for this run.
334
+ 6. Provider Vault/Credentials created on Claude's side for MCP wiring.
335
+ 7. The antpath per-run Vault entry: `vault.deleteSecret(runs.execution_secret_id)`, then clear the column.
336
+ 8. Local output metadata/storage only when the user deletes a run.
307
337
 
308
338
  Cleanup properties:
309
339
 
@@ -319,10 +349,19 @@ Resource leak recovery:
319
349
  - A reconciliation sweeper reviews unfinished intended rows, expired leases, and provider-listable resources tagged with antpath metadata.
320
350
  - If provider create succeeded but provider id was not persisted before a crash, the sweeper attempts to match by deterministic name/metadata and attach the provider id for cleanup.
321
351
 
322
- Workspace/key deletion:
352
+ Workspace deletion:
323
353
 
324
354
  - Workspace deletion blocks new runs, requests cancellation for active runs, drains cleanup, deletes stored outputs, then purges metadata.
325
- - Provider key revocation blocks new runs immediately and may force existing runs to fail/cancel if cleanup can no longer authenticate.
355
+ - There is no separate "provider key revocation" flow because there is no persistent provider key. Submitting the next run with a different key is the rotation mechanism.
356
+
357
+ ## Audit, rate limits, quotas, and deletion
358
+
359
+ - Abuse-sensitive actions are rate-limited before provider, Vault, Storage, or mutation side effects where practical.
360
+ - Audit logs must never include provider keys, OAuth tokens, MCP credentials, raw prompts, raw outputs, signed URLs, or service-role credentials.
361
+ - Workspace storage quota is enforced before output download/upload when file size is known, with streaming hard caps for unknown-size files.
362
+ - App-user deletion is soft/disable-first; hard deletion must not break historical run, usage, output, or audit references.
363
+ - Workspace deletion is pending-first: block new submissions, mark active runs pending-delete, allow cleanup/storage deletion to drain, then purge only safe metadata.
364
+ - Recovery operations should be driven by explicit cleanup/deletion state and audit logs, not manual DB guessing.
326
365
 
327
366
  ## Run state model
328
367
 
@@ -364,6 +403,8 @@ Error classes:
364
403
  - Database: Supabase Postgres.
365
404
  - Storage: Supabase Storage private bucket.
366
405
  - Secrets: Vercel/Railway environment variables plus Supabase Vault for tenant provider keys.
406
+ - CI: GitHub Actions with pnpm, default lint/test/build/package checks, and a separate local Supabase integration job.
407
+ - Local dev stack: `pnpm dev:stack` starts Supabase without printing local keys, validates local config, checks migrations, builds shared packages, and runs the dashboard and worker together. Missing Auth.js providers do not block local service startup unless `ANTPATH_DEV_STACK_REQUIRE_AUTH_PROVIDER=1` is set.
367
408
 
368
409
  Required runtime config includes:
369
410
 
@@ -374,7 +415,10 @@ Required runtime config includes:
374
415
  - Auth.js secret/providers;
375
416
  - SDK token hashing secret/pepper;
376
417
  - worker identity;
377
- - provider API base/version settings.
418
+ - provider API base/version settings;
419
+ - dashboard BFF rate-limit defaults;
420
+ - worker polling/lease/metrics settings.
421
+ - dev stack ports for dashboard and worker.
378
422
 
379
423
  Environment-configurable defaults include:
380
424
 
@@ -396,26 +440,28 @@ Environment-configurable defaults include:
396
440
  - workspace storage cap;
397
441
  - signed URL TTL;
398
442
  - free user allowance;
399
- - metadata retention toggles.
443
+ - metadata retention toggles;
444
+ - audit/rate-limit windows for sensitive BFF actions.
445
+ - live e2e opt-in flags for provider sessions, public MCP endpoints, Anthropic skill IDs, and timeouts.
400
446
 
401
447
  Missing optional env vars must fall back to conservative low limits. Missing required secret/connectivity env vars must fail service startup.
402
448
 
449
+ Live public MCP/skills e2e tests are intentionally opt-in because they depend on external provider and public MCP availability. Deterministic unit/component tests validate request wiring for MCP server declarations, tool policies, skill references, and secret non-leakage; the live e2e validates the real provider accepts and runs that configuration end to end.
450
+
451
+ No-cost provider resilience and load testing uses sanitized replay snapshots from real Managed Agents sessions. Replay tests inject network disconnects, transient HTTP failures, provider latency, slow SSE chunks, cleanup failures, and concurrent full-session load around those snapshots. Idempotent read/delete-style calls may retry with bounded backoff; provider create/send POST calls are not retried by default because their side effects may already have happened.
452
+
403
453
  ## Open implementation details
404
454
 
405
455
  These are not architecture blockers, but must be pinned during implementation planning:
406
456
 
407
- - Exact plan tier values for duration, concurrency, storage, polling, and free user allowance.
408
- - Exact Auth.js providers.
409
- - Auth.js adapter/session mode and user mirror lifecycle.
410
- - Supabase Vault access method and SQL privilege wrapper shape.
411
- - Signed URL TTL and whether storage paths include random unguessable components in addition to workspace/run ids.
412
- - Deletion audit requirements.
457
+ - Production plan tier values for duration, concurrency, storage, polling, and free user allowance.
413
458
  - Provider metadata naming convention.
414
459
  - Provider list/search capabilities and reconciliation coverage for each resource type.
415
460
  - Exact Claude Managed Agents event pagination/filter semantics and bounded polling fallback.
416
461
 
417
462
  ## Backlog
418
463
 
464
+ - Persistent workspace-level provider connections (BYO Anthropic key custody across runs, with rotation, revocation, and re-use). The MVP submission contract carries the key inline per run instead.
419
465
  - Provider webhooks as wakeup/reconciliation accelerator.
420
466
  - SSE live event stream for richer UI.
421
467
  - Supabase Realtime with explicit Auth.js-to-Supabase authorization design.
@@ -8,7 +8,7 @@ scope: platform MVP
8
8
 
9
9
  ## Goal
10
10
 
11
- Convert antpath from an SDK-only package into a TypeScript workspace containing:
11
+ Convert antpath from an SDK-only package into a pnpm TypeScript workspace containing:
12
12
 
13
13
  - a platform SDK in `packages/sdk`;
14
14
  - a dashboard app in `apps/dashboard`;
@@ -21,27 +21,30 @@ Implementation is test-driven. Each behavior starts with the narrowest failing t
21
21
 
22
22
  ## Acceptance criteria
23
23
 
24
- - The repository is an npm workspace and the current SDK is moved to `packages/sdk`.
24
+ - The repository is a pnpm workspace and the current SDK is moved to `packages/sdk`.
25
25
  - Dashboard and worker app folders exist with build/test integration.
26
26
  - Auth.js authenticates dashboard users.
27
27
  - SDK API tokens authenticate programmatic clients.
28
28
  - Workspaces are the tenant boundary.
29
29
  - BFF/server actions scope every dashboard/API operation by workspace membership or API-token scope.
30
30
  - Supabase service-role credentials are never exposed to browser/client bundles.
31
- - Supabase Postgres stores durable run, attempt, provider resource, event, output, cleanup, usage, workspace, membership, and provider connection metadata.
32
- - Provider keys are stored encrypted through Supabase Vault and resolved only by trusted server/worker code.
33
- - Run submission idempotency is enforced by workspace and request hash.
31
+ - Supabase Postgres stores durable run, attempt, provider resource, event, output, output-capture-failure, cleanup, usage, workspace, and membership metadata. There is no persistent `provider_connections` table in MVP.
32
+ - Per-run secrets bundles (Anthropic key, optional MCP credentials, optional skill references) arrive inline on submission, are encrypted through Supabase Vault for the lifetime of that single run, and are deleted at terminal cleanup. The Vault entry is the only durable trace of the user's provider key.
33
+ - Run submission idempotency is enforced by workspace and request hash. The hash excludes the `secrets` block so re-submitting the same logical run with a new key still matches.
34
34
  - Workers claim due runs with leases and `FOR UPDATE SKIP LOCKED`.
35
35
  - Multiple workers can run concurrently without duplicate lifecycle ownership.
36
36
  - Worker polling recovers from missed `NOTIFY`, worker restart, deploy, and expired leases.
37
37
  - Worker creates per-run provider resources and journals them for cleanup.
38
38
  - Worker polls provider status and events and stores only redacted metadata.
39
- - Output capture writes configured files to private Supabase Storage within per-file, per-run, and workspace quotas.
39
+ - Output capture is unconditional: every artifact Claude exposes on the run's session is written to private Supabase Storage, bounded only by the workspace storage cap. Files that would exceed the cap are persisted as `output_capture_failures` rows.
40
40
  - BFF returns signed output links only after workspace authorization.
41
- - Cleanup runs after terminal provider state and output capture.
41
+ - Cleanup runs after terminal provider state and output capture, deletes the per-run Vault entry, and (by default) deletes Claude-side resources. `cleanup.claudeSession: "retain"` only affects Claude-side resources.
42
42
  - Reconciliation can recover intended provider resources after partial worker crashes.
43
43
  - User deletion is pending/soft until cleanup and storage deletion are complete.
44
44
  - Exact tier/cap values are configurable through environment variables with conservative defaults and run-level snapshots.
45
+ - Security-sensitive BFF/API actions are audited and rate-limited.
46
+ - Workspace storage quota and workspace deletion are enforced before provider/storage side effects proceed.
47
+ - CI runs pnpm lint/test/build/package checks plus a local Supabase integration job.
45
48
  - Tests follow the accepted taxonomy: deterministic unit tests (including fakes and sanitized recorded snapshots), live external-system integration tests with no skip flags, and top-to-bottom live e2e tests.
46
49
 
47
50
  ## Phase 1: Workspace foundation
@@ -50,16 +53,16 @@ Create the workspace structure without changing runtime behavior.
50
53
 
51
54
  Deliverables:
52
55
 
53
- - Root npm workspace configuration.
56
+ - Root pnpm workspace configuration.
54
57
  - Current SDK moved to `packages/sdk`.
55
58
  - `apps/dashboard` placeholder.
56
59
  - `apps/worker` placeholder.
57
60
  - Shared TypeScript config/build/test commands.
58
61
  - Existing SDK exports preserved or intentionally migrated with compatibility notes.
59
62
  - Root validation commands:
60
- - `npm run lint`
61
- - `npm test`
62
- - `npm run build`
63
+ - `pnpm lint`
64
+ - `pnpm test`
65
+ - `pnpm build`
63
66
 
64
67
  TDD gate:
65
68
 
@@ -113,23 +116,25 @@ Deliverables:
113
116
 
114
117
  - Migration framework.
115
118
  - Tables:
116
- - `users`;
119
+ - Auth.js adapter tables: `users`, `accounts`, `sessions`, `verification_token`;
120
+ - antpath `app_users`;
117
121
  - `workspaces`;
118
122
  - `workspace_memberships`;
119
123
  - `api_tokens`;
120
- - `provider_connections`;
121
- - `runs`;
124
+ - `runs` (includes `execution_secret_id` referencing the per-run Vault entry);
122
125
  - `run_attempts`;
123
126
  - `provider_resources`;
124
127
  - `run_events`;
125
128
  - `output_objects`;
129
+ - `output_capture_failures`;
126
130
  - `cleanup_attempts`;
127
131
  - `usage_ledger`.
128
132
  - Constraints:
129
133
  - unique `(workspace_id, idempotency_key)` on runs;
130
134
  - request-hash conflict handling;
131
135
  - unique `(run_attempt_id, provider_event_id)` on events;
132
- - foreign keys for workspace and attributed user where practical.
136
+ - foreign keys for workspace and attributed `app_user_id` where practical;
137
+ - RLS enabled and `anon`/`authenticated` direct table access revoked for platform/Auth.js tables.
133
138
  - DB query helpers for tenant-scoped access.
134
139
 
135
140
  TDD gate:
@@ -174,10 +179,11 @@ Turn the SDK into the programmatic client for the platform while preserving Temp
174
179
  Deliverables:
175
180
 
176
181
  - SDK client for platform API base URL and API token.
177
- - Submit run API.
182
+ - `AntpathPlatformClient` constructor accepts a default `secrets` bundle (Anthropic key + optional MCP credentials + optional skill references); every method accepts a per-call override.
183
+ - Submit run API (carries inline `secrets`).
178
184
  - Get run status/detail API.
179
185
  - List metadata events API.
180
- - List outputs API.
186
+ - List outputs API (returns successful captures and capture failures).
181
187
  - Create signed output link API.
182
188
  - Cancel run API.
183
189
  - Delete run API.
@@ -187,10 +193,11 @@ Deliverables:
187
193
  TDD gate:
188
194
 
189
195
  - Type/contract tests for public SDK API and runtime tests with fake platform responses.
196
+ - Tests asserting `secrets` never appears in serialized request hashes, error metadata, or retry telemetry.
190
197
 
191
198
  Validation:
192
199
 
193
- - SDK never accepts or stores provider keys for platform runs.
200
+ - SDK does not persist provider keys: it forwards them to the platform per submission, where they are vaulted for the lifetime of one run and then deleted.
194
201
  - SDK handles idempotency conflict, unauthorized, quota, and terminal states deterministically.
195
202
 
196
203
  ## Phase 6: Worker claim loop and state machine
@@ -203,7 +210,7 @@ Deliverables:
203
210
  - Optional Postgres `NOTIFY` listener for fast wakeup.
204
211
  - Lease claim/release helpers.
205
212
  - Lease-guarded status update helper.
206
- - Per-workspace/provider-key rate limit hooks.
213
+ - Per-workspace rate limit hooks.
207
214
  - Fair due-run ordering across workspaces.
208
215
  - Cancellation/delete request checks before side effects.
209
216
  - Timeout handling.
@@ -295,58 +302,65 @@ Validation:
295
302
 
296
303
  ## Phase 10: Output capture and Supabase Storage
297
304
 
298
- Capture configured outputs safely.
305
+ Unconditionally capture every artifact the user's Claude session exposes into private Supabase Storage. The workspace storage cap is the only user-visible quota.
299
306
 
300
307
  Deliverables:
301
308
 
302
- - Output policy from Template/run request.
303
- - Provider file metadata inspection.
304
- - Per-file and per-run caps.
305
- - Streaming download with hard byte cap and abort.
306
- - Private Supabase Storage upload.
307
- - Deterministic plus unguessable storage path policy if required.
308
- - `output_objects` metadata.
309
- - Workspace storage quota accounting.
310
- - Per-user attribution from run row.
311
- - Signed-link BFF action/API.
309
+ - Worker terminal-state list of session-scoped files via the Claude Files API (`GET /v1/files?scope_id=<sessionId>`), returning `id`, `filename`, and `size_bytes`.
310
+ - Workspace storage usage accounting helper.
311
+ - Pre-download quota check: if `(workspace_used + size_bytes) > workspace cap`, write an `output_capture_failures` row with `reason = "workspace_quota_exceeded"` and continue without downloading.
312
+ - Streaming download via `GET /v1/files/{file_id}/content` with a worker-internal safety cap so malformed listing responses cannot OOM the worker; safety-cap aborts are recorded as `output_capture_failures` with `reason = "download_failed"`.
313
+ - Private Supabase Storage upload with workspace-scoped path policy.
314
+ - `output_objects` metadata insert for each successful capture.
315
+ - Per-user attribution frozen from the run row (audit/billing dimension only; not a quota).
316
+ - Signed-link BFF action/API for both `output_objects` rows; capture failures are read-only.
312
317
 
313
318
  TDD gate:
314
319
 
315
- - Add failing tests for quota checks before download, unknown-size streaming caps, storage metadata, signed-link authorization, and cross-workspace denial.
320
+ - Add failing tests for: pre-download quota check choosing failure-row over download when over cap; safety-cap abort recording a failure row; happy path writing both an `output_objects` row and an audit entry; signed-link authorization; cross-workspace denial; listing returning zero files producing zero rows; listing returning N files with mixed sizes recording the expected mix of successes and failures.
321
+ - Tests must assert there is no user-facing knob for `capture: boolean`, `globs`, or per-file caps.
316
322
 
317
323
  Validation:
318
324
 
319
- - Oversized outputs do not OOM the worker.
325
+ - Output capture is unconditional and cannot be opted out by the submitter.
326
+ - Oversized output payloads do not OOM the worker.
320
327
  - BFF only creates signed links for authorized workspace users/tokens.
328
+ - Dashboard renders successful captures and capture failures side by side.
321
329
 
322
330
  ## Phase 11: Cleanup, deletion, and retention
323
331
 
324
- Make cleanup and deletion first-class state machines.
332
+ Make cleanup and deletion first-class state machines. Cleanup must also destroy the per-run Vault secret.
325
333
 
326
334
  Deliverables:
327
335
 
328
- - Cleanup ordering:
329
- - provider credentials/vaults;
330
- - session files/session where supported;
331
- - agent/archive;
332
- - environment/archive/delete;
333
- - uploaded provider file resources;
334
- - local storage only on user deletion.
335
- - Cleanup retry/backoff.
336
+ - Cleanup ordering (per the architecture decisions doc):
337
+ - capture session-scoped files (Phase 10);
338
+ - provider session files / session where supported;
339
+ - agent / archive;
340
+ - environment / archive or delete;
341
+ - skills uploaded for this session and other ephemeral provider resources;
342
+ - provider Vault/credentials created on Claude's side for MCP wiring;
343
+ - the antpath per-run Vault entry: `vault.deleteSecret(runs.execution_secret_id)` and clear the column;
344
+ - local Supabase Storage and metadata only on user deletion.
345
+ - The Claude-side cleanup steps are skipped when `cleanup.claudeSession === "retain"` (or the worker default is `retain`); the per-run Vault deletion still runs.
346
+ - Cleanup retry/backoff with idempotent calls.
336
347
  - `cleanup_attempts` records.
337
348
  - Cleanup state separate from user-facing run terminal state.
338
349
  - User `pending_delete` flow.
339
350
  - Workspace deletion flow.
340
- - Provider key revocation behavior.
351
+ - Audit logs and rate limits for delete/cancel/signed-link/API-token mutations.
341
352
 
342
353
  TDD gate:
343
354
 
344
- - Add tests for cleanup after success, failure, timeout, cancellation, partial provider creation, duplicate cleanup calls, key revocation, and pending-delete races.
355
+ - Add tests for cleanup after success, failure, timeout, cancellation, partial provider creation, duplicate cleanup calls, and pending-delete races.
356
+ - Add tests that the per-run Vault entry is deleted exactly once and `runs.execution_secret_id` is cleared whether or not Claude-side resources are retained.
357
+ - Add tests that a key rotated on the provider side mid-run produces a `tenant_permanent` failure on the next provider call and that cleanup still runs (and the Vault entry is still deleted).
345
358
 
346
359
  Validation:
347
360
 
348
361
  - Cleanup failures surface actionable redacted errors.
349
362
  - Hard deletion only happens after cleanup/storage deletion succeeds.
363
+ - The per-run Vault entry is always destroyed at terminal cleanup, regardless of retention knobs.
350
364
 
351
365
  ## Phase 12: Minimal dashboard
352
366
 
@@ -407,18 +421,26 @@ Deliverables:
407
421
  - Cleanup in `finally`.
408
422
  - Release/readiness docs.
409
423
  - Updated README and examples for platform SDK usage.
424
+ - GitHub Actions pnpm CI and local Supabase integration job.
425
+ - Vercel dashboard and Railway worker environment-variable contracts.
426
+ - `pnpm dev:stack` for local Supabase/dashboard/worker startup with fail-fast config checks.
427
+ - Deterministic MCP/skills request-wiring tests for SDK and worker providers.
428
+ - Separately gated public MCP plus Anthropic skill full-session e2e.
410
429
 
411
430
  TDD gate:
412
431
 
413
432
  - Live e2e is not a TDD driver for core logic, but must prove final integration before release.
433
+ - MCP/skills request shape, permission policy, and secret non-leakage must be covered by normal deterministic tests before relying on public live e2e.
414
434
 
415
435
  Validation:
416
436
 
417
437
  - Full submit -> provider session -> metadata poll -> output capture -> signed link -> cleanup works.
418
- - Default `npm run lint`, `npm test`, and `npm run build` pass.
438
+ - Public MCP/skills e2e runs through the explicit `pnpm test:e2e:live` command with live credentials, reaches success, emits SDK/provider events, records safe provider IDs, exercises the default cleanup path, and exercises the `cleanup.claudeSession = "retain"` override path with a follow-up provider API check.
439
+ - Default `pnpm lint`, `pnpm test`, and `pnpm build` pass.
419
440
 
420
441
  ## Backlog
421
442
 
443
+ - Persistent workspace-level provider connections (saved Anthropic keys with rotation/revocation/re-use). The MVP submission contract carries the key inline per run.
422
444
  - Provider webhooks as wakeup/reconciliation accelerator.
423
445
  - SSE live event stream for richer dashboard UI.
424
446
  - Supabase Realtime with explicit Auth.js-to-Supabase authorization design.