Files
archy/.planning/phases/01-federation-mesh-hardening/01-02-SUMMARY.md
T
archipelagoandClaude Opus 5 90884e6259
Demo images / Build & push demo images (push) Successful in 4m21s
feat(01-02): chat mutations mutate demo state instead of acking (FED-04)
Reactions, replies, read-receipts, edits, deletes, forwards and channel sends
shared one bare `{ ok: true, sent: true }` case, so none of them rendered on
the demo — the UI derives reaction chips and reply quotes from the message
store, and there was nothing in it to derive from.

Each now mirrors its daemon counterpart. Reactions/replies/receipts push typed
messages carrying the { sender_pubkey, sender_seq } target key Mesh.vue's
reactionIndex and replyTargetPreview read. Edits rewrite the text and set
edited_at; deletes tombstone IN PLACE (plaintext, typed_payload.deleted,
message_type 'delete') because that is what mesh/mod.rs apply_local_delete
does — it does not remove the row.

Edits and deletes go through a per-session overrides overlay keyed by
sender_seq, because mesh.messages rebuilds its seed array on every read, so
in-place mutation would only ever work for messages sent this session.

mesh.refresh and mesh.reboot-radio stay acknowledgements on purpose — the
daemon's handlers have no message-store effect either — with a comment saying
so, so a later reader does not "fix" them into divergence.

Also completes the phase bookkeeping for 01-02/03/11/12/13/14/15 and lands the
orphaned 01-12/01-14 SUMMARYs.

Verified: parity harness 17/17 live assertions; full frontend suite 102 files
/ 822 tests green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 06:37:00 -04:00

10 KiB

phase, plan, subsystem, tags, requires, provides, affects, tech-stack, key-files, key-decisions, requirements-completed, coverage, duration, completed, status
phase plan subsystem tags requires provides affects tech-stack key-files key-decisions requirements-completed coverage duration completed status
01-federation-mesh-hardening 02 demo
mock-backend
rpc
parity
mesh
federation
harness
phase provides
01-federation-mesh-hardening mock-backend.js's per-session store (mesh.dynamic/blobs) and the attachment-parity handlers landed in c2ce71c6 (send-content-inline / send-content / fetch-content / transport-advice)
Ten previously-missing mesh/federation RPC handlers, each mirroring and citing its daemon counterpart
Stateful chat mutations (reaction, reply, read-receipt, edit, delete, forward, channel) visible on the next mesh.messages read
scripts/mock-rpc-parity.mjs + npm run test:mock-parity — a single command that fails when the demo drifts from the UI's call surface
demo
mesh
federation
added patterns
Parity harnesses must match call syntax (`method: '<x>'`), not bare string literals. The same dotted names are used as resource-cache keys in the UI (`key: 'federation.nodes'`), so a literal scan reports phantom gaps that can never be closed — which would make the harness permanently red and therefore ignored.
Mirror-don't-invent: every mock handler carries a comment naming the Rust file and function it mirrors, so a future reader can re-check parity instead of guessing what the demo is 'supposed' to do.
created modified
neode-ui/scripts/mock-rpc-parity.mjs
neode-ui/mock-backend.js
neode-ui/package.json
The STATIC stage matches `method: '<x>'` rather than every `'mesh.*'`/`'federation.*'` string literal as the plan specified — see Deviations. This is what let KNOWN_GAPS actually reach zero.
Edits and deletes are applied through a per-session `overrides` overlay keyed by sender_seq rather than by mutating a message array, because the demo's seed message list is rebuilt on every mesh.messages read. The overlay makes edit/delete observable on seeded messages too, not just ones sent this session.
Delete tombstones in place (plaintext '🗑 message deleted', typed_payload { deleted: true }, message_type 'delete') because that is precisely what mesh/mod.rs apply_local_delete does — it does not remove the row. A comment says so, since 'simplifying' it to a splice would be a silent divergence.
mesh.refresh and mesh.reboot-radio were deliberately LEFT as bare acknowledgements: the daemon's handlers have no message-store effect either, so giving them demo state would be divergence rather than parity. The comment records this so a later reader does not 'fix' them.
mesh.peers and mesh.contacts-list now read from one shared DEMO_MESH_PEERS constant, so the two can never disagree about who is on the mesh — the daemon merges contacts over the same peer map for the same reason.
An outbound/'sent' pending request was added to the demo seed. federation.cancel-request faithfully rejects anything that is not outbound-and-sent, so without such a request the demo's cancel button could only ever produce an error.
FED-04
id description requirement verification human_judgment
D1 Every mesh.*/federation.* RPC the UI calls has a mock handler — the demo never answers a UI call with 'Method not found' FED-04
kind ref status
integration neode-ui/scripts/mock-rpc-parity.mjs STATIC stage — 57 UI-called methods, 0 unhandled pass
false
id description requirement verification human_judgment
D2 Renaming a mesh peer on the demo persists across a contacts-list read FED-04
kind ref status
integration …LIVE stage — contacts-save then contacts-list round-trips the alias pass
false
id description requirement verification human_judgment
D3 Reaction, reply, edit, delete and forward mutate demo state and are visible on the next mesh.messages read FED-04
kind ref status
integration …LIVE stage — six mutation assertions, each re-reading mesh.messages and checking the UI-expected shape pass
false
id description requirement verification human_judgment
D4 The parity check fails when a UI-called method has no mock handler FED-04
kind ref status
integration Fail-first proof: disabling the mesh.clear-all case → exit 1, 'no mock handler for mesh.clear-all'; restored → exit 0 twice consecutively pass
false
id description requirement verification human_judgment
D5 The harness leaves no stray listener behind FED-04
kind ref status
integration Two consecutive runs both exit 0 (child killed in a finally block; ephemeral port via MOCK_BACKEND_PORT) pass
false
75min 2026-08-01 complete

