Skip to content

Commit f866d78

Browse files
Merge feature/digital-twin-mesh-artifacts
# Conflicts: # internal/api/handlers.go # internal/api/handlers_test.go # internal/api/ledger_sql.go # requirements-ci.txt
2 parents de6c62d + 2abbb02 commit f866d78

10 files changed

Lines changed: 691 additions & 10 deletions
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<!-- markdownlint-disable MD022 MD032 -->
2+
3+
# Quantum KEX Drill Retention Policy
4+
5+
## Purpose
6+
Define incident-grade retention controls for Quantum KEX drill evidence bundles.
7+
8+
## Policy Controls
9+
- Classification: security-compliance-evidence
10+
- Minimum retention window: 2555 days (7 years)
11+
- Immutability requirement: required
12+
- Integrity requirement: required checksum catalog (`checksums.sha256`)
13+
14+
## Required Storage Controls
15+
- Use immutable object storage with object lock/WORM semantics.
16+
- Enable cross-region replication for disaster resilience.
17+
- Restrict deletion permissions to break-glass roles only.
18+
19+
## Transfer and Verification Procedure
20+
1. Generate artifact bundle with `make quantum-kex-rotation-drill`.
21+
1. Validate local checksums:
22+
23+
```bash
24+
cd artifacts/quantum-kex-rotation/<drill-id>
25+
sha256sum -c checksums.sha256
26+
```
27+
28+
1. Upload to immutable storage path:
29+
30+
```text
31+
security-evidence/quantum-kex-rotation/<drill-id>/
32+
```
33+
34+
1. Re-validate checksums after download from immutable storage.
35+
1. Record storage URI and verification timestamp in your governance ledger/report.
36+
37+
## Audit Expectations
38+
- Every drill must include:
39+
- `drill-summary.json`
40+
- `checksums.sha256`
41+
- `retention-policy.json`
42+
- `immutability-notice.txt`
43+
- Keep the rolling public index (`public-drill-index.md`) aligned with the latest 12 drills.
44+
45+
## Exception Handling
46+
- If immutable storage is unavailable, mark the drill as provisional and complete immutable archival within 24 hours.
47+
- If checksum verification fails at any stage, open incident triage and invalidate public claims for that drill until remediated.
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
<!-- markdownlint-disable MD022 MD032 -->
2+
3+
# Quantum KEX Rotation Drill Runbook (Genesis Testnet)
4+
5+
## Purpose
6+
This runbook defines a public, repeatable post-quantum key exchange (KEX) rotation drill on Genesis Testnet.
7+
8+
The drill demonstrates three things in one auditable sequence:
9+
1. Transport security controls execute a key rotation path successfully.
10+
2. Proof verification and ledger writes remain healthy before and after rotation.
11+
3. Operators can publish a concrete evidence bundle for community and investor review.
12+
13+
## Scope
14+
- Environment: Genesis Testnet
15+
- Runtime surface: node-agent auth-gated proof and ledger endpoints
16+
- Evidence source: local artifact bundle produced by the drill script
17+
18+
## Controls Exercised
19+
- Session key rotation pathway in crypto transport tests:
20+
- TestRotateSessionKeyNoDeadlock
21+
- TestRotateSessionKeyReestablishesSharedSecret
22+
- TestHandshakeVerification
23+
- Hybrid verification continuity:
24+
- POST /api/v1/proof/hybrid/verify
25+
- Fallback backend rehearsal:
26+
- POST /api/v1/proof/hybrid/verify with `stark_backend=winterfell_mock`
27+
- Ledger integrity continuity:
28+
- GET /api/v1/ledger
29+
- GET /api/v1/ledger/reconcile
30+
- Role policy enforcement negative-path:
31+
- GET /api/v1/ledger with unauthorized role (expects 401/403)
32+
33+
## Preconditions
34+
- Genesis Testnet stack reachable, with node-agent endpoint available.
35+
- API token available via one of:
36+
- MOHAWK_API_TOKEN environment variable
37+
- MOHAWK_API_TOKEN_FILE (default /run/secrets/mohawk_api_token)
38+
- Tooling: curl, go, python3
39+
40+
## Execution
41+
### Recommended one-command path
42+
43+
```bash
44+
make quantum-kex-rotation-drill
45+
```
46+
47+
### Direct script path
48+
49+
```bash
50+
NODE_AGENT_BASE_URL=http://localhost:8082 \
51+
MOHAWK_API_TOKEN_FILE=/run/secrets/mohawk_api_token \
52+
bash scripts/quantum-kex-rotation-drill.sh
53+
```
54+
55+
### Optional explicit token override
56+
57+
```bash
58+
NODE_AGENT_BASE_URL=http://localhost:8082 \
59+
MOHAWK_API_TOKEN="<redacted-token>" \
60+
bash scripts/quantum-kex-rotation-drill.sh
61+
```
62+
63+
## Artifact Output
64+
Default output directory:
65+
66+
```text
67+
artifacts/quantum-kex-rotation/<drill-id>/
68+
```
69+
70+
Expected files:
71+
- readiness_pre.json
72+
- ledger_pre.json
73+
- hybrid_verify_pre_rotation.json
74+
- crypto_rotation_test.log
75+
- hybrid_verify_post_rotation.json
76+
- hybrid_verify_fallback_backend.json
77+
- role_failure_negative_response.txt
78+
- role_failure_negative_test.json
79+
- ledger_post.json
80+
- ledger_reconcile_post.json
81+
- retention-policy.json
82+
- immutability-notice.txt
83+
- checksums.sha256
84+
- drill-summary.json
85+
- drill-summary.md
86+
87+
Generated cross-run index files:
88+
- artifacts/quantum-kex-rotation/public-drill-index.json
89+
- artifacts/quantum-kex-rotation/public-drill-index.md
90+
91+
## Success Criteria
92+
- Readiness pre-check is true.
93+
- Hybrid verification accepted both before and after rotation tests.
94+
- Fallback backend rehearsal is accepted.
95+
- Unauthorized role negative test returns 401/403.
96+
- Ledger reconciliation healthy after the drill.
97+
- Ledger entry count increases by at least 2 during the drill.
98+
- All artifact files present and readable.
99+
100+
## Public Disclosure Template
101+
Use the generated drill summary values and publish a concise statement:
102+
103+
```text
104+
Genesis Testnet Quantum KEX Rotation Drill complete.
105+
106+
- Drill ID: <drill-id>
107+
- Window (UTC): <start> -> <end>
108+
- Pre/Post hybrid verification accepted: true/true
109+
- Ledger reconcile healthy after drill: true
110+
- Ledger entries added: <n>
111+
112+
Evidence bundle path: artifacts/quantum-kex-rotation/<drill-id>/
113+
```
114+
115+
## Operational Notes
116+
- This drill is non-destructive and uses existing verification endpoints.
117+
- If auth fails, recheck token source and X-API-Role permissions.
118+
- If reconciliation fails, stop public messaging and open incident triage before rerun.
119+
120+
## Retention and Compliance
121+
- Canonical policy: [Documentation/Security/QUANTUM_KEX_DRILL_RETENTION_POLICY.md](Documentation/Security/QUANTUM_KEX_DRILL_RETENTION_POLICY.md)
122+
- Treat each bundle as immutable security evidence for at least 2555 days.
123+
- Validate `checksums.sha256` before and after archival transfer.
124+
125+
## Suggested Cadence Through 2027 Epoch Deadline
126+
- Public drill cadence: monthly
127+
- Add an additional ad hoc drill after any transport/auth policy change
128+
- Keep the last 12 drill summaries in release and governance reporting channels

