Skip to content

Commit 6f2875a

Browse files
committed
feat: Phase 12e -- 8 new kernel primitives (dissent, recall, swarm, validator)
- canon.py: dissent_hash, dissent_resolution_chain_hash, context_reconstruction_hash, recall_scope_hash, local_dominance_hash, consensus_record_hash, swarm_inclusion_proof, validator_record_hash - canon.py: run_self_test() extended with primitive smoke-tests (order-independence, position-tamper-evidence) - standalone_verify.py: VECTOR_PACK_VERSION bumped 1.0.0 -> 1.1.0 - standalone_verify.py: 5 new standalone implementations (_sv_*) and 3 new suite handlers (dissent-hash, recall, swarm) added to _SUITE_RUNNERS - generate_vectors.py: imports extended; VECTOR_PACK_VERSION 1.0.0 -> 1.1.0 - generate_vectors.py: 5 new conformance vectors (DIS-002, RECALL-001, RECALL-002, SWARM-001, SWARM-002) -- all extended profile - AIEP-VECTORS v1.1.0: 9 core + 6 extended = 15 total -- 15/15 PASS AIEP-COMPLIANT
1 parent 34ac58c commit 6f2875a

3 files changed

Lines changed: 436 additions & 8 deletions

File tree

scripts/generate_vectors.py