Phase 1 Plan 2: Demo/Real Mesh RPC Parity (FED-04) Summary

Closed the ten mesh/federation methods the demo answered with "Method not found", made the six ack-only chat mutations actually mutate demo state, and replaced "squint at the browser console" with a single command that fails when the demo drifts.

Performance

  • Duration: ~75 min
  • Completed: 2026-08-01
  • Tasks: 3/3
  • Files modified: 3 (mock backend, new harness, package.json)

Accomplishments

  • Ten new handlers, each citing the Rust it mirrors: mesh.contacts-list/-save (typed_messages.rs), mesh.clear-all (status.rs), mesh.schedule-message/list-scheduled/cancel-scheduled (assistant.rs + scheduler.rs), mesh.assistant-status/-configure (assistant.rs), federation.cancel-request and federation.notify-did-change (federation/handlers.rs).
  • The chat mutations are no longer bare acknowledgements. Reactions, replies and read-receipts push typed messages carrying the { sender_pubkey, sender_seq } target key the UI's reactionIndex/replyTargetPreview read; edits rewrite the text and set edited_at; deletes tombstone in place; forwards copy to the destination peer; channel sends are channel-addressed.
  • mesh.peers and mesh.contacts-list share one DEMO_MESH_PEERS list, and the peer with no pubkey_hex is omitted from contacts exactly as the daemon's if let Some(pk) guard omits it.
  • scripts/mock-rpc-parity.mjs runs a static cross-reference then boots the mock on an ephemeral port and drives 17 live assertions. No || fallback escapes anywhere in it — a failed spawn or fetch fails the run rather than producing a green run that measured nothing.

Task Commits

  1. Tasks 1 + 2: contacts round-trip, harness, and the ten missing methodsb8979f36 (feat)
  2. Task 3: make the message-mutation stubs mutate demo state — committed with this SUMMARY

Deviations from Plan

The STATIC scan matches call syntax, not every string literal — and the plan's gap list had two false positives

Found during: Task 1, building the static cross-reference Issue: The plan specifies scanning src/** for "every 'mesh.<verb>' / 'federation.<verb>' string literal". That over-reports badly: Mesh.vue and Federation.vue use the same dotted names as resource-cache keys (key: 'mesh.self-did', key: 'mesh.transport-status', key: 'federation.nodes', key: 'federation.dwn-status'), and stores/sync.ts invalidates by the same strings. None are RPC methods. A literal scan reports 18 gaps where 10 exist.

Two of those phantoms — federation.nodes and federation.dwn-status — are named in the plan's own Task 2 gap list. They are not RPC methods and the daemon's dispatcher has no such routes; Federation.vue uses them purely as cache keys. Implementing them would have added demo-only endpoints the real node does not serve, which the plan's own transparency prohibition forbids. Resolution: The scan matches method:\s*['"]…['"], i.e. an actual call site. The gap list becomes exactly the 10 real methods, KNOWN_GAPS is empty, and the harness is genuinely failable — proven by disabling a case and watching it exit 1. Files modified: neode-ui/scripts/mock-rpc-parity.mjs

Added an outbound pending request to the demo seed

Found during: Task 2, implementing federation.cancel-request Issue: The daemon only permits cancelling an outbound request in sent state. The demo seed contained a single inbound/pending request, so a faithful handler could only ever return an error and the cancel path was unexercisable. Resolution: Added preq-demo-out-1 (outbound, state sent) to pendingPeerRequests. The handler stays faithful — it still rejects non-outbound and non-sent requests with the daemon's own message. Files modified: neode-ui/mock-backend.js

Edits/deletes use an overlay rather than in-place mutation

Found during: Task 3 Issue: mesh.messages rebuilds its seeded message array on every read, so mutating "the message" in place would be lost immediately for any seeded message and only work for messages sent in this session. Resolution: A per-session overrides map keyed by sender_seq, applied over the merged list on read. This matches the daemon's matching rule (own-Sent message by sender_seq) and makes edit/delete observable for seeded messages too. Files modified: neode-ui/mock-backend.js

Known Stubs

mesh.assistant-status reports ollama_detected: false, claude_available: false and an empty model list. That is the honest answer for a browser demo with no local model — the UI's "not detected" path is what a visitor should see, and fabricating a model list would be exactly the demo-only divergence this plan's prohibition forbids.

Threat Flags

None. This plan touches only the demo/mock backend and a dev-time harness; no production code path, endpoint or trust boundary is involved. The T-01-SC package-install threat does not apply — no dependencies were added (the harness uses only node: builtins and global fetch).

Self-Check: PASSED

  • CONFIRMED: node --check mock-backend.js exits 0
  • CONFIRMED: node scripts/mock-rpc-parity.mjs exits 0 — 17 live assertions, 0 unhandled methods
  • CONFIRMED: fail-first proof (disable a case → exit 1 naming it; restore → exit 0 twice in a row)
  • CONFIRMED: each of the ten methods appears exactly once as a case
  • CONFIRMED: KNOWN_GAPS is present and empty; MOCK_BACKEND_PORT honoured; typed_messages.rs cited twice
  • CONFIRMED: npm run test:mock-parity registered in package.json