Take-Home · Part 1
25 +Review of the Platform Foundation handover
26 +Reading review/handover-architectu
+re.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 tha
+t looks wrong but is actually fine.
The parts the doc is confident about — the transactional -outbox, "standard JWT", "nothing exotic" — are broadly the rig -ht shapes. The damage is concentrated in the one thing it asks - the reader to trust the database to backstop: tenant isolatio -n. That control is undercut at several layers at once, and for - a clinic under Israeli privacy law plus a foundation and a pu -blic body, an isolation failure is the whole ballgame.
30 +The parts the doc is confident about — the transactiona +l outbox, "standard JWT", "nothing exotic" — are broadly the r +ight shapes. The damage is concentrated in the one thing it as +ks the reader to trust the database to backstop: tenant isolat +ion. That control is undercut at several layers at once, and f +or a clinic under Israeli privacy law plus a foundation and a +public body, an isolation failure is the whole ballgame.
31 105 -Critical — data leak or le -gal exposure
32 +Critical — data leak or +legal exposure
33 107 -C1 Tenant context leaks acros -s pooled connections Critical
109 -SET app.current_tenant = '…', which i
-s session-scoped, not SET LOCAL. The val
-ue persists on the pooled connection after the request finishe
-s. The next request that grabs that same connection — before,
-or if it ever skips, re-setting the value — runs under the pre
-vious tenant's identity.await client.query('BEGIN');
34 +
35 + C1 Tenant context leaks acr
+oss pooled connections Critical
36 + ProblemThe tenan
+t is set with SET app.current_tenant = '…', which
+ is session-scoped, not SET LOCAL. The v
+alue persists on the pooled connection after the request finis
+hes. The next request that grabs that same connection — before
+, or if it ever skips, re-setting the value — runs under the p
+revious tenant's identity.
37 + In practiceSilen
+t cross-tenant reads and writes. No error, no log — the wrong
+client's rows just come back. It is invisible in any test that
+ uses a single tenant, which is exactly how it survived to han
+dover.
38 + FixTransaction-l
+ocal, parameterized context on a connection pinned for the who
+le request:
39 + await client.query('BEGIN');
40 await client.query(
41 "SELECT set_config('app.current_tenant', $1, true)", [tenant
Id]);
42 // …all queries on this same `client`…
43 await client.query('COMMIT');
117 - The true (is_local) scopes it to the transact
-ion; parameterizing also removes the string-interpolation smel
-l (latent, since tenantId is a server-derived UUI
-D, but still wrong).
118 -
44 + The true (is_local) scopes it to the transa
+ction; parameterizing also removes the string-interpolation sm
+ell (latent, since tenantId is a server-derived U
+UID, but still wrong).C2 No field-level authorizati -on — confidential columns reach every role Critical
122 -SELECT * and the only access check is a coa
-rse per-route role gate. There is no redaction keyed to the clinical_notes and nat
-ional_id — both marked confidential, and both
-things the brief says reception must not see. That is disclosu
-re 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
-code>, applied server-side before serialization — never rely o
-n the client to hide a column it received.C2 No field-level authoriza +tion — confidential columns reach every role Critical
49 +SELECT * and the only access check is a c
+oarse per-route role gate. There is no redaction keyed to the
+sensitivity flag in the field definitions.clinical_notes and n
+ational_id — both marked confidential, and bot
+h things the brief says reception must not see. That is disclo
+sure of health data and a national identifier to an unauthoriz
+ed role, under a regulator that expects to audit. Legal exposu
+re, live today.field_definitions.sensitivity
+, applied server-side before serialization — never rely
+ on the client to hide a column it received.C3 Verify Row-Level Security -is actually forced Critica -l
129 -FORCE ROW LEVEL
- SECURITY is set. If app_user owns the ten
-ant tables (easy to end up with if the same role ran the migra
-tions), every policy in the doc is a silent no-op and there is
- zero isolation.ALTER TAB
-LE <t> FORCE ROW LEVEL SECURITY; on every tenant
-table; confirm app_user is not the owner and lack
-s BYPASSRLS. Lock it down with a test that connec
-ts as app_user and asserts it cannot read or writ
-e another tenant's row.C3 Verify Row-Level Securit +y is actually forced Criti +cal
56 +FORCE ROW LEV
+EL SECURITY is set. If app_user owns the t
+enant tables (easy to end up with if the same role ran the mig
+rations), every policy in the doc is a silent no-op and there
+is zero isolation.ALTER T
+ABLE <t> FORCE ROW LEVEL SECURITY; on every tenan
+t table; confirm app_user is not the owner and la
+cks BYPASSRLS. Lock it down with a test that conn
+ects as app_user and asserts it cannot read or wr
+ite another tenant's row.C4 audit_log has
- no tenant_id Critical
-
136 - tenan
-t_id NOT NULL, enable and force RLS on it, backfill fro
-m the referenced entities, index (tenant_id, occurred_at
-), and write it in the same transaction as the action s
-o the log can't diverge from what committed.C4 audit_log h
+as no tenant_id Critic
+al
63 + ten
+ant_id NOT NULL, enable and force RLS on it, backfill f
+rom 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.Serious — breaks under real - load or real use
68 +Serious — breaks under re +al load or real use
69 143 -S1 Outbox dedup key drops leg -itimate repeat events Serious -
145 -(event_type, entity_id). That pa
-ir is unique per entity per type, not per event.application.status_changed for the
- same application is discarded as a duplicate. Worse for Clien
-t C: a referral that goes urgent → is handled → goes urgent ag
-ain suppresses the second duty-nurse alert. A dropped four-hou
-r urgent alert is a patient-safety issue, not a missed email.<
-/div>
147 - event_id), stored in a handler-side inbox table — not on the entit
-y.S1 Outbox dedup key drops l +egitimate repeat events Serious
72 +(event_type, entity_id). That
+pair is unique per entity per type, not per event.
73 + application.status_changed for t
+he same application is discarded as a duplicate. Worse for Cli
+ent C: a referral that goes urgent → is handled → goes urgent
+again suppresses the second duty-nurse alert. A dropped four-h
+our urgent alert is a patient-safety issue, not a missed email
+.event_id<
+/code>), stored in a handler-side inbox table — not on the ent
+ity.S2 Background workers run wit
-h no tenant context Serious
-h3>
152 - ProblemThe outbox
-relay and handlers run detached from the request where the ten
-ant context is set. Nothing shows them re-applying it.
153 - In practiceUnder f
-ail-closed RLS the handler's queries return nothing, so the 14
--day email and the duty-nurse alert silently never fire; on a
-leaked pooled connection (see C1) they could act as the wrong
-tenant. The outbox row already carries tena
-nt_id — it just isn't being used.
154 - Fixset_confi
-g('app.current_tenant', row.tenant_id, true) at the sta
-rt of each event, same pinned-connection/transaction disciplin
-e as requests.
155 -
outbox row already carries tena
-nt_id — it just isn't being used.set_confi
-g('app.current_tenant', row.tenant_id, true) at the sta
-rt of each event, same pinned-connection/transaction disciplin
-e as requests.S2 Background workers run w +ith no tenant context Serious +
79 +outbox row already carries te
+nant_id — it just isn't being used.set_con
+fig('app.current_tenant', row.tenant_id, true) at the s
+tart of each event, same pinned-connection/transaction discipl
+ine as requests.S3 Create path misreads the r -esult and notifies inline Serious
159 -const record = await db.query(…RETURNING *
-) assigns the pg result object, so recor
-d.resident_phone and record.id are u
-ndefined. And the SMS is sent synchronously, after an a
-lready-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 dupli
-cate report and a duplicate SMS. The doc frames "send before t
-he 201" as a safety feature — it's the opposite, and it bypass
-es the very outbox built to solve it.resul
-t.rows[0]; enqueue the notification through the existin
-g transactional outbox instead of sending inline.S3 Create path misreads the + result and notifies inline Serious< +/span>
86 +const record = await db.query(…RETURNING
+ *) assigns the pg result object, so rec
+ord.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 i
+s the raw result envelope. If the send throws, the caller gets
+ a 500 even though the record exists, so a retry creates a dup
+licate report and a duplicate SMS. The doc frames "send before
+ the 201" as a safety feature — it's the opposite, and it bypa
+sses the very outbox built to solve it.res
+ult.rows[0]; enqueue the notification through the exist
+ing transactional outbox instead of sending inline.Minor / verify — worth fixi -ng, not urgent
165 --
167 -
- Verify (not a finding yet): the doc
- says context is set on "the connection checked out for that r
-equest," but the samples call
db.query(the pool) -. If context and queries ever land on different connections, C -1 gets much worse. Confirm the connection is genuinely pinned -— I won't assert a bug from an illustrative snippet.
168 - listRecordsinterpolates the ta -ble name (SELECT * FROM ${table}). Curre -ntly fed from a fixed map, so not live injection — but it's a -landmine for the next person. Route it through a hardcoded all -owlist. seri -ous if user-reachable
169 - - Pagination has no
limitcap and uses deepOFFSET. A client can pull an -entire table in one query, and deep pages scan-and-discard. Cl -amp the limit; move to keyset pagination for large tables. (serious under load)
170 - - No rate limiting in a shared pool. -One client's spike exhausts the connection pool for everyone, -including the clinic. Per-tenant limits + query budgets. ( -serious as clients grow) 171 -
custom_fieldsJSONB is unindexe -d and validation is app-side only; drifts from-field_definitionswith no DB constraint.
172 - - Silo / dedicated-DB isolation is unbuilt. Technically minor today, but a business risk: raise it -before the unknown fourth client signs, in case they carry a d -ata-residency clause. 173 -
app_admin(RLS-bypass) used for - ad-hoc prod fixes, and JWT revocation/TTL is unconfi -rmed — both worth tightening.
174 -
Minor / verify — worth fi +xing, not urgent
92 +-
94 +
- Verify (not a finding yet): the d
+oc says context is set on "the connection checked out for that
+ request," but the samples call
db.query(the poo +l). If context and queries ever land on different connections, + C1 gets much worse. Confirm the connection is genuinely pinne +d — I won't assert a bug from an illustrative snippet.
95 + listRecordsinterpolates the +table name (SELECT * FROM ${table}). Cur +rently fed from a fixed map, so not live injection — but it's +a landmine for the next person. Route it through a hardcoded a +llowlist. se +rious if user-reachable
96 + - Pagination has no
limitcap +strong> and uses deepOFFSET. A client can pull a +n entire table in one query, and deep pages scan-and-discard. +Clamp the limit; move to keyset pagination for large tables. < +em>(serious under load)
97 + - No rate limiting in a shared pool. One client's spike exhausts the connection pool for everyone +, including the clinic. Per-tenant limits + query budgets. (serious as clients grow) 98 +
custom_fieldsJSONB is uninde +xed and validation is app-side only; drifts fromfield_definitions with no DB constraint.
99 + - Silo / dedicated-DB isolation is unbuilt.< +/strong> Technically minor today, but a business risk: raise i +t before the unknown fourth client signs, in case they carry a + data-residency clause. 100 +
app_admin(RLS-bypass) used f +or ad-hoc prod fixes, and JWT revocation/TTL is uncon +firmed — both worth tightening.
101 +
Looks wrong, but is actually fine
178 -The RLS policy has USING but no
My first instinct was to flag this as a critical: USING filters reads, so surely writes are unconstraine
-d 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, PostgreS
-QL applies the USING expression as the write chec
-k too, for both INSERT and UPDATE. S
-o a cross-tenant write is already rejected by tenant_id
-= current_setting('app.current_tenant', true)::uuid.
Adding an explicit WITH CHECK is still wor
-th doing for clarity and to survive a future refactor that int
-roduces a separate USING clause — but it is not a
- security hole, and I'd be wrong to send someone to "fix" it a
-s one. I'm calling it out because the difference between "look
-s suspicious" and "is actually broken" is a claim about Postgr
-es I had to verify, not assume.
Looks wrong, but is actually fine
105 +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: Adding an explicit WITH CHECK is omitted, Postgr
+eSQL applies the USING expression as the write ch
+eck too, for both INSERT and UPDATE.
+ So a cross-tenant write is already rejected by tenant_i
+d = current_setting('app.current_tenant', true)::uuid.<
+/p>
108 + WITH CHECK is still w
+orth doing for clarity and to survive a future refactor that i
+ntroduces 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 "lo
+oks suspicious" and "is actually broken" is a claim about Post
+gres I had to verify, not assume.
Which I'd fix first
185 -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_use
-r and asserts it cannot read or write another tenant's
-row. That single test also settles C3.
Why ahead of C2, which is genuinely te
-mpting 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
Pin the connection, make context transaction-local and -parameterized, get the isolation test green — then C2 next, be -cause it's regulated data leaking now and it's a contained, we -ll-understood fix once the foundation holds.
189 -Which I'd fix first
112 +C1 — the tenant-context leak — gated behind t
+he app_user isolation test from C3. Befo
+re writing any fix I'd add a test that connects as app_u
+ser 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 tod +ay: blast radius and detectability. C2 is bounded to one tenan +t's internal staff and is visible once you look for it. C1 is +cross-tenant, affects every client including the clin +ic, and fails silently. It's also load-bearing — ever +y other control, including the redaction fix for C2 and the au +dit 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.
115 +Pin the connection, make context transaction-local an +d 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.
116 +Take-Home · Part 2
94 -Extend the validation library
95 -Add cross-field validation to the field-de
-finition format — so a rule like "the project end date must no
-t be before the project start date" can be declared as
- data, not written as a one-off if. The
-format design is the point; here's what I chose and why.
Take-Home · Part 2
25 +Extend the validation library
26 +Add cross-field validation to the field-
+definition format — so a rule like "the project end date must
+not be before the project start date" can be declared
+as data, not written as a one-off if. Th
+e format design is the point; here's what I chose and why.
lib/lib/The shape
111 -Rules live in an optional top-level rules ar
-ray, a sibling of fields. A definition with no { field, message } shape appended
-to the same list — so validateRecord(definition, record)
- → { valid, errors } is unchanged.
There are two rule kinds. That's the cen -tral design bet: instead of one general expression language st -uffed into JSON, the format has a small vocabulary of rule kinds, the same way the library already has a vocabulary - of field types. Two kinds cover the two dominant rea -l families — "field A relates to field B" and "field A is requ -ired depending on field B" — and each is trivial to read and t -o write.
41 +The shape
42 +Rules live in an optional top-level rules
+array, a sibling of fields. A definition with no
+rules key behaves exactly as before, and rule err
+ors use the same { field, message } shape appende
+d to the same list — so validateRecord(definition, recor
+d) → { valid, errors } is unchanged.
There are two rule kinds. That's the c +entral design bet: instead of one general expression language +stuffed into JSON, the format has a small vocabulary of rule < +em>kinds, the same way the library already has a vocabula +ry of field types. Two kinds cover the two dominant r +eal families — "field A relates to field B" and "field A is re +quired depending on field B" — and each is trivial to read and + to write.
44 114 -compare — one field against ano
-ther field or a literal
45 + compare — one field against a
+nother field or a literal
46 {
47 "rule": "compare",
48 "field": "project_end_date",
...
51 "report_on": "field",
52 "message": "Project end date must not be before the project
start date"
53 }
123 - Operators: == != > >= < <=. { "field": "…" } or
Operators: == != > >= < <=. { "field": "…" } or
+{ "value": … }. Comparison is driven by the l
+eft field's declared type — numbers numerically; dates as
+ ISO strings (whose lexicographic order is already chronologic
+al, so no timezone traps); text / choice / boolean by equality
+ only. An ordering operator on a non-orderable type is a confi
+g error, not a silent pass.
required_if — a field becomes r
-equired on a condition
56 + required_if — a field becomes
+ required on a condition
57 {
58 "rule": "required_if",
59 "field": "clinical_notes",
60 "when": { "field": "priority_level", "op": "==", "value": "u
rgent" },
61 "message": "Clinical notes are required for urgent referrals
"
62 }
132 - The when condition compares another field to
- a literal, using the same operators. If it holds and the targ
-et field is absent, the error reports on the target field.
The when condition compares another field
+to a literal, using the same operators. If it holds and the ta
+rget field is absent, the error reports on the target field.
+p>
64
134 -
The four decisions the brief asks for
65 +The four decisions the brief asks for
66 136 -1 · How a rule refers to another field
138 -By name, through an explicit operand object: { "f
-ield": "<name>" } for a field reference, {
-"value": … } for a literal. The two are distinguished s
-tructurally (which key is present), so there's never ambiguity
- between "the field named 5" and "the number 5."
-The field must exist in the same definition.
1 · How a rule refers to another field
69 +By name, through an explicit operand object: {
+"field": "<name>" } for a field reference,
+{ "value": … } for a literal. The two are distinguished
+ structurally (which key is present), so there's never ambigui
+ty between "the field named 5" and "the number 5.
+" The field must exist in the same definition.
2 · Which field the error is reported against
143 -Default is the left field
- — the subject of the sentence the rule expresses ("end da
-te must not be before start date"), and the field a user
-reads as "the one I got wrong." Overridable with report_
-on: "against" or "both" (the latter for a
-symmetric min/max pair where neither
- field is privileged). required_if always reports
- on the missing field.
2 · Which field the error is reported against
74 +Default is the left field — the subject of the sentence the rule expresses ("end
+date must not be before start date"), and the field a use
+r reads as "the one I got wrong." Overridable with repor
+t_on: "against" or "both" (the latter for
+a symmetric min/max pair where neith
+er field is privileged). required_if always repor
+ts on the missing field.
3 · What happens when a dependency is missing or inval -id
148 -Cross-field rules are a second layer t
-hat only runs when the fields it reads are individually sound
-— present and not already failing their own per-field
- validation. If a dependency is missing or invalid, the
-compare rule is skipped: the field's o
-wn error (its required or format error) already t
-ells the user what to fix, and "end must be after start" when
-start is blank or malformed is noise — or literally meaningles
-s, since you'd be comparing against a non-date. This is a deli
-berate silent-pass at the cross-field layer, not over
-all — the underlying field error still makes the record invali
-d. For required_if, if the condition fie
-ld is missing or invalid the rule doesn't fire: I won't impose
- a requirement based on an input I couldn't trust.
3 · What happens when a dependency is missing or inv +alid
79 +Cross-field rules are a second layer
+ that only runs when the fields it reads are individually soun
+d — present and not already failing their own per-fie
+ld validation. If a dependency is missing or invalid, the required or format error) already
+ tells the user what to fix, and "end must be after start" whe
+n start is blank or malformed is noise — or literally meaningl
+ess, since you'd be comparing against a non-date. This is a de
+liberate silent-pass at the cross-field layer, not ov
+erall — the underlying field error still makes the record inva
+lid. For required_if, if the condition f
+ield is missing or invalid the rule doesn't fire: I won't impo
+se a requirement based on an input I couldn't trust.
4 · Where I stopped, and why
153 -Two fixed rule kinds, one operator each — deliberately -not a general expression language. Out of sco -pe, on purpose:
154 --
155 -
- Boolean composition (AND/OR/NOT). "
-Required when urgent and cardiology" should become a
-third named rule kind with a clear schema — greppable
-, documentable, safe — not an
exprmini-language -inside JSON.
156 - - Arithmetic / derived values (
-amount / turnover < 0.5). That's Client B's eligibil -ity scoring — business logic that also needs an exter -nal API call. It belongs in service code (see Part 3, Decision - 2), not a validation definition.
157 - - Cross-record / cross-time rules (un -iqueness, non-overlap). Those need the database, not a single- -record validator. 158 -
The test is the one the brief sets: everything above ca
-n be written by someone who has never opened validate.js
-. If a needed rule can't be expressed in these two kind
-s, that's the signal to add a new named kind — with its own do
-cumented schema — not to overload the two that exist.
4 · Where I stopped, and why
84 +Two fixed rule kinds, one operator each — deliberatel +y not a general expression language. Out of s +cope, on purpose:
85 +-
86 +
- Boolean composition (AND/OR/NOT).
+ "Required when urgent and cardiology" should become
+a third named rule kind with a clear schema — greppab
+le, documentable, safe — not an
exprmini-languag +e inside JSON.
87 + - Arithmetic / derived values (
amount / turnover < 0.5). That's Client B's eligib +ility scoring — business logic that also needs an ext +ernal API call. It belongs in service code (see Part 3, Decisi +on 2), not a validation definition.
88 + - Cross-record / cross-time rules ( +uniqueness, non-overlap). Those need the database, not a singl +e-record validator. 89 +
The test is the one the brief sets: everything above
+can be written by someone who has never opened validate.
+js. If a needed rule can't be expressed in these two ki
+nds, that's the signal to add a new named kind — with its own
+documented schema — not to overload the two that exist.
Malformed rules fail closed
163 -A rule that can't be interpreted becomes a visible error, - never a silent pass — matching the library's existing "unknow -n field type produces an error rather than a crash." That cove -rs an unknown rule kind, a reference to a field that doesn't e -xist, an unknown operator, and an ordering operator on a non-o -rderable type. A bad rule surfaces in tests, not in production -.
93 +Malformed rules fail closed
94 +A rule that can't be interpreted becomes a visible erro +r, never a silent pass — matching the library's existing "unkn +own field type produces an error rather than a crash." That co +vers an unknown rule kind, a reference to a field that doesn't + exist, an unknown operator, and an ordering operator on a non +-orderable type. A bad rule surfaces in tests, not in producti +on.
95 165 -Verification & honest notes
166 -| Check | Result |
|---|---|
| Original suite (16 tests) | unmodified · all pass |
| New cross-field suite (16 tests) | pass — happy path, missing dep, inval
-id dep, report_on: both, literals, all four required_if branches, every malformed-rule case |
-tr>
171 -
lib/ client-agnostic | clean — no client names or client fi -eld names |
-
174 -
- The canonical date rule was added to Client -B's fixture (a fixture, not the library) so it's exer -cised against a real definition. 175 -
- I deliberately did not add "clinica
-l notes required for urgent referrals" to Client C's fixture:
-the existing suite asserts an urgent referral with no notes is
- valid, and inventing a business rule to break a pass
-ing test I wasn't asked to change is the wrong trade.
re -quired_ifis covered with synthetic definitions instead -.
176 -
Verification & honest notes
97 +| Check | Result |
|---|---|
| Original suite (16 tests) | unmodified · all pass |
| New cross-field suite (16 tests) | pass — happy path, missing dep, inv
+alid dep, report_on: both, literals, all four |
+
lib/ client-agnostic |
-
105 +
- The canonical date rule was added to Clien +t B's fixture (a fixture, not the library) so it's ex +ercised against a real definition. 106 +
- I deliberately did not add "clini
+cal notes required for urgent referrals" to Client C's fixture
+: the existing suite asserts an urgent referral with no notes
+is valid, and inventing a business rule to break a pa
+ssing test I wasn't asked to change is the wrong trade.
+required_ifis covered with synthetic definitions inste +ad.
107 +