diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d38adbed2..3dafc990b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -151,7 +151,7 @@ jobs: cargo clippy -p warp-core --features native_rule_bootstrap,trusted_runtime,host_test --test executable_operation_pipeline_tests -- -D warnings -D missing_docs - name: cargo clippy (compiler-produced pure evaluation) - run: cargo clippy -p warp-core --features trusted_runtime --lib --test edict_pure_evaluation_tests --test edict_pure_unsigned_subtraction_tests --test edict_pure_byte_length_tests --test edict_byte_equality_tests --test edict_byte_slice_tests --test edict_node_read_tests --test edict_byte_concat_tests -- -D warnings -D missing_docs + run: cargo clippy -p warp-core --features trusted_runtime --lib --test edict_pure_evaluation_tests --test edict_pure_unsigned_subtraction_tests --test edict_pure_byte_length_tests --test edict_byte_equality_tests --test edict_byte_slice_tests --test edict_node_read_tests --test edict_byte_concat_tests --test edict_source_functions_tests -- -D warnings -D missing_docs clippy-det-fixed: name: Clippy (det_fixed) @@ -322,7 +322,7 @@ jobs: cargo test -p warp-core --features native_rule_bootstrap,trusted_runtime,host_test --test executable_operation_pipeline_tests - name: cargo test (compiler-produced pure evaluation) run: | - cargo test -p warp-core --features trusted_runtime --test edict_pure_evaluation_tests --test edict_pure_unsigned_subtraction_tests --test edict_pure_byte_length_tests --test edict_byte_equality_tests --test edict_byte_slice_tests --test edict_node_read_tests --test edict_byte_concat_tests + cargo test -p warp-core --features trusted_runtime --test edict_pure_evaluation_tests --test edict_pure_unsigned_subtraction_tests --test edict_pure_byte_length_tests --test edict_byte_equality_tests --test edict_byte_slice_tests --test edict_node_read_tests --test edict_byte_concat_tests --test edict_source_functions_tests cargo test -p warp-core --features trusted_runtime --lib edict_pure:: - name: cargo test --doc (warp-core) run: cargo test -p warp-core --doc diff --git a/CHANGELOG.md b/CHANGELOG.md index a787ded5f..aabe85c22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ ### Fixed +- Private Edict runtime decoding resolves named integer types through their declared width, matching source-function provider acceptance. + - Filesystem writer takeover preserves unused LSNs after empty epochs and refuses unreconciled tails before changing the epoch ledger. - Incremental snapshot state roots now include the existing v1 domain separator and agree with canonical snapshot hashing. Previously emitted incorrect accumulator roots are not migrated. @@ -111,6 +113,18 @@ ### Added +- Generic source-owned Edict functions execute through both private pure and + bounded-read evaluation with fresh lexical frames, ordered argument evaluation, + ordered local bindings, and one cumulative budget. Independent provider + judgments validate the complete source/imported closure, supported types, + totality, combined runtime depth, and conservative whole-operation costs. + Providers and runtime reject source functions that shadow authenticated + imported effects, including otherwise valid unused definitions. Bounded-read + call lookup distinguishes source and imported functions by exact membership, + including disjoint names under one package prefix. + Independent provider checks retain nominal type identity for source-function + compatibility while deriving runtime costs from the underlying representation. + - Bounded pure evaluation supports generic `core.bytes.concat` with independent operand byte bounds, checked maximum composition, and combined-byte work and result storage charged before allocation/copy. Application names and text diff --git a/crates/echo-edict-provider-lowerer/README.md b/crates/echo-edict-provider-lowerer/README.md index 808ac50c5..07b9343aa 100644 --- a/crates/echo-edict-provider-lowerer/README.md +++ b/crates/echo-edict-provider-lowerer/README.md @@ -138,8 +138,8 @@ proposal constructor supports mutations and refuses a `Query`; authored reads remain a separate bounded observer/optic path and must never be represented as synthetic mutations. -The refreshed 381,459-byte checked lowerer component has SHA-256 -`4b594a8165079f0a973741b9e27b468cf041f4ad53108fccae641dc35355bd2a` and has +The refreshed 457,154-byte checked lowerer component has SHA-256 +`dc3edc7c4f194bd2fdd2b5a1ecc79f8fb7a24a5a31294a1f1fc30369c053b8d4` and has crossed the reproducible promotion boundary. The pinned Edict host admits its generated envelope under the owning `generated-artifact` CDDL root, and the isolated host fixture exercises the exact helper binding, typed codecs, EINT diff --git a/crates/echo-edict-provider-lowerer/src/executable_operation.rs b/crates/echo-edict-provider-lowerer/src/executable_operation.rs index 534f15593..f46b30fd9 100644 --- a/crates/echo-edict-provider-lowerer/src/executable_operation.rs +++ b/crates/echo-edict-provider-lowerer/src/executable_operation.rs @@ -3,6 +3,7 @@ //! Generic data-only lowering into Echo's bounded executable-operation profile. mod bounded_read; +mod source_functions; use std::collections::BTreeSet; @@ -227,6 +228,7 @@ fn lower_compiler_produced_pure( intent, )?; validate_pure_target_ir(target_ir, request, closure.lawpack, intent_name, intent)?; + source_functions::validate(core, exports, false)?; validate_pure_result_projection( result_projection, closure.result_projection, diff --git a/crates/echo-edict-provider-lowerer/src/executable_operation/bounded_read.rs b/crates/echo-edict-provider-lowerer/src/executable_operation/bounded_read.rs index 01d11a6ad..736480f27 100644 --- a/crates/echo-edict-provider-lowerer/src/executable_operation/bounded_read.rs +++ b/crates/echo-edict-provider-lowerer/src/executable_operation/bounded_read.rs @@ -67,18 +67,19 @@ pub(super) fn lower( )?; let source = validate_bound(&closure.source.artifact, SOURCE_DOMAIN)?; let source = validate_source(&source, closure.source, &request.core)?; - require_exact_fields( - &core, - &[ - "apiVersion", - "coordinate", - "imports", - "types", - "intents", - "requiredCoreCapabilities", - ], - SUBJECT, - )?; + let mut core_fields = vec![ + "apiVersion", + "coordinate", + "imports", + "types", + "intents", + "requiredCoreCapabilities", + ]; + if map_field(&core, "functions").is_some() { + core_fields.push("functions"); + } + require_exact_fields(&core, &core_fields, SUBJECT)?; + super::source_functions::validate(&core, &exports, true)?; require_exact_fields( &configuration, &["apiVersion", "programKind", "maxReads", "maxReadBytes"], diff --git a/crates/echo-edict-provider-lowerer/src/executable_operation/bounded_read/relation.rs b/crates/echo-edict-provider-lowerer/src/executable_operation/bounded_read/relation.rs index d695ba5b2..a827b7e34 100644 --- a/crates/echo-edict-provider-lowerer/src/executable_operation/bounded_read/relation.rs +++ b/crates/echo-edict-provider-lowerer/src/executable_operation/bounded_read/relation.rs @@ -84,8 +84,17 @@ impl ReadRelation<'_> { } let body = field(intent, "body")?; require_exact_fields(body, &["locals", "nodes", "result"], SUBJECT)?; - let mut scope = Scope::new(self.core, array(body, "locals")?, text(intent, "input")?)?; - if scope.expression(field(intent, "basis")?, 0)? != ReadType::Bytes(32, 32) { + let mut scope = Scope::new( + self.core, + self.exports, + array(body, "locals")?, + text(intent, "input")?, + )?; + if scope + .expression(field(intent, "basis")?, 0)? + .representation() + != &ReadType::Bytes(32, 32) + { return Err(invalid()); } for constraint in array(intent, "inputConstraints")? { diff --git a/crates/echo-edict-provider-lowerer/src/executable_operation/bounded_read/relation/types.rs b/crates/echo-edict-provider-lowerer/src/executable_operation/bounded_read/relation/types.rs index 88ab99f7c..f6249a072 100644 --- a/crates/echo-edict-provider-lowerer/src/executable_operation/bounded_read/relation/types.rs +++ b/crates/echo-edict-provider-lowerer/src/executable_operation/bounded_read/relation/types.rs @@ -14,17 +14,27 @@ pub(super) enum ReadType { Bytes(u64, u64), Unsigned(u64), Record(BTreeMap), + Nominal(String, Box), } impl ReadType { + pub(super) fn representation(&self) -> &Self { + match self { + Self::Nominal(_, inner) => inner.representation(), + _ => self, + } + } + pub(super) fn is_address(&self) -> bool { let Self::Record(fields) = self else { return false; }; fields.len() == 3 - && ["nodeId", "typeId", "warpId"] - .iter() - .all(|key| fields.get(*key) == Some(&Self::Bytes(32, 32))) + && ["nodeId", "typeId", "warpId"].iter().all(|key| { + fields + .get(*key) + .is_some_and(|ty| ty.representation() == &Self::Bytes(32, 32)) + }) } fn accepts(&self, actual: &Self) -> bool { @@ -32,6 +42,12 @@ impl ReadType { (Self::Bytes(min, max), Self::Bytes(actual_min, actual_max)) => { min <= actual_min && actual_max <= max } + (Self::Record(expected), Self::Record(actual)) => { + expected.len() == actual.len() + && expected + .iter() + .all(|(name, ty)| actual.get(name).is_some_and(|value| ty.accepts(value))) + } _ => self == actual, } } @@ -39,6 +55,7 @@ impl ReadType { pub(super) struct Scope<'a> { core: &'a Value, + exports: &'a Value, inventory: BTreeMap<&'a str, &'a Value>, available: BTreeMap<&'a str, &'a Value>, used: BTreeSet<&'a str>, @@ -49,6 +66,7 @@ pub(super) struct Scope<'a> { impl<'a> Scope<'a> { pub(super) fn new( core: &'a Value, + exports: &'a Value, locals: &'a [Value], input_type: &str, ) -> Result { @@ -67,6 +85,7 @@ impl<'a> Scope<'a> { } let scope = Self { core, + exports, inventory, available: BTreeMap::from([("arg.0", input)]), used: BTreeSet::from(["arg.0"]), @@ -116,6 +135,16 @@ impl<'a> Scope<'a> { if let Some(max) = spec.strip_prefix("max=") { return Ok(ReadType::Bytes(0, canonical_number(max)?)); } + if let Some((left, right)) = spec + .strip_prefix("min=") + .and_then(|value| value.split_once(",max=")) + { + let min = canonical_number(left)?; + let max = canonical_number(right)?; + if min < max { + return Ok(ReadType::Bytes(min, max)); + } + } return Err(invalid()); } let prefix = format!("{}.", text(self.core, "coordinate")?); @@ -134,6 +163,15 @@ impl<'a> Scope<'a> { Ok(ReadType::Bytes(min, max)) } "Int" => self.resolve(text(value, "width")?, depth + 1), + "Nominal" => { + if text(value, "contract")? != key { + return Err(invalid()); + } + Ok(ReadType::Nominal( + key.to_owned(), + Box::new(self.resolve(text(value, "representation")?, depth + 1)?), + )) + } "Record" => { let fields = entries(field(value, "fields")?)? .iter() @@ -227,18 +265,129 @@ impl<'a> Scope<'a> { .collect::, ProviderRefusalV1>>()?; Ok(ReadType::Record(fields)) } + "const" => { + require_exact_fields(value, &["kind", "value"], SUBJECT)?; + let literal = field(value, "value")?; + require_exact_fields(literal, &["kind", "value", "width"], SUBJECT)?; + let width = text(literal, "width")?; + if text(literal, "kind")? != "int" || !["U32", "U64"].contains(&width) { + return Err(invalid()); + } + let ReadType::Unsigned(max) = self.ty(width)? else { + return Err(invalid()); + }; + if required_u64(literal, "value", SUBJECT)? > max { + return Err(invalid()); + } + Ok(ReadType::Unsigned(max)) + } + "if" => { + require_exact_fields(value, &["kind", "predicate", "then", "else"], SUBJECT)?; + self.predicate_at(field(value, "predicate")?, depth + 1)?; + let yes = self.expression(field(value, "then")?, depth + 1)?; + let no = self.expression(field(value, "else")?, depth + 1)?; + match (yes, no) { + (ReadType::Bytes(a, b), ReadType::Bytes(c, d)) => { + Ok(ReadType::Bytes(a.min(c), b.max(d))) + } + (a, b) if a == b => Ok(a), + _ => Err(invalid()), + } + } + "call" => self.call(value, depth), + _ => Err(invalid()), + } + } + + fn call(&self, value: &Value, depth: usize) -> Result { + require_exact_fields(value, &["kind", "callee", "args", "typeArgs"], SUBJECT)?; + let args = super::array(value, "args")?; + let types = super::array(value, "typeArgs")?; + let name = text(value, "callee")?; + let actual = args + .iter() + .map(|value| self.expression(value, depth + 1)) + .collect::, _>>()?; + let prefix = format!("{}.", text(self.core, "coordinate")?); + let signature = name.strip_prefix(&prefix).and_then(|member| { + super::map_field(self.core, "functions") + .and_then(|functions| super::map_field(functions, member)) + }); + if let Some(function) = signature { + let params = super::array(function, "params")?; + if !types.is_empty() || params.len() != actual.len() { + return Err(invalid()); + } + for (parameter, ty) in params.iter().zip(&actual) { + if !self.ty(text(parameter, "type")?)?.accepts(ty) { + return Err(invalid()); + } + } + return self.ty(text(function, "returnType")?); + } + if let Some(function) = super::array(self.exports, "pureFunctions")? + .iter() + .find(|function| super::text_field(function, "coordinate") == Some(name)) + { + if !types.is_empty() + || !args.is_empty() + || !super::array(function, "parameterTypes")?.is_empty() + { + return Err(invalid()); + } + return self.ty(text(function, "returnType")?); + } + let declared = types + .iter() + .map(|value| { + let Value::Text(name) = value else { + return Err(invalid()); + }; + self.ty(name) + }) + .collect::, _>>()?; + match (name, actual.as_slice(), declared.as_slice()) { + ("core.bytes.length", [actual], [expected @ ReadType::Bytes(_, _)]) + if expected.accepts(actual) => + { + Ok(ReadType::Unsigned(u64::MAX)) + } + ( + "core.bytes.concat", + [left, right], + [a @ ReadType::Bytes(a_min, a_max), b @ ReadType::Bytes(b_min, b_max)], + ) if a.accepts(left) && b.accepts(right) => Ok(ReadType::Bytes( + a_min.checked_add(*b_min).ok_or_else(invalid)?, + a_max.checked_add(*b_max).ok_or_else(invalid)?, + )), _ => Err(invalid()), } } pub(super) fn predicate(&self, value: &Value) -> Result<(), ProviderRefusalV1> { + self.predicate_at(value, 0) + } + + fn predicate_at(&self, value: &Value, depth: usize) -> Result<(), ProviderRefusalV1> { + if depth > 64 { + return Err(invalid()); + } require_exact_fields(value, &["kind", "op", "left", "right"], SUBJECT)?; if text(value, "kind")? != "compare" { return Err(invalid()); } - let left = self.expression(field(value, "left")?, 0)?; - let right = self.expression(field(value, "right")?, 0)?; - match (text(value, "op")?, left, right) { + let left = self.expression(field(value, "left")?, depth + 1)?; + let right = self.expression(field(value, "right")?, depth + 1)?; + if (matches!(left, ReadType::Nominal(..)) || matches!(right, ReadType::Nominal(..))) + && left != right + { + return Err(invalid()); + } + match ( + text(value, "op")?, + left.representation(), + right.representation(), + ) { ("==", ReadType::Bytes(_, _), ReadType::Bytes(_, _)) => Ok(()), ("==" | "<=", ReadType::Unsigned(left), ReadType::Unsigned(right)) if left == right => { Ok(()) @@ -290,6 +439,36 @@ mod tests { ) } + #[test] + fn distinct_nominals_without_source_functions_do_not_convert() -> Result<(), ProviderRefusalV1> + { + let nominal = |contract: &str| { + map([ + ("kind", Value::Text("Nominal".into())), + ("contract", Value::Text(contract.into())), + ("representation", Value::Text("U64".into())), + ]) + }; + let core = map([ + ("coordinate", Value::Text("example@1".into())), + ("types", map([("A", nominal("A")), ("B", nominal("B"))])), + ]); + let scope = Scope { + core: &core, + exports: &core, + inventory: BTreeMap::new(), + available: BTreeMap::new(), + used: BTreeSet::new(), + input: &core, + remaining_type_work: Cell::new(65_536), + }; + let a = scope.ty("A")?; + assert!(scope.ty("example@1.A")?.accepts(&a)); + assert!(!scope.ty("B")?.accepts(&a)); + assert!(!scope.ty("U64")?.accepts(&a)); + Ok(()) + } + #[test] fn named_types_starting_with_u_are_not_unsigned_widths() { let core = map([ @@ -307,6 +486,7 @@ mod tests { ]); let scope = Scope { core: &core, + exports: &core, inventory: BTreeMap::new(), available: BTreeMap::new(), used: BTreeSet::new(), diff --git a/crates/echo-edict-provider-lowerer/src/executable_operation/source_functions.rs b/crates/echo-edict-provider-lowerer/src/executable_operation/source_functions.rs new file mode 100644 index 000000000..0c30c8c61 --- /dev/null +++ b/crates/echo-edict-provider-lowerer/src/executable_operation/source_functions.rs @@ -0,0 +1,891 @@ +// SPDX-License-Identifier: Apache-2.0 +// © James Ross Ω FLYING•ROBOTS +//! Lowerer-owned forward type, totality, depth and deterministic cost judgment. +//! Applies to the complete source-function module, including unused definitions. +use super::ProviderRefusalV1; +use echo_edict_canonical::CanonicalValueV1 as Value; +use std::collections::{BTreeMap, BTreeSet}; + +type Check = Result; +const DEPTH: usize = 64; +const WORK: usize = 65_536; +#[derive(Clone, Debug, PartialEq, Eq)] +enum Ty { + Word(u64), + Bytes(u64, u64), + Record(BTreeMap), + Nominal(String, Box), +} +impl Ty { + fn representation(&self) -> &Self { + match self { + Self::Nominal(_, representation) => representation.representation(), + _ => self, + } + } +} +#[derive(Clone, Copy, Default)] +struct Cost { + steps: u64, + bytes: u64, +} +impl Cost { + fn plus(self, other: Self) -> Check { + Ok(Self { + steps: self.steps.checked_add(other.steps).ok_or(())?, + bytes: self.bytes.checked_add(other.bytes).ok_or(())?, + }) + } + fn maximum(self, other: Self) -> Self { + Self { + steps: self.steps.max(other.steps), + bytes: self.bytes.max(other.bytes), + } + } +} +#[derive(Clone)] +struct Fact { + ty: Ty, + cost: Cost, + depth: usize, +} +#[derive(Clone, Copy)] +struct Function<'a> { + params: &'a [Value], + body: &'a Value, + result: &'a str, + source: bool, +} +type Scope<'a> = BTreeMap; +struct Judgment<'a> { + core: &'a Value, + functions: BTreeMap>, + completed: BTreeMap, + active: BTreeSet, + work: usize, +} + +pub(super) fn validate(core: &Value, exports: &Value, read: bool) -> Result<(), ProviderRefusalV1> { + run(core, exports, read) + .map_err(|()| super::super::unsupported_semantics("core.source-functions")) +} +fn run(core: &Value, exports: &Value, read: bool) -> Check<()> { + let mut functions = BTreeMap::new(); + let imported_functions = super::map_field(exports, "pureFunctions") + .map(list) + .transpose()? + .unwrap_or(&[]); + for item in imported_functions { + if string(get(item, "source")?)? != "edict" { + return Err(()); + } + let implementation = get(item, "body")?; + let body = get(implementation, "body")?; + for (value, key) in [ + (item, "parameterTypes"), + (item, "typeParameters"), + (implementation, "params"), + (body, "locals"), + (body, "bindings"), + ] { + if !list(get(value, key)?)?.is_empty() { + return Err(()); + } + } + let name = string(get(item, "coordinate")?)?; + if name.starts_with("core.") + || functions + .insert( + name.to_owned(), + Function { + params: list(get(implementation, "params")?)?, + body, + result: string(get(item, "returnType")?)?, + source: false, + }, + ) + .is_some() + { + return Err(()); + } + } + // Exported effects own their coordinates even when no source body calls them. + // This exclusion does not add effects to the pure-call inventory. + let effect_names = list(get(exports, "effects")?)? + .iter() + .map(|effect| string(get(effect, "coordinate")?)) + .collect::>>()?; + let source_functions = super::map_field(core, "functions") + .map(members) + .transpose()? + .unwrap_or(&[]); + for (name, function) in source_functions { + let name = string(name)?; + if !identifier(name) + || super::map_field(get(core, "intents")?, name).is_some() + || super::map_field(get(core, "types")?, name).is_some() + { + return Err(()); + } + exact(function, &["params", "returnType", "body"])?; + let name = format!("{}.{}", string(get(core, "coordinate")?)?, name); + if name.starts_with("core.") + || effect_names.contains(name.as_str()) + || functions + .insert( + name, + Function { + params: list(get(function, "params")?)?, + body: get(function, "body")?, + result: string(get(function, "returnType")?)?, + source: true, + }, + ) + .is_some() + { + return Err(()); + } + } + let mut proof = Judgment { + core, + functions, + completed: BTreeMap::new(), + active: BTreeSet::new(), + work: WORK, + }; + for name in proof.functions.keys().cloned().collect::>() { + proof.function(&name, 1)?; + } + if super::map_field(core, "functions").is_none() { + return Ok(()); + } + for (_, intent) in members(get(core, "intents")?)? { + proof.intent(intent, read)?; + } + Ok(()) +} +impl Judgment<'_> { + fn charge(&mut self, depth: usize) -> Check<()> { + self.work = self.work.checked_sub(1).ok_or(())?; + if depth > DEPTH { + Err(()) + } else { + Ok(()) + } + } + fn ty(&mut self, name: &str, depth: usize) -> Check { + self.charge(depth)?; + if let Some(max) = match name { + "U8" => Some(u8::MAX.into()), + "U16" => Some(u16::MAX.into()), + "U32" => Some(u32::MAX.into()), + "U64" => Some(u64::MAX), + _ => None, + } { + return Ok(Ty::Word(max)); + } + if let Some(bounds) = name + .strip_prefix("Bytes<") + .and_then(|value| value.strip_suffix('>')) + { + if let Some(n) = bounds.strip_prefix("exact=") { + let n = decimal(n)?; + return Ok(Ty::Bytes(n, n)); + } + if let Some(n) = bounds.strip_prefix("max=") { + return Ok(Ty::Bytes(0, decimal(n)?)); + } + if let Some((min, max)) = bounds + .strip_prefix("min=") + .and_then(|v| v.split_once(",max=")) + { + let (min, max) = (decimal(min)?, decimal(max)?); + if min < max { + return Ok(Ty::Bytes(min, max)); + } + } + return Err(()); + } + let prefix = format!("{}.", string(get(self.core, "coordinate")?)?); + let key = name.strip_prefix(&prefix).unwrap_or(name); + let definition = get(get(self.core, "types")?, key)?; + match string(get(definition, "kind")?)? { + "Int" => self.ty(string(get(definition, "width")?)?, depth + 1), + "Nominal" => { + if string(get(definition, "contract")?)? != key { + return Err(()); + } + Ok(Ty::Nominal( + key.to_owned(), + Box::new(self.ty(string(get(definition, "representation")?)?, depth + 1)?), + )) + } + "Bytes" => { + let min = super::map_field(definition, "min") + .map(number) + .transpose()? + .unwrap_or(0); + let max = number(get(definition, "max")?)?; + if min > max { + return Err(()); + } + Ok(Ty::Bytes(min, max)) + } + "Record" => { + let mut fields = BTreeMap::new(); + for (key, value) in members(get(definition, "fields")?)? { + if fields + .insert(string(key)?.to_owned(), self.ty(string(value)?, depth + 1)?) + .is_some() + { + return Err(()); + } + } + Ok(Ty::Record(fields)) + } + _ => Err(()), + } + } + fn declaration<'a>(&mut self, value: &'a Value, scope: &mut Scope<'a>) -> Check { + exact(value, &["id", "type", "alphaName"])?; + let id = string(get(value, "id")?)?; + string(get(value, "alphaName")?)?; + let ty = self.ty(string(get(value, "type")?)?, 0)?; + if scope.insert(id.to_owned(), (value, ty.clone())).is_some() { + return Err(()); + } + Ok(ty) + } + fn function(&mut self, name: &str, prefix: usize) -> Check { + self.charge(prefix)?; + if self.active.contains(name) { + return Err(()); + } + if let Some(fact) = self.completed.get(name) { + if prefix.checked_add(fact.depth).ok_or(())? > DEPTH { + return Err(()); + } + self.work = self + .work + .checked_sub(usize::try_from(shape(&fact.ty)?.0.steps).map_err(|_| ())?) + .ok_or(())?; + return Ok(fact.clone()); + } + let function = *self.functions.get(name).ok_or(())?; + self.active.insert(name.to_owned()); + exact(function.body, &["locals", "bindings", "result"])?; + let mut scope = BTreeMap::new(); + let mut depth = 0; + for parameter in function.params { + depth = depth.max(shape(&self.declaration(parameter, &mut scope)?)?.3); + } + let mut pending = BTreeMap::new(); + for local in list(get(function.body, "locals")?)? { + exact(local, &["id", "type", "alphaName"])?; + let id = string(get(local, "id")?)?; + if scope.contains_key(id) || pending.insert(id, local).is_some() { + return Err(()); + } + } + let mut cost = Cost::default(); + for binding in list(get(function.body, "bindings")?)? { + exact(binding, &["kind", "binding", "value"])?; + if string(get(binding, "kind")?)? != "let" { + return Err(()); + } + let local = get(binding, "binding")?; + if pending.remove(string(get(local, "id")?)?) != Some(local) { + return Err(()); + } + let fact = self.expression(get(binding, "value")?, &scope, function.source, prefix)?; + let expected = self.declaration(local, &mut scope)?; + if !includes(&expected, &fact.ty) { + return Err(()); + } + let (_, validations, _, ty_depth) = shape(&expected)?; + cost = cost.plus(fact.cost)?.plus(Cost { + steps: validations, + bytes: 0, + })?; + depth = depth.max(fact.depth).max(ty_depth); + } + if !pending.is_empty() { + return Err(()); + } + let result = self.expression( + get(function.body, "result")?, + &scope, + function.source, + prefix, + )?; + let ty = self.ty(function.result, 0)?; + if !includes(&ty, &result.ty) { + return Err(()); + } + let (_, validations, _, ty_depth) = shape(&ty)?; + let fact = Fact { + ty, + cost: cost.plus(result.cost)?.plus(Cost { + steps: validations, + bytes: 0, + })?, + depth: depth.max(result.depth).max(ty_depth), + }; + if prefix.checked_add(fact.depth).ok_or(())? > DEPTH { + return Err(()); + } + self.active.remove(name); + self.completed.insert(name.to_owned(), fact.clone()); + Ok(fact) + } + fn expression( + &mut self, + value: &Value, + scope: &Scope<'_>, + source: bool, + prefix: usize, + ) -> Check { + self.charge(prefix)?; + let mut cost = Cost { steps: 1, bytes: 0 }; + let mut depth = 0; + let ty = match string(get(value, "kind")?)? { + "local" => { + exact(value, &["kind", "ref"])?; + let local = get(value, "ref")?; + let (declared, ty) = scope.get(string(get(local, "id")?)?).ok_or(())?; + if *declared != local { + return Err(()); + } + let copied = shape(ty)?.0; + self.work = self + .work + .checked_sub(usize::try_from(copied.steps).map_err(|_| ())?) + .ok_or(())?; + cost = cost.plus(copied)?; + ty.clone() + } + "const" => { + exact(value, &["kind", "value"])?; + let literal = get(value, "value")?; + exact(literal, &["kind", "value", "width"])?; + let width = string(get(literal, "width")?)?; + if string(get(literal, "kind")?)? != "int" || !["U32", "U64"].contains(&width) { + return Err(()); + } + let ty = self.ty(width, 0)?; + let Ty::Word(max) = ty else { + return Err(()); + }; + if number(get(literal, "value")?)? > max { + return Err(()); + } + cost = cost.plus(Cost { + steps: 1, + bytes: 64, + })?; + ty + } + "field" => { + exact(value, &["kind", "base", "field"])?; + let base = self.expression(get(value, "base")?, scope, source, prefix + 1)?; + cost = cost.plus(base.cost)?; + depth = base.depth + 1; + let Ty::Record(mut fields) = base.ty else { + return Err(()); + }; + fields.remove(string(get(value, "field")?)?).ok_or(())? + } + "record" => { + exact(value, &["kind", "fields"])?; + cost.bytes = 64; + let mut fields = BTreeMap::new(); + for (key, value) in members(get(value, "fields")?)? { + let key = string(key)?; + let fact = self.expression(value, scope, source, prefix + 1)?; + cost = cost.plus(fact.cost)?.plus(Cost { + steps: 0, + bytes: 64_u64.checked_add(key.len() as u64).ok_or(())?, + })?; + depth = depth.max(fact.depth + 1); + if fields.insert(key.to_owned(), fact.ty).is_some() { + return Err(()); + } + } + Ty::Record(fields) + } + "if" => { + exact(value, &["kind", "predicate", "then", "else"])?; + let predicate = + self.predicate(get(value, "predicate")?, scope, source, prefix + 1)?; + let yes = self.expression(get(value, "then")?, scope, source, prefix + 1)?; + let no = self.expression(get(value, "else")?, scope, source, prefix + 1)?; + cost = cost.plus(predicate.0)?.plus(yes.cost.maximum(no.cost))?; + depth = predicate.1.max(yes.depth).max(no.depth) + 1; + union(yes.ty, no.ty)? + } + "call" => { + exact(value, &["kind", "callee", "args", "typeArgs"])?; + let name = string(get(value, "callee")?)?; + let arguments = list(get(value, "args")?)?; + let types = list(get(value, "typeArgs")?)?; + let mut actual = Vec::new(); + for argument in arguments { + let fact = self.expression(argument, scope, source, prefix + 1)?; + cost = cost.plus(fact.cost)?; + depth = depth.max(fact.depth + 1); + actual.push(fact.ty); + } + if let Some(function) = self.functions.get(name).copied() { + if !types.is_empty() + || arguments.len() != function.params.len() + || (!source && function.source) + { + return Err(()); + } + for (actual, parameter) in actual.iter().zip(function.params) { + let ty = self.ty(string(get(parameter, "type")?)?, 0)?; + if !includes(&ty, actual) { + return Err(()); + } + let (_, validations, _, ty_depth) = shape(&ty)?; + cost = cost.plus(Cost { + steps: validations, + bytes: 0, + })?; + depth = depth.max(ty_depth + 1); + } + let result = self.function(name, prefix + 1)?; + cost = cost.plus(result.cost)?; + depth = depth.max(result.depth + 1); + result.ty + } else { + let declared = types + .iter() + .map(|value| self.ty(string(value)?, prefix + 1)) + .collect::>>()?; + depth = depth.max(1); + match (name, actual.as_slice(), declared.as_slice()) { + ("core.bytes.length", [actual], [expected @ Ty::Bytes(_, _)]) + if includes(expected, actual) => + { + cost = cost.plus(Cost { + steps: 2, + bytes: 64, + })?; + Ty::Word(u64::MAX) + } + ( + "core.bytes.concat", + [left, right], + [a @ Ty::Bytes(a_min, a_max), b @ Ty::Bytes(b_min, b_max)], + ) if includes(a, left) && includes(b, right) => { + let min = a_min.checked_add(*b_min).ok_or(())?; + let max = a_max.checked_add(*b_max).ok_or(())?; + cost = cost.plus(Cost { + steps: max.checked_add(3).ok_or(())?, + bytes: max.checked_add(64).ok_or(())?, + })?; + Ty::Bytes(min, max) + } + ("core.integer.subtract", [left, right], [expected @ Ty::Word(_)]) + if includes(expected, left) && includes(expected, right) => + { + literal(&arguments[0])? + .checked_sub(literal(&arguments[1])?) + .ok_or(())?; + cost = cost.plus(Cost { + steps: 3, + bytes: 64, + })?; + expected.clone() + } + ( + "core.bytes.slice", + [actual, Ty::Word(a), Ty::Word(b)], + [expected @ Ty::Bytes(min, _)], + ) if *a == u64::MAX && *b == u64::MAX && includes(expected, actual) => { + let start = literal(&arguments[1])?; + let end = literal(&arguments[2])?; + if start > end || end > *min { + return Err(()); + } + let count = end - start; + cost = cost.plus(Cost { + steps: count.checked_add(4).ok_or(())?, + bytes: count.checked_add(64).ok_or(())?, + })?; + Ty::Bytes(0, count) + } + _ => return Err(()), + } + } + } + _ => return Err(()), + }; + if prefix.checked_add(depth).ok_or(())? > DEPTH { + return Err(()); + } + Ok(Fact { ty, cost, depth }) + } + fn predicate( + &mut self, + value: &Value, + scope: &Scope<'_>, + source: bool, + prefix: usize, + ) -> Check<(Cost, usize)> { + self.charge(prefix)?; + exact(value, &["kind", "op", "left", "right"])?; + if string(get(value, "kind")?)? != "compare" { + return Err(()); + } + let left = self.expression(get(value, "left")?, scope, source, prefix + 1)?; + let right = self.expression(get(value, "right")?, scope, source, prefix + 1)?; + match (&left.ty, &right.ty) { + (Ty::Nominal(..), _) | (_, Ty::Nominal(..)) if left.ty != right.ty => return Err(()), + _ => {} + } + let extra = match ( + string(get(value, "op")?)?, + left.ty.representation(), + right.ty.representation(), + ) { + ("==", Ty::Bytes(_, a), Ty::Bytes(_, b)) => (*a).max(*b), + ("==" | "<=", Ty::Word(a), Ty::Word(b)) if a == b => 0, + _ => return Err(()), + }; + Ok(( + left.cost.plus(right.cost)?.plus(Cost { + steps: extra.checked_add(1).ok_or(())?, + bytes: 0, + })?, + left.depth.max(right.depth) + 1, + )) + } + fn intent(&mut self, intent: &Value, read: bool) -> Check<()> { + let body = get(intent, "body")?; + exact(body, &["locals", "nodes", "result"])?; + let mut pending = BTreeMap::new(); + for declaration in list(get(body, "locals")?)? { + exact(declaration, &["id", "type", "alphaName"])?; + string(get(declaration, "alphaName")?)?; + self.ty(string(get(declaration, "type")?)?, 0)?; + if pending + .insert(string(get(declaration, "id")?)?, declaration) + .is_some() + { + return Err(()); + } + } + let input = pending.remove("arg.0").ok_or(())?; + if get(input, "type")? != get(intent, "input")? { + return Err(()); + } + let mut scope = BTreeMap::new(); + let ty = self.declaration(input, &mut scope)?; + let (copy, validation, _, _) = shape(&ty)?; + let mut cost = copy.plus(Cost { + steps: validation, + bytes: 0, + })?; + if let Some(basis) = super::map_field(intent, "basis") { + let checked = self.expression(basis, &scope, true, 0)?; + // The pure profile retains basis authority without executing it. + if read { + cost = cost.plus(checked.cost)?; + } + } else if read { + return Err(()); + } + for constraint in list(get(intent, "inputConstraints")?)? { + cost = cost.plus( + self.predicate(get(constraint, "predicate")?, &scope, true, 0)? + .0, + )?; + } + for node in list(get(body, "nodes")?)? { + if read { + cost = cost.plus(Cost { steps: 1, bytes: 0 })?; + } + let kind = string(get(node, "kind")?)?; + if kind == "let" || (read && kind == "effect") { + let binding = get(node, "binding")?; + if pending.remove(string(get(binding, "id")?)?) != Some(binding) { + return Err(()); + } + } + match kind { + "let" => { + exact(node, &["kind", "binding", "value"])?; + let fact = self.expression(get(node, "value")?, &scope, true, 0)?; + let ty = self.declaration(get(node, "binding")?, &mut scope)?; + if !includes(&ty, &fact.ty) { + return Err(()); + } + cost = cost.plus(fact.cost)?.plus(Cost { + steps: shape(&ty)?.1, + bytes: 0, + })?; + } + "effect" if read => { + cost = + cost.plus(self.expression(get(node, "input")?, &scope, true, 0)?.cost)?; + let declared = self.declaration(get(node, "binding")?, &mut scope)?; + let Ty::Bytes(_, max) = declared.representation() else { + return Err(()); + }; + cost = cost.plus(Cost { + steps: max.checked_add(1).ok_or(())?, + bytes: max.checked_add(64).ok_or(())?, + })?; + } + "require" if read => { + cost = + cost.plus(self.predicate(get(node, "predicate")?, &scope, true, 0)?.0)?; + } + _ => return Err(()), + } + } + // Failure-handler declarations on the read route are checked by its + // full ordered relation; a pure intent has no other declaration owner. + if !read && !pending.is_empty() { + return Err(()); + } + let result = self.expression(get(body, "result")?, &scope, true, 0)?; + let expected = self.ty(string(get(intent, "output")?)?, 0)?; + if !includes(&expected, &result.ty) { + return Err(()); + } + let (scratch, validations, output, _) = shape(&expected)?; + cost = cost.plus(result.cost)?.plus(scratch)?.plus(Cost { + steps: validations, + bytes: 0, + })?; + let budget = get(intent, "coreEvaluationBudget")?; + if cost.steps > number(get(budget, "maxSteps")?)? + || cost.bytes > number(get(budget, "maxAllocatedBytes")?)? + || output > number(get(budget, "maxOutputBytes")?)? + { + return Err(()); + } + Ok(()) + } +} +fn shape(ty: &Ty) -> Check<(Cost, u64, u64, usize)> { + match ty { + // Nominal identity is static authority, not an extra runtime value cell. + Ty::Nominal(_, representation) => shape(representation), + Ty::Word(_) => Ok(( + Cost { + steps: 1, + bytes: 64, + }, + 1, + 9, + 0, + )), + Ty::Bytes(_, max) => Ok(( + Cost { + steps: 1, + bytes: max.checked_add(64).ok_or(())?, + }, + 1, + max.checked_add(9).ok_or(())?, + 0, + )), + Ty::Record(fields) => { + let mut cost = Cost { + steps: 1, + bytes: 64, + }; + let mut validations = 1_u64; + let mut encoded = 9_u64; + let mut depth = 0; + for (name, ty) in fields { + let (child, validation, bytes, child_depth) = shape(ty)?; + cost = cost.plus(child)?.plus(Cost { + steps: 1, + bytes: 64_u64.checked_add(name.len() as u64).ok_or(())?, + })?; + validations = validations.checked_add(validation).ok_or(())?; + encoded = encoded + .checked_add(9) + .and_then(|v| v.checked_add(name.len() as u64)) + .and_then(|v| v.checked_add(bytes)) + .ok_or(())?; + depth = depth.max(child_depth + 1); + } + Ok((cost, validations, encoded, depth)) + } + } +} +fn includes(expected: &Ty, actual: &Ty) -> bool { + match (expected, actual) { + (Ty::Bytes(a, b), Ty::Bytes(c, d)) => a <= c && d <= b, + (Ty::Record(a), Ty::Record(b)) => { + a.len() == b.len() + && a.iter() + .all(|(name, ty)| b.get(name).is_some_and(|actual| includes(ty, actual))) + } + _ => expected == actual, + } +} +fn union(left: Ty, right: Ty) -> Check { + match (left, right) { + (Ty::Bytes(a, b), Ty::Bytes(c, d)) => Ok(Ty::Bytes(a.min(c), b.max(d))), + (a, b) if a == b => Ok(a), + _ => Err(()), + } +} +fn identifier(value: &str) -> bool { + !value.is_empty() + && value.len() <= 1024 + && value + .bytes() + .enumerate() + .all(|(i, c)| c == b'_' || c.is_ascii_alphabetic() || (i > 0 && c.is_ascii_digit())) +} +fn get<'a>(value: &'a Value, name: &str) -> Check<&'a Value> { + super::map_field(value, name).ok_or(()) +} +fn members(value: &Value) -> Check<&[(Value, Value)]> { + if let Value::Map(values) = value { + Ok(values) + } else { + Err(()) + } +} +fn list(value: &Value) -> Check<&[Value]> { + if let Value::Array(values) = value { + Ok(values) + } else { + Err(()) + } +} +fn string(value: &Value) -> Check<&str> { + if let Value::Text(value) = value { + if !value.is_empty() && value.len() <= 1024 { + return Ok(value); + } + } + Err(()) +} +fn number(value: &Value) -> Check { + if let Value::Integer(value) = value { + u64::try_from(*value).map_err(|_| ()) + } else { + Err(()) + } +} +fn decimal(value: &str) -> Check { + let parsed: u64 = value.parse().map_err(|_| ())?; + if parsed.to_string() != value { + return Err(()); + } + Ok(parsed) +} +fn literal(value: &Value) -> Check { + if string(get(value, "kind")?)? != "const" { + return Err(()); + } + number(get(get(value, "value")?, "value")?) +} +fn exact(value: &Value, names: &[&str]) -> Check<()> { + let fields = members(value)?; + if fields.len() != names.len() + || fields + .iter() + .any(|(key, _)| !names.contains(&string(key).unwrap_or(""))) + { + return Err(()); + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn map(fields: impl IntoIterator) -> Value { + Value::Map( + fields + .into_iter() + .map(|(key, value)| (Value::Text(key.into()), value)) + .collect(), + ) + } + + #[test] + fn imported_inventory_is_audited_without_source_functions() { + let core = map([("types", map([])), ("intents", map([]))]); + let exports = map([ + ("effects", Value::Array(vec![])), + ( + "pureFunctions", + Value::Array(vec![map([("source", Value::Text("component".into()))])]), + ), + ]); + assert!(validate(&core, &exports, true).is_err()); + } + + #[test] + fn empty_inventory_without_source_functions_is_valid() { + let core = map([("types", map([])), ("intents", map([]))]); + let exports = map([ + ("effects", Value::Array(vec![])), + ("pureFunctions", Value::Array(vec![])), + ]); + assert!(validate(&core, &exports, true).is_ok()); + } + #[test] + fn source_functions_accept_named_unsigned_int_aliases() { + let parameter = map([ + ("id", Value::Text("arg.0".into())), + ("type", Value::Text("Count".into())), + ("alphaName", Value::Text("count".into())), + ]); + let core = map([ + ("coordinate", Value::Text("example@1".into())), + ( + "types", + map([( + "Count", + map([ + ("kind", Value::Text("Int".into())), + ("width", Value::Text("U64".into())), + ]), + )]), + ), + ("intents", map([])), + ( + "functions", + map([( + "identity", + map([ + ("params", Value::Array(vec![parameter.clone()])), + ("returnType", Value::Text("Count".into())), + ( + "body", + map([ + ("locals", Value::Array(vec![])), + ("bindings", Value::Array(vec![])), + ( + "result", + map([ + ("kind", Value::Text("local".into())), + ("ref", parameter), + ]), + ), + ]), + ), + ]), + )]), + ), + ]); + let exports = map([ + ("effects", Value::Array(vec![])), + ("pureFunctions", Value::Array(vec![])), + ]); + assert!(validate(&core, &exports, false).is_ok()); + } +} diff --git a/crates/echo-edict-provider-verifier/README.md b/crates/echo-edict-provider-verifier/README.md index ebe32cc99..747bcc9c3 100644 --- a/crates/echo-edict-provider-verifier/README.md +++ b/crates/echo-edict-provider-verifier/README.md @@ -108,9 +108,9 @@ route's fixed 64 KiB output ceiling. The `wasm32` guest adapter vendors Edict's exact frozen `edict:target-provider/verifier@1.0.0` WIT world and performs only exhaustive -transport-to-model conversion. Its reproducibly built 389,503-byte checked +transport-to-model conversion. Its reproducibly built 469,772-byte checked component has SHA-256 -`d0be4d283399aefc45d75b395b3568f9983521ca5543c9dfd23b9495758050ed`. +`23d1d256ec66138697f93babcd2be57a4f56de1eb2d1fb6910e5aef881c4dd0e`. Component identity and admitted host replay remain separate propositions: the pinned Edict host preflights the request artifacts and declared output schema, invokes the checked component, then admits and manifests each returned accepted diff --git a/crates/echo-edict-provider-verifier/src/executable_operation.rs b/crates/echo-edict-provider-verifier/src/executable_operation.rs index ba67fd0d8..324b964eb 100644 --- a/crates/echo-edict-provider-verifier/src/executable_operation.rs +++ b/crates/echo-edict-provider-verifier/src/executable_operation.rs @@ -3,6 +3,7 @@ //! Independent verification for generic bounded executable-operation packages. mod bounded_read; +mod source_functions; use std::collections::BTreeSet; @@ -263,6 +264,7 @@ fn verify_compiler_produced_pure( request, )?; validate_pure_target_ir(target_ir, request, closure.lawpack, intent_name, intent)?; + source_functions::validate(core, exports, false)?; if !crate::pure_relation::preserves(intent, target_ir, intent_name) { return Err(super::unsupported_semantics("target-ir.echo-pure-relation")); } diff --git a/crates/echo-edict-provider-verifier/src/executable_operation/bounded_read/audit.rs b/crates/echo-edict-provider-verifier/src/executable_operation/bounded_read/audit.rs index bdb9365d7..d40c09999 100644 --- a/crates/echo-edict-provider-verifier/src/executable_operation/bounded_read/audit.rs +++ b/crates/echo-edict-provider-verifier/src/executable_operation/bounded_read/audit.rs @@ -26,17 +26,19 @@ pub(super) fn check(e: &Evidence<'_>) -> Check<()> { { return Err(()); } - exact( - &e.core, - &[ - "apiVersion", - "coordinate", - "imports", - "types", - "intents", - "requiredCoreCapabilities", - ], - )?; + let mut fields = vec![ + "apiVersion", + "coordinate", + "imports", + "types", + "intents", + "requiredCoreCapabilities", + ]; + if super::map_field(&e.core, "functions").is_some() { + fields.push("functions"); + } + exact(&e.core, &fields)?; + super::super::source_functions::validate(&e.core, &e.exports, true).map_err(|_| ())?; if text(&e.core, "apiVersion")? != "edict.core/v1" || text(&e.core, "coordinate")? != e.request.core.reference.coordinate || !sequence(&e.core, "requiredCoreCapabilities")?.is_empty() @@ -150,9 +152,16 @@ pub(super) fn check(e: &Evidence<'_>) -> Check<()> { )?; let body = get(intent, "body")?; exact(body, &["locals", "nodes", "result"])?; - let mut symbols = Symbols::new(&e.core, sequence(body, "locals")?, text(intent, "input")?)?; - if symbols.expression(get(target, "basis")?, 0)? - != (Schema::Bytes { + let mut symbols = Symbols::new( + &e.core, + &e.exports, + sequence(body, "locals")?, + text(intent, "input")?, + )?; + if symbols + .expression(get(target, "basis")?, 0)? + .representation() + != &(Schema::Bytes { lower: 32, upper: 32, }) diff --git a/crates/echo-edict-provider-verifier/src/executable_operation/bounded_read/audit/types.rs b/crates/echo-edict-provider-verifier/src/executable_operation/bounded_read/audit/types.rs index c5871898e..1de0a2bfa 100644 --- a/crates/echo-edict-provider-verifier/src/executable_operation/bounded_read/audit/types.rs +++ b/crates/echo-edict-provider-verifier/src/executable_operation/bounded_read/audit/types.rs @@ -7,23 +7,39 @@ use std::collections::{BTreeMap, BTreeSet}; #[derive(Clone, PartialEq, Eq)] pub(super) enum Schema { - Bytes { lower: u64, upper: u64 }, + Bytes { + lower: u64, + upper: u64, + }, Word(u64), Row(BTreeMap), + Nominal { + identity: String, + storage: Box, + }, } impl Schema { + pub(super) fn representation(&self) -> &Self { + match self { + Self::Nominal { storage, .. } => storage.representation(), + _ => self, + } + } + pub(super) fn address(&self) -> bool { let Self::Row(fields) = self else { return false; }; fields.len() == 3 && ["nodeId", "warpId", "typeId"].iter().all(|key| { - fields.get(*key) - == Some(&Self::Bytes { - lower: 32, - upper: 32, - }) + fields.get(*key).is_some_and(|ty| { + ty.representation() + == &Self::Bytes { + lower: 32, + upper: 32, + } + }) }) } fn includes(&self, actual: &Self) -> bool { @@ -31,6 +47,12 @@ impl Schema { (Self::Bytes { lower, upper }, Self::Bytes { lower: a, upper: b }) => { lower <= a && b <= upper } + (Self::Row(expected), Self::Row(actual)) => { + expected.len() == actual.len() + && expected + .iter() + .all(|(name, ty)| actual.get(name).is_some_and(|value| ty.includes(value))) + } _ => self == actual, } } @@ -38,6 +60,7 @@ impl Schema { pub(super) struct Symbols<'a> { core: &'a Value, + exports: &'a Value, declared: BTreeMap, live: BTreeSet, claimed: BTreeSet, @@ -45,7 +68,12 @@ pub(super) struct Symbols<'a> { } impl<'a> Symbols<'a> { - pub(super) fn new(core: &'a Value, declarations: &[Value], input: &str) -> Check { + pub(super) fn new( + core: &'a Value, + exports: &'a Value, + declarations: &[Value], + input: &str, + ) -> Check { let mut declared = BTreeMap::new(); for declaration in declarations { exact(declaration, &["id", "type", "alphaName"])?; @@ -63,6 +91,7 @@ impl<'a> Symbols<'a> { } let mut symbols = Self { core, + exports, declared, live: BTreeSet::from(["arg.0".to_owned()]), claimed: BTreeSet::from(["arg.0".to_owned()]), @@ -109,13 +138,21 @@ impl<'a> Symbols<'a> { upper: decimal(value)?, }); } + if let Some((left, right)) = bound + .strip_prefix("min=") + .and_then(|value| value.split_once(",max=")) + { + let lower = decimal(left)?; + let upper = decimal(right)?; + if lower < upper { + return Ok(Schema::Bytes { lower, upper }); + } + } return Err(()); } let prefix = format!("{}.", text(self.core, "coordinate")?); - let value = get( - get(self.core, "types")?, - name.strip_prefix(&prefix).unwrap_or(name), - )?; + let key = name.strip_prefix(&prefix).unwrap_or(name); + let value = get(get(self.core, "types")?, key)?; match text(value, "kind")? { "Bytes" => { let upper = number(get(value, "max")?)?; @@ -129,6 +166,15 @@ impl<'a> Symbols<'a> { Ok(Schema::Bytes { lower, upper }) } "Int" => self.schema(text(value, "width")?, depth + 1), + "Nominal" => { + if text(value, "contract")? != key { + return Err(()); + } + Ok(Schema::Nominal { + identity: key.to_owned(), + storage: Box::new(self.schema(text(value, "representation")?, depth + 1)?), + }) + } "Record" => { let mut fields = BTreeMap::new(); for (key, value) in members(get(value, "fields")?)? { @@ -200,18 +246,134 @@ impl<'a> Symbols<'a> { } Ok(Schema::Row(fields)) } + "const" => { + exact(value, &["kind", "value"])?; + let literal = get(value, "value")?; + exact(literal, &["kind", "value", "width"])?; + let width = text(literal, "width")?; + if text(literal, "kind")? != "int" || !["U32", "U64"].contains(&width) { + return Err(()); + } + let Schema::Word(max) = self.schema(width, 0)? else { + return Err(()); + }; + if number(get(literal, "value")?)? > max { + return Err(()); + } + Ok(Schema::Word(max)) + } + "if" => { + exact(value, &["kind", "predicate", "then", "else"])?; + self.predicate_at(get(value, "predicate")?, depth + 1)?; + let yes = self.expression(get(value, "then")?, depth + 1)?; + let no = self.expression(get(value, "else")?, depth + 1)?; + match (yes, no) { + ( + Schema::Bytes { lower: a, upper: b }, + Schema::Bytes { lower: c, upper: d }, + ) => Ok(Schema::Bytes { + lower: a.min(c), + upper: b.max(d), + }), + (a, b) if a == b => Ok(a), + _ => Err(()), + } + } + "call" => self.call(value, depth), + _ => Err(()), + } + } + + fn call(&mut self, value: &Value, depth: usize) -> Check { + exact(value, &["kind", "callee", "args", "typeArgs"])?; + let name = text(value, "callee")?; + let args = super::sequence(value, "args")?; + let types = super::sequence(value, "typeArgs")?; + let mut actual = Vec::new(); + for argument in args { + actual.push(self.expression(argument, depth + 1)?); + } + let core = self.core; + let prefix = format!("{}.", text(core, "coordinate")?); + let source_definition = name.strip_prefix(&prefix).and_then(|member| { + super::super::map_field(core, "functions") + .and_then(|functions| super::super::map_field(functions, member)) + }); + if let Some(definition) = source_definition { + let parameters = super::sequence(definition, "params")?; + if !types.is_empty() || actual.len() != parameters.len() { + return Err(()); + } + for (parameter, argument) in parameters.iter().zip(&actual) { + if !self.schema(text(parameter, "type")?, 0)?.includes(argument) { + return Err(()); + } + } + return self.schema(text(definition, "returnType")?, 0); + } + let exports = self.exports; + if let Some(function) = super::sequence(exports, "pureFunctions")? + .iter() + .find(|function| text(function, "coordinate") == Ok(name)) + { + if !types.is_empty() + || !args.is_empty() + || !super::sequence(function, "parameterTypes")?.is_empty() + { + return Err(()); + } + return self.schema(text(function, "returnType")?, 0); + } + let mut declared = Vec::new(); + for coordinate in types { + declared.push(self.schema(string(coordinate)?, 0)?); + } + match (name, actual.as_slice(), declared.as_slice()) { + ("core.bytes.length", [actual], [expected @ Schema::Bytes { .. }]) + if expected.includes(actual) => + { + Ok(Schema::Word(u64::MAX)) + } + ( + "core.bytes.concat", + [left, right], + [a @ Schema::Bytes { + lower: a_min, + upper: a_max, + }, b @ Schema::Bytes { + lower: b_min, + upper: b_max, + }], + ) if a.includes(left) && b.includes(right) => Ok(Schema::Bytes { + lower: a_min.checked_add(*b_min).ok_or(())?, + upper: a_max.checked_add(*b_max).ok_or(())?, + }), _ => Err(()), } } pub(super) fn predicate(&mut self, value: &Value) -> Check<()> { + self.predicate_at(value, 0) + } + + fn predicate_at(&mut self, value: &Value, depth: usize) -> Check<()> { + self.charge(depth)?; exact(value, &["kind", "op", "left", "right"])?; if text(value, "kind")? != "compare" { return Err(()); } - let left = self.expression(get(value, "left")?, 0)?; - let right = self.expression(get(value, "right")?, 0)?; - match (text(value, "op")?, left, right) { + let left = self.expression(get(value, "left")?, depth + 1)?; + let right = self.expression(get(value, "right")?, depth + 1)?; + if (matches!(left, Schema::Nominal { .. }) || matches!(right, Schema::Nominal { .. })) + && left != right + { + return Err(()); + } + match ( + text(value, "op")?, + left.representation(), + right.representation(), + ) { ("==", Schema::Bytes { .. }, Schema::Bytes { .. }) => Ok(()), ("==" | "<=", Schema::Word(a), Schema::Word(b)) if a == b => Ok(()), _ => Err(()), @@ -234,3 +396,43 @@ fn decimal(value: &str) -> Check { } Ok(number) } + +#[cfg(test)] +mod tests { + use super::*; + fn map(fields: impl IntoIterator) -> Value { + Value::Map( + fields + .into_iter() + .map(|(key, value)| (Value::Text(key.into()), value)) + .collect(), + ) + } + #[test] + fn distinct_nominals_without_source_functions_do_not_convert() -> Check<()> { + let nominal = |contract: &str| { + map([ + ("kind", Value::Text("Nominal".into())), + ("contract", Value::Text(contract.into())), + ("representation", Value::Text("U64".into())), + ]) + }; + let core = map([ + ("coordinate", Value::Text("example@1".into())), + ("types", map([("A", nominal("A")), ("B", nominal("B"))])), + ]); + let mut symbols = Symbols { + core: &core, + exports: &core, + declared: BTreeMap::new(), + live: BTreeSet::new(), + claimed: BTreeSet::new(), + work: 65_536, + }; + let a = symbols.schema("A", 0)?; + assert!(symbols.schema("example@1.A", 0)?.includes(&a)); + assert!(!symbols.schema("B", 0)?.includes(&a)); + assert!(!symbols.schema("U64", 0)?.includes(&a)); + Ok(()) + } +} diff --git a/crates/echo-edict-provider-verifier/src/executable_operation/source_functions.rs b/crates/echo-edict-provider-verifier/src/executable_operation/source_functions.rs new file mode 100644 index 000000000..fd9d77abf --- /dev/null +++ b/crates/echo-edict-provider-verifier/src/executable_operation/source_functions.rs @@ -0,0 +1,803 @@ +// SPDX-License-Identifier: Apache-2.0 +// © James Ross Ω FLYING•ROBOTS +//! Independent verifier judgment: inventory authority, eliminate dependency leaves, +//! then audit ordered lexical frames and conservative whole-operation charges. +mod types; +use super::ProviderRefusalV1; +use echo_edict_canonical::CanonicalValueV1 as Value; +use std::collections::{BTreeMap, BTreeSet}; +use types::{Charge, Schema, Types}; +type Check = Result; + +struct Definition<'a> { + source: bool, + params: &'a [Value], + body: &'a Value, + parameter_types: Vec, + result: Schema, +} +#[derive(Clone, Copy)] +struct Summary { + charge: Charge, + height: usize, +} +struct Expression { + schema: Schema, + charge: Charge, + height: usize, +} +struct Frame<'a> { + declared: BTreeMap<&'a str, &'a Value>, + values: BTreeMap<&'a str, (&'a Value, Schema)>, +} +impl<'a> Frame<'a> { + fn new( + declarations: impl IntoIterator, + types: &mut Types<'_>, + ) -> Check { + let mut declared = BTreeMap::new(); + for local in declarations { + types.local(local)?; + if declared.insert(string(get(local, "id")?)?, local).is_some() { + return Err(()); + } + } + Ok(Self { + declared, + values: BTreeMap::new(), + }) + } + fn define(&mut self, local: &'a Value, schema: Schema) -> Check<()> { + let id = string(get(local, "id")?)?; + if self.declared.get(id).copied() != Some(local) + || self.values.insert(id, (local, schema)).is_some() + { + return Err(()); + } + Ok(()) + } + fn complete(&self) -> Check<()> { + if self.values.len() == self.declared.len() { + Ok(()) + } else { + Err(()) + } + } +} + +pub(super) fn validate(core: &Value, exports: &Value, read: bool) -> Result<(), ProviderRefusalV1> { + audit(core, exports, read) + .map_err(|()| super::super::unsupported_semantics("core.source-functions-verification")) +} +fn audit(core: &Value, exports: &Value, read: bool) -> Check<()> { + let mut types = Types::new(core); + let definitions = inventory(core, exports, &mut types)?; + let mut pending = BTreeMap::new(); + let mut callers: BTreeMap<&str, Vec<&str>> = BTreeMap::new(); + for (name, definition) in &definitions { + let mut edges = BTreeSet::new(); + let mut expressions = Vec::new(); + for binding in sequence(get(definition.body, "bindings")?)? { + expressions.push((get(binding, "value")?, 0)); + } + expressions.push((get(definition.body, "result")?, 0)); + while let Some((expression, depth)) = expressions.pop() { + types.step(depth)?; + match string(get(expression, "kind")?)? { + "const" | "local" => {} + "field" => expressions.push((get(expression, "base")?, depth + 1)), + "record" => { + for (_, value) in members(get(expression, "fields")?)? { + expressions.push((value, depth + 1)); + } + } + "if" => { + let predicate = get(expression, "predicate")?; + expressions.push((get(predicate, "left")?, depth + 2)); + expressions.push((get(predicate, "right")?, depth + 2)); + expressions.push((get(expression, "then")?, depth + 1)); + expressions.push((get(expression, "else")?, depth + 1)); + } + "call" => { + let callee = string(get(expression, "callee")?)?; + if let Some(target) = definitions.get(callee) { + if !definition.source && target.source { + return Err(()); + } + edges.insert(callee); + } else if ![ + "core.bytes.length", + "core.bytes.concat", + "core.bytes.slice", + "core.integer.subtract", + ] + .contains(&callee) + { + return Err(()); + } + for argument in sequence(get(expression, "args")?)? { + expressions.push((argument, depth + 1)); + } + } + _ => return Err(()), + } + } + pending.insert(name.as_str(), edges.len()); + for edge in edges { + callers.entry(edge).or_default().push(name); + } + } + let mut ready: BTreeSet<&str> = pending + .iter() + .filter_map(|(name, degree)| (*degree == 0).then_some(*name)) + .collect(); + let mut summaries = BTreeMap::new(); + // No native recursion crosses a function edge. Dependencies receive their + // own complete summary before any caller can use it, in either name order. + while let Some(name) = ready.pop_first() { + types.step(0)?; + let definition = definitions.get(name).ok_or(())?; + let summary = Checker { + types: &mut types, + definitions: &definitions, + summaries: &summaries, + } + .function(definition)?; + if summary.height + 1 > 64 { + return Err(()); + } + summaries.insert(name.to_owned(), summary); + if let Some(users) = callers.get(name) { + for user in users { + let degree = pending.get_mut(user).ok_or(())?; + *degree = degree.checked_sub(1).ok_or(())?; + if *degree == 0 { + ready.insert(user); + } + } + } + } + if summaries.len() != definitions.len() { + return Err(()); + } + let mut checker = Checker { + types: &mut types, + definitions: &definitions, + summaries: &summaries, + }; + if super::map_field(core, "functions").is_none() { + return Ok(()); + } + for (_, intent) in members(get(core, "intents")?)? { + checker.intent(intent, read)?; + } + Ok(()) +} +fn inventory<'a>( + core: &'a Value, + exports: &'a Value, + types: &mut Types<'_>, +) -> Check>> { + let mut definitions = BTreeMap::new(); + let imported_functions = super::map_field(exports, "pureFunctions") + .map(sequence) + .transpose()? + .unwrap_or(&[]); + for export in imported_functions { + if string(get(export, "source")?)? != "edict" { + return Err(()); + } + let implementation = get(export, "body")?; + let body = get(implementation, "body")?; + for (value, key) in [ + (export, "parameterTypes"), + (export, "typeParameters"), + (implementation, "params"), + (body, "locals"), + (body, "bindings"), + ] { + if !sequence(get(value, key)?)?.is_empty() { + return Err(()); + } + } + exact(body, &["locals", "bindings", "result"])?; + let name = string(get(export, "coordinate")?)?; + if name.starts_with("core.") + || definitions + .insert( + name.to_owned(), + Definition { + source: false, + params: sequence(get(implementation, "params")?)?, + body, + parameter_types: vec![], + result: types.resolve(string(get(export, "returnType")?)?, 0)?, + }, + ) + .is_some() + { + return Err(()); + } + } + // Exported effects own their coordinates even when no source body calls them. + // This exclusion does not add effects to the pure-call inventory. + let effect_names = sequence(get(exports, "effects")?)? + .iter() + .map(|effect| string(get(effect, "coordinate")?)) + .collect::>>()?; + let source_functions = super::map_field(core, "functions") + .map(members) + .transpose()? + .unwrap_or(&[]); + for (name, definition) in source_functions { + types.step(0)?; + let name = string(name)?; + if !name.bytes().enumerate().all(|(index, byte)| { + byte == b'_' || byte.is_ascii_alphabetic() || (index > 0 && byte.is_ascii_digit()) + }) || super::map_field(get(core, "types")?, name).is_some() + || super::map_field(get(core, "intents")?, name).is_some() + { + return Err(()); + } + exact(definition, &["params", "returnType", "body"])?; + let body = get(definition, "body")?; + exact(body, &["locals", "bindings", "result"])?; + let params = sequence(get(definition, "params")?)?; + let mut parameter_types = Vec::new(); + for parameter in params { + parameter_types.push(types.local(parameter)?); + } + let name = format!("{}.{}", string(get(core, "coordinate")?)?, name); + if name.starts_with("core.") + || effect_names.contains(name.as_str()) + || definitions + .insert( + name, + Definition { + source: true, + params, + body, + parameter_types, + result: types.resolve(string(get(definition, "returnType")?)?, 0)?, + }, + ) + .is_some() + { + return Err(()); + } + } + Ok(definitions) +} + +struct Checker<'a, 'b> { + types: &'b mut Types<'a>, + definitions: &'b BTreeMap>, + summaries: &'b BTreeMap, +} +impl Checker<'_, '_> { + fn function(&mut self, definition: &Definition<'_>) -> Check { + let declarations = sequence(get(definition.body, "locals")?)?; + let mut frame = Frame::new(definition.params.iter().chain(declarations), self.types)?; + let mut height = definition.result.measure()?.height; + for (local, schema) in definition.params.iter().zip(&definition.parameter_types) { + height = height.max(schema.measure()?.height); + frame.define(local, self.types.copy_type(schema)?)?; + } + let mut charge = Charge::default(); + for binding in sequence(get(definition.body, "bindings")?)? { + exact(binding, &["kind", "binding", "value"])?; + if string(get(binding, "kind")?)? != "let" { + return Err(()); + } + let local = get(binding, "binding")?; + let value = self.expression(get(binding, "value")?, &frame, 0)?; + let declared = self.types.local(local)?; + if !declared.encloses(&value.schema) { + return Err(()); + } + let size = declared.measure()?; + charge.append(value.charge)?; + charge.append(Charge { + ticks: size.checks, + storage: 0, + })?; + height = height.max(value.height).max(size.height); + frame.define(local, declared)?; + } + frame.complete()?; + let value = self.expression(get(definition.body, "result")?, &frame, 0)?; + if !definition.result.encloses(&value.schema) { + return Err(()); + } + charge.append(value.charge)?; + charge.append(Charge { + ticks: definition.result.measure()?.checks, + storage: 0, + })?; + Ok(Summary { + charge, + height: height.max(value.height), + }) + } + fn expression(&mut self, value: &Value, frame: &Frame<'_>, depth: usize) -> Check { + self.types.step(depth)?; + let mut charge = Charge { + ticks: 1, + storage: 0, + }; + let mut height = 0; + let schema = match string(get(value, "kind")?)? { + "const" => { + exact(value, &["kind", "value"])?; + let literal = get(value, "value")?; + exact(literal, &["kind", "value", "width"])?; + let width = string(get(literal, "width")?)?; + if string(get(literal, "kind")?)? != "int" || !["U32", "U64"].contains(&width) { + return Err(()); + } + let Schema::Word(max) = self.types.resolve(width, 0)? else { + return Err(()); + }; + if unsigned(get(literal, "value")?)? > max { + return Err(()); + } + charge.append(Charge { + ticks: 1, + storage: 64, + })?; + Schema::Word(max) + } + "local" => { + exact(value, &["kind", "ref"])?; + let local = get(value, "ref")?; + let (declared, schema) = frame.values.get(string(get(local, "id")?)?).ok_or(())?; + if *declared != local { + return Err(()); + } + charge.append(schema.measure()?.copy)?; + self.types.copy_type(schema)? + } + "field" => { + exact(value, &["kind", "base", "field"])?; + let base = self.expression(get(value, "base")?, frame, depth + 1)?; + charge.append(base.charge)?; + height = base.height + 1; + let Schema::Fields(mut fields) = base.schema else { + return Err(()); + }; + fields.remove(string(get(value, "field")?)?).ok_or(())? + } + "record" => { + exact(value, &["kind", "fields"])?; + charge.storage = 64; + let mut fields = BTreeMap::new(); + for (name, child) in members(get(value, "fields")?)? { + let name = string(name)?; + let child = self.expression(child, frame, depth + 1)?; + height = height.max(child.height + 1); + charge.append(child.charge)?; + charge.append(Charge { + ticks: 0, + storage: 64 + name.len() as u128, + })?; + if fields.insert(name.to_owned(), child.schema).is_some() { + return Err(()); + } + } + Schema::Fields(fields) + } + "if" => { + exact(value, &["kind", "predicate", "then", "else"])?; + let (predicate, predicate_height) = + self.predicate(get(value, "predicate")?, frame, depth + 1)?; + let yes = self.expression(get(value, "then")?, frame, depth + 1)?; + let no = self.expression(get(value, "else")?, frame, depth + 1)?; + height = predicate_height.max(yes.height).max(no.height) + 1; + charge.append(predicate)?; + charge.append(Charge::choice(yes.charge, no.charge))?; + yes.schema.choice(no.schema)? + } + "call" => { + exact(value, &["kind", "callee", "args", "typeArgs"])?; + let name = string(get(value, "callee")?)?; + let arguments = sequence(get(value, "args")?)?; + let types = sequence(get(value, "typeArgs")?)?; + let mut argument_schemas = Vec::new(); + for argument in arguments { + let argument = self.expression(argument, frame, depth + 1)?; + charge.append(argument.charge)?; + height = height.max(argument.height + 1); + argument_schemas.push(argument.schema); + } + if let Some(definition) = self.definitions.get(name) { + if !types.is_empty() || arguments.len() != definition.parameter_types.len() { + return Err(()); + } + for (expected, actual) in + definition.parameter_types.iter().zip(&argument_schemas) + { + if !expected.encloses(actual) { + return Err(()); + } + let size = expected.measure()?; + charge.append(Charge { + ticks: size.checks, + storage: 0, + })?; + height = height.max(size.height + 1); + } + let summary = self.summaries.get(name).ok_or(())?; + charge.append(summary.charge)?; + height = height.max(summary.height + 1); + self.types.copy_type(&definition.result)? + } else { + let mut declared = Vec::new(); + for coordinate in types { + declared.push(self.types.resolve(string(coordinate)?, depth + 1)?); + } + height = height.max(1); + let (schema, intrinsic) = + intrinsic(name, arguments, &argument_schemas, &declared)?; + charge.append(intrinsic)?; + schema + } + } + _ => return Err(()), + }; + if depth.checked_add(height).ok_or(())? > 64 { + return Err(()); + } + Ok(Expression { + schema, + charge, + height, + }) + } + fn predicate( + &mut self, + predicate: &Value, + frame: &Frame<'_>, + depth: usize, + ) -> Check<(Charge, usize)> { + self.types.step(depth)?; + exact(predicate, &["kind", "op", "left", "right"])?; + if string(get(predicate, "kind")?)? != "compare" { + return Err(()); + } + let left = self.expression(get(predicate, "left")?, frame, depth + 1)?; + let right = self.expression(get(predicate, "right")?, frame, depth + 1)?; + let mut charge = left.charge; + charge.append(right.charge)?; + if (matches!(left.schema, Schema::Nominal { .. }) + || matches!(right.schema, Schema::Nominal { .. })) + && left.schema != right.schema + { + return Err(()); + } + let compare = match ( + string(get(predicate, "op")?)?, + left.schema.representation(), + right.schema.representation(), + ) { + ("==", Schema::Blob { high: a, .. }, Schema::Blob { high: b, .. }) => { + u128::from((*a).max(*b)) + } + ("==" | "<=", Schema::Word(a), Schema::Word(b)) if a == b => 0, + _ => return Err(()), + }; + charge.append(Charge { + ticks: compare + 1, + storage: 0, + })?; + let height = left.height.max(right.height) + 1; + if depth.checked_add(height).ok_or(())? > 64 { + return Err(()); + } + Ok((charge, height)) + } + fn intent(&mut self, intent: &Value, read: bool) -> Check<()> { + let body = get(intent, "body")?; + exact(body, &["locals", "nodes", "result"])?; + let mut frame = Frame::new(sequence(get(body, "locals")?)?, self.types)?; + let input = *frame.declared.get("arg.0").ok_or(())?; + if get(input, "type")? != get(intent, "input")? { + return Err(()); + } + let input_type = self.types.local(input)?; + let size = input_type.measure()?; + let mut charge = size.copy; + charge.append(Charge { + ticks: size.checks, + storage: 0, + })?; + frame.define(input, input_type)?; + if let Some(basis) = super::map_field(intent, "basis") { + let checked = self.expression(basis, &frame, 0)?; + if read { + charge.append(checked.charge)?; + } + } else if read { + return Err(()); + } + for constraint in sequence(get(intent, "inputConstraints")?)? { + charge.append(self.predicate(get(constraint, "predicate")?, &frame, 0)?.0)?; + } + for node in sequence(get(body, "nodes")?)? { + if read { + charge.append(Charge { + ticks: 1, + storage: 0, + })?; + } + match string(get(node, "kind")?)? { + "let" => { + let value = self.expression(get(node, "value")?, &frame, 0)?; + let local = get(node, "binding")?; + let expected = self.types.local(local)?; + if !expected.encloses(&value.schema) { + return Err(()); + } + charge.append(value.charge)?; + charge.append(Charge { + ticks: expected.measure()?.checks, + storage: 0, + })?; + frame.define(local, expected)?; + } + "require" if read => { + charge.append(self.predicate(get(node, "predicate")?, &frame, 0)?.0)?; + } + "effect" if read => { + charge.append(self.expression(get(node, "input")?, &frame, 0)?.charge)?; + let local = get(node, "binding")?; + let expected = self.types.local(local)?; + let Schema::Blob { high, .. } = expected.representation() else { + return Err(()); + }; + charge.append(Charge { + ticks: u128::from(*high) + 1, + storage: u128::from(*high) + 64, + })?; + frame.define(local, expected)?; + } + _ => return Err(()), + } + } + if !read { + frame.complete()?; + } + let output = self.expression(get(body, "result")?, &frame, 0)?; + let expected = self.types.resolve(string(get(intent, "output")?)?, 0)?; + if !expected.encloses(&output.schema) { + return Err(()); + } + let size = expected.measure()?; + charge.append(output.charge)?; + charge.append(size.copy)?; + charge.append(Charge { + ticks: size.checks, + storage: 0, + })?; + let ceiling = get(intent, "coreEvaluationBudget")?; + if charge.ticks > u128::from(unsigned(get(ceiling, "maxSteps")?)?) + || charge.storage > u128::from(unsigned(get(ceiling, "maxAllocatedBytes")?)?) + || size.encoded > u128::from(unsigned(get(ceiling, "maxOutputBytes")?)?) + { + return Err(()); + } + Ok(()) + } +} +fn intrinsic( + name: &str, + arguments: &[Value], + actual: &[Schema], + declared: &[Schema], +) -> Check<(Schema, Charge)> { + let literal = |value: &Value| -> Check { + if string(get(value, "kind")?)? != "const" { + return Err(()); + } + unsigned(get(get(value, "value")?, "value")?) + }; + match (name, actual, declared) { + ("core.bytes.length", [actual], [expected @ Schema::Blob { .. }]) + if expected.encloses(actual) => + { + Ok(( + Schema::Word(u64::MAX), + Charge { + ticks: 2, + storage: 64, + }, + )) + } + ( + "core.bytes.concat", + [left, right], + [a @ Schema::Blob { + low: a_low, + high: a_high, + }, b @ Schema::Blob { + low: b_low, + high: b_high, + }], + ) if a.encloses(left) && b.encloses(right) => { + let low = a_low.checked_add(*b_low).ok_or(())?; + let high = a_high.checked_add(*b_high).ok_or(())?; + Ok(( + Schema::Blob { low, high }, + Charge { + ticks: u128::from(high) + 3, + storage: u128::from(high) + 64, + }, + )) + } + ("core.integer.subtract", [left, right], [expected @ Schema::Word(_)]) + if expected.encloses(left) && expected.encloses(right) => + { + literal(&arguments[0])? + .checked_sub(literal(&arguments[1])?) + .ok_or(())?; + Ok(( + expected.clone(), + Charge { + ticks: 3, + storage: 64, + }, + )) + } + ( + "core.bytes.slice", + [actual, Schema::Word(a), Schema::Word(b)], + [expected @ Schema::Blob { low, .. }], + ) if *a == u64::MAX && *b == u64::MAX && expected.encloses(actual) => { + let start = literal(&arguments[1])?; + let end = literal(&arguments[2])?; + if start > end || end > *low { + return Err(()); + } + let high = end - start; + Ok(( + Schema::Blob { low: 0, high }, + Charge { + ticks: u128::from(high) + 4, + storage: u128::from(high) + 64, + }, + )) + } + _ => Err(()), + } +} +fn get<'a>(value: &'a Value, name: &str) -> Check<&'a Value> { + super::map_field(value, name).ok_or(()) +} +fn members(value: &Value) -> Check<&[(Value, Value)]> { + if let Value::Map(values) = value { + Ok(values) + } else { + Err(()) + } +} +fn sequence(value: &Value) -> Check<&[Value]> { + if let Value::Array(values) = value { + Ok(values) + } else { + Err(()) + } +} +fn string(value: &Value) -> Check<&str> { + if let Value::Text(value) = value { + if !value.is_empty() && value.len() <= 1024 { + return Ok(value); + } + } + Err(()) +} +fn unsigned(value: &Value) -> Check { + if let Value::Integer(value) = value { + u64::try_from(*value).map_err(|_| ()) + } else { + Err(()) + } +} +fn exact(value: &Value, fields: &[&str]) -> Check<()> { + let values = members(value)?; + if values.len() != fields.len() { + return Err(()); + } + for (name, _) in values { + if !fields.contains(&string(name)?) { + return Err(()); + } + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn map(fields: impl IntoIterator) -> Value { + Value::Map( + fields + .into_iter() + .map(|(key, value)| (Value::Text(key.into()), value)) + .collect(), + ) + } + + #[test] + fn imported_inventory_is_audited_without_source_functions() { + let core = map([("types", map([])), ("intents", map([]))]); + let exports = map([ + ("effects", Value::Array(vec![])), + ( + "pureFunctions", + Value::Array(vec![map([("source", Value::Text("component".into()))])]), + ), + ]); + assert!(validate(&core, &exports, true).is_err()); + } + + #[test] + fn empty_inventory_without_source_functions_is_valid() { + let core = map([("types", map([])), ("intents", map([]))]); + let exports = map([ + ("effects", Value::Array(vec![])), + ("pureFunctions", Value::Array(vec![])), + ]); + assert!(validate(&core, &exports, true).is_ok()); + } + #[test] + fn source_functions_accept_named_unsigned_int_aliases() { + let parameter = map([ + ("id", Value::Text("arg.0".into())), + ("type", Value::Text("Count".into())), + ("alphaName", Value::Text("count".into())), + ]); + let core = map([ + ("coordinate", Value::Text("example@1".into())), + ( + "types", + map([( + "Count", + map([ + ("kind", Value::Text("Int".into())), + ("width", Value::Text("U64".into())), + ]), + )]), + ), + ("intents", map([])), + ( + "functions", + map([( + "identity", + map([ + ("params", Value::Array(vec![parameter.clone()])), + ("returnType", Value::Text("Count".into())), + ( + "body", + map([ + ("locals", Value::Array(vec![])), + ("bindings", Value::Array(vec![])), + ( + "result", + map([ + ("kind", Value::Text("local".into())), + ("ref", parameter), + ]), + ), + ]), + ), + ]), + )]), + ), + ]); + let exports = map([ + ("effects", Value::Array(vec![])), + ("pureFunctions", Value::Array(vec![])), + ]); + assert!(validate(&core, &exports, false).is_ok()); + } +} diff --git a/crates/echo-edict-provider-verifier/src/executable_operation/source_functions/types.rs b/crates/echo-edict-provider-verifier/src/executable_operation/source_functions/types.rs new file mode 100644 index 000000000..5c7a3caa9 --- /dev/null +++ b/crates/echo-edict-provider-verifier/src/executable_operation/source_functions/types.rs @@ -0,0 +1,241 @@ +// SPDX-License-Identifier: Apache-2.0 +// © James Ross Ω FLYING•ROBOTS +//! Verifier-owned bounded representations and independently accumulated charges. +use super::{exact, get, members, string, unsigned, Check}; +use echo_edict_canonical::CanonicalValueV1 as Value; +use std::collections::BTreeMap; + +#[derive(Clone, PartialEq, Eq)] +pub(super) enum Schema { + Word(u64), + Blob { + low: u64, + high: u64, + }, + Fields(BTreeMap), + Nominal { + identity: String, + representation: Box, + }, +} +#[derive(Clone, Copy, Default)] +pub(super) struct Charge { + pub ticks: u128, + pub storage: u128, +} +impl Charge { + pub(super) fn append(&mut self, other: Self) -> Check<()> { + self.ticks = self.ticks.checked_add(other.ticks).ok_or(())?; + self.storage = self.storage.checked_add(other.storage).ok_or(())?; + if self.ticks > u128::from(u64::MAX) || self.storage > u128::from(u64::MAX) { + return Err(()); + } + Ok(()) + } + pub(super) fn choice(a: Self, b: Self) -> Self { + Self { + ticks: a.ticks.max(b.ticks), + storage: a.storage.max(b.storage), + } + } +} +#[derive(Clone, Copy)] +pub(super) struct Size { + pub copy: Charge, + pub checks: u128, + pub encoded: u128, + pub height: usize, +} +impl Schema { + pub(super) fn representation(&self) -> &Self { + match self { + Self::Nominal { representation, .. } => representation.representation(), + _ => self, + } + } + pub(super) fn encloses(&self, other: &Self) -> bool { + match (self, other) { + (Self::Blob { low, high }, Self::Blob { low: a, high: b }) => low <= a && b <= high, + (Self::Fields(a), Self::Fields(b)) => { + a.len() == b.len() + && a.iter() + .all(|(name, ty)| b.get(name).is_some_and(|value| ty.encloses(value))) + } + _ => self == other, + } + } + pub(super) fn choice(self, other: Self) -> Check { + match (self, other) { + (Self::Blob { low: a, high: b }, Self::Blob { low: c, high: d }) => Ok(Self::Blob { + low: a.min(c), + high: b.max(d), + }), + (a, b) if a == b => Ok(a), + _ => Err(()), + } + } + pub(super) fn measure(&self) -> Check { + let mut size = Size { + copy: Charge { + ticks: 1, + storage: 64, + }, + checks: 1, + encoded: 9, + height: 0, + }; + match self { + // Nominal types retain identity without adding physical value tags. + Self::Nominal { representation, .. } => return representation.measure(), + Self::Word(_) => {} + Self::Blob { high, .. } => { + size.copy.storage += u128::from(*high); + size.encoded += u128::from(*high); + } + Self::Fields(fields) => { + for (name, ty) in fields { + let child = ty.measure()?; + size.copy.append(child.copy)?; + size.copy.append(Charge { + ticks: 1, + storage: 64 + name.len() as u128, + })?; + size.checks = size.checks.checked_add(child.checks).ok_or(())?; + size.encoded = size + .encoded + .checked_add(9 + name.len() as u128) + .and_then(|value| value.checked_add(child.encoded)) + .ok_or(())?; + size.height = size.height.max(child.height + 1); + } + } + } + if size.copy.storage > u128::from(u64::MAX) || size.encoded > u128::from(u64::MAX) { + return Err(()); + } + Ok(size) + } +} + +pub(super) struct Types<'a> { + pub module: &'a Value, + remaining: usize, +} +impl<'a> Types<'a> { + pub(super) fn new(module: &'a Value) -> Self { + Self { + module, + remaining: 65_536, + } + } + pub(super) fn step(&mut self, depth: usize) -> Check<()> { + if depth > 64 { + return Err(()); + } + self.remaining = self.remaining.checked_sub(1).ok_or(())?; + Ok(()) + } + pub(super) fn copy_type(&mut self, schema: &Schema) -> Check { + let count = usize::try_from(schema.measure()?.copy.ticks).map_err(|_| ())?; + self.remaining = self.remaining.checked_sub(count).ok_or(())?; + Ok(schema.clone()) + } + pub(super) fn resolve(&mut self, name: &str, depth: usize) -> Check { + self.step(depth)?; + if let Some(width) = match name { + "U8" => Some(u8::MAX.into()), + "U16" => Some(u16::MAX.into()), + "U32" => Some(u32::MAX.into()), + "U64" => Some(u64::MAX), + _ => None, + } { + return Ok(Schema::Word(width)); + } + if let Some(spec) = name + .strip_prefix("Bytes<") + .and_then(|s| s.strip_suffix('>')) + { + let decimal = |s: &str| -> Check { + let value = s.parse::().map_err(|_| ())?; + if value.to_string() != s { + return Err(()); + } + Ok(value) + }; + if let Some(s) = spec.strip_prefix("max=") { + return Ok(Schema::Blob { + low: 0, + high: decimal(s)?, + }); + } + if let Some(s) = spec.strip_prefix("exact=") { + let value = decimal(s)?; + return Ok(Schema::Blob { + low: value, + high: value, + }); + } + if let Some((a, b)) = spec + .strip_prefix("min=") + .and_then(|s| s.split_once(",max=")) + { + let (low, high) = (decimal(a)?, decimal(b)?); + if low < high { + return Ok(Schema::Blob { low, high }); + } + } + return Err(()); + } + let prefix = format!("{}.", string(get(self.module, "coordinate")?)?); + let resolved = name.strip_prefix(&prefix).unwrap_or(name); + let definition = get(get(self.module, "types")?, resolved)?; + match string(get(definition, "kind")?)? { + "Int" => self.resolve(string(get(definition, "width")?)?, depth + 1), + "Nominal" => { + let identity = string(get(definition, "contract")?)?; + if identity != resolved { + return Err(()); + } + let representation = + self.resolve(string(get(definition, "representation")?)?, depth + 1)?; + Ok(Schema::Nominal { + identity: identity.to_owned(), + representation: Box::new(representation), + }) + } + "Bytes" => { + let high = unsigned(get(definition, "max")?)?; + let low = super::super::map_field(definition, "min") + .map(unsigned) + .transpose()? + .unwrap_or(0); + if low > high { + return Err(()); + } + Ok(Schema::Blob { low, high }) + } + "Record" => { + let mut fields = BTreeMap::new(); + for (name, coordinate) in members(get(definition, "fields")?)? { + if fields + .insert( + string(name)?.to_owned(), + self.resolve(string(coordinate)?, depth + 1)?, + ) + .is_some() + { + return Err(()); + } + } + Ok(Schema::Fields(fields)) + } + _ => Err(()), + } + } + pub(super) fn local(&mut self, value: &Value) -> Check { + exact(value, &["id", "type", "alphaName"])?; + string(get(value, "id")?)?; + string(get(value, "alphaName")?)?; + self.resolve(string(get(value, "type")?)?, 0) + } +} diff --git a/crates/echo-edict-provider-verifier/tests/bounded_read.rs b/crates/echo-edict-provider-verifier/tests/bounded_read.rs index 6678e188b..b2de9416e 100644 --- a/crates/echo-edict-provider-verifier/tests/bounded_read.rs +++ b/crates/echo-edict-provider-verifier/tests/bounded_read.rs @@ -15,6 +15,9 @@ use echo_edict_provider_lowerer::{ use echo_edict_provider_verifier as verifier; +#[path = "bounded_read/source_functions.rs"] +mod source_functions; + const PACKAGE: &str = "echo.operation-package/v1"; fn bound(coordinate: &str, domain: &str, bytes: Vec) -> BoundArtifact { diff --git a/crates/echo-edict-provider-verifier/tests/bounded_read/imported_calls.rs b/crates/echo-edict-provider-verifier/tests/bounded_read/imported_calls.rs new file mode 100644 index 000000000..a5afd2665 --- /dev/null +++ b/crates/echo-edict-provider-verifier/tests/bounded_read/imported_calls.rs @@ -0,0 +1,308 @@ +// SPDX-License-Identifier: Apache-2.0 +// © James Ross Ω FLYING•ROBOTS +//! Synthetic mixed read/pure authority; retained export body, not public compilation. +use super::*; + +fn retained_unsigned_export() -> Value { + let raw = + hex::decode(include_str!("../fixtures/compiler-produced-pure/package.cbor.hex").trim()) + .expect("retained public compiler package"); + let package = decode(&raw).expect("package"); + let Value::Bytes(program) = field(&package, "program") else { + panic!("program") + }; + let program = decode(program).expect("program"); + let Value::Bytes(exports) = field(&program, "lawpack_exports_artifact") else { + panic!("exports") + }; + let exports = decode(exports).expect("exports"); + let Value::Array(functions) = field(&exports, "pureFunctions") else { + panic!("functions") + }; + let [function] = functions.as_slice() else { + panic!("one retained helper") + }; + assert_eq!( + field(function, "coordinate"), + &text("jedit.text@1.maxCreatedLeafCount") + ); + assert_eq!(field(function, "returnType"), &text("U64")); + assert_eq!(field(function, "parameterTypes"), &Value::Array(vec![])); + function.clone() +} + +fn add_imported_authority( + source: &mut LoweringRequestV1, + core: &mut Value, + target: &mut Value, + authored: &mut String, +) { + let mut exports = + decode(&source.semantic_inputs[1].artifact.artifact.bytes).expect("read exports"); + let Value::Array(functions) = field_mut(&mut exports, "pureFunctions") else { + panic!("functions") + }; + assert!(functions.is_empty()); + functions.push(retained_unsigned_export()); + let exports_id = source.semantic_inputs[1] + .artifact + .reference + .coordinate + .clone(); + let exports_owner = digest(&exports_id, &exports).expect("exports owner identity"); + source.semantic_inputs[1].artifact = bound( + &exports_id, + "edict.lawpack-exports/v1", + encode(&exports).expect("exports bytes"), + ); + let mut lawpack = decode(&source.semantic_inputs[2].artifact.artifact.bytes).expect("lawpack"); + *field_mut(field_mut(&mut lawpack, "exports"), "digest") = + Value::Array(vec![text("sha256"), Value::Bytes(exports_owner.into())]); + let old = format!( + "sha256:{}", + hex::encode(&source.semantic_inputs[2].artifact.reference.digest.bytes) + ); + source.semantic_inputs[2].artifact = bound( + "jedit.text@1", + "edict.lawpack/v1", + encode(&lawpack).expect("lawpack bytes"), + ); + let identity = source.semantic_inputs[2] + .artifact + .reference + .digest + .bytes + .clone(); + let lawpack_ref = map([ + ("id", text("jedit.text@1")), + ( + "digest", + Value::Array(vec![text("sha256"), Value::Bytes(identity.clone())]), + ), + ]); + let Value::Array(imports) = field_mut(core, "imports") else { + panic!("imports") + }; + *field_mut(&mut imports[0], "ref") = lawpack_ref.clone(); + *field_mut(field_mut(target, "semanticClosure"), "lawpacks") = Value::Array(vec![lawpack_ref]); + assert_eq!(authored.matches(&old).count(), 1); + *authored = authored.replace(&old, &format!("sha256:{}", hex::encode(identity))); +} + +fn imported_call_request(same_package: bool, unknown: bool) -> LoweringRequestV1 { + let mut source = read_helper(); + let mut core = decode(&source.core.artifact.bytes).expect("core"); + let mut target = decode(&source.semantic_inputs[5].artifact.artifact.bytes).expect("target"); + let mut projection = + decode(&source.semantic_inputs[6].artifact.artifact.bytes).expect("projection"); + let Value::Bytes(authored) = + decode(&source.semantic_inputs[3].artifact.artifact.bytes).expect("source") + else { + panic!("source bytes") + }; + let mut authored = String::from_utf8(authored).expect("source UTF-8"); + add_imported_authority(&mut source, &mut core, &mut target, &mut authored); + let member = if unknown { + "unknownImportedHelper" + } else { + "maxCreatedLeafCount" + }; + let binding = local("source.imported", "U64"); + let call = map([ + ("kind", text("call")), + ("callee", text(&format!("jedit.text@1.{member}"))), + ("args", Value::Array(vec![])), + ("typeArgs", Value::Array(vec![])), + ]); + let body = field_mut(intent(&mut core), "body"); + let Value::Array(locals) = field_mut(body, "locals") else { + panic!("locals") + }; + locals.push(binding.clone()); + let Value::Array(nodes) = field_mut(body, "nodes") else { + panic!("nodes") + }; + nodes.push(map([ + ("kind", text("let")), + ("binding", binding.clone()), + ("value", call.clone()), + ])); + let target_intent = intent(&mut target); + let Value::Array(bindings) = field_mut(target_intent, "pureBindings") else { + panic!("bindings") + }; + bindings.push(map([ + ("id", text("replaceRange.binding.imported")), + ("binding", binding), + ("value", call), + ])); + let Value::Array(order) = field_mut(target_intent, "executionOrder") else { + panic!("order") + }; + order.push(text("replaceRange.binding.imported")); + assert_eq!(authored.matches("intent replaceRange(").count(), 1); + authored = authored.replace("intent replaceRange(", + "fn retain(value: text.FactBytes) -> text.FactBytes { let bytes = value; return bytes; }\nintent replaceRange("); + assert_eq!(authored.matches(" return actual;").count(), 1); + authored = authored.replace( + " return actual;", + &format!( + " let retained = retain(actual);\n let imported = text.{member}();\n return retained;"), + ); + let previous = source.core.reference.coordinate.clone(); + let coordinate = if same_package { + "jedit.text@1" + } else { + &previous + }; + if same_package { + for value in [&mut core, &mut target, &mut projection] { + rename_owned_coordinates(value, &previous, coordinate); + } + // This isolates call ownership using the supported relative Core table + // spelling. It does not establish exact-key-first type lookup or public + // compiler compatibility for a same-package imported type closure. + let Value::Map(types) = field_mut(&mut core, "types") else { + panic!("types") + }; + let prefix = format!("{coordinate}."); + let mut names = std::collections::BTreeSet::new(); + for (key, definition) in types { + let Value::Text(name) = key else { + panic!("type name") + }; + if let Some(relative) = name.strip_prefix(&prefix) { + assert_ne!(field(definition, "kind"), &text("Nominal")); + *name = relative.to_owned(); + } + assert!(names.insert(name.clone())); + } + let header = format!("package {previous};"); + assert_eq!(authored.matches(&header).count(), 1); + authored = authored.replace(&header, &format!("package {coordinate};")); + } + source.core = bound( + coordinate, + "edict.core.module/v1", + encode(&core).expect("core bytes"), + ); + *field_mut(field_mut(&mut target, "semanticClosure"), "sourceCore") = map([ + ("id", text(coordinate)), + ( + "digest", + Value::Array(vec![ + text("sha256"), + Value::Bytes(source.core.reference.digest.bytes.clone()), + ]), + ), + ]); + source.semantic_inputs[3].artifact = bound( + coordinate, + "edict.source/v1", + encode(&Value::Bytes(authored.into_bytes())).expect("source bytes"), + ); + source.semantic_inputs[5].artifact = bound( + "echo.span-ir/v2", + "edict.target-ir.artifact/v1", + encode(&target).expect("target bytes"), + ); + source.semantic_inputs[6].artifact = bound( + &format!("{coordinate}.replaceRange"), + "edict.result-projection.artifact/v1", + encode(&projection).expect("projection bytes"), + ); + source +} + +fn independently_rebound_request(candidate: LoweringRequestV1) -> verifier::VerificationRequestV1 { + let admitted = imported_call_request(false, false); + for index in [0, 1, 2, 4] { + assert_eq!( + admitted.semantic_inputs[index].artifact, candidate.semantic_inputs[index].artifact, + "package naming or unknown call cannot change imported authority" + ); + } + // Only the separate-package positive is lowered. Same-package verification + // must remain observable even while its independent lowerer control is RED. + let mut verification = request(admitted); + verification.core = transport(candidate.core.clone()); + verification.target_ir = transport(candidate.semantic_inputs[5].artifact.clone()); + for input in &mut verification.semantic_inputs { + if let Some(replacement) = candidate + .semantic_inputs + .iter() + .find(|value| value.role == input.role) + { + input.artifact = transport(replacement.artifact.clone()); + } + } + change_package(&mut verification, |package| { + *field_mut(package, "operation_coordinate") = text(&format!( + "{}.replaceRange", + candidate.core.reference.coordinate + )); + let closure = field_mut(package, "semantic_closure"); + for (key, artifact) in [ + ("canonical_meaning_identity", &candidate.core), + ("core_identity", &candidate.core), + ("target_ir_identity", &candidate.semantic_inputs[5].artifact), + ( + "edict_source_identity", + &candidate.semantic_inputs[3].artifact, + ), + ] { + *field_mut(closure, key) = Value::Bytes(artifact.reference.digest.bytes.clone()); + } + let Value::Bytes(raw) = field(package, "program") else { + panic!("program bytes") + }; + let mut program = decode(raw).expect("program"); + for (key, artifact) in [ + ("core_artifact", &candidate.core), + ("target_ir_artifact", &candidate.semantic_inputs[5].artifact), + ("source_artifact", &candidate.semantic_inputs[3].artifact), + ( + "result_projection_artifact", + &candidate.semantic_inputs[6].artifact, + ), + ] { + *field_mut(&mut program, key) = Value::Bytes(artifact.artifact.bytes.clone()); + } + *field_mut(package, "program") = Value::Bytes(encode(&program).expect("program bytes")); + }); + verification +} + +#[test] +fn separate_package_direct_imported_pure_call_is_admitted() { + let source = imported_call_request(false, false); + lower(source.clone()).expect("separate-package direct pure call lowers"); + let response = verifier::verify(request(source)).expect("separate-package independent report"); + let report = decode(&response.outputs[0].artifact.bytes).expect("report"); + assert_eq!(field(&report, "outcome"), &text("accepted")); +} + +#[test] +fn lowerer_same_package_imported_pure_call_is_not_claimed_by_source_prefix() { + lower(imported_call_request(true, false)) + .expect("exact source membership permits disjoint imported helper"); +} + +#[test] +fn verifier_same_package_imported_pure_call_is_not_claimed_by_source_prefix() { + let verification = independently_rebound_request(imported_call_request(true, false)); + let response = verifier::verify(verification).expect("coherently rebound same-package report"); + let report = decode(&response.outputs[0].artifact.bytes).expect("report"); + assert_eq!(field(&report, "outcome"), &text("accepted")); +} + +#[test] +fn an_unknown_qualified_pure_call_remains_rejected_by_both_judgments() { + let source = imported_call_request(true, true); + let refusal = lower(source.clone()).expect_err("unknown imported coordinate is not authority"); + assert_eq!( + refusal.kind, + echo_edict_provider_lowerer::ProviderRefusalKind::UnsupportedSemantics + ); + assert_rejected(independently_rebound_request(source)); +} diff --git a/crates/echo-edict-provider-verifier/tests/bounded_read/source_functions.rs b/crates/echo-edict-provider-verifier/tests/bounded_read/source_functions.rs new file mode 100644 index 000000000..532c06cfd --- /dev/null +++ b/crates/echo-edict-provider-verifier/tests/bounded_read/source_functions.rs @@ -0,0 +1,573 @@ +// SPDX-License-Identifier: Apache-2.0 +// © James Ross Ω FLYING•ROBOTS +//! Native, synthetically rebound read-provider coverage; not public compilation. +use super::*; + +fn text(value: &str) -> Value { + Value::Text(value.to_owned()) +} +fn map(fields: impl IntoIterator) -> Value { + Value::Map( + fields + .into_iter() + .map(|(name, value)| (text(name), value)) + .collect(), + ) +} +fn local(id: &str, ty: &str) -> Value { + map([ + ("id", text(id)), + ("alphaName", text(id)), + ("type", text(ty)), + ]) +} +fn reference(local: &Value) -> Value { + map([("kind", text("local")), ("ref", local.clone())]) +} +fn field_mut<'a>(value: &'a mut Value, name: &str) -> &'a mut Value { + let Value::Map(entries) = value else { + panic!("map") + }; + &mut entries + .iter_mut() + .find(|(key, _)| key == &text(name)) + .expect("field") + .1 +} +fn insert(value: &mut Value, name: &str, replacement: Value) { + let Value::Map(entries) = value else { + panic!("map") + }; + assert!(!entries.iter().any(|(key, _)| key == &text(name))); + entries.push((text(name), replacement)); +} +fn intent(value: &mut Value) -> &mut Value { + field_mut(field_mut(value, "intents"), "replaceRange") +} +fn read_helper() -> LoweringRequestV1 { + read_helper_with_budget(4_194_304) +} +fn read_helper_with_budget(max_steps: u64) -> LoweringRequestV1 { + let mut source = fixture(); + let mut core = decode(&source.core.artifact.bytes).expect("core"); + let mut target = decode(&source.semantic_inputs[5].artifact.artifact.bytes).expect("target"); + let mut projection = + decode(&source.semantic_inputs[6].artifact.artifact.bytes).expect("projection"); + let Value::Text(coordinate) = field(&core, "coordinate") else { + panic!("coordinate") + }; + let coordinate = coordinate.clone(); + let Value::Text(output_type) = field(intent(&mut core), "output") else { + panic!("output type") + }; + let output_type = output_type.clone(); + let parameter = local("arg.0", &output_type); + let helper_local = local("local.0", &output_type); + insert( + &mut core, + "functions", + map([( + "retain", + map([ + ("params", Value::Array(vec![parameter.clone()])), + ("returnType", text(&output_type)), + ( + "body", + map([ + ("locals", Value::Array(vec![helper_local.clone()])), + ( + "bindings", + Value::Array(vec![map([ + ("kind", text("let")), + ("binding", helper_local.clone()), + ("value", reference(¶meter)), + ])]), + ), + ("result", reference(&helper_local)), + ]), + ), + ]), + )]), + ); + let saved = local("source.return", &output_type); + let body = field_mut(intent(&mut core), "body"); + let call = map([ + ("kind", text("call")), + ("callee", text(&format!("{coordinate}.retain"))), + ("args", Value::Array(vec![field(body, "result").clone()])), + ("typeArgs", Value::Array(vec![])), + ]); + let Value::Array(locals) = field_mut(body, "locals") else { + panic!("locals") + }; + locals.push(saved.clone()); + let Value::Array(nodes) = field_mut(body, "nodes") else { + panic!("nodes") + }; + nodes.push(map([ + ("kind", text("let")), + ("binding", saved.clone()), + ("value", call.clone()), + ])); + *field_mut(body, "result") = reference(&saved); + let target_intent = intent(&mut target); + let binding = map([ + ("id", text("replaceRange.binding.0")), + ("binding", saved.clone()), + ("value", call), + ]); + let Value::Map(entries) = target_intent else { + panic!("intent") + }; + match entries + .iter_mut() + .find(|(key, _)| key == &text("pureBindings")) + { + Some((_, Value::Array(bindings))) => bindings.push(binding), + None => entries.push((text("pureBindings"), Value::Array(vec![binding]))), + _ => panic!("pure bindings"), + } + let Value::Array(order) = field_mut(target_intent, "executionOrder") else { + panic!("order") + }; + order.push(text("replaceRange.binding.0")); + *field_mut(target_intent, "result") = reference(&saved); + *field_mut(&mut projection, "expression") = map([ + ("kind", text("source")), + ( + "source", + map([ + ("kind", text("pureBinding")), + ("bindingId", text("replaceRange.binding.0")), + ]), + ), + ("path", Value::Array(vec![])), + ]); + // This synthetic fixture explicitly increases its declared step ceiling. + // At FactBytes.max=1 MiB, read work plus the required byte equality already + // exceeds the legacy 1 MiB step ceiling. Allocation stays at its old 16 MiB. + for artifact in [&mut core, &mut target] { + *field_mut( + field_mut(intent(artifact), "coreEvaluationBudget"), + "maxSteps", + ) = Value::Integer(i128::from(max_steps)); + } + fork_adapter_budget(&mut source, &mut core, &mut target, max_steps); + source.core = bound( + &coordinate, + "edict.core.module/v1", + encode(&core).expect("core bytes"), + ); + *field_mut(field_mut(&mut target, "semanticClosure"), "sourceCore") = map([ + ("id", text(&coordinate)), + ( + "digest", + Value::Array(vec![ + text("sha256"), + Value::Bytes(source.core.reference.digest.bytes.clone()), + ]), + ), + ]); + for (index, value) in [(5, target), (6, projection)] { + let artifact = &mut source.semantic_inputs[index].artifact; + *artifact = bound( + &artifact.reference.coordinate, + &artifact.artifact.domain, + encode(&value).expect("changed artifact"), + ); + } + source +} + +#[test] +fn bounded_read_source_function_is_admitted_after_a_real_read_producer() { + let source = read_helper(); + let lowered = lower(source.clone()).expect("read plus pure source helper lowers"); + assert_eq!( + lowered, + lower(source.clone()).expect("repeat native lowering") + ); + let verification = request(source); + let response = + verifier::verify(verification.clone()).expect("independent read helper verification"); + let report = decode(&response.outputs[0].artifact.bytes).expect("report"); + assert_eq!(field(&report, "outcome"), &text("accepted")); + assert_eq!( + response, + verifier::verify(verification).expect("repeat independent judgment") + ); +} + +#[test] +fn bounded_read_source_call_target_substitution_rejects_after_coherent_rebinding() { + let mut verification = request(read_helper()); + changed_target(&mut verification, |intent| { + let Value::Array(bindings) = field_mut(intent, "pureBindings") else { + panic!("bindings") + }; + // Wrong arity is rehashed into the package and Target, while authenticated + // Core still calls retain(actual). The lowerer never endorses this change. + *field_mut(field_mut(&mut bindings[0], "value"), "args") = Value::Array(vec![]); + }); + if let Ok(response) = verifier::verify(verification) { + let report = decode(&response.outputs[0].artifact.bytes).expect("report"); + assert_ne!(field(&report, "outcome"), &text("accepted")); + } +} + +// A budget is adapter-owned authority, not a free Core number. This synthetic +// fixture forks the complete adapter/lawpack/source/import closure explicitly. +fn fork_adapter_budget( + source: &mut LoweringRequestV1, + core: &mut Value, + target: &mut Value, + max_steps: u64, +) { + let mut adapter = decode(&source.semantic_inputs[0].artifact.artifact.bytes).expect("adapter"); + let Value::Map(profiles) = field(&adapter, "operationProfiles") else { + panic!("profiles") + }; + let Value::Text(obligation) = field(&profiles[0].1, "budgetObligation") else { + panic!("budget obligation") + }; + let obligation = obligation.clone(); + *field_mut( + field_mut(field_mut(&mut adapter, "budgets"), &obligation), + "maxSteps", + ) = Value::Integer(i128::from(max_steps)); + let adapter_id = source.semantic_inputs[0] + .artifact + .reference + .coordinate + .clone(); + let owner_identity = digest(&adapter_id, &adapter) + .expect("adapter owner identity") + .to_vec(); + source.semantic_inputs[0].artifact = bound( + &adapter_id, + "edict.lawpack-adapter/v1", + encode(&adapter).expect("adapter bytes"), + ); + let mut lawpack = decode(&source.semantic_inputs[2].artifact.artifact.bytes).expect("lawpack"); + let Value::Array(adapters) = field_mut(&mut lawpack, "targetAdapters") else { + panic!("adapters") + }; + *field_mut(field_mut(&mut adapters[0], "adapter"), "digest") = + Value::Array(vec![text("sha256"), Value::Bytes(owner_identity)]); + let old_digest = source.semantic_inputs[2] + .artifact + .reference + .digest + .bytes + .clone(); + source.semantic_inputs[2].artifact = bound( + "jedit.text@1", + "edict.lawpack/v1", + encode(&lawpack).expect("lawpack bytes"), + ); + let new_digest = source.semantic_inputs[2] + .artifact + .reference + .digest + .bytes + .clone(); + let lawpack_ref = map([ + ("id", text("jedit.text@1")), + ( + "digest", + Value::Array(vec![text("sha256"), Value::Bytes(new_digest.clone())]), + ), + ]); + let Value::Array(imports) = field_mut(core, "imports") else { + panic!("imports") + }; + *field_mut(&mut imports[0], "ref") = lawpack_ref.clone(); + *field_mut(field_mut(target, "semanticClosure"), "lawpacks") = Value::Array(vec![lawpack_ref]); + let Value::Bytes(authored) = + decode(&source.semantic_inputs[3].artifact.artifact.bytes).expect("source") + else { + panic!("source bytes") + }; + let authored = String::from_utf8(authored).expect("UTF-8 source"); + let old = format!("sha256:{}", hex::encode(old_digest)); + assert_eq!( + authored.matches(&old).count(), + 1, + "one imported lawpack pin" + ); + let authored = authored.replace(&old, &format!("sha256:{}", hex::encode(new_digest))); + source.semantic_inputs[3].artifact = bound( + "jedit.text.replace_range@1", + "edict.source/v1", + encode(&Value::Bytes(authored.into_bytes())).expect("source bytes"), + ); +} + +#[test] +fn bounded_read_source_helper_refuses_the_original_insufficient_step_ceiling() { + let rejected = read_helper_with_budget(1_048_576); + let refusal = + lower(rejected.clone()).expect_err("whole-operation cost exceeds the original authority"); + assert_eq!( + refusal.kind, + echo_edict_provider_lowerer::ProviderRefusalKind::UnsupportedSemantics + ); + // Build only the valid positive through the lowerer, then independently + // replace every affected budget/authority carrier from the rejected request. + let mut verification = request(read_helper()); + verification.core = transport(rejected.core.clone()); + verification.target_ir = transport(rejected.semantic_inputs[5].artifact.clone()); + for input in &mut verification.semantic_inputs { + if let Some(replacement) = rejected + .semantic_inputs + .iter() + .find(|candidate| candidate.role == input.role) + { + input.artifact = transport(replacement.artifact.clone()); + } + } + change_package(&mut verification, |package| { + *field_mut(field_mut(package, "budget_ceiling"), "max_steps") = Value::Integer(1_048_576); + let closure = field_mut(package, "semantic_closure"); + for (key, artifact) in [ + ("canonical_meaning_identity", &rejected.core), + ("core_identity", &rejected.core), + ("target_ir_identity", &rejected.semantic_inputs[5].artifact), + ("lawpack_identity", &rejected.semantic_inputs[2].artifact), + ( + "edict_source_identity", + &rejected.semantic_inputs[3].artifact, + ), + ] { + *field_mut(closure, key) = Value::Bytes(artifact.reference.digest.bytes.clone()); + } + let Value::Bytes(raw) = field(package, "program") else { + panic!("program bytes") + }; + let mut program = decode(raw).expect("program"); + for (key, artifact) in [ + ("core_artifact", &rejected.core), + ("target_ir_artifact", &rejected.semantic_inputs[5].artifact), + ( + "lawpack_adapter_artifact", + &rejected.semantic_inputs[0].artifact, + ), + ("lawpack_artifact", &rejected.semantic_inputs[2].artifact), + ("source_artifact", &rejected.semantic_inputs[3].artifact), + ] { + *field_mut(&mut program, key) = Value::Bytes(artifact.artifact.bytes.clone()); + } + *field_mut(package, "program") = Value::Bytes(encode(&program).expect("program bytes")); + }); + if let Ok(response) = verifier::verify(verification) { + let report = decode(&response.outputs[0].artifact.bytes).expect("report"); + assert_ne!(field(&report, "outcome"), &text("accepted")); + } +} + +fn rename_owned_coordinates(value: &mut Value, previous: &str, replacement: &str) { + match value { + Value::Text(name) => { + if name == previous { + replacement.clone_into(name); + } else if let Some(member) = name.strip_prefix(&format!("{previous}.")) { + *name = format!("{replacement}.{member}"); + } + } + Value::Array(values) => { + for value in values { + rename_owned_coordinates(value, previous, replacement); + } + } + Value::Map(entries) => { + for (key, value) in entries { + rename_owned_coordinates(key, previous, replacement); + rename_owned_coordinates(value, previous, replacement); + } + } + _ => {} + } +} + +// Only source-owned coordinates move. The actual imported read effect and its +// authenticated exports, adapter, lawpack and configuration stay byte-identical. +fn same_package_source_function(collision: bool) -> LoweringRequestV1 { + let mut source = read_helper(); + let exports = decode(&source.semantic_inputs[1].artifact.artifact.bytes).expect("exports"); + let Value::Array(effects) = field(&exports, "effects") else { + panic!("effects") + }; + let Value::Text(effect) = field(&effects[0], "coordinate") else { + panic!("effect coordinate") + }; + let (coordinate, effect_name) = effect.rsplit_once('.').expect("qualified read effect"); + assert_eq!(effect, "jedit.text@1.readFact"); + let previous = source.core.reference.coordinate.clone(); + let mut core = decode(&source.core.artifact.bytes).expect("core"); + let mut target = decode(&source.semantic_inputs[5].artifact.artifact.bytes).expect("target"); + let mut projection = + decode(&source.semantic_inputs[6].artifact.artifact.bytes).expect("projection"); + for value in [&mut core, &mut target, &mut projection] { + rename_owned_coordinates(value, &previous, coordinate); + } + // Core permits module-relative named-type table keys. Once the module is + // in the imported package, move those definitions to the relative spelling + // expected by this runtime aperture; keep their qualified references and + // exact definitions. Do not create duplicate aliases or fork the lawpack. + let Value::Map(types) = field_mut(&mut core, "types") else { + panic!("type inventory") + }; + let prefix = format!("{coordinate}."); + let mut names = std::collections::BTreeSet::new(); + for (key, definition) in types { + let Value::Text(name) = key else { + panic!("type name") + }; + if let Some(relative) = name.strip_prefix(&prefix) { + assert_ne!( + field(definition, "kind"), + &text("Nominal"), + "this fixture must not move a nominal authority definition" + ); + *name = relative.to_owned(); + } + assert!(names.insert(name.clone()), "type inventory remains unique"); + } + let unused_name = if collision { effect_name } else { "unusedRead" }; + let functions = field_mut(&mut core, "functions"); + let unused = field(functions, "retain").clone(); + insert(functions, unused_name, unused); + source.core = bound( + coordinate, + "edict.core.module/v1", + encode(&core).expect("core bytes"), + ); + *field_mut(field_mut(&mut target, "semanticClosure"), "sourceCore") = map([ + ("id", text(coordinate)), + ( + "digest", + Value::Array(vec![ + text("sha256"), + Value::Bytes(source.core.reference.digest.bytes.clone()), + ]), + ), + ]); + let Value::Bytes(authored) = + decode(&source.semantic_inputs[3].artifact.artifact.bytes).expect("source") + else { + panic!("source bytes") + }; + let mut authored = String::from_utf8(authored).expect("UTF-8 source"); + let package_header = format!("package {previous};"); + assert_eq!(authored.matches(&package_header).count(), 1); + authored = authored.replace(&package_header, &format!("package {coordinate};")); + let helpers = format!( + "fn retain(value: text.FactBytes) -> text.FactBytes {{ let bytes = value; return bytes; }}\n\ + fn {unused_name}(value: text.FactBytes) -> text.FactBytes {{ let bytes = value; return bytes; }}\n" + ); + assert_eq!(authored.matches("intent replaceRange(").count(), 1); + authored = authored.replace( + "intent replaceRange(", + &format!("{helpers}intent replaceRange("), + ); + assert_eq!(authored.matches(" return actual;").count(), 1); + authored = authored.replace( + " return actual;", + " let retained = retain(actual);\n return retained;", + ); + source.semantic_inputs[3].artifact = bound( + coordinate, + "edict.source/v1", + encode(&Value::Bytes(authored.into_bytes())).expect("source bytes"), + ); + source.semantic_inputs[5].artifact = bound( + "echo.span-ir/v2", + "edict.target-ir.artifact/v1", + encode(&target).expect("target bytes"), + ); + source.semantic_inputs[6].artifact = bound( + &format!("{coordinate}.replaceRange"), + "edict.result-projection.artifact/v1", + encode(&projection).expect("projection bytes"), + ); + source +} + +fn collision_verification() -> verifier::VerificationRequestV1 { + let positive = same_package_source_function(false); + let negative = same_package_source_function(true); + for index in [0, 1, 2, 4, 6] { + assert_eq!( + positive.semantic_inputs[index].artifact, negative.semantic_inputs[index].artifact, + "changing an unused source name must preserve imported authority and result projection" + ); + } + // Only the disjoint control goes through the lowerer. The collision package + // is assembled independently, so a future lowerer refusal cannot mask a + // verifier that still accepts the same authenticated ownership ambiguity. + let mut verification = request(positive); + verification.core = transport(negative.core.clone()); + verification.target_ir = transport(negative.semantic_inputs[5].artifact.clone()); + verification + .semantic_inputs + .iter_mut() + .find(|input| input.role == "04-source") + .expect("source input") + .artifact = transport(negative.semantic_inputs[3].artifact.clone()); + change_package(&mut verification, |package| { + let closure = field_mut(package, "semantic_closure"); + for (key, artifact) in [ + ("canonical_meaning_identity", &negative.core), + ("core_identity", &negative.core), + ("target_ir_identity", &negative.semantic_inputs[5].artifact), + ( + "edict_source_identity", + &negative.semantic_inputs[3].artifact, + ), + ] { + *field_mut(closure, key) = Value::Bytes(artifact.reference.digest.bytes.clone()); + } + let Value::Bytes(raw) = field(package, "program") else { + panic!("program bytes") + }; + let mut program = decode(raw).expect("program"); + for (key, artifact) in [ + ("core_artifact", &negative.core), + ("target_ir_artifact", &negative.semantic_inputs[5].artifact), + ("source_artifact", &negative.semantic_inputs[3].artifact), + ] { + *field_mut(&mut program, key) = Value::Bytes(artifact.artifact.bytes.clone()); + } + *field_mut(package, "program") = Value::Bytes(encode(&program).expect("program bytes")); + }); + verification +} + +#[test] +fn bounded_read_disjoint_source_functions_share_the_imported_effect_package() { + let source = same_package_source_function(false); + lower(source.clone()).expect("same-package disjoint called and unused source functions lower"); + let verification = request(source); + let response = verifier::verify(verification).expect("same-package independent verification"); + let report = decode(&response.outputs[0].artifact.bytes).expect("report"); + assert_eq!(field(&report, "outcome"), &text("accepted")); +} + +#[test] +fn lowerer_source_function_names_cannot_shadow_imported_read_effects() { + let source = same_package_source_function(true); + let refusal = + lower(source).expect_err("unused source function collides with imported read effect"); + assert_eq!( + refusal.kind, + echo_edict_provider_lowerer::ProviderRefusalKind::UnsupportedSemantics + ); +} + +#[test] +fn verifier_source_function_names_cannot_shadow_imported_read_effects() { + assert_rejected(collision_verification()); +} + +#[path = "imported_calls.rs"] +mod imported_calls; diff --git a/crates/echo-edict-provider-verifier/tests/executable_operation_package.rs b/crates/echo-edict-provider-verifier/tests/executable_operation_package.rs index 477ffe9d2..aeeefe6ee 100644 --- a/crates/echo-edict-provider-verifier/tests/executable_operation_package.rs +++ b/crates/echo-edict-provider-verifier/tests/executable_operation_package.rs @@ -14,6 +14,7 @@ mod pure_profile; mod pure_projection_bounds; mod pure_relation; mod pure_source; +mod source_functions; const TARGET_PROFILE: &[u8] = include_bytes!("../resources/target-profile.echo-dpo.cbor"); const PACKAGE_ROLE: &str = "executable-operation-package.echo"; diff --git a/crates/echo-edict-provider-verifier/tests/source_functions/mod.rs b/crates/echo-edict-provider-verifier/tests/source_functions/mod.rs new file mode 100644 index 000000000..205200c34 --- /dev/null +++ b/crates/echo-edict-provider-verifier/tests/source_functions/mod.rs @@ -0,0 +1,756 @@ +// SPDX-License-Identifier: Apache-2.0 +// © James Ross Ω FLYING•ROBOTS +//! Deliberately rebound native-provider tests, not compiler acceptance evidence. +use super::*; + +fn fixture_with_functions( + functions: CanonicalValueV1, +) -> (FixtureNames<'static>, RawFixture, Vec) { + edited_fixture(|core, _| add_field(core, "functions", functions)) +} + +fn edited_fixture( + edit: impl FnOnce(&mut CanonicalValueV1, &mut CanonicalValueV1), +) -> (FixtureNames<'static>, RawFixture, Vec) { + let (names, mut fixture, package) = pure_relation::compiler_fixture(); + let mut core = decode_canonical_cbor_v1(&fixture.core).expect("Core"); + let mut target = decode_canonical_cbor_v1(&fixture.target_ir).expect("Target"); + edit(&mut core, &mut target); + fixture.core = canonical_bytes(&core); + let core_reference = raw_ref(names.application, "edict.core.module/v1", &fixture.core); + *map_field_mut(map_field_mut(&mut target, "semanticClosure"), "sourceCore") = map([ + ("id", text(names.application)), + ( + "digest", + CanonicalValueV1::Array(vec![ + text("sha256"), + CanonicalValueV1::Bytes(core_reference.digest.clone()), + ]), + ), + ]); + fixture.target_ir = canonical_bytes(&target); + // Construct the independent verifier request without asking the lowerer to + // endorse this changed Core. Rebind the retained package's exact carriers. + let mut package = decode_canonical_cbor_v1(&package).expect("package"); + let CanonicalValueV1::Bytes(bytes) = map_field(&package, "program") else { + panic!("program") + }; + let mut program = decode_canonical_cbor_v1(bytes).expect("program"); + for (name, bytes) in [ + ("core_artifact", &fixture.core), + ("target_ir_artifact", &fixture.target_ir), + ] { + *map_field_mut(&mut program, name) = CanonicalValueV1::Bytes(bytes.clone()); + } + *map_field_mut(&mut package, "program") = CanonicalValueV1::Bytes(canonical_bytes(&program)); + let budget = map_field( + map_field(map_field(&core, "intents"), names.intent), + "coreEvaluationBudget", + ); + for (package_key, core_key) in [ + ("max_steps", "maxSteps"), + ("max_allocated_bytes", "maxAllocatedBytes"), + ("max_output_bytes", "maxOutputBytes"), + ] { + *map_field_mut(map_field_mut(&mut package, "budget_ceiling"), package_key) = + map_field(budget, core_key).clone(); + } + let closure = map_field_mut(&mut package, "semantic_closure"); + *map_field_mut(closure, "canonical_meaning_identity") = + CanonicalValueV1::Bytes(core_reference.digest.clone()); + *map_field_mut(closure, "core_identity") = CanonicalValueV1::Bytes(core_reference.digest); + *map_field_mut(closure, "target_ir_identity") = CanonicalValueV1::Bytes( + raw_ref( + "echo.span-ir/v1", + "edict.target-ir.artifact/v1", + &fixture.target_ir, + ) + .digest, + ); + (names, fixture, canonical_bytes(&package)) +} + +#[test] +fn lowerer_rejects_unused_malformed_source_function_authority() { + let (names, fixture, _) = fixture_with_functions(map([("unused", CanonicalValueV1::Null)])); + assert!( + lowerer::lower(lowering_request(names, &fixture)).is_err(), + "lowerer ignored authenticated malformed source function" + ); +} + +#[test] +fn verifier_independently_rejects_unused_malformed_source_function_authority() { + let (names, fixture, package) = + fixture_with_functions(map([("unused", CanonicalValueV1::Null)])); + match verifier::verify(verification_request(names, &fixture, package)) { + Err(_) => {} + Ok(success) => { + let report = + decode_canonical_cbor_v1(&success.outputs[0].artifact.bytes).expect("report"); + assert_ne!( + text_field(&report, "outcome"), + Some("accepted"), + "verifier ignored authenticated malformed source function" + ); + } + } +} + +#[test] +fn valid_unused_source_function_has_coherent_package_carriers() { + let function = map([ + ("params", CanonicalValueV1::Array(vec![])), + ("returnType", text("U32")), + ( + "body", + map([ + ("locals", CanonicalValueV1::Array(vec![])), + ("bindings", CanonicalValueV1::Array(vec![])), + ( + "result", + map([ + ("kind", text("const")), + ( + "value", + map([ + ("kind", text("int")), + ("width", text("U32")), + ("value", integer(1)), + ]), + ), + ]), + ), + ]), + ), + ]); + let (names, fixture, package) = fixture_with_functions(map([("unused", function)])); + let lower = + lowerer::lower(lowering_request(names, &fixture)).expect("valid unused definition lowers"); + assert_eq!( + lower.outputs[0].artifact.bytes, package, + "independently rebound carriers match real lowering" + ); + let success = verifier::verify(verification_request(names, &fixture, package)) + .expect("valid source authority"); + let report = decode_canonical_cbor_v1(&success.outputs[0].artifact.bytes).expect("report"); + assert_eq!(text_field(&report, "outcome"), Some("accepted")); +} + +#[test] +fn stale_canonical_meaning_identity_is_a_carrier_disagreement_control() { + let (names, fixture, package) = + fixture_with_functions(map([("unused", CanonicalValueV1::Null)])); + let mut package = decode_canonical_cbor_v1(&package).expect("package"); + *map_field_mut( + map_field_mut(&mut package, "semantic_closure"), + "canonical_meaning_identity", + ) = CanonicalValueV1::Bytes(vec![0; 32]); + if let Ok(success) = verifier::verify(verification_request( + names, + &fixture, + canonical_bytes(&package), + )) { + let report = decode_canonical_cbor_v1(&success.outputs[0].artifact.bytes).expect("report"); + assert_ne!(text_field(&report, "outcome"), Some("accepted")); + } +} + +fn add_field(value: &mut CanonicalValueV1, name: &str, replacement: CanonicalValueV1) { + let CanonicalValueV1::Map(fields) = value else { + panic!("map") + }; + assert!(!fields.iter().any(|(key, _)| key == &text(name))); + fields.push((text(name), replacement)); +} +fn list(values: Vec) -> CanonicalValueV1 { + CanonicalValueV1::Array(values) +} +fn local(id: &str, ty: &str) -> CanonicalValueV1 { + map([ + ("id", text(id)), + ("alphaName", text(id)), + ("type", text(ty)), + ]) +} +fn reference(local: &CanonicalValueV1) -> CanonicalValueV1 { + map([("kind", text("local")), ("ref", local.clone())]) +} +fn call(name: &str, args: Vec) -> CanonicalValueV1 { + map([ + ("kind", text("call")), + ("callee", text(name)), + ("args", list(args)), + ("typeArgs", list(vec![])), + ]) +} +fn literal(width: &str, n: i128) -> CanonicalValueV1 { + map([ + ("kind", text("const")), + ( + "value", + map([ + ("kind", text("int")), + ("width", text(width)), + ("value", CanonicalValueV1::Integer(n)), + ]), + ), + ]) +} +fn function( + params: Vec, + ty: &str, + locals: Vec, + bindings: Vec, + result: CanonicalValueV1, +) -> CanonicalValueV1 { + map([ + ("params", list(params)), + ("returnType", text(ty)), + ( + "body", + map([ + ("locals", list(locals)), + ("bindings", list(bindings)), + ("result", result), + ]), + ), + ]) +} +fn intent(artifact: &mut CanonicalValueV1) -> &mut CanonicalValueV1 { + map_field_mut(map_field_mut(artifact, "intents"), "replaceRange") +} +fn called_fixture( + edit: impl FnOnce(&mut CanonicalValueV1, &mut CanonicalValueV1), +) -> (FixtureNames<'static>, RawFixture, Vec) { + edited_fixture(|core, target| { + let coordinate = text_field(core, "coordinate") + .expect("coordinate") + .to_owned(); + let a = local("arg.0", "U64"); + let b = local("arg.1", "U64"); + let value = local("local.0", "U64"); + let binding = map([ + ("kind", text("let")), + ("binding", value.clone()), + ( + "value", + call(&format!("{coordinate}.passNext"), vec![reference(&a)]), + ), + ]); + let condition = map([ + ("kind", text("if")), + ( + "predicate", + map([ + ("kind", text("compare")), + ("op", text("==")), + ("left", reference(&a)), + ("right", reference(&b)), + ]), + ), + ("then", literal("U32", 1)), + ("else", literal("U32", 0)), + ]); + add_field( + core, + "functions", + map([ + ( + "pass", + function( + vec![a.clone()], + "U64", + vec![value.clone()], + vec![binding], + reference(&value), + ), + ), + ( + "passNext", + function(vec![a.clone()], "U64", vec![], vec![], reference(&a)), + ), + ( + "empty", + function(vec![a, b], "U32", vec![], vec![], condition), + ), + ]), + ); + for (artifact, key) in [(&mut *core, "nodes"), (&mut *target, "pureBindings")] { + let body = if key == "nodes" { + map_field_mut(intent(artifact), "body") + } else { + intent(artifact) + }; + let CanonicalValueV1::Array(bindings) = map_field_mut(body, key) else { + panic!("bindings") + }; + let original = map_field(&bindings[0], "value").clone(); + *map_field_mut(&mut bindings[0], "value") = + call(&format!("{coordinate}.pass"), vec![original]); + let predicate = map_field(map_field(&bindings[1], "value"), "predicate"); + let arguments = vec![ + map_field(predicate, "left").clone(), + map_field(predicate, "right").clone(), + ]; + *map_field_mut(&mut bindings[1], "value") = + call(&format!("{coordinate}.empty"), arguments); + } + edit(core, target); + }) +} +fn verifier_accepts(names: FixtureNames<'_>, fixture: &RawFixture, package: Vec) -> bool { + verifier::verify(verification_request(names, fixture, package)).is_ok_and(|success| { + let report = decode_canonical_cbor_v1(&success.outputs[0].artifact.bytes).expect("report"); + text_field(&report, "outcome") == Some("accepted") + }) +} +fn both_refuse(names: FixtureNames<'_>, fixture: &RawFixture, package: Vec, case: &str) { + let lower = lowerer::lower(lowering_request(names, fixture)).is_ok(); + let verify = verifier_accepts(names, fixture, package); + assert!( + !lower && !verify, + "{case}: lowerer accepted={lower}, verifier accepted={verify}" + ); +} + +#[test] +fn source_functions_called_forward_bindings_conditions_and_imported_arguments_are_admitted() { + let (names, fixture, package) = called_fixture(|_, _| {}); + let lowered = + lowerer::lower(lowering_request(names, &fixture)).expect("called source functions lower"); + assert_eq!( + lowered.outputs[0].artifact.bytes, package, + "independent carrier construction" + ); + assert!(verifier_accepts(names, &fixture, package)); +} + +#[test] +fn source_functions_unused_bodies_have_fresh_complete_typed_lexical_frames() { + for case in [ + "duplicate_parameter", + "capture", + "return_type", + "unknown_callee", + "recursion", + "unsupported_bool", + "unused_local", + "forward_local", + "partial_subtract", + ] { + let (names, fixture, package) = called_fixture(|core, _| { + let coordinate = text_field(core, "coordinate") + .expect("coordinate") + .to_owned(); + let a = local("arg.0", "U64"); + let b = local("arg.1", "U64"); + let saved = local("local.0", "U64"); + let mut definition = function(vec![a.clone()], "U64", vec![], vec![], reference(&a)); + match case { + "duplicate_parameter" => { + *map_field_mut(&mut definition, "params") = list(vec![a.clone(), a]); + } + "capture" => { + *map_field_mut(map_field_mut(&mut definition, "body"), "result") = + reference(&b); + } + "return_type" => *map_field_mut(&mut definition, "returnType") = text("U32"), + "unknown_callee" | "recursion" => { + let callee = if case == "recursion" { + "unused" + } else { + "missing" + }; + *map_field_mut(map_field_mut(&mut definition, "body"), "result") = + call(&format!("{coordinate}.{callee}"), vec![reference(&a)]); + } + "unsupported_bool" => *map_field_mut(&mut definition, "returnType") = text("Bool"), + "unused_local" => { + *map_field_mut(map_field_mut(&mut definition, "body"), "locals") = + list(vec![saved]); + } + "forward_local" => { + *map_field_mut(map_field_mut(&mut definition, "body"), "locals") = + list(vec![saved.clone()]); + *map_field_mut(map_field_mut(&mut definition, "body"), "bindings") = + list(vec![map([ + ("kind", text("let")), + ("binding", saved.clone()), + ("value", reference(&saved)), + ])]); + } + "partial_subtract" => { + *map_field_mut(&mut definition, "params") = list(vec![a.clone(), b.clone()]); + let mut subtraction = + call("core.integer.subtract", vec![reference(&a), reference(&b)]); + *map_field_mut(&mut subtraction, "typeArgs") = list(vec![text("U64")]); + *map_field_mut(map_field_mut(&mut definition, "body"), "result") = subtraction; + } + _ => panic!("case"), + } + add_field(map_field_mut(core, "functions"), "unused", definition); + }); + both_refuse(names, &fixture, package, case); + } +} + +#[test] +fn source_functions_core_calls_require_the_exact_declared_signature() { + for case in ["arity", "argument_type", "generic"] { + let (names, fixture, package) = called_fixture(|core, target| { + for (artifact, key) in [(core, "nodes"), (target, "pureBindings")] { + let body = if key == "nodes" { + map_field_mut(intent(artifact), "body") + } else { + intent(artifact) + }; + let CanonicalValueV1::Array(bindings) = map_field_mut(body, key) else { + panic!("bindings") + }; + let expression = map_field_mut(&mut bindings[0], "value"); + match case { + "arity" => *map_field_mut(expression, "args") = list(vec![]), + "argument_type" => { + *map_field_mut(expression, "args") = list(vec![literal("U32", 1)]); + } + "generic" => *map_field_mut(expression, "typeArgs") = list(vec![text("U64")]), + _ => panic!("case"), + } + } + }); + both_refuse(names, &fixture, package, case); + } +} + +#[test] +fn source_functions_independent_relation_rejects_coherently_rebound_target_call_substitution() { + let (names, fixture, package) = called_fixture(|_, target| { + let CanonicalValueV1::Array(bindings) = map_field_mut(intent(target), "pureBindings") + else { + panic!("bindings") + }; + *map_field_mut(map_field_mut(&mut bindings[0], "value"), "args") = + list(vec![literal("U64", 7)]); + }); + assert!( + !verifier_accepts(names, &fixture, package), + "valid hashes cannot endorse a different call argument" + ); +} + +#[test] +fn source_functions_pure_intent_inventory_is_complete_and_unique() { + for case in ["missing", "extra", "duplicate"] { + let (names, fixture, package) = called_fixture(|core, _| { + let CanonicalValueV1::Array(locals) = + map_field_mut(map_field_mut(intent(core), "body"), "locals") + else { + panic!("locals") + }; + match case { + "missing" => { + locals.pop().expect("declared let"); + } + "extra" => locals.push(local("ghost", "U64")), + "duplicate" => locals.push(locals[0].clone()), + _ => panic!("case"), + } + }); + both_refuse(names, &fixture, package, case); + } +} + +#[test] +fn source_functions_combined_source_import_depth_counts_shared_suffixes_in_both_orders() { + for count in [63, 64] { + for shallow_first in [false, true] { + let (names, fixture, package) = edited_fixture(|core, _| { + let coordinate = text_field(core, "coordinate") + .expect("coordinate") + .to_owned(); + let CanonicalValueV1::Array(nodes) = + map_field(map_field(intent(core), "body"), "nodes") + else { + panic!("nodes") + }; + let imported = map_field(&nodes[0], "value").clone(); + let name = |index: usize| { + format!( + "f{:03}", + if shallow_first { + count - 1 - index + } else { + index + } + ) + }; + let mut functions = Vec::new(); + for index in 0..count { + let result = if index + 1 == count { + imported.clone() + } else { + call(&format!("{coordinate}.{}", name(index + 1)), vec![]) + }; + functions.push(( + text(name(index)), + function(vec![], "U64", vec![], vec![], result), + )); + } + add_field(core, "functions", CanonicalValueV1::Map(functions)); + }); + if count == 63 { + lowerer::lower(lowering_request(names, &fixture)).expect("exact combined 64 depth"); + assert!( + verifier_accepts(names, &fixture, package), + "exact depth with shallow_first={shallow_first}" + ); + } else { + both_refuse( + names, + &fixture, + package, + "over combined source/import depth", + ); + } + } + } +} + +fn diamond(core: &mut CanonicalValueV1, layers: usize) -> String { + let coordinate = text_field(core, "coordinate") + .expect("coordinate") + .to_owned(); + let mut functions = Vec::new(); + for layer in 0..=layers { + let body = if layer == layers { + function(vec![], "U64", vec![], vec![], literal("U64", 1)) + } else { + let a = local("local.0", "U64"); + let b = local("local.1", "U64"); + let binding = |local: &CanonicalValueV1| { + map([ + ("kind", text("let")), + ("binding", local.clone()), + ( + "value", + call(&format!("{coordinate}.f{:03}", layer + 1), vec![]), + ), + ]) + }; + function( + vec![], + "U64", + vec![a.clone(), b.clone()], + vec![binding(&a), binding(&b)], + reference(&a), + ) + }; + functions.push((text(format!("f{layer:03}")), body)); + } + add_field(core, "functions", CanonicalValueV1::Map(functions)); + format!("{coordinate}.f000") +} + +#[test] +fn source_functions_diamond_cost_counts_occurrences_and_refuses_u64_storage_overflow() { + // Helper storage B(n)=2*B(n-1)+64, B(0)=64. B(57)=2^64-64 + // fits, B(58)=2^65-64 does not. Both graphs fit the depth aperture. + for layers in [57, 58] { + let (names, fixture, package) = edited_fixture(|core, _| { + diamond(core, layers); + }); + if layers == 57 { + lowerer::lower(lowering_request(names, &fixture)) + .expect("checked u64 storage just below overflow"); + assert!(verifier_accepts(names, &fixture, package)); + } else { + both_refuse(names, &fixture, package, "diamond cost overflow"); + } + } +} + +#[test] +fn source_functions_called_diamond_must_fit_the_whole_operation_budget() { + for layers in [0, 20] { + let (names, fixture, package) = edited_fixture(|core, target| { + let root = diamond(core, layers); + for (artifact, key) in [(core, "nodes"), (target, "pureBindings")] { + let body = if key == "nodes" { + map_field_mut(intent(artifact), "body") + } else { + intent(artifact) + }; + let CanonicalValueV1::Array(bindings) = map_field_mut(body, key) else { + panic!("bindings") + }; + *map_field_mut(&mut bindings[0], "value") = call(&root, vec![]); + } + }); + if layers == 0 { + lowerer::lower(lowering_request(names, &fixture)) + .expect("small called body fits operation"); + assert!(verifier_accepts(names, &fixture, package)); + } else { + both_refuse( + names, + &fixture, + package, + "each repeated call occurrence must spend budget", + ); + } + } +} + +#[test] +fn source_functions_coherent_declared_budgets_cannot_omit_entry_and_result_materialization() { + for key in ["maxSteps", "maxAllocatedBytes"] { + let (names, fixture, package) = called_fixture(|core, target| { + for artifact in [core, target] { + *map_field_mut(map_field_mut(intent(artifact), "coreEvaluationBudget"), key) = + integer(1); + } + }); + both_refuse(names, &fixture, package, key); + } +} + +#[test] +fn source_functions_signature_value_depth_is_part_of_the_call_aperture() { + for layers in [63, 64] { + let (names, fixture, package) = edited_fixture(|core, _| { + let coordinate = text_field(core, "coordinate") + .expect("coordinate") + .to_owned(); + for layer in 0..layers { + let child = if layer + 1 == layers { + "U64".to_owned() + } else { + format!("{coordinate}.Deep{}", layer + 1) + }; + add_field( + map_field_mut(core, "types"), + &format!("Deep{layer}"), + map([ + ("kind", text("Record")), + ("fields", map([("child", text(child))])), + ]), + ); + } + let ty = format!("{coordinate}.Deep0"); + let parameter = local("arg.0", &ty); + add_field( + core, + "functions", + map([( + "identity", + function( + vec![parameter.clone()], + &ty, + vec![], + vec![], + reference(¶meter), + ), + )]), + ); + }); + if layers == 63 { + lowerer::lower(lowering_request(names, &fixture)) + .expect("63 record levels plus one call frame"); + assert!(verifier_accepts(names, &fixture, package)); + } else { + both_refuse( + names, + &fixture, + package, + "record validation would exceed call aperture", + ); + } + } +} + +#[test] +fn source_functions_pure_input_identity_does_not_depend_on_declaration_inventory_order() { + let (names, fixture, package) = called_fixture(|core, _| { + let CanonicalValueV1::Array(locals) = + map_field_mut(map_field_mut(intent(core), "body"), "locals") + else { + panic!("locals") + }; + assert!(locals.len() > 1); + locals.reverse(); + }); + let lowered = lowerer::lower(lowering_request(names, &fixture)) + .expect("declaration inventory order carries no execution order"); + assert_eq!(lowered.outputs[0].artifact.bytes, package); + assert!(verifier_accepts(names, &fixture, package)); +} + +#[test] +fn source_functions_pure_basis_has_complete_input_only_call_authority() { + for case in [ + "none", + "valid", + "representation", + "null", + "unknown", + "arity", + "capture", + ] { + let (names, fixture, package) = called_fixture(|core, target| { + let coordinate = text_field(core, "coordinate") + .expect("coordinate") + .to_owned(); + // The retained basis is input.basisHeadId: nominal HeadId, not its + // structural byte representation. Keep the old signature as refusal. + let parameter_type = if case == "representation" { + "Bytes" + } else { + "jedit.text@1.HeadId" + }; + let parameter = local("arg.0", parameter_type); + add_field( + map_field_mut(core, "functions"), + "basisValue", + function( + vec![parameter.clone()], + parameter_type, + vec![], + vec![], + reference(¶meter), + ), + ); + let original = map_field(intent(core), "basis").clone(); + let expression = match case { + "none" | "null" => CanonicalValueV1::Null, + "valid" | "representation" => { + call(&format!("{coordinate}.basisValue"), vec![original]) + } + "unknown" => call(&format!("{coordinate}.missing"), vec![]), + "arity" => call(&format!("{coordinate}.basisValue"), vec![]), + "capture" => reference(&local("local.0", "U64")), + _ => panic!("case"), + }; + for artifact in [core, target] { + if case == "none" { + let CanonicalValueV1::Map(fields) = intent(artifact) else { + panic!("intent map") + }; + fields.retain(|(name, _)| name != &text("basis")); + } else { + *map_field_mut(intent(artifact), "basis") = expression.clone(); + } + } + }); + if ["none", "valid"].contains(&case) { + let lowered = lowerer::lower(lowering_request(names, &fixture)) + .expect("coherent valid optional basis"); + assert_eq!(lowered.outputs[0].artifact.bytes, package); + assert!(verifier_accepts(names, &fixture, package)); + } else { + both_refuse(names, &fixture, package, case); + } + } +} + +mod nominal_types; diff --git a/crates/echo-edict-provider-verifier/tests/source_functions/nominal_types.rs b/crates/echo-edict-provider-verifier/tests/source_functions/nominal_types.rs new file mode 100644 index 000000000..bdf2330cc --- /dev/null +++ b/crates/echo-edict-provider-verifier/tests/source_functions/nominal_types.rs @@ -0,0 +1,239 @@ +// SPDX-License-Identifier: Apache-2.0 +// © James Ross Ω FLYING•ROBOTS +//! Synthetic Core extensions, not public compiler acceptance evidence. +//! The retained source and imported authority remain unchanged. The independent +//! carrier helper rebinds changed Core/Target/package identities without lowering. +use super::*; + +fn add_nominal_types(core: &mut CanonicalValueV1) { + for name in ["A", "B"] { + // Exact bare keys and matching contracts avoid the separate qualified-key + // lookup question. Equal U64 representation does not equate A with B. + add_field( + map_field_mut(core, "types"), + name, + map([ + ("kind", text("Nominal")), + ("contract", text(name)), + ("representation", text("U64")), + ]), + ); + } +} + +fn return_fixture(result_type: &str) -> (FixtureNames<'static>, RawFixture, Vec) { + typed_return_fixture("A", result_type) +} + +fn typed_return_fixture( + parameter_type: &str, + result_type: &str, +) -> (FixtureNames<'static>, RawFixture, Vec) { + edited_fixture(|core, _| { + add_nominal_types(core); + let parameter = local("arg.0", parameter_type); + add_field( + core, + "functions", + map([( + "unusedIdentity", + function( + vec![parameter.clone()], + result_type, + vec![], + vec![], + reference(¶meter), + ), + )]), + ); + }) +} + +fn argument_fixture(parameter_type: &str) -> (FixtureNames<'static>, RawFixture, Vec) { + edited_fixture(|core, _| { + add_nominal_types(core); + let coordinate = text_field(core, "coordinate").expect("coordinate"); + let call_name = format!("{coordinate}.constant"); + let argument = local("arg.0", "A"); + add_field( + core, + "functions", + map([ + ( + "constant", + function( + vec![local("arg.0", parameter_type)], + "U64", + vec![], + vec![], + literal("U64", 0), + ), + ), + ( + "unusedCaller", + function( + vec![argument.clone()], + "U64", + vec![], + vec![], + call(&call_name, vec![reference(&argument)]), + ), + ), + ]), + ); + }) +} + +fn assert_admitted((names, fixture, package): (FixtureNames<'_>, RawFixture, Vec)) { + let lowered = lowerer::lower(lowering_request(names, &fixture)) + .expect("same-nominal source signature is valid"); + assert_eq!( + lowered.outputs[0].artifact.bytes, package, + "independently rebound package matches actual lowering" + ); + assert!( + verifier_accepts(names, &fixture, package), + "independent verifier admits the same-nominal control" + ); +} + +#[test] +fn unused_same_nominal_return_is_admitted() { + assert_admitted(return_fixture("A")); +} + +#[test] +fn lowerer_rejects_distinct_nominal_return_with_identical_representation() { + // Only the unused helper's return declaration differs from the valid A -> A. + let (names, fixture, _) = return_fixture("B"); + assert!( + lowerer::lower(lowering_request(names, &fixture)).is_err(), + "U64 representation must not authorize returning A as distinct nominal B" + ); +} + +#[test] +fn verifier_independently_rejects_distinct_nominal_return_with_identical_representation() { + let (names, fixture, package) = return_fixture("B"); + assert!( + !verifier_accepts(names, &fixture, package), + "coherent carriers must not authorize returning A as distinct nominal B" + ); +} + +#[test] +fn unused_same_nominal_call_argument_is_admitted() { + assert_admitted(argument_fixture("A")); +} + +#[test] +fn lowerer_rejects_distinct_nominal_call_argument_with_identical_representation() { + // Both helpers still return a valid U64. Only the callee's parameter type + // changes; its literal body is valid for either signature. The caller has A. + let (names, fixture, _) = argument_fixture("B"); + assert!( + lowerer::lower(lowering_request(names, &fixture)).is_err(), + "U64 representation must not authorize passing A to a parameter of nominal B" + ); +} + +#[test] +fn verifier_independently_rejects_distinct_nominal_call_argument_with_identical_representation() { + let (names, fixture, package) = argument_fixture("B"); + assert!( + !verifier_accepts(names, &fixture, package), + "coherent carriers must not authorize passing A to a parameter of nominal B" + ); +} + +fn predicate_fixture( + right_type: &str, + representation: &str, + operator: &str, +) -> (FixtureNames<'static>, RawFixture, Vec) { + edited_fixture(|core, _| { + add_nominal_types(core); + for name in ["A", "B"] { + *map_field_mut( + map_field_mut(map_field_mut(core, "types"), name), + "representation", + ) = text(representation); + } + let left = local("arg.0", "A"); + let right = local("arg.1", right_type); + let predicate = map([ + ("kind", text("compare")), + ("op", text(operator)), + ("left", reference(&left)), + ("right", reference(&right)), + ]); + let result = map([ + ("kind", text("if")), + ("predicate", predicate), + ("then", literal("U32", 1)), + ("else", literal("U32", 0)), + ]); + add_field( + core, + "functions", + map([( + "unusedCompare", + function(vec![left, right], "U32", vec![], vec![], result), + )]), + ); + }) +} + +#[test] +fn same_nominal_comparisons_preserve_the_supported_representation_aperture() { + for (representation, operator) in [("U64", "=="), ("U64", "<="), ("Bytes", "==")] { + assert_admitted(predicate_fixture("A", representation, operator)); + } +} + +#[test] +fn predicates_refuse_distinct_nominal_or_bare_representation_operands() { + for representation in ["U64", "Bytes"] { + for right in ["B", representation] { + let (names, fixture, package) = predicate_fixture(right, representation, "=="); + both_refuse(names, &fixture, package, "nominal comparison authority"); + } + } +} + +#[test] +fn nominal_and_bare_representation_are_not_interchangeable_return_types() { + for (parameter, result) in [("A", "U64"), ("U64", "A")] { + let (names, fixture, package) = typed_return_fixture(parameter, result); + both_refuse( + names, + &fixture, + package, + "nominal is not its representation", + ); + } +} + +#[test] +fn nominal_contract_must_match_the_resolved_type_table_key() { + let (names, fixture, package) = edited_fixture(|core, _| { + add_nominal_types(core); + *map_field_mut(map_field_mut(map_field_mut(core, "types"), "A"), "contract") = text("B"); + let parameter = local("arg.0", "A"); + add_field( + core, + "functions", + map([( + "unusedIdentity", + function( + vec![parameter.clone()], + "A", + vec![], + vec![], + reference(¶meter), + ), + )]), + ); + }); + both_refuse(names, &fixture, package, "nominal contract authority"); +} diff --git a/crates/echo-wesley-gen/README.md b/crates/echo-wesley-gen/README.md index c7b6ef677..852ac99ff 100644 --- a/crates/echo-wesley-gen/README.md +++ b/crates/echo-wesley-gen/README.md @@ -57,11 +57,13 @@ validation does not install an artifact or confer Echo runtime authority. `admit_provider_contract_pack_for_publication_v1(...)` also permits explicit selection of the [ordered-instruction publication](../../schemas/edict-provider/contracts/ordered/README.md) -from Edict #219. Its schema and manifest have separate fixed digests and a +from Edict #219 and the +[source-function publication](../../schemas/edict-provider/contracts/source-functions-v1/README.md) +from Edict #226. Each schema/manifest pair has separate fixed digests and a publication-specific size bound. The original entry point and checked provider generation still select the original pure-binding publication. Neither API infers a publication from input bytes or treats schema admission as support for -ordered execution or stateful effects. +source-function execution, ordered execution, or stateful effects. `provider_generation::build_provider_generation_input_v1(...)` joins that admitted pack with exact Echo semantic-source bytes and the checked versioned @@ -176,7 +178,7 @@ unique expected inventory before resolving the root, caps that inventory at 256 files and 64 MiB, caps an actual scan at 1,024 entries, and never opens or reads an unexpected regular file. -`echo-edict-provider-assets` maintains the exact 40-file package-local carrier +`echo-edict-provider-assets` maintains the exact 42-file package-local carrier tree under `assets/v1/`. The physical carrier names are packaging locations, not replacement source identities: generator provenance continues to name the original repository-relative authored paths. Read-only mode requires every @@ -253,6 +255,20 @@ leaving provider component bytes unchanged. Verify that boundary separately with the authenticated component build/check and the standalone package admission witness. Do not edit generated files or hash locks manually to bypass drift. +The source-function publication is an explicit candidate route: + +```bash +cargo +1.96.0 run --locked -p echo-wesley-gen \ + --example source_functions_publication_witness -- /owned-data/source-functions-provider +``` + +Run this only inside the same guarded worker, after refreshing the checked +component carriers, with a new destination in its guarded data root. It selects +`ProviderContractPublicationV1::SourceFunctions` and admits the complete package +before writing it. The default checked generators retain the pure-binding +publication. Neither route grants runtime authority merely by generating a +schema-valid package. + ## Usage ```bash diff --git a/crates/echo-wesley-gen/assets/v1/edict-provider/contracts/source-functions-v1/edict-provider-contracts.cddl b/crates/echo-wesley-gen/assets/v1/edict-provider/contracts/source-functions-v1/edict-provider-contracts.cddl new file mode 100644 index 000000000..c7a0e9dd4 --- /dev/null +++ b/crates/echo-wesley-gen/assets/v1/edict-provider/contracts/source-functions-v1/edict-provider-contracts.cddl @@ -0,0 +1,1036 @@ +; SPDX-License-Identifier: Apache-2.0 +; edict-provider-contracts.cddl +; Generated from Edict-owned ABI fragments. DO NOT EDIT. + +; --- edict-common.cddl --- +; edict-common.cddl +; Shared CDDL types for the Edict ABIs, defined ONCE here so they cannot drift +; (EDICT-ABI-NODUP-001). Two groups: +; - resource-ref, sha256-digest, effect-failure-body, effect-kind, authority-class, +; core-type-ref, core-named-type-ref: assembled with edict-core.cddl, +; edict-target-profile.cddl, and +; edict-lawpack.cddl by the build; those schemas do not redefine them. +; - operation-profile, optic-template, aperture-requirement and their refs: +; consumed by the Core/optic layer (edict-core.cddl) and referenced by the +; language/target-profile specs. +; Authoritative byte encoding: edict.canonical-cbor/v1. + +; A normative subcomponent referenced by identity plus digest. Manifests never +; embed their own self-digest in their preimage (EDICT-CORE-SELFHASH-001). +resource-ref = { id: tstr, digest: sha256-digest } + +; Digests are authoritative as typed byte values, never hex strings. Review JSON +; renders this as "sha256:<64 lowercase hex>" (EDICT-DIGEST-WIRE-001). +sha256-digest = [ algorithm: "sha256", bytes: bstr .size 32 ] + +; A named low-level failure an effect can raise. The source obstruction map +; binds it (by coordinate) and constructs a typed domain obstruction from its +; payload (EDICT-ABI-FAILURE-NAMED-001). +; An effect's `effectFailures` list MUST have unique `coordinate`s: since the +; obstruction map is keyed by coordinate, two failures sharing a coordinate (even +; with different authorityClass/payloadType) make exhaustive mapping and binder +; typing ambiguous and are rejected (EDICT-ABI-FAILURE-UNIQUE-001). +; +; Effects carry their failures as a map `{ failure-ident => effect-failure-body }` +; (see the target/lawpack effect schemas). The failure coordinate is the map +; KEY, so it is not repeated in the body and cannot disagree with the key. +effect-failure-body = { + authorityClass: authority-class, + payloadType: core-type-ref, ; typed, bounded (may be empty record) +} + +; A failure coordinate must be a bare Edict `ident` (letter/underscore then +; letters/digits/underscores) AND must not be a reserved keyword (e.g. `else`, +; `basis`, `where`, `require`, `for`, `if`). The source obstruction-map LHS only +; accepts a non-keyword `ident`, so a hyphen/dot/keyword coordinate would be +; ABI-valid yet impossible to map exhaustively in source. The regex captures the +; lexical shape; keyword exclusion is an additional validation rule +; (EDICT-ABI-FAILURE-IDENT-001). +failure-ident = tstr .regexp "[A-Za-z_][A-Za-z0-9_]*" + +effect-kind = "read" / "create" / "ensure" / "replace" / "delete" / + "append" / "reduce" / "semantic.emit" / "custom" + +authority-class = "domainMappable" / "participantOwned" / "integrityFault" / + "resourceFault" / "internalFault" + +; The normative lexical and semantic grammar for Core type references lives in +; SPEC_edict-language-v1.md. CDDL can express only the wire-level text shape. +core-type-ref = tstr + +; A core.types map key is narrower than a general core-type-ref: it must be a +; named identity, never an intrinsic or canonical structural reference. The +; formal spec and executable Core validator enforce that semantic distinction. +core-named-type-ref = tstr + +; An operation profile supplies the optic template a Core intent resolves its +; opticKind/boundaryKind/supportPolicy/lossDisposition from. Target profiles and +; lawpacks publish these as a map `{ coordinate => operation-profile }`, so the +; coordinate is the KEY, not a value field (EDICT-OPTIC-TEMPLATE-OWNER-001, +; EDICT-ABI-OPPROFILE-UNIQUE-001). +operation-profile = { + opticTemplate: optic-template, + effectPredicate: tstr, ; coordinate of the operation-mode predicate +} + +optic-template = { + opticKind: "revelation" / "affectReintegration", + boundaryKind: "projection" / "affect", + supportPolicy: tstr, ; canonical support-policy coordinate + lossDisposition: tstr, ; canonical loss-disposition coordinate + ? basisTemplate: tstr, ; optional digest-locked basis template coord + ; the aperture requirement this template supplies. Required when the template + ; is the source of a Core optic's apertureRequirement (i.e. the intent has no + ; source `footprint <= ...`), since apertureRequirement is mandatory in Core + ; (EDICT-OPTIC-APERTURE-REF-001). + ? apertureRequirement: aperture-requirement, +} + +; apertureRequirement is a typed reference, never a free-form string. A review +; rendering may show its coordinate (EDICT-OPTIC-APERTURE-REF-001). +aperture-requirement = footprint-ceiling-ref / abstract-footprint-obligation-ref +footprint-ceiling-ref = { kind: "footprintCeiling", ref: tstr } +abstract-footprint-obligation-ref = { kind: "abstractFootprintObligation", ref: tstr } + +; --- edict-core.cddl --- +; edict-core.cddl +; Normative schema for the Edict Core v1 semantic model. +; +; Scope boundary: this file defines Core meaning and schema shape only. It does +; not define a canonical encoder, Core module hash fields, hash fixtures, target +; lowering, admission bundles, or target-owned IR. + +core-module = { + apiVersion: "edict.core/v1", + coordinate: tstr, + imports: [* core-import], + types: { * core-named-type-ref => core-type }, + ? functions: { + tstr => core-source-function }, + intents: { + tstr => core-intent }, + requiredCoreCapabilities: [* tstr], +} + +core-import = { + kind: "lawpack" / "target" / "core" / "capability", + ref: resource-ref, + ? alias: tstr, +} + +; --- types --------------------------------------------------------------- + +core-type = core-scalar-type / core-record-type / core-variant-type / + core-option-type / core-list-type / core-map-type / + core-capability-ref-type / core-external-action-request-type / + core-nominal-type + +core-scalar-type = core-bool-type / core-int-type / core-string-type / + core-bytes-type / core-unit-type + +core-bool-type = { kind: "Bool" } +core-unit-type = { kind: "Unit" } +core-int-type = { + kind: "I64" / "U64" / "I32" / "U32" / "I16" / "U16" / "I8" / "U8", +} +core-string-type = { + kind: "String", + max: uint, + canonical: "unicode-scalar-nfc" / "raw-utf8", +} +core-bytes-type = { + kind: "Bytes", + ? min: uint, + max: uint, +} +core-nominal-type = { + kind: "Nominal", + contract: tstr, + representation: core-type-ref, +} +core-record-type = { + kind: "Record", + fields: { * tstr => core-type-ref }, +} +core-variant-type = { + kind: "Variant", + cases: { + tstr => variant-case-body }, +} +variant-case-body = { + ? payload: core-type-ref, +} +core-option-type = { + kind: "Option", + item: core-type-ref, +} +core-list-type = { + kind: "List", + item: core-type-ref, + max: uint, +} +core-map-type = { + kind: "Map", + key: core-type-ref, + value: core-type-ref, + max: uint, +} +core-capability-ref-type = { + kind: "CapabilityRef", + item: core-type-ref, +} +core-external-action-request-type = { + kind: "ExternalActionRequest", + settlement: core-type-ref, +} + +; core-type-ref is defined in edict-common.cddl and assembled with this schema. + +; --- references and values ---------------------------------------------- + +; Local identity is alpha-stable. `id` is the compiler-owned local coordinate; +; `alphaName` is the normalized human/debug name. Source binder spelling is not +; identity. +local-ref = { + id: tstr, + alphaName: tstr, + type: core-type-ref, +} + +core-value = core-null-value / core-bool-value / core-int-value / + core-string-value / core-bytes-value / core-record-value / + core-variant-value / core-list-value / core-map-value / + core-capability-value + +core-null-value = { kind: "null" } +core-bool-value = { kind: "bool", value: bool } +core-int-value = { kind: "int", width: tstr, value: int } +core-string-value = { kind: "string", value: tstr } +core-bytes-value = { kind: "bytes", value: bstr } +core-record-value = { kind: "record", fields: { * tstr => core-value } } +core-variant-value = { + kind: "variant", + type: core-type-ref, + case: tstr, + ? payload: core-value, +} +core-list-value = { kind: "list", values: [* core-value] } +core-map-value = { kind: "map", entries: [* [key: core-value, value: core-value]] } +core-capability-value = { + kind: "capability", + receipt: sha256-digest, +} + +; Edict-authored pure helpers use a pure Core function body. The body can bind +; pure expressions and return an expression, but it cannot contain Core effect, +; guard, branch, loop, match-node, or proof-obligation nodes. +; Source-owned functions use the same pure body algebra, with their result type +; authenticated by the enclosing module. An empty function table is omitted. +core-source-function = { + params: [* local-ref], + returnType: core-type-ref, + body: core-pure-block, +} + +core-fn-body = { + params: [* local-ref], + body: core-pure-block, +} + +core-pure-block = { + locals: [* local-ref], + bindings: [* pure-let-node], + result: core-expr, +} + +pure-let-node = { + kind: "let", + binding: local-ref, + value: core-expr, +} + +; --- expressions and predicates ----------------------------------------- + +core-expr = local-expr / const-expr / record-expr / field-expr / + variant-expr / match-expr / call-expr / list-expr / map-expr / + if-expr + +local-expr = { kind: "local", ref: local-ref } +const-expr = { kind: "const", value: core-value } +record-expr = { kind: "record", fields: { * tstr => core-expr } } +field-expr = { kind: "field", base: core-expr, field: tstr } +variant-expr = { + kind: "variant", + type: core-type-ref, + case: tstr, + ? payload: core-expr, +} +match-expr = { + kind: "match", + scrutinee: core-expr, + arms: [+ match-arm], +} +match-arm = { + case: tstr, + ? binder: local-ref, + body: core-expr, +} +call-expr = { + kind: "call", + callee: tstr, + typeArgs: [* core-type-ref], + args: [* core-expr], +} +list-expr = { kind: "list", values: [* core-expr] } +map-expr = { kind: "map", entries: [* [key: core-expr, value: core-expr]] } +if-expr = { + kind: "if", + predicate: core-predicate, + then: core-expr, + else: core-expr, +} + +core-predicate = true-predicate / false-predicate / not-predicate / + all-predicate / any-predicate / compare-predicate / + call-predicate / obstruction-predicate + +true-predicate = { kind: "true" } +false-predicate = { kind: "false" } +not-predicate = { kind: "not", value: core-predicate } +all-predicate = { kind: "all", values: [+ core-predicate] } +any-predicate = { kind: "any", values: [+ core-predicate] } +compare-predicate = { + kind: "compare", + op: "==" / "!=" / "<" / "<=" / ">" / ">=", + left: core-expr, + right: core-expr, +} +call-predicate = { + kind: "call", + predicate: tstr, + args: [* core-expr], +} +obstruction-predicate = { + kind: "obstruction", + coordinate: failure-ident, + payload: core-expr, +} + +input-constraint = { + coordinate: tstr, + source: "where" / "compiler", + predicate: core-predicate, +} + +; --- intents, blocks, and nodes ----------------------------------------- + +core-intent = { + input: core-type-ref, + output: core-type-ref, + requiredOperationProfile: tstr, + ? basis: core-expr, + inputConstraints: [* input-constraint], + coreEvaluationBudget: core-budget, + body: core-block, + ? optic: core-optic, +} + +core-budget = { + maxSteps: uint, + maxAllocatedBytes: uint, + maxOutputBytes: uint, +} + +core-optic = { + opticKind: "revelation" / "affectReintegration", + boundaryKind: "projection" / "affect", + apertureRequirement: aperture-requirement, + supportPolicy: tstr, + lossDisposition: tstr, +} + +core-block = { + locals: [* local-ref], + nodes: [* core-node], + result: core-expr, +} + +core-node = let-node / require-node / effect-node / + external-action-request-node / guard-node / branch-node / + for-node / match-node / proof-obligation-node + +let-node = { + kind: "let", + binding: local-ref, + value: core-expr, +} +require-node = { + kind: "require", + predicate: core-predicate, + onFailure: require-failure-arm, +} +require-failure-arm = terminal-require-failure / + continue-obstructed-require-failure +terminal-require-failure = { + kind: "terminal", + reason: obstruction-reason, +} +continue-obstructed-require-failure = { + kind: "continueObstructed", + reason: obstruction-reason, +} +obstruction-reason = { + reasonKind: tstr, + payload: { * tstr => core-expr }, +} +effect-node = { + kind: "effect", + binding: local-ref, + effect: tstr, + input: core-expr, + obstructionMap: { * failure-ident => obstruction-arm }, +} +external-action-request-node = { + kind: "externalActionRequest", + binding: local-ref, + operation: resource-ref, + inputType: core-type-ref, + settlementType: core-type-ref, + inputSchema: resource-ref, + settlementSchema: resource-ref, + input: core-expr, + authorityScope: core-expr, + basis: core-expr, + budget: external-action-budget, + reconciliationLaw: resource-ref, + state: "awaitingSettlement", + settlementAdmission: "schemaRequired", +} +external-action-budget = { + maxSettlementBytes: core-expr, + maxAttempts: core-expr, +} +obstruction-arm = { + binder: local-ref, + value: core-expr, +} +guard-node = { + kind: "guard", + predicate: core-predicate, + obstruction: core-expr, +} +branch-node = { + kind: "branch", + ? binding: local-ref, + predicate: core-predicate, + then: core-block, + else: core-block, +} +for-node = { + kind: "for", + binder: local-ref, + iter: core-expr, + bound: core-bound, + body: core-block, +} +match-node = { + kind: "match", + scrutinee: core-expr, + arms: [+ match-block-arm], +} +match-block-arm = { + case: tstr, + ? binder: local-ref, + body: core-block, +} +proof-obligation-node = { + kind: "proof", + coordinate: tstr, + predicate: core-predicate, +} + +core-bound = literal-bound / coordinate-bound +literal-bound = { kind: "literal", value: uint } +coordinate-bound = { kind: "coordinate", ref: tstr } + +; Shared resource-ref, sha256-digest, failure-ident, aperture-requirement, +; core-type-ref, and core-named-type-ref are defined once in edict-common.cddl. + +; --- edict-lawpack.cddl --- +; edict-lawpack.cddl +; Canonical schema for the Edict lawpack manifest and export surface. +; Authoritative byte encoding: edict.canonical-cbor/v1. JSON in the prose specs +; is a review rendering generated from this schema (EDICT-ABI-NODUP-001). + +lawpack-manifest = { + apiVersion: "edict.lawpack/v1", + id: tstr, + version: tstr, + acceptedCoreAbi: [+ tstr], + dependencies: [* lawpack-dep], ; acyclic, digest-locked (EDICT-LAWPACK-DAG-001) + exports: resource-ref, + ? targetAdapters: [+ target-adapter], ; required only if any runtime effect exists + ? helperComponent: executable-component, ; executable helpers carry their own sandbox+fuel + verifier: verifier, ; classified: declarative or executable + compatibility: resource-ref, + conformanceFixtureCorpus: resource-ref, +} + +; A verifier is either a declarative ruleset (no runtime) or an executable +; component. An executable verifier MUST carry its own sandbox and fuel model, +; so the schema enforces that no executable component is left unbounded +; (EDICT-ABI-VERIFIER-BOUND-001). +verifier = declarative-verifier / executable-verifier +declarative-verifier = { class: "declarative", ruleset: resource-ref } +executable-verifier = { + class: "executable", + component: resource-ref, + sandbox: resource-ref, + fuelModel: resource-ref, +} + +; Any executable component is bounded by its own sandbox + fuel model. +executable-component = { + component: resource-ref, + sandbox: resource-ref, + fuelModel: resource-ref, +} + +lawpack-dep = { id: tstr, version: tstr, digest: sha256-digest } + +; Adapter selection keys SOLELY off the digest-locked `acceptedTargetProfile` +; (its `id` is the profile id; its `digest` pins the exact profile/version). There +; are no independent display strings that could disagree with the lock, so a +; resolver cannot bind an adapter to one target while the lock proves another +; (EDICT-LAWPACK-ADAPTER-TARGETIR-001). +target-adapter = { + acceptedTargetProfile: resource-ref, ; digest-locked, authoritative selector + acceptedTargetIr: resource-ref, ; digest-locked + adapter: resource-ref, +} + +; Shared types resource-ref and sha256-digest are defined once in +; edict-common.cddl and assembled with this schema (EDICT-ABI-NODUP-001). + +; --- export surface ------------------------------------------------------ + +lawpack-exports = { + types: [* exported-type], + constants: [* exported-constant], + pureFunctions: [* pure-function], + effects: [* semantic-effect], + obstructions: [* obstruction-def], + ; keyed by operation-profile coordinate → uniqueness enforced + ; (EDICT-ABI-OPPROFILE-UNIQUE-001) + ; operation-profile records this lawpack exports (optic templates that + ; `implements`/`profile` clauses resolve against). operation-profile is + ; defined in edict-common.cddl (EDICT-ABI-OPPROFILE-SLOT-001). + operationProfiles: { * tstr => operation-profile }, ; keyed by coordinate +} + +exported-type = { coordinate: tstr, definition: core-type-ref } +exported-constant = { coordinate: tstr, type: core-type-ref, value: any } + +; A pure helper is a discriminated union by `source`, so the schema itself +; guarantees an implementation exists (EDICT-LAWPACK-PURE-IMPL-001): +; - "edict": authored in Edict/Core; the Core body is carried inline (hashed +; with the export surface). The schema requires the `body` field. +; - "component": implemented outside Edict; carries no inline body and instead +; carries its own digest-locked `implementation` (sandbox + fuel). It does +; not depend on the optional manifest-level helperComponent. +pure-function = edict-pure-function / component-pure-function + +pure-function-common = ( + coordinate: tstr, + typeParameters: [* tstr], + parameterTypes: [* core-type-ref], ; all bounded + returnType: core-type-ref, ; bounded + costTemplate: tstr, + determinismClass: "total" / "total-with-typed-diagnostic", +) + +edict-pure-function = { + pure-function-common, + source: "edict", + body: core-fn-body, ; inline, hash-significant +} + +component-pure-function = { + pure-function-common, + source: "component", + ; the digest-locked component implementing this helper. Required at the schema + ; level so a component helper can never validate without a hash-bound, + ; sandbox+fuel-described implementation (EDICT-LAWPACK-PURE-IMPL-001). + implementation: executable-component, +} + +; core-fn-body is defined by edict-core.cddl and assembled with this lawpack +; schema. It is a pure Core function body, not an effect-capable core-block. + +semantic-effect = { + coordinate: tstr, + typeParameters: [* tstr], + inputType: core-type-ref, ; bounded + outputType: core-type-ref, ; bounded + executionClass: "proofOnly" / "runtime", ; orthogonal to writeClass + effectKindHint: effect-kind, + footprintObligation: tstr, + costObligation: tstr, + effectFailures: { * failure-ident => effect-failure-body }, ; keyed by coordinate; unique + guardSupport: bool, +} + +obstruction-def = { + coordinate: tstr, + authorityClass: authority-class, + payloadSchema: core-type-ref, ; typed, bounded (may be empty record) +} + +; effect-failure-body, effect-kind, authority-class, and core-type-ref are defined +; once in edict-common.cddl and assembled with this schema (EDICT-ABI-NODUP-001). + +; --- edict-lawpack-adapter.cddl --- +; edict-lawpack-adapter.cddl +; Canonical schema for one direct declarative lawpack target adapter. +; Authoritative byte encoding: edict.canonical-cbor/v1. +; +; The enclosing lawpack manifest selects the exact target profile, target IR, +; and adapter resource digest. Those identities are not repeated here. + +lawpack-adapter = { + apiVersion: "edict.lawpack-adapter/v1", + class: "declarative", + operationProfiles: { + * tstr => lawpack-adapter-operation-profile + }, + effectImplementations: { + * tstr => lawpack-adapter-effect + }, + budgets: { + * tstr => lawpack-adapter-budget + }, +} + +; Keys are canonical lawpack operation-profile coordinates. +lawpack-adapter-operation-profile = + { + core: tstr, + semanticEffects: [+ tstr], + ? budgetObligation: tstr, + ? targetConfiguration: resource-ref, + } / + { + core: tstr, + semanticEffects: [], + budgetObligation: tstr, + targetConfiguration: resource-ref, + } + +; Keys are canonical lawpack semantic-effect coordinates. Footprint, cost, and +; failure fields must exactly discharge the matching exported effect. +lawpack-adapter-effect = { + targetIntrinsic: tstr, + targetConfiguration: resource-ref, + writeClass: lawpack-adapter-write-class, + footprintObligation: tstr, + costObligation: tstr, + failureMappings: { * failure-ident => tstr }, +} + +; Keys are canonical exported cost-obligation coordinates. +lawpack-adapter-budget = { + maxSteps: uint, + maxAllocatedBytes: uint, + maxOutputBytes: uint, +} + +lawpack-adapter-write-class = "none" / "read" / "create" / "ensure" / + "append" / "replace" / "delete" / "custom" + +; failure-ident is defined in edict-common.cddl. + +; --- edict-target-profile.cddl --- +; edict-target-profile.cddl +; Canonical schema for the Edict target profile manifest. +; Authoritative byte encoding: edict.canonical-cbor/v1 +; (see SPEC_continuum-contract-bundle-v1.md). JSON in the prose specs is a +; review rendering generated from this schema; this CDDL is the single source +; of truth (EDICT-ABI-NODUP-001). + +target-profile-manifest = { + apiVersion: "edict.target-profile/v1", + id: tstr, ; e.g. "echo.dpo" + version: tstr, ; e.g. "1" + acceptedCoreAbi: [+ tstr], ; e.g. ["edict.core/v1"] + + intrinsics: resource-ref, + intrinsicNamespace: tstr, + ; publishes this profile's operation-profile records (optic templates that + ; `profile`/`implements` clauses resolve against). References an + ; operation-profiles-document (EDICT-ABI-OPPROFILE-SLOT-001). + operationProfiles: resource-ref, + footprintAlgebra: resource-ref, + costAlgebra: resource-ref, + targetIr: resource-ref, + obstructionTaxonomy: resource-ref, + verifier: resource-ref, + lowerer: resource-ref, + sandbox: resource-ref, + fuelModel: resource-ref, + + ; fields the language spec requires of every profile + bundleProfile: resource-ref, + generatedArtifactProfiles: [* resource-ref], + canonicalEncodingRules: resource-ref, + ; A profile that accepts the direct declarative lawpack-adapter ABI names it + ; exactly once. Profiles that do not consume lawpack adapters leave this + ; optional slot absent or empty. + ? acceptedLawpackAdapterAbi: [] / ["edict.lawpack-adapter/v1"], + diagnosticAbi: resource-ref, + + ; application doctrine + applicationModel: "atomic", + readConsistency: "application-snapshot" / tstr, + guardEvaluation: "precommit-atomic" / tstr, + obstructionRollback: "no-visible-effects" / tstr, + multiTarget: bool, + ; whether the target can evaluate precommit postcondition (`guarantee`) checks + ; inside the atomic application unit (EDICT-TARGET-POSTCOND-001) + postconditionSupport: bool, + + deterministicExecution: resource-ref, + conformanceFixtureCorpus: resource-ref, +} + +; Shared types resource-ref and sha256-digest are defined once in +; edict-common.cddl and assembled with this schema by the build +; (EDICT-ABI-NODUP-001). They are not redefined here. + +; --- intrinsic signature ------------------------------------------------- +; The artifact referenced by the manifest's `intrinsics` resource-ref is the +; intrinsic-signature corpus document below. Its layout is fixed so two +; independent profiles validate/hash the corpus identically +; (EDICT-ABI-INTRINSICS-DOC-001). + +; intrinsics is a MAP keyed by coordinate, so the schema itself enforces +; coordinate uniqueness. A provider receives the resolved corpus as a +; digest-bound semantic input and resolves coordinates within that artifact. +; Each map key MUST equal its record's `coordinate` field +; (EDICT-ABI-INTRINSIC-UNIQUE-001). +intrinsics-document = { + apiVersion: "edict.target-profile.intrinsics/v1", + intrinsics: { * tstr => intrinsic }, +} + +; The artifact referenced by the manifest's `operationProfiles` resource-ref. +; operation-profile / optic-template are defined in edict-common.cddl. Keyed by +; coordinate so resolution can't pick between two same-coordinate profiles +; (EDICT-ABI-OPPROFILE-SLOT-001, EDICT-ABI-OPPROFILE-UNIQUE-001). +operation-profiles-document = { + apiVersion: "edict.target-profile.operation-profiles/v1", + profiles: { * tstr => operation-profile }, +} + +; A typed pre-lowering question that can be proposed by Watson or an agent and +; checked by the compiler. It is canonical-CBOR encoded under +; `edict.lowering-requirements/v1`; the compiler checks this artifact, not the +; prose that produced it. +lowering-requirements = { + apiVersion: "edict.lowering-requirements/v1", + operationProfile: tstr, + semanticEffects: [* semantic-effect-requirement], + requiredWriteClasses: [* write-class], + guardKinds: [* guard-kind], + atomicity: atomicity-requirement, + postconditionSupport: bool, + obstructionCoordinates: [* tstr], + footprintObligations: [* tstr], + costObligations: [* tstr], + opticContract: tstr, +} + +semantic-effect-requirement = { + coordinate: tstr, + writeClass: write-class, + guardKinds: [* guard-kind], + obstructionCoordinates: [* tstr], + footprintObligations: [* tstr], + costObligations: [* tstr], +} + +write-class = "none" / "read" / "create" / "ensure" / "append" / + "replace" / "delete" / tstr +guard-kind = "precommit-atomic" / tstr +atomicity-requirement = "atomic" / tstr + +; A genuine union: pure constructors carry no effect kind or failures; effect +; intrinsics must (EDICT-TARGET-INTRINSIC-CLASS-001). The schema enforces this, +; not a comment. + +; The intrinsic's coordinate is the intrinsics map KEY, not a value field, so the +; key and coordinate can never disagree (EDICT-ABI-INTRINSIC-UNIQUE-001). +intrinsic = pure-intrinsic / effect-intrinsic + +pure-intrinsic = { + intrinsicClass: "pure", + typeParameters: [* tstr], + argumentTypes: [* core-type-ref], + returnType: core-type-ref, + guardSupport: false, + footprintTemplate: tstr, + costTemplate: tstr, + writeClass: "none", +} + +effect-intrinsic = { + intrinsicClass: "effect", + typeParameters: [* tstr], + argumentTypes: [* core-type-ref], + returnType: core-type-ref, + effectKind: effect-kind, + ; map keyed by failure coordinate (failure-ident); the failure coordinate is + ; the key, not a value field, so uniqueness is structural + ; (EDICT-ABI-FAILURE-UNIQUE-001). + effectFailures: { * failure-ident => effect-failure-body }, + guardSupport: bool, + footprintTemplate: tstr, + costTemplate: tstr, + writeClass: "read" / "create" / "ensure" / "append" / "replace" / + "delete" / "custom", + canParticipateInAtomicGuard: bool, +} + +; effect-failure-body, effect-kind, authority-class, and core-type-ref are defined +; once in edict-common.cddl and assembled with this schema (EDICT-ABI-NODUP-001). + +; --- edict-authority-facts.cddl --- +; edict-authority-facts.cddl +; Canonical schema for the first compiler-context authority-facts document. +; Authoritative byte encoding: edict.canonical-cbor/v1. +; +; This schema is assembled with edict-common.cddl so source.digest uses the +; shared sha256-digest typed value. JSON is a review/input rendering: its +; `sha256:<64 hex>` source digest is projected to [`sha256`, 32 raw bytes] on +; the wire, and its fact arrays project to the coordinate-keyed maps below. + +authority-facts = { + apiVersion: "edict.authority-facts/v1", + source: authority-fact-source, + operationProfiles: { * tstr => authority-operation-profile-fact }, + effectWriteClasses: { * tstr => authority-write-class }, + budgets: { * tstr => authority-budget-fact }, +} + +authority-fact-source = { + kind: "lawpack" / "targetProfile", + coordinate: tstr, + digest: sha256-digest, +} + +; The map key is the source operation-profile coordinate. It is not repeated in +; the value, so a key and embedded coordinate cannot disagree. Allowed write +; classes are a canonical map-set: the class is the unique key and null is the +; unit marker. Canonical CBOR fixes key order without a second ordering rule. +authority-operation-profile-fact = { + core: tstr, + allowedWriteClasses: { * authority-write-class => null }, +} + +; The effectWriteClasses map key is the semantic effect coordinate. The budgets +; map key is the source budget coordinate. Canonical CBOR map-key uniqueness +; makes duplicate fact coordinates structurally unrepresentable. +authority-budget-fact = { + maxSteps: uint, + maxAllocatedBytes: uint, + maxOutputBytes: uint, +} + +; AuthorityFactsDocument v1 intentionally accepts only the write classes the +; current compiler model can consume. `custom` is the sole v1 custom spelling; +; arbitrary target-profile extension strings do not enter this compiler path. +authority-write-class = "none" / "read" / "create" / "ensure" / "append" / + "replace" / "delete" / "custom" + +; --- edict-result-projection.cddl --- +; SPDX-License-Identifier: Apache-2.0 +; edict-result-projection.cddl +; Canonical schema for compiler-owned application-result projections. +; Authoritative byte encoding: edict.canonical-cbor/v1. + +result-projection = { + schema: "edict.result-projection/v1", + operationCoordinate: bounded-projection-text, + outputType: bounded-projection-text, + maxOutputBytes: uint .gt 0, + expression: result-projection-expr, +} + +result-projection-expr = result-projection-record / result-projection-source + +result-projection-record = { + kind: "record", + ; The root record counts as one of the Rust decoder's 256 expression nodes. + ; Nested aggregate node count remains an authoritative decoder check. + fields: { 0*255 bounded-projection-text => result-projection-expr }, +} + +result-projection-source = { + kind: "source", + source: result-projection-source-kind, + ; Matches MAX_RESULT_PROJECTION_PATH_SEGMENTS in edict-syntax. + path: [0*32 bounded-projection-text], +} + +result-projection-source-kind = + { kind: "applicationInput" } / + { + kind: "pureBinding", + bindingId: bounded-projection-text, + } / + { + kind: "capabilityResult", + stepId: bounded-projection-text, + } + +bounded-projection-text = tstr .size (1..1024) + +; --- edict-target-ir.cddl --- +; SPDX-License-Identifier: Apache-2.0 +; edict-target-ir.cddl +; Canonical schema for the Edict-owned Target IR artifact envelope. +; Authoritative byte encoding: edict.canonical-cbor/v1. +; +; This schema is assembled with edict-common.cddl and edict-core.cddl. It +; deliberately reuses Core expressions, predicates, budgets, local references, +; obstruction reasons, and obstruction arms so the schema matches the value +; emitted by the canonical Target IR encoder rather than restating those types. +; It describes the structural shape of valid lowering-produced artifacts. The +; lowering and encoder contracts separately enforce semantic identifier rules +; and canonical ordering/deduplication for set-like values. + +; Target IR encoding rejects an empty target-profile coordinate before bytes +; exist, so this root tightens the shared structural resource-ref accordingly. +target-ir-resource-ref = { + id: tstr .regexp "(?s).+", + digest: sha256-digest, +} + +target-ir-artifact = target-ir-phased-artifact / target-ir-ordered-artifact + +target-ir-phased-artifact = target-ir-closed-artifact / target-ir-legacy-artifact + +target-ir-artifact-common = ( + domain: tstr, + targetProfile: target-ir-resource-ref, + sourceCoreCoordinate: tstr .regexp "(?s).+", +) + +; v2 binds one execution order over the instruction tables. The encoder also +; validates the exact permutation and producer-before-consumer dependencies. +target-ir-ordered-artifact = target-ir-ordered-closed / target-ir-ordered-legacy + +target-ir-ordered-common = ( + domain: "echo.span-ir/v2", + targetProfile: target-ir-resource-ref, + sourceCoreCoordinate: tstr .regexp "(?s).+", +) + +target-ir-ordered-closed = { + kind: "orderedTargetIrArtifact", + target-ir-ordered-common, + semanticClosure: target-ir-semantic-closure, + intents: { * tstr => target-ir-ordered-intent }, +} + +target-ir-ordered-legacy = { + kind: "orderedTargetIrArtifact", + target-ir-ordered-common, + intents: { * tstr => target-ir-ordered-legacy-intent }, +} + +target-ir-ordered-intent = { + target-ir-intent-common, + ? basis: core-expr, + ? pureBindings: [1* target-ir-pure-binding], + executionOrder: [* tstr .regexp "(?s).+"], +} + +target-ir-ordered-legacy-intent = { + target-ir-intent-common, + executionOrder: [* tstr .regexp "(?s).+"], +} + +target-ir-closed-artifact = { + kind: "targetIrArtifact", + target-ir-artifact-common, + semanticClosure: target-ir-semantic-closure, + intents: { * tstr => target-ir-intent }, +} + +target-ir-legacy-artifact = { + kind: "targetIrArtifact", + target-ir-artifact-common, + intents: { * tstr => target-ir-legacy-intent }, +} + +target-ir-semantic-closure = { + sourceCore: target-ir-resource-ref, + lawpacks: [* target-ir-resource-ref], + ? capabilities: [* target-ir-resource-ref], +} + +target-ir-intent = { + target-ir-intent-common, + ? basis: core-expr, + ? pureBindings: [1* target-ir-pure-binding], + ? externalActionRequests: [* target-ir-external-action-request], +} + +target-ir-legacy-intent = { + target-ir-intent-common, +} + +target-ir-intent-common = ( + operationProfile: tstr, + inputConstraints: [* input-constraint], + coreEvaluationBudget: core-budget, + requirements: [* target-ir-requirement], + steps: [* target-ir-step], + result: core-expr, +) + +target-ir-requirement = { + id: tstr, + predicate: core-predicate, + onFailure: require-failure-arm, +} + +target-ir-pure-binding = { + id: tstr .regexp "(?s).+", + binding: local-ref, + value: core-expr, +} + +target-ir-step = { + id: tstr, + binding: local-ref, + effect: tstr, + targetIntrinsic: tstr, + input: core-expr, + obstructionFailures: [* failure-ident], + obstructionArms: { * failure-ident => obstruction-arm }, +} + +target-ir-external-action-request = { + id: tstr, + binding: local-ref, + operation: target-ir-resource-ref, + inputType: core-type-ref, + settlementType: core-type-ref, + inputSchema: target-ir-resource-ref, + settlementSchema: target-ir-resource-ref, + input: core-expr, + authorityScope: core-expr, + basis: core-expr, + budget: external-action-budget, + reconciliationLaw: target-ir-resource-ref, + state: "awaitingSettlement", + settlementAdmission: "schemaRequired", +} diff --git a/crates/echo-wesley-gen/assets/v1/edict-provider/contracts/source-functions-v1/manifest.json b/crates/echo-wesley-gen/assets/v1/edict-provider/contracts/source-functions-v1/manifest.json new file mode 100644 index 000000000..267a5878a --- /dev/null +++ b/crates/echo-wesley-gen/assets/v1/edict-provider/contracts/source-functions-v1/manifest.json @@ -0,0 +1,83 @@ +{ + "apiVersion": "edict.provider-contract-pack/v1", + "coordinate": "edict.provider-contract-pack.cddl@1", + "license": "Apache-2.0", + "schema": { + "bytesHex": "3b20535044582d4c6963656e73652d4964656e7469666965723a204170616368652d322e300a3b2065646963742d70726f76696465722d636f6e7472616374732e6364646c0a3b2047656e6572617465642066726f6d2045646963742d6f776e65642041424920667261676d656e74732e20444f204e4f5420454449542e0a0a3b202d2d2d2065646963742d636f6d6d6f6e2e6364646c202d2d2d0a3b2065646963742d636f6d6d6f6e2e6364646c0a3b20536861726564204344444c20747970657320666f722074686520456469637420414249732c20646566696e6564204f4e4345206865726520736f20746865792063616e6e6f742064726966740a3b202845444943542d4142492d4e4f4455502d303031292e2054776f2067726f7570733a0a3b2020202d207265736f757263652d7265662c207368613235362d6469676573742c206566666563742d6661696c7572652d626f64792c206566666563742d6b696e642c20617574686f726974792d636c6173732c0a3b2020202020636f72652d747970652d7265662c20636f72652d6e616d65642d747970652d7265663a20617373656d626c656420776974682065646963742d636f72652e6364646c2c0a3b202020202065646963742d7461726765742d70726f66696c652e6364646c2c20616e640a3b202020202065646963742d6c61777061636b2e6364646c20627920746865206275696c643b2074686f736520736368656d617320646f206e6f74207265646566696e65207468656d2e0a3b2020202d206f7065726174696f6e2d70726f66696c652c206f707469632d74656d706c6174652c2061706572747572652d726571756972656d656e7420616e6420746865697220726566733a0a3b2020202020636f6e73756d65642062792074686520436f72652f6f70746963206c61796572202865646963742d636f72652e6364646c2920616e64207265666572656e636564206279207468650a3b20202020206c616e67756167652f7461726765742d70726f66696c652073706563732e0a3b20417574686f7269746174697665206279746520656e636f64696e673a2065646963742e63616e6f6e6963616c2d63626f722f76312e0a0a3b2041206e6f726d617469766520737562636f6d706f6e656e74207265666572656e636564206279206964656e7469747920706c7573206469676573742e204d616e696665737473206e657665720a3b20656d626564207468656972206f776e2073656c662d64696765737420696e20746865697220707265696d616765202845444943542d434f52452d53454c46484153482d303031292e0a7265736f757263652d726566203d207b2069643a20747374722c206469676573743a207368613235362d646967657374207d0a0a3b20446967657374732061726520617574686f726974617469766520617320747970656420627974652076616c7565732c206e657665722068657820737472696e67732e20526576696577204a534f4e0a3b2072656e64657273207468697320617320227368613235363a3c3634206c6f77657263617365206865783e22202845444943542d4449474553542d574952452d303031292e0a7368613235362d646967657374203d205b20616c676f726974686d3a2022736861323536222c2062797465733a2062737472202e73697a65203332205d0a0a3b2041206e616d6564206c6f772d6c6576656c206661696c75726520616e206566666563742063616e2072616973652e2054686520736f75726365206f62737472756374696f6e206d61700a3b2062696e64732069742028627920636f6f7264696e6174652920616e6420636f6e73747275637473206120747970656420646f6d61696e206f62737472756374696f6e2066726f6d206974730a3b207061796c6f6164202845444943542d4142492d4641494c5552452d4e414d45442d303031292e0a3b20416e20656666656374277320606566666563744661696c7572657360206c697374204d555354206861766520756e697175652060636f6f7264696e61746560733a2073696e6365207468650a3b206f62737472756374696f6e206d6170206973206b6579656420627920636f6f7264696e6174652c2074776f206661696c757265732073686172696e67206120636f6f7264696e61746520286576656e0a3b207769746820646966666572656e7420617574686f72697479436c6173732f7061796c6f61645479706529206d616b652065786861757374697665206d617070696e6720616e642062696e6465720a3b20747970696e6720616d626967756f757320616e64206172652072656a6563746564202845444943542d4142492d4641494c5552452d554e495155452d303031292e0a3b0a3b2045666665637473206361727279207468656972206661696c757265732061732061206d617020607b206661696c7572652d6964656e74203d3e206566666563742d6661696c7572652d626f6479207d600a3b202873656520746865207461726765742f6c61777061636b2065666665637420736368656d6173292e20546865206661696c75726520636f6f7264696e61746520697320746865206d61700a3b204b45592c20736f206974206973206e6f7420726570656174656420696e2074686520626f647920616e642063616e6e6f74206469736167726565207769746820746865206b65792e0a6566666563742d6661696c7572652d626f6479203d207b0a2020617574686f72697479436c6173733a20617574686f726974792d636c6173732c0a20207061796c6f6164547970653a20636f72652d747970652d7265662c202020202020202020202020203b2074797065642c20626f756e64656420286d617920626520656d707479207265636f7264290a7d0a0a3b2041206661696c75726520636f6f7264696e617465206d7573742062652061206261726520456469637420606964656e746020286c65747465722f756e64657273636f7265207468656e0a3b206c6574746572732f6469676974732f756e64657273636f7265732920414e44206d757374206e6f742062652061207265736572766564206b6579776f72642028652e672e2060656c7365602c0a3b20606261736973602c20607768657265602c206072657175697265602c2060666f72602c2060696660292e2054686520736f75726365206f62737472756374696f6e2d6d6170204c4853206f6e6c790a3b20616363657074732061206e6f6e2d6b6579776f726420606964656e74602c20736f20612068797068656e2f646f742f6b6579776f726420636f6f7264696e61746520776f756c642062650a3b204142492d76616c69642079657420696d706f737369626c6520746f206d617020657868617573746976656c7920696e20736f757263652e20546865207265676578206361707475726573207468650a3b206c65786963616c2073686170653b206b6579776f7264206578636c7573696f6e20697320616e206164646974696f6e616c2076616c69646174696f6e2072756c650a3b202845444943542d4142492d4641494c5552452d4944454e542d303031292e0a6661696c7572652d6964656e74203d2074737472202e72656765787020225b412d5a612d7a5f5d5b412d5a612d7a302d395f5d2a220a0a6566666563742d6b696e64203d20227265616422202f202263726561746522202f2022656e7375726522202f20227265706c61636522202f202264656c65746522202f0a202020202020202020202020202022617070656e6422202f202272656475636522202f202273656d616e7469632e656d697422202f2022637573746f6d220a0a617574686f726974792d636c617373203d2022646f6d61696e4d61707061626c6522202f20227061727469636970616e744f776e656422202f2022696e746567726974794661756c7422202f0a202020202020202020202020202020202020227265736f757263654661756c7422202f2022696e7465726e616c4661756c74220a0a3b20546865206e6f726d6174697665206c65786963616c20616e642073656d616e746963206772616d6d617220666f7220436f72652074797065207265666572656e636573206c6976657320696e0a3b20535045435f65646963742d6c616e67756167652d76312e6d642e204344444c2063616e2065787072657373206f6e6c792074686520776972652d6c6576656c20746578742073686170652e0a636f72652d747970652d726566203d20747374720a0a3b204120636f72652e7479706573206d6170206b6579206973206e6172726f776572207468616e20612067656e6572616c20636f72652d747970652d7265663a206974206d75737420626520610a3b206e616d6564206964656e746974792c206e6576657220616e20696e7472696e736963206f722063616e6f6e6963616c207374727563747572616c207265666572656e63652e205468650a3b20666f726d616c207370656320616e642065786563757461626c6520436f72652076616c696461746f7220656e666f72636520746861742073656d616e7469632064697374696e6374696f6e2e0a636f72652d6e616d65642d747970652d726566203d20747374720a0a3b20416e206f7065726174696f6e2070726f66696c6520737570706c69657320746865206f707469632074656d706c617465206120436f726520696e74656e74207265736f6c766573206974730a3b206f707469634b696e642f626f756e646172794b696e642f737570706f7274506f6c6963792f6c6f7373446973706f736974696f6e2066726f6d2e205461726765742070726f66696c657320616e640a3b206c61777061636b73207075626c6973682074686573652061732061206d617020607b20636f6f7264696e617465203d3e206f7065726174696f6e2d70726f66696c65207d602c20736f207468650a3b20636f6f7264696e61746520697320746865204b45592c206e6f7420612076616c7565206669656c64202845444943542d4f505449432d54454d504c4154452d4f574e45522d3030312c0a3b2045444943542d4142492d4f5050524f46494c452d554e495155452d303031292e0a6f7065726174696f6e2d70726f66696c65203d207b0a20206f7074696354656d706c6174653a206f707469632d74656d706c6174652c0a20206566666563745072656469636174653a20747374722c20202020202020202020203b20636f6f7264696e617465206f6620746865206f7065726174696f6e2d6d6f6465207072656469636174650a7d0a0a6f707469632d74656d706c617465203d207b0a20206f707469634b696e643a2022726576656c6174696f6e22202f20226166666563745265696e746567726174696f6e222c0a2020626f756e646172794b696e643a202270726f6a656374696f6e22202f2022616666656374222c0a2020737570706f7274506f6c6963793a20747374722c202020202020202020202020203b2063616e6f6e6963616c20737570706f72742d706f6c69637920636f6f7264696e6174650a20206c6f7373446973706f736974696f6e3a20747374722c20202020202020202020203b2063616e6f6e6963616c206c6f73732d646973706f736974696f6e20636f6f7264696e6174650a20203f20626173697354656d706c6174653a20747374722c20202020202020202020203b206f7074696f6e616c206469676573742d6c6f636b65642062617369732074656d706c61746520636f6f72640a20203b2074686520617065727475726520726571756972656d656e7420746869732074656d706c61746520737570706c6965732e205265717569726564207768656e207468652074656d706c6174650a20203b2069732074686520736f75726365206f66206120436f7265206f707469632773206170657274757265526571756972656d656e742028692e652e2074686520696e74656e7420686173206e6f0a20203b20736f757263652060666f6f747072696e74203c3d202e2e2e60292c2073696e6365206170657274757265526571756972656d656e74206973206d616e6461746f727920696e20436f72650a20203b202845444943542d4f505449432d41504552545552452d5245462d303031292e0a20203f206170657274757265526571756972656d656e743a2061706572747572652d726571756972656d656e742c0a7d0a0a3b206170657274757265526571756972656d656e742069732061207479706564207265666572656e63652c206e65766572206120667265652d666f726d20737472696e672e2041207265766965770a3b2072656e646572696e67206d61792073686f772069747320636f6f7264696e617465202845444943542d4f505449432d41504552545552452d5245462d303031292e0a61706572747572652d726571756972656d656e74203d20666f6f747072696e742d6365696c696e672d726566202f2061627374726163742d666f6f747072696e742d6f626c69676174696f6e2d7265660a666f6f747072696e742d6365696c696e672d726566203d207b206b696e643a2022666f6f747072696e744365696c696e67222c207265663a2074737472207d0a61627374726163742d666f6f747072696e742d6f626c69676174696f6e2d726566203d207b206b696e643a20226162737472616374466f6f747072696e744f626c69676174696f6e222c207265663a2074737472207d0a0a3b202d2d2d2065646963742d636f72652e6364646c202d2d2d0a3b2065646963742d636f72652e6364646c0a3b204e6f726d617469766520736368656d6120666f722074686520456469637420436f72652076312073656d616e746963206d6f64656c2e0a3b0a3b2053636f706520626f756e646172793a20746869732066696c6520646566696e657320436f7265206d65616e696e6720616e6420736368656d61207368617065206f6e6c792e20497420646f65730a3b206e6f7420646566696e6520612063616e6f6e6963616c20656e636f6465722c20436f7265206d6f64756c652068617368206669656c64732c20686173682066697874757265732c207461726765740a3b206c6f776572696e672c2061646d697373696f6e2062756e646c65732c206f72207461726765742d6f776e65642049522e0a0a636f72652d6d6f64756c65203d207b0a202061706956657273696f6e3a202265646963742e636f72652f7631222c0a2020636f6f7264696e6174653a20747374722c0a2020696d706f7274733a205b2a20636f72652d696d706f72745d2c0a202074797065733a207b202a20636f72652d6e616d65642d747970652d726566203d3e20636f72652d74797065207d2c0a20203f2066756e6374696f6e733a207b202b2074737472203d3e20636f72652d736f757263652d66756e6374696f6e207d2c0a2020696e74656e74733a207b202b2074737472203d3e20636f72652d696e74656e74207d2c0a20207265717569726564436f72654361706162696c69746965733a205b2a20747374725d2c0a7d0a0a636f72652d696d706f7274203d207b0a20206b696e643a20226c61777061636b22202f202274617267657422202f2022636f726522202f20226361706162696c697479222c0a20207265663a207265736f757263652d7265662c0a20203f20616c6961733a20747374722c0a7d0a0a3b202d2d2d207479706573202d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d0a0a636f72652d74797065203d20636f72652d7363616c61722d74797065202f20636f72652d7265636f72642d74797065202f20636f72652d76617269616e742d74797065202f0a202020202020202020202020636f72652d6f7074696f6e2d74797065202f20636f72652d6c6973742d74797065202f20636f72652d6d61702d74797065202f0a202020202020202020202020636f72652d6361706162696c6974792d7265662d74797065202f20636f72652d65787465726e616c2d616374696f6e2d726571756573742d74797065202f0a202020202020202020202020636f72652d6e6f6d696e616c2d747970650a0a636f72652d7363616c61722d74797065203d20636f72652d626f6f6c2d74797065202f20636f72652d696e742d74797065202f20636f72652d737472696e672d74797065202f0a20202020202020202020202020202020202020636f72652d62797465732d74797065202f20636f72652d756e69742d747970650a0a636f72652d626f6f6c2d74797065203d207b206b696e643a2022426f6f6c22207d0a636f72652d756e69742d74797065203d207b206b696e643a2022556e697422207d0a636f72652d696e742d74797065203d207b0a20206b696e643a202249363422202f202255363422202f202249333222202f202255333222202f202249313622202f202255313622202f2022493822202f20225538222c0a7d0a636f72652d737472696e672d74797065203d207b0a20206b696e643a2022537472696e67222c0a20206d61783a2075696e742c0a202063616e6f6e6963616c3a2022756e69636f64652d7363616c61722d6e666322202f20227261772d75746638222c0a7d0a636f72652d62797465732d74797065203d207b0a20206b696e643a20224279746573222c0a20203f206d696e3a2075696e742c0a20206d61783a2075696e742c0a7d0a636f72652d6e6f6d696e616c2d74797065203d207b0a20206b696e643a20224e6f6d696e616c222c0a2020636f6e74726163743a20747374722c0a2020726570726573656e746174696f6e3a20636f72652d747970652d7265662c0a7d0a636f72652d7265636f72642d74797065203d207b0a20206b696e643a20225265636f7264222c0a20206669656c64733a207b202a2074737472203d3e20636f72652d747970652d726566207d2c0a7d0a636f72652d76617269616e742d74797065203d207b0a20206b696e643a202256617269616e74222c0a202063617365733a207b202b2074737472203d3e2076617269616e742d636173652d626f6479207d2c0a7d0a76617269616e742d636173652d626f6479203d207b0a20203f207061796c6f61643a20636f72652d747970652d7265662c0a7d0a636f72652d6f7074696f6e2d74797065203d207b0a20206b696e643a20224f7074696f6e222c0a20206974656d3a20636f72652d747970652d7265662c0a7d0a636f72652d6c6973742d74797065203d207b0a20206b696e643a20224c697374222c0a20206974656d3a20636f72652d747970652d7265662c0a20206d61783a2075696e742c0a7d0a636f72652d6d61702d74797065203d207b0a20206b696e643a20224d6170222c0a20206b65793a20636f72652d747970652d7265662c0a202076616c75653a20636f72652d747970652d7265662c0a20206d61783a2075696e742c0a7d0a636f72652d6361706162696c6974792d7265662d74797065203d207b0a20206b696e643a20224361706162696c697479526566222c0a20206974656d3a20636f72652d747970652d7265662c0a7d0a636f72652d65787465726e616c2d616374696f6e2d726571756573742d74797065203d207b0a20206b696e643a202245787465726e616c416374696f6e52657175657374222c0a2020736574746c656d656e743a20636f72652d747970652d7265662c0a7d0a0a3b20636f72652d747970652d72656620697320646566696e656420696e2065646963742d636f6d6d6f6e2e6364646c20616e6420617373656d626c65642077697468207468697320736368656d612e0a0a3b202d2d2d207265666572656e63657320616e642076616c756573202d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d0a0a3b204c6f63616c206964656e7469747920697320616c7068612d737461626c652e20606964602069732074686520636f6d70696c65722d6f776e6564206c6f63616c20636f6f7264696e6174653b0a3b2060616c7068614e616d656020697320746865206e6f726d616c697a65642068756d616e2f6465627567206e616d652e20536f757263652062696e646572207370656c6c696e67206973206e6f740a3b206964656e746974792e0a6c6f63616c2d726566203d207b0a202069643a20747374722c0a2020616c7068614e616d653a20747374722c0a2020747970653a20636f72652d747970652d7265662c0a7d0a0a636f72652d76616c7565203d20636f72652d6e756c6c2d76616c7565202f20636f72652d626f6f6c2d76616c7565202f20636f72652d696e742d76616c7565202f0a20202020202020202020202020636f72652d737472696e672d76616c7565202f20636f72652d62797465732d76616c7565202f20636f72652d7265636f72642d76616c7565202f0a20202020202020202020202020636f72652d76617269616e742d76616c7565202f20636f72652d6c6973742d76616c7565202f20636f72652d6d61702d76616c7565202f0a20202020202020202020202020636f72652d6361706162696c6974792d76616c75650a0a636f72652d6e756c6c2d76616c7565203d207b206b696e643a20226e756c6c22207d0a636f72652d626f6f6c2d76616c7565203d207b206b696e643a2022626f6f6c222c2076616c75653a20626f6f6c207d0a636f72652d696e742d76616c7565203d207b206b696e643a2022696e74222c2077696474683a20747374722c2076616c75653a20696e74207d0a636f72652d737472696e672d76616c7565203d207b206b696e643a2022737472696e67222c2076616c75653a2074737472207d0a636f72652d62797465732d76616c7565203d207b206b696e643a20226279746573222c2076616c75653a2062737472207d0a636f72652d7265636f72642d76616c7565203d207b206b696e643a20227265636f7264222c206669656c64733a207b202a2074737472203d3e20636f72652d76616c7565207d207d0a636f72652d76617269616e742d76616c7565203d207b0a20206b696e643a202276617269616e74222c0a2020747970653a20636f72652d747970652d7265662c0a2020636173653a20747374722c0a20203f207061796c6f61643a20636f72652d76616c75652c0a7d0a636f72652d6c6973742d76616c7565203d207b206b696e643a20226c697374222c2076616c7565733a205b2a20636f72652d76616c75655d207d0a636f72652d6d61702d76616c7565203d207b206b696e643a20226d6170222c20656e74726965733a205b2a205b6b65793a20636f72652d76616c75652c2076616c75653a20636f72652d76616c75655d5d207d0a636f72652d6361706162696c6974792d76616c7565203d207b0a20206b696e643a20226361706162696c697479222c0a2020726563656970743a207368613235362d6469676573742c0a7d0a0a3b2045646963742d617574686f72656420707572652068656c70657273207573652061207075726520436f72652066756e6374696f6e20626f64792e2054686520626f64792063616e2062696e640a3b20707572652065787072657373696f6e7320616e642072657475726e20616e2065787072657373696f6e2c206275742069742063616e6e6f7420636f6e7461696e20436f7265206566666563742c0a3b2067756172642c206272616e63682c206c6f6f702c206d617463682d6e6f64652c206f722070726f6f662d6f626c69676174696f6e206e6f6465732e0a3b20536f757263652d6f776e65642066756e6374696f6e7320757365207468652073616d65207075726520626f647920616c67656272612c207769746820746865697220726573756c7420747970650a3b2061757468656e746963617465642062792074686520656e636c6f73696e67206d6f64756c652e20416e20656d7074792066756e6374696f6e207461626c65206973206f6d69747465642e0a636f72652d736f757263652d66756e6374696f6e203d207b0a2020706172616d733a205b2a206c6f63616c2d7265665d2c0a202072657475726e547970653a20636f72652d747970652d7265662c0a2020626f64793a20636f72652d707572652d626c6f636b2c0a7d0a0a636f72652d666e2d626f6479203d207b0a2020706172616d733a205b2a206c6f63616c2d7265665d2c0a2020626f64793a20636f72652d707572652d626c6f636b2c0a7d0a0a636f72652d707572652d626c6f636b203d207b0a20206c6f63616c733a205b2a206c6f63616c2d7265665d2c0a202062696e64696e67733a205b2a20707572652d6c65742d6e6f64655d2c0a2020726573756c743a20636f72652d657870722c0a7d0a0a707572652d6c65742d6e6f6465203d207b0a20206b696e643a20226c6574222c0a202062696e64696e673a206c6f63616c2d7265662c0a202076616c75653a20636f72652d657870722c0a7d0a0a3b202d2d2d2065787072657373696f6e7320616e642070726564696361746573202d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d0a0a636f72652d65787072203d206c6f63616c2d65787072202f20636f6e73742d65787072202f207265636f72642d65787072202f206669656c642d65787072202f0a20202020202020202020202076617269616e742d65787072202f206d617463682d65787072202f2063616c6c2d65787072202f206c6973742d65787072202f206d61702d65787072202f0a20202020202020202020202069662d657870720a0a6c6f63616c2d65787072203d207b206b696e643a20226c6f63616c222c207265663a206c6f63616c2d726566207d0a636f6e73742d65787072203d207b206b696e643a2022636f6e7374222c2076616c75653a20636f72652d76616c7565207d0a7265636f72642d65787072203d207b206b696e643a20227265636f7264222c206669656c64733a207b202a2074737472203d3e20636f72652d65787072207d207d0a6669656c642d65787072203d207b206b696e643a20226669656c64222c20626173653a20636f72652d657870722c206669656c643a2074737472207d0a76617269616e742d65787072203d207b0a20206b696e643a202276617269616e74222c0a2020747970653a20636f72652d747970652d7265662c0a2020636173653a20747374722c0a20203f207061796c6f61643a20636f72652d657870722c0a7d0a6d617463682d65787072203d207b0a20206b696e643a20226d61746368222c0a20207363727574696e65653a20636f72652d657870722c0a202061726d733a205b2b206d617463682d61726d5d2c0a7d0a6d617463682d61726d203d207b0a2020636173653a20747374722c0a20203f2062696e6465723a206c6f63616c2d7265662c0a2020626f64793a20636f72652d657870722c0a7d0a63616c6c2d65787072203d207b0a20206b696e643a202263616c6c222c0a202063616c6c65653a20747374722c0a202074797065417267733a205b2a20636f72652d747970652d7265665d2c0a2020617267733a205b2a20636f72652d657870725d2c0a7d0a6c6973742d65787072203d207b206b696e643a20226c697374222c2076616c7565733a205b2a20636f72652d657870725d207d0a6d61702d65787072203d207b206b696e643a20226d6170222c20656e74726965733a205b2a205b6b65793a20636f72652d657870722c2076616c75653a20636f72652d657870725d5d207d0a69662d65787072203d207b0a20206b696e643a20226966222c0a20207072656469636174653a20636f72652d7072656469636174652c0a20207468656e3a20636f72652d657870722c0a2020656c73653a20636f72652d657870722c0a7d0a0a636f72652d707265646963617465203d20747275652d707265646963617465202f2066616c73652d707265646963617465202f206e6f742d707265646963617465202f0a2020202020202020202020202020202020616c6c2d707265646963617465202f20616e792d707265646963617465202f20636f6d706172652d707265646963617465202f0a202020202020202020202020202020202063616c6c2d707265646963617465202f206f62737472756374696f6e2d7072656469636174650a0a747275652d707265646963617465203d207b206b696e643a20227472756522207d0a66616c73652d707265646963617465203d207b206b696e643a202266616c736522207d0a6e6f742d707265646963617465203d207b206b696e643a20226e6f74222c2076616c75653a20636f72652d707265646963617465207d0a616c6c2d707265646963617465203d207b206b696e643a2022616c6c222c2076616c7565733a205b2b20636f72652d7072656469636174655d207d0a616e792d707265646963617465203d207b206b696e643a2022616e79222c2076616c7565733a205b2b20636f72652d7072656469636174655d207d0a636f6d706172652d707265646963617465203d207b0a20206b696e643a2022636f6d70617265222c0a20206f703a20223d3d22202f2022213d22202f20223c22202f20223c3d22202f20223e22202f20223e3d222c0a20206c6566743a20636f72652d657870722c0a202072696768743a20636f72652d657870722c0a7d0a63616c6c2d707265646963617465203d207b0a20206b696e643a202263616c6c222c0a20207072656469636174653a20747374722c0a2020617267733a205b2a20636f72652d657870725d2c0a7d0a6f62737472756374696f6e2d707265646963617465203d207b0a20206b696e643a20226f62737472756374696f6e222c0a2020636f6f7264696e6174653a206661696c7572652d6964656e742c0a20207061796c6f61643a20636f72652d657870722c0a7d0a0a696e7075742d636f6e73747261696e74203d207b0a2020636f6f7264696e6174653a20747374722c0a2020736f757263653a2022776865726522202f2022636f6d70696c6572222c0a20207072656469636174653a20636f72652d7072656469636174652c0a7d0a0a3b202d2d2d20696e74656e74732c20626c6f636b732c20616e64206e6f646573202d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d0a0a636f72652d696e74656e74203d207b0a2020696e7075743a20636f72652d747970652d7265662c0a20206f75747075743a20636f72652d747970652d7265662c0a202072657175697265644f7065726174696f6e50726f66696c653a20747374722c0a20203f2062617369733a20636f72652d657870722c0a2020696e707574436f6e73747261696e74733a205b2a20696e7075742d636f6e73747261696e745d2c0a2020636f72654576616c756174696f6e4275646765743a20636f72652d6275646765742c0a2020626f64793a20636f72652d626c6f636b2c0a20203f206f707469633a20636f72652d6f707469632c0a7d0a0a636f72652d627564676574203d207b0a20206d617853746570733a2075696e742c0a20206d6178416c6c6f636174656442797465733a2075696e742c0a20206d61784f757470757442797465733a2075696e742c0a7d0a0a636f72652d6f70746963203d207b0a20206f707469634b696e643a2022726576656c6174696f6e22202f20226166666563745265696e746567726174696f6e222c0a2020626f756e646172794b696e643a202270726f6a656374696f6e22202f2022616666656374222c0a20206170657274757265526571756972656d656e743a2061706572747572652d726571756972656d656e742c0a2020737570706f7274506f6c6963793a20747374722c0a20206c6f7373446973706f736974696f6e3a20747374722c0a7d0a0a636f72652d626c6f636b203d207b0a20206c6f63616c733a205b2a206c6f63616c2d7265665d2c0a20206e6f6465733a205b2a20636f72652d6e6f64655d2c0a2020726573756c743a20636f72652d657870722c0a7d0a0a636f72652d6e6f6465203d206c65742d6e6f6465202f20726571756972652d6e6f6465202f206566666563742d6e6f6465202f0a20202020202020202020202065787465726e616c2d616374696f6e2d726571756573742d6e6f6465202f2067756172642d6e6f6465202f206272616e63682d6e6f6465202f0a202020202020202020202020666f722d6e6f6465202f206d617463682d6e6f6465202f2070726f6f662d6f626c69676174696f6e2d6e6f64650a0a6c65742d6e6f6465203d207b0a20206b696e643a20226c6574222c0a202062696e64696e673a206c6f63616c2d7265662c0a202076616c75653a20636f72652d657870722c0a7d0a726571756972652d6e6f6465203d207b0a20206b696e643a202272657175697265222c0a20207072656469636174653a20636f72652d7072656469636174652c0a20206f6e4661696c7572653a20726571756972652d6661696c7572652d61726d2c0a7d0a726571756972652d6661696c7572652d61726d203d207465726d696e616c2d726571756972652d6661696c757265202f0a20202020202020202020202020202020202020202020636f6e74696e75652d6f6273747275637465642d726571756972652d6661696c7572650a7465726d696e616c2d726571756972652d6661696c757265203d207b0a20206b696e643a20227465726d696e616c222c0a2020726561736f6e3a206f62737472756374696f6e2d726561736f6e2c0a7d0a636f6e74696e75652d6f6273747275637465642d726571756972652d6661696c757265203d207b0a20206b696e643a2022636f6e74696e75654f627374727563746564222c0a2020726561736f6e3a206f62737472756374696f6e2d726561736f6e2c0a7d0a6f62737472756374696f6e2d726561736f6e203d207b0a2020726561736f6e4b696e643a20747374722c0a20207061796c6f61643a207b202a2074737472203d3e20636f72652d65787072207d2c0a7d0a6566666563742d6e6f6465203d207b0a20206b696e643a2022656666656374222c0a202062696e64696e673a206c6f63616c2d7265662c0a20206566666563743a20747374722c0a2020696e7075743a20636f72652d657870722c0a20206f62737472756374696f6e4d61703a207b202a206661696c7572652d6964656e74203d3e206f62737472756374696f6e2d61726d207d2c0a7d0a65787465726e616c2d616374696f6e2d726571756573742d6e6f6465203d207b0a20206b696e643a202265787465726e616c416374696f6e52657175657374222c0a202062696e64696e673a206c6f63616c2d7265662c0a20206f7065726174696f6e3a207265736f757263652d7265662c0a2020696e707574547970653a20636f72652d747970652d7265662c0a2020736574746c656d656e74547970653a20636f72652d747970652d7265662c0a2020696e707574536368656d613a207265736f757263652d7265662c0a2020736574746c656d656e74536368656d613a207265736f757263652d7265662c0a2020696e7075743a20636f72652d657870722c0a2020617574686f7269747953636f70653a20636f72652d657870722c0a202062617369733a20636f72652d657870722c0a20206275646765743a2065787465726e616c2d616374696f6e2d6275646765742c0a20207265636f6e63696c696174696f6e4c61773a207265736f757263652d7265662c0a202073746174653a20226177616974696e67536574746c656d656e74222c0a2020736574746c656d656e7441646d697373696f6e3a2022736368656d615265717569726564222c0a7d0a65787465726e616c2d616374696f6e2d627564676574203d207b0a20206d6178536574746c656d656e7442797465733a20636f72652d657870722c0a20206d6178417474656d7074733a20636f72652d657870722c0a7d0a6f62737472756374696f6e2d61726d203d207b0a202062696e6465723a206c6f63616c2d7265662c0a202076616c75653a20636f72652d657870722c0a7d0a67756172642d6e6f6465203d207b0a20206b696e643a20226775617264222c0a20207072656469636174653a20636f72652d7072656469636174652c0a20206f62737472756374696f6e3a20636f72652d657870722c0a7d0a6272616e63682d6e6f6465203d207b0a20206b696e643a20226272616e6368222c0a20203f2062696e64696e673a206c6f63616c2d7265662c0a20207072656469636174653a20636f72652d7072656469636174652c0a20207468656e3a20636f72652d626c6f636b2c0a2020656c73653a20636f72652d626c6f636b2c0a7d0a666f722d6e6f6465203d207b0a20206b696e643a2022666f72222c0a202062696e6465723a206c6f63616c2d7265662c0a2020697465723a20636f72652d657870722c0a2020626f756e643a20636f72652d626f756e642c0a2020626f64793a20636f72652d626c6f636b2c0a7d0a6d617463682d6e6f6465203d207b0a20206b696e643a20226d61746368222c0a20207363727574696e65653a20636f72652d657870722c0a202061726d733a205b2b206d617463682d626c6f636b2d61726d5d2c0a7d0a6d617463682d626c6f636b2d61726d203d207b0a2020636173653a20747374722c0a20203f2062696e6465723a206c6f63616c2d7265662c0a2020626f64793a20636f72652d626c6f636b2c0a7d0a70726f6f662d6f626c69676174696f6e2d6e6f6465203d207b0a20206b696e643a202270726f6f66222c0a2020636f6f7264696e6174653a20747374722c0a20207072656469636174653a20636f72652d7072656469636174652c0a7d0a0a636f72652d626f756e64203d206c69746572616c2d626f756e64202f20636f6f7264696e6174652d626f756e640a6c69746572616c2d626f756e64203d207b206b696e643a20226c69746572616c222c2076616c75653a2075696e74207d0a636f6f7264696e6174652d626f756e64203d207b206b696e643a2022636f6f7264696e617465222c207265663a2074737472207d0a0a3b20536861726564207265736f757263652d7265662c207368613235362d6469676573742c206661696c7572652d6964656e742c2061706572747572652d726571756972656d656e742c0a3b20636f72652d747970652d7265662c20616e6420636f72652d6e616d65642d747970652d7265662061726520646566696e6564206f6e636520696e2065646963742d636f6d6d6f6e2e6364646c2e0a0a3b202d2d2d2065646963742d6c61777061636b2e6364646c202d2d2d0a3b2065646963742d6c61777061636b2e6364646c0a3b2043616e6f6e6963616c20736368656d6120666f7220746865204564696374206c61777061636b206d616e696665737420616e64206578706f727420737572666163652e0a3b20417574686f7269746174697665206279746520656e636f64696e673a2065646963742e63616e6f6e6963616c2d63626f722f76312e204a534f4e20696e207468652070726f73652073706563730a3b2069732061207265766965772072656e646572696e672067656e6572617465642066726f6d207468697320736368656d61202845444943542d4142492d4e4f4455502d303031292e0a0a6c61777061636b2d6d616e6966657374203d207b0a202061706956657273696f6e3a202265646963742e6c61777061636b2f7631222c0a202069643a20747374722c0a202076657273696f6e3a20747374722c0a20206163636570746564436f72654162693a205b2b20747374725d2c0a2020646570656e64656e636965733a205b2a206c61777061636b2d6465705d2c202020202020202020203b20616379636c69632c206469676573742d6c6f636b6564202845444943542d4c41575041434b2d4441472d303031290a20206578706f7274733a207265736f757263652d7265662c0a20203f2074617267657441646170746572733a205b2b207461726765742d616461707465725d2c2020203b207265717569726564206f6e6c7920696620616e792072756e74696d6520656666656374206578697374730a20203f2068656c706572436f6d706f6e656e743a2065786563757461626c652d636f6d706f6e656e742c203b2065786563757461626c652068656c70657273206361727279207468656972206f776e2073616e64626f782b6675656c0a202076657269666965723a2076657269666965722c202020202020202020202020202020202020202020203b20636c61737369666965643a206465636c61726174697665206f722065786563757461626c650a2020636f6d7061746962696c6974793a207265736f757263652d7265662c0a2020636f6e666f726d616e636546697874757265436f727075733a207265736f757263652d7265662c0a7d0a0a3b2041207665726966696572206973206569746865722061206465636c617261746976652072756c6573657420286e6f2072756e74696d6529206f7220616e2065786563757461626c650a3b20636f6d706f6e656e742e20416e2065786563757461626c65207665726966696572204d55535420636172727920697473206f776e2073616e64626f7820616e64206675656c206d6f64656c2c0a3b20736f2074686520736368656d6120656e666f726365732074686174206e6f2065786563757461626c6520636f6d706f6e656e74206973206c65667420756e626f756e6465640a3b202845444943542d4142492d56455249464945522d424f554e442d303031292e0a7665726966696572203d206465636c617261746976652d7665726966696572202f2065786563757461626c652d76657269666965720a6465636c617261746976652d7665726966696572203d207b20636c6173733a20226465636c61726174697665222c2072756c657365743a207265736f757263652d726566207d0a65786563757461626c652d7665726966696572203d207b0a2020636c6173733a202265786563757461626c65222c0a2020636f6d706f6e656e743a207265736f757263652d7265662c0a202073616e64626f783a207265736f757263652d7265662c0a20206675656c4d6f64656c3a207265736f757263652d7265662c0a7d0a0a3b20416e792065786563757461626c6520636f6d706f6e656e7420697320626f756e64656420627920697473206f776e2073616e64626f78202b206675656c206d6f64656c2e0a65786563757461626c652d636f6d706f6e656e74203d207b0a2020636f6d706f6e656e743a207265736f757263652d7265662c0a202073616e64626f783a207265736f757263652d7265662c0a20206675656c4d6f64656c3a207265736f757263652d7265662c0a7d0a0a6c61777061636b2d646570203d207b2069643a20747374722c2076657273696f6e3a20747374722c206469676573743a207368613235362d646967657374207d0a0a3b20416461707465722073656c656374696f6e206b65797320534f4c454c59206f666620746865206469676573742d6c6f636b65642060616363657074656454617267657450726f66696c65600a3b20286974732060696460206973207468652070726f66696c652069643b206974732060646967657374602070696e73207468652065786163742070726f66696c652f76657273696f6e292e2054686572650a3b20617265206e6f20696e646570656e64656e7420646973706c617920737472696e6773207468617420636f756c64206469736167726565207769746820746865206c6f636b2c20736f20610a3b207265736f6c7665722063616e6e6f742062696e6420616e206164617074657220746f206f6e6520746172676574207768696c6520746865206c6f636b2070726f76657320616e6f746865720a3b202845444943542d4c41575041434b2d414441505445522d54415247455449522d303031292e0a7461726765742d61646170746572203d207b0a2020616363657074656454617267657450726f66696c653a207265736f757263652d7265662c202020203b206469676573742d6c6f636b65642c20617574686f72697461746976652073656c6563746f720a2020616363657074656454617267657449723a207265736f757263652d7265662c2020202020202020203b206469676573742d6c6f636b65640a2020616461707465723a207265736f757263652d7265662c0a7d0a0a3b20536861726564207479706573207265736f757263652d72656620616e64207368613235362d6469676573742061726520646566696e6564206f6e636520696e0a3b2065646963742d636f6d6d6f6e2e6364646c20616e6420617373656d626c65642077697468207468697320736368656d61202845444943542d4142492d4e4f4455502d303031292e0a0a3b202d2d2d206578706f72742073757266616365202d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d0a0a6c61777061636b2d6578706f727473203d207b0a202074797065733a205b2a206578706f727465642d747970655d2c0a2020636f6e7374616e74733a205b2a206578706f727465642d636f6e7374616e745d2c0a20207075726546756e6374696f6e733a205b2a20707572652d66756e6374696f6e5d2c0a2020656666656374733a205b2a2073656d616e7469632d6566666563745d2c0a20206f62737472756374696f6e733a205b2a206f62737472756374696f6e2d6465665d2c0a20203b206b65796564206279206f7065726174696f6e2d70726f66696c6520636f6f7264696e61746520e2869220756e697175656e65737320656e666f726365640a20203b202845444943542d4142492d4f5050524f46494c452d554e495155452d303031290a20203b206f7065726174696f6e2d70726f66696c65207265636f7264732074686973206c61777061636b206578706f72747320286f707469632074656d706c6174657320746861740a20203b2060696d706c656d656e7473602f6070726f66696c656020636c6175736573207265736f6c766520616761696e7374292e206f7065726174696f6e2d70726f66696c652069730a20203b20646566696e656420696e2065646963742d636f6d6d6f6e2e6364646c202845444943542d4142492d4f5050524f46494c452d534c4f542d303031292e0a20206f7065726174696f6e50726f66696c65733a207b202a2074737472203d3e206f7065726174696f6e2d70726f66696c65207d2c20203b206b6579656420627920636f6f7264696e6174650a7d0a0a6578706f727465642d7479706520202020203d207b20636f6f7264696e6174653a20747374722c20646566696e6974696f6e3a20636f72652d747970652d726566207d0a6578706f727465642d636f6e7374616e74203d207b20636f6f7264696e6174653a20747374722c20747970653a20636f72652d747970652d7265662c2076616c75653a20616e79207d0a0a3b204120707572652068656c7065722069732061206469736372696d696e6174656420756e696f6e2062792060736f75726365602c20736f2074686520736368656d6120697473656c660a3b2067756172616e7465657320616e20696d706c656d656e746174696f6e20657869737473202845444943542d4c41575041434b2d505552452d494d504c2d303031293a0a3b2020202d20226564696374223a20617574686f72656420696e2045646963742f436f72653b2074686520436f726520626f6479206973206361727269656420696e6c696e6520286861736865640a3b20202020207769746820746865206578706f72742073757266616365292e2054686520736368656d61207265717569726573207468652060626f647960206669656c642e0a3b2020202d2022636f6d706f6e656e74223a20696d706c656d656e746564206f7574736964652045646963743b2063617272696573206e6f20696e6c696e6520626f647920616e6420696e73746561640a3b20202020206361727269657320697473206f776e206469676573742d6c6f636b65642060696d706c656d656e746174696f6e60202873616e64626f78202b206675656c292e20497420646f65730a3b20202020206e6f7420646570656e64206f6e20746865206f7074696f6e616c206d616e69666573742d6c6576656c2068656c706572436f6d706f6e656e742e0a707572652d66756e6374696f6e203d2065646963742d707572652d66756e6374696f6e202f20636f6d706f6e656e742d707572652d66756e6374696f6e0a0a707572652d66756e6374696f6e2d636f6d6d6f6e203d20280a2020636f6f7264696e6174653a20747374722c0a202074797065506172616d65746572733a205b2a20747374725d2c0a2020706172616d6574657254797065733a205b2a20636f72652d747970652d7265665d2c2020202020203b20616c6c20626f756e6465640a202072657475726e547970653a20636f72652d747970652d7265662c20202020202020202020202020203b20626f756e6465640a2020636f737454656d706c6174653a20747374722c0a202064657465726d696e69736d436c6173733a2022746f74616c22202f2022746f74616c2d776974682d74797065642d646961676e6f73746963222c0a290a0a65646963742d707572652d66756e6374696f6e203d207b0a2020707572652d66756e6374696f6e2d636f6d6d6f6e2c0a2020736f757263653a20226564696374222c0a2020626f64793a20636f72652d666e2d626f64792c2020202020202020202020202020202020202020203b20696e6c696e652c20686173682d7369676e69666963616e740a7d0a0a636f6d706f6e656e742d707572652d66756e6374696f6e203d207b0a2020707572652d66756e6374696f6e2d636f6d6d6f6e2c0a2020736f757263653a2022636f6d706f6e656e74222c0a20203b20746865206469676573742d6c6f636b656420636f6d706f6e656e7420696d706c656d656e74696e6720746869732068656c7065722e2052657175697265642061742074686520736368656d610a20203b206c6576656c20736f206120636f6d706f6e656e742068656c7065722063616e206e657665722076616c696461746520776974686f7574206120686173682d626f756e642c0a20203b2073616e64626f782b6675656c2d64657363726962656420696d706c656d656e746174696f6e202845444943542d4c41575041434b2d505552452d494d504c2d303031292e0a2020696d706c656d656e746174696f6e3a2065786563757461626c652d636f6d706f6e656e742c0a7d0a0a3b20636f72652d666e2d626f647920697320646566696e65642062792065646963742d636f72652e6364646c20616e6420617373656d626c656420776974682074686973206c61777061636b0a3b20736368656d612e2049742069732061207075726520436f72652066756e6374696f6e20626f64792c206e6f7420616e206566666563742d63617061626c6520636f72652d626c6f636b2e0a0a73656d616e7469632d656666656374203d207b0a2020636f6f7264696e6174653a20747374722c0a202074797065506172616d65746572733a205b2a20747374725d2c0a2020696e707574547970653a20636f72652d747970652d7265662c2020202020202020202020202020203b20626f756e6465640a20206f7574707574547970653a20636f72652d747970652d7265662c20202020202020202020202020203b20626f756e6465640a2020657865637574696f6e436c6173733a202270726f6f664f6e6c7922202f202272756e74696d65222c2020203b206f7274686f676f6e616c20746f207772697465436c6173730a20206566666563744b696e6448696e743a206566666563742d6b696e642c0a2020666f6f747072696e744f626c69676174696f6e3a20747374722c0a2020636f73744f626c69676174696f6e3a20747374722c0a20206566666563744661696c757265733a207b202a206661696c7572652d6964656e74203d3e206566666563742d6661696c7572652d626f6479207d2c20203b206b6579656420627920636f6f7264696e6174653b20756e697175650a20206775617264537570706f72743a20626f6f6c2c0a7d0a0a6f62737472756374696f6e2d646566203d207b0a2020636f6f7264696e6174653a20747374722c0a2020617574686f72697479436c6173733a20617574686f726974792d636c6173732c0a20207061796c6f6164536368656d613a20636f72652d747970652d7265662c20202020202020202020203b2074797065642c20626f756e64656420286d617920626520656d707479207265636f7264290a7d0a0a3b206566666563742d6661696c7572652d626f64792c206566666563742d6b696e642c20617574686f726974792d636c6173732c20616e6420636f72652d747970652d7265662061726520646566696e65640a3b206f6e636520696e2065646963742d636f6d6d6f6e2e6364646c20616e6420617373656d626c65642077697468207468697320736368656d61202845444943542d4142492d4e4f4455502d303031292e0a0a3b202d2d2d2065646963742d6c61777061636b2d616461707465722e6364646c202d2d2d0a3b2065646963742d6c61777061636b2d616461707465722e6364646c0a3b2043616e6f6e6963616c20736368656d6120666f72206f6e6520646972656374206465636c61726174697665206c61777061636b2074617267657420616461707465722e0a3b20417574686f7269746174697665206279746520656e636f64696e673a2065646963742e63616e6f6e6963616c2d63626f722f76312e0a3b0a3b2054686520656e636c6f73696e67206c61777061636b206d616e69666573742073656c6563747320746865206578616374207461726765742070726f66696c652c207461726765742049522c0a3b20616e642061646170746572207265736f75726365206469676573742e2054686f7365206964656e74697469657320617265206e6f7420726570656174656420686572652e0a0a6c61777061636b2d61646170746572203d207b0a202061706956657273696f6e3a202265646963742e6c61777061636b2d616461707465722f7631222c0a2020636c6173733a20226465636c61726174697665222c0a20206f7065726174696f6e50726f66696c65733a207b0a202020202a2074737472203d3e206c61777061636b2d616461707465722d6f7065726174696f6e2d70726f66696c650a20207d2c0a2020656666656374496d706c656d656e746174696f6e733a207b0a202020202a2074737472203d3e206c61777061636b2d616461707465722d6566666563740a20207d2c0a2020627564676574733a207b0a202020202a2074737472203d3e206c61777061636b2d616461707465722d6275646765740a20207d2c0a7d0a0a3b204b657973206172652063616e6f6e6963616c206c61777061636b206f7065726174696f6e2d70726f66696c6520636f6f7264696e617465732e0a6c61777061636b2d616461707465722d6f7065726174696f6e2d70726f66696c65203d0a20207b0a20202020636f72653a20747374722c0a2020202073656d616e746963456666656374733a205b2b20747374725d2c0a202020203f206275646765744f626c69676174696f6e3a20747374722c0a202020203f20746172676574436f6e66696775726174696f6e3a207265736f757263652d7265662c0a20207d202f0a20207b0a20202020636f72653a20747374722c0a2020202073656d616e746963456666656374733a205b5d2c0a202020206275646765744f626c69676174696f6e3a20747374722c0a20202020746172676574436f6e66696775726174696f6e3a207265736f757263652d7265662c0a20207d0a0a3b204b657973206172652063616e6f6e6963616c206c61777061636b2073656d616e7469632d65666665637420636f6f7264696e617465732e20466f6f747072696e742c20636f73742c20616e640a3b206661696c757265206669656c6473206d7573742065786163746c792064697363686172676520746865206d61746368696e67206578706f72746564206566666563742e0a6c61777061636b2d616461707465722d656666656374203d207b0a2020746172676574496e7472696e7369633a20747374722c0a2020746172676574436f6e66696775726174696f6e3a207265736f757263652d7265662c0a20207772697465436c6173733a206c61777061636b2d616461707465722d77726974652d636c6173732c0a2020666f6f747072696e744f626c69676174696f6e3a20747374722c0a2020636f73744f626c69676174696f6e3a20747374722c0a20206661696c7572654d617070696e67733a207b202a206661696c7572652d6964656e74203d3e2074737472207d2c0a7d0a0a3b204b657973206172652063616e6f6e6963616c206578706f7274656420636f73742d6f626c69676174696f6e20636f6f7264696e617465732e0a6c61777061636b2d616461707465722d627564676574203d207b0a20206d617853746570733a2075696e742c0a20206d6178416c6c6f636174656442797465733a2075696e742c0a20206d61784f757470757442797465733a2075696e742c0a7d0a0a6c61777061636b2d616461707465722d77726974652d636c617373203d20226e6f6e6522202f20227265616422202f202263726561746522202f2022656e7375726522202f0a20202020202020202020202020202020202020202020202020202020202022617070656e6422202f20227265706c61636522202f202264656c65746522202f2022637573746f6d220a0a3b206661696c7572652d6964656e7420697320646566696e656420696e2065646963742d636f6d6d6f6e2e6364646c2e0a0a3b202d2d2d2065646963742d7461726765742d70726f66696c652e6364646c202d2d2d0a3b2065646963742d7461726765742d70726f66696c652e6364646c0a3b2043616e6f6e6963616c20736368656d6120666f7220746865204564696374207461726765742070726f66696c65206d616e69666573742e0a3b20417574686f7269746174697665206279746520656e636f64696e673a2065646963742e63616e6f6e6963616c2d63626f722f76310a3b202873656520535045435f636f6e74696e75756d2d636f6e74726163742d62756e646c652d76312e6d64292e204a534f4e20696e207468652070726f736520737065637320697320610a3b207265766965772072656e646572696e672067656e6572617465642066726f6d207468697320736368656d613b2074686973204344444c206973207468652073696e676c6520736f757263650a3b206f66207472757468202845444943542d4142492d4e4f4455502d303031292e0a0a7461726765742d70726f66696c652d6d616e6966657374203d207b0a202061706956657273696f6e3a202265646963742e7461726765742d70726f66696c652f7631222c0a202069643a20747374722c202020202020202020202020202020202020202020202020203b20652e672e20226563686f2e64706f220a202076657273696f6e3a20747374722c20202020202020202020202020202020202020203b20652e672e202231220a20206163636570746564436f72654162693a205b2b20747374725d2c20202020202020203b20652e672e205b2265646963742e636f72652f7631225d0a0a2020696e7472696e736963733a207265736f757263652d7265662c0a2020696e7472696e7369634e616d6573706163653a20747374722c0a20203b207075626c697368657320746869732070726f66696c652773206f7065726174696f6e2d70726f66696c65207265636f72647320286f707469632074656d706c6174657320746861740a20203b206070726f66696c65602f60696d706c656d656e74736020636c6175736573207265736f6c766520616761696e7374292e205265666572656e63657320616e0a20203b206f7065726174696f6e2d70726f66696c65732d646f63756d656e74202845444943542d4142492d4f5050524f46494c452d534c4f542d303031292e0a20206f7065726174696f6e50726f66696c65733a207265736f757263652d7265662c0a2020666f6f747072696e74416c67656272613a207265736f757263652d7265662c0a2020636f7374416c67656272613a207265736f757263652d7265662c0a202074617267657449723a207265736f757263652d7265662c0a20206f62737472756374696f6e5461786f6e6f6d793a207265736f757263652d7265662c0a202076657269666965723a207265736f757263652d7265662c0a20206c6f77657265723a207265736f757263652d7265662c0a202073616e64626f783a207265736f757263652d7265662c0a20206675656c4d6f64656c3a207265736f757263652d7265662c0a0a20203b206669656c647320746865206c616e67756167652073706563207265717569726573206f662065766572792070726f66696c650a202062756e646c6550726f66696c653a207265736f757263652d7265662c0a202067656e657261746564417274696661637450726f66696c65733a205b2a207265736f757263652d7265665d2c0a202063616e6f6e6963616c456e636f64696e6752756c65733a207265736f757263652d7265662c0a20203b20412070726f66696c65207468617420616363657074732074686520646972656374206465636c61726174697665206c61777061636b2d6164617074657220414249206e616d65732069740a20203b2065786163746c79206f6e63652e2050726f66696c6573207468617420646f206e6f7420636f6e73756d65206c61777061636b206164617074657273206c6561766520746869730a20203b206f7074696f6e616c20736c6f7420616273656e74206f7220656d7074792e0a20203f2061636365707465644c61777061636b416461707465724162693a205b5d202f205b2265646963742e6c61777061636b2d616461707465722f7631225d2c0a2020646961676e6f737469634162693a207265736f757263652d7265662c0a0a20203b206170706c69636174696f6e20646f637472696e650a20206170706c69636174696f6e4d6f64656c3a202261746f6d6963222c0a202072656164436f6e73697374656e63793a20226170706c69636174696f6e2d736e617073686f7422202f20747374722c0a202067756172644576616c756174696f6e3a2022707265636f6d6d69742d61746f6d696322202f20747374722c0a20206f62737472756374696f6e526f6c6c6261636b3a20226e6f2d76697369626c652d6566666563747322202f20747374722c0a20206d756c74695461726765743a20626f6f6c2c0a20203b207768657468657220746865207461726765742063616e206576616c7561746520707265636f6d6d697420706f7374636f6e646974696f6e20286067756172616e746565602920636865636b730a20203b20696e73696465207468652061746f6d6963206170706c69636174696f6e20756e6974202845444943542d5441524745542d504f5354434f4e442d303031290a2020706f7374636f6e646974696f6e537570706f72743a20626f6f6c2c0a0a202064657465726d696e6973746963457865637574696f6e3a207265736f757263652d7265662c0a2020636f6e666f726d616e636546697874757265436f727075733a207265736f757263652d7265662c0a7d0a0a3b20536861726564207479706573207265736f757263652d72656620616e64207368613235362d6469676573742061726520646566696e6564206f6e636520696e0a3b2065646963742d636f6d6d6f6e2e6364646c20616e6420617373656d626c65642077697468207468697320736368656d6120627920746865206275696c640a3b202845444943542d4142492d4e4f4455502d303031292e205468657920617265206e6f74207265646566696e656420686572652e0a0a3b202d2d2d20696e7472696e736963207369676e6174757265202d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d0a3b20546865206172746966616374207265666572656e63656420627920746865206d616e696665737427732060696e7472696e7369637360207265736f757263652d726566206973207468650a3b20696e7472696e7369632d7369676e617475726520636f7270757320646f63756d656e742062656c6f772e20497473206c61796f757420697320666978656420736f2074776f0a3b20696e646570656e64656e742070726f66696c65732076616c69646174652f686173682074686520636f72707573206964656e746963616c6c790a3b202845444943542d4142492d494e5452494e534943532d444f432d303031292e0a0a3b20696e7472696e736963732069732061204d4150206b6579656420627920636f6f7264696e6174652c20736f2074686520736368656d6120697473656c6620656e666f726365730a3b20636f6f7264696e61746520756e697175656e6573732e20412070726f766964657220726563656976657320746865207265736f6c76656420636f7270757320617320610a3b206469676573742d626f756e642073656d616e74696320696e70757420616e64207265736f6c76657320636f6f7264696e617465732077697468696e20746861742061727469666163742e0a3b2045616368206d6170206b6579204d55535420657175616c20697473207265636f726427732060636f6f7264696e61746560206669656c640a3b202845444943542d4142492d494e5452494e5349432d554e495155452d303031292e0a696e7472696e736963732d646f63756d656e74203d207b0a202061706956657273696f6e3a202265646963742e7461726765742d70726f66696c652e696e7472696e736963732f7631222c0a2020696e7472696e736963733a207b202a2074737472203d3e20696e7472696e736963207d2c0a7d0a0a3b20546865206172746966616374207265666572656e63656420627920746865206d616e6966657374277320606f7065726174696f6e50726f66696c657360207265736f757263652d7265662e0a3b206f7065726174696f6e2d70726f66696c65202f206f707469632d74656d706c6174652061726520646566696e656420696e2065646963742d636f6d6d6f6e2e6364646c2e204b657965642062790a3b20636f6f7264696e61746520736f207265736f6c7574696f6e2063616e2774207069636b206265747765656e2074776f2073616d652d636f6f7264696e6174652070726f66696c65730a3b202845444943542d4142492d4f5050524f46494c452d534c4f542d3030312c2045444943542d4142492d4f5050524f46494c452d554e495155452d303031292e0a6f7065726174696f6e2d70726f66696c65732d646f63756d656e74203d207b0a202061706956657273696f6e3a202265646963742e7461726765742d70726f66696c652e6f7065726174696f6e2d70726f66696c65732f7631222c0a202070726f66696c65733a207b202a2074737472203d3e206f7065726174696f6e2d70726f66696c65207d2c0a7d0a0a3b2041207479706564207072652d6c6f776572696e67207175657374696f6e20746861742063616e2062652070726f706f73656420627920576174736f6e206f7220616e206167656e7420616e640a3b20636865636b65642062792074686520636f6d70696c65722e2049742069732063616e6f6e6963616c2d43424f5220656e636f64656420756e6465720a3b206065646963742e6c6f776572696e672d726571756972656d656e74732f7631603b2074686520636f6d70696c657220636865636b7320746869732061727469666163742c206e6f74207468650a3b2070726f736520746861742070726f64756365642069742e0a6c6f776572696e672d726571756972656d656e7473203d207b0a202061706956657273696f6e3a202265646963742e6c6f776572696e672d726571756972656d656e74732f7631222c0a20206f7065726174696f6e50726f66696c653a20747374722c0a202073656d616e746963456666656374733a205b2a2073656d616e7469632d6566666563742d726571756972656d656e745d2c0a202072657175697265645772697465436c61737365733a205b2a2077726974652d636c6173735d2c0a202067756172644b696e64733a205b2a2067756172642d6b696e645d2c0a202061746f6d69636974793a2061746f6d69636974792d726571756972656d656e742c0a2020706f7374636f6e646974696f6e537570706f72743a20626f6f6c2c0a20206f62737472756374696f6e436f6f7264696e617465733a205b2a20747374725d2c0a2020666f6f747072696e744f626c69676174696f6e733a205b2a20747374725d2c0a2020636f73744f626c69676174696f6e733a205b2a20747374725d2c0a20206f70746963436f6e74726163743a20747374722c0a7d0a0a73656d616e7469632d6566666563742d726571756972656d656e74203d207b0a2020636f6f7264696e6174653a20747374722c0a20207772697465436c6173733a2077726974652d636c6173732c0a202067756172644b696e64733a205b2a2067756172642d6b696e645d2c0a20206f62737472756374696f6e436f6f7264696e617465733a205b2a20747374725d2c0a2020666f6f747072696e744f626c69676174696f6e733a205b2a20747374725d2c0a2020636f73744f626c69676174696f6e733a205b2a20747374725d2c0a7d0a0a77726974652d636c617373203d20226e6f6e6522202f20227265616422202f202263726561746522202f2022656e7375726522202f2022617070656e6422202f0a2020202020202020202020202020227265706c61636522202f202264656c65746522202f20747374720a67756172642d6b696e64203d2022707265636f6d6d69742d61746f6d696322202f20747374720a61746f6d69636974792d726571756972656d656e74203d202261746f6d696322202f20747374720a0a3b20412067656e75696e6520756e696f6e3a207075726520636f6e7374727563746f7273206361727279206e6f20656666656374206b696e64206f72206661696c757265733b206566666563740a3b20696e7472696e73696373206d757374202845444943542d5441524745542d494e5452494e5349432d434c4153532d303031292e2054686520736368656d6120656e666f7263657320746869732c0a3b206e6f74206120636f6d6d656e742e0a0a3b2054686520696e7472696e736963277320636f6f7264696e6174652069732074686520696e7472696e73696373206d6170204b45592c206e6f7420612076616c7565206669656c642c20736f207468650a3b206b657920616e6420636f6f7264696e6174652063616e206e65766572206469736167726565202845444943542d4142492d494e5452494e5349432d554e495155452d303031292e0a696e7472696e736963203d20707572652d696e7472696e736963202f206566666563742d696e7472696e7369630a0a707572652d696e7472696e736963203d207b0a2020696e7472696e736963436c6173733a202270757265222c0a202074797065506172616d65746572733a205b2a20747374725d2c0a2020617267756d656e7454797065733a205b2a20636f72652d747970652d7265665d2c0a202072657475726e547970653a20636f72652d747970652d7265662c0a20206775617264537570706f72743a2066616c73652c0a2020666f6f747072696e7454656d706c6174653a20747374722c0a2020636f737454656d706c6174653a20747374722c0a20207772697465436c6173733a20226e6f6e65222c0a7d0a0a6566666563742d696e7472696e736963203d207b0a2020696e7472696e736963436c6173733a2022656666656374222c0a202074797065506172616d65746572733a205b2a20747374725d2c0a2020617267756d656e7454797065733a205b2a20636f72652d747970652d7265665d2c0a202072657475726e547970653a20636f72652d747970652d7265662c0a20206566666563744b696e643a206566666563742d6b696e642c0a20203b206d6170206b65796564206279206661696c75726520636f6f7264696e61746520286661696c7572652d6964656e74293b20746865206661696c75726520636f6f7264696e6174652069730a20203b20746865206b65792c206e6f7420612076616c7565206669656c642c20736f20756e697175656e657373206973207374727563747572616c0a20203b202845444943542d4142492d4641494c5552452d554e495155452d303031292e0a20206566666563744661696c757265733a207b202a206661696c7572652d6964656e74203d3e206566666563742d6661696c7572652d626f6479207d2c0a20206775617264537570706f72743a20626f6f6c2c0a2020666f6f747072696e7454656d706c6174653a20747374722c0a2020636f737454656d706c6174653a20747374722c0a20207772697465436c6173733a20227265616422202f202263726561746522202f2022656e7375726522202f2022617070656e6422202f20227265706c61636522202f0a20202020202020202020202020202264656c65746522202f2022637573746f6d222c0a202063616e5061727469636970617465496e41746f6d696347756172643a20626f6f6c2c0a7d0a0a3b206566666563742d6661696c7572652d626f64792c206566666563742d6b696e642c20617574686f726974792d636c6173732c20616e6420636f72652d747970652d7265662061726520646566696e65640a3b206f6e636520696e2065646963742d636f6d6d6f6e2e6364646c20616e6420617373656d626c65642077697468207468697320736368656d61202845444943542d4142492d4e4f4455502d303031292e0a0a3b202d2d2d2065646963742d617574686f726974792d66616374732e6364646c202d2d2d0a3b2065646963742d617574686f726974792d66616374732e6364646c0a3b2043616e6f6e6963616c20736368656d6120666f722074686520666972737420636f6d70696c65722d636f6e7465787420617574686f726974792d666163747320646f63756d656e742e0a3b20417574686f7269746174697665206279746520656e636f64696e673a2065646963742e63616e6f6e6963616c2d63626f722f76312e0a3b0a3b205468697320736368656d6120697320617373656d626c656420776974682065646963742d636f6d6d6f6e2e6364646c20736f20736f757263652e6469676573742075736573207468650a3b20736861726564207368613235362d6469676573742074797065642076616c75652e204a534f4e2069732061207265766965772f696e7075742072656e646572696e673a206974730a3b20607368613235363a3c3634206865783e6020736f75726365206469676573742069732070726f6a656374656420746f205b60736861323536602c203332207261772062797465735d206f6e0a3b2074686520776972652c20616e64206974732066616374206172726179732070726f6a65637420746f2074686520636f6f7264696e6174652d6b65796564206d6170732062656c6f772e0a0a617574686f726974792d6661637473203d207b0a202061706956657273696f6e3a202265646963742e617574686f726974792d66616374732f7631222c0a2020736f757263653a20617574686f726974792d666163742d736f757263652c0a20206f7065726174696f6e50726f66696c65733a207b202a2074737472203d3e20617574686f726974792d6f7065726174696f6e2d70726f66696c652d66616374207d2c0a20206566666563745772697465436c61737365733a207b202a2074737472203d3e20617574686f726974792d77726974652d636c617373207d2c0a2020627564676574733a207b202a2074737472203d3e20617574686f726974792d6275646765742d66616374207d2c0a7d0a0a617574686f726974792d666163742d736f75726365203d207b0a20206b696e643a20226c61777061636b22202f202274617267657450726f66696c65222c0a2020636f6f7264696e6174653a20747374722c0a20206469676573743a207368613235362d6469676573742c0a7d0a0a3b20546865206d6170206b65792069732074686520736f75726365206f7065726174696f6e2d70726f66696c6520636f6f7264696e6174652e204974206973206e6f7420726570656174656420696e0a3b207468652076616c75652c20736f2061206b657920616e6420656d62656464656420636f6f7264696e6174652063616e6e6f742064697361677265652e20416c6c6f7765642077726974650a3b20636c61737365732061726520612063616e6f6e6963616c206d61702d7365743a2074686520636c6173732069732074686520756e69717565206b657920616e64206e756c6c206973207468650a3b20756e6974206d61726b65722e2043616e6f6e6963616c2043424f52206669786573206b6579206f7264657220776974686f75742061207365636f6e64206f72646572696e672072756c652e0a617574686f726974792d6f7065726174696f6e2d70726f66696c652d66616374203d207b0a2020636f72653a20747374722c0a2020616c6c6f7765645772697465436c61737365733a207b202a20617574686f726974792d77726974652d636c617373203d3e206e756c6c207d2c0a7d0a0a3b20546865206566666563745772697465436c6173736573206d6170206b6579206973207468652073656d616e7469632065666665637420636f6f7264696e6174652e2054686520627564676574730a3b206d6170206b65792069732074686520736f757263652062756467657420636f6f7264696e6174652e2043616e6f6e6963616c2043424f52206d61702d6b657920756e697175656e6573730a3b206d616b6573206475706c6963617465206661637420636f6f7264696e61746573207374727563747572616c6c7920756e726570726573656e7461626c652e0a617574686f726974792d6275646765742d66616374203d207b0a20206d617853746570733a2075696e742c0a20206d6178416c6c6f636174656442797465733a2075696e742c0a20206d61784f757470757442797465733a2075696e742c0a7d0a0a3b20417574686f726974794661637473446f63756d656e7420763120696e74656e74696f6e616c6c792061636365707473206f6e6c792074686520777269746520636c6173736573207468650a3b2063757272656e7420636f6d70696c6572206d6f64656c2063616e20636f6e73756d652e2060637573746f6d602069732074686520736f6c6520763120637573746f6d207370656c6c696e673b0a3b20617262697472617279207461726765742d70726f66696c6520657874656e73696f6e20737472696e677320646f206e6f7420656e746572207468697320636f6d70696c657220706174682e0a617574686f726974792d77726974652d636c617373203d20226e6f6e6522202f20227265616422202f202263726561746522202f2022656e7375726522202f2022617070656e6422202f0a202020202020202020202020202020202020202020202020227265706c61636522202f202264656c65746522202f2022637573746f6d220a0a3b202d2d2d2065646963742d726573756c742d70726f6a656374696f6e2e6364646c202d2d2d0a3b20535044582d4c6963656e73652d4964656e7469666965723a204170616368652d322e300a3b2065646963742d726573756c742d70726f6a656374696f6e2e6364646c0a3b2043616e6f6e6963616c20736368656d6120666f7220636f6d70696c65722d6f776e6564206170706c69636174696f6e2d726573756c742070726f6a656374696f6e732e0a3b20417574686f7269746174697665206279746520656e636f64696e673a2065646963742e63616e6f6e6963616c2d63626f722f76312e0a0a726573756c742d70726f6a656374696f6e203d207b0a2020736368656d613a202265646963742e726573756c742d70726f6a656374696f6e2f7631222c0a20206f7065726174696f6e436f6f7264696e6174653a20626f756e6465642d70726f6a656374696f6e2d746578742c0a20206f7574707574547970653a20626f756e6465642d70726f6a656374696f6e2d746578742c0a20206d61784f757470757442797465733a2075696e74202e677420302c0a202065787072657373696f6e3a20726573756c742d70726f6a656374696f6e2d657870722c0a7d0a0a726573756c742d70726f6a656374696f6e2d65787072203d20726573756c742d70726f6a656374696f6e2d7265636f7264202f20726573756c742d70726f6a656374696f6e2d736f757263650a0a726573756c742d70726f6a656374696f6e2d7265636f7264203d207b0a20206b696e643a20227265636f7264222c0a20203b2054686520726f6f74207265636f726420636f756e7473206173206f6e65206f66207468652052757374206465636f6465722773203235362065787072657373696f6e206e6f6465732e0a20203b204e657374656420616767726567617465206e6f646520636f756e742072656d61696e7320616e20617574686f7269746174697665206465636f64657220636865636b2e0a20206669656c64733a207b20302a32353520626f756e6465642d70726f6a656374696f6e2d74657874203d3e20726573756c742d70726f6a656374696f6e2d65787072207d2c0a7d0a0a726573756c742d70726f6a656374696f6e2d736f75726365203d207b0a20206b696e643a2022736f75726365222c0a2020736f757263653a20726573756c742d70726f6a656374696f6e2d736f757263652d6b696e642c0a20203b204d617463686573204d41585f524553554c545f50524f4a454354494f4e5f504154485f5345474d454e545320696e2065646963742d73796e7461782e0a2020706174683a205b302a333220626f756e6465642d70726f6a656374696f6e2d746578745d2c0a7d0a0a726573756c742d70726f6a656374696f6e2d736f757263652d6b696e64203d0a20207b206b696e643a20226170706c69636174696f6e496e70757422207d202f0a20207b0a202020206b696e643a20227075726542696e64696e67222c0a2020202062696e64696e6749643a20626f756e6465642d70726f6a656374696f6e2d746578742c0a20207d202f0a20207b0a202020206b696e643a20226361706162696c697479526573756c74222c0a202020207374657049643a20626f756e6465642d70726f6a656374696f6e2d746578742c0a20207d0a0a626f756e6465642d70726f6a656374696f6e2d74657874203d2074737472202e73697a652028312e2e31303234290a0a3b202d2d2d2065646963742d7461726765742d69722e6364646c202d2d2d0a3b20535044582d4c6963656e73652d4964656e7469666965723a204170616368652d322e300a3b2065646963742d7461726765742d69722e6364646c0a3b2043616e6f6e6963616c20736368656d6120666f72207468652045646963742d6f776e65642054617267657420495220617274696661637420656e76656c6f70652e0a3b20417574686f7269746174697665206279746520656e636f64696e673a2065646963742e63616e6f6e6963616c2d63626f722f76312e0a3b0a3b205468697320736368656d6120697320617373656d626c656420776974682065646963742d636f6d6d6f6e2e6364646c20616e642065646963742d636f72652e6364646c2e2049740a3b2064656c696265726174656c792072657573657320436f72652065787072657373696f6e732c20707265646963617465732c20627564676574732c206c6f63616c207265666572656e6365732c0a3b206f62737472756374696f6e20726561736f6e732c20616e64206f62737472756374696f6e2061726d7320736f2074686520736368656d61206d617463686573207468652076616c75650a3b20656d6974746564206279207468652063616e6f6e6963616c2054617267657420495220656e636f64657220726174686572207468616e20726573746174696e672074686f73652074797065732e0a3b2049742064657363726962657320746865207374727563747572616c207368617065206f662076616c6964206c6f776572696e672d70726f6475636564206172746966616374732e205468650a3b206c6f776572696e6720616e6420656e636f64657220636f6e7472616374732073657061726174656c7920656e666f7263652073656d616e746963206964656e7469666965722072756c65730a3b20616e642063616e6f6e6963616c206f72646572696e672f64656475706c69636174696f6e20666f72207365742d6c696b652076616c7565732e0a0a3b2054617267657420495220656e636f64696e672072656a6563747320616e20656d707479207461726765742d70726f66696c6520636f6f7264696e617465206265666f72652062797465730a3b2065786973742c20736f207468697320726f6f74207469676874656e732074686520736861726564207374727563747572616c207265736f757263652d726566206163636f7264696e676c792e0a7461726765742d69722d7265736f757263652d726566203d207b0a202069643a2074737472202e7265676578702022283f73292e2b222c0a20206469676573743a207368613235362d6469676573742c0a7d0a0a7461726765742d69722d6172746966616374203d207461726765742d69722d7068617365642d6172746966616374202f207461726765742d69722d6f7264657265642d61727469666163740a0a7461726765742d69722d7068617365642d6172746966616374203d207461726765742d69722d636c6f7365642d6172746966616374202f207461726765742d69722d6c65676163792d61727469666163740a0a7461726765742d69722d61727469666163742d636f6d6d6f6e203d20280a2020646f6d61696e3a20747374722c0a202074617267657450726f66696c653a207461726765742d69722d7265736f757263652d7265662c0a2020736f75726365436f7265436f6f7264696e6174653a2074737472202e7265676578702022283f73292e2b222c0a290a0a3b2076322062696e6473206f6e6520657865637574696f6e206f72646572206f7665722074686520696e737472756374696f6e207461626c65732e2054686520656e636f64657220616c736f0a3b2076616c69646174657320746865206578616374207065726d75746174696f6e20616e642070726f64756365722d6265666f72652d636f6e73756d657220646570656e64656e636965732e0a7461726765742d69722d6f7264657265642d6172746966616374203d207461726765742d69722d6f7264657265642d636c6f736564202f207461726765742d69722d6f7264657265642d6c65676163790a0a7461726765742d69722d6f7264657265642d636f6d6d6f6e203d20280a2020646f6d61696e3a20226563686f2e7370616e2d69722f7632222c0a202074617267657450726f66696c653a207461726765742d69722d7265736f757263652d7265662c0a2020736f75726365436f7265436f6f7264696e6174653a2074737472202e7265676578702022283f73292e2b222c0a290a0a7461726765742d69722d6f7264657265642d636c6f736564203d207b0a20206b696e643a20226f72646572656454617267657449724172746966616374222c0a20207461726765742d69722d6f7264657265642d636f6d6d6f6e2c0a202073656d616e746963436c6f737572653a207461726765742d69722d73656d616e7469632d636c6f737572652c0a2020696e74656e74733a207b202a2074737472203d3e207461726765742d69722d6f7264657265642d696e74656e74207d2c0a7d0a0a7461726765742d69722d6f7264657265642d6c6567616379203d207b0a20206b696e643a20226f72646572656454617267657449724172746966616374222c0a20207461726765742d69722d6f7264657265642d636f6d6d6f6e2c0a2020696e74656e74733a207b202a2074737472203d3e207461726765742d69722d6f7264657265642d6c65676163792d696e74656e74207d2c0a7d0a0a7461726765742d69722d6f7264657265642d696e74656e74203d207b0a20207461726765742d69722d696e74656e742d636f6d6d6f6e2c0a20203f2062617369733a20636f72652d657870722c0a20203f207075726542696e64696e67733a205b312a207461726765742d69722d707572652d62696e64696e675d2c0a2020657865637574696f6e4f726465723a205b2a2074737472202e7265676578702022283f73292e2b225d2c0a7d0a0a7461726765742d69722d6f7264657265642d6c65676163792d696e74656e74203d207b0a20207461726765742d69722d696e74656e742d636f6d6d6f6e2c0a2020657865637574696f6e4f726465723a205b2a2074737472202e7265676578702022283f73292e2b225d2c0a7d0a0a7461726765742d69722d636c6f7365642d6172746966616374203d207b0a20206b696e643a202274617267657449724172746966616374222c0a20207461726765742d69722d61727469666163742d636f6d6d6f6e2c0a202073656d616e746963436c6f737572653a207461726765742d69722d73656d616e7469632d636c6f737572652c0a2020696e74656e74733a207b202a2074737472203d3e207461726765742d69722d696e74656e74207d2c0a7d0a0a7461726765742d69722d6c65676163792d6172746966616374203d207b0a20206b696e643a202274617267657449724172746966616374222c0a20207461726765742d69722d61727469666163742d636f6d6d6f6e2c0a2020696e74656e74733a207b202a2074737472203d3e207461726765742d69722d6c65676163792d696e74656e74207d2c0a7d0a0a7461726765742d69722d73656d616e7469632d636c6f73757265203d207b0a2020736f75726365436f72653a207461726765742d69722d7265736f757263652d7265662c0a20206c61777061636b733a205b2a207461726765742d69722d7265736f757263652d7265665d2c0a20203f206361706162696c69746965733a205b2a207461726765742d69722d7265736f757263652d7265665d2c0a7d0a0a7461726765742d69722d696e74656e74203d207b0a20207461726765742d69722d696e74656e742d636f6d6d6f6e2c0a20203f2062617369733a20636f72652d657870722c0a20203f207075726542696e64696e67733a205b312a207461726765742d69722d707572652d62696e64696e675d2c0a20203f2065787465726e616c416374696f6e52657175657374733a205b2a207461726765742d69722d65787465726e616c2d616374696f6e2d726571756573745d2c0a7d0a0a7461726765742d69722d6c65676163792d696e74656e74203d207b0a20207461726765742d69722d696e74656e742d636f6d6d6f6e2c0a7d0a0a7461726765742d69722d696e74656e742d636f6d6d6f6e203d20280a20206f7065726174696f6e50726f66696c653a20747374722c0a2020696e707574436f6e73747261696e74733a205b2a20696e7075742d636f6e73747261696e745d2c0a2020636f72654576616c756174696f6e4275646765743a20636f72652d6275646765742c0a2020726571756972656d656e74733a205b2a207461726765742d69722d726571756972656d656e745d2c0a202073746570733a205b2a207461726765742d69722d737465705d2c0a2020726573756c743a20636f72652d657870722c0a290a0a7461726765742d69722d726571756972656d656e74203d207b0a202069643a20747374722c0a20207072656469636174653a20636f72652d7072656469636174652c0a20206f6e4661696c7572653a20726571756972652d6661696c7572652d61726d2c0a7d0a0a7461726765742d69722d707572652d62696e64696e67203d207b0a202069643a2074737472202e7265676578702022283f73292e2b222c0a202062696e64696e673a206c6f63616c2d7265662c0a202076616c75653a20636f72652d657870722c0a7d0a0a7461726765742d69722d73746570203d207b0a202069643a20747374722c0a202062696e64696e673a206c6f63616c2d7265662c0a20206566666563743a20747374722c0a2020746172676574496e7472696e7369633a20747374722c0a2020696e7075743a20636f72652d657870722c0a20206f62737472756374696f6e4661696c757265733a205b2a206661696c7572652d6964656e745d2c0a20206f62737472756374696f6e41726d733a207b202a206661696c7572652d6964656e74203d3e206f62737472756374696f6e2d61726d207d2c0a7d0a0a7461726765742d69722d65787465726e616c2d616374696f6e2d72657175657374203d207b0a202069643a20747374722c0a202062696e64696e673a206c6f63616c2d7265662c0a20206f7065726174696f6e3a207461726765742d69722d7265736f757263652d7265662c0a2020696e707574547970653a20636f72652d747970652d7265662c0a2020736574746c656d656e74547970653a20636f72652d747970652d7265662c0a2020696e707574536368656d613a207461726765742d69722d7265736f757263652d7265662c0a2020736574746c656d656e74536368656d613a207461726765742d69722d7265736f757263652d7265662c0a2020696e7075743a20636f72652d657870722c0a2020617574686f7269747953636f70653a20636f72652d657870722c0a202062617369733a20636f72652d657870722c0a20206275646765743a2065787465726e616c2d616374696f6e2d6275646765742c0a20207265636f6e63696c696174696f6e4c61773a207461726765742d69722d7265736f757263652d7265662c0a202073746174653a20226177616974696e67536574746c656d656e74222c0a2020736574746c656d656e7441646d697373696f6e3a2022736368656d615265717569726564222c0a7d0a", + "rawSha256": "e484eabd615584a38cb57454b52747786f2314c135c13f99da6f6bae619a709f" + }, + "contracts": [ + {"contract": "authority-facts", "rootRule": "authority-facts"}, + {"contract": "core-module", "rootRule": "core-module"}, + {"contract": "lawpack-adapter", "rootRule": "lawpack-adapter"}, + {"contract": "lawpack-exports", "rootRule": "lawpack-exports"}, + {"contract": "lawpack-manifest", "rootRule": "lawpack-manifest"}, + {"contract": "lowering-requirements", "rootRule": "lowering-requirements"}, + {"contract": "result-projection", "rootRule": "result-projection"}, + {"contract": "target-ir-artifact", "rootRule": "target-ir-artifact"}, + {"contract": "target-profile-intrinsics", "rootRule": "intrinsics-document"}, + {"contract": "target-profile-manifest", "rootRule": "target-profile-manifest"}, + {"contract": "target-profile-operation-profiles", "rootRule": "operation-profiles-document"} + ], + "domains": [ + {"domain": "edict.authority-facts/v1", "rootRule": "authority-facts"}, + {"domain": "edict.core.module/v1", "rootRule": "core-module"}, + {"domain": "edict.lawpack/v1", "rootRule": "lawpack-manifest"}, + {"domain": "edict.lowering-requirements/v1", "rootRule": "lowering-requirements"}, + {"domain": "edict.result-projection.artifact/v1", "rootRule": "result-projection"}, + {"domain": "edict.target-ir.artifact/v1", "rootRule": "target-ir-artifact"}, + {"domain": "edict.target-profile/v1", "rootRule": "target-profile-manifest"} + ], + "resources": [ + { + "coordinate": "edict.canonical-cbor/v1", + "canonicalBytesHex": "a368636f6e7472616374a96a76616c75654d6f64656c8765617272617964626f6f6c65627974657367696e7465676572636d6170646e756c6c64746578746b6469676573744672616d656f65646963742e6469676573742f76316b6d61704f72646572696e67781b63616e6f6e6963616c2d656e636f6465642d6b65792d62797465736c74657874456e636f64696e67657574662d386d747261696c696e6742797465736672656a6563746f696e7465676572456e636f64696e676873686f727465737470636f6e7461696e65724c656e6774687368646566696e697465706475706c69636174654d61704b6579736672656a656374736d6178696d756d4e657374696e67446570746818806a61706956657273696f6e782965646963742e7461726765742d70726f66696c652e636f6e74726163742d7265736f757263652f76316a636f6f7264696e6174657765646963742e63616e6f6e6963616c2d63626f722f7631", + "rawSha256": "8306e4f08c1e4e7d29ab22bcf55c324312712aac3eeeb675857ced57c3e48bdc", + "domainFramedDigest": "sha256:d1ea6d3de2a9762a438cbf4fac1d5ae2f357a4b27d13e0347e94ea655bf40f9d", + "provenance": { + "repository": "https://github.com/flyingrobots/edict", + "sourcePath": "fixtures/target-profile/contract-resources/canonical-cbor.cbor" + } + }, + { + "coordinate": "edict.determinism/v1", + "canonicalBytesHex": "a368636f6e7472616374aa6c616d6269656e74436c6f636bf46e616d6269656e744e6574776f726bf46e6f75747075744964656e74697479781e686f73742d636f6d70757465642d61667465722d76616c69646174696f6e6e72656769737472794c6f6f6b7570f471616d6269656e7446696c6573797374656df471616d6269656e7452616e646f6d6e657373f472616d6269656e74456e7669726f6e6d656e74f4756d757461626c654e616d655265736f6c7574696f6ef477667265736853746f7265506572496e766f636174696f6ef5782263616e6f6e6963616c526573756c74496e646570656e64656e744f664c696d697473f56a61706956657273696f6e782965646963742e7461726765742d70726f66696c652e636f6e74726163742d7265736f757263652f76316a636f6f7264696e6174657465646963742e64657465726d696e69736d2f7631", + "rawSha256": "84073f5c1734b625e16799048e28458d43e0a10befdae56a79d906f5e37ef76a", + "domainFramedDigest": "sha256:af4e6c774d5ea82db30e680be2ba7abc5ba04c6aead146139de32d8c5bb4981e", + "provenance": { + "repository": "https://github.com/flyingrobots/edict", + "sourcePath": "fixtures/target-profile/contract-resources/determinism.cbor" + } + }, + { + "coordinate": "edict.diagnostics/v1", + "canonicalBytesHex": "a368636f6e7472616374a7666669656c64738464636f6465687365766572697479676d65737361676566726570616972686f72646572696e67781c636f64652d73657665726974792d6d6573736167652d726570616972697265636f7264416269782f65646963743a7461726765742d70726f76696465722f70726f746f636f6c40312e302e3023646961676e6f737469636a7472756e636174696f6e69666f7262696464656e6d73657665726974794f7264657283656572726f72677761726e696e6764696e666f6e6c697374436f756e74426f756e64f57261676772656761746542797465426f756e64f56a61706956657273696f6e782965646963742e7461726765742d70726f66696c652e636f6e74726163742d7265736f757263652f76316a636f6f7264696e6174657465646963742e646961676e6f73746963732f7631", + "rawSha256": "e465a28f1170fe478db5ff65d96a1fdbfbbcb95d327e6965021c252437991e4b", + "domainFramedDigest": "sha256:28fd72a98223153982ca084c29dbb1b2d430623967ab3b6db9d7fee668e614b9", + "provenance": { + "repository": "https://github.com/flyingrobots/edict", + "sourcePath": "fixtures/target-profile/contract-resources/diagnostics.cbor" + } + }, + { + "coordinate": "edict.fuel/v1", + "canonicalBytesHex": "a368636f6e7472616374a6687265717569726564f56a6163636f756e74696e67781864657465726d696e69737469632d67756573742d776f726b6a627564676574556e6974646675656c6a65786861757374696f6e72686f73742d6f776e65642d6661696c7572656d706572496e766f636174696f6ef57165706f6368496e74657272757074696f6e736e6f742d612d7265706c61792d6275646765746a61706956657273696f6e782965646963742e7461726765742d70726f66696c652e636f6e74726163742d7265736f757263652f76316a636f6f7264696e6174656d65646963742e6675656c2f7631", + "rawSha256": "c712c2d831cc8e731bdc1dfb8ea536f4630f38f0b0e0c6448b2df57176d3d0bd", + "domainFramedDigest": "sha256:006c6ebc01a3c5d36d50bd390b69d47e42378be61074e3b5c96ecb9f5ee53207", + "provenance": { + "repository": "https://github.com/flyingrobots/edict", + "sourcePath": "fixtures/target-profile/contract-resources/fuel.cbor" + } + }, + { + "coordinate": "edict.wasm-component/v1", + "canonicalBytesHex": "a368636f6e7472616374a7647761736969666f7262696464656e6e70726f746f636f6c496d706f727469747970652d6f6e6c796f63616c6c61626c65496d706f72747369666f7262696464656e70636f6e74726163744964656e746974796565786163747070726f7669646572436f6e7472616374781b65646963743a7461726765742d70726f766964657240312e302e3077667265736853746f7265506572496e766f636174696f6ef5781b636f6d706f6e656e74446967657374566572696669636174696f6e6d6265666f72652d6465636f64656a61706956657273696f6e782965646963742e7461726765742d70726f66696c652e636f6e74726163742d7265736f757263652f76316a636f6f7264696e6174657765646963742e7761736d2d636f6d706f6e656e742f7631", + "rawSha256": "cd09d702db1d10be825e72effe35627bec56963cb3d78601b69478c58787e34d", + "domainFramedDigest": "sha256:095b4dd18f1a6a7276533f758665be319c5f476e7bdc70cc56d30b6b3e9f0a80", + "provenance": { + "repository": "https://github.com/flyingrobots/edict", + "sourcePath": "fixtures/target-profile/contract-resources/wasm-component.cbor" + } + } + ] +} diff --git a/crates/echo-wesley-gen/assets/v1/edict-provider/package/v1/components/lowerer.echo-dpo.component.wasm b/crates/echo-wesley-gen/assets/v1/edict-provider/package/v1/components/lowerer.echo-dpo.component.wasm index 903933cd7..555e4dcf6 100644 Binary files a/crates/echo-wesley-gen/assets/v1/edict-provider/package/v1/components/lowerer.echo-dpo.component.wasm and b/crates/echo-wesley-gen/assets/v1/edict-provider/package/v1/components/lowerer.echo-dpo.component.wasm differ diff --git a/crates/echo-wesley-gen/assets/v1/edict-provider/package/v1/components/verifier.echo-dpo.component.wasm b/crates/echo-wesley-gen/assets/v1/edict-provider/package/v1/components/verifier.echo-dpo.component.wasm index 14c6c1479..74fe228fd 100644 Binary files a/crates/echo-wesley-gen/assets/v1/edict-provider/package/v1/components/verifier.echo-dpo.component.wasm and b/crates/echo-wesley-gen/assets/v1/edict-provider/package/v1/components/verifier.echo-dpo.component.wasm differ diff --git a/crates/echo-wesley-gen/assets/v1/edict-provider/package/v1/generated/evidence/provenance.provider-generation.json b/crates/echo-wesley-gen/assets/v1/edict-provider/package/v1/generated/evidence/provenance.provider-generation.json index 83cdbd543..2a01bb289 100644 --- a/crates/echo-wesley-gen/assets/v1/edict-provider/package/v1/generated/evidence/provenance.provider-generation.json +++ b/crates/echo-wesley-gen/assets/v1/edict-provider/package/v1/generated/evidence/provenance.provider-generation.json @@ -1 +1 @@ -{"apiVersion":"wesley.generation-provenance-manifest/v1","contractVersions":{"generatorAbi":"wesley.extension-generator/v1","inputSchema":"wesley.extension-generation-input/v1","provenanceSchema":"wesley.generation-provenance-manifest/v1"},"emittedArtifacts":[{"coordinate":"echo.dpo-authority-facts@1","digest":"sha256:e2a6a539a7156296c18ff1bc02f80dcabf62b42bb4314d0737527869e43986d6"},{"coordinate":"echo.dpo-lawpack-authority-facts@1","digest":"sha256:ce1af559551f8b8242c4b290b620c5a28323b9e43f8bc6bb161a191001b6f4b9"},{"coordinate":"echo.dpo-lawpack@1","digest":"sha256:8c570362671a0b1cd1a992d8210e4a90ffd153ad06dcdf1e0cb36033cc971e2c"},{"coordinate":"echo.dpo.registration/v1","digest":"sha256:4ef3aaad0d2131ba7129a3e2ae178d10c44f7e9b062af2c1fe211de781462f34"},{"coordinate":"echo.dpo@1","digest":"sha256:1b105d1b1f6cdf5fecdef98b7adeb238525047d43581fe9fd8c44fd213e1788e"},{"coordinate":"echo.provider-artifacts.cddl@1","digest":"sha256:9078b84c47f2e5b722f8fea56cd0bca585cfd150c8114c22994b67cf19f6d9ab"}],"generationInputDigest":"sha256:0c07274803d465ec212c313f74dd46396851f43019fa0f97da9571a167c84587","generator":{"coordinate":"echo-wesley-gen.provider-artifact-generator@1","digest":"sha256:dd2532814deb3022e05d020706bfc2ab9a56167cccc89ea2545b1cce78e69547","version":"0.1.0"},"settingsDigest":"sha256:0f708e76898de6fdb8186352e81d0f5c445adf94bb6c7de9204952d9fe913d4a","sourceArtifacts":[{"coordinate":"echo.semantic-schema@1","digest":"sha256:322d9eb5eb8273108eaa602f2eb58ba591efcf829fecb10bb705717efae18852"},{"coordinate":"edict.provider-contract-pack.cddl@1","digest":"sha256:8d77c84ecbd7d7e38496479b2f8346c15504a56f076170d79c0eca2a57fdb3a5"},{"coordinate":"edict.provider-contract-pack.manifest@1","digest":"sha256:bc222c4a669b888a60df6f398412bae232967c11841b9a27c7ba6c18eb41e362"}]} \ No newline at end of file +{"apiVersion":"wesley.generation-provenance-manifest/v1","contractVersions":{"generatorAbi":"wesley.extension-generator/v1","inputSchema":"wesley.extension-generation-input/v1","provenanceSchema":"wesley.generation-provenance-manifest/v1"},"emittedArtifacts":[{"coordinate":"echo.dpo-authority-facts@1","digest":"sha256:e2a6a539a7156296c18ff1bc02f80dcabf62b42bb4314d0737527869e43986d6"},{"coordinate":"echo.dpo-lawpack-authority-facts@1","digest":"sha256:ce1af559551f8b8242c4b290b620c5a28323b9e43f8bc6bb161a191001b6f4b9"},{"coordinate":"echo.dpo-lawpack@1","digest":"sha256:8c570362671a0b1cd1a992d8210e4a90ffd153ad06dcdf1e0cb36033cc971e2c"},{"coordinate":"echo.dpo.registration/v1","digest":"sha256:4ef3aaad0d2131ba7129a3e2ae178d10c44f7e9b062af2c1fe211de781462f34"},{"coordinate":"echo.dpo@1","digest":"sha256:1b105d1b1f6cdf5fecdef98b7adeb238525047d43581fe9fd8c44fd213e1788e"},{"coordinate":"echo.provider-artifacts.cddl@1","digest":"sha256:9078b84c47f2e5b722f8fea56cd0bca585cfd150c8114c22994b67cf19f6d9ab"}],"generationInputDigest":"sha256:0c07274803d465ec212c313f74dd46396851f43019fa0f97da9571a167c84587","generator":{"coordinate":"echo-wesley-gen.provider-artifact-generator@1","digest":"sha256:cb9c6569a8f3c0ed3a0bd500151a3673f7f03b6f9d06d3b7edf227760fa895df","version":"0.1.0"},"settingsDigest":"sha256:0f708e76898de6fdb8186352e81d0f5c445adf94bb6c7de9204952d9fe913d4a","sourceArtifacts":[{"coordinate":"echo.semantic-schema@1","digest":"sha256:322d9eb5eb8273108eaa602f2eb58ba591efcf829fecb10bb705717efae18852"},{"coordinate":"edict.provider-contract-pack.cddl@1","digest":"sha256:8d77c84ecbd7d7e38496479b2f8346c15504a56f076170d79c0eca2a57fdb3a5"},{"coordinate":"edict.provider-contract-pack.manifest@1","digest":"sha256:bc222c4a669b888a60df6f398412bae232967c11841b9a27c7ba6c18eb41e362"}]} \ No newline at end of file diff --git a/crates/echo-wesley-gen/assets/v1/edict-provider/package/v1/generated/evidence/review.provider-generation.json b/crates/echo-wesley-gen/assets/v1/edict-provider/package/v1/generated/evidence/review.provider-generation.json index 95605ec67..b960179af 100644 --- a/crates/echo-wesley-gen/assets/v1/edict-provider/package/v1/generated/evidence/review.provider-generation.json +++ b/crates/echo-wesley-gen/assets/v1/edict-provider/package/v1/generated/evidence/review.provider-generation.json @@ -1 +1 @@ -{"apiVersion":"wesley.generation-review/v1","authoritative":false,"emittedArtifacts":[{"coordinate":"echo.dpo-authority-facts@1","digest":"sha256:e2a6a539a7156296c18ff1bc02f80dcabf62b42bb4314d0737527869e43986d6"},{"coordinate":"echo.dpo-lawpack-authority-facts@1","digest":"sha256:ce1af559551f8b8242c4b290b620c5a28323b9e43f8bc6bb161a191001b6f4b9"},{"coordinate":"echo.dpo-lawpack@1","digest":"sha256:8c570362671a0b1cd1a992d8210e4a90ffd153ad06dcdf1e0cb36033cc971e2c"},{"coordinate":"echo.dpo.registration/v1","digest":"sha256:4ef3aaad0d2131ba7129a3e2ae178d10c44f7e9b062af2c1fe211de781462f34"},{"coordinate":"echo.dpo@1","digest":"sha256:1b105d1b1f6cdf5fecdef98b7adeb238525047d43581fe9fd8c44fd213e1788e"},{"coordinate":"echo.provider-artifacts.cddl@1","digest":"sha256:9078b84c47f2e5b722f8fea56cd0bca585cfd150c8114c22994b67cf19f6d9ab"}],"generationInputDigest":"sha256:0c07274803d465ec212c313f74dd46396851f43019fa0f97da9571a167c84587","generator":{"coordinate":"echo-wesley-gen.provider-artifact-generator@1","digest":"sha256:dd2532814deb3022e05d020706bfc2ab9a56167cccc89ea2545b1cce78e69547","version":"0.1.0"},"projectionRoles":["authority-facts.echo-dpo","authority-facts.echo-lawpack","generated-artifact-profile.echo-dpo-registration","lawpack.echo-dpo","schema.echo-provider-artifacts","target-profile.echo-dpo"],"provenanceManifestDigest":"sha256:cbaf79293ebb33cd8825847b4ba848c20c2dab9b4e6cf26931c3e830bf9e7ca1","sourceArtifacts":[{"coordinate":"echo.semantic-schema@1","digest":"sha256:322d9eb5eb8273108eaa602f2eb58ba591efcf829fecb10bb705717efae18852"},{"coordinate":"edict.provider-contract-pack.cddl@1","digest":"sha256:8d77c84ecbd7d7e38496479b2f8346c15504a56f076170d79c0eca2a57fdb3a5"},{"coordinate":"edict.provider-contract-pack.manifest@1","digest":"sha256:bc222c4a669b888a60df6f398412bae232967c11841b9a27c7ba6c18eb41e362"}]} \ No newline at end of file +{"apiVersion":"wesley.generation-review/v1","authoritative":false,"emittedArtifacts":[{"coordinate":"echo.dpo-authority-facts@1","digest":"sha256:e2a6a539a7156296c18ff1bc02f80dcabf62b42bb4314d0737527869e43986d6"},{"coordinate":"echo.dpo-lawpack-authority-facts@1","digest":"sha256:ce1af559551f8b8242c4b290b620c5a28323b9e43f8bc6bb161a191001b6f4b9"},{"coordinate":"echo.dpo-lawpack@1","digest":"sha256:8c570362671a0b1cd1a992d8210e4a90ffd153ad06dcdf1e0cb36033cc971e2c"},{"coordinate":"echo.dpo.registration/v1","digest":"sha256:4ef3aaad0d2131ba7129a3e2ae178d10c44f7e9b062af2c1fe211de781462f34"},{"coordinate":"echo.dpo@1","digest":"sha256:1b105d1b1f6cdf5fecdef98b7adeb238525047d43581fe9fd8c44fd213e1788e"},{"coordinate":"echo.provider-artifacts.cddl@1","digest":"sha256:9078b84c47f2e5b722f8fea56cd0bca585cfd150c8114c22994b67cf19f6d9ab"}],"generationInputDigest":"sha256:0c07274803d465ec212c313f74dd46396851f43019fa0f97da9571a167c84587","generator":{"coordinate":"echo-wesley-gen.provider-artifact-generator@1","digest":"sha256:cb9c6569a8f3c0ed3a0bd500151a3673f7f03b6f9d06d3b7edf227760fa895df","version":"0.1.0"},"projectionRoles":["authority-facts.echo-dpo","authority-facts.echo-lawpack","generated-artifact-profile.echo-dpo-registration","lawpack.echo-dpo","schema.echo-provider-artifacts","target-profile.echo-dpo"],"provenanceManifestDigest":"sha256:9490680660ecb2ff932c5fbd650252a45252e7be0fffd7fda7b70f47c64b20d1","sourceArtifacts":[{"coordinate":"echo.semantic-schema@1","digest":"sha256:322d9eb5eb8273108eaa602f2eb58ba591efcf829fecb10bb705717efae18852"},{"coordinate":"edict.provider-contract-pack.cddl@1","digest":"sha256:8d77c84ecbd7d7e38496479b2f8346c15504a56f076170d79c0eca2a57fdb3a5"},{"coordinate":"edict.provider-contract-pack.manifest@1","digest":"sha256:bc222c4a669b888a60df6f398412bae232967c11841b9a27c7ba6c18eb41e362"}]} \ No newline at end of file diff --git a/crates/echo-wesley-gen/assets/v1/edict-provider/package/v1/provider-manifest.echo.json b/crates/echo-wesley-gen/assets/v1/edict-provider/package/v1/provider-manifest.echo.json index ce5c8a3b3..ffda753cd 100644 --- a/crates/echo-wesley-gen/assets/v1/edict-provider/package/v1/provider-manifest.echo.json +++ b/crates/echo-wesley-gen/assets/v1/edict-provider/package/v1/provider-manifest.echo.json @@ -3,7 +3,7 @@ "providerAbi": "edict:target-provider@1.0.0", "provider": { "coordinate": "echo.edict-provider@1", - "digest": "sha256:0e58e22e034ce81ecbb57d085505ab314c769bcda24b2d06bfa77d9062729116" + "digest": "sha256:21e5267310b6b4c06bf24a814d76790cbd58a4a9dcc33205dddd6fc1937594dd" }, "artifacts": [ { @@ -21,7 +21,7 @@ }, "generator": { "coordinate": "echo-wesley-gen.provider-artifact-generator@1", - "digest": "sha256:dd2532814deb3022e05d020706bfc2ab9a56167cccc89ea2545b1cce78e69547" + "digest": "sha256:cb9c6569a8f3c0ed3a0bd500151a3673f7f03b6f9d06d3b7edf227760fa895df" } } }, @@ -40,7 +40,7 @@ }, "generator": { "coordinate": "echo-wesley-gen.provider-artifact-generator@1", - "digest": "sha256:dd2532814deb3022e05d020706bfc2ab9a56167cccc89ea2545b1cce78e69547" + "digest": "sha256:cb9c6569a8f3c0ed3a0bd500151a3673f7f03b6f9d06d3b7edf227760fa895df" } } }, @@ -59,7 +59,7 @@ }, "generator": { "coordinate": "echo-wesley-gen.provider-artifact-generator@1", - "digest": "sha256:dd2532814deb3022e05d020706bfc2ab9a56167cccc89ea2545b1cce78e69547" + "digest": "sha256:cb9c6569a8f3c0ed3a0bd500151a3673f7f03b6f9d06d3b7edf227760fa895df" } } }, @@ -78,7 +78,7 @@ }, "generator": { "coordinate": "echo-wesley-gen.provider-artifact-generator@1", - "digest": "sha256:dd2532814deb3022e05d020706bfc2ab9a56167cccc89ea2545b1cce78e69547" + "digest": "sha256:cb9c6569a8f3c0ed3a0bd500151a3673f7f03b6f9d06d3b7edf227760fa895df" } } }, @@ -87,13 +87,13 @@ "artifactKind": "lowerer", "resource": { "coordinate": "echo.dpo.lowerer/component@1", - "digest": "sha256:4b594a8165079f0a973741b9e27b468cf041f4ad53108fccae641dc35355bd2a" + "digest": "sha256:dc3edc7c4f194bd2fdd2b5a1ecc79f8fb7a24a5a31294a1f1fc30369c053b8d4" }, "source": { "kind": "component", "component": { "coordinate": "echo.dpo.lowerer/component@1", - "digest": "sha256:4b594a8165079f0a973741b9e27b468cf041f4ad53108fccae641dc35355bd2a" + "digest": "sha256:dc3edc7c4f194bd2fdd2b5a1ecc79f8fb7a24a5a31294a1f1fc30369c053b8d4" } } }, @@ -102,7 +102,7 @@ "artifactKind": "generationProvenance", "resource": { "coordinate": "echo.edict-provider-generation-provenance@1", - "digest": "sha256:399c917c5cc9e86854a96852bdce898ec79df7b1236a8edb13a13b1afa72b664" + "digest": "sha256:2b27a04863c3660e8ce716cabfd63fd047d3f75a884e28d4222de5564c254142" }, "source": { "kind": "generated", @@ -112,7 +112,7 @@ }, "generator": { "coordinate": "echo-wesley-gen.provider-artifact-generator@1", - "digest": "sha256:dd2532814deb3022e05d020706bfc2ab9a56167cccc89ea2545b1cce78e69547" + "digest": "sha256:cb9c6569a8f3c0ed3a0bd500151a3673f7f03b6f9d06d3b7edf227760fa895df" } } }, @@ -121,7 +121,7 @@ "artifactKind": "reviewArtifact", "resource": { "coordinate": "echo.edict-provider-generation-review@1", - "digest": "sha256:8b7d9873deb7c517cb92a2dfc8256ff1395636eaeaaba308a9a6d3d0423913e7" + "digest": "sha256:8000857ec83afd059b0747376c01699b8c97052c84ff6a69d3e7eed407910a8a" }, "source": { "kind": "generated", @@ -131,7 +131,7 @@ }, "generator": { "coordinate": "echo-wesley-gen.provider-artifact-generator@1", - "digest": "sha256:dd2532814deb3022e05d020706bfc2ab9a56167cccc89ea2545b1cce78e69547" + "digest": "sha256:cb9c6569a8f3c0ed3a0bd500151a3673f7f03b6f9d06d3b7edf227760fa895df" } } }, @@ -150,7 +150,7 @@ }, "generator": { "coordinate": "echo-wesley-gen.provider-artifact-generator@1", - "digest": "sha256:dd2532814deb3022e05d020706bfc2ab9a56167cccc89ea2545b1cce78e69547" + "digest": "sha256:cb9c6569a8f3c0ed3a0bd500151a3673f7f03b6f9d06d3b7edf227760fa895df" } } }, @@ -169,7 +169,7 @@ }, "generator": { "coordinate": "echo-wesley-gen.provider-artifact-generator@1", - "digest": "sha256:dd2532814deb3022e05d020706bfc2ab9a56167cccc89ea2545b1cce78e69547" + "digest": "sha256:cb9c6569a8f3c0ed3a0bd500151a3673f7f03b6f9d06d3b7edf227760fa895df" } } }, @@ -178,13 +178,13 @@ "artifactKind": "verifier", "resource": { "coordinate": "echo.dpo.verifier/component@1", - "digest": "sha256:d0be4d283399aefc45d75b395b3568f9983521ca5543c9dfd23b9495758050ed" + "digest": "sha256:23d1d256ec66138697f93babcd2be57a4f56de1eb2d1fb6910e5aef881c4dd0e" }, "source": { "kind": "component", "component": { "coordinate": "echo.dpo.verifier/component@1", - "digest": "sha256:d0be4d283399aefc45d75b395b3568f9983521ca5543c9dfd23b9495758050ed" + "digest": "sha256:23d1d256ec66138697f93babcd2be57a4f56de1eb2d1fb6910e5aef881c4dd0e" } } } diff --git a/crates/echo-wesley-gen/examples/source_functions_publication_witness.rs b/crates/echo-wesley-gen/examples/source_functions_publication_witness.rs new file mode 100644 index 000000000..3e467ad7e --- /dev/null +++ b/crates/echo-wesley-gen/examples/source_functions_publication_witness.rs @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: Apache-2.0 +// © James Ross Ω FLYING•ROBOTS +//! Generates an explicitly selected source-function publication candidate. +//! Supports public compiler witnesses; generation grants no runtime authority. + +use anyhow::{bail, Context, Result}; +use echo_wesley_gen::provider_artifacts::generate_provider_primary_artifacts_v1; +use echo_wesley_gen::provider_contract_pack::{ + admit_provider_contract_pack_for_publication_v1, ProviderContractPublicationV1, +}; +use echo_wesley_gen::provider_corpus::checked_provider_generator_source_bundle_v1; +use echo_wesley_gen::provider_generation::build_provider_generation_input_v1; +use echo_wesley_gen::provider_package::{ + admit_provider_package_v1, assemble_provider_package_v1, ProviderPackageComponentMaterialV1, +}; +use echo_wesley_gen::provider_provenance::generate_provider_generation_provenance_v1; +use echo_wesley_gen::provider_review::generate_provider_generation_review_v1; +use std::path::PathBuf; + +fn main() -> Result<()> { + if !std::path::Path::new("/.dockerenv").is_file() { + bail!("run this consumer witness in a guarded Docker worker with copied sources"); + } + let mut arguments = std::env::args_os().skip(1); + let output = PathBuf::from( + arguments + .next() + .context("expected a new output directory")?, + ); + if arguments.next().is_some() || output.exists() { + bail!("expected exactly one new output directory"); + } + let contracts = admit_provider_contract_pack_for_publication_v1( + ProviderContractPublicationV1::SourceFunctions, + include_bytes!( + "../assets/v1/edict-provider/contracts/source-functions-v1/edict-provider-contracts.cddl" + ), + include_bytes!("../assets/v1/edict-provider/contracts/source-functions-v1/manifest.json"), + )?; + let input = build_provider_generation_input_v1( + include_bytes!("../assets/v1/edict-provider/echo-provider-semantics-v1.json"), + &contracts, + include_bytes!("../assets/v1/edict-provider/generation-settings-v1.json"), + )?; + let primary = generate_provider_primary_artifacts_v1(&input, &contracts)?; + let generator = checked_provider_generator_source_bundle_v1()?.generator_material()?; + let provenance = generate_provider_generation_provenance_v1(&input, &primary, &generator)?; + let review = generate_provider_generation_review_v1(&input, &provenance)?; + let candidate = assemble_provider_package_v1( + &input, + &primary, + &generator, + &provenance, + &review, + vec![ + ProviderPackageComponentMaterialV1::new( + "lowerer.echo-dpo", + include_bytes!("../assets/v1/edict-provider/package/v1/components/lowerer.echo-dpo.component.wasm"), + )?, + ProviderPackageComponentMaterialV1::new( + "verifier.echo-dpo", + include_bytes!("../assets/v1/edict-provider/package/v1/components/verifier.echo-dpo.component.wasm"), + )?, + ], + )?; + let admitted = + admit_provider_package_v1(candidate.files().to_vec(), candidate.provider_reference())?; + // Publish only after the complete generated package has passed digest admission. + std::fs::create_dir(&output)?; + for file in admitted.files() { + let path = output.join(file.relative_path()); + std::fs::create_dir_all(path.parent().context("package file parent")?)?; + std::fs::write(path, file.bytes())?; + } + Ok(()) +} diff --git a/crates/echo-wesley-gen/src/bin/echo-edict-provider-assets.rs b/crates/echo-wesley-gen/src/bin/echo-edict-provider-assets.rs index d8256ce3a..350640b4e 100644 --- a/crates/echo-wesley-gen/src/bin/echo-edict-provider-assets.rs +++ b/crates/echo-wesley-gen/src/bin/echo-edict-provider-assets.rs @@ -49,7 +49,7 @@ impl AssetSpec { } } -const ASSETS: [AssetSpec; 40] = [ +const ASSETS: [AssetSpec; 42] = [ AssetSpec::new( "edict-provider/contracts/ordered/edict-provider-contracts.cddl", "schemas/edict-provider/contracts/ordered/edict-provider-contracts.cddl", @@ -58,6 +58,14 @@ const ASSETS: [AssetSpec; 40] = [ "edict-provider/contracts/ordered/manifest.json", "schemas/edict-provider/contracts/ordered/manifest.json", ), + AssetSpec::new( + "edict-provider/contracts/source-functions-v1/edict-provider-contracts.cddl", + "schemas/edict-provider/contracts/source-functions-v1/edict-provider-contracts.cddl", + ), + AssetSpec::new( + "edict-provider/contracts/source-functions-v1/manifest.json", + "schemas/edict-provider/contracts/source-functions-v1/manifest.json", + ), AssetSpec::new( "edict-provider/contracts/v1/edict-provider-contracts.cddl", "schemas/edict-provider/contracts/v1/edict-provider-contracts.cddl", diff --git a/crates/echo-wesley-gen/src/provider_contract_pack.rs b/crates/echo-wesley-gen/src/provider_contract_pack.rs index 09f59362f..0631e6493 100644 --- a/crates/echo-wesley-gen/src/provider_contract_pack.rs +++ b/crates/echo-wesley-gen/src/provider_contract_pack.rs @@ -50,6 +50,8 @@ pub enum ProviderContractPublicationV1 { PureBindings, /// Ordered-instruction publication from Edict commit `2405a550e93e1e97fff640caa44bbd0f65ffff3c`. OrderedInstructions, + /// Source-owned pure functions, selected by the exact upstream publication pair. + SourceFunctions, } impl ProviderContractPublicationV1 { @@ -59,6 +61,9 @@ impl ProviderContractPublicationV1 { Self::OrderedInstructions => { "82273f3ea016a421c881f15b0fd451802205903ac9177bac8accbf3173f66d2c" } + Self::SourceFunctions => { + "e484eabd615584a38cb57454b52747786f2314c135c13f99da6f6bae619a709f" + } } } @@ -68,6 +73,9 @@ impl ProviderContractPublicationV1 { Self::OrderedInstructions => { "6303668861667a30418870ef25e5f169017905ae1f9d261451ba298120afdd9d" } + Self::SourceFunctions => { + "aebc2e4133407ae433c18b55421a89bf15dc5d489368f82ce4874f948d4f9c79" + } } } @@ -75,6 +83,7 @@ impl ProviderContractPublicationV1 { match self { Self::PureBindings => EDICT_PROVIDER_CONTRACT_PACK_MANIFEST_MAX_BYTES, Self::OrderedInstructions => 75_359, + Self::SourceFunctions => 75_989, } } } diff --git a/crates/echo-wesley-gen/tests/provider_package.rs b/crates/echo-wesley-gen/tests/provider_package.rs index 48a7fe2e0..30dfe86aa 100644 --- a/crates/echo-wesley-gen/tests/provider_package.rs +++ b/crates/echo-wesley-gen/tests/provider_package.rs @@ -65,7 +65,7 @@ const SEMANTIC_DIGEST: &str = const RELEASE_DIGEST: &str = "sha256:c39449495281b51f978468d08c21e93bcfa423176063b41675da61e4674b0066"; const PACKAGE_ARTIFACT_SHA256: &str = - "0e58e22e034ce81ecbb57d085505ab314c769bcda24b2d06bfa77d9062729116"; + "21e5267310b6b4c06bf24a814d76790cbd58a4a9dcc33205dddd6fc1937594dd"; const OTHER_PACKAGE_ARTIFACT_SHA256: &str = "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"; @@ -302,6 +302,10 @@ fn checked_materials_assemble_one_digest_locked_provider_package() { let reordered = assemble(true); assert_eq!(package, reordered); + assert_eq!( + package.provider_reference().digest, + format!("sha256:{PACKAGE_ARTIFACT_SHA256}") + ); assert_eq!(package.members().len(), 24); assert_eq!(package.files().len(), 25); assert_eq!(package.manifest().artifacts.len(), 10); diff --git a/crates/echo-wesley-gen/tests/provider_package_assets.rs b/crates/echo-wesley-gen/tests/provider_package_assets.rs index 211e348dc..4d53cfeba 100644 --- a/crates/echo-wesley-gen/tests/provider_package_assets.rs +++ b/crates/echo-wesley-gen/tests/provider_package_assets.rs @@ -7,7 +7,7 @@ use std::path::{Path, PathBuf}; use std::process::{Command, Output}; use std::sync::atomic::{AtomicU64, Ordering}; -const EXPECTED_ASSET_FILE_COUNT: usize = 40; +const EXPECTED_ASSET_FILE_COUNT: usize = 42; const EXPECTED_LOWERER_RESOURCE_COUNT: usize = 4; const EXPECTED_VERIFIER_RESOURCE_COUNT: usize = 15; static NEXT_TEMP_DIRECTORY: AtomicU64 = AtomicU64::new(0); diff --git a/crates/echo-wesley-gen/tests/provider_package_corpus.rs b/crates/echo-wesley-gen/tests/provider_package_corpus.rs index 098935813..1f64abbaa 100644 --- a/crates/echo-wesley-gen/tests/provider_package_corpus.rs +++ b/crates/echo-wesley-gen/tests/provider_package_corpus.rs @@ -11,9 +11,9 @@ use std::sync::atomic::{AtomicU64, Ordering}; use sha2::{Digest as _, Sha256}; const EXPECTED_PROVIDER_DIGEST: &str = - "sha256:0e58e22e034ce81ecbb57d085505ab314c769bcda24b2d06bfa77d9062729116"; + "sha256:21e5267310b6b4c06bf24a814d76790cbd58a4a9dcc33205dddd6fc1937594dd"; const EXPECTED_MANIFEST_RAW_SHA256: &str = - "12be23c8ffcac95ed7bc51bbdf5497b72891650bdd7a4439bcb79c15fbcfa971"; + "821c54b6d16f0a895769630fa6adf0401cef4578e5439cea79162d5e9c20b9b6"; const PACKAGE_PATHS: [&str; 25] = [ "components/lowerer.echo-dpo.component.wasm", diff --git a/crates/echo-wesley-gen/tests/provider_publication_binding.rs b/crates/echo-wesley-gen/tests/provider_publication_binding.rs index d1eee3109..0dbaacdb5 100644 --- a/crates/echo-wesley-gen/tests/provider_publication_binding.rs +++ b/crates/echo-wesley-gen/tests/provider_publication_binding.rs @@ -29,6 +29,14 @@ fn pack(publication: ProviderContractPublicationV1) -> AdmittedProviderContractP ), include_bytes!("../assets/v1/edict-provider/contracts/ordered/manifest.json"), ), + ProviderContractPublicationV1::SourceFunctions => ( + include_bytes!( + "../../../schemas/edict-provider/contracts/source-functions-v1/edict-provider-contracts.cddl" + ), + include_bytes!( + "../../../schemas/edict-provider/contracts/source-functions-v1/manifest.json" + ), + ), }; admit_provider_contract_pack_for_publication_v1(publication, schema, manifest) .expect("exact publication admits") @@ -74,6 +82,7 @@ fn each_matching_publication_generates_its_bound_schema() { for publication in [ ProviderContractPublicationV1::PureBindings, ProviderContractPublicationV1::OrderedInstructions, + ProviderContractPublicationV1::SourceFunctions, ] { let bound = pack(publication); let input = build_provider_generation_input_v1(SOURCE, &bound, SETTINGS) @@ -84,3 +93,14 @@ fn each_matching_publication_generates_its_bound_schema() { assert_eq!(primary.generation_input_digest(), input.digest()); } } + +#[test] +fn source_function_generation_rejects_every_other_publication_in_both_directions() { + for other in [ + ProviderContractPublicationV1::PureBindings, + ProviderContractPublicationV1::OrderedInstructions, + ] { + rejects_crossed_publication(ProviderContractPublicationV1::SourceFunctions, other); + rejects_crossed_publication(other, ProviderContractPublicationV1::SourceFunctions); + } +} diff --git a/crates/echo-wesley-gen/tests/provider_source_function_contract_pack.rs b/crates/echo-wesley-gen/tests/provider_source_function_contract_pack.rs new file mode 100644 index 000000000..0e3b255ba --- /dev/null +++ b/crates/echo-wesley-gen/tests/provider_source_function_contract_pack.rs @@ -0,0 +1,375 @@ +// SPDX-License-Identifier: Apache-2.0 +// © James Ross Ω FLYING•ROBOTS +#![allow(clippy::expect_used)] +//! Explicit upstream publication admission is separate from executable support. + +use echo_wesley_gen::provider_canonical::{encode_canonical_cbor_v1, CanonicalValueV1 as Value}; +use echo_wesley_gen::provider_contract_pack::{ + admit_provider_contract_pack_for_publication_v1, admit_provider_contract_pack_v1, + AdmittedProviderContractPackV1, ProviderContractPackError, ProviderContractPackErrorKind, + ProviderContractPublicationV1, ProviderContractValidationErrorKind, +}; +use sha2::{Digest, Sha256}; + +const LEGACY_CDDL: &[u8] = + include_bytes!("../assets/v1/edict-provider/contracts/v1/edict-provider-contracts.cddl"); +const LEGACY_MANIFEST: &[u8] = + include_bytes!("../assets/v1/edict-provider/contracts/v1/manifest.json"); +const ORDERED_CDDL: &[u8] = + include_bytes!("../assets/v1/edict-provider/contracts/ordered/edict-provider-contracts.cddl"); +const ORDERED_MANIFEST: &[u8] = + include_bytes!("../assets/v1/edict-provider/contracts/ordered/manifest.json"); +const SOURCE_CDDL: &[u8] = include_bytes!( + "../assets/v1/edict-provider/contracts/source-functions-v1/edict-provider-contracts.cddl" +); +const SOURCE_MANIFEST: &[u8] = + include_bytes!("../assets/v1/edict-provider/contracts/source-functions-v1/manifest.json"); + +#[test] +fn exact_upstream_source_function_pair_is_the_test_input() { + assert_eq!(SOURCE_CDDL.len(), 34_460); + assert_eq!(SOURCE_MANIFEST.len(), 75_989); + assert_eq!( + hex::encode(Sha256::digest(SOURCE_CDDL)), + "e484eabd615584a38cb57454b52747786f2314c135c13f99da6f6bae619a709f" + ); + assert_eq!( + hex::encode(Sha256::digest(SOURCE_MANIFEST)), + "aebc2e4133407ae433c18b55421a89bf15dc5d489368f82ce4874f948d4f9c79" + ); +} + +#[test] +fn existing_publications_still_admit_their_exact_bytes() { + let legacy = admit_provider_contract_pack_v1(LEGACY_CDDL, LEGACY_MANIFEST) + .expect("default admission retains the original publication"); + assert_eq!( + legacy.publication(), + ProviderContractPublicationV1::PureBindings + ); + assert_eq!(legacy.schema_bytes(), LEGACY_CDDL); + assert_eq!(legacy.manifest_bytes(), LEGACY_MANIFEST); + let ordered = admit_provider_contract_pack_for_publication_v1( + ProviderContractPublicationV1::OrderedInstructions, + ORDERED_CDDL, + ORDERED_MANIFEST, + ) + .expect("explicit ordered admission retains its original publication"); + assert_eq!(ordered.schema_bytes(), ORDERED_CDDL); + assert_eq!(ordered.manifest_bytes(), ORDERED_MANIFEST); +} + +#[test] +fn source_function_bytes_never_silently_upgrade_an_existing_selection() { + for publication in [ + ProviderContractPublicationV1::PureBindings, + ProviderContractPublicationV1::OrderedInstructions, + ] { + assert_eq!( + admit_provider_contract_pack_for_publication_v1( + publication, + SOURCE_CDDL, + SOURCE_MANIFEST, + ) + .expect_err("an existing publication must not discover the new schema") + .kind(), + ProviderContractPackErrorKind::ManifestSizeExceeded, + ); + } + assert_eq!( + admit_provider_contract_pack_v1(SOURCE_CDDL, SOURCE_MANIFEST) + .expect_err("default admission must retain its original publication") + .kind(), + ProviderContractPackErrorKind::ManifestSizeExceeded, + ); +} + +#[test] +fn source_function_publication_has_an_explicit_admission_route() { + // Exactly one explicit selector admits these bytes; neither existing + // publication is upgraded by recognizing a new manifest. + let publications = [ + ProviderContractPublicationV1::PureBindings, + ProviderContractPublicationV1::OrderedInstructions, + ProviderContractPublicationV1::SourceFunctions, + ]; + let results = publications.map(|publication| { + admit_provider_contract_pack_for_publication_v1(publication, SOURCE_CDDL, SOURCE_MANIFEST) + }); + assert_eq!( + results.iter().filter(|result| result.is_ok()).count(), + 1, + "one explicit publication must admit the pinned source-function pair: {results:?}", + ); + let selected = results + .into_iter() + .find_map(Result::ok) + .expect("the exact source-function publication has been selected"); + assert_eq!( + selected.publication(), + ProviderContractPublicationV1::SourceFunctions + ); + assert_eq!(selected.schema_bytes(), SOURCE_CDDL); + assert_eq!(selected.manifest_bytes(), SOURCE_MANIFEST); + assert_eq!(selected.contract_count(), 11); + assert_eq!(selected.domain_count(), 7); + assert_eq!(selected.resource_count(), 5); +} + +fn admit_source_functions( + schema: &[u8], + manifest: &[u8], +) -> Result { + admit_provider_contract_pack_for_publication_v1( + ProviderContractPublicationV1::SourceFunctions, + schema, + manifest, + ) +} + +#[test] +fn source_selection_rejects_old_publications_and_crossed_pairs() { + for (schema, manifest) in [ + (LEGACY_CDDL, LEGACY_MANIFEST), + (ORDERED_CDDL, ORDERED_MANIFEST), + ] { + assert_eq!( + admit_source_functions(schema, manifest) + .expect_err("explicit new selection cannot accept an old publication") + .kind(), + ProviderContractPackErrorKind::SchemaDigestMismatch, + ); + for (crossed_schema, crossed_manifest) in + [(SOURCE_CDDL, manifest), (schema, SOURCE_MANIFEST)] + { + assert_eq!( + admit_source_functions(crossed_schema, crossed_manifest) + .expect_err("schema and manifest must come from one publication") + .kind(), + ProviderContractPackErrorKind::SchemaBytesMismatch, + ); + } + } +} + +#[test] +fn source_publication_rejects_tampering_and_self_consistent_republication() { + let mut schema = SOURCE_CDDL.to_vec(); + schema[0] ^= 1; + assert_eq!( + admit_source_functions(&schema, SOURCE_MANIFEST) + .expect_err("changed CDDL must reject") + .kind(), + ProviderContractPackErrorKind::SchemaBytesMismatch, + ); + let mut manifest: serde_json::Value = serde_json::from_slice(SOURCE_MANIFEST).expect("JSON"); + manifest["schema"]["bytesHex"] = hex::encode(&schema).into(); + manifest["schema"]["rawSha256"] = hex::encode(Sha256::digest(&schema)).into(); + assert_eq!( + admit_source_functions(&schema, &serde_json::to_vec(&manifest).expect("JSON")) + .expect_err("internal consistency does not authorize a new publication") + .kind(), + ProviderContractPackErrorKind::SchemaDigestMismatch, + ); +} + +#[test] +fn source_manifest_requires_exact_bytes_and_checks_size_before_parsing() { + let manifest: serde_json::Value = serde_json::from_slice(SOURCE_MANIFEST).expect("JSON"); + assert_eq!( + admit_source_functions(SOURCE_CDDL, &serde_json::to_vec(&manifest).expect("JSON")) + .expect_err("reformatting changes the pinned manifest") + .kind(), + ProviderContractPackErrorKind::ManifestDigestMismatch, + ); + for oversized in [ + [SOURCE_MANIFEST, b" "].concat(), + vec![b'{'; SOURCE_MANIFEST.len() + 1], + ] { + assert_eq!( + admit_source_functions(SOURCE_CDDL, &oversized) + .expect_err("size ceiling applies before JSON parsing") + .kind(), + ProviderContractPackErrorKind::ManifestSizeExceeded, + ); + } +} + +#[test] +fn source_manifest_still_checks_api_and_embedded_resources() { + for (field, expected) in [ + ("api", ProviderContractPackErrorKind::UnsupportedApiVersion), + ( + "resource", + ProviderContractPackErrorKind::ResourceRawDigestMismatch, + ), + ] { + let mut manifest: serde_json::Value = + serde_json::from_slice(SOURCE_MANIFEST).expect("JSON"); + if field == "api" { + manifest["apiVersion"] = "edict.provider-contract-pack/v99".into(); + } else { + manifest["resources"][0]["rawSha256"] = "0".repeat(64).into(); + } + assert_eq!( + admit_source_functions(SOURCE_CDDL, &serde_json::to_vec(&manifest).expect("JSON")) + .expect_err("invalid publication") + .kind(), + expected, + ); + } +} + +fn text(value: &str) -> Value { + Value::Text(value.to_owned()) +} + +fn map(entries: impl IntoIterator) -> Value { + Value::Map( + entries + .into_iter() + .map(|(key, value)| (text(key), value)) + .collect(), + ) +} + +fn parameter() -> Value { + map([ + ("id", text("arg.0")), + ("alphaName", text("arg.0")), + ("type", text("U64")), + ]) +} + +fn identity_function(return_type: Option) -> Value { + let mut fields = vec![ + ("params", Value::Array(vec![parameter()])), + ( + "body", + map([ + ("locals", Value::Array(vec![])), + ("bindings", Value::Array(vec![])), + ( + "result", + map([("kind", text("local")), ("ref", parameter())]), + ), + ]), + ), + ]; + if let Some(return_type) = return_type { + fields.push(("returnType", return_type)); + } + map(fields) +} + +// Structural CDDL specimens only. Public compiler/provider/runtime witnesses +// independently establish source meaning and executable capability support. +fn core_module(functions: Option) -> Value { + let mut fields = vec![ + ("apiVersion", text("edict.core/v1")), + ("coordinate", text("conformance.source-functions@1")), + ("imports", Value::Array(vec![])), + ("types", map([])), + ( + "intents", + map([( + "observe", + map([ + ("input", text("U64")), + ("output", text("U64")), + ( + "requiredOperationProfile", + text("continuum.profile.read-only/v1"), + ), + ("inputConstraints", Value::Array(vec![])), + ( + "coreEvaluationBudget", + map([ + ("maxSteps", Value::Integer(100)), + ("maxAllocatedBytes", Value::Integer(1024)), + ("maxOutputBytes", Value::Integer(64)), + ]), + ), + ( + "body", + map([ + ("locals", Value::Array(vec![])), + ("nodes", Value::Array(vec![])), + ( + "result", + map([ + ("kind", text("const")), + ( + "value", + map([ + ("kind", text("int")), + ("width", text("U64")), + ("value", Value::Integer(7)), + ]), + ), + ]), + ), + ]), + ), + ]), + )]), + ), + ("requiredCoreCapabilities", Value::Array(vec![])), + ]; + if let Some(functions) = functions { + fields.push(("functions", functions)); + } + map(fields) +} + +#[test] +fn schema_preserves_function_free_core_and_requires_new_selection_for_functions() { + let source = admit_source_functions(SOURCE_CDDL, SOURCE_MANIFEST).expect("source schema"); + let legacy = admit_provider_contract_pack_v1(LEGACY_CDDL, LEGACY_MANIFEST).expect("legacy"); + let ordered = admit_provider_contract_pack_for_publication_v1( + ProviderContractPublicationV1::OrderedInstructions, + ORDERED_CDDL, + ORDERED_MANIFEST, + ) + .expect("ordered"); + let baseline = encode_canonical_cbor_v1(&core_module(None)).expect("canonical control"); + let functions = encode_canonical_cbor_v1(&core_module(Some(map([( + "identity", + identity_function(Some(text("U64"))), + )])))) + .expect("canonical source-function specimen"); + for pack in [&legacy, &ordered, &source] { + pack.validate_contract_bytes("core-module", &baseline) + .expect("function-free Core keeps its old schema shape"); + } + source + .validate_contract_bytes("core-module", &functions) + .expect("new schema admits source-owned function shape"); + for pack in [&legacy, &ordered] { + assert_eq!( + pack.validate_contract_bytes("core-module", &functions) + .expect_err("old schemas must not discover source authority") + .kind(), + ProviderContractValidationErrorKind::SchemaMismatch, + ); + } +} + +#[test] +fn schema_refuses_empty_function_tables_and_missing_or_malformed_return_types() { + let source = admit_source_functions(SOURCE_CDDL, SOURCE_MANIFEST).expect("source schema"); + for table in [ + map([]), + map([("identity", identity_function(None))]), + map([("identity", identity_function(Some(Value::Null)))]), + ] { + let bytes = encode_canonical_cbor_v1(&core_module(Some(table))).expect("canonical"); + assert_eq!( + source + .validate_contract_bytes("core-module", &bytes) + .expect_err("malformed function table must reject") + .kind(), + ProviderContractValidationErrorKind::SchemaMismatch, + ); + } +} diff --git a/crates/warp-core/Cargo.toml b/crates/warp-core/Cargo.toml index 2588de508..b1c4fc660 100644 --- a/crates/warp-core/Cargo.toml +++ b/crates/warp-core/Cargo.toml @@ -130,6 +130,10 @@ required-features = ["trusted_runtime"] name = "edict_byte_concat_tests" required-features = ["trusted_runtime"] +[[test]] +name = "edict_source_functions_tests" +required-features = ["trusted_runtime"] + [build-dependencies] blake3 = "1.0" diff --git a/crates/warp-core/src/edict_pure.rs b/crates/warp-core/src/edict_pure.rs index 8351ac56a..f27165b37 100644 --- a/crates/warp-core/src/edict_pure.rs +++ b/crates/warp-core/src/edict_pure.rs @@ -8,6 +8,7 @@ mod decode; pub(crate) mod evaluate; +pub(crate) mod functions; pub(crate) mod model; pub(crate) mod syntax; pub(crate) mod values; diff --git a/crates/warp-core/src/edict_pure/decode.rs b/crates/warp-core/src/edict_pure/decode.rs index fe86cf868..096c26224 100644 --- a/crates/warp-core/src/edict_pure/decode.rs +++ b/crates/warp-core/src/edict_pure/decode.rs @@ -7,7 +7,7 @@ use echo_edict_canonical::{ CanonicalValueV1 as Value, }; -use super::model::{Binding, Helper, Program, MAX_PROGRAM_NODES}; +use super::model::{Binding, Program, MAX_PROGRAM_NODES}; use super::syntax::Parser; use super::values::{array, bytes, exact_fields, field, number, require_text, text_field}; use super::{EvaluationError as Error, EvaluationLimits}; @@ -117,7 +117,8 @@ pub(super) fn package( } } let input_local = array(field(field(core_intent, "body")?, "locals")?)? - .first() + .iter() + .find(|local| text_field(local, "id") == Ok("arg.0")) .ok_or(Error::InvalidArtifact)?; let input_id = text_field(input_local, "id")?.to_owned(); if text_field(input_local, "type")? != text_field(core_intent, "input")? { @@ -168,7 +169,18 @@ pub(super) fn package( parser.predicate(field(constraint, "predicate")?, 0)?, )); } - let helpers = helpers(&exports, &mut parser)?; + let helpers = super::functions::decode(&core, &exports, &mut parser)?; + if !array(field(&exports, "effects")?)?.is_empty() { + return Err(Error::UnsupportedProgram); + } + super::functions::check_roots( + &helpers, + bindings + .iter() + .map(|binding| &binding.value) + .chain(std::iter::once(&result)), + constraints.iter().map(|(_, predicate)| predicate), + )?; let budget = field(value, "budget_ceiling")?; let core_budget = field(core_intent, "coreEvaluationBudget")?; if field(target_intent, "coreEvaluationBudget")? != core_budget { @@ -209,38 +221,3 @@ pub(super) fn package( fn embedded(program: &Value, key: &str) -> Result { decode(bytes(field(program, key)?)?).map_err(|_| Error::InvalidArtifact) } - -fn helpers(exports: &Value, parser: &mut Parser<'_>) -> Result, Error> { - let mut result = BTreeMap::new(); - for helper in array(field(exports, "pureFunctions")?)? { - require_text(helper, "source", "edict")?; - let implementation = field(helper, "body")?; - let body = field(implementation, "body")?; - for (value, name) in [ - (helper, "parameterTypes"), - (helper, "typeParameters"), - (implementation, "params"), - (body, "bindings"), - (body, "locals"), - ] { - if !array(field(value, name)?)?.is_empty() { - return Err(Error::UnsupportedProgram); - } - } - let definition = Helper { - result: parser.expr(field(body, "result")?, 0)?, - ty: parser.ty(text_field(helper, "returnType")?, 0)?, - }; - if result - .insert(text_field(helper, "coordinate")?.to_owned(), definition) - .is_some() - { - return Err(Error::InvalidArtifact); - } - } - // Reject effects even when a malformed package claims an empty authority profile. - if !array(field(exports, "effects")?)?.is_empty() { - return Err(Error::UnsupportedProgram); - } - Ok(result) -} diff --git a/crates/warp-core/src/edict_pure/evaluate.rs b/crates/warp-core/src/edict_pure/evaluate.rs index ba95925c0..6f0e6329c 100644 --- a/crates/warp-core/src/edict_pure/evaluate.rs +++ b/crates/warp-core/src/edict_pure/evaluate.rs @@ -130,7 +130,7 @@ pub(super) fn run(program: &Program, input: Value) -> Result Result Result, + locals: &BTreeMap<&str, Value>, helpers: &BTreeMap, meter: &mut Meter, depth: usize, @@ -166,7 +166,7 @@ pub(crate) fn expression( meter.step(depth)?; match expr { Expr::Constant(value) => meter.copy(value), - Expr::Local(id) => meter.copy(locals.get(id).ok_or(Error::InvalidArtifact)?), + Expr::Local(id) => meter.copy(locals.get(id.as_str()).ok_or(Error::InvalidArtifact)?), Expr::Field(base, name) => { let record = expression(base, locals, helpers, meter, depth + 1)?; let Value::Map(fields) = record else { @@ -204,10 +204,25 @@ pub(crate) fn expression( }; expression(branch, locals, helpers, meter, depth + 1) } - Expr::Call(name) => { + Expr::Call(name, args) => { let helper = helpers.get(name).ok_or(Error::UnsupportedProgram)?; - // A helper has its own lexical scope, never the caller's local bindings. - let value = expression(&helper.result, &BTreeMap::new(), helpers, meter, depth + 1)?; + if args.len() != helper.params.len() { + return Err(Error::InvalidArtifact); + } + // Arguments run once, left to right in the caller's frame. Move each + // value into the fresh frame; later local reads charge their own copies. + let mut frame = BTreeMap::new(); + for (argument, parameter) in args.iter().zip(&helper.params) { + let value = expression(argument, locals, helpers, meter, depth + 1)?; + validate(&value, ¶meter.ty, meter, depth + 1)?; + frame.insert(parameter.id.as_str(), value); + } + for binding in &helper.bindings { + let value = expression(&binding.value, &frame, helpers, meter, depth + 1)?; + validate(&value, &binding.ty, meter, depth + 1)?; + frame.insert(binding.id.as_str(), value); + } + let value = expression(&helper.result, &frame, helpers, meter, depth + 1)?; validate(&value, &helper.ty, meter, depth + 1)?; Ok(value) } @@ -324,7 +339,7 @@ fn byte_slice( pub(crate) fn predicate( predicate: &Predicate, - locals: &BTreeMap, + locals: &BTreeMap<&str, Value>, helpers: &BTreeMap, meter: &mut Meter, depth: usize, diff --git a/crates/warp-core/src/edict_pure/functions.rs b/crates/warp-core/src/edict_pure/functions.rs new file mode 100644 index 000000000..a429ffa07 --- /dev/null +++ b/crates/warp-core/src/edict_pure/functions.rs @@ -0,0 +1,623 @@ +// SPDX-License-Identifier: Apache-2.0 +// © James Ross Ω FLYING•ROBOTS +//! Closed source/imported helper authority, lexical scopes, and runtime apertures. +use super::{ + model::{ + Binding, Expr, Helper, Parameter, Predicate, RuntimeType, MAX_DEPTH, MAX_PROGRAM_NODES, + }, + syntax::Parser, + values::{array, exact_fields, field, map, number, require_text, text, text_field}, + EvaluationError as Error, +}; +use echo_edict_canonical::CanonicalValueV1 as Value; +use std::collections::{BTreeMap, BTreeSet}; + +struct Definition<'a> { + params: &'a [Value], + body: &'a Value, + result_type: &'a str, + source_owned: bool, +} + +pub(crate) fn decode( + core: &Value, + exports: &Value, + parser: &mut Parser<'_>, +) -> Result, Error> { + let mut definitions = BTreeMap::new(); + for item in array(field(exports, "pureFunctions")?)? { + require_text(item, "source", "edict")?; + let implementation = field(item, "body")?; + let body = field(implementation, "body")?; + // This publication preserves the previous imported helper subset. It + // does not upgrade imported authority merely because source fn exists. + for (value, name) in [ + (item, "parameterTypes"), + (item, "typeParameters"), + (implementation, "params"), + (body, "locals"), + (body, "bindings"), + ] { + if !array(field(value, name)?)?.is_empty() { + return Err(Error::UnsupportedProgram); + } + } + let name = text_field(item, "coordinate")?; + if name.starts_with("core.") + || definitions + .insert( + name.to_owned(), + Definition { + params: array(field(implementation, "params")?)?, + body, + result_type: text_field(item, "returnType")?, + source_owned: false, + }, + ) + .is_some() + { + return Err(Error::InvalidArtifact); + } + } + if let Ok(functions) = field(core, "functions") { + // Imported effect names remain reserved authority, including when the + // colliding source definition is unused. They are never pure helpers. + let effect_names = array(field(exports, "effects")?)? + .iter() + .map(|effect| text_field(effect, "coordinate")) + .collect::, Error>>()?; + for (name, value) in map(functions)? { + let name = text(name)?; + if name.is_empty() + || name.len() > 1024 + || !name.bytes().enumerate().all(|(i, c)| { + c == b'_' || c.is_ascii_alphabetic() || (i > 0 && c.is_ascii_digit()) + }) + { + return Err(Error::InvalidArtifact); + } + if field(field(core, "intents")?, name).is_ok() + || field(field(core, "types")?, name).is_ok() + { + return Err(Error::InvalidArtifact); + } + exact_fields(value, &["params", "returnType", "body"])?; + let coordinate = format!("{}.{}", parser.coordinate, name); + if coordinate.starts_with("core.") + || effect_names.contains(coordinate.as_str()) + || definitions + .insert( + coordinate, + Definition { + params: array(field(value, "params")?)?, + body: field(value, "body")?, + result_type: text_field(value, "returnType")?, + source_owned: true, + }, + ) + .is_some() + { + return Err(Error::InvalidArtifact); + } + } + } + let mut helpers = BTreeMap::new(); + // Collect all signatures before resolving calls: definition order carries + // no execution meaning. Every body is checked, including unused functions. + for (name, definition) in &definitions { + parser.enter(0)?; + exact_fields(definition.body, &["locals", "bindings", "result"])?; + let mut scope = BTreeMap::new(); + let mut params = Vec::new(); + for parameter in definition.params { + local(parameter)?; + let id = text_field(parameter, "id")?; + if scope.insert(id, parameter).is_some() { + return Err(Error::InvalidArtifact); + } + params.push(Parameter { + id: id.to_owned(), + ty: parser.ty(text_field(parameter, "type")?, 0)?, + }); + } + let mut pending = BTreeMap::new(); + for declaration in array(field(definition.body, "locals")?)? { + local(declaration)?; + let id = text_field(declaration, "id")?; + if scope.contains_key(id) || pending.insert(id, declaration).is_some() { + return Err(Error::InvalidArtifact); + } + } + let mut bindings = Vec::new(); + for binding in array(field(definition.body, "bindings")?)? { + exact_fields(binding, &["kind", "binding", "value"])?; + require_text(binding, "kind", "let")?; + let declaration = field(binding, "binding")?; + let id = text_field(declaration, "id")?; + if pending.remove(id) != Some(declaration) { + return Err(Error::InvalidArtifact); + } + let value = field(binding, "value")?; + let actual = infer( + value, + &scope, + &definitions, + definition.source_owned, + parser, + 0, + )?; + let ty = parser.ty(text_field(declaration, "type")?, 0)?; + if !accepts(&ty, &actual) { + return Err(Error::InvalidArtifact); + } + bindings.push(Binding { + id: id.to_owned(), + ty, + value: parser.expr(value, 0)?, + }); + scope.insert(id, declaration); + } + if !pending.is_empty() { + return Err(Error::InvalidArtifact); + } + let result = field(definition.body, "result")?; + let actual = infer( + result, + &scope, + &definitions, + definition.source_owned, + parser, + 0, + )?; + let ty = parser.ty(definition.result_type, 0)?; + if !accepts(&ty, &actual) { + return Err(Error::InvalidArtifact); + } + helpers.insert( + name.clone(), + Helper { + source_owned: definition.source_owned, + params, + bindings, + result: parser.expr(result, 0)?, + ty, + }, + ); + } + let mut basis_roots = Vec::new(); + if field(core, "functions").is_ok() { + for (_, intent) in map(field(core, "intents")?)? { + let Some(basis) = map(intent)? + .iter() + .find_map(|(key, value)| (text(key) == Ok("basis")).then_some(value)) + else { + continue; + }; + let input = array(field(field(intent, "body")?, "locals")?)? + .iter() + .find(|value| text_field(value, "id") == Ok("arg.0")) + .ok_or(Error::InvalidArtifact)?; + local(input)?; + let scope = BTreeMap::from([("arg.0", input)]); + // A pure basis is retained authority, not executed work. Admit its + // input-only scope, calls and combined depth without spending Meter. + infer(basis, &scope, &definitions, true, parser, 0)?; + basis_roots.push(parser.expr(basis, 0)?); + } + } + check_roots(&helpers, basis_roots.iter(), std::iter::empty())?; + Ok(helpers) +} + +fn local(value: &Value) -> Result<(), Error> { + exact_fields(value, &["id", "type", "alphaName"])?; + for name in ["id", "type", "alphaName"] { + let value = text_field(value, name)?; + if value.is_empty() || value.len() > 1024 { + return Err(Error::InvalidArtifact); + } + } + Ok(()) +} + +fn accepts(expected: &RuntimeType, actual: &RuntimeType) -> bool { + match (expected, actual) { + (RuntimeType::Bytes { min, max }, RuntimeType::Bytes { min: a, max: b }) => { + min <= a && b <= max + } + (RuntimeType::Record(left), RuntimeType::Record(right)) => { + left.len() == right.len() + && left.iter().all(|(key, ty)| { + right + .iter() + .find(|(name, _)| name == key) + .is_some_and(|(_, value)| accepts(ty, value)) + }) + } + _ => expected == actual, + } +} + +fn join(left: RuntimeType, right: RuntimeType) -> Result { + match (left, right) { + (RuntimeType::Bytes { min: a, max: b }, RuntimeType::Bytes { min: c, max: d }) => { + Ok(RuntimeType::Bytes { + min: a.min(c), + max: b.max(d), + }) + } + (left, right) if left == right => Ok(left), + _ => Err(Error::UnsupportedProgram), + } +} + +type Scope<'a> = BTreeMap<&'a str, &'a Value>; +fn infer( + value: &Value, + scope: &Scope<'_>, + definitions: &BTreeMap>, + source: bool, + parser: &mut Parser<'_>, + depth: usize, +) -> Result { + parser.enter(depth)?; + match text_field(value, "kind")? { + "local" => { + exact_fields(value, &["kind", "ref"])?; + let reference = field(value, "ref")?; + if scope.get(text_field(reference, "id")?).copied() != Some(reference) { + return Err(Error::InvalidArtifact); + } + parser.ty(text_field(reference, "type")?, 0) + } + "const" => { + exact_fields(value, &["kind", "value"])?; + let literal = field(value, "value")?; + exact_fields(literal, &["kind", "value", "width"])?; + require_text(literal, "kind", "int")?; + let ty = parser.ty(text_field(literal, "width")?, 0)?; + if !matches!(ty, RuntimeType::Unsigned(max) if number(field(literal, "value")?)? <= max) + { + return Err(Error::UnsupportedProgram); + } + Ok(ty) + } + "field" => { + exact_fields(value, &["kind", "base", "field"])?; + let RuntimeType::Record(fields) = infer( + field(value, "base")?, + scope, + definitions, + source, + parser, + depth + 1, + )? + else { + return Err(Error::InvalidArtifact); + }; + fields + .into_iter() + .find_map(|(name, ty)| { + (text_field(value, "field") == Ok(name.as_str())).then_some(ty) + }) + .ok_or(Error::InvalidArtifact) + } + "record" => { + exact_fields(value, &["kind", "fields"])?; + let fields = map(field(value, "fields")?)? + .iter() + .map(|(name, expression)| { + Ok(( + text(name)?.to_owned(), + infer(expression, scope, definitions, source, parser, depth + 1)?, + )) + }) + .collect::, Error>>()?; + Ok(RuntimeType::Record(fields)) + } + "if" => { + exact_fields(value, &["kind", "predicate", "then", "else"])?; + check_predicate( + field(value, "predicate")?, + scope, + definitions, + source, + parser, + depth + 1, + )?; + let left = infer( + field(value, "then")?, + scope, + definitions, + source, + parser, + depth + 1, + )?; + let right = infer( + field(value, "else")?, + scope, + definitions, + source, + parser, + depth + 1, + )?; + join(left, right) + } + "call" => { + exact_fields(value, &["kind", "callee", "args", "typeArgs"])?; + let name = text_field(value, "callee")?; + let arguments = array(field(value, "args")?)?; + let types = array(field(value, "typeArgs")?)?; + if let Some(callee) = definitions.get(name) { + if (!source && callee.source_owned) + || !types.is_empty() + || arguments.len() != callee.params.len() + { + return Err(Error::InvalidArtifact); + } + for (argument, parameter) in arguments.iter().zip(callee.params) { + let actual = infer(argument, scope, definitions, source, parser, depth + 1)?; + let expected = parser.ty(text_field(parameter, "type")?, 0)?; + if !accepts(&expected, &actual) { + return Err(Error::InvalidArtifact); + } + } + return parser.ty(callee.result_type, 0); + } + let actual = arguments + .iter() + .map(|argument| infer(argument, scope, definitions, source, parser, depth + 1)) + .collect::, _>>()?; + let declared = types + .iter() + .map(|coordinate| parser.ty(text(coordinate)?, depth + 1)) + .collect::, _>>()?; + match (name, actual.as_slice(), declared.as_slice()) { + ("core.bytes.length", [actual], [expected @ RuntimeType::Bytes { .. }]) + if accepts(expected, actual) => + { + Ok(RuntimeType::Unsigned(u64::MAX)) + } + ( + "core.bytes.concat", + [left, right], + [a @ RuntimeType::Bytes { + min: a_min, + max: a_max, + }, b @ RuntimeType::Bytes { + min: b_min, + max: b_max, + }], + ) if accepts(a, left) && accepts(b, right) => Ok(RuntimeType::Bytes { + min: a_min.checked_add(*b_min).ok_or(Error::InvalidArtifact)?, + max: a_max.checked_add(*b_max).ok_or(Error::InvalidArtifact)?, + }), + // No caller where proof crosses a fresh frame. This target's + // first source-function subset proves partial intrinsics only + // from literal offsets/operands, refusing all other cases. + ("core.integer.subtract", [left, right], [expected @ RuntimeType::Unsigned(_)]) + if accepts(expected, left) && accepts(expected, right) => + { + let left = unsigned_literal(&arguments[0])?; + let right = unsigned_literal(&arguments[1])?; + left.checked_sub(right).ok_or(Error::UnsupportedProgram)?; + Ok(expected.clone()) + } + ( + "core.bytes.slice", + [actual, RuntimeType::Unsigned(a), RuntimeType::Unsigned(b)], + [expected @ RuntimeType::Bytes { min, .. }], + ) if *a == u64::MAX && *b == u64::MAX && accepts(expected, actual) => { + let start = unsigned_literal(&arguments[1])?; + let end = unsigned_literal(&arguments[2])?; + if start > end || end > *min { + return Err(Error::UnsupportedProgram); + } + Ok(RuntimeType::Bytes { + min: 0, + max: end - start, + }) + } + _ => Err(Error::UnsupportedProgram), + } + } + _ => Err(Error::UnsupportedProgram), + } +} + +fn unsigned_literal(value: &Value) -> Result { + if text_field(value, "kind")? != "const" { + return Err(Error::UnsupportedProgram); + } + number(field(field(value, "value")?, "value")?) +} +fn check_predicate( + value: &Value, + scope: &Scope<'_>, + definitions: &BTreeMap>, + source: bool, + parser: &mut Parser<'_>, + depth: usize, +) -> Result<(), Error> { + parser.enter(depth)?; + exact_fields(value, &["kind", "op", "left", "right"])?; + require_text(value, "kind", "compare")?; + let left = infer( + field(value, "left")?, + scope, + definitions, + source, + parser, + depth + 1, + )?; + let right = infer( + field(value, "right")?, + scope, + definitions, + source, + parser, + depth + 1, + )?; + match (text_field(value, "op")?, left, right) { + ("==", RuntimeType::Bytes { .. }, RuntimeType::Bytes { .. }) => Ok(()), + ("==" | "<=", RuntimeType::Unsigned(left), RuntimeType::Unsigned(right)) + if left == right => + { + Ok(()) + } + _ => Err(Error::UnsupportedProgram), + } +} + +/// Prove the actual combined expression/predicate/call aperture before running +/// any instruction. Suffix height is intrinsic, independent of traversal order. +pub(crate) fn check_roots<'a>( + helpers: &BTreeMap, + expressions: impl IntoIterator, + predicates: impl IntoIterator, +) -> Result<(), Error> { + let mut heights = Heights { + helpers, + completed: BTreeMap::new(), + visiting: BTreeSet::new(), + remaining: MAX_PROGRAM_NODES, + }; + for name in helpers.keys() { + if heights.helper(name, 1)? + 1 > MAX_DEPTH { + return Err(Error::UnsupportedProgram); + } + } + for expression in expressions { + heights.expr(expression, 0)?; + } + for predicate in predicates { + heights.predicate(predicate, 0)?; + } + Ok(()) +} +struct Heights<'a> { + helpers: &'a BTreeMap, + completed: BTreeMap, + visiting: BTreeSet, + remaining: usize, +} +impl Heights<'_> { + fn enter(&mut self, depth: usize) -> Result<(), Error> { + if depth > MAX_DEPTH { + return Err(Error::UnsupportedProgram); + } + self.remaining = self + .remaining + .checked_sub(1) + .ok_or(Error::UnsupportedProgram)?; + Ok(()) + } + fn helper(&mut self, name: &str, prefix: usize) -> Result { + self.enter(prefix)?; + if self.visiting.contains(name) { + return Err(Error::InvalidArtifact); + } + if let Some(height) = self.completed.get(name) { + if prefix + height > MAX_DEPTH { + return Err(Error::UnsupportedProgram); + } + return Ok(*height); + } + let helper = self.helpers.get(name).ok_or(Error::UnsupportedProgram)?; + self.visiting.insert(name.to_owned()); + let mut height = type_height(&helper.ty); + for parameter in &helper.params { + height = height.max(type_height(¶meter.ty)); + } + for binding in &helper.bindings { + height = height + .max(self.expr(&binding.value, prefix)?) + .max(type_height(&binding.ty)); + } + height = height.max(self.expr(&helper.result, prefix)?); + if prefix + height > MAX_DEPTH { + return Err(Error::UnsupportedProgram); + } + self.visiting.remove(name); + self.completed.insert(name.to_owned(), height); + Ok(height) + } + fn expr(&mut self, expression: &Expr, depth: usize) -> Result { + self.enter(depth)?; + let height = match expression { + Expr::Constant(_) | Expr::Local(_) => 0, + Expr::Field(base, _) => self.expr(base, depth + 1)? + 1, + Expr::Record(fields) => { + let mut maximum = 0; + for (_, value) in fields { + maximum = maximum.max(self.expr(value, depth + 1)? + 1); + } + maximum + } + Expr::If(condition, yes, no) => { + self.predicate(condition, depth + 1)? + .max(self.expr(yes, depth + 1)?) + .max(self.expr(no, depth + 1)?) + + 1 + } + Expr::Call(name, args) => { + let helper = self.helpers.get(name).ok_or(Error::UnsupportedProgram)?; + if helper.params.len() != args.len() { + return Err(Error::InvalidArtifact); + } + if helper.source_owned + && self + .visiting + .iter() + .any(|owner| !self.helpers[owner].source_owned) + { + return Err(Error::InvalidArtifact); + } + let mut maximum = self.helper(name, depth + 1)?; + for argument in args { + maximum = maximum.max(self.expr(argument, depth + 1)?); + } + maximum + 1 + } + Expr::ByteLength { value, .. } => self.expr(value, depth + 1)? + 1, + Expr::ByteSlice { + value, start, end, .. + } => { + self.expr(value, depth + 1)? + .max(self.expr(start, depth + 1)?) + .max(self.expr(end, depth + 1)?) + + 1 + } + Expr::ByteConcat { left, right, .. } | Expr::UnsignedSubtract { left, right, .. } => { + self.expr(left, depth + 1)? + .max(self.expr(right, depth + 1)?) + + 1 + } + }; + if depth + height > MAX_DEPTH { + return Err(Error::UnsupportedProgram); + } + Ok(height) + } + fn predicate(&mut self, predicate: &Predicate, depth: usize) -> Result { + self.enter(depth)?; + let height = self + .expr(&predicate.left, depth + 1)? + .max(self.expr(&predicate.right, depth + 1)?) + + 1; + if depth + height > MAX_DEPTH { + return Err(Error::UnsupportedProgram); + } + Ok(height) + } +} +fn type_height(ty: &RuntimeType) -> usize { + match ty { + RuntimeType::Record(fields) => fields + .iter() + .map(|(_, ty)| type_height(ty) + 1) + .max() + .unwrap_or(0), + _ => 0, + } +} diff --git a/crates/warp-core/src/edict_pure/model.rs b/crates/warp-core/src/edict_pure/model.rs index 099c738b6..04a40b19f 100644 --- a/crates/warp-core/src/edict_pure/model.rs +++ b/crates/warp-core/src/edict_pure/model.rs @@ -17,7 +17,7 @@ pub(crate) enum Expr { Field(Box, String), Record(Vec<(String, Expr)>), If(Box, Box, Box), - Call(String), + Call(String, Vec), ByteLength { min: u64, max: u64, @@ -58,6 +58,7 @@ pub(crate) enum Comparison { LessOrEqual, } +#[derive(Clone, Debug, PartialEq, Eq)] pub(crate) enum RuntimeType { Unsigned(u64), Bytes { min: u64, max: u64 }, @@ -70,7 +71,15 @@ pub(crate) struct Binding { pub value: Expr, } +pub(crate) struct Parameter { + pub id: String, + pub ty: RuntimeType, +} + pub(crate) struct Helper { + pub source_owned: bool, + pub params: Vec, + pub bindings: Vec, pub result: Expr, pub ty: RuntimeType, } diff --git a/crates/warp-core/src/edict_pure/syntax.rs b/crates/warp-core/src/edict_pure/syntax.rs index 3d513cce0..02766c18b 100644 --- a/crates/warp-core/src/edict_pure/syntax.rs +++ b/crates/warp-core/src/edict_pure/syntax.rs @@ -15,7 +15,7 @@ pub(crate) struct Parser<'a> { } impl Parser<'_> { - fn enter(&mut self, depth: usize) -> Result<(), Error> { + pub(crate) fn enter(&mut self, depth: usize) -> Result<(), Error> { if depth > MAX_DEPTH { return Err(Error::UnsupportedProgram); } @@ -46,6 +46,7 @@ impl Parser<'_> { .unwrap_or(name); let definition = field(self.types, name)?; match text_field(definition, "kind")? { + "Int" => self.ty(text_field(definition, "width")?, depth + 1), "Nominal" => self.ty(text_field(definition, "representation")?, depth + 1), "Bytes" => { let min = field(definition, "min").map_or(Ok(0), number)?; @@ -196,10 +197,14 @@ impl Parser<'_> { right: Box::new(self.expr(right, depth + 1)?), }); } - if !args.is_empty() || !types.is_empty() { + if !types.is_empty() { return Err(Error::UnsupportedProgram); } - Ok(Expr::Call(callee.to_owned())) + let args = args + .iter() + .map(|value| self.expr(value, depth + 1)) + .collect::, _>>()?; + Ok(Expr::Call(callee.to_owned(), args)) } pub fn predicate(&mut self, value: &Value, depth: usize) -> Result { diff --git a/crates/warp-core/src/edict_read/decode.rs b/crates/warp-core/src/edict_read/decode.rs index bf6b64853..ac9302a83 100644 --- a/crates/warp-core/src/edict_read/decode.rs +++ b/crates/warp-core/src/edict_read/decode.rs @@ -152,6 +152,17 @@ pub(super) fn package(raw: &[u8], pin: [u8; 32], host: ReadLimits) -> Result = constraints.iter().map(|(_, predicate)| predicate).collect(); + for instruction in &ordered.ordered { + match instruction { + super::model::Instruction::Read(read) => expressions.push(&read.input), + super::model::Instruction::Let { value, .. } => expressions.push(value), + super::model::Instruction::Require { predicate, .. } => predicates.push(predicate), + } + } + crate::edict_pure::functions::check_roots(&helpers, expressions, predicates)?; Ok(Program { input_id, input_type, @@ -160,6 +171,7 @@ pub(super) fn package(raw: &[u8], pin: [u8; 32], host: ReadLimits) -> Result { - let address = expression(&read.input, &locals, &helpers, &mut meter, 0)?; + let address = expression(&read.input, &locals, helpers, &mut meter, 0)?; let (node, expected_type) = address_value(&address)?; if !view.contains(node) { return Err(ReadError::OutsideAperture(node)); @@ -72,18 +72,18 @@ pub(super) fn run( )?; let value = Value::Bytes(bytes.to_vec()); validate(&value, &read.ty, &mut meter, 0)?; - locals.insert(read.binding.clone(), value); + locals.insert(read.binding.as_str(), value); } Instruction::Let { binding, ty, value } => { - let value = expression(value, &locals, &helpers, &mut meter, 0)?; + let value = expression(value, &locals, helpers, &mut meter, 0)?; validate(&value, ty, &mut meter, 0)?; - locals.insert(binding.clone(), value); + locals.insert(binding.as_str(), value); } Instruction::Require { predicate: condition, obstruction, } => { - if !predicate(condition, &locals, &helpers, &mut meter, 0)? { + if !predicate(condition, &locals, helpers, &mut meter, 0)? { return Err(ReadError::Obstructed { coordinate: obstruction.clone(), cause: ReadObstruction::Guard, @@ -92,7 +92,7 @@ pub(super) fn run( } } } - let value = expression(&program.result, &locals, &helpers, &mut meter, 0)?; + let value = expression(&program.result, &locals, helpers, &mut meter, 0)?; validate(&value, &program.output_type, &mut meter, 0)?; meter.charge(&value, 0)?; let output = encode(&value).map_err(|_| Error::InvalidArtifact)?; diff --git a/crates/warp-core/src/edict_read/model.rs b/crates/warp-core/src/edict_read/model.rs index ef4ae76c7..adf9346b4 100644 --- a/crates/warp-core/src/edict_read/model.rs +++ b/crates/warp-core/src/edict_read/model.rs @@ -2,7 +2,8 @@ // © James Ross Ω FLYING•ROBOTS use super::ReadLimits; pub(super) use crate::edict_pure::model::MAX_ARTIFACT_BYTES; -use crate::edict_pure::model::{Expr, Predicate, RuntimeType}; +use crate::edict_pure::model::{Expr, Helper, Predicate, RuntimeType}; +use std::collections::BTreeMap; pub(super) struct ReadInstruction { pub binding: String, @@ -36,5 +37,6 @@ pub(super) struct Program { pub constraints: Vec<(String, Predicate)>, pub instructions: Vec, pub result: Expr, + pub helpers: BTreeMap, pub limits: ReadLimits, } diff --git a/crates/warp-core/tests/edict_source_functions_tests.rs b/crates/warp-core/tests/edict_source_functions_tests.rs new file mode 100644 index 000000000..d88c24d04 --- /dev/null +++ b/crates/warp-core/tests/edict_source_functions_tests.rs @@ -0,0 +1,1177 @@ +// SPDX-License-Identifier: Apache-2.0 +// © James Ross Ω FLYING•ROBOTS +//! Synthetic runtime boundary tests. Rebound pins are deliberately test-owned; +//! these are not public compiler, provider verification or release witnesses. +#![cfg(feature = "trusted_runtime")] +#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)] + +mod concat { + use echo_edict_canonical::{encode_canonical_cbor_v1 as encode, CanonicalValueV1 as Value}; + use warp_core::edict_pure::EvaluationLimits; + + pub fn package() -> Vec { + hex::decode( + include_str!("fixtures/edict-byte-concat/executable-operation-package.cbor.hex").trim(), + ) + .unwrap() + } + + pub fn limits() -> EvaluationLimits { + EvaluationLimits { + max_package_bytes: 32_768, + max_input_bytes: 2_097_152, + max_steps: 10_000, + max_allocated_bytes: 16_777_216, + max_output_bytes: 2_097_152, + } + } + + pub fn record(fields: impl IntoIterator) -> Value { + Value::Map( + fields + .into_iter() + .map(|(key, value)| (Value::Text(key.into()), value)) + .collect(), + ) + } + + pub fn input(left: &[u8], right: &[u8]) -> Vec { + encode(&record([ + ("firstFragment", Value::Bytes(left.into())), + ("secondFragment", Value::Bytes(right.into())), + ])) + .unwrap() + } + + pub fn field<'a>(value: &'a Value, name: &str) -> &'a Value { + let Value::Map(fields) = value else { + panic!("map") + }; + &fields + .iter() + .find(|(key, _)| key == &Value::Text(name.into())) + .unwrap() + .1 + } + + pub fn field_mut<'a>(value: &'a mut Value, name: &str) -> &'a mut Value { + let Value::Map(fields) = value else { + panic!("map") + }; + &mut fields + .iter_mut() + .find(|(key, _)| key == &Value::Text(name.into())) + .unwrap() + .1 + } + + pub fn set_field(value: &mut Value, name: &str, replacement: Value) { + *field_mut(value, name) = replacement; + } +} + +use concat::{field, field_mut, record, set_field}; +use echo_edict_canonical::{ + decode_canonical_cbor_v1 as decode, digest_canonical_value_bytes_v1 as digest, + encode_canonical_cbor_v1 as encode, CanonicalValueV1 as Value, +}; +use warp_core::edict_pure::{evaluate, EvaluationError}; + +fn text(value: &str) -> Value { + Value::Text(value.into()) +} +fn array(values: Vec) -> Value { + Value::Array(values) +} +fn insert(value: &mut Value, key: &str, replacement: Value) { + let Value::Map(entries) = value else { + panic!("map") + }; + assert!(!entries.iter().any(|(name, _)| name == &text(key))); + entries.push((text(key), replacement)); +} +fn local(id: &str, ty: &str) -> Value { + record([ + ("id", text(id)), + ("alphaName", text(id)), + ("type", text(ty)), + ]) +} +fn reference(local: &Value) -> Value { + record([("kind", text("local")), ("ref", local.clone())]) +} +fn call(callee: &str, args: Vec) -> Value { + record([ + ("kind", text("call")), + ("callee", text(callee)), + ("args", array(args)), + ("typeArgs", array(vec![])), + ]) +} +fn function( + params: Vec, + ty: &str, + locals: Vec, + bindings: Vec, + result: Value, +) -> Value { + record([ + ("params", array(params)), + ("returnType", text(ty)), + ( + "body", + record([ + ("locals", array(locals)), + ("bindings", array(bindings)), + ("result", result), + ]), + ), + ]) +} +fn embedded(program: &Value, name: &str) -> Value { + let Value::Bytes(bytes) = field(program, name) else { + panic!("artifact bytes") + }; + decode(bytes).unwrap() +} +fn rewrite( + raw: Vec, + edit: impl FnOnce(&mut Value, &mut Value, &mut Value), +) -> (Vec, [u8; 32]) { + let mut package = decode(&raw).unwrap(); + let mut program = embedded(&package, "program"); + let mut core = embedded(&program, "core_artifact"); + let mut target = embedded(&program, "target_ir_artifact"); + edit(&mut program, &mut core, &mut target); + let core_digest = digest("edict.core.module/v1", &core).unwrap(); + set_field( + field_mut(&mut package, "semantic_closure"), + "canonical_meaning_identity", + Value::Bytes(core_digest.into()), + ); + let core_ref = record([ + ("id", field(&core, "coordinate").clone()), + ( + "digest", + array(vec![text("sha256"), Value::Bytes(core_digest.into())]), + ), + ]); + set_field( + field_mut(&mut target, "semanticClosure"), + "sourceCore", + core_ref, + ); + for (key, closure, domain, value) in [ + ( + "core_artifact", + "core_identity", + "edict.core.module/v1", + core, + ), + ( + "target_ir_artifact", + "target_ir_identity", + "edict.target-ir.artifact/v1", + target, + ), + ] { + set_field(&mut program, key, Value::Bytes(encode(&value).unwrap())); + set_field( + field_mut(&mut package, "semantic_closure"), + closure, + Value::Bytes(digest(domain, &value).unwrap().into()), + ); + } + set_field( + &mut package, + "program", + Value::Bytes(encode(&program).unwrap()), + ); + let pin = digest("echo.operation-package/v1", &package).unwrap(); + (encode(&package).unwrap(), pin) +} +fn factored_concat() -> (Vec, [u8; 32]) { + rewrite(concat::package(), |_, core, target| { + let coordinate = match field(core, "coordinate") { + Value::Text(value) => value.clone(), + _ => panic!("coordinate"), + }; + let left = local("arg.0", "Bytes"); + let right = local("arg.1", "Bytes"); + let saved = local("local.0", "Bytes"); + let mut intrinsic = call( + "core.bytes.concat", + vec![reference(&left), reference(&right)], + ); + set_field( + &mut intrinsic, + "typeArgs", + array(vec![text("Bytes"), text("Bytes")]), + ); + let join = function( + vec![left.clone(), right.clone()], + "Bytes", + vec![], + vec![], + intrinsic, + ); + let binding = record([ + ("kind", text("let")), + ("binding", saved.clone()), + ( + "value", + call( + &format!("{coordinate}.join"), + vec![reference(&left), reference(&right)], + ), + ), + ]); + let assemble = function( + vec![left, right], + "Bytes", + vec![saved.clone()], + vec![binding], + reference(&saved), + ); + insert( + core, + "functions", + record([("assemble", assemble), ("join", join)]), + ); + for (artifact, bindings) in [(core, "nodes"), (target, "pureBindings")] { + let intent = field_mut(field_mut(artifact, "intents"), "assembleRange"); + let body = if bindings == "nodes" { + field_mut(intent, "body") + } else { + intent + }; + let Value::Array(nodes) = field_mut(body, bindings) else { + panic!("bindings") + }; + let args = match field(field(&nodes[0], "value"), "args") { + Value::Array(args) => args.clone(), + _ => panic!("args"), + }; + set_field( + &mut nodes[0], + "value", + call(&format!("{coordinate}.assemble"), args), + ); + } + }) +} + +#[test] +fn source_functions_named_int_alias_executes_in_a_helper_signature() { + let (original, _) = factored_concat(); + let (package, pin) = rewrite(original, |_, core, _| { + let coordinate = match field(core, "coordinate") { + Value::Text(value) => value.clone(), + _ => panic!("coordinate"), + }; + insert( + field_mut(core, "types"), + "Count", + record([("kind", text("Int")), ("width", text("U64"))]), + ); + let parameter = local("arg.0", "Count"); + insert( + field_mut(core, "functions"), + "identityCount", + function( + vec![parameter.clone()], + "Count", + vec![], + vec![], + reference(¶meter), + ), + ); + let saved = local("local.count", "Count"); + let value = record([ + ("kind", text("const")), + ( + "value", + record([ + ("kind", text("int")), + ("width", text("U64")), + ("value", Value::Integer(7)), + ]), + ), + ]); + let body = field_mut(field_mut(field_mut(core, "functions"), "join"), "body"); + set_field(body, "locals", array(vec![saved.clone()])); + set_field( + body, + "bindings", + array(vec![record([ + ("kind", text("let")), + ("binding", saved), + ( + "value", + call(&format!("{coordinate}.identityCount"), vec![value]), + ), + ])]), + ); + }); + let result = evaluate( + &package, + pin, + &concat::input(b"ab", b"cd"), + concat::limits(), + ) + .expect("named Int parameter/result must decode and execute"); + assert_eq!( + decode(&result.output).unwrap(), + record([("bytes", Value::Bytes(b"abcd".to_vec()))]) + ); +} + +#[test] +fn source_functions_nested_forward_calls_and_bindings_use_fresh_parameter_frames() { + let (package, pin) = factored_concat(); + for (left, right) in [ + (b"".as_slice(), b"".as_slice()), + (b"ab", b"cd"), + (b"cd", b"ab"), + (&[0, 255], &[128, 127]), + ] { + let result = + evaluate(&package, pin, &concat::input(left, right), concat::limits()).unwrap(); + let expected = [left, right].concat(); + assert_eq!( + result.output, + encode(&record([("bytes", Value::Bytes(expected))])).unwrap() + ); + } +} + +#[test] +fn source_functions_nested_frames_share_the_same_cumulative_meter() { + let (package, pin) = factored_concat(); + let supplied = concat::input(b"ab", b"cde"); + let result = evaluate(&package, pin, &supplied, concat::limits()).unwrap(); + let mut exact = concat::limits(); + exact.max_steps = result.steps; + exact.max_allocated_bytes = result.allocated_bytes; + assert_eq!(evaluate(&package, pin, &supplied, exact).unwrap(), result); + let mut short = exact; + short.max_steps -= 1; + assert_eq!( + evaluate(&package, pin, &supplied, short), + Err(EvaluationError::StepBudgetExceeded) + ); + short = exact; + short.max_allocated_bytes -= 1; + assert_eq!( + evaluate(&package, pin, &supplied, short), + Err(EvaluationError::AllocationBudgetExceeded) + ); +} + +#[test] +fn source_functions_unused_malformed_definition_is_not_silently_ignored() { + let (package, pin) = rewrite(concat::package(), |_, core, _| { + insert(core, "functions", record([("unused", Value::Null)])); + }); + assert!( + evaluate(&package, pin, &concat::input(b"a", b"b"), concat::limits()).is_err(), + "runtime ignored authenticated malformed source authority" + ); +} + +#[test] +fn source_functions_bounded_read_passes_a_real_retained_atom_to_a_helper() { + use warp_core::edict_read::{evaluate as read, ReadBasis, ReadLimits, ReadView}; + use warp_core::{ + make_node_id, make_type_id, AtomPayload, AttachmentValue, GraphStore, NodeKey, NodeRecord, + WorldlineFrontier, WorldlineId, WorldlineState, + }; + let original = hex::decode( + include_str!("fixtures/edict-node-read/single-executable-operation-package.hex").trim(), + ) + .unwrap(); + let (package, pin) = rewrite(original, |program, core, target| { + let coordinate = match field(core, "coordinate") { + Value::Text(value) => value.clone(), + _ => panic!("coordinate"), + }; + let output_type = match field(field(field(core, "intents"), "replaceRange"), "output") { + Value::Text(value) => value.clone(), + _ => panic!("output"), + }; + let parameter = local("arg.0", &output_type); + insert( + core, + "functions", + record([( + "retain", + function( + vec![parameter.clone()], + &output_type, + vec![], + vec![], + reference(¶meter), + ), + )]), + ); + let saved = local("source.return", &output_type); + let body = field_mut( + field_mut(field_mut(core, "intents"), "replaceRange"), + "body", + ); + let helper = call( + &format!("{coordinate}.retain"), + vec![field(body, "result").clone()], + ); + let Value::Array(locals) = field_mut(body, "locals") else { + panic!("locals") + }; + locals.push(saved.clone()); + let Value::Array(nodes) = field_mut(body, "nodes") else { + panic!("nodes") + }; + nodes.push(record([ + ("kind", text("let")), + ("binding", saved.clone()), + ("value", helper.clone()), + ])); + set_field(body, "result", reference(&saved)); + let intent = field_mut(field_mut(target, "intents"), "replaceRange"); + let binding = record([ + ("id", text("replaceRange.binding.source")), + ("binding", saved.clone()), + ("value", helper), + ]); + if let Value::Map(entries) = intent { + if let Some((_, Value::Array(bindings))) = entries + .iter_mut() + .find(|(key, _)| key == &text("pureBindings")) + { + bindings.push(binding); + } else { + entries.push((text("pureBindings"), array(vec![binding]))); + } + } + let Value::Array(order) = field_mut(intent, "executionOrder") else { + panic!("order") + }; + order.push(text("replaceRange.binding.source")); + set_field(intent, "result", reference(&saved)); + let mut projection = embedded(program, "result_projection_artifact"); + set_field( + &mut projection, + "expression", + record([ + ("kind", text("source")), + ( + "source", + record([ + ("kind", text("pureBinding")), + ("bindingId", text("replaceRange.binding.source")), + ]), + ), + ("path", array(vec![])), + ]), + ); + set_field( + program, + "result_projection_artifact", + Value::Bytes(encode(&projection).unwrap()), + ); + }); + let mut store = GraphStore::default(); + let node = make_node_id("source-function-read"); + store.insert_node( + node, + NodeRecord { + ty: make_type_id("node"), + }, + ); + store.set_node_attachment( + node, + Some(AttachmentValue::Atom(AtomPayload { + type_id: make_type_id("document"), + bytes: b"alpha".to_vec().into(), + })), + ); + let key = NodeKey { + warp_id: store.warp_id(), + local_id: node, + }; + let state = WorldlineState::from_root_store(store, node).unwrap(); + let frontier = WorldlineFrontier::new(WorldlineId::from_bytes([7; 32]), state); + let before = frontier.state().state_root(); + let basis = ReadBasis::at(&frontier); + let aperture = [key]; + let view = ReadView::new(&frontier, basis, &aperture).unwrap(); + let supplied = encode(&record([ + ( + "address", + record([ + ("warpId", Value::Bytes(key.warp_id.0.to_vec())), + ("nodeId", Value::Bytes(key.local_id.0.to_vec())), + ("typeId", Value::Bytes(make_type_id("document").0.to_vec())), + ]), + ), + ("expected", Value::Bytes(b"alpha".to_vec())), + ])) + .unwrap(); + let limits = ReadLimits { + evaluation: concat::limits(), + max_reads: 1, + max_read_bytes: 5, + }; + let result = read(&package, pin, &supplied, &view, limits).unwrap(); + assert_eq!( + decode(&result.output).unwrap(), + Value::Bytes(b"alpha".to_vec()) + ); + assert_eq!((result.reads, result.read_bytes), (1, 5)); + assert_eq!(result.basis, basis); + assert_eq!(frontier.state().state_root(), before); +} + +#[test] +fn source_functions_complete_graph_depth_counts_every_shared_suffix_in_both_orders() { + for count in [64, 65] { + for shallow_first in [false, true] { + let (package, pin) = rewrite(concat::package(), |_, core, _| { + let Value::Text(coordinate) = field(core, "coordinate") else { + panic!("coordinate") + }; + let coordinate = coordinate.clone(); + let name = |index: usize| { + format!( + "f{:03}", + if shallow_first { + count - 1 - index + } else { + index + } + ) + }; + let mut functions = Vec::new(); + for index in 0..count { + let result = if index + 1 == count { + record([ + ("kind", text("const")), + ( + "value", + record([ + ("kind", text("int")), + ("width", text("U64")), + ("value", Value::Integer(7)), + ]), + ), + ]) + } else { + call(&format!("{coordinate}.{}", name(index + 1)), vec![]) + }; + functions.push(( + text(&name(index)), + function(vec![], "U64", vec![], vec![], result), + )); + } + insert(core, "functions", Value::Map(functions)); + }); + let result = evaluate(&package, pin, &concat::input(b"a", b"b"), concat::limits()); + if count == 64 { + assert!( + result.is_ok(), + "exact depth, shallow first {shallow_first}: {result:?}" + ); + } else { + assert_eq!( + result, + Err(EvaluationError::UnsupportedProgram), + "over depth, shallow first {shallow_first}" + ); + } + } + } +} + +#[test] +fn source_functions_unused_definition_cannot_collide_with_an_intent() { + let (package, pin) = rewrite(concat::package(), |_, core, _| { + let result = record([ + ("kind", text("const")), + ( + "value", + record([ + ("kind", text("int")), + ("width", text("U64")), + ("value", Value::Integer(7)), + ]), + ), + ]); + insert( + core, + "functions", + record([( + "assembleRange", + function(vec![], "U64", vec![], vec![], result), + )]), + ); + }); + assert!( + evaluate(&package, pin, &concat::input(b"a", b"b"), concat::limits()).is_err(), + "a source function and intent cannot share one module member" + ); +} + +// The expected counters below are derived from the public fixed-cell meter: +// input materialization S = 347 + L + R; each field selection copies S; +// repeat(N) costs 18 + 2*N steps and S + 192 + 4*N storage. The unused +// second parameter must still pay for its argument exactly once. +fn metered_arguments(reverse: bool, lazy: Option) -> (Vec, [u8; 32]) { + rewrite(concat::package(), |_, core, target| { + let Value::Text(coordinate) = field(core, "coordinate") else { + panic!("coordinate") + }; + let coordinate = coordinate.clone(); + let value = local("arg.0", "Bytes"); + let left = local("arg.0", "Bytes"); + let right = local("arg.1", "Bytes"); + let mut duplicate = call( + "core.bytes.concat", + vec![reference(&value), reference(&value)], + ); + set_field( + &mut duplicate, + "typeArgs", + array(vec![text("Bytes"), text("Bytes")]), + ); + insert( + core, + "functions", + record([ + ( + "repeat", + function(vec![value], "Bytes", vec![], vec![], duplicate), + ), + ( + "first", + function( + vec![left.clone(), right], + "Bytes", + vec![], + vec![], + reference(&left), + ), + ), + ]), + ); + for (artifact, bindings) in [(core, "nodes"), (target, "pureBindings")] { + let intent = field_mut(field_mut(artifact, "intents"), "assembleRange"); + let body = if bindings == "nodes" { + field_mut(intent, "body") + } else { + intent + }; + let Value::Array(nodes) = field_mut(body, bindings) else { + panic!("bindings") + }; + let Value::Array(original) = field(field(&nodes[0], "value"), "args") else { + panic!("args") + }; + let repeat = |argument| call(&format!("{coordinate}.repeat"), vec![argument]); + let first = match lazy { + None => repeat(original[0].clone()), + Some(expensive_else) => { + let predicate = record([ + ("kind", text("compare")), + ("op", text("==")), + ("left", original[0].clone()), + ("right", original[1].clone()), + ]); + let cheap = original[0].clone(); + let costly = repeat(cheap.clone()); + let (yes, no) = if expensive_else { + (cheap, costly) + } else { + (costly, cheap) + }; + record([ + ("kind", text("if")), + ("predicate", predicate), + ("then", yes), + ("else", no), + ]) + } + }; + let mut args = vec![first, repeat(original[1].clone())]; + if reverse { + args.reverse(); + } + set_field( + &mut nodes[0], + "value", + call(&format!("{coordinate}.first"), args), + ); + } + }) +} + +#[test] +fn source_functions_evaluate_used_and_unused_arguments_once_with_fixed_cost_oracles() { + let (package, pin) = metered_arguments(false, None); + for (left, right) in [ + (b"".as_slice(), b"".as_slice()), + (b"ab", b"cde"), + (b"12345678", b"12345678"), + ] { + let (l, r) = (left.len() as u64, right.len() as u64); + let mut exact = concat::limits(); + exact.max_steps = 59 + 2 * (l + r); + exact.max_allocated_bytes = 1883 + 13 * l + 7 * r; + let input = concat::input(left, right); + let result = + evaluate(&package, pin, &input, exact).expect("independently calculated exact budget"); + assert_eq!( + (result.steps, result.allocated_bytes), + (exact.max_steps, exact.max_allocated_bytes) + ); + assert_eq!( + decode(&result.output).unwrap(), + record([("bytes", Value::Bytes([left, left].concat()))]) + ); + let mut short = exact; + short.max_steps -= 1; + assert_eq!( + evaluate(&package, pin, &input, short), + Err(EvaluationError::StepBudgetExceeded) + ); + short = exact; + short.max_allocated_bytes -= 1; + assert_eq!( + evaluate(&package, pin, &input, short), + Err(EvaluationError::AllocationBudgetExceeded) + ); + } +} + +#[test] +fn source_functions_argument_order_is_observable_through_distinct_budget_refusals() { + let mut limits = concat::limits(); + limits.max_steps = 30; + limits.max_allocated_bytes = 890; + let input = concat::input(b"12345678", b""); + let (package, pin) = metered_arguments(false, None); + assert_eq!( + evaluate(&package, pin, &input, limits), + Err(EvaluationError::StepBudgetExceeded) + ); + let (package, pin) = metered_arguments(true, None); + assert_eq!( + evaluate(&package, pin, &input, limits), + Err(EvaluationError::AllocationBudgetExceeded) + ); +} + +#[test] +fn source_functions_only_the_selected_valid_branch_spends_execution_budget() { + let input = concat::input(b"12345678", b""); + for expensive_else in [false, true] { + let (package, pin) = metered_arguments(false, Some(expensive_else)); + let mut exact = concat::limits(); + exact.max_steps = if expensive_else { 99 } else { 72 }; + exact.max_allocated_bytes = if expensive_else { 2697 } else { 2449 }; + let result = evaluate(&package, pin, &input, exact).expect("selected branch exact budget"); + assert_eq!( + (result.steps, result.allocated_bytes), + (exact.max_steps, exact.max_allocated_bytes) + ); + let expected = if expensive_else { + b"1234567812345678".as_slice() + } else { + b"12345678".as_slice() + }; + assert_eq!( + decode(&result.output).unwrap(), + record([("bytes", Value::Bytes(expected.to_vec()))]) + ); + } +} + +#[test] +fn source_functions_executed_call_depth_includes_surrounding_predicate_frames() { + for count in [62, 63] { + for shallow_first in [false, true] { + let (package, pin) = rewrite(concat::package(), |_, core, target| { + let Value::Text(coordinate) = field(core, "coordinate") else { + panic!("coordinate") + }; + let coordinate = coordinate.clone(); + let name = |index: usize| { + format!( + "f{:03}", + if shallow_first { + count - 1 - index + } else { + index + } + ) + }; + let one = record([ + ("kind", text("const")), + ( + "value", + record([ + ("kind", text("int")), + ("width", text("U64")), + ("value", Value::Integer(1)), + ]), + ), + ]); + let functions = (0..count) + .map(|index| { + let result = if index + 1 == count { + one.clone() + } else { + call(&format!("{coordinate}.{}", name(index + 1)), vec![]) + }; + ( + text(&name(index)), + function(vec![], "U64", vec![], vec![], result), + ) + }) + .collect(); + insert(core, "functions", Value::Map(functions)); + for (artifact, key) in [(core, "nodes"), (target, "pureBindings")] { + let intent = field_mut(field_mut(artifact, "intents"), "assembleRange"); + let body = if key == "nodes" { + field_mut(intent, "body") + } else { + intent + }; + let Value::Array(nodes) = field_mut(body, key) else { + panic!("nodes") + }; + let original = field(&nodes[0], "value").clone(); + let predicate = record([ + ("kind", text("compare")), + ("op", text("==")), + ("left", call(&format!("{coordinate}.{}", name(0)), vec![])), + ("right", one.clone()), + ]); + set_field( + &mut nodes[0], + "value", + record([ + ("kind", text("if")), + ("predicate", predicate), + ("then", original.clone()), + ("else", original), + ]), + ); + } + }); + let result = evaluate(&package, pin, &concat::input(b"ab", b"c"), concat::limits()); + if count == 62 { + let result = result.expect( + "62 function frames plus two surrounding predicate frames execute at 64", + ); + assert_eq!( + decode(&result.output).unwrap(), + record([("bytes", Value::Bytes(b"abc".to_vec()))]) + ); + } else { + assert_eq!( + result, + Err(EvaluationError::UnsupportedProgram), + "65 total frames, shallow first {shallow_first}" + ); + } + } + } +} + +#[test] +fn source_functions_pure_input_uses_arg_zero_identity_after_declaration_reordering() { + let (original, _) = factored_concat(); + let (package, pin) = rewrite(original, |_, core, _| { + let body = field_mut( + field_mut(field_mut(core, "intents"), "assembleRange"), + "body", + ); + let Value::Array(locals) = field_mut(body, "locals") else { + panic!("locals") + }; + assert!(locals.len() > 1); + locals.reverse(); + }); + let result = evaluate( + &package, + pin, + &concat::input(b"ab", b"cd"), + concat::limits(), + ) + .expect("provider-admitted declaration inventory order"); + assert_eq!( + decode(&result.output).unwrap(), + record([("bytes", Value::Bytes(b"abcd".to_vec()))]) + ); +} + +#[test] +fn source_functions_pure_basis_is_admitted_but_does_not_spend_execution_budget() { + let (baseline, baseline_pin) = factored_concat(); + let input = concat::input(b"ab", b"cd"); + let expected = evaluate(&baseline, baseline_pin, &input, concat::limits()).unwrap(); + for case in ["none", "valid", "null", "unknown", "arity", "capture"] { + let (package, pin) = rewrite(baseline.clone(), |_, core, target| { + let Value::Text(coordinate) = field(core, "coordinate") else { + panic!("coordinate") + }; + let coordinate = coordinate.clone(); + let one = record([ + ("kind", text("const")), + ( + "value", + record([ + ("kind", text("int")), + ("width", text("U64")), + ("value", Value::Integer(1)), + ]), + ), + ]); + insert( + field_mut(core, "functions"), + "basisValue", + function(vec![], "U64", vec![], vec![], one.clone()), + ); + let expression = match case { + "none" | "null" => Value::Null, + "valid" => call(&format!("{coordinate}.basisValue"), vec![]), + "unknown" => call(&format!("{coordinate}.missing"), vec![]), + "arity" => call(&format!("{coordinate}.basisValue"), vec![one]), + "capture" => reference(&local("local.0", "Bytes")), + _ => panic!("case"), + }; + if case != "none" { + for artifact in [core, target] { + insert( + field_mut(field_mut(artifact, "intents"), "assembleRange"), + "basis", + expression.clone(), + ); + } + } + }); + let result = evaluate(&package, pin, &input, concat::limits()); + if ["none", "valid"].contains(&case) { + assert_eq!(result.unwrap(), expected); + } else { + assert!(result.is_err(), "invalid basis {case} was ignored"); + } + } +} + +mod effect_name_collision { + use super::*; + use warp_core::edict_read::{evaluate as read, ReadBasis, ReadError, ReadLimits, ReadView}; + use warp_core::{ + make_node_id, make_type_id, AtomPayload, AttachmentValue, GraphStore, NodeKey, NodeRecord, + WorldlineFrontier, WorldlineId, WorldlineState, + }; + + fn rename(value: &mut Value, previous: &str, coordinate: &str) { + match value { + Value::Text(name) => { + if name.as_str() == previous { + coordinate.clone_into(name); + } else if let Some(member) = name.strip_prefix(&format!("{previous}.")) { + *name = format!("{coordinate}.{member}"); + } + } + Value::Array(values) => { + for value in values { + rename(value, previous, coordinate); + } + } + Value::Map(entries) => { + for (key, value) in entries { + rename(key, previous, coordinate); + rename(value, previous, coordinate); + } + } + _ => {} + } + } + + fn package(collision: bool) -> (Vec, [u8; 32]) { + let original = hex::decode( + include_str!("fixtures/edict-node-read/single-executable-operation-package.hex").trim(), + ) + .unwrap(); + let (raw, _) = rewrite(original, |program, core, target| { + let exports = embedded(program, "lawpack_exports_artifact"); + let Value::Array(effects) = field(&exports, "effects") else { + panic!("effects") + }; + assert_eq!( + field(&effects[0], "coordinate"), + &text("jedit.text@1.readFact") + ); + let Value::Text(previous) = field(core, "coordinate") else { + panic!("coordinate") + }; + let previous = previous.clone(); + let coordinate = "jedit.text@1"; + for value in [&mut *core, target] { + rename(value, &previous, coordinate); + } + // The admitted synthetic Core uses unique module-relative type keys; + // qualified type references and imported authority remain unchanged. + let Value::Map(types) = field_mut(core, "types") else { + panic!("types") + }; + let prefix = format!("{coordinate}."); + let mut names = std::collections::BTreeSet::new(); + for (key, definition) in types { + let Value::Text(name) = key else { + panic!("type name") + }; + if let Some(relative) = name.strip_prefix(&prefix) { + assert_ne!(field(definition, "kind"), &text("Nominal")); + *name = relative.to_owned(); + } + assert!(names.insert(name.clone())); + } + let name = if collision { "readFact" } else { "unusedRead" }; + let result = record([ + ("kind", text("const")), + ( + "value", + record([ + ("kind", text("int")), + ("width", text("U64")), + ("value", Value::Integer(0)), + ]), + ), + ]); + insert( + core, + "functions", + record([(name, function(vec![], "U64", vec![], vec![], result))]), + ); + let mut projection = embedded(program, "result_projection_artifact"); + rename(&mut projection, &previous, coordinate); + set_field( + program, + "result_projection_artifact", + Value::Bytes(encode(&projection).unwrap()), + ); + let Value::Bytes(source) = embedded(program, "source_artifact") else { + panic!("source") + }; + let mut source = String::from_utf8(source).unwrap(); + let header = format!("package {previous};"); + assert_eq!(source.matches(&header).count(), 1); + source = source.replace(&header, &format!("package {coordinate};")); + assert_eq!(source.matches("intent replaceRange(").count(), 1); + source = source.replace( + "intent replaceRange(", + &format!("fn {name}() -> U64 {{ return 0u64; }}\nintent replaceRange("), + ); + set_field( + program, + "source_artifact", + Value::Bytes(encode(&Value::Bytes(source.into_bytes())).unwrap()), + ); + }); + let mut package = decode(&raw).unwrap(); + let program = embedded(&package, "program"); + let source = embedded(&program, "source_artifact"); + set_field( + &mut package, + "operation_coordinate", + text("jedit.text@1.replaceRange"), + ); + set_field( + field_mut(&mut package, "semantic_closure"), + "edict_source_identity", + Value::Bytes(digest("edict.source/v1", &source).unwrap().into()), + ); + let pin = digest("echo.operation-package/v1", &package).unwrap(); + (encode(&package).unwrap(), pin) + } + + #[test] + fn runtime_refuses_an_unused_source_function_named_for_an_imported_effect() { + let mut store = GraphStore::default(); + let node = make_node_id("effect-name-control"); + store.insert_node( + node, + NodeRecord { + ty: make_type_id("node"), + }, + ); + store.set_node_attachment( + node, + Some(AttachmentValue::Atom(AtomPayload { + type_id: make_type_id("document"), + bytes: b"alpha".to_vec().into(), + })), + ); + let key = NodeKey { + warp_id: store.warp_id(), + local_id: node, + }; + let frontier = WorldlineFrontier::new( + WorldlineId::from_bytes([7; 32]), + WorldlineState::from_root_store(store, node).unwrap(), + ); + let before = frontier.state().state_root(); + let basis = ReadBasis::at(&frontier); + let aperture = [key]; + let view = ReadView::new(&frontier, basis, &aperture).unwrap(); + let supplied = encode(&record([ + ( + "address", + record([ + ("warpId", Value::Bytes(key.warp_id.0.to_vec())), + ("nodeId", Value::Bytes(key.local_id.0.to_vec())), + ("typeId", Value::Bytes(make_type_id("document").0.to_vec())), + ]), + ), + ("expected", Value::Bytes(b"alpha".to_vec())), + ])) + .unwrap(); + let limits = ReadLimits { + evaluation: concat::limits(), + max_reads: 1, + max_read_bytes: 5, + }; + let (positive, pin) = package(false); + let result = read(&positive, pin, &supplied, &view, limits) + .expect("disjoint unused function admits"); + assert_eq!( + decode(&result.output).unwrap(), + Value::Bytes(b"alpha".to_vec()) + ); + assert_eq!((result.reads, result.read_bytes), (1, 5)); + assert_eq!(result.basis, basis); + let (negative, pin) = package(true); + let positive_program = embedded(&decode(&positive).unwrap(), "program"); + let negative_program = embedded(&decode(&negative).unwrap(), "program"); + for name in [ + "lawpack_exports_artifact", + "lawpack_adapter_artifact", + "lawpack_artifact", + "target_configuration_artifact", + "result_projection_artifact", + ] { + assert_eq!( + field(&positive_program, name), + field(&negative_program, name) + ); + } + assert_eq!( + read(&negative, pin, &supplied, &view, limits), + Err(ReadError::Evaluation(EvaluationError::InvalidArtifact)) + ); + assert_eq!(frontier.state().state_root(), before); + } +} diff --git a/docs/architecture/application-contract-hosting.md b/docs/architecture/application-contract-hosting.md index f272e5552..6df22056f 100644 --- a/docs/architecture/application-contract-hosting.md +++ b/docs/architecture/application-contract-hosting.md @@ -189,14 +189,15 @@ clock, native callback, or WAL access and produces no Tick or Receipt. The interpreter implements the generic subset demanded by the first real compiler witness: unsigned integer constants, records, locals, field access, integer equality and ordering, bounded byte equality, lazy conditionals, and -zero-argument authored pure helpers, plus `core.integer.subtract` with -two operands. Subtraction +source-owned pure functions with ordered parameters and local bindings, +separately authenticated zero-argument imported helpers, and +`core.integer.subtract` with two operands. Subtraction validates both values against the declared unsigned width and refuses underflow with `InvalidArtifact`; it never wraps or saturates. Operands and the result use the ordinary execution meter. The compiler and target verifier own static totality evidence; the interpreter additionally checks runtime values even when -the supplied package pin matches. Calls resolve opaque lawpack coordinates to retained Edict bodies -with separate lexical scope. No application coordinate selects a native +the supplied package pin matches. Calls resolve opaque source or lawpack +coordinates to retained Edict bodies with separate lexical scope. No application coordinate selects a native implementation. Unsupported expression forms are rejected during decoding, including unselected branches. Authored runtime types constrain input, bindings, helper returns, and output. Nominal contracts resolve their declared @@ -255,6 +256,88 @@ the canonical decoder's node limit, and a 64-level interpreter depth limit. Syntax and type expansion share a 65,536-node decode budget. Decode and code storage are bounded by those admission apertures, outside execution accounting. +Source functions remain authoritative in `Core.functions`; Target IR retains +calls to their fully qualified coordinates. Both pure and bounded-read providers +validate the complete source/imported function closure, including unused +functions. The lowerer derives a forward judgment, while the independent +verifier audits dependency summaries and lexical frames before checking the +Core/Target relation. Rebinding artifact hashes does not establish that relation. +Unknown or colliding function names, wrong argument types or arity, generic +calls, cycles, undeclared or duplicate locals, forward local references, and +caller-local captures reject. Source function coordinates must be disjoint from +both authenticated imported pure-function and effect exports, including unused +source definitions. Reserving an effect name does not permit calling it from a +pure helper. Imported functions cannot refer back to source-owned functions. +The imported subset remains zero-argument bodies without local bindings. +Source-call ownership requires exact membership in `Core.functions`. A shared +package prefix alone does not make an imported pure function source-owned. +Calls must also satisfy the type-resolution aperture: same-prefix named types +currently resolve through module-relative `Core.types` keys; fully qualified +imported type keys under that prefix remain a provider/runtime compatibility +limitation. + +Each call evaluates every argument once, from left to right in the caller's +frame, including unused arguments. Validated argument values move into a fresh +parameter-only frame. Ordered local bindings and the return value are validated +there. Parameter reads incur their ordinary copy charges. The operation shares +one cumulative execution meter across arguments, nested calls, bindings and +returns; returning from a frame does not refund allocation charges. Conditional +branches are checked during admission, but only the selected branch executes. +Read instructions obtain opaque atom bytes before passing ordinary values to a +pure helper; helpers gain no read or mutation authority. + +The pure profile retains an optional authored basis and checks any present +expression under the input-only scope, including call signatures and combined +depth. `basis none` omits the canonical field; a present null rejects. Private +pure evaluation does not resolve, execute or charge that basis, so valid basis +helpers do not change its result or runtime counters. The bounded-read profile +executes and charges its basis expression as part of the operation. This is a +target-profile distinction, not permission to retain malformed basis authority. + +The source-function target subset supports unsigned words, bounded bytes, +records and nominal types, with U32/U64 integer literals. Provider compatibility +retains each nominal type's contract identity, which must match its resolved +Core type-table key. Equal representations do not permit assignment, arguments, +returns or comparisons across distinct nominal identities, or between a nominal +type and its representation. Same-nominal byte and unsigned-word comparisons +use the ordinary representation costs. Named `Int` definitions resolve through +their declared unsigned width in both provider judgments and the shared +pure/read runtime type parser, under its existing depth and work limits. +Runtime values carry no additional +nominal tag; materialization, validation and encoding bounds remain based on +the representation. Both bounded-read provider judgments preserve nominal identity even when the optional source-function table is absent; basis and address roles inspect physical representation only at their explicit role checks. Boolean, string, list, variant and effectful helper forms +receive provider refusal. +For modules containing source functions, partial subtraction and slicing need +literal evidence sufficient for totality: ordered unsigned operands, or ordered +U64 slice offsets within the operand's guaranteed minimum length. Caller +`where` constraints are not inherited into helper frames. Function-free legacy +fixtures retain their existing admission and runtime behavior. + +Source-function admission derives conservative bounds for the whole operation: +input validation and materialization, every argument and call occurrence, +ordered bindings, parameter and return checks, predicate and byte work, +bounded reads, output validation, and encoding scratch. Branch bounds take +the maximum after predicate work. Checked arithmetic rejects overflow, and +shared dependency summaries do not deduplicate repeated execution. A declared +step, allocation or output ceiling below the derived bound rejects before +package acceptance; tighter host ceilings can still refuse an evaluation. + +The combined interpreter aperture is 64 levels across surrounding expressions, +predicates and calls, including nested parameter, binding and return validation. +Admission checks every source and imported suffix independently of traversal +order. This is stricter than Edict's separate 128-source-helper compiler limit; +that compiler limit does not promise 128-frame Echo execution. Syntax and type +expansion remain bounded separately from runtime value materialization. + +The native +[`edict_source_functions_tests.rs`](../../crates/warp-core/tests/edict_source_functions_tests.rs) +uses deliberately rebound test-owned packages to check fresh frames, argument +order and single evaluation, cumulative fixed-cost oracles, lazy branches, +combined depth, malformed authority, and a real stored atom passed to a helper. +These synthetic boundary controls establish no public compiler or release +provenance by themselves. Its Cargo target requires `trusted_runtime`; CI and +local affected-test routing select the same feature explicitly. + The executable witness is [`edict_pure_evaluation_tests.rs`](../../crates/warp-core/tests/edict_pure_evaluation_tests.rs). It consumes retained exact external compiler output, checks both authored @@ -264,6 +347,8 @@ reports and reproduction coordinates. Reversed input ordering, invalid runtime representations, package substitution, noncanonical input, and exhausted host budgets produce errors without returning an application result. +The guarded public source-function consumer in `xtask/examples/source_functions_public_runtime.rs` selects the complete compiler evidence by an externally supplied SHA-256. It binds the original literal cases and exact repeated package/report bytes to that selection, checks verifier approval and artifact-domain bindings, and consumes the same owned byte vectors through the public pure and immutable-read interpreters. The read witness selects an explicit frontier and aperture and checks unchanged state. These results establish private computation and reading evidence; they do not claim installation, a decided Tick, a causal Receipt, or physical power-loss durability. + The paired nominal-ID and variable-payload equality witnesses in [`edict_byte_equality_tests.rs`](../../crates/warp-core/tests/edict_byte_equality_tests.rs) consume separately verified packages built from Jim-owned source using the @@ -344,9 +429,12 @@ selects the original publication. The native lowerer source additionally recognizes the opt-in `compiler-produced-bounded-read/v1` configuration. This separate profile carries explicit read-count and aggregate read-byte ceilings; it does not reinterpret -pure packages as effectful programs. Its current expression subset covers -locals, record construction/selection, byte equality, and unsigned equality or -ordering guards. Reads select opaque atom bytes by WARP/node/expected-type IDs. +pure packages as effectful programs. Its expression subset includes typed +source/imported helper calls, U32/U64 constants, lazy conditionals, byte length +and concatenation, locals, record construction/selection, byte equality, and +unsigned equality or ordering guards. The source-function totality and resource +rules above also apply to this route. Reads select opaque atom bytes by +WARP/node/expected-type IDs. Core and ordered Target IR must agree on every producer, guard, failure mapping, and result, with exact imported read signatures and scoped local identities. The package retains source, Core, Target IR, exports, lawpack, adapter, @@ -515,7 +603,7 @@ directory capabilities and reports drift without repair. This distribution copy is a release occurrence, not a second semantic authority. Native readiness is a later, independent crossing pinned to Edict revision -`c75c3f550d049485ba00eae0dc272c6dd6aca11f`. The exact manifest constructs the +`2e3f52f9e6d615f96eb594a40126e223a9253d98`. The exact manifest constructs the immutable schema registry; all five canonical primaries and 14 generated resources validate under their owning roots; every lawpack/target-profile field is bound to the expected coordinate and independently recomputed domain-framed @@ -525,7 +613,7 @@ proofs establish schema, identity-graph, component-contract, and request readiness only. They still do not install, authorize, schedule, execute, commit, observe, or receipt anything in Echo. -The publishable Rust crate uses a separate 40-file package-local carrier tree +The publishable Rust crate uses a separate 42-file package-local carrier tree for exact repository sources and provider bytes that would otherwise live above the crate root. Carrier locations never replace the logical authored paths in generation provenance. Generated artifacts and components remain authoritative; diff --git a/schemas/edict-provider/README.md b/schemas/edict-provider/README.md index 6bd0e7df3..d42ec83e9 100644 --- a/schemas/edict-provider/README.md +++ b/schemas/edict-provider/README.md @@ -169,10 +169,10 @@ nor manifest. Independent fresh-store replay and separate host processes reproduce all three completed outcomes identically. Both checked components remain uninstalled package material; neither they nor the generated authority-facts documents are runtime Echo authority. The promoted lowerer is -381,459 bytes with SHA-256 -`4b594a8165079f0a973741b9e27b468cf041f4ad53108fccae641dc35355bd2a`; the -promoted verifier is 389,503 bytes with SHA-256 -`d0be4d283399aefc45d75b395b3568f9983521ca5543c9dfd23b9495758050ed`. +457,154 bytes with SHA-256 +`dc3edc7c4f194bd2fdd2b5a1ecc79f8fb7a24a5a31294a1f1fc30369c053b8d4`; the +promoted verifier is 469,772 bytes with SHA-256 +`23d1d256ec66138697f93babcd2be57a4f56de1eb2d1fb6910e5aef881c4dd0e`. The checked schema/components also support the explicit `compiler-produced-bounded-read/v1` configuration with required `maxReads` @@ -330,15 +330,24 @@ writes only the two exact components, those generated members, and the derived manifest. Run `echo-edict-provider-package --check` to report drift without creating, deleting, or rewriting package files. -The isolated Edict c75 host gate then consumes that exact checked package. It -constructs all 24 native schema bindings, validates the five canonical primaries +The isolated legacy Edict host gate pins revision +`2e3f52f9e6d615f96eb594a40126e223a9253d98` and consumes that exact checked package. +It constructs all 31 native schema bindings, validates the five canonical primaries and 14 generated resources, proves every owner field names the expected exact resource digest, prepares both components, and obtains both opaque request proofs without guest invocation. Schema-valid resource substitution, reference swaps, authority-source disagreement, and malformed contract material fail before execution. This does not grant Echo runtime authority. -The crate-local `assets/v1/` tree is an exact 40-file publication carrier for +The [source-function contract publication](contracts/source-functions-v1/README.md) +is selected explicitly through `ProviderContractPublicationV1::SourceFunctions`. +It preserves the existing pure-binding and ordered publication bytes and does +not change the default checked generation route. Its candidate generator uses +the same artifact/provenance/package admission libraries with the selected new +pair and the refreshed checked component carriers. Source-owned call authority +and runtime capability still require independent provider validation. + +The crate-local `assets/v1/` tree is an exact 42-file publication carrier for the same provider bytes plus the repository sources needed for generator provenance. The compile-time generator identity enumerates a 20-file source closure, including the provider-generic registry implementation. Carrier paths diff --git a/schemas/edict-provider/components/v1/README.md b/schemas/edict-provider/components/v1/README.md index 22881fcd0..7c923e53e 100644 --- a/schemas/edict-provider/components/v1/README.md +++ b/schemas/edict-provider/components/v1/README.md @@ -23,8 +23,8 @@ is componentized with `wit-component` 0.251.0. The source WIT is the exact 7,392-byte Edict contract with SHA-256 `2971fe44def7e51d5271dfc0f04f3088aa58754cffdc847681a587605aac749e`. -The checked component is 381,459 bytes with SHA-256 -`4b594a8165079f0a973741b9e27b468cf041f4ad53108fccae641dc35355bd2a`. +The checked component is 457,154 bytes with SHA-256 +`dc3edc7c4f194bd2fdd2b5a1ecc79f8fb7a24a5a31294a1f1fc30369c053b8d4`. Its sole contract attestation is the top-level custom section `edict:target-provider-contract` containing `edict:target-provider/lowerer@1.0.0`. Its only imports are the frozen WIT's @@ -35,9 +35,9 @@ capability imports. `verifier.echo-dpo.component.wasm` implements `edict:target-provider/verifier@1.0.0`. It uses the same immutable builder, authenticated Rust/Cargo identities, frozen WIT bytes, path-remapping law, and -`wit-component` version recorded above. Its checked component is 389,503 bytes +`wit-component` version recorded above. Its checked component is 469,772 bytes with SHA-256 -`d0be4d283399aefc45d75b395b3568f9983521ca5543c9dfd23b9495758050ed`. +`23d1d256ec66138697f93babcd2be57a4f56de1eb2d1fb6910e5aef881c4dd0e`. Its sole contract attestation is the top-level custom section `edict:target-provider-contract` containing `edict:target-provider/verifier@1.0.0`. Its only imports are the frozen WIT's diff --git a/schemas/edict-provider/components/v1/lowerer.echo-dpo.component.wasm b/schemas/edict-provider/components/v1/lowerer.echo-dpo.component.wasm index 903933cd7..555e4dcf6 100644 Binary files a/schemas/edict-provider/components/v1/lowerer.echo-dpo.component.wasm and b/schemas/edict-provider/components/v1/lowerer.echo-dpo.component.wasm differ diff --git a/schemas/edict-provider/components/v1/verifier.echo-dpo.component.wasm b/schemas/edict-provider/components/v1/verifier.echo-dpo.component.wasm index 14c6c1479..74fe228fd 100644 Binary files a/schemas/edict-provider/components/v1/verifier.echo-dpo.component.wasm and b/schemas/edict-provider/components/v1/verifier.echo-dpo.component.wasm differ diff --git a/schemas/edict-provider/contracts/source-functions-v1/README.md b/schemas/edict-provider/contracts/source-functions-v1/README.md new file mode 100644 index 000000000..434d5d9c9 --- /dev/null +++ b/schemas/edict-provider/contracts/source-functions-v1/README.md @@ -0,0 +1,77 @@ + + + +# Source-function Edict contract publication + +This complete Apache-2.0 contract pair was copied from Edict commit +[`acd71fce03d2b5fc6d78c2a92ca922a45da6bac2`](https://github.com/flyingrobots/edict/commit/acd71fce03d2b5fc6d78c2a92ca922a45da6bac2), +under `fixtures/provider-contracts/source-functions-v1/`, for +[Edict #226](https://github.com/flyingrobots/edict/issues/226). The pair is byte-identical at the +merged compiler revision +[`01161c1745baad0d713234ba1a671b9a26923aa4`](https://github.com/flyingrobots/edict/commit/01161c1745baad0d713234ba1a671b9a26923aa4). + +The CDDL contains 34,460 bytes with SHA-256 +`e484eabd615584a38cb57454b52747786f2314c135c13f99da6f6bae619a709f`. +The manifest contains 75,989 bytes with SHA-256 +`aebc2e4133407ae433c18b55421a89bf15dc5d489368f82ce4874f948d4f9c79`. +Keep the pair byte-for-byte intact. Replace both only from one explicit upstream +publication, updating the digest pins and provenance together. + +Select this pair with `admit_provider_contract_pack_for_publication_v1` and +`ProviderContractPublicationV1::SourceFunctions`. The unchanged manifest API and +coordinate do not select a publication: exact digests and the explicit enum +variant do. Admission checks the 75,989-byte ceiling before parsing JSON, +embedded schema/resource consistency, provenance, and both pinned raw digests. +It performs no discovery or automatic upgrade. + +The [pure-binding](../v1/README.md) and +[ordered-instruction](../ordered/README.md) pairs and selectors remain separate. +The original admission API and checked artifact/package generators still select +pure bindings. This schema accepts function-free Core and adds an optional, +nonempty `functions` map whose entries carry typed parameters, a return type, +ordered pure bindings, and a result. Old publications reject that new map. +Omitting an empty map preserves the old canonical shape. + +Schema admission does not establish call authority, lexical scope, totality, +combined call depth, resource bounds, or executable support. The provider and +its independent verifier must validate source-owned definitions against the +exact authenticated Core, separately from imported lawpack authority. Runtime +support and limits belong to the +[application contract hosting contract](../../../../docs/architecture/application-contract-hosting.md). + +The isolated `tests/edict-provider-host-v1` suite retains its frozen +`2e3f52f9e6d615f96eb594a40126e223a9253d98` compiler/host and exact compatibility +corpus. Source-function consumer checks use the separately pinned public +compiler, whose application build invokes both provider components through its +actual host. Record that compiler revision and the exact emitted package/report +bytes for runtime checks. Preserve the frozen suite's broader refusal and replay +checks when refreshing the provider components. + +## Explicit candidate generation + +The [candidate generator](../../../../crates/echo-wesley-gen/examples/source_functions_publication_witness.rs) +selects this pair, generates provider artifacts with the normal owned library, +assembles the exact checked lowerer/verifier component bytes, and digest-admits +the complete package before writing a new destination directory. It does not +replace the existing checked pure-binding corpus or choose a Jedit producer pin. +Run it only after the component and asset refresh described in the +[generator operating instructions](../../../../crates/echo-wesley-gen/README.md#refreshing-source-bound-provider-publication). + +Inside the admitted guarded Docker worker, using its accounted Cargo target: + +```sh +cargo +1.96.0 run --locked -p echo-wesley-gen \ + --example source_functions_publication_witness -- /owned-data/source-functions-provider +``` + +The new directory must be inside the runner's guarded data root. The Docker +presence check does not enforce shared storage budgets, free-space floors, +exclusive ownership, CPU/memory ceilings, log caps, or process cleanup; the +outer runner must enforce them. No new worker or Cargo cache is required. + +Changing generator source changes its exact source identity and therefore +regenerated provenance and provider-package identity. Preserving old contract +publication bytes does not mean those newly generated package identities stay +unchanged. Retain an exact old provider package for compatibility controls. +Component reproducibility remains subject to the independent designated-build +policy in [the component contract](../../components/v1/README.md). diff --git a/schemas/edict-provider/contracts/source-functions-v1/edict-provider-contracts.cddl b/schemas/edict-provider/contracts/source-functions-v1/edict-provider-contracts.cddl new file mode 100644 index 000000000..c7a0e9dd4 --- /dev/null +++ b/schemas/edict-provider/contracts/source-functions-v1/edict-provider-contracts.cddl @@ -0,0 +1,1036 @@ +; SPDX-License-Identifier: Apache-2.0 +; edict-provider-contracts.cddl +; Generated from Edict-owned ABI fragments. DO NOT EDIT. + +; --- edict-common.cddl --- +; edict-common.cddl +; Shared CDDL types for the Edict ABIs, defined ONCE here so they cannot drift +; (EDICT-ABI-NODUP-001). Two groups: +; - resource-ref, sha256-digest, effect-failure-body, effect-kind, authority-class, +; core-type-ref, core-named-type-ref: assembled with edict-core.cddl, +; edict-target-profile.cddl, and +; edict-lawpack.cddl by the build; those schemas do not redefine them. +; - operation-profile, optic-template, aperture-requirement and their refs: +; consumed by the Core/optic layer (edict-core.cddl) and referenced by the +; language/target-profile specs. +; Authoritative byte encoding: edict.canonical-cbor/v1. + +; A normative subcomponent referenced by identity plus digest. Manifests never +; embed their own self-digest in their preimage (EDICT-CORE-SELFHASH-001). +resource-ref = { id: tstr, digest: sha256-digest } + +; Digests are authoritative as typed byte values, never hex strings. Review JSON +; renders this as "sha256:<64 lowercase hex>" (EDICT-DIGEST-WIRE-001). +sha256-digest = [ algorithm: "sha256", bytes: bstr .size 32 ] + +; A named low-level failure an effect can raise. The source obstruction map +; binds it (by coordinate) and constructs a typed domain obstruction from its +; payload (EDICT-ABI-FAILURE-NAMED-001). +; An effect's `effectFailures` list MUST have unique `coordinate`s: since the +; obstruction map is keyed by coordinate, two failures sharing a coordinate (even +; with different authorityClass/payloadType) make exhaustive mapping and binder +; typing ambiguous and are rejected (EDICT-ABI-FAILURE-UNIQUE-001). +; +; Effects carry their failures as a map `{ failure-ident => effect-failure-body }` +; (see the target/lawpack effect schemas). The failure coordinate is the map +; KEY, so it is not repeated in the body and cannot disagree with the key. +effect-failure-body = { + authorityClass: authority-class, + payloadType: core-type-ref, ; typed, bounded (may be empty record) +} + +; A failure coordinate must be a bare Edict `ident` (letter/underscore then +; letters/digits/underscores) AND must not be a reserved keyword (e.g. `else`, +; `basis`, `where`, `require`, `for`, `if`). The source obstruction-map LHS only +; accepts a non-keyword `ident`, so a hyphen/dot/keyword coordinate would be +; ABI-valid yet impossible to map exhaustively in source. The regex captures the +; lexical shape; keyword exclusion is an additional validation rule +; (EDICT-ABI-FAILURE-IDENT-001). +failure-ident = tstr .regexp "[A-Za-z_][A-Za-z0-9_]*" + +effect-kind = "read" / "create" / "ensure" / "replace" / "delete" / + "append" / "reduce" / "semantic.emit" / "custom" + +authority-class = "domainMappable" / "participantOwned" / "integrityFault" / + "resourceFault" / "internalFault" + +; The normative lexical and semantic grammar for Core type references lives in +; SPEC_edict-language-v1.md. CDDL can express only the wire-level text shape. +core-type-ref = tstr + +; A core.types map key is narrower than a general core-type-ref: it must be a +; named identity, never an intrinsic or canonical structural reference. The +; formal spec and executable Core validator enforce that semantic distinction. +core-named-type-ref = tstr + +; An operation profile supplies the optic template a Core intent resolves its +; opticKind/boundaryKind/supportPolicy/lossDisposition from. Target profiles and +; lawpacks publish these as a map `{ coordinate => operation-profile }`, so the +; coordinate is the KEY, not a value field (EDICT-OPTIC-TEMPLATE-OWNER-001, +; EDICT-ABI-OPPROFILE-UNIQUE-001). +operation-profile = { + opticTemplate: optic-template, + effectPredicate: tstr, ; coordinate of the operation-mode predicate +} + +optic-template = { + opticKind: "revelation" / "affectReintegration", + boundaryKind: "projection" / "affect", + supportPolicy: tstr, ; canonical support-policy coordinate + lossDisposition: tstr, ; canonical loss-disposition coordinate + ? basisTemplate: tstr, ; optional digest-locked basis template coord + ; the aperture requirement this template supplies. Required when the template + ; is the source of a Core optic's apertureRequirement (i.e. the intent has no + ; source `footprint <= ...`), since apertureRequirement is mandatory in Core + ; (EDICT-OPTIC-APERTURE-REF-001). + ? apertureRequirement: aperture-requirement, +} + +; apertureRequirement is a typed reference, never a free-form string. A review +; rendering may show its coordinate (EDICT-OPTIC-APERTURE-REF-001). +aperture-requirement = footprint-ceiling-ref / abstract-footprint-obligation-ref +footprint-ceiling-ref = { kind: "footprintCeiling", ref: tstr } +abstract-footprint-obligation-ref = { kind: "abstractFootprintObligation", ref: tstr } + +; --- edict-core.cddl --- +; edict-core.cddl +; Normative schema for the Edict Core v1 semantic model. +; +; Scope boundary: this file defines Core meaning and schema shape only. It does +; not define a canonical encoder, Core module hash fields, hash fixtures, target +; lowering, admission bundles, or target-owned IR. + +core-module = { + apiVersion: "edict.core/v1", + coordinate: tstr, + imports: [* core-import], + types: { * core-named-type-ref => core-type }, + ? functions: { + tstr => core-source-function }, + intents: { + tstr => core-intent }, + requiredCoreCapabilities: [* tstr], +} + +core-import = { + kind: "lawpack" / "target" / "core" / "capability", + ref: resource-ref, + ? alias: tstr, +} + +; --- types --------------------------------------------------------------- + +core-type = core-scalar-type / core-record-type / core-variant-type / + core-option-type / core-list-type / core-map-type / + core-capability-ref-type / core-external-action-request-type / + core-nominal-type + +core-scalar-type = core-bool-type / core-int-type / core-string-type / + core-bytes-type / core-unit-type + +core-bool-type = { kind: "Bool" } +core-unit-type = { kind: "Unit" } +core-int-type = { + kind: "I64" / "U64" / "I32" / "U32" / "I16" / "U16" / "I8" / "U8", +} +core-string-type = { + kind: "String", + max: uint, + canonical: "unicode-scalar-nfc" / "raw-utf8", +} +core-bytes-type = { + kind: "Bytes", + ? min: uint, + max: uint, +} +core-nominal-type = { + kind: "Nominal", + contract: tstr, + representation: core-type-ref, +} +core-record-type = { + kind: "Record", + fields: { * tstr => core-type-ref }, +} +core-variant-type = { + kind: "Variant", + cases: { + tstr => variant-case-body }, +} +variant-case-body = { + ? payload: core-type-ref, +} +core-option-type = { + kind: "Option", + item: core-type-ref, +} +core-list-type = { + kind: "List", + item: core-type-ref, + max: uint, +} +core-map-type = { + kind: "Map", + key: core-type-ref, + value: core-type-ref, + max: uint, +} +core-capability-ref-type = { + kind: "CapabilityRef", + item: core-type-ref, +} +core-external-action-request-type = { + kind: "ExternalActionRequest", + settlement: core-type-ref, +} + +; core-type-ref is defined in edict-common.cddl and assembled with this schema. + +; --- references and values ---------------------------------------------- + +; Local identity is alpha-stable. `id` is the compiler-owned local coordinate; +; `alphaName` is the normalized human/debug name. Source binder spelling is not +; identity. +local-ref = { + id: tstr, + alphaName: tstr, + type: core-type-ref, +} + +core-value = core-null-value / core-bool-value / core-int-value / + core-string-value / core-bytes-value / core-record-value / + core-variant-value / core-list-value / core-map-value / + core-capability-value + +core-null-value = { kind: "null" } +core-bool-value = { kind: "bool", value: bool } +core-int-value = { kind: "int", width: tstr, value: int } +core-string-value = { kind: "string", value: tstr } +core-bytes-value = { kind: "bytes", value: bstr } +core-record-value = { kind: "record", fields: { * tstr => core-value } } +core-variant-value = { + kind: "variant", + type: core-type-ref, + case: tstr, + ? payload: core-value, +} +core-list-value = { kind: "list", values: [* core-value] } +core-map-value = { kind: "map", entries: [* [key: core-value, value: core-value]] } +core-capability-value = { + kind: "capability", + receipt: sha256-digest, +} + +; Edict-authored pure helpers use a pure Core function body. The body can bind +; pure expressions and return an expression, but it cannot contain Core effect, +; guard, branch, loop, match-node, or proof-obligation nodes. +; Source-owned functions use the same pure body algebra, with their result type +; authenticated by the enclosing module. An empty function table is omitted. +core-source-function = { + params: [* local-ref], + returnType: core-type-ref, + body: core-pure-block, +} + +core-fn-body = { + params: [* local-ref], + body: core-pure-block, +} + +core-pure-block = { + locals: [* local-ref], + bindings: [* pure-let-node], + result: core-expr, +} + +pure-let-node = { + kind: "let", + binding: local-ref, + value: core-expr, +} + +; --- expressions and predicates ----------------------------------------- + +core-expr = local-expr / const-expr / record-expr / field-expr / + variant-expr / match-expr / call-expr / list-expr / map-expr / + if-expr + +local-expr = { kind: "local", ref: local-ref } +const-expr = { kind: "const", value: core-value } +record-expr = { kind: "record", fields: { * tstr => core-expr } } +field-expr = { kind: "field", base: core-expr, field: tstr } +variant-expr = { + kind: "variant", + type: core-type-ref, + case: tstr, + ? payload: core-expr, +} +match-expr = { + kind: "match", + scrutinee: core-expr, + arms: [+ match-arm], +} +match-arm = { + case: tstr, + ? binder: local-ref, + body: core-expr, +} +call-expr = { + kind: "call", + callee: tstr, + typeArgs: [* core-type-ref], + args: [* core-expr], +} +list-expr = { kind: "list", values: [* core-expr] } +map-expr = { kind: "map", entries: [* [key: core-expr, value: core-expr]] } +if-expr = { + kind: "if", + predicate: core-predicate, + then: core-expr, + else: core-expr, +} + +core-predicate = true-predicate / false-predicate / not-predicate / + all-predicate / any-predicate / compare-predicate / + call-predicate / obstruction-predicate + +true-predicate = { kind: "true" } +false-predicate = { kind: "false" } +not-predicate = { kind: "not", value: core-predicate } +all-predicate = { kind: "all", values: [+ core-predicate] } +any-predicate = { kind: "any", values: [+ core-predicate] } +compare-predicate = { + kind: "compare", + op: "==" / "!=" / "<" / "<=" / ">" / ">=", + left: core-expr, + right: core-expr, +} +call-predicate = { + kind: "call", + predicate: tstr, + args: [* core-expr], +} +obstruction-predicate = { + kind: "obstruction", + coordinate: failure-ident, + payload: core-expr, +} + +input-constraint = { + coordinate: tstr, + source: "where" / "compiler", + predicate: core-predicate, +} + +; --- intents, blocks, and nodes ----------------------------------------- + +core-intent = { + input: core-type-ref, + output: core-type-ref, + requiredOperationProfile: tstr, + ? basis: core-expr, + inputConstraints: [* input-constraint], + coreEvaluationBudget: core-budget, + body: core-block, + ? optic: core-optic, +} + +core-budget = { + maxSteps: uint, + maxAllocatedBytes: uint, + maxOutputBytes: uint, +} + +core-optic = { + opticKind: "revelation" / "affectReintegration", + boundaryKind: "projection" / "affect", + apertureRequirement: aperture-requirement, + supportPolicy: tstr, + lossDisposition: tstr, +} + +core-block = { + locals: [* local-ref], + nodes: [* core-node], + result: core-expr, +} + +core-node = let-node / require-node / effect-node / + external-action-request-node / guard-node / branch-node / + for-node / match-node / proof-obligation-node + +let-node = { + kind: "let", + binding: local-ref, + value: core-expr, +} +require-node = { + kind: "require", + predicate: core-predicate, + onFailure: require-failure-arm, +} +require-failure-arm = terminal-require-failure / + continue-obstructed-require-failure +terminal-require-failure = { + kind: "terminal", + reason: obstruction-reason, +} +continue-obstructed-require-failure = { + kind: "continueObstructed", + reason: obstruction-reason, +} +obstruction-reason = { + reasonKind: tstr, + payload: { * tstr => core-expr }, +} +effect-node = { + kind: "effect", + binding: local-ref, + effect: tstr, + input: core-expr, + obstructionMap: { * failure-ident => obstruction-arm }, +} +external-action-request-node = { + kind: "externalActionRequest", + binding: local-ref, + operation: resource-ref, + inputType: core-type-ref, + settlementType: core-type-ref, + inputSchema: resource-ref, + settlementSchema: resource-ref, + input: core-expr, + authorityScope: core-expr, + basis: core-expr, + budget: external-action-budget, + reconciliationLaw: resource-ref, + state: "awaitingSettlement", + settlementAdmission: "schemaRequired", +} +external-action-budget = { + maxSettlementBytes: core-expr, + maxAttempts: core-expr, +} +obstruction-arm = { + binder: local-ref, + value: core-expr, +} +guard-node = { + kind: "guard", + predicate: core-predicate, + obstruction: core-expr, +} +branch-node = { + kind: "branch", + ? binding: local-ref, + predicate: core-predicate, + then: core-block, + else: core-block, +} +for-node = { + kind: "for", + binder: local-ref, + iter: core-expr, + bound: core-bound, + body: core-block, +} +match-node = { + kind: "match", + scrutinee: core-expr, + arms: [+ match-block-arm], +} +match-block-arm = { + case: tstr, + ? binder: local-ref, + body: core-block, +} +proof-obligation-node = { + kind: "proof", + coordinate: tstr, + predicate: core-predicate, +} + +core-bound = literal-bound / coordinate-bound +literal-bound = { kind: "literal", value: uint } +coordinate-bound = { kind: "coordinate", ref: tstr } + +; Shared resource-ref, sha256-digest, failure-ident, aperture-requirement, +; core-type-ref, and core-named-type-ref are defined once in edict-common.cddl. + +; --- edict-lawpack.cddl --- +; edict-lawpack.cddl +; Canonical schema for the Edict lawpack manifest and export surface. +; Authoritative byte encoding: edict.canonical-cbor/v1. JSON in the prose specs +; is a review rendering generated from this schema (EDICT-ABI-NODUP-001). + +lawpack-manifest = { + apiVersion: "edict.lawpack/v1", + id: tstr, + version: tstr, + acceptedCoreAbi: [+ tstr], + dependencies: [* lawpack-dep], ; acyclic, digest-locked (EDICT-LAWPACK-DAG-001) + exports: resource-ref, + ? targetAdapters: [+ target-adapter], ; required only if any runtime effect exists + ? helperComponent: executable-component, ; executable helpers carry their own sandbox+fuel + verifier: verifier, ; classified: declarative or executable + compatibility: resource-ref, + conformanceFixtureCorpus: resource-ref, +} + +; A verifier is either a declarative ruleset (no runtime) or an executable +; component. An executable verifier MUST carry its own sandbox and fuel model, +; so the schema enforces that no executable component is left unbounded +; (EDICT-ABI-VERIFIER-BOUND-001). +verifier = declarative-verifier / executable-verifier +declarative-verifier = { class: "declarative", ruleset: resource-ref } +executable-verifier = { + class: "executable", + component: resource-ref, + sandbox: resource-ref, + fuelModel: resource-ref, +} + +; Any executable component is bounded by its own sandbox + fuel model. +executable-component = { + component: resource-ref, + sandbox: resource-ref, + fuelModel: resource-ref, +} + +lawpack-dep = { id: tstr, version: tstr, digest: sha256-digest } + +; Adapter selection keys SOLELY off the digest-locked `acceptedTargetProfile` +; (its `id` is the profile id; its `digest` pins the exact profile/version). There +; are no independent display strings that could disagree with the lock, so a +; resolver cannot bind an adapter to one target while the lock proves another +; (EDICT-LAWPACK-ADAPTER-TARGETIR-001). +target-adapter = { + acceptedTargetProfile: resource-ref, ; digest-locked, authoritative selector + acceptedTargetIr: resource-ref, ; digest-locked + adapter: resource-ref, +} + +; Shared types resource-ref and sha256-digest are defined once in +; edict-common.cddl and assembled with this schema (EDICT-ABI-NODUP-001). + +; --- export surface ------------------------------------------------------ + +lawpack-exports = { + types: [* exported-type], + constants: [* exported-constant], + pureFunctions: [* pure-function], + effects: [* semantic-effect], + obstructions: [* obstruction-def], + ; keyed by operation-profile coordinate → uniqueness enforced + ; (EDICT-ABI-OPPROFILE-UNIQUE-001) + ; operation-profile records this lawpack exports (optic templates that + ; `implements`/`profile` clauses resolve against). operation-profile is + ; defined in edict-common.cddl (EDICT-ABI-OPPROFILE-SLOT-001). + operationProfiles: { * tstr => operation-profile }, ; keyed by coordinate +} + +exported-type = { coordinate: tstr, definition: core-type-ref } +exported-constant = { coordinate: tstr, type: core-type-ref, value: any } + +; A pure helper is a discriminated union by `source`, so the schema itself +; guarantees an implementation exists (EDICT-LAWPACK-PURE-IMPL-001): +; - "edict": authored in Edict/Core; the Core body is carried inline (hashed +; with the export surface). The schema requires the `body` field. +; - "component": implemented outside Edict; carries no inline body and instead +; carries its own digest-locked `implementation` (sandbox + fuel). It does +; not depend on the optional manifest-level helperComponent. +pure-function = edict-pure-function / component-pure-function + +pure-function-common = ( + coordinate: tstr, + typeParameters: [* tstr], + parameterTypes: [* core-type-ref], ; all bounded + returnType: core-type-ref, ; bounded + costTemplate: tstr, + determinismClass: "total" / "total-with-typed-diagnostic", +) + +edict-pure-function = { + pure-function-common, + source: "edict", + body: core-fn-body, ; inline, hash-significant +} + +component-pure-function = { + pure-function-common, + source: "component", + ; the digest-locked component implementing this helper. Required at the schema + ; level so a component helper can never validate without a hash-bound, + ; sandbox+fuel-described implementation (EDICT-LAWPACK-PURE-IMPL-001). + implementation: executable-component, +} + +; core-fn-body is defined by edict-core.cddl and assembled with this lawpack +; schema. It is a pure Core function body, not an effect-capable core-block. + +semantic-effect = { + coordinate: tstr, + typeParameters: [* tstr], + inputType: core-type-ref, ; bounded + outputType: core-type-ref, ; bounded + executionClass: "proofOnly" / "runtime", ; orthogonal to writeClass + effectKindHint: effect-kind, + footprintObligation: tstr, + costObligation: tstr, + effectFailures: { * failure-ident => effect-failure-body }, ; keyed by coordinate; unique + guardSupport: bool, +} + +obstruction-def = { + coordinate: tstr, + authorityClass: authority-class, + payloadSchema: core-type-ref, ; typed, bounded (may be empty record) +} + +; effect-failure-body, effect-kind, authority-class, and core-type-ref are defined +; once in edict-common.cddl and assembled with this schema (EDICT-ABI-NODUP-001). + +; --- edict-lawpack-adapter.cddl --- +; edict-lawpack-adapter.cddl +; Canonical schema for one direct declarative lawpack target adapter. +; Authoritative byte encoding: edict.canonical-cbor/v1. +; +; The enclosing lawpack manifest selects the exact target profile, target IR, +; and adapter resource digest. Those identities are not repeated here. + +lawpack-adapter = { + apiVersion: "edict.lawpack-adapter/v1", + class: "declarative", + operationProfiles: { + * tstr => lawpack-adapter-operation-profile + }, + effectImplementations: { + * tstr => lawpack-adapter-effect + }, + budgets: { + * tstr => lawpack-adapter-budget + }, +} + +; Keys are canonical lawpack operation-profile coordinates. +lawpack-adapter-operation-profile = + { + core: tstr, + semanticEffects: [+ tstr], + ? budgetObligation: tstr, + ? targetConfiguration: resource-ref, + } / + { + core: tstr, + semanticEffects: [], + budgetObligation: tstr, + targetConfiguration: resource-ref, + } + +; Keys are canonical lawpack semantic-effect coordinates. Footprint, cost, and +; failure fields must exactly discharge the matching exported effect. +lawpack-adapter-effect = { + targetIntrinsic: tstr, + targetConfiguration: resource-ref, + writeClass: lawpack-adapter-write-class, + footprintObligation: tstr, + costObligation: tstr, + failureMappings: { * failure-ident => tstr }, +} + +; Keys are canonical exported cost-obligation coordinates. +lawpack-adapter-budget = { + maxSteps: uint, + maxAllocatedBytes: uint, + maxOutputBytes: uint, +} + +lawpack-adapter-write-class = "none" / "read" / "create" / "ensure" / + "append" / "replace" / "delete" / "custom" + +; failure-ident is defined in edict-common.cddl. + +; --- edict-target-profile.cddl --- +; edict-target-profile.cddl +; Canonical schema for the Edict target profile manifest. +; Authoritative byte encoding: edict.canonical-cbor/v1 +; (see SPEC_continuum-contract-bundle-v1.md). JSON in the prose specs is a +; review rendering generated from this schema; this CDDL is the single source +; of truth (EDICT-ABI-NODUP-001). + +target-profile-manifest = { + apiVersion: "edict.target-profile/v1", + id: tstr, ; e.g. "echo.dpo" + version: tstr, ; e.g. "1" + acceptedCoreAbi: [+ tstr], ; e.g. ["edict.core/v1"] + + intrinsics: resource-ref, + intrinsicNamespace: tstr, + ; publishes this profile's operation-profile records (optic templates that + ; `profile`/`implements` clauses resolve against). References an + ; operation-profiles-document (EDICT-ABI-OPPROFILE-SLOT-001). + operationProfiles: resource-ref, + footprintAlgebra: resource-ref, + costAlgebra: resource-ref, + targetIr: resource-ref, + obstructionTaxonomy: resource-ref, + verifier: resource-ref, + lowerer: resource-ref, + sandbox: resource-ref, + fuelModel: resource-ref, + + ; fields the language spec requires of every profile + bundleProfile: resource-ref, + generatedArtifactProfiles: [* resource-ref], + canonicalEncodingRules: resource-ref, + ; A profile that accepts the direct declarative lawpack-adapter ABI names it + ; exactly once. Profiles that do not consume lawpack adapters leave this + ; optional slot absent or empty. + ? acceptedLawpackAdapterAbi: [] / ["edict.lawpack-adapter/v1"], + diagnosticAbi: resource-ref, + + ; application doctrine + applicationModel: "atomic", + readConsistency: "application-snapshot" / tstr, + guardEvaluation: "precommit-atomic" / tstr, + obstructionRollback: "no-visible-effects" / tstr, + multiTarget: bool, + ; whether the target can evaluate precommit postcondition (`guarantee`) checks + ; inside the atomic application unit (EDICT-TARGET-POSTCOND-001) + postconditionSupport: bool, + + deterministicExecution: resource-ref, + conformanceFixtureCorpus: resource-ref, +} + +; Shared types resource-ref and sha256-digest are defined once in +; edict-common.cddl and assembled with this schema by the build +; (EDICT-ABI-NODUP-001). They are not redefined here. + +; --- intrinsic signature ------------------------------------------------- +; The artifact referenced by the manifest's `intrinsics` resource-ref is the +; intrinsic-signature corpus document below. Its layout is fixed so two +; independent profiles validate/hash the corpus identically +; (EDICT-ABI-INTRINSICS-DOC-001). + +; intrinsics is a MAP keyed by coordinate, so the schema itself enforces +; coordinate uniqueness. A provider receives the resolved corpus as a +; digest-bound semantic input and resolves coordinates within that artifact. +; Each map key MUST equal its record's `coordinate` field +; (EDICT-ABI-INTRINSIC-UNIQUE-001). +intrinsics-document = { + apiVersion: "edict.target-profile.intrinsics/v1", + intrinsics: { * tstr => intrinsic }, +} + +; The artifact referenced by the manifest's `operationProfiles` resource-ref. +; operation-profile / optic-template are defined in edict-common.cddl. Keyed by +; coordinate so resolution can't pick between two same-coordinate profiles +; (EDICT-ABI-OPPROFILE-SLOT-001, EDICT-ABI-OPPROFILE-UNIQUE-001). +operation-profiles-document = { + apiVersion: "edict.target-profile.operation-profiles/v1", + profiles: { * tstr => operation-profile }, +} + +; A typed pre-lowering question that can be proposed by Watson or an agent and +; checked by the compiler. It is canonical-CBOR encoded under +; `edict.lowering-requirements/v1`; the compiler checks this artifact, not the +; prose that produced it. +lowering-requirements = { + apiVersion: "edict.lowering-requirements/v1", + operationProfile: tstr, + semanticEffects: [* semantic-effect-requirement], + requiredWriteClasses: [* write-class], + guardKinds: [* guard-kind], + atomicity: atomicity-requirement, + postconditionSupport: bool, + obstructionCoordinates: [* tstr], + footprintObligations: [* tstr], + costObligations: [* tstr], + opticContract: tstr, +} + +semantic-effect-requirement = { + coordinate: tstr, + writeClass: write-class, + guardKinds: [* guard-kind], + obstructionCoordinates: [* tstr], + footprintObligations: [* tstr], + costObligations: [* tstr], +} + +write-class = "none" / "read" / "create" / "ensure" / "append" / + "replace" / "delete" / tstr +guard-kind = "precommit-atomic" / tstr +atomicity-requirement = "atomic" / tstr + +; A genuine union: pure constructors carry no effect kind or failures; effect +; intrinsics must (EDICT-TARGET-INTRINSIC-CLASS-001). The schema enforces this, +; not a comment. + +; The intrinsic's coordinate is the intrinsics map KEY, not a value field, so the +; key and coordinate can never disagree (EDICT-ABI-INTRINSIC-UNIQUE-001). +intrinsic = pure-intrinsic / effect-intrinsic + +pure-intrinsic = { + intrinsicClass: "pure", + typeParameters: [* tstr], + argumentTypes: [* core-type-ref], + returnType: core-type-ref, + guardSupport: false, + footprintTemplate: tstr, + costTemplate: tstr, + writeClass: "none", +} + +effect-intrinsic = { + intrinsicClass: "effect", + typeParameters: [* tstr], + argumentTypes: [* core-type-ref], + returnType: core-type-ref, + effectKind: effect-kind, + ; map keyed by failure coordinate (failure-ident); the failure coordinate is + ; the key, not a value field, so uniqueness is structural + ; (EDICT-ABI-FAILURE-UNIQUE-001). + effectFailures: { * failure-ident => effect-failure-body }, + guardSupport: bool, + footprintTemplate: tstr, + costTemplate: tstr, + writeClass: "read" / "create" / "ensure" / "append" / "replace" / + "delete" / "custom", + canParticipateInAtomicGuard: bool, +} + +; effect-failure-body, effect-kind, authority-class, and core-type-ref are defined +; once in edict-common.cddl and assembled with this schema (EDICT-ABI-NODUP-001). + +; --- edict-authority-facts.cddl --- +; edict-authority-facts.cddl +; Canonical schema for the first compiler-context authority-facts document. +; Authoritative byte encoding: edict.canonical-cbor/v1. +; +; This schema is assembled with edict-common.cddl so source.digest uses the +; shared sha256-digest typed value. JSON is a review/input rendering: its +; `sha256:<64 hex>` source digest is projected to [`sha256`, 32 raw bytes] on +; the wire, and its fact arrays project to the coordinate-keyed maps below. + +authority-facts = { + apiVersion: "edict.authority-facts/v1", + source: authority-fact-source, + operationProfiles: { * tstr => authority-operation-profile-fact }, + effectWriteClasses: { * tstr => authority-write-class }, + budgets: { * tstr => authority-budget-fact }, +} + +authority-fact-source = { + kind: "lawpack" / "targetProfile", + coordinate: tstr, + digest: sha256-digest, +} + +; The map key is the source operation-profile coordinate. It is not repeated in +; the value, so a key and embedded coordinate cannot disagree. Allowed write +; classes are a canonical map-set: the class is the unique key and null is the +; unit marker. Canonical CBOR fixes key order without a second ordering rule. +authority-operation-profile-fact = { + core: tstr, + allowedWriteClasses: { * authority-write-class => null }, +} + +; The effectWriteClasses map key is the semantic effect coordinate. The budgets +; map key is the source budget coordinate. Canonical CBOR map-key uniqueness +; makes duplicate fact coordinates structurally unrepresentable. +authority-budget-fact = { + maxSteps: uint, + maxAllocatedBytes: uint, + maxOutputBytes: uint, +} + +; AuthorityFactsDocument v1 intentionally accepts only the write classes the +; current compiler model can consume. `custom` is the sole v1 custom spelling; +; arbitrary target-profile extension strings do not enter this compiler path. +authority-write-class = "none" / "read" / "create" / "ensure" / "append" / + "replace" / "delete" / "custom" + +; --- edict-result-projection.cddl --- +; SPDX-License-Identifier: Apache-2.0 +; edict-result-projection.cddl +; Canonical schema for compiler-owned application-result projections. +; Authoritative byte encoding: edict.canonical-cbor/v1. + +result-projection = { + schema: "edict.result-projection/v1", + operationCoordinate: bounded-projection-text, + outputType: bounded-projection-text, + maxOutputBytes: uint .gt 0, + expression: result-projection-expr, +} + +result-projection-expr = result-projection-record / result-projection-source + +result-projection-record = { + kind: "record", + ; The root record counts as one of the Rust decoder's 256 expression nodes. + ; Nested aggregate node count remains an authoritative decoder check. + fields: { 0*255 bounded-projection-text => result-projection-expr }, +} + +result-projection-source = { + kind: "source", + source: result-projection-source-kind, + ; Matches MAX_RESULT_PROJECTION_PATH_SEGMENTS in edict-syntax. + path: [0*32 bounded-projection-text], +} + +result-projection-source-kind = + { kind: "applicationInput" } / + { + kind: "pureBinding", + bindingId: bounded-projection-text, + } / + { + kind: "capabilityResult", + stepId: bounded-projection-text, + } + +bounded-projection-text = tstr .size (1..1024) + +; --- edict-target-ir.cddl --- +; SPDX-License-Identifier: Apache-2.0 +; edict-target-ir.cddl +; Canonical schema for the Edict-owned Target IR artifact envelope. +; Authoritative byte encoding: edict.canonical-cbor/v1. +; +; This schema is assembled with edict-common.cddl and edict-core.cddl. It +; deliberately reuses Core expressions, predicates, budgets, local references, +; obstruction reasons, and obstruction arms so the schema matches the value +; emitted by the canonical Target IR encoder rather than restating those types. +; It describes the structural shape of valid lowering-produced artifacts. The +; lowering and encoder contracts separately enforce semantic identifier rules +; and canonical ordering/deduplication for set-like values. + +; Target IR encoding rejects an empty target-profile coordinate before bytes +; exist, so this root tightens the shared structural resource-ref accordingly. +target-ir-resource-ref = { + id: tstr .regexp "(?s).+", + digest: sha256-digest, +} + +target-ir-artifact = target-ir-phased-artifact / target-ir-ordered-artifact + +target-ir-phased-artifact = target-ir-closed-artifact / target-ir-legacy-artifact + +target-ir-artifact-common = ( + domain: tstr, + targetProfile: target-ir-resource-ref, + sourceCoreCoordinate: tstr .regexp "(?s).+", +) + +; v2 binds one execution order over the instruction tables. The encoder also +; validates the exact permutation and producer-before-consumer dependencies. +target-ir-ordered-artifact = target-ir-ordered-closed / target-ir-ordered-legacy + +target-ir-ordered-common = ( + domain: "echo.span-ir/v2", + targetProfile: target-ir-resource-ref, + sourceCoreCoordinate: tstr .regexp "(?s).+", +) + +target-ir-ordered-closed = { + kind: "orderedTargetIrArtifact", + target-ir-ordered-common, + semanticClosure: target-ir-semantic-closure, + intents: { * tstr => target-ir-ordered-intent }, +} + +target-ir-ordered-legacy = { + kind: "orderedTargetIrArtifact", + target-ir-ordered-common, + intents: { * tstr => target-ir-ordered-legacy-intent }, +} + +target-ir-ordered-intent = { + target-ir-intent-common, + ? basis: core-expr, + ? pureBindings: [1* target-ir-pure-binding], + executionOrder: [* tstr .regexp "(?s).+"], +} + +target-ir-ordered-legacy-intent = { + target-ir-intent-common, + executionOrder: [* tstr .regexp "(?s).+"], +} + +target-ir-closed-artifact = { + kind: "targetIrArtifact", + target-ir-artifact-common, + semanticClosure: target-ir-semantic-closure, + intents: { * tstr => target-ir-intent }, +} + +target-ir-legacy-artifact = { + kind: "targetIrArtifact", + target-ir-artifact-common, + intents: { * tstr => target-ir-legacy-intent }, +} + +target-ir-semantic-closure = { + sourceCore: target-ir-resource-ref, + lawpacks: [* target-ir-resource-ref], + ? capabilities: [* target-ir-resource-ref], +} + +target-ir-intent = { + target-ir-intent-common, + ? basis: core-expr, + ? pureBindings: [1* target-ir-pure-binding], + ? externalActionRequests: [* target-ir-external-action-request], +} + +target-ir-legacy-intent = { + target-ir-intent-common, +} + +target-ir-intent-common = ( + operationProfile: tstr, + inputConstraints: [* input-constraint], + coreEvaluationBudget: core-budget, + requirements: [* target-ir-requirement], + steps: [* target-ir-step], + result: core-expr, +) + +target-ir-requirement = { + id: tstr, + predicate: core-predicate, + onFailure: require-failure-arm, +} + +target-ir-pure-binding = { + id: tstr .regexp "(?s).+", + binding: local-ref, + value: core-expr, +} + +target-ir-step = { + id: tstr, + binding: local-ref, + effect: tstr, + targetIntrinsic: tstr, + input: core-expr, + obstructionFailures: [* failure-ident], + obstructionArms: { * failure-ident => obstruction-arm }, +} + +target-ir-external-action-request = { + id: tstr, + binding: local-ref, + operation: target-ir-resource-ref, + inputType: core-type-ref, + settlementType: core-type-ref, + inputSchema: target-ir-resource-ref, + settlementSchema: target-ir-resource-ref, + input: core-expr, + authorityScope: core-expr, + basis: core-expr, + budget: external-action-budget, + reconciliationLaw: target-ir-resource-ref, + state: "awaitingSettlement", + settlementAdmission: "schemaRequired", +} diff --git a/schemas/edict-provider/contracts/source-functions-v1/manifest.json b/schemas/edict-provider/contracts/source-functions-v1/manifest.json new file mode 100644 index 000000000..267a5878a --- /dev/null +++ b/schemas/edict-provider/contracts/source-functions-v1/manifest.json @@ -0,0 +1,83 @@ +{ + "apiVersion": "edict.provider-contract-pack/v1", + "coordinate": "edict.provider-contract-pack.cddl@1", + "license": "Apache-2.0", + "schema": { + "bytesHex": "3b20535044582d4c6963656e73652d4964656e7469666965723a204170616368652d322e300a3b2065646963742d70726f76696465722d636f6e7472616374732e6364646c0a3b2047656e6572617465642066726f6d2045646963742d6f776e65642041424920667261676d656e74732e20444f204e4f5420454449542e0a0a3b202d2d2d2065646963742d636f6d6d6f6e2e6364646c202d2d2d0a3b2065646963742d636f6d6d6f6e2e6364646c0a3b20536861726564204344444c20747970657320666f722074686520456469637420414249732c20646566696e6564204f4e4345206865726520736f20746865792063616e6e6f742064726966740a3b202845444943542d4142492d4e4f4455502d303031292e2054776f2067726f7570733a0a3b2020202d207265736f757263652d7265662c207368613235362d6469676573742c206566666563742d6661696c7572652d626f64792c206566666563742d6b696e642c20617574686f726974792d636c6173732c0a3b2020202020636f72652d747970652d7265662c20636f72652d6e616d65642d747970652d7265663a20617373656d626c656420776974682065646963742d636f72652e6364646c2c0a3b202020202065646963742d7461726765742d70726f66696c652e6364646c2c20616e640a3b202020202065646963742d6c61777061636b2e6364646c20627920746865206275696c643b2074686f736520736368656d617320646f206e6f74207265646566696e65207468656d2e0a3b2020202d206f7065726174696f6e2d70726f66696c652c206f707469632d74656d706c6174652c2061706572747572652d726571756972656d656e7420616e6420746865697220726566733a0a3b2020202020636f6e73756d65642062792074686520436f72652f6f70746963206c61796572202865646963742d636f72652e6364646c2920616e64207265666572656e636564206279207468650a3b20202020206c616e67756167652f7461726765742d70726f66696c652073706563732e0a3b20417574686f7269746174697665206279746520656e636f64696e673a2065646963742e63616e6f6e6963616c2d63626f722f76312e0a0a3b2041206e6f726d617469766520737562636f6d706f6e656e74207265666572656e636564206279206964656e7469747920706c7573206469676573742e204d616e696665737473206e657665720a3b20656d626564207468656972206f776e2073656c662d64696765737420696e20746865697220707265696d616765202845444943542d434f52452d53454c46484153482d303031292e0a7265736f757263652d726566203d207b2069643a20747374722c206469676573743a207368613235362d646967657374207d0a0a3b20446967657374732061726520617574686f726974617469766520617320747970656420627974652076616c7565732c206e657665722068657820737472696e67732e20526576696577204a534f4e0a3b2072656e64657273207468697320617320227368613235363a3c3634206c6f77657263617365206865783e22202845444943542d4449474553542d574952452d303031292e0a7368613235362d646967657374203d205b20616c676f726974686d3a2022736861323536222c2062797465733a2062737472202e73697a65203332205d0a0a3b2041206e616d6564206c6f772d6c6576656c206661696c75726520616e206566666563742063616e2072616973652e2054686520736f75726365206f62737472756374696f6e206d61700a3b2062696e64732069742028627920636f6f7264696e6174652920616e6420636f6e73747275637473206120747970656420646f6d61696e206f62737472756374696f6e2066726f6d206974730a3b207061796c6f6164202845444943542d4142492d4641494c5552452d4e414d45442d303031292e0a3b20416e20656666656374277320606566666563744661696c7572657360206c697374204d555354206861766520756e697175652060636f6f7264696e61746560733a2073696e6365207468650a3b206f62737472756374696f6e206d6170206973206b6579656420627920636f6f7264696e6174652c2074776f206661696c757265732073686172696e67206120636f6f7264696e61746520286576656e0a3b207769746820646966666572656e7420617574686f72697479436c6173732f7061796c6f61645479706529206d616b652065786861757374697665206d617070696e6720616e642062696e6465720a3b20747970696e6720616d626967756f757320616e64206172652072656a6563746564202845444943542d4142492d4641494c5552452d554e495155452d303031292e0a3b0a3b2045666665637473206361727279207468656972206661696c757265732061732061206d617020607b206661696c7572652d6964656e74203d3e206566666563742d6661696c7572652d626f6479207d600a3b202873656520746865207461726765742f6c61777061636b2065666665637420736368656d6173292e20546865206661696c75726520636f6f7264696e61746520697320746865206d61700a3b204b45592c20736f206974206973206e6f7420726570656174656420696e2074686520626f647920616e642063616e6e6f74206469736167726565207769746820746865206b65792e0a6566666563742d6661696c7572652d626f6479203d207b0a2020617574686f72697479436c6173733a20617574686f726974792d636c6173732c0a20207061796c6f6164547970653a20636f72652d747970652d7265662c202020202020202020202020203b2074797065642c20626f756e64656420286d617920626520656d707479207265636f7264290a7d0a0a3b2041206661696c75726520636f6f7264696e617465206d7573742062652061206261726520456469637420606964656e746020286c65747465722f756e64657273636f7265207468656e0a3b206c6574746572732f6469676974732f756e64657273636f7265732920414e44206d757374206e6f742062652061207265736572766564206b6579776f72642028652e672e2060656c7365602c0a3b20606261736973602c20607768657265602c206072657175697265602c2060666f72602c2060696660292e2054686520736f75726365206f62737472756374696f6e2d6d6170204c4853206f6e6c790a3b20616363657074732061206e6f6e2d6b6579776f726420606964656e74602c20736f20612068797068656e2f646f742f6b6579776f726420636f6f7264696e61746520776f756c642062650a3b204142492d76616c69642079657420696d706f737369626c6520746f206d617020657868617573746976656c7920696e20736f757263652e20546865207265676578206361707475726573207468650a3b206c65786963616c2073686170653b206b6579776f7264206578636c7573696f6e20697320616e206164646974696f6e616c2076616c69646174696f6e2072756c650a3b202845444943542d4142492d4641494c5552452d4944454e542d303031292e0a6661696c7572652d6964656e74203d2074737472202e72656765787020225b412d5a612d7a5f5d5b412d5a612d7a302d395f5d2a220a0a6566666563742d6b696e64203d20227265616422202f202263726561746522202f2022656e7375726522202f20227265706c61636522202f202264656c65746522202f0a202020202020202020202020202022617070656e6422202f202272656475636522202f202273656d616e7469632e656d697422202f2022637573746f6d220a0a617574686f726974792d636c617373203d2022646f6d61696e4d61707061626c6522202f20227061727469636970616e744f776e656422202f2022696e746567726974794661756c7422202f0a202020202020202020202020202020202020227265736f757263654661756c7422202f2022696e7465726e616c4661756c74220a0a3b20546865206e6f726d6174697665206c65786963616c20616e642073656d616e746963206772616d6d617220666f7220436f72652074797065207265666572656e636573206c6976657320696e0a3b20535045435f65646963742d6c616e67756167652d76312e6d642e204344444c2063616e2065787072657373206f6e6c792074686520776972652d6c6576656c20746578742073686170652e0a636f72652d747970652d726566203d20747374720a0a3b204120636f72652e7479706573206d6170206b6579206973206e6172726f776572207468616e20612067656e6572616c20636f72652d747970652d7265663a206974206d75737420626520610a3b206e616d6564206964656e746974792c206e6576657220616e20696e7472696e736963206f722063616e6f6e6963616c207374727563747572616c207265666572656e63652e205468650a3b20666f726d616c207370656320616e642065786563757461626c6520436f72652076616c696461746f7220656e666f72636520746861742073656d616e7469632064697374696e6374696f6e2e0a636f72652d6e616d65642d747970652d726566203d20747374720a0a3b20416e206f7065726174696f6e2070726f66696c6520737570706c69657320746865206f707469632074656d706c617465206120436f726520696e74656e74207265736f6c766573206974730a3b206f707469634b696e642f626f756e646172794b696e642f737570706f7274506f6c6963792f6c6f7373446973706f736974696f6e2066726f6d2e205461726765742070726f66696c657320616e640a3b206c61777061636b73207075626c6973682074686573652061732061206d617020607b20636f6f7264696e617465203d3e206f7065726174696f6e2d70726f66696c65207d602c20736f207468650a3b20636f6f7264696e61746520697320746865204b45592c206e6f7420612076616c7565206669656c64202845444943542d4f505449432d54454d504c4154452d4f574e45522d3030312c0a3b2045444943542d4142492d4f5050524f46494c452d554e495155452d303031292e0a6f7065726174696f6e2d70726f66696c65203d207b0a20206f7074696354656d706c6174653a206f707469632d74656d706c6174652c0a20206566666563745072656469636174653a20747374722c20202020202020202020203b20636f6f7264696e617465206f6620746865206f7065726174696f6e2d6d6f6465207072656469636174650a7d0a0a6f707469632d74656d706c617465203d207b0a20206f707469634b696e643a2022726576656c6174696f6e22202f20226166666563745265696e746567726174696f6e222c0a2020626f756e646172794b696e643a202270726f6a656374696f6e22202f2022616666656374222c0a2020737570706f7274506f6c6963793a20747374722c202020202020202020202020203b2063616e6f6e6963616c20737570706f72742d706f6c69637920636f6f7264696e6174650a20206c6f7373446973706f736974696f6e3a20747374722c20202020202020202020203b2063616e6f6e6963616c206c6f73732d646973706f736974696f6e20636f6f7264696e6174650a20203f20626173697354656d706c6174653a20747374722c20202020202020202020203b206f7074696f6e616c206469676573742d6c6f636b65642062617369732074656d706c61746520636f6f72640a20203b2074686520617065727475726520726571756972656d656e7420746869732074656d706c61746520737570706c6965732e205265717569726564207768656e207468652074656d706c6174650a20203b2069732074686520736f75726365206f66206120436f7265206f707469632773206170657274757265526571756972656d656e742028692e652e2074686520696e74656e7420686173206e6f0a20203b20736f757263652060666f6f747072696e74203c3d202e2e2e60292c2073696e6365206170657274757265526571756972656d656e74206973206d616e6461746f727920696e20436f72650a20203b202845444943542d4f505449432d41504552545552452d5245462d303031292e0a20203f206170657274757265526571756972656d656e743a2061706572747572652d726571756972656d656e742c0a7d0a0a3b206170657274757265526571756972656d656e742069732061207479706564207265666572656e63652c206e65766572206120667265652d666f726d20737472696e672e2041207265766965770a3b2072656e646572696e67206d61792073686f772069747320636f6f7264696e617465202845444943542d4f505449432d41504552545552452d5245462d303031292e0a61706572747572652d726571756972656d656e74203d20666f6f747072696e742d6365696c696e672d726566202f2061627374726163742d666f6f747072696e742d6f626c69676174696f6e2d7265660a666f6f747072696e742d6365696c696e672d726566203d207b206b696e643a2022666f6f747072696e744365696c696e67222c207265663a2074737472207d0a61627374726163742d666f6f747072696e742d6f626c69676174696f6e2d726566203d207b206b696e643a20226162737472616374466f6f747072696e744f626c69676174696f6e222c207265663a2074737472207d0a0a3b202d2d2d2065646963742d636f72652e6364646c202d2d2d0a3b2065646963742d636f72652e6364646c0a3b204e6f726d617469766520736368656d6120666f722074686520456469637420436f72652076312073656d616e746963206d6f64656c2e0a3b0a3b2053636f706520626f756e646172793a20746869732066696c6520646566696e657320436f7265206d65616e696e6720616e6420736368656d61207368617065206f6e6c792e20497420646f65730a3b206e6f7420646566696e6520612063616e6f6e6963616c20656e636f6465722c20436f7265206d6f64756c652068617368206669656c64732c20686173682066697874757265732c207461726765740a3b206c6f776572696e672c2061646d697373696f6e2062756e646c65732c206f72207461726765742d6f776e65642049522e0a0a636f72652d6d6f64756c65203d207b0a202061706956657273696f6e3a202265646963742e636f72652f7631222c0a2020636f6f7264696e6174653a20747374722c0a2020696d706f7274733a205b2a20636f72652d696d706f72745d2c0a202074797065733a207b202a20636f72652d6e616d65642d747970652d726566203d3e20636f72652d74797065207d2c0a20203f2066756e6374696f6e733a207b202b2074737472203d3e20636f72652d736f757263652d66756e6374696f6e207d2c0a2020696e74656e74733a207b202b2074737472203d3e20636f72652d696e74656e74207d2c0a20207265717569726564436f72654361706162696c69746965733a205b2a20747374725d2c0a7d0a0a636f72652d696d706f7274203d207b0a20206b696e643a20226c61777061636b22202f202274617267657422202f2022636f726522202f20226361706162696c697479222c0a20207265663a207265736f757263652d7265662c0a20203f20616c6961733a20747374722c0a7d0a0a3b202d2d2d207479706573202d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d0a0a636f72652d74797065203d20636f72652d7363616c61722d74797065202f20636f72652d7265636f72642d74797065202f20636f72652d76617269616e742d74797065202f0a202020202020202020202020636f72652d6f7074696f6e2d74797065202f20636f72652d6c6973742d74797065202f20636f72652d6d61702d74797065202f0a202020202020202020202020636f72652d6361706162696c6974792d7265662d74797065202f20636f72652d65787465726e616c2d616374696f6e2d726571756573742d74797065202f0a202020202020202020202020636f72652d6e6f6d696e616c2d747970650a0a636f72652d7363616c61722d74797065203d20636f72652d626f6f6c2d74797065202f20636f72652d696e742d74797065202f20636f72652d737472696e672d74797065202f0a20202020202020202020202020202020202020636f72652d62797465732d74797065202f20636f72652d756e69742d747970650a0a636f72652d626f6f6c2d74797065203d207b206b696e643a2022426f6f6c22207d0a636f72652d756e69742d74797065203d207b206b696e643a2022556e697422207d0a636f72652d696e742d74797065203d207b0a20206b696e643a202249363422202f202255363422202f202249333222202f202255333222202f202249313622202f202255313622202f2022493822202f20225538222c0a7d0a636f72652d737472696e672d74797065203d207b0a20206b696e643a2022537472696e67222c0a20206d61783a2075696e742c0a202063616e6f6e6963616c3a2022756e69636f64652d7363616c61722d6e666322202f20227261772d75746638222c0a7d0a636f72652d62797465732d74797065203d207b0a20206b696e643a20224279746573222c0a20203f206d696e3a2075696e742c0a20206d61783a2075696e742c0a7d0a636f72652d6e6f6d696e616c2d74797065203d207b0a20206b696e643a20224e6f6d696e616c222c0a2020636f6e74726163743a20747374722c0a2020726570726573656e746174696f6e3a20636f72652d747970652d7265662c0a7d0a636f72652d7265636f72642d74797065203d207b0a20206b696e643a20225265636f7264222c0a20206669656c64733a207b202a2074737472203d3e20636f72652d747970652d726566207d2c0a7d0a636f72652d76617269616e742d74797065203d207b0a20206b696e643a202256617269616e74222c0a202063617365733a207b202b2074737472203d3e2076617269616e742d636173652d626f6479207d2c0a7d0a76617269616e742d636173652d626f6479203d207b0a20203f207061796c6f61643a20636f72652d747970652d7265662c0a7d0a636f72652d6f7074696f6e2d74797065203d207b0a20206b696e643a20224f7074696f6e222c0a20206974656d3a20636f72652d747970652d7265662c0a7d0a636f72652d6c6973742d74797065203d207b0a20206b696e643a20224c697374222c0a20206974656d3a20636f72652d747970652d7265662c0a20206d61783a2075696e742c0a7d0a636f72652d6d61702d74797065203d207b0a20206b696e643a20224d6170222c0a20206b65793a20636f72652d747970652d7265662c0a202076616c75653a20636f72652d747970652d7265662c0a20206d61783a2075696e742c0a7d0a636f72652d6361706162696c6974792d7265662d74797065203d207b0a20206b696e643a20224361706162696c697479526566222c0a20206974656d3a20636f72652d747970652d7265662c0a7d0a636f72652d65787465726e616c2d616374696f6e2d726571756573742d74797065203d207b0a20206b696e643a202245787465726e616c416374696f6e52657175657374222c0a2020736574746c656d656e743a20636f72652d747970652d7265662c0a7d0a0a3b20636f72652d747970652d72656620697320646566696e656420696e2065646963742d636f6d6d6f6e2e6364646c20616e6420617373656d626c65642077697468207468697320736368656d612e0a0a3b202d2d2d207265666572656e63657320616e642076616c756573202d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d0a0a3b204c6f63616c206964656e7469747920697320616c7068612d737461626c652e20606964602069732074686520636f6d70696c65722d6f776e6564206c6f63616c20636f6f7264696e6174653b0a3b2060616c7068614e616d656020697320746865206e6f726d616c697a65642068756d616e2f6465627567206e616d652e20536f757263652062696e646572207370656c6c696e67206973206e6f740a3b206964656e746974792e0a6c6f63616c2d726566203d207b0a202069643a20747374722c0a2020616c7068614e616d653a20747374722c0a2020747970653a20636f72652d747970652d7265662c0a7d0a0a636f72652d76616c7565203d20636f72652d6e756c6c2d76616c7565202f20636f72652d626f6f6c2d76616c7565202f20636f72652d696e742d76616c7565202f0a20202020202020202020202020636f72652d737472696e672d76616c7565202f20636f72652d62797465732d76616c7565202f20636f72652d7265636f72642d76616c7565202f0a20202020202020202020202020636f72652d76617269616e742d76616c7565202f20636f72652d6c6973742d76616c7565202f20636f72652d6d61702d76616c7565202f0a20202020202020202020202020636f72652d6361706162696c6974792d76616c75650a0a636f72652d6e756c6c2d76616c7565203d207b206b696e643a20226e756c6c22207d0a636f72652d626f6f6c2d76616c7565203d207b206b696e643a2022626f6f6c222c2076616c75653a20626f6f6c207d0a636f72652d696e742d76616c7565203d207b206b696e643a2022696e74222c2077696474683a20747374722c2076616c75653a20696e74207d0a636f72652d737472696e672d76616c7565203d207b206b696e643a2022737472696e67222c2076616c75653a2074737472207d0a636f72652d62797465732d76616c7565203d207b206b696e643a20226279746573222c2076616c75653a2062737472207d0a636f72652d7265636f72642d76616c7565203d207b206b696e643a20227265636f7264222c206669656c64733a207b202a2074737472203d3e20636f72652d76616c7565207d207d0a636f72652d76617269616e742d76616c7565203d207b0a20206b696e643a202276617269616e74222c0a2020747970653a20636f72652d747970652d7265662c0a2020636173653a20747374722c0a20203f207061796c6f61643a20636f72652d76616c75652c0a7d0a636f72652d6c6973742d76616c7565203d207b206b696e643a20226c697374222c2076616c7565733a205b2a20636f72652d76616c75655d207d0a636f72652d6d61702d76616c7565203d207b206b696e643a20226d6170222c20656e74726965733a205b2a205b6b65793a20636f72652d76616c75652c2076616c75653a20636f72652d76616c75655d5d207d0a636f72652d6361706162696c6974792d76616c7565203d207b0a20206b696e643a20226361706162696c697479222c0a2020726563656970743a207368613235362d6469676573742c0a7d0a0a3b2045646963742d617574686f72656420707572652068656c70657273207573652061207075726520436f72652066756e6374696f6e20626f64792e2054686520626f64792063616e2062696e640a3b20707572652065787072657373696f6e7320616e642072657475726e20616e2065787072657373696f6e2c206275742069742063616e6e6f7420636f6e7461696e20436f7265206566666563742c0a3b2067756172642c206272616e63682c206c6f6f702c206d617463682d6e6f64652c206f722070726f6f662d6f626c69676174696f6e206e6f6465732e0a3b20536f757263652d6f776e65642066756e6374696f6e7320757365207468652073616d65207075726520626f647920616c67656272612c207769746820746865697220726573756c7420747970650a3b2061757468656e746963617465642062792074686520656e636c6f73696e67206d6f64756c652e20416e20656d7074792066756e6374696f6e207461626c65206973206f6d69747465642e0a636f72652d736f757263652d66756e6374696f6e203d207b0a2020706172616d733a205b2a206c6f63616c2d7265665d2c0a202072657475726e547970653a20636f72652d747970652d7265662c0a2020626f64793a20636f72652d707572652d626c6f636b2c0a7d0a0a636f72652d666e2d626f6479203d207b0a2020706172616d733a205b2a206c6f63616c2d7265665d2c0a2020626f64793a20636f72652d707572652d626c6f636b2c0a7d0a0a636f72652d707572652d626c6f636b203d207b0a20206c6f63616c733a205b2a206c6f63616c2d7265665d2c0a202062696e64696e67733a205b2a20707572652d6c65742d6e6f64655d2c0a2020726573756c743a20636f72652d657870722c0a7d0a0a707572652d6c65742d6e6f6465203d207b0a20206b696e643a20226c6574222c0a202062696e64696e673a206c6f63616c2d7265662c0a202076616c75653a20636f72652d657870722c0a7d0a0a3b202d2d2d2065787072657373696f6e7320616e642070726564696361746573202d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d0a0a636f72652d65787072203d206c6f63616c2d65787072202f20636f6e73742d65787072202f207265636f72642d65787072202f206669656c642d65787072202f0a20202020202020202020202076617269616e742d65787072202f206d617463682d65787072202f2063616c6c2d65787072202f206c6973742d65787072202f206d61702d65787072202f0a20202020202020202020202069662d657870720a0a6c6f63616c2d65787072203d207b206b696e643a20226c6f63616c222c207265663a206c6f63616c2d726566207d0a636f6e73742d65787072203d207b206b696e643a2022636f6e7374222c2076616c75653a20636f72652d76616c7565207d0a7265636f72642d65787072203d207b206b696e643a20227265636f7264222c206669656c64733a207b202a2074737472203d3e20636f72652d65787072207d207d0a6669656c642d65787072203d207b206b696e643a20226669656c64222c20626173653a20636f72652d657870722c206669656c643a2074737472207d0a76617269616e742d65787072203d207b0a20206b696e643a202276617269616e74222c0a2020747970653a20636f72652d747970652d7265662c0a2020636173653a20747374722c0a20203f207061796c6f61643a20636f72652d657870722c0a7d0a6d617463682d65787072203d207b0a20206b696e643a20226d61746368222c0a20207363727574696e65653a20636f72652d657870722c0a202061726d733a205b2b206d617463682d61726d5d2c0a7d0a6d617463682d61726d203d207b0a2020636173653a20747374722c0a20203f2062696e6465723a206c6f63616c2d7265662c0a2020626f64793a20636f72652d657870722c0a7d0a63616c6c2d65787072203d207b0a20206b696e643a202263616c6c222c0a202063616c6c65653a20747374722c0a202074797065417267733a205b2a20636f72652d747970652d7265665d2c0a2020617267733a205b2a20636f72652d657870725d2c0a7d0a6c6973742d65787072203d207b206b696e643a20226c697374222c2076616c7565733a205b2a20636f72652d657870725d207d0a6d61702d65787072203d207b206b696e643a20226d6170222c20656e74726965733a205b2a205b6b65793a20636f72652d657870722c2076616c75653a20636f72652d657870725d5d207d0a69662d65787072203d207b0a20206b696e643a20226966222c0a20207072656469636174653a20636f72652d7072656469636174652c0a20207468656e3a20636f72652d657870722c0a2020656c73653a20636f72652d657870722c0a7d0a0a636f72652d707265646963617465203d20747275652d707265646963617465202f2066616c73652d707265646963617465202f206e6f742d707265646963617465202f0a2020202020202020202020202020202020616c6c2d707265646963617465202f20616e792d707265646963617465202f20636f6d706172652d707265646963617465202f0a202020202020202020202020202020202063616c6c2d707265646963617465202f206f62737472756374696f6e2d7072656469636174650a0a747275652d707265646963617465203d207b206b696e643a20227472756522207d0a66616c73652d707265646963617465203d207b206b696e643a202266616c736522207d0a6e6f742d707265646963617465203d207b206b696e643a20226e6f74222c2076616c75653a20636f72652d707265646963617465207d0a616c6c2d707265646963617465203d207b206b696e643a2022616c6c222c2076616c7565733a205b2b20636f72652d7072656469636174655d207d0a616e792d707265646963617465203d207b206b696e643a2022616e79222c2076616c7565733a205b2b20636f72652d7072656469636174655d207d0a636f6d706172652d707265646963617465203d207b0a20206b696e643a2022636f6d70617265222c0a20206f703a20223d3d22202f2022213d22202f20223c22202f20223c3d22202f20223e22202f20223e3d222c0a20206c6566743a20636f72652d657870722c0a202072696768743a20636f72652d657870722c0a7d0a63616c6c2d707265646963617465203d207b0a20206b696e643a202263616c6c222c0a20207072656469636174653a20747374722c0a2020617267733a205b2a20636f72652d657870725d2c0a7d0a6f62737472756374696f6e2d707265646963617465203d207b0a20206b696e643a20226f62737472756374696f6e222c0a2020636f6f7264696e6174653a206661696c7572652d6964656e742c0a20207061796c6f61643a20636f72652d657870722c0a7d0a0a696e7075742d636f6e73747261696e74203d207b0a2020636f6f7264696e6174653a20747374722c0a2020736f757263653a2022776865726522202f2022636f6d70696c6572222c0a20207072656469636174653a20636f72652d7072656469636174652c0a7d0a0a3b202d2d2d20696e74656e74732c20626c6f636b732c20616e64206e6f646573202d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d0a0a636f72652d696e74656e74203d207b0a2020696e7075743a20636f72652d747970652d7265662c0a20206f75747075743a20636f72652d747970652d7265662c0a202072657175697265644f7065726174696f6e50726f66696c653a20747374722c0a20203f2062617369733a20636f72652d657870722c0a2020696e707574436f6e73747261696e74733a205b2a20696e7075742d636f6e73747261696e745d2c0a2020636f72654576616c756174696f6e4275646765743a20636f72652d6275646765742c0a2020626f64793a20636f72652d626c6f636b2c0a20203f206f707469633a20636f72652d6f707469632c0a7d0a0a636f72652d627564676574203d207b0a20206d617853746570733a2075696e742c0a20206d6178416c6c6f636174656442797465733a2075696e742c0a20206d61784f757470757442797465733a2075696e742c0a7d0a0a636f72652d6f70746963203d207b0a20206f707469634b696e643a2022726576656c6174696f6e22202f20226166666563745265696e746567726174696f6e222c0a2020626f756e646172794b696e643a202270726f6a656374696f6e22202f2022616666656374222c0a20206170657274757265526571756972656d656e743a2061706572747572652d726571756972656d656e742c0a2020737570706f7274506f6c6963793a20747374722c0a20206c6f7373446973706f736974696f6e3a20747374722c0a7d0a0a636f72652d626c6f636b203d207b0a20206c6f63616c733a205b2a206c6f63616c2d7265665d2c0a20206e6f6465733a205b2a20636f72652d6e6f64655d2c0a2020726573756c743a20636f72652d657870722c0a7d0a0a636f72652d6e6f6465203d206c65742d6e6f6465202f20726571756972652d6e6f6465202f206566666563742d6e6f6465202f0a20202020202020202020202065787465726e616c2d616374696f6e2d726571756573742d6e6f6465202f2067756172642d6e6f6465202f206272616e63682d6e6f6465202f0a202020202020202020202020666f722d6e6f6465202f206d617463682d6e6f6465202f2070726f6f662d6f626c69676174696f6e2d6e6f64650a0a6c65742d6e6f6465203d207b0a20206b696e643a20226c6574222c0a202062696e64696e673a206c6f63616c2d7265662c0a202076616c75653a20636f72652d657870722c0a7d0a726571756972652d6e6f6465203d207b0a20206b696e643a202272657175697265222c0a20207072656469636174653a20636f72652d7072656469636174652c0a20206f6e4661696c7572653a20726571756972652d6661696c7572652d61726d2c0a7d0a726571756972652d6661696c7572652d61726d203d207465726d696e616c2d726571756972652d6661696c757265202f0a20202020202020202020202020202020202020202020636f6e74696e75652d6f6273747275637465642d726571756972652d6661696c7572650a7465726d696e616c2d726571756972652d6661696c757265203d207b0a20206b696e643a20227465726d696e616c222c0a2020726561736f6e3a206f62737472756374696f6e2d726561736f6e2c0a7d0a636f6e74696e75652d6f6273747275637465642d726571756972652d6661696c757265203d207b0a20206b696e643a2022636f6e74696e75654f627374727563746564222c0a2020726561736f6e3a206f62737472756374696f6e2d726561736f6e2c0a7d0a6f62737472756374696f6e2d726561736f6e203d207b0a2020726561736f6e4b696e643a20747374722c0a20207061796c6f61643a207b202a2074737472203d3e20636f72652d65787072207d2c0a7d0a6566666563742d6e6f6465203d207b0a20206b696e643a2022656666656374222c0a202062696e64696e673a206c6f63616c2d7265662c0a20206566666563743a20747374722c0a2020696e7075743a20636f72652d657870722c0a20206f62737472756374696f6e4d61703a207b202a206661696c7572652d6964656e74203d3e206f62737472756374696f6e2d61726d207d2c0a7d0a65787465726e616c2d616374696f6e2d726571756573742d6e6f6465203d207b0a20206b696e643a202265787465726e616c416374696f6e52657175657374222c0a202062696e64696e673a206c6f63616c2d7265662c0a20206f7065726174696f6e3a207265736f757263652d7265662c0a2020696e707574547970653a20636f72652d747970652d7265662c0a2020736574746c656d656e74547970653a20636f72652d747970652d7265662c0a2020696e707574536368656d613a207265736f757263652d7265662c0a2020736574746c656d656e74536368656d613a207265736f757263652d7265662c0a2020696e7075743a20636f72652d657870722c0a2020617574686f7269747953636f70653a20636f72652d657870722c0a202062617369733a20636f72652d657870722c0a20206275646765743a2065787465726e616c2d616374696f6e2d6275646765742c0a20207265636f6e63696c696174696f6e4c61773a207265736f757263652d7265662c0a202073746174653a20226177616974696e67536574746c656d656e74222c0a2020736574746c656d656e7441646d697373696f6e3a2022736368656d615265717569726564222c0a7d0a65787465726e616c2d616374696f6e2d627564676574203d207b0a20206d6178536574746c656d656e7442797465733a20636f72652d657870722c0a20206d6178417474656d7074733a20636f72652d657870722c0a7d0a6f62737472756374696f6e2d61726d203d207b0a202062696e6465723a206c6f63616c2d7265662c0a202076616c75653a20636f72652d657870722c0a7d0a67756172642d6e6f6465203d207b0a20206b696e643a20226775617264222c0a20207072656469636174653a20636f72652d7072656469636174652c0a20206f62737472756374696f6e3a20636f72652d657870722c0a7d0a6272616e63682d6e6f6465203d207b0a20206b696e643a20226272616e6368222c0a20203f2062696e64696e673a206c6f63616c2d7265662c0a20207072656469636174653a20636f72652d7072656469636174652c0a20207468656e3a20636f72652d626c6f636b2c0a2020656c73653a20636f72652d626c6f636b2c0a7d0a666f722d6e6f6465203d207b0a20206b696e643a2022666f72222c0a202062696e6465723a206c6f63616c2d7265662c0a2020697465723a20636f72652d657870722c0a2020626f756e643a20636f72652d626f756e642c0a2020626f64793a20636f72652d626c6f636b2c0a7d0a6d617463682d6e6f6465203d207b0a20206b696e643a20226d61746368222c0a20207363727574696e65653a20636f72652d657870722c0a202061726d733a205b2b206d617463682d626c6f636b2d61726d5d2c0a7d0a6d617463682d626c6f636b2d61726d203d207b0a2020636173653a20747374722c0a20203f2062696e6465723a206c6f63616c2d7265662c0a2020626f64793a20636f72652d626c6f636b2c0a7d0a70726f6f662d6f626c69676174696f6e2d6e6f6465203d207b0a20206b696e643a202270726f6f66222c0a2020636f6f7264696e6174653a20747374722c0a20207072656469636174653a20636f72652d7072656469636174652c0a7d0a0a636f72652d626f756e64203d206c69746572616c2d626f756e64202f20636f6f7264696e6174652d626f756e640a6c69746572616c2d626f756e64203d207b206b696e643a20226c69746572616c222c2076616c75653a2075696e74207d0a636f6f7264696e6174652d626f756e64203d207b206b696e643a2022636f6f7264696e617465222c207265663a2074737472207d0a0a3b20536861726564207265736f757263652d7265662c207368613235362d6469676573742c206661696c7572652d6964656e742c2061706572747572652d726571756972656d656e742c0a3b20636f72652d747970652d7265662c20616e6420636f72652d6e616d65642d747970652d7265662061726520646566696e6564206f6e636520696e2065646963742d636f6d6d6f6e2e6364646c2e0a0a3b202d2d2d2065646963742d6c61777061636b2e6364646c202d2d2d0a3b2065646963742d6c61777061636b2e6364646c0a3b2043616e6f6e6963616c20736368656d6120666f7220746865204564696374206c61777061636b206d616e696665737420616e64206578706f727420737572666163652e0a3b20417574686f7269746174697665206279746520656e636f64696e673a2065646963742e63616e6f6e6963616c2d63626f722f76312e204a534f4e20696e207468652070726f73652073706563730a3b2069732061207265766965772072656e646572696e672067656e6572617465642066726f6d207468697320736368656d61202845444943542d4142492d4e4f4455502d303031292e0a0a6c61777061636b2d6d616e6966657374203d207b0a202061706956657273696f6e3a202265646963742e6c61777061636b2f7631222c0a202069643a20747374722c0a202076657273696f6e3a20747374722c0a20206163636570746564436f72654162693a205b2b20747374725d2c0a2020646570656e64656e636965733a205b2a206c61777061636b2d6465705d2c202020202020202020203b20616379636c69632c206469676573742d6c6f636b6564202845444943542d4c41575041434b2d4441472d303031290a20206578706f7274733a207265736f757263652d7265662c0a20203f2074617267657441646170746572733a205b2b207461726765742d616461707465725d2c2020203b207265717569726564206f6e6c7920696620616e792072756e74696d6520656666656374206578697374730a20203f2068656c706572436f6d706f6e656e743a2065786563757461626c652d636f6d706f6e656e742c203b2065786563757461626c652068656c70657273206361727279207468656972206f776e2073616e64626f782b6675656c0a202076657269666965723a2076657269666965722c202020202020202020202020202020202020202020203b20636c61737369666965643a206465636c61726174697665206f722065786563757461626c650a2020636f6d7061746962696c6974793a207265736f757263652d7265662c0a2020636f6e666f726d616e636546697874757265436f727075733a207265736f757263652d7265662c0a7d0a0a3b2041207665726966696572206973206569746865722061206465636c617261746976652072756c6573657420286e6f2072756e74696d6529206f7220616e2065786563757461626c650a3b20636f6d706f6e656e742e20416e2065786563757461626c65207665726966696572204d55535420636172727920697473206f776e2073616e64626f7820616e64206675656c206d6f64656c2c0a3b20736f2074686520736368656d6120656e666f726365732074686174206e6f2065786563757461626c6520636f6d706f6e656e74206973206c65667420756e626f756e6465640a3b202845444943542d4142492d56455249464945522d424f554e442d303031292e0a7665726966696572203d206465636c617261746976652d7665726966696572202f2065786563757461626c652d76657269666965720a6465636c617261746976652d7665726966696572203d207b20636c6173733a20226465636c61726174697665222c2072756c657365743a207265736f757263652d726566207d0a65786563757461626c652d7665726966696572203d207b0a2020636c6173733a202265786563757461626c65222c0a2020636f6d706f6e656e743a207265736f757263652d7265662c0a202073616e64626f783a207265736f757263652d7265662c0a20206675656c4d6f64656c3a207265736f757263652d7265662c0a7d0a0a3b20416e792065786563757461626c6520636f6d706f6e656e7420697320626f756e64656420627920697473206f776e2073616e64626f78202b206675656c206d6f64656c2e0a65786563757461626c652d636f6d706f6e656e74203d207b0a2020636f6d706f6e656e743a207265736f757263652d7265662c0a202073616e64626f783a207265736f757263652d7265662c0a20206675656c4d6f64656c3a207265736f757263652d7265662c0a7d0a0a6c61777061636b2d646570203d207b2069643a20747374722c2076657273696f6e3a20747374722c206469676573743a207368613235362d646967657374207d0a0a3b20416461707465722073656c656374696f6e206b65797320534f4c454c59206f666620746865206469676573742d6c6f636b65642060616363657074656454617267657450726f66696c65600a3b20286974732060696460206973207468652070726f66696c652069643b206974732060646967657374602070696e73207468652065786163742070726f66696c652f76657273696f6e292e2054686572650a3b20617265206e6f20696e646570656e64656e7420646973706c617920737472696e6773207468617420636f756c64206469736167726565207769746820746865206c6f636b2c20736f20610a3b207265736f6c7665722063616e6e6f742062696e6420616e206164617074657220746f206f6e6520746172676574207768696c6520746865206c6f636b2070726f76657320616e6f746865720a3b202845444943542d4c41575041434b2d414441505445522d54415247455449522d303031292e0a7461726765742d61646170746572203d207b0a2020616363657074656454617267657450726f66696c653a207265736f757263652d7265662c202020203b206469676573742d6c6f636b65642c20617574686f72697461746976652073656c6563746f720a2020616363657074656454617267657449723a207265736f757263652d7265662c2020202020202020203b206469676573742d6c6f636b65640a2020616461707465723a207265736f757263652d7265662c0a7d0a0a3b20536861726564207479706573207265736f757263652d72656620616e64207368613235362d6469676573742061726520646566696e6564206f6e636520696e0a3b2065646963742d636f6d6d6f6e2e6364646c20616e6420617373656d626c65642077697468207468697320736368656d61202845444943542d4142492d4e4f4455502d303031292e0a0a3b202d2d2d206578706f72742073757266616365202d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d0a0a6c61777061636b2d6578706f727473203d207b0a202074797065733a205b2a206578706f727465642d747970655d2c0a2020636f6e7374616e74733a205b2a206578706f727465642d636f6e7374616e745d2c0a20207075726546756e6374696f6e733a205b2a20707572652d66756e6374696f6e5d2c0a2020656666656374733a205b2a2073656d616e7469632d6566666563745d2c0a20206f62737472756374696f6e733a205b2a206f62737472756374696f6e2d6465665d2c0a20203b206b65796564206279206f7065726174696f6e2d70726f66696c6520636f6f7264696e61746520e2869220756e697175656e65737320656e666f726365640a20203b202845444943542d4142492d4f5050524f46494c452d554e495155452d303031290a20203b206f7065726174696f6e2d70726f66696c65207265636f7264732074686973206c61777061636b206578706f72747320286f707469632074656d706c6174657320746861740a20203b2060696d706c656d656e7473602f6070726f66696c656020636c6175736573207265736f6c766520616761696e7374292e206f7065726174696f6e2d70726f66696c652069730a20203b20646566696e656420696e2065646963742d636f6d6d6f6e2e6364646c202845444943542d4142492d4f5050524f46494c452d534c4f542d303031292e0a20206f7065726174696f6e50726f66696c65733a207b202a2074737472203d3e206f7065726174696f6e2d70726f66696c65207d2c20203b206b6579656420627920636f6f7264696e6174650a7d0a0a6578706f727465642d7479706520202020203d207b20636f6f7264696e6174653a20747374722c20646566696e6974696f6e3a20636f72652d747970652d726566207d0a6578706f727465642d636f6e7374616e74203d207b20636f6f7264696e6174653a20747374722c20747970653a20636f72652d747970652d7265662c2076616c75653a20616e79207d0a0a3b204120707572652068656c7065722069732061206469736372696d696e6174656420756e696f6e2062792060736f75726365602c20736f2074686520736368656d6120697473656c660a3b2067756172616e7465657320616e20696d706c656d656e746174696f6e20657869737473202845444943542d4c41575041434b2d505552452d494d504c2d303031293a0a3b2020202d20226564696374223a20617574686f72656420696e2045646963742f436f72653b2074686520436f726520626f6479206973206361727269656420696e6c696e6520286861736865640a3b20202020207769746820746865206578706f72742073757266616365292e2054686520736368656d61207265717569726573207468652060626f647960206669656c642e0a3b2020202d2022636f6d706f6e656e74223a20696d706c656d656e746564206f7574736964652045646963743b2063617272696573206e6f20696e6c696e6520626f647920616e6420696e73746561640a3b20202020206361727269657320697473206f776e206469676573742d6c6f636b65642060696d706c656d656e746174696f6e60202873616e64626f78202b206675656c292e20497420646f65730a3b20202020206e6f7420646570656e64206f6e20746865206f7074696f6e616c206d616e69666573742d6c6576656c2068656c706572436f6d706f6e656e742e0a707572652d66756e6374696f6e203d2065646963742d707572652d66756e6374696f6e202f20636f6d706f6e656e742d707572652d66756e6374696f6e0a0a707572652d66756e6374696f6e2d636f6d6d6f6e203d20280a2020636f6f7264696e6174653a20747374722c0a202074797065506172616d65746572733a205b2a20747374725d2c0a2020706172616d6574657254797065733a205b2a20636f72652d747970652d7265665d2c2020202020203b20616c6c20626f756e6465640a202072657475726e547970653a20636f72652d747970652d7265662c20202020202020202020202020203b20626f756e6465640a2020636f737454656d706c6174653a20747374722c0a202064657465726d696e69736d436c6173733a2022746f74616c22202f2022746f74616c2d776974682d74797065642d646961676e6f73746963222c0a290a0a65646963742d707572652d66756e6374696f6e203d207b0a2020707572652d66756e6374696f6e2d636f6d6d6f6e2c0a2020736f757263653a20226564696374222c0a2020626f64793a20636f72652d666e2d626f64792c2020202020202020202020202020202020202020203b20696e6c696e652c20686173682d7369676e69666963616e740a7d0a0a636f6d706f6e656e742d707572652d66756e6374696f6e203d207b0a2020707572652d66756e6374696f6e2d636f6d6d6f6e2c0a2020736f757263653a2022636f6d706f6e656e74222c0a20203b20746865206469676573742d6c6f636b656420636f6d706f6e656e7420696d706c656d656e74696e6720746869732068656c7065722e2052657175697265642061742074686520736368656d610a20203b206c6576656c20736f206120636f6d706f6e656e742068656c7065722063616e206e657665722076616c696461746520776974686f7574206120686173682d626f756e642c0a20203b2073616e64626f782b6675656c2d64657363726962656420696d706c656d656e746174696f6e202845444943542d4c41575041434b2d505552452d494d504c2d303031292e0a2020696d706c656d656e746174696f6e3a2065786563757461626c652d636f6d706f6e656e742c0a7d0a0a3b20636f72652d666e2d626f647920697320646566696e65642062792065646963742d636f72652e6364646c20616e6420617373656d626c656420776974682074686973206c61777061636b0a3b20736368656d612e2049742069732061207075726520436f72652066756e6374696f6e20626f64792c206e6f7420616e206566666563742d63617061626c6520636f72652d626c6f636b2e0a0a73656d616e7469632d656666656374203d207b0a2020636f6f7264696e6174653a20747374722c0a202074797065506172616d65746572733a205b2a20747374725d2c0a2020696e707574547970653a20636f72652d747970652d7265662c2020202020202020202020202020203b20626f756e6465640a20206f7574707574547970653a20636f72652d747970652d7265662c20202020202020202020202020203b20626f756e6465640a2020657865637574696f6e436c6173733a202270726f6f664f6e6c7922202f202272756e74696d65222c2020203b206f7274686f676f6e616c20746f207772697465436c6173730a20206566666563744b696e6448696e743a206566666563742d6b696e642c0a2020666f6f747072696e744f626c69676174696f6e3a20747374722c0a2020636f73744f626c69676174696f6e3a20747374722c0a20206566666563744661696c757265733a207b202a206661696c7572652d6964656e74203d3e206566666563742d6661696c7572652d626f6479207d2c20203b206b6579656420627920636f6f7264696e6174653b20756e697175650a20206775617264537570706f72743a20626f6f6c2c0a7d0a0a6f62737472756374696f6e2d646566203d207b0a2020636f6f7264696e6174653a20747374722c0a2020617574686f72697479436c6173733a20617574686f726974792d636c6173732c0a20207061796c6f6164536368656d613a20636f72652d747970652d7265662c20202020202020202020203b2074797065642c20626f756e64656420286d617920626520656d707479207265636f7264290a7d0a0a3b206566666563742d6661696c7572652d626f64792c206566666563742d6b696e642c20617574686f726974792d636c6173732c20616e6420636f72652d747970652d7265662061726520646566696e65640a3b206f6e636520696e2065646963742d636f6d6d6f6e2e6364646c20616e6420617373656d626c65642077697468207468697320736368656d61202845444943542d4142492d4e4f4455502d303031292e0a0a3b202d2d2d2065646963742d6c61777061636b2d616461707465722e6364646c202d2d2d0a3b2065646963742d6c61777061636b2d616461707465722e6364646c0a3b2043616e6f6e6963616c20736368656d6120666f72206f6e6520646972656374206465636c61726174697665206c61777061636b2074617267657420616461707465722e0a3b20417574686f7269746174697665206279746520656e636f64696e673a2065646963742e63616e6f6e6963616c2d63626f722f76312e0a3b0a3b2054686520656e636c6f73696e67206c61777061636b206d616e69666573742073656c6563747320746865206578616374207461726765742070726f66696c652c207461726765742049522c0a3b20616e642061646170746572207265736f75726365206469676573742e2054686f7365206964656e74697469657320617265206e6f7420726570656174656420686572652e0a0a6c61777061636b2d61646170746572203d207b0a202061706956657273696f6e3a202265646963742e6c61777061636b2d616461707465722f7631222c0a2020636c6173733a20226465636c61726174697665222c0a20206f7065726174696f6e50726f66696c65733a207b0a202020202a2074737472203d3e206c61777061636b2d616461707465722d6f7065726174696f6e2d70726f66696c650a20207d2c0a2020656666656374496d706c656d656e746174696f6e733a207b0a202020202a2074737472203d3e206c61777061636b2d616461707465722d6566666563740a20207d2c0a2020627564676574733a207b0a202020202a2074737472203d3e206c61777061636b2d616461707465722d6275646765740a20207d2c0a7d0a0a3b204b657973206172652063616e6f6e6963616c206c61777061636b206f7065726174696f6e2d70726f66696c6520636f6f7264696e617465732e0a6c61777061636b2d616461707465722d6f7065726174696f6e2d70726f66696c65203d0a20207b0a20202020636f72653a20747374722c0a2020202073656d616e746963456666656374733a205b2b20747374725d2c0a202020203f206275646765744f626c69676174696f6e3a20747374722c0a202020203f20746172676574436f6e66696775726174696f6e3a207265736f757263652d7265662c0a20207d202f0a20207b0a20202020636f72653a20747374722c0a2020202073656d616e746963456666656374733a205b5d2c0a202020206275646765744f626c69676174696f6e3a20747374722c0a20202020746172676574436f6e66696775726174696f6e3a207265736f757263652d7265662c0a20207d0a0a3b204b657973206172652063616e6f6e6963616c206c61777061636b2073656d616e7469632d65666665637420636f6f7264696e617465732e20466f6f747072696e742c20636f73742c20616e640a3b206661696c757265206669656c6473206d7573742065786163746c792064697363686172676520746865206d61746368696e67206578706f72746564206566666563742e0a6c61777061636b2d616461707465722d656666656374203d207b0a2020746172676574496e7472696e7369633a20747374722c0a2020746172676574436f6e66696775726174696f6e3a207265736f757263652d7265662c0a20207772697465436c6173733a206c61777061636b2d616461707465722d77726974652d636c6173732c0a2020666f6f747072696e744f626c69676174696f6e3a20747374722c0a2020636f73744f626c69676174696f6e3a20747374722c0a20206661696c7572654d617070696e67733a207b202a206661696c7572652d6964656e74203d3e2074737472207d2c0a7d0a0a3b204b657973206172652063616e6f6e6963616c206578706f7274656420636f73742d6f626c69676174696f6e20636f6f7264696e617465732e0a6c61777061636b2d616461707465722d627564676574203d207b0a20206d617853746570733a2075696e742c0a20206d6178416c6c6f636174656442797465733a2075696e742c0a20206d61784f757470757442797465733a2075696e742c0a7d0a0a6c61777061636b2d616461707465722d77726974652d636c617373203d20226e6f6e6522202f20227265616422202f202263726561746522202f2022656e7375726522202f0a20202020202020202020202020202020202020202020202020202020202022617070656e6422202f20227265706c61636522202f202264656c65746522202f2022637573746f6d220a0a3b206661696c7572652d6964656e7420697320646566696e656420696e2065646963742d636f6d6d6f6e2e6364646c2e0a0a3b202d2d2d2065646963742d7461726765742d70726f66696c652e6364646c202d2d2d0a3b2065646963742d7461726765742d70726f66696c652e6364646c0a3b2043616e6f6e6963616c20736368656d6120666f7220746865204564696374207461726765742070726f66696c65206d616e69666573742e0a3b20417574686f7269746174697665206279746520656e636f64696e673a2065646963742e63616e6f6e6963616c2d63626f722f76310a3b202873656520535045435f636f6e74696e75756d2d636f6e74726163742d62756e646c652d76312e6d64292e204a534f4e20696e207468652070726f736520737065637320697320610a3b207265766965772072656e646572696e672067656e6572617465642066726f6d207468697320736368656d613b2074686973204344444c206973207468652073696e676c6520736f757263650a3b206f66207472757468202845444943542d4142492d4e4f4455502d303031292e0a0a7461726765742d70726f66696c652d6d616e6966657374203d207b0a202061706956657273696f6e3a202265646963742e7461726765742d70726f66696c652f7631222c0a202069643a20747374722c202020202020202020202020202020202020202020202020203b20652e672e20226563686f2e64706f220a202076657273696f6e3a20747374722c20202020202020202020202020202020202020203b20652e672e202231220a20206163636570746564436f72654162693a205b2b20747374725d2c20202020202020203b20652e672e205b2265646963742e636f72652f7631225d0a0a2020696e7472696e736963733a207265736f757263652d7265662c0a2020696e7472696e7369634e616d6573706163653a20747374722c0a20203b207075626c697368657320746869732070726f66696c652773206f7065726174696f6e2d70726f66696c65207265636f72647320286f707469632074656d706c6174657320746861740a20203b206070726f66696c65602f60696d706c656d656e74736020636c6175736573207265736f6c766520616761696e7374292e205265666572656e63657320616e0a20203b206f7065726174696f6e2d70726f66696c65732d646f63756d656e74202845444943542d4142492d4f5050524f46494c452d534c4f542d303031292e0a20206f7065726174696f6e50726f66696c65733a207265736f757263652d7265662c0a2020666f6f747072696e74416c67656272613a207265736f757263652d7265662c0a2020636f7374416c67656272613a207265736f757263652d7265662c0a202074617267657449723a207265736f757263652d7265662c0a20206f62737472756374696f6e5461786f6e6f6d793a207265736f757263652d7265662c0a202076657269666965723a207265736f757263652d7265662c0a20206c6f77657265723a207265736f757263652d7265662c0a202073616e64626f783a207265736f757263652d7265662c0a20206675656c4d6f64656c3a207265736f757263652d7265662c0a0a20203b206669656c647320746865206c616e67756167652073706563207265717569726573206f662065766572792070726f66696c650a202062756e646c6550726f66696c653a207265736f757263652d7265662c0a202067656e657261746564417274696661637450726f66696c65733a205b2a207265736f757263652d7265665d2c0a202063616e6f6e6963616c456e636f64696e6752756c65733a207265736f757263652d7265662c0a20203b20412070726f66696c65207468617420616363657074732074686520646972656374206465636c61726174697665206c61777061636b2d6164617074657220414249206e616d65732069740a20203b2065786163746c79206f6e63652e2050726f66696c6573207468617420646f206e6f7420636f6e73756d65206c61777061636b206164617074657273206c6561766520746869730a20203b206f7074696f6e616c20736c6f7420616273656e74206f7220656d7074792e0a20203f2061636365707465644c61777061636b416461707465724162693a205b5d202f205b2265646963742e6c61777061636b2d616461707465722f7631225d2c0a2020646961676e6f737469634162693a207265736f757263652d7265662c0a0a20203b206170706c69636174696f6e20646f637472696e650a20206170706c69636174696f6e4d6f64656c3a202261746f6d6963222c0a202072656164436f6e73697374656e63793a20226170706c69636174696f6e2d736e617073686f7422202f20747374722c0a202067756172644576616c756174696f6e3a2022707265636f6d6d69742d61746f6d696322202f20747374722c0a20206f62737472756374696f6e526f6c6c6261636b3a20226e6f2d76697369626c652d6566666563747322202f20747374722c0a20206d756c74695461726765743a20626f6f6c2c0a20203b207768657468657220746865207461726765742063616e206576616c7561746520707265636f6d6d697420706f7374636f6e646974696f6e20286067756172616e746565602920636865636b730a20203b20696e73696465207468652061746f6d6963206170706c69636174696f6e20756e6974202845444943542d5441524745542d504f5354434f4e442d303031290a2020706f7374636f6e646974696f6e537570706f72743a20626f6f6c2c0a0a202064657465726d696e6973746963457865637574696f6e3a207265736f757263652d7265662c0a2020636f6e666f726d616e636546697874757265436f727075733a207265736f757263652d7265662c0a7d0a0a3b20536861726564207479706573207265736f757263652d72656620616e64207368613235362d6469676573742061726520646566696e6564206f6e636520696e0a3b2065646963742d636f6d6d6f6e2e6364646c20616e6420617373656d626c65642077697468207468697320736368656d6120627920746865206275696c640a3b202845444943542d4142492d4e4f4455502d303031292e205468657920617265206e6f74207265646566696e656420686572652e0a0a3b202d2d2d20696e7472696e736963207369676e6174757265202d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d0a3b20546865206172746966616374207265666572656e63656420627920746865206d616e696665737427732060696e7472696e7369637360207265736f757263652d726566206973207468650a3b20696e7472696e7369632d7369676e617475726520636f7270757320646f63756d656e742062656c6f772e20497473206c61796f757420697320666978656420736f2074776f0a3b20696e646570656e64656e742070726f66696c65732076616c69646174652f686173682074686520636f72707573206964656e746963616c6c790a3b202845444943542d4142492d494e5452494e534943532d444f432d303031292e0a0a3b20696e7472696e736963732069732061204d4150206b6579656420627920636f6f7264696e6174652c20736f2074686520736368656d6120697473656c6620656e666f726365730a3b20636f6f7264696e61746520756e697175656e6573732e20412070726f766964657220726563656976657320746865207265736f6c76656420636f7270757320617320610a3b206469676573742d626f756e642073656d616e74696320696e70757420616e64207265736f6c76657320636f6f7264696e617465732077697468696e20746861742061727469666163742e0a3b2045616368206d6170206b6579204d55535420657175616c20697473207265636f726427732060636f6f7264696e61746560206669656c640a3b202845444943542d4142492d494e5452494e5349432d554e495155452d303031292e0a696e7472696e736963732d646f63756d656e74203d207b0a202061706956657273696f6e3a202265646963742e7461726765742d70726f66696c652e696e7472696e736963732f7631222c0a2020696e7472696e736963733a207b202a2074737472203d3e20696e7472696e736963207d2c0a7d0a0a3b20546865206172746966616374207265666572656e63656420627920746865206d616e6966657374277320606f7065726174696f6e50726f66696c657360207265736f757263652d7265662e0a3b206f7065726174696f6e2d70726f66696c65202f206f707469632d74656d706c6174652061726520646566696e656420696e2065646963742d636f6d6d6f6e2e6364646c2e204b657965642062790a3b20636f6f7264696e61746520736f207265736f6c7574696f6e2063616e2774207069636b206265747765656e2074776f2073616d652d636f6f7264696e6174652070726f66696c65730a3b202845444943542d4142492d4f5050524f46494c452d534c4f542d3030312c2045444943542d4142492d4f5050524f46494c452d554e495155452d303031292e0a6f7065726174696f6e2d70726f66696c65732d646f63756d656e74203d207b0a202061706956657273696f6e3a202265646963742e7461726765742d70726f66696c652e6f7065726174696f6e2d70726f66696c65732f7631222c0a202070726f66696c65733a207b202a2074737472203d3e206f7065726174696f6e2d70726f66696c65207d2c0a7d0a0a3b2041207479706564207072652d6c6f776572696e67207175657374696f6e20746861742063616e2062652070726f706f73656420627920576174736f6e206f7220616e206167656e7420616e640a3b20636865636b65642062792074686520636f6d70696c65722e2049742069732063616e6f6e6963616c2d43424f5220656e636f64656420756e6465720a3b206065646963742e6c6f776572696e672d726571756972656d656e74732f7631603b2074686520636f6d70696c657220636865636b7320746869732061727469666163742c206e6f74207468650a3b2070726f736520746861742070726f64756365642069742e0a6c6f776572696e672d726571756972656d656e7473203d207b0a202061706956657273696f6e3a202265646963742e6c6f776572696e672d726571756972656d656e74732f7631222c0a20206f7065726174696f6e50726f66696c653a20747374722c0a202073656d616e746963456666656374733a205b2a2073656d616e7469632d6566666563742d726571756972656d656e745d2c0a202072657175697265645772697465436c61737365733a205b2a2077726974652d636c6173735d2c0a202067756172644b696e64733a205b2a2067756172642d6b696e645d2c0a202061746f6d69636974793a2061746f6d69636974792d726571756972656d656e742c0a2020706f7374636f6e646974696f6e537570706f72743a20626f6f6c2c0a20206f62737472756374696f6e436f6f7264696e617465733a205b2a20747374725d2c0a2020666f6f747072696e744f626c69676174696f6e733a205b2a20747374725d2c0a2020636f73744f626c69676174696f6e733a205b2a20747374725d2c0a20206f70746963436f6e74726163743a20747374722c0a7d0a0a73656d616e7469632d6566666563742d726571756972656d656e74203d207b0a2020636f6f7264696e6174653a20747374722c0a20207772697465436c6173733a2077726974652d636c6173732c0a202067756172644b696e64733a205b2a2067756172642d6b696e645d2c0a20206f62737472756374696f6e436f6f7264696e617465733a205b2a20747374725d2c0a2020666f6f747072696e744f626c69676174696f6e733a205b2a20747374725d2c0a2020636f73744f626c69676174696f6e733a205b2a20747374725d2c0a7d0a0a77726974652d636c617373203d20226e6f6e6522202f20227265616422202f202263726561746522202f2022656e7375726522202f2022617070656e6422202f0a2020202020202020202020202020227265706c61636522202f202264656c65746522202f20747374720a67756172642d6b696e64203d2022707265636f6d6d69742d61746f6d696322202f20747374720a61746f6d69636974792d726571756972656d656e74203d202261746f6d696322202f20747374720a0a3b20412067656e75696e6520756e696f6e3a207075726520636f6e7374727563746f7273206361727279206e6f20656666656374206b696e64206f72206661696c757265733b206566666563740a3b20696e7472696e73696373206d757374202845444943542d5441524745542d494e5452494e5349432d434c4153532d303031292e2054686520736368656d6120656e666f7263657320746869732c0a3b206e6f74206120636f6d6d656e742e0a0a3b2054686520696e7472696e736963277320636f6f7264696e6174652069732074686520696e7472696e73696373206d6170204b45592c206e6f7420612076616c7565206669656c642c20736f207468650a3b206b657920616e6420636f6f7264696e6174652063616e206e65766572206469736167726565202845444943542d4142492d494e5452494e5349432d554e495155452d303031292e0a696e7472696e736963203d20707572652d696e7472696e736963202f206566666563742d696e7472696e7369630a0a707572652d696e7472696e736963203d207b0a2020696e7472696e736963436c6173733a202270757265222c0a202074797065506172616d65746572733a205b2a20747374725d2c0a2020617267756d656e7454797065733a205b2a20636f72652d747970652d7265665d2c0a202072657475726e547970653a20636f72652d747970652d7265662c0a20206775617264537570706f72743a2066616c73652c0a2020666f6f747072696e7454656d706c6174653a20747374722c0a2020636f737454656d706c6174653a20747374722c0a20207772697465436c6173733a20226e6f6e65222c0a7d0a0a6566666563742d696e7472696e736963203d207b0a2020696e7472696e736963436c6173733a2022656666656374222c0a202074797065506172616d65746572733a205b2a20747374725d2c0a2020617267756d656e7454797065733a205b2a20636f72652d747970652d7265665d2c0a202072657475726e547970653a20636f72652d747970652d7265662c0a20206566666563744b696e643a206566666563742d6b696e642c0a20203b206d6170206b65796564206279206661696c75726520636f6f7264696e61746520286661696c7572652d6964656e74293b20746865206661696c75726520636f6f7264696e6174652069730a20203b20746865206b65792c206e6f7420612076616c7565206669656c642c20736f20756e697175656e657373206973207374727563747572616c0a20203b202845444943542d4142492d4641494c5552452d554e495155452d303031292e0a20206566666563744661696c757265733a207b202a206661696c7572652d6964656e74203d3e206566666563742d6661696c7572652d626f6479207d2c0a20206775617264537570706f72743a20626f6f6c2c0a2020666f6f747072696e7454656d706c6174653a20747374722c0a2020636f737454656d706c6174653a20747374722c0a20207772697465436c6173733a20227265616422202f202263726561746522202f2022656e7375726522202f2022617070656e6422202f20227265706c61636522202f0a20202020202020202020202020202264656c65746522202f2022637573746f6d222c0a202063616e5061727469636970617465496e41746f6d696347756172643a20626f6f6c2c0a7d0a0a3b206566666563742d6661696c7572652d626f64792c206566666563742d6b696e642c20617574686f726974792d636c6173732c20616e6420636f72652d747970652d7265662061726520646566696e65640a3b206f6e636520696e2065646963742d636f6d6d6f6e2e6364646c20616e6420617373656d626c65642077697468207468697320736368656d61202845444943542d4142492d4e4f4455502d303031292e0a0a3b202d2d2d2065646963742d617574686f726974792d66616374732e6364646c202d2d2d0a3b2065646963742d617574686f726974792d66616374732e6364646c0a3b2043616e6f6e6963616c20736368656d6120666f722074686520666972737420636f6d70696c65722d636f6e7465787420617574686f726974792d666163747320646f63756d656e742e0a3b20417574686f7269746174697665206279746520656e636f64696e673a2065646963742e63616e6f6e6963616c2d63626f722f76312e0a3b0a3b205468697320736368656d6120697320617373656d626c656420776974682065646963742d636f6d6d6f6e2e6364646c20736f20736f757263652e6469676573742075736573207468650a3b20736861726564207368613235362d6469676573742074797065642076616c75652e204a534f4e2069732061207265766965772f696e7075742072656e646572696e673a206974730a3b20607368613235363a3c3634206865783e6020736f75726365206469676573742069732070726f6a656374656420746f205b60736861323536602c203332207261772062797465735d206f6e0a3b2074686520776972652c20616e64206974732066616374206172726179732070726f6a65637420746f2074686520636f6f7264696e6174652d6b65796564206d6170732062656c6f772e0a0a617574686f726974792d6661637473203d207b0a202061706956657273696f6e3a202265646963742e617574686f726974792d66616374732f7631222c0a2020736f757263653a20617574686f726974792d666163742d736f757263652c0a20206f7065726174696f6e50726f66696c65733a207b202a2074737472203d3e20617574686f726974792d6f7065726174696f6e2d70726f66696c652d66616374207d2c0a20206566666563745772697465436c61737365733a207b202a2074737472203d3e20617574686f726974792d77726974652d636c617373207d2c0a2020627564676574733a207b202a2074737472203d3e20617574686f726974792d6275646765742d66616374207d2c0a7d0a0a617574686f726974792d666163742d736f75726365203d207b0a20206b696e643a20226c61777061636b22202f202274617267657450726f66696c65222c0a2020636f6f7264696e6174653a20747374722c0a20206469676573743a207368613235362d6469676573742c0a7d0a0a3b20546865206d6170206b65792069732074686520736f75726365206f7065726174696f6e2d70726f66696c6520636f6f7264696e6174652e204974206973206e6f7420726570656174656420696e0a3b207468652076616c75652c20736f2061206b657920616e6420656d62656464656420636f6f7264696e6174652063616e6e6f742064697361677265652e20416c6c6f7765642077726974650a3b20636c61737365732061726520612063616e6f6e6963616c206d61702d7365743a2074686520636c6173732069732074686520756e69717565206b657920616e64206e756c6c206973207468650a3b20756e6974206d61726b65722e2043616e6f6e6963616c2043424f52206669786573206b6579206f7264657220776974686f75742061207365636f6e64206f72646572696e672072756c652e0a617574686f726974792d6f7065726174696f6e2d70726f66696c652d66616374203d207b0a2020636f72653a20747374722c0a2020616c6c6f7765645772697465436c61737365733a207b202a20617574686f726974792d77726974652d636c617373203d3e206e756c6c207d2c0a7d0a0a3b20546865206566666563745772697465436c6173736573206d6170206b6579206973207468652073656d616e7469632065666665637420636f6f7264696e6174652e2054686520627564676574730a3b206d6170206b65792069732074686520736f757263652062756467657420636f6f7264696e6174652e2043616e6f6e6963616c2043424f52206d61702d6b657920756e697175656e6573730a3b206d616b6573206475706c6963617465206661637420636f6f7264696e61746573207374727563747572616c6c7920756e726570726573656e7461626c652e0a617574686f726974792d6275646765742d66616374203d207b0a20206d617853746570733a2075696e742c0a20206d6178416c6c6f636174656442797465733a2075696e742c0a20206d61784f757470757442797465733a2075696e742c0a7d0a0a3b20417574686f726974794661637473446f63756d656e7420763120696e74656e74696f6e616c6c792061636365707473206f6e6c792074686520777269746520636c6173736573207468650a3b2063757272656e7420636f6d70696c6572206d6f64656c2063616e20636f6e73756d652e2060637573746f6d602069732074686520736f6c6520763120637573746f6d207370656c6c696e673b0a3b20617262697472617279207461726765742d70726f66696c6520657874656e73696f6e20737472696e677320646f206e6f7420656e746572207468697320636f6d70696c657220706174682e0a617574686f726974792d77726974652d636c617373203d20226e6f6e6522202f20227265616422202f202263726561746522202f2022656e7375726522202f2022617070656e6422202f0a202020202020202020202020202020202020202020202020227265706c61636522202f202264656c65746522202f2022637573746f6d220a0a3b202d2d2d2065646963742d726573756c742d70726f6a656374696f6e2e6364646c202d2d2d0a3b20535044582d4c6963656e73652d4964656e7469666965723a204170616368652d322e300a3b2065646963742d726573756c742d70726f6a656374696f6e2e6364646c0a3b2043616e6f6e6963616c20736368656d6120666f7220636f6d70696c65722d6f776e6564206170706c69636174696f6e2d726573756c742070726f6a656374696f6e732e0a3b20417574686f7269746174697665206279746520656e636f64696e673a2065646963742e63616e6f6e6963616c2d63626f722f76312e0a0a726573756c742d70726f6a656374696f6e203d207b0a2020736368656d613a202265646963742e726573756c742d70726f6a656374696f6e2f7631222c0a20206f7065726174696f6e436f6f7264696e6174653a20626f756e6465642d70726f6a656374696f6e2d746578742c0a20206f7574707574547970653a20626f756e6465642d70726f6a656374696f6e2d746578742c0a20206d61784f757470757442797465733a2075696e74202e677420302c0a202065787072657373696f6e3a20726573756c742d70726f6a656374696f6e2d657870722c0a7d0a0a726573756c742d70726f6a656374696f6e2d65787072203d20726573756c742d70726f6a656374696f6e2d7265636f7264202f20726573756c742d70726f6a656374696f6e2d736f757263650a0a726573756c742d70726f6a656374696f6e2d7265636f7264203d207b0a20206b696e643a20227265636f7264222c0a20203b2054686520726f6f74207265636f726420636f756e7473206173206f6e65206f66207468652052757374206465636f6465722773203235362065787072657373696f6e206e6f6465732e0a20203b204e657374656420616767726567617465206e6f646520636f756e742072656d61696e7320616e20617574686f7269746174697665206465636f64657220636865636b2e0a20206669656c64733a207b20302a32353520626f756e6465642d70726f6a656374696f6e2d74657874203d3e20726573756c742d70726f6a656374696f6e2d65787072207d2c0a7d0a0a726573756c742d70726f6a656374696f6e2d736f75726365203d207b0a20206b696e643a2022736f75726365222c0a2020736f757263653a20726573756c742d70726f6a656374696f6e2d736f757263652d6b696e642c0a20203b204d617463686573204d41585f524553554c545f50524f4a454354494f4e5f504154485f5345474d454e545320696e2065646963742d73796e7461782e0a2020706174683a205b302a333220626f756e6465642d70726f6a656374696f6e2d746578745d2c0a7d0a0a726573756c742d70726f6a656374696f6e2d736f757263652d6b696e64203d0a20207b206b696e643a20226170706c69636174696f6e496e70757422207d202f0a20207b0a202020206b696e643a20227075726542696e64696e67222c0a2020202062696e64696e6749643a20626f756e6465642d70726f6a656374696f6e2d746578742c0a20207d202f0a20207b0a202020206b696e643a20226361706162696c697479526573756c74222c0a202020207374657049643a20626f756e6465642d70726f6a656374696f6e2d746578742c0a20207d0a0a626f756e6465642d70726f6a656374696f6e2d74657874203d2074737472202e73697a652028312e2e31303234290a0a3b202d2d2d2065646963742d7461726765742d69722e6364646c202d2d2d0a3b20535044582d4c6963656e73652d4964656e7469666965723a204170616368652d322e300a3b2065646963742d7461726765742d69722e6364646c0a3b2043616e6f6e6963616c20736368656d6120666f72207468652045646963742d6f776e65642054617267657420495220617274696661637420656e76656c6f70652e0a3b20417574686f7269746174697665206279746520656e636f64696e673a2065646963742e63616e6f6e6963616c2d63626f722f76312e0a3b0a3b205468697320736368656d6120697320617373656d626c656420776974682065646963742d636f6d6d6f6e2e6364646c20616e642065646963742d636f72652e6364646c2e2049740a3b2064656c696265726174656c792072657573657320436f72652065787072657373696f6e732c20707265646963617465732c20627564676574732c206c6f63616c207265666572656e6365732c0a3b206f62737472756374696f6e20726561736f6e732c20616e64206f62737472756374696f6e2061726d7320736f2074686520736368656d61206d617463686573207468652076616c75650a3b20656d6974746564206279207468652063616e6f6e6963616c2054617267657420495220656e636f64657220726174686572207468616e20726573746174696e672074686f73652074797065732e0a3b2049742064657363726962657320746865207374727563747572616c207368617065206f662076616c6964206c6f776572696e672d70726f6475636564206172746966616374732e205468650a3b206c6f776572696e6720616e6420656e636f64657220636f6e7472616374732073657061726174656c7920656e666f7263652073656d616e746963206964656e7469666965722072756c65730a3b20616e642063616e6f6e6963616c206f72646572696e672f64656475706c69636174696f6e20666f72207365742d6c696b652076616c7565732e0a0a3b2054617267657420495220656e636f64696e672072656a6563747320616e20656d707479207461726765742d70726f66696c6520636f6f7264696e617465206265666f72652062797465730a3b2065786973742c20736f207468697320726f6f74207469676874656e732074686520736861726564207374727563747572616c207265736f757263652d726566206163636f7264696e676c792e0a7461726765742d69722d7265736f757263652d726566203d207b0a202069643a2074737472202e7265676578702022283f73292e2b222c0a20206469676573743a207368613235362d6469676573742c0a7d0a0a7461726765742d69722d6172746966616374203d207461726765742d69722d7068617365642d6172746966616374202f207461726765742d69722d6f7264657265642d61727469666163740a0a7461726765742d69722d7068617365642d6172746966616374203d207461726765742d69722d636c6f7365642d6172746966616374202f207461726765742d69722d6c65676163792d61727469666163740a0a7461726765742d69722d61727469666163742d636f6d6d6f6e203d20280a2020646f6d61696e3a20747374722c0a202074617267657450726f66696c653a207461726765742d69722d7265736f757263652d7265662c0a2020736f75726365436f7265436f6f7264696e6174653a2074737472202e7265676578702022283f73292e2b222c0a290a0a3b2076322062696e6473206f6e6520657865637574696f6e206f72646572206f7665722074686520696e737472756374696f6e207461626c65732e2054686520656e636f64657220616c736f0a3b2076616c69646174657320746865206578616374207065726d75746174696f6e20616e642070726f64756365722d6265666f72652d636f6e73756d657220646570656e64656e636965732e0a7461726765742d69722d6f7264657265642d6172746966616374203d207461726765742d69722d6f7264657265642d636c6f736564202f207461726765742d69722d6f7264657265642d6c65676163790a0a7461726765742d69722d6f7264657265642d636f6d6d6f6e203d20280a2020646f6d61696e3a20226563686f2e7370616e2d69722f7632222c0a202074617267657450726f66696c653a207461726765742d69722d7265736f757263652d7265662c0a2020736f75726365436f7265436f6f7264696e6174653a2074737472202e7265676578702022283f73292e2b222c0a290a0a7461726765742d69722d6f7264657265642d636c6f736564203d207b0a20206b696e643a20226f72646572656454617267657449724172746966616374222c0a20207461726765742d69722d6f7264657265642d636f6d6d6f6e2c0a202073656d616e746963436c6f737572653a207461726765742d69722d73656d616e7469632d636c6f737572652c0a2020696e74656e74733a207b202a2074737472203d3e207461726765742d69722d6f7264657265642d696e74656e74207d2c0a7d0a0a7461726765742d69722d6f7264657265642d6c6567616379203d207b0a20206b696e643a20226f72646572656454617267657449724172746966616374222c0a20207461726765742d69722d6f7264657265642d636f6d6d6f6e2c0a2020696e74656e74733a207b202a2074737472203d3e207461726765742d69722d6f7264657265642d6c65676163792d696e74656e74207d2c0a7d0a0a7461726765742d69722d6f7264657265642d696e74656e74203d207b0a20207461726765742d69722d696e74656e742d636f6d6d6f6e2c0a20203f2062617369733a20636f72652d657870722c0a20203f207075726542696e64696e67733a205b312a207461726765742d69722d707572652d62696e64696e675d2c0a2020657865637574696f6e4f726465723a205b2a2074737472202e7265676578702022283f73292e2b225d2c0a7d0a0a7461726765742d69722d6f7264657265642d6c65676163792d696e74656e74203d207b0a20207461726765742d69722d696e74656e742d636f6d6d6f6e2c0a2020657865637574696f6e4f726465723a205b2a2074737472202e7265676578702022283f73292e2b225d2c0a7d0a0a7461726765742d69722d636c6f7365642d6172746966616374203d207b0a20206b696e643a202274617267657449724172746966616374222c0a20207461726765742d69722d61727469666163742d636f6d6d6f6e2c0a202073656d616e746963436c6f737572653a207461726765742d69722d73656d616e7469632d636c6f737572652c0a2020696e74656e74733a207b202a2074737472203d3e207461726765742d69722d696e74656e74207d2c0a7d0a0a7461726765742d69722d6c65676163792d6172746966616374203d207b0a20206b696e643a202274617267657449724172746966616374222c0a20207461726765742d69722d61727469666163742d636f6d6d6f6e2c0a2020696e74656e74733a207b202a2074737472203d3e207461726765742d69722d6c65676163792d696e74656e74207d2c0a7d0a0a7461726765742d69722d73656d616e7469632d636c6f73757265203d207b0a2020736f75726365436f72653a207461726765742d69722d7265736f757263652d7265662c0a20206c61777061636b733a205b2a207461726765742d69722d7265736f757263652d7265665d2c0a20203f206361706162696c69746965733a205b2a207461726765742d69722d7265736f757263652d7265665d2c0a7d0a0a7461726765742d69722d696e74656e74203d207b0a20207461726765742d69722d696e74656e742d636f6d6d6f6e2c0a20203f2062617369733a20636f72652d657870722c0a20203f207075726542696e64696e67733a205b312a207461726765742d69722d707572652d62696e64696e675d2c0a20203f2065787465726e616c416374696f6e52657175657374733a205b2a207461726765742d69722d65787465726e616c2d616374696f6e2d726571756573745d2c0a7d0a0a7461726765742d69722d6c65676163792d696e74656e74203d207b0a20207461726765742d69722d696e74656e742d636f6d6d6f6e2c0a7d0a0a7461726765742d69722d696e74656e742d636f6d6d6f6e203d20280a20206f7065726174696f6e50726f66696c653a20747374722c0a2020696e707574436f6e73747261696e74733a205b2a20696e7075742d636f6e73747261696e745d2c0a2020636f72654576616c756174696f6e4275646765743a20636f72652d6275646765742c0a2020726571756972656d656e74733a205b2a207461726765742d69722d726571756972656d656e745d2c0a202073746570733a205b2a207461726765742d69722d737465705d2c0a2020726573756c743a20636f72652d657870722c0a290a0a7461726765742d69722d726571756972656d656e74203d207b0a202069643a20747374722c0a20207072656469636174653a20636f72652d7072656469636174652c0a20206f6e4661696c7572653a20726571756972652d6661696c7572652d61726d2c0a7d0a0a7461726765742d69722d707572652d62696e64696e67203d207b0a202069643a2074737472202e7265676578702022283f73292e2b222c0a202062696e64696e673a206c6f63616c2d7265662c0a202076616c75653a20636f72652d657870722c0a7d0a0a7461726765742d69722d73746570203d207b0a202069643a20747374722c0a202062696e64696e673a206c6f63616c2d7265662c0a20206566666563743a20747374722c0a2020746172676574496e7472696e7369633a20747374722c0a2020696e7075743a20636f72652d657870722c0a20206f62737472756374696f6e4661696c757265733a205b2a206661696c7572652d6964656e745d2c0a20206f62737472756374696f6e41726d733a207b202a206661696c7572652d6964656e74203d3e206f62737472756374696f6e2d61726d207d2c0a7d0a0a7461726765742d69722d65787465726e616c2d616374696f6e2d72657175657374203d207b0a202069643a20747374722c0a202062696e64696e673a206c6f63616c2d7265662c0a20206f7065726174696f6e3a207461726765742d69722d7265736f757263652d7265662c0a2020696e707574547970653a20636f72652d747970652d7265662c0a2020736574746c656d656e74547970653a20636f72652d747970652d7265662c0a2020696e707574536368656d613a207461726765742d69722d7265736f757263652d7265662c0a2020736574746c656d656e74536368656d613a207461726765742d69722d7265736f757263652d7265662c0a2020696e7075743a20636f72652d657870722c0a2020617574686f7269747953636f70653a20636f72652d657870722c0a202062617369733a20636f72652d657870722c0a20206275646765743a2065787465726e616c2d616374696f6e2d6275646765742c0a20207265636f6e63696c696174696f6e4c61773a207461726765742d69722d7265736f757263652d7265662c0a202073746174653a20226177616974696e67536574746c656d656e74222c0a2020736574746c656d656e7441646d697373696f6e3a2022736368656d615265717569726564222c0a7d0a", + "rawSha256": "e484eabd615584a38cb57454b52747786f2314c135c13f99da6f6bae619a709f" + }, + "contracts": [ + {"contract": "authority-facts", "rootRule": "authority-facts"}, + {"contract": "core-module", "rootRule": "core-module"}, + {"contract": "lawpack-adapter", "rootRule": "lawpack-adapter"}, + {"contract": "lawpack-exports", "rootRule": "lawpack-exports"}, + {"contract": "lawpack-manifest", "rootRule": "lawpack-manifest"}, + {"contract": "lowering-requirements", "rootRule": "lowering-requirements"}, + {"contract": "result-projection", "rootRule": "result-projection"}, + {"contract": "target-ir-artifact", "rootRule": "target-ir-artifact"}, + {"contract": "target-profile-intrinsics", "rootRule": "intrinsics-document"}, + {"contract": "target-profile-manifest", "rootRule": "target-profile-manifest"}, + {"contract": "target-profile-operation-profiles", "rootRule": "operation-profiles-document"} + ], + "domains": [ + {"domain": "edict.authority-facts/v1", "rootRule": "authority-facts"}, + {"domain": "edict.core.module/v1", "rootRule": "core-module"}, + {"domain": "edict.lawpack/v1", "rootRule": "lawpack-manifest"}, + {"domain": "edict.lowering-requirements/v1", "rootRule": "lowering-requirements"}, + {"domain": "edict.result-projection.artifact/v1", "rootRule": "result-projection"}, + {"domain": "edict.target-ir.artifact/v1", "rootRule": "target-ir-artifact"}, + {"domain": "edict.target-profile/v1", "rootRule": "target-profile-manifest"} + ], + "resources": [ + { + "coordinate": "edict.canonical-cbor/v1", + "canonicalBytesHex": "a368636f6e7472616374a96a76616c75654d6f64656c8765617272617964626f6f6c65627974657367696e7465676572636d6170646e756c6c64746578746b6469676573744672616d656f65646963742e6469676573742f76316b6d61704f72646572696e67781b63616e6f6e6963616c2d656e636f6465642d6b65792d62797465736c74657874456e636f64696e67657574662d386d747261696c696e6742797465736672656a6563746f696e7465676572456e636f64696e676873686f727465737470636f6e7461696e65724c656e6774687368646566696e697465706475706c69636174654d61704b6579736672656a656374736d6178696d756d4e657374696e67446570746818806a61706956657273696f6e782965646963742e7461726765742d70726f66696c652e636f6e74726163742d7265736f757263652f76316a636f6f7264696e6174657765646963742e63616e6f6e6963616c2d63626f722f7631", + "rawSha256": "8306e4f08c1e4e7d29ab22bcf55c324312712aac3eeeb675857ced57c3e48bdc", + "domainFramedDigest": "sha256:d1ea6d3de2a9762a438cbf4fac1d5ae2f357a4b27d13e0347e94ea655bf40f9d", + "provenance": { + "repository": "https://github.com/flyingrobots/edict", + "sourcePath": "fixtures/target-profile/contract-resources/canonical-cbor.cbor" + } + }, + { + "coordinate": "edict.determinism/v1", + "canonicalBytesHex": "a368636f6e7472616374aa6c616d6269656e74436c6f636bf46e616d6269656e744e6574776f726bf46e6f75747075744964656e74697479781e686f73742d636f6d70757465642d61667465722d76616c69646174696f6e6e72656769737472794c6f6f6b7570f471616d6269656e7446696c6573797374656df471616d6269656e7452616e646f6d6e657373f472616d6269656e74456e7669726f6e6d656e74f4756d757461626c654e616d655265736f6c7574696f6ef477667265736853746f7265506572496e766f636174696f6ef5782263616e6f6e6963616c526573756c74496e646570656e64656e744f664c696d697473f56a61706956657273696f6e782965646963742e7461726765742d70726f66696c652e636f6e74726163742d7265736f757263652f76316a636f6f7264696e6174657465646963742e64657465726d696e69736d2f7631", + "rawSha256": "84073f5c1734b625e16799048e28458d43e0a10befdae56a79d906f5e37ef76a", + "domainFramedDigest": "sha256:af4e6c774d5ea82db30e680be2ba7abc5ba04c6aead146139de32d8c5bb4981e", + "provenance": { + "repository": "https://github.com/flyingrobots/edict", + "sourcePath": "fixtures/target-profile/contract-resources/determinism.cbor" + } + }, + { + "coordinate": "edict.diagnostics/v1", + "canonicalBytesHex": "a368636f6e7472616374a7666669656c64738464636f6465687365766572697479676d65737361676566726570616972686f72646572696e67781c636f64652d73657665726974792d6d6573736167652d726570616972697265636f7264416269782f65646963743a7461726765742d70726f76696465722f70726f746f636f6c40312e302e3023646961676e6f737469636a7472756e636174696f6e69666f7262696464656e6d73657665726974794f7264657283656572726f72677761726e696e6764696e666f6e6c697374436f756e74426f756e64f57261676772656761746542797465426f756e64f56a61706956657273696f6e782965646963742e7461726765742d70726f66696c652e636f6e74726163742d7265736f757263652f76316a636f6f7264696e6174657465646963742e646961676e6f73746963732f7631", + "rawSha256": "e465a28f1170fe478db5ff65d96a1fdbfbbcb95d327e6965021c252437991e4b", + "domainFramedDigest": "sha256:28fd72a98223153982ca084c29dbb1b2d430623967ab3b6db9d7fee668e614b9", + "provenance": { + "repository": "https://github.com/flyingrobots/edict", + "sourcePath": "fixtures/target-profile/contract-resources/diagnostics.cbor" + } + }, + { + "coordinate": "edict.fuel/v1", + "canonicalBytesHex": "a368636f6e7472616374a6687265717569726564f56a6163636f756e74696e67781864657465726d696e69737469632d67756573742d776f726b6a627564676574556e6974646675656c6a65786861757374696f6e72686f73742d6f776e65642d6661696c7572656d706572496e766f636174696f6ef57165706f6368496e74657272757074696f6e736e6f742d612d7265706c61792d6275646765746a61706956657273696f6e782965646963742e7461726765742d70726f66696c652e636f6e74726163742d7265736f757263652f76316a636f6f7264696e6174656d65646963742e6675656c2f7631", + "rawSha256": "c712c2d831cc8e731bdc1dfb8ea536f4630f38f0b0e0c6448b2df57176d3d0bd", + "domainFramedDigest": "sha256:006c6ebc01a3c5d36d50bd390b69d47e42378be61074e3b5c96ecb9f5ee53207", + "provenance": { + "repository": "https://github.com/flyingrobots/edict", + "sourcePath": "fixtures/target-profile/contract-resources/fuel.cbor" + } + }, + { + "coordinate": "edict.wasm-component/v1", + "canonicalBytesHex": "a368636f6e7472616374a7647761736969666f7262696464656e6e70726f746f636f6c496d706f727469747970652d6f6e6c796f63616c6c61626c65496d706f72747369666f7262696464656e70636f6e74726163744964656e746974796565786163747070726f7669646572436f6e7472616374781b65646963743a7461726765742d70726f766964657240312e302e3077667265736853746f7265506572496e766f636174696f6ef5781b636f6d706f6e656e74446967657374566572696669636174696f6e6d6265666f72652d6465636f64656a61706956657273696f6e782965646963742e7461726765742d70726f66696c652e636f6e74726163742d7265736f757263652f76316a636f6f7264696e6174657765646963742e7761736d2d636f6d706f6e656e742f7631", + "rawSha256": "cd09d702db1d10be825e72effe35627bec56963cb3d78601b69478c58787e34d", + "domainFramedDigest": "sha256:095b4dd18f1a6a7276533f758665be319c5f476e7bdc70cc56d30b6b3e9f0a80", + "provenance": { + "repository": "https://github.com/flyingrobots/edict", + "sourcePath": "fixtures/target-profile/contract-resources/wasm-component.cbor" + } + } + ] +} diff --git a/schemas/edict-provider/generated/v1/evidence/provenance.provider-generation.json b/schemas/edict-provider/generated/v1/evidence/provenance.provider-generation.json index 83cdbd543..2a01bb289 100644 --- a/schemas/edict-provider/generated/v1/evidence/provenance.provider-generation.json +++ b/schemas/edict-provider/generated/v1/evidence/provenance.provider-generation.json @@ -1 +1 @@ -{"apiVersion":"wesley.generation-provenance-manifest/v1","contractVersions":{"generatorAbi":"wesley.extension-generator/v1","inputSchema":"wesley.extension-generation-input/v1","provenanceSchema":"wesley.generation-provenance-manifest/v1"},"emittedArtifacts":[{"coordinate":"echo.dpo-authority-facts@1","digest":"sha256:e2a6a539a7156296c18ff1bc02f80dcabf62b42bb4314d0737527869e43986d6"},{"coordinate":"echo.dpo-lawpack-authority-facts@1","digest":"sha256:ce1af559551f8b8242c4b290b620c5a28323b9e43f8bc6bb161a191001b6f4b9"},{"coordinate":"echo.dpo-lawpack@1","digest":"sha256:8c570362671a0b1cd1a992d8210e4a90ffd153ad06dcdf1e0cb36033cc971e2c"},{"coordinate":"echo.dpo.registration/v1","digest":"sha256:4ef3aaad0d2131ba7129a3e2ae178d10c44f7e9b062af2c1fe211de781462f34"},{"coordinate":"echo.dpo@1","digest":"sha256:1b105d1b1f6cdf5fecdef98b7adeb238525047d43581fe9fd8c44fd213e1788e"},{"coordinate":"echo.provider-artifacts.cddl@1","digest":"sha256:9078b84c47f2e5b722f8fea56cd0bca585cfd150c8114c22994b67cf19f6d9ab"}],"generationInputDigest":"sha256:0c07274803d465ec212c313f74dd46396851f43019fa0f97da9571a167c84587","generator":{"coordinate":"echo-wesley-gen.provider-artifact-generator@1","digest":"sha256:dd2532814deb3022e05d020706bfc2ab9a56167cccc89ea2545b1cce78e69547","version":"0.1.0"},"settingsDigest":"sha256:0f708e76898de6fdb8186352e81d0f5c445adf94bb6c7de9204952d9fe913d4a","sourceArtifacts":[{"coordinate":"echo.semantic-schema@1","digest":"sha256:322d9eb5eb8273108eaa602f2eb58ba591efcf829fecb10bb705717efae18852"},{"coordinate":"edict.provider-contract-pack.cddl@1","digest":"sha256:8d77c84ecbd7d7e38496479b2f8346c15504a56f076170d79c0eca2a57fdb3a5"},{"coordinate":"edict.provider-contract-pack.manifest@1","digest":"sha256:bc222c4a669b888a60df6f398412bae232967c11841b9a27c7ba6c18eb41e362"}]} \ No newline at end of file +{"apiVersion":"wesley.generation-provenance-manifest/v1","contractVersions":{"generatorAbi":"wesley.extension-generator/v1","inputSchema":"wesley.extension-generation-input/v1","provenanceSchema":"wesley.generation-provenance-manifest/v1"},"emittedArtifacts":[{"coordinate":"echo.dpo-authority-facts@1","digest":"sha256:e2a6a539a7156296c18ff1bc02f80dcabf62b42bb4314d0737527869e43986d6"},{"coordinate":"echo.dpo-lawpack-authority-facts@1","digest":"sha256:ce1af559551f8b8242c4b290b620c5a28323b9e43f8bc6bb161a191001b6f4b9"},{"coordinate":"echo.dpo-lawpack@1","digest":"sha256:8c570362671a0b1cd1a992d8210e4a90ffd153ad06dcdf1e0cb36033cc971e2c"},{"coordinate":"echo.dpo.registration/v1","digest":"sha256:4ef3aaad0d2131ba7129a3e2ae178d10c44f7e9b062af2c1fe211de781462f34"},{"coordinate":"echo.dpo@1","digest":"sha256:1b105d1b1f6cdf5fecdef98b7adeb238525047d43581fe9fd8c44fd213e1788e"},{"coordinate":"echo.provider-artifacts.cddl@1","digest":"sha256:9078b84c47f2e5b722f8fea56cd0bca585cfd150c8114c22994b67cf19f6d9ab"}],"generationInputDigest":"sha256:0c07274803d465ec212c313f74dd46396851f43019fa0f97da9571a167c84587","generator":{"coordinate":"echo-wesley-gen.provider-artifact-generator@1","digest":"sha256:cb9c6569a8f3c0ed3a0bd500151a3673f7f03b6f9d06d3b7edf227760fa895df","version":"0.1.0"},"settingsDigest":"sha256:0f708e76898de6fdb8186352e81d0f5c445adf94bb6c7de9204952d9fe913d4a","sourceArtifacts":[{"coordinate":"echo.semantic-schema@1","digest":"sha256:322d9eb5eb8273108eaa602f2eb58ba591efcf829fecb10bb705717efae18852"},{"coordinate":"edict.provider-contract-pack.cddl@1","digest":"sha256:8d77c84ecbd7d7e38496479b2f8346c15504a56f076170d79c0eca2a57fdb3a5"},{"coordinate":"edict.provider-contract-pack.manifest@1","digest":"sha256:bc222c4a669b888a60df6f398412bae232967c11841b9a27c7ba6c18eb41e362"}]} \ No newline at end of file diff --git a/schemas/edict-provider/generated/v1/evidence/review.provider-generation.json b/schemas/edict-provider/generated/v1/evidence/review.provider-generation.json index 95605ec67..b960179af 100644 --- a/schemas/edict-provider/generated/v1/evidence/review.provider-generation.json +++ b/schemas/edict-provider/generated/v1/evidence/review.provider-generation.json @@ -1 +1 @@ -{"apiVersion":"wesley.generation-review/v1","authoritative":false,"emittedArtifacts":[{"coordinate":"echo.dpo-authority-facts@1","digest":"sha256:e2a6a539a7156296c18ff1bc02f80dcabf62b42bb4314d0737527869e43986d6"},{"coordinate":"echo.dpo-lawpack-authority-facts@1","digest":"sha256:ce1af559551f8b8242c4b290b620c5a28323b9e43f8bc6bb161a191001b6f4b9"},{"coordinate":"echo.dpo-lawpack@1","digest":"sha256:8c570362671a0b1cd1a992d8210e4a90ffd153ad06dcdf1e0cb36033cc971e2c"},{"coordinate":"echo.dpo.registration/v1","digest":"sha256:4ef3aaad0d2131ba7129a3e2ae178d10c44f7e9b062af2c1fe211de781462f34"},{"coordinate":"echo.dpo@1","digest":"sha256:1b105d1b1f6cdf5fecdef98b7adeb238525047d43581fe9fd8c44fd213e1788e"},{"coordinate":"echo.provider-artifacts.cddl@1","digest":"sha256:9078b84c47f2e5b722f8fea56cd0bca585cfd150c8114c22994b67cf19f6d9ab"}],"generationInputDigest":"sha256:0c07274803d465ec212c313f74dd46396851f43019fa0f97da9571a167c84587","generator":{"coordinate":"echo-wesley-gen.provider-artifact-generator@1","digest":"sha256:dd2532814deb3022e05d020706bfc2ab9a56167cccc89ea2545b1cce78e69547","version":"0.1.0"},"projectionRoles":["authority-facts.echo-dpo","authority-facts.echo-lawpack","generated-artifact-profile.echo-dpo-registration","lawpack.echo-dpo","schema.echo-provider-artifacts","target-profile.echo-dpo"],"provenanceManifestDigest":"sha256:cbaf79293ebb33cd8825847b4ba848c20c2dab9b4e6cf26931c3e830bf9e7ca1","sourceArtifacts":[{"coordinate":"echo.semantic-schema@1","digest":"sha256:322d9eb5eb8273108eaa602f2eb58ba591efcf829fecb10bb705717efae18852"},{"coordinate":"edict.provider-contract-pack.cddl@1","digest":"sha256:8d77c84ecbd7d7e38496479b2f8346c15504a56f076170d79c0eca2a57fdb3a5"},{"coordinate":"edict.provider-contract-pack.manifest@1","digest":"sha256:bc222c4a669b888a60df6f398412bae232967c11841b9a27c7ba6c18eb41e362"}]} \ No newline at end of file +{"apiVersion":"wesley.generation-review/v1","authoritative":false,"emittedArtifacts":[{"coordinate":"echo.dpo-authority-facts@1","digest":"sha256:e2a6a539a7156296c18ff1bc02f80dcabf62b42bb4314d0737527869e43986d6"},{"coordinate":"echo.dpo-lawpack-authority-facts@1","digest":"sha256:ce1af559551f8b8242c4b290b620c5a28323b9e43f8bc6bb161a191001b6f4b9"},{"coordinate":"echo.dpo-lawpack@1","digest":"sha256:8c570362671a0b1cd1a992d8210e4a90ffd153ad06dcdf1e0cb36033cc971e2c"},{"coordinate":"echo.dpo.registration/v1","digest":"sha256:4ef3aaad0d2131ba7129a3e2ae178d10c44f7e9b062af2c1fe211de781462f34"},{"coordinate":"echo.dpo@1","digest":"sha256:1b105d1b1f6cdf5fecdef98b7adeb238525047d43581fe9fd8c44fd213e1788e"},{"coordinate":"echo.provider-artifacts.cddl@1","digest":"sha256:9078b84c47f2e5b722f8fea56cd0bca585cfd150c8114c22994b67cf19f6d9ab"}],"generationInputDigest":"sha256:0c07274803d465ec212c313f74dd46396851f43019fa0f97da9571a167c84587","generator":{"coordinate":"echo-wesley-gen.provider-artifact-generator@1","digest":"sha256:cb9c6569a8f3c0ed3a0bd500151a3673f7f03b6f9d06d3b7edf227760fa895df","version":"0.1.0"},"projectionRoles":["authority-facts.echo-dpo","authority-facts.echo-lawpack","generated-artifact-profile.echo-dpo-registration","lawpack.echo-dpo","schema.echo-provider-artifacts","target-profile.echo-dpo"],"provenanceManifestDigest":"sha256:9490680660ecb2ff932c5fbd650252a45252e7be0fffd7fda7b70f47c64b20d1","sourceArtifacts":[{"coordinate":"echo.semantic-schema@1","digest":"sha256:322d9eb5eb8273108eaa602f2eb58ba591efcf829fecb10bb705717efae18852"},{"coordinate":"edict.provider-contract-pack.cddl@1","digest":"sha256:8d77c84ecbd7d7e38496479b2f8346c15504a56f076170d79c0eca2a57fdb3a5"},{"coordinate":"edict.provider-contract-pack.manifest@1","digest":"sha256:bc222c4a669b888a60df6f398412bae232967c11841b9a27c7ba6c18eb41e362"}]} \ No newline at end of file diff --git a/schemas/edict-provider/package/v1/components/lowerer.echo-dpo.component.wasm b/schemas/edict-provider/package/v1/components/lowerer.echo-dpo.component.wasm index 903933cd7..555e4dcf6 100644 Binary files a/schemas/edict-provider/package/v1/components/lowerer.echo-dpo.component.wasm and b/schemas/edict-provider/package/v1/components/lowerer.echo-dpo.component.wasm differ diff --git a/schemas/edict-provider/package/v1/components/verifier.echo-dpo.component.wasm b/schemas/edict-provider/package/v1/components/verifier.echo-dpo.component.wasm index 14c6c1479..74fe228fd 100644 Binary files a/schemas/edict-provider/package/v1/components/verifier.echo-dpo.component.wasm and b/schemas/edict-provider/package/v1/components/verifier.echo-dpo.component.wasm differ diff --git a/schemas/edict-provider/package/v1/generated/evidence/provenance.provider-generation.json b/schemas/edict-provider/package/v1/generated/evidence/provenance.provider-generation.json index 83cdbd543..2a01bb289 100644 --- a/schemas/edict-provider/package/v1/generated/evidence/provenance.provider-generation.json +++ b/schemas/edict-provider/package/v1/generated/evidence/provenance.provider-generation.json @@ -1 +1 @@ -{"apiVersion":"wesley.generation-provenance-manifest/v1","contractVersions":{"generatorAbi":"wesley.extension-generator/v1","inputSchema":"wesley.extension-generation-input/v1","provenanceSchema":"wesley.generation-provenance-manifest/v1"},"emittedArtifacts":[{"coordinate":"echo.dpo-authority-facts@1","digest":"sha256:e2a6a539a7156296c18ff1bc02f80dcabf62b42bb4314d0737527869e43986d6"},{"coordinate":"echo.dpo-lawpack-authority-facts@1","digest":"sha256:ce1af559551f8b8242c4b290b620c5a28323b9e43f8bc6bb161a191001b6f4b9"},{"coordinate":"echo.dpo-lawpack@1","digest":"sha256:8c570362671a0b1cd1a992d8210e4a90ffd153ad06dcdf1e0cb36033cc971e2c"},{"coordinate":"echo.dpo.registration/v1","digest":"sha256:4ef3aaad0d2131ba7129a3e2ae178d10c44f7e9b062af2c1fe211de781462f34"},{"coordinate":"echo.dpo@1","digest":"sha256:1b105d1b1f6cdf5fecdef98b7adeb238525047d43581fe9fd8c44fd213e1788e"},{"coordinate":"echo.provider-artifacts.cddl@1","digest":"sha256:9078b84c47f2e5b722f8fea56cd0bca585cfd150c8114c22994b67cf19f6d9ab"}],"generationInputDigest":"sha256:0c07274803d465ec212c313f74dd46396851f43019fa0f97da9571a167c84587","generator":{"coordinate":"echo-wesley-gen.provider-artifact-generator@1","digest":"sha256:dd2532814deb3022e05d020706bfc2ab9a56167cccc89ea2545b1cce78e69547","version":"0.1.0"},"settingsDigest":"sha256:0f708e76898de6fdb8186352e81d0f5c445adf94bb6c7de9204952d9fe913d4a","sourceArtifacts":[{"coordinate":"echo.semantic-schema@1","digest":"sha256:322d9eb5eb8273108eaa602f2eb58ba591efcf829fecb10bb705717efae18852"},{"coordinate":"edict.provider-contract-pack.cddl@1","digest":"sha256:8d77c84ecbd7d7e38496479b2f8346c15504a56f076170d79c0eca2a57fdb3a5"},{"coordinate":"edict.provider-contract-pack.manifest@1","digest":"sha256:bc222c4a669b888a60df6f398412bae232967c11841b9a27c7ba6c18eb41e362"}]} \ No newline at end of file +{"apiVersion":"wesley.generation-provenance-manifest/v1","contractVersions":{"generatorAbi":"wesley.extension-generator/v1","inputSchema":"wesley.extension-generation-input/v1","provenanceSchema":"wesley.generation-provenance-manifest/v1"},"emittedArtifacts":[{"coordinate":"echo.dpo-authority-facts@1","digest":"sha256:e2a6a539a7156296c18ff1bc02f80dcabf62b42bb4314d0737527869e43986d6"},{"coordinate":"echo.dpo-lawpack-authority-facts@1","digest":"sha256:ce1af559551f8b8242c4b290b620c5a28323b9e43f8bc6bb161a191001b6f4b9"},{"coordinate":"echo.dpo-lawpack@1","digest":"sha256:8c570362671a0b1cd1a992d8210e4a90ffd153ad06dcdf1e0cb36033cc971e2c"},{"coordinate":"echo.dpo.registration/v1","digest":"sha256:4ef3aaad0d2131ba7129a3e2ae178d10c44f7e9b062af2c1fe211de781462f34"},{"coordinate":"echo.dpo@1","digest":"sha256:1b105d1b1f6cdf5fecdef98b7adeb238525047d43581fe9fd8c44fd213e1788e"},{"coordinate":"echo.provider-artifacts.cddl@1","digest":"sha256:9078b84c47f2e5b722f8fea56cd0bca585cfd150c8114c22994b67cf19f6d9ab"}],"generationInputDigest":"sha256:0c07274803d465ec212c313f74dd46396851f43019fa0f97da9571a167c84587","generator":{"coordinate":"echo-wesley-gen.provider-artifact-generator@1","digest":"sha256:cb9c6569a8f3c0ed3a0bd500151a3673f7f03b6f9d06d3b7edf227760fa895df","version":"0.1.0"},"settingsDigest":"sha256:0f708e76898de6fdb8186352e81d0f5c445adf94bb6c7de9204952d9fe913d4a","sourceArtifacts":[{"coordinate":"echo.semantic-schema@1","digest":"sha256:322d9eb5eb8273108eaa602f2eb58ba591efcf829fecb10bb705717efae18852"},{"coordinate":"edict.provider-contract-pack.cddl@1","digest":"sha256:8d77c84ecbd7d7e38496479b2f8346c15504a56f076170d79c0eca2a57fdb3a5"},{"coordinate":"edict.provider-contract-pack.manifest@1","digest":"sha256:bc222c4a669b888a60df6f398412bae232967c11841b9a27c7ba6c18eb41e362"}]} \ No newline at end of file diff --git a/schemas/edict-provider/package/v1/generated/evidence/review.provider-generation.json b/schemas/edict-provider/package/v1/generated/evidence/review.provider-generation.json index 95605ec67..b960179af 100644 --- a/schemas/edict-provider/package/v1/generated/evidence/review.provider-generation.json +++ b/schemas/edict-provider/package/v1/generated/evidence/review.provider-generation.json @@ -1 +1 @@ -{"apiVersion":"wesley.generation-review/v1","authoritative":false,"emittedArtifacts":[{"coordinate":"echo.dpo-authority-facts@1","digest":"sha256:e2a6a539a7156296c18ff1bc02f80dcabf62b42bb4314d0737527869e43986d6"},{"coordinate":"echo.dpo-lawpack-authority-facts@1","digest":"sha256:ce1af559551f8b8242c4b290b620c5a28323b9e43f8bc6bb161a191001b6f4b9"},{"coordinate":"echo.dpo-lawpack@1","digest":"sha256:8c570362671a0b1cd1a992d8210e4a90ffd153ad06dcdf1e0cb36033cc971e2c"},{"coordinate":"echo.dpo.registration/v1","digest":"sha256:4ef3aaad0d2131ba7129a3e2ae178d10c44f7e9b062af2c1fe211de781462f34"},{"coordinate":"echo.dpo@1","digest":"sha256:1b105d1b1f6cdf5fecdef98b7adeb238525047d43581fe9fd8c44fd213e1788e"},{"coordinate":"echo.provider-artifacts.cddl@1","digest":"sha256:9078b84c47f2e5b722f8fea56cd0bca585cfd150c8114c22994b67cf19f6d9ab"}],"generationInputDigest":"sha256:0c07274803d465ec212c313f74dd46396851f43019fa0f97da9571a167c84587","generator":{"coordinate":"echo-wesley-gen.provider-artifact-generator@1","digest":"sha256:dd2532814deb3022e05d020706bfc2ab9a56167cccc89ea2545b1cce78e69547","version":"0.1.0"},"projectionRoles":["authority-facts.echo-dpo","authority-facts.echo-lawpack","generated-artifact-profile.echo-dpo-registration","lawpack.echo-dpo","schema.echo-provider-artifacts","target-profile.echo-dpo"],"provenanceManifestDigest":"sha256:cbaf79293ebb33cd8825847b4ba848c20c2dab9b4e6cf26931c3e830bf9e7ca1","sourceArtifacts":[{"coordinate":"echo.semantic-schema@1","digest":"sha256:322d9eb5eb8273108eaa602f2eb58ba591efcf829fecb10bb705717efae18852"},{"coordinate":"edict.provider-contract-pack.cddl@1","digest":"sha256:8d77c84ecbd7d7e38496479b2f8346c15504a56f076170d79c0eca2a57fdb3a5"},{"coordinate":"edict.provider-contract-pack.manifest@1","digest":"sha256:bc222c4a669b888a60df6f398412bae232967c11841b9a27c7ba6c18eb41e362"}]} \ No newline at end of file +{"apiVersion":"wesley.generation-review/v1","authoritative":false,"emittedArtifacts":[{"coordinate":"echo.dpo-authority-facts@1","digest":"sha256:e2a6a539a7156296c18ff1bc02f80dcabf62b42bb4314d0737527869e43986d6"},{"coordinate":"echo.dpo-lawpack-authority-facts@1","digest":"sha256:ce1af559551f8b8242c4b290b620c5a28323b9e43f8bc6bb161a191001b6f4b9"},{"coordinate":"echo.dpo-lawpack@1","digest":"sha256:8c570362671a0b1cd1a992d8210e4a90ffd153ad06dcdf1e0cb36033cc971e2c"},{"coordinate":"echo.dpo.registration/v1","digest":"sha256:4ef3aaad0d2131ba7129a3e2ae178d10c44f7e9b062af2c1fe211de781462f34"},{"coordinate":"echo.dpo@1","digest":"sha256:1b105d1b1f6cdf5fecdef98b7adeb238525047d43581fe9fd8c44fd213e1788e"},{"coordinate":"echo.provider-artifacts.cddl@1","digest":"sha256:9078b84c47f2e5b722f8fea56cd0bca585cfd150c8114c22994b67cf19f6d9ab"}],"generationInputDigest":"sha256:0c07274803d465ec212c313f74dd46396851f43019fa0f97da9571a167c84587","generator":{"coordinate":"echo-wesley-gen.provider-artifact-generator@1","digest":"sha256:cb9c6569a8f3c0ed3a0bd500151a3673f7f03b6f9d06d3b7edf227760fa895df","version":"0.1.0"},"projectionRoles":["authority-facts.echo-dpo","authority-facts.echo-lawpack","generated-artifact-profile.echo-dpo-registration","lawpack.echo-dpo","schema.echo-provider-artifacts","target-profile.echo-dpo"],"provenanceManifestDigest":"sha256:9490680660ecb2ff932c5fbd650252a45252e7be0fffd7fda7b70f47c64b20d1","sourceArtifacts":[{"coordinate":"echo.semantic-schema@1","digest":"sha256:322d9eb5eb8273108eaa602f2eb58ba591efcf829fecb10bb705717efae18852"},{"coordinate":"edict.provider-contract-pack.cddl@1","digest":"sha256:8d77c84ecbd7d7e38496479b2f8346c15504a56f076170d79c0eca2a57fdb3a5"},{"coordinate":"edict.provider-contract-pack.manifest@1","digest":"sha256:bc222c4a669b888a60df6f398412bae232967c11841b9a27c7ba6c18eb41e362"}]} \ No newline at end of file diff --git a/schemas/edict-provider/package/v1/provider-manifest.echo.json b/schemas/edict-provider/package/v1/provider-manifest.echo.json index ce5c8a3b3..ffda753cd 100644 --- a/schemas/edict-provider/package/v1/provider-manifest.echo.json +++ b/schemas/edict-provider/package/v1/provider-manifest.echo.json @@ -3,7 +3,7 @@ "providerAbi": "edict:target-provider@1.0.0", "provider": { "coordinate": "echo.edict-provider@1", - "digest": "sha256:0e58e22e034ce81ecbb57d085505ab314c769bcda24b2d06bfa77d9062729116" + "digest": "sha256:21e5267310b6b4c06bf24a814d76790cbd58a4a9dcc33205dddd6fc1937594dd" }, "artifacts": [ { @@ -21,7 +21,7 @@ }, "generator": { "coordinate": "echo-wesley-gen.provider-artifact-generator@1", - "digest": "sha256:dd2532814deb3022e05d020706bfc2ab9a56167cccc89ea2545b1cce78e69547" + "digest": "sha256:cb9c6569a8f3c0ed3a0bd500151a3673f7f03b6f9d06d3b7edf227760fa895df" } } }, @@ -40,7 +40,7 @@ }, "generator": { "coordinate": "echo-wesley-gen.provider-artifact-generator@1", - "digest": "sha256:dd2532814deb3022e05d020706bfc2ab9a56167cccc89ea2545b1cce78e69547" + "digest": "sha256:cb9c6569a8f3c0ed3a0bd500151a3673f7f03b6f9d06d3b7edf227760fa895df" } } }, @@ -59,7 +59,7 @@ }, "generator": { "coordinate": "echo-wesley-gen.provider-artifact-generator@1", - "digest": "sha256:dd2532814deb3022e05d020706bfc2ab9a56167cccc89ea2545b1cce78e69547" + "digest": "sha256:cb9c6569a8f3c0ed3a0bd500151a3673f7f03b6f9d06d3b7edf227760fa895df" } } }, @@ -78,7 +78,7 @@ }, "generator": { "coordinate": "echo-wesley-gen.provider-artifact-generator@1", - "digest": "sha256:dd2532814deb3022e05d020706bfc2ab9a56167cccc89ea2545b1cce78e69547" + "digest": "sha256:cb9c6569a8f3c0ed3a0bd500151a3673f7f03b6f9d06d3b7edf227760fa895df" } } }, @@ -87,13 +87,13 @@ "artifactKind": "lowerer", "resource": { "coordinate": "echo.dpo.lowerer/component@1", - "digest": "sha256:4b594a8165079f0a973741b9e27b468cf041f4ad53108fccae641dc35355bd2a" + "digest": "sha256:dc3edc7c4f194bd2fdd2b5a1ecc79f8fb7a24a5a31294a1f1fc30369c053b8d4" }, "source": { "kind": "component", "component": { "coordinate": "echo.dpo.lowerer/component@1", - "digest": "sha256:4b594a8165079f0a973741b9e27b468cf041f4ad53108fccae641dc35355bd2a" + "digest": "sha256:dc3edc7c4f194bd2fdd2b5a1ecc79f8fb7a24a5a31294a1f1fc30369c053b8d4" } } }, @@ -102,7 +102,7 @@ "artifactKind": "generationProvenance", "resource": { "coordinate": "echo.edict-provider-generation-provenance@1", - "digest": "sha256:399c917c5cc9e86854a96852bdce898ec79df7b1236a8edb13a13b1afa72b664" + "digest": "sha256:2b27a04863c3660e8ce716cabfd63fd047d3f75a884e28d4222de5564c254142" }, "source": { "kind": "generated", @@ -112,7 +112,7 @@ }, "generator": { "coordinate": "echo-wesley-gen.provider-artifact-generator@1", - "digest": "sha256:dd2532814deb3022e05d020706bfc2ab9a56167cccc89ea2545b1cce78e69547" + "digest": "sha256:cb9c6569a8f3c0ed3a0bd500151a3673f7f03b6f9d06d3b7edf227760fa895df" } } }, @@ -121,7 +121,7 @@ "artifactKind": "reviewArtifact", "resource": { "coordinate": "echo.edict-provider-generation-review@1", - "digest": "sha256:8b7d9873deb7c517cb92a2dfc8256ff1395636eaeaaba308a9a6d3d0423913e7" + "digest": "sha256:8000857ec83afd059b0747376c01699b8c97052c84ff6a69d3e7eed407910a8a" }, "source": { "kind": "generated", @@ -131,7 +131,7 @@ }, "generator": { "coordinate": "echo-wesley-gen.provider-artifact-generator@1", - "digest": "sha256:dd2532814deb3022e05d020706bfc2ab9a56167cccc89ea2545b1cce78e69547" + "digest": "sha256:cb9c6569a8f3c0ed3a0bd500151a3673f7f03b6f9d06d3b7edf227760fa895df" } } }, @@ -150,7 +150,7 @@ }, "generator": { "coordinate": "echo-wesley-gen.provider-artifact-generator@1", - "digest": "sha256:dd2532814deb3022e05d020706bfc2ab9a56167cccc89ea2545b1cce78e69547" + "digest": "sha256:cb9c6569a8f3c0ed3a0bd500151a3673f7f03b6f9d06d3b7edf227760fa895df" } } }, @@ -169,7 +169,7 @@ }, "generator": { "coordinate": "echo-wesley-gen.provider-artifact-generator@1", - "digest": "sha256:dd2532814deb3022e05d020706bfc2ab9a56167cccc89ea2545b1cce78e69547" + "digest": "sha256:cb9c6569a8f3c0ed3a0bd500151a3673f7f03b6f9d06d3b7edf227760fa895df" } } }, @@ -178,13 +178,13 @@ "artifactKind": "verifier", "resource": { "coordinate": "echo.dpo.verifier/component@1", - "digest": "sha256:d0be4d283399aefc45d75b395b3568f9983521ca5543c9dfd23b9495758050ed" + "digest": "sha256:23d1d256ec66138697f93babcd2be57a4f56de1eb2d1fb6910e5aef881c4dd0e" }, "source": { "kind": "component", "component": { "coordinate": "echo.dpo.verifier/component@1", - "digest": "sha256:d0be4d283399aefc45d75b395b3568f9983521ca5543c9dfd23b9495758050ed" + "digest": "sha256:23d1d256ec66138697f93babcd2be57a4f56de1eb2d1fb6910e5aef881c4dd0e" } } } diff --git a/scripts/consumer-witnesses/source-functions-publication.py b/scripts/consumer-witnesses/source-functions-publication.py new file mode 100644 index 000000000..76ecaf079 --- /dev/null +++ b/scripts/consumer-witnesses/source-functions-publication.py @@ -0,0 +1,553 @@ +# SPDX-License-Identifier: Apache-2.0 +# © James Ross Ω FLYING•ROBOTS +"""Build authored source functions with Edict and explicitly selected providers. + +Run only in an admitted, resource-guarded reusable Docker worker. This witness +does not build compilers/components, synthesize IR, or execute Echo programs. +The paired runtime witness consumes its unchanged package/report bytes. + +The caller supplies a hash-pinned JSON --input-manifest whose fields are the +selected_inputs() result, in addition to the pinned compiler source snapshot. +The compiler binary hash identifies the executed binary; source-to-binary build +provenance belongs to the caller's guarded build receipt. This script does not +claim reproducible compilation from a source/binary hash pair alone. + +Eight sequential variants retain at most 64 MiB / 2048 filesystem entries here, +with 1 MiB per child-created file and 120 seconds per compiler invocation. +The shared runner must also enforce the aggregate project budget and host/VM +free-space floors, covering this work root, compiler caches and container layer. +On failure, compiler process groups are stopped and partial evidence is kept. +""" + +import argparse +import hashlib +import json +import os +from pathlib import Path +import re +import resource +import shutil +import signal +import stat +import subprocess +import time + + +MAX_INPUT_BYTES = 16 * 1024 * 1024 +MAX_INPUT_FILES = 256 +MAX_CHILD_FILE_BYTES = 1024 * 1024 +MAX_WORK_BYTES = 64 * 1024 * 1024 +MAX_WORK_ENTRIES = 2048 +BUILD_TIMEOUT_SECONDS = 120 +READ_OLD_STEPS = 1_048_576 +READ_HELPER_STEPS = 4_194_304 +READ_BUDGET = "jedit.text@1.replaceRangeBudget" +OUTPUTS = {"executable-operation-package.cbor", "verification-report.cbor"} +PURE_COORDINATE = "jedit.text.replace_range@1" +RENAMED_COORDINATE = "consumer.fragment_assembly@1" +PURE_HELPERS = """fn assembleFragments(left: Bytes, right: Bytes) -> Bytes { + let bytes = joinFragments(left, right); + return bytes; +} + +fn joinFragments(left: Bytes, right: Bytes) -> Bytes { + let bytes = left + right; + return bytes; +} + +""" +READ_HELPER = """fn retainBytes(value: text.FactBytes) -> text.FactBytes { + let bytes = value; + return bytes; +} + +""" + + +def regular_file(path, maximum=MAX_INPUT_BYTES): + if path.is_symlink() or not path.is_file() or path.stat().st_size > maximum: + raise RuntimeError(f"Expected a bounded regular file: {path}") + return path + + +def sha256(path): + digest = hashlib.sha256() + with regular_file(path, maximum=256 * 1024 * 1024).open("rb") as stream: + for block in iter(lambda: stream.read(1024 * 1024), b""): + digest.update(block) + return digest.hexdigest() + + +def inventory(root): + """Bound selected trees before copying; reject links and non-file leaves.""" + if root.is_symlink() or not root.is_dir(): + raise RuntimeError(f"Expected a regular directory: {root}") + result, size = {}, 0 + for path in root.rglob("*"): + if path.is_symlink(): + raise RuntimeError(f"Selected tree contains a symlink: {path}") + if path.is_dir(): + continue + if not path.is_file(): + raise RuntimeError(f"Selected tree contains a special file: {path}") + size += path.stat().st_size + if size > MAX_INPUT_BYTES or len(result) >= MAX_INPUT_FILES: + raise RuntimeError(f"Selected tree exceeds witness bounds: {root}") + result[str(path.relative_to(root))] = sha256(path) + return dict(sorted(result.items())) + + +def verify_compiler(args): + manifest = json.loads(regular_file(args.compiler_manifest).read_text()) + if not manifest.get("files") or len(manifest["files"]) > 16384: + raise RuntimeError("Expected a bounded, nonempty compiler source snapshot") + if manifest["head"] != args.compiler_revision: + raise RuntimeError("Compiler source revision differs from explicit selection") + for name, expected in manifest["files"].items(): + relative = Path(name) + if relative.is_absolute() or ".." in relative.parts: + raise RuntimeError("Unsafe compiler snapshot path") + path = args.compiler_source / relative + if not path.resolve(strict=True).is_relative_to(args.compiler_source): + raise RuntimeError("Compiler source escaped its explicit root") + if sha256(path) != expected: + raise RuntimeError(f"Compiler source changed: {name}") + + +def selected_inputs(args): + return { + "compilerBinarySha256": sha256(args.compiler), + "compilerManifestSha256": sha256(args.compiler_manifest), + "pureApplicationSha256": sha256(args.pure_application_source / "edict.application.json"), + "pureSourceSha256": sha256(args.pure_source), + "pureCasesSha256": sha256(args.pure_cases), + "pureVendor": inventory(args.pure_application_source / "vendor"), + "readApplicationSha256": sha256(args.read_application_source / "edict.application.json"), + "readLawpackSha256": sha256(args.read_application_source / "edict.lawpack.json"), + "readSourceSha256": sha256(args.read_source), + "oldProvider": inventory(args.old_provider), + "provider": inventory(args.provider), + } + + +def replace_once(source, old, new): + if source.count(old) != 1: + raise RuntimeError(f"Expected one source anchor: {old!r}") + return source.replace(old, new, 1) + + +def source_variants(args): + baseline = regular_file(args.pure_source).read_text() + pure = replace_once(baseline, "intent assembleRange(", PURE_HELPERS + "intent assembleRange(") + pure = replace_once(pure, + "let bytes = input.firstFragment + input.secondFragment;", + "let bytes = assembleFragments(input.firstFragment, input.secondFragment);") + renamed = replace_once(pure, f"package {PURE_COORDINATE};", + f"package {RENAMED_COORDINATE};") + renamed = replace_once(renamed, "intent assembleRange(", "intent combineFragments(") + for old, new in (("assembleFragments", "composePieces"), ("joinFragments", "appendPieces")): + if renamed.count(old) != 2: + raise RuntimeError(f"Expected one definition and call for {old}") + renamed = renamed.replace(old, new) + changed = replace_once(pure, "let bytes = left + right;", "let bytes = right + left;") + read = regular_file(args.read_source).read_text() + read = replace_once(read, "intent replaceRange(", READ_HELPER + "intent replaceRange(") + read = replace_once(read, " return actual;", + " let retained = retainBytes(actual);\n return retained;") + return {"function-free": baseline, "pure": pure, "renamed-pure": renamed, + "changed-body": changed, "read": read} + + +def child_limits(): + # This caps every child-created file, including artifacts and both streams. + resource.setrlimit(resource.RLIMIT_FSIZE, (MAX_CHILD_FILE_BYTES, MAX_CHILD_FILE_BYTES)) + resource.setrlimit(resource.RLIMIT_CORE, (0, 0)) + + +def stop_group(child): + try: + os.killpg(child.pid, signal.SIGTERM) + except ProcessLookupError: + return + try: + child.wait(timeout=5) + except subprocess.TimeoutExpired: + pass + try: + os.killpg(child.pid, signal.SIGKILL) + except ProcessLookupError: + pass + child.wait() + + +def check_work_bound(root): + """Supplement the shared guard with a live bound on this witness's outputs.""" + total, entries = 0, 0 + for path in root.rglob("*"): + entries += 1 + if entries > MAX_WORK_ENTRIES: + raise RuntimeError("Witness output count exceeded") + try: + metadata = path.lstat() + except FileNotFoundError: + # Atomic compiler publication can remove a temporary regular file. + continue + if stat.S_ISDIR(metadata.st_mode): + continue + if not stat.S_ISREG(metadata.st_mode): + raise RuntimeError(f"Witness output is not a regular file: {path}") + total += metadata.st_size + if total > MAX_WORK_BYTES: + raise RuntimeError("Witness outputs exceed the 64 MiB local ceiling") + return {"bytes": total, "entries": entries} + + +def compiler_build(args, root, document, label): + request = {"schema": "edict.compiler.settings/v1", "type": "compilerSettings", + "operation": "build", document: f"edict.{document}.json"} + logs = root / "logs" + logs.mkdir(exist_ok=True) + temporary = root / "temporary" + temporary.mkdir(exist_ok=True) + environment = dict(os.environ, TMPDIR=str(temporary), TMP=str(temporary), + TEMP=str(temporary), XDG_CACHE_HOME=str(temporary / "cache")) + streams = [logs / f"{label}.stdout.jsonl", logs / f"{label}.stderr.jsonl"] + check_work_bound(args.work_root) + with streams[0].open("xb") as stdout, streams[1].open("xb") as stderr: + child = subprocess.Popen([str(args.compiler)], cwd=root, stdin=subprocess.PIPE, + stdout=stdout, stderr=stderr, start_new_session=True, + env=environment, preexec_fn=child_limits) + deadline = time.monotonic() + BUILD_TIMEOUT_SECONDS + payload = (json.dumps(request) + "\n").encode() + try: + while True: + try: + child.communicate(payload, timeout=0.25) + break + except subprocess.TimeoutExpired: + payload = None + check_work_bound(args.work_root) + if time.monotonic() >= deadline: + raise RuntimeError(f"Compiler exceeded {BUILD_TIMEOUT_SECONDS}s; " + f"partial streams retained at {logs}") + finally: + # Kill descendants even after their direct parent exits successfully. + stop_group(child) + check_work_bound(args.work_root) + events = [] + for path in streams: + regular_file(path, MAX_CHILD_FILE_BYTES) + for line in path.read_text().splitlines(): + event = json.loads(line) + if not isinstance(event, dict): + raise RuntimeError(f"Non-object compiler event retained in {path}") + events.append(event) + statuses = [event for event in events if event.get("type") == "status"] + if len(statuses) != 1 or statuses[0].get("exitCode") != child.returncode: + raise RuntimeError(f"Compiler status disagrees with exit {child.returncode}: {streams}") + output = root / "application-output" + return {"exitCode": child.returncode, + "diagnostics": [event for event in events if event.get("type") == "diagnostic"], + "artifacts": inventory(output) if output.exists() else {}, + "stdoutSha256": sha256(streams[0]), "stderrSha256": sha256(streams[1])} + + +def require_success(result, application=True): + if result["exitCode"] != 0 or result["diagnostics"]: + raise RuntimeError(f"Public compiler refused the authored program: {result}") + if application and set(result["artifacts"]) != OUTPUTS: + raise RuntimeError(f"Compiler did not emit exactly one package/report pair: {result}") + + +def require_old_refusal(result): + diagnostics = result["diagnostics"] + if (result["exitCode"] != 2 or result["artifacts"] or len(diagnostics) != 1 + or diagnostics[0].get("kind") != "InvalidProviderInvocation" + or "ArtifactSchemaMismatch" not in diagnostics[0].get("message", "") + or "core.artifact" not in diagnostics[0].get("message", "")): + raise RuntimeError(f"Old provider did not explicitly refuse source Core: {result}") + + +def require_budget_refusal(result): + diagnostics = result["diagnostics"] + if result["exitCode"] != 2 or result["artifacts"] or len(diagnostics) != 1: + raise RuntimeError(f"Original budget did not produce one explicit refusal: {result}") + diagnostic = diagnostics[0] + message = diagnostic.get("message", "") + boundaries = { + "ApplicationCompilationFailed": ("InvalidBound", "pure-helper cost", + "exceeds operation budget"), + "ProviderLowererRefused": ("UnsupportedSemantics", "core.source-functions"), + } + required = boundaries.get(diagnostic.get("kind")) + if required is None or any(detail not in message for detail in required): + raise RuntimeError(f"Unexpected original-budget refusal: {result}") + return diagnostic["kind"] + + +def relative_path(text): + path = Path(text) + if not text or path.is_absolute() or ".." in path.parts or path == Path("."): + raise RuntimeError(f"Expected a contained relative authored path: {text}") + return path + + +def prepare_application(args, root, source, *, read=False, renamed=False, read_steps=None): + root.mkdir() + original = args.read_application_source if read else args.pure_application_source + original_application = regular_file(original / "edict.application.json") + shutil.copyfile(original_application, root / "upstream-application.json") + application = json.loads(original_application.read_text()) + application["sources"] = ["source.edict"] + application["target"]["providerPackage"] = "provider" + application["outputDirectory"] = "application-output" + for bundle in application["lawpacks"]: + for value in bundle.values(): + if relative_path(value).parts[0] != "vendor": + raise RuntimeError("Selected lawpack inputs must remain in vendor/") + if renamed: + if application["coordinate"] != PURE_COORDINATE: + raise RuntimeError("Unexpected original application coordinate") + application["coordinate"] = RENAMED_COORDINATE + (root / "edict.application.json").write_text(json.dumps(application, indent=2) + "\n") + (root / "source.edict").write_text(source) + shutil.copyfile(args.read_source if read else args.pure_source, + root / "upstream-source.edict") + if not read: + shutil.copytree(original / "vendor", root / "vendor") + if root.name in {"pure", "renamed-pure"}: + shutil.copyfile(args.pure_cases, root / "cases.json") + return None + original_lawpack = regular_file(original / "edict.lawpack.json") + shutil.copyfile(original_lawpack, root / "upstream-lawpack.json") + lawpack = json.loads(original_lawpack.read_text()) + if lawpack["dependencyBundles"]: + raise RuntimeError("The read seed must have no external dependency bundles") + output = relative_path(lawpack["outputDirectory"]) + if output != Path("vendor/atom-read"): + raise RuntimeError("Expected the explicit node-atom-read output directory") + authored = lawpack["lawpack"] + if len(authored["targetAdapters"]) != 1: + raise RuntimeError("Expected exactly one authored read adapter") + adapter = authored["targetAdapters"][0] + if adapter["coordinate"] != "jedit.text.echo-adapter/v1": + raise RuntimeError("Unexpected read adapter coordinate") + budget = adapter["budgets"][READ_BUDGET] + if budget != {"maxSteps": READ_OLD_STEPS, "maxAllocatedBytes": 16_777_216, + "maxOutputBytes": 8_388_608}: + raise RuntimeError("Original read budget differs from the explicit seed") + configs = [item for item in authored["localResources"] if item["name"] == "echo-config"] + if (len(configs) != 1 or configs[0]["value"]["programKind"] + != "compiler-produced-bounded-read/v1"): + raise RuntimeError("Read seed must already select the bounded-read profile") + for item in [adapter, *authored["localResources"]]: + relative_path(item["output"]) + if read_steps not in {READ_OLD_STEPS, READ_HELPER_STEPS}: + raise RuntimeError("Unexpected authored budget variant") + budget["maxSteps"] = read_steps + definition = root / "edict.lawpack.json" + if read_steps == READ_OLD_STEPS: + shutil.copyfile(original_lawpack, definition) + else: + definition.write_text(json.dumps(lawpack, indent=2) + "\n") + result = compiler_build(args, root, "lawpack", "lawpack") + require_success(result, application=False) + new_digest = regular_file(root / output / "manifest.sha256").read_text().strip() + if not re.fullmatch(r"sha256:[0-9a-f]{64}", new_digest): + raise RuntimeError("Public lawpack build emitted an invalid manifest digest") + pattern = r'(use lawpack jedit\.text@1 digest ")[^"\n]+(" as text;)' + updated, count = re.subn(pattern, lambda match: match[1] + new_digest + match[2], source) + if count != 1: + raise RuntimeError("Expected exactly one explicit authored lawpack import") + (root / "source.edict").write_text(updated) + return {"build": result, "manifestDigest": new_digest, + "outputs": inventory(root / output), + "upstreamLawpackSha256": sha256(root / "upstream-lawpack.json"), + "authoredLawpackSha256": sha256(definition), + "authoredChanges": [] if read_steps == READ_OLD_STEPS else [{ + "path": f"/lawpack/targetAdapters/0/budgets/{READ_BUDGET}/maxSteps", + "before": READ_OLD_STEPS, "after": read_steps}], + "authoredBeforeImportSha256": hashlib.sha256(source.encode()).hexdigest(), + "authoredAfterImportSha256": sha256(root / "source.edict")} + + +def run_variant(args, name, source, provider, *, refusal=None, read=False, + renamed=False, read_steps=None): + root = args.work_root / name + lawpack = prepare_application(args, root, source, read=read, renamed=renamed, + read_steps=read_steps) + shutil.copytree(provider, root / "provider") + authored_hash = sha256(root / "source.edict") + result = compiler_build(args, root, "application", "first") + provenance = {"kind": "public-compiler-output", "compilerRevision": args.compiler_revision, + "compilerBinarySha256": sha256(args.compiler), + "compilerManifestSha256": sha256(args.compiler_manifest), + "providerManifestSha256": sha256(provider / "provider-manifest.echo.json"), + "inputManifestSha256": args.input_manifest_sha256, + "applicationSha256": sha256(root / "edict.application.json"), + "upstreamApplicationSha256": sha256(root / "upstream-application.json"), + "sourceSha256": authored_hash, "firstBuild": result, + "upstreamSourceSha256": sha256(root / "upstream-source.edict"), + "authoredTransformation": name, + "casesSha256": sha256(root / "cases.json") if (root / "cases.json").exists() else None, + "lawpack": lawpack} + (root / "provenance.json").write_text(json.dumps(provenance, indent=2) + "\n") + if refusal == "old-publication": + require_old_refusal(result) + provenance["refusalBoundary"] = "InvalidProviderInvocation" + elif refusal == "original-budget": + provenance["refusalBoundary"] = require_budget_refusal(result) + else: + require_success(result) + shutil.rmtree(root / "application-output") + repeated = compiler_build(args, root, "application", "repeated") + provenance["repeatedBuild"] = repeated + (root / "provenance.json").write_text(json.dumps(provenance, indent=2) + "\n") + require_success(repeated) + if result["artifacts"] != repeated["artifacts"]: + raise RuntimeError(f"Repeated public build changed exact output bytes: {name}") + for filename in sorted(OUTPUTS): + original = root / "application-output" / filename + if original.stat().st_size == 0: + raise RuntimeError("Compiler emitted an empty artifact") + shutil.copyfile(original, root / filename) + if sha256(root / filename) != result["artifacts"][filename]: + raise RuntimeError("Retained compiler output bytes changed during copy") + (root / "provenance.json").write_text(json.dumps(provenance, indent=2) + "\n") + if sha256(root / "source.edict") != authored_hash: + raise RuntimeError("Public compilation changed authored source") + # All authoritative provider inputs are retained externally and rehashed. + # Recycle only this witness's disposable copy to keep peak storage bounded. + if inventory(root / "provider") != inventory(provider): + raise RuntimeError("Public compilation changed its provider copy") + shutil.rmtree(root / "provider") + check_work_bound(args.work_root) + print(json.dumps({"variant": name, **provenance}, sort_keys=True), flush=True) + return provenance + + +def retain_evidence(root, evidence, *, final=False): + """Preflight the exact replacement and include final report bytes in usage.""" + usage = check_work_bound(root) + destination = root / "evidence.json" + previous_bytes = destination.stat().st_size if destination.exists() else 0 + entries = usage["entries"] + (0 if destination.exists() else 1) + if final: + evidence["finalUsage"] = dict(usage, entries=entries) + for _ in range(8): + encoded = (json.dumps(evidence, indent=2) + "\n").encode("utf-8") + projected = {"bytes": usage["bytes"] - previous_bytes + len(encoded), + "entries": entries} + if final and evidence["finalUsage"] != projected: + evidence["finalUsage"] = projected + continue + if projected["bytes"] > MAX_WORK_BYTES or entries > MAX_WORK_ENTRIES: + raise RuntimeError("Final evidence exceeds the local witness ceiling") + destination.write_bytes(encoded) + measured = check_work_bound(root) + if measured != projected: + raise RuntimeError("Witness evidence usage changed during publication") + return + raise RuntimeError("Final evidence usage did not stabilize") + + +def finalize_evidence(root, evidence): + evidence["outcome"] = "public-compiler-and-provider-verifier-accepted" + evidence["runtimeExecution"] = "not-run-by-this-witness" + retain_evidence(root, evidence, final=True) + + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + for name in ("compiler", "compiler-source", "compiler-manifest", "pure-application-source", + "pure-source", "pure-cases", "read-application-source", "read-source", + "old-provider", "provider", "data-root", "work-root", "input-manifest"): + parser.add_argument("--" + name, type=Path, required=True) + for name in ("compiler-revision", "old-provider-manifest-sha256", "provider-manifest-sha256", + "input-manifest-sha256"): + parser.add_argument("--" + name, required=True) + args = parser.parse_args() + if not Path("/.dockerenv").is_file(): + raise RuntimeError("This witness requires the shared guarded Docker worker") + if args.work_root.exists() or not args.work_root.resolve().is_relative_to(args.data_root.resolve()): + raise RuntimeError("Use a new work directory inside the guarded data root") + for name, value in vars(args).items(): + if isinstance(value, Path): + if value.is_symlink(): + raise RuntimeError(f"Explicit input may not be a symlink: {value}") + setattr(args, name, value.resolve(strict=name != "work_root")) + if sha256(args.input_manifest) != args.input_manifest_sha256: + raise RuntimeError("Input manifest differs from explicit selection") + expected_inputs = json.loads(regular_file(args.input_manifest).read_text()) + for name, value in vars(args).items(): + if isinstance(value, Path) and name not in {"data_root", "work_root"}: + if value == args.work_root or value.is_relative_to(args.work_root): + raise RuntimeError("Authoritative inputs must remain outside the output root") + verify_compiler(args) + for root, expected in [(args.old_provider, args.old_provider_manifest_sha256), + (args.provider, args.provider_manifest_sha256)]: + if sha256(root / "provider-manifest.echo.json") != expected: + raise RuntimeError("Provider manifest differs from explicit selection") + original = selected_inputs(args) + if original != expected_inputs: + raise RuntimeError("Compiler/source/provider inputs differ from the selected manifest") + cases = json.loads(regular_file(args.pure_cases).read_text()) + if len(cases["cases"]) != 12: + raise RuntimeError("Expected the original twelve literal assembly cases") + variants = source_variants(args) + args.work_root.mkdir() + evidence = {"selectedInputs": original, "inputManifestSha256": args.input_manifest_sha256, + "limits": {"workBytes": MAX_WORK_BYTES, "workEntries": MAX_WORK_ENTRIES, + "childFileBytes": MAX_CHILD_FILE_BYTES, + "buildTimeoutSeconds": BUILD_TIMEOUT_SECONDS}, "results": {}} + shutil.copyfile(args.input_manifest, args.work_root / "selected-inputs.json") + try: + specs = [ + ("function-free-old", variants["function-free"], args.old_provider, {}), + ("source-function-old", variants["pure"], args.old_provider, {"refusal": "old-publication"}), + ("function-free-new", variants["function-free"], args.provider, {}), + ("pure", variants["pure"], args.provider, {}), + ("renamed-pure", variants["renamed-pure"], args.provider, {"renamed": True}), + ("changed-body", variants["changed-body"], args.provider, {}), + ("read-original-budget", variants["read"], args.provider, + {"read": True, "read_steps": READ_OLD_STEPS, "refusal": "original-budget"}), + ("read", variants["read"], args.provider, + {"read": True, "read_steps": READ_HELPER_STEPS}), + ] + for name, source, provider, options in specs: + evidence["results"][name] = run_variant(args, name, source, provider, **options) + retain_evidence(args.work_root, evidence) + pure = evidence["results"]["pure"]["firstBuild"]["artifacts"] + for name in ["renamed-pure", "changed-body"]: + other = evidence["results"][name]["firstBuild"]["artifacts"] + if any(pure[path] == other[path] for path in OUTPUTS): + raise RuntimeError(f"Changing {name} did not change package and report identity") + if (evidence["results"]["read-original-budget"]["lawpack"]["manifestDigest"] + == evidence["results"]["read"]["lawpack"]["manifestDigest"]): + raise RuntimeError("Authored budget change did not change lawpack authority") + if sha256(args.input_manifest) != args.input_manifest_sha256: + raise RuntimeError("Selected input manifest changed during the witness") + if selected_inputs(args) != original: + raise RuntimeError("An authoritative input changed during the witness") + verify_compiler(args) + finalize_evidence(args.work_root, evidence) + except BaseException as error: + evidence.pop("outcome", None) + evidence["failure"] = {"type": type(error).__name__, "message": str(error)} + try: + retain_evidence(args.work_root, evidence) + except RuntimeError as retention_error: + print(f"Failure report could not fit the witness bound: {retention_error}", flush=True) + raise + print("SOURCE_FUNCTION_PUBLIC_COMPILER_AND_VERIFIER_ACCEPTED", flush=True) + + +def cancelled(signum, _frame): + # Convert the runner's cancellation into finally cleanup of the compiler group. + raise RuntimeError(f"Witness cancelled by signal {signum}") + + +if __name__ == "__main__": + signal.signal(signal.SIGTERM, cancelled) + main() diff --git a/scripts/tests/source_functions_publication_test.py b/scripts/tests/source_functions_publication_test.py new file mode 100644 index 000000000..04b32128f --- /dev/null +++ b/scripts/tests/source_functions_publication_test.py @@ -0,0 +1,80 @@ +# SPDX-License-Identifier: Apache-2.0 +# © James Ross Ω FLYING•ROBOTS +"""Deterministic publication-witness regressions; execute only in Docker.""" +import contextlib +import importlib.util +import io +from pathlib import Path +import tempfile +from types import SimpleNamespace +import unittest +from unittest.mock import patch + +SPEC = importlib.util.spec_from_file_location( + "publication", Path(__file__).parents[1] / "consumer-witnesses/source-functions-publication.py") +publication = importlib.util.module_from_spec(SPEC) +SPEC.loader.exec_module(publication) + + +class PublicationTests(unittest.TestCase): + def repeat(self, emit_repeated): + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) + provider = root / "selected-provider" + provider.mkdir() + (provider / "provider-manifest.echo.json").write_text("{}") + compiler = root / "compiler" + compiler.write_bytes(b"selected compiler") + manifest = root / "compiler-manifest" + manifest.write_text("{}") + work = root / "work" + work.mkdir() + args = SimpleNamespace(work_root=work, compiler_revision="selected", + compiler=compiler, compiler_manifest=manifest, input_manifest_sha256="selected") + + def prepare(args, destination, source, **options): + destination.mkdir() + for name in ["source.edict", "upstream-source.edict", "edict.application.json", "upstream-application.json"]: + (destination / name).write_text(source) + return {} + + def build(args, destination, document, label): + output = destination / "application-output" + if label == "first" or emit_repeated: + output.mkdir(exist_ok=True) + for name in publication.OUTPUTS: + (output / name).write_bytes(b"first build") + return {"exitCode": 0, "diagnostics": [], + "artifacts": publication.inventory(output) if output.exists() else {}} + + with patch.object(publication, "prepare_application", prepare), patch.object(publication, "compiler_build", build), contextlib.redirect_stdout(io.StringIO()): + return publication.run_variant(args, "repeat", "authored source", provider) + + def test_successful_repeat_without_outputs_cannot_reuse_first_build(self): + with self.assertRaisesRegex(RuntimeError, "did not emit exactly one package/report pair"): + self.repeat(False) + + def test_repeat_emitting_the_same_pair_is_accepted(self): + result = self.repeat(True) + self.assertEqual(result["firstBuild"]["artifacts"], result["repeatedBuild"]["artifacts"]) + + + def test_final_usage_includes_final_evidence_bytes(self): + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) + (root / "evidence.json").write_text("{}\n") + evidence = {"results": {}} + publication.finalize_evidence(root, evidence) + self.assertEqual(evidence["finalUsage"], publication.check_work_bound(root)) + + def test_final_evidence_cannot_finish_above_the_local_ceiling(self): + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) + (root / "evidence.json").write_text("{}\n") + with patch.object(publication, "MAX_WORK_BYTES", 4): + with self.assertRaises(RuntimeError): + publication.finalize_evidence(root, {"results": {}}) + + +if __name__ == "__main__": + unittest.main() diff --git a/scripts/verify-local.sh b/scripts/verify-local.sh index 7c961d75d..39d650db6 100755 --- a/scripts/verify-local.sh +++ b/scripts/verify-local.sh @@ -1178,7 +1178,7 @@ pre_push_feature_string_for_test_target() { warp-core:parallel_parallel_exec) printf '%s\n' "delta_validate" ;; - warp-core:scheduler_fault_recovery_authority|warp-core:edict_pure_evaluation_tests|warp-core:edict_pure_unsigned_subtraction_tests|warp-core:edict_pure_byte_length_tests|warp-core:edict_byte_equality_tests|warp-core:edict_byte_slice_tests|warp-core:edict_node_read_tests|warp-core:edict_byte_concat_tests) + warp-core:scheduler_fault_recovery_authority|warp-core:edict_pure_evaluation_tests|warp-core:edict_pure_unsigned_subtraction_tests|warp-core:edict_pure_byte_length_tests|warp-core:edict_byte_equality_tests|warp-core:edict_byte_slice_tests|warp-core:edict_node_read_tests|warp-core:edict_byte_concat_tests|warp-core:edict_source_functions_tests) printf '%s\n' "trusted_runtime" ;; warp-math:determinism_policy_tests) @@ -1383,7 +1383,7 @@ prepare_warp_core_scope() { while IFS= read -r file; do [[ -z "$file" ]] && continue case "$file" in - crates/warp-core/src/edict_pure.rs|crates/warp-core/src/edict_pure/*.rs|crates/warp-core/tests/fixtures/edict-pure-jedit/*|crates/warp-core/tests/fixtures/edict-pure-subtraction/*|crates/warp-core/tests/fixtures/edict-pure-byte-length/*|crates/warp-core/tests/fixtures/edict-byte-equality/*|crates/warp-core/tests/fixtures/edict-byte-slice/*|crates/warp-core/src/edict_read.rs|crates/warp-core/src/edict_read/*.rs|crates/warp-core/tests/fixtures/edict-node-read/*|crates/warp-core/tests/fixtures/edict-byte-concat/*) + crates/warp-core/src/edict_pure.rs|crates/warp-core/src/edict_pure/*.rs|crates/warp-core/tests/fixtures/edict-pure-jedit/*|crates/warp-core/tests/fixtures/edict-pure-subtraction/*|crates/warp-core/tests/fixtures/edict-pure-byte-length/*|crates/warp-core/tests/fixtures/edict-byte-equality/*|crates/warp-core/tests/fixtures/edict-byte-slice/*|crates/warp-core/src/edict_read.rs|crates/warp-core/src/edict_read/*.rs|crates/warp-core/tests/fixtures/edict-node-read/*|crates/warp-core/tests/fixtures/edict-byte-concat/*|crates/warp-core/tests/fixtures/edict-source-functions/*) append_unique "edict_pure_evaluation_tests" FULL_SCOPE_WARP_CORE_EXTRA_TESTS append_unique "edict_pure_evaluation_tests" FULL_SCOPE_WARP_CORE_CLIPPY_TESTS append_unique "edict_pure_unsigned_subtraction_tests" FULL_SCOPE_WARP_CORE_EXTRA_TESTS @@ -1398,6 +1398,8 @@ prepare_warp_core_scope() { append_unique "edict_node_read_tests" FULL_SCOPE_WARP_CORE_CLIPPY_TESTS append_unique "edict_byte_concat_tests" FULL_SCOPE_WARP_CORE_EXTRA_TESTS append_unique "edict_byte_concat_tests" FULL_SCOPE_WARP_CORE_CLIPPY_TESTS + append_unique "edict_source_functions_tests" FULL_SCOPE_WARP_CORE_EXTRA_TESTS + append_unique "edict_source_functions_tests" FULL_SCOPE_WARP_CORE_CLIPPY_TESTS ;; crates/warp-core/tests/support/edict_byte_concat.rs) append_unique "edict_byte_concat_tests" FULL_SCOPE_WARP_CORE_EXTRA_TESTS diff --git a/xtask/examples/source_functions_public_runtime.rs b/xtask/examples/source_functions_public_runtime.rs new file mode 100644 index 000000000..5a8a9b913 --- /dev/null +++ b/xtask/examples/source_functions_public_runtime.rs @@ -0,0 +1,312 @@ +// SPDX-License-Identifier: Apache-2.0 +// © James Ross Ω FLYING•ROBOTS +#![allow(clippy::print_stdout)] +//! Consumes unchanged public compiler package/report pairs; never synthesizes IR. +use anyhow::{anyhow, bail, ensure, Context, Result}; +use echo_edict_canonical::{ + decode_canonical_cbor_v1 as decode, digest_canonical_value_bytes_v1 as digest, + encode_canonical_cbor_v1 as encode, CanonicalValueV1 as Value, +}; +use sha2::{Digest, Sha256}; +use std::io::Read; +use std::path::Path; +use warp_core::edict_pure::{self, EvaluationError, EvaluationLimits}; +use warp_core::edict_read::{ReadBasis, ReadView}; +use warp_core::{ + make_node_id, make_type_id, AtomPayload, AttachmentValue, GraphStore, NodeKey, NodeRecord, + WorldlineFrontier, WorldlineId, WorldlineState, +}; + +fn field<'a>(value: &'a Value, name: &str) -> Result<&'a Value> { + let Value::Map(fields) = value else { + bail!("expected map"); + }; + fields + .iter() + .find_map(|(key, value)| (key == &Value::Text(name.into())).then_some(value)) + .context("missing field") +} +fn text(value: &Value) -> Result<&str> { + let Value::Text(value) = value else { + bail!("expected text"); + }; + Ok(value) +} +fn bytes(value: &Value) -> Result<&[u8]> { + let Value::Bytes(value) = value else { + bail!("expected bytes"); + }; + Ok(value) +} +fn hash(value: &Value) -> Result<[u8; 32]> { + bytes(value)?.try_into().context("expected bytes32") +} +fn resource(value: &Value, id: &str) -> Result<[u8; 32]> { + ensure!(text(field(value, "id")?)? == id, "wrong resource identity"); + let Value::Array(values) = field(value, "digest")? else { + bail!("expected digest pair"); + }; + ensure!( + values.len() == 2 && text(&values[0])? == "sha256", + "wrong digest algorithm" + ); + hash(&values[1]) +} +fn record(fields: impl IntoIterator) -> Value { + Value::Map( + fields + .into_iter() + .map(|(key, value)| (Value::Text(key.into()), value)) + .collect(), + ) +} +fn read(path: &Path) -> Result> { + let metadata = path.symlink_metadata()?; + ensure!( + metadata.is_file() && metadata.len() <= 1_048_576, + "unbounded/nonregular witness input" + ); + let mut data = Vec::new(); + std::fs::File::open(path)? + .take(1_048_577) + .read_to_end(&mut data)?; + ensure!( + data.len() <= 1_048_576, + "witness input grew above its bound" + ); + Ok(data) +} +fn emitted(root: &Path, evidence: &serde_json::Value) -> Result<(Vec, Vec)> { + let name = root + .file_name() + .and_then(|name| name.to_str()) + .context("variant name")?; + let result = &evidence["results"][name]; + ensure!( + result["firstBuild"]["exitCode"] == 0 + && result["firstBuild"]["diagnostics"] + .as_array() + .is_some_and(Vec::is_empty), + "compiler/verifier did not succeed" + ); + let package = read(&root.join("executable-operation-package.cbor"))?; + let report = read(&root.join("verification-report.cbor"))?; + for (filename, data) in [ + ("executable-operation-package.cbor", &package), + ("verification-report.cbor", &report), + ] { + let expected = result["firstBuild"]["artifacts"][filename] + .as_str() + .context("missing emitted digest")?; + ensure!( + result["repeatedBuild"]["artifacts"][filename].as_str() == Some(expected), + "repeat did not corroborate exact bytes" + ); + ensure!( + hex::encode(Sha256::digest(data)) == expected, + "retained emitted bytes changed" + ); + } + Ok((package, report)) +} +fn checked_pair(package: Vec, report_bytes: Vec) -> Result<(Vec, [u8; 32])> { + let value = decode(&package)?; + let report = decode(&report_bytes)?; + ensure!( + text(field(&report, "apiVersion")?)? == "echo.operation-package-verifier-report/v1", + "wrong report ABI" + ); + ensure!( + text(field(&report, "outcome")?)? == "accepted" + && bytes(field(&report, "diagnosticBytes")?)?.is_empty(), + "verifier did not accept" + ); + let pin = resource( + field(&report, "package")?, + "executable-operation-package.echo", + )?; + ensure!( + pin == digest("echo.operation-package/v1", &value)?, + "report/package mismatch" + ); + let closure = field(&value, "semantic_closure")?; + let target = field(&report, "targetIr")?; + ensure!( + ["echo.span-ir/v1", "echo.span-ir/v2"].contains(&text(field(target, "id")?)?), + "unsupported Target IR role" + ); + ensure!( + resource(target, text(field(target, "id")?)?)? + == hash(field(closure, "target_ir_identity")?)?, + "report/Target IR mismatch" + ); + let program = decode(bytes(field(&value, "program")?)?)?; + let projection = decode(bytes(field(&program, "result_projection_artifact")?)?)?; + ensure!( + resource( + field(&report, "applicationResultProjection")?, + text(field(&value, "operation_coordinate")?)? + )? == digest("edict.result-projection.artifact/v1", &projection)?, + "report/projection mismatch" + ); + let _ = resource(field(&report, "diagnosticAbi")?, "edict.diagnostics/v1")?; + Ok((package, pin)) +} +fn limits() -> EvaluationLimits { + EvaluationLimits { + max_package_bytes: 1_048_576, + max_input_bytes: 32_768, + max_steps: 16_777_216, + max_allocated_bytes: 67_108_864, + max_output_bytes: 32_768, + } +} +fn pure(root: &Path, name: &str, reversed: bool, evidence: &serde_json::Value) -> Result { + let directory = root.join(name); + let (package_bytes, report_bytes) = emitted(&directory, evidence)?; + let (package, pin) = checked_pair(package_bytes, report_bytes)?; + let case_bytes = read(&root.join("pure/cases.json"))?; + let case_hash = hex::encode(Sha256::digest(&case_bytes)); + ensure!( + evidence["results"]["pure"]["casesSha256"].as_str() == Some(case_hash.as_str()) + && evidence["selectedInputs"]["pureCasesSha256"].as_str() == Some(case_hash.as_str()), + "original literal cases changed" + ); + let cases: serde_json::Value = serde_json::from_slice(&case_bytes)?; + ensure!( + cases["schema"] == "jedit.range-fragment-assembly-cases/v1" + && cases["operation"] == "jedit.text.replace_range@1.assembleRange", + "unexpected authored case identity" + ); + let cases = cases["cases"].as_array().context("cases")?; + ensure!(cases.len() == 12, "expected twelve literal cases"); + for case in cases { + let first = hex::decode(case["firstHex"].as_str().context("firstHex")?)?; + let second = hex::decode(case["secondHex"].as_str().context("secondHex")?)?; + let input = encode(&record([ + ("firstFragment", Value::Bytes(first.clone())), + ("secondFragment", Value::Bytes(second.clone())), + ]))?; + let actual = edict_pure::evaluate(&package, pin, &input, limits()); + if case["refusal"].as_str() == Some("input-type") { + ensure!( + actual == Err(EvaluationError::InvalidInput), + "expected input-type refusal" + ); + continue; + } + let expected = if reversed { + [second, first].concat() + } else { + hex::decode(case["bytesHex"].as_str().context("bytesHex")?)? + }; + ensure!( + decode( + &actual + .map_err(|error| anyhow!("pure runtime refused: {error:?}"))? + .output + )? == record([("bytes", Value::Bytes(expected))]), + "wrong literal assembly output" + ); + } + println!("PUBLIC_PURE_RUNTIME_OK {name} {}", cases.len()); + Ok(cases.len()) +} +fn atom_read(root: &Path, evidence: &serde_json::Value) -> Result<()> { + let (package_bytes, report_bytes) = emitted(&root.join("read"), evidence)?; + let (package, pin) = checked_pair(package_bytes, report_bytes)?; + let mut store = GraphStore::default(); + let node = make_node_id("public-source-function-read"); + let ty = make_type_id("document"); + store.insert_node( + node, + NodeRecord { + ty: make_type_id("node"), + }, + ); + store.set_node_attachment( + node, + Some(AttachmentValue::Atom(AtomPayload::new( + ty, + b"alpha".to_vec().into(), + ))), + ); + let key = NodeKey { + warp_id: store.warp_id(), + local_id: node, + }; + let frontier = WorldlineFrontier::new( + WorldlineId::from_bytes([7; 32]), + WorldlineState::from_root_store(store, node)?, + ); + let before = frontier.state().state_root(); + let basis = ReadBasis::at(&frontier); + let aperture = [key]; + let view = ReadView::new(&frontier, basis, &aperture) + .map_err(|error| anyhow!("view refused: {error:?}"))?; + let input = encode(&record([ + ( + "address", + record([ + ("warpId", Value::Bytes(key.warp_id.0.to_vec())), + ("nodeId", Value::Bytes(key.local_id.0.to_vec())), + ("typeId", Value::Bytes(ty.0.to_vec())), + ]), + ), + ("expected", Value::Bytes(b"alpha".to_vec())), + ]))?; + let result = warp_core::edict_read::evaluate( + &package, + pin, + &input, + &view, + warp_core::edict_read::ReadLimits { + evaluation: limits(), + max_reads: 1, + max_read_bytes: 5, + }, + ) + .map_err(|error| anyhow!("read runtime refused: {error:?}"))?; + ensure!( + decode(&result.output)? == Value::Bytes(b"alpha".to_vec()), + "wrong stored-atom result" + ); + ensure!( + result.basis == basis + && result.reads == 1 + && result.read_bytes == 5 + && frontier.state().state_root() == before, + "wrong read evidence or mutation" + ); + println!("PUBLIC_READ_RUNTIME_OK 1 5"); + Ok(()) +} +fn main() -> Result<()> { + ensure!( + Path::new("/.dockerenv").is_file(), + "run inside the guarded copied-source Docker worker" + ); + let root = std::env::args_os() + .nth(1) + .context("expected retained public compiler work root")?; + let selected = std::env::args() + .nth(2) + .context("expected independently selected compiler evidence SHA-256")?; + let root = Path::new(&root); + let evidence_bytes = read(&root.join("evidence.json"))?; + ensure!( + hex::encode(Sha256::digest(&evidence_bytes)) == selected, + "compiler evidence selection mismatch" + ); + let evidence: serde_json::Value = serde_json::from_slice(&evidence_bytes)?; + ensure!( + evidence["outcome"] == "public-compiler-and-provider-verifier-accepted", + "compiler witness incomplete" + ); + pure(root, "pure", false, &evidence)?; + pure(root, "renamed-pure", false, &evidence)?; + pure(root, "changed-body", true, &evidence)?; + atom_read(root, &evidence)?; + println!("PUBLIC_SOURCE_FUNCTION_RUNTIME_ACCEPTED"); + Ok(()) +} diff --git a/xtask/src/provider_lowerer_component.rs b/xtask/src/provider_lowerer_component.rs index 1ed2a24b3..86c4e15c3 100644 --- a/xtask/src/provider_lowerer_component.rs +++ b/xtask/src/provider_lowerer_component.rs @@ -53,7 +53,7 @@ const PINNED_CARGO_COMMIT: &str = "840b83a10fb0e039a83f4d70ad032892c287570a"; /// Reviewed identity that the portable promotion command is permitted to install. pub(crate) const APPROVED_CHECKED_COMPONENT_SHA256: &str = - "4b594a8165079f0a973741b9e27b468cf041f4ad53108fccae641dc35355bd2a"; + "dc3edc7c4f194bd2fdd2b5a1ecc79f8fb7a24a5a31294a1f1fc30369c053b8d4"; pub(crate) const CHECKED_COMPONENT_REPOSITORY_PATH: &str = "schemas/edict-provider/components/v1/lowerer.echo-dpo.component.wasm"; @@ -62,7 +62,7 @@ pub(crate) const VERIFIER_CHECKED_COMPONENT_REPOSITORY_PATH: &str = "schemas/edict-provider/components/v1/verifier.echo-dpo.component.wasm"; /// Approved SHA-256 identity of the checked verifier component. pub(crate) const APPROVED_CHECKED_VERIFIER_COMPONENT_SHA256: &str = - "d0be4d283399aefc45d75b395b3568f9983521ca5543c9dfd23b9495758050ed"; + "23d1d256ec66138697f93babcd2be57a4f56de1eb2d1fb6910e5aef881c4dd0e"; #[derive(Clone, Copy, Debug, Eq, PartialEq)] struct ProviderComponentSpec {