@delegance/claude-autopilot 7.4.2 → 7.5.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 CHANGED
@@ -2,6 +2,150 @@
2
2
 
3
3
  - v5.6 Phase 7 (docs reconciliation) — pending.
4
4
 
5
+ ## 7.5.0 (2026-05-10)
6
+
7
+ **v7.5.0 — route-sensitivity-tiered membership revocation.** Minor
8
+ release. Closes W4 from the v7.4.1 codex strategic review without
9
+ adding infrastructure (Redis / Realtime / KV all rejected — see
10
+ `docs/specs/v7.5.0-route-sensitivity.md` for the trade-off analysis).
11
+
12
+ **Problem.** v7.0 Phase 6 caches `check_membership_status` for 60s
13
+ in an HMAC-signed cookie. Worst-case revocation window = 60s for
14
+ EVERY dashboard request, including admin-class mutations and
15
+ sensitive reads where ≤1-request revocation is the bar.
16
+
17
+ **Solution.** Split the policy by route sensitivity instead of
18
+ tightening globally:
19
+
20
+ | Tier | Revocation window | Behavior |
21
+ |---|---|---|
22
+ | LOW (default) | ≤60s | v7.0 cookie cache (no change) |
23
+ | HIGH (mutations + sensitive reads) | ≤1 request | Skip cookie, always RPC |
24
+
25
+ HIGH list (locked in code, codex-reviewed): mutations on any
26
+ `/api/dashboard/*` route + GETs under
27
+ `/api/dashboard/orgs/:id/{audit,cost,cost.csv,sso/**,members/**,billing/**}`
28
+ + all `/api/dashboard/api-keys/*` (including GET — codex pass-2 W4
29
+ flagged the listing as sensitive).
30
+
31
+ **Defense in depth (codex pass-2 CRITICAL #3).** Middleware regex
32
+ matching is brittle — a new sensitive handler that's not yet in
33
+ `HIGH_SENSITIVITY_PATTERNS` would default to LOW. Mitigation: every
34
+ high-sensitivity route handler now calls
35
+ `assertActiveMembershipForOrg()` at the top as the inner correctness
36
+ gate. Middleware is the outer optimization (skips the cookie cache);
37
+ the handler call is the inner gate (doesn't depend on the regex
38
+ list staying in sync).
39
+
40
+ **Path-vs-active-org assertion (codex pass-2 CRITICAL #2).** For
41
+ high-sensitivity org-scoped routes the middleware also extracts
42
+ `:orgId` from the request path and asserts it matches the
43
+ `cao_active_org` cookie. A user active in Org A reaching an Org B
44
+ URL gets a 403/302 immediately — defense against a sloppy
45
+ downstream handler.
46
+
47
+ **New files:**
48
+
49
+ - `apps/web/lib/middleware/route-sensitivity.ts` (~85 LOC) —
50
+ `HIGH_SENSITIVITY_PATTERNS` + `isHighSensitivityRoute()`.
51
+ - `apps/web/lib/dashboard/assert-active-membership-for-org.ts`
52
+ (~165 LOC) — defense-in-depth helper + `respondToMembershipError()`
53
+ response builder.
54
+ - `apps/web/__tests__/middleware/route-sensitivity.test.ts` (24
55
+ cases — spec list of 7 + non-GET coverage + boundary regex tests
56
+ for codex pass-2 W4/W5).
57
+ - `apps/web/__tests__/middleware/revocation-integration.test.ts`
58
+ (8 cases — disabled-with-fresh-cookie + low-tier cache-still-wins
59
+ + helper error-mapping integration).
60
+ - `apps/web/__tests__/lib/dashboard/assert-active-membership-for-org.test.ts`
61
+ (16 cases — 4 error codes + happy path + UUID validation +
62
+ respondToMembershipError variants).
63
+
64
+ **Modified files:**
65
+
66
+ - `apps/web/middleware.ts` — sensitivity branch BEFORE cookie verify;
67
+ parseOrgIdFromPath check on high-sensitivity routes; skip cookie
68
+ mint on high-sensitivity success.
69
+ - 10 route handlers wired with `assertActiveMembershipForOrg()` as
70
+ the first authorization step (members CRUD + invite + enable +
71
+ disable; org PATCH; audit; cost JSON + CSV; SSO disconnect +
72
+ required + setup + domains + verify).
73
+
74
+ **Test regressions promoted to v7.5.0 expectations:** 8 existing
75
+ tests that asserted the OLD pre-helper behavior (non-member → 404
76
+ via RPC's `not_admin`, disabled-user → `not_owner`/`not_admin`) now
77
+ assert the NEW uniform helper behavior (non-member → 403
78
+ `no_membership`; disabled-user → 403 `member_disabled`). Status
79
+ codes unchanged for the disabled-user cases; status code for
80
+ non-member shifted from 404 → 403 with a non-enumerating body code.
81
+ Comments inline in each updated test point at this v7.5.0 trade-off.
82
+
83
+ **Test count:** 621 → 669 (+48 web). CLI suite unchanged at 1606
84
+ (no CLI changes).
85
+
86
+ **Codex traceability:**
87
+
88
+ | Finding | Resolution |
89
+ |---|---|
90
+ | Pass-1 C1 (W4 not closed) | Reframed: route-tier closes the security/compliance subset of W4. |
91
+ | Pass-1 C2 (Vercel/ECS confusion) | False positive — explicit deployment-context section in spec. |
92
+ | Pass-1 W1 (Redis ROI) | False positive — autopilot.dev has no Redis. |
93
+ | Pass-1 W3 (route-sensitivity split) | **Adopted as the central design.** |
94
+ | Pass-2 CRITICAL #2 (path-vs-activeOrg) | `parseOrgIdFromPath` + middleware assertion. |
95
+ | Pass-2 CRITICAL #3 (regex bypass risk) | `assertActiveMembershipForOrg()` defense-in-depth helper called at top of every HIGH handler. |
96
+ | Pass-2 W4 (api-keys GET sensitivity) | `/api/dashboard/api-keys/*` (including GET) added to HIGH list. |
97
+ | Pass-2 W5 (boundary pattern correctness) | Anchored regex with explicit `/`/`$` terminator; tests cover `/costume`, `/auditor`, trailing-slash, nested-path. |
98
+
99
+ **Out of scope.** Vercel KV / Realtime websocket (deferred), JWT-
100
+ side revocation (v7.1 already collapses ingest to ≤1 request),
101
+ configurable sensitivity classification (locked in code on purpose),
102
+ wrapper `withActiveMembershipRequired()` decorator (deferred to
103
+ v7.6+ refactor; the explicit call is sufficient for v7.5.0).
104
+
105
+ ## 7.4.3 (2026-05-11)
106
+
107
+ **v7.4.3 — FastAPI scaffold respects spec-derived package name.**
108
+ Patch release. Real-package end-to-end test on v7.4.2 surfaced
109
+ this regression: the v7.4.0 Python/FastAPI scaffolder always used
110
+ `basename(cwd)` for the package directory and ignored spec-listed
111
+ `src/<pkg>/main.py` paths. Result: scaffolding a spec that listed
112
+ `src/fastapi_test/main.py` from a directory called `v742-fastapi`
113
+ produced TWO competing trees:
114
+
115
+ * `src/v742_fastapi/` — auto-generated FastAPI app (correct
116
+ content, wrong location)
117
+ * `src/fastapi_test/main.py` — empty placeholder from the spec's
118
+ bullet (right location, no content)
119
+
120
+ `pyproject.toml`'s `[project.scripts]` pointed at the
121
+ auto-generated tree (`v742_fastapi.main:run`) — the spec's intent
122
+ was clearly the named package.
123
+
124
+ **Fix:** new `packageNameFromSpec(parsed)` extracts the package
125
+ name from the first `src/<pkg>/<*>.py` entry in the spec's
126
+ `## Files` section. Falls back to the cwd-derived default only
127
+ when the spec doesn't list any `src/<pkg>/` path.
128
+
129
+ 8 new tests in `tests/scaffold-python.test.ts` cover:
130
+ * extraction from `src/<pkg>/main.py` (the conventional case)
131
+ * extraction from `src/<pkg>/<other>.py`
132
+ * null when no `src/<pkg>/<*>.py` listed
133
+ * null for non-`src/` paths
134
+ * first-match-wins on multiple `src/<pkg>/` entries
135
+ * rejects invalid Python identifier characters in the path
136
+ * the exact regression case (`src/fastapi_test/main.py` from
137
+ cwd `v742-fastapi`)
138
+ * fallback to cwd basename when spec has no `src/<pkg>/`
139
+
140
+ Plus 2 end-to-end tests verifying:
141
+ * scaffold from `cwd=v742-real` + `src/intentional_pkg/main.py`
142
+ spec → SINGLE `src/intentional_pkg/` directory (no competing
143
+ tree); `pyproject.toml` consistent throughout
144
+ * scaffold from `cwd=myapp` + spec without `src/<pkg>/` → still
145
+ uses `src/myapp/` (preserves v7.4.0 default behavior)
146
+
147
+ 1597 → 1606 CLI tests (+9). tsc clean. build clean.
148
+
5
149
  ## 7.4.2 (2026-05-11)
6
150
 
7
151
  **v7.4.2 — risk-tiered codex pass policy in autopilot skill.**
@@ -1,4 +1,4 @@
1
- import type { ScaffoldResult, ScaffoldRunContext } from './types.ts';
1
+ import type { ParsedFiles, ScaffoldResult, ScaffoldRunContext } from './types.ts';
2
2
  /**
3
3
  * PEP 503 distribution-name normalization, restricted to what we need
4
4
  * here. Lowercase, runs of `[._-]+` collapse to a single `-`, leading +
@@ -65,6 +65,16 @@ export declare function buildFastapiTest(packageName: string): string;
65
65
  * not listed in `## Files` — without them the generated pyproject.toml
66
66
  * is invalid (missing package dir) or has dead config (no tests).
67
67
  */
68
+ /**
69
+ * Extract the Python package name from a spec's `## Files` paths if the
70
+ * spec lists a `src/<pkg>/<*>.py` entry. Returns null if no spec-derived
71
+ * package name is present — caller falls back to the cwd-derived default.
72
+ *
73
+ * v7.4.3 hotfix — the v7.4.0 scaffolder always used basename(cwd) and
74
+ * ignored spec-listed src/<pkg>/ paths, producing two competing trees
75
+ * (one auto-generated, one empty placeholder from the spec).
76
+ */
77
+ export declare function packageNameFromSpec(parsed: ParsedFiles): string | null;
68
78
  export declare function scaffoldPython(ctx: ScaffoldRunContext, opts: {
69
79
  isFastapi: boolean;
70
80
  }): Promise<ScaffoldResult>;
@@ -206,11 +206,30 @@ pytest
206
206
  * not listed in `## Files` — without them the generated pyproject.toml
207
207
  * is invalid (missing package dir) or has dead config (no tests).
208
208
  */
209
+ /**
210
+ * Extract the Python package name from a spec's `## Files` paths if the
211
+ * spec lists a `src/<pkg>/<*>.py` entry. Returns null if no spec-derived
212
+ * package name is present — caller falls back to the cwd-derived default.
213
+ *
214
+ * v7.4.3 hotfix — the v7.4.0 scaffolder always used basename(cwd) and
215
+ * ignored spec-listed src/<pkg>/ paths, producing two competing trees
216
+ * (one auto-generated, one empty placeholder from the spec).
217
+ */
218
+ export function packageNameFromSpec(parsed) {
219
+ for (const p of parsed.paths) {
220
+ const m = /^src\/([a-zA-Z_][a-zA-Z0-9_]*)\/[^/]+\.py$/.exec(p);
221
+ if (m && m[1])
222
+ return m[1];
223
+ }
224
+ return null;
225
+ }
209
226
  export async function scaffoldPython(ctx, opts) {
210
227
  const { cwd, parsed, dryRun } = ctx;
211
228
  const { isFastapi } = opts;
229
+ // v7.4.3: prefer spec-derived package name; fall back to cwd basename.
230
+ const specPackage = packageNameFromSpec(parsed);
212
231
  const distributionName = normalizeDistributionName(path.basename(cwd));
213
- const packageName = packageNameFromDistribution(distributionName);
232
+ const packageName = specPackage ?? packageNameFromDistribution(distributionName);
214
233
  const filesCreated = [];
215
234
  const filesSkippedExisting = [];
216
235
  const dirsCreated = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@delegance/claude-autopilot",
3
- "version": "7.4.2",
3
+ "version": "7.5.0",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "tag": "next"