File size: 7,038 Bytes
6f9a5fd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# HearthNet Glossary

Canonical names. Every spec uses these. Do not introduce synonyms.

---

## Identifiers

| Term | Form | Notes |
|------|------|-------|
| **NodeID** | `ed25519:XXXX-XXXX-XXXX-XXXX` | First 8 bytes of Ed25519 public key, base32, grouped by 4 |
| **NodeID (full)** | `ed25519:<base64-url-nopad>` of full pubkey | Used only in manifests; never displayed to users |
| **CommunityID** | `ed25519:<...>` of community root pubkey | Same format as full NodeID |
| **CapabilityName** | dotted lowercase: `llm.chat`, `rag.query` | Namespace allocation: see CONTRACT §3.1 |
| **Version** | `(major, minor)` tuple — wire form `"1.0"` | Patch component does not exist on the wire |
| **SchemaHash** | `blake3:<hex>` | BLAKE3 of canonical-JSON of capability schema |
| **CID** | `blake3:<hex>` | Content identifier for blobs |
| **EventID** | ULID (26 chars, monotonic, encodes wall-clock) | Globally unique per community in practice |
| **TraceID** | ULID | Identifies one logical request across hops |
| **Signature** | `ed25519:<base64-url-nopad>` | Always over canonical-JSON |
| **WallClock** | RFC 3339 UTC: `2026-05-26T08:14:22Z` | Display-only; never used for ordering |
| **Lamport** | `int >= 0` | Per-community monotonic logical counter |
| **Topic** | dotted lowercase: `marketplace.post.created` | Pub-sub topic name |

---

## Concepts

| Term | Definition |
|------|------------|
| **Anchor** | A node profile: always-on, GPU-equipped, primary capability provider |
| **Hearth** | A node profile: mid-tier, typically a laptop, runs some services |
| **Spark** | A node profile: thin client (Pi, mobile, browser); consumes capabilities |
| **Bridge** | A node profile: relay-only, no inference, federates communities (Phase 2) |
| **Profile** | One of `anchor` / `hearth` / `spark` / `bridge` — declared in node manifest |
| **Community** | A trust root: a group of nodes sharing one root key and one event log |
| **Federation** | Cross-community trust + capability access (Phase 2) |
| **Capability** | A named, versioned, schema-bound RPC offered by a node |
| **Capability descriptor** | The metadata for one capability: name, version, schema, params, guarantees |
| **Capability entry** | The bus's local record of one remote capability: descriptor + health |
| **Bus / capability bus** | The L3 routing component each service registers with |
| **Service** | An L4 module that provides one or more capabilities |
| **Node manifest** | A signed JSON document describing what a node is and offers; expires every 30s |
| **Community manifest** | A signed JSON document describing community membership and policy |
| **Event** | A signed, Lamport-stamped record in the community's append-only log |
| **Event log** | The community's full ordered history; one SQLite db per node |
| **Snapshot** | A signed materialised state at some Lamport |
| **Materialised view** | A derived index built by replaying events (member list, marketplace, etc.) |
| **Blob** | An immutable byte string identified by its CID |
| **Chunk** | A 256KB slice of a blob, itself CID-addressed |
| **TrustLevel** | One of `unknown` / `member` / `trusted` / `anchor` |
| **Stability** | One of `experimental` / `beta` / `stable` for a capability |
| **Emergency mode** | UI + behavioural state when the internet-detector reports offline |

---

## Errors (closed set — see CONTRACT §9)

| Code | Meaning |
|------|---------|
| `not_found` | No node offers the requested capability, or the requested resource is gone |
| `capacity_exceeded` | Node is at declared `max_concurrent`; retry with backoff or pick another |
| `schema_mismatch` | Request body does not match the declared schema for this capability/version |
| `unauthorized` | Caller is not a community member, or lacks the required trust level |
| `revoked` | Caller's NodeID is in the revoked set of the community |
| `internal_error` | Service crashed handling this request |
| `not_implemented` | Capability is declared but the handler is a stub |
| `timeout` | Operation exceeded its declared deadline |
| `partition` | Remote node is presumed unreachable (mDNS lost, repeated failures) |
| `invalid_signature` | Signature verification failed on a manifest, event, or request |
| `expired` | Manifest, token, or event TTL has passed |
| `rate_limited` | Caller exceeded the per-peer-per-capability rate budget |
| `bad_request` | Malformed JSON, missing required field, etc. |

