Repository navigation
feat(protocol): subscription groups and snapshot reads at one revision - #278
Merged
Merged
Conversation
…n, in the protocol and both SDKs (EN-15)
# Conflicts: # docs/content/docs/guides/websocket-api.mdx # docs/spec/asyncapi.yaml # gui/lib/docs-bundle.ts # packages/inputlayer-js/src/connection.ts # packages/inputlayer-js/src/protocol.ts # packages/inputlayer-py/src/inputlayer/connection.py # src/protocol/rest/handlers/ws.rs # src/protocol/rest/handlers/ws/execute.rs # src/protocol/rest/handlers/ws/request.rs # src/protocol/subscription/connection.rs # src/protocol/subscription/connection/tests.rs # src/protocol/subscription/dependencies.rs # src/protocol/subscription/subscriber/tests.rs # ws-protocol/src/client.rs # ws-protocol/src/lib.rs
jsam
marked this pull request as ready for review
October 5, 2026 14:05
…ide iql=, narrow read options
… stale test assertion. The failing test was `tests/test_meta.py::TestSessionCommands::test_list_rules_sends_session_and_parses_the_clauses`. **Cause:** the round-1 review-5 fix (which the user chose) made `Session.list_rules` accept a keyword-only `timeout` and always pass it on as `execute('.session', timeout=timeout)`. That way `read`'s session-rule check uses the read's own timeout. The test still expected `execute('.session')` with no `timeout` argument, so it failed against the intended behaviour. **Rule that must hold:** `list_rules` sends `.session` with the caller's timeout, and the default is `None`. I checked the other places that call `execute('.session')`: - The drop-clause test only checks positional args (`.args`), so it is unaffected. - The subscription and group tests' fake connections match on the program string, so they are unaffected. - The `*_live.py` tests call `kg.execute` directly, not `list_rules`. **Fix:** the assertion now expects `execute('.session', timeout=None)`. I made no change to SDK source code, and the review-5 behaviour stays as the user chose it. **Verified:** `make python-test` gives 1339 passed, 35 skipped, 0 failed
This was referenced Oct 5, 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.
Intent
I want you to really hyper focus on making the symbolic engine, you know, crisp and really truly production ready. Like I really need you to, you know, put insane amount of effort into doing that. And then once you have that, I will give you the endpoints, but only then.
(Context: approved plan ~/firstmate/data/nsdl-plan/report.md §6.1, GitHub milestone 7 'Engine production readiness'. This task is item EN-15: subscription groups: several queries in one subscribe frame, an atomic snapshot, one publication per commit touching any member, all members exact at one revision, unchanged members marked — why: coherent windows with one
revision_seen(challenge-1 B2). M1 critical path)Substance of the referenced item (plan §6.1 EN-15, GitHub issue #265 "Engine: snapshot multi-query reads and subscription groups at one revision"): a caller that renders a state window from several views must see them all at the same revision; today an unchanged view publishes nothing, so a subscriber cannot tell a view is still current at a revision, and a program with several
?queries replies with the last one only. Deliver (1) a snapshot read of several queries at one revision in one request, and (2) subscription groups: several queries in one subscribe frame, an atomic initial snapshot, one publication per commit that touches any member, all members exact at one revision, unchanged members marked. Done when both are in the protocol and both SDKs (Python and JS), with tests for atomicity under concurrent writes. Every engine PR must not regress millisecond query and update latency.What Changed
ws-protocol,src/protocol) gets two new client frames.readruns several named?queries on one pinned knowledge-graph snapshot and replies with asnapshotframe: one result per query, all exact at a singlerevision. Large replies stream assnapshot_start/snapshot_chunk/snapshot_end. A read sees persistent data only, uses the request's deadline and cancellation, and fails as a whole while naming the failing query.subscribeopens a subscription group of several queries. It starts with one atomic initial snapshot, then sends onesubscription_group_delta(or a chunked start/chunk/end) per commit that touches any member. Every member is exact at that delta's revision, and members that didn't change are markedunchanged. A group ends with.unsubscribe.kg.read()andkg.subscribeGroup()(GroupSubscription,GroupChange,MemberChange,ReadResult). The Python SDK adds the same set with matching connection, protocol and exception handling. Each SDK checks streamed snapshots and group deltas (chunk order, row counts, an error arriving mid-stream) before applying them.ws_cancelandws_deliverytests), along with new JS and Python SDK tests. The subscription benchmarks are adjusted to the new code.Risk Assessment
Testing
I built a release head server and a release base-commit server outside the worktree, each on its own throwaway data dir and port, and drove EN-15 end to end against them. Every live scenario passed. With raw /ws frames I checked: - the multi-queryread; - a subscription group: unchanged members are marked, one push per commit, and no push when no member changes; - atomicity across 5 runs of 4 concurrent writers, with zero disagreements. Early runs of my own driver failed because it exceeded the engine's 100 msgs/s per-connection rate limit. Once I paced the driver, every run passed. The repo's live suites passed: Pythonsubscriptions_live.py(17 tests) and JSsubscriptions.integration.test.ts(15 tests). Scripts I wrote confirmed review fixes 1, 2 and 6 live in both SDKs; review-1 used a proxy that rewrites protocol_version to 4. Review-3 is a compile-time type shape with no runtime surface, so it is untested live. An offline tsc check with a negative control supports it. The latency probe showed base and head within noise. I tore down the servers, the proxy, the data dirs and the build dirs, and the worktree is clean. There is no UI, so the evidence is transcripts.readframe with 3 named queries returns onesnapshotat one revision, results in request order; limit honoured with truncated flag; a bad query fails the whole read naming it ('Query 'brok…subscribegroup: atomic snapshot naming the subscription at one revision; a commit touching one member gives ONE subscription_group_delta listing every member in order, with the untouched me…pytest tests/subscriptions_live.py, 17 passedvitest run tests/subscriptions.integration.test.ts, 15 passedEvidence: Raw /ws protocol transcript (read, group, atomicity)
Evidence: Atomicity under 4 concurrent writers, 5 runs
Evidence: Python SDK live suite output
Evidence: JS SDK live integration output
Evidence: Python SDK review-fix checks (review-1, -2, -6)
Evidence: JS SDK review-1 and group smoke
Evidence: review-3 ReadOptions type check (offline)
Evidence: Latency base vs head
base query p50 1.788/1.823ms, update p50 2.081/2.047ms; head query p50 1.805/1.791ms, update p50 2.025/2.038ms; head with a group live: query 2.061ms, update 2.244msEvidence: Driver and proxy scripts
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
packages/inputlayer-js/src/subscription.ts:721- A protocol-version mismatch reaches the user as a retryable ConnectionLostError. When kg.subscribeGroup() runs against an engine reporting protocol_version 4, the pump in connection.ts:642-651 rejects the call with a plain ConnectionError ('need version 5'). openWithBackoff then rewraps every non-lost ConnectionError as ConnectionLostError('Connection lost: ...', 'closed'). The same thing happens on a resync after reconnecting to an older engine. kg.read returns the plain ConnectionError correctly. Fix: let non-lost ConnectionErrors through unchanged.packages/inputlayer-py/src/inputlayer/knowledge_graph.py:883- _shape returns early for a target given as iql= and silently drops limit, offset and order_by, which now mean something for read. For example, await kg.read({'a': {'iql': '?a(X)', 'limit': 10}}) returns every row instead of 10 and raises no error. Fix: refuse limit, offset and order_by alongside iql=.packages/inputlayer-js/src/knowledge-graph.ts:593- kg.read accepts ExecuteOptions, which includes params. The params are stored on the call but only execute sends them, so kg.read(..., {params: {x: 1}}) drops them silently. Fix: use a narrower options type for read, or refuse params.packages/inputlayer-js/src/subscription.ts:1108- Unverified group events mark every member unchanged: true. That contradicts the documented meaning 'exact at the event's revision', because an unverified event carries the last verified revision.packages/inputlayer-py/src/inputlayer/subscription.py:470- read's session-rule check calls list_rules() with the connection's default timeout, not the read's timeout. With timeout=None and a short default, a slow '.session list' can fail a read that succeeded. Fix: pass the read's timeout to list_rules().packages/inputlayer-py/src/inputlayer/knowledge_graph.py:848- Only SubscriptionRejected errors get the 'Query <name>:' prefix, so a CompileError from one member doesn't say which member failed. Also, misspelled keys in a dict target (e.g. 'limt') are taken as computed columns instead of being rejected.🔧 Fix applied.
2 infos still open:
packages/inputlayer-js/src/subscription.ts:1108- Unverified group events mark every member unchanged: true. That contradicts the documented meaning 'exact at the event's revision', because an unverified event carries the last verified revision.packages/inputlayer-py/src/inputlayer/subscription.py:470- read's session-rule check calls list_rules() with the connection's default timeout, not the read's timeout. With timeout=None and a short default, a slow '.session list' can fail a read that succeeded. Fix: pass the read's timeout to list_rules().✅ **Test** - passed
✅ No issues found.
readframe with 3 named queries returns onesnapshotat one revision, results in request order; limit honoured with truncated flag; a bad query fails the whole read naming it ('Query 'brok…subscribegroup: atomic snapshot naming the subscription at one revision; a commit touching one member gives ONE subscription_group_delta listing every member in order, with the untouched me…pytest tests/subscriptions_live.py, 17 passedvitest run tests/subscriptions.integration.test.ts, 15 passedcargo build --release --bin inputlayer-server(head, CARGO_TARGET_DIR in /tmp), server on a throwaway --data-dir, port 18734python raw_protocol_driver.py: raw /ws read, subscribe group and concurrent-writer atomicity (5 repeated runs)pytest tests/subscriptions_live.py -vagainst the live server (17 passed)vitest run tests/subscriptions.integration.test.tsagainst the live server (15 passed)python py_sdk_review_fixes.py: review-1 (via v4_proxy.py), review-2 and review-6 in the Python SDK, livenode js_sdk_review_fixes.cjs(esbuild bundle of the worktree src): JS read and subscribeGroup smoke plus review-1 via v4_proxy.py, livetsc --noEmit read_options_typecheck.tsplus a negative control (review-3, offline compile-time check)git archive c550f359plus a release build of the base server;latency_probe.pyalternating base and head, and head with a group live✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.