Skip to content

feat(protocol): return the committed revision on write replies - #277

Closed
jsam wants to merge 5 commits into
mainfrom
fm/en-en3
Closed

jsam wants to merge 5 commits into
mainfrom
fm/en-en3

Conversation

@jsam

@jsam jsam commented Oct 4, 2026 •

Copy link
Copy Markdown
Contributor

Intent

please continue developing the input layer engine so we make sure that the symbolic engine is ready to go live and completely production ready while we develop the GoTM uh, market.

(Context: the captain approved the neurosymbolic decision engine plan; its milestone "Engine readiness" makes the symbolic engine production-ready for the hosted decision API. This task is item EN-3: commit revision on write replies (E-3): a program whose writes commit returns the revision it committed at (result.revision for committed programs) — why: every API write returns revision; decision records carry revision_commit without a second query)

What Changed

  • Write replies now include an optional revision field (QueryResult, plus ResultFrame and ResultStartFrame in ws-protocol, on both the single-frame and streamed WebSocket paths). It names the knowledge-graph revision that standing-query pushes carry once the program's writes are visible. It is set only when the program changed persistent state: committed fact, schema or rule writes, .rel drop, .clear prefix, .index create|drop|rebuild, .kg create and .ontology install|remove|upgrade. When a program does several of these, the reply carries the last one's revision. Session-only schemas, queries and rolled-back writes leave it out.
  • The storage engine now reports the revision each of these writes published. ProgramCommit gained a revision field, and create_knowledge_graph_at, drop_relation_in, clear_relations_by_prefix_in and the create_index_in/drop_index_in/rebuild_index_in functions return it. The protocol handler and write run pass it through, and keep the meta-command's revision when a later write run in the same program commits nothing durable.
  • Documented the revision field in the AsyncAPI spec and the WebSocket API guide. Added tests covering atomic programs, HNSW index commands, standing queries (a push at the returned revision reflects the write), pinned proofs, and ws-protocol frame serialization.

Risk Assessment

✅ Low: The change adds an optional revision field to the reply. Each storage path reads it under the KG write lock it already holds, right after publishing. I traced every write path (committed write runs, durable no-ops, session-schema-only runs, .rel drop, .clear prefix, .index create/drop/rebuild, .kg create, .ontology install/remove/upgrade, pinned proofs): each returns the revision the docs describe. The program-boundary check stops meta commands from sharing a program with writes, so the revisions can't be mixed up within one program.

Testing

I built and started a real inputlayer-server on a throwaway data dir and port, then drove it over the WebSocket API with Node scripts, one subscriber connection and one writer. I covered 15 scenarios, including the edge cases: a write that changes nothing, a rolled-back write, a session schema alone, a meta command mixed with a session schema, and a write followed by a proof. All of them passed. Each persistent write (facts, rules, .rel drop, .clear prefix, .index create/rebuild/drop, .kg create) named a revision. Where a subscription could see the change, the next push carried exactly that revision. Queries, rolled-back writes, session schemas, .kg use, .kg drop and inspection commands named none. I also ran the four new targeted integration and unit tests, and all passed. This is a protocol change with no visual UI, so the evidence is the transcripts. I stopped the server and deleted its data dir. The worktree is clean.

  • Live validation: ✅ go - 13 of 13 scenarios driven live against the product
