feat: M3 peer gossip protocol

Refs #1

- peers table (peer_id, addr, cursor, last_handshake)
- watch serves HTTP API: GET /v1/ping (knowledge vector),
  GET /v1/log?node=&after= (cursor-paged pull), POST /v1/obs/batch
- anti-entropy sweep (Run): ping, pull what we lack, push own obs;
  echo suppressed by node_id ownership; reconcile-on-pull
- config via KNOX_PEERS / KNOX_PEER_ADDR; knox gossip status
- db: GossipObservation wire type, PushObservations, ObservationsAfter,
  KnowledgeVector, peer upsert/list
- integration tests: bidirectional convergence + idempotency
This commit is contained in:
2026-08-29 04:53:26 -07:00
parent aa0dec68c1
commit 8c054094a1
10 changed files with 755 additions and 20 deletions
+50
View File
@@ -0,0 +1,50 @@
package cmd
import (
"fmt"
"github.com/spf13/cobra"
"github.com/david/knox/internal/db"
"github.com/david/knox/internal/watch"
)
// NewGossipCmd exposes peer status/diff for the gossip protocol.
func NewGossipCmd(kdb *db.KnoxDB) *cobra.Command {
cmd := &cobra.Command{
Use: "gossip",
Short: "Inspect peer-to-peer observation sync",
}
cmd.AddCommand(
&cobra.Command{
Use: "status",
Short: "Show known peers and knowledge vectors",
RunE: func(c *cobra.Command, args []string) error {
peers, err := kdb.ListPeers()
if err != nil {
return err
}
vector, err := kdb.KnowledgeVector()
if err != nil {
return err
}
fmt.Printf("node_id: %s\n", kdb.NodeID())
fmt.Printf("peers (static: %d configured):\n", len(watch.PeerAddrs()))
for _, p := range peers {
fmt.Printf(" %-24s %-28s last=%s vector=%d\n", p.PeerID, p.Addr, p.LastHandshake, p.Cursor)
}
if len(peers) == 0 {
fmt.Println(" (none — set KNOX_PEERS to sync with other nodes)")
}
fmt.Println("knowledge vector (node_id -> max hcl):")
for nid, hcl := range vector {
if nid == kdb.NodeID() {
continue
}
fmt.Printf(" %-24s -> %d\n", nid, hcl)
}
return nil
},
},
)
return cmd
}