arkhe-forge-core 0.13.0

L1 primitives for ArkheForge Runtime: Core 5 (User / Actor / Space / Entry / Activity) + ShellBrand invariant-lifetime isolation + deterministic entity-id derivation. Pure compute, no I/O.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
//! `ArkheEvent` sealed trait + Core Event catalog.
//!
//! The trait is the runtime's wire-contract marker — `#[derive(ArkheEvent)]`
//! in `arkhe-forge-macros` is the only way to satisfy it. The catalog in
//! this module defines all fourteen Core-range Events
//! (`0x0003_0F01..=0x0003_0F0E`): `HookModuleRegister` anchors hook-module
//! ingestion receipts, `ObserverQuarantine` anchors observer-host trap
//! quarantines, and the `ReplicaIdAllocation` + `AuditReceiptKeyPolicy`
//! pair reserves the forward-looking event surface for federation /
//! long-term audit activation.
//!
//! ## Forward-looking events — 0-emission posture
//!
//! These events are **define-only**: type + `ArkheEvent`
//! derive + `TypeCode` reservation, but **no production code path emits
//! them**. A 3-layer 0-emission defense:
//!
//! - **(a) `emit()` not called** — runtime never invokes
//!   `emit_event::<…>` for either type. A workspace grep test verifies
//!   0 production occurrences.
//! - **(b) Cargo feature gate on type definition** — each type sits
//!   behind a semantic feature flag (`federation-archive-hardened` /
//!   `audit-receipt-key-identified`) so default builds do not even
//!   compile the type.
//! - **(c) Registry test under default features** — the runtime registry
//!   inventory does not contain the reserved TypeCodes when the feature
//!   gates are off.

use arkhe_kernel::abi::{ExternalId, Tick, TypeCode};
use bytes::Bytes;
use serde::{Deserialize, Serialize};

use crate::actor::ActorId;
use crate::brand::ShellId;
use crate::component::BoundedString;
use crate::pii::DekId;
use crate::user::UserId;
// `ArkheEvent` here refers to the runtime-derive macro (re-exported by the
// crate root from `arkhe-forge-macros`); the type-level `ArkheEvent` trait
// defined below lives in the trait namespace, so both names coexist.
use crate::ArkheEvent;

/// Sealed marker trait for runtime Event types. Implementations come only
/// from `#[derive(ArkheEvent)]`.
pub trait ArkheEvent:
    crate::__sealed::__Sealed + Serialize + for<'de> Deserialize<'de> + 'static
{
    /// Runtime `TypeCode` registry pin — Core Events live in
    /// `0x0003_0F00..=0x0003_FFFF` (TypeCode sub-range split).
    const TYPE_CODE: u32;

    /// Monotone schema version — same rules as `ArkheComponent`.
    const SCHEMA_VERSION: u16;

    /// Convenience `TypeCode` accessor.
    fn type_code() -> TypeCode {
        TypeCode(Self::TYPE_CODE)
    }
}

// ===================== Support types =====================

/// Runtime SemVer — fixed-layout 3-tuple, postcard-stable.
///
/// `semver` crate's pre-release / build metadata strings are variable-width;
/// the Runtime reserves a minimal 6-byte canonical shape instead.
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Serialize, Deserialize)]
pub struct SemVer {
    /// Major version.
    pub major: u16,
    /// Minor version.
    pub minor: u16,
    /// Patch version.
    pub patch: u16,
}

impl SemVer {
    /// Construct from components.
    #[inline]
    #[must_use]
    pub const fn new(major: u16, minor: u16, patch: u16) -> Self {
        Self {
            major,
            minor,
            patch,
        }
    }
}

/// Runtime-only wire-format class tag for audit receipts.
///
/// Distinct from L0 `arkhe_kernel::persist::SignatureClass` (which
/// holds key material). The L0 type is unserializable by design; this type
/// is the serializable projection.
#[non_exhaustive]
#[repr(u8)]
#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
pub enum RuntimeSignatureClass {
    /// No signature attached.
    None = 0,
    /// Classical Ed25519.
    Ed25519 = 1,
    /// Post-quantum ML-DSA-65 (Dilithium, FIPS 204).
    MlDsa65 = 2,
    /// Hybrid Ed25519 + ML-DSA-65 dual-sign.
    Hybrid = 3,
}

/// Compliance tier classifier — crypto-erasure protection level
/// Compliance tier indicator.
#[non_exhaustive]
#[repr(u8)]
#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
pub enum ComplianceTier {
    /// Tier-0 — software KEK (dev / non-production).
    Tier0 = 0,
    /// Tier-1 — single KMS free-tier.
    Tier1 = 1,
    /// Tier-2 — production Multi-KMS + threshold HSM (t-of-n Shamir).
    Tier2 = 2,
}

/// Trap classification for [`ObserverQuarantine`] — surfaced into the
/// chain-anchored receipt so replay + audit can distinguish each
/// sandbox-boundary failure mode.
///
/// **Wire-stable enum** (mirrors `RuntimeSignatureClass` / `ComplianceTier`):
/// `#[repr(u8)]` + `#[non_exhaustive]` so additive expansion is
/// non-breaking. Each variant has a fixed discriminant so the postcard
/// wire format stays stable across schema-version bumps.
///
/// Mirrored as a host-internal type by `arkhe_forge_platform::observer_host`
/// (re-exports this same enum). Single source of truth lives here in
/// `arkhe-forge-core` because the value enters the L0 chain via the
/// `ObserverQuarantine` event.
#[non_exhaustive]
#[repr(u8)]
#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
pub enum ObserverTrapClass {
    /// wasm panic / cranelift trap — observer code itself faulted.
    Panic = 0,
    /// Fuel exhaustion — observer exceeded the per-invocation budget.
    BudgetExceeded = 1,
    /// Observer attempted to call a host-fn for which the active
    /// per-invocation capability set lacks the matching token.
    CapabilityDenied = 2,
    /// Catch-all for cranelift trap variants not classified above
    /// (incl. operator host-config errors like "no capability impl
    /// registered" — distinguished only at audit-log granularity).
    Other = 3,
}