Scenario Result Live Evidence
Inserting a persistent fact replies with a revision equal to the subscriber's next push revision ✅ pass live transcript.txt S1: reply revision 6, push revision 6
Inserting a fact that is already present names the current revision ✅ pass live transcript.txt S2: revision 6, same as the previous write
A query with no writes replies with no revision ✅ pass live transcript.txt S3
A program that writes then queries names the commit revision, which matches the push ✅ pass live transcript.txt S4: reply 7, push 7
A program rolled back by an arity error names no revision and applies nothing ✅ pass live transcript.txt S5/S5b
A program that only declares a session schema names no revision ✅ pass live transcript.txt S6
Registering a persistent rule names a revision ✅ pass live transcript.txt S7: revision 8
.rel drop and .clear prefix name the revision their retraction push carries ✅ pass live transcript.txt S8 (10/10) and S9 (11/11)
.rel drop mixed with a session schema in one program is rejected, applies nothing and names nothing ✅ pass live transcript.txt S10
.kg use, .rel, .index list and .kg drop name no revision ✅ pass live transcript.txt S11/S11b, transcript2.txt S15b/S14
.kg create names the new graph's revision ✅ pass live transcript.txt S12: revision 12
.index create, .index rebuild and .index drop each name a revision higher than the one before ✅ pass live transcript2.txt S15: 17, 18, 19
A write followed by .why names the commit revision; the proof is pinned to the snapshot before it ✅ pass live transcript2.txt S13: proof trees at 22, reply revision 23
Evidence: Live WebSocket transcript: facts, rules, relation commands, kg create
subscribed {"subscription":"nsub","generation":1,"revision":5}
{"name":"S1 persistent insert names revision","program":"+n(1)","type":"result","revision":6,"rows":[["Inserted 1 fact(s) into 'n'."]]}
S1 push {"revision":6,"inserted":[[1]]} MATCH= true
{"name":"S2 no-op insert names current revision","program":"+n(1)","type":"result","revision":6,"rows":[["Inserted 0 fact(s) into 'n'."]]}
S2 equals prior= true
{"name":"S3 query only: no revision","program":"?n(X)","type":"result","revision":null,"rows":[[1]]}
{"name":"S4 write+query names revision","program":"+n(2)\n?n(X)","type":"result","revision":7,"rows":[[1],[2]]}
S4 push {"revision":7,"inserted":[[2]]} MATCH= true
{"name":"S5 rolled back write: no revision","program":"+n(3)\n+n(\"three\", 4)","type":"result","revision":null,"errors":[{"index":1,"code":"validation","message":"Arity mismatch for relation 'n': existing arity is 1, but trying to insert tuples with arity 2 (rolled back: none of the program's 2 write statements 0-1 was applied)"}],"rows":[["Arity mismatch for relation 'n': existing arity is 1, but trying to insert tuples with arity 2 (rolled back: none of the program's 2 write statements 0-1 was applied)"]]}
{"name":"S5b n(3) not present","program":"?n(3)","type":"result","revision":null,"rows":[]}
{"name":"S6 session schema only: no revision","program":"s(a: int)","type":"result","revision":null,"rows":[["Schema for 's' registered with 1 columns (session)"]]}
{"name":"S7 persistent rule names revision","program":"+m(X) <- n(X)","type":"result","revision":8,"rows":[["Rule 'm' registered."]]}
{"name":"S8 .rel drop names revision","program":".rel drop gone","type":"result","revision":10,"rows":[["Relation 'gone' dropped."]]}
S8 push {"revision":10,"retracted":[[1]]} MATCH= true
{"name":"S9 .clear prefix names revision","program":".clear prefix p_","type":"result","revision":11,"rows":[["Cleared 1 fact(s) from 1 relation(s) with prefix 'p_': p_x (1)"]]}
S9 push {"revision":11,"retracted":[[1]]} MATCH= true
{"name":"S10 mixed .rel drop + session schema","program":".rel drop n\ns(a: int)","type":"result","revision":null,"errors":[{"index":0,"code":"unsupported","message":"'.rel drop n' cannot run in a program that changes facts, rules or schemas: those changes commit together as one transaction, and this command cannot join it. Nothing was applied; send the command in a separate request."}],"rows":[["'.rel drop n' cannot run in a program that changes facts, rules or schemas: those changes commit together as one transaction, and this command cannot join it. Nothing was applied; send the command in a separate request."]]}
{"name":"S11 .kg use: no revision","program":".kg use revtest","type":"result","revision":null,"rows":[["Switched to knowledge graph: revtest"]]}
{"name":"S11b .rel inspection: no revision","program":".rel","type":"result","revision":null,"rows":[["Relations:"],["  n (arity: 1, columns: [col0: any], tuples: 2)"],["  p_x (arity: 1, columns: [col0: any], tuples: 0)"]]}
{"name":"S12 .kg create names revision","program":".kg create revtest2","type":"result","revision":12,"rows":[["Knowledge graph 'revtest2' created."],["Switched to knowledge graph: revtest2"]]}
{"name":"S13 write + .why proof names commit revision","program":"+n(9)\n.why ?n(9)","type":"result","revision":13,"rows":[["Inserted 1 fact(s) into 'n'."]]}
S13 proof revisions [["Inserted 1 fact(s) into 'n'."]]
{"name":"S14 .kg drop: no revision","program":".kg drop revtest2","type":"error","revision":null,"code":"conflict","message":"Cannot drop current knowledge graph. Switch to another first."}
Evidence: Live WebSocket transcript: index commands, write followed by proof, kg drop
{"name":"setup schema","program":"+docs(...)","type":"result","revision":15,"rows":[["Schema for 'docs' registered with 3 columns (persistent)"]]}
{"name":"setup facts","program":"+docs[...]","type":"result","revision":16,"rows":[["Inserted 2 fact(s) into 'docs'."]]}
{"name":"S15 .index create doc_idx on docs(emb)","program":".index create doc_idx on docs(emb)","type":"result","revision":17,"rows":[["Index 'doc_idx' created on docs.emb (2 vectors)."]]}
  increased= true
{"name":"S15 .index rebuild doc_idx","program":".index rebuild doc_idx","type":"result","revision":18,"rows":[["Index 'doc_idx' rebuilt (2 vectors)."]]}
  increased= true
{"name":"S15 .index drop doc_idx","program":".index drop doc_idx","type":"result","revision":19,"rows":[["Index 'doc_idx' dropped."]]}
  increased= true
{"name":"S15b .index list: no revision","program":".index list","type":"result","revision":null,"rows":[["No indexes."]]}
{"name":"S13 write then .why proof","program":"+need(\"s2\",\"say_eta\")\\n.why ?need(S,T)","type":"result","revision":23,"rows":[["Inserted 1 fact(s) into 'need'."]]}
  proof frame keys: type,id,columns,rows,row_count,total_count,truncated,execution_time_ms,row_provenance,proof_trees,timing_breakdown,errors,statements,revision  proof revisions: ["\"revision\":22","\"revision\":22","\"revision\":23"]
{"name":"S14 .kg drop: no revision","program":".kg drop revtest2","type":"result","revision":null,"rows":[["Knowledge graph 'revtest2' dropped."]]}
Evidence: Driver scripts
const URL = 'ws://127.0.0.1:18431/ws';
function conn() {
  return new Promise((res, rej) => {
    const ws = new WebSocket(URL); const q = []; const waiters = [];
    ws.onmessage = e => { const m = JSON.parse(e.data); if (waiters.length) waiters.shift()(m); else q.push(m); };
    ws.onerror = rej;
    const next = () => q.length ? Promise.resolve(q.shift()) : new Promise(r => waiters.push(r));
    const c = { ws, next,
      async reply(id) { for (;;) { const m = await next(); if (m.id === id) return m; c.pushes.push(m); } },
      pushes: [],
      async push(sub) { const i = c.pushes.findIndex(p => p.type==='subscription_delta' && p.subscription===sub);
        if (i>=0) return c.pushes.splice(i,1)[0];
        const t = setTimeout(()=>{}, 0);
        for (;;) { const m = await Promise.race([next(), new Promise(r=>setTimeout(()=>r(null),3000))]); if (!m) return null; if (m.type==='subscription_delta' && m.subscription===sub) return m; c.pushes.push(m);} },
      n: 0,
      async exec(program) { const id = 'r' + (++c.n); ws.send(JSON.stringify({type:'execute', id, program})); return c.reply(id); } };
    ws.onopen = async () => { ws.send(JSON.stringify({type:'login', id:'l', username:'admin', password:'testpw-123456'})); const a = await c.reply('l'); if (a.type!=='authenticated') rej(a); else res(c); };
  });
}
const out = [];
const log = (name, prog, r) => { const s = {name, program: prog, type: r.type, revision: r.revision ?? null, errors: r.errors?.length ? r.errors : undefined, rows: r.rows, code: r.code, message: r.message}; out.push(s); console.log(JSON.stringify(s)); return r; };
const sub = await conn(); const w = await conn();
await w.exec('.kg create revtest'); await w.exec('.kg use revtest'); await sub.exec('.kg use revtest');
const S = await sub.exec('.subscribe nsub ?n(X)'); console.log('subscribed', JSON.stringify(S.subscribed));
let r;
r = log('S1 persistent insert names revision', '+n(1)', await w.exec('+n(1)'));
let d = await sub.push('nsub'); console.log('S1 push', JSON.stringify({revision: d?.revision, inserted: d?.inserted}), 'MATCH=', d?.revision === r.revision);
const rev1 = r.revision;
r = log('S2 no-op insert names current revision', '+n(1)', await w.exec('+n(1)')); console.log('S2 equals prior=', r.revision === rev1);
r = log('S3 query only: no revision', '?n(X)', await w.exec('?n(X)'));
r = log('S4 write+query names revision', '+n(2)\n?n(X)', await w.exec('+n(2)\n?n(X)'));
d = await sub.push('nsub'); console.log('S4 push', JSON.stringify({revision: d?.revision, inserted: d?.inserted}), 'MATCH=', d?.revision === r.revision);
r = log('S5 rolled back write: no revision', '+n(3)\n+n("three", 4)', await w.exec('+n(3)\n+n("three", 4)'));
r = log('S5b n(3) not present', '?n(3)', await w.exec('?n(3)'));
r = log('S6 session schema only: no revision', 's(a: int)', await w.exec('s(a: int)'));
r = log('S7 persistent rule names revision', '+m(X) <- n(X)', await w.exec('+m(X) <- n(X)'));
await w.exec('+gone(1)\n+p_x(1)');
await sub.exec('.subscribe gsub ?gone(X)'); await sub.exec('.subscribe psub ?p_x(X)');
r = log('S8 .rel drop names revision', '.rel drop gone', await w.exec('.rel drop gone'));
d = await sub.push('gsub'); console.log('S8 push', JSON.stringify({revision: d?.revision, retracted: d?.retracted}), 'MATCH=', d?.revision === r.revision);
r = log('S9 .clear prefix names revision', '.clear prefix p_', await w.exec('.clear prefix p_'));
d = await sub.push('psub'); console.log('S9 push', JSON.stringify({revision: d?.revision, retracted: d?.retracted}), 'MATCH=', d?.revision === r.revision);
r = log('S10 mixed .rel drop + session schema', '.rel drop n\ns(a: int)', await w.exec('.rel drop n\ns(a: int)'));
r = log('S11 .kg use: no revision', '.kg use revtest', await w.exec('.kg use revtest'));
r = log('S11b .rel inspection: no revision', '.rel', await w.exec('.rel'));
r = log('S12 .kg create names revision', '.kg create revtest2', await w.exec('.kg create revtest2'));
r = log('S13 write + .why proof names commit revision', '+n(9)\n.why ?n(9)', await w.exec('+n(9)\n.why ?n(9)'));
console.log('S13 proof revisions', JSON.stringify(r.rows).slice(0,400));
r = log('S14 .kg drop: no revision', '.kg drop revtest2', await w.exec('.kg drop revtest2'));
process.exit(0);
Evidence: Targeted test runs
test test_relation_commands_name_the_revision_their_push_carries ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 21 filtered out; finished in 1.89s
test test_index_commands_name_the_revision_they_publish ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 14 filtered out; finished in 0.03s
test protocol::handler::pinned_proof_tests::the_reply_names_the_revision_the_writes_committed_at ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 3047 filtered out; finished in 0.03s
test test_write_reply_names_the_revision_its_push_carries ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 21 filtered out; finished in 1.80s
Evidence: Key excerpt
subscribed revision 5
+n(1) -> revision 6; push revision 6 MATCH=true
+n(1) again -> revision 6 (unchanged)
?n(X) -> no revision
+n(2)\n?n(X) -> revision 7; push 7 MATCH=true
rolled-back write -> no revision
s(a: int) -> no revision
.rel drop gone -> 10; push 10 MATCH=true
.clear prefix p_ -> 11; push 11 MATCH=true
.index create/rebuild/drop -> 17/18/19
+need(..)\n.why -> reply 23, proof 22

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