---

## File paths (XDG-style; resolved via `platformdirs`)

| Path | Purpose |
|------|---------|
| `<DATA>/keys/device.ed25519` | Private key, 0600 |
| `<DATA>/keys/device.pub` | Public key |
| `<DATA>/communities/<community_id>/manifest.json` | Latest signed community manifest |
| `<DATA>/communities/<community_id>/events.sqlite` | Event log |
| `<DATA>/communities/<community_id>/snapshots/<lamport>.bin` | Signed snapshots |
| `<DATA>/blobs/<aa>/<bb...>` | CID-addressed blobs |
| `<CONFIG>/config.toml` | User configuration |
| `<CACHE>/embeddings/<corpus>` | Vector store on-disk files |
| `<LOG>/<date>.log` | Daily rotating logs |

`<DATA>` = `platformdirs.user_data_dir("hearthnet")` (Linux: `~/.local/share/hearthnet/`).

---

## Default ports

| Port | Purpose | Configurable? |
|------|---------|---------------|
| 7080 | Bus HTTP server | yes (`config.transport.port`) |
| 7860 | Gradio UI | yes (`config.ui.port`) |
| 42424 | UDP discovery multicast | no (interop) |
| 5353 | mDNS | no (system) |

---

## Defaults (numeric — central reference)

| Constant | Value | Where defined |
|----------|-------|---------------|
| `MANIFEST_TTL_SECONDS` | 30 | M01 |
| `MANIFEST_REPUBLISH_INTERVAL_SECONDS` | 20 | M01 |
| `DISCOVERY_UDP_INTERVAL_SECONDS` | 5 (active) / 30 (stable) | M02 |
| `EMERGENCY_PROBE_INTERVAL_SECONDS` | 10 (online) / 2 (offline) | M09 |
| `EMERGENCY_PROBE_TIMEOUT_SECONDS` | 2 | M09 |
| `EMERGENCY_TRANSITION_DEBOUNCE_SECONDS` | 30 | M09 |
| `CONNECTION_IDLE_SECONDS` | 60 | X01 |
| `RECONNECT_BACKOFF_CAP_SECONDS` | 30 | X01 |
| `STREAM_WINDOW_FRAMES` | 16 | X01 |
| `STREAM_ACK_INTERVAL_FRAMES` | 8 | X01 |
| `STREAM_ACK_TIMEOUT_SECONDS` | 5 | X01 |
| `RPC_DEFAULT_TIMEOUT_SECONDS` | 30 | X01 |
| `LLM_GENERATION_DEFAULT_TIMEOUT_SECONDS` | 120 | M04 |
| `CHUNK_SIZE_BYTES` | 262144 (256 KiB) | M07 |
| `BLOB_GC_DISK_THRESHOLD` | 0.80 | M07 |
| `RAG_CHUNK_TOKENS` | 1000 | M05 |
| `RAG_CHUNK_OVERLAP_TOKENS` | 200 | M05 |
| `RAG_DEFAULT_K` | 5 | M05 |
| `RAG_MAX_K` | 20 | M05 |
| `HEALTH_WINDOW_CALLS` | 100 | M03 |
| `HEALTH_QUARANTINE_THRESHOLD` | 0.5 | M03 |
| `HEALTH_QUARANTINE_SECONDS` | 60 | M03 |
| `RATE_LIMIT_SOFT_RPS_PER_CAP` | 10 | X01 |
| `RATE_LIMIT_HARD_RPS_PER_CAP` | 100 | X01 |
| `RATE_LIMIT_SOFT_RPS_TOTAL` | 100 | X01 |
| `RATE_LIMIT_HARD_RPS_TOTAL` | 1000 | X01 |
| `EVENT_LOG_RETENTION_DAYS` | 30 | X02 |
| `SNAPSHOT_LAG_LAMPORT` | 1000 | X02 |
| `TRACE_RING_BUFFER` | 10000 | X03 |
| `LOG_RETENTION_DAYS` | 14 | X03 |

Implementation hint: put these in `hearthnet/constants.py` and import everywhere. Do not hardcode in modules.