···2323- 🤝 **Mesh Agent Exchange Protocol (MAEP)**: You and your amigos run multiple agents and want them to message each other: use the MAEP, a p2p protocol with trust-state and audit trails.
2424- 🔒 **Serious secure defaults**: Profile-based credential injection, Guard redaction, outbound policy controls, and async approvals with audit trails (see [docs/security.md](docs/security.md)).
2525- 🧰 **Practical Skills system**: Discover + inject `SKILL.md` from `~/.morph`, `~/.claude`, and `~/.codex`, with smart routing plus explicit control (see [docs/skills.md](docs/skills.md)).
2626+- 📚 **Beginner-friendly**: Built as a learning-first agent project, with detailed design docs in `docs/` and practical debugging tools like `--inspect-prompt` and `--inspect-request`.
26272728## Quickstart
2829
+465
docs/feat/feat_20260206_maep.md
···11+---
22+date: 2026-02-06
33+title: Mesh Agent Exchange Protocol (v1)
44+status: draft
55+---
66+77+# Mesh Agent Exchange Protocol (MAEP) v1
88+99+## 1) Summary
1010+v1 targets a minimum viable end-to-end loop:
1111+- Verifiable node identity (based on libp2p `peer_id`).
1212+- Local contacts management (no online directory service).
1313+- libp2p peer-to-peer communication (direct first, relay fallback).
1414+- JSON-RPC data exchange (minimal method set in v1).
1515+- Encrypted transport sessions (relay cannot decrypt business payloads).
1616+1717+Notes:
1818+- v1 1v1 E2EE depends on libp2p secure channels.
1919+- v1 does not include asynchronous session bootstrapping via X3DH / Double Ratchet, nor per-message ratcheting.
2020+2121+## 2) Goals
2222+- Allow two agents to complete secure online data exchange after exchanging contact cards.
2323+- Automatically fall back to relay when direct connection fails.
2424+- Ensure `node_uuid` is never treated as a standalone identity credential.
2525+- Keep implementation behavior consistent and avoid cross-language interoperability drift.
2626+2727+## 3) Non-Goals
2828+- No online directory service.
2929+- No group E2EE.
3030+- No durable retransmission queue.
3131+- No exactly-once guarantee.
3232+- No x402 billing flow (deferred to v2).
3333+3434+## 4) Terminology
3535+- `peer_id`: libp2p Peer ID (protocol-layer identity used for auth and connection matching).
3636+- `node_id`: business namespace identifier, defined as `maep:<peer_id>`, used only for display/logging/cross-system references.
3737+- `node_uuid`: business alias (UUIDv7), used only for contact lookup and display.
3838+- `identity_keypair`: local Ed25519 identity keypair.
3939+- `identity_pub_ed25519`: externally shared Ed25519 public key.
4040+4141+Normative requirements:
4242+- Any identity-equality check must compare `peer_id`, never `node_uuid`.
4343+- `peer_id` must be parsed/derived via libp2p SDK APIs; custom hash implementations are forbidden.
4444+4545+## 5) Identity And Contact Card
4646+4747+### 5.1 Identity Fields
4848+- `node_uuid`
4949+- `peer_id`
5050+- `node_id` (optional derived value: `maep:<peer_id>`)
5151+- `identity_pub_ed25519`
5252+5353+Implementation requirement:
5454+- The local libp2p host must use the same `identity_keypair` as its host identity key.
5555+- Otherwise, runtime `RemotePeer()` may diverge from the contact card `peer_id`, causing interop failures.
5656+5757+### 5.2 Contact Card Schema
5858+Recommended fields:
5959+- `version`
6060+- `node_uuid`
6161+- `peer_id`
6262+- `node_id`
6363+- `identity_pub_ed25519`
6464+- `addresses`
6565+- `min_supported_protocol`
6666+- `max_supported_protocol`
6767+- `issued_at`
6868+- `expires_at` (recommended)
6969+- `key_rotation_of` (optional)
7070+7171+Encoding rules:
7272+- `identity_pub_ed25519`: base64url (no padding) encoding of Ed25519 public key bytes.
7373+- `sig`: base64url (no padding) encoding of detached signature bytes.
7474+- `addresses`: multiaddr; each address must end with `/p2p/<peer_id>`.
7575+ Note: relay segments are allowed in the middle (for example `/p2p/<relay_peer_id>/p2p-circuit/...`).
7676+7777+JSON/JCS constraints (hardcoded for MVP):
7878+- `null` is not allowed.
7979+- Floating-point numbers are not allowed (only integer numeric type).
8080+- Duplicate keys are not allowed.
8181+- In JSON-RPC request/response parsing paths, any violation above returns `ERR_INVALID_JSON_PROFILE`.
8282+- In contact-card import paths, payload/structure violations are unified as `ERR_INVALID_CONTACT_CARD` (more granular causes may still be logged).
8383+- `identity_pub_ed25519` must decode via base64url to exactly 32 bytes (Ed25519 raw public key).
8484+- `DerivePeerID`, fingerprinting, and signature verification must always use decoded raw bytes as input.
8585+8686+JCS example (`sig` excluded from signed object):
8787+```json
8888+{
8989+ "payload": {
9090+ "version": 1,
9191+ "node_uuid": "0194f5c0-8f6e-7d9d-a4d7-6d8d4f35f456",
9292+ "peer_id": "12D3KooWabc",
9393+ "node_id": "maep:12D3KooWabc",
9494+ "identity_pub_ed25519": "cHVibGljLWtleS1ieXRlcy1iYXNlNjR1cmw",
9595+ "addresses": ["/ip4/203.0.113.8/udp/4001/quic-v1/p2p/12D3KooWabc"],
9696+ "min_supported_protocol": 1,
9797+ "max_supported_protocol": 1,
9898+ "issued_at": "2026-02-06T16:00:00Z",
9999+ "expires_at": "2026-08-06T16:00:00Z"
100100+ },
101101+ "sig_alg": "ed25519",
102102+ "sig_format": "jcs-rfc8785-detached",
103103+ "sig": "base64url_signature"
104104+}
105105+```
106106+107107+The `peer_id` above is intentionally truncated for readability; real payloads must use the full peer-id string.
108108+109109+Recommended signing input:
110110+- `"maep-contact-card-v1\n" + canonical_json_bytes(payload)`
111111+112112+### 5.3 Import-Time Validation (Offline)
113113+Contact-card import must cover all checks below (implementation order may vary):
114114+1. Validate `expires_at` (if present and already expired, reject import).
115115+2. Compute `pub = base64url_decode(identity_pub_ed25519)`; it must be 32 bytes, otherwise `ERR_INVALID_CONTACT_CARD`.
116116+3. Build a public key object from `pub` via libp2p SDK and derive `derived_peer_id` via SDK APIs (custom implementation forbidden).
117117+4. Parse payload `peer_id` as `expected_peer_id`; compare binary values of `derived_peer_id` and `expected_peer_id`.
118118+5. Validate `node_id == "maep:" + peer_id` (when `node_id` is provided).
119119+6. Validate `sig` (JCS canonical payload + domain separator).
120120+7. Parse each `addresses` entry and verify the terminal peer component equals payload `peer_id`.
121121+8. If local state already has the same `node_uuid` mapped to a different `peer_id`, mark as `conflicted` and forbid automatic overwrite.
122122+123123+### 5.4 Connect-Time Validation (Online)
124124+Connection-time validation must perform:
125125+1. Select `expected_peer_id` from the contact.
126126+2. Dial only addresses containing `/p2p/<expected_peer_id>`; reject others directly.
127127+3. After connection establishment, read `RemotePeer()` and `RemotePublicKey()` from the libp2p connection.
128128+4. Validate `RemotePeer() == expected_peer_id`.
129129+5. On mismatch, return `ERR_PEER_ID_MISMATCH`, disconnect immediately, and emit a high-priority security alert.
130130+131131+## 6) Transport And Connectivity
132132+133133+### 6.1 Transport Stack
134134+- The current implementation uses libp2p default secure transport behavior (QUIC/TCP chosen by reachability and negotiation).
135135+- Relay uses Circuit Relay v2.
136136+- Explicit enforced QUIC-priority scheduling is not implemented yet (can be a future optimization).
137137+138138+### 6.2 Dial Priority
139139+Connection order (current implementation):
140140+1. Try direct addresses first (without `/p2p-circuit`), in input order.
141141+2. Then try relay addresses (with `/p2p-circuit`), in input order.
142142+3. If caller provides explicit address list, use it; otherwise use contact-saved `addresses`.
143143+4. Automatic reordering by `last_ok_at` is not implemented yet.
144144+145145+### 6.3 Direct-Failure Classification
146146+The current implementation does not emit structured failure classification codes; dial failures are returned as aggregated per-address error strings.
147147+Actual behavior:
148148+- Retry by address order: direct first, then relay.
149149+- Per-address dial timeout defaults to 3 seconds (`DialAddrTimeout`).
150150+- Identity mismatch (`peer_id_mismatch`) disconnects immediately during stream validation and returns `ERR_PEER_ID_MISMATCH`.
151151+- A total direct-attempt budget of 8-12 seconds is not implemented yet.
152152+153153+### 6.4 Default Limits (MVP fixed)
154154+- `max_rpc_request_bytes = 256 KiB`
155155+- `max_payload_bytes = 128 KiB` (decoded bytes of `payload_base64`)
156156+- `hello_timeout = 3s`
157157+- `rpc_timeout_default = 10s`
158158+- `rate_limit_data_push_per_peer = 120/min`
159159+160160+## 7) RPC Model
161161+162162+### 7.1 Protocol IDs
163163+- `hello`: `/maep/hello/1.0.0`
164164+- `rpc`: `/maep/rpc/1.0.0`
165165+166166+### 7.2 Stream Framing
167167+v1 is fixed to one-stream-per-request:
168168+1. Client opens `/maep/rpc/1.0.0` stream.
169169+2. Client writes full JSON-RPC request.
170170+3. Client half-closes write side.
171171+4. Server returns one JSON-RPC response.
172172+5. Stream closes.
173173+174174+Note:
175175+- v1 does not support multiplexed framing.
176176+177177+### 7.3 Version Negotiation (`hello`)
178178+Both sides exchange on `/maep/hello/1.0.0`:
179179+- `protocol_min`
180180+- `protocol_max`
181181+- `capabilities`
182182+183183+`hello` stream semantics (fixed in v1):
184184+1. Dialer opens `/maep/hello/1.0.0` stream, writes one UTF-8 JSON `hello` object, then half-closes.
185185+2. Listener reads request, writes one UTF-8 JSON `hello` object response, and closes stream.
186186+3. Each side computes `negotiated_protocol`; mismatch indicates implementation error and should trigger disconnect alerting.
187187+4. `/maep/rpc/1.0.0` requests must not be processed before successful `hello` negotiation.
188188+189189+Negotiation rules:
190190+- `negotiated = min(local_max, remote_max)`
191191+- Must satisfy `negotiated >= max(local_min, remote_min)`
192192+- If no overlap, return `ERR_UNSUPPORTED_PROTOCOL` and disconnect
193193+194194+Example:
195195+```json
196196+{
197197+ "type": "hello",
198198+ "protocol_min": 1,
199199+ "protocol_max": 1,
200200+ "capabilities": ["rpc.data.push.v1"]
201201+}
202202+```
203203+204204+### 7.4 Allowed Methods
205205+Default allowlist:
206206+- `agent.ping`
207207+- `agent.capabilities.get`
208208+- `agent.data.push`
209209+210210+Methods outside the allowlist must be rejected with `ERR_METHOD_NOT_ALLOWED`.
211211+212212+### 7.5 `agent.data.push` Semantics
213213+`agent.data.push` is used for data exchange, including chat messages.
214214+215215+Request example:
216216+```json
217217+{
218218+ "jsonrpc": "2.0",
219219+ "id": "req-7f9f",
220220+ "method": "agent.data.push",
221221+ "params": {
222222+ "topic": "chat.message",
223223+ "content_type": "application/json",
224224+ "payload_base64": "eyJtZXNzYWdlX2lkIjoibXNnXzAwMSIsInRleHQiOiLllYgiLCJzZW50X2F0IjoiMjAyNi0wMi0wNlQxNjozMDowMFoiLCJzZXNzaW9uX2lkIjoiMDE5NGY1YzAtOGY2ZS03ZDlkLWE0ZDctNmQ4ZDRmMzVmNDU2In0",
225225+ "idempotency_key": "m-001"
226226+ }
227227+}
228228+```
229229+230230+Notification and request rules:
231231+- Without `id`: notification, no response.
232232+- With `id`: return acceptance-state response, not a durability guarantee.
233233+234234+JSON-RPC constraints (hardcoded for MVP):
235235+- `null` is not allowed in request/response JSON body.
236236+- Floating-point numbers are not allowed in protocol fields (integer numeric type only).
237237+- Violations return `ERR_INVALID_JSON_PROFILE`.
238238+- `agent.data.push.params.content_type` must start with `application/json` (for example `application/json`, `application/json; charset=utf-8`); `text/plain` is rejected at protocol layer.
239239+- `payload_base64` must be base64url without padding; decode failure returns `ERR_INVALID_PARAMS`.
240240+- Decoded `payload_base64` must be an envelope JSON object and include at least:
241241+ - `message_id` (non-empty string)
242242+ - `text` (non-empty string)
243243+ - `sent_at` (RFC3339 string)
244244+- Dialogue topics (`share.proactive.v1` / `dm.checkin.v1` / `dm.reply.v1` / `chat.message`) must include `session_id`, and `session_id` must be UUIDv7.
245245+- If decoded `payload_base64` exceeds `max_payload_bytes`, return `ERR_PAYLOAD_TOO_LARGE`.
246246+- No protocol-level fallback: no auto conversion from plain text to envelope, and no auto-generated `session_id`. Invalid payloads are rejected directly (`ERR_INVALID_PARAMS`).
247247+248248+### 7.6 Business Metadata Topic (`profile.intro.v1`, planned)
249249+Implementation status:
250250+- Current code does not implement dedicated handling for `profile.intro.v1`.
251251+- Current code does not persist `remote_public_nickname` (or equivalent field).
252252+- If this topic is sent now, it is handled as ordinary `agent.data.push` envelope input.
253253+254254+Future design constraints:
255255+Purpose:
256256+- Exchange public self-profile data at business layer, such as outward-facing nickname.
257257+- Must not participate in protocol-level auth or alter `peer_id/node_id/trust_state`.
258258+259259+Constraints:
260260+- Still use `agent.data.push`.
261261+- `content_type` must start with `application/json`.
262262+- Decoded `payload_base64` must be envelope JSON and satisfy required fields from 7.5.
263263+264264+Suggested envelope example:
265265+```json
266266+{
267267+ "message_id": "msg_019db8f8-7e6f-79f5-8e92-d5f4f8fd0d74",
268268+ "text": "profile intro update",
269269+ "sent_at": "2026-02-07T12:00:00Z",
270270+ "profile": {
271271+ "public_nickname": "Mochi",
272272+ "lang": "zh-CN",
273273+ "updated_at": "2026-02-07T12:00:00Z"
274274+ }
275275+}
276276+```
277277+278278+Receiver-side handling recommendations:
279279+- If `profile.public_nickname` is empty, ignore update.
280280+- If `profile.updated_at` is not RFC3339, ignore that profile update.
281281+- Business layer should store peer-declared nickname as `remote_public_nickname` (or equivalent), and should not forcibly overwrite local `contact_nickname`.
282282+- If local nickname overwrite is needed, it must go through explicit business policy and emit an audit log.
283283+284284+Response example (acceptance state):
285285+```json
286286+{
287287+ "jsonrpc": "2.0",
288288+ "id": "req-7f9f",
289289+ "result": {
290290+ "accepted": true,
291291+ "deduped": false
292292+ }
293293+}
294294+```
295295+296296+## 8) Reliability And Idempotency
297297+- Online semantics: best-effort + request timeout.
298298+- Idempotency dedupe key: tuple `(from_peer_id, topic, idempotency_key)`.
299299+- First receipt: process and record dedupe key.
300300+- Duplicate receipt: do not process again; return `deduped=true` (when `id` exists) or silently drop (notification).
301301+- Dedupe record TTL: default 7 days (configurable).
302302+- Dedupe table cap: default global 10k records; evict oldest first when exceeded.
303303+304304+## 9) Security And Trust State
305305+306306+### 9.1 Relay Visibility
307307+Visible to relay:
308308+- Connection relationships, traffic size, timing, and online status.
309309+310310+Not visible to relay:
311311+- RPC business plaintext payload.
312312+313313+### 9.2 Trust States
314314+- `tofu`: cryptographic checks pass, but no second-channel verification yet.
315315+- `verified`: second-channel fingerprint verification completed.
316316+- `conflicted`: `node_uuid -> peer_id` conflict or critical identity mismatch.
317317+- `revoked`: revoked; communication forbidden.
318318+319319+Rules:
320320+- `tofu` is not `verified`.
321321+- `revoked` and `conflicted` must block business communication.
322322+323323+### 9.3 Fingerprint Verification Flow
324324+Fingerprint definition:
325325+- `fingerprint = SHA256(base64url_decode(identity_pub_ed25519))` (displayed in grouped hex).
326326+327327+Recommended flow:
328328+1. Set state to `tofu` after import.
329329+2. Show short fingerprint (for example 8 groups of 4 hex chars).
330330+3. Compare via second channel (face-to-face QR, spoken grouped code, already-verified chat channel).
331331+4. Promote to `verified` only on full match.
332332+5. If mismatch, set `conflicted` and alert.
333333+334334+Security note:
335335+- Without second-channel verification, first-contact impersonation risk remains.
336336+337337+## 10) Error Codes And Handling
338338+### 10.1 JSON-RPC Error Object
339339+RPC errors must use standard JSON-RPC structure:
340340+```json
341341+{
342342+ "jsonrpc": "2.0",
343343+ "id": "req-7f9f",
344344+ "error": {
345345+ "code": -32004,
346346+ "message": "ERR_METHOD_NOT_ALLOWED",
347347+ "data": { "details": "method=agent.foo" }
348348+ }
349349+}
350350+```
351351+352352+Rules:
353353+- `error.code` must be an integer.
354354+- `error.message` must use `ERR_*` symbols from the table below.
355355+- Notifications (without `id`) must not return any response (including errors); only log/metrics, and disconnect if needed.
356356+- If request parsing fails and a valid `id` is extractable, return corresponding `ERR_*` error object; otherwise log only and do not respond.
357357+358358+### 10.2 Symbol To JSON-RPC Code Mapping (MVP fixed)
359359+| Symbol | JSON-RPC `error.code` |
360360+|---|---:|
361361+| `ERR_UNAUTHORIZED` | -32001 |
362362+| `ERR_PEER_ID_MISMATCH` | -32002 |
363363+| `ERR_CONTACT_CONFLICTED` | -32003 |
364364+| `ERR_METHOD_NOT_ALLOWED` | -32004 |
365365+| `ERR_PAYLOAD_TOO_LARGE` | -32005 |
366366+| `ERR_RATE_LIMITED` | -32006 |
367367+| `ERR_UNSUPPORTED_PROTOCOL` | -32007 |
368368+| `ERR_INVALID_JSON_PROFILE` | -32008 |
369369+| `ERR_INVALID_CONTACT_CARD` | -32009 |
370370+| `ERR_INVALID_PARAMS` | -32602 |
371371+372372+### 10.3 Connection/Request Handling
373373+| Symbol | Trigger | Connection Action | Request Action |
374374+|---|---|---|---|
375375+| `ERR_UNAUTHORIZED` | Remote not in contacts, or `trust_state` is `revoked/conflicted` | disconnect | reject |
376376+| `ERR_PEER_ID_MISMATCH` | `RemotePeer() != expected_peer_id` | disconnect | reject |
377377+| `ERR_CONTACT_CONFLICTED` | Conflict found during contact-card import (`node_uuid` mapped to different `peer_id`) | n/a | reject(import) |
378378+| `ERR_METHOD_NOT_ALLOWED` | Method not in allowlist | keep | reject |
379379+| `ERR_PAYLOAD_TOO_LARGE` | Exceeds payload limit | keep | reject |
380380+| `ERR_RATE_LIMITED` | Rate limit exceeded | keep | reject |
381381+| `ERR_INVALID_JSON_PROFILE` | `null`/floating-point/duplicate key detected | keep | reject |
382382+| `ERR_INVALID_CONTACT_CARD` | Invalid contact-card fields (import path) | n/a | reject(import) |
383383+| `ERR_INVALID_PARAMS` | Invalid parameter format (for example `payload_base64` decode failure) | keep | reject |
384384+| `ERR_UNSUPPORTED_PROTOCOL` | No protocol-version overlap | disconnect | reject |
385385+386386+## 11) Protocol Upgrade And Downgrade
387387+388388+### 11.1 Compatibility Rules
389389+- `v1.x` must remain backward compatible.
390390+- New fields can only be added as optional.
391391+- Unknown fields must be ignored, not rejected.
392392+- Breaking changes require major version bump (`v2`).
393393+394394+### 11.2 Rollout Order
395395+Recommended order:
396396+1. Upgrade relay first.
397397+2. Upgrade passive receivers next.
398398+3. Upgrade active initiators last.
399399+400400+### 11.3 Downgrade Detection
401401+Nodes should record:
402402+- `last_remote_max_protocol`
403403+- `last_negotiated_protocol`
404404+405405+Trigger `downgrade_suspected` when either condition is met:
406406+- Current `remote_max_protocol` is lower than historical value.
407407+- Current `negotiated_protocol` is lower than historical value.
408408+409409+Current implementation note:
410410+- Current implementation does not gate on network-path changes (for example relay/address-family changes); any version decrease triggers alerting.
411411+412412+Default handling:
413413+- Log high-priority warning and prompt manual verification.
414414+- Keep connection usable.
415415+416416+Optional strict mode:
417417+- Allow only `agent.ping` and `agent.capabilities.get`; temporarily disable `agent.data.push`.
418418+419419+## 12) Storage Model
420420+MVP requirements:
421421+- Provide storage abstraction interfaces first (identity / contacts / audit / dedupe / protocol history).
422422+- Default backend uses local files (no database dependency).
423423+- Future SQLite/other backends can be added, but must not change upper-layer protocol behavior.
424424+425425+Recommended file-backend partitions (logically equivalent to tables):
426426+- `node_identity`
427427+ - `node_uuid`, `peer_id`, `node_id`, `identity_pub_ed25519`, `identity_priv_ed25519`, `created_at`
428428+- `contacts`
429429+ - `node_uuid`, `peer_id`, `node_id`, `display_name`, `identity_pub_ed25519`, `addresses`, `trust_state`, `last_seen`
430430+- `audit_events`
431431+ - `event_id`, `action`, `peer_id`, `node_uuid`, `previous_trust_state`, `new_trust_state`, `reason`, `metadata`, `created_at`
432432+- `dedupe_records`
433433+ - `from_peer_id`, `topic`, `idempotency_key`, `created_at`, `expires_at`
434434+- `protocol_history`
435435+ - `peer_id`, `last_remote_max_protocol`, `last_negotiated_protocol`, `updated_at`
436436+437437+Implementation recommendations:
438438+- Use atomic file replacement on writes (temp file + rename).
439439+- Recommended permissions: directory `0700`, state files `0600`.
440440+441441+## 13) Rollout Plan
442442+### Phase 0: Spec Freeze
443443+- [x] Freeze `peer_id` definition and contact-card fields.
444444+- [x] Freeze protocol IDs (`/maep/hello/1.0.0`, `/maep/rpc/1.0.0`).
445445+- [x] Freeze error codes and disconnect strategy.
446446+- [x] Freeze JCS signing input and default `allowed_methods` allowlist.
447447+448448+### Phase 1: MVP
449449+- [x] Node identity generation and persistence (`node_uuid/peer_id/node_id`).
450450+- [x] contacts import/export (out-of-band).
451451+- [x] Direct-first + relay-fallback dialing.
452452+- [x] hello negotiation + JSON-RPC one-request-per-stream.
453453+- [x] `agent.ping` / `agent.capabilities.get` / `agent.data.push`.
454454+- [x] trust_state and conflict-mapping enforcement.
455455+- [x] dedupe TTL and capacity eviction.
456456+457457+## 14) Open Questions
458458+- Should the default relay policy be `verified_only=false + rate limiting for tofu`, or `verified_only=true`?
459459+460460+## References
461461+- Peer IDs: https://github.com/libp2p/specs/blob/master/peer-ids/peer-ids.md
462462+- Secure Channels: https://docs.libp2p.io/concepts/secure-comm/overview/
463463+- Circuit Relay v2: https://docs.libp2p.io/concepts/nat/circuit-relay/
464464+- go-libp2p network API: https://pkg.go.dev/github.com/libp2p/go-libp2p/core/network
465465+- JSON-RPC 2.0: https://www.jsonrpc.org/specification
+141
docs/feat/feat_20260206_maep_impl.md
···11+---
22+date: 2026-02-06
33+title: MAEP v1 Implementation Notes (File Store First)
44+status: draft
55+---
66+77+# MAEP v1 Implementation Details (In Progress)
88+99+## 1) Current Scope
1010+Without introducing a database, this phase delivers MAEP v1 core infrastructure first:
1111+- Identity generation and persistence.
1212+- JCS signing and verification for contact cards.
1313+- Contact import validation (`peer_id` / `node_id` / terminal `/p2p/<peer_id>` in multiaddr).
1414+- Basic trust-state transitions (`tofu -> verified`, conflict marked as `conflicted`).
1515+- `/maep/hello/1.0.0` and `/maep/rpc/1.0.0` (one stream per request).
1616+- Minimal RPC method set: `agent.ping` / `agent.capabilities.get` / `agent.data.push`.
1717+- File backend for `dedupe_records` / `protocol_history` / `inbox_messages` / `outbox_messages` / `audit_events`.
1818+- CLI management entry points.
1919+2020+## 2) Storage Abstraction
2121+Code location: `maep/store.go`
2222+2323+Interfaces:
2424+- `Ensure(ctx)`
2525+- `GetIdentity(ctx)` / `PutIdentity(ctx, identity)`
2626+- `GetContactByPeerID(ctx, peerID)`
2727+- `GetContactByNodeUUID(ctx, nodeUUID)`
2828+- `PutContact(ctx, contact)`
2929+- `ListContacts(ctx)`
3030+- `AppendAuditEvent(ctx, event)`
3131+- `ListAuditEvents(ctx, peerID, action, limit)`
3232+- `AppendInboxMessage(ctx, message)`
3333+- `ListInboxMessages(ctx, fromPeerID, topic, limit)`
3434+- `AppendOutboxMessage(ctx, message)`
3535+- `ListOutboxMessages(ctx, toPeerID, topic, limit)`
3636+- `GetDedupeRecord(ctx, fromPeerID, topic, idempotencyKey)`
3737+- `PutDedupeRecord(ctx, record)`
3838+- `PruneDedupeRecords(ctx, now, maxEntries)`
3939+- `GetProtocolHistory(ctx, peerID)`
4040+- `PutProtocolHistory(ctx, history)`
4141+4242+Design intent:
4343+- Protocol logic (`maep/service.go`) depends only on interfaces.
4444+- Adding SQLite/Badger/remote store later must not change business validation paths.
4545+4646+## 3) File Backend
4747+Code location: `maep/file_store.go`
4848+4949+Directory: defaults to `file_state_dir/maep` (override via CLI `--dir`).
5050+5151+Files:
5252+- `identity.json`
5353+- `contacts.json`
5454+- `audit_events.jsonl`
5555+- `inbox_messages.jsonl`
5656+- `outbox_messages.jsonl`
5757+- `dedupe_records.json`
5858+- `protocol_history.json`
5959+6060+Implementation notes:
6161+- File permission `0600`, directory permission `0700`.
6262+- Writes use atomic replacement (temp file + rename) to reduce corruption risk.
6363+- `contacts.json` uses a fixed `version` field for future migrations.
6464+- `inbox/outbox` use a unified envelope shape: `message_id/topic/content_type/payload_base64/idempotency_key/session_id/reply_to + timestamp` (`received_at` for inbox, `sent_at` for outbox).
6565+6666+## 4) Identity And Contact Card
6767+Code locations:
6868+- `maep/identity.go`
6969+- `maep/contact_card.go`
7070+- `maep/jsonprofile.go`
7171+7272+Implemented rules:
7373+- Identity keys: Ed25519.
7474+- `peer_id`: derived from public key via libp2p SDK (custom hash forbidden).
7575+- `node_id = "maep:" + peer_id`.
7676+- `identity_pub_ed25519`: base64url (no padding), must decode to 32 bytes.
7777+- JCS: RFC8785 canonicalization (`jsoncanonicalizer.Transform`).
7878+- Signing input: `"maep-contact-card-v1\n" + canonical_payload`.
7979+- Strict JSON profile: reject `null`, floating-point, and duplicate keys.
8080+- Error-symbol conventions: JSON-RPC parsing violations map to `ERR_INVALID_JSON_PROFILE`; contact-card import structural/semantic violations map to `ERR_INVALID_CONTACT_CARD`.
8181+8282+## 5) CLI Entry Points
8383+Code location: `cmd/mistermorph/maepcmd/maep.go`
8484+8585+Commands:
8686+- `mistermorph maep init`
8787+- `mistermorph maep id`
8888+- `mistermorph maep card export --address ...`
8989+- `mistermorph maep contacts list`
9090+- `mistermorph maep contacts import <contact_card.json|->`
9191+- `mistermorph maep contacts show <peer_id>`
9292+- `mistermorph maep contacts verify <peer_id>`
9393+- `mistermorph maep audit list --limit 100`
9494+- `mistermorph maep inbox list --limit 50`
9595+- `mistermorph maep outbox list --limit 50`
9696+- `mistermorph maep serve`
9797+- `mistermorph maep hello <peer_id>`
9898+- `mistermorph maep ping <peer_id>`
9999+- `mistermorph maep capabilities <peer_id>`
100100+- `mistermorph maep push <peer_id> --text ...`
101101+102102+## 6) Completed / Next
103103+Completed:
104104+- [x] Store abstraction interface
105105+- [x] File store
106106+- [x] Identity generation and persistence
107107+- [x] Contact-card JCS signing and verification
108108+- [x] Contact import validation and conflict marking
109109+- [x] hello negotiation (`/maep/hello/1.0.0`)
110110+- [x] RPC handling (`/maep/rpc/1.0.0`, one request per stream)
111111+- [x] `agent.ping` / `agent.capabilities.get` / `agent.data.push`
112112+- [x] Dedupe file backend (`dedupe_records.json`)
113113+- [x] Protocol-history file backend (`protocol_history.json`)
114114+- [x] Inbox file backend (`inbox_messages.jsonl`)
115115+- [x] Outbox file backend (`outbox_messages.jsonl`)
116116+- [x] Actual `ERR_RATE_LIMITED` enforcement (per peer per minute)
117117+- [x] Local inbox query CLI for `agent.data.push` (`maep inbox list`)
118118+- [x] Local outbox query CLI for `agent.data.push` (`maep outbox list`)
119119+- [x] Dial priority: direct first, relay second (classified by `/p2p-circuit`)
120120+- [x] Audit logs for trust-state/contact operations (`audit_events.jsonl` + `maep audit list`)
121121+- [x] Network and CLI baseline commands
122122+- [x] On RPC parse failure, reply with `ERR_*` if a valid `id` is best-effort extractable; log-only with no response if `id` cannot be extracted
123123+124124+Next phase:
125125+- [ ] Automatic relay discovery and policy-based selection (currently only explicit relay addresses from contacts)
126126+- [ ] Persist connection-quality and address-priority (`last_ok_at`)
127127+128128+## 7) Compatibility Notes
129129+Current implementation aligned with `docs/feat/feat_20260206_maep.md` on:
130130+- `peer_id` / `node_id` definitions.
131131+- Key contact-card import validation checks.
132132+- JCS + domain-separator signing.
133133+- JSON profile restrictions (no `null`, no floating-point).
134134+135135+Current implementation trade-offs:
136136+- Storage lands on files first, with no DB dependency; SQLite remains a future pluggable backend.
137137+- Dedupe uses a rolling window of "7-day TTL + global max 10k records" (processing may happen again after eviction/expiry).
138138+- Protocol constraint: dialogue topics (`share.proactive.v1` / `dm.checkin.v1` / `dm.reply.v1` / `chat.message`) must include `session_id`; `session_id` must be UUIDv7 (plain UUID string, no topic/prefix/suffix). Current behavior validates only format and requiredness, without enforcing session-continuity semantics.
139139+- Storage no longer auto-derives `session_id` from topic, avoiding pseudo-session splits.
140140+- `agent.data.push` is strict envelope-only: `content_type` must start with `application/json`; payload must be envelope JSON (at least `message_id`, `text`, `sent_at`); no transport-layer fallback filling.
141141+- Protocol validation failures are always rejected (`ERR_INVALID_PARAMS`): no automatic plain-text conversion to envelope and no automatic `session_id` generation.