Repository navigation
Let a condition read a value from a linked row - #95
Merged
Merged
Conversation
Advances #85: priority 1 only. A `select_one` still cannot filter its source table, and that half is left open. `${data_field.special_category}` resolves through a link element of the current form to a column of the row it selects. The register can now stop asking whether this is special category data when the linked data_fields row already records it, and stop asking whether the recipient is outside the UK/EEA when the linked organisations row already implies it. That was a correctness problem, not an ergonomic one: the same fact stored twice with nothing to stop the copies disagreeing is the failure mode a compliance record cannot survive. expressions.py stays ignorant of the database. A LinkRef evaluates by calling a resolver handed to evaluate(), so the language gained a reference form and no capability - it can still read nothing its caller did not supply, which is the whole reason this is a parser and not eval. relevance.py owns the resolver, built from the form's own parameters: and rebuilt per evaluation, so its per-row cache serves six conditions over one row with one query and cannot outlive the callback that made it. The dot lives inside the braces so `${a}.__class__` is the syntax error it has always been - attribute access is a reference form, not an operator. Startup validation keeps the property it had: a reference is checked against the linked table's columns, and a misspelt column names the form and the element rather than becoming a condition that is quietly always false. Pointing at an element that is not a link, or at a link keyed on anything but id, is refused too. The submit path compiles without a schema to check against and must stay able to, so the column check is the startup one. `required:` reads the same references as `relevant:`, because the two take one language and a field whose relevance comes from the linked row takes its obligation from there too. A reference crosses at most one link. Two hops would let one form's condition walk the whole schema, and each hop is a row fetch on every keystroke. Backwards compatible: every existing expression parses and evaluates as before, and a deployment with no cross-link reference never builds a resolver that runs. 254 -> 293 tests, failure capability checked by fifteen deliberate mutations rather than assumed; two of them were missed on the first pass and the tests that now catch them exist because of it. Open: examples/ropa/ still asks special_category_data and transfer_outside_uk_eea on the record. Removing those columns is a schema change to a draft this commit does not own. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This was referenced Aug 11, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Delivers the cross-link read from #85. The remaining two acceptance items are split out as #93 (
select_onefiltering) and #94 (dropping the now-redundant ROPA columns).The dot lives inside the braces. Before it, an element of the current form with a
parameters.source_table; after it, a column of that table.Why this is a correctness fix, not a convenience
Drafting the ROPA schema (#92) forced the register to ask "is this special category data?" when the linked
data_fieldsrow already recorded it, and "is this outside the UK/EEA?" when the linked organisation already implied it. The same fact was stored twice with nothing keeping the copies consistent — in a compliance record, the failure that costs you credibility.The design property worth keeping
expressions.pystayed pure.evaluate()takes an optionalresolvecallable, and that is the only route out of the context dict. The language gained a reference form and no capability: it still cannot invent a lookup its caller did not supply, which is the reason it is a parser and noteval(#76). The database side lives inrelevance.py, withdbimported inside the function so a deployment using no cross-link reference never pulls it in.Server-side recomputation follows links too —
irrelevant_elementsandoutstandingbuild the resolver themselves — so what actually gets stored is decided with the same information the form showed.Judgement calls a reviewer should check
One hop only.
${data_field.product.name}is a parse error. Two hops let one condition walk the entire schema, and each hop is a fetch on every keystroke. The tokeniser already carries the extra segments, so relaxing it is one line if a real case appears.A reference through a soft-deleted row reads as unanswered, so a record pointing at a deleted
data_fieldsrow loses the condition rather than keeping the last known value. A deleted row is not evidence — but this is a real behaviour change if a dimension row is deleted under live records. Tracked for documentation in #94.value_columnmust beid, refused at boot. Resolving aname-keyed link againstidwould silently read the wrong row.Readable in
required:as well asrelevant:— they take one language, and in the ROPA draft nearly every element pairs the two on an identical condition.Freshness has a seam, and it is cosmetic. An edit to the linked row in another tab is not noticed until reload; the submit-time check re-reads the row, so it is never a correctness gap.
Startup validation extended: a misspelt column of the linked table, a reference through a non-link element, a bad
value_column, or an undefinedsource_tableall fail at boot naming the form, the element and the keyword.Verification
254 → 293 tests. Failure capability checked by 15 deliberate mutations — two of which initially passed. Chasing those removed a dead string-coercion path (SQLite's column affinity already matches
'1'to1) and added a test pinning that behaviour, because if it ever changes every cross-link condition on an edit form quietly reads as unanswered.Not verified: never driven in a browser, and the ROPA config contains no cross-link reference yet, so the feature has not been exercised against
examples/ropa/config/at startup.🤖 Generated with Claude Code