/// Progress scope selector for multi-region / multi-KMS erasure progress
/// (TypeCode reservation).
#[non_exhaustive]
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub enum ProgressScope {
    /// Region scope — geographic / cloud region identifier.
    Region(BoundedString<64>),
    /// KMS identifier scope.
    KmsIdentifier(BoundedString<64>),
}

// ===================== Core Events =====================

/// `RuntimeBootstrap` — chain-anchored bootstrap receipt (the E12 axiom).
///
/// Emitted at instance first-tick, manifest change, and runtime semver bump.
/// The `manifest_digest` + `typecode_pins` pair is how WAL replay validates
/// that the runtime environment matches what produced the log.
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, ArkheEvent)]
#[arkhe(type_code = 0x0003_0F01, schema_version = 1)]
pub struct RuntimeBootstrap {
    /// Wire schema version.
    pub schema_version: u16,
    /// L0 kernel semver at the bootstrap tick.
    pub l0_semver: SemVer,
    /// Runtime semver at the bootstrap tick.
    pub runtime_semver: SemVer,
    /// Canonical BLAKE3 digest of the manifest TOML.
    pub manifest_digest: [u8; 32],
    /// Active TypeCode registry snapshot — derive injects
    /// canonical ascending sort before serialize.
    #[arkhe(canonical_sort)]
    pub typecode_pins: Vec<TypeCode>,
    /// Tick at which bootstrap was recorded.
    pub bootstrap_tick: Tick,
}

/// `UserErasureScheduled` — GDPR erasure lease accepted; cascade observer
/// will complete the crypto-shred.
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, ArkheEvent)]
#[arkhe(type_code = 0x0003_0F02, schema_version = 1)]
pub struct UserErasureScheduled {
    /// Wire schema version.
    pub schema_version: u16,
    /// Target User.
    pub user: UserId,
    /// Tick at which erasure was scheduled.
    pub scheduled_tick: Tick,
}

/// `UserErasureCompleted` — crypto-erasure completion receipt
/// (chain-anchored transparency).
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, ArkheEvent)]
#[arkhe(type_code = 0x0003_0F03, schema_version = 1)]
pub struct UserErasureCompleted {
    /// Wire schema version.
    pub schema_version: u16,
    /// Target User.
    pub user: UserId,
    /// Tick at which the DEK was shredded.
    pub dek_shred_tick: Tick,
    /// Signature class used for the attestation payload.
    pub attestation_class: RuntimeSignatureClass,
    /// HSM attestation bytes (typically 64 or 128 B).
    pub attestation_bytes: Bytes,
    /// Transparency-log entry index.
    pub transparency_log_index: u64,
}

/// `BackupErasurePropagated` — per-region offsite tombstone evidence
/// Restore must refuse if any region is missing.
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, ArkheEvent)]
#[arkhe(type_code = 0x0003_0F04, schema_version = 1)]
pub struct BackupErasurePropagated {
    /// Wire schema version.
    pub schema_version: u16,
    /// Target User.
    pub user: UserId,
    /// Region identifier (e.g. `"eu-west-1"`).
    pub region: BoundedString<32>,
    /// Tick at which the tombstone was applied.
    pub applied_tick: Tick,
    /// Signature class used for the receipt.
    pub receipt_class: RuntimeSignatureClass,
    /// Receipt payload bytes.
    pub receipt_bytes: Bytes,
}

/// `GdprPolicyViolation` — audit trail for an actor-originated Action that
/// targeted an ErasurePending User (L1 compute MC gate).
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, ArkheEvent)]
#[arkhe(type_code = 0x0003_0F05, schema_version = 1)]
pub struct GdprPolicyViolation {
    /// Wire schema version.
    pub schema_version: u16,
    /// Acting actor.
    pub actor: ActorId,
    /// Tick at which the violating Action was attempted.
    pub attempted_tick: Tick,
    /// TypeCode of the rejected Action.
    pub action_type_code: TypeCode,
}

/// `SignatureClassPolicy` — chain-anchored shell audit signature policy
/// (the E13 axiom). Downgrade-resistant by construction.
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, ArkheEvent)]
#[arkhe(type_code = 0x0003_0F06, schema_version = 1)]
pub struct SignatureClassPolicy {
    /// Wire schema version.
    pub schema_version: u16,
    /// Shell for which this policy applies.
    pub shell_id: ShellId,
    /// Required signature class.
    pub class: RuntimeSignatureClass,
    /// Tick at which the policy becomes effective.
    pub effective_tick: Tick,
}

/// `CrossShellActivity` — audit trail for a replay/admin path that
/// observed a record whose `shell_id` mismatched the actor's shell
/// (E-act-2 dual-tier RA side).
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, ArkheEvent)]
#[arkhe(type_code = 0x0003_0F07, schema_version = 1)]
pub struct CrossShellActivity {
    /// Wire schema version.
    pub schema_version: u16,
    /// Acting actor.
    pub actor: ActorId,
    /// Shell the target entity actually belongs to.
    pub target_shell_id: ShellId,
    /// Shell the record claimed the activity belongs to.
    pub record_shell_id: ShellId,
    /// Tick at which the mismatch was detected.
    pub detected_tick: Tick,
}

/// `PerRegionErasureProgress` — multi-region or multi-KMS DEK-shred progress
/// record (two-phase-commit).
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, ArkheEvent)]
#[arkhe(type_code = 0x0003_0F08, schema_version = 1)]
pub struct PerRegionErasureProgress {
    /// Wire schema version.
    pub schema_version: u16,
    /// Target User.
    pub user: UserId,
    /// Progress scope — region or KMS identifier.
    pub scope: ProgressScope,
    /// Tick at which this scope's shred completed.
    pub shred_tick: Tick,
    /// Signature class used for the attestation payload.
    pub attestation_class: RuntimeSignatureClass,
    /// HSM attestation bytes for this scope.
    pub attestation_bytes: Bytes,
}

