Take-Home · Part 1
Reading review/handover-architecture.md as the engineer who now owns the system. What I'd act on, how bad each item is, what I'd do — and one thing that looks wrong but is actually fine.
The parts the doc is confident about — the transactional outbox, "standard JWT", "nothing exotic" — are broadly the right shapes. The damage is concentrated in the one thing it asks the reader to trust the database to backstop: tenant isolation. That control is undercut at several layers at once, and for a clinic under Israeli privacy law plus a foundation and a public body, an isolation failure is the whole ballgame.
SET app.current_tenant = '…', which is session-scoped, not SET LOCAL. The value persists on the pooled connection after the request finishes. The next request that grabs that same connection — before, or if it ever skips, re-setting the value — runs under the previous tenant's identity.await client.query('BEGIN');
await client.query(
"SELECT set_config('app.current_tenant', $1, true)", [tenantId]);
// …all queries on this same `client`…
await client.query('COMMIT');
The true (is_local) scopes it to the transaction; parameterizing also removes the string-interpolation smell (latent, since tenantId is a server-derived UUID, but still wrong).SELECT * and the only access check is a coarse per-route role gate. There is no redaction keyed to the sensitivity flag in the field definitions.clinical_notes and national_id — both marked confidential, and both things the brief says reception must not see. That is disclosure of health data and a national identifier to an unauthorized role, under a regulator that expects to audit. Legal exposure, live today.field_definitions.sensitivity, applied server-side before serialization — never rely on the client to hide a column it received.FORCE ROW LEVEL SECURITY is set. If app_user owns the tenant tables (easy to end up with if the same role ran the migrations), every policy in the doc is a silent no-op and there is zero isolation.ALTER TABLE <t> FORCE ROW LEVEL SECURITY; on every tenant table; confirm app_user is not the owner and lacks BYPASSRLS. Lock it down with a test that connects as app_user and asserts it cannot read or write another tenant's row.audit_log has no tenant_id Criticaltenant_id NOT NULL, enable and force RLS on it, backfill from the referenced entities, index (tenant_id, occurred_at), and write it in the same transaction as the action so the log can't diverge from what committed.(event_type, entity_id). That pair is unique per entity per type, not per event.application.status_changed for the same application is discarded as a duplicate. Worse for Client C: a referral that goes urgent → is handled → goes urgent again suppresses the second duty-nurse alert. A dropped four-hour urgent alert is a patient-safety issue, not a missed email.event_id), stored in a handler-side inbox table — not on the entity.outbox row already carries tenant_id — it just isn't being used.set_config('app.current_tenant', row.tenant_id, true) at the start of each event, same pinned-connection/transaction discipline as requests.const record = await db.query(…RETURNING *) assigns the pg result object, so record.resident_phone and record.id are undefined. And the SMS is sent synchronously, after an already-committed insert, before the 201.undefined; the 201 body is the raw result envelope. If the send throws, the caller gets a 500 even though the record exists, so a retry creates a duplicate report and a duplicate SMS. The doc frames "send before the 201" as a safety feature — it's the opposite, and it bypasses the very outbox built to solve it.result.rows[0]; enqueue the notification through the existing transactional outbox instead of sending inline.db.query (the pool). If context and queries ever land on different connections, C1 gets much worse. Confirm the connection is genuinely pinned — I won't assert a bug from an illustrative snippet.listRecords interpolates the table name (SELECT * FROM ${table}). Currently fed from a fixed map, so not live injection — but it's a landmine for the next person. Route it through a hardcoded allowlist. serious if user-reachablelimit cap and uses deep OFFSET. A client can pull an entire table in one query, and deep pages scan-and-discard. Clamp the limit; move to keyset pagination for large tables. (serious under load)custom_fields JSONB is unindexed and validation is app-side only; drifts from field_definitions with no DB constraint.app_admin (RLS-bypass) used for ad-hoc prod fixes, and JWT revocation/TTL is unconfirmed — both worth tightening.The RLS policy has USING but no WITH CHECK — and that's OK. Correct as written
My first instinct was to flag this as a critical: USING filters reads, so surely writes are unconstrained and a handler could insert or move a row into another tenant. I checked the Postgres semantics before writing it down, and it's wrong. When WITH CHECK is omitted, PostgreSQL applies the USING expression as the write check too, for both INSERT and UPDATE. So a cross-tenant write is already rejected by tenant_id = current_setting('app.current_tenant', true)::uuid.
Adding an explicit WITH CHECK is still worth doing for clarity and to survive a future refactor that introduces a separate USING clause — but it is not a security hole, and I'd be wrong to send someone to "fix" it as one. I'm calling it out because the difference between "looks suspicious" and "is actually broken" is a claim about Postgres I had to verify, not assume.
C1 — the tenant-context leak — gated behind the app_user isolation test from C3. Before writing any fix I'd add a test that connects as app_user and asserts it cannot read or write another tenant's row. That single test also settles C3.
Why ahead of C2, which is genuinely tempting to grab first because it's regulated data leaking today: blast radius and detectability. C2 is bounded to one tenant's internal staff and is visible once you look for it. C1 is cross-tenant, affects every client including the clinic, and fails silently. It's also load-bearing — every other control, including the redaction fix for C2 and the audit trail in C4, assumes the database knows which tenant it is acting as. Layering fixes on top of a context that can serve the wrong tenant's rows is polishing a broken foundation.
Pin the connection, make context transaction-local and parameterized, get the isolation test green — then C2 next, because it's regulated data leaking now and it's a contained, well-understood fix once the foundation holds.