@@ -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+
191318def 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