Accept bare dates in temporal prefixes; restrict arithmetic to linear forms#62
Merged
rmichaelthomas merged 2 commits intoJul 20, 2026
Merged
Conversation
reorder() guarded the starting/until temporal-prefix branch on TokenType.QUOTED_STRING only, but the lexer emits TokenType.DATE for bare dates (starting 2025-07-01 ...). The parser already handled both forms correctly, but every existing test called parse(tokenize(...)) directly and never exercised reorder(), so liminate.run() — the only route real callers (including seshat-app's check_action) use — silently rejected bare-date temporal windows with ERROR_PARSE. Confirmed identical at parent commit c918fa0, so this predates PR #60/#61 and is not a regression from either. Widens both guards to accept QUOTED_STRING or DATE via one shared _TEMPORAL_DATE_TOKENS constant, so the two sites can't drift apart again the same way.
Fable decidability condition (c): a nonlinear condition (both operands of multiplied_by/divided_by runtime-resolved, e.g. `beta multiplied by beta`) parsed, analyzed, and executed inside forbid/require/permit/ expect/choose conditions with no restriction. plus/minus are linear and stay unrestricted in every operand combination; dates already reject multiplied_by/divided_by at runtime and that message is untouched. The restriction is scoped to the value side of a deontic/choice condition only — the one place that will ever reach a future SMT encoder — via a `restrict_nonlinear` flag threaded through _check_arithmetic/_check_arithmetic_operand, defaulting False and set True only at the _resolve_choose_operand call site. An earlier, broader version of this fix applied the restriction everywhere _check_arithmetic is reached and broke 8 pre-existing tests that multiply two ordinary remembered values (price * quantity, rate * hours, etc.) — value computation that never reaches a policy encoder. Those tests are untouched. Known gap, documented rather than fixed: the restriction only inspects a condition's own expression tree. `remember a value called doubled from beta multiplied by beta` followed by `forbid alpha is above doubled` currently passes, because the condition sees a bare NameRef, never the ArithmeticNode that produced it. This is not composition- specific — any remembered-value indirection reproduces it, and it's the common authoring form, not an edge case. Closing it requires value provenance (taint at store time, a definition-site walk, or resolving names to their defining expressions inside the future Z3 encoder) — none of which are built. Condition (c) is therefore syntactically enforced but not fully closed; a dedicated test pins the current behaviour so provenance work finds it immediately.
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.
Summary
Two independent fixes in the enforcement fragment, both verified against the live build at
92eb657(PR #61 merge).Fix A — temporal-prefix bare-date acceptance (bug fix)
reorder()guarded thestarting/untiltemporal-prefix branch onTokenType.QUOTED_STRINGonly; the lexer emitsTokenType.DATEfor bare dates. The parser already handles both forms correctly, but every existing test calledparse(tokenize(...))directly and never exercisedreorder()— soliminate.run(), the only route real callers (includingseshat-app'scheck_action) use, silently rejected bare-date temporal windows withERROR_PARSE.Root cause: token-type guard mismatch between lexer output and reorderer expectation.
Not a regression —
reorderer.pyis byte-identical between parent commitc918fa0(pre PR #60/#61) and92eb657, confirmed viagit diff.Fix: both guards widened to accept
QUOTED_STRINGorDATEvia one shared_TEMPORAL_DATE_TOKENSconstant, so the two sites can't drift apart the same way again.Fix B — arithmetic linearity restriction (Fable decidability condition (c))
A nonlinear expression (both operands of
multiplied_by/divided_byruntime-resolved, e.g.beta multiplied by beta) parsed, analyzed, and executed insideforbid/require/permit/expect/chooseconditions with no restriction — a precondition gap for the future Z3 satisfiability encoder (Fable Step 2, not built yet).plus/minusare linear and stay unrestricted in every operand combination; date arithmetic already rejectsmultiplied_by/divided_byat runtime and that message is untouched.Scope note: the restriction is deliberately narrower than the original build prompt specified. An initial universal version — restricting nonlinearity everywhere
_check_arithmeticis reached — broke 8 pre-existing tests that multiply two ordinary remembered values (price * quantity,rate * hours, etc.), because_check_arithmeticis shared by 6 call sites and only 2 of them are ever reachable from a deontic/choice condition. The restriction is scoped to exactly those 2 sites via arestrict_nonlinearflag threaded through_check_arithmetic/_check_arithmetic_operand, defaultingFalse, setTrueonly at the_resolve_choose_operandcall site (the value side offorbid/require/permit/expect/choose). All 1668 baseline tests pass unmodified.Known gap, documented rather than fixed: the restriction only inspects a condition's own expression tree.
remember a value called doubled from beta multiplied by betafollowed byforbid alpha is above doubledcurrently passes analysis and evaluates, because the condition sees a bareNameRef, never theArithmeticNodethat produced it. This is not composition-specific — any remembered-value indirection reproduces it, and it is the common authoring form, not an edge case. Condition (c) is therefore syntactically enforced for direct condition arithmetic, but not fully closed — closing it requires value provenance (taint at store time, a definition-site walk, or resolving names to their defining expressions inside the future Z3 encoder), none of which exist yet. A dedicated test (test_KNOWN_GAP_value_indirection_bypasses_the_linearity_restriction) pins the current behavior so provenance work finds it immediately.Files modified
src/liminate/reorderer.py— Fix Asrc/liminate/analyzer.py— Fix Btests/test_reorderer.py— Fix A tests (8 new, throughliminate.run())tests/test_arithmetic.py— Fix B tests (18 new, including the boundary pair and the documented known-gap test)Invariants satisfied
plus/minusunrestricted in every operand combination.git diff --statontests/shows insertions only.Suite count
main@92eb657(measured via isolated worktree): 1668 passed / 0 failed / 0 skipped — the original build prompt's stated "1,666 passing + 2 skipped" was stale/inaccurate for this environment.Test plan
python3 -m pytest -q— 1694 passed, 0 failed, 0 skippedliminate.run()starting_date/until_datepopulate on the AST for bare dates;starting ... until ... inheritedcombination parsesbeta * beta,beta / beta, nested) rejects withERROR_SEMANTICinsideforbid/require/permit/expect/chooseplus/minus, all-literal nested) unchanged🤖 Generated with Claude Code