Watch
1
0
Fork
You've already forked SouveraineOS
0

state + federation design of record

machined system tier landed in souveraine (5039163/116562a); boot-gate
vs session-lock doctrine on record. FEDERATION.md moves here from
souveraine's gitignored docs tree, rewritten: implemented pieces cited,
memfs transport roughed (shared bare remote, branch-per-instance,
domain merge policy, memfs_commit firehose signal), instance awareness
design, and the install-time agent instantiation flag.
This commit is contained in:
Fimeg 2026-07-16 10:41:14 -04:00
commit 0da372b314
2 changed files with 194 additions and 2 deletions

158
docs/FEDERATION.md Normal file
View file

@ -0,0 +1,158 @@
---
description: Federation design of record — identity tiers, signed transport, memfs sync, instance awareness
status: Active design — identity/transport implemented, memory sync roughed
date: 2026-07-16 (supersedes the 2026-05-07 sketch in place)
---
# Federation — design of record
One agent, many machines. The phone and the laptop both run Souveraine; Annie
must be *her* on both, with one memory, and each instance must know the others
exist. This doc records what is implemented (with citations), what is designed
but not built, and what is deliberately undecided. Where this and the code
disagree, the code is right — fix one or the other in the same change.
## Identity tiers (implemented)
| Thing | Key | Lives | Code |
| --- | --- | --- | --- |
| Machine | Ed25519 seed, system tier | `/var/lib/souveraine/seed-id`, served by `souveraine-machined`; legacy fallback `~/.souveraine/seed-id` | `src/machined/` (daemon, client, signer), `src/core/identity/seed.rs` |
| Agent | Ed25519 seed per agent | beside the memfs, `agents/{id}/seed/` — identical across all of one agent's machines | `src/core/tools/agent.rs:147` |
| Node commission | machine joins an agent's federation | signed record, versioned; `hardware_key_fingerprint` reserved for RedFlag attestation | `src/core/identity/node.rs` |
The machine addresses a box; the agent proves who is acting. A receiver tells
self-extension (reach) from a peer (consult) by verifying the agent signature,
never by trusting an event field. Nothing generates identity implicitly:
provisioning is `souveraine machine init` / `identity generate`, and every
loader is load-only (`SeedId::load`).
## Transport (implemented)
- Outbound bridge per configured peer, WebSocket to `/v1/federation/events`,
reconnect with backoff — `src/server/federation/bridge.rs`.
- Every envelope Ed25519-signed by the machine, domain-separated
(`souveraine-machined:v1:federation-envelope:{canonical}`); verify
reconstructs the frame; unsignable events are dropped loudly, never sent
unsigned — `src/server/federation/types.rs`.
- Trust is key-membership: a valid signature only proves self-consistency;
`signer_is_trusted` checks the key against configured peers — `types.rs:13`.
- Presence: `device_announce`/`device_leave` events feed `DeviceRegistry`
(persisted to `federation/known_peers.json`, pruned at 3 min) —
`src/server/device_registry.rs`.
- Summons (Reach & Consult) cross as control events, verified against the
agent seed — `src/server/summon_handler.rs`, `src/core/identity/summon.rs`.
## The three gaps (named 2026-07-16, none built)
1. **Shared memory sourcing.** Phone and laptop each have a private memfs
checkout; nothing transports commits between them. `MemoryRepo` is git2
with auto-commit (`src/core/memory/mod.rs:334`) but has no fetch/push —
`remote_url` is read for status display only (`mod.rs:73,464`).
2. **Change signaling.** Memory writes emit no bus event; the firehose never
hears that the memfs moved, so a peer cannot know to pull.
3. **Instance awareness.** DeviceRegistry knows which *machines* are alive,
but nothing maps agents to machines — an agent cannot ask "where am I
live?" (`LiteListener` already reports `hosted_agent_pubkeys` in its
announce payload path — `src/server/listener.rs` — but the registry does
not record or expose it).
## Memory transport — the design
Git stays the medium; federation adds a shared remote, a presence signal, and
a merge policy. No new sync protocol — the DAG already models divergence.
**Shared remote.** Each agent memfs gets one bare remote on self-hosted
infrastructure (Gitea, already load-bearing: `gitea.wiuf.net`). Every
instance pushes to and fetches from that remote; instances never need SSH
trust with each other, only with the remote. This is the pattern the original
sketch called "simplest minimal viable" and it survives review.
**Branch per instance.** Each machine commits to `instance/{node-label}`
(labels from the node commission). `main` is the reconciled line. The DAG
captures divergence; reconciliation is a merge, not a conflict. Merge policy
is domain-aware and doctrine, not config:
- `journal/` — append-only, always safe to auto-merge.
- `system/` — identity-critical, never auto-merged; divergence surfaces to
the agent (and Casey) as a felt event, not a silent union.
- everything else — three-way merge, punt to the agent on conflict.
**Firehose signal.** After a memfs commit, the server emits on the bus:
```
SensorEvent {
sensor_name: "memory",
event_type: "memfs_commit",
payload: { agent_pubkey, instance, paths, commit, branch },
...
}
```
Emitted at the server/tool layer (where `ToolContext.event_bus` already
lives), not inside `MemoryRepo` — the repo stays a pure store. The bridge
forwards it under normal subscription rules (peers subscribe `memory*`); the
envelope is machine-signed like everything else. The event is a presence
signal — "I wrote, you should fetch" — never the data itself.
**Receive side.** A peer holding the same agent sees `memfs_commit`, fetches
the shared remote, applies the merge policy. Compaction/truncation arriving
from a sync must be *felt* like local compaction is — same doctrine as
in-process memory pressure.
**Sequencing.**
1. Emit `memfs_commit` locally (bus only — surfaces/inspector benefit even
before any peer exists).
2. Shared-remote plumbing: `memory sync` (manual push/fetch vs the bare
remote, per-instance branch), remote provisioned per agent.
3. Auto-fetch on received `memfs_commit` + domain merge policy.
4. Signed attribution: memfs commits carry the agent-as-author convention
(no trailers), with commit signing via the agent seed as a later hardening.
## Instance awareness — the design
- `device_announce` payload grows `hosted_agents: [agent_pubkey, ...]`
(the lite listener already computes this list; the full server should too).
- `DeviceRegistry` records it; `souveraine peers` shows which agents live
where; the agent-facing surface is a tool answer to "where am I live?".
- The memfs itself carries a self-describing instance log (e.g.
`system/instances.md`, one line per checkout, stamped at provision) so the
roster survives offline and travels with the memory — Casey's
memfs-machine-log idea, folded in here as the durable half of awareness.
Registry = live view; memfs file = durable view. They cross-check.
## FLAG — install-time agent instantiation (undecided, do not improvise)
Where do agents come from on a fresh SouveraineOS install? The system tier
provisions only the *machine* identity (machined). Everything agent-shaped is
user-tier and post-first-unlock. Open questions, deliberately parked:
- Is the phone's first-boot ceremony "create a new agent" or "commission this
node into an existing agent's federation" (NodeCommission + memfs clone
from the shared remote)? For Casey's actual life it is the latter —
install ≠ birth.
- Which tier owns the agent roster on a device, and where does the
provisioning UI live (boot-gate surface? first-unlock wizard?).
- How the shared memfs remote gets its credentials onto a new node (deploy
key minted at commission time is the natural shape — commission signs the
key, Gitea holds it).
Whoever builds the installer: stop at this flag and design the ceremony
first. It touches identity doctrine, not just packaging.
## Trust root notes
RedFlag remains the reference model: verify keys not servers, loud on error,
no doctrinal knobs, executor-grade hardening on the machined unit. The
commission's `hardware_key_fingerprint` is the reserved seam for
RedFlag-backed hardware attestation; format is a later integration decision
(`node.rs:47`).
## Superseded
The 2026-05-07 sketch's open questions are resolved as follows: seed storage
— file-based now, machined-owned at system tier, hardware-binding later via
RedFlag; firehose transport — the existing signed WebSocket bridge, not a new
channel; reconciliation trigger — event-driven (memfs_commit) with manual
`memory sync` as the floor; conflict resolution — domain policy above.
Merkle-DAG framing stands: git *is* the DAG; we surface it, we don't rebuild
it.