Makefile

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,10 @@ testnet-wallet-readiness:
216216
@echo "🧪 Running testnet wallet readiness checks..."
217217
@bash scripts/testnet-wallet-readiness.sh
218218

219+
quantum-kex-rotation-drill:
220+
@echo "🔐 Running Genesis Testnet Quantum KEX Rotation Drill..."
221+
@bash scripts/quantum-kex-rotation-drill.sh
222+
219223
# =============================================================================
220224
# Development Helpers
221225
# =============================================================================
@@ -281,5 +285,6 @@ help:
281285
@echo " make observability-live-smoke - Validate lower-half operations panel queries against live Prometheus"
282286
@echo " make compose-service-drift-check - Detect stale compose service names in scripts"
283287
@echo " make quickstart-verify - Run onboarding-safe baseline verification targets"
288+
@echo " make quantum-kex-rotation-drill - Run public Genesis Testnet KEX rotation evidence drill"
284289
@echo " make check - Run all checks"
285290
@echo ""

README.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,32 @@ Documentation entrypoint: [docs/README.md](docs/README.md)
6363

6464
For current roadmap status and remaining open items, see [Documentation/Performance/OPS_OPTIMIZATION_ROADMAP_2026-03-24.md](Documentation/Performance/OPS_OPTIMIZATION_ROADMAP_2026-03-24.md) and [Documentation/Project/ROADMAP.md](Documentation/Project/ROADMAP.md).
6565