🔧 **Review** - 2 issues found → auto-fixed (2) ✅
  • ⚠️ src/storage_engine/program_commit.rs:84 - Programs that only change session state still get a revision back, which the docs say they shouldn't. A non-persistent schema declaration is queued into the write run as CatalogStatement::Schema → CatalogChange::DefineSessionSchema (catalog_staging.rs:45). commit_program then takes the early return for session-schema-only deltas, and that return now sets revision: db.snapshot.load().revision. commit_write_run returns this as Ok(revision), so handler.rs:3268 attaches it to the reply. Example: a program made of one session (non-persistent) schema declaration replies with revision: N. Three places promise otherwise: QueryResult.revision says "Present only when the program's writes to persistent state committed" (src/protocol/wire.rs:335-339), ResultFrame.revision says "Set only when the program wrote persistent state" (ws-protocol/src/server.rs:189-193), and the guide says session-scoped writes carry no revision (docs/content/docs/guides/websocket-api.mdx:159). Fix one side so they agree: either return no revision when the delta has no durable facts or catalog (e.g. make ProgramCommit.revision an Option, or have commit_write_run return None when no relation or durable catalog changed), or reword the three doc sites to say session-only writes name the current revision.
  • ⚠️ src/protocol/handler.rs:3264 - Some writes to persistent state never get a revision. Only statements that go through WriteRun produce one. .rel drop &lt;name&gt; (handler.rs ~2773, storage.drop_relation_in) and .clear prefix &lt;p&gt; (handler.rs ~2860, storage.clear_relations_by_prefix_in) write and publish facts and schemas directly, so their replies have no revision. Example: a program that is only .rel drop eta deletes persistent facts, publishes a new snapshot that standing-query pushes will carry, and replies without revision. The intent says "every API write returns revision". Whether these admin meta commands count as API writes, or the decision API will only ever send fact/rule programs, is a product decision. If they count, the fix is to return the published revision from those storage calls as well.

