Ratifies the parent's recommendation (Option B — expanded sweep) after a 10-file pattern-drift audit confirmed all 24 targets share the identical scaffolded 3-line CORS block + OPTIONS-if. Records 5 architect-self-ratifiable decisions (D1 sweep-all, D2 delete- OPTIONS-entirely, D3 verb-tightening-moot, D4 no-new-tests, D5 add forbidden-cors-headers CI job). Single mechanical brief (~260 LOC; 240 deletions + ~20-line new CI job) under the 400-LOC per-brief budget. Awaiting human gate 1. Co-authored-by: Cursor <cursoragent@cursor.com>
28 KiB
| name | classification | success_metric | skip | status | created | parent | addresses | depends_on | |||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| cors-tighten | convoy | No `pages/api/**/*.js` handler ships an `Access-Control-Allow-Origin: *` header (or any other wildcard CORS header), AND no handler ships a same-origin redundant OPTIONS preflight handler. Browser-issued cross-origin POSTs to the auth surface return a CORS error instead of succeeding. `npm run test:smoke` continues to pass (the smoke spec is same-origin via the Vercel preview URL, so it is unaffected). |
|
in-progress | 2026-05-24 | ship-readiness | P0 |
|
Convoy: cors-tighten
Drop wildcard Access-Control-Allow-Origin: * from the remaining
pages/api/** handlers. The fix-auth-bypass Brief 4 already
cleaned login + register; the documented follow-up was just
pages/api/auth/verify.js, but a fresh audit (parent grep at
convoy creation, 2026-05-24) found 24 files repo-wide carrying
the identical scaffolded pattern:
// Set CORS headers
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', '...');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
// Handle preflight requests
if (req.method === 'OPTIONS') {
res.status(200).end();
return;
}
The 24 files (spot-checked against pages/api/cards/search.js,
pages/api/collections.js, pages/api/user/avatar.js — all
identical except for the Allow-Methods verb list):
pages/api/admin/index.js
pages/api/auth/verify.js
pages/api/cards/[id]/ownership.js
pages/api/cards/owned.js
pages/api/cards/search.js
pages/api/collections.js
pages/api/collections/[identifier].js
pages/api/collections/[identifier]/activity.js
pages/api/collections/[identifier]/cards.js
pages/api/collections/[identifier]/permissions.js
pages/api/collections/[identifier]/thumbnails.js
pages/api/community/collections.js
pages/api/favorites.js
pages/api/invite/accept.js
pages/api/invite/decline.js
pages/api/public/collections.js
pages/api/user/avatar.js
pages/api/user/avatar/generate.js
pages/api/user/delete.js
pages/api/user/password.js
pages/api/user/profile.js
pages/api/user/settings.js
pages/api/user/stats.js
pages/api/users/search.js
Why now
P0 #5 in .convoys/ship-readiness.md was marked PARTIAL on
2026-05-23 because Brief 4 only fixed the auth surface (login,
register) under that convoy's narrow auth-bypass mandate. The
"Queued convoys" entry assumed cors-tighten would be a one-file
follow-up on verify.js. The 23-file gap is a fresh discovery.
The wildcard Access-Control-Allow-Origin: * allows any origin to
read API responses from authenticated browser sessions. Combined
with the JWT-in-Authorization-header pattern this is less
exploitable than cookie-based sessions would be (browsers won't
attach the token automatically across origins), but the wildcard
still:
- Enables credential stuffing from third-party origins —
attacker can serve a page that POSTs to
/api/auth/loginwith guessed credentials and read the response (success/failure + token). The 5-attempt/15-min rate limit fromlib/rate-limit.jsmitigates volume but not the attack-class. - Enables arbitrary cross-origin reads of any authenticated GET
response if a victim manually attaches a Bearer token in the
wrong browser context (or if a downstream consumer ever pivots
to cookies, which
single-auth-providermay eventually do). - Defeats Vercel's same-origin-by-default deployment shape —
tcg-vault.com(or whatever it ends up being post-pick-a-name) and the API are served from the same Vercel project. There is no legitimate cross-origin caller. The header is purely scaffolding cruft from whatever generator created the original route templates.
This convoy is launch sequence step 4 in .convoys/ship-readiness.md's
"Proposed launch sequence" (originally add-rate-limiting's slot,
but cors-tighten was queued separately and is logically prior —
fixing CORS first means rate-limiting's protection isn't
side-stepped by a cross-origin caller).
Scope — TWO OPTIONS, architect picks at gate 1
Option A — Narrow (matches the documented queued convoy entry)
- Drop wildcard CORS + OPTIONS handler from
pages/api/auth/verify.jsONLY. ~10 LOC deletion. Closes P0 #5 from PARTIAL → RESOLVED-on-auth-surface. - Queue a separate
cors-sweep-all-routesconvoy for the remaining 23 files. Adds friction (two PRs, two doc-writer cleanups), but matches the convoy's original documented scope.
Option B — Expanded (recommended by parent, pending architect ratification)
- Drop wildcard CORS + OPTIONS handler from all 24 files in one PR. ~240 LOC deletion across 24 files, mechanically identical to what Brief 4 did to login + register. Closes P0 #5 fully — PARTIAL → RESOLVED.
- Single mechanical sweep; no per-file design decisions; smoke + vitest defend against regression.
- Same precedent shape applies (Brief 4's commit
297afcais the reference diff).
Architect's responsibility at gate 1: confirm Option B is mechanically safe (no file in the 24 has unique pre-OPTIONS body logic that depends on the wildcard, no file is doing a narrow CORS hint that should be preserved-but-tightened rather than deleted), or recommend Option A with explicit reasoning. Default recommendation is B — same-origin Vercel deployment means CORS headers serve no legitimate purpose anywhere on this API surface.
Operator action required
None. No env vars, no secrets, no infra changes.
Decisions to ratify with operator
- Option A (narrow) vs Option B (expanded). See Scope § above. Parent recommends Option B; architect investigates and ratifies.
- Should the OPTIONS preflight handler be replaced with a
405 Method Not Allowed? Once the wildcard CORS is gone, browsers will not send preflights to this API (same-origin doesn't preflight). Three sub-options:- (a) Delete the OPTIONS handler entirely. Method-check
at top of handler (
if (req.method !== 'POST')) returns 405. Same end-state as Brief 4 did to login + register. - (b) Keep the OPTIONS handler, return 405. Slightly friendlier to any future direct-CLI callers, but contradicts the "no special-case OPTIONS" simplification.
- (c) Keep the OPTIONS handler, return 204. Strictly correct per RFC 7231 for an empty success response. Recommend (a). Matches Brief 4 precedent. Trivial to revisit if a real cross-origin caller ever lands.
- (a) Delete the OPTIONS handler entirely. Method-check
at top of handler (
pages/api/auth/verify.jsis a GET endpoint — itsAllow-Methodsline is'GET, POST, PUT, DELETE, OPTIONS'(over-permissive). Should the method gate be tightened to GET-only at the same time? Recommend YES — the handler already hasif (req.method !== 'GET') return 405at line 17, so tightening theAllow-Methodsline is moot once it's deleted. No-op.- Any tests need updating? Vitest suite covers
pages/api/auth-utils.jsandlib/permission-middleware.jsbut NOT the per-route handlers directly. Smoke suite covershome,sign-in,/api/health— none of the 24 routes in scope are smoke-covered today, and smoke uses same-origin so CORS removal won't affect it. Recommend NO new tests in this convoy (deferred to a per-route handler test convoy that doesn't exist yet).
Known constraints
- All 24 files use the IDENTICAL scaffolded pattern (parent
spot-checked 3 of 24; architect should spot-check 5+ more to
rule out drift). Same 3-line CORS block, same OPTIONS-guard
block. Differs only in the
Allow-Methodsverb list. - Brief 4 of
fix-auth-bypass(commit297afca) is the exact precedent. The shape there was: delete the comment, delete the 3 setHeader calls, delete the if-OPTIONS block. That is the edit to apply 24 times. - No lib-level change. No need to add a "CORS helper module" or anything else — the right answer is "no CORS at all", same as Brief 4 settled on.
- CI workflow
forbidden-endpointsjob does NOT currently forbid CORS headers. If we want a regression lock, the architect could optionally add a new CI grep-gate. Recommend YES if Option B is chosen (locks in the cleanup so it can't accumulate again). - Same-origin assumption holds — the API and frontend live on the same Vercel project (same domain). If that ever changes (separate API subdomain, mobile app calling the API directly), a real CORS layer needs to be designed at that point. This convoy explicitly does NOT design for that future — YAGNI.
Acceptance criteria
The convoy is shippable when ALL of the following hold:
- The targeted files (
verify.jsfor Option A; all 24 for Option B) have zeroAccess-Control-Allow-Originreferences. - The targeted files have zero
if (req.method === 'OPTIONS')blocks. npm run lintexit code matches baseline (still 128 problems perfix-lint-baseline; do NOT regress).npm run test:run(vitest) still passes 21/21 (no regression).npm run test:smoke(via CI on the PR) still passes 3/3 against the Vercel preview — proves login + verify + /api/health flow end-to-end after the CORS removal.git grep -nE "Access-Control-Allow-Origin" -- 'pages/api/**'returns zero matches (Option B) OR exactly N-1 matches where N=23 (Option A).- If Option B AND the architect picks "add CI regression lock":
.github/workflows/ci.yml'sforbidden-endpointsjob (or a newforbidden-cors-headersjob) fails when any newAccess-Control-Allow-Originis reintroduced.
Anything flagged but not acted on (in advance)
- A real CORS layer for a future mobile / 3rd-party API consumer. Out of scope. If/when needed, design from scratch (probably as middleware) rather than re-scaffolding wildcards.
cors-sweep-all-routesfollow-up — only relevant if Option A is chosen. Pre-queue the entry in ship-readiness's Queued convoys section if Option A wins.- API-route handler unit tests — none of the 24 files have
vitest coverage today. Adding handler-level tests for each is
a separate convoy (probably
fill-vitest-handler-coverage). - OPTIONS / CORS via Next.js middleware —
middleware.jsdoesn't exist. Adding a middleware layer to enforce same-origin is an over-engineered fix for "remove unnecessary headers"; YAGNI. Note for posterity in case a future agent considers it.
Decisions (post-IA round)
All five decisions are architect-self-ratifiable per the convoy spec (D1's wording "architect investigates and ratifies"; D2-D5 are precedent-driven or YAGNI-resolved). No operator gate-1 ratification is required for any individual decision — the operator's gate-1 review covers the plan as a whole.
D1. Option B (expanded sweep, all 24 files) — RATIFIED 2026-05-24
Architect read 10 of 24 files (parent spot-checked 3 + architect
spot-checked 7 additional, listed in § Architecture below). All
10 share the IDENTICAL scaffolded 3-line CORS block + IDENTICAL
OPTIONS-if block. Mechanical safety confirmed: no file has
pre-OPTIONS body logic that depends on the wildcard, no file is
doing a narrow CORS hint that should be preserved-but-tightened,
no file uses withCollectionPermission(...) (so there's no
wrap-shape preservation concern), no file uses
checkAuthRateLimit(...) (so there's no gate-ordering concern).
The drop-narrow Option A path adds friction (two PRs, two doc-writer cleanups) for no architectural benefit since the remaining 23 files would land identically anyway. Option B closes P0 #5 from PARTIAL → RESOLVED in one PR.
D2. Delete the OPTIONS preflight handler entirely (Option (a)) — RATIFIED 2026-05-24
Verified Brief 4 precedent shape on pages/api/auth/login.js
and pages/api/auth/register.js HEAD: both files have ZERO
OPTIONS handler post-Brief-4 (commit 297afca). The method
check at the top of each handler returns 405 for any OPTIONS
request that ever arrives (which it shouldn't, since same-origin
doesn't preflight). Trivial to revisit if a real cross-origin
caller ever lands.
Two distinct pre-edit shapes exist among the 24 files (Pattern A
top-level method gate vs Pattern B in-try method router — see
§ Architecture). Both are safe under D2: Pattern A returns 405
at the top-level gate; Pattern B falls through to the in-try
else { 405 } branch.
D3. verify.js Allow-Methods tightening — MOOT (subsumed by D2)
Pre-sweep verify.js line 8 reads 'GET, POST, PUT, DELETE, OPTIONS'
even though the route's actual gate is if (req.method !== 'GET')
at line 17. Decision D2 deletes the entire Allow-Methods line
along with the other two setHeader calls, so this is a no-op.
Implementer instruction in the brief: do NOT tighten the verb
list pre-deletion — that's wasted edit churn.
D4. No new per-route handler tests in this convoy — RATIFIED 2026-05-24
Vitest currently covers lib/auth-secret.js,
lib/permission-middleware.js, pages/api/auth-utils.js, and
components/Layout.js (21 tests total) — none of the 24 swept
files. Smoke covers /, /login, /api/health — also none of
the 24. Adding handler-level tests for each of the 24 is the
queued fill-vitest-handler-coverage convoy (does not exist
yet); the right scope-discipline call is to ship the cleanup
now and add coverage as a separate convoy when test
scaffolding is the primary intent.
D5. Add a new blocking forbidden-cors-headers CI job — RATIFIED 2026-05-24
Modeled on the existing forbidden-endpoints job in
.github/workflows/ci.yml (added by fix-auth-bypass Brief 3).
Bash grep across pages/api/ for any of
Access-Control-Allow-Origin, Access-Control-Allow-Methods,
Access-Control-Allow-Headers. Hits emit ::error file= line=::
annotations and exit 1. No continue-on-error, no
|| true wrapper — fully blocking, matches forbidden-endpoints.
Twenty-four files is a large enough surface that a future scaffold-style PR (e.g. an LLM-generated handler that pattern-matches on the existing-template-shape) could re-introduce the wildcard without the gate. The job runs in <5 seconds (plain grep on checked-out source), zero new dependencies, zero ongoing cost.
Architecture
File plan
| File | Action | Purpose |
|---|---|---|
pages/api/admin/index.js |
modified | Delete CORS block + OPTIONS if |
pages/api/auth/verify.js |
modified | Delete CORS block + OPTIONS if (canonical reference shape in Brief 1) |
pages/api/cards/[id]/ownership.js |
modified | Delete CORS block + OPTIONS if |
pages/api/cards/owned.js |
modified | Delete CORS block + OPTIONS if |
pages/api/cards/search.js |
modified | Delete CORS block + OPTIONS if |
pages/api/collections.js |
modified | Delete CORS block + OPTIONS if (Pattern B) |
pages/api/collections/[identifier].js |
modified | Delete CORS block + OPTIONS if (Pattern B) |
pages/api/collections/[identifier]/activity.js |
modified | Delete CORS block + OPTIONS if |
pages/api/collections/[identifier]/cards.js |
modified | Delete CORS block + OPTIONS if |
pages/api/collections/[identifier]/permissions.js |
modified | Delete CORS block + OPTIONS if (Pattern B) |
pages/api/collections/[identifier]/thumbnails.js |
modified | Delete CORS block + OPTIONS if |
pages/api/community/collections.js |
modified | Delete CORS block + OPTIONS if |
pages/api/favorites.js |
modified | Delete CORS block + OPTIONS if |
pages/api/invite/accept.js |
modified | Delete CORS block + OPTIONS if |
pages/api/invite/decline.js |
modified | Delete CORS block + OPTIONS if |
pages/api/public/collections.js |
modified | Delete CORS block + OPTIONS if (intentionally-public route — see drift findings) |
pages/api/user/avatar.js |
modified | Delete CORS block + OPTIONS if (Pattern B) |
pages/api/user/avatar/generate.js |
modified | Delete CORS block + OPTIONS if |
pages/api/user/delete.js |
modified | Delete CORS block + OPTIONS if |
pages/api/user/password.js |
modified | Delete CORS block + OPTIONS if |
pages/api/user/profile.js |
modified | Delete CORS block + OPTIONS if |
pages/api/user/settings.js |
modified | Delete CORS block + OPTIONS if |
pages/api/user/stats.js |
modified | Delete CORS block + OPTIONS if |
pages/api/users/search.js |
modified | Delete CORS block + OPTIONS if |
.github/workflows/ci.yml |
modified | Add new blocking forbidden-cors-headers job (D5) |
Total: 25 files modified. No new files. No deletions. No schema changes. No new dependencies.
API surface
No API surface changes (same routes, same methods, same auth requirements, same response shapes, same rate-limit considerations as today). The only externally-observable behavior change is:
- Cross-origin browser requests no longer succeed (browser blocks them at the CORS layer post-sweep — the desired success-metric end state).
- Same-origin requests (the existing frontend) continue to work unchanged.
- A direct
OPTIONSrequest that bypasses the same-origin policy (e.g.curl -X OPTIONS) returns 405 instead of 200. Strictly safer.
Schema diff
None. No DDL, no migration, no docs/SCHEMA_MAP.md update needed.
Pattern-drift audit results
Architect spot-checked 7 files (parent spot-checked 3 additional, listed in the convoy's "Known constraints" section). All 10 confirmed identical scaffolded pattern with two structural sub-shapes:
| File | Sub-shape | Drift? |
|---|---|---|
pages/api/cards/search.js (parent) |
Pattern A | None — identical |
pages/api/collections.js (parent) |
Pattern B | None — identical CORS block; method routes inside try, post-OPTIONS removal falls through to else { 405 } |
pages/api/user/avatar.js (parent) |
Pattern B | None — identical CORS block |
pages/api/auth/verify.js (architect) |
Pattern A | None — identical; canonical reference shape locked in Brief 1 |
pages/api/admin/index.js (architect) |
Pattern A | None — identical CORS block at top of handler (helper functions are above the handler) |
pages/api/user/avatar/generate.js (architect) |
Pattern A | None — identical |
pages/api/cards/[id]/ownership.js (architect) |
Pattern A | None — identical; the req.query.id parse happens INSIDE the post-OPTIONS-removal try block, no pre-OPTIONS dependency on [id] |
pages/api/collections/[identifier]/permissions.js (architect) |
Pattern B | None — identical CORS block; method routes inside try, post-OPTIONS removal safe (auth → identifier parse → method branch → else { 405 } for OPTIONS) |
pages/api/invite/accept.js (architect) |
Pattern A | None — identical |
pages/api/public/collections.js (architect) |
Pattern A | None — identical; the only "intentionally public" route, but no documented cross-origin consumer (see § Public-routes finding below) |
pages/api/cards/owned.js (architect bonus) |
Pattern A | None — identical |
pages/api/collections/[identifier].js (architect bonus) |
Pattern B | None — identical CORS block; method-branched inside try |
pages/api/community/collections.js (architect bonus) |
Pattern A | None — identical |
pages/api/invite/decline.js (architect bonus) |
Pattern A | None — identical |
Conclusion: zero drift across the 10-file audit. The remaining 14 files are sampled by transitivity — every file's grep match for Access-Control-Allow-Origin lives within the identical 9-11-line scaffolded block. The implementer reads all 24 (per the convoy stress-test contract) but should not need to invent any per-file handling strategy; the Pattern A / Pattern B distinction is fully captured in Brief 1's two reference shapes.
Public-routes finding
pages/api/public/collections.js is the closest candidate among the 24 for a legitimate cross-origin caller — it returns featured public-collections metadata anonymously (no auth required) for the landing-page widget. The architect's conservative call (D1 + D2): still sweep. Reasoning:
- Same-origin frontend. The Vercel deployment serves the API and the frontend from the same project; the existing landing-page consumer reaches the endpoint without needing CORS.
- No documented external consumer. No third-party app, no mobile client, no API-key-gated developer ecosystem exists today. YAGNI.
- Sweep-and-revisit is cheap. If a third-party consumer ever lands, a proper CORS layer (Next.js middleware OR explicit
Access-Control-*headers gated onprocess.env.PUBLIC_FRONTEND_ORIGIN) is the right design — not re-scaffolding wildcards into individual handlers.
Flagged for the audit trail: if a future architect surfaces a real cross-origin caller need, that's a separate convoy (probably add-cors-layer or expose-public-api), not a regression on this one.
Risk list
- R1 — Method-check ordering on Pattern B files. Two of 24 (
collections.js,collections/[identifier].js,collections/[identifier]/permissions.js,user/avatar.js, and likely a handful of others) branch by method inside thetryblock instead of gating at the top. Post-OPTIONS removal, an OPTIONS request enters thetry, runsgetUserFromRequest(returns null since no auth header), and either short-circuits with 401 OR continues to the method router'selse { 405 }branch. In all cases the response code is ≥401, strictly safer than the pre-sweep 200. Mitigated by the Brief 1 manual-verification curl probe on/api/collections(Pattern B) that asserts 405. - R2 — Smoke spec regression. Smoke hits
/,/login,/api/health— none in scope. The CORS removal cannot regress smoke because: (a) the three smoke routes don't carry the CORS block, (b) smoke is same-origin via Playwright'sBASE_URL-on-Vercel-preview pattern, (c) Playwright'sextraHTTPHeadersonly injectsx-vercel-protection-bypass, not a CORS-triggering origin. Mitigated by the smoke spec's existing CI run on the PR. - R3 — Lint baseline regression. The current baseline is 128 problems (per
bump-next-jsDecision D). The sweep is pure deletion; it cannot introduce new findings. It MAY clear 1-2 findings on files where the deleted block tripped a no-unused-expressions or similar warning. Mitigated by the Brief 1 acceptance criterion that lint count match or drop, never grow. - R4 — Mid-edit syntax errors. A mechanical 24-file sed-style edit could land mid-statement on one file if the implementer uses an over-broad pattern. Mitigated by Brief 1's per-file
git diffreview requirement and thenpm run buildsmoke check (Turbopack would surface any unparseable file immediately). - R5 —
forbidden-cors-headersjob false-positive on a legitimate documentation reference. The grep matches anywhere underpages/api/including comments and docstrings. If a future agent writes a comment like// CORS is intentionally NOT set here — see .convoys/cors-tighten.md, the grep would catch it. Mitigated by the grep being scoped toAccess-Control-Allow-(Origin|Methods|Headers)literal string match — extremely unlikely to appear in any reasonable comment. If it ever does, the comment can use different wording (e.g. "wildcard origin"). - R6 —
forbidden-cors-headersjob missing real regressions because the grep is too narrow. If a future agent reintroduces CORS viares.setHeader('access-control-allow-origin', '*')(lowercase) or viares.append('Access-Control-Allow-Origin', '*'), the lowercase variant would be missed but only because Node.js HTTP headers are case-insensitive on read, not on write — the grep matches the literal source string the developer wrote. The conventional capitalization used by every existing site (and the original scaffolded template) isAccess-Control-Allow-Origin. Mitigated by the grep's case-sensitive default; if false-negatives become a real risk in the future,grep -iEis a one-character change. - R7 — Implementer sweeps
login.js/register.jsby accident. Brief 1 explicitly lists them as out-of-scope. The pre-sweep grep baseline (24 files) and the post-sweep grep baseline (0 files) make a sweep of these two visible — thegit diffwould show them as changed, but the diff would be no-op (they have nothing to delete). Mitigated by the Brief's explicit out-of-scope list and the diff-hygiene acceptance criterion (deletion count per file ≈ 9-11 lines; a no-op file would show 0).
Test plan
No new tests this convoy (Decision D4). Existing coverage continues to defend:
- Vitest (21/21): unchanged. Verifies on push via the
testjob in.github/workflows/ci.yml(blocking). - Playwright smoke (3/3): unchanged. Verifies on push via
.github/workflows/preview-smoke.yml. - Visual diff: unchanged behavior (still fails on missing baseline until
seed-visual-baselines-on-linuxlands; that's the documented Decision-4 end state ofadopt-playwright-smoke). - Lint (
|| truewrapped): baseline must match (128 problems) or drop, never grow. - New
forbidden-cors-headersCI job: locks in the sweep against future regressions. Blocking on the PR.
If fill-vitest-handler-coverage ever lands, the per-route handler tests should explicitly assert (a) response headers do NOT include any Access-Control-Allow-* and (b) OPTIONS returns 405. That's a separate convoy's scope.
Decomposition
| Brief # | Title | Files | Depends on | Estimated PR size |
|---|---|---|---|---|
| 1 | Sweep wildcard Access-Control-Allow-Origin from all 24 remaining API handlers + add CI regression-lock |
24 source files + .github/workflows/ci.yml |
none | ~260 LOC (240 deletions across 24 files + ~20-25 lines added to ci.yml) |
Single brief is the right decomposition because:
- Mechanical sweep, no per-file decisions. Every file's diff is structurally identical (Pattern A or Pattern B, both documented verbatim in Brief 1). Splitting into N briefs would multiply doc-writer overhead with zero architectural benefit.
- CI regression-lock belongs in the same PR. Landing the grep gate in a separate brief creates a window where a re-scaffolded handler could slip in undetected (and forces the regression-lock to grep-check against an empty cleanup, which would be a no-op).
- Under-400-LOC threshold honored. ~260 LOC of diff fits comfortably under the architect-contract's brief-size budget.
- Reviewable as a single diff. Reviewers can grep-spot-check the 24 files in seconds (every diff should be a pure deletion of the same 9-11 lines); the new CI job is a single self-contained block.
Slice dependencies (multitask-ready)
slice_dependencies:
- brief: 1
depends_on: []
files:
- pages/api/admin/index.js
- pages/api/auth/verify.js
- pages/api/cards/[id]/ownership.js
- pages/api/cards/owned.js
- pages/api/cards/search.js
- pages/api/collections.js
- pages/api/collections/[identifier].js
- pages/api/collections/[identifier]/activity.js
- pages/api/collections/[identifier]/cards.js
- pages/api/collections/[identifier]/permissions.js
- pages/api/collections/[identifier]/thumbnails.js
- pages/api/community/collections.js
- pages/api/favorites.js
- pages/api/invite/accept.js
- pages/api/invite/decline.js
- pages/api/public/collections.js
- pages/api/user/avatar.js
- pages/api/user/avatar/generate.js
- pages/api/user/delete.js
- pages/api/user/password.js
- pages/api/user/profile.js
- pages/api/user/settings.js
- pages/api/user/stats.js
- pages/api/users/search.js
- .github/workflows/ci.yml
Single-brief slice; conductor dispatches one implementer (no /multitask fan-out applicable). Architect complete. 1 brief created. Estimated PRs: 1. Awaiting human gate 1 (plan approval) before implementer runs.