Wires the gondwana P1 directory (NodeRecord/ConfigBlob/full-log federation +
mesh_sync loop) onto #766's trustless bootstrap:
- mesh listener (annuaire-mesh.conf.tpl) now also serves GET /log/export
(signed, self-certifying, read-only) for cross-node replication.
- mesh_sync pulls /log/export from the #766 listener (wg-mesh-ip:8799), not the
main :9080.
- node identity reuses #766's genesis()/annuairectl/node.key.
Combined suite 167 green.
Security-review follow-ups (no exploitable forgery was found; these address
board-wide nginx availability):
- postinst validates the rendered /etc/nginx/conf.d/annuaire-mesh.conf with
nginx -t and removes it on failure, so a broken render can never persist and
break a later nginx -t by any other actor (reboot, sibling postinst).
- ship /etc/sysctl.d/30-secubox-nonlocal-bind.conf (ip_nonlocal_bind=1) so
nginx binds the specific wg-mesh IP even when wg-quick@wg-mesh starts after
nginx at boot (the board currently has it =0, and already binds specific IPs
→ real bind-failure risk). postinst applies it before rendering the listener.
Self-certifying federation (the core fix):
- ingest_offer enforces did_from_pubkey(pubkey) == offer.provider BEFORE the
signature check — closes 'bring your own key, claim any provider DID'. The
sig alone only proves key-ownership; did:plc = sha256(pubkey)[:32] binds the
key to the DID with no directory and no prior trust.
- _get_offers / GET /services emit self-contained verifiable offers: the stored
payload omits sig/signer_did and never carried the pubkey (so a stranger
could not verify at all). Now carry sig + signer_did + provider_pubkey;
provider_pubkey is transport metadata, stripped before reconstructing the
extra=forbid ServiceOffer model.
- pull_services uses the carried pubkey → self-certifying ingest.
Node bootstrap:
- genesis() verb: a node self-attests as founding MEMBER (root of trust),
breaking the invite/join/subscribe chicken-and-egg. Self-cert DID, empty
invited_by, idempotent. Op.GENESIS (additive, chain-compatible).
- crypto.public_from_private().
- /usr/sbin/annuairectl: init|whoami|status|offer|services|pull. Operates the
journal directly as secubox (no JWT for one-time bootstrap); key 0600 in
/etc/secubox/secrets/annuaire/.
Mesh listener:
- nginx/annuaire-mesh.conf.tpl: binds ONLY wg-mesh IPv4:8799, allow
10.10.0.0/24 + deny all, ONLY GET /api/v1/annuaire/services. postinst renders
with detected mesh IP (skips if no mesh); postrm cleans up.
tests: +7 federation tests (134 passing). changelog 0.2.0.