🔧 Fix applied.
2 warnings still open:

  • ⚠️ src/protocol/handler.rs:3264 - Some writes to persistent state never get a revision. Only statements that go through WriteRun produce one. .rel drop &lt;name&gt; (handler.rs ~2773, storage.drop_relation_in) and .clear prefix &lt;p&gt; (handler.rs ~2860, storage.clear_relations_by_prefix_in) write and publish facts and schemas directly, so their replies have no revision. Example: a program that is only .rel drop eta deletes persistent facts, publishes a new snapshot that standing-query pushes will carry, and replies without revision. The intent says "every API write returns revision". Whether these admin meta commands count as API writes, or the decision API will only ever send fact/rule programs, is a product decision. If they count, the fix is to return the published revision from those storage calls as well.
  • ⚠️ src/protocol/handler.rs:3283 - A session-only write run wipes out a revision that an earlier meta command in the same program already set. Round 1 introduced this when it combined two of its own changes: meta commands now set revision as they run (.rel drop at 2785, .clear prefix at 2873, .index create|drop|rebuild at 3065/3094/3175, .kg create at 2633), and commit_write_run now returns None when every queued statement is a session schema (write_run.rs:425). The write run commits after the statement loop, and Ok(committed) =&gt; revision = committed overwrites unconditionally. Concrete case: the program .rel drop gone followed by s(a: int) (a session schema). drop_relation_in publishes a new snapshot and sets revision = Some(N). The write run then holds only the session schema, so commit_write_run returns Ok(None) and revision becomes None. The reply has no revision even though the program changed persistent state. That breaks the operator's decision that every statement changing persistent state returns a revision, and it contradicts the docs (wire.rs:489-495, server.rs:778-783, websocket-api.mdx:159), which say a program with several such statements names the last one's. Fix: keep the earlier value, i.e. Ok(committed) =&gt; revision = committed.or(revision). A durable commit always runs after the loop, so its revision is the latest and still wins. Add the mixed program above to test_relation_commands_name_the_revision_their_push_carries. No other site has this overwrite: the ontology paths already use .or(revision) (handler.rs:4993, 5052).

