Skip to content

fix(inputlayer-py): reject value and sort-direction keywords in query/why/debug with where=/order_by= hints - #246

Merged
jsam merged 3 commits into
mainfrom
fm/sdk-py-why-fix
Oct 4, 2026
Merged

jsam merged 3 commits into
mainfrom
fm/sdk-py-why-fix

Conversation

@jsam

@jsam jsam commented Oct 4, 2026 •

Copy link
Copy Markdown
Contributor

Intent

So fix the SDKs by improving them. (Context: the messaging challenger found that kg.why is broken in the Python SDK on main today: kg.why(View, order=...) or kg.why(View, key=value) raised a TypeError, and kg.why on a derived view was reported to send an invalid '.why ?' statement, while the engine's own .why returns proof trees fine. The JS twin moved debug/why onto the same compiled QueryPlan as query in #229.)

What Changed

  • KnowledgeGraph._plan (used by query, why and debug) no longer passes non-expression keyword arguments through as computed columns. It now raises a CompileError before anything is sent to the engine. A sort direction such as order=Employee.id.desc() gets an order_by= hint. A plain value such as department="eng" gets a where=lambda r: r.department == 'eng' hint when the keyword names a real column of the queried relations, and a generic where=/order_by= hint when it doesn't.
  • Added unit tests in test_compile_rules.py. For each of query, why and debug, they check the three keyword error cases (value keyword, sort keyword, keyword that names no column) and confirm nothing is executed.
  • Added live tests in docs_queries_live.py for kg.why on the derived Reachable view. They cover where=, order_by= with limit=, and a count() aggregate, and check that each row's proof-tree conclusion matches its result row. One more test checks that kg.why(Reachable, src=1) is rejected with the where= hint.

Risk Assessment

✅ Low: The change is small. It turns a non-Expr keyword value, which already failed with a TypeError inside the compiler, into a CompileError with a hint, so no call that worked before is broken. It also adds live and unit tests that exercise behavior.

Testing

I built the release inputlayer-server and let the build finish, as the operator asked. I started it on a disposable port with its own data directory and ran a driver script that logs every statement the SDK sends. With the head SDK, all six scenarios passed. The same script on the base-commit SDK reproduced the original TypeErrors and the invalid sort IQL reaching the engine. The SDK's live test files (docs_queries_live, compile_rules_live, subscriptions_live) passed against the same server, as did the mocked keyword tests. Afterwards I stopped the server, deleted its data directory and ran cargo clean.

  • Live validation: ✅ go - 6 of 6 scenarios driven live against the product
Scenario Result Live Evidence
kg.why(Reachable, where=src==1) on a derived view returns (1,2),(1,3),(1,4), and each row's proof tree conclusion matches it ✅ pass live live_head.txt S1: sent '.why il_q(Src, Dst) <- reachable(Src, Dst), Src = 1'; 3 trees, MATCH=True
kg.why(Reachable, order_by=dst.desc(), limit=2) returns (1,4),(2,4), and the proofs match the rows in order ✅ pass live live_head.txt S2: MATCH(in order)=True dst desc=[4,4]
kg.why(Reachable.src, count(Reachable.dst)), an aggregate over the derived view, returns (1,3),(2,2),(3,1) with matching proofs ✅ pass live live_head.txt S3: MATCH=True
Adversarial: why/query/debug(Reachable, src=1) raises a CompileError with the hint where=lambda r: r.src == 1, and nothing is sent (base SDK raised a TypeError) ✅ pass live live_head.txt S4/S4b/S4c: statements sent NONE; live_base.txt: TypeError: Cannot compile expression: 1
Adversarial: why/query(Reachable, order=Reachable.dst.desc()) and order='desc' raise a CompileError with an order_by= hint, and nothing is sent (base SDK sent invalid 'Order = Dst:desc') ✅ pass live live_head.txt S5a-c: statements sent NONE; live_base.txt S5a/S5c: engine rejected 'Dst:desc'
Regression: computed-column keywords n=count(Reachable.dst) and d=Reachable.dst+1 still return correct rows from query ✅ pass live live_head.txt S6a: [(1,3),(2,2),(3,1)]; S6b: [(1,2,3),(1,3,4),(1,4,5)]
Evidence: Live transcript, head SDK (rows, proof conclusions, statements sent)
== SDK under test: head (~/.no-mistakes/worktrees/9eea230c3aae/01M43JZYRYY4ZZR5395W6HHBD9/packages/inputlayer-py/src/inputlayer/__init__.py)

--- S1 why(Reachable, where=src==1) on derived view
  columns=['src', 'dst'] rows=[(1, 2), (1, 3), (1, 4)]
  proof conclusions=[[1, 2], [1, 3], [1, 4]]
  MATCH=True n_trees=3
  statements sent: ['.why il_q(Src, Dst) <- reachable(Src, Dst), Src = 1']

--- S2 why(Reachable, order_by=dst.desc(), limit=2)
  rows=[(1, 4), (2, 4)]
  proof conclusions=[[1, 4], [2, 4]]
  MATCH(in order)=True dst desc=[4, 4]
  statements sent: ['.why il_q(Src, Dst) <- reachable(Src, Dst)']

--- S3 why(Reachable.src, count(Reachable.dst)) aggregate over derived view
  rows=[(1, 3), (2, 2), (3, 1)]
  proof conclusions=[[1, 3], [2, 2], [3, 1]]
  MATCH=True
  statements sent: ['.why il_q(Src, count<Dst>) <- reachable(Src, Dst)']

--- S4 why(Reachable, src=1)  [value keyword]
  CompileError: src=1 is not an expression: keyword arguments name computed columns (to filter on a value, pass where=lambda r: r.src == 1)
  hint: to filter on a value, pass where=lambda r: r.src == 1
  statements sent: NONE

--- S4b query(Reachable, src=1)
  CompileError: src=1 is not an expression: keyword arguments name computed columns (to filter on a value, pass where=lambda r: r.src == 1)
  hint: to filter on a value, pass where=lambda r: r.src == 1
  statements sent: NONE

--- S4c debug(Reachable, src=1)
  CompileError: src=1 is not an expression: keyword arguments name computed columns (to filter on a value, pass where=lambda r: r.src == 1)
  hint: to filter on a value, pass where=lambda r: r.src == 1
  statements sent: NONE

--- S5a why(Reachable, order=Reachable.dst.desc())
  CompileError: order= is a sort direction, not a computed column (to sort, pass order_by= instead of order=)
  hint: to sort, pass order_by= instead of order=
  statements sent: NONE

--- S5b why(Reachable, order='desc')
  CompileError: order='desc' is not an expression: keyword arguments name computed columns (to filter, pass where=; to sort, pass order_by=)
  hint: to filter, pass where=; to sort, pass order_by=
  statements sent: NONE

--- S5c query(Reachable, order=Reachable.dst.desc())
  CompileError: order= is a sort direction, not a computed column (to sort, pass order_by= instead of order=)
  hint: to sort, pass order_by= instead of order=
  statements sent: NONE

--- S6a query(Reachable.src, n=count(Reachable.dst)) computed agg keyword
  columns=['src', 'n'] rows=[(1, 3), (2, 2), (3, 1)]
  statements sent: ['il_q(Src, count<Dst>) <- reachable(Src, Dst)\n?il_q(Src, N)']

--- S6b query(Reachable, d=Reachable.dst + 1, where src==1) computed expr keyword
  columns=['src', 'dst', 'd'] rows=[(1, 2, 3), (1, 3, 4), (1, 4, 5)]
  statements sent: ['?reachable(Src, Dst), D = Dst + 1, Src = 1']
Evidence: Live transcript, base-commit SDK (reproduces TypeError and invalid Dst:desc IQL)
== SDK under test: base (~/.no-mistakes/evidence/01M43JZYRYY4ZZR5395W6HHBD9/base_sdk/packages/inputlayer-py/src/inputlayer/__init__.py)

--- S1 why(Reachable, where=src==1) on derived view
  columns=['src', 'dst'] rows=[(1, 2), (1, 3), (1, 4)]
  proof conclusions=[[1, 2], [1, 3], [1, 4]]
  MATCH=True n_trees=3
  statements sent: ['.why il_q(Src, Dst) <- reachable(Src, Dst), Src = 1']

--- S2 why(Reachable, order_by=dst.desc(), limit=2)
  rows=[(1, 4), (2, 4)]
  proof conclusions=[[1, 4], [2, 4]]
  MATCH(in order)=True dst desc=[4, 4]
  statements sent: ['.why il_q(Src, Dst) <- reachable(Src, Dst)']

--- S3 why(Reachable.src, count(Reachable.dst)) aggregate over derived view
  rows=[(1, 3), (2, 2), (3, 1)]
  proof conclusions=[[1, 3], [2, 2], [3, 1]]
  MATCH=True
  statements sent: ['.why il_q(Src, count<Dst>) <- reachable(Src, Dst)']

--- S4 why(Reachable, src=1)  [value keyword]
  TypeError: Cannot compile expression: 1
  statements sent: NONE

--- S4b query(Reachable, src=1)
  TypeError: Cannot compile expression: 1
  statements sent: NONE

--- S4c debug(Reachable, src=1)
  TypeError: Cannot compile expression: 1
  statements sent: NONE

--- S5a why(Reachable, order=Reachable.dst.desc())
  QueryError: Why error: Query execution failed: Invalid term: 'Dst:desc'
  query: .why il_q(Src, Dst, Order) <- reachable(Src, Dst), Order = Dst:desc
  statements sent: ['.why il_q(Src, Dst, Order) <- reachable(Src, Dst), Order = Dst:desc']

--- S5b why(Reachable, order='desc')
  TypeError: Cannot compile expression: 'desc'
  statements sent: NONE

--- S5c query(Reachable, order=Reachable.dst.desc())
  QueryError: Program has 1 parse error(s)
  query: ?reachable(Src, Dst), Order = Dst:desc
  statements sent: ['?reachable(Src, Dst), Order = Dst:desc']

--- S6a query(Reachable.src, n=count(Reachable.dst)) computed agg keyword
  columns=['src', 'n'] rows=[(1, 3), (2, 2), (3, 1)]
  statements sent: ['il_q(Src, count<Dst>) <- reachable(Src, Dst)\n?il_q(Src, N)']

--- S6b query(Reachable, d=Reachable.dst + 1, where src==1) computed expr keyword
  columns=['src', 'dst', 'd'] rows=[(1, 2, 3), (1, 3, 4), (1, 4, 5)]
  statements sent: ['?reachable(Src, Dst), D = Dst + 1, Src = 1']
Evidence: Live pytest run (docs_queries_live, compile_rules_live, subscriptions_live)
..............................................................           [100%]
62 passed in 27.31s
Evidence: Live scenario driver
"""Live driver: drives kg.why/query/debug scenarios against a running inputlayer-server.

Every statement the SDK sends is logged (wrapping KnowledgeGraph._execute), so
"nothing sent" is observable. Run with PYTHONPATH pointed at the SDK under test.
"""
import asyncio, os, sys, traceback
from typing import ClassVar
import inputlayer
from inputlayer import Derived, From, InputLayer, Relation, count
from inputlayer.exceptions import CompileError

URL = os.environ["INPUTLAYER_TEST_SERVER"]
LABEL = sys.argv[1] if len(sys.argv) > 1 else "sdk"
print(f"== SDK under test: {LABEL} ({inputlayer.__file__})")

class Edge(Relation):
    src: int
    dst: int

class Reachable(Derived):
    src: int
    dst: int
    rules: ClassVar[list] = []

Reachable.rules = [
    From(Edge).select(src=Edge.src, dst=Edge.dst),
    From(Reachable, Edge).where(lambda r, e: r.dst == e.src).select(src=Reachable.src, dst=Edge.dst),
]

def proved(why):
    return [t.nodes[t.roots[0]].conclusion.args for t in why.proof_trees]

async def main():
    il = InputLayer(URL, username="admin", password="admin")
    await il.connect()
    name = f"why_drive_{LABEL.replace('-', '_')}"
    try:
        await il.drop_knowledge_graph(name)
    except Exception:
        pass
    kg = il.knowledge_graph(name)
    await kg.define(Edge)
    await kg.insert([Edge(src=1, dst=2), Edge(src=2, dst=3), Edge(src=3, dst=4)])
    await kg.define_rules(Reachable)

    sent = []
    orig = kg._execute
    async def spy(iql, **kw):
        sent.append(iql)
        return await orig(iql, **kw)
    kg._execute = spy

    async def scenario(title, fn):
        sent.clear()
        print(f"\n--- {title}")
        try:
            await fn()
        except CompileError as e:
            print(f"  CompileError: {e}")
            print(f"  hint: {getattr(e, 'hint', None)}")
        except Exception as e:
            print(f"  {type(e).__name__}: {e}")
        print(f"  statements sent: {sent if sent else 'NONE'}")

    async def s1():
        w = await kg.why(Reachable, where=lambda r: r.src == 1)
        rows = w.results.to_tuples()
        print(f"  columns={w.results.columns} rows={rows}")
        print(f"  proof conclusions={proved(w)}")
        print(f"  MATCH={sorted(map(list, rows)) == sorted(proved(w))} n_trees={len(w.proof_trees)}")
    async def s2():
        w = await kg.why(Reachable, order_by=Reachable.dst.desc(), limit=2)
        rows = w.results.to_tuples()
        print(f"  rows={rows}")
        print(f"  proof conclusions={proved(w)}")
        print(f"  MATCH(in order)={[list(r) for r in rows] == proved(w)} dst desc={[r[1] for r in rows]}")
    async def s3():
        w = await kg.why(Reachable.src, count(Reachable.dst), join=[Reachable])
        rows = w.results.to_tuples()
        print(f"  rows={rows}")
        print(f"  proof conclusions={proved(w)}")
        print(f"  MATCH={[list(r) for r in rows] == proved(w)}")
    async def s4():
        w = await kg.why(Reachable, src=1)
        print(f"  returned (no error): {w.results.to_tuples()}")
    async def s4q():
        r = await kg.query(Reachable, src=1)
        print(f"  returned (no error): {r.to_tuples()}")
    async def s4d():
        r = await kg.debug(Reachable, src=1)
        print(f"  returned (no error): {r.iql}")
    async def s5a():
        w = await kg.why(Reachable, order=Reachable.dst.desc())
        print(f"  returned (no error): {w.results.to_tuples()}")
    async def s5b():
        w = await kg.why(Reachable, order="desc")
        print(f"  returned (no error): {w.results.to_tuples()}")
    async def s5c():
        r = await kg.query(Reachable, order=Reachable.dst.desc())
        print(f"  returned (no error): {r.to_tuples()}")
    async def s6a():
        r = await kg.query(Reachable.src, n=count(Reachable.dst), join=[Reachable])
        print(f"  columns={r.columns} rows={sorted(r.to_tuples())}")
    async def s6b():
        r = await kg.query(Reachable, d=Reachable.dst + 1, where=lambda r: r.src == 1)
        print(f"  columns={r.columns} rows={sorted(r.to_tuples())}")

    await scenario("S1 why(Reachable, where=src==1) on derived view", s1)
    await scenario("S2 why(Reachable, order_by=dst.desc(), limit=2)", s2)
    await scenario("S3 why(Reachable.src, count(Reachable.dst)) aggregate over derived view", s3)
    await scenario("S4 why(Reachable, src=1)  [value keyword]", s4)
    await scenario("S4b query(Reachable, src=1)", s4q)
    await scenario("S4c debug(Reachable, src=1)", s4d)
    await scenario("S5a why(Reachable, order=Reachable.dst.desc())", s5a)
    await scenario("S5b why(Reachable, order='desc')", s5b)
    await scenario("S5c query(Reachable, order=Reachable.dst.desc())", s5c)
    await scenario("S6a query(Reachable.src, n=count(Reachable.dst)) computed agg keyword", s6a)
    await scenario("S6b query(Reachable, d=Reachable.dst + 1, where src==1) computed expr keyword", s6b)

    kg._execute = orig
    await il.drop_knowledge_graph(name)
    await il.close()

asyncio.run(main())
- Outcome: 🔧 2 issues found → no changes applied ✅ across 2 runs (8m36s)

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

⚠️ **Review** - 3 issues (1 warning, 2 infos)
  • ⚠️ packages/inputlayer-py/src/inputlayer/knowledge_graph.py:491 - The intent names kg.why(View, order=...) as one of the failing calls. The new check only catches plain values. OrderedColumn subclasses Expr, so kg.why(Reachable, order=Reachable.dst.desc()) (and the same call through query/debug) still passes the isinstance(v, Expr) branch and becomes a computed column named order. The compiler's bind() then emits Order = Dst:desc (compile_expr turns an OrderedColumn into var:desc). That is invalid IQL, so the call fails at the engine with an opaque error instead of an SDK hint. Fix: before the Expr branch at line 491, reject OrderedColumn values with a CompileError whose hint is order_by=. OrderedColumn is already imported. This applies to the shared _plan, which covers query, debug and why.

🔧 Fix applied.
2 issues (1 warning, 1 info) still open:

  • ⚠️ packages/inputlayer-py/src/inputlayer/knowledge_graph.py:491 - The intent names kg.why(View, order=...) as one of the failing calls. The new check only catches plain values. OrderedColumn subclasses Expr, so kg.why(Reachable, order=Reachable.dst.desc()) (and the same call through query/debug) still passes the isinstance(v, Expr) branch and becomes a computed column named order. The compiler's bind() then emits Order = Dst:desc (compile_expr turns an OrderedColumn into var:desc). That is invalid IQL, so the call fails at the engine with an opaque error instead of an SDK hint. Fix: before the Expr branch at line 491, reject OrderedColumn values with a CompileError whose hint is order_by=. OrderedColumn is already imported. This applies to the shared _plan, which covers query, debug and why.
  • ℹ️ packages/inputlayer-py/src/inputlayer/knowledge_graph.py:501 - The plain-value branch always suggests where=lambda r: r.{k} == {v!r}, even when k is not a column of any selected or joined relation. Example: kg.why(Reachable, order=&#34;desc&#34;) raises a CompileError (correct: nothing is sent), but its hint where=lambda r: r.order == &#39;desc&#39; points at a column that doesn't exist. This is the same sort-keyword misuse that round 1's OrderedColumn branch (line 491) handles, so the string spelling was left behind. Optional fix: suggest where= only when k names a column of a relation in the plan. Otherwise give a generic hint that mentions where=/order_by=.

🔧 Fix applied.
3 issues (1 warning, 2 infos) still open:

  • ⚠️ packages/inputlayer-py/src/inputlayer/knowledge_graph.py:491 - The intent names kg.why(View, order=...) as one of the failing calls. The new check only catches plain values. OrderedColumn subclasses Expr, so kg.why(Reachable, order=Reachable.dst.desc()) (and the same call through query/debug) still passes the isinstance(v, Expr) branch and becomes a computed column named order. The compiler's bind() then emits Order = Dst:desc (compile_expr turns an OrderedColumn into var:desc). That is invalid IQL, so the call fails at the engine with an opaque error instead of an SDK hint. Fix: before the Expr branch at line 491, reject OrderedColumn values with a CompileError whose hint is order_by=. OrderedColumn is already imported. This applies to the shared _plan, which covers query, debug and why.
  • ℹ️ packages/inputlayer-py/src/inputlayer/knowledge_graph.py:501 - The plain-value branch always suggests where=lambda r: r.{k} == {v!r}, even when k is not a column of any selected or joined relation. Example: kg.why(Reachable, order=&#34;desc&#34;) raises a CompileError (correct: nothing is sent), but its hint where=lambda r: r.order == &#39;desc&#39; points at a column that doesn't exist. This is the same sort-keyword misuse that round 1's OrderedColumn branch (line 491) handles, so the string spelling was left behind. Optional fix: suggest where= only when k names a column of a relation in the plan. Otherwise give a generic hint that mentions where=/order_by=.
  • ℹ️ packages/inputlayer-py/src/inputlayer/knowledge_graph.py:508 - The where= hint added in the round-2 fix always suggests a one-argument lambda (where=lambda r: r.{k} == {v!r}). _plan calls where(*proxies) with one proxy per relation. So with more than one relation in play, for example kg.query(Employee, Dept, join=[Employee, Dept], department=&#39;eng&#39;), following the hint literally raises a TypeError. A related ordering issue: is_column only sees relations added so far. A value keyword placed before an aggregate keyword that brings in its relation, as in query(department=&#39;eng&#39;, total=count(Employee.id)), gets the generic hint instead of the where= one. Both only affect hint wording. No invalid IQL is ever sent. Optional fix: emit the lambda form only when len(relations) == 1.
🔧 **Test** - 2 issues found → no changes applied ✅
  • ⚠️ I could not show the live intent (why() on derived views returning proof trees end-to-end) because the inputlayer-server release build had not finished when results were required. Only the mocked unit tests for keyword rejection were run, and they passed. Run make python-sdk-live, or the evidence driver script against a local server, to complete live validation.
  • ⚠️ live validation verdict: inconclusive (0 of 6 scenarios were driven live against the product); untested: kg.why(Reachable, where=src==1) on a derived view returns rows with matching proof trees instead of sending an invalid '.why ?', kg.why(Reachable, order_by=dst.desc(), limit=2) returns ordered, paged rows whose proofs match the rows, kg.why on an aggregate over a derived view (count(dst) by src) returns proof trees, Adversarial: kg.why(View, key=value) raises a CompileError with a where= hint instead of a TypeError, and sends nothing, Adversarial: kg.why(View, order=View.dst.desc()) or order='desc' raises a CompileError with an order_by= hint, and sends nothing, Regression: real computed-column keywords (n=count(dst), d=dst+1) still work in query
  • Live validation: ⚠️ inconclusive - 0 of 6 scenarios driven live against the product
Scenario Result Live Evidence
kg.why(Reachable, where=src==1) on a derived view returns rows with matching proof trees instead of sending an invalid '.why ?' ⏸️ untested no This needs a running inputlayer-server. The release build (cargo build --release into /tmp/nm-01M43-target) was still compiling when the result was required. Re-run once the binary exists: `make pytho…
kg.why(Reachable, order_by=dst.desc(), limit=2) returns ordered, paged rows whose proofs match the rows ⏸️ untested no Same blocker: the engine binary had not finished building, so there was no live server.
kg.why on an aggregate over a derived view (count(dst) by src) returns proof trees ⏸️ untested no Same blocker: the engine binary had not finished building, so there was no live server.
Adversarial: kg.why(View, key=value) raises a CompileError with a where= hint instead of a TypeError, and sends nothing ⏸️ untested no Passed at unit level only (mocked _execute). The live run against a real server was blocked by the unfinished engine build.
Adversarial: kg.why(View, order=View.dst.desc()) or order='desc' raises a CompileError with an order_by= hint, and sends nothing ⏸️ untested no Passed at unit level only. The live run was blocked by the unfinished engine build.
Regression: real computed-column keywords (n=count(dst), d=dst+1) still work in query ⏸️ untested no Not driven live: the engine binary had not finished building.
  • uv run --extra dev pytest tests/test_compile_rules.py -k KeywordArguments -q (9 passed; transport is mocked, so this is not a live run)
  • CARGO_TARGET_DIR=/tmp/nm-01M43-target cargo build --release --bin inputlayer-server (still compiling when this result was due)
  • Wrote the live driver ~/.no-mistakes/evidence/01M43JZYRYY4ZZR5395W6HHBD9/drive_why.py and extracted the base-commit SDK to /tmp/nm-01M43-base for a before/after comparison (not executed: no server yet)

🔧 No changes applied.
✅ Re-checked - no issues remain.

  • Live validation: ✅ go - 6 of 6 scenarios driven live against the product
Scenario Result Live Evidence
kg.why(Reachable, where=src==1) on a derived view returns (1,2),(1,3),(1,4), and each row's proof tree conclusion matches it ✅ pass live live_head.txt S1: sent '.why il_q(Src, Dst) <- reachable(Src, Dst), Src = 1'; 3 trees, MATCH=True
kg.why(Reachable, order_by=dst.desc(), limit=2) returns (1,4),(2,4), and the proofs match the rows in order ✅ pass live live_head.txt S2: MATCH(in order)=True dst desc=[4,4]
kg.why(Reachable.src, count(Reachable.dst)), an aggregate over the derived view, returns (1,3),(2,2),(3,1) with matching proofs ✅ pass live live_head.txt S3: MATCH=True
Adversarial: why/query/debug(Reachable, src=1) raises a CompileError with the hint where=lambda r: r.src == 1, and nothing is sent (base SDK raised a TypeError) ✅ pass live live_head.txt S4/S4b/S4c: statements sent NONE; live_base.txt: TypeError: Cannot compile expression: 1
Adversarial: why/query(Reachable, order=Reachable.dst.desc()) and order='desc' raise a CompileError with an order_by= hint, and nothing is sent (base SDK sent invalid 'Order = Dst:desc') ✅ pass live live_head.txt S5a-c: statements sent NONE; live_base.txt S5a/S5c: engine rejected 'Dst:desc'
Regression: computed-column keywords n=count(Reachable.dst) and d=Reachable.dst+1 still return correct rows from query ✅ pass live live_head.txt S6a: [(1,3),(2,2),(3,1)]; S6b: [(1,2,3),(1,3,4),(1,4,5)]
  • CARGO_INCREMENTAL=0 CARGO_PROFILE_RELEASE_DEBUG=0 cargo build --release --bin inputlayer-server (completed)
  • INPUTLAYER_ADMIN_PASSWORD=admin target/release/inputlayer-server --host 127.0.0.1 --port 18392 --data-dir <tmp>
  • PYTHONPATH=src python drive_why_scenarios.py head (live, head SDK)
  • PYTHONPATH=&lt;base sdk&gt; python drive_why_scenarios.py base (live, base-commit SDK, before/after)
  • pytest tests/docs_queries_live.py tests/compile_rules_live.py tests/subscriptions_live.py against the live server (62 passed)
  • pytest tests/test_compile_rules.py -k KeywordArguments (mocked)
  • cargo clean
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

jsam added 3 commits October 4, 2026 13:46
… keywords with a where= hint

why() on a derived relation, filtered, ordered and aggregated, now has live
tests pinning each proof tree to its row. A keyword argument that is not an
expression (why(View, key=1)) raised 'TypeError: Cannot compile expression';
it is now a CompileError naming where= as the way to filter.
@jsam jsam changed the title fix(inputlayer-py): why() on derived views, live-tested; value keywords get a where= hint fix(inputlayer-py): reject value and sort-direction keywords in query/why/debug with where=/order_by= hints Oct 4, 2026
@jsam
jsam marked this pull request as ready for review October 4, 2026 14:09
@jsam
jsam merged commit ae6f895 into main Oct 4, 2026
20 checks passed
@jsam
jsam deleted the fm/sdk-py-why-fix branch October 4, 2026 14:10
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