Skip to content

Commit d83e81e

Browse files
hyperpolymathclaude
andcommitted
feat(verisim-modular-experiment): Ed25519, multi-peer, VCL subset
All three deferred Phase-3 follow-ups complete. 126/126 assertions green across 5 test suites. Ed25519 (task 7): - impl/Crypto.jl wraps Sodium.jl libsodium bindings - VerisimCore.Store holds Ed25519KeyPair; Signature.key_id now carries 32-byte pubkey; ed25519_sign/ed25519_verify replace SHA-256 placeholders - VectorPeer and DocumentPeer hold their own independent keypairs - test_seams.jl +7 assertions: crypto round-trip, tampered-signature rejection, hash-chain integrity under real Ed25519 Multi-peer non-interference (task 8): - impl/peers/DocumentPeer.jl — second Federable shape - drift/Metrics.jl: added d_VD (TeX §4.3) and d_SD; dispatcher canonicalises {semantic, vector, document} pair ordering - test_noninterference.jl: 15 assertions — 3-way parity over (S,V), (S,D), (V,D); adding Document peer leaves (S,V) drift unchanged; Document LWW writes don't affect Vector state; independent Ed25519 keypairs per peer VCL subset (task 9): - impl/vcl/Query.jl — AST: ProofIntegrity, ProofConsistency, ProofFreshness - impl/vcl/Prover.jl — routes clauses to VerisimCore + FederationManager - impl/vcl/Parser.jl — hand-written tokeniser + recursive descent for PROOF INTEGRITY/CONSISTENCY/FRESHNESS syntax - test_vcl.jl: 32 assertions — parse+prove round-trips, tampered-chain detection, renormalisation over absent shapes, error cases Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 7ac61e8 commit d83e81e

16 files changed

Lines changed: 1145 additions & 87 deletions

File tree

verisim-modular-experiment/.machine_readable/6a2/STATE.a2ml

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
(purpose "Research experiment: can any octad shape be entirely absent (store-level)
1111
without making VCL consonance claims unsound?")
1212
(type "research-prototype")
13-
(completion-percentage 55))
13+
(completion-percentage 78))
1414

1515
(current-position
1616
(phase "phase-3-complete-seams-sorted")
@@ -63,9 +63,21 @@
6363
(description "First Federable peer: DriftProjector, CoherenceProjector, LWWAcceptor.")
6464
(completion 100))
6565
(component "julia-crypto-ed25519"
66-
(status in-progress)
67-
(description "Replacing placeholder SHA-256 with real Ed25519 via libsodium.")
68-
(completion 50))
66+
(status complete)
67+
(description "Real Ed25519 via Sodium.jl/libsodium. Signatures over provenance
68+
entries and attestations. 7 round-trip assertions in test_seams.jl.")
69+
(completion 100))
70+
(component "document-peer"
71+
(status complete)
72+
(description "Second Federable peer. Exercises multi-peer non-interference:
73+
drift pairs (S,V), (S,D), (V,D); independent Ed25519 keys.")
74+
(completion 100))
75+
(component "vcl-subset"
76+
(status complete)
77+
(description "VCLQuery AST, VCLProver evaluator, VCLParser string parser.
78+
Supports PROOF INTEGRITY, CONSISTENCY with DRIFT < θ, FRESHNESS.
79+
32 assertions in test_vcl.jl.")
80+
(completion 100))
6981
(component "documentation"
7082
(status complete)
7183
(description "README, PLAN, PROOF-NEEDS, DESIGN, OCTAD-SHAPES, CORE-CANDIDATES,
@@ -87,7 +99,17 @@
8799
(result "seam-tests"
88100
(command "julia --project=. test/test_seams.jl")
89101
(status pass)
90-
(assertions 21)))
102+
(assertions 30))
103+
(result "non-interference-tests"
104+
(command "julia --project=. test/test_noninterference.jl")
105+
(status pass)
106+
(assertions 15))
107+
(result "vcl-tests"
108+
(command "julia --project=. test/test_vcl.jl")
109+
(status pass)
110+
(assertions 32)))
111+
112+
(total-assertions 126)
91113

92114
(foldback-to-main-verisimdb
93115
(applied
@@ -98,10 +120,10 @@
98120
(item "Normalizer regeneration stub-status doc update (documented in FOLDBACK.adoc)")))
99121

100122
(critical-next-actions
101-
(action "Finish Ed25519 wiring (impl/Crypto.jl written, needs VerisimCore/VectorPeer update)")
102-
(action "Add second Federable peer (Document) for multi-peer non-interference test")
103-
(action "Write minimal VCL parser/evaluator exercising FROM FEDERATION syntax")
104-
(action "Update this STATE.a2ml after each"))
123+
(action "Phase 4 dogfood: wire KRLAdapter.jl against VerisimCore+FederationManager")
124+
(action "Phase 5 writeup: document findings, adoption guide, foldback to main verisimdb")
125+
(action "Optional: add Tensor and Spatial Federable peers for 4-peer parity coverage")
126+
(action "Optional: Zig FFI port of VerisimCore for production readiness"))
105127

106128
(session-history
107129
(session "2026-04-05"

verisim-modular-experiment/EXPLAINME.adoc

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,9 +111,22 @@ See `spec/federation-contract.adoc` for details.
111111

112112
== What's deferred past Phase 3?
113113

114-
* **Ed25519 placeholder → real** — code in `impl/Crypto.jl`, wiring into VerisimCore in progress.
115-
* **Multi-peer non-interference** — Phase 3 tested one peer; need N≥2.
116-
* **VCL parser/evaluator surface** — parity was proved at the drift layer; syntax layer is convenience.
114+
All three immediate follow-ups now complete:
115+
116+
* ✓ **Ed25519** — real Ed25519 via libsodium (Sodium.jl). `impl/Crypto.jl` +
117+
VerisimCore/VectorPeer/DocumentPeer all signed with real keypairs.
118+
* ✓ **Multi-peer non-interference** — `impl/peers/DocumentPeer.jl` registered
119+
alongside VectorPeer. 15 assertions confirm drift pairs (S,V), (S,D), (V,D)
120+
compose; each peer has an independent Ed25519 keypair.
121+
* ✓ **VCL parser/evaluator** — `impl/vcl/{Query,Prover,Parser}.jl`. Supports
122+
PROOF INTEGRITY, PROOF CONSISTENCY WITH DRIFT < θ (with OVER scope),
123+
PROOF FRESHNESS. 32 assertions.
124+
125+
Remaining:
126+
127+
* **Phase 4 dogfood** — wire `KRLAdapter.jl` against this VerisimCore.
128+
* **Phase 5 writeup** — findings document, adoption guide, main-verisimdb foldback.
129+
* **Optional Zig FFI port** — production readiness if Path B ships.
117130

118131
== What if the result had been negative?
119132

verisim-modular-experiment/docs/sessions/2026-04-05-blitz.adoc

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,13 @@ absent?") and blitz pace.
1111
* **Phase 0 — Octad archaeology:** COMPLETED.
1212
* **Phase 1 — Core identification:** COMPLETED.
1313
* **Phase 2 — Federation contract (Idris2 ABI):** COMPLETED — typechecks clean in Idris2 0.8.0.
14-
* **Phase 3 — Reference core + Vector federation (Julia):** decision gate PASSED — 70/70 assertions green. Deferred: Ed25519, multi-peer non-interference, VCL parser.
14+
* **Phase 3 — Reference core + Vector federation (Julia):** decision gate PASSED — 70/70 assertions green.
1515
* **Seam audit:** COMPLETED — `docs/SEAMS.adoc`. High+medium priority seams fixed; low-priority documented as language-idiom.
16+
* **Ed25519 wiring:** COMPLETED — real libsodium-backed signing via `impl/Crypto.jl`. Store and peers hold Ed25519 keypairs; signatures verified end-to-end.
17+
* **Multi-peer non-interference:** COMPLETED — `impl/peers/DocumentPeer.jl` as second Federable peer. 15 assertions confirm drift pairs (S,V), (S,D), (V,D) compose, independent keys, LWW isolation.
18+
* **VCL subset:** COMPLETED — `impl/vcl/{Query,Prover,Parser}.jl`. 32 assertions covering PROOF INTEGRITY, CONSISTENCY with DRIFT, FRESHNESS, parser round-trips, tamper detection.
19+
20+
**Grand total: 126 assertions green across 5 test suites.**
1621

1722
== Key findings
1823

verisim-modular-experiment/impl/FederationManager.jl

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,10 +199,12 @@ end
199199
# Duck-typed peer value retrieval. Each peer type provides a `_peer_value`
200200
# specialisation at the call site via method dispatch or via this path.
201201
function _peer_value(peer, octad_id)
202-
# Try method-based lookup; fall back to Dict access on .embeddings
203-
# for VectorPeer compatibility.
202+
# Duck-typed peer storage access. Each peer type carries its shape's
203+
# values in a named Dict field; we check the known ones.
204204
if hasfield(typeof(peer), :embeddings)
205205
return get(peer.embeddings, octad_id, nothing)
206+
elseif hasfield(typeof(peer), :documents)
207+
return get(peer.documents, octad_id, nothing)
206208
end
207209
error("FederationManager: don't know how to extract value from peer type $(typeof(peer))")
208210
end

verisim-modular-experiment/impl/VerisimCore.jl

Lines changed: 33 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,14 @@ module VerisimCore
1515

1616
using SHA
1717

18+
# Crypto module is loaded by the including script before VerisimCore
19+
# (flat-include scaffold). Reached via Main namespace.
20+
1821
export OctadId, Timestamp, Signature, SemanticBlob,
1922
ProvenanceEntry, ProvenanceChain, TemporalHistory,
2023
CoreOctad, Store,
21-
get_core, enrich!, attest, verify_attest, now_ts
24+
get_core, enrich!, attest, verify_attest, now_ts,
25+
ed25519_sign, ed25519_verify
2226

2327
# -----------------------------------------------------------------------
2428
# Primitive types (mirror Idris2 ABI)
@@ -52,12 +56,14 @@ Base.:(==)(a::Timestamp, b::Timestamp) = a.epoch_nanos == b.epoch_nanos
5256
now_ts() = Timestamp(Int64(time_ns()))
5357

5458
"""
55-
Signature placeholder. TODO(Phase 3 defaults validation): replace with
56-
Ed25519 via Nettle.jl or similar. For scaffold, using SHA-256 as a
57-
stand-in 'signature' (signer_id || payload). This is NOT cryptographic
58-
signing — it is a placeholder so the ABI shapes compile and test paths
59-
work. Cryptographic fidelity is not required for Phase 3's correctness
60-
experiment; it IS required before any production consideration.
59+
Ed25519 detached signature. Matches Idris2 ABI
60+
`Signature { keyId, sigBytes }`. In this implementation `key_id` holds
61+
the signer's Ed25519 **public key** (32 bytes); pubkeys are both
62+
compact and self-identifying, so no separate key_id registry is
63+
needed.
64+
65+
- `key_id` : 32 bytes (Ed25519 public key)
66+
- `sig_bytes`: 64 bytes (Ed25519 detached signature)
6167
"""
6268
struct Signature
6369
key_id::Vector{UInt8}
@@ -108,18 +114,18 @@ end
108114
# Store
109115
# -----------------------------------------------------------------------
110116

111-
"In-memory ephemeral Core store. Dict-backed, no persistence."
117+
"In-memory ephemeral Core store. Dict-backed, no persistence. Holds
118+
a fresh Ed25519 keypair for signing attestations and provenance entries."
112119
struct Store
113120
octads::Dict{OctadId, CoreOctad}
114-
# For placeholder signing — real impl would hold an Ed25519 keypair
115-
signing_key_id::Vector{UInt8}
121+
keypair::Any # Main.Crypto.Ed25519KeyPair (duck-typed to avoid load order)
116122
# Attestation freshness window, in nanoseconds (default: 60s)
117123
freshness_window_ns::Int64
118124
end
119125

120126
Store(; freshness_window_ns::Int64 = Int64(60_000_000_000)) = Store(
121127
Dict{OctadId, CoreOctad}(),
122-
collect(codeunits("verisim-core-test-key")),
128+
Main.Crypto.generate_keypair(),
123129
freshness_window_ns,
124130
)
125131

@@ -196,8 +202,8 @@ function enrich!(store::Store,
196202
)
197203
this_hash = sha256(to_hash)
198204

199-
# Placeholder signature — see Signature docstring.
200-
sig = placeholder_sign(store.signing_key_id, this_hash)
205+
# Real Ed25519 signature over this_hash.
206+
sig = ed25519_sign(store.keypair, this_hash)
201207

202208
push!(octad.provenance.entries, ProvenanceEntry(
203209
prev, this_hash, actor, t, sig,
@@ -215,27 +221,25 @@ function bytes2hex_summary(blob::SemanticBlob)::Vector{UInt8}
215221
end
216222

217223
"""
218-
placeholder_sign(key_id, payload) -> Signature
224+
ed25519_sign(keypair, payload) -> Signature
219225
220-
Placeholder for Ed25519 signing. Current impl: SHA-256 of
221-
(key_id || payload). NOT cryptographically sound. See Signature docstring.
226+
Produce an Ed25519 detached signature over `payload`. Returns a
227+
Signature whose `key_id` field carries the public key (32 bytes) and
228+
`sig_bytes` carries the 64-byte signature.
222229
"""
223-
function placeholder_sign(key_id::Vector{UInt8},
224-
payload::Vector{UInt8})::Signature
225-
sig = sha256(vcat(key_id, payload))
226-
Signature(key_id, sig)
230+
function ed25519_sign(keypair, payload::Vector{UInt8})::Signature
231+
sig_bytes = Main.Crypto.sign_detached(keypair, payload)
232+
Signature(copy(keypair.pk), sig_bytes)
227233
end
228234

229235
"""
230-
placeholder_verify(sig, payload) -> Bool
236+
ed25519_verify(sig, payload) -> Bool
231237
232-
Placeholder verification matching placeholder_sign. Recomputes the
233-
expected SHA-256 and compares. This provides the ABI shape; real
234-
Ed25519 verification is a Phase 4+ task.
238+
Verify an Ed25519 signature. Uses `sig.key_id` as the public key.
239+
Returns true iff the signature is valid for the given payload.
235240
"""
236-
function placeholder_verify(sig::Signature, payload::Vector{UInt8})::Bool
237-
expected = sha256(vcat(sig.key_id, payload))
238-
expected == sig.sig_bytes
241+
function ed25519_verify(sig::Signature, payload::Vector{UInt8})::Bool
242+
Main.Crypto.verify_detached(sig.key_id, sig.sig_bytes, payload)
239243
end
240244

241245
# -----------------------------------------------------------------------
@@ -256,7 +260,7 @@ function attest(store::Store, id::OctadId)
256260
# Sign a digest of (id || t || semantic summary).
257261
sem_summary = octad.semantic === nothing ? UInt8[] : bytes2hex_summary(octad.semantic)
258262
payload = vcat(octad.id.bytes, reinterpret(UInt8, [t.epoch_nanos]), sem_summary)
259-
sig = placeholder_sign(store.signing_key_id, payload)
263+
sig = ed25519_sign(store.keypair, payload)
260264
(octad, sig, t)
261265
end
262266

@@ -280,7 +284,7 @@ function verify_attest(store::Store,
280284
# Recompute expected payload and verify.
281285
sem_summary = octad.semantic === nothing ? UInt8[] : bytes2hex_summary(octad.semantic)
282286
payload = vcat(octad.id.bytes, reinterpret(UInt8, [t.epoch_nanos]), sem_summary)
283-
placeholder_verify(sig, payload)
287+
ed25519_verify(sig, payload)
284288
end
285289

286290
end # module

verisim-modular-experiment/impl/drift/Metrics.jl

Lines changed: 58 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ module Metrics
1414

1515
using SHA
1616

17-
export cosine_distance, hash_embedding, d_SV, drift
17+
export cosine_distance, hash_embedding, d_SV, d_VD, d_SD, drift
1818

1919
# -----------------------------------------------------------------------
2020
# Primitive drift functions
@@ -97,6 +97,49 @@ function d_SV(type_uris::Vector{String},
9797
cosine_distance(ref, vector_embedding)
9898
end
9999

100+
# -----------------------------------------------------------------------
101+
# Pairwise drift — Vector × Document (d_VD, TeX §4.3)
102+
# -----------------------------------------------------------------------
103+
104+
"""
105+
d_VD(vector_embedding, document_bytes) -> Float64
106+
107+
Vector-Document drift (TeX §4.3, d_{V,D}): cosine distance between
108+
stored embedding and the embedding that *would* be computed from the
109+
current document content. A drift value near 0 indicates the embedding
110+
is fresh; near 1 indicates the document has changed since the
111+
embedding was computed.
112+
"""
113+
function d_VD(vector_embedding::Vector{Float32},
114+
document_bytes::Vector{UInt8})::Float64
115+
ref = hash_embedding(document_bytes, length(vector_embedding))
116+
cosine_distance(ref, vector_embedding)
117+
end
118+
119+
# -----------------------------------------------------------------------
120+
# Pairwise drift — Semantic × Document (d_SD)
121+
# -----------------------------------------------------------------------
122+
123+
"""
124+
d_SD(semantic_type_uris, semantic_proof, document_bytes) -> Float64
125+
126+
Semantic-Document drift. Research-prototype definition: cosine distance
127+
between hash-derived embeddings of the Semantic blob and the Document
128+
bytes. Plays the role TeX §4.3 leaves implicit for this pair.
129+
"""
130+
function d_SD(type_uris::Vector{String},
131+
proof_bytes::Vector{UInt8},
132+
document_bytes::Vector{UInt8})::Float64
133+
semantic_bytes = vcat(
134+
collect(codeunits(join(type_uris, ","))),
135+
proof_bytes,
136+
)
137+
dim = 384
138+
s_ref = hash_embedding(semantic_bytes, dim)
139+
d_ref = hash_embedding(document_bytes, dim)
140+
cosine_distance(s_ref, d_ref)
141+
end
142+
100143
# -----------------------------------------------------------------------
101144
# Drift dispatcher — absent-pair convention
102145
# -----------------------------------------------------------------------
@@ -117,20 +160,26 @@ function drift(shape1::Symbol, val1,
117160
# Absent-pair convention: d(⊥, ·) = d(·, ⊥) = 0.
118161
(val1 === nothing || val2 === nothing) && return 0.0
119162

120-
# Normalise pair order so (A,B) and (B,A) dispatch the same way.
121-
if (shape1, shape2) == (:vector, :semantic)
122-
shape1, shape2 = :semantic, :vector
123-
val1, val2 = val2, val1
163+
# Normalise pair order — canonicalise so (A,B) and (B,A) dispatch the
164+
# same way. Priority order: :semantic < :vector < :document.
165+
canonical_order = Dict(:semantic => 1, :vector => 2, :document => 3)
166+
if haskey(canonical_order, shape1) && haskey(canonical_order, shape2)
167+
if canonical_order[shape1] > canonical_order[shape2]
168+
shape1, shape2 = shape2, shape1
169+
val1, val2 = val2, val1
170+
end
124171
end
125172

126173
if shape1 == :semantic && shape2 == :vector
127-
# val1 is a SemanticBlob-like (has .type_uris, .proof_bytes fields
128-
# via duck-typing); val2 is a Vector{Float32}.
129174
return d_SV(val1.type_uris, val1.proof_bytes, val2)
175+
elseif shape1 == :vector && shape2 == :document
176+
return d_VD(val1, val2)
177+
elseif shape1 == :semantic && shape2 == :document
178+
return d_SD(val1.type_uris, val1.proof_bytes, val2)
130179
end
131180

132-
error("drift: pair ($shape1, $shape2) not implemented in Phase 3 " *
133-
"(only :semantic × :vector is wired). Add it to drift/Metrics.jl.")
181+
error("drift: pair ($shape1, $shape2) not implemented. " *
182+
"Wired pairs: (S,V), (V,D), (S,D). Add to drift/Metrics.jl.")
134183
end
135184

136185
end # module

0 commit comments

Comments
 (0)