/// `DekMigrationCompleted` — alpha→beta DEK rotation receipt
/// Emitted when `runtime-doctor pqc-reseal` or similar
/// rotation completes for a user.
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, ArkheEvent)]
#[arkhe(type_code = 0x0003_0F09, schema_version = 1)]
pub struct DekMigrationCompleted {
    /// Wire schema version.
    pub schema_version: u16,
    /// Target User.
    pub user: UserId,
    /// Previous DEK identifier.
    pub old_dek_id: DekId,
    /// New DEK identifier after rotation.
    pub new_dek_id: DekId,
    /// Tick at which the migration completed.
    pub migrated_tick: Tick,
}

/// `ComplianceTierChange` — operator-driven Tier transition record
/// Compliance tier indicator.
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, ArkheEvent)]
#[arkhe(type_code = 0x0003_0F0A, schema_version = 1)]
pub struct ComplianceTierChange {
    /// Wire schema version.
    pub schema_version: u16,
    /// Previous compliance tier.
    pub old_tier: ComplianceTier,
    /// New compliance tier.
    pub new_tier: ComplianceTier,
    /// Tick at which the transition becomes effective.
    pub effective_tick: Tick,
    /// External identity of the operator who authorized the change.
    pub operator: ExternalId,
}

/// `HookModuleRegister` — chain-anchored Hook host v2 module-registration
/// receipt (E14.L2 axiom).
///
/// Emitted by the wasmtime hook host on every successful
/// `register_module(bytes, expected_digest)`. Pairs the operator's
/// manifest digest (which pins the expected module digest) with the
/// actually-registered module digest — replay validates the host
/// instantiated the module the manifest demanded, not a substitute.
///
/// **3-tier ingestion** anchored here:
///
/// - **Tier 1 (BLAKE3 digest pin)** — `module_digest` matches the value
///   the operator pinned in `manifest_digest`-anchored manifest TOML.
///   Catches operator config typos + accidental file substitution.
///   This tier ships fully.
/// - **Tier 2 (sigstore sign-before-load)** — recorded via
///   `attestation_class` field; the verification closure is provided
///   by an integration layer above this crate. Tier 1 alone is
///   sufficient for the runtime contract: operator config is the trust
///   root.
/// - **Tier 3 (cargo-vet provenance)** — build-time check; runtime only
///   records the attestation hash for chain-anchored audit.
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, ArkheEvent)]
#[arkhe(type_code = 0x0003_0F0B, schema_version = 1)]
pub struct HookModuleRegister {
    /// Wire schema version.
    pub schema_version: u16,
    /// BLAKE3 digest of the manifest TOML that pinned the expected
    /// module digest. Replay uses this to verify the manifest itself
    /// was unchanged between registration time and replay time.
    ///
    /// **Caller responsibility**: this field is host-side recorded but
    /// NOT host-side enforced. The integration layer
    /// (`arkhe-forge-platform/src/manifest.rs`) is responsible for
    /// hashing the operator's manifest TOML and passing the result
    /// through to the event emission, alongside the manifest-signature
    /// verification closure that makes the field cryptographically
    /// meaningful.
    pub manifest_digest: [u8; 32],
    /// BLAKE3 digest of the registered wasm module bytes. Equals the
    /// `expected_digest` parameter the operator passed; recorded so
    /// replay can re-verify the module bytes against the same hash.
    pub module_digest: [u8; 32],
    /// Tick at which the module was registered.
    pub register_tick: Tick,
    /// Attestation class signalling Tier 2/3 presence. The default
    /// path is [`RuntimeSignatureClass::None`] (Tier 1 BLAKE3 digest
    /// pin only); Tier 2 sigstore integrations set the field to
    /// `Ed25519` / `MlDsa65` / `HybridEd25519MlDsa65` once a
    /// verification closure is wired in.
    ///
    /// **Semantics distinction**: in this `HookModuleRegister` context
    /// `None` means "Tier 1 BLAKE3 digest pin only; no Tier 2/3
    /// attestation present". Distinct from the audit-receipt
    /// `None` (= "no signature class") which carries different
    /// operational semantics. Same enum, context-specific reading.
    pub attestation_class: RuntimeSignatureClass,
}

/// `ObserverQuarantine` — chain-anchored Observer host v2 trap-
/// quarantine receipt (E15 axiom).
///
/// Emitted by the runtime supervisor when an observer wasm execution
/// trips a sandbox-boundary failure (panic / budget / capability
/// denial / other trap). The receipt anchors the operator's audit
/// trail without observer wasm authorship — chain-non-affecting
/// clause 3: the *host* supervises emission.
///
/// **Trigger boundary**: only `ObserverError` variants from the host
/// trip Quarantine emission. `CapabilityExecutionError` (PG unreachable
/// etc.) is **operational, NOT chain-anchored** — those surface via
/// metric / `runtime_doctor_journal` instead.
///
/// **Replay-side verification**: replay re-checks the
/// `observer_module_digest` against the bytes the manifest pinned at
/// registration time (mirrors `HookModuleRegister`'s replay
/// verification). Mismatch indicates manifest tampering or operator
/// mis-deployment.
///
/// **3-tier ingestion mirror**: `attestation_class` records the
/// observer module's ingestion attestation tier (Tier 1 BLAKE3 digest
/// pin active by default; Tier 2 sigstore + Tier 3 cargo-vet
/// scaffolded). Per-Quarantine the `attestation_class` reflects the
/// state at registration time so audit logs distinguish "trapped after
/// Tier-1-only ingestion" from Tier-2/3 paths.
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, ArkheEvent)]
#[arkhe(type_code = 0x0003_0F0C, schema_version = 1)]
pub struct ObserverQuarantine {
    /// Wire schema version.
    pub schema_version: u16,
    /// BLAKE3 digest of the registered observer module bytes that
    /// trapped. Equals the `expected_digest` the operator pinned at
    /// registration; recorded so replay can re-verify the module
    /// bytes against the same hash.
    pub observer_module_digest: [u8; 32],
    /// Tick at which the trap occurred + Quarantine was emitted by
    /// the host supervisor.
    pub quarantine_tick: Tick,
    /// Trap classification — distinguishes panic / budget / cap-
    /// deny / other for forensic + operator triage.
    pub trap_class: ObserverTrapClass,
    /// Attestation class signalling the Tier 2/3 ingestion state at
    /// registration time. The default path is
    /// [`RuntimeSignatureClass::None`] (Tier 1 BLAKE3 digest pin
    /// only); Tier 2/3 paths set Ed25519 / MlDsa65 / Hybrid.
    ///
    /// **Semantics distinction**: in this `ObserverQuarantine`
    /// context the value records the *observer module ingestion*
    /// attestation tier — NOT the event-signing class. The
    /// Quarantine event itself is chain-anchored under the runtime's
    /// standard signing path (E13 shell-per-tick
    /// `SignatureClassPolicy`), independent of this field.
    pub attestation_class: RuntimeSignatureClass,
}

// ============================================================================
// Cryptographic primitives — Attestation newtype +
// AttestationSignerPolicy enum.
// ============================================================================

/// Length-sealed 64-byte attestation signature wrapper.
///
/// Constructed only via [`Attestation::from_bytes`] (which takes a
/// `[u8; 64]` literal — length statically enforced by the Rust type
/// system). The wire format is a postcard length-prefixed byte
/// sequence (`Vec<u8>`-equivalent) with a strict 64-byte length check
/// on deserialize. This combination produces a *length-sealed* type:
///
/// - **Constructor side**: any caller producing an `Attestation` does
///   so via the `[u8; 64]` constructor — the type system rejects any
///   other byte width at compile time.
/// - **Deserialize side**: any wire bytes whose payload length is
///   not exactly 64 produce a `serde::de::Error`, never a panic.
///
/// **Why a custom serde impl** (rather than `#[derive]` over `[u8; 64]`):
/// serde's stock array deserializer caps at 32 bytes — the L0
/// `WalRecord.signature` workaround uses `Vec<u8>` with length
/// validation at the application layer. `Attestation` lifts the
/// length invariant from convention to the type itself: any value of
/// type `Attestation` that exists has 64 bytes, and the Deserialize
/// impl is the exhaustive admission check.
///
/// Used by `ReplicaIdAllocation::registry_attestation` and
/// `AuditReceiptKeyPolicy::attestation`.
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Attestation {
    /// 64-byte payload. Private to enforce the constructor invariant —
    /// any `Attestation` value that exists has `inner.len() == 64`.
    inner: Vec<u8>,
}

impl Attestation {
    /// Construct an [`Attestation`] from a fixed 64-byte signature.
    ///
    /// The `[u8; 64]` parameter type makes the length invariant
    /// statically enforced by the Rust type system — any caller
    /// passing a non-64-byte input is rejected at compile time.
    #[must_use]
    pub fn from_bytes(bytes: [u8; 64]) -> Self {
        Self {
            inner: bytes.to_vec(),
        }
    }

    /// Borrow the underlying 64-byte payload as a slice.
    #[must_use]
    pub fn as_bytes(&self) -> &[u8] {
        &self.inner
    }
}

impl Serialize for Attestation {
    /// Serializes as the underlying `Vec<u8>` (postcard length-prefix
    /// plus bytes). Wire format is identical to a bare `Vec<u8>` of
    /// length 64, so the wire baseline is preserved byte-for-byte.
    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
        self.inner.serialize(serializer)
    }
}

impl<'de> Deserialize<'de> for Attestation {
    /// Deserializes from a `Vec<u8>` and rejects (with `serde::de::Error`,
    /// never a panic) any payload whose length is not exactly 64. This
    /// makes `Attestation` the single admission check for the 64-byte
    /// invariant on the wire side.
    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
        let inner: Vec<u8> = Vec::deserialize(deserializer)?;
        if inner.len() != 64 {
            return Err(serde::de::Error::custom(format!(
                "Attestation: expected 64 bytes, got {}",
                inner.len()
            )));
        }
        Ok(Self { inner })
    }
}

/// Signer policy for `AuditReceiptKeyPolicy::attestation`.
///
/// Each `AuditReceiptKeyPolicy` entry's attestation signature binds
/// the inventory entry to *some* signing authority — but "which
/// authority" is an operator-policy choice the runtime merely records.
/// Three variants cover the expected operator topologies;
/// `#[non_exhaustive]` lets additive variants land without breaking
/// existing wire bytes.
///
/// The enum is paired with `AuditReceiptKeyPolicy::attestation` at
/// the same struct level — currently no other event references it,
/// so cohesion is preferred over abstraction. If a second user
/// emerges, the enum can be lifted to a shared type with no
/// wire-format change (additive non-breaking refactor).
///
/// **`Copy` derive — forward-compat constraint**: the derive
/// constrains future variants to be field-less. A variant carrying
/// data (e.g., `HardwareAttestation { tpm_version: u32 }` or
/// threshold-signature parameters) would require the `Copy` derive
/// to be removed, which is a breaking API change. Field-less policy
/// reservation is the contract; data-bearing variants would arrive
/// alongside the `Copy` removal as a coordinated breaking change.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub enum AttestationSignerPolicy {
    /// Successor key signed by predecessor key — rotation chain
    /// integrity. The recipient verifies the attestation against
    /// the predecessor entry's `public_key`.
    Predecessor,
    /// Direct signature by an operator-root authority (HW-signed
    /// or air-gapped key per `docs/release-keys.md §3` co-custody).
    /// The recipient verifies against the operator-root public key
    /// pinned in the runtime's release-keys metadata.
    OperatorRoot,
    /// Genesis self-signed proof-of-possession — the signing key
    /// signs its own inventory entry. Reserved for the very first
    /// inventory entry (no predecessor, no operator-root yet
    /// pinned). Recipient verification = fixed-point check against
    /// the entry's own `public_key`.
    SelfSigned,
}

/// `ReplicaIdAllocation` — federation-replica registration receipt
/// **Define-only**: the type sits behind the
/// `federation-archive-hardened` Cargo feature so default builds do
/// not compile the type at all (3-layer 0-emission defense, layer
/// (b)).
///
/// Activation: when a federation registry signs off on a new replica
/// entering the federation, the runtime would emit one
/// `ReplicaIdAllocation` event into the chain so cross-replica audit
/// can trace the membership lineage. The wire surface (TypeCode +
/// schema) is reserved here. Activation gate: federation prerequisites
/// complete (archive-hardening + `SignedArkheUri` + identity
/// federation layer).
///
/// **0-emission posture**: no production code path calls
/// `emit_event::<ReplicaIdAllocation>(..)`. A workspace grep test
/// confirms zero emission sites; Cargo feature gating provides
/// compile-time exclusion.
#[cfg(feature = "federation-archive-hardened")]
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, ArkheEvent)]
#[arkhe(type_code = 0x0003_0F0D, schema_version = 1)]
pub struct ReplicaIdAllocation {
    /// Wire schema version.
    pub schema_version: u16,
    /// Federation identifier (128-bit). `[16]` is collision-resistant
    /// for any plausible federation count.
    pub federation_id: [u8; 16],
    /// Replica identifier within the federation. The width is u64
    /// (federation-scale collision-resistant — ~10^19
    /// replicas/federation, well beyond any plausible ultra-scale
    /// federation deployment). A narrower u32 would cap at ~4B
    /// replicas, an order-of-magnitude tight bound on the long tail
    /// of federated systems.
    pub replica_id: u64,
    /// 32-bit nonce drawn at allocation time, fed into the
    /// registry attestation signature alongside the (federation,
    /// replica, tick) tuple. Defends against replay of older
    /// allocation requests.
    pub allocation_nonce: u32,
    /// Tick at which the allocation became effective (chain-anchor
    /// for ordering relative to other federation events).
    pub effective_tick: Tick,
    /// Federation-registry attestation over (federation_id,
    /// replica_id, allocation_nonce, effective_tick). 64-byte
    /// signature (Ed25519 width) wrapped in [`Attestation`] —
    /// length-sealed at construction (`from_bytes([u8; 64])`) and
    /// strictly verified on deserialize (length invariant lifted from
    /// convention to type). The signer is the federation registry;
    /// the signer-policy enum reservation lives on
    /// `AuditReceiptKeyPolicy::signer_policy` (audit-receipt key
    /// rotation context where signer choice is operator-variable).
    pub registry_attestation: Attestation,
}

/// `AuditReceiptKeyPolicy` — audit-receipt key inventory + rotation
/// manifest (the E13 axiom). **Define-only**: the type sits
/// behind the `audit-receipt-key-identified` Cargo feature so default
/// builds do not compile the type (3-layer 0-emission defense, layer
/// (b)).
///
/// Activation: when the operator rotates the audit-receipt signing key
/// (or initially declares the genesis key in the
/// `docs/release-keys.md §1` inventory), the runtime would emit one
/// `AuditReceiptKeyPolicy` event into the chain so audit-trail
/// consumers can verify which key was active at which tick. The wire
/// surface is reserved here. Activation gate: operator-side carry-over
/// (g) "audit-receipt key identity declared in `docs/release-keys.md`
/// inventory anchor.
///
/// **0-emission posture**: identical to `ReplicaIdAllocation` —
/// production code path emits zero, a workspace grep test verifies,
/// Cargo feature gating closes the compile.
#[cfg(feature = "audit-receipt-key-identified")]
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, ArkheEvent)]
#[arkhe(type_code = 0x0003_0F0E, schema_version = 1)]
pub struct AuditReceiptKeyPolicy {
    /// Wire schema version.
    pub schema_version: u16,
    /// Audit-receipt key identifier. The width is `[u8; 16]` (128-bit,
    /// UUID-class collision space). A narrower 8-byte width would give
    /// only a 2^32 birthday bound, tight at federation scale where
    /// multiple operators independently mint keys; 16 bytes raises the
    /// bound to 2^64 = computationally infeasible across any plausible
    /// key population. The `docs/release-keys.md §1` inventory entry
    /// maps this identifier to the physical key material.
    pub key_id: [u8; 16],
    /// Signature class for receipts under this key. Reuses the
    /// `RuntimeSignatureClass` enum so the wire
    /// tagging is consistent with `SignatureClassPolicy` (E13).
    pub algorithm: RuntimeSignatureClass,
    /// Public-key wire bytes. Variable length to accommodate
    /// classical (Ed25519, 32 bytes), post-quantum (ML-DSA-65,
    /// ~1952 bytes), and hybrid representations. Bounded by the
    /// runtime's per-event size cap on encode.
    pub public_key: Bytes,
    /// Predecessor `key_id` if this entry succeeds an earlier key
    /// (rotation chain). `None` = genesis (first entry in the
    /// inventory) or an operator-policy-determined unrelated key.
    /// Width matches `key_id` ([u8; 16]).
    pub predecessor_key_id: Option<[u8; 16]>,
    /// Tick at which this key entry becomes effective for new
    /// audit-receipt signatures.
    pub effective_tick: Tick,
    /// Tick at which this key entry retires (no further new
    /// signatures, but historical receipts remain valid). `None`
    /// for the currently-active entry.
    pub retirement_tick: Option<Tick>,
    /// Signer policy for the [`Self::attestation`] field —
    /// declares whether the signature was produced by the
    /// predecessor key (rotation chain), an operator-root authority,
    /// or as a genesis self-signed proof-of-possession. The enum +
    /// field reservation lets emission code populate the value per
    /// operator policy without further schema work.
    /// `#[non_exhaustive]` keeps additive variants forward-compat.
    pub signer_policy: AttestationSignerPolicy,
    /// Attestation signature binding the inventory entry to the
    /// operator's signing authority. 64-byte signature (Ed25519
    /// width) wrapped in [`Attestation`] — length-sealed at
    /// construction and strictly verified on deserialize (length
    /// invariant lifted from convention to type). Recipient
    /// verification path is selected by [`Self::signer_policy`].
    pub attestation: Attestation,
}