66+
## Quantum KEX Rotation Drill (Genesis Testnet)
67+
68+
The repository includes a public-facing Genesis Testnet drill to demonstrate post-quantum migration readiness with auditable artifacts.
69+
70+
Run the drill:
71+
72+
```bash
73+
make quantum-kex-rotation-drill
74+
```
75+
76+
Or run directly with explicit endpoint/token inputs:
77+
78+
```bash
79+
NODE_AGENT_BASE_URL=http://localhost:8082 \
80+
MOHAWK_API_TOKEN_FILE=/run/secrets/mohawk_api_token \
81+
bash scripts/quantum-kex-rotation-drill.sh
82+
```
83+
84+
Operator runbook and disclosure guidance:
85+
86+
- [Documentation/Security/QUANTUM_KEX_ROTATION_DRILL_RUNBOOK.md](Documentation/Security/QUANTUM_KEX_ROTATION_DRILL_RUNBOOK.md)
87+
88+
Artifact output:
89+
90+
- `artifacts/quantum-kex-rotation/<drill-id>/`
91+
6692
## New Contributor Fast Path
6793

6894
If you just cloned the repo and want to run tests quickly, use this sequence.

internal/api/handlers.go

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -358,15 +358,17 @@ func (h *Handler) HealthCheck(w http.ResponseWriter, r *http.Request) {
358358
status = "degraded"
359359
}
360360

361+
// Error details are omitted from this unauthenticated endpoint to avoid
362+
// leaking internal connection strings or credentials. Full details are
363+
// available on the auth-protected /api/v1/ledger endpoint.
361364
response := map[string]interface{}{
362365
"status": status,
363366
"service": "sovereign-map-fl",
364367
"time": time.Now().UTC().Format(time.RFC3339),
365368
"ledger": map[string]interface{}{
366369
"ready": ledgerReady,
367370
"storage_mode": h.ledger.StorageMode(),
368-
"init_error": h.ledgerInitError,
369-
"error": ledgerErr,
371+
"has_error": strings.TrimSpace(ledgerErr) != "" || strings.TrimSpace(h.ledgerInitError) != "",
370372
},
371373
}
372374

@@ -390,15 +392,17 @@ func (h *Handler) ReadinessCheck(w http.ResponseWriter, r *http.Request) {
390392
w.Header().Set("X-API-Version", "v1")
391393
w.Header().Set("Content-Type", "application/json")
392394
w.WriteHeader(status)
395+
// Error details are omitted from this unauthenticated endpoint to avoid
396+
// leaking internal connection strings or credentials. Full details are
397+
// available on the auth-protected /api/v1/ledger endpoint.
393398
_ = json.NewEncoder(w).Encode(map[string]interface{}{
394399
"ready": ready,
395400
"service": "sovereign-map-fl",
396401
"time": time.Now().UTC().Format(time.RFC3339),
397402
"ledger": map[string]interface{}{
398403
"ready": ledgerReady,
399404
"storage_mode": h.ledger.StorageMode(),
400-
"init_error": h.ledgerInitError,
401-
"error": ledgerErr,
405+
"has_error": strings.TrimSpace(ledgerErr) != "" || strings.TrimSpace(h.ledgerInitError) != "",
402406
},
403407
})
404408
}
@@ -1041,11 +1045,14 @@ func (h *Handler) GetCapabilities(w http.ResponseWriter, r *http.Request) {
10411045
"mohawk_ledger_events_total",
10421046
"mohawk_ledger_entries",
10431047
},
1048+
// Error details are omitted from this unauthenticated endpoint to avoid
1049+
// leaking internal connection strings or credentials. Full details are
1050+
// available on the auth-protected /api/v1/ledger endpoint.
10441051
"ledger_state": map[string]interface{}{
10451052
"entries": h.ledger.Len(),
10461053
"capacity": h.ledger.Capacity(),
10471054
"storage_mode": h.ledger.StorageMode(),
1048-
"init_error": h.ledgerInitError,
1055+
"has_error": strings.TrimSpace(h.ledgerInitError) != "",
10491056
},
10501057
},
10511058
}