🔧 Fix applied.
✅ Re-checked - no issues remain.

✅ No issues found.

✅ **Test** - passed

✅ No issues found.

  • Live validation: ✅ go - 11 of 12 scenarios driven live against the product
Scenario Result Live Evidence
A committed fact write replies with a revision equal to the subscriber's next push revision, and greater than the subscribe revision ✅ pass live transcript.txt S1 (reply revision 7, push revision 7, subscribed at 6)
Re-inserting a fact that is already present names the current revision ✅ pass live transcript.txt S2 (Inserted 0 facts, revision 7)
A query-only program carries no revision ✅ pass live transcript.txt S3
Adversarial: a program rolled back by an arity error carries no revision and leaves no fact behind ✅ pass live transcript.txt S4/S4b
A program with only a session schema carries no revision (F1) ✅ pass live transcript.txt S5
A program that writes and queries returns rows and names the revision its push carries ✅ pass live transcript.txt S6 (revision 8 = push 8)
.clear prefix and .rel drop name the revision their retraction push carries (F2) ✅ pass live transcript.txt S7/S8 (10=10, 11=11)
Adversarial (R2-1): a program combining .rel drop with a session schema is refused whole, with no revision and nothing applied ✅ pass live transcript.txt S9 (unsupported error at index 0, no revision)
Inspection commands (.rel, .kg use, .index list) carry no revision ✅ pass live transcript.txt S10/S11b; transcript_index.txt
.kg create names the new graph's revision ✅ pass live transcript.txt S11; transcript_index.txt (vec_nm revision 13)
.index create, rebuild and drop each name a new revision, and a following no-op write names the same one ✅ pass live transcript_index.txt (16, 17, 18)
.ontology install and remove name revisions, and later no-op writes name them ⏸️ untested no The prior payload did not establish a live result. This scenario was not driven against the live server because .ontology install|remove needs an ontology pack registry on disk, which the live setup…
  • cargo build --bin inputlayer-server --bin inputlayer-client, then ran inputlayer-server on 127.0.0.1:18731 with a throwaway INPUTLAYER_STORAGE__DATA_DIR in /tmp and INPUTLAYER_BOOTSTRAP_API_KEY
  • node drive.mjs (WebSocket subscriber and writer): 13 live checks covering write and push revisions, no-op writes, queries, rollback, session schema, write+query, .clear prefix, .rel drop, a mixed meta+session program, .rel, .kg create and .kg use
  • node drive_index.mjs: .index create, rebuild and drop each name a new revision and a following no-op write names the same one; .index list names none
  • cargo test --test standing_query_tests -- revision (3 tests)
  • cargo test --test atomic_program_tests failed_pack_installation_applies_nothing (ontology install/remove revisions)
  • cargo test --test hnsw_index_tests test_index_commands_name_the_revision
  • cargo test -p inputlayer-ws-protocol revision (serialization of the ResultFrame revision field)

