Skip to content

Report the evaluated value when a predicate application violates a prohibition or requirement - #70

Merged
rmichaelthomas merged 1 commit into
mainfrom
fix/predicate-actual-values
Aug 2, 2026
Merged

Report the evaluated value when a predicate application violates a prohibition or requirement#70
rmichaelthomas merged 1 commit into
mainfrom
fix/predicate-actual-values

Conversation

@rmichaelthomas

Copy link
Copy Markdown
Owner

Summary

  • _condition_actual_values (interpreter.py:1915) handled ConditionNode/CompoundConditionNode but fell through to "" for PredicateApplicationNode. _exec_forbid/_exec_require only append the result when non-empty, so forbid <field> is <predicate> named the field and the predicate but never the value that tripped it — e.g. a Section 42 income-limit violation said "household-income-at-move-in is initial-income-over-limit." with no mention of the $65,000 that actually triggered it. Equality-style conditions already reported their value; this was a gap, not a design choice.
  • Adds a PredicateApplicationNode branch: reports the subject's value plus the predicate's own body — household-income-at-move-in is 65000. initial-income-over-limit: is above 58320.
  • Judgment call (flagging per the build prompt): a predicate body's field is an implicit EachPronoun, so rendering it directly produces each is above 58320 — "each" reads as noise in a violation message, so I strip a leading each before appending. This is a formatting choice, not a correctness one; happy to go the other way if you'd rather see it verbatim.
  • Scoped to exactly one function. _exec_forbid/_exec_require are unchanged (they already append actual when non-empty). render()'s output for every existing node type is unchanged — confirmed by the full existing suite passing, including round-trip tests.
  • Bumped pyproject.toml to 0.18.1 (bugfix in message content, not a language or API change).

Pre-branch confirmation (B.0)

Confirmed HEAD (de96e45) is exactly what PyPI serves as 0.18.0 before touching anything: downloaded the real liminate-0.18.0.tar.gz sdist from PyPI and diffed its src/liminate against this repo at de96e45 — byte-identical except __pycache__. Also confirmed 5db033d..de96e45 shows the TI-Q15 commit (#69) itself bumped pyproject.toml to 0.18.0, so there's no unexplained gap. No divergence to report.

Tests

Added 8 tests to tests/test_define.py, all smoke-tested against the running fix before being written down:

  1. forbid <field> is <predicate> violating → message contains the value and the predicate body
  2. require <field> is <predicate> not met → same
  3. Predicate applied to an unset subject → returns "", nothing raises (direct call, mirroring the pre-existing ConditionNode test shape)
  4. is not <predicate> (negated) violating → value still reported
    5/6. Compound condition (A and B) → reports whichever branch actually failed, whether it's the ConditionNode or the PredicateApplicationNode side (two tests, one per direction)
    7/8. Regression, blocking: exact byte-for-byte pre-existing message text for plain ConditionNode forbid/require violations, unchanged

Verification

  • pytest tests/ — full suite: 1859 passed, 0 failed

  • pytest tests/test_define.py — 55 passed (44 pre-existing + 11 new, including 3 parse-only predicate tests untouched by this change)

  • Local end-to-end check against the real Section 42 evidence-noncompliant.json (household-income-at-move-in: 65000) through the fixed interpreter and the boundary-corrected agreement (liminate-dev PR #100) produces exactly:

    Prohibition violated: household-income-at-move-in is initial-income-over-limit. household-income-at-move-in is 65000. initial-income-over-limit: is above 58320.

    This is a local sanity check, not the live re-run — that happens after publish + the liminate-dev pin bump (B.6), against the real Agreements API.

Test plan

  • pytest tests/ — 1859 passed
  • Local end-to-end check against real Section 42 evidence — exact expected message
  • Rob: merge, then publish 0.18.1 to PyPI
  • Rob: bump the liminate pin to >=0.18.1 in liminate-dev, deploy, confirm live
  • Then: re-run the §42 noncompliant fact pattern live, save receipt-noncompliant-v2.json, confirm the income-limit rule's message contains 65000

🤖 Generated with Claude Code

…ohibition or requirement

_condition_actual_values handled ConditionNode and CompoundConditionNode
but fell through to "" for PredicateApplicationNode. _exec_forbid and
_exec_require both append the result to the violation message only when
non-empty, so `forbid <field> is <predicate>` named the field and the
predicate but never the value that tripped it — e.g. "Prohibition
violated: household-income-at-move-in is initial-income-over-limit."
with no mention of the actual income. Equality-style conditions already
reported their value via the ConditionNode branch; this was a gap for
named predicates, not a design choice.

Adds a PredicateApplicationNode branch that reports the subject's actual
value plus the predicate's own body, e.g. "household-income-at-move-in
is 65000. initial-income-over-limit: is above 58320." A predicate body's
field is an implicit EachPronoun ("each is above 58320" when rendered
directly), which reads as noise in a violation message, so a leading
"each " is stripped before appending — a formatting call, not a
correctness one; noted here in case a different convention is preferred.

Scoped to exactly one function: _exec_forbid and _exec_require are
unchanged (they already append `actual` when non-empty), and render()'s
output for every existing node type is unchanged (confirmed by the full
existing suite passing, including round-trip tests).

Confirmed before branching that HEAD (de96e45) is what PyPI actually
serves as liminate 0.18.0: the published sdist's src/liminate is
byte-identical to this repo at HEAD (only __pycache__ differs), and
5db033d..de96e45 shows the TI-Q15 commit itself bumped pyproject.toml
to 0.18.0. No divergence to report.

Bumps to 0.18.1 (bugfix in message content, not a language or API
change). Adds 8 tests to tests/test_define.py: forbid/require predicate
violations report the value, an unset-subject predicate degrades to ""
without raising (direct call, mirroring the pre-existing ConditionNode
test shape), a negated predicate violation still reports, a compound
condition reports whichever branch actually failed regardless of its
type, and two regression tests pinning the exact pre-existing
ConditionNode message text byte-for-byte.

Local end-to-end check against the real Section 42 evidence-noncompliant
fact pattern (household-income-at-move-in: 65000) produces exactly:
"Prohibition violated: household-income-at-move-in is
initial-income-over-limit. household-income-at-move-in is 65000.
initial-income-over-limit: is above 58320." — not a substitute for the
post-deploy live re-run against the real Agreements API once this is
published and the liminate-dev pin is bumped, but confirms the fix and
the Build C boundary correction compose correctly.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@rmichaelthomas
rmichaelthomas merged commit ab49c24 into main Aug 2, 2026
2 checks passed
@rmichaelthomas
rmichaelthomas deleted the fix/predicate-actual-values branch August 2, 2026 18:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant