| name | red-team-review |
| description | Adversarial bug-hunting code review of the apps/ monorepo — apps/web, @iep/domain, @iep/data, @iep/services, @iep/auth. Hunts real bugs in state-machine guards, rights derivation, Drizzle queries, server actions, persona/audit emission, and idempotent seed/migration code. Not style nits. Runs inline in the current chat (no subagent fork). Use when the user asks for a "red team", "bug hunt", "adversarial review", or at checkpoints during long lifts. |
Red-team adversary review (inline)
Perform this review inline in the current chat using the session's own Read, Grep, and Bash tools. Do NOT spawn an Agent — the goal is to use the active session, not to fork a separate run.
Scope is the consolidated app: apps/web and packages/{ui,domain,data,services,auth}. Prototypes in prototypes/ are out of scope (they're a source-of-lift, not a thing we ship). The development harness at apps/control-room is out of scope too — it isn't shipped.
When to invoke
- User says: "red team", "bug hunt", "adversarial review", "find what's broken", "find real bugs"
- After a non-trivial change to a state machine, rights derivation, server action, or Drizzle query
- After a prototype lift, before its commit lands
- Before a demo or external review
Setup — read these first
Scope to the change set. If none was named, default to the load-bearing surfaces:
packages/domain/src/machines/ard/ — ARD lifecycle machine, guards (quorumGate, recordingGate, recessAllowed, amendmentInScope), replayARDLifecycle.
packages/domain/src/rights/ — deriveVisibility, canPerform, role-grant time windows, edge filtering.
packages/data/src/queries/*.ts — every Drizzle query. Pay special attention to where(...) clauses (boundary conditions, null handling, time windows) and inArray(...) with possibly-empty inputs.
packages/data/src/schema/*.ts — column constraints, FK cascade rules, jsonb shapes.
apps/web/src/app/**/actions.ts and apps/web/src/app/**/route.ts — server actions and route handlers (auth, validation, revalidation, audit emission).
apps/web/src/lib/composition.ts (thin re-export) and packages/services/src/composition.ts — composition root. Wiring mistakes here propagate.
apps/web/src/lib/persona.ts + packages/auth/src/persona-token.ts — persona resolution, signed cookies.
- Anything the user names as recently changed.
Skim, don't deep-read — the goal is to spot bugs, not summarize files.
What to hunt — adapt to the change set
Find real bugs, correctness issues, and abuse risks — not style nits. Rank findings CRITICAL / HIGH / MEDIUM / LOW.
-
State-machine replay correctness. replayARDLifecycle rebuilds state from audit_logs. Are initialContext flags (activePhysicalDanger, expellableOffense, requiredRoles) supplied at every call site, or do default values silently change carve-out behavior? Does a missing actorPersonId (null in the audit row) get coerced consistently? Are at timestamps monotonic, or can clock skew put events out of order on replay?
-
Guard predicates. In machine.ts, quorumGate counts excused-with-input as covered. Does it still pass when requiredRoles is empty (vacuous quorum) — is that the desired behavior for the seed-stage IEP, or a leak? Does recordingGate block on the six-key special-factors checklist when specialFactorsConsidered is null vs. an empty object? Does amendmentInScope accept a scopeDiff with zero fields (no-op amendment = "in scope" trivially)?
-
Rights derivation. deriveVisibility walks graph edges. Are role-grant start/end windows applied consistently between loadActiveEdges (which filters) and canPerform (which may or may not)? An expired CASE_MANAGES edge that still appears in a stale snapshot lets a former case manager read a child's plan. Does canSeeStudent short-circuit administrators correctly without also bypassing time-window checks?
-
TOCTOU in server actions. Does any actions.ts read state, compute, and write in two separate round-trips without a transaction? Concurrent clicks (or duplicate-fire from stale useTransition state) can replay the read and double-insert. Look for "count then insert" or "select then update" patterns missing db.transaction(...) or SELECT FOR UPDATE.
-
Audit emission gaps. Every state transition should emit to audit_logs. Check that server actions wrap the data write + audit emit in the same tx so an audit failure rolls back the data write (and vice versa). A naked db.insert(...) followed by services.audit.emit(...) is two transactions.
-
Drizzle correctness. with is undefined-behavior territory — confirm callers guard. where could be undefined silently becomes in some Drizzle versions. chains with nullable cols on the right — confirm SELECT returns expected nulls vs. omitting rows. without a tiebreaker on a non-unique key gives nondeterministic pagination.
Walk the list. Skip what doesn't apply. Add branches that fit the specific area touched.
Output format
Inline in the conversation, ~300–500 words. Group by severity. For each finding:
file:line reference (markdown link form [name](path#Lline) for VSCode click-through).
- one-sentence description of the bug.
- one-sentence trigger / exploit condition.
Do NOT propose fixes — just the bug. End with a one-sentence delta vs. the last red-team if one exists. Be terse. Be specific.
Cleanup
You are the main session — don't create scratch files. Output findings inline. End with a git status check; nothing new should be staged.