✅ No issues found.

  • Live validation: ✅ go - 13 of 13 scenarios driven live against the product
Scenario Result Live Evidence
Inserting a persistent fact replies with a revision equal to the subscriber's next push revision ✅ pass live transcript.txt S1: reply revision 6, push revision 6
Inserting a fact that is already present names the current revision ✅ pass live transcript.txt S2: revision 6, same as the previous write
A query with no writes replies with no revision ✅ pass live transcript.txt S3
A program that writes then queries names the commit revision, which matches the push ✅ pass live transcript.txt S4: reply 7, push 7
A program rolled back by an arity error names no revision and applies nothing ✅ pass live transcript.txt S5/S5b
A program that only declares a session schema names no revision ✅ pass live transcript.txt S6
Registering a persistent rule names a revision ✅ pass live transcript.txt S7: revision 8
.rel drop and .clear prefix name the revision their retraction push carries ✅ pass live transcript.txt S8 (10/10) and S9 (11/11)
.rel drop mixed with a session schema in one program is rejected, applies nothing and names nothing ✅ pass live transcript.txt S10
.kg use, .rel, .index list and .kg drop name no revision ✅ pass live transcript.txt S11/S11b, transcript2.txt S15b/S14
.kg create names the new graph's revision ✅ pass live transcript.txt S12: revision 12
.index create, .index rebuild and .index drop each name a revision higher than the one before ✅ pass live transcript2.txt S15: 17, 18, 19
A write followed by .why names the commit revision; the proof is pinned to the snapshot before it ✅ pass live transcript2.txt S13: proof trees at 22, reply revision 23
  • Started target/debug/inputlayer-server --config &lt;evidence&gt;/config.toml --port 18431 with a throwaway data dir and INPUTLAYER_ADMIN_PASSWORD, then drove it with Node WebSocket scripts (node drive.mjs, node drive2.mjs), one subscriber connection and one writer connection
  • +n(1) with a .subscribe nsub ?n(X) open: reply revision compared to the push revision
  • +n(1) sent again (changes nothing): reply revision compared to the previous one
  • ?n(X), .rel, .index list, .kg use, .kg drop: checked that the reply has no revision
  • +n(2)\n?n(X) (write plus query): reply revision compared to the push revision
  • +n(3)\n+n(&#34;three&#34;, 4) (arity error rolls the program back): no revision and n(3) is absent
  • s(a: int) (session schema only): no revision
  • +m(X) &lt;- n(X) (persistent rule): revision present
  • .rel drop gone and .clear prefix p_: reply revision compared to the subscription push revision
  • .rel drop n\ns(a: int) (meta command mixed with a session schema): rejected whole, nothing named
  • .kg create, .index create/rebuild/drop: each names a revision higher than the one before
  • +need(&#34;s2&#34;,&#34;say_eta&#34;)\n.why ?need(S,T): reply revision (23) is after the pinned proof's revision (22)
  • cargo test --test standing_query_tests test_write_reply_names_the_revision
  • cargo test --test standing_query_tests name_the_revision
  • cargo test --test hnsw_index_tests test_index_commands_name_the_revision
  • cargo test --lib the_reply_names_the_revision
✅ **Document** - passed

✅ No issues found.

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

✅ No issues found.

jsam added 5 commits October 4, 2026 19:39
A program whose writes commit now replies with `revision`: the revision
of the snapshot its commit published, the one standing-query pushes carry.
A write that changes nothing names the current revision. Queries, rolled
back programs and session-scoped writes carry none.
@jsam
jsam marked this pull request as draft October 4, 2026 21:04
@jsam

jsam commented Oct 5, 2026

Copy link
Copy Markdown
Contributor Author

Superseded by #293 (same work, synced with main).

@jsam jsam closed this Oct 5, 2026
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