Commit Graph
3 Commits
Author SHA1 Message Date
tsunagiandClaude Opus 5 8a6fb39689 Fold doctor into status, and stop id taking the lock
`status` asked a running agent and failed without one; `doctor` checked
the host and ignored the agent. Between them they answered one question
in two halves. They are now one command: device, agent, networks, host
capability, local addresses, all graded and aligned the same way.

`id` was worse than either. It spawned a whole agent to print three
facts, which took the directory lock and so failed with "owned by
another running agent instance" exactly when the answer was most wanted.
The lock exists to keep one writer over the mandatory state; reading who
this device is needs no such thing.

So both commands now prefer the running agent, which is live and
authoritative, and fall back to the state store, which takes no lock.
StateStore gains device_identity(), which reads and never writes:
load_or_create had the side effect of deciding an identity as a
consequence of asking about one.

Presentation moved out of the library. StatusReport::render is gone and
the CLI renders the structured data, so there is one renderer rather than
two that would drift. Health gains an Info level for rows that are facts
rather than checks — an endpoint id is neither good nor bad, and a column
of green next to plain data teaches the eye to ignore the column. A
report with no checks in it now ends without a summary instead of
claiming that everything checked out.

PathAddr and TransportKind grew Display impls; both were reaching the
user through {:?}, which is how "Direct via Ip(88.198.17.44:49792)" got
printed. The transport grading compares without regard to case, because
the agent answering can be a different build from the client asking and
this field's spelling has now changed once.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-21 15:07:54 +01:00
tsunagiandClaude Opus 5 84c06c6cac Allocate IPv4 addresses and keep them, as signed state
Derived IPv4 addresses could not survive anything: they changed with the
range, and there was no way for a member to come back to the one it had.
Addresses are now allocated and recorded as signed facts, which is the
first slice of the model in docs/sync-model.md.

src/state/ holds one record per author per network, carrying that author's
complete current statement, signed with its persistent device key over a
length-prefixed canonical encoding. Merging follows the model's rules: a
higher version wins, an older one never rolls back a newer, duplicates are
idempotent, absence from a snapshot is not deletion, and a same-version
conflict is resolved identically on every replica and reported rather than
letting replicas diverge. Records are persisted in state.sqlite, with the
record and the author's version counter committed in one transaction
before anything is announced, and distributed as a State control message
that is merged into what the receiver already holds.

No vote, deliberately, despite the request. A majority is not a trust root
here — anyone with the secret can mint identities — and a quorum would
stall with one peer online and diverge across a partition. Signatures plus
a deterministic merge converge without either failure mode: two members
claiming one address at once are resolved by the lower endpoint id, and
the loser allocates again with a higher version.

The range moved from the plugin to the agent, defaults to 10.13.37.0/24,
and is now agreed rather than configured per member: a joining agent
adopts what the network already uses, so --ipv4-range only matters for
whoever starts it. The announcement went back to identity only (version 3)
since the range travels in signed records now.

A release tombstone exists and merges correctly, but nothing emits one
yet.

116 tests. The headline ones: an address survives restarting both agents,
three members get three distinct addresses, and a member started with a
different range adopts the one in use. Confirmed by hand with two CLI
agents restarted end to end.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-21 13:43:01 +01:00
tsunagiandClaude Opus 5 7cea9afa37 Proof-of-concept mesh agent library over iroh
Working library with real iroh connections, not an interface sketch:

- persistent device identity in state.sqlite, stable across restarts
- deterministic network space derived from name + secret via HKDF-SHA256,
  with frozen labels and unambiguous length-prefixed encoding
- replaceable discovery returning unverified candidates only; static
  bootstrap, in-memory test backend and a composite
- real iroh connections plus an explicit mutual membership proof:
  HMAC-SHA256 over a role-separated transcript bound to the TLS exporter,
  the network id and both endpoint identities
- small versioned control protocol: handshake, announcement, ping/pong
- multiple networks per agent with enforced isolation
- automatic reconnect with bounded backoff and jitter
- mandatory state vs disposable cache, with a real directory ownership lock
- status snapshots, event stream and honest diagnostics

47 integration and unit tests cover the required scenarios offline on
loopback. Snapshots, revocations and WireGuard are designed for and
documented, not implemented.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-21 00:10:07 +01:00