internal/api/handlers_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -663,8 +663,8 @@ func TestCockroachBackendFallbackMetadata(t *testing.T) {
663663
if ledgerState["storage_mode"] != "cockroach-compatible-inmemory" {
664664
t.Fatalf("storage_mode = %v, want cockroach-compatible-inmemory fallback", ledgerState["storage_mode"])
665665
}
666-
if ledgerState["init_error"] == "" {
667-
t.Fatalf("init_error should be populated for fallback: %v", ledgerState)
666+
if ledgerState["has_error"] != true {
667+
t.Fatalf("has_error should be true for fallback: %v", ledgerState)
668668
}
669669
}
670670

internal/api/ledger_sql.go

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -201,19 +201,29 @@ func (l *SQLProofLedger) RecordWithOptions(eventType string, proofBytes []byte,
201201
return entry, false
202202
}
203203

204+
// Entries returns ledger rows in ascending order. In SQL mode, l.cap limits only
205+
// how many rows are returned by this query; it does not prune or retain rows in
206+
// mohawk_ledger_entries. SQL retention must be managed externally (for example,
207+
// by a database TTL policy or cleanup job).
204208
func (l *SQLProofLedger) Entries() []LedgerEntry {
205209
query := `SELECT id, entry_id, stream_id, seq_no, created_at, event_type, proof_hash, prev_hash, entry_hash, idempotency_key, role, accepted, latency_ms, error_text
206210
FROM mohawk_ledger_entries ORDER BY id DESC`
207211
args := []interface{}{}
208212
if l.cap > 0 {
213+
// cap is a read/query limit only; it does not enforce SQL row retention.
209214
query += ` LIMIT $1`
210215
args = append(args, l.cap)
211216
}
212217
rows, err := l.db.Query(query, args...)
213218
if err != nil {
214219
return []LedgerEntry{}
215220
}
216-
defer rows.Close()
221+
defer func() {
222+
if closeErr := rows.Close(); closeErr != nil {
223+
// Preserve existing method contract (best-effort, empty-on-error behavior).
224+
_ = closeErr
225+
}
226+
}()
217227

218228
entries := make([]LedgerEntry, 0)
219229
for rows.Next() {
@@ -257,7 +267,12 @@ func (l *SQLProofLedger) Checkpoints() []LedgerCheckpoint {
257267
if err != nil {
258268
return []LedgerCheckpoint{}
259269
}
260-
defer rows.Close()
270+
defer func() {
271+
if closeErr := rows.Close(); closeErr != nil {
272+
// Preserve existing method contract (best-effort, empty-on-error behavior).
273+
_ = closeErr
274+
}
275+
}()
261276

262277
out := make([]LedgerCheckpoint, 0)
263278
for rows.Next() {

internal/crypto/secure_comm_test.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
package crypto
44

55
import (
6+
"bytes"
67
"crypto/ecdsa"
78
"crypto/elliptic"
89
"crypto/rand"
@@ -111,6 +112,46 @@ func TestRotateSessionKeyNoDeadlock(t *testing.T) {
111112
}
112113
}
113114

115+
func TestRotateSessionKeyReestablishesSharedSecret(t *testing.T) {
116+
alice, _ := NewSecureChannel()
117+
bob, _ := NewSecureChannel()
118+
_ = alice.RegisterPeer("bob", bob.publicKey)
119+
120+
if _, err := alice.EncryptMessage("bob", []byte("baseline")); err != nil {
121+
t.Fatalf("initial encrypt: %v", err)
122+
}
123+
124+
alice.mu.RLock()
125+
original := append([]byte(nil), alice.sessionKeys["bob"]...)
126+
alice.mu.RUnlock()
127+
if len(original) == 0 {
128+
t.Fatal("expected non-empty pre-rotation session key")
129+
}
130+
131+
// Simulate a bad cached key and verify rotation reconstructs the canonical key.
132+
forged := bytes.Repeat([]byte{0x42}, len(original))
133+
alice.mu.Lock()
134+
alice.sessionKeys["bob"] = append([]byte(nil), forged...)
135+
alice.mu.Unlock()
136+
137+
if err := alice.RotateSessionKey("bob"); err != nil {
138+
t.Fatalf("RotateSessionKey: %v", err)
139+
}
140+
141+
alice.mu.RLock()
142+
after := append([]byte(nil), alice.sessionKeys["bob"]...)
143+
alice.mu.RUnlock()
144+
if len(after) == 0 {
145+
t.Fatal("expected non-empty post-rotation session key")
146+
}
147+
if bytes.Equal(after, forged) {
148+
t.Fatal("expected rotation to replace forged cached key")
149+
}
150+
if !bytes.Equal(after, original) {
151+
t.Fatal("expected rotation to re-establish canonical shared secret")
152+
}
153+
}
154+
114155
// TestHandshakeVerification performs an in-memory TLS 1.3 handshake and
115156
// asserts the negotiated version is TLS 1.3.
116157
func TestHandshakeVerification(t *testing.T) {

requirements-ci.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
numpy==2.3.4
1+
numpy==2.4.4

0 commit comments

Comments
 (0)