Lines changed: 142 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,14 @@
88
Usage (from genome-sdk root, venv active):
99
python scripts/generate_vectors.py [--output DIR]
1010
11-
Default output: ../AIEP-VECTORS/v1.0.0/
11+
Default output: ../AIEP-VECTORS/v1.1.0/
1212
1313
Generates:
14-
9 core vectors + 1 extended vector (dissent-preservation)
14+
9 core vectors + 6 extended vectors (dissent-preservation + Phase 12e primitives)
1515
MANIFEST.json, SHA256SUMS.txt, CANON_SPEC.md copy (with provenance header)
1616
1717
Cross-check:
18-
aiep-verify --vectors <OUTPUT>/v1.0.0/ # must report AIEP-COMPLIANT v1.0.0
18+
aiep-verify --vectors <OUTPUT>/v1.1.0/ # must report AIEP-COMPLIANT v1.1.0
1919
"""
2020
from __future__ import annotations
2121

@@ -36,6 +36,11 @@
3636
concat_hash,
3737
pack_hash,
3838
negative_proof_hash,
39+
dissent_hash,
40+
context_reconstruction_hash,
41+
recall_scope_hash,
42+
consensus_record_hash,
43+
swarm_inclusion_proof,
3944
)
4045
except ImportError as exc:
4146
print(f"ERROR: Cannot import kernel: {exc}", file=sys.stderr)
@@ -45,7 +50,7 @@
4550
KERNEL_VERSION = "1.0.0"
4651
LOCKFILE_VERSION = "1.0.0"
4752
CANON_SPEC_VERSION = "1.0.0"
48-
VECTOR_PACK_VERSION = "1.0.0"
53+
VECTOR_PACK_VERSION = "1.1.0"
4954
SCHEMA_VERSION_ID = "aiep.canonical.v2.0.0"
5055
CANON_BLOBSHA = "3b100b43bf59ce6f6d35397e8c8c173a4447de5d"
5156

@@ -357,7 +362,139 @@ def build_vectors() -> list:
357362
"pack_hash_branch_LOW": ph_low,
358363
},
359364
}),
360-
]
365+
# ── Phase 12e: New kernel primitives ─────────────────────────────────────────
366+
# These vectors verify the Phase 12e kernel primitives.
367+
# profile=extended: verified by standalone_verify but not required for
368+
# the AIEP-COMPLIANT verdict (which is a minimum bar for record verification).
369+
370+
("dis-002.json", {
371+
**_meta("dis-002", "dissent-hash",
372+
"P27: dissent_hash -- position field is tamper-evident "
373+
"(\"reject\" neq \"archive\" over same inputs)",
374+
profile="extended"),
375+
"input": {
376+
"branch_id": "branch-001",
377+
"conclusion_hash": sha256_hex("conclusion-for-dis-002"),
378+
"dissenting_evidence": EV_1,
379+
"schema_version_id": sv,
380+
},
381+
"expected": {
382+
"hash_position_reject": dissent_hash(
383+
"branch-001", sha256_hex("conclusion-for-dis-002"),
384+
"reject", EV_1, sv),
385+
"hash_position_archive": dissent_hash(
386+
"branch-001", sha256_hex("conclusion-for-dis-002"),
387+
"archive", EV_1, sv),
388+
"must_differ": True,
389+
},
390+
}),
391+
392+
("recall-001.json", {
393+
**_meta("recall-001", "recall",
394+
"P22: context_reconstruction_hash is order-independent "
395+
"-- same artefacts in any order produce the same hash",
396+
profile="extended"),
397+
"input": {
398+
"artefact_hashes_order_a": [
399+
sha256_hex("artefact-A"),
400+
sha256_hex("artefact-B"),
401+
sha256_hex("artefact-C"),
402+
],
403+
"artefact_hashes_order_b": [
404+
sha256_hex("artefact-C"),
405+
sha256_hex("artefact-A"),
406+
sha256_hex("artefact-B"),
407+
],
408+
"canonical_ordering_rule": "sorted-asc",
409+
"schema_version_id": sv,
410+
},
411+
"expected": {
412+
"context_reconstruction_hash": context_reconstruction_hash(
413+
[sha256_hex("artefact-A"), sha256_hex("artefact-B"), sha256_hex("artefact-C")],
414+
"sorted-asc", sv),
415+
"both_must_equal": True,
416+
},
417+
}),
418+
419+
("recall-002.json", {
420+
**_meta("recall-002", "recall",
421+
"P83: recall_scope_hash is committed at archival time -- deterministic",
422+
profile="extended"),
423+
"input": {
424+
"archived_dissent_hash": sha256_hex("archived-dissent-for-recall-002"),
425+
"eligible_trigger_domains": ["sensor.telemetry", "control.actuator"],
426+
"plausibility_threshold_str": "0.7",
427+
"schema_version_id": sv,
428+
},
429+
"expected": {
430+
"recall_scope_hash": recall_scope_hash(
431+
sha256_hex("archived-dissent-for-recall-002"),
432+
["sensor.telemetry", "control.actuator"],
433+
"0.7", sv),
434+
},
435+
}),
436+
437+
("swarm-001.json", {
438+
**_meta("swarm-001", "swarm",
439+
"P90: consensus_record_hash is order-independent "
440+
"-- contributing_hashes in any order produce the same hash",
441+
profile="extended"),
442+
"input": {
443+
"branch_id": "branch-001",
444+
"global_dominance_state": "HIGH",
445+
"contributing_hashes_order_a": [
446+
sha256_hex("node-A-dominance"),
447+
sha256_hex("node-B-dominance"),
448+
],
449+
"contributing_hashes_order_b": [
450+
sha256_hex("node-B-dominance"),
451+
sha256_hex("node-A-dominance"),
452+
],
453+
"schema_version_id": sv,
454+
},
455+
"expected": {
456+
"consensus_record_hash": consensus_record_hash(
457+
"branch-001", "HIGH",
458+
[sha256_hex("node-A-dominance"), sha256_hex("node-B-dominance")],
459+
sv),
460+
"both_must_equal": True,
461+
},
462+
}),
463+
464+
("swarm-002.json", {
465+
**_meta("swarm-002", "swarm",
466+
"P90: swarm_inclusion_proof -- exclusion of a dissenting node "
467+
"is cryptographically committed and detectable",
468+
profile="extended"),
469+
"input": {
470+
"consensus_record_hash": consensus_record_hash(
471+
"branch-001", "HIGH",
472+
[sha256_hex("node-A-dominance"), sha256_hex("node-B-dominance")],
473+
sv),
474+
"contributing_hashes": [
475+
sha256_hex("node-A-dominance"),
476+
sha256_hex("node-B-dominance"),
477+
],
478+
"excluded_hashes": [
479+
sha256_hex("node-C-dissent"),
480+
],
481+
"exclusion_reasons": {
482+
sha256_hex("node-C-dissent"): "plausibility below threshold",
483+
},
484+
"schema_version_id": sv,
485+
},
486+
"expected": {
487+
"swarm_inclusion_proof": swarm_inclusion_proof(
488+
consensus_record_hash(
489+
"branch-001", "HIGH",
490+
[sha256_hex("node-A-dominance"), sha256_hex("node-B-dominance")],
491+
sv),
492+
[sha256_hex("node-A-dominance"), sha256_hex("node-B-dominance")],
493+
[sha256_hex("node-C-dissent")],
494+
{sha256_hex("node-C-dissent"): "plausibility below threshold"},
495+
sv),
496+
},
497+
}), ]
361498

362499

363500
# ── File writing helpers ──────────────────────────────────────────────────────

src/aiep_genome/kernel/canon.py

Lines changed: 142 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,133 @@ def is_genesis(digest: str) -> bool:
188188
return digest == _GENESIS_SENTINEL
189189

190190

191+
# ── Phase 12e: New kernel primitives ─────────────────────────────────────────
192+
# Added after Phase 12d conformance hardening. All functions use concat_hash
193+
# (R7 length-prefixed) for collision resistance. All are kernel-level primitives
194+
# with conformance vectors in AIEP-VECTORS.
195+
196+
def dissent_hash(branch_id: str, conclusion_hash: str, position: str,
197+
dissenting_evidence: list, schema_version_id: str) -> str:
198+
"""
199+
P27: Cryptographic commitment of a dissent record.
200+
position must be one of: "reject" | "escalate" | "archive"
201+
Different positions over the same evidence produce different hashes —
202+
the dissent position is itself tamper-evident.
203+
"""
204+
return concat_hash(
205+
branch_id, conclusion_hash, position,
206+
canonical_json(dissenting_evidence), schema_version_id
207+
)
208+
209+
210+
def dissent_resolution_chain_hash(original_dissent_hash: str, resolution_hash: str,
211+
counter_resolution_hash: str,
212+
schema_version_id: str) -> str:
213+
"""
214+
P27: Resolution of dissent is itself subject to dissent.
215+
counter_resolution_hash is "NONE" if the resolution was not challenged.
216+
The chain terminates — and the termination is a NegativeProofRecord —
217+
when no counter-resolution arrives within the declared temporal window.
218+
"""
219+
return concat_hash(
220+
original_dissent_hash, resolution_hash,
221+
counter_resolution_hash or "NONE",
222+
schema_version_id
223+
)
224+
225+
226+
def context_reconstruction_hash(artefact_hashes: list, canonical_ordering_rule: str,
227+
schema_version_id: str) -> str:
228+
"""
229+
P22: Deterministic context reconstruction.
230+
Same artefacts + same ordering rule = same hash on any node, everywhere.
231+
Artefacts are sorted before hashing — insertion order does not matter.
232+
"""
233+
ordered = sorted(artefact_hashes)
234+
return concat_hash(canonical_json(ordered), canonical_ordering_rule, schema_version_id)
235+
236+
237+
def recall_scope_hash(archived_dissent_hash: str, eligible_trigger_domains: list,
238+
plausibility_threshold_str: str, schema_version_id: str) -> str:
239+
"""
240+
P83: The RecallScope is itself a cryptographic commitment.
241+
Two nodes must agree on this hash before either can initiate recall.
242+
plausibility_threshold_str: canonical string form of the threshold
243+
(use _canon_number() or str(Decimal) — do NOT pass a raw float).
244+
The conditions for recall are committed at the moment of archival, not at recall time.
245+
"""
246+
return concat_hash(
247+
archived_dissent_hash,
248+
canonical_json(sorted(eligible_trigger_domains)),
249+
plausibility_threshold_str,
250+
schema_version_id
251+
)
252+
253+
254+
def local_dominance_hash(evidence_weight_vector: dict, node_id: str,
255+
schema_version_id: str) -> str:
256+
"""
257+
P90: Each node's evidence-weighted contribution to swarm consensus.
258+
evidence_weight_vector: {evidence_id: weight_str} — values as canonical strings.
259+
"""
260+
return concat_hash(
261+
canonical_json(evidence_weight_vector),
262+
node_id, schema_version_id
263+
)
264+
265+
266+
def consensus_record_hash(branch_id: str, global_dominance_state: str,
267+
contributing_hashes: list, schema_version_id: str) -> str:
268+
"""
269+
P90: Verifiable swarm consensus commitment.
270+
contributing_hashes is sorted before hashing — the order in which nodes
271+
responded does not affect the consensus hash. Two nodes that receive the
272+
same set of local_dominance_hash values in any order must agree.
273+
"""
274+
return concat_hash(
275+
branch_id, global_dominance_state,
276+
canonical_json(sorted(contributing_hashes)),
277+
schema_version_id
278+
)
279+
280+
281+
def swarm_inclusion_proof(consensus_hash: str, contributing_hashes: list,
282+
excluded_hashes: list, exclusion_reasons: dict,
283+
schema_version_id: str) -> str:
284+
"""
285+
P90: The swarm must declare what it excluded, not just what it included.
286+
A swarm that silently excludes a dissenting node produces a different
287+
swarm_inclusion_proof than one that declared the exclusion.
288+
Suppression of dissent within a swarm is cryptographically detectable.
289+
excluded_hashes: list of local_dominance_hash values not included.
290+
exclusion_reasons: {hash: reason_string} for each excluded hash.
291+
"""
292+
return concat_hash(
293+
consensus_hash,
294+
canonical_json(sorted(contributing_hashes)),
295+
canonical_json(sorted(excluded_hashes)),
296+
canonical_json(exclusion_reasons),
297+
schema_version_id
298+
)
299+
300+
301+
def validator_record_hash(monitored_record_hash: str, rule_violated: str,
302+
violation_evidence: str, validator_version: str,
303+
schema_version_id: str) -> str:
304+
"""
305+
P01R: The validator's own verdict is an AIEP-committed record.
306+
The monitoring operates under the protocol it enforces — recursive closure.
307+
A validator that suppresses violations produces a different hash than one
308+
that reports them. Suppression is detectable.
309+
rule_violated: e.g. "CC-005", "R5", "R1" — or "NONE" for a clean record.
310+
"""
311+
return concat_hash(
312+
monitored_record_hash, rule_violated,
313+
violation_evidence, validator_version,
314+
schema_version_id
315+
)
316+
317+
191318
def run_self_test() -> bool:
192319
"""Run built-in determinism self-test. Returns True if all pass."""
193320
# R1 key ordering
@@ -206,6 +333,19 @@ def run_self_test() -> bool:
206333
# Known SHA-256 vector
207334
empty_hash = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
208335
assert sha256_hex("") == empty_hash, "known vector FAIL"
209-
# Known deterministic trace vectors from README
210-
assert sha256_hex(canonical_json({"b": 2, "a": 1})) == sha256_hex(canonical_json({"a": 1, "b": 2}))
336+
# Phase 12e — new primitives smoke-test
337+
_sv = "aiep.canonical.v2.0.0"
338+
_dh = dissent_hash("branch-001", "a" * 64, "reject", [], _sv)
339+
assert len(_dh) == 64, "dissent_hash length FAIL"
340+
assert dissent_hash("branch-001", "a" * 64, "reject", [], _sv) == _dh, "dissent_hash non-deterministic"
341+
assert dissent_hash("branch-001", "a" * 64, "archive", [], _sv) != _dh, "dissent_hash position insensitive"
342+
_crh = context_reconstruction_hash(["h1", "h2", "h3"], "sorted-asc", _sv)
343+
assert _crh == context_reconstruction_hash(["h3", "h1", "h2"], "sorted-asc", _sv), "context_reconstruction_hash order FAIL"
344+
_ldh_a = local_dominance_hash({"ev-001": "0.8"}, "node-A", _sv)
345+
_ldh_b = local_dominance_hash({"ev-001": "0.8"}, "node-B", _sv)
346+
assert _ldh_a != _ldh_b, "local_dominance_hash node collision"
347+
_crec = consensus_record_hash("branch-001", "HIGH", [_ldh_a, _ldh_b], _sv)
348+
_crec2 = consensus_record_hash("branch-001", "HIGH", [_ldh_b, _ldh_a], _sv)
349+
assert _crec == _crec2, "consensus_record_hash order-dependent FAIL"
211350
return True
351+

0 commit comments

Comments
 (0)