Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
5.6 KiB
HANDOFF — audit / cleanup / architecture engagement
Branch: audit/cleanup-architecture (40 commits ahead of main, not pushed).
Status: all gates green — tsc 0 errors · eslint 0 errors · 645 tests · next build ✓.
Full audit + plan + progress log: docs/AUDIT-2026-08.md. Architecture: ARCHITECTURE.md.
⚠️
mainauto-deploys to production on push (.github/workflows/deploy.yml→ SSH → dropletdeploy.sh). Do not push tomainuntil this branch is reviewed and you intend to deploy.
What was done
Delivered against the original brief (audit → fixes → tests → refactor → docs), then a second separation-of-concerns pass on top.
- Type safety. 586 → 0
tscerrors; removednext.configtypescript.ignoreBuildErrors, so every build/deploy now type-checks. Root cause was the@supabase/ssr↔supabase-jsversion drift (schema collapsed tonever), fixed by annotating the client factories (no dependency bump). - Tooling / CI. Added ESLint 9 flat config (
eslint.config.mjs), atypecheckscript, and a checks-only CI workflow (.github/workflows/ci.yml: typecheck + lint + unit tests), separate from deploy. - Confirmed bugs fixed (one commit + regression test each): invite
max_usesTOCTOU (atomic CAS), board invite re-arming,appealIncidentadmin-gate + orphan rollback, SSRF hex-IPv6, payment dues regression (advance-only) + paymentmember_idIDOR scoping, three null-deref crashes, broken member-directory visibility gate, equipment/proposals scoping + unverified-member gating, door-log parser, CSV-import hang, and a plaintext-secrets security bug (ops "Add Secret" wrote the value unencrypted from the browser — now viacreateSecret, AES-GCM). - Tests. Replaced a tautological
actions.test.ts(asserted string literals) with real authz/IDOR tests; added authz-primitive tests and per-fix regression tests. - Refactor (all 10 slices).
lib/validations.ts(143 exports) →lib/validations/*barrel;lib/types.ts→types/domain/*;lib/actions/{classes,door,forms}.ts→ per-capability folders behind thelib/actionsbarrel; the 663–956-lineops/settings/membersclient components decomposed into thin orchestrators +panels/+components/(also fixed a form-field-focus footgun); dead code removed.types/database.tsis left pristine (regenerate, don't hand-edit). - Separation of concerns. No client component writes to the DB directly anymore. The ops area-lead roster and the comms message send were the last holdouts — both now go through server actions (validated, authz-gated, space-scoped, audited); comms derives sender identity server-side (was impersonation-prone). Only browser-client usage left: auth (login/signup) and the comms realtime read.
- Verification. A re-audit found zero refactor regressions (every authz gate +
space_idscope + handler independently verified across the splits/decompositions). A final independent adversarial review of the separation-of-concerns commits found no introduced bug, security hole, or regression.
How to verify (from a clean checkout of the branch)
pnpm install --frozen-lockfile
pnpm run typecheck # 0 errors
pnpm run lint # 0 errors (warnings are tracked debt)
pnpm run test:run # 645 pass
pnpm build # compiles + type-checks all routes
What is NOT done / residual risk
- Full authenticated UI testing did not run — the dev environment had no Docker → no local Supabase.
Public pages + routing were browser-smoke-tested (render clean; authed routes redirect to
/login), and all routes compile vianext build, but the decomposed ops/settings/members screens were not clicked through against real data. Do a manual smoke-test against a real backend before merge. types/database.tsneeds regeneration against the live schema (supabase gen types typescript --schema public > types/database.ts). That removes 3 hand-added RPC entries and ~11// TODO(types)join casts, and lets the governance rows intypes/domain/governance.tscollapse toTables<>/Enums<>. Needs DB credentials. Highest-leverage next step.- Deferred bug L3 —
markOnboardingStepDoneread-modify-writes a JSON column; a concurrent double-submit can drop a step. Needs a DB-side atomic append (RPC / jsonb operator + migration). - CI runs only unit tests — the integration + Playwright e2e suites need a DB service wired in (and the run should fail on 0 executed integration tests so a missing DB can't masquerade as passing).
- Pre-existing comms realtime dedup edge case (
app/(app)/comms/comms-client.tsx~L82) — two identical messages fired in quick succession before the first realtime event lands can collapse onto one row (duplicate render / duplicate React key). Not introduced by this work (the old direct-insert path had the same race); the robust fix is a client-generated message id / nonce for dedup. - Commit granularity caveat — commits are logically split (one per fix), and most were gated before
commit; a few in the final SoC round were split from a combined working tree and were not each
independently
tsc/test-gated. The final tree is fully green. If you need per-commit bisectability, verify per commit or squash-merge.
Recommended merge path
Review Phases A + B + CI first (low-risk, high-value: type safety, bug fixes, tests). Then the refactor
slices (structural, verified by tsc + build + tests). Then the SoC pass. Merge via PR; a squash or
merge-commit is fine. After merge to main it deploys — have the manual UI smoke-test done first.