use arkhe_forge_core::context::{ActionContext, ActionError};
use arkhe_forge_core::event::{RuntimeBootstrap, SemVer};
use arkhe_kernel::abi::{Tick, TypeCode};
use serde::{Deserialize, Serialize};
pub type ManifestDigest = [u8; 32];
const DIGEST_DOMAIN: &str = "arkhe-forge-manifest-digest";
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ShellSection {
pub shell_id: String,
pub display_name: String,
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RuntimeSection {
pub runtime_max: String,
pub runtime_current: String,
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct AuditSection {
pub pii_cipher: String,
pub dek_backend: String,
pub kms_auto_promote: String,
pub signature_class: String,
pub compliance_tier: u8,
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
#[serde(default, deny_unknown_fields)]
pub struct FrontendSection {
pub tls_required: bool,
pub alpha_credential_rotation_required: bool,
}
impl Default for FrontendSection {
fn default() -> Self {
Self {
tls_required: true,
alpha_credential_rotation_required: true,
}
}
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ManifestSnapshot {
pub schema_version: u16,
pub shell: ShellSection,
pub runtime: RuntimeSection,
pub audit: AuditSection,
#[serde(default)]
pub frontend: FrontendSection,
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum ManifestError {
#[error("parse error: {0}")]
ParseError(String),
#[error("missing required field: {0}")]
MissingRequired(&'static str),
#[error("tier {tier} incompatible with backend '{backend}'")]
TierBackendMismatch {
tier: u8,
backend: String,
},
#[error("software-kek rejected: runtime_current {current} > 0.15")]
SoftwareKekProductionRefused {
current: String,
},
#[error("version mismatch: runtime_max < runtime_current")]
VersionMismatch,
#[error("unknown field: {0}")]
UnknownField(String),
#[error("invalid value for {field}: {reason}")]
InvalidValue {
field: &'static str,
reason: String,
},
}
pub struct ManifestLoader;
impl ManifestLoader {
pub fn load(toml_bytes: &[u8]) -> Result<(ManifestSnapshot, ManifestDigest), ManifestError> {
let text = core::str::from_utf8(toml_bytes)
.map_err(|e| ManifestError::ParseError(format!("utf-8: {}", e)))?;
let snapshot: ManifestSnapshot =
toml::from_str(text).map_err(|e| ManifestError::ParseError(e.to_string()))?;
Self::validate(&snapshot)?;
let digest = Self::canonical_digest(&snapshot)?;
Ok((snapshot, digest))
}
pub fn validate(m: &ManifestSnapshot) -> Result<(), ManifestError> {
if m.shell.shell_id.len() > 32 {
return Err(ManifestError::InvalidValue {
field: "shell.shell_id",
reason: format!("length {} > 32", m.shell.shell_id.len()),
});
}
let current =
parse_version(&m.runtime.runtime_current).ok_or(ManifestError::InvalidValue {
field: "runtime.runtime_current",
reason: format!(
"not a 'M.N' or 'M.N.P' semver: {}",
m.runtime.runtime_current
),
})?;
let max = parse_version(&m.runtime.runtime_max).ok_or(ManifestError::InvalidValue {
field: "runtime.runtime_max",
reason: format!("not a 'M.N' or 'M.N.P' semver: {}", m.runtime.runtime_max),
})?;
if max < current {
return Err(ManifestError::VersionMismatch);
}
match (m.audit.compliance_tier, m.audit.dek_backend.as_str()) {
(0, "software-kek") => {}
(0, other) => {
return Err(ManifestError::TierBackendMismatch {
tier: 0,
backend: other.to_string(),
});
}
(1 | 2, "software-kek") => {
return Err(ManifestError::TierBackendMismatch {
tier: m.audit.compliance_tier,
backend: "software-kek".to_string(),
});
}
(1 | 2, _) => {}
_ => {
return Err(ManifestError::InvalidValue {
field: "audit.compliance_tier",
reason: format!("unsupported tier {}", m.audit.compliance_tier),
});
}
}
if m.audit.dek_backend == "software-kek" && (current.0, current.1) > (0, 15) {
return Err(ManifestError::SoftwareKekProductionRefused {
current: m.runtime.runtime_current.clone(),
});
}
Ok(())
}
pub fn canonical_digest(m: &ManifestSnapshot) -> Result<ManifestDigest, ManifestError> {
let canonical = toml::to_string(m)
.map_err(|e| ManifestError::ParseError(format!("toml serialize: {}", e)))?;
let key = blake3::derive_key(DIGEST_DOMAIN, &[]);
let hash = blake3::keyed_hash(&key, canonical.as_bytes());
let mut out = [0u8; 32];
out.copy_from_slice(hash.as_bytes());
Ok(out)
}
}
pub fn emit_runtime_bootstrap(
digest: &ManifestDigest,
l0_semver: SemVer,
runtime_semver: SemVer,
typecode_pins: Vec<TypeCode>,
bootstrap_tick: Tick,
ctx: &mut ActionContext<'_>,
) -> Result<(), ActionError> {
let event = RuntimeBootstrap {
schema_version: 1,
l0_semver,
runtime_semver,
manifest_digest: *digest,
typecode_pins,
bootstrap_tick,
};
ctx.emit_event(&event)
}
fn parse_version(s: &str) -> Option<(u16, u16, u16)> {
let mut parts = s.splitn(3, '.');
let major: u16 = parts.next()?.parse().ok()?;
let minor: u16 = parts.next()?.parse().ok()?;
let patch: u16 = match parts.next() {
Some(p) => p.parse().ok()?,
None => 0,
};
Some((major, minor, patch))
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
use arkhe_kernel::abi::{CapabilityMask, InstanceId, Principal};
pub(super) const TIER0_DEV_TOML: &str = r#"
schema_version = 1
[shell]
shell_id = "shell.dev.example"
display_name = "Dev Sandbox"
[runtime]
runtime_max = "0.15"
runtime_current = "0.13"
[audit]
pii_cipher = "xchacha20-poly1305"
dek_backend = "software-kek"
kms_auto_promote = "manual"
signature_class = "ed25519"
compliance_tier = 0
"#;
const TIER1_PROD_TOML: &str = r#"
schema_version = 1
[shell]
shell_id = "shell.prod.example"
display_name = "Prod Shell"
[runtime]
runtime_max = "0.20"
runtime_current = "0.13"
[audit]
pii_cipher = "aes-256-gcm-siv"
dek_backend = "aws-kms"
kms_auto_promote = "after_60min"
signature_class = "hybrid"
compliance_tier = 1
[frontend]
tls_required = true
alpha_credential_rotation_required = true
"#;
#[test]
fn load_valid_tier0_dev_manifest() {
let (snap, digest) = ManifestLoader::load(TIER0_DEV_TOML.as_bytes()).unwrap();
assert_eq!(snap.schema_version, 1);
assert_eq!(snap.audit.compliance_tier, 0);
assert_eq!(snap.audit.dek_backend, "software-kek");
assert_eq!(digest.len(), 32);
assert!(snap.frontend.tls_required); }
#[test]
fn load_valid_tier1_prod_manifest() {
let (snap, digest) = ManifestLoader::load(TIER1_PROD_TOML.as_bytes()).unwrap();
assert_eq!(snap.audit.compliance_tier, 1);
assert_eq!(snap.audit.dek_backend, "aws-kms");
assert_eq!(digest.len(), 32);
}
#[test]
fn digest_is_deterministic_across_loads() {
let (_, d1) = ManifestLoader::load(TIER1_PROD_TOML.as_bytes()).unwrap();
let (_, d2) = ManifestLoader::load(TIER1_PROD_TOML.as_bytes()).unwrap();
assert_eq!(d1, d2);
}
#[test]
fn digest_is_whitespace_and_comment_invariant() {
let a = TIER0_DEV_TOML;
let b = r#"
# comment block
schema_version = 1
[shell]
shell_id = "shell.dev.example" # trailing comment
display_name = "Dev Sandbox"
[runtime]
runtime_max = "0.15"
runtime_current = "0.13"
[audit]
pii_cipher = "xchacha20-poly1305"
dek_backend = "software-kek"
kms_auto_promote = "manual"
signature_class = "ed25519"
compliance_tier = 0
"#;
let (_, da) = ManifestLoader::load(a.as_bytes()).unwrap();
let (_, db) = ManifestLoader::load(b.as_bytes()).unwrap();
assert_eq!(da, db, "comments / whitespace must not affect digest");
}
#[test]
fn digest_differs_for_semantically_different_manifest() {
let (_, d0) = ManifestLoader::load(TIER0_DEV_TOML.as_bytes()).unwrap();
let (_, d1) = ManifestLoader::load(TIER1_PROD_TOML.as_bytes()).unwrap();
assert_ne!(d0, d1);
}
#[test]
fn print_tier0_dev_digest_for_pin() {
let (_, d) = ManifestLoader::load(TIER0_DEV_TOML.as_bytes()).unwrap();
eprintln!(
"tier0_dev digest = {:?}",
d.iter().map(|b| format!("0x{b:02x}")).collect::<Vec<_>>()
);
}
#[test]
fn tier0_with_kms_backend_rejected() {
let bad = TIER0_DEV_TOML.replace("software-kek", "aws-kms");
let err = ManifestLoader::load(bad.as_bytes()).unwrap_err();
assert!(matches!(
err,
ManifestError::TierBackendMismatch { tier: 0, .. }
));
}
#[test]
fn tier1_with_software_kek_rejected() {
let bad = TIER1_PROD_TOML.replace("aws-kms", "software-kek");
let err = ManifestLoader::load(bad.as_bytes()).unwrap_err();
assert!(matches!(
err,
ManifestError::TierBackendMismatch { tier: 1, .. }
));
}
#[test]
fn software_kek_past_0_15_refused() {
let bad = TIER0_DEV_TOML
.replace("runtime_current = \"0.13\"", "runtime_current = \"0.16\"")
.replace("runtime_max = \"0.15\"", "runtime_max = \"0.20\"");
let err = ManifestLoader::load(bad.as_bytes()).unwrap_err();
assert!(matches!(
err,
ManifestError::SoftwareKekProductionRefused { .. }
));
}
#[test]
fn runtime_max_less_than_current_rejected() {
let bad = TIER0_DEV_TOML.replace("runtime_max = \"0.15\"", "runtime_max = \"0.5\"");
let err = ManifestLoader::load(bad.as_bytes()).unwrap_err();
assert!(matches!(err, ManifestError::VersionMismatch));
}
#[test]
fn parse_error_on_malformed_toml() {
let err = ManifestLoader::load(b"this is not toml == invalid").unwrap_err();
assert!(matches!(err, ManifestError::ParseError(_)));
}
#[test]
fn unknown_top_level_field_rejected() {
let bad = format!("{}\nunknown_field = 42\n", TIER0_DEV_TOML);
let err = ManifestLoader::load(bad.as_bytes()).unwrap_err();
assert!(matches!(err, ManifestError::ParseError(_)));
}
#[test]
fn bad_semver_rejected() {
let bad = TIER0_DEV_TOML.replace(
"runtime_current = \"0.13\"",
"runtime_current = \"not-a-version\"",
);
let err = ManifestLoader::load(bad.as_bytes()).unwrap_err();
assert!(matches!(
err,
ManifestError::InvalidValue {
field: "runtime.runtime_current",
..
}
));
}
#[test]
fn shell_id_over_32_bytes_rejected() {
let long_id = "a".repeat(33);
let bad = TIER0_DEV_TOML.replace("shell.dev.example", &long_id);
let err = ManifestLoader::load(bad.as_bytes()).unwrap_err();
assert!(matches!(
err,
ManifestError::InvalidValue {
field: "shell.shell_id",
..
}
));
}
#[test]
fn emit_runtime_bootstrap_appends_event() {
let (_, digest) = ManifestLoader::load(TIER0_DEV_TOML.as_bytes()).unwrap();
let mut ctx = ActionContext::new(
[0x11u8; 32],
InstanceId::new(1).unwrap(),
Tick(1),
Principal::System,
CapabilityMask::SYSTEM,
);
emit_runtime_bootstrap(
&digest,
SemVer::new(0, 11, 0),
SemVer::new(0, 11, 0),
vec![TypeCode(0x0003_0001), TypeCode(0x0003_0002)],
Tick(1),
&mut ctx,
)
.unwrap();
let events = ctx.drain_events();
assert_eq!(events.len(), 1);
assert_eq!(events[0].type_code, 0x0003_0F01);
assert_eq!(events[0].tick, Tick(1));
let bootstrap: RuntimeBootstrap = postcard::from_bytes(&events[0].payload).unwrap();
assert_eq!(bootstrap.manifest_digest, digest);
assert_eq!(bootstrap.l0_semver, SemVer::new(0, 11, 0));
}
#[test]
fn frontend_defaults_when_omitted() {
let (snap, _) = ManifestLoader::load(TIER0_DEV_TOML.as_bytes()).unwrap();
assert!(snap.frontend.tls_required);
assert!(snap.frontend.alpha_credential_rotation_required);
}
#[test]
fn parse_version_accepts_two_or_three_components() {
assert_eq!(parse_version("0.13"), Some((0, 13, 0)));
assert_eq!(parse_version("0.13.3"), Some((0, 13, 3)));
assert_eq!(parse_version(""), None);
assert_eq!(parse_version("0.13.3.4"), None); assert_eq!(parse_version("abc.def"), None);
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod digest_invariant {
use super::tests::TIER0_DEV_TOML;
use super::ManifestLoader;
const TIER0_DEV_DIGEST_V0_11: [u8; 32] = [
0xa1, 0xbf, 0xe8, 0x2a, 0x57, 0xe1, 0xde, 0x55, 0x05, 0x7e, 0x47, 0x51, 0xd0, 0xeb, 0xed,
0x3d, 0x48, 0x82, 0xdb, 0xd5, 0x95, 0x2d, 0x83, 0xcd, 0x52, 0x10, 0x6c, 0x0f, 0xae, 0x3b,
0xab, 0x3c,
];
#[test]
fn tier0_dev_digest_matches_v0_11_sentinel() {
let (_, digest) = ManifestLoader::load(TIER0_DEV_TOML.as_bytes())
.expect("TIER0_DEV_TOML must parse cleanly");
assert_eq!(
digest, TIER0_DEV_DIGEST_V0_11,
"manifest canonical_digest drifted from the pinned sentinel — check toml crate \
version, ManifestSnapshot field order, and DIGEST_DOMAIN. Update the sentinel + \
write a manifest schema micro-patch if the change is intentional."
);
}
}