#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
    use super::*;

    #[test]
    fn runtime_bootstrap_serde_roundtrip() {
        let rb = RuntimeBootstrap {
            schema_version: 1,
            l0_semver: SemVer::new(0, 11, 0),
            runtime_semver: SemVer::new(0, 11, 0),
            manifest_digest: [0xABu8; 32],
            typecode_pins: vec![TypeCode(0x0003_0001), TypeCode(0x0003_0002)],
            bootstrap_tick: Tick(1),
        };
        let bytes = postcard::to_stdvec(&rb).unwrap();
        let back: RuntimeBootstrap = postcard::from_bytes(&bytes).unwrap();
        assert_eq!(rb, back);
    }

    #[test]
    fn per_region_progress_with_region_scope_roundtrip() {
        let ev = PerRegionErasureProgress {
            schema_version: 1,
            user: crate::user::UserId::new(arkhe_kernel::abi::EntityId::new(42).unwrap()),
            scope: ProgressScope::Region(BoundedString::<64>::new("eu-west-1").unwrap()),
            shred_tick: Tick(100),
            attestation_class: RuntimeSignatureClass::Ed25519,
            attestation_bytes: Bytes::from_static(&[0u8; 64]),
        };
        let bytes = postcard::to_stdvec(&ev).unwrap();
        let back: PerRegionErasureProgress = postcard::from_bytes(&bytes).unwrap();
        assert_eq!(ev, back);
    }

    #[test]
    fn core_event_type_code_pins_match_spec() {
        assert_eq!(RuntimeBootstrap::TYPE_CODE, 0x0003_0F01);
        assert_eq!(UserErasureScheduled::TYPE_CODE, 0x0003_0F02);
        assert_eq!(UserErasureCompleted::TYPE_CODE, 0x0003_0F03);
        assert_eq!(BackupErasurePropagated::TYPE_CODE, 0x0003_0F04);
        assert_eq!(GdprPolicyViolation::TYPE_CODE, 0x0003_0F05);
        assert_eq!(SignatureClassPolicy::TYPE_CODE, 0x0003_0F06);
        assert_eq!(CrossShellActivity::TYPE_CODE, 0x0003_0F07);
        assert_eq!(PerRegionErasureProgress::TYPE_CODE, 0x0003_0F08);
        assert_eq!(DekMigrationCompleted::TYPE_CODE, 0x0003_0F09);
        assert_eq!(ComplianceTierChange::TYPE_CODE, 0x0003_0F0A);
        assert_eq!(HookModuleRegister::TYPE_CODE, 0x0003_0F0B);
        assert_eq!(ObserverQuarantine::TYPE_CODE, 0x0003_0F0C);
        // Forward-looking event TypeCode pins are verified only when
        // the activation feature is enabled (cfg-gate per 3-layer
        // 0-emission defense, layer (b)). The TypeCode constants in
        // `typecode::core_event` remain unconditional anchors.
        #[cfg(feature = "federation-archive-hardened")]
        assert_eq!(ReplicaIdAllocation::TYPE_CODE, 0x0003_0F0D);
        #[cfg(feature = "audit-receipt-key-identified")]
        assert_eq!(AuditReceiptKeyPolicy::TYPE_CODE, 0x0003_0F0E);
    }

    #[test]
    fn hook_module_register_serde_roundtrip() {
        let ev = HookModuleRegister {
            schema_version: 1,
            manifest_digest: [0xAAu8; 32],
            module_digest: [0xBBu8; 32],
            register_tick: Tick(123),
            attestation_class: RuntimeSignatureClass::None,
        };
        let bytes = postcard::to_stdvec(&ev).unwrap();
        let back: HookModuleRegister = postcard::from_bytes(&bytes).unwrap();
        assert_eq!(ev, back);
    }

    #[test]
    fn hook_module_register_type_code_matches_typecode_constant() {
        // The typecode.rs core_event::HOOK_MODULE_REGISTER constant and
        // the #[arkhe(type_code = ...)] derive must agree — guards against
        // accidental drift between the catalog and the struct attribute.
        assert_eq!(
            HookModuleRegister::TYPE_CODE,
            crate::typecode::core_event::HOOK_MODULE_REGISTER
        );
    }

    #[test]
    fn observer_quarantine_serde_roundtrip() {
        let ev = ObserverQuarantine {
            schema_version: 1,
            observer_module_digest: [0xCCu8; 32],
            quarantine_tick: Tick(456),
            trap_class: ObserverTrapClass::Panic,
            attestation_class: RuntimeSignatureClass::None,
        };
        let bytes = postcard::to_stdvec(&ev).unwrap();
        let back: ObserverQuarantine = postcard::from_bytes(&bytes).unwrap();
        assert_eq!(ev, back);
    }

    #[test]
    fn observer_quarantine_type_code_matches_typecode_constant() {
        assert_eq!(
            ObserverQuarantine::TYPE_CODE,
            crate::typecode::core_event::OBSERVER_QUARANTINE
        );
    }

    #[test]
    fn observer_trap_class_wire_discriminants_stable() {
        // Verify each variant's wire-stable discriminant — `#[repr(u8)]`
        // pins these so the postcard format stays bit-identical across
        // schema-version bumps. Drift here = wire breakage.
        for (variant, expected_disc) in [
            (ObserverTrapClass::Panic, 0u8),
            (ObserverTrapClass::BudgetExceeded, 1u8),
            (ObserverTrapClass::CapabilityDenied, 2u8),
            (ObserverTrapClass::Other, 3u8),
        ] {
            // Round-trip through postcard verifies the wire byte
            // matches the declared discriminant. (postcard encodes
            // unit-variant enums as a single varint of the
            // discriminant.)
            let bytes = postcard::to_stdvec(&variant).unwrap();
            assert_eq!(
                bytes,
                vec![expected_disc],
                "ObserverTrapClass::{variant:?} discriminant drift"
            );
        }
    }

    #[test]
    fn observer_quarantine_with_each_trap_class_roundtrips() {
        for trap_class in [
            ObserverTrapClass::Panic,
            ObserverTrapClass::BudgetExceeded,
            ObserverTrapClass::CapabilityDenied,
            ObserverTrapClass::Other,
        ] {
            let ev = ObserverQuarantine {
                schema_version: 1,
                observer_module_digest: [0u8; 32],
                quarantine_tick: Tick(1),
                trap_class,
                attestation_class: RuntimeSignatureClass::None,
            };
            let bytes = postcard::to_stdvec(&ev).unwrap();
            let back: ObserverQuarantine = postcard::from_bytes(&bytes).unwrap();
            assert_eq!(ev.trap_class, back.trap_class);
        }
    }

    #[test]
    fn semver_roundtrip_is_stable() {
        let v = SemVer::new(0, 11, 0);
        let a = postcard::to_stdvec(&v).unwrap();
        let b = postcard::to_stdvec(&v).unwrap();
        assert_eq!(a, b);
        let back: SemVer = postcard::from_bytes(&a).unwrap();
        assert_eq!(back, v);
    }

    // ----- Forward-looking event tests -----
    //
    // Each test is feature-gated to its activation flag (3-layer
    // 0-emission defense, layer (b)). The default-features build skips
    // these tests because the underlying type is not compiled.

    #[cfg(feature = "federation-archive-hardened")]
    #[test]
    fn replica_id_allocation_serde_roundtrip() {
        let ev = ReplicaIdAllocation {
            schema_version: 1,
            federation_id: [0xF1u8; 16],
            replica_id: 7,
            allocation_nonce: 0xCAFE_BABE,
            effective_tick: Tick(1234),
            registry_attestation: Attestation::from_bytes([0x55u8; 64]),
        };
        let bytes = postcard::to_stdvec(&ev).unwrap();
        let back: ReplicaIdAllocation = postcard::from_bytes(&bytes).unwrap();
        assert_eq!(ev, back);
    }

    /// Verify u64 width preserved for replica_id values above
    /// `u32::MAX`. Regression sentinel against any schema width
    /// change that would silently truncate the high 32 bits
    /// (federation-scale concern). Pin chosen to span the upper
    /// half of u64 so byte-level postcard varint
    /// encoding exercises the >5-byte continuation path.
    #[cfg(feature = "federation-archive-hardened")]
    #[test]
    fn replica_id_allocation_high_replica_id_preserves_u64_width() {
        let high_replica = 0x1234_5678_9ABC_DEF0u64;
        assert!(high_replica > u64::from(u32::MAX));
        let ev = ReplicaIdAllocation {
            schema_version: 1,
            federation_id: [0xA5u8; 16],
            replica_id: high_replica,
            allocation_nonce: 0xDEAD_BEEF,
            effective_tick: Tick(99_999),
            registry_attestation: Attestation::from_bytes([0x33u8; 64]),
        };
        let bytes = postcard::to_stdvec(&ev).unwrap();
        let back: ReplicaIdAllocation = postcard::from_bytes(&bytes).unwrap();
        assert_eq!(ev, back);
        assert_eq!(back.replica_id, high_replica);
    }

    #[cfg(feature = "federation-archive-hardened")]
    #[test]
    fn replica_id_allocation_type_code_matches_typecode_constant() {
        assert_eq!(
            ReplicaIdAllocation::TYPE_CODE,
            crate::typecode::core_event::REPLICA_ID_ALLOCATION
        );
    }

    #[cfg(feature = "audit-receipt-key-identified")]
    #[test]
    fn audit_receipt_key_policy_serde_roundtrip_with_genesis_entry() {
        let ev = AuditReceiptKeyPolicy {
            schema_version: 1,
            key_id: [0xABu8; 16],
            algorithm: RuntimeSignatureClass::Ed25519,
            public_key: Bytes::from_static(&[0u8; 32]),
            predecessor_key_id: None,
            effective_tick: Tick(0),
            retirement_tick: None,
            signer_policy: AttestationSignerPolicy::SelfSigned,
            attestation: Attestation::from_bytes([0x77u8; 64]),
        };
        let bytes = postcard::to_stdvec(&ev).unwrap();
        let back: AuditReceiptKeyPolicy = postcard::from_bytes(&bytes).unwrap();
        assert_eq!(ev, back);
    }

    #[cfg(feature = "audit-receipt-key-identified")]
    #[test]
    fn audit_receipt_key_policy_serde_roundtrip_with_rotation_entry() {
        let ev = AuditReceiptKeyPolicy {
            schema_version: 1,
            key_id: [0xCDu8; 16],
            algorithm: RuntimeSignatureClass::MlDsa65,
            public_key: Bytes::from_static(&[0xEEu8; 1952]), // ML-DSA-65 wire size
            predecessor_key_id: Some([0xABu8; 16]),
            effective_tick: Tick(100),
            retirement_tick: Some(Tick(1000)),
            signer_policy: AttestationSignerPolicy::Predecessor,
            attestation: Attestation::from_bytes([0x99u8; 64]),
        };
        let bytes = postcard::to_stdvec(&ev).unwrap();
        let back: AuditReceiptKeyPolicy = postcard::from_bytes(&bytes).unwrap();
        assert_eq!(ev, back);
    }

    /// Verify [u8; 16] width preserved with high-entropy distinct
    /// bytes in both `key_id` and `predecessor_key_id`. Repeated-byte
    /// literals like `[0xABu8; 16]` / `[0xCDu8; 16]` would silently
    /// round-trip through any narrower
    /// width that happens to truncate to the same repeated byte. This
    /// test pins distinct bytes per position so a regression to
    /// `[u8; 8]` would catch on the upper-half `key_id[8..16]` and
    /// `predecessor_key_id[8..16]` byte mismatch. Belt-and-suspenders
    /// against silent width truncation.
    #[cfg(feature = "audit-receipt-key-identified")]
    #[test]
    fn audit_receipt_key_policy_distinct_bytes_preserve_full_16_byte_widths() {
        let key_id: [u8; 16] = [
            0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54,
            0x32, 0x10,
        ];
        let predecessor_key_id: [u8; 16] = [
            0xDE, 0xAD, 0xBE, 0xEF, 0xCA, 0xFE, 0xBA, 0xBE, 0xF0, 0x0D, 0xFA, 0xCE, 0x12, 0x34,
            0x56, 0x78,
        ];
        let ev = AuditReceiptKeyPolicy {
            schema_version: 1,
            key_id,
            algorithm: RuntimeSignatureClass::Ed25519,
            public_key: Bytes::from_static(&[0xC3u8; 32]),
            predecessor_key_id: Some(predecessor_key_id),
            effective_tick: Tick(2_500),
            retirement_tick: Some(Tick(5_000)),
            signer_policy: AttestationSignerPolicy::OperatorRoot,
            attestation: Attestation::from_bytes([0x44u8; 64]),
        };
        let bytes = postcard::to_stdvec(&ev).unwrap();
        let back: AuditReceiptKeyPolicy = postcard::from_bytes(&bytes).unwrap();
        assert_eq!(ev, back);
        assert_eq!(back.key_id, key_id);
        assert_eq!(back.predecessor_key_id, Some(predecessor_key_id));
        // Explicit upper-half spot-check (catches truncation regression
        // even if compiler accepted [u8; 8] literal somehow).
        assert_eq!(back.key_id[8..16], key_id[8..16]);
    }

    #[cfg(feature = "audit-receipt-key-identified")]
    #[test]
    fn audit_receipt_key_policy_type_code_matches_typecode_constant() {
        assert_eq!(
            AuditReceiptKeyPolicy::TYPE_CODE,
            crate::typecode::core_event::AUDIT_RECEIPT_KEY_POLICY
        );
    }

    /// 0-emission posture confirmation (layer (a) — `emit()` not
    /// called). The `ArkheEvent` trait makes the type *eligible* for
    /// chain emission via `Op::EmitEvent`, but **no production code**
    /// calls `emit_event::<ReplicaIdAllocation>(..)` or
    /// `emit_event::<AuditReceiptKeyPolicy>(..)`. A workspace grep
    /// verification scans the source tree and asserts 0 occurrences.
    /// This test is the structural anchor — type definitions exist,
    /// but the trait constants alone do not constitute emission.
    ///
    /// Feature-gated under both activation flags so the test compiles
    /// only when the types compile (cfg-gate, layer (b)).
    #[cfg(all(
        feature = "federation-archive-hardened",
        feature = "audit-receipt-key-identified"
    ))]
    #[test]
    fn forward_looking_events_are_define_only() {
        // The TYPE_CODE constants exist + are pinned. Schema version
        // is 1 (initial wire format). No emission entry point is
        // defined for either type — verified structurally by Track
        // H.3 grep. This test is the architecture anchor.
        assert_eq!(ReplicaIdAllocation::SCHEMA_VERSION, 1);
        assert_eq!(AuditReceiptKeyPolicy::SCHEMA_VERSION, 1);
        assert_eq!(ReplicaIdAllocation::TYPE_CODE, 0x0003_0F0D);
        assert_eq!(AuditReceiptKeyPolicy::TYPE_CODE, 0x0003_0F0E);
    }

    // ----- Attestation newtype + AttestationSignerPolicy tests.
    // These are unconditional (no cfg-gate) because the types themselves
    // live unconditionally in the module — only the events that
    // *consume* them are cfg-gated under the activation flags.

    /// `Attestation` round-trips through postcard byte-identical to a
    /// bare `Vec<u8>` of length 64 (wire baseline preservation).
    #[test]
    fn attestation_serde_round_trip_preserves_64_bytes() {
        let payload: [u8; 64] = [0xA1; 64];
        let att = Attestation::from_bytes(payload);
        let bytes = postcard::to_stdvec(&att).unwrap();
        let back: Attestation = postcard::from_bytes(&bytes).unwrap();
        assert_eq!(att, back);
        assert_eq!(back.as_bytes(), &payload);
        assert_eq!(back.as_bytes().len(), 64);
    }

    /// Wire format invariance with bare `Vec<u8>` of length 64. The
    /// custom `Serialize` impl delegates to `Vec<u8>::serialize`, so
    /// an `Attestation` and an equivalent `vec![0x..; 64]` produce
    /// byte-for-byte identical postcard output. This preserves the
    /// sealed wire baseline — any emitted bytes decode equivalently
    /// whether the receiver expects `Attestation` or `Vec<u8>`.
    #[test]
    fn attestation_wire_format_byte_identical_to_vec_u8_length_64() {
        let payload: [u8; 64] = [0xC7; 64];
        let att_bytes = postcard::to_stdvec(&Attestation::from_bytes(payload)).unwrap();
        let vec_bytes = postcard::to_stdvec(&payload.to_vec()).unwrap();
        assert_eq!(att_bytes, vec_bytes);
    }

    /// `Attestation` deserialize **rejects** payloads whose length is
    /// not exactly 64 with a `serde::de::Error`, never a panic. This is
    /// the single admission check that lifts the 64-byte invariant from
    /// convention to type.
    #[test]
    fn attestation_deserialize_rejects_short_payload() {
        // Postcard-encode a 32-byte Vec<u8> (still serde-valid, but
        // shorter than the Attestation contract).
        let short_payload: Vec<u8> = vec![0xBB; 32];
        let bytes = postcard::to_stdvec(&short_payload).unwrap();
        let result: Result<Attestation, _> = postcard::from_bytes(&bytes);
        assert!(
            result.is_err(),
            "32-byte payload must be rejected as not-64-bytes"
        );
    }

    #[test]
    fn attestation_deserialize_rejects_long_payload() {
        let long_payload: Vec<u8> = vec![0xCC; 65];
        let bytes = postcard::to_stdvec(&long_payload).unwrap();
        let result: Result<Attestation, _> = postcard::from_bytes(&bytes);
        assert!(
            result.is_err(),
            "65-byte payload must be rejected as not-64-bytes"
        );
    }

    #[test]
    fn attestation_deserialize_rejects_empty_payload() {
        let empty_payload: Vec<u8> = Vec::new();
        let bytes = postcard::to_stdvec(&empty_payload).unwrap();
        let result: Result<Attestation, _> = postcard::from_bytes(&bytes);
        assert!(
            result.is_err(),
            "empty payload must be rejected as not-64-bytes"
        );
    }

    /// All three [`AttestationSignerPolicy`] variants round-trip
    /// through postcard. The variant tag is a postcard varint; an
    /// additive variant (operator-policy expansion) lands as a new
    /// tag without disturbing existing tags (`#[non_exhaustive]` on
    /// the enum + variant order preservation).
    #[test]
    fn attestation_signer_policy_round_trip_all_three_variants() {
        for variant in [
            AttestationSignerPolicy::Predecessor,
            AttestationSignerPolicy::OperatorRoot,
            AttestationSignerPolicy::SelfSigned,
        ] {
            let bytes = postcard::to_stdvec(&variant).unwrap();
            let back: AttestationSignerPolicy = postcard::from_bytes(&bytes).unwrap();
            assert_eq!(variant, back);
        }
    }
}