@delegance/claude-autopilot 6.2.2 → 7.2.0
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.
- package/CHANGELOG.md +841 -0
- package/README.md +10 -1
- package/bin/_launcher.js +38 -23
- package/dist/src/cli/autopilot.d.ts +4 -0
- package/dist/src/cli/autopilot.js +15 -0
- package/dist/src/cli/dashboard/index.d.ts +5 -0
- package/dist/src/cli/dashboard/index.js +49 -0
- package/dist/src/cli/dashboard/login.d.ts +22 -0
- package/dist/src/cli/dashboard/login.js +260 -0
- package/dist/src/cli/dashboard/logout.d.ts +12 -0
- package/dist/src/cli/dashboard/logout.js +45 -0
- package/dist/src/cli/dashboard/status.d.ts +30 -0
- package/dist/src/cli/dashboard/status.js +65 -0
- package/dist/src/cli/dashboard/upload.d.ts +16 -0
- package/dist/src/cli/dashboard/upload.js +48 -0
- package/dist/src/cli/engine-flag-deprecation.d.ts +14 -0
- package/dist/src/cli/engine-flag-deprecation.js +20 -0
- package/dist/src/cli/help-text.d.ts +1 -1
- package/dist/src/cli/help-text.js +44 -28
- package/dist/src/cli/index.d.ts +2 -1
- package/dist/src/cli/index.js +72 -17
- package/dist/src/cli/scaffold.d.ts +39 -0
- package/dist/src/cli/scaffold.js +287 -0
- package/dist/src/cli/setup.d.ts +30 -0
- package/dist/src/cli/setup.js +137 -0
- package/dist/src/core/run-state/events.js +10 -2
- package/dist/src/core/run-state/resolve-engine.d.ts +26 -81
- package/dist/src/core/run-state/resolve-engine.js +39 -155
- package/dist/src/core/run-state/run-phase-with-lifecycle.d.ts +5 -9
- package/dist/src/core/run-state/run-phase-with-lifecycle.js +26 -19
- package/dist/src/core/run-state/state.d.ts +1 -1
- package/dist/src/core/run-state/types.d.ts +8 -2
- package/dist/src/core/run-state/types.js +8 -2
- package/dist/src/dashboard/auto-upload.d.ts +26 -0
- package/dist/src/dashboard/auto-upload.js +107 -0
- package/dist/src/dashboard/config.d.ts +22 -0
- package/dist/src/dashboard/config.js +109 -0
- package/dist/src/dashboard/upload/canonical.d.ts +3 -0
- package/dist/src/dashboard/upload/canonical.js +16 -0
- package/dist/src/dashboard/upload/chain.d.ts +9 -0
- package/dist/src/dashboard/upload/chain.js +27 -0
- package/dist/src/dashboard/upload/snapshot.d.ts +23 -0
- package/dist/src/dashboard/upload/snapshot.js +66 -0
- package/dist/src/dashboard/upload/uploader.d.ts +54 -0
- package/dist/src/dashboard/upload/uploader.js +330 -0
- package/package.json +18 -3
- package/scripts/test-runner.mjs +4 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,847 @@
|
|
|
2
2
|
|
|
3
3
|
- v5.6 Phase 7 (docs reconciliation) — pending.
|
|
4
4
|
|
|
5
|
+
## 7.2.0 (2026-05-10)
|
|
6
|
+
|
|
7
|
+
**v7.2.0 — `claude-autopilot scaffold --from-spec <path>`.** Closes
|
|
8
|
+
the biggest remaining day-1 friction the v7.1.6 blank-repo benchmark
|
|
9
|
+
identified. Even with auto-scaffolded `CLAUDE.md` + `.gitignore`
|
|
10
|
+
(v7.1.7), a fresh repo still needs a hand-written `package.json`,
|
|
11
|
+
`tsconfig.json`, and directory skeleton before any feature work
|
|
12
|
+
happens. The new verb collapses that step.
|
|
13
|
+
|
|
14
|
+
**New verb** reads a spec markdown file's `## Files` section and:
|
|
15
|
+
|
|
16
|
+
* Creates listed directories (`mkdir -p`).
|
|
17
|
+
* Creates empty placeholder files for each path in the section.
|
|
18
|
+
* Generates a starter `package.json` (Node 22 ESM defaults +
|
|
19
|
+
hint-merged `bin` / `dependencies` / `scripts` parsed loosely
|
|
20
|
+
from the spec prose).
|
|
21
|
+
* Generates a starter `tsconfig.json` — JS-flavor (`allowJs +
|
|
22
|
+
checkJs + noEmit`) when the spec lists predominantly `.js` files,
|
|
23
|
+
TS-flavor (compiled to `dist/`) for `.ts` files.
|
|
24
|
+
|
|
25
|
+
**Never overwrites existing files** — operator opted into autopilot,
|
|
26
|
+
not into us nuking their package.json. Reports `· exists` for each
|
|
27
|
+
preserved file. Idempotent: re-running on a partially-scaffolded
|
|
28
|
+
repo only fills the gaps.
|
|
29
|
+
|
|
30
|
+
`--dry-run` flag logs what would happen without writing.
|
|
31
|
+
|
|
32
|
+
**End-to-end smoke**: scaffold from the actual v7.1.6 benchmark
|
|
33
|
+
spec produces a 100%-correct skeleton in ~50ms (3 dirs + 5
|
|
34
|
+
placeholder files + matching package.json bin/deps/scripts).
|
|
35
|
+
|
|
36
|
+
**Out of scope (deferred to v8):**
|
|
37
|
+
|
|
38
|
+
* Per-stack scaffolding (Python `pyproject.toml`, Go `go.mod`,
|
|
39
|
+
Rust `Cargo.toml`). v7.2.0 ships Node ESM only — covers the
|
|
40
|
+
v7.1.6 benchmark stack and the most common starter case.
|
|
41
|
+
* Running `npm install`. Operator picks the package manager.
|
|
42
|
+
|
|
43
|
+
11 new tests (4 parser + 2 builder + 5 end-to-end). 1548 → 1559
|
|
44
|
+
CLI tests; tsc clean; build clean. New verb registered in
|
|
45
|
+
`src/cli/index.ts` + listed in `Pipeline:` help group. Version
|
|
46
|
+
7.1.9 → 7.2.0 (minor bump for new verb surface).
|
|
47
|
+
|
|
48
|
+
## 7.1.9 (2026-05-10)
|
|
49
|
+
|
|
50
|
+
**v7.1.9 — build fix + Generic-stack next-steps hint.** Two
|
|
51
|
+
micro-fixes from the v7.1.8 benchmark re-run.
|
|
52
|
+
|
|
53
|
+
* **`canonicalize` declared at root** (`package.json`). The CLI's
|
|
54
|
+
`src/dashboard/upload/canonical.ts` (RFC 8785 / JCS parity copy
|
|
55
|
+
of `apps/web/lib/upload/canonical.ts`) imports `canonicalize`
|
|
56
|
+
but the module was only declared in `apps/web/package.json`.
|
|
57
|
+
Root build hit `TS2307: Cannot find module 'canonicalize'` even
|
|
58
|
+
though the package was actually installed via npm hoisting. Now
|
|
59
|
+
declared at root — `npm run build` from a fresh clone is clean.
|
|
60
|
+
* **Generic+low-confidence next-steps hint** (`src/cli/setup.ts`).
|
|
61
|
+
The v7.1.8 benchmark re-run on a truly blank repo reported
|
|
62
|
+
"Detected: Generic (low confidence)" with no actionable next
|
|
63
|
+
step. Setup now surfaces a one-liner:
|
|
64
|
+
`npm init -y` → `npx claude-autopilot setup --force`. Skipped
|
|
65
|
+
silently on high-confidence detections (the common case).
|
|
66
|
+
|
|
67
|
+
2 new tests (`tests/setup.test.ts`); 1546 → 1548 CLI tests; tsc
|
|
68
|
+
clean; build clean. Version 7.1.8 → 7.1.9.
|
|
69
|
+
|
|
70
|
+
## 7.1.8 (2026-05-10)
|
|
71
|
+
|
|
72
|
+
**v7.1.8 — blank-repo benchmark re-run on v7.1.7.** Docs-only PR.
|
|
73
|
+
Friction-reduction delta measurement after the v7.1.7 polish PR.
|
|
74
|
+
|
|
75
|
+
**All three v7.1.7 fixes verified end-to-end** on a fresh `git init`
|
|
76
|
+
repo:
|
|
77
|
+
|
|
78
|
+
* `.gitignore` auto-created with `.guardrail-cache/` + `node_modules/`.
|
|
79
|
+
* `CLAUDE.md` auto-scaffolded with detected stack, test command,
|
|
80
|
+
Conventional Commits convention, error class shape, branch
|
|
81
|
+
naming, TODO slots.
|
|
82
|
+
* Deprecation banner deduped per UTC day via
|
|
83
|
+
`~/.claude-autopilot/.deprecation-shown` stamp.
|
|
84
|
+
|
|
85
|
+
**Friction score: 3 of 6 v7.1.6 friction points closed; 1 partially
|
|
86
|
+
closed; 2 deferred.** Matches v7.1.6 prediction ("would close ~5 of
|
|
87
|
+
6") with minor over-promise.
|
|
88
|
+
|
|
89
|
+
**New friction surfaced:**
|
|
90
|
+
|
|
91
|
+
* Stale `dist/` after merge requires `npm run build` for local
|
|
92
|
+
contributors (invisible to `npm install -g` users).
|
|
93
|
+
* Build hits one stale TS error (`canonicalize` not declared at
|
|
94
|
+
root level) — 4 v7.1.7 helpers compiled, setup ran end-to-end,
|
|
95
|
+
filing as separate followup.
|
|
96
|
+
* `Detected: Generic (low confidence)` on truly blank repos —
|
|
97
|
+
honest but suggests next-step "scaffold a `package.json` first
|
|
98
|
+
for higher-confidence detection."
|
|
99
|
+
|
|
100
|
+
**New recommendations:** suggest stack-scaffold step in `setup`
|
|
101
|
+
next-steps when detection is `Generic` (~20min ship);
|
|
102
|
+
`scaffold --from-spec` verb (deferred from v7.1.6, ~1-day);
|
|
103
|
+
per-stack starter `tsconfig.json` / `pyproject.toml` (~2-4hr per
|
|
104
|
+
stack).
|
|
105
|
+
|
|
106
|
+
**Methodology caveat:** Phase B (impl agent) NOT re-run — wall-clock
|
|
107
|
+
impact is downstream and would need another full agent dispatch to
|
|
108
|
+
measure precisely. The friction-point table tells most of the story.
|
|
109
|
+
|
|
110
|
+
Full report at `docs/benchmarks/2026-05-10-blank-repo-v7.1.7.md`.
|
|
111
|
+
No code change; bumping to 7.1.8 to keep CHANGELOG/version line in
|
|
112
|
+
lockstep with master HEAD.
|
|
113
|
+
|
|
114
|
+
## 7.1.7 (2026-05-10)
|
|
115
|
+
|
|
116
|
+
**v7.1.7 — `setup` verb day-1 polish.** Three fixes from the v7.1.6
|
|
117
|
+
blank-repo benchmark report. Operator-facing improvements; no
|
|
118
|
+
breaking changes; no migration.
|
|
119
|
+
|
|
120
|
+
* **Per-calendar-day deprecation dedup** (`bin/_launcher.js`). The
|
|
121
|
+
v6.3+ stamp was keyed by `process.ppid + tty/pipe` — fine for
|
|
122
|
+
interactive shells, broken for git hooks (fresh shell per hook =
|
|
123
|
+
fresh ppid = stamp re-created every commit, notice printed every
|
|
124
|
+
commit). New stamp at `~/.claude-autopilot/.deprecation-shown`
|
|
125
|
+
contains `YYYY-MM-DD` and dedups by UTC day per machine.
|
|
126
|
+
Override env vars (`CLAUDE_AUTOPILOT_DEPRECATION=always|never`)
|
|
127
|
+
preserved.
|
|
128
|
+
* **Auto-add `node_modules/` + `.guardrail-cache/` to `.gitignore`**
|
|
129
|
+
on `setup` (`src/cli/setup.ts`). New `ensureGitignoreEntries()`
|
|
130
|
+
helper: idempotent (re-running never duplicates), preserves
|
|
131
|
+
existing entries, creates `.gitignore` from scratch if missing.
|
|
132
|
+
* **Auto-scaffold starter `CLAUDE.md`** when one doesn't exist
|
|
133
|
+
(`src/cli/setup.ts`). New `ensureStarterClaudeMd()` helper writes
|
|
134
|
+
~35 lines covering: detected stack + confidence, test command,
|
|
135
|
+
Conventional Commits convention, error class shape, branch naming,
|
|
136
|
+
TODO slots for "patterns to mimic" + "common pitfalls". Closes
|
|
137
|
+
~5 of 6 friction points the benchmark agent reported. Never
|
|
138
|
+
overwrites an existing `CLAUDE.md`.
|
|
139
|
+
|
|
140
|
+
13 new tests (4 setup + 6 launcher + 3 idempotency / overwrite-safety).
|
|
141
|
+
1539 → 1546 CLI tests. tsc clean. Version bump 7.1.6 → 7.1.7 to
|
|
142
|
+
keep CHANGELOG/version line in lockstep with master HEAD.
|
|
143
|
+
|
|
144
|
+
## 7.1.6 (2026-05-09)
|
|
145
|
+
|
|
146
|
+
**v7.1.6 — blank-repo benchmark report.** Docs-only PR. Captures
|
|
147
|
+
the day-1 experience of using `claude-autopilot` on a true `git init`
|
|
148
|
+
repo, end-to-end from "empty directory" to "feature shipped + tests
|
|
149
|
+
passing." Triggered by codex W5 from the autopilot product-direction
|
|
150
|
+
brainstorm.
|
|
151
|
+
|
|
152
|
+
**Headline:** ~17 minutes from `git init` to working MVP (small CLI,
|
|
153
|
+
Node 22 ESM, with a real Anthropic API call). Setup itself is ~6
|
|
154
|
+
seconds. Pre-commit static-rules hook caught accidentally-staged
|
|
155
|
+
secrets on day 1 (real-world value, not theoretical).
|
|
156
|
+
|
|
157
|
+
**Top friction points:** no `CLAUDE.md` scaffolded by `setup`;
|
|
158
|
+
deprecation banner prints on every commit; `.gitignore` doesn't
|
|
159
|
+
auto-add `node_modules/` or `.guardrail-cache/`; no `scaffold
|
|
160
|
+
--from-spec` verb.
|
|
161
|
+
|
|
162
|
+
**Top recommendations:** dedup deprecation banner (~30min ship),
|
|
163
|
+
auto-add cache dirs to `.gitignore` (~10min ship), auto-scaffold
|
|
164
|
+
starter `CLAUDE.md` on `setup` (~2-4hr ship). Fully-autonomous-from-
|
|
165
|
+
blank requires Option C (standalone daemon) work first — flagged as
|
|
166
|
+
v8 dependency.
|
|
167
|
+
|
|
168
|
+
Full report at `docs/benchmarks/2026-05-09-blank-repo.md`. Bumping
|
|
169
|
+
to 7.1.6 to keep CHANGELOG/version line in lockstep with master HEAD.
|
|
170
|
+
|
|
171
|
+
## 7.1.5 (2026-05-09)
|
|
172
|
+
|
|
173
|
+
**v7.1.5 — change-aware CI matrix.** CI infra optimization;
|
|
174
|
+
no application code change; no test additions.
|
|
175
|
+
|
|
176
|
+
The v7.0+ repo runs 6 GitHub Actions workflows on every PR
|
|
177
|
+
(bin smoke ×6 OS×Node + Test Node 22 + Delegance regression +
|
|
178
|
+
tarball check + apps/web typecheck/build/tests + RLS). Many of
|
|
179
|
+
those are irrelevant to PRs that only touch a different layer
|
|
180
|
+
(apps/web-only PRs don't need bin smoke; CLI-only PRs don't
|
|
181
|
+
need apps/web tests; docs-only PRs don't need anything).
|
|
182
|
+
|
|
183
|
+
Each workflow's `pull_request:` trigger now includes a `paths:`
|
|
184
|
+
filter — GitHub Actions skips the workflow entirely on PRs that
|
|
185
|
+
don't touch any matching file:
|
|
186
|
+
|
|
187
|
+
* `ci.yml` (Test Node 22), `bin-parity.yml` (bin smoke ×6),
|
|
188
|
+
`delegance-regression.yml`: triggered by CLI changes (`src/**`,
|
|
189
|
+
`bin/**`, `tests/**` for ci.yml, `scripts/**`, `presets/**`)
|
|
190
|
+
and conservative shared paths (`tsconfig*`, `package.json`,
|
|
191
|
+
`package-lock.json`, the workflow file itself).
|
|
192
|
+
* `web-tests.yml`: triggered by `apps/**`, `tsconfig*`,
|
|
193
|
+
`package.json`, `package-lock.json`, the workflow file.
|
|
194
|
+
* `db-tests.yml`: triggered by `db/**`, `tests/rls/**`,
|
|
195
|
+
`package.json`, `package-lock.json`, the workflow file.
|
|
196
|
+
* `npm-tarball-check.yml`: triggered by anything that affects
|
|
197
|
+
the published artifact (`package.json`, `.npmignore`,
|
|
198
|
+
`package-lock.json`, CLI source).
|
|
199
|
+
|
|
200
|
+
**Codex pass W4 safety net:** `push:` triggers (master + tag
|
|
201
|
+
pushes) deliberately have NO `paths:` filter. Every master merge
|
|
202
|
+
runs the full matrix, catching anything that slipped past the
|
|
203
|
+
PR-level filter (e.g. a config change in a directory we forgot
|
|
204
|
+
to enumerate). The PR-level filter is a latency optimization,
|
|
205
|
+
not a correctness boundary.
|
|
206
|
+
|
|
207
|
+
**Expected effect:** apps/web-only PRs (Phase 5.7-7.1.4 polish
|
|
208
|
+
shape) drop from ~12-15min CI wall clock to ~5-7min. Docs-only
|
|
209
|
+
PRs become a no-op CI run.
|
|
210
|
+
|
|
211
|
+
No package code change; bumping to 7.1.5 to keep CHANGELOG/
|
|
212
|
+
version-line in lockstep with master HEAD.
|
|
213
|
+
|
|
214
|
+
## 7.1.4 (2026-05-09)
|
|
215
|
+
|
|
216
|
+
**v7.1.4 — fix recurring PGRST002 RLS workflow flake.** CI infra
|
|
217
|
+
fix; no application code change; no test additions. Phase 5.1, 5.7,
|
|
218
|
+
and 7.1.3 all hit the same intermittent failure in the RLS negative
|
|
219
|
+
tests workflow:
|
|
220
|
+
|
|
221
|
+
```
|
|
222
|
+
PGRST002 — Could not query the database for the schema cache. Retrying.
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
PostgREST caches the database schema asynchronously AFTER
|
|
226
|
+
`supabase db reset` returns. The first SDK queries from the test
|
|
227
|
+
runner often arrive before the cache has finished warming, hard-
|
|
228
|
+
failing instead of waiting.
|
|
229
|
+
|
|
230
|
+
Fix: new "Wait for PostgREST schema cache to warm up" workflow step
|
|
231
|
+
between `Apply migrations` and `Run RLS tests`. Polls
|
|
232
|
+
`GET /rest/v1/` (PostgREST OpenAPI doc) up to 60s; succeeds on the
|
|
233
|
+
first response that parses as JSON with an `info` field. Times out
|
|
234
|
+
with diagnostic body if the cache doesn't warm.
|
|
235
|
+
|
|
236
|
+
Changes only `.github/workflows/db-tests.yml`. No package code
|
|
237
|
+
change, but bumping to 7.1.4 to keep version-line/CHANGELOG in
|
|
238
|
+
lockstep with master HEAD.
|
|
239
|
+
|
|
240
|
+
## 7.1.3 (2026-05-09)
|
|
241
|
+
|
|
242
|
+
**v7.1.3 — `/api/health/v7-readiness` deploy-verification endpoint.**
|
|
243
|
+
Hosted product (`apps/web/`) only. Operator-facing improvement; no
|
|
244
|
+
breaking changes; no migration.
|
|
245
|
+
|
|
246
|
+
* New `GET /api/health/v7-readiness` route, gated by
|
|
247
|
+
`Authorization: Bearer ${CRON_SECRET}` (constant-time compare via
|
|
248
|
+
`crypto.timingSafeEqual`).
|
|
249
|
+
* Verifies in one HTTP call:
|
|
250
|
+
- `check_membership_status` RPC is present + executable (closes
|
|
251
|
+
codex PR #141 PR-pass WARNING #3 — the Phase 6 migration must
|
|
252
|
+
be applied before deploying any v7.0+ web image, or every
|
|
253
|
+
org-scoped dashboard request returns `check_failed` within 60s).
|
|
254
|
+
- All 12 required env vars are set (Supabase, Stripe, WorkOS,
|
|
255
|
+
JWT/SSO/cookie secrets meeting ≥32-byte minimums where
|
|
256
|
+
applicable).
|
|
257
|
+
* Response: `200 {ok: true, totalChecks, passed, failed: 0, checks}`
|
|
258
|
+
on full pass; `503 {ok: false, ...}` with per-check
|
|
259
|
+
`{name, status, required, message?}` diagnostic on any required
|
|
260
|
+
failure.
|
|
261
|
+
* Operator runbook updated with `curl -fsSL` example for an
|
|
262
|
+
automated deploy-step gate.
|
|
263
|
+
* 8 new tests in `apps/web/__tests__/api/health/v7-readiness.test.ts`
|
|
264
|
+
covering happy path, missing env, too-short secret, RPC missing,
|
|
265
|
+
three auth-failure modes (no header, wrong secret, malformed
|
|
266
|
+
Bearer), and missing CRON_SECRET → 500.
|
|
267
|
+
* 613 → 621 web tests; 1536 CLI unchanged; tsc clean.
|
|
268
|
+
|
|
269
|
+
## 7.1.2 (2026-05-09)
|
|
270
|
+
|
|
271
|
+
**v7.1.2 — configurable membership-check TTL.** Hosted product
|
|
272
|
+
(`apps/web/`) only. Operator-facing improvement; no breaking changes;
|
|
273
|
+
no migration.
|
|
274
|
+
|
|
275
|
+
* New optional env var `MEMBERSHIP_CHECK_TTL_SECONDS` overrides the
|
|
276
|
+
default 60s `cao_membership_check` cookie TTL. Bounded `[1, 3600]`.
|
|
277
|
+
* Lower TTL = tighter revocation window (≤N seconds for a disabled
|
|
278
|
+
member to see 403 on next dashboard request) at the cost of more
|
|
279
|
+
`check_membership_status` RPC calls per dashboard navigation.
|
|
280
|
+
* Higher TTL = fewer RPC calls but extends the v7.0 documented
|
|
281
|
+
"≤60s revocation latency" guarantee.
|
|
282
|
+
* Invalid values (non-integer, < 1, > 3600) silently fall back to 60
|
|
283
|
+
with a one-shot warn (same pattern as the v7.1.1 PREVIOUS-secret
|
|
284
|
+
validator).
|
|
285
|
+
* 6 new tests in `cookie-hmac.test.ts` cover: default 60 when unset;
|
|
286
|
+
valid integer in range; non-numeric falls back; float falls back;
|
|
287
|
+
out-of-range (< 1, < 0, > 3600) falls back; signed cookie exp
|
|
288
|
+
respects the configured TTL via sign+verify roundtrip.
|
|
289
|
+
* 607 → 613 web tests; 1536 CLI unchanged; tsc clean.
|
|
290
|
+
|
|
291
|
+
## 7.1.1 (2026-05-09)
|
|
292
|
+
|
|
293
|
+
**v7.1.1 — dual-secret rotation for `MEMBERSHIP_CHECK_COOKIE_SECRET`.**
|
|
294
|
+
Hosted product (`apps/web/`) only. Operator-facing improvement;
|
|
295
|
+
no breaking changes; no migration; no new tests fail/skip.
|
|
296
|
+
|
|
297
|
+
* New optional env var `MEMBERSHIP_CHECK_COOKIE_SECRET_PREVIOUS`.
|
|
298
|
+
When set, `verifyMembershipCookie()` tries `CURRENT` first; on
|
|
299
|
+
signature mismatch, tries `PREVIOUS`. New cookies always sign
|
|
300
|
+
with `CURRENT`. Closes the v7.0 runbook follow-up where rotating
|
|
301
|
+
the secret invalidated every outstanding cookie at once = a
|
|
302
|
+
thundering-herd of `check_membership_status` RPC calls on every
|
|
303
|
+
active dashboard session.
|
|
304
|
+
* Operator rotation flow (4 steps) documented in `docs/v7/runbook.md`
|
|
305
|
+
+ `apps/web/.env.example`.
|
|
306
|
+
* `MEMBERSHIP_CHECK_COOKIE_SECRET_PREVIOUS` validation: same
|
|
307
|
+
≥32-byte minimum as `CURRENT`. Malformed/too-short `PREVIOUS`
|
|
308
|
+
is ignored with a one-shot warn — does not break the happy path.
|
|
309
|
+
* 5 new tests in `apps/web/__tests__/lib/middleware/cookie-hmac.test.ts`
|
|
310
|
+
cover: PREVIOUS verifies during rotation; new cookies sign with
|
|
311
|
+
CURRENT not PREVIOUS; forged-third-secret fails even with both;
|
|
312
|
+
PREVIOUS unset behaves identically to v7.1.0; PREVIOUS too short
|
|
313
|
+
is ignored without breaking CURRENT.
|
|
314
|
+
* 602 → 607 web tests; 1536 CLI unchanged; tsc clean.
|
|
315
|
+
|
|
316
|
+
## 7.1.0 (2026-05-09)
|
|
317
|
+
|
|
318
|
+
**v7.1 — symmetric ingest revocation closure.** Hosted product
|
|
319
|
+
(`apps/web/`) only. Closes the JWT-authenticated ingest gap that v7.0
|
|
320
|
+
Phase 6 explicitly deferred: collapses the per-request revocation
|
|
321
|
+
window from ≤15min (the JWT TTL) to **≤1 request** for org-scoped runs.
|
|
322
|
+
|
|
323
|
+
### apps/web — JWT-authenticated ingest membership re-check
|
|
324
|
+
|
|
325
|
+
- New helper `assertActiveMembership(claims)` in
|
|
326
|
+
`apps/web/lib/upload/membership-recheck.ts` — calls the existing
|
|
327
|
+
Phase 6 `check_membership_status` RPC and maps statuses to typed
|
|
328
|
+
errors. Personal runs short-circuit via `!claims.org_id`. Authority
|
|
329
|
+
is `claims.org_id`; the new `mint_status` claim is observability-
|
|
330
|
+
only (codex pass-1 CRITICAL #2 — closed bypass where a v7.0 token
|
|
331
|
+
could skip the check).
|
|
332
|
+
- New orchestrator `verifyTokenAndAssertRunMembership(token, runId,
|
|
333
|
+
supabase)` in `apps/web/lib/upload/auth.ts` — single chokepoint that
|
|
334
|
+
every JWT-authenticated ingest route calls. Combines (1) JWT shape
|
|
335
|
+
+ signature verify, (2) JWT.run_id ↔ route runId consistency,
|
|
336
|
+
(3) persisted runs lookup, (4) JWT.org_id ↔ run.organization_id
|
|
337
|
+
consistency (closes cross-org JWT replay AND personal-shortcut
|
|
338
|
+
bypass — codex pass-3 CRITICAL #2), and (5) per-request membership
|
|
339
|
+
re-check.
|
|
340
|
+
- `PUT /api/runs/:runId/events/:seq` and `POST /api/runs/:runId/finalize`
|
|
341
|
+
both call the orchestrator before any side-effect RPC / Storage
|
|
342
|
+
write. Disabled / inactive / no-membership returns 403; transient
|
|
343
|
+
RPC failure returns retryable 503; opaque 404 for run mismatches
|
|
344
|
+
(no enumeration leakage).
|
|
345
|
+
- `POST /api/upload-session` does its own pre-mint
|
|
346
|
+
`check_membership_status` RPC for org-scoped runs. Non-active
|
|
347
|
+
members get 403 `member_not_active` + `audit_events` row with
|
|
348
|
+
`action: 'ingest.mint_refused'`. No upload session created on
|
|
349
|
+
refusal. RPC failure → 503 (retryable parity with event-write/
|
|
350
|
+
finalize, codex pass-2 WARNING #2).
|
|
351
|
+
- JWT shape: `UploadTokenClaims.org_id` is now `string | null` (verify
|
|
352
|
+
normalizes wire-format `''` → `null`); new optional
|
|
353
|
+
`mint_status: 'active' | 'personal'` claim. `MintInput.mintStatus`
|
|
354
|
+
is required.
|
|
355
|
+
- `verifyUploadToken()` is preserved for the JWT-shape unit tests but
|
|
356
|
+
marked `@deprecated`. Routes under `app/api/runs/**` are blocked
|
|
357
|
+
from importing it directly via ESLint `no-restricted-imports`
|
|
358
|
+
(`apps/web/.eslintrc.json`). Defense-in-depth chokepoint
|
|
359
|
+
(codex pass-3 WARNING #5).
|
|
360
|
+
|
|
361
|
+
### Tests
|
|
362
|
+
|
|
363
|
+
- 32 new/modified web tests (566 → 598). Coverage: mint-time
|
|
364
|
+
membership snapshot (4), event-write re-check (8 — incl. ordering
|
|
365
|
+
spy + v7.0-shape regression), finalize re-check (4), helper unit
|
|
366
|
+
(10 — status enum + RPC error + personal shortcut + v7.0
|
|
367
|
+
back-compat), end-to-end disable-mid-session (1), identity invariant
|
|
368
|
+
(3), JWT shape (4 modified).
|
|
369
|
+
- `__tests__/_helpers/supabase-stub.ts` adds a
|
|
370
|
+
`check_membership_status` RPC handler that reads from the seeded
|
|
371
|
+
`memberships` table.
|
|
372
|
+
|
|
373
|
+
### Documentation
|
|
374
|
+
|
|
375
|
+
- `docs/v7/breaking-changes.md` — appended "v7.0 → v7.1" section
|
|
376
|
+
covering the rollout (no coordinated cutover; in-flight org-scoped
|
|
377
|
+
tokens enforce immediately).
|
|
378
|
+
- `apps/web/lib/dashboard/auth.ts` — extended the API-key audit
|
|
379
|
+
comment block with the new ingest-API JWT caller list and the
|
|
380
|
+
invariant.
|
|
381
|
+
|
|
382
|
+
### No SQL migration
|
|
383
|
+
|
|
384
|
+
Phase 6's `check_membership_status` RPC is reused verbatim. v7.1 ships
|
|
385
|
+
pure TypeScript + a single test-stub change.
|
|
386
|
+
|
|
387
|
+
## 7.0.0 (2026-05-09)
|
|
388
|
+
|
|
389
|
+
**v7.0 — hosted product MVP cutover.** First major bump since v6.0
|
|
390
|
+
(2026-04-22). Drops the engine-off code path, ships the autopilot.dev
|
|
391
|
+
hosted dashboard MVP, closes the last operational gap in dashboard
|
|
392
|
+
session revocation, and bumps the run-state schema_version to mark the
|
|
393
|
+
v7 era.
|
|
394
|
+
|
|
395
|
+
### Breaking changes (read this first)
|
|
396
|
+
|
|
397
|
+
See [docs/v7/breaking-changes.md](docs/v7/breaking-changes.md) for the
|
|
398
|
+
full migration checklist. The shortlist:
|
|
399
|
+
|
|
400
|
+
- **`--no-engine` removed.** Exits 1 with `invalid_config` if passed.
|
|
401
|
+
The engine is unconditionally on.
|
|
402
|
+
- **`CLAUDE_AUTOPILOT_ENGINE=off` removed (soft).** The env value is
|
|
403
|
+
ignored — engine still runs — but a one-shot stderr deprecation
|
|
404
|
+
banner fires + a `run.warning` event with code `engine_off_removed`
|
|
405
|
+
is emitted into the durable run log. Softer than `--no-engine`
|
|
406
|
+
because env vars in CI are sticky.
|
|
407
|
+
- **`ENGINE_DEFAULT_V6_0` and `ENGINE_DEFAULT_V6_1` exports removed**
|
|
408
|
+
from `src/core/run-state/resolve-engine.ts`. Direct importers must
|
|
409
|
+
replace with literal `true`. `resolveEngineEnabled()` itself is
|
|
410
|
+
preserved for source compatibility but always returns
|
|
411
|
+
`{enabled: true, source: 'default'}`.
|
|
412
|
+
- **`runEngineOff` callback on `runPhaseWithLifecycle` is preserved as
|
|
413
|
+
optional**, but the helper NEVER invokes it in v7.0. New call sites
|
|
414
|
+
should omit it.
|
|
415
|
+
- **`RUN_STATE_SCHEMA_VERSION` bumped 1 → 2.** v6.x runs are still
|
|
416
|
+
readable on v7 (`MIN_SUPPORTED` stays at 1). v6 binaries reading v7
|
|
417
|
+
runs hit a `corrupted_state` error with a "downgrade resume is not
|
|
418
|
+
supported" hint + `[1..1]` range.
|
|
419
|
+
- **`--engine` becomes a no-op shim** with one-shot per-process
|
|
420
|
+
stderr deprecation banner. Flag preserved so existing scripts don't
|
|
421
|
+
break; remove at your leisure (slated for v8).
|
|
422
|
+
|
|
423
|
+
### apps/web — real-time membership revocation
|
|
424
|
+
|
|
425
|
+
- New middleware extension on `/dashboard/**` and `/api/dashboard/**`.
|
|
426
|
+
Verifies the `cao_active_org` cookie + the HMAC-signed
|
|
427
|
+
`cao_membership_check` cookie cache; on miss/expired/wrong-identity,
|
|
428
|
+
calls the new `check_membership_status(p_org_id, p_user_id)` RPC
|
|
429
|
+
(1.5s timeout, fail-closed on error).
|
|
430
|
+
- Worst-case revocation window collapses from ≤1h (= access-token
|
|
431
|
+
expiry, the v6 baseline) to ≤60s (= cookie cache TTL).
|
|
432
|
+
- New env var: `MEMBERSHIP_CHECK_COOKIE_SECRET` (≥32 bytes;
|
|
433
|
+
`openssl rand -hex 32`). Lazy/runtime validation — `next build` in
|
|
434
|
+
CI without the secret won't crash; middleware fails closed at
|
|
435
|
+
request time if missing.
|
|
436
|
+
- Middleware runtime explicitly set to `nodejs` (was Edge default).
|
|
437
|
+
Required for `node:crypto` HMAC + `crypto.timingSafeEqual`.
|
|
438
|
+
- New page: `/access-revoked?reason=<code>` (Server Component, NOT
|
|
439
|
+
auth-gated, does NOT auto-forward authenticated users to avoid
|
|
440
|
+
redirect loops). Renders one of four reasons with a Sign-out form.
|
|
441
|
+
- Status → reason mapping table is the single source of truth (codex
|
|
442
|
+
pass-3 WARNING #5):
|
|
443
|
+
- `disabled` → `member_disabled`
|
|
444
|
+
- `inactive` / `invite_pending` → `member_inactive`
|
|
445
|
+
- `no_row` → `no_membership`
|
|
446
|
+
- RPC error / timeout → `check_failed`
|
|
447
|
+
- New SQL migration: `data/deltas/20260509200000_phase6_check_membership_rpc.sql`.
|
|
448
|
+
`SECURITY INVOKER` (NOT DEFINER per codex pass-2 WARNING #5 +
|
|
449
|
+
pass-3 WARNING #2 — `service_role` bypasses RLS already, so DEFINER
|
|
450
|
+
would only widen blast radius). REVOKE'd from PUBLIC/anon/authenticated;
|
|
451
|
+
GRANT EXECUTE to `service_role` only.
|
|
452
|
+
|
|
453
|
+
### Deferred to v7.1
|
|
454
|
+
|
|
455
|
+
- `MEMBERSHIP_CHECK_TTL_SECONDS` env var to let enterprise customers
|
|
456
|
+
tighten the 60s cache window.
|
|
457
|
+
- Server-side cache invalidation on `change_member_role` /
|
|
458
|
+
`disable_member` (would tighten role-change visibility from ≤60s to
|
|
459
|
+
immediate).
|
|
460
|
+
- Phase 2.2 ingest API JWT mint embeds `mint_membership_status` so
|
|
461
|
+
finalize/event endpoints can refuse disabled members within the
|
|
462
|
+
≤30min JWT TTL.
|
|
463
|
+
|
|
464
|
+
### Documentation
|
|
465
|
+
|
|
466
|
+
- New: `docs/v7/breaking-changes.md` — explicit v6 → v7 migration
|
|
467
|
+
checklist.
|
|
468
|
+
- New: `docs/v7/runbook.md` — production deployment runbook for the
|
|
469
|
+
hosted product (Vercel env vars grouped by purpose, WorkOS dashboard
|
|
470
|
+
hookups, Stripe products + webhook config, cron secret rotation,
|
|
471
|
+
first-deploy checklist).
|
|
472
|
+
- README — new "Hosted product (v7)" section pointing at autopilot.dev,
|
|
473
|
+
install snippet updated to `npm install -g
|
|
474
|
+
@delegance/claude-autopilot@latest`.
|
|
475
|
+
- `docs/v6/migration-guide.md` — appended v6.2.x → v7.0 section.
|
|
476
|
+
|
|
477
|
+
### CI / publishing
|
|
478
|
+
|
|
479
|
+
- `.github/workflows/ci.yml` now tags pushes matching
|
|
480
|
+
`v[0-9]+.[0-9]+.[0-9]+` (no suffix) with `--tag latest`; everything
|
|
481
|
+
else stays `--tag next`. `package.json` `publishConfig.tag` stays at
|
|
482
|
+
`next` as a hand-publish fallback only — the workflow is the source
|
|
483
|
+
of truth.
|
|
484
|
+
|
|
485
|
+
### Phase rollup (v7.0 cycle)
|
|
486
|
+
|
|
487
|
+
- **Phase 1** (schema/RLS) — multi-tenant Postgres + RLS policies for
|
|
488
|
+
the hosted product.
|
|
489
|
+
- **Phase 2.1** (Next.js scaffold) — `apps/web/` workspace, Vercel
|
|
490
|
+
deploy.
|
|
491
|
+
- **Phase 2.2** (ingest API) — signed-session JWT pipeline for
|
|
492
|
+
CLI → dashboard run uploads.
|
|
493
|
+
- **Phase 2.3** (CLI dashboard verbs) — `dashboard {login,logout,
|
|
494
|
+
status,upload}` + cli-auth loopback OAuth.
|
|
495
|
+
- **Phase 3** (Stripe) — entitlements, tiered pricing, webhook.
|
|
496
|
+
- **Phase 4** (dashboard UI + cli-auth hardening) — homepage, auth,
|
|
497
|
+
CSP-locked /cli-auth.
|
|
498
|
+
- **Phases 5.1-5.4** (org admin / WorkOS setup) — members, audit, cost,
|
|
499
|
+
per-tenant SSO connection management.
|
|
500
|
+
- **Phase 5.6** (WorkOS sign-in) — domain verification, SSO
|
|
501
|
+
enforcement chokepoint.
|
|
502
|
+
- **Phase 5.7** (admin lifecycle) — disable_member, sso_disconnect,
|
|
503
|
+
enable_member, last-owner race protection.
|
|
504
|
+
- **Phase 5.8** (lifecycle gap closure) — disabled-API-key
|
|
505
|
+
authorization fix + Vercel cron for cleanup_expired_sso_states.
|
|
506
|
+
- **Phase 6** (this release) — engine-off removal, schema bump, real-
|
|
507
|
+
time membership revocation, runbook, breaking-changes docs.
|
|
508
|
+
|
|
509
|
+
### Tests
|
|
510
|
+
|
|
511
|
+
- 1500+ existing CLI tests pass (engine-off tests collapsed to
|
|
512
|
+
always-on; net delta near zero).
|
|
513
|
+
- 510 → 566 web tests (+56 across cookie-hmac, check-membership, RPC
|
|
514
|
+
privilege grep, middleware revocation surface, response composition,
|
|
515
|
+
matcher, integration).
|
|
516
|
+
- tsc clean across both `@delegance/claude-autopilot` and
|
|
517
|
+
`@delegance/claude-autopilot-web`.
|
|
518
|
+
|
|
519
|
+
## 6.3.0-pre.13 (2026-05-09)
|
|
520
|
+
|
|
521
|
+
**v7.0 Phase 5.8 — Lifecycle gap closure.** Closes the two known gaps from Phase 5.7:
|
|
522
|
+
|
|
523
|
+
1. **Disabled-API-key authorization fix.** The Phase 2.2 `upload-session` and Phase 4 `artifact` routes had `let allowed = run.user_id === auth.userId` as the first authorization check. This allowed a member who got disabled AFTER creating an org-scoped run to keep uploading via their API key. Both routes now ALWAYS require active membership when `run.organization_id` is set, regardless of ownership. Personal (un-org-scoped) runs still use the ownership check. Regression test (`__tests__/api/dashboard/runs/disabled-api-key.test.ts`, 4 cases) locks this in.
|
|
524
|
+
2. **Vercel cron wiring for `cleanup_expired_sso_states` RPC.** New `GET /api/cron/cleanup-expired-sso-state` route (Vercel cron-secret-gated; rejects any caller without `Authorization: Bearer ${CRON_SECRET}`). Schedule `0 3 * * *` (daily 03:00 UTC) added to `vercel.json`. Calls the Phase 5.7 RPC with default args (24h state age, 30d event age). 4-test coverage (auth happy/fail paths + missing env).
|
|
525
|
+
|
|
526
|
+
New env: `CRON_SECRET` (Vercel sets automatically on production cron-attached projects; local-dev override via `.env.local`). Documented in `.env.example`.
|
|
527
|
+
|
|
528
|
+
Tests: 502 → 510 web. tsc clean.
|
|
529
|
+
|
|
530
|
+
## 6.3.0-pre.12 (2026-05-09)
|
|
531
|
+
|
|
532
|
+
**v7.0 Phase 5.7 — Admin lifecycle controls + session revocation.** Closes the lifecycle/revocation gap that Phases 5.4 and 5.6 explicitly deferred.
|
|
533
|
+
|
|
534
|
+
Three lifecycle controls:
|
|
535
|
+
|
|
536
|
+
1. **Admin disable-user** — `POST /api/dashboard/orgs/:orgId/members/:userId/disable` flips `memberships.status='disabled'`, captures `disabled_at`/`disabled_by`, deletes `auth.refresh_tokens` for the user. Existing access tokens expire ≤1h (Supabase default; documented in spec). Idempotent on already-disabled (returns `noop:true`, no duplicate audit, no duplicate revocation). Owner-protection (admin cannot disable owner) + last-owner guard.
|
|
537
|
+
2. **SSO disconnect cascade** — `apply_workos_event(connection.deleted)` set-based DELETE of refresh tokens for org members (status active OR disabled per codex plan-pass WARNING #1) with verified-domain emails. Audit metadata captures `cascadeRevokedUserCount` + `cascadeRevokedTokenCount` (no user IDs per plan-pass WARNING #5).
|
|
538
|
+
3. **`cleanup_expired_sso_states` RPC** — service-role only, called via `scripts/cleanup-expired-sso-state.ts` (no HTTP route per codex pass-1 CRITICAL #3). Phase 6 wires a cron.
|
|
539
|
+
|
|
540
|
+
Migration `data/deltas/20260509140000_phase5_7_lifecycle.sql`:
|
|
541
|
+
- ALTER `memberships.status` CHECK extended with `'disabled'` + `disabled_at`/`disabled_by` columns.
|
|
542
|
+
- 4 new SECURITY DEFINER RPCs (REVOKE FROM PUBLIC,anon,authenticated; GRANT TO service_role): `revoke_user_sessions`, `disable_member`, `enable_member`, `cleanup_expired_sso_states`.
|
|
543
|
+
- 2 RPC REPLACEs: `record_workos_sign_in` now refuses `member_disabled` / `member_inactive` / `invite_pending` (codex pass-2 WARNING #1); `apply_workos_event` adds set-based cascade DELETE on `connection.deleted`.
|
|
544
|
+
|
|
545
|
+
Surfaces:
|
|
546
|
+
- `POST /api/dashboard/orgs/:orgId/members/:userId/disable` (admin/owner-gated).
|
|
547
|
+
- `POST /api/dashboard/orgs/:orgId/members/:userId/enable` (admin/owner-gated, symmetric owner protection — only owners can re-enable owners per pass-2 WARNING #3).
|
|
548
|
+
- `GET /api/auth/sso/callback` modified to redirect 302 → `/login/sso?reason={member_disabled|member_inactive|invite_pending}` instead of returning 403 JSON.
|
|
549
|
+
|
|
550
|
+
`/login/sso` page renders 3 new banner reasons. `lib/dashboard/membership-guard.ts` MAP gains 10 new error codes. `package.json` 6.3.0-pre.11 → 6.3.0-pre.12.
|
|
551
|
+
|
|
552
|
+
Tests: 6 new test files (49 tests). disable.test.ts (11), enable.test.ts (4), webhook-cascade.test.ts (5), sso-signin-phase5-7.test.ts (4), phase5-7-privilege.test.ts (16 grep assertions), cleanup-expired-sso-state.test.ts (4), disabled-user-jwt.test.ts (4 — codex plan-pass CRITICAL #2 regression: proves disabled member with still-valid JWT can't access dashboard routes via 4 representative paths). 451 → 500 web tests. tsc clean.
|
|
553
|
+
|
|
554
|
+
**Known gaps (Phase 5.8):**
|
|
555
|
+
- API keys (Phase 2.3) are user-scoped not org-scoped; disabling membership in org A doesn't auto-revoke. Phase 5.8 will add a membership-active check in the API-key auth helper.
|
|
556
|
+
- Access-token expiry is the upper bound on revocation latency (≤1h Supabase default). Real-time revocation requires a request-time denylist + middleware (Phase 6).
|
|
557
|
+
- Cleanup script not yet cron-scheduled (Phase 6).
|
|
558
|
+
|
|
559
|
+
**Codex passes folded:** spec pass-1 (3C+5W+2N), pass-2 (1C+6W), plan-pass (2C+6W+2N). Highlights: dropped global API-key revocation due to cross-tenant blast (gap explicitly documented + deferred); cascade scope includes `'disabled'` per plan WARNING #1; audit metadata drops user IDs sample per plan WARNING #5; explicit disabled-user-JWT regression test proves spec's enforcement-audit table is correct.
|
|
560
|
+
|
|
561
|
+
## 6.3.0-pre.11 (2026-05-09)
|
|
562
|
+
|
|
563
|
+
**v7.0 Phase 5.6 — WorkOS SSO sign-in flow.** End-to-end SSO sign-in built on the Phase 5.4 foundation. Three sub-features that ship together (any subset is unusable):
|
|
564
|
+
|
|
565
|
+
- **Domain claim with DNS TXT challenge.** Admin-gated `POST/DELETE /api/dashboard/orgs/:orgId/sso/domains` + `POST .../verify`. Codex pass-1 CRITICAL #1 — `ever_verified` flag + unique partial index on `(lower(domain)) WHERE ever_verified=TRUE` blocks revoke-then-takeover by another org.
|
|
566
|
+
- **Sign-in flow.** Public `POST /api/auth/sso/start` (email-only — `orgId`-mode removed for anti-enumeration per codex pass-2 WARNING #8) → `GET /api/auth/sso/callback`. State binding (codex pass-2 CRITICAL #2): single canonical protocol — cookie holds HMAC-signed `{stateId, nonce}`, WorkOS state param = stateId only, server-stored `sso_authentication_states` row + atomic `consume_sso_authentication_state` RPC validates `(stateId, sha256(nonce))` + workos org/connection match. Session minted via admin-mediated magic link (codex pass-1 CRITICAL #4 — `verifyOtp` uses `token_hash` not `token`); session-user-mismatch verification revokes + audits + 500.
|
|
567
|
+
- **`sso_required` toggle.** Owner-only `PATCH /api/dashboard/orgs/:orgId/sso/required`. Asymmetric guard (codex pass-1 WARNING #7): turning OFF always allowed; turning ON requires active SSO. UI banner per codex pass-2 NOTE #2 explains the asymmetric state.
|
|
568
|
+
|
|
569
|
+
Single chokepoint enforcement: `enforceSsoRequired()` helper called from `/api/auth/callback` after every Google/magic-link `exchangeCodeForSession`. Sign-in surface registry table in spec documents the auth boundary.
|
|
570
|
+
|
|
571
|
+
Identity link (codex pass-1 WARNING #6): `workos_user_identities` table preserves `(workos_user_id, workos_organization_id) → user_id` mapping so future sign-ins re-use the same Supabase user even if IdP email changes. Magic link minted with the linked Supabase user's CURRENT email (looked up via `auth.admin.getUserById`), not the WorkOS profile email.
|
|
572
|
+
|
|
573
|
+
Migration `data/deltas/20260509120000_phase5_6_workos_signin.sql`:
|
|
574
|
+
- ALTER `organization_settings` ADD `sso_required BOOLEAN DEFAULT FALSE`.
|
|
575
|
+
- 3 new tables (`organization_domain_claims`, `sso_authentication_states`, `workos_user_identities`) with RLS + service-role grants.
|
|
576
|
+
- 6 SECURITY DEFINER RPCs: `claim_domain`, `mark_domain_verified`, `revoke_domain_claim`, `set_sso_required`, `consume_sso_authentication_state` (atomic UPDATE...RETURNING per codex plan-pass WARNING #5), `record_workos_sign_in` (verified-domain match required per codex pass-1 CRITICAL #3). All REVOKE FROM PUBLIC,anon,authenticated; GRANT TO service_role.
|
|
577
|
+
|
|
578
|
+
New deps: `tldts` (maintained PSL package per codex pass-1 NOTE #1).
|
|
579
|
+
New env vars: `SSO_STATE_SIGNING_SECRET` (≥32 bytes, module-load validation per codex plan-pass WARNING #4), `WORKOS_CLIENT_ID` (required by `workos.sso.getAuthorizationUrl`).
|
|
580
|
+
|
|
581
|
+
Helpers:
|
|
582
|
+
- `lib/dns/normalize-domain.ts` — `normalizeDomain` + `normalizeEmailDomain` (IDN, public-suffix-aware) used by every domain-touching surface.
|
|
583
|
+
- `lib/dns/verify-txt.ts` — `Promise.race`-bounded TXT lookup (codex pass-2 WARNING #4 — `node:dns/promises.resolveTxt` doesn't honor AbortSignal).
|
|
584
|
+
- `lib/auth/enforce-sso-required.ts` — sign-in surface chokepoint.
|
|
585
|
+
- `lib/workos/sign-in.ts` — `getSsoStateSigningSecret` (length-validated singleton), `signStateCookie` / `parseStateCookie` (HMAC), `buildAuthorizeUrl` (passes clientId per codex plan-pass CRITICAL #3).
|
|
586
|
+
- `lib/dashboard/membership-guard.ts` MAP gains 13 new error codes.
|
|
587
|
+
|
|
588
|
+
UI:
|
|
589
|
+
- `/login/sso` page + `<SsoSignInForm>` client component.
|
|
590
|
+
- `<SsoDomainsCard>` + `<SsoRequiredToggle>` embedded in admin SSO page (toggle renders even when SSO inactive per codex pass-1 WARNING #7).
|
|
591
|
+
|
|
592
|
+
Tests: 5 new test files (54 tests). domains.test.ts (11), required.test.ts (4), start.test.ts (5), callback.test.ts (10), sso-signin-privilege.test.ts (13), normalize-domain.test.ts (19), verify-txt.test.ts (6), enforce-sso-required.test.ts (7), sign-in.test.ts (11). Stub extensions for 7 new RPCs (`claim_domain`, `mark_domain_verified`, `revoke_domain_claim`, `set_sso_required`, `consume_sso_authentication_state`, `record_workos_sign_in`, `audit_append`) + 3 new tables + `auth.admin.{getUserById,createUser,generateLink,signOut}` + `auth.verifyOtp` mocks.
|
|
593
|
+
|
|
594
|
+
## 6.3.0-pre.10 (2026-05-08)
|
|
595
|
+
|
|
596
|
+
**v7.0 Phase 5.4 — WorkOS SSO setup.** Foundational SSO wiring: server-owned WorkOS organization correlation, admin-gated portal link, signature-verified lifecycle webhook, owner-gated disconnect.
|
|
597
|
+
|
|
598
|
+
New env vars: `WORKOS_API_KEY`, `WORKOS_WEBHOOK_SECRET`.
|
|
599
|
+
|
|
600
|
+
Migration `data/deltas/20260508180000_phase5_4_workos_setup.sql`:
|
|
601
|
+
- ALTER `organization_settings` adds 7 SSO columns (workos_organization_id, workos_connection_id, sso_connection_status, sso_connected_at, sso_disabled_at, sso_last_workos_event_at, sso_last_workos_event_id) + unique partial indexes on workos_organization_id and workos_connection_id.
|
|
602
|
+
- New `processed_workos_events` ledger with claim/lease/complete columns (status, processing_started_at, locked_until, attempt_count) — enables idempotent webhook retry.
|
|
603
|
+
- Three SECURITY DEFINER RPCs (REVOKE FROM PUBLIC,anon,authenticated; GRANT service_role): `record_sso_setup_initiated` (admin-gated, raises `workos_org_already_bound` if a different active WorkOS org would be swapped), `apply_workos_event` (claim/lease/complete + lifecycle ordering via sso_last_workos_event_at + state transition + audit append in one txn — connection.deleted always wins over older updated), `disable_sso_connection` (owner-only soft-disable).
|
|
604
|
+
|
|
605
|
+
Surfaces:
|
|
606
|
+
- `POST /api/dashboard/orgs/:orgId/sso/setup` — 6-step admin-gated portal-link sequence. Server-creates the WorkOS org via `externalId=orgId` so correlation is server-owned; idempotent on retry. Returns `{ portalUrl, workosOrganizationId }` with `Cache-Control: private, no-store`.
|
|
607
|
+
- `DELETE /api/dashboard/orgs/:orgId/sso` — owner-only two-step disconnect (RPC sets status='disabled'; route then calls `workos.sso.deleteConnection`; failure non-fatal — eventual `connection.deleted` webhook clears connection_id via apply_workos_event).
|
|
608
|
+
- `POST /api/workos/webhook` — runtime nodejs, raw `req.text()` body, HMAC verified via `workos.webhooks.constructEvent` (5-min tolerance). Maps connection.activated/deactivated/deleted (and dsync.* variants) through apply_workos_event RPC. 401 on bad signature, 500 on RPC error so WorkOS retries.
|
|
609
|
+
- `/dashboard/admin/sso` page (owner-only, 404 otherwise) + `<SsoSetupCard>` client component.
|
|
610
|
+
|
|
611
|
+
Helpers:
|
|
612
|
+
- `lib/workos/client.ts` — lazy `getWorkOS()` singleton + async `verifyWorkOSSignature()` wrapper (returns `{ok, event} | {ok:false, reason}`).
|
|
613
|
+
- `lib/dashboard/membership-guard.ts` MAP gains `workos_org_already_bound: 422`, `bad_workos_org_id: 422`, `webhook_signature_invalid: 401`.
|
|
614
|
+
|
|
615
|
+
Sidebar: admin layout adds "SSO" link.
|
|
616
|
+
|
|
617
|
+
Tests: 5 new test files (40 tests). setup.test.ts (11), disconnect.test.ts (6), webhook.test.ts (6), client.test.ts (6), sso-privilege.test.ts (11 — REVOKE/GRANT, SECURITY DEFINER, schema-qualified refs, claim/lease/complete columns, lifecycle handlers). Stub extensions for `record_sso_setup_initiated`, `apply_workos_event`, `disable_sso_connection` RPCs + `processed_workos_events` table behavior.
|
|
618
|
+
|
|
619
|
+
## 6.3.0-pre.9 (2026-05-08)
|
|
620
|
+
|
|
621
|
+
**v7.0 Phase 5.3 — Org switcher.** Replaces the "first admin/owner membership" hack across `/dashboard` + `/dashboard/admin/*` with a real org switcher backed by an HTTP-only cookie.
|
|
622
|
+
|
|
623
|
+
- New: `POST /api/dashboard/active-org` sets `cao_active_org` cookie (HttpOnly Secure SameSite=Lax 14d). Body `{ orgId }` validates caller is active member; `{ orgId: null }` clears.
|
|
624
|
+
- New: `lib/dashboard/active-org.ts` exports `resolveActiveOrg(svc, userId)` (cookie → first-membership fallback) and `listActiveOrgs(svc, userId)` (with names + roles).
|
|
625
|
+
- New: `<OrgSwitcher>` client component in dashboard sidebar (only shows when caller has 2+ active memberships).
|
|
626
|
+
- Modified: `/dashboard/layout.tsx`, `/dashboard/page.tsx`, `/dashboard/billing/page.tsx`, `/dashboard/admin/layout.tsx` all now consult `resolveActiveOrg` instead of `memberships[0]`.
|
|
627
|
+
- Admin layout cookie restricted to admin/owner orgs — cannot escalate a member-only org into the admin surface.
|
|
628
|
+
- 11 new tests (6 backend route + 5 helper). Stale-cookie test asserts the membership check rejects removed members.
|
|
629
|
+
- No new env vars, no migration.
|
|
630
|
+
|
|
631
|
+
## 6.3.0-pre.8 (2026-05-08)
|
|
632
|
+
|
|
633
|
+
**v7.0 Phase 5.2 — Audit log viewer + cost reporting (CSV export).** Closes the audit half of the original Phase 5 scope.
|
|
634
|
+
|
|
635
|
+
New surfaces:
|
|
636
|
+
1. `/dashboard/admin/audit` — server-rendered, role-gated. Paginated audit log with single-action filter, cursor-based pagination, prev_hash/this_hash exposed for chain-replay debugging.
|
|
637
|
+
2. `/dashboard/admin/cost` — owner/admin only. Per-user cost breakdown for a YYYY-MM period, default current UTC month. Download CSV button.
|
|
638
|
+
|
|
639
|
+
3 new API routes (all under `/api/dashboard/orgs/:orgId/`):
|
|
640
|
+
- `GET /audit` — list_audit_events RPC; cursor decode + ISO since/until validation route-side; nextCursor base64-re-encoded
|
|
641
|
+
- `GET /cost` — org_cost_report RPC; period response normalized to `{ since, until, sinceTs, untilTs }`
|
|
642
|
+
- `GET /cost.csv` — same RPC, formats as RFC 4180 CSV (CRLF, UTF-8 no BOM, double-quote escape); filename `cost-<orgId>-<since>-<until>.csv` (no org-name interpolation)
|
|
643
|
+
|
|
644
|
+
2 SECURITY DEFINER Postgres RPCs in `data/deltas/20260508160000_phase5_2_audit_cost_rpcs.sql`:
|
|
645
|
+
- `list_audit_events` — keyset pagination on `(occurred_at DESC, id DESC)` with index `audit_events_org_keyset_idx`. LEFT JOIN auth.users for actor_email.
|
|
646
|
+
- `org_cost_report` — aggregates runs by user_id; coalesce-in-coalesce-out NULL safety; LEFT JOIN auth.users.
|
|
647
|
+
- Both `SECURITY DEFINER SET search_path = public, audit, auth, pg_temp`. `REVOKE ALL FROM PUBLIC, anon, authenticated; GRANT EXECUTE TO service_role` only.
|
|
648
|
+
|
|
649
|
+
3 new helpers:
|
|
650
|
+
- `lib/dashboard/period.ts` — YYYY-MM parser converting `since/until` to (sinceTs, untilTs exclusive). UTC. Default to current month when both null.
|
|
651
|
+
- `lib/dashboard/cost-csv.ts` — RFC 4180 encoder + safe filename builder (validates against `[a-zA-Z0-9._-]`).
|
|
652
|
+
- `lib/dashboard/audit-cursor.ts` — base64 JSON cursor encode/decode + ISO 8601 UTC validator.
|
|
653
|
+
|
|
654
|
+
Codex passes folded:
|
|
655
|
+
- Spec pass 1 (3 CRITICAL: cost period semantics, runs.created_at vs nonexistent occurred_at, CSV filename injection + 7 WARNING + 2 NOTE)
|
|
656
|
+
- Spec pass 2 (2 CRITICAL: filename surface contradiction, deployment target clarification + 7 WARNING: cursor validation in route, error contract, JSON shape unification, audit period parsing, cache headers + 2 NOTE)
|
|
657
|
+
- Plan pass (2 CRITICAL: schema-qualify audit.events + SET search_path lock, runs.cost_usd ordering guard + 6 WARNING)
|
|
658
|
+
|
|
659
|
+
All routes return `Cache-Control: private, no-store`. All pages declare `force-dynamic`.
|
|
660
|
+
|
|
661
|
+
39 new tests (Phase 5.1's 237 → **276 web tests**). Helper unit tests: 21. Backend route tests: 32. Integration: 4. Privilege: 7 (incl. SECURITY DEFINER + search_path + schema-qualification + Phase 4 dependency check).
|
|
662
|
+
|
|
663
|
+
**Operator follow-up:** run `/migrate` to apply `20260508160000_phase5_2_audit_cost_rpcs.sql` against dev → QA → prod.
|
|
664
|
+
|
|
665
|
+
## 6.3.0-pre.7 (2026-05-08)
|
|
666
|
+
|
|
667
|
+
**v7.0 Phase 5.1 — Members management + RBAC enforcement.** First Org-tier user-visible surface. After Phase 5.1 ships, an Org-tier admin (small/mid Stripe plans, or free org owner) can actually manage their team.
|
|
668
|
+
|
|
669
|
+
New surfaces:
|
|
670
|
+
1. `/dashboard/admin/members` — server-rendered, role-gated. Lists active members with email, role dropdown per row, remove button. Embedded invite form (email + role).
|
|
671
|
+
2. `/dashboard/admin/settings` — owner-only. Edit org name (1..100 chars).
|
|
672
|
+
3. `/dashboard/admin/layout.tsx` — sidebar nav. 404s if signed-out OR caller has no admin/owner membership in any org.
|
|
673
|
+
4. Sidebar "Admin" link in `/dashboard/layout.tsx` — visible only when caller has admin/owner membership somewhere.
|
|
674
|
+
|
|
675
|
+
5 new API routes (all under `/api/dashboard/orgs/`):
|
|
676
|
+
- `GET /:orgId/members` — list active members + emails.
|
|
677
|
+
- `POST /:orgId/members/invite` — admin/owner invites by email; reactivates removed members.
|
|
678
|
+
- `PATCH /:orgId/members/:userId` — change role (matrix-gated).
|
|
679
|
+
- `DELETE /:orgId/members/:userId` — soft-remove (admin: members only; owner: any).
|
|
680
|
+
- `PATCH /:orgId` — owner updates org name.
|
|
681
|
+
|
|
682
|
+
4 SECURITY DEFINER Postgres RPCs in `data/deltas/20260508140000_phase5_1_member_rpcs.sql`:
|
|
683
|
+
- `invite_member`, `change_member_role`, `remove_member`, `update_org_name`.
|
|
684
|
+
- Each acquires `FOR UPDATE` lock on `memberships` rows for the org BEFORE re-reading caller role + authorizing. Atomically count + write + audit.append in one transaction.
|
|
685
|
+
- `REVOKE ALL FROM PUBLIC, anon, authenticated; GRANT EXECUTE TO service_role;` — direct authenticated RPC calls fail with `42501 permission denied`.
|
|
686
|
+
- Codex pass 1 CRITICAL (TOCTOU last-owner race) + codex pass 2 CRITICAL (caller-spoofing + lock-before-authorize) + codex plan-pass CRITICAL (`update_org_name` NULL-role guard) — all folded.
|
|
687
|
+
|
|
688
|
+
CSRF: `assertSameOrigin(req)` on every mutating route (5 of 5).
|
|
689
|
+
|
|
690
|
+
Audit events: `org.member.invited`, `org.member.role_changed`, `org.member.removed`, `org.settings.updated`. Written by RPCs only — never in route code.
|
|
691
|
+
|
|
692
|
+
35 backend tests + 4 integration tests = 39 new web tests. Existing 180 still pass. Concurrency test (#31) proves serial last-owner check via stub mutex; static migration test (#31b) proves REVOKE/GRANT.
|
|
693
|
+
|
|
694
|
+
No new env vars. No CLI changes.
|
|
695
|
+
|
|
696
|
+
**Operator follow-up:** run `/migrate` to apply `20260508140000_phase5_1_member_rpcs.sql` against dev → QA → prod.
|
|
697
|
+
|
|
698
|
+
## 6.3.0-pre.6 (2026-05-08)
|
|
699
|
+
|
|
700
|
+
**v7.0 Phase 4 — Free tier dashboard UI + `/cli-auth` page + public share-by-URL.** Closes the loop on Phase 3's commercially load-bearing 402: free users now SEE "you've used 87/100 this month" and one click away from upgrading.
|
|
701
|
+
|
|
702
|
+
Eight new UI surfaces:
|
|
703
|
+
1. `/dashboard` overview — auth-gated, server-rendered. Run count this month, cost MTD, current plan, recent runs (5), 30-day cost chart (inline SVG, no library).
|
|
704
|
+
2. `/dashboard/runs` — paginated list (20/page, offset-based via `range()`).
|
|
705
|
+
3. `/dashboard/runs/[runId]` — detail page with manifest-driven event replay (lazy chunk loading, hard 1000-event cap for MVP), state inspector, cost breakdown, visibility toggle.
|
|
706
|
+
4. `/dashboard/billing` — current plan/caps/usage; Upgrade/Manage subscription buttons that POST to Phase 3 endpoints.
|
|
707
|
+
5. `/dashboard/billing/success` — post-checkout polling page.
|
|
708
|
+
6. `/cli-auth` (DEFERRED FROM 2.3) — completes the CLI dashboard login flow. Server-validates `cb` (loopback only, port 56000-56050) + `nonce` (32 hex). Authenticated user clicks "Sign in CLI" → mints API key via `/api/dashboard/api-keys/mint` → POSTs to loopback with `mode: 'cors'`. CLI loopback listener (Phase 2.3, EXTENDED) gains OPTIONS preflight + `Access-Control-Allow-Origin` matching the configured `AUTOPILOT_PUBLIC_BASE_URL`.
|
|
709
|
+
7. `/runs/[runShareId]` — public share-by-URL. Server-side anon Supabase client (NOT createBrowserClient). Read-only events replay + state.
|
|
710
|
+
8. `PATCH /api/dashboard/runs/:runId/visibility` — narrow owner-only endpoint with explicit owner check + assertSameOrigin guard. NOT direct UPDATE on runs from client.
|
|
711
|
+
|
|
712
|
+
Plus required infrastructure:
|
|
713
|
+
- **Authorized signed-URL minter** at `GET /api/dashboard/runs/:runId/artifact?kind=manifest|chunk|state[&seq=N]` — verifies owner OR `visibility='public'` BEFORE calling `storage.from('run-uploads').createSignedUrl(path, 60)`. Bucket stays fully private. Chunk seq bounded against `upload_session_chunks` count → 422 on out-of-range. Path derived ONLY from DB-trusted values via `chunkPath()` helper.
|
|
714
|
+
- **assertSameOrigin guard** on cookie-authenticated mutating routes (mint, revoke, visibility, checkout, portal). Compares `Origin` header against `loadPublicBillingConfig().AUTOPILOT_PUBLIC_BASE_URL`. Skipped when API-key bearer auth is used.
|
|
715
|
+
- **`/cli-auth` security headers via middleware** — `Cache-Control: no-store`, `Referrer-Policy: no-referrer`, `X-Frame-Options: DENY`, and CSP including exact `connect-src 'self' http://127.0.0.1:* http://localhost:*` for the loopback POST. Headers set in middleware.ts (Server Component `headers()` reads request, not response).
|
|
716
|
+
- **Finalize handler** persists sanitized `cost_usd`/`duration_ms`/`run_status` from CLI state.json. TS-side bounds + enum validation BEFORE DB UPDATE so a buggy CLI doesn't trip the new CHECK and bring down the whole UPDATE. Wrapped in try/catch for graceful degradation during the rollout window before `/migrate` applies the new columns. Display-only — labeled "Reported by CLI", no entitlement/billing logic reads them.
|
|
717
|
+
- **safeRedirect** allowlist accepts `/cli-auth` AND preserves the full `?cb=&nonce=` query string when bouncing through Supabase Auth.
|
|
718
|
+
- **Env unification** — `AUTOPILOT_PUBLIC_BASE_URL` is now the canonical name everywhere (web AND CLI). The CLI's older `AUTOPILOT_DASHBOARD_BASE_URL` is a deprecated alias (warn-once on use).
|
|
719
|
+
|
|
720
|
+
Component breakdown: `<RunListItem>` server, `<EventReplay>` client (manifest-driven, lazy chunks, 1000-event cap), `<StateInspector>` client (recursive tree, no JSON-tree library), `<CostChart>` server (inline SVG, ~80 LOC), `<PlanCard>` server with client `<UpgradeButtons>`/`<ManageSubscriptionButton>`, `<VisibilityToggle>` client (optimistic update + confirmation modal).
|
|
721
|
+
|
|
722
|
+
30+ new tests: 6 visibility (incl. CSRF) + 14 artifact (9 base + 3 RLS + 2 seq-bounds) + 1 finalize-persists + 9 sanitize + 1 finalize-malformed-status + 3 cli-auth validate + 4 cli-auth headers + 1 cli-auth redirect round-trip + 2 cost-chart + 6 dashboard-pages integration + 4 origin-mismatch (mint/revoke/checkout/portal) + 1 CLI OPTIONS preflight = ~52 added tests across web + CLI.
|
|
723
|
+
|
|
724
|
+
**Migration:** `data/deltas/20260508120000_phase4_runs_metadata.sql` — `runs.cost_usd NUMERIC(12,4)`, `duration_ms INTEGER`, `run_status TEXT` with CHECK enum; cost-chart partial indexes (user vs org); `runs_select_public` policy for anon/authenticated on `visibility='public'`; column-level GRANT to anon (only safe public columns, NOT `SELECT *`). Operator runs `/migrate` post-merge BEFORE the code deploy fully exercises the new columns; finalize handler graceful-drops if columns missing.
|
|
725
|
+
|
|
726
|
+
**No new env vars** — all reuse Phase 2.1 + 2.3 + 3 vars. Consider standardizing `AUTOPILOT_PUBLIC_BASE_URL` in any custom CLI deployments (Phase 2.3's `AUTOPILOT_DASHBOARD_BASE_URL` still works but logs deprecation warning).
|
|
727
|
+
|
|
728
|
+
**Operator follow-ups:**
|
|
729
|
+
- Run `/migrate` to apply `data/deltas/20260508120000_phase4_runs_metadata.sql`.
|
|
730
|
+
- (Optional) Configure Stripe Customer Portal in dashboard if not already (allows cancellation, payment update from `/dashboard/billing`).
|
|
731
|
+
|
|
732
|
+
## 6.3.0-pre.5 (2026-05-08)
|
|
733
|
+
|
|
734
|
+
**v7.0 Phase 3 — Stripe entitlement enforcement.** Makes the cryptographic credibility boundary commercially load-bearing: every engine-on `autopilot --mode full` upload is now gated on the org's monthly run cap and retained-storage cap.
|
|
735
|
+
|
|
736
|
+
Five new surfaces:
|
|
737
|
+
1. `POST /api/stripe/webhook` — `runtime='nodejs'`, raw-body signature verification, claim/lease/complete idempotency (status='processing' + locked_until+attempt_count, stale leases reclaimed atomically), `last_stripe_event_at` watermark for out-of-order delivery. Handles `checkout.session.completed`, `customer.subscription.updated`, `customer.subscription.deleted`, `invoice.payment_failed`.
|
|
738
|
+
2. `POST /api/dashboard/billing/checkout` — Supabase session auth with role check (owner/admin), Stripe Checkout Session create with `idempotencyKey='${orgId}:${tier}:${interval}'` and customer reuse via `billing_customers.stripe_customer_id`. Returns `{ url }`.
|
|
739
|
+
3. `POST /api/dashboard/billing/portal` — same auth, returns Stripe Customer Portal session URL.
|
|
740
|
+
4. `POST /api/upload-session` — Phase 2.2 endpoint extended with entitlement gate between ownership pass and JWT mint. Returns 402 `{ error: 'limit_reached', limit, current, max, upgrade_url }`. New body field `expectedBytes` from `fs.stat(events.ndjson).size` for storage cap preflight (catches the 4.9-of-5GiB user uploading 20GiB pattern).
|
|
741
|
+
5. CLI uploader catches 402 → throws typed `UploadLimitError`. Auto-upload entry point (`auto-upload.ts`) detects, prints friendly message, returns `reason='limit-reached'` without bubbling. Run's exit code preserved.
|
|
742
|
+
|
|
743
|
+
Pricing tiers (per v7.0 MVP): Free (100 runs/mo, 5 GiB, $0), Org Small (1000, 50 GiB, $99/mo or $990/yr), Org Mid (10000, 500 GiB, $499/mo or $4990/yr), Enterprise (NULL caps = no enforcement, sales-led). PLAN_MAP keys by `(tier, interval)` for all 4 price IDs. Free organizations DO exist and share an org-level cap (NOT each-user-gets-personal-cap) — seeded by AFTER INSERT trigger on `organizations`.
|
|
744
|
+
|
|
745
|
+
Run-count cap uses STRICT `>` comparison (the runs row already exists when /api/upload-session is called, so count=100 is the 100th and is allowed; reject only at 101+). Storage cap = `sum_retained_bytes(orgId, userId, 90 days)` SQL aggregate, with `expectedBytes` preflight at mint time.
|
|
746
|
+
|
|
747
|
+
`loadBillingConfig()` validates Stripe env at runtime with zod; `loadPublicBillingConfig()` only reads `AUTOPILOT_PUBLIC_BASE_URL` so missing Stripe env doesn't break the upload-session entitlement gate. Subscription state grace logic: canceled-and-past-period-end → free; cancel_at past → free; payment_failed_at older than 7 days → free.
|
|
748
|
+
|
|
749
|
+
31 new tests: 8 webhook + 4 checkout + 3 portal + 10 checkEntitlement + 2 plan-map + 2 upload-session integration (web) + 3 CLI 402 handling.
|
|
750
|
+
|
|
751
|
+
**Migration:** `data/deltas/20260507180000_phase3_billing.sql` — `billing_customers`, augments `entitlements` with Stripe state + caps + watermark, `stripe_webhook_events` with claim/lease, `personal_entitlements`, augments `runs` with `total_bytes`+`deleted_at`, `sum_retained_bytes` + `count_runs_this_month` + `seed_free_entitlements` SECURITY DEFINER RPCs/trigger. CHECK constraint enforces free/small/mid have explicit caps and enterprise has NULLs. Backfills existing rows BEFORE adding the constraint. Operator runs `/migrate` post-merge.
|
|
752
|
+
|
|
753
|
+
**New env vars (Vercel):**
|
|
754
|
+
- `STRIPE_SECRET_KEY`
|
|
755
|
+
- `STRIPE_WEBHOOK_SECRET`
|
|
756
|
+
- `STRIPE_PRICE_SMALL_MONTHLY`
|
|
757
|
+
- `STRIPE_PRICE_SMALL_YEARLY`
|
|
758
|
+
- `STRIPE_PRICE_MID_MONTHLY`
|
|
759
|
+
- `STRIPE_PRICE_MID_YEARLY`
|
|
760
|
+
|
|
761
|
+
**Operator follow-ups:**
|
|
762
|
+
- Run `/migrate` to apply the migration through dev → QA → prod.
|
|
763
|
+
- Set the 6 Stripe env vars above in Vercel.
|
|
764
|
+
- Configure Stripe webhook in dashboard pointing at `https://autopilot.dev/api/stripe/webhook` and subscribe to: `checkout.session.completed`, `customer.subscription.updated`, `customer.subscription.deleted`, `invoice.payment_failed`.
|
|
765
|
+
- Create Stripe Products + 4 Prices: small ($99/mo + $990/yr), mid ($499/mo + $4990/yr).
|
|
766
|
+
|
|
767
|
+
## 6.3.0-pre.4 (2026-05-07)
|
|
768
|
+
|
|
769
|
+
**v7.0 Phase 2.3 — CLI dashboard verbs + auto-upload at run.complete.** Connects v6.x autopilot pipeline to Phase 2.2's ingest API.
|
|
770
|
+
|
|
771
|
+
Four new CLI verbs: `claude-autopilot dashboard {login,logout,status,upload}`. After `dashboard login`, every engine-on `autopilot --mode full` automatically uploads to autopilot.dev when `run.complete` fires. Login flow uses 128-bit nonce-bound loopback HTTP listener (port 56000-56050) with strict server-side `callbackUrl` validation, `crypto.timingSafeEqual` nonce verify, and atomic config write at `~/.claude-autopilot/dashboard.json` (mode 0600, dir 0700). Snapshot-before-upload (events.ndjson + state.json copied to `<runDir>/.upload-snapshot/` with stat-before/stat-after defense) so streaming writers can't tear the chunk reads. Auto-upload is foreground await with SIGINT/AbortController; failure prints `claude-autopilot dashboard upload <runId>` resume command and never overrides the run's exit code. Empty events.ndjson skips upload cleanly. Opt out per-run with `--no-upload` or globally with `CLAUDE_AUTOPILOT_UPLOAD=off`.
|
|
772
|
+
|
|
773
|
+
Web side adds four new endpoints under `/api/dashboard/`: `POST api-keys/mint` (Supabase session auth → atomic `mint_api_key_with_nonce` RPC, 128-bit `clp_<64-hex>` keys, SHA256-hashed at rest, 12-char prefix display), `POST api-keys/revoke` (idempotent, ownership-scoped), `GET me` (memberships + lastUploadAt), `GET runs/:runId/upload-session` (resume in-flight session). Centralized `authViaApiKey()` helper in `apps/web/lib/dashboard/auth.ts` looks up keys by deterministic hash with `eq + maybeSingle` (O(1)) and filters revoked keys. Strict `validateCallbackUrl()` regex restricts callbacks to `http://(127.0.0.1|localhost):560(0[0-9]|[1-4][0-9]|50)/cli-callback` with double-parse defense.
|
|
774
|
+
|
|
775
|
+
CLI ↔ web parity guaranteed by shared fixtures: `apps/web/lib/upload/__fixtures__/{chain-vectors,state-canonicalization-vectors}.json` are loaded byte-for-byte by `tests/dashboard/parity.test.ts`. Identical chain-root and JCS-canonical sha256 in both directions.
|
|
776
|
+
|
|
777
|
+
**Migration:** `data/deltas/20260507120000_phase2_3_api_keys.sql` — adds `api_keys` (RLS, key_hash regex check, prefix_display regex check), `api_key_mint_nonces` (RLS, service-role-only), `expire_mint_nonces()` SECURITY DEFINER RPC, and the atomic `mint_api_key_with_nonce()` SECURITY DEFINER RPC that fuses sweep + dedup-check + insert key + insert nonce in a single transaction. Operator runs `/migrate` post-merge.
|
|
778
|
+
|
|
779
|
+
**New env vars:**
|
|
780
|
+
- Web (Vercel): `NEXT_PUBLIC_AUTOPILOT_BASE_URL` — used by the `cli-auth` web page (deferred to Phase 4 dashboard UI) to display loopback callback URL.
|
|
781
|
+
- CLI: `AUTOPILOT_DASHBOARD_BASE_URL` (defaults `https://autopilot.dev`); `CLAUDE_AUTOPILOT_HOME` (defaults `~/.claude-autopilot`); `CLAUDE_AUTOPILOT_UPLOAD=off` opts out of auto-upload; `CLAUDE_AUTOPILOT_UPLOAD_RETRY_MS` overrides retry backoff (test seam).
|
|
782
|
+
|
|
783
|
+
**Operator follow-ups:**
|
|
784
|
+
- Run `/migrate` to apply the migration through dev → QA → prod.
|
|
785
|
+
- Set `NEXT_PUBLIC_AUTOPILOT_BASE_URL=https://autopilot.dev` in Vercel.
|
|
786
|
+
- Implement the `/cli-auth` web page in Phase 4 dashboard UI. The page must mint via `POST /api/dashboard/api-keys/mint` then POST `{ apiKey, fingerprint, accountEmail, nonce }` to the loopback callback (URL passed in `?cb=`). Phase 2.3 tests use a mock handler that simulates this flow end-to-end.
|
|
787
|
+
|
|
788
|
+
## 6.3.0-pre.3 (2026-05-07)
|
|
789
|
+
|
|
790
|
+
**v7.0 Phase 2.2 — ingest API + tamper-evident events.** First server endpoints in the repo. Three routes (`POST /api/upload-session`, `PUT /api/runs/:runId/events/:seq`, `POST /api/runs/:runId/finalize`) implement signed-session uploads with hash-chain verification and idempotent finalize. Per-chunk immutable Storage objects, DB row lock + unique constraint + Storage `upsert: false` triple-defense against concurrent corruption. Two-phase write ordering with `upload_session_chunks.status` for crash recovery. Dedicated `UPLOAD_SESSION_JWT_SECRET` (HS256, 15-min TTL, full claim hardening). RFC 8785 (JCS) state canonicalization. 38 new tests across upload-session, events-chunk, finalize, hash-chain vectors, JCS vectors, JWT, and storage helpers.
|
|
791
|
+
|
|
792
|
+
**Migration:** `data/deltas/20260507000000_phase2_2_ingest.sql` — adds `upload_session_chunks` table, augments `upload_sessions` with `next_expected_seq` + `chain_tip_hash`, adds `runs.state_sha256` + `runs.events_index_path`, partial unique index on `upload_sessions(run_id) WHERE consumed_at IS NULL`, CHECK constraints on hash-format columns, plus `claim_chunk_slot` and `mark_chunk_persisted` SECURITY DEFINER RPCs. Operator runs via `/migrate` post-merge.
|
|
793
|
+
|
|
794
|
+
**New env var:** `UPLOAD_SESSION_JWT_SECRET` — set in Vercel + local `.env.local`. Generate with `openssl rand -hex 32`. NOT shared with `SUPABASE_JWT_SECRET`.
|
|
795
|
+
|
|
796
|
+
**Storage bucket:** `run-uploads` — operator one-time setup in the Supabase project (private; service-role-only writes).
|
|
797
|
+
|
|
798
|
+
## 6.3.0-pre.2 (2026-05-07)
|
|
799
|
+
|
|
800
|
+
**v7.0 Phase 2.1 — Next.js scaffold + Supabase Auth (Free tier sign-in).**
|
|
801
|
+
|
|
802
|
+
First sub-PR of v7.0 Phase 2 (Ingest API + CLI integration). Pure foundation; no API endpoints related to ingest, no CLI dashboard verbs.
|
|
803
|
+
|
|
804
|
+
**What landed:**
|
|
805
|
+
- `apps/web/` Next.js 16 App Router app with React 19 + Tailwind v4
|
|
806
|
+
- npm workspaces (`workspaces: ["apps/*", "packages/*"]`) — CLI deps stay where they are; web deps live in `apps/web/package.json`
|
|
807
|
+
- `tsconfig.base.json` shared between CLI and web; `apps/web/` uses `bundler` module resolution, CLI keeps `NodeNext`
|
|
808
|
+
- Supabase Auth Google sign-in via PKCE callback (`/api/auth/callback`)
|
|
809
|
+
- Sign-out (`/api/auth/sign-out`) clears only configured project ref's cookies — never `sb-*` wildcard
|
|
810
|
+
- `safeRedirect` whitelist with documented change policy
|
|
811
|
+
- Scoped middleware matcher: refreshes session on page + `/api/auth/*` routes ONLY; excludes static assets, `/api/health`, and non-auth `/api/*` (ingest endpoints in 2.2 handle their own auth)
|
|
812
|
+
- Health endpoint `/api/health` for platform health checks
|
|
813
|
+
- 22 web tests via Vitest (10 redirect + 5 callback + 2 signout + 4 matcher + 1 typecheck-guard)
|
|
814
|
+
- `web-tests.yml` workflow runs typecheck + Next.js build + tests on every PR
|
|
815
|
+
- `npm-tarball-check.yml` workflow asserts `apps/` is excluded from the published CLI tarball
|
|
816
|
+
- `vercel.json` configured for monorepo build with `apps/web/` root
|
|
817
|
+
|
|
818
|
+
**Spec:** `docs/specs/v7.0-phase2.1-nextjs-scaffold.md` (PR #116)
|
|
819
|
+
**Plan:** `docs/superpowers/plans/2026-05-07-v7.0-phase2.1-nextjs-scaffold.md`
|
|
820
|
+
|
|
821
|
+
Pre-release on the npm `next` tag. `latest` stays on `6.2.2`.
|
|
822
|
+
|
|
823
|
+
## 6.3.0-pre.1 (2026-05-07)
|
|
824
|
+
|
|
825
|
+
**v7.0 Phase 1 — Foundation: schema + RLS + cross-tenant negative tests.**
|
|
826
|
+
|
|
827
|
+
First step toward the v7.0 hosted product. Database-only PR; no endpoints, no UI, no Stripe integration.
|
|
828
|
+
|
|
829
|
+
**What landed:**
|
|
830
|
+
|
|
831
|
+
- `db/supabase/` Supabase project bootstrap with 8 numbered migrations
|
|
832
|
+
- 7 multi-tenant tables: `organizations`, `memberships`, `runs`, `upload_sessions`, `entitlements`, `audit_events`, `organization_settings`
|
|
833
|
+
- RLS enabled on every table with two-branch pattern: `(organization_id IS NOT NULL AND active member)` OR `(organization_id IS NULL AND user_id = auth.uid())`
|
|
834
|
+
- `audit.append()` SQL function with hash-chain immutability; app roles get INSERT only via the function
|
|
835
|
+
- Supabase Storage buckets `org-runs` and `user-runs` with tenant-scoped path-prefix RLS
|
|
836
|
+
- `entitlements.plan` CHECK constraint matching `organizations.plan` exactly
|
|
837
|
+
- `upload_sessions` stores only `jti` + token hash (never raw signing material)
|
|
838
|
+
- 7 RLS negative test files covering: runs cross-tenant, free-vs-org-tier branches, audit immutability, storage path isolation, entitlements admin-only, membership edge cases, upload_sessions single-use
|
|
839
|
+
- CI workflow `.github/workflows/db-tests.yml` runs the test suite against a Dockerized Supabase on every PR
|
|
840
|
+
|
|
841
|
+
**Spec:** `docs/specs/v7.0-hosted-product-mvp.md` (PR #114)
|
|
842
|
+
**Plan:** `docs/superpowers/plans/2026-05-07-v7.0-phase1-foundation.md`
|
|
843
|
+
|
|
844
|
+
Pre-release on the npm `next` tag. `latest` stays on `6.2.2`.
|
|
845
|
+
|
|
5
846
|
## 6.2.2 — `claude-autopilot autopilot --json` envelope + cache version policy (2026-05-07)
|
|
6
847
|
|
|
7
848
|
**Headline.** Closes out the v6.2.x track. `claude-autopilot autopilot --json` now emits exactly one machine-readable envelope on stdout — successful runs, pre-run failures, and mid-pipeline failures all produce the same shape so CI consumers can branch on `.exitCode` / `.failedPhase` / `.errorCode` directly without parsing stderr NDJSON. The cache contract gains a `MIN_SUPPORTED..MAX_SUPPORTED` schema-version window so a stale run dir from a future binary fails with a clear error instead of an opaque shape crash. The migration guide gets a new "v6.1 → v6.2: one runId across the pipeline" section.
|