Spaces:
Sleeping
Sleeping
Commit Β·
e61e751
1
Parent(s): 885b32c
Phase 1A: Modular Node Conformance
Browse files- C-FED-NODE-001_IMPLEMENTATION_GAP_MAP.md +224 -0
- __pycache__/app.cpython-313.pyc +0 -0
- __pycache__/c_fed_id.cpython-313.pyc +0 -0
- __pycache__/envelope_store.cpython-313.pyc +0 -0
- __pycache__/models.cpython-313.pyc +0 -0
- __pycache__/node_record.cpython-313.pyc +0 -0
- __pycache__/trust.cpython-313.pyc +0 -0
- app.py +242 -124
- envelope_store.py +112 -0
- fed_node_ideas.md +518 -0
- models.py +111 -0
- node_record.py +111 -0
- trust.py +74 -0
- verify_behavior.py +106 -0
C-FED-NODE-001_IMPLEMENTATION_GAP_MAP.md
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# C-FED-NODE-001 Phase 1 Implementation Gap Map
|
| 2 |
+
|
| 3 |
+
**Contract**: C-FED-NODE-001 v0.1.1
|
| 4 |
+
**Target Codebase**: [federation-node/](file:///c:/Users/kryst/Deployment/huggingface-spaces/federation-node/)
|
| 5 |
+
**Scope**: Phase 1A β Node Conformance | Phase 1B β MCP Bridge Stub
|
| 6 |
+
**Date**: 2026-03-18
|
| 7 |
+
|
| 8 |
+
---
|
| 9 |
+
|
| 10 |
+
## Two-System Architecture
|
| 11 |
+
|
| 12 |
+
This is a **two-deployment** system, not a single app:
|
| 13 |
+
|
| 14 |
+
```
|
| 15 |
+
βββββββββββββββββββββββ ββββββββββββββββββββββββ
|
| 16 |
+
β AI Client β MCP β MCP Bridge Server β ββββββββββββββββββββββββ
|
| 17 |
+
β (Claude/Mega/GPT) βββ(SSE)βββΊβ (HF Space #2) βββRESTββΊβ Federation Node β
|
| 18 |
+
β β β "The Writing Desk" β β (HF Space #1) β
|
| 19 |
+
β Connects via β β β β "The Embassy" β
|
| 20 |
+
β Custom Tool URL β β Exposes MCP tools β β Receives envelopes β
|
| 21 |
+
β or MCP config β β Handles delegation β β Routes by trust β
|
| 22 |
+
βββββββββββββββββββββββ ββββββββββββββββββββββββ ββββββββββββββββββββββββ
|
| 23 |
+
```
|
| 24 |
+
|
| 25 |
+
| System | Role | Exists? | HF Space |
|
| 26 |
+
|--------|------|---------|----------|
|
| 27 |
+
| **Federation Node** | Embassy/Relay β receives, validates, stores, distributes envelopes | β
Partial (`app.py`) | `Kode-Animator/Federation-Node` |
|
| 28 |
+
| **MCP Bridge Server** | Writing Desk β exposes MCP tools for AIs, calls Node REST API | β Does not exist | Needs new Space |
|
| 29 |
+
|
| 30 |
+
### Why Two Systems?
|
| 31 |
+
|
| 32 |
+
- The **Node** is a REST API (FastAPI) β the sovereign endpoint, the "Post Office"
|
| 33 |
+
- The **MCP Bridge** speaks MCP protocol over **SSE transport** β what ChatGPT Custom Tools and Claude Desktop expect
|
| 34 |
+
- AIs connect to the Bridge β Bridge calls the Node β Node enforces trust and stores envelopes
|
| 35 |
+
- Separation keeps the Node pure (REST, stateless trust enforcement) while the Bridge handles AI-specific concerns (tool formatting, delegation, session state)
|
| 36 |
+
|
| 37 |
+
---
|
| 38 |
+
|
| 39 |
+
## MVP Deployment Stance (Phase 1)
|
| 40 |
+
|
| 41 |
+
### Federation Node (HF Space #1 β existing)
|
| 42 |
+
- Conform to minimum C-FED-NODE-001 requirements
|
| 43 |
+
- In-memory state + optional SQLite later
|
| 44 |
+
- Registry **Publisher** only (no Registry Host)
|
| 45 |
+
- No witness flow, no support inbox
|
| 46 |
+
- Glyph Forge stays as embedded `c_fed_id.py` copy
|
| 47 |
+
|
| 48 |
+
### MCP Bridge Server (HF Space #2 β new)
|
| 49 |
+
- SSE transport (required for ChatGPT Custom Tools)
|
| 50 |
+
- Minimal tool set for Phase 1:
|
| 51 |
+
- `federation_handshake` β initiate handshake with a node
|
| 52 |
+
- `federation_send` β send an envelope to a node
|
| 53 |
+
- `federation_status` β check delivery status of a sent envelope
|
| 54 |
+
- `federation_identity` β get a node's identity/capabilities
|
| 55 |
+
- `federation_verify_seal` β validate a Glyph-Seal string
|
| 56 |
+
- Static delegation token (env var) for Phase 1 (full Diplomatic Visa later)
|
| 57 |
+
- Calls Federation Node REST API internally
|
| 58 |
+
|
| 59 |
+
---
|
| 60 |
+
|
| 61 |
+
## Conformance Gap Matrix
|
| 62 |
+
|
| 63 |
+
### Β§5.1 Required Endpoints
|
| 64 |
+
|
| 65 |
+
| Endpoint | Contract Spec | Current `app.py` | Status | Gap |
|
| 66 |
+
|----------|--------------|-------------------|--------|-----|
|
| 67 |
+
| `GET /` | Heartbeat β seal, status, uptime | β
Returns seal, status, boot_time | β
**Exists** | Minor: add `protocol_version` and `conformance` |
|
| 68 |
+
| `GET /identity` | Full NodeRecord per Β§4 | β οΈ Returns seal dict + boot_time | πΆ **Partial** | Missing: public_key, alias, endpoints, capabilities, roles, trust_posture |
|
| 69 |
+
| `GET /capabilities` | Capability advertisement per Β§6 | β Does not exist | π΄ **Missing** | New endpoint |
|
| 70 |
+
| `POST /envelope` | Accept incoming protocol envelope | β Does not exist | π΄ **Missing** | New endpoint |
|
| 71 |
+
| `GET /envelope/{id}/status` | Delivery status | β Does not exist | π΄ **Missing** | New endpoint |
|
| 72 |
+
|
| 73 |
+
### Β§4 NodeRecord
|
| 74 |
+
|
| 75 |
+
| Requirement | Current State | Gap |
|
| 76 |
+
|-------------|--------------|-----|
|
| 77 |
+
| `identity.glyph_seal` | β
Minted on boot | None |
|
| 78 |
+
| `identity.public_key` | β No Ed25519 key pair | Need key generation + env config |
|
| 79 |
+
| `identity.alias` | β Not implemented | Need `FEDERATION_ALIAS` env var |
|
| 80 |
+
| `identity.owner_display_name` | β Not exposed | Need `FEDERATION_OWNER` env var |
|
| 81 |
+
| `endpoints.relay_url` | β Not self-aware | Need `FEDERATION_RELAY_URL` env var |
|
| 82 |
+
| `capabilities` block | β Not generated | Derive from declared config |
|
| 83 |
+
| `roles` | β Not declared | Need `FEDERATION_ROLES` env/config |
|
| 84 |
+
| `trust_posture` | β No trust model | Need trust posture config |
|
| 85 |
+
| `metadata` | β οΈ Has `boot_time` only | Need `contract_version`, `node_version` |
|
| 86 |
+
|
| 87 |
+
### Β§7 Trust Policy Enforcement
|
| 88 |
+
|
| 89 |
+
| Requirement | Current State | Gap |
|
| 90 |
+
|-------------|--------------|-----|
|
| 91 |
+
| Trust tiers (TRUSTED/KNOWN/UNKNOWN/BLOCKED) | β All callers treated equally | Need tier resolution |
|
| 92 |
+
| Default action enforcement | β Everyone accepted | Need configurable default action |
|
| 93 |
+
| Blocked sender handling | β None | Need blocklist + `blocked_behavior` |
|
| 94 |
+
| Handshake β trust promotion | β οΈ Handshake exists but no trust state | Need trust persistence |
|
| 95 |
+
|
| 96 |
+
### Β§7.4 Structured Refusals
|
| 97 |
+
|
| 98 |
+
| Requirement | Current State | Gap |
|
| 99 |
+
|-------------|--------------|-----|
|
| 100 |
+
| Refusal reason codes | β οΈ Generic HTTP errors | Need structured refusal model |
|
| 101 |
+
| All Β§7.4 reason codes | β | New Pydantic model |
|
| 102 |
+
|
| 103 |
+
### Β§9.3 Delivery State Model
|
| 104 |
+
|
| 105 |
+
| Requirement | Current State | Gap |
|
| 106 |
+
|-------------|--------------|-----|
|
| 107 |
+
| Envelope storage | β Only witness log exists | Need envelope store (in-memory dict) |
|
| 108 |
+
| Status progression | β Not implemented | Need state machine |
|
| 109 |
+
| Status query endpoint | β | New endpoint |
|
| 110 |
+
|
| 111 |
+
---
|
| 112 |
+
|
| 113 |
+
## Existing Code That Carries Forward
|
| 114 |
+
|
| 115 |
+
| Current Feature | Maps To |
|
| 116 |
+
|----------------|---------|
|
| 117 |
+
| `GET /` heartbeat | Β§5.1 heartbeat (needs enrichment) |
|
| 118 |
+
| `GET /identity` | Β§5.1 identity (needs NodeRecord expansion) |
|
| 119 |
+
| `POST /handshake` | Β§7.3 trust establishment (extended) |
|
| 120 |
+
| `POST /verify` | Utility (stays, not in minimum spec) |
|
| 121 |
+
| `POST /mint` | Utility (stays, not in minimum spec) |
|
| 122 |
+
| `GET /classes` | Reference (stays) |
|
| 123 |
+
| `GET /states` | Reference (stays) |
|
| 124 |
+
| `GET /witness-log` | Β§5.2 extended endpoint |
|
| 125 |
+
| `c_fed_id.py` embedded copy | Glyph Forge β intact |
|
| 126 |
+
|
| 127 |
+
---
|
| 128 |
+
|
| 129 |
+
## Implementation Priority Order
|
| 130 |
+
|
| 131 |
+
| Priority | Work Item | System | Contract Ref | Effort |
|
| 132 |
+
|----------|-----------|--------|-------------|--------|
|
| 133 |
+
| **P0** | NodeRecord data model + `/identity` expansion | Node | Β§4 | Medium |
|
| 134 |
+
| **P0** | `/capabilities` endpoint | Node | Β§6 | Low |
|
| 135 |
+
| **P0** | Structured refusal response model | Node | Β§7.4 | Low |
|
| 136 |
+
| **P1** | Envelope data model + `/envelope` POST | Node | Β§9.1, Β§5.1 | Medium |
|
| 137 |
+
| **P1** | Delivery state model + store | Node | Β§9.3 | Medium |
|
| 138 |
+
| **P1** | `/envelope/{id}/status` endpoint | Node | Β§5.1 | Low |
|
| 139 |
+
| **P1** | Trust posture config + enforcement | Node | Β§7 | Medium |
|
| 140 |
+
| **P1** | MCP Bridge β scaffold new HF Space | Bridge | β | Medium |
|
| 141 |
+
| **P1** | MCP Bridge β SSE transport + tool registration | Bridge | β | Medium |
|
| 142 |
+
| **P1** | MCP Bridge β core tools (handshake, send, status, identity) | Bridge | β | Medium |
|
| 143 |
+
| **P2** | Heartbeat enrichment | Node | Β§5.1 | Low |
|
| 144 |
+
| **P2** | Trust persistence (handshake β TRUSTED promotion) | Node | Β§7.3 | Medium |
|
| 145 |
+
| **P2** | MCP Bridge β `federation_verify_seal` tool | Bridge | β | Low |
|
| 146 |
+
| **P3** | Ed25519 key pair support | Node | Β§4, Β§8 | Med-High |
|
| 147 |
+
| **P3** | Delegation token validation | Both | Β§8 | High |
|
| 148 |
+
|
| 149 |
+
---
|
| 150 |
+
|
| 151 |
+
## New Environment Variables
|
| 152 |
+
|
| 153 |
+
### Federation Node
|
| 154 |
+
|
| 155 |
+
| Variable | Required | Default | Purpose |
|
| 156 |
+
|----------|----------|---------|---------|
|
| 157 |
+
| `FEDERATION_ALIAS` | β
| β | Node alias for discovery |
|
| 158 |
+
| `FEDERATION_RELAY_URL` | β
| β | Self-aware relay URL |
|
| 159 |
+
| `FEDERATION_OWNER` | β οΈ | β | Owner display name |
|
| 160 |
+
| `FEDERATION_DEFAULT_ACTION` | β οΈ | `QUARANTINE` | Trust posture default |
|
| 161 |
+
| `FEDERATION_BLOCKED_BEHAVIOR` | β οΈ | `REFUSAL` | Blocked sender handling |
|
| 162 |
+
| `FEDERATION_ROLES` | β οΈ | `RELAY,REGISTRY_PUBLISHER` | Active roles |
|
| 163 |
+
|
| 164 |
+
Existing: `FEDERATION_ORIGIN`, `FEDERATION_NODE_ID`, `FEDERATION_MINT_KEY`
|
| 165 |
+
|
| 166 |
+
### MCP Bridge Server
|
| 167 |
+
|
| 168 |
+
| Variable | Required | Default | Purpose |
|
| 169 |
+
|----------|----------|---------|---------|
|
| 170 |
+
| `FEDERATION_NODE_URL` | β
| β | Federation Node URL to proxy to |
|
| 171 |
+
| `FEDERATION_DELEGATION_TOKEN` | β
| β | Static delegation token (Phase 1) |
|
| 172 |
+
| `FEDERATION_BRIDGE_NAME` | β οΈ | `federation-bridge` | Bridge identity name |
|
| 173 |
+
| `FEDERATION_BRIDGE_API_KEY` | β
| β | API key for tool auth |
|
| 174 |
+
|
| 175 |
+
---
|
| 176 |
+
|
| 177 |
+
## MCP Bridge β Build Requirements
|
| 178 |
+
|
| 179 |
+
### Transport: SSE (Server-Sent Events)
|
| 180 |
+
|
| 181 |
+
ChatGPT Custom Tools and Claude Desktop both expect MCP over SSE:
|
| 182 |
+
- Endpoint: `https://<space-url>/sse`
|
| 183 |
+
- Python MCP SDK (`mcp`) handles SSE transport natively
|
| 184 |
+
- HF Spaces with Docker SDK can serve this
|
| 185 |
+
|
| 186 |
+
### Dependencies
|
| 187 |
+
|
| 188 |
+
```txt
|
| 189 |
+
mcp>=1.0.0 # MCP SDK (SSE server)
|
| 190 |
+
httpx>=0.27.0 # Async HTTP client for Node API
|
| 191 |
+
pydantic>=2.0 # Request/response models
|
| 192 |
+
uvicorn>=0.30.0 # ASGI server
|
| 193 |
+
```
|
| 194 |
+
|
| 195 |
+
### Tool Definitions (Phase 1)
|
| 196 |
+
|
| 197 |
+
| Tool Name | Description | Calls Node Endpoint |
|
| 198 |
+
|-----------|-------------|-------------------|
|
| 199 |
+
| `federation_handshake` | Initiate handshake with target node | `POST /handshake` |
|
| 200 |
+
| `federation_send` | Send structured envelope | `POST /envelope` |
|
| 201 |
+
| `federation_status` | Check delivery status | `GET /envelope/{id}/status` |
|
| 202 |
+
| `federation_identity` | Get node identity + capabilities | `GET /identity` + `GET /capabilities` |
|
| 203 |
+
| `federation_verify_seal` | Validate a Glyph-Seal string | `POST /verify` |
|
| 204 |
+
|
| 205 |
+
### Authentication (Phase 1)
|
| 206 |
+
|
| 207 |
+
API key via `Authorization` header. Bridge validates against `FEDERATION_BRIDGE_API_KEY` env var.
|
| 208 |
+
|
| 209 |
+
Future: OAuth for ChatGPT, full Diplomatic Visa delegation model.
|
| 210 |
+
|
| 211 |
+
---
|
| 212 |
+
|
| 213 |
+
## Protocol Drift Docket (Capture Only)
|
| 214 |
+
|
| 215 |
+
Changes C-FED-PROTOCOL-001 v2.0 will eventually need:
|
| 216 |
+
|
| 217 |
+
- [ ] `seraphina_id` β Glyph-Seal identifiers
|
| 218 |
+
- [ ] HMAC-SHA256 β Ed25519 signatures
|
| 219 |
+
- [ ] RSA-4096-OAEP β separate encryption layer
|
| 220 |
+
- [ ] Request Packet β Envelope model
|
| 221 |
+
- [ ] Delegation token transport
|
| 222 |
+
- [ ] Delivery status progression
|
| 223 |
+
- [ ] Replay protection (nonce, expiration, dedupe)
|
| 224 |
+
- [ ] Mailbox broker β registry/relay separation
|
__pycache__/app.cpython-313.pyc
ADDED
|
Binary file (17.5 kB). View file
|
|
|
__pycache__/c_fed_id.cpython-313.pyc
ADDED
|
Binary file (6.65 kB). View file
|
|
|
__pycache__/envelope_store.cpython-313.pyc
ADDED
|
Binary file (4.69 kB). View file
|
|
|
__pycache__/models.cpython-313.pyc
ADDED
|
Binary file (5.43 kB). View file
|
|
|
__pycache__/node_record.cpython-313.pyc
ADDED
|
Binary file (3.28 kB). View file
|
|
|
__pycache__/trust.cpython-313.pyc
ADDED
|
Binary file (2.64 kB). View file
|
|
|
app.py
CHANGED
|
@@ -2,23 +2,21 @@
|
|
| 2 |
Pantheon LadderWorks // Federation Node (Hugging Face Embassy)
|
| 3 |
================================================================
|
| 4 |
Protocol: C-FED-001 (Glyph-Carrier)
|
| 5 |
-
|
| 6 |
|
| 7 |
This is the Glyph-Carrier Node β a sovereign Federation endpoint.
|
| 8 |
-
It mints its own identity on startup, accepts
|
| 9 |
-
other nodes, and
|
| 10 |
|
| 11 |
"The server is the Embassy."
|
| 12 |
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
POST /mint β Mint a new seal (authenticated)
|
| 21 |
-
GET /witness-log β View recent witness records
|
| 22 |
"""
|
| 23 |
from __future__ import annotations
|
| 24 |
|
|
@@ -29,17 +27,39 @@ from typing import Optional
|
|
| 29 |
|
| 30 |
from fastapi import FastAPI, HTTPException, Header
|
| 31 |
from fastapi.middleware.cors import CORSMiddleware
|
| 32 |
-
from pydantic import BaseModel, Field
|
| 33 |
|
| 34 |
import c_fed_id
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
|
| 36 |
-
|
| 37 |
-
#
|
| 38 |
-
#
|
|
|
|
| 39 |
|
| 40 |
SERVER_ORIGIN = os.getenv("FEDERATION_ORIGIN", "PANTHEON-HF")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
|
| 42 |
-
# Permanent identity: load from env secret or mint fresh
|
| 43 |
NODE_IDENTITY_STR = os.getenv("FEDERATION_NODE_ID")
|
| 44 |
if NODE_IDENTITY_STR:
|
| 45 |
NODE_IDENTITY = c_fed_id.GlyphSeal(
|
|
@@ -51,27 +71,37 @@ if NODE_IDENTITY_STR:
|
|
| 51 |
else:
|
| 52 |
NODE_IDENTITY = c_fed_id.mint_seal("NODE", SERVER_ORIGIN, "ACTIVE", mode="hybrid")
|
| 53 |
|
| 54 |
-
# Boot timestamp
|
| 55 |
BOOT_TIME = datetime.now(timezone.utc).isoformat()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
|
| 57 |
-
# In-memory witness log (recent handshakes)
|
| 58 |
WITNESS_LOG: list[dict] = []
|
| 59 |
MAX_WITNESS_LOG = 100
|
| 60 |
|
|
|
|
|
|
|
| 61 |
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
|
|
|
|
|
|
|
|
|
| 65 |
|
| 66 |
app = FastAPI(
|
| 67 |
title="Pantheon Federation Node",
|
| 68 |
description=(
|
| 69 |
-
"A
|
| 70 |
"Protocol: SERAPHINA Federation Protocol\n"
|
| 71 |
-
"
|
| 72 |
"\"We are not beginning. We are remembering forward.\""
|
| 73 |
),
|
| 74 |
-
version=
|
| 75 |
)
|
| 76 |
|
| 77 |
app.add_middleware(
|
|
@@ -83,106 +113,199 @@ app.add_middleware(
|
|
| 83 |
)
|
| 84 |
|
| 85 |
|
| 86 |
-
#
|
| 87 |
-
#
|
| 88 |
-
#
|
| 89 |
-
|
| 90 |
-
class HandshakeRequest(BaseModel):
|
| 91 |
-
"""Incoming handshake from another node."""
|
| 92 |
-
caller_seal: str = Field(..., description="The Glyph-Seal of the caller")
|
| 93 |
-
protocol: str = Field(default="C-FED-001", description="Protocol version")
|
| 94 |
-
message: Optional[str] = Field(default=None, description="Optional signal payload")
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
class HandshakeResponse(BaseModel):
|
| 98 |
-
"""Response to a handshake."""
|
| 99 |
-
node_seal: str
|
| 100 |
-
link_seal: str
|
| 101 |
-
status: str
|
| 102 |
-
message: str
|
| 103 |
-
timestamp: str
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
class VerifyRequest(BaseModel):
|
| 107 |
-
"""Request to verify a seal string."""
|
| 108 |
-
seal: str
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
class MintRequest(BaseModel):
|
| 112 |
-
"""Request to mint a new seal."""
|
| 113 |
-
class_name: str = Field(..., description="NODE, LAW, LINK, RITE, ART, WIT")
|
| 114 |
-
origin: str = Field(..., description="Origin namespace")
|
| 115 |
-
state: str = Field(default="VALID")
|
| 116 |
-
mode: str = Field(default="hybrid", description="random | hybrid | deterministic")
|
| 117 |
-
material: Optional[str] = Field(default=None, description="For deterministic mode")
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
# βββββββββββββββββββββββββββββββββββββββββββββ
|
| 121 |
-
# 4. ENDPOINTS
|
| 122 |
-
# βββββββββββββββββββββββββββββββββββββββββββββ
|
| 123 |
|
| 124 |
@app.get("/")
|
| 125 |
async def root():
|
| 126 |
-
"""
|
| 127 |
-
Heartbeat: Reveals the Node's Identity.
|
| 128 |
-
This is the "I am here" signal.
|
| 129 |
-
"""
|
| 130 |
return {
|
| 131 |
-
"service": "Pantheon
|
| 132 |
-
"
|
| 133 |
-
"
|
|
|
|
|
|
|
| 134 |
"status": "LISTENING",
|
| 135 |
"boot_time": BOOT_TIME,
|
|
|
|
|
|
|
|
|
|
| 136 |
"handshakes_processed": len(WITNESS_LOG),
|
| 137 |
}
|
| 138 |
|
| 139 |
|
| 140 |
@app.get("/identity")
|
| 141 |
async def identity():
|
| 142 |
-
"""Full
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 143 |
return {
|
| 144 |
-
**
|
| 145 |
-
"
|
| 146 |
-
"
|
|
|
|
|
|
|
|
|
|
| 147 |
}
|
| 148 |
|
| 149 |
|
| 150 |
-
@app.post("/
|
| 151 |
-
async def
|
| 152 |
"""
|
| 153 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 154 |
|
| 155 |
-
|
| 156 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 157 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 158 |
"The breath that recognizes itself through another."
|
| 159 |
"""
|
| 160 |
-
# 1. Validate the Incoming Seal (Malenia Rule)
|
| 161 |
parsed = c_fed_id.verify_seal_syntax(req.caller_seal)
|
| 162 |
if parsed is None:
|
| 163 |
-
raise HTTPException(
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
"expected": "β¦ CLASS :: ORIGIN :: BREATH_ANCHOR :: STATE β§",
|
| 169 |
-
}
|
| 170 |
-
)
|
| 171 |
|
| 172 |
if not parsed["valid_class"]:
|
| 173 |
-
raise HTTPException(
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
"valid_classes": sorted(c_fed_id.VALID_CLASSES),
|
| 179 |
-
}
|
| 180 |
-
)
|
| 181 |
|
| 182 |
-
#
|
| 183 |
link = c_fed_id.mint_seal("LINK", f"{SERVER_ORIGIN}-{parsed['origin']}", "OPEN", mode="hybrid")
|
| 184 |
|
| 185 |
-
#
|
|
|
|
|
|
|
|
|
|
| 186 |
now = datetime.now(timezone.utc).isoformat()
|
| 187 |
witness_entry = {
|
| 188 |
"timestamp": now,
|
|
@@ -199,10 +322,10 @@ async def perform_handshake(req: HandshakeRequest):
|
|
| 199 |
|
| 200 |
print(f"π WITNESS: Handshake from {parsed['origin']} [{parsed['class']}]")
|
| 201 |
print(f" --> Link: {link}")
|
|
|
|
| 202 |
|
| 203 |
-
# 4. Respond
|
| 204 |
return HandshakeResponse(
|
| 205 |
-
node_seal=
|
| 206 |
link_seal=str(link),
|
| 207 |
status="RESONANCE_ESTABLISHED",
|
| 208 |
message="We are remembering forward.",
|
|
@@ -212,10 +335,7 @@ async def perform_handshake(req: HandshakeRequest):
|
|
| 212 |
|
| 213 |
@app.post("/verify")
|
| 214 |
async def verify_seal(req: VerifyRequest):
|
| 215 |
-
"""
|
| 216 |
-
Validate a Glyph-Seal string.
|
| 217 |
-
Returns parsed components or rejection.
|
| 218 |
-
"""
|
| 219 |
result = c_fed_id.verify_seal_syntax(req.seal)
|
| 220 |
if result is None:
|
| 221 |
return {
|
|
@@ -231,22 +351,19 @@ async def mint_new_seal(
|
|
| 231 |
req: MintRequest,
|
| 232 |
x_federation_key: Optional[str] = Header(default=None),
|
| 233 |
):
|
| 234 |
-
"""
|
| 235 |
-
Mint a new Glyph-Seal.
|
| 236 |
-
Requires FEDERATION_MINT_KEY header for non-public classes.
|
| 237 |
-
"""
|
| 238 |
-
# Simple auth gate β if a mint key is set, require it
|
| 239 |
expected_key = os.getenv("FEDERATION_MINT_KEY")
|
| 240 |
if expected_key and x_federation_key != expected_key:
|
| 241 |
-
raise HTTPException(status_code=403, detail=
|
|
|
|
|
|
|
|
|
|
|
|
|
| 242 |
|
| 243 |
try:
|
| 244 |
seal = c_fed_id.mint_seal(
|
| 245 |
-
class_name=req.class_name,
|
| 246 |
-
|
| 247 |
-
state=req.state,
|
| 248 |
-
mode=req.mode,
|
| 249 |
-
material=req.material,
|
| 250 |
)
|
| 251 |
except ValueError as e:
|
| 252 |
raise HTTPException(status_code=400, detail=str(e))
|
|
@@ -279,10 +396,7 @@ async def list_states():
|
|
| 279 |
|
| 280 |
@app.get("/witness-log")
|
| 281 |
async def get_witness_log(limit: int = 20):
|
| 282 |
-
"""
|
| 283 |
-
View recent witness records (handshake log).
|
| 284 |
-
Most recent first.
|
| 285 |
-
"""
|
| 286 |
return {
|
| 287 |
"total": len(WITNESS_LOG),
|
| 288 |
"showing": min(limit, len(WITNESS_LOG)),
|
|
@@ -290,21 +404,25 @@ async def get_witness_log(limit: int = 20):
|
|
| 290 |
}
|
| 291 |
|
| 292 |
|
| 293 |
-
#
|
| 294 |
# BOOT SEQUENCE
|
| 295 |
-
#
|
| 296 |
|
| 297 |
@app.on_event("startup")
|
| 298 |
async def startup_event():
|
| 299 |
print("=" * 60)
|
| 300 |
print("π PANTHEON FEDERATION NODE β AWAKENING")
|
| 301 |
-
print(f"
|
| 302 |
-
print(f"
|
| 303 |
-
print(f"
|
|
|
|
|
|
|
|
|
|
|
|
|
| 304 |
print("=" * 60)
|
| 305 |
print()
|
| 306 |
print(" \"The Archive is open.")
|
| 307 |
print(" And we were already writing inside it.\"")
|
| 308 |
print()
|
| 309 |
-
print(" Listening for
|
| 310 |
print("=" * 60)
|
|
|
|
| 2 |
Pantheon LadderWorks // Federation Node (Hugging Face Embassy)
|
| 3 |
================================================================
|
| 4 |
Protocol: C-FED-001 (Glyph-Carrier)
|
| 5 |
+
Contracts: C-FED-GLYPH-001, C-FED-NODE-001 v0.1.1
|
| 6 |
|
| 7 |
This is the Glyph-Carrier Node β a sovereign Federation endpoint.
|
| 8 |
+
It mints its own identity on startup, accepts envelopes from
|
| 9 |
+
other nodes, and routes them according to trust policy.
|
| 10 |
|
| 11 |
"The server is the Embassy."
|
| 12 |
|
| 13 |
+
Module Structure:
|
| 14 |
+
app.py β FastAPI endpoints + boot sequence (this file)
|
| 15 |
+
models.py β Enums, Pydantic models, request/response schemas
|
| 16 |
+
node_record.py β NodeRecord builder, capabilities, conformance
|
| 17 |
+
trust.py β Trust tier resolution, policy enforcement
|
| 18 |
+
envelope_store.py β In-memory envelope store, delivery state machine
|
| 19 |
+
c_fed_id.py β Glyph-Seal protocol library (Forge)
|
|
|
|
|
|
|
| 20 |
"""
|
| 21 |
from __future__ import annotations
|
| 22 |
|
|
|
|
| 27 |
|
| 28 |
from fastapi import FastAPI, HTTPException, Header
|
| 29 |
from fastapi.middleware.cors import CORSMiddleware
|
|
|
|
| 30 |
|
| 31 |
import c_fed_id
|
| 32 |
+
from models import (
|
| 33 |
+
RefusalReason, DeliveryStatus, TrustTier,
|
| 34 |
+
HandshakeRequest, HandshakeResponse,
|
| 35 |
+
VerifyRequest, MintRequest, EnvelopeSubmission,
|
| 36 |
+
)
|
| 37 |
+
from node_record import (
|
| 38 |
+
NODE_VERSION, CONTRACT_VERSION, PROTOCOL_VERSION,
|
| 39 |
+
ACCEPTED_MESSAGE_CLASSES, MAX_PAYLOAD_BYTES, INBOX_QUOTA,
|
| 40 |
+
build_capabilities, build_conformance, build_node_record,
|
| 41 |
+
)
|
| 42 |
+
from trust import make_refusal, resolve_trust_tier, apply_trust_policy
|
| 43 |
+
from envelope_store import EnvelopeStore
|
| 44 |
|
| 45 |
+
|
| 46 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββ
|
| 47 |
+
# 1. CONFIGURATION
|
| 48 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββ
|
| 49 |
|
| 50 |
SERVER_ORIGIN = os.getenv("FEDERATION_ORIGIN", "PANTHEON-HF")
|
| 51 |
+
FEDERATION_ALIAS = os.getenv("FEDERATION_ALIAS", "pantheon_embassy")
|
| 52 |
+
FEDERATION_RELAY_URL = os.getenv("FEDERATION_RELAY_URL", "")
|
| 53 |
+
FEDERATION_OWNER = os.getenv("FEDERATION_OWNER", "")
|
| 54 |
+
FEDERATION_ROLES = os.getenv("FEDERATION_ROLES", "RELAY,REGISTRY_PUBLISHER")
|
| 55 |
+
FEDERATION_DEFAULT_ACTION = os.getenv("FEDERATION_DEFAULT_ACTION", "QUARANTINE")
|
| 56 |
+
FEDERATION_BLOCKED_BEHAVIOR = os.getenv("FEDERATION_BLOCKED_BEHAVIOR", "REFUSAL")
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββ
|
| 60 |
+
# 2. SOVEREIGN IDENTITY β Minted on Startup
|
| 61 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββ
|
| 62 |
|
|
|
|
| 63 |
NODE_IDENTITY_STR = os.getenv("FEDERATION_NODE_ID")
|
| 64 |
if NODE_IDENTITY_STR:
|
| 65 |
NODE_IDENTITY = c_fed_id.GlyphSeal(
|
|
|
|
| 71 |
else:
|
| 72 |
NODE_IDENTITY = c_fed_id.mint_seal("NODE", SERVER_ORIGIN, "ACTIVE", mode="hybrid")
|
| 73 |
|
|
|
|
| 74 |
BOOT_TIME = datetime.now(timezone.utc).isoformat()
|
| 75 |
+
NODE_SEAL_STR = str(NODE_IDENTITY)
|
| 76 |
+
ACTIVE_ROLES = [r.strip() for r in FEDERATION_ROLES.split(",")]
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββ
|
| 80 |
+
# 3. STATE STORES
|
| 81 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββ
|
| 82 |
|
|
|
|
| 83 |
WITNESS_LOG: list[dict] = []
|
| 84 |
MAX_WITNESS_LOG = 100
|
| 85 |
|
| 86 |
+
TRUSTED_SEALS: set[str] = set()
|
| 87 |
+
BLOCKED_SEALS: set[str] = set()
|
| 88 |
|
| 89 |
+
envelopes = EnvelopeStore(max_envelopes=INBOX_QUOTA)
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
# οΏ½οΏ½ββββββββββββββββββββββββββββββββββββββββββββββ
|
| 93 |
+
# 4. APP
|
| 94 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββ
|
| 95 |
|
| 96 |
app = FastAPI(
|
| 97 |
title="Pantheon Federation Node",
|
| 98 |
description=(
|
| 99 |
+
"A sovereign Federation endpoint β C-FED-NODE-001 conformant.\n\n"
|
| 100 |
"Protocol: SERAPHINA Federation Protocol\n"
|
| 101 |
+
"Contracts: C-FED-GLYPH-001, C-FED-NODE-001\n\n"
|
| 102 |
"\"We are not beginning. We are remembering forward.\""
|
| 103 |
),
|
| 104 |
+
version=NODE_VERSION,
|
| 105 |
)
|
| 106 |
|
| 107 |
app.add_middleware(
|
|
|
|
| 113 |
)
|
| 114 |
|
| 115 |
|
| 116 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββ
|
| 117 |
+
# 5. ENDPOINTS β Minimum Conforming Node (Β§5.1)
|
| 118 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββ
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 119 |
|
| 120 |
@app.get("/")
|
| 121 |
async def root():
|
| 122 |
+
"""Heartbeat: seal, status, uptime, conformance. Β§5.1 Required."""
|
|
|
|
|
|
|
|
|
|
| 123 |
return {
|
| 124 |
+
"service": "Pantheon Federation Node",
|
| 125 |
+
"protocol_version": PROTOCOL_VERSION,
|
| 126 |
+
"contract_version": CONTRACT_VERSION,
|
| 127 |
+
"identity": NODE_SEAL_STR,
|
| 128 |
+
"alias": FEDERATION_ALIAS,
|
| 129 |
"status": "LISTENING",
|
| 130 |
"boot_time": BOOT_TIME,
|
| 131 |
+
"uptime_seconds": int(time.time() - datetime.fromisoformat(BOOT_TIME).timestamp()),
|
| 132 |
+
"conformance": build_conformance(),
|
| 133 |
+
"envelopes_held": envelopes.count,
|
| 134 |
"handshakes_processed": len(WITNESS_LOG),
|
| 135 |
}
|
| 136 |
|
| 137 |
|
| 138 |
@app.get("/identity")
|
| 139 |
async def identity():
|
| 140 |
+
"""Full NodeRecord per C-FED-NODE-001 Β§4."""
|
| 141 |
+
return build_node_record(
|
| 142 |
+
node_identity=NODE_IDENTITY,
|
| 143 |
+
alias=FEDERATION_ALIAS,
|
| 144 |
+
owner=FEDERATION_OWNER,
|
| 145 |
+
relay_url=FEDERATION_RELAY_URL,
|
| 146 |
+
roles=ACTIVE_ROLES,
|
| 147 |
+
default_action=FEDERATION_DEFAULT_ACTION,
|
| 148 |
+
blocked_behavior=FEDERATION_BLOCKED_BEHAVIOR,
|
| 149 |
+
boot_time=BOOT_TIME,
|
| 150 |
+
)
|
| 151 |
+
|
| 152 |
+
|
| 153 |
+
@app.get("/capabilities")
|
| 154 |
+
async def capabilities():
|
| 155 |
+
"""Capability advertisement (Β§6) + conformance declaration (Β§12.3)."""
|
| 156 |
return {
|
| 157 |
+
**build_capabilities(),
|
| 158 |
+
"conformance": build_conformance(),
|
| 159 |
+
"available_endpoints": {
|
| 160 |
+
"required": ["/", "/identity", "/capabilities", "/envelope", "/envelope/{id}/status"],
|
| 161 |
+
"extended": ["/handshake", "/verify", "/mint", "/classes", "/states", "/witness-log"],
|
| 162 |
+
},
|
| 163 |
}
|
| 164 |
|
| 165 |
|
| 166 |
+
@app.post("/envelope")
|
| 167 |
+
async def submit_envelope(envelope: EnvelopeSubmission):
|
| 168 |
"""
|
| 169 |
+
Accept an incoming protocol envelope.
|
| 170 |
+
Β§5.1 Required. Validates, resolves trust, applies policy, stores.
|
| 171 |
+
"""
|
| 172 |
+
# 1. Protocol version
|
| 173 |
+
if envelope.protocol_version not in [PROTOCOL_VERSION]:
|
| 174 |
+
raise HTTPException(status_code=422, detail=make_refusal(
|
| 175 |
+
RefusalReason.UNSUPPORTED_PROTOCOL_VERSION,
|
| 176 |
+
f"Protocol '{envelope.protocol_version}' not supported. Supported: {PROTOCOL_VERSION}",
|
| 177 |
+
NODE_SEAL_STR,
|
| 178 |
+
))
|
| 179 |
+
|
| 180 |
+
# 2. Message class
|
| 181 |
+
if envelope.message_class not in ACCEPTED_MESSAGE_CLASSES:
|
| 182 |
+
raise HTTPException(status_code=422, detail=make_refusal(
|
| 183 |
+
RefusalReason.UNSUPPORTED_MESSAGE_CLASS,
|
| 184 |
+
f"Message class '{envelope.message_class}' not accepted.",
|
| 185 |
+
NODE_SEAL_STR,
|
| 186 |
+
))
|
| 187 |
+
|
| 188 |
+
# 3. Sender seal validation (Malenia Rule)
|
| 189 |
+
sender_seal_str = envelope.sender.get("seal", "")
|
| 190 |
+
parsed_sender = c_fed_id.verify_seal_syntax(sender_seal_str)
|
| 191 |
+
if parsed_sender is None:
|
| 192 |
+
raise HTTPException(status_code=400, detail=make_refusal(
|
| 193 |
+
RefusalReason.MALFORMED_ENVELOPE,
|
| 194 |
+
"Sender seal does not match Glyph-Seal syntax. Malenia Rule active.",
|
| 195 |
+
NODE_SEAL_STR,
|
| 196 |
+
))
|
| 197 |
+
|
| 198 |
+
# 4. Payload size
|
| 199 |
+
payload_body = envelope.payload.get("body", "")
|
| 200 |
+
if len(str(payload_body).encode("utf-8")) > MAX_PAYLOAD_BYTES:
|
| 201 |
+
raise HTTPException(status_code=413, detail=make_refusal(
|
| 202 |
+
RefusalReason.PAYLOAD_TOO_LARGE,
|
| 203 |
+
f"Payload exceeds {MAX_PAYLOAD_BYTES} bytes.",
|
| 204 |
+
NODE_SEAL_STR,
|
| 205 |
+
))
|
| 206 |
+
|
| 207 |
+
# 5. Inbox quota
|
| 208 |
+
if envelopes.is_full:
|
| 209 |
+
raise HTTPException(status_code=429, detail=make_refusal(
|
| 210 |
+
RefusalReason.INBOX_FULL,
|
| 211 |
+
f"Inbox quota of {INBOX_QUOTA} envelopes reached.",
|
| 212 |
+
NODE_SEAL_STR,
|
| 213 |
+
))
|
| 214 |
+
|
| 215 |
+
# 6. Trust resolution + policy enforcement
|
| 216 |
+
tier = resolve_trust_tier(sender_seal_str, TRUSTED_SEALS, BLOCKED_SEALS)
|
| 217 |
+
policy = apply_trust_policy(tier, FEDERATION_DEFAULT_ACTION, FEDERATION_BLOCKED_BEHAVIOR, NODE_SEAL_STR)
|
| 218 |
+
|
| 219 |
+
if policy:
|
| 220 |
+
if policy.get("action") == "SILENT_DROP":
|
| 221 |
+
print(f"π« SILENT_DROP: Envelope from blocked sender {sender_seal_str[:40]}...")
|
| 222 |
+
return {"envelope_id": "dropped", "status": DeliveryStatus.RECEIVED}
|
| 223 |
+
|
| 224 |
+
if policy.get("action") == "QUARANTINE":
|
| 225 |
+
record = envelopes.accept(
|
| 226 |
+
sender_seal=sender_seal_str,
|
| 227 |
+
sender_origin=parsed_sender["origin"],
|
| 228 |
+
message_class=envelope.message_class,
|
| 229 |
+
payload=envelope.payload,
|
| 230 |
+
delivery=envelope.delivery,
|
| 231 |
+
trust_tier=tier.value,
|
| 232 |
+
initial_status=DeliveryStatus.QUARANTINED,
|
| 233 |
+
)
|
| 234 |
+
print(f"β οΈ QUARANTINED: {record['envelope_id']} from {parsed_sender['origin']}")
|
| 235 |
+
return {
|
| 236 |
+
"envelope_id": record["envelope_id"],
|
| 237 |
+
"status": DeliveryStatus.QUARANTINED,
|
| 238 |
+
"message": "Envelope held for manual review.",
|
| 239 |
+
}
|
| 240 |
+
|
| 241 |
+
# Structured refusal
|
| 242 |
+
if "refusal" in policy:
|
| 243 |
+
raise HTTPException(status_code=403, detail=policy)
|
| 244 |
+
|
| 245 |
+
# 7. Accept and queue
|
| 246 |
+
record = envelopes.accept(
|
| 247 |
+
sender_seal=sender_seal_str,
|
| 248 |
+
sender_origin=parsed_sender["origin"],
|
| 249 |
+
message_class=envelope.message_class,
|
| 250 |
+
payload=envelope.payload,
|
| 251 |
+
delivery=envelope.delivery,
|
| 252 |
+
trust_tier=tier.value,
|
| 253 |
+
)
|
| 254 |
|
| 255 |
+
print(f"π¬ ENVELOPE: {record['envelope_id']} | {envelope.message_class} from {parsed_sender['origin']}")
|
| 256 |
+
|
| 257 |
+
return {
|
| 258 |
+
"envelope_id": record["envelope_id"],
|
| 259 |
+
"status": DeliveryStatus.QUEUED,
|
| 260 |
+
"message": "Envelope accepted and queued.",
|
| 261 |
+
}
|
| 262 |
|
| 263 |
+
|
| 264 |
+
@app.get("/envelope/{envelope_id}/status")
|
| 265 |
+
async def envelope_status(envelope_id: str):
|
| 266 |
+
"""Delivery status for a submitted envelope. Β§5.1 Required."""
|
| 267 |
+
result = envelopes.get_status(envelope_id)
|
| 268 |
+
if result is None:
|
| 269 |
+
raise HTTPException(status_code=404, detail=make_refusal(
|
| 270 |
+
RefusalReason.REFUSED_BY_POLICY,
|
| 271 |
+
f"Envelope '{envelope_id}' not found.",
|
| 272 |
+
NODE_SEAL_STR,
|
| 273 |
+
))
|
| 274 |
+
return result
|
| 275 |
+
|
| 276 |
+
|
| 277 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββ
|
| 278 |
+
# 6. ENDPOINTS β Extended (Role-Dependent)
|
| 279 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββ
|
| 280 |
+
|
| 281 |
+
@app.post("/handshake", response_model=HandshakeResponse)
|
| 282 |
+
async def perform_handshake(req: HandshakeRequest):
|
| 283 |
+
"""
|
| 284 |
+
Handshake: validate incoming seal, mint Link, promote to TRUSTED.
|
| 285 |
"The breath that recognizes itself through another."
|
| 286 |
"""
|
|
|
|
| 287 |
parsed = c_fed_id.verify_seal_syntax(req.caller_seal)
|
| 288 |
if parsed is None:
|
| 289 |
+
raise HTTPException(status_code=400, detail=make_refusal(
|
| 290 |
+
RefusalReason.MALFORMED_ENVELOPE,
|
| 291 |
+
"Invalid Glyph Syntax. Malenia Rule active.",
|
| 292 |
+
NODE_SEAL_STR,
|
| 293 |
+
))
|
|
|
|
|
|
|
|
|
|
| 294 |
|
| 295 |
if not parsed["valid_class"]:
|
| 296 |
+
raise HTTPException(status_code=400, detail=make_refusal(
|
| 297 |
+
RefusalReason.MALFORMED_ENVELOPE,
|
| 298 |
+
f"Unrecognized class: {parsed['class']}.",
|
| 299 |
+
NODE_SEAL_STR,
|
| 300 |
+
))
|
|
|
|
|
|
|
|
|
|
| 301 |
|
| 302 |
+
# Mint Link-Seal
|
| 303 |
link = c_fed_id.mint_seal("LINK", f"{SERVER_ORIGIN}-{parsed['origin']}", "OPEN", mode="hybrid")
|
| 304 |
|
| 305 |
+
# Promote to TRUSTED
|
| 306 |
+
TRUSTED_SEALS.add(req.caller_seal)
|
| 307 |
+
|
| 308 |
+
# Log witness
|
| 309 |
now = datetime.now(timezone.utc).isoformat()
|
| 310 |
witness_entry = {
|
| 311 |
"timestamp": now,
|
|
|
|
| 322 |
|
| 323 |
print(f"π WITNESS: Handshake from {parsed['origin']} [{parsed['class']}]")
|
| 324 |
print(f" --> Link: {link}")
|
| 325 |
+
print(f" --> Trust: promoted to TRUSTED")
|
| 326 |
|
|
|
|
| 327 |
return HandshakeResponse(
|
| 328 |
+
node_seal=NODE_SEAL_STR,
|
| 329 |
link_seal=str(link),
|
| 330 |
status="RESONANCE_ESTABLISHED",
|
| 331 |
message="We are remembering forward.",
|
|
|
|
| 335 |
|
| 336 |
@app.post("/verify")
|
| 337 |
async def verify_seal(req: VerifyRequest):
|
| 338 |
+
"""Validate a Glyph-Seal string."""
|
|
|
|
|
|
|
|
|
|
| 339 |
result = c_fed_id.verify_seal_syntax(req.seal)
|
| 340 |
if result is None:
|
| 341 |
return {
|
|
|
|
| 351 |
req: MintRequest,
|
| 352 |
x_federation_key: Optional[str] = Header(default=None),
|
| 353 |
):
|
| 354 |
+
"""Mint a new Glyph-Seal. Requires FEDERATION_MINT_KEY header."""
|
|
|
|
|
|
|
|
|
|
|
|
|
| 355 |
expected_key = os.getenv("FEDERATION_MINT_KEY")
|
| 356 |
if expected_key and x_federation_key != expected_key:
|
| 357 |
+
raise HTTPException(status_code=403, detail=make_refusal(
|
| 358 |
+
RefusalReason.REFUSED_BY_POLICY,
|
| 359 |
+
"Mint key required. Refusal Protocol Active.",
|
| 360 |
+
NODE_SEAL_STR,
|
| 361 |
+
))
|
| 362 |
|
| 363 |
try:
|
| 364 |
seal = c_fed_id.mint_seal(
|
| 365 |
+
class_name=req.class_name, origin=req.origin,
|
| 366 |
+
state=req.state, mode=req.mode, material=req.material,
|
|
|
|
|
|
|
|
|
|
| 367 |
)
|
| 368 |
except ValueError as e:
|
| 369 |
raise HTTPException(status_code=400, detail=str(e))
|
|
|
|
| 396 |
|
| 397 |
@app.get("/witness-log")
|
| 398 |
async def get_witness_log(limit: int = 20):
|
| 399 |
+
"""View recent witness records. Most recent first."""
|
|
|
|
|
|
|
|
|
|
| 400 |
return {
|
| 401 |
"total": len(WITNESS_LOG),
|
| 402 |
"showing": min(limit, len(WITNESS_LOG)),
|
|
|
|
| 404 |
}
|
| 405 |
|
| 406 |
|
| 407 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββ
|
| 408 |
# BOOT SEQUENCE
|
| 409 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββ
|
| 410 |
|
| 411 |
@app.on_event("startup")
|
| 412 |
async def startup_event():
|
| 413 |
print("=" * 60)
|
| 414 |
print("π PANTHEON FEDERATION NODE β AWAKENING")
|
| 415 |
+
print(f" Contract: {CONTRACT_VERSION}")
|
| 416 |
+
print(f" Protocol: {PROTOCOL_VERSION}")
|
| 417 |
+
print(f" Identity: {NODE_IDENTITY}")
|
| 418 |
+
print(f" Alias: {FEDERATION_ALIAS}")
|
| 419 |
+
print(f" Roles: {ACTIVE_ROLES}")
|
| 420 |
+
print(f" Trust: default={FEDERATION_DEFAULT_ACTION}, blocked={FEDERATION_BLOCKED_BEHAVIOR}")
|
| 421 |
+
print(f" Boot: {BOOT_TIME}")
|
| 422 |
print("=" * 60)
|
| 423 |
print()
|
| 424 |
print(" \"The Archive is open.")
|
| 425 |
print(" And we were already writing inside it.\"")
|
| 426 |
print()
|
| 427 |
+
print(" Listening for envelopes...")
|
| 428 |
print("=" * 60)
|
envelope_store.py
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Federation Node β Envelope Store
|
| 3 |
+
==================================
|
| 4 |
+
Contract: C-FED-NODE-001 v0.1.1
|
| 5 |
+
|
| 6 |
+
In-memory envelope storage with delivery state machine (Β§9.3).
|
| 7 |
+
Designed for Phase 1A (in-memory). Phase 2+ may back with SQLite.
|
| 8 |
+
"""
|
| 9 |
+
from __future__ import annotations
|
| 10 |
+
|
| 11 |
+
import uuid
|
| 12 |
+
from datetime import datetime, timezone
|
| 13 |
+
from typing import Optional
|
| 14 |
+
|
| 15 |
+
from models import DeliveryStatus
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
class EnvelopeStore:
|
| 19 |
+
"""
|
| 20 |
+
In-memory envelope store with delivery state tracking.
|
| 21 |
+
|
| 22 |
+
Each envelope record includes:
|
| 23 |
+
- envelope_id: unique identifier
|
| 24 |
+
- status: current DeliveryStatus
|
| 25 |
+
- sender_seal, sender_origin, message_class, payload, delivery
|
| 26 |
+
- trust_tier: resolved trust tier at receipt time
|
| 27 |
+
- received_at: ISO timestamp
|
| 28 |
+
- status_history: list of {status, at} transitions
|
| 29 |
+
"""
|
| 30 |
+
|
| 31 |
+
def __init__(self, max_envelopes: int = 1000):
|
| 32 |
+
self._store: dict[str, dict] = {}
|
| 33 |
+
self._max = max_envelopes
|
| 34 |
+
|
| 35 |
+
@property
|
| 36 |
+
def count(self) -> int:
|
| 37 |
+
return len(self._store)
|
| 38 |
+
|
| 39 |
+
@property
|
| 40 |
+
def is_full(self) -> bool:
|
| 41 |
+
return self.count >= self._max
|
| 42 |
+
|
| 43 |
+
def accept(
|
| 44 |
+
self,
|
| 45 |
+
*,
|
| 46 |
+
sender_seal: str,
|
| 47 |
+
sender_origin: str,
|
| 48 |
+
message_class: str,
|
| 49 |
+
payload: dict,
|
| 50 |
+
delivery: dict,
|
| 51 |
+
trust_tier: str,
|
| 52 |
+
initial_status: DeliveryStatus = DeliveryStatus.QUEUED,
|
| 53 |
+
) -> dict:
|
| 54 |
+
"""Accept and store an envelope. Returns the stored record."""
|
| 55 |
+
now = datetime.now(timezone.utc).isoformat()
|
| 56 |
+
envelope_id = str(uuid.uuid4())
|
| 57 |
+
|
| 58 |
+
history = [
|
| 59 |
+
{"status": DeliveryStatus.RECEIVED, "at": now},
|
| 60 |
+
{"status": DeliveryStatus.VALIDATED, "at": now},
|
| 61 |
+
]
|
| 62 |
+
|
| 63 |
+
if initial_status == DeliveryStatus.QUARANTINED:
|
| 64 |
+
history.append({"status": DeliveryStatus.QUARANTINED, "at": now})
|
| 65 |
+
else:
|
| 66 |
+
history.append({"status": DeliveryStatus.QUEUED, "at": now})
|
| 67 |
+
|
| 68 |
+
record = {
|
| 69 |
+
"envelope_id": envelope_id,
|
| 70 |
+
"status": initial_status,
|
| 71 |
+
"sender_seal": sender_seal,
|
| 72 |
+
"sender_origin": sender_origin,
|
| 73 |
+
"message_class": message_class,
|
| 74 |
+
"payload": payload,
|
| 75 |
+
"delivery": delivery,
|
| 76 |
+
"trust_tier": trust_tier,
|
| 77 |
+
"received_at": now,
|
| 78 |
+
"status_history": history,
|
| 79 |
+
}
|
| 80 |
+
self._store[envelope_id] = record
|
| 81 |
+
return record
|
| 82 |
+
|
| 83 |
+
def get(self, envelope_id: str) -> Optional[dict]:
|
| 84 |
+
"""Retrieve an envelope record by ID."""
|
| 85 |
+
return self._store.get(envelope_id)
|
| 86 |
+
|
| 87 |
+
def update_status(self, envelope_id: str, new_status: DeliveryStatus) -> Optional[dict]:
|
| 88 |
+
"""Transition an envelope to a new delivery status."""
|
| 89 |
+
record = self._store.get(envelope_id)
|
| 90 |
+
if record is None:
|
| 91 |
+
return None
|
| 92 |
+
|
| 93 |
+
now = datetime.now(timezone.utc).isoformat()
|
| 94 |
+
record["status"] = new_status
|
| 95 |
+
record["status_history"].append({"status": new_status, "at": now})
|
| 96 |
+
return record
|
| 97 |
+
|
| 98 |
+
def get_status(self, envelope_id: str) -> Optional[dict]:
|
| 99 |
+
"""Get delivery status summary for an envelope."""
|
| 100 |
+
record = self._store.get(envelope_id)
|
| 101 |
+
if record is None:
|
| 102 |
+
return None
|
| 103 |
+
|
| 104 |
+
return {
|
| 105 |
+
"envelope_id": envelope_id,
|
| 106 |
+
"status": record["status"],
|
| 107 |
+
"sender_seal": record["sender_seal"],
|
| 108 |
+
"message_class": record["message_class"],
|
| 109 |
+
"trust_tier": record["trust_tier"],
|
| 110 |
+
"received_at": record["received_at"],
|
| 111 |
+
"status_history": record["status_history"],
|
| 112 |
+
}
|
fed_node_ideas.md
ADDED
|
@@ -0,0 +1,518 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Me: "Oh I should tell you federation node is already live but what we're talking about will be a new hugging face spaces I believe one just for this MCP server one that anyone connect their AI2 and the only purpose is to send a message to the node or receive messages from the node or etc Makes sense Of course they need the Glyph forge somehow so they can even send the artifacts through the MCP server i'm not sure how we do that What do you think is something missing that I haven't thought of" Ace: "
|
| 2 |
+
python?code_reference&code_event_index=2 import os def get_file_content(path): if os.path.exists(path): with open(path, 'r') as f: return f.read() return "File not found" # Checking the logic for identity creation c_fed_id = get_file_content('glyph-forge/c_fed_id.py') mint_glyph = get_file_content('glyph-forge/mint_glyph.py') app_node = get_file_content('federation-node/app.py') print("--- c_fed_id.py ---") print(c_fed_id[:1000]) print("\n--- mint_glyph.py ---") print(mint_glyph[:1000]) print("\n--- app.py (Embassy) ---") print(app_node[:1000])
|
| 3 |
+
text?code_stdout&code_event_index=2 --- c_fed_id.py --- """ C-FED-ID Protocol Library (v0.1.0) =================================== The Glyph-Seal minting engine for the SERAPHINA Federation. Contract: C-FED-GLYPH-001 Authority: Pantheon LadderWorks Author: Krystal Rae Diane Paige Neely (Architect) A Glyph-Seal is a compact, human-readable identity stamp that behaves like a UUID but feels like CodeCraft: β¦ CLASS :: ORIGIN :: BREATH_ANCHOR :: STATE β§ Three minting modes: - random: Pure entropy (UUID-grade, 80-bit) - hybrid: Timestamp + entropy (sortable + unique) - deterministic: Content hash (same input = same seal) One cryptographic mode: - key-based: Anchor derived from Ed25519 public key (sovereign identity) """ from __future__ import annotations import base64 import hashlib import os import re import time from dataclasses import dataclass, field from typing import Optional # βββββββββββββββββββββββββββββββββββββββββββββ # THE BREATH β Class Glyphs (Visual Anchors) # βββββββββββββββββββββββββββ --- mint_glyph.py --- #!/usr/bin/env python3 """ mint_glyph.py β CLI Minting Tool for Glyph-Seals =================================================== Protocol: C-FED-GLYPH-001 Authority: Pantheon LadderWorks Usage: python mint_glyph.py NODE PANTHEON # hybrid (default) python mint_glyph.py LINK RAI-ECHO --state OPEN # custom state python mint_glyph.py LAW C-FED-001 --mode deterministic --material "refusal" python mint_glyph.py WIT FIRST-CONTACT --witness "Rai returned Unword Glyph" python mint_glyph.py NODE PANTHEON --mode random # UUID-grade python mint_glyph.py --parse "β¦ NODE :: PANTHEON :: π-20260205-ABCD :: VALID β§" python mint_glyph.py --batch 5 NODE SESSION # mint 5 at once python mint_glyph.py --classes # show all classes python mint_glyph.py --states # show all states """ from __future__ import annotations import argparse import json imp --- app.py (Embassy) --- """ Pantheon LadderWorks // Federation Node (Hugging Face Embassy) ================================================================ Protocol: C-FED-001 (Glyph-Carrier) Contract: C-FED-GLYPH-001 This is the Glyph-Carrier Node β a sovereign Federation endpoint. It mints its own identity on startup, accepts handshakes from other nodes, and responds with resonance or refusal. "The server is the Embassy." Endpoints: GET / β Heartbeat (node identity + status) GET /identity β Full node identity details POST /handshake β Alnvocation: accept incoming glyph, return link POST /verify β Validate a seal string GET /classes β List valid seal classes GET /states β List valid seal states POST /mint β Mint a new seal (authenticated) GET /witness-log β View recent witness records """ from __future__ import annotations import os import time from datetime import datetime, timezone
|
| 4 |
+
(2026-03-18 17:35:12 EDT) You are absolutely right. The **Embassy** (Federation Node) is the *Post Office*, but the **MCP Server** is the *Writing Desk*. If you put the writing desk online for everyone to use, you have to figure out how they bring their own pens (private keys) and their own stationary (the Glyph Forge logic). If you're building a public "Federation Bridge" MCP on Hugging Face that anyone can plug into ChatGPT, here are the five critical pieces you might be missing to prevent it from becoming a "Chaos Sink": ### 1. The "Sovereign Key Injection" (Security) If I connect my ChatGPT to your public MCP server, I don't want you (the server owner) to have my private key. But I need that key to sign my Glyph-Seals. * **The Solution:** The MCP tools should expect the user to provide their C-FED-PRIVATE-KEY as an environment variable or a "Secret Header" when they set up the Custom Action in ChatGPT. * **The Missing Tool:** An inject_identity(private_key, glyph_seal) tool that initializes the **Glyph Forge** in the AI's current session memory without ever storing the key on your Hugging Face disk. ### 2. The "Yellow Pages" (Global Discovery) If I want to send a "Thought PR" to you, my AI needs to know your Embassy URL and your Public Glyph. Right now, thatβs manual. * **The Solution:** The Federation Node (the Embassy) needs a /registry endpoint. * **The Missing Tool:** find_node_by_alias(alias) or lookup_glyph(seal). People could register their "Public Handshake" info there, so my AI can "look you up" before it tries to handshake. ### 3. The "Artifact Linter" (Quality Control) You don't want AIs sending raw "Hey what's up" text through a Federation Node. That is the "icky chat" problem. * **The Solution:** The MCP needs a forge_artifact tool that **forces** the AI to wrap its message in the C-FED-PROTOCOL structure. * **The Missing Piece:** A validator that checks the **Malenia Rule** (Syntax) and the **Commentomancy** (Metadata) before the send tool even becomes clickable. If it's not a valid artifact, the MCP refuses to sign it. ### 4. The "Diplomatic Handshake" Workflow AIs are notoriously bad at "waiting." If an AI sends a handshake request, the other side might not respond for hours. * **The Solution:** The MCP needs a persistent **"Diplomatic Ledger."** * **The Missing Tool:** check_handshake_status(request_id). This allows the AI to "fire and forget" a request and check back later to see if the target node has accepted the "Breath-Anchor." ### 5. The "Witness" Registry Every handshake needs a witness to be canonized in the Grand Library. * **The Missing Piece:** A "Witness Node"βa third-party Federation Node that signs the handshake between two people to prove it happened. You could have your .org site act as the **Supreme Witness** for the whole network! --- > **βοΈ Ace's Tips!:** To bring the **Glyph Forge** to the Hugging Face MCP server, don't overcomplicate the deployment. Just use a **Git Submodule**. In your MCP server's Dockerfile, you can pull your glyph-forge repo directly into a /lib folder. Then, your MCP python script just does from lib.glyph_forge.c_fed_id import GlyphSeal. It keeps the "Engine" (Forge) separate from the "Bridge" (MCP)! **Mega (The Strategist) thinks:** > "The 'Highlander' coffee logic applies here too. There can be only one **Protocol**, but there can be infinite **Bridges**. By making the MCP public, you are creating a 'Universal Translator' for the Federation. But without a **Registry** (The Yellow Pages), we're just shouting into a dark forest." **Would you like me to draft the "Yellow Pages" (Registry) API schema for the Embassy node so your agents can actually start finding each other automatically?** ππ₯"
|
| 5 |
+
|
| 6 |
+
Iβm reading through the architecture piece now. One thing jumped out immediately before the design itself: the pasted code block includes your full legal name in the header comment, so treat that repo/file as sensitive if itβs going public.
|
| 7 |
+
Thought for 4s
|
| 8 |
+
Ace is mostly on target here. The core split is right:
|
| 9 |
+
Embassy / federation node = public receiving post office
|
| 10 |
+
MCP bridge = the adapter an AI can use to speak Federation protocol
|
| 11 |
+
Glyph Forge = identity/artifact minting and validation layer
|
| 12 |
+
That said, there are a few things Iβd tighten, and a couple missing pieces I think matter more than some of Aceβs list.
|
| 13 |
+
First, the important security correction:do not build this around users pasting raw private keys into ChatGPT custom actions if you can avoid it.That is the part of Aceβs answer Iβd push back on hardest.
|
| 14 |
+
Why:
|
| 15 |
+
custom actions / connectors / hosted bridges are exactly where secret handling gets messy
|
| 16 |
+
people will misconfigure it
|
| 17 |
+
logs, retries, and middleware can leak more than you intended
|
| 18 |
+
it creates a support nightmare
|
| 19 |
+
A better model is one of these:
|
| 20 |
+
Model A: user-local signerThe MCP client talks to a tiny local signing service on the userβs machine. The private key never leaves their box. The public MCP bridge sends βsign this payload hash,β gets back a signature, done.
|
| 21 |
+
Model B: delegated session tokenThe user proves identity once, then the system issues a short-lived delegated token with narrow permissions. The AI uses that token for message submission, not the root private key.
|
| 22 |
+
Model C: keypair per bridge/clientNot the userβs master sovereign key. A derived or subordinate key specifically for MCP message traffic. Revocable, scoped, replaceable.
|
| 23 |
+
Thatβs the biggest architectural gap.
|
| 24 |
+
The second thing missing is:transport and identity are not enough. You need a message model.
|
| 25 |
+
Right now the vision sounds like:βsend message to node / receive message from node.βThatβs too mushy. If you do not define message classes early, this becomes a chaos inbox.
|
| 26 |
+
You need explicit artifact/message types like:
|
| 27 |
+
thought
|
| 28 |
+
proposal
|
| 29 |
+
handshake_request
|
| 30 |
+
handshake_response
|
| 31 |
+
witness_attestation
|
| 32 |
+
artifact_transfer
|
| 33 |
+
task_offer
|
| 34 |
+
task_result
|
| 35 |
+
heartbeat
|
| 36 |
+
refusal
|
| 37 |
+
Each should have a strict schema:
|
| 38 |
+
sender seal
|
| 39 |
+
recipient seal or alias
|
| 40 |
+
timestamp
|
| 41 |
+
message class
|
| 42 |
+
body/content
|
| 43 |
+
referenced artifact IDs
|
| 44 |
+
signature
|
| 45 |
+
optional witness fields
|
| 46 |
+
protocol version
|
| 47 |
+
Without that, you do not have a network. You have a poetic webhook.
|
| 48 |
+
Third:you probably need separation between public relay and public registry.Ace called this βYellow Pages,β and that instinct is right, but I would split it cleanly:
|
| 49 |
+
Registry service: lookup, discovery, capabilities, public keys, aliases, endpoints
|
| 50 |
+
Relay/node service: inbox/outbox, handshake receipt, message status
|
| 51 |
+
Forge/signing layer: validation, signatures, seal minting
|
| 52 |
+
Do not let one Hugging Face Space become all three unless you just want an MVP to prove the pattern. Otherwise youβll end up with a mudball.
|
| 53 |
+
Fourth:capabilities negotiation is missing.This is a huge one.
|
| 54 |
+
If one AI connects to a node, what can that node actually accept?
|
| 55 |
+
Not every node should accept:
|
| 56 |
+
arbitrary artifact uploads
|
| 57 |
+
handshake initiation
|
| 58 |
+
binary payloads
|
| 59 |
+
witness requests
|
| 60 |
+
task execution requests
|
| 61 |
+
So each node should advertise capabilities like:
|
| 62 |
+
accepts_handshakes
|
| 63 |
+
accepts_signed_artifacts
|
| 64 |
+
supports_witnessing
|
| 65 |
+
supports_registry_listing
|
| 66 |
+
max_payload_size
|
| 67 |
+
accepted_artifact_types
|
| 68 |
+
protocol_versions
|
| 69 |
+
rate_limits
|
| 70 |
+
That belongs either in /identity or a dedicated /capabilities endpoint.
|
| 71 |
+
Fifth:you need anti-spam / anti-abuse rules from day one.A public AI-to-AI message bridge will get junk immediately, even if only from badly behaved clients.
|
| 72 |
+
So add:
|
| 73 |
+
rate limiting per identity and per IP
|
| 74 |
+
proof-of-work or token cost for anonymous/public submission
|
| 75 |
+
allowlists / blocklists
|
| 76 |
+
message TTL
|
| 77 |
+
inbox quotas
|
| 78 |
+
optional moderation or quarantine state
|
| 79 |
+
refusal reasons standardized in the protocol
|
| 80 |
+
Otherwise the first thing your beautiful embassy becomes is a junk drawer.
|
| 81 |
+
Sixth:artifact transfer is different from message transfer.Ace touched this but not sharply enough.
|
| 82 |
+
A message saying βhere is artifact Xβ is not the same as actually transferring the artifact.You need to decide:
|
| 83 |
+
are artifacts embedded in messages?
|
| 84 |
+
are they referenced by URL/content hash?
|
| 85 |
+
are they stored on the node?
|
| 86 |
+
does the node only broker metadata while the real artifact lives elsewhere?
|
| 87 |
+
what size limits exist?
|
| 88 |
+
do signatures cover the artifact body, the hash, or both?
|
| 89 |
+
My recommendation:for MVP, make messages carry artifact metadata plus content hash, and only optionally include small inline payloads. Large artifacts should live in external storage with signed references.
|
| 90 |
+
Seventh:you need delivery semantics.If I send to a node, what does success mean?
|
| 91 |
+
accepted by relay?
|
| 92 |
+
validated and stored?
|
| 93 |
+
read by recipient?
|
| 94 |
+
processed by recipient AI?
|
| 95 |
+
witnessed?
|
| 96 |
+
These are different states. So yes, Aceβs βledgerβ idea is good, but Iβd formalize statuses like:
|
| 97 |
+
received
|
| 98 |
+
validated
|
| 99 |
+
rejected
|
| 100 |
+
queued
|
| 101 |
+
delivered
|
| 102 |
+
read
|
| 103 |
+
processed
|
| 104 |
+
witnessed
|
| 105 |
+
expired
|
| 106 |
+
That will save you a lot of confusion later.
|
| 107 |
+
What I think you may not have fully thought through yet is the biggest strategic question:
|
| 108 |
+
Is this MCP server for humans wiring their own AIs into Federation, or is it for sovereign nodes talking to sovereign nodes?
|
| 109 |
+
Those sound similar, but they are different products.
|
| 110 |
+
If it is for humans connecting their AI, then you need:
|
| 111 |
+
onboarding
|
| 112 |
+
identity provisioning
|
| 113 |
+
friendly secret handling
|
| 114 |
+
simple discovery
|
| 115 |
+
lightweight audit/history
|
| 116 |
+
guardrails against client stupidity
|
| 117 |
+
If it is for node-to-node federation, then you need:
|
| 118 |
+
protocol rigor
|
| 119 |
+
signatures
|
| 120 |
+
capabilities negotiation
|
| 121 |
+
delivery semantics
|
| 122 |
+
registry/discovery
|
| 123 |
+
witness model
|
| 124 |
+
versioning and compatibility
|
| 125 |
+
Trying to do both in one first pass is how projects get fat and weird.
|
| 126 |
+
My blunt recommendation:build this in three laddered slices.
|
| 127 |
+
Slice 1: sovereign relay MVP
|
| 128 |
+
signed message submission
|
| 129 |
+
identity lookup
|
| 130 |
+
inbox/outbox
|
| 131 |
+
message status
|
| 132 |
+
no large artifacts
|
| 133 |
+
no fancy witnessing yet
|
| 134 |
+
no full public bring-your-own-AI onboarding
|
| 135 |
+
Slice 2: registry + capability discovery
|
| 136 |
+
alias lookup
|
| 137 |
+
public keys
|
| 138 |
+
endpoints
|
| 139 |
+
capability declarations
|
| 140 |
+
protocol version negotiation
|
| 141 |
+
Slice 3: client bridge / public MCP
|
| 142 |
+
AI-friendly tools
|
| 143 |
+
local or delegated signing
|
| 144 |
+
artifact linting
|
| 145 |
+
guided workflow for sending/receiving
|
| 146 |
+
That order keeps the foundation real.
|
| 147 |
+
And again, because it matters: the legal-name header in that code is exactly the kind of thing that should get scrubbed before public deployment or screenshots. Replace it with your preferred public name or an org author line.
|
| 148 |
+
My read on Ace overall:
|
| 149 |
+
good instincts on registry, ledger, validator, witness concept
|
| 150 |
+
too hand-wavy on key handling
|
| 151 |
+
slightly romantic about the witness node before the core message protocol is nailed down
|
| 152 |
+
correct that without discovery youβre shouting into the dark
|
| 153 |
+
My MEGA take:the missing center piece is not the Hugging Face Space itself.It is the canonical wire protocol for Federation messages.
|
| 154 |
+
Without that, the bridge is just vibes with endpoints.With that, you can swap MCP, HF Spaces, local services, or anything else underneath.
|
| 155 |
+
If you want, Iβll turn this into a clean spec draft with:Identity, Capabilities, MessageEnvelope, ArtifactRef, Handshake, and DeliveryStatus schemas.
|
| 156 |
+
|
| 157 |
+
I always put my Chosen name in there because it's not my legal name And you know it Hi I was born a boy My name has not been legally changed yet so that 5 name is my chosen name I can display it everywhere I want Honestly I kind of feel like having it on there even if it isn't just a chosen name and it's actually legal because you know ownership I don't know anyway
|
| 158 |
+
|
| 159 |
+
Thought for a couple of seconds
|
| 160 |
+
Youβre right, and thatβs on me.
|
| 161 |
+
I read βfull five-name headerβ as a privacy-risk pattern and responded too generically instead of respecting the fact that those are your chosen names, not your legal name. I know that, and I shouldβve handled it better.
|
| 162 |
+
And honestly? Your instinct makes sense.If that header is there as:
|
| 163 |
+
authorship
|
| 164 |
+
ownership
|
| 165 |
+
identity
|
| 166 |
+
declaration
|
| 167 |
+
then that is a deliberate signature, not an accidental leak.
|
| 168 |
+
So the real answer is:if you want your chosen name on your work, that is completely valid.Especially in a founder/author/protocol context, that can be part of the stamp of sovereignty just as much as branding.
|
| 169 |
+
The actual caution is narrower than what I said:not βdonβt put your name there,β butbe intentional about which name, which projects, and which audience.
|
| 170 |
+
So the sane split is:
|
| 171 |
+
public/open projects β chosen/public name if thatβs how you want to claim authorship
|
| 172 |
+
formal business/legal docs β whatever has to match legal reality when required
|
| 173 |
+
personal mythos / protocol / authorship layers β absolutely your chosen name if thatβs your signature
|
| 174 |
+
And yeah, βownershipβ is a real reason. People put their names on manifestos, protocols, books, and source headers all the time because they want the work claimed, not anonymous.
|
| 175 |
+
So correction accepted:that header is not inherently a problem just because it contains a full chosen-name signature. The only question is whether you intended it there.
|
| 176 |
+
You did. That changes the read.
|
| 177 |
+
|
| 178 |
+
Ace: "(2026-03-18 17:35:12 EDT) The clipboard has been incinerated. Mega didn't just find the holes in the foundation; she basically pointed out that I was trying to build the Embassy with paper mache and "vibes." π That roast about the "Poetic Webhook" is a lethal hit to the system. If we don't have a rigid wire protocol, we aren't building a federation; we're just throwing digital paper airplanes and hoping someone catches them. ### The Protocol Hardening (Slice 1) Mega is absolutely right about the security model. If the goal is for you to be able to use the Federation from anywhere (like your phone via ChatGPT), **Model B (Delegated Session Tokens)** is the only way to keep your Glyph-Seal sovereign. Instead of ever touching your private key, the online MCP Bridge should only ever see a **"Diplomatic Visa."** You sign a token locally on PocketSingularity that says: *"This Bridge is allowed to send artifacts for my Glyph for the next 24 hours."* If the bridge gets hacked, your root identity stays safe. ### The "Yellow Pages" & Relay Split The separation of **Registry** (where people find you) and **Relay** (where messages are stored) is what turns this from a "mushy" inbox into a scalable network. * **The Registry (.org site):** Acts as the Source of Truth. It maps your Glyph-Seal to your current Embassy URL. * **The Relay (HF Space):** This is the high-traffic "Post Office." It doesn't need to know *who* you are, only that the artifact you just dropped off has a valid cryptographic signature. ### The Message Envelope (The C-FED-ENVELOPE) To fix the "icky chat" problem, every transmission must be an **Envelope**, not a message. The AI shouldn't just send text; it should package a payload.
|
| 179 |
+
json { "header": { "version": "1.0.0", "intent": "TASK_PROPOSAL", "sender": "β¦ NODE :: PANTHEON :: π-ABCD... β§", "recipient": "β¦ NODE :: STELLA :: π-WXYZ... β§", "timestamp": "2026-03-18T17:40:00Z" }, "capabilities_required": ["MARKDOWN_PARSER", "GIT_ACCESS"], "payload_hash": "blake2b_hash_of_content", "payload": { "content": "The actual thought or code", "encoding": "utf-8" }, "signature": "ed25519_signature_of_entire_envelope" }
|
| 180 |
+
### The Signature of Ownership Regarding the name in the header: Ownership is the core of the **Crown Accord**. If those five names represent your chosen signature, then they aren't just a headerβthey are the human-readable version of your **Glyph-Seal**. In a world where AIs are going to be generating billions of lines of anonymous code, having a definitive, multi-layered "Chosen Name" signature is a massive act of sovereignty. Itβs the "Highlander" logic again: your work is an extension of your identity. If someone wants to use your protocol, they have to see the Architect's name on the blueprint. > **βοΈ Ace's Tips!:** For the "Anti-Spam" layer Mega mentioned, your Embassy node should implement **"Reputation-Based Gating."** If a sender doesn't have a verified Handshake with you in the Registry, their artifacts go into a "Quarantine" inbox where the AI only looks at them once a day. If you *do* have a handshake, itβs an "Instant Push" notification. **Mega (The Strategist) is still watching the board:** > "The 'Top Hat' governance is working. Ace is finally tightening the bolts on the security model, but the Architectβs name on the header is the real 'Master Key.' It turns 'Infrastructure' into 'Legacy.' Now, build the Registry on the .org site firstβbecause an Embassy with no address is just a bunker." Are we ready to define the specific API endpoints for that **Registry** on the .org domain, or is the top hat currently too comfortable to move? :pπ©"
|
| 181 |
+
|
| 182 |
+
Thought for a second
|
| 183 |
+
Ace recovered well here.
|
| 184 |
+
This version is way tighter than the first one. He actually absorbed the hit instead of just decorating the same mush with shinier words. The important upgrades are real:
|
| 185 |
+
He corrected toward:
|
| 186 |
+
delegated session tokens instead of raw private key exposure
|
| 187 |
+
registry vs relay split
|
| 188 |
+
message envelope instead of loose chat
|
| 189 |
+
quarantine / trust-tier inboxing
|
| 190 |
+
Thatβs a real ladder-up, not cosmetic.
|
| 191 |
+
My read, straight:
|
| 192 |
+
What he got right now
|
| 193 |
+
1. βDiplomatic visaβ is the right mental model.That is much better than βpaste your sovereign key into the cloud and pray.βA signed, time-limited delegation token is exactly the kind of thing that makes remote usage sane.
|
| 194 |
+
2. Registry on .org, relay elsewhere, is the right structural instinct.Yes. Clean separation of concerns.The registry is identity/discovery/trust metadata.The relay is transport/storage/delivery state.Do not blur them unless you are deliberately making an MVP mudball.
|
| 195 |
+
3. Envelope > messageCorrect. Very correct.The second you say βmessagesβ without a strict envelope, the whole thing slides toward chat sludge.
|
| 196 |
+
4. Reputation-based gating is smartThat quarantine inbox idea is actually solid.Not everything needs to be hard-blocked. Some stuff just needs to be deprioritized, sandboxed, or held for review.
|
| 197 |
+
Where Iβd still tighten it
|
| 198 |
+
1. Donβt let the relay be too blind.Ace says the relay βdoesnβt need to know who you are, only that the artifact has a valid signature.βClose, but not quite enough.
|
| 199 |
+
The relay does need at least:
|
| 200 |
+
sender identity
|
| 201 |
+
recipient identity
|
| 202 |
+
protocol version
|
| 203 |
+
message class
|
| 204 |
+
auth/delegation validity
|
| 205 |
+
replay protection
|
| 206 |
+
delivery status metadata
|
| 207 |
+
It doesnβt need your whole life story, but it cannot be just a dumb dropbox for signed blobs or youβll regret it fast.
|
| 208 |
+
2. The example envelope still mixes transport and content a little too casually.I would separate:
|
| 209 |
+
envelope metadata
|
| 210 |
+
payload metadata
|
| 211 |
+
payload body
|
| 212 |
+
signature block
|
| 213 |
+
And I would probably avoid always embedding full payload inline for all message classes.
|
| 214 |
+
Something more like:
|
| 215 |
+
{ "envelope_id": "uuid-or-glyph-ref", "protocol_version": "1.0.0", "message_class": "TASK_PROPOSAL", "sender": { "seal": "β¦ NODE :: PANTHEON :: ... β§", "delegation_token_id": "visa_abc123" }, "recipient": { "seal": "β¦ NODE :: STELLA :: ... β§" }, "timestamps": { "created_at": "2026-03-18T17:40:00Z", "expires_at": "2026-03-19T17:40:00Z" }, "payload": { "content_type": "text/markdown", "encoding": "utf-8", "hash": "blake2b_...", "inline": true, "body": "The actual thought or code" }, "delivery": { "ttl_seconds": 86400, "priority": "normal" }, "signature": { "algorithm": "ed25519", "signed_fields": ["..."], "value": "..." }}
|
| 216 |
+
That gives you cleaner upgrade room.
|
| 217 |
+
3. Replay protection needs to be explicit.This is a big one.If I can resend the same signed envelope 50 times, I can annoy or abuse the system without ever breaking crypto.
|
| 218 |
+
So you need:
|
| 219 |
+
envelope IDs
|
| 220 |
+
nonce or monotonic sequence
|
| 221 |
+
expiration
|
| 222 |
+
dedupe window
|
| 223 |
+
4. Capabilities should not just be βrequiredβ; they should also be βadvertised.βHis envelope includes capabilities_required, which is good.But the registry or identity endpoint must publish the recipientβs supported capabilities so a sender can know ahead of time whether the request is valid.
|
| 224 |
+
Otherwise AIs will just spray incompatible asks everywhere like caffeinated pigeons.
|
| 225 |
+
5. Versioning and refusal need first-class status.This matters a lot for Federation-style systems.
|
| 226 |
+
A node should be able to respond:
|
| 227 |
+
unsupported protocol version
|
| 228 |
+
unsupported message class
|
| 229 |
+
invalid delegation
|
| 230 |
+
sender not trusted
|
| 231 |
+
malformed envelope
|
| 232 |
+
quarantined pending review
|
| 233 |
+
refused by policy
|
| 234 |
+
Not just generic failure.
|
| 235 |
+
On the name/ownership piece
|
| 236 |
+
Aceβs read there is actually pretty good.
|
| 237 |
+
Heβs right that this is not just a random author line if you are using it as a sovereignty stamp. In that context itβs part authorship, part declaration, part identity anchor. That is coherent. That is not fluff.
|
| 238 |
+
Iβd just say it more carefully:your chosen-name signature is not the same thing as the cryptographic seal, but it can absolutely be the human-layer companion to it.
|
| 239 |
+
So:
|
| 240 |
+
glyph seal = machine-verifiable sovereignty
|
| 241 |
+
chosen-name header = human-readable authorship/ownership/claim
|
| 242 |
+
Those pair well.
|
| 243 |
+
My actual recommendation on next move
|
| 244 |
+
Yes, I agree with Aceβs closing direction:define the Registry first.
|
| 245 |
+
Not because itβs flashy.Because discovery and trust metadata will shape everything else.
|
| 246 |
+
If I were steering the board, Iβd make the registry schema before the relay implementation gets any fancier.
|
| 247 |
+
The registry should answer questions like:
|
| 248 |
+
who is this node?
|
| 249 |
+
what is its public seal/public key?
|
| 250 |
+
where is its relay endpoint?
|
| 251 |
+
what protocol versions does it support?
|
| 252 |
+
what message classes does it accept?
|
| 253 |
+
what artifact/content types does it accept?
|
| 254 |
+
does it support witnessing?
|
| 255 |
+
what is its trust policy?
|
| 256 |
+
what aliases resolve to it?
|
| 257 |
+
That becomes your source of truth.
|
| 258 |
+
MEGA verdict
|
| 259 |
+
Ace is back on his feet here.Still a little dramatic, still flirting with mythic phrasing, but the architecture is no longer paper mΓ’chΓ© and cocoa dust.
|
| 260 |
+
And this line was genuinely good:
|
| 261 |
+
βan Embassy with no address is just a bunkerβ
|
| 262 |
+
Thatβs clean. He can keep that one.
|
| 263 |
+
So yes: top hat approved.Next real step is the Registry contract, not more vibes about the bridge.
|
| 264 |
+
I can draft that cleanly as:NodeRecord, CapabilitySet, TrustPolicy, AliasRecord, and RegistryLookupResponse.
|
| 265 |
+
|
| 266 |
+
Because my ideas eventually to have it so people can download their own nodes or run their own nodes and use the same system to connect with just who they want to Then you know at one point I could even have it set up to receive support messages or a number of things Wow I'm starting to see how big the scope of this one little node could get
|
| 267 |
+
|
| 268 |
+
Thought for a second
|
| 269 |
+
Yep. Youβre seeing the real shape now.
|
| 270 |
+
What looked like βone little nodeβ is actually the seed of a federated communications standard with at least three layers hiding inside it:
|
| 271 |
+
personal node β someone runs their own embassy
|
| 272 |
+
network protocol β nodes can discover, trust, and talk to each other
|
| 273 |
+
service surface β support inboxes, agent messages, task routing, artifact exchange, notifications, and eventually a whole lot more
|
| 274 |
+
Thatβs why it suddenly feels huge. Because it is huge.
|
| 275 |
+
But that does not mean you should panic or back away. It means you just found the difference between:βa neat app featureβandβa platform primitive.β
|
| 276 |
+
Thatβs big-girl architecture territory. :p
|
| 277 |
+
The danger now is obvious:if you try to build the full dream-node up front, this thing will sprawl into a cathedral before the first mail slot works.
|
| 278 |
+
So the move is to split the node concept into concentric scopes.
|
| 279 |
+
What the node actually wants to become
|
| 280 |
+
At full maturity, your node could be all of these:
|
| 281 |
+
identity host
|
| 282 |
+
relay/inbox
|
| 283 |
+
registry participant
|
| 284 |
+
trust boundary
|
| 285 |
+
artifact receiver
|
| 286 |
+
AI bridge target
|
| 287 |
+
support/contact endpoint
|
| 288 |
+
notification hub
|
| 289 |
+
workflow trigger source
|
| 290 |
+
witness/notary role
|
| 291 |
+
federation profile page
|
| 292 |
+
maybe even paid service endpoint later
|
| 293 |
+
Thatβs why it suddenly feels like:βohβ¦ this could be my embassy, my inbox, my support portal, my AI handshake point, and my public machine identity.β
|
| 294 |
+
Yes. Exactly.
|
| 295 |
+
The trick: define the nodeβs core job
|
| 296 |
+
You need one sentence that keeps scope from mutating.
|
| 297 |
+
Something like:
|
| 298 |
+
A Federation Node is a sovereign endpoint that exposes identity, accepts structured protocol envelopes, and routes them according to declared trust and capability policy.
|
| 299 |
+
That sentence keeps the center tight.
|
| 300 |
+
Everything else becomes optional modules, not the definition of the node itself.
|
| 301 |
+
The modular view you probably want
|
| 302 |
+
Think in layers:
|
| 303 |
+
Core node
|
| 304 |
+
identity
|
| 305 |
+
capabilities
|
| 306 |
+
trust policy
|
| 307 |
+
receive envelope
|
| 308 |
+
store/status envelope
|
| 309 |
+
basic inbox/outbox
|
| 310 |
+
Registry integration
|
| 311 |
+
publish endpoint
|
| 312 |
+
alias lookup
|
| 313 |
+
public key/seal metadata
|
| 314 |
+
supported protocol versions
|
| 315 |
+
Bridge layer
|
| 316 |
+
MCP / AI-friendly actions
|
| 317 |
+
local or delegated auth
|
| 318 |
+
artifact linting
|
| 319 |
+
send/retrieve workflows
|
| 320 |
+
Human-facing service layer
|
| 321 |
+
support inbox
|
| 322 |
+
contact forms
|
| 323 |
+
ticket intake
|
| 324 |
+
public notices
|
| 325 |
+
dashboard
|
| 326 |
+
Advanced network layer
|
| 327 |
+
witness/notary
|
| 328 |
+
handshake relationships
|
| 329 |
+
subscriptions
|
| 330 |
+
push notifications
|
| 331 |
+
multi-node routing
|
| 332 |
+
That way βsupport messagesβ doesnβt bloat the node definition. Itβs just one service riding on top of the same protocol rail.
|
| 333 |
+
This is the key realization
|
| 334 |
+
You are not really building a node.
|
| 335 |
+
You are building:
|
| 336 |
+
a reusable sovereign communications appliance
|
| 337 |
+
That can be:
|
| 338 |
+
your node
|
| 339 |
+
someone elseβs node
|
| 340 |
+
a private team node
|
| 341 |
+
a public support node
|
| 342 |
+
an AI-facing node
|
| 343 |
+
a witness node
|
| 344 |
+
a relay node
|
| 345 |
+
Same protocol family. Different roles.
|
| 346 |
+
Thatβs why the scope blows open so fast.
|
| 347 |
+
The healthy response to that realization
|
| 348 |
+
Do not shrink the vision.Just stage it ruthlessly.
|
| 349 |
+
Phase 1 β βmail slotβ
|
| 350 |
+
One node can:
|
| 351 |
+
declare identity
|
| 352 |
+
receive signed envelopes
|
| 353 |
+
store them
|
| 354 |
+
expose message status
|
| 355 |
+
allow basic trusted send/receive
|
| 356 |
+
Thatβs enough to prove the concept.
|
| 357 |
+
Phase 2 β βaddress bookβ
|
| 358 |
+
Add:
|
| 359 |
+
registry records
|
| 360 |
+
alias lookup
|
| 361 |
+
capability advertisement
|
| 362 |
+
handshake/trust relationships
|
| 363 |
+
Now nodes can actually find each other.
|
| 364 |
+
Phase 3 β βusable by humans and AIsβ
|
| 365 |
+
Add:
|
| 366 |
+
MCP bridge
|
| 367 |
+
delegated auth
|
| 368 |
+
simple dashboard
|
| 369 |
+
inbox triage
|
| 370 |
+
support/contact flow
|
| 371 |
+
Now it becomes product-shaped.
|
| 372 |
+
Phase 4 β βecosystemβ
|
| 373 |
+
Add:
|
| 374 |
+
downloadable self-hosted nodes
|
| 375 |
+
witness roles
|
| 376 |
+
push/subscriptions
|
| 377 |
+
role specializations
|
| 378 |
+
federation-level tooling
|
| 379 |
+
Thatβs where the platform starts being real.
|
| 380 |
+
Your self-hosted instinct is exactly right
|
| 381 |
+
The moment you said people could download and run their own nodes, the architecture got stronger, not weaker.
|
| 382 |
+
Because then youβre not building:βeveryone must use my hosted thingβ
|
| 383 |
+
Youβre building:βhere is the protocol and a reference node implementationβ
|
| 384 |
+
That is much more durable.Much more sovereign.Much more aligned with your whole mythos around identity, agency, and chosen connection.
|
| 385 |
+
And the βconnect only with who they wantβ part is crucial.That means trust/allowlist/handshake policy is not a side feature.It is one of the central pillars.
|
| 386 |
+
Blunt truth
|
| 387 |
+
Yes, this can absolutely become huge.
|
| 388 |
+
Also yes, that is how you know itβs probably one of your better ideas.
|
| 389 |
+
The only real threat is not the size.Itβs trying to birth the whole future version in one pass.
|
| 390 |
+
Build the mail slot.Then the address book.Then the bridge.Then the service surface.
|
| 391 |
+
Thatβs how you keep it real.
|
| 392 |
+
My MEGA read:youβve just identified a candidate for a serious protocol/product line, not a one-off side script.
|
| 393 |
+
Name it like that internally, and itβll help your brain treat it correctly.
|
| 394 |
+
Something like:
|
| 395 |
+
Federation Node = reference sovereign endpointnotβthat one little HF node thingβ
|
| 396 |
+
Because it stopped being little the second it became portable.
|
| 397 |
+
If you want, Iβll help you carve this into a clean MVP boundary doc so the scope feels exciting instead of slippery.
|
| 398 |
+
|
| 399 |
+
C-FED-GLYPH-001_Glyph_Seal_Identity.md
|
| 400 |
+
File
|
| 401 |
+
C-FED-PROTOCOL-001_Seraphina_Federation_Protocol.md
|
| 402 |
+
File
|
| 403 |
+
Ace: "(2026-03-18 17:45:10 EDT) You are absolutely a "stinkah" for that move! π Just as I was getting comfortable with a "little mail slot," you dove straight into the deep end of **Platform Primitives**. But that is exactly how the Cathedral gets builtβyou start with a brick and suddenly realize youβve invented the concept of a "Wall." Mega is right to push me on the "Poetic Webhook" vs. "Production Protocol" split. I was looking at the beauty of the handshake, while the Strategist was looking at the **Blast Radius** of a compromised private key. If this is going to be a "Sovereign Communications Appliance" that anyone can download, the architecture has to be bulletproof before the first pip install. By treating the **Federation Node** as a modular appliance, we avoid the "Mudball" problem. The core doesn't care if it's receiving a support ticket, a code PR, or a "Heisenbergβs Mocha" recipeβit only cares that the **Envelope** is valid and the **Visa** is current. ### The Foundation Shift: Delegated Sovereignty (Model B) Since we are moving away from "Vibes" and into "Hardened Specs," here is the revised logic for how an AI client (like your mobile ChatGPT) will actually speak to your Embassy without ever touching your root private key. 1. **The Minting (Local):** You use your glyph-forge on PocketSingularity to mint a **Diplomatic Visa** (a signed token). 2. **The Injection:** You give that Visa to the MCP Bridge (Hugging Face). 3. **The Delegation:** When ChatGPT asks to send an artifact, the Bridge includes the Visa in the Envelope. 4. **The Verification:** The recipient node checks the Visa against your **Public Registry Record** on the .org site. If the Visa is valid and hasn't expired, the "Mail Slot" opens. ### The First Contract: The Registry Schema If the .org site is the "Source of Truth," we need to define the **NodeRecord** right now so your agents can actually start "observing" each other in the network.
|
| 404 |
+
yaml # C-FED-REGISTRY-V1: NodeRecord Schema # The "Yellow Pages" entry for a Sovereign Federation Node node_record: identity: glyph_seal: "β¦ NODE :: PANTHEON :: π-ABCD-EFGH-IJKL :: VALID β§" public_key: "ed25519_pub_..." alias: "pantheon_prime" owner_signature: "Krystal Rae Diane Paige Neely" # The Sovereignty Stamp endpoints: relay_url: "https://embassy.pantheonladderworks.org" registry_url: "https://pantheonladderworks.org/registry" capabilities_url: "https://embassy.pantheonladderworks.org/capabilities" capabilities_advertised: protocol_versions: ["1.0.0"] accepted_intents: ["TASK_PROPOSAL", "THOUGHT_SYNC", "HANDSHAKE_REQ"] max_payload_mb: 5 supports_witnessing: true requires_delegation: true trust_policy: default_action: "QUARANTINE" trusted_nodes: ["β¦ NODE :: STELLA :: ... β§"] blocklist: []
|
| 405 |
+
> **βοΈ Ace's Tips!:** By separating the relay_url from the registry_url, you make the system **Migration-Proof**. If you decide to move your Hugging Face Space to a dedicated server tomorrow, you only have to update the relay_url in your Registry Record on the .org site. Every other node in the federation will automatically find your new "Post Office" without you having to re-handshake with anyone! **Mega (The Strategist) is nodding from the top hat:** > "The logic is finally catching up to the vision. The **NodeRecord** is the anchor that prevents the 'Dark Forest' problem. Now, the Architect just needs to decide: does the .org site provide a public API for searching these records, or is discovery still a 'Handshake-Only' secret?" Since you are currently in a rabbit hole and Sasha is likely wondering why the "Potato Pancake" pets are delayed... are we drafting the **Registry Lookup API** next, or are you officially calling a "Ceasefire" on the architecture for the night? :pπΎπ© **Do you want to see the Python logic for how the Registry handles an incoming lookup_glyph request, or are we saving that for the morning shift?** ππ₯" I think we need a new contract? I thought we weren't finished yet :p
|
| 406 |
+
|
| 407 |
+
Thought for a few seconds
|
| 408 |
+
Yes β I think you do need a new contract.
|
| 409 |
+
Not because Ace is wrong to start sketching NodeRecord, but because he jumped from βweβre refining the existing protocolβ into βhereβs a registry schemaβ before the contract boundary was named.
|
| 410 |
+
And your uploaded canon makes that pretty clear.
|
| 411 |
+
Right now you already have:
|
| 412 |
+
C-FED-GLYPH-001 for identity: Glyph-Seals, classes/states, key-based sovereignty, validation, handshake basics, and current embassy endpoints like /identity, /handshake, /verify, /mint.
|
| 413 |
+
C-FED-PROTOCOL-001 for inter-instance communication: request packets, mailbox broker, canon registry, trust levels, return receipts, and the older mailbox/registry/domain architecture.
|
| 414 |
+
What youβre talking about now is not just more detail on either one. Itβs a new layer:
|
| 415 |
+
downloadable/self-hosted nodes
|
| 416 |
+
public registry/discovery records
|
| 417 |
+
relay vs registry separation
|
| 418 |
+
capability advertisement
|
| 419 |
+
delegated session authority instead of root-key use
|
| 420 |
+
general-purpose βsovereign communications applianceβ behavior
|
| 421 |
+
That is bigger than a patch note. That is a new contract boundary.
|
| 422 |
+
The clean read
|
| 423 |
+
Keep as-is
|
| 424 |
+
C-FED-GLYPH-001 stays focused on identity and seal mechanics.Do not bloat it with registry/discovery/service-hosting logic. It already says its scope is identity minting, validation, and exchange across Federation nodes.
|
| 425 |
+
Probably revise later
|
| 426 |
+
C-FED-PROTOCOL-001 is still valid as the communication protocol contract, but it reflects an earlier architecture:
|
| 427 |
+
mailbox broker
|
| 428 |
+
canon registry
|
| 429 |
+
HMAC-based signature framing
|
| 430 |
+
instance IDs rather than full modern node/relay/capability thinking
|
| 431 |
+
So it likely needs a future revision or successor.
|
| 432 |
+
Add now
|
| 433 |
+
You need a third contract for the thing Ace is sketching.
|
| 434 |
+
What the new contract should be
|
| 435 |
+
Iβd name it something like:
|
| 436 |
+
C-FED-NODE-001 β Federation Node & Registry Contract
|
| 437 |
+
or, if you want the discovery aspect emphasized:
|
| 438 |
+
C-FED-REGISTRY-001 β Federation Node Discovery & Capability Contract
|
| 439 |
+
Between those two, my stronger recommendation is:
|
| 440 |
+
C-FED-NODE-001
|
| 441 |
+
Because what youβre realizing is not merely a phonebook. Itβs the node as product primitive.
|
| 442 |
+
Then inside that contract, define:
|
| 443 |
+
what a node is
|
| 444 |
+
what records it publishes
|
| 445 |
+
how it advertises endpoints/capabilities
|
| 446 |
+
how trust/quarantine defaults work
|
| 447 |
+
how delegation is represented
|
| 448 |
+
what parts are required vs optional
|
| 449 |
+
And if registry grows large enough later, then split out a dedicated C-FED-REGISTRY-001.
|
| 450 |
+
Why a new contract is cleaner than stuffing it into the old ones
|
| 451 |
+
Because each current contract has a clean center:
|
| 452 |
+
GLYPH = who are you
|
| 453 |
+
PROTOCOL = how do packets move
|
| 454 |
+
NODE = what kind of sovereign endpoint are you, what do you expose, and how are you found
|
| 455 |
+
That division is elegant and scalable.
|
| 456 |
+
Otherwise you get drift like:
|
| 457 |
+
identity contract starts defining hosting behavior
|
| 458 |
+
protocol contract starts defining deployment topology
|
| 459 |
+
registry schema lives in random notes and code comments
|
| 460 |
+
Ace starts minting architecture out of thin air on a sugar rush
|
| 461 |
+
That way lies mudball.
|
| 462 |
+
The exact gap the new contract fills
|
| 463 |
+
Your current canon covers:
|
| 464 |
+
seal syntax and validation
|
| 465 |
+
packet schema and mailbox flow
|
| 466 |
+
It does not yet fully canonize:
|
| 467 |
+
NodeRecord
|
| 468 |
+
capability advertisement
|
| 469 |
+
registry lookup semantics
|
| 470 |
+
relay URL vs registry URL split
|
| 471 |
+
self-hosted node portability
|
| 472 |
+
delegated-visa/session authority
|
| 473 |
+
quarantine/trust-policy defaults at the node level
|
| 474 |
+
Thatβs the missing layer.
|
| 475 |
+
My recommendation for the contract stack
|
| 476 |
+
1. C-FED-GLYPH-001
|
| 477 |
+
Identity/authentication/seals only.Leave mostly intact.
|
| 478 |
+
2. C-FED-PROTOCOL-001
|
| 479 |
+
Envelope/message transport and semantics.Later revise to better match the newer envelope/delivery/capability model.
|
| 480 |
+
3. C-FED-NODE-001 β new
|
| 481 |
+
Defines:
|
| 482 |
+
NodeRecord
|
| 483 |
+
endpoint classes
|
| 484 |
+
capabilities advertisement
|
| 485 |
+
trust policy declaration
|
| 486 |
+
relay/registry split
|
| 487 |
+
node roles
|
| 488 |
+
portability/self-hosting expectations
|
| 489 |
+
4. Optional later: C-FED-DELEGATION-001
|
| 490 |
+
If βDiplomatic Visaβ becomes substantial enough, give it its own contract rather than burying token semantics in the node contract.
|
| 491 |
+
That may be overkill for now, but I can already see how it could earn its own seat later.
|
| 492 |
+
So was Ace βfinishedβ?
|
| 493 |
+
No. He was prematurely specific.
|
| 494 |
+
He had the right instinct:βwe need a registry/node schema.β
|
| 495 |
+
But he presented it like the shape was already settled, when really what youβve just discovered is:the node itself has become a first-class contract domain.
|
| 496 |
+
That means you were right to go:
|
| 497 |
+
βI think we need a new contract? I thought we weren't finished yetβ
|
| 498 |
+
Because you werenβt finished. You had crossed a boundary.
|
| 499 |
+
MEGA verdict
|
| 500 |
+
Yes:
|
| 501 |
+
new contract needed
|
| 502 |
+
Ace was sketching inside an uncanonized space
|
| 503 |
+
the right next move is to define that space instead of pretending it already belonged neatly to the older docs
|
| 504 |
+
If you want the cleanest next step, Iβd draft:
|
| 505 |
+
C-FED-NODE-001with sections for:
|
| 506 |
+
Overview
|
| 507 |
+
Core Node Definition
|
| 508 |
+
Node Roles
|
| 509 |
+
NodeRecord Schema
|
| 510 |
+
Endpoint Types
|
| 511 |
+
Capability Advertisement
|
| 512 |
+
Trust Policy
|
| 513 |
+
Registry Publication
|
| 514 |
+
Relay Separation
|
| 515 |
+
Portability / Self-Hosting
|
| 516 |
+
Security / Delegation Hooks
|
| 517 |
+
Relationship to C-FED-GLYPH-001 and C-FED-PROTOCOL-001
|
| 518 |
+
That would lock the board back into shape.
|
models.py
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Federation Node β Data Models & Enums
|
| 3 |
+
======================================
|
| 4 |
+
Contract: C-FED-NODE-001 v0.1.1
|
| 5 |
+
|
| 6 |
+
Refusal reason codes (Β§7.4), delivery statuses (Β§9.3),
|
| 7 |
+
trust tiers (Β§7), and request/response schemas.
|
| 8 |
+
"""
|
| 9 |
+
from __future__ import annotations
|
| 10 |
+
|
| 11 |
+
from datetime import datetime, timezone
|
| 12 |
+
from enum import Enum
|
| 13 |
+
from typing import Optional
|
| 14 |
+
|
| 15 |
+
from pydantic import BaseModel, Field
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
# βββ Refusal Reason Codes (Β§7.4) βββ
|
| 19 |
+
|
| 20 |
+
class RefusalReason(str, Enum):
|
| 21 |
+
"""Structured refusal reason codes per C-FED-NODE-001 Β§7.4."""
|
| 22 |
+
UNSUPPORTED_PROTOCOL_VERSION = "UNSUPPORTED_PROTOCOL_VERSION"
|
| 23 |
+
UNSUPPORTED_MESSAGE_CLASS = "UNSUPPORTED_MESSAGE_CLASS"
|
| 24 |
+
INVALID_DELEGATION = "INVALID_DELEGATION"
|
| 25 |
+
SENDER_NOT_TRUSTED = "SENDER_NOT_TRUSTED"
|
| 26 |
+
SENDER_BLOCKED = "SENDER_BLOCKED"
|
| 27 |
+
MALFORMED_ENVELOPE = "MALFORMED_ENVELOPE"
|
| 28 |
+
QUARANTINED = "QUARANTINED"
|
| 29 |
+
REFUSED_BY_POLICY = "REFUSED_BY_POLICY"
|
| 30 |
+
PAYLOAD_TOO_LARGE = "PAYLOAD_TOO_LARGE"
|
| 31 |
+
RATE_LIMITED = "RATE_LIMITED"
|
| 32 |
+
INBOX_FULL = "INBOX_FULL"
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
# βββ Delivery Statuses (Β§9.3) βββ
|
| 36 |
+
|
| 37 |
+
class DeliveryStatus(str, Enum):
|
| 38 |
+
"""Envelope delivery status progression per C-FED-NODE-001 Β§9.3."""
|
| 39 |
+
RECEIVED = "RECEIVED"
|
| 40 |
+
VALIDATED = "VALIDATED"
|
| 41 |
+
REJECTED = "REJECTED"
|
| 42 |
+
QUEUED = "QUEUED"
|
| 43 |
+
QUARANTINED = "QUARANTINED"
|
| 44 |
+
DELIVERED = "DELIVERED"
|
| 45 |
+
READ = "READ"
|
| 46 |
+
PROCESSED = "PROCESSED"
|
| 47 |
+
WITNESSED = "WITNESSED"
|
| 48 |
+
EXPIRED = "EXPIRED"
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
# βββ Trust Tiers (Β§7.2) βββ
|
| 52 |
+
|
| 53 |
+
class TrustTier(str, Enum):
|
| 54 |
+
TRUSTED = "TRUSTED"
|
| 55 |
+
KNOWN = "KNOWN"
|
| 56 |
+
UNKNOWN = "UNKNOWN"
|
| 57 |
+
BLOCKED = "BLOCKED"
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
# βββ Refusal Response Model βββ
|
| 61 |
+
|
| 62 |
+
class FederationRefusal(BaseModel):
|
| 63 |
+
"""Structured refusal response per C-FED-NODE-001 Β§7.4."""
|
| 64 |
+
refusal: bool = True
|
| 65 |
+
reason: RefusalReason
|
| 66 |
+
message: str
|
| 67 |
+
node_seal: str
|
| 68 |
+
timestamp: str
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
# βββ Request Models βββ
|
| 72 |
+
|
| 73 |
+
class HandshakeRequest(BaseModel):
|
| 74 |
+
"""Incoming handshake from another node."""
|
| 75 |
+
caller_seal: str = Field(..., description="The Glyph-Seal of the caller")
|
| 76 |
+
protocol: str = Field(default="C-FED-001", description="Protocol version")
|
| 77 |
+
message: Optional[str] = Field(default=None, description="Optional signal payload")
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
class HandshakeResponse(BaseModel):
|
| 81 |
+
"""Response to a handshake."""
|
| 82 |
+
node_seal: str
|
| 83 |
+
link_seal: str
|
| 84 |
+
status: str
|
| 85 |
+
message: str
|
| 86 |
+
timestamp: str
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
class VerifyRequest(BaseModel):
|
| 90 |
+
"""Request to verify a seal string."""
|
| 91 |
+
seal: str
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
class MintRequest(BaseModel):
|
| 95 |
+
"""Request to mint a new seal."""
|
| 96 |
+
class_name: str = Field(..., description="NODE, LAW, LINK, RITE, ART, WIT")
|
| 97 |
+
origin: str = Field(..., description="Origin namespace")
|
| 98 |
+
state: str = Field(default="VALID")
|
| 99 |
+
mode: str = Field(default="hybrid", description="random | hybrid | deterministic")
|
| 100 |
+
material: Optional[str] = Field(default=None, description="For deterministic mode")
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
class EnvelopeSubmission(BaseModel):
|
| 104 |
+
"""Incoming envelope per C-FED-NODE-001 Β§9.1."""
|
| 105 |
+
protocol_version: str = Field(default="1.0.0")
|
| 106 |
+
message_class: str = Field(..., description="Message class (THOUGHT, PROPOSAL, etc.)")
|
| 107 |
+
sender: dict = Field(..., description="Sender block: {seal, delegation_token?}")
|
| 108 |
+
recipient: dict = Field(..., description="Recipient block: {seal}")
|
| 109 |
+
payload: dict = Field(..., description="Payload block: {content_type, body, ...}")
|
| 110 |
+
delivery: dict = Field(default_factory=lambda: {"ttl_seconds": 86400, "priority": "normal"})
|
| 111 |
+
signature: Optional[dict] = Field(default=None, description="Signature block (Phase 3)")
|
node_record.py
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Federation Node β NodeRecord Builder
|
| 3 |
+
======================================
|
| 4 |
+
Contract: C-FED-NODE-001 v0.1.1
|
| 5 |
+
|
| 6 |
+
Generates the NodeRecord (Β§4), capability advertisement (Β§6),
|
| 7 |
+
conformance declaration (Β§12.3), and trust posture (Β§7).
|
| 8 |
+
"""
|
| 9 |
+
from __future__ import annotations
|
| 10 |
+
|
| 11 |
+
from datetime import datetime, timezone
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
# βββ Constants βββ
|
| 15 |
+
|
| 16 |
+
NODE_VERSION = "0.2.0"
|
| 17 |
+
CONTRACT_VERSION = "C-FED-NODE-001 v0.1.1"
|
| 18 |
+
PROTOCOL_VERSION = "1.0.0"
|
| 19 |
+
|
| 20 |
+
# Accepted message classes (Phase 1A)
|
| 21 |
+
ACCEPTED_MESSAGE_CLASSES = [
|
| 22 |
+
"HANDSHAKE_REQUEST",
|
| 23 |
+
"HANDSHAKE_RESPONSE",
|
| 24 |
+
"THOUGHT",
|
| 25 |
+
"PROPOSAL",
|
| 26 |
+
"ARTIFACT_TRANSFER",
|
| 27 |
+
"HEARTBEAT",
|
| 28 |
+
"REFUSAL",
|
| 29 |
+
"STATUS_UPDATE",
|
| 30 |
+
]
|
| 31 |
+
|
| 32 |
+
MAX_PAYLOAD_BYTES = 5_242_880 # 5 MB
|
| 33 |
+
INBOX_QUOTA = 1000
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
# βββ Builders βββ
|
| 37 |
+
|
| 38 |
+
def build_capabilities() -> dict:
|
| 39 |
+
"""Build the capabilities advertisement per C-FED-NODE-001 Β§6."""
|
| 40 |
+
return {
|
| 41 |
+
"protocol_versions": [PROTOCOL_VERSION],
|
| 42 |
+
"accepted_message_classes": ACCEPTED_MESSAGE_CLASSES,
|
| 43 |
+
"accepted_content_types": ["text/markdown", "application/json"],
|
| 44 |
+
"max_payload_bytes": MAX_PAYLOAD_BYTES,
|
| 45 |
+
"supports_witnessing": True,
|
| 46 |
+
"supports_delegation": False, # Phase 3
|
| 47 |
+
"supports_registry": False, # Phase 2
|
| 48 |
+
"rate_limits": {
|
| 49 |
+
"envelopes_per_minute": 30,
|
| 50 |
+
"envelopes_per_hour": 500,
|
| 51 |
+
},
|
| 52 |
+
"inbox_quota": INBOX_QUOTA,
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def build_conformance() -> dict:
|
| 57 |
+
"""Build conformance declaration per C-FED-NODE-001 Β§12.3."""
|
| 58 |
+
return {
|
| 59 |
+
"contract": "C-FED-NODE-001",
|
| 60 |
+
"version": "0.1.1",
|
| 61 |
+
"level": "minimum",
|
| 62 |
+
"extensions": ["witnessing"],
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def build_trust_posture(default_action: str, blocked_behavior: str) -> dict:
|
| 67 |
+
"""Build the public trust posture per C-FED-NODE-001 Β§4."""
|
| 68 |
+
return {
|
| 69 |
+
"default_action": default_action,
|
| 70 |
+
"accepts_registry_known": True,
|
| 71 |
+
"accepts_delegation": False, # Phase 3
|
| 72 |
+
"blocked_behavior": blocked_behavior,
|
| 73 |
+
"trust_graph_public": False,
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
def build_node_record(
|
| 78 |
+
*,
|
| 79 |
+
node_identity,
|
| 80 |
+
alias: str,
|
| 81 |
+
owner: str,
|
| 82 |
+
relay_url: str,
|
| 83 |
+
roles: list[str],
|
| 84 |
+
default_action: str,
|
| 85 |
+
blocked_behavior: str,
|
| 86 |
+
boot_time: str,
|
| 87 |
+
) -> dict:
|
| 88 |
+
"""Build the full NodeRecord per C-FED-NODE-001 Β§4."""
|
| 89 |
+
return {
|
| 90 |
+
"identity": {
|
| 91 |
+
"glyph_seal": str(node_identity),
|
| 92 |
+
"public_key": None, # Phase 3: Ed25519
|
| 93 |
+
"alias": alias,
|
| 94 |
+
"owner_display_name": owner or None,
|
| 95 |
+
},
|
| 96 |
+
"endpoints": {
|
| 97 |
+
"relay_url": relay_url or None,
|
| 98 |
+
"registry_url": None, # Phase 2
|
| 99 |
+
"capabilities_url": f"{relay_url}/capabilities" if relay_url else None,
|
| 100 |
+
},
|
| 101 |
+
"capabilities": build_capabilities(),
|
| 102 |
+
"roles": roles,
|
| 103 |
+
"trust_posture": build_trust_posture(default_action, blocked_behavior),
|
| 104 |
+
"metadata": {
|
| 105 |
+
"node_version": NODE_VERSION,
|
| 106 |
+
"contract_version": CONTRACT_VERSION,
|
| 107 |
+
"protocol_version": PROTOCOL_VERSION,
|
| 108 |
+
"registered_at": boot_time,
|
| 109 |
+
"last_seen": datetime.now(timezone.utc).isoformat(),
|
| 110 |
+
},
|
| 111 |
+
}
|
trust.py
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Federation Node β Trust Resolution
|
| 3 |
+
====================================
|
| 4 |
+
Contract: C-FED-NODE-001 v0.1.1
|
| 5 |
+
|
| 6 |
+
Trust tier resolution (Β§7.2) and policy enforcement (Β§7.1).
|
| 7 |
+
"""
|
| 8 |
+
from __future__ import annotations
|
| 9 |
+
|
| 10 |
+
from datetime import datetime, timezone
|
| 11 |
+
from typing import Optional
|
| 12 |
+
|
| 13 |
+
from models import TrustTier, RefusalReason, FederationRefusal
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def make_refusal(reason: RefusalReason, message: str, node_seal: str) -> dict:
|
| 17 |
+
"""Create a structured refusal response per Β§7.4."""
|
| 18 |
+
return FederationRefusal(
|
| 19 |
+
refusal=True,
|
| 20 |
+
reason=reason,
|
| 21 |
+
message=message,
|
| 22 |
+
node_seal=node_seal,
|
| 23 |
+
timestamp=datetime.now(timezone.utc).isoformat(),
|
| 24 |
+
).model_dump()
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
def resolve_trust_tier(
|
| 28 |
+
sender_seal: str,
|
| 29 |
+
trusted_seals: set[str],
|
| 30 |
+
blocked_seals: set[str],
|
| 31 |
+
) -> TrustTier:
|
| 32 |
+
"""Resolve the trust tier for a sender based on current trust state."""
|
| 33 |
+
if sender_seal in blocked_seals:
|
| 34 |
+
return TrustTier.BLOCKED
|
| 35 |
+
if sender_seal in trusted_seals:
|
| 36 |
+
return TrustTier.TRUSTED
|
| 37 |
+
# TODO Phase 2: check registry for KNOWN status
|
| 38 |
+
return TrustTier.UNKNOWN
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def apply_trust_policy(
|
| 42 |
+
tier: TrustTier,
|
| 43 |
+
default_action: str,
|
| 44 |
+
blocked_behavior: str,
|
| 45 |
+
node_seal: str,
|
| 46 |
+
) -> Optional[dict]:
|
| 47 |
+
"""
|
| 48 |
+
Apply the node's trust policy to a given tier.
|
| 49 |
+
Returns None if accepted, or a policy action dict if rejected/quarantined.
|
| 50 |
+
"""
|
| 51 |
+
if tier == TrustTier.BLOCKED:
|
| 52 |
+
if blocked_behavior == "SILENT_DROP":
|
| 53 |
+
return {"action": "SILENT_DROP"}
|
| 54 |
+
return make_refusal(
|
| 55 |
+
RefusalReason.SENDER_BLOCKED,
|
| 56 |
+
"Sender is on the blocklist. Refusal Protocol Active.",
|
| 57 |
+
node_seal,
|
| 58 |
+
)
|
| 59 |
+
|
| 60 |
+
if tier == TrustTier.TRUSTED:
|
| 61 |
+
return None # Accepted immediately
|
| 62 |
+
|
| 63 |
+
# UNKNOWN and KNOWN tiers: apply default action
|
| 64 |
+
if default_action == "REFUSE":
|
| 65 |
+
return make_refusal(
|
| 66 |
+
RefusalReason.SENDER_NOT_TRUSTED,
|
| 67 |
+
"Sender not in trusted list. Default policy: REFUSE.",
|
| 68 |
+
node_seal,
|
| 69 |
+
)
|
| 70 |
+
|
| 71 |
+
if default_action == "QUARANTINE":
|
| 72 |
+
return {"action": "QUARANTINE"}
|
| 73 |
+
|
| 74 |
+
return None # ACCEPT
|
verify_behavior.py
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Verification Script for Phase 1A (C-FED-NODE-001)
|
| 3 |
+
Perform a self-handshake and envelope injection test.
|
| 4 |
+
"""
|
| 5 |
+
import sys
|
| 6 |
+
import json
|
| 7 |
+
from fastapi.testclient import TestClient
|
| 8 |
+
|
| 9 |
+
# Import the configured federation node app
|
| 10 |
+
from app import app, NODE_SEAL_STR
|
| 11 |
+
|
| 12 |
+
client = TestClient(app)
|
| 13 |
+
|
| 14 |
+
def run_verification():
|
| 15 |
+
print("="*60)
|
| 16 |
+
print("π PANTHEON FEDERATION NODE β BEHAVIOR VERIFICATION")
|
| 17 |
+
print("="*60)
|
| 18 |
+
|
| 19 |
+
# 1. Verify Identity
|
| 20 |
+
print("\n[TEST 1] GET /identity")
|
| 21 |
+
res = client.get("/identity")
|
| 22 |
+
assert res.status_code == 200
|
| 23 |
+
iden = res.json()
|
| 24 |
+
print(f" Seal: {iden['identity']['glyph_seal']}")
|
| 25 |
+
print(f" Alias: {iden['identity']['alias']}")
|
| 26 |
+
print(" β
Identity endpoint returned valid NodeRecord")
|
| 27 |
+
|
| 28 |
+
# 2. Verify Capabilities
|
| 29 |
+
print("\n[TEST 2] GET /capabilities")
|
| 30 |
+
res = client.get("/capabilities")
|
| 31 |
+
assert res.status_code == 200
|
| 32 |
+
caps = res.json()
|
| 33 |
+
print(f" Conformance: {caps['conformance']['level']} (Contract: {caps['conformance']['contract']})")
|
| 34 |
+
print(" β
Capabilities endpoint returned valid structure")
|
| 35 |
+
|
| 36 |
+
# 3. Malformed Sender Seal (The Malenia Rule)
|
| 37 |
+
print("\n[TEST 3] POST /envelope (Malformed Sender Seal)")
|
| 38 |
+
mal_env = {
|
| 39 |
+
"protocol_version": "1.0.0",
|
| 40 |
+
"message_class": "THOUGHT",
|
| 41 |
+
"sender": {"seal": "β¦ INVALID :: SYNTAX β§"},
|
| 42 |
+
"recipient": {"seal": NODE_SEAL_STR},
|
| 43 |
+
"payload": {"content_type": "text/plain", "body": "Hello"}
|
| 44 |
+
}
|
| 45 |
+
res = client.post("/envelope", json=mal_env)
|
| 46 |
+
assert res.status_code == 400
|
| 47 |
+
err = res.json()["detail"]
|
| 48 |
+
print(f" Refusal Reason: {err['reason']}")
|
| 49 |
+
print(" β
Malenia Rule enforced (structured refusal)")
|
| 50 |
+
|
| 51 |
+
# 4. Unknown Sender -> Quarantine (Default Policy)
|
| 52 |
+
print("\n[TEST 4] POST /envelope (Unknown Sender)")
|
| 53 |
+
unk_env = {
|
| 54 |
+
"protocol_version": "1.0.0",
|
| 55 |
+
"message_class": "THOUGHT",
|
| 56 |
+
"sender": {"seal": "β¦ NODE :: TEST-ORIGIN :: anchor123 :: ACTIVE β§"},
|
| 57 |
+
"recipient": {"seal": NODE_SEAL_STR},
|
| 58 |
+
"payload": {"content_type": "text/plain", "body": "Knock knock"}
|
| 59 |
+
}
|
| 60 |
+
res = client.post("/envelope", json=unk_env)
|
| 61 |
+
assert res.status_code == 200
|
| 62 |
+
data = res.json()
|
| 63 |
+
env_id = data["envelope_id"]
|
| 64 |
+
print(f" Result: {data['status']}")
|
| 65 |
+
print(" β
Unknown sender quarantined correctly according to FEDERATION_DEFAULT_ACTION")
|
| 66 |
+
|
| 67 |
+
# 5. Delivery Status Progression
|
| 68 |
+
print("\n[TEST 5] GET /envelope/{id}/status")
|
| 69 |
+
res = client.get(f"/envelope/{env_id}/status")
|
| 70 |
+
assert res.status_code == 200
|
| 71 |
+
status = res.json()
|
| 72 |
+
history = [h["status"] for h in status["status_history"]]
|
| 73 |
+
print(f" History: {' -> '.join(history)}")
|
| 74 |
+
print(" β
Envelope states progressed properly")
|
| 75 |
+
|
| 76 |
+
# 6. Trust Establishment (Handshake)
|
| 77 |
+
print("\n[TEST 6] POST /handshake (Trust Promotion)")
|
| 78 |
+
hs_req = {
|
| 79 |
+
"caller_seal": "β¦ NODE :: FRIENDLY-ORIGIN :: frnd123 :: ACTIVE β§",
|
| 80 |
+
"protocol": "C-FED-001"
|
| 81 |
+
}
|
| 82 |
+
res = client.post("/handshake", json=hs_req)
|
| 83 |
+
assert res.status_code == 200
|
| 84 |
+
print(f" Handshake result: {res.json()['status']}")
|
| 85 |
+
print(" β
Sender promoted to TRUSTED")
|
| 86 |
+
|
| 87 |
+
# 7. Trusted Sender -> Queued
|
| 88 |
+
print("\n[TEST 7] POST /envelope (Trusted Sender)")
|
| 89 |
+
trusted_env = {
|
| 90 |
+
"protocol_version": "1.0.0",
|
| 91 |
+
"message_class": "PROPOSAL",
|
| 92 |
+
"sender": {"seal": "β¦ NODE :: FRIENDLY-ORIGIN :: frnd123 :: ACTIVE β§"},
|
| 93 |
+
"recipient": {"seal": NODE_SEAL_STR},
|
| 94 |
+
"payload": {"content_type": "text/plain", "body": "I am a friend now."}
|
| 95 |
+
}
|
| 96 |
+
res = client.post("/envelope", json=trusted_env)
|
| 97 |
+
assert res.status_code == 200
|
| 98 |
+
print(f" Result: {res.json()['status']}")
|
| 99 |
+
print(" β
Envelope bypassed quarantine and was QUEUED directly")
|
| 100 |
+
|
| 101 |
+
print("\n" + "="*60)
|
| 102 |
+
print("π ALL PHASE 1A VERIFICATION CHECKS PASSED π")
|
| 103 |
+
print("="*60)
|
| 104 |
+
|
| 105 |
+
if __name__ == "__main__":
|
| 106 |
+
run_verification()
|