Skip to content

docs: describe recompute-on-read for persistent rules and re-evaluate-and-diff subscriptions - #372

Merged
jsam merged 3 commits into
mainfrom
fm/m9-d1-docs
Oct 5, 2026
Merged

jsam merged 3 commits into
mainfrom
fm/m9-d1-docs

Conversation

@jsam

@jsam jsam commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

Intent

please leave this query batching functionliaty just refactor things so its clear and optimized for this ... auto materialize needs to be default mode of operation where rules get deployed as views and everything else is a query .. make the docs true with the correct systems design ... this is not accceptable state for production readiness (Context: implement #306 exactly as written.)

What Changed

  • Rewrote the docs, guides, spec pages, READMEs, examples, blog/compare/customer content and landing-page copy (plus the bundled front/gui content) so they no longer say persistent rules are incrementally maintained or materialized. They now say that persistent rules are stored definitions, evaluated from the current base facts on every query that reads them; that subscriptions re-evaluate after each relevant commit and push the diff; and that the Magic Sets figures in BENCHMARKS.md and the benchmark blog compare a bound query against a full-closure recomputation, not an incremental update against a full refresh.
  • Trimmed docs/content/docs/internals/architecture.mdx to match the current server path. docs/internals/architecture.md is now a short pointer to it, since its old content described a rule-materialization subsystem that doesn't run on the server. The OpenAPI views stat and the AsyncAPI subscribe description now spell out the evaluate-on-query and re-evaluate-and-diff behaviour, and the docs point to milestone 9 (Epic: persistent rules are incrementally maintained views; everything else is a query (E0) #305) for maintained views.
  • Corrected doc comments in src/incremental.rs, src/derived_relations.rs, src/storage_engine/mod.rs, src/statement/mod.rs, src/lib.rs and the JS SDK's clearRule: IncrementalEngine and auto_materialize are test-only base-relation mirrors and nothing derived is stored. Also fixed a broken intra-doc link to params::bind_statement in src/ast/mod.rs. No code behaviour changes.

Risk Assessment

✅ Low: The change touches only documentation, marketing copy and doc comments, with no executable code changes; regenerating both bundles reproduced them byte-for-byte, and the remaining issues are a few small factual inaccuracies in the docs.

Testing

This is a docs and comments change (plus regenerated bundles). Validation had two parts. First, both generated doc bundles regenerate byte-identical from source. Second, I built and ran a disposable local server and drove the behaviours the rewritten docs now describe. A recursive persistent rule is recomputed on read, so deleting an edge correctly removes downstream reachability. Persistent rules and base facts survive a restart; facts are stored as WAL plus Parquet batches. Type declarations and derived rows are not stored. Standing queries emit a snapshot followed by exact insert and retract deltas after each commit. All the changed example scripts still run, and those with snapshot files produce output identical to their base-commit versions. Everything matched the docs. The only note is an informational one about leftover wording in the crate description. All temporary builds, servers and data were removed and the worktree is clean.

  • Live validation: ✅ go - 6 of 6 scenarios driven live against the product
Scenario Result Live Evidence
Generated GUI docs bundle (gui/lib/docs-bundle.ts) matches a fresh regeneration from docs/content ✅ pass live docs-bundle-sync.txt: node gui/scripts/bundle-docs.mjs leaves no diff
Generated front content bundle matches a fresh regeneration from content/ ✅ pass live front-bundle-sync.txt
Recompute on read: deleting an edge in a recursive rule removes the downstream rows on the next query, and a new path appears (BENCHMARKS headline and architecture claim) ✅ pass live s1-recursive-retraction.txt: reach(1,Y)={2,3,4,5} becomes {2} after -edge(2,3), then {2,5,9} after adding 2->9->5
Persistence table: facts and persistent rules survive a restart (facts in WAL plus Parquet); type declarations and derived rows are not stored ✅ pass live s2-after-restart.txt plus data-dir inspection: persist/wal and persist/batches/*.parquet, rules/catalog.json with reach, .rel lists only edge, no type store
Standing query emits a snapshot, then re-evaluate-and-diff deltas after each commit, including retraction through recursion ✅ pass live s3-subscription-deltas.txt: snapshot {Y=2,3}; +edge(3,4) gives delta +{Y=4}; -edge(2,3) gives delta -{Y=3,Y=4}
Example IQL scripts whose comments changed still run, with output unchanged from the base commit ✅ pass live example-*.txt; 121 and 03 identical to base-commit output (03's parse errors are pre-existing and present in its .out)
Evidence: Recursive retraction recomputed on read (live CLI transcript)
Connecting to server at http://127.0.0.1:18491...
Connected!

Server status: healthy
Authenticated as: admin
Current knowledge graph: default

> .kg create nm_docs_check
Knowledge graph 'nm_docs_check' created.
Switched to knowledge graph: nm_docs_check
> .kg use nm_docs_check
Switched to knowledge graph: nm_docs_check
> +edge[(1,2),(2,3),(3,4),(4,5)]
Inserted 4 fact(s) into 'edge'.
> +reach(X,Y) <- edge(X,Y)
Rule 'reach' registered.
> +reach(X,Z) <- edge(X,Y), reach(Y,Z)
Rule 'reach' registered.
> type Email: string
Type 'Email' declared.
> ?reach(1, Y)
┌───┬───┐
│ 1 │ Y │
├───┼───┤
│ 1 │ 2 │
│ 1 │ 3 │
│ 1 │ 4 │
│ 1 │ 5 │
└───┴───┘
4 rows
> -edge(2,3)
Deleted 1 facts from 'edge'.
> ?reach(1, Y)
┌───┬───┐
│ 1 │ Y │
├───┼───┤
│ 1 │ 2 │
└───┴───┘
1 rows
> +edge(2,9)
Inserted 1 fact(s) into 'edge'.
> +edge(9,5)
Inserted 1 fact(s) into 'edge'.
> ?reach(1, Y)
┌───┬───┐
│ 1 │ Y │
├───┼───┤
│ 1 │ 2 │
│ 1 │ 5 │
│ 1 │ 9 │
└───┴───┘
3 rows
> .rule list
Rules:
  reach (2 clause(s))
Evidence: After restart: rules and facts persisted, no stored derived relation
Connecting to server at http://127.0.0.1:18491...
Connected!

Server status: healthy
Authenticated as: admin
Current knowledge graph: default

> .kg use nm_docs_check
Switched to knowledge graph: nm_docs_check
> .rel
Relations:
  edge (arity: 2, columns: [col0: any, col1: any], tuples: 5)
> .rule list
Rules:
  reach (2 clause(s))
> ?edge(X, Y)
┌───┬───┐
│ X │ Y │
├───┼───┤
│ 1 │ 2 │
│ 2 │ 9 │
│ 3 │ 4 │
│ 4 │ 5 │
│ 9 │ 5 │
└───┴───┘
5 rows
> ?reach(1, Y)
┌───┬───┐
│ 1 │ Y │
├───┼───┤
│ 1 │ 2 │
│ 1 │ 5 │
│ 1 │ 9 │
└───┴───┘
3 rows
> +contact(email: Email)
Schema for 'contact' registered with 1 columns (persistent)
Evidence: Subscription snapshot and deltas (Python SDK, live)
event: snapshot inserted= ['Row(1=1, Y=2)', 'Row(1=1, Y=3)'] retracted= [] verified= True
commit: +edge(3,4)
event: delta inserted= ['Row(1=1, Y=4)'] retracted= [] verified= True
commit: -edge(2,3)
event: delta inserted= [] retracted= ['Row(1=1, Y=3)', 'Row(1=1, Y=4)'] verified= True
Evidence: GUI docs bundle sync check
BUNDLE IN SYNC
Evidence: Front content bundle sync check
FRONT BUNDLE IN SYNC
Evidence: Changed landing-page example 02_incremental run
Connecting to server at http://127.0.0.1:18491...
Connected!

Server status: healthy
Authenticated as: admin
Current knowledge graph: default

> .kg create landing_incremental
Knowledge graph 'landing_incremental' created.
Switched to knowledge graph: landing_incremental
> .kg use landing_incremental
Switched to knowledge graph: landing_incremental
> +manages[("alice", "bob"), ("bob", "charlie")]
Inserted 2 fact(s) into 'manages'.
> +authority(X, Y) <- manages(X, Y)
Rule 'authority' registered.
> +authority(X, Z) <- authority(X, Y), manages(Y, Z)
Rule 'authority' registered.
> ?authority("alice", Who)
┌─────────┬───────────┐
│ alice   │ Who       │
├─────────┼───────────┤
│ "alice" │ "bob"     │
│ "alice" │ "charlie" │
└─────────┴───────────┘
2 rows
> +manages("charlie", "diana")
Inserted 1 fact(s) into 'manages'.
> ?authority("alice", Who)
┌─────────┬───────────┐
│ alice   │ Who       │
├─────────┼───────────┤
│ "alice" │ "bob"     │
│ "alice" │ "charlie" │
│ "alice" │ "diana"   │
└─────────┴───────────┘
3 rows
> .kg use default
Switched to knowledge graph: default
> .kg drop landing_incremental
Knowledge graph 'landing_incremental' dropped.
- Outcome: ⚠️ 1 info across 1 run (5m40s)

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

⚠️ **Review** - 1 info
  • ⚠️ docs/content/docs/internals/type-system.mdx:228 - The new 'Persisted' column says type declarations are persisted ('Yes'). That is false. The server's handler for Statement::TypeDecl (src/protocol/handler.rs:2750) only pushes the message "Type 'X' declared." and does not register or store anything, so the type is gone after the request. The same table also says base facts are persisted in 'WAL + relation store' (line 232). The relation store is in memory; facts are persisted through the WAL and Parquet batch files, which is what the architecture doc Section 8 says. This adds a new false statement in a PR whose goal is making the docs true. Fix: mark type as 'No' (or 'N/A, not stored'), change the facts cell to 'Yes (WAL + Parquet)', and regenerate gui/lib/docs-bundle.ts. The legacy docs/internals/type-system.md table has no Persisted column and needs no change.
  • ⚠️ BENCHMARKS.md:77 - Issue Docs truth: every statement that claims materialization or incremental maintenance (D1) #306 marks BENCHMARKS.md:77 as misleading ('correctness comes from recomputation'), but the edit keeps the bold headline 'No other IQL engine handles retraction through recursive fixpoints.' The rewritten sentence after it now says InputLayer is correct because every query recomputes from the remaining facts, which is also how a Souffle re-run or PostgreSQL REFRESH reaches correct results. The headline still implies a unique retraction mechanism, contradicting both the new text and line 65 ('Nothing is retracted from maintained state; there is none'). Fix: reword the headline to the real difference, for example that a bound recursive re-query after a delete stays correct without a full-closure refresh, instead of claiming unique retraction handling.
  • ℹ️ docs/content/docs/internals/coding-standards.mdx:684 - This PR retires docs/internals/architecture.md and replaces it with a stub that points to the .mdx. It updated the legacy docs/internals/coding-standards.md:683 to point to architecture.mdx, but the canonical, bundled coding-standards.mdx:684 still tells readers to 'Read docs/internals/architecture.md', which is now the stub. The two edits are the wrong way round: point the canonical .mdx at docs/content/docs/internals/architecture.mdx (and regenerate the gui docs bundle).
  • ℹ️ src/incremental.rs:6 - The rewritten module doc says the engine is enabled only by tests through StorageEngine::enable_incremental. No such method exists. The method is KnowledgeGraph::enable_incremental (src/storage_engine/mod.rs:2319), which tests reach through with_kg_mut / kg.write(). Fix the path in the doc comment.
  • ℹ️ docs/content/docs/internals/architecture.mdx:645 - Scope note, no action needed. The free-text intent says 'auto materialize needs to be default mode... rules get deployed as views', but the authoritative context is to implement Docs truth: every statement that claims materialization or incremental maintenance (D1) #306 (D1) exactly. Docs truth: every statement that claims materialization or incremental maintenance (D1) #306 requires the docs to describe recompute-on-read until V20 ships, and this change does that. The target design (deployed rules as maintained views, everything else a query, query batching kept and simplified, the cost probe removed) is recorded as planned in architecture.mdx Section 16 and is not claimed as shipped. Making views the engine default belongs to the Epic: persistent rules are incrementally maintained views; everything else is a query (E0) #305 V-children, not to this docs PR.

🔧 Fix applied.
1 info still open:

⚠️ **Test** - 1 info
  • ℹ️ Cargo.toml:16 - The crate description in Cargo.toml:16, which is also what inputlayer-server --help prints, still says "conclusions stay current as facts change". This PR reworded the same phrase in front/app/layout.tsx to "derives every conclusion from the current facts when it is read". It is arguably still true from a user's point of view because reads recompute, but it is not consistent with the rewording. Leaving it to the author.
  • Live validation: ✅ go - 6 of 6 scenarios driven live against the product
Scenario Result Live Evidence
Generated GUI docs bundle (gui/lib/docs-bundle.ts) matches a fresh regeneration from docs/content ✅ pass live docs-bundle-sync.txt: node gui/scripts/bundle-docs.mjs leaves no diff
Generated front content bundle matches a fresh regeneration from content/ ✅ pass live front-bundle-sync.txt
Recompute on read: deleting an edge in a recursive rule removes the downstream rows on the next query, and a new path appears (BENCHMARKS headline and architecture claim) ✅ pass live s1-recursive-retraction.txt: reach(1,Y)={2,3,4,5} becomes {2} after -edge(2,3), then {2,5,9} after adding 2->9->5
Persistence table: facts and persistent rules survive a restart (facts in WAL plus Parquet); type declarations and derived rows are not stored ✅ pass live s2-after-restart.txt plus data-dir inspection: persist/wal and persist/batches/*.parquet, rules/catalog.json with reach, .rel lists only edge, no type store
Standing query emits a snapshot, then re-evaluate-and-diff deltas after each commit, including retraction through recursion ✅ pass live s3-subscription-deltas.txt: snapshot {Y=2,3}; +edge(3,4) gives delta +{Y=4}; -edge(2,3) gives delta -{Y=3,Y=4}
Example IQL scripts whose comments changed still run, with output unchanged from the base commit ✅ pass live example-*.txt; 121 and 03 identical to base-commit output (03's parse errors are pre-existing and present in its .out)
  • node gui/scripts/bundle-docs.mjs then git diff --quiet gui/lib/docs-bundle.ts: in sync
  • node front/scripts/bundle-content.mjs then git diff --quiet: in sync
  • cargo build --bin inputlayer-server --bin inputlayer-client (isolated CARGO_TARGET_DIR in /tmp)
  • Started inputlayer-server --port 18491 --data-dir /tmp/... with a throwaway admin password and API key; via inputlayer-client: inserted edge facts, declared recursive persistent rule reach, ran ?reach(1,Y), deleted -edge(2,3), re-queried, added a new path, re-queried
  • Restarted the server on the same data dir and ran .rel, .rule list, ?edge, ?reach; inspected data dir layout (persist/wal, persist/batches/*.parquet, rules/catalog.json, no stored type or derived relation)
  • Python SDK kg.subscribe(iql=&#39;?reach(1, Y)&#39;) against the live server, committing +edge(3,4) then -edge(2,3)
  • Ran the changed example scripts (121_materialized_view, 03_schema_with_views, 02_incremental, 05_manufacturing, hello-consistency.iql, ingest/rules.iql) through inputlayer-client; for those with .out files, compared output to the base-commit version of the same script
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

jsam added 3 commits October 5, 2026 18:09
…-and-diff subscriptions (#306)

Persistent rules are stored definitions re-derived from the base facts on
every read; subscriptions re-run their query after each relevant commit and
diff. Remove every claim of materialization, caching or incremental
maintenance across docs, the legacy docs tree, README, BENCHMARKS, SDK docs,
marketing content, examples, the landing page and Rust doc comments; rewrite
the stale materialization parts of the architecture doc; retire the legacy
architecture.md copy in favour of the .mdx source; describe the 1,652x figure
as a bound Magic Sets query against a full closure; note what milestone 9
(#305, live views) will change. Regenerate gui/lib/docs-bundle.ts and
front/lib/content-bundle.ts.
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