diff --git a/.ban-nondeterminism-allowlist b/.ban-nondeterminism-allowlist index 7683628c9..8406656ac 100644 --- a/.ban-nondeterminism-allowlist +++ b/.ban-nondeterminism-allowlist @@ -28,6 +28,9 @@ std-fs crates/warp-core/tests/bounded_workspace_patch_tests.rs bounded-patch fil std-process crates/warp-core/tests/bounded_workspace_patch_tests.rs bounded-patch filesystem fixture temp directory disambiguation only. std-fs crates/warp-core/tests/external_consumer_contract_fixture_tests.rs installed-contract restart WAL fixture I/O only. std-fs crates/warp-core/tests/executable_operation_pipeline_tests.rs executable-operation restart WAL fixture I/O only. +std-env crates/warp-core/tests/edict_projected_cas_tests.rs compare-and-set WAL fixture temp directory selection only; no environment value enters semantic history. +std-fs crates/warp-core/tests/edict_projected_cas_tests.rs compare-and-set WAL fixture owned directory creation and cleanup only. +std-process crates/warp-core/tests/edict_projected_cas_tests.rs compare-and-set WAL fixture temp directory disambiguation only; process identity never enters operation bytes. std-fs crates/warp-core/tests/provider_contract_admission_tests.rs provider invocation restart WAL fixture I/O only. std-fs crates/warp-core/tests/trusted_runtime_host_loop_tests.rs trusted runtime filesystem WAL fixture I/O only. std-env crates/warp-core/tests/wsc_store_tests.rs WSC filesystem fixture temp directory selection only. diff --git a/CHANGELOG.md b/CHANGELOG.md index ce47f8176..53c1cc291 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ ### Added +- Native projected compare-and-set invocation encoding retains canonical application input and statically binds the expected attachment-value digest. The native lowerer and independent verifier reconstruct CAS-specific profiles and reject rebound digest paths or under-budget configurations. Generated schema, reproducible components and a retained public Edict compiler build establish package production. The retained compiler package now has scheduler execution, pending/committed Action recovery, stale-digest noncommit recovery and typed refusal witnesses. + - Bounded executable-operation host sessions retain immutable observations and logical-request bindings in the native WAL. Echo evaluates supplied node/atom preconditions inside operation preparation, includes their reads in scheduler @@ -22,6 +24,10 @@ ### Fixed +- Package admission rejects result projections whose expected-digest binding conflicts with the selected mutation program, before they can occupy an installed operation coordinate. + +- Both executable mutation providers refuse transformed effect arguments instead of silently projecting the caller input into a different mutation. Direct application-input arguments remain supported; the independent verifier enforces the same boundary. + - Observation slots preflight their canonical byte bound and charge execution reads before copying Atom payloads. - Private Edict runtime decoding resolves named integer types through their declared width, matching source-function provider acceptance. diff --git a/README.md b/README.md index 6637e28ed..d4355ad49 100644 --- a/README.md +++ b/README.md @@ -292,6 +292,12 @@ result bytes, and domain-separated result identity. A second fresh host must recover byte-identical result evidence from the decided-Tick WAL; Echo does not invoke a native application callback or reconstruct the result from target state. +An Edict-authored compare-and-set operation also produces an independently +accepted package. The [compiler-output integration tests](crates/warp-core/tests/edict_projected_cas_tests.rs) +exercise that exact package through scheduler updates, stale-digest obstructions, +and fresh-host pending/completed Action recovery. This path uses the trusted-host +Action API; the CLI runner above still selects create-if-absent. + Canonical projected invocation input is capped at 65,536 bytes, and the compiler-declared maximum result size is capped at 65,536 before private scheduler evaluation. Produced canonical result bytes are measured against that diff --git a/crates/echo-edict-provider-lowerer/src/executable_operation.rs b/crates/echo-edict-provider-lowerer/src/executable_operation.rs index f46b30fd9..6e6cd7a02 100644 --- a/crates/echo-edict-provider-lowerer/src/executable_operation.rs +++ b/crates/echo-edict-provider-lowerer/src/executable_operation.rs @@ -70,8 +70,85 @@ const MAX_RESULT_PROJECTION_TEXT_BYTES: usize = 1_024; const MAX_RESULT_PROJECTION_ARTIFACT_BYTES: usize = 64 * 1_024; const MAX_APPLICATION_RESULT_BYTES: u64 = 64 * 1_024; +#[derive(Clone, Copy, PartialEq, Eq)] +enum OperationProgram { + Create, + CompareAndSet, +} + +impl OperationProgram { + const fn select(self, create: &'static str, cas: &'static str) -> &'static str { + match self { + Self::Create => create, + Self::CompareAndSet => cas, + } + } + const fn write_class(self) -> &'static str { + self.select("create", "replace") + } + const fn kind(self) -> &'static str { + self.select(PROGRAM_KIND, "anchored-node-attachment-compare-and-set/v1") + } + const fn operation_profile(self) -> &'static str { + self.select(OPERATION_PROFILE, "continuum.profile.write/v1") + } + const fn intrinsic(self) -> &'static str { + self.select( + TARGET_INTRINSIC, + "echo.dpo@1.anchored-node-attachment-compare-and-set", + ) + } + const fn input_schema(self) -> &'static str { + self.select( + INPUT_SCHEMA, + "echo.operation.input.anchored-node-alpha-cas/v1", + ) + } + const fn result_schema(self) -> &'static str { + self.select( + RESULT_SCHEMA, + "echo.operation.result.anchored-node-alpha-cas/v1", + ) + } + const fn obstruction_schema(self) -> &'static str { + self.select( + OBSTRUCTION_SCHEMA, + "echo.operation.obstruction.anchored-node-alpha-cas/v1", + ) + } + const fn result_interpretation(self) -> &'static str { + self.select( + RESULT_INTERPRETATION, + "echo.operation.result-interpretation.anchored-node-alpha-cas/v1", + ) + } + const fn obstruction_interpretation(self) -> &'static str { + self.select( + OBSTRUCTION_INTERPRETATION, + "echo.operation.obstruction-interpretation.anchored-node-alpha-cas/v1", + ) + } + const fn basis_schema(self) -> &'static str { + self.select( + APPLICATION_BASIS_SCHEMA, + "echo.operation.basis.anchored-node-alpha/v1", + ) + } + const fn footprint(self) -> &'static str { + self.select(FOOTPRINT_CONTRACT, "anchored-node-alpha-exact/v1") + } + const fn target_profile(self) -> &'static str { + self.select( + TARGET_PROFILE, + "echo.operation-target.anchored-node-alpha-cas/v1", + ) + } +} + #[derive(Clone, Copy)] struct ProgramConfiguration<'a> { + program: OperationProgram, + expected_value_digest_field: Option<&'a str>, authority_profile: &'a str, required_node_type_profile: &'a str, required_attachment_type_profile: &'a str, @@ -84,6 +161,7 @@ struct ProgramConfiguration<'a> { } struct ApplicationIntent<'a> { + program: OperationProgram, name: &'a str, operation_coordinate: String, obstruction_coordinate: &'a str, @@ -174,6 +252,11 @@ pub(super) fn lower(request: &LoweringRequestV1) -> Result( let (intent_name, intent) = single_text_map_entry(required_map(value, "intents", "core.echo-operation")?) .ok_or_else(|| super::unsupported_semantics("core.echo-operation"))?; - if text_field(intent, "requiredOperationProfile") != Some(OPERATION_PROFILE) { - return Err(super::unsupported_semantics(coordinate)); - } + let program = match text_field(intent, "requiredOperationProfile") { + Some(OPERATION_PROFILE) => OperationProgram::Create, + Some("continuum.profile.write/v1") => OperationProgram::CompareAndSet, + _ => return Err(super::unsupported_semantics(coordinate)), + }; let body = required_map(intent, "body", coordinate)?; let [node] = required_array(body, "nodes", coordinate)?.as_slice() else { return Err(super::unsupported_semantics(coordinate)); @@ -856,6 +941,7 @@ fn validate_core<'a>( if text_field(node, "kind") != Some("effect") { return Err(super::unsupported_semantics(coordinate)); } + validate_application_argument(intent, body, node, coordinate)?; let effect_coordinate = required_text(node, "effect", coordinate)?; let (failure_name, obstruction_arm) = single_text_map_entry(required_map(node, "obstructionMap", coordinate)?) @@ -866,6 +952,7 @@ fn validate_core<'a>( return Err(super::unsupported_semantics(coordinate)); } Ok(ApplicationIntent { + program, name: intent_name, operation_coordinate: format!("{coordinate}.{intent_name}"), obstruction_coordinate, @@ -874,6 +961,54 @@ fn validate_core<'a>( }) } +fn validate_application_argument( + intent: &CanonicalValueV1, + body: &CanonicalValueV1, + effect: &CanonicalValueV1, + subject: &str, +) -> Result<(), ProviderRefusalV1> { + // This program binds invocation fields directly to application input. It + // cannot evaluate a transformed effect argument; accepting one would bind + // a different mutation than the authored program describes. + let input_type = required_nonempty_text(intent, "input", subject)?; + let argument = required_map(effect, "input", subject)?; + if text_field(argument, "kind") != Some("local") { + return Err(super::unsupported_semantics( + "core.echo-operation.effect-input", + )); + } + require_exact_fields(argument, &["kind", "ref"], subject)?; + let reference = required_map(argument, "ref", subject)?; + require_exact_fields(reference, &["id", "alphaName", "type"], subject)?; + if text_field(reference, "id") != Some("arg.0") + || text_field(reference, "type") != Some(input_type) + { + return Err(super::unsupported_semantics( + "core.echo-operation.effect-input", + )); + } + let alpha_name = required_nonempty_text(reference, "alphaName", subject)?; + let locals = required_array(body, "locals", subject)?; + let mut inputs = locals + .iter() + .filter(|local| text_field(local, "id") == Some("arg.0")); + let Some(input) = inputs.next() else { + return Err(super::unsupported_semantics( + "core.echo-operation.input-local", + )); + }; + if inputs.next().is_some() + || text_field(input, "type") != Some(input_type) + || text_field(input, "alphaName") != Some(alpha_name) + { + return Err(super::unsupported_semantics( + "core.echo-operation.input-local", + )); + } + require_exact_fields(input, &["id", "alphaName", "type"], subject)?; + Ok(()) +} + fn validate_source<'a>( value: &'a CanonicalValueV1, source: &SemanticInput, @@ -965,8 +1100,8 @@ fn validate_adapter( } let effects = required_map(value, "effectImplementations", "adapter.echo-operation")?; let implementation = required_map_field(effects, semantic_effect, "adapter.echo-operation")?; - if text_field(implementation, "targetIntrinsic") != Some(TARGET_INTRINSIC) - || text_field(implementation, "writeClass") != Some("create") + if text_field(implementation, "targetIntrinsic") != Some(intent.program.intrinsic()) + || text_field(implementation, "writeClass") != Some(intent.program.write_class()) { return Err(super::unsupported_semantics("adapter.echo-operation")); } @@ -1069,7 +1204,7 @@ fn validate_target_ir( let (intent_name, target_intent) = single_text_map_entry(intents) .ok_or_else(|| super::unsupported_semantics("target-ir.echo-operation"))?; if intent_name != intent.name - || text_field(target_intent, "operationProfile") != Some(OPERATION_PROFILE) + || text_field(target_intent, "operationProfile") != Some(intent.program.operation_profile()) { return Err(super::unsupported_semantics("target-ir.echo-operation")); } @@ -1082,7 +1217,7 @@ fn validate_target_ir( else { return Err(super::unsupported_semantics("target-ir.echo-operation")); }; - if text_field(step, "targetIntrinsic") != Some(TARGET_INTRINSIC) + if text_field(step, "targetIntrinsic") != Some(intent.program.intrinsic()) || as_text(failure) != Some(PRECONDITION_MISMATCH) { return Err(super::unsupported_semantics("target-ir.echo-operation")); @@ -1093,13 +1228,20 @@ fn validate_target_ir( fn validate_configuration( value: &CanonicalValueV1, ) -> Result, ProviderRefusalV1> { - if text_field(value, "apiVersion") != Some(CONFIGURATION_ABI) - || text_field(value, "programKind") != Some(PROGRAM_KIND) - { + if text_field(value, "apiVersion") != Some(CONFIGURATION_ABI) { return Err(super::unsupported_semantics( "target-configuration.echo-operation", )); } + let program = match text_field(value, "programKind") { + Some(PROGRAM_KIND) => OperationProgram::Create, + Some("anchored-node-attachment-compare-and-set/v1") => OperationProgram::CompareAndSet, + _ => { + return Err(super::unsupported_semantics( + "target-configuration.program-kind", + )) + } + }; let invocation = required_map( value, "invocationBinding", @@ -1125,12 +1267,38 @@ fn validate_configuration( "target-configuration.echo-operation", )); } + let expected_value_digest_field = match program { + OperationProgram::CompareAndSet => { + let field = required_nonempty_text( + invocation, + "expectedValueDigestField", + "target-configuration.echo-operation", + )?; + validate_projection_text(field, "target-configuration.echo-operation")?; + if field == node_key_field || field == replacement_field { + return Err(super::unsupported_semantics( + "target-configuration.expected-digest-field", + )); + } + Some(field) + } + OperationProgram::Create => { + if map_field(invocation, "expectedValueDigestField").is_some() { + return Err(super::unsupported_semantics( + "target-configuration.expected-digest-field", + )); + } + None + } + }; let budget = required_map( value, "budgetCeiling", "target-configuration.echo-operation", )?; let configuration = ProgramConfiguration { + program, + expected_value_digest_field, authority_profile: required_nonempty_text( value, "authorityProfile", @@ -1158,7 +1326,12 @@ fn validate_configuration( replacement_field, }; if configuration.max_replacement_bytes == 0 - || configuration.steps < 3 + || configuration.steps + < if program == OperationProgram::CompareAndSet { + 4 + } else { + 3 + } || configuration.read_bytes < 64 || configuration.write_bytes < 64 { @@ -1423,7 +1596,7 @@ fn encode_package( "intrinsic_profile_identity", hash_value(profile_digest(INTRINSIC_PROFILE)), ), - ("kind", canonical_text(PROGRAM_KIND)), + ("kind", canonical_text(configuration.program.kind())), ( "max_replacement_bytes", CanonicalValueV1::Integer(i128::from(configuration.max_replacement_bytes)), @@ -1475,49 +1648,54 @@ fn encode_package( hash_value(hash_from_bound(&closure.target_ir.artifact)?), ), ]); - let package = canonical_map([ + let mut projection_value = canonical_map([ ( - "application_result_projection", - canonical_map([ - ( - "application_input_node_key_path", - CanonicalValueV1::Array( - result_projection - .node_key_path - .iter() - .map(|segment| canonical_text(segment)) - .collect(), - ), - ), - ( - "application_input_replacement_path", - CanonicalValueV1::Array( - result_projection - .replacement_path - .iter() - .map(|segment| canonical_text(segment)) - .collect(), - ), - ), - ( - "artifact_bytes", - CanonicalValueV1::Bytes( - closure.result_projection.artifact.artifact.bytes.clone(), - ), - ), - ( - "artifact_identity", - hash_value(hash_from_bound(&closure.result_projection.artifact)?), - ), - ( - "runtime_expression", - result_projection.runtime_expression.clone(), - ), - ]), + "application_input_node_key_path", + CanonicalValueV1::Array( + result_projection + .node_key_path + .iter() + .map(|segment| canonical_text(segment)) + .collect(), + ), + ), + ( + "application_input_replacement_path", + CanonicalValueV1::Array( + result_projection + .replacement_path + .iter() + .map(|segment| canonical_text(segment)) + .collect(), + ), + ), + ( + "artifact_bytes", + CanonicalValueV1::Bytes(closure.result_projection.artifact.artifact.bytes.clone()), ), + ( + "artifact_identity", + hash_value(hash_from_bound(&closure.result_projection.artifact)?), + ), + ( + "runtime_expression", + result_projection.runtime_expression.clone(), + ), + ]); + if let (Some(field), CanonicalValueV1::Map(fields)) = ( + configuration.expected_value_digest_field, + &mut projection_value, + ) { + fields.push(( + canonical_text("application_input_expected_value_digest_path"), + CanonicalValueV1::Array(vec![canonical_text(field)]), + )); + } + let package = canonical_map([ + ("application_result_projection", projection_value), ( "application_basis_schema_identity", - hash_value(profile_digest(APPLICATION_BASIS_SCHEMA)), + hash_value(profile_digest(configuration.program.basis_schema())), ), ( "authority_profile_identity", @@ -1546,7 +1724,7 @@ fn encode_package( ), ( "footprint_contract_identity", - hash_value(profile_digest(FOOTPRINT_CONTRACT)), + hash_value(profile_digest(configuration.program.footprint())), ), ( "interpreter_profile_identity", @@ -1554,7 +1732,7 @@ fn encode_package( ), ( "input_schema_identity", - hash_value(profile_digest(INPUT_SCHEMA)), + hash_value(profile_digest(configuration.program.input_schema())), ), ( "intrinsic_profile_identity", @@ -1562,11 +1740,13 @@ fn encode_package( ), ( "obstruction_schema_identity", - hash_value(profile_digest(OBSTRUCTION_SCHEMA)), + hash_value(profile_digest(configuration.program.obstruction_schema())), ), ( "obstruction_interpretation_identity", - hash_value(profile_digest(OBSTRUCTION_INTERPRETATION)), + hash_value(profile_digest( + configuration.program.obstruction_interpretation(), + )), ), ( "obstruction_coordinate", @@ -1579,17 +1759,19 @@ fn encode_package( ("program", CanonicalValueV1::Bytes(program_bytes)), ( "result_schema_identity", - hash_value(profile_digest(RESULT_SCHEMA)), + hash_value(profile_digest(configuration.program.result_schema())), ), ( "result_interpretation_identity", - hash_value(profile_digest(RESULT_INTERPRETATION)), + hash_value(profile_digest( + configuration.program.result_interpretation(), + )), ), ("schema", canonical_text(PACKAGE_SCHEMA)), ("semantic_closure", semantic_closure), ( "target_profile_identity", - hash_value(profile_digest(TARGET_PROFILE)), + hash_value(profile_digest(configuration.program.target_profile())), ), ]); encode_canonical_cbor_v1(&package) diff --git a/crates/echo-edict-provider-lowerer/src/lib.rs b/crates/echo-edict-provider-lowerer/src/lib.rs index 2ff27be66..8cee0b892 100644 --- a/crates/echo-edict-provider-lowerer/src/lib.rs +++ b/crates/echo-edict-provider-lowerer/src/lib.rs @@ -809,7 +809,7 @@ pub mod echo_dpo { pub const PROVIDER_SCHEMA_COORDINATE: &str = "echo.provider-artifacts.cddl@1"; /// Raw SHA-256 of the exact self-contained provider CDDL bytes. pub const PROVIDER_SCHEMA_SHA256_HEX: &str = - "9078b84c47f2e5b722f8fea56cd0bca585cfd150c8114c22994b67cf19f6d9ab"; + "16e8b1aa45f77bf2f69f538e7120cfb398d85525fdc6947a8cdd86d7b317ea2e"; /// Exact generated-artifact profile coordinate owning operation schemas. pub const GENERATED_ARTIFACT_PROFILE: &str = "echo.dpo.registration/v1"; /// Digest-framing domain for the generated-artifact profile. @@ -846,7 +846,7 @@ pub mod echo_dpo { const MUTATION_RULE_NAME: &str = concat!( "cmd/contract/", - "9078b84c47f2e5b722f8fea56cd0bca585cfd150c8114c22994b67cf19f6d9ab", + "16e8b1aa45f77bf2f69f538e7120cfb398d85525fdc6947a8cdd86d7b317ea2e", "/3389142194/a.b@1.t" ); const PROVIDER_OPERATIONS: [ProviderOperationV1<'static>; 1] = [ProviderOperationV1 { diff --git a/crates/echo-edict-provider-lowerer/tests/executable_operation_package.rs b/crates/echo-edict-provider-lowerer/tests/executable_operation_package.rs index 2a42f5245..77d72870d 100644 --- a/crates/echo-edict-provider-lowerer/tests/executable_operation_package.rs +++ b/crates/echo-edict-provider-lowerer/tests/executable_operation_package.rs @@ -755,37 +755,69 @@ fn core(names: FixtureNames<'_>) -> CanonicalValueV1 { ), ( "body", - owned_map([( - "nodes", - CanonicalValueV1::Array(vec![owned_map([ - ("kind", text("effect")), - ( - "effect", - text(format!("{}.{}", names.alias, names.effect_member)), - ), - ( - "obstructionMap", - dynamic_map([( - names.failure, - owned_map([( - "value", + owned_map([ + ( + "locals", + CanonicalValueV1::Array(vec![owned_map([ + ("id", text("arg.0")), + ("alphaName", text("input")), + ("type", text(format!("{}.Input", names.application))), + ])]), + ), + ( + "nodes", + CanonicalValueV1::Array(vec![owned_map([ + ("kind", text("effect")), + ( + "input", + owned_map([ + ("kind", text("local")), + ( + "ref", + owned_map([ + ("id", text("arg.0")), + ("alphaName", text("input")), + ( + "type", + text(format!( + "{}.Input", + names.application + )), + ), + ]), + ), + ]), + ), + ( + "effect", + text(format!("{}.{}", names.alias, names.effect_member)), + ), + ( + "obstructionMap", + dynamic_map([( + names.failure, owned_map([( - "callee", - text(format!( - "{}.{}", - names.alias, - names - .obstruction - .rsplit_once('.') - .expect("fixture obstruction has a member",) - .1 - )), + "value", + owned_map([( + "callee", + text(format!( + "{}.{}", + names.alias, + names + .obstruction + .rsplit_once('.') + .expect( + "fixture obstruction has a member", + ) + .1 + )), + )]), )]), )]), - )]), - ), - ])]), - )]), + ), + ])]), + ), + ]), ), ]), )]), diff --git a/crates/echo-edict-provider-lowerer/tests/fixtures/generated_echo_dpo.rs b/crates/echo-edict-provider-lowerer/tests/fixtures/generated_echo_dpo.rs index 2735603e1..fd743b8bd 100644 --- a/crates/echo-edict-provider-lowerer/tests/fixtures/generated_echo_dpo.rs +++ b/crates/echo-edict-provider-lowerer/tests/fixtures/generated_echo_dpo.rs @@ -71,7 +71,7 @@ pub mod echo_dpo { pub const PROVIDER_SCHEMA_COORDINATE: &str = "echo.provider-artifacts.cddl@1"; /// Raw SHA-256 of the exact self-contained provider CDDL bytes. pub const PROVIDER_SCHEMA_SHA256_HEX: &str = - "9078b84c47f2e5b722f8fea56cd0bca585cfd150c8114c22994b67cf19f6d9ab"; + "16e8b1aa45f77bf2f69f538e7120cfb398d85525fdc6947a8cdd86d7b317ea2e"; /// Exact generated-artifact profile coordinate owning operation schemas. pub const GENERATED_ARTIFACT_PROFILE: &str = "echo.dpo.registration/v1"; /// Digest-framing domain for the generated-artifact profile. @@ -108,7 +108,7 @@ pub mod echo_dpo { const MUTATION_RULE_NAME: &str = concat!( "cmd/contract/", - "9078b84c47f2e5b722f8fea56cd0bca585cfd150c8114c22994b67cf19f6d9ab", + "16e8b1aa45f77bf2f69f538e7120cfb398d85525fdc6947a8cdd86d7b317ea2e", "/3389142194/a.b@1.t" ); const PROVIDER_OPERATIONS: [ProviderOperationV1<'static>; 1] = [ProviderOperationV1 { diff --git a/crates/echo-edict-provider-lowerer/tests/lowerer_contract.rs b/crates/echo-edict-provider-lowerer/tests/lowerer_contract.rs index 1920257a2..6d9443c5a 100644 --- a/crates/echo-edict-provider-lowerer/tests/lowerer_contract.rs +++ b/crates/echo-edict-provider-lowerer/tests/lowerer_contract.rs @@ -45,7 +45,7 @@ const REVIEW_MEDIA_TYPE: &str = "application/json"; const GENERATED_SOURCE_PATH: &str = "generated/echo_dpo.rs"; const REVIEW_PATH: &str = "review/echo_dpo.json"; const EXPECTED_PROVIDER_SCHEMA_SHA256_HEX: &str = - "9078b84c47f2e5b722f8fea56cd0bca585cfd150c8114c22994b67cf19f6d9ab"; + "16e8b1aa45f77bf2f69f538e7120cfb398d85525fdc6947a8cdd86d7b317ea2e"; const EXPECTED_OPERATION_ID_LAW: &str = "echo.semantic-operation-id.fnv1-32/v1"; const EXPECTED_OPERATION_ID: u32 = 3_389_142_194; diff --git a/crates/echo-edict-provider-verifier/src/executable_operation.rs b/crates/echo-edict-provider-verifier/src/executable_operation.rs index 324b964eb..6a6c95c0d 100644 --- a/crates/echo-edict-provider-verifier/src/executable_operation.rs +++ b/crates/echo-edict-provider-verifier/src/executable_operation.rs @@ -76,8 +76,85 @@ pub(super) const MAX_RESULT_PROJECTION_TEXT_BYTES: usize = 1_024; const MAX_RESULT_PROJECTION_ARTIFACT_BYTES: usize = 64 * 1_024; const MAX_APPLICATION_RESULT_BYTES: u64 = 64 * 1_024; +#[derive(Clone, Copy, PartialEq, Eq)] +enum OperationProgram { + Create, + CompareAndSet, +} + +impl OperationProgram { + const fn select(self, create: &'static str, cas: &'static str) -> &'static str { + match self { + Self::Create => create, + Self::CompareAndSet => cas, + } + } + const fn write_class(self) -> &'static str { + self.select("create", "replace") + } + const fn kind(self) -> &'static str { + self.select(PROGRAM_KIND, "anchored-node-attachment-compare-and-set/v1") + } + const fn operation_profile(self) -> &'static str { + self.select(OPERATION_PROFILE, "continuum.profile.write/v1") + } + const fn intrinsic(self) -> &'static str { + self.select( + TARGET_INTRINSIC, + "echo.dpo@1.anchored-node-attachment-compare-and-set", + ) + } + const fn input_schema(self) -> &'static str { + self.select( + INPUT_SCHEMA, + "echo.operation.input.anchored-node-alpha-cas/v1", + ) + } + const fn result_schema(self) -> &'static str { + self.select( + RESULT_SCHEMA, + "echo.operation.result.anchored-node-alpha-cas/v1", + ) + } + const fn obstruction_schema(self) -> &'static str { + self.select( + OBSTRUCTION_SCHEMA, + "echo.operation.obstruction.anchored-node-alpha-cas/v1", + ) + } + const fn result_interpretation(self) -> &'static str { + self.select( + RESULT_INTERPRETATION, + "echo.operation.result-interpretation.anchored-node-alpha-cas/v1", + ) + } + const fn obstruction_interpretation(self) -> &'static str { + self.select( + OBSTRUCTION_INTERPRETATION, + "echo.operation.obstruction-interpretation.anchored-node-alpha-cas/v1", + ) + } + const fn basis_schema(self) -> &'static str { + self.select( + APPLICATION_BASIS_SCHEMA, + "echo.operation.basis.anchored-node-alpha/v1", + ) + } + const fn footprint(self) -> &'static str { + self.select(FOOTPRINT_CONTRACT, "anchored-node-alpha-exact/v1") + } + const fn target_profile(self) -> &'static str { + self.select( + TARGET_PROFILE, + "echo.operation-target.anchored-node-alpha-cas/v1", + ) + } +} + #[derive(Clone, Copy)] struct ProgramConfiguration<'a> { + program: OperationProgram, + expected_value_digest_field: Option<&'a str>, authority_profile: &'a str, required_node_type_profile: &'a str, required_attachment_type_profile: &'a str, @@ -90,6 +167,7 @@ struct ProgramConfiguration<'a> { } struct ApplicationIntent<'a> { + program: OperationProgram, name: &'a str, operation_coordinate: String, obstruction_coordinate: &'a str, @@ -182,6 +260,11 @@ pub(super) fn verify( validate_adapter(&adapter, closure.configuration, &intent, &semantic_effect)?; validate_target_ir(&target_ir, request, closure.lawpack, &intent)?; let configuration = validate_configuration(&configuration)?; + if configuration.program != intent.program { + return Err(super::unsupported_semantics( + "target-configuration.operation-profile", + )); + } let result_projection = validate_result_projection( &result_projection, closure.result_projection, @@ -889,9 +972,11 @@ fn validate_core<'a>( let (intent_name, intent) = single_text_map_entry(required_map(value, "intents", "core.echo-operation")?) .ok_or_else(|| super::unsupported_semantics("core.echo-operation"))?; - if text_field(intent, "requiredOperationProfile") != Some(OPERATION_PROFILE) { - return Err(super::unsupported_semantics(coordinate)); - } + let program = match text_field(intent, "requiredOperationProfile") { + Some(OPERATION_PROFILE) => OperationProgram::Create, + Some("continuum.profile.write/v1") => OperationProgram::CompareAndSet, + _ => return Err(super::unsupported_semantics(coordinate)), + }; let body = required_map(intent, "body", coordinate)?; let [node] = required_array(body, "nodes", coordinate)?.as_slice() else { return Err(super::unsupported_semantics(coordinate)); @@ -899,6 +984,7 @@ fn validate_core<'a>( if text_field(node, "kind") != Some("effect") { return Err(super::unsupported_semantics(coordinate)); } + validate_application_argument(intent, body, node, coordinate)?; let effect_coordinate = required_text(node, "effect", coordinate)?; let (failure_name, obstruction_arm) = single_text_map_entry(required_map(node, "obstructionMap", coordinate)?) @@ -909,6 +995,7 @@ fn validate_core<'a>( return Err(super::unsupported_semantics(coordinate)); } Ok(ApplicationIntent { + program, name: intent_name, operation_coordinate: format!("{coordinate}.{intent_name}"), obstruction_coordinate, @@ -917,6 +1004,54 @@ fn validate_core<'a>( }) } +fn validate_application_argument( + intent: &CanonicalValueV1, + body: &CanonicalValueV1, + effect: &CanonicalValueV1, + subject: &str, +) -> Result<(), ProviderRefusalV1> { + // This program binds invocation fields directly to application input. It + // cannot evaluate a transformed effect argument; accepting one would bind + // a different mutation than the authored program describes. + let input_type = required_nonempty_text(intent, "input", subject)?; + let argument = required_map(effect, "input", subject)?; + if text_field(argument, "kind") != Some("local") { + return Err(super::unsupported_semantics( + "core.echo-operation.effect-input", + )); + } + require_exact_fields(argument, &["kind", "ref"], subject)?; + let reference = required_map(argument, "ref", subject)?; + require_exact_fields(reference, &["id", "alphaName", "type"], subject)?; + if text_field(reference, "id") != Some("arg.0") + || text_field(reference, "type") != Some(input_type) + { + return Err(super::unsupported_semantics( + "core.echo-operation.effect-input", + )); + } + let alpha_name = required_nonempty_text(reference, "alphaName", subject)?; + let locals = required_array(body, "locals", subject)?; + let mut inputs = locals + .iter() + .filter(|local| text_field(local, "id") == Some("arg.0")); + let Some(input) = inputs.next() else { + return Err(super::unsupported_semantics( + "core.echo-operation.input-local", + )); + }; + if inputs.next().is_some() + || text_field(input, "type") != Some(input_type) + || text_field(input, "alphaName") != Some(alpha_name) + { + return Err(super::unsupported_semantics( + "core.echo-operation.input-local", + )); + } + require_exact_fields(input, &["id", "alphaName", "type"], subject)?; + Ok(()) +} + fn validate_source<'a>( value: &'a CanonicalValueV1, source: &SemanticInput, @@ -1012,8 +1147,8 @@ fn validate_adapter( semantic_effect, "adapter.echo-operation", )?; - if text_field(implementation, "targetIntrinsic") != Some(TARGET_INTRINSIC) - || text_field(implementation, "writeClass") != Some("create") + if text_field(implementation, "targetIntrinsic") != Some(intent.program.intrinsic()) + || text_field(implementation, "writeClass") != Some(intent.program.write_class()) { return Err(super::unsupported_semantics("adapter.echo-operation")); } @@ -1118,7 +1253,7 @@ fn validate_target_ir( single_text_map_entry(required_map(value, "intents", "target-ir.echo-operation")?) .ok_or_else(|| super::unsupported_semantics("target-ir.echo-operation"))?; if intent_name != intent.name - || text_field(target_intent, "operationProfile") != Some(OPERATION_PROFILE) + || text_field(target_intent, "operationProfile") != Some(intent.program.operation_profile()) { return Err(super::unsupported_semantics("target-ir.echo-operation")); } @@ -1131,7 +1266,7 @@ fn validate_target_ir( else { return Err(super::unsupported_semantics("target-ir.echo-operation")); }; - if text_field(step, "targetIntrinsic") != Some(TARGET_INTRINSIC) + if text_field(step, "targetIntrinsic") != Some(intent.program.intrinsic()) || as_text(failure) != Some(PRECONDITION_MISMATCH) { return Err(super::unsupported_semantics("target-ir.echo-operation")); @@ -1142,13 +1277,20 @@ fn validate_target_ir( fn validate_configuration( value: &CanonicalValueV1, ) -> Result, ProviderRefusalV1> { - if text_field(value, "apiVersion") != Some(CONFIGURATION_ABI) - || text_field(value, "programKind") != Some(PROGRAM_KIND) - { + if text_field(value, "apiVersion") != Some(CONFIGURATION_ABI) { return Err(super::unsupported_semantics( "target-configuration.echo-operation", )); } + let program = match text_field(value, "programKind") { + Some(PROGRAM_KIND) => OperationProgram::Create, + Some("anchored-node-attachment-compare-and-set/v1") => OperationProgram::CompareAndSet, + _ => { + return Err(super::unsupported_semantics( + "target-configuration.program-kind", + )) + } + }; let invocation = required_map( value, "invocationBinding", @@ -1174,12 +1316,38 @@ fn validate_configuration( "target-configuration.echo-operation", )); } + let expected_value_digest_field = match program { + OperationProgram::CompareAndSet => { + let field = required_nonempty_text( + invocation, + "expectedValueDigestField", + "target-configuration.echo-operation", + )?; + validate_projection_text(field, "target-configuration.echo-operation")?; + if field == node_key_field || field == replacement_field { + return Err(super::unsupported_semantics( + "target-configuration.expected-digest-field", + )); + } + Some(field) + } + OperationProgram::Create => { + if map_field(invocation, "expectedValueDigestField").is_some() { + return Err(super::unsupported_semantics( + "target-configuration.expected-digest-field", + )); + } + None + } + }; let budget = required_map( value, "budgetCeiling", "target-configuration.echo-operation", )?; let configuration = ProgramConfiguration { + program, + expected_value_digest_field, authority_profile: required_nonempty_text( value, "authorityProfile", @@ -1207,7 +1375,12 @@ fn validate_configuration( replacement_field, }; if configuration.max_replacement_bytes == 0 - || configuration.steps < 3 + || configuration.steps + < if program == OperationProgram::CompareAndSet { + 4 + } else { + 3 + } || configuration.read_bytes < 64 || configuration.write_bytes < 64 { @@ -1470,7 +1643,7 @@ fn encode_expected_package( "intrinsic_profile_identity", hash_value(profile_digest(INTRINSIC_PROFILE)), ), - ("kind", canonical_text(PROGRAM_KIND)), + ("kind", canonical_text(configuration.program.kind())), ( "max_replacement_bytes", CanonicalValueV1::Integer(i128::from(configuration.max_replacement_bytes)), @@ -1519,49 +1692,54 @@ fn encode_expected_package( hash_value(hash_from_bound(&request.target_ir)?), ), ]); - let package = canonical_map([ + let mut projection_value = canonical_map([ ( - "application_result_projection", - canonical_map([ - ( - "application_input_node_key_path", - CanonicalValueV1::Array( - result_projection - .node_key_path - .iter() - .map(|segment| canonical_text(segment)) - .collect(), - ), - ), - ( - "application_input_replacement_path", - CanonicalValueV1::Array( - result_projection - .replacement_path - .iter() - .map(|segment| canonical_text(segment)) - .collect(), - ), - ), - ( - "artifact_bytes", - CanonicalValueV1::Bytes( - closure.result_projection.artifact.artifact.bytes.clone(), - ), - ), - ( - "artifact_identity", - hash_value(hash_from_bound(&closure.result_projection.artifact)?), - ), - ( - "runtime_expression", - result_projection.runtime_expression.clone(), - ), - ]), + "application_input_node_key_path", + CanonicalValueV1::Array( + result_projection + .node_key_path + .iter() + .map(|segment| canonical_text(segment)) + .collect(), + ), + ), + ( + "application_input_replacement_path", + CanonicalValueV1::Array( + result_projection + .replacement_path + .iter() + .map(|segment| canonical_text(segment)) + .collect(), + ), + ), + ( + "artifact_bytes", + CanonicalValueV1::Bytes(closure.result_projection.artifact.artifact.bytes.clone()), ), + ( + "artifact_identity", + hash_value(hash_from_bound(&closure.result_projection.artifact)?), + ), + ( + "runtime_expression", + result_projection.runtime_expression.clone(), + ), + ]); + if let (Some(field), CanonicalValueV1::Map(fields)) = ( + configuration.expected_value_digest_field, + &mut projection_value, + ) { + fields.push(( + canonical_text("application_input_expected_value_digest_path"), + CanonicalValueV1::Array(vec![canonical_text(field)]), + )); + } + let package = canonical_map([ + ("application_result_projection", projection_value), ( "application_basis_schema_identity", - hash_value(profile_digest(APPLICATION_BASIS_SCHEMA)), + hash_value(profile_digest(configuration.program.basis_schema())), ), ( "authority_profile_identity", @@ -1590,7 +1768,7 @@ fn encode_expected_package( ), ( "footprint_contract_identity", - hash_value(profile_digest(FOOTPRINT_CONTRACT)), + hash_value(profile_digest(configuration.program.footprint())), ), ( "interpreter_profile_identity", @@ -1598,7 +1776,7 @@ fn encode_expected_package( ), ( "input_schema_identity", - hash_value(profile_digest(INPUT_SCHEMA)), + hash_value(profile_digest(configuration.program.input_schema())), ), ( "intrinsic_profile_identity", @@ -1606,11 +1784,13 @@ fn encode_expected_package( ), ( "obstruction_schema_identity", - hash_value(profile_digest(OBSTRUCTION_SCHEMA)), + hash_value(profile_digest(configuration.program.obstruction_schema())), ), ( "obstruction_interpretation_identity", - hash_value(profile_digest(OBSTRUCTION_INTERPRETATION)), + hash_value(profile_digest( + configuration.program.obstruction_interpretation(), + )), ), ( "obstruction_coordinate", @@ -1623,17 +1803,19 @@ fn encode_expected_package( ("program", CanonicalValueV1::Bytes(program_bytes)), ( "result_schema_identity", - hash_value(profile_digest(RESULT_SCHEMA)), + hash_value(profile_digest(configuration.program.result_schema())), ), ( "result_interpretation_identity", - hash_value(profile_digest(RESULT_INTERPRETATION)), + hash_value(profile_digest( + configuration.program.result_interpretation(), + )), ), ("schema", canonical_text(PACKAGE_SCHEMA)), ("semantic_closure", semantic_closure), ( "target_profile_identity", - hash_value(profile_digest(TARGET_PROFILE)), + hash_value(profile_digest(configuration.program.target_profile())), ), ]); encode_canonical_cbor_v1(&package) 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 aeeefe6ee..b7f96d712 100644 --- a/crates/echo-edict-provider-verifier/tests/executable_operation_package.rs +++ b/crates/echo-edict-provider-verifier/tests/executable_operation_package.rs @@ -97,6 +97,195 @@ struct RawFixture { result_projection: Vec, } +#[test] +fn providers_refuse_effect_inputs_not_equal_to_the_declared_application_argument() { + let names = FIXTURES[0]; + for cas in [false, true] { + let mut config = configuration(names); + if cas { + *map_field_mut(&mut config, "programKind") = + text("anchored-node-attachment-compare-and-set/v1"); + let CanonicalValueV1::Map(fields) = map_field_mut(&mut config, "invocationBinding") + else { + panic!("binding map") + }; + fields.push((text("expectedValueDigestField"), text("expected"))); + } + let valid = raw_fixture_with_values(names, config.clone(), result_projection(names)); + let package = lower_package(names, &valid); + let accepted = verifier::verify(verification_request(names, &valid, package.clone())) + .expect("direct input verifies"); + let report = decode_canonical_cbor_v1(&accepted.outputs[0].artifact.bytes).expect("report"); + assert_eq!(text_field(&report, "outcome"), Some("accepted")); + for case in [ + "value", + "key", + "expected", + "local", + "type", + "missing-local", + "duplicate-local", + ] { + let fixture = raw_fixture_with_core_mutation( + names, + config.clone(), + result_projection(names), + |core| { + let intent = map_field_mut(map_field_mut(core, "intents"), names.intent); + let body = map_field_mut(intent, "body"); + if case == "missing-local" || case == "duplicate-local" { + let CanonicalValueV1::Array(locals) = map_field_mut(body, "locals") else { + panic!("locals") + }; + if case == "missing-local" { + locals.clear(); + } else { + locals.push(locals[0].clone()); + } + return; + } + let CanonicalValueV1::Array(nodes) = map_field_mut(body, "nodes") else { + panic!("nodes") + }; + let input = map_field_mut(&mut nodes[0], "input"); + if case == "local" { + *map_field_mut(map_field_mut(input, "ref"), "id") = text("local.99"); + } else if case == "type" { + *map_field_mut(map_field_mut(input, "ref"), "type") = text("other.Input"); + } else { + *input = map([ + ("kind", text("record")), + ( + "fields", + dynamic_map([( + case, + map([("kind", text("string")), ("value", text("forced"))]), + )]), + ), + ]); + } + }, + ); + let lower = lowerer::lower(lowering_request(names, &fixture)).expect_err(case); + assert_eq!( + lower.kind, + lowerer::ProviderRefusalKind::UnsupportedSemantics, + "cas={cas} case={case}" + ); + let verify = verifier::verify(verification_request(names, &fixture, package.clone())) + .expect_err(case); + assert_eq!( + verify.kind, + verifier::ProviderRefusalKind::UnsupportedSemantics, + "cas={cas} case={case}" + ); + } + } +} + +#[test] +fn cas_providers_refuse_missing_aliased_and_underbudget_digest_bindings() { + let names = FIXTURES[0]; + let mut valid = configuration(names); + *map_field_mut(&mut valid, "programKind") = text("anchored-node-attachment-compare-and-set/v1"); + let CanonicalValueV1::Map(fields) = map_field_mut(&mut valid, "invocationBinding") else { + panic!("map"); + }; + fields.push((text("expectedValueDigestField"), text("expected"))); + let valid_fixture = raw_fixture_with_values(names, valid.clone(), result_projection(names)); + let package = lower_package(names, &valid_fixture); + for case in ["missing", "key", "value", "budget"] { + let mut config = valid.clone(); + if case == "budget" { + *map_field_mut(map_field_mut(&mut config, "budgetCeiling"), "steps") = integer(3); + } else if case == "missing" { + let CanonicalValueV1::Map(fields) = map_field_mut(&mut config, "invocationBinding") + else { + panic!("map"); + }; + fields.retain(|(key, _)| key != &text("expectedValueDigestField")); + } else { + *map_field_mut( + map_field_mut(&mut config, "invocationBinding"), + "expectedValueDigestField", + ) = text(case); + } + let fixture = raw_fixture_with_values(names, config, result_projection(names)); + let lower = lowerer::lower(lowering_request(names, &fixture)).expect_err(case); + assert_eq!( + lower.kind, + lowerer::ProviderRefusalKind::UnsupportedSemantics, + "{case}" + ); + let verify = verifier::verify(verification_request(names, &fixture, package.clone())) + .expect_err(case); + assert_eq!( + verify.kind, + verifier::ProviderRefusalKind::UnsupportedSemantics, + "{case}" + ); + } +} + +#[test] +fn verifier_accepts_projected_compare_and_set_and_rejects_rebound_digest_path() { + let names = FIXTURES[0]; + let mut config = configuration(names); + *map_field_mut(&mut config, "programKind") = + text("anchored-node-attachment-compare-and-set/v1"); + let CanonicalValueV1::Map(bindings) = map_field_mut(&mut config, "invocationBinding") else { + panic!("binding map"); + }; + bindings.push((text("expectedValueDigestField"), text("expected"))); + let fixture = raw_fixture_with_values(names, config, result_projection(names)); + let package = lower_package(names, &fixture); + let mut value = decode_canonical_cbor_v1(&package).expect("package decodes"); + let projection = map_field_mut(&mut value, "application_result_projection"); + assert_eq!( + map_field(projection, "application_input_expected_value_digest_path"), + &CanonicalValueV1::Array(vec![text("expected")]) + ); + let accepted = verifier::verify(verification_request(names, &fixture, package)) + .expect("CAS verification completes"); + let report = + decode_canonical_cbor_v1(&accepted.outputs[0].artifact.bytes).expect("report decodes"); + assert_eq!(text_field(&report, "outcome"), Some("accepted")); + *map_field_mut(projection, "application_input_expected_value_digest_path") = + CanonicalValueV1::Array(vec![text("other")]); + let rejected = verifier::verify(verification_request( + names, + &fixture, + canonical_bytes(&value), + )) + .expect("rejection report"); + let report = + decode_canonical_cbor_v1(&rejected.outputs[0].artifact.bytes).expect("report decodes"); + assert_eq!(text_field(&report, "outcome"), Some("rejected")); +} + +fn cas_profile_value(value: CanonicalValueV1) -> CanonicalValueV1 { + match value { + CanonicalValueV1::Text(text_value) => text(match text_value.as_str() { + "continuum.profile.create/v1" => "continuum.profile.write/v1", + "echo.dpo@1.anchored-node-attachment-create-if-absent" => { + "echo.dpo@1.anchored-node-attachment-compare-and-set" + } + "create" => "replace", + _ => return CanonicalValueV1::Text(text_value), + }), + CanonicalValueV1::Map(entries) => CanonicalValueV1::Map( + entries + .into_iter() + .map(|(key, value)| (key, cas_profile_value(value))) + .collect(), + ), + CanonicalValueV1::Array(values) => { + CanonicalValueV1::Array(values.into_iter().map(cas_profile_value).collect()) + } + other => other, + } +} + #[test] fn verifier_accepts_generic_lowerer_output_for_two_application_vocabularies() { for names in FIXTURES { @@ -751,6 +940,24 @@ fn raw_fixture_with_values( configuration_value: CanonicalValueV1, result_projection_value: CanonicalValueV1, ) -> RawFixture { + raw_fixture_with_core_mutation(names, configuration_value, result_projection_value, |_| {}) +} + +fn raw_fixture_with_core_mutation( + names: FixtureNames<'_>, + configuration_value: CanonicalValueV1, + result_projection_value: CanonicalValueV1, + mutate: impl FnOnce(&mut CanonicalValueV1), +) -> RawFixture { + let is_cas = text_field(&configuration_value, "programKind") + == Some("anchored-node-attachment-compare-and-set/v1"); + let select_profile = |value| { + if is_cas { + cas_profile_value(value) + } else { + value + } + }; let target_profile = TARGET_PROFILE.to_vec(); let target_profile_ref = raw_ref("echo.dpo@1", "edict.target-profile/v1", &target_profile); let exports = canonical_bytes(&exports(names)); @@ -761,7 +968,7 @@ fn raw_fixture_with_values( "echo.operation-lowering-configuration/v1", &configuration, ); - let adapter = canonical_bytes(&adapter(names, &configuration_ref)); + let adapter = canonical_bytes(&select_profile(adapter(names, &configuration_ref))); let adapter_ref = raw_ref(names.adapter, "edict.lawpack-adapter/v1", &adapter); let lawpack = canonical_bytes(&lawpack( names, @@ -772,7 +979,9 @@ fn raw_fixture_with_values( &target_profile_ref, )); let lawpack_ref = raw_ref(names.lawpack, "edict.lawpack/v1", &lawpack); - let core = canonical_bytes(&core(names)); + let mut core_value = select_profile(core(names)); + mutate(&mut core_value); + let core = canonical_bytes(&core_value); let core_ref = raw_ref(names.application, "edict.core.module/v1", &core); let source = canonical_bytes(&CanonicalValueV1::Bytes( format!( @@ -785,12 +994,12 @@ fn raw_fixture_with_values( ) .into_bytes(), )); - let target_ir = canonical_bytes(&target_ir( + let target_ir = canonical_bytes(&select_profile(target_ir( names, &core_ref, &lawpack_ref, &target_profile_ref, - )); + ))); let result_projection = canonical_bytes(&result_projection_value); RawFixture { core, @@ -830,43 +1039,76 @@ fn core(names: FixtureNames<'_>) -> CanonicalValueV1 { dynamic_map([( names.intent, map([ + ("input", text(format!("{}.Input", names.application))), ( "requiredOperationProfile", text("continuum.profile.create/v1"), ), ( "body", - map([( - "nodes", - CanonicalValueV1::Array(vec![map([ - ("kind", text("effect")), - ( - "effect", - text(format!("{}.{}", names.alias, names.effect_member)), - ), - ( - "obstructionMap", - dynamic_map([( - names.failure, - map([( - "value", + map([ + ( + "locals", + CanonicalValueV1::Array(vec![map([ + ("id", text("arg.0")), + ("alphaName", text("input")), + ("type", text(format!("{}.Input", names.application))), + ])]), + ), + ( + "nodes", + CanonicalValueV1::Array(vec![map([ + ("kind", text("effect")), + ( + "input", + map([ + ("kind", text("local")), + ( + "ref", + map([ + ("id", text("arg.0")), + ("alphaName", text("input")), + ( + "type", + text(format!( + "{}.Input", + names.application + )), + ), + ]), + ), + ]), + ), + ( + "effect", + text(format!("{}.{}", names.alias, names.effect_member)), + ), + ( + "obstructionMap", + dynamic_map([( + names.failure, map([( - "callee", - text(format!( - "{}.{}", - names.alias, - names - .obstruction - .rsplit_once('.') - .expect("fixture obstruction has a member",) - .1 - )), + "value", + map([( + "callee", + text(format!( + "{}.{}", + names.alias, + names + .obstruction + .rsplit_once('.') + .expect( + "fixture obstruction has a member", + ) + .1 + )), + )]), )]), )]), - )]), - ), - ])]), - )]), + ), + ])]), + ), + ]), ), ]), )]), diff --git a/crates/echo-edict-provider-verifier/tests/fixtures/compiler-produced-cas/README.md b/crates/echo-edict-provider-verifier/tests/fixtures/compiler-produced-cas/README.md new file mode 100644 index 000000000..67c4f544d --- /dev/null +++ b/crates/echo-edict-provider-verifier/tests/fixtures/compiler-produced-cas/README.md @@ -0,0 +1,54 @@ + + + +# Reproduce the projected CAS compiler witness + +Run `build.py` inside the guarded Echo worker with a pinned Edict compiler binary, +its SHA-256, the candidate provider package and a fresh output directory. The +recipe invokes the public JSONL application-build command; it does not synthesize +Core, Target IR, a package or a verifier report. + +`seed/` preserves the canonical hello-echo lawpack inputs from Edict commit +`3f81f759e921a69b04fe8cf8e62e62f8f3dc7b7e`. The recipe deterministically derives a +CAS lawpack with an explicit `Bytes` expected digest, replace write class, +CAS intrinsic and input binding. It recomputes the affected export, adapter and +manifest references, then fills the authored source template's lawpack digest. +Keep BlobIds are not used as attachment-value digests. + +The retained build used the pinned Edict compiler from source commit +`2405a550e93e1e97fff640caa44bbd0f65ffff3c` and executable SHA-256 +`3b082d61c8cd23b0f917efb55c4eebd54e75c0df5d31c72f90ad856853678bf5`. +`built/` preserves the authored source with its exact digest, the public compiler's +successful status, and exact package/report bytes as lowercase hex. The report +accepts this package. This fixture proves compiler/provider production. +`crates/warp-core/tests/edict_projected_cas_tests.rs` uses these exact bytes for +separate scheduler and fresh-host WAL recovery witnesses, including pending +Actions, committed results and stale-digest obstructions. It checks typed target +bytes directly because the fixture target is detached from the reachable root. + +## Direct-input and transformed-argument controls + +Inside the guarded worker, from the repository root, run the positive control +with a fresh output directory: + +```sh +python3 crates/echo-edict-provider-verifier/tests/fixtures/compiler-produced-cas/build.py \ + --compiler /tmp/echo-726-runtime/741-frozen-edict \ + --compiler-sha256 3b082d61c8cd23b0f917efb55c4eebd54e75c0df5d31c72f90ad856853678bf5 \ + --provider-package schemas/edict-provider/package/v1 \ + --output-directory /tmp/echo-726-runtime/cas-direct-input-control +``` + +For the negative control, use another fresh output directory and append: + +```sh + --source-template crates/echo-edict-provider-verifier/tests/fixtures/compiler-produced-cas/counterexamples/transformed-replacement/update-cell.edict \ + --expect-refusal ProviderLowererRefused +``` + +The helper requires a nonzero compiler exit, the structured refusal in its JSONL +output, and no published CBOR artifacts. It reads diagnostics from both stdout +and stderr. The retained counterexample package/report are historical erroneous +outputs, not expected output for corrected components. Both mutation profiles +now require a direct reference to the declared application input; arbitrary +transformed arguments remain outside their supported semantics. diff --git a/crates/echo-edict-provider-verifier/tests/fixtures/compiler-produced-cas/build.py b/crates/echo-edict-provider-verifier/tests/fixtures/compiler-produced-cas/build.py new file mode 100644 index 000000000..18021f1a2 --- /dev/null +++ b/crates/echo-edict-provider-verifier/tests/fixtures/compiler-produced-cas/build.py @@ -0,0 +1,109 @@ +# SPDX-License-Identifier: Apache-2.0 +# © James Ross Ω FLYING•ROBOTS + +# Reproduce the projected CAS application build inside the guarded worker. +import hashlib,json,shutil,subprocess +from pathlib import Path + +def enc(v): + def head(m,n): + if n<24:return bytes([m*32+n]) + for a,k in [(24,1),(25,2),(26,4),(27,8)]: + if n<1<<(8*k):return bytes([m*32+a])+n.to_bytes(k,'big') + raise ValueError('large integer') + if v is None:return b'\xf6' + if isinstance(v,bool):return b'\xf5' if v else b'\xf4' + if isinstance(v,int):return head(0,v) if v>=0 else head(1,-1-v) + if isinstance(v,str): + b=v.encode();return head(3,len(b))+b + if isinstance(v,bytes):return head(2,len(v))+v + if isinstance(v,list):return head(4,len(v))+b''.join(map(enc,v)) + if isinstance(v,dict): + items=sorted([(enc(k),enc(x)) for k,x in v.items()],key=lambda p:(len(p[0]),p[0])) + return head(5,len(items))+b''.join(k+x for k,x in items) + raise ValueError(type(v)) +def dec(b): + i=0 + def item(): + nonlocal i + h=b[i];i+=1;m=h>>5;a=h&31 + if a>=24: + assert a<=27 + n=1<<(a-24);a=int.from_bytes(b[i:i+n],'big');i+=n + if m==0:return a + if m==1:return -1-a + if m in (2,3): + v=b[i:i+a];i+=a;return v if m==2 else v.decode() + if m==4:return [item() for _ in range(a)] + if m==5:return {item():item() for _ in range(a)} + if m==7:return {20:False,21:True,22:None}[a] + raise ValueError(m) + v=item();assert i==len(b) and enc(v)==b;return v + +def digest(domain,value):return hashlib.sha256(enc(['edict.digest/v1',domain,value])).digest() +import argparse +parser=argparse.ArgumentParser() +parser.add_argument('--compiler',type=Path,required=True) +parser.add_argument('--compiler-sha256',required=True) +parser.add_argument('--provider-package',type=Path,required=True) +parser.add_argument('--output-directory',type=Path,required=True) +parser.add_argument('--source-template',type=Path) +parser.add_argument('--expect-refusal',choices=['ProviderLowererRefused']) +args=parser.parse_args() +root=args.output_directory.resolve();root.mkdir(exist_ok=False) +fixture=Path(__file__).resolve().parent +compiler=args.compiler.resolve() +assert hashlib.sha256(compiler.read_bytes()).hexdigest()==args.compiler_sha256 +print('COMPILER_BINARY_SHA256',args.compiler_sha256,flush=True) +replacements=[('hello.echo','cas.echo'),('CreateGreetingInput','CompareAndSetInput'),('GreetingReceipt','UpdateReceipt'),('ExistingGreeting','ExistingValue'),('createGreeting','compareAndSet'),('smallCreateBudget','smallUpdateBudget'),('greetingKeyFootprint','cellKeyFootprint'),('alreadyExists','staleExpected'),('AlreadyExists','StaleExpected')] +def rewrite(value): + if isinstance(value,str): + for old,new in replacements:value=value.replace(old,new) + return value + if isinstance(value,list):return [rewrite(x) for x in value] + if isinstance(value,dict):return {rewrite(k):rewrite(v) for k,v in value.items()} + return value +config=rewrite(dec((fixture/'seed/echo-operation-configuration.cbor').read_bytes())) +config['programKind']='anchored-node-attachment-compare-and-set/v1' +config['invocationBinding']['expectedValueDigestField']='expected' +config['budgetCeiling']['readBytes']=1024 +exports=rewrite(dec((fixture/'seed/exports.cbor').read_bytes())) +for entry in exports['types']: + if entry['coordinate']=='cas.echo@1.CompareAndSetInput': + entry['definition']=entry['definition'].replace('>,key:', '>,expected:Bytes,key:') +for effect in exports['effects']:effect['effectKindHint']='replace' +adapter=rewrite(dec((fixture/'seed/adapter.cbor').read_bytes())) +for profile in adapter['operationProfiles'].values():profile['core']='continuum.profile.write/v1' +for impl in adapter['effectImplementations'].values(): + impl['writeClass']='replace' + impl['targetIntrinsic']='echo.dpo@1.anchored-node-attachment-compare-and-set' + impl['targetConfiguration']={'id':'echo.operation-lowering-configuration/v1','digest':['sha256',digest('echo.operation-lowering-configuration/v1',config)]} +manifest=rewrite(dec((fixture/'seed/manifest.cbor').read_bytes())) +manifest['exports']['digest']=['sha256',digest(manifest['exports']['id'],exports)] +for selection in manifest['targetAdapters']: + ref=selection['adapter'];ref['digest']=['sha256',digest(ref['id'],adapter)] +for name,value in [('echo-operation-configuration.cbor',config),('exports.cbor',exports),('adapter.cbor',adapter),('manifest.cbor',manifest)]: (root/name).write_bytes(enc(value)) +source=(args.source_template or fixture/'update-cell.edict.in').read_text().replace('LAWPACK_DIGEST',digest('edict.lawpack/v1',manifest).hex()) +assert 'LAWPACK_DIGEST' not in source +(root/'update-cell.edict').write_text(source) +shutil.copytree(args.provider_package.resolve(),root/'provider') +app={'schema':'edict.application/v1','coordinate':'examples.cas_echo@1','sources':['update-cell.edict'],'lawpacks':[{'manifest':'manifest.cbor','exports':'exports.cbor','adapter':'adapter.cbor','targetConfiguration':'echo-operation-configuration.cbor'}],'target':{'profile':'echo.dpo@1','providerPackage':'provider'},'outputDirectory':'build'} +(root/'edict.application.json').write_text(json.dumps(app)) +request={'schema':'edict.compiler.settings/v1','type':'compilerSettings','operation':'build','application':str(root/'edict.application.json')} +result=subprocess.run([str(compiler)],input=json.dumps(request)+'\n',text=True,capture_output=True,timeout=90) +assert len(result.stdout)+len(result.stderr)<128*1024 +(root/'compiler.stdout.jsonl').write_text(result.stdout);(root/'compiler.stderr.txt').write_text(result.stderr) +print(result.stdout,result.stderr,flush=True) +if args.expect_refusal: + events=[json.loads(line) for line in (result.stdout+'\n'+result.stderr).splitlines() if line.strip()] + def has_code(value): + if isinstance(value,dict): + return any(key in ('kind','code') and entry==args.expect_refusal for key,entry in value.items()) or any(has_code(entry) for entry in value.values()) + if isinstance(value,list):return any(has_code(entry) for entry in value) + return False + assert result.returncode!=0 and has_code(events),(result.returncode,events) + assert not any((root/'build').rglob('*.cbor')),'refused build published artifacts' + print('EXPECTED_STRUCTURED_REFUSAL',args.expect_refusal,flush=True) +else: + assert result.returncode==0,result.returncode +print('OUTPUT_FILES',sorted(str(p.relative_to(root)) for p in (root/'build').rglob('*') if p.is_file()),flush=True) diff --git a/crates/echo-edict-provider-verifier/tests/fixtures/compiler-produced-cas/built/compiler.stdout.jsonl b/crates/echo-edict-provider-verifier/tests/fixtures/compiler-produced-cas/built/compiler.stdout.jsonl new file mode 100644 index 000000000..a668538ec --- /dev/null +++ b/crates/echo-edict-provider-verifier/tests/fixtures/compiler-produced-cas/built/compiler.stdout.jsonl @@ -0,0 +1 @@ +{"checked":1,"command":"build","errors":0,"exitCode":0,"schema":"edict.cli.event/v1","status":"ok","type":"status"} diff --git a/crates/echo-edict-provider-verifier/tests/fixtures/compiler-produced-cas/built/executable-operation-package.cbor.hex b/crates/echo-edict-provider-verifier/tests/fixtures/compiler-produced-cas/built/executable-operation-package.cbor.hex new file mode 100644 index 000000000..042594180 --- /dev/null +++ b/crates/echo-edict-provider-verifier/tests/fixtures/compiler-produced-cas/built/executable-operation-package.cbor.hex @@ -0,0 +1 @@ +b366736368656d6178196563686f2e6f7065726174696f6e2d7061636b6167652f76316770726f6772616d59015da7646b696e64782b616e63686f7265642d6e6f64652d6174746163686d656e742d636f6d706172652d616e642d7365742f763166736368656d6178196563686f2e6f7065726174696f6e2d70726f6772616d2f76317272657175697265645f6e6f64655f747970655820f2f3958c741aaab3ed53fad32957d312f08e456139e9d1b2517b9859af0e7dea756d61785f7265706c6163656d656e745f6279746573190100781872657175697265645f6174746163686d656e745f7479706558203e0816f30d503b656d0550364a893a2ee7128287dcd6bdffd49c96149cd8fc6e781a696e7472696e7369635f70726f66696c655f6964656e746974795820dcc797883efcfd39231a4cee8b2574f5dee46a72be884e5a006f5cddfbfa4c1b781c696e7465727072657465725f70726f66696c655f6964656e746974795820415105a805a80d5e1de76c6eee9b3a9a9a17fec80f7f8f7ab6ef2026c0dd0dec6e6275646765745f6365696c696e67a3657374657073106a726561645f62797465731904006b77726974655f62797465731901407073656d616e7469635f636c6f73757265a86d636f72655f6964656e746974795820d0b3ab6db58d94ddf46d2e6e0600416e94e4a06818e596d565db61cac5195205706c61777061636b5f6964656e746974795820c5e61ed6aae005531a642287d00ed8bff56754ffec50fe0a2a419f9bc0cb28f9726c61777061636b5f636f6f7264696e6174656a6361732e6563686f4031727461726765745f69725f6964656e7469747958206d8035d56340e3ea220923c941d49c630898cdcc54c507a2725ebfbc90b7c5ea7565646963745f736f757263655f6964656e746974795820425772ad12c762527b1e85dec6b0cfe586cfdc3eb9ce3fa5af8f972dd840053e781a63616e6f6e6963616c5f6d65616e696e675f6964656e746974795820d0b3ab6db58d94ddf46d2e6e0600416e94e4a06818e596d565db61cac5195205781b6170706c69636174696f6e5f736368656d615f6964656e7469747958204fb7a2e90eaa9631a196329f37aded975b0ff5a8d4b67bb663cc2ce05808a017781d6170706c69636174696f6e5f736368656d615f636f6f7264696e617465736361732e6563686f2e6578706f7274732f7631746f7065726174696f6e5f636f6f7264696e617465781e6578616d706c65732e6361735f6563686f40312e75706461746543656c6c75696e7075745f736368656d615f6964656e7469747958202bc24c4c2899025a8f546ebcc5e9d92021a663d99ae5627137b7ad670936f966766f62737472756374696f6e5f636f6f7264696e61746578186361732e6563686f40312e5374616c65457870656374656476726573756c745f736368656d615f6964656e746974795820bd5045b1b0a667a9504aaedf73ff4857c96f4c85cadc611e0183a70ef3a06100777461726765745f70726f66696c655f6964656e7469747958203539cc6cf3b869e324d67b6ed688ea418f5518d34ef4c8d5b55e44981b8cec5c781a617574686f726974795f70726f66696c655f6964656e746974795820cf680697b82373550f58688a4eaaf3f63b268c8d616fd52f35dc76aeafba74ba781a696e7472696e7369635f70726f66696c655f6964656e746974795820dcc797883efcfd39231a4cee8b2574f5dee46a72be884e5a006f5cddfbfa4c1b781b666f6f747072696e745f636f6e74726163745f6964656e7469747958204841286db863695ecda9bebaa6d613b408d2201aee6d7e0feb8f5a1ce7b94b86781b6f62737472756374696f6e5f736368656d615f6964656e746974795820479fa916a43aefaa2b21a3411cfa0da3683c4e6edba43ed40b1f701be2564f49781c696e7465727072657465725f70726f66696c655f6964656e746974795820415105a805a80d5e1de76c6eee9b3a9a9a17fec80f7f8f7ab6ef2026c0dd0dec781d6170706c69636174696f6e5f726573756c745f70726f6a656374696f6ea66e61727469666163745f6279746573590149a566736368656d61781a65646963742e726573756c742d70726f6a656374696f6e2f76316a65787072657373696f6ea2646b696e64667265636f7264666669656c6473a2636b6579a3646b696e6466736f75726365647061746881636b657966736f75726365a2646b696e64706361706162696c697479526573756c74667374657049647175706461746543656c6c2e737465702e30676d657373616765a3646b696e6466736f75726365647061746881676d65737361676566736f75726365a1646b696e64706170706c69636174696f6e496e7075746a6f757470757454797065781f6578616d706c65732e6361735f6563686f40312e5570646174656443656c6c6e6d61784f75747075744279746573190200736f7065726174696f6e436f6f7264696e617465781e6578616d706c65732e6361735f6563686f40312e75706461746543656c6c7161727469666163745f6964656e746974795820f18f9a3d65491af531a6b8c4b84fd945daa5bcacb5df854874eec06fc606de7e7272756e74696d655f65787072657373696f6ea2646b696e64667265636f7264666669656c6473a2636b6579a3646b696e6466736f75726365647061746881636b657966736f75726365a1646b696e64706170706c69636174696f6e496e707574676d657373616765a3646b696e6466736f75726365647061746881676d65737361676566736f75726365a1646b696e64706170706c69636174696f6e496e707574781f6170706c69636174696f6e5f696e7075745f6e6f64655f6b65795f7061746881636b657978226170706c69636174696f6e5f696e7075745f7265706c6163656d656e745f7061746881676d657373616765782c6170706c69636174696f6e5f696e7075745f65787065637465645f76616c75655f6469676573745f7061746881686578706563746564781e726573756c745f696e746572707265746174696f6e5f6964656e74697479582063cb5cd4efb6bb4056de3736bc79a2f5acca11f5c4b068c147f88fa55ccb6b5278206576616c756174696f6e5f62617369735f736368656d615f6964656e74697479582020b6239db569aa86ae1c27596b374df90caa5ea41e8e005fd4fe00bfe5c3b38178216170706c69636174696f6e5f62617369735f736368656d615f6964656e746974795820464e830e037906c22daff080e9a478ad058f7c80150cfee3dad91ae4f8ca479f78236f62737472756374696f6e5f696e746572707265746174696f6e5f6964656e7469747958206c5158111419d8368d00b47264dda2bc8ec22f34b8b67f56c7dd5680ed3de6d7 diff --git a/crates/echo-edict-provider-verifier/tests/fixtures/compiler-produced-cas/built/update-cell.edict b/crates/echo-edict-provider-verifier/tests/fixtures/compiler-produced-cas/built/update-cell.edict new file mode 100644 index 000000000..9f57d578f --- /dev/null +++ b/crates/echo-edict-provider-verifier/tests/fixtures/compiler-produced-cas/built/update-cell.edict @@ -0,0 +1,18 @@ +package examples.cas_echo@1; + +use lawpack cas.echo@1 digest "sha256:c5e61ed6aae005531a642287d00ed8bff56754ffec50fe0a2a419f9bc0cb28f9" as cell; + +type UpdatedCell = { + key: String, + message: String, +}; + +intent updateCell(input: cell.CompareAndSetInput) returns UpdatedCell + profile cell.compareAndSet + basis input.basis + budget <= cell.smallUpdateBudget +{ + let receipt: cell.UpdateReceipt = cell.compareAndSet(input) + else { staleExpected(existing) => cell.StaleExpected }; + return { key: receipt.key, message: input.message }; +} diff --git a/crates/echo-edict-provider-verifier/tests/fixtures/compiler-produced-cas/built/verification-report.cbor.hex b/crates/echo-edict-provider-verifier/tests/fixtures/compiler-produced-cas/built/verification-report.cbor.hex new file mode 100644 index 000000000..ff3ba8303 --- /dev/null +++ b/crates/echo-edict-provider-verifier/tests/fixtures/compiler-produced-cas/built/verification-report.cbor.hex @@ -0,0 +1 @@ +a8676f7574636f6d65686163636570746564677061636b616765a2626964782165786563757461626c652d6f7065726174696f6e2d7061636b6167652e6563686f66646967657374826673686132353658205cfe5f533a8cff7d02934c142508eab90f88e093a7b752d4b35e771e08d75e64687461726765744972a26269646f6563686f2e7370616e2d69722f763166646967657374826673686132353658206d8035d56340e3ea220923c941d49c630898cdcc54c507a2725ebfbc90b7c5ea6a61706956657273696f6e78296563686f2e6f7065726174696f6e2d7061636b6167652d76657269666965722d7265706f72742f76316d646961676e6f73746963416269a26269647465646963742e646961676e6f73746963732f7631666469676573748266736861323536582028fd72a98223153982ca084c29dbb1b2d430623967ab3b6db9d7fee668e614b96f646961676e6f737469634279746573407165786563757461626c655375626a656374a2656279746573590148a4677061636b616765a2626964782165786563757461626c652d6f7065726174696f6e2d7061636b6167652e6563686f66646967657374826673686132353658205cfe5f533a8cff7d02934c142508eab90f88e093a7b752d4b35e771e08d75e64687461726765744972a26269646f6563686f2e7370616e2d69722f763166646967657374826673686132353658206d8035d56340e3ea220923c941d49c630898cdcc54c507a2725ebfbc90b7c5ea6a61706956657273696f6e781a6563686f2e65786563757461626c652d7375626a6563742f7631781b6170706c69636174696f6e526573756c7450726f6a656374696f6ea2626964781e6578616d706c65732e6361735f6563686f40312e75706461746543656c6c6664696765737482667368613235365820f18f9a3d65491af531a6b8c4b84fd945daa5bcacb5df854874eec06fc606de7e697265666572656e6365a2626964781a6563686f2e65786563757461626c652d7375626a6563742f76316664696765737482667368613235365820d3c1d3d91802dfdc91429dd5968b210b2cb51ed688e43d2d11b29924cb895c4b781b6170706c69636174696f6e526573756c7450726f6a656374696f6ea2626964781e6578616d706c65732e6361735f6563686f40312e75706461746543656c6c6664696765737482667368613235365820f18f9a3d65491af531a6b8c4b84fd945daa5bcacb5df854874eec06fc606de7e diff --git a/crates/echo-edict-provider-verifier/tests/fixtures/compiler-produced-cas/counterexamples/transformed-replacement/README.md b/crates/echo-edict-provider-verifier/tests/fixtures/compiler-produced-cas/counterexamples/transformed-replacement/README.md new file mode 100644 index 000000000..8b88a1546 --- /dev/null +++ b/crates/echo-edict-provider-verifier/tests/fixtures/compiler-produced-cas/counterexamples/transformed-replacement/README.md @@ -0,0 +1,35 @@ + + + +# Authored replacement ignored by the accepted package + +This is retained failing evidence for the blocking argument-binding finding on +[PR 776](https://github.com/flyingrobots/echo/pull/776#issuecomment-6055363576), +not an accepted language/runtime contract or a positive golden fixture. + +The source calls the imported effect with `message: "forced"`, while its returned +record independently uses `input.message`. The pinned public compiler documented +in the parent fixture accepted this source using the provider components at +`7bda832c15960e6ddd54e0d6c1fa91a079b9b20f`. The exact emitted executable package +and accepted verification report are preserved here as lowercase hex. + +The emitted mutation binding is `application_input_replacement_path = ["message"]`. +That selects the caller's field rather than the authored effect argument. The +observed failure is successful production/verification of this mismatched +binding. Execution of this counterexample package has not been performed. + +The corrected providers explicitly refuse this source because their bounded +mutation profiles support only the declared application input as the effect +argument. The regression +`providers_refuse_effect_inputs_not_equal_to_the_declared_application_argument` +checks lowerer and verifier refusals independently for both mutation profiles. +Its first run failed because the lowerer accepted a transformed argument; after +the fix the verifier executable suite passed 70 tests and the lowerer suite 13. + +To reproduce the public compiler refusal, run the command in the parent README +with `--source-template` pointing to this `update-cell.edict` and +`--expect-refusal ProviderLowererRefused`. The corrected component pair produced +that structured refusal with compiler exit code 2 and no published CBOR outputs. +The original direct-input positive control still emitted byte-identical package +and report artifacts. These checks establish production/refusal; they do not +claim execution of the historical erroneous package or final PR approval. diff --git a/crates/echo-edict-provider-verifier/tests/fixtures/compiler-produced-cas/counterexamples/transformed-replacement/executable-operation-package.cbor.hex b/crates/echo-edict-provider-verifier/tests/fixtures/compiler-produced-cas/counterexamples/transformed-replacement/executable-operation-package.cbor.hex new file mode 100644 index 000000000..a48252ed7 --- /dev/null +++ b/crates/echo-edict-provider-verifier/tests/fixtures/compiler-produced-cas/counterexamples/transformed-replacement/executable-operation-package.cbor.hex @@ -0,0 +1 @@ +b366736368656d6178196563686f2e6f7065726174696f6e2d7061636b6167652f76316770726f6772616d59015da7646b696e64782b616e63686f7265642d6e6f64652d6174746163686d656e742d636f6d706172652d616e642d7365742f763166736368656d6178196563686f2e6f7065726174696f6e2d70726f6772616d2f76317272657175697265645f6e6f64655f747970655820f2f3958c741aaab3ed53fad32957d312f08e456139e9d1b2517b9859af0e7dea756d61785f7265706c6163656d656e745f6279746573190100781872657175697265645f6174746163686d656e745f7479706558203e0816f30d503b656d0550364a893a2ee7128287dcd6bdffd49c96149cd8fc6e781a696e7472696e7369635f70726f66696c655f6964656e746974795820dcc797883efcfd39231a4cee8b2574f5dee46a72be884e5a006f5cddfbfa4c1b781c696e7465727072657465725f70726f66696c655f6964656e746974795820415105a805a80d5e1de76c6eee9b3a9a9a17fec80f7f8f7ab6ef2026c0dd0dec6e6275646765745f6365696c696e67a3657374657073106a726561645f62797465731904006b77726974655f62797465731901407073656d616e7469635f636c6f73757265a86d636f72655f6964656e746974795820345543a34adf8ded70b323f47ba470ff25720104e929ac95d91df8737fbe4fba706c61777061636b5f6964656e746974795820c5e61ed6aae005531a642287d00ed8bff56754ffec50fe0a2a419f9bc0cb28f9726c61777061636b5f636f6f7264696e6174656a6361732e6563686f4031727461726765745f69725f6964656e746974795820f76704b6c9c3ecb8634f42143b6aa2fa6e2e009c692f1b3e378b1cab8d9a2bf97565646963745f736f757263655f6964656e7469747958209a38bbdf715736e9033b70da41ef0274c57be1456054e345f946b11dfbc296d4781a63616e6f6e6963616c5f6d65616e696e675f6964656e746974795820345543a34adf8ded70b323f47ba470ff25720104e929ac95d91df8737fbe4fba781b6170706c69636174696f6e5f736368656d615f6964656e7469747958204fb7a2e90eaa9631a196329f37aded975b0ff5a8d4b67bb663cc2ce05808a017781d6170706c69636174696f6e5f736368656d615f636f6f7264696e617465736361732e6563686f2e6578706f7274732f7631746f7065726174696f6e5f636f6f7264696e617465781e6578616d706c65732e6361735f6563686f40312e75706461746543656c6c75696e7075745f736368656d615f6964656e7469747958202bc24c4c2899025a8f546ebcc5e9d92021a663d99ae5627137b7ad670936f966766f62737472756374696f6e5f636f6f7264696e61746578186361732e6563686f40312e5374616c65457870656374656476726573756c745f736368656d615f6964656e746974795820bd5045b1b0a667a9504aaedf73ff4857c96f4c85cadc611e0183a70ef3a06100777461726765745f70726f66696c655f6964656e7469747958203539cc6cf3b869e324d67b6ed688ea418f5518d34ef4c8d5b55e44981b8cec5c781a617574686f726974795f70726f66696c655f6964656e746974795820cf680697b82373550f58688a4eaaf3f63b268c8d616fd52f35dc76aeafba74ba781a696e7472696e7369635f70726f66696c655f6964656e746974795820dcc797883efcfd39231a4cee8b2574f5dee46a72be884e5a006f5cddfbfa4c1b781b666f6f747072696e745f636f6e74726163745f6964656e7469747958204841286db863695ecda9bebaa6d613b408d2201aee6d7e0feb8f5a1ce7b94b86781b6f62737472756374696f6e5f736368656d615f6964656e746974795820479fa916a43aefaa2b21a3411cfa0da3683c4e6edba43ed40b1f701be2564f49781c696e7465727072657465725f70726f66696c655f6964656e746974795820415105a805a80d5e1de76c6eee9b3a9a9a17fec80f7f8f7ab6ef2026c0dd0dec781d6170706c69636174696f6e5f726573756c745f70726f6a656374696f6ea66e61727469666163745f6279746573590149a566736368656d61781a65646963742e726573756c742d70726f6a656374696f6e2f76316a65787072657373696f6ea2646b696e64667265636f7264666669656c6473a2636b6579a3646b696e6466736f75726365647061746881636b657966736f75726365a2646b696e64706361706162696c697479526573756c74667374657049647175706461746543656c6c2e737465702e30676d657373616765a3646b696e6466736f75726365647061746881676d65737361676566736f75726365a1646b696e64706170706c69636174696f6e496e7075746a6f757470757454797065781f6578616d706c65732e6361735f6563686f40312e5570646174656443656c6c6e6d61784f75747075744279746573190200736f7065726174696f6e436f6f7264696e617465781e6578616d706c65732e6361735f6563686f40312e75706461746543656c6c7161727469666163745f6964656e746974795820f18f9a3d65491af531a6b8c4b84fd945daa5bcacb5df854874eec06fc606de7e7272756e74696d655f65787072657373696f6ea2646b696e64667265636f7264666669656c6473a2636b6579a3646b696e6466736f75726365647061746881636b657966736f75726365a1646b696e64706170706c69636174696f6e496e707574676d657373616765a3646b696e6466736f75726365647061746881676d65737361676566736f75726365a1646b696e64706170706c69636174696f6e496e707574781f6170706c69636174696f6e5f696e7075745f6e6f64655f6b65795f7061746881636b657978226170706c69636174696f6e5f696e7075745f7265706c6163656d656e745f7061746881676d657373616765782c6170706c69636174696f6e5f696e7075745f65787065637465645f76616c75655f6469676573745f7061746881686578706563746564781e726573756c745f696e746572707265746174696f6e5f6964656e74697479582063cb5cd4efb6bb4056de3736bc79a2f5acca11f5c4b068c147f88fa55ccb6b5278206576616c756174696f6e5f62617369735f736368656d615f6964656e74697479582020b6239db569aa86ae1c27596b374df90caa5ea41e8e005fd4fe00bfe5c3b38178216170706c69636174696f6e5f62617369735f736368656d615f6964656e746974795820464e830e037906c22daff080e9a478ad058f7c80150cfee3dad91ae4f8ca479f78236f62737472756374696f6e5f696e746572707265746174696f6e5f6964656e7469747958206c5158111419d8368d00b47264dda2bc8ec22f34b8b67f56c7dd5680ed3de6d7 diff --git a/crates/echo-edict-provider-verifier/tests/fixtures/compiler-produced-cas/counterexamples/transformed-replacement/update-cell.edict b/crates/echo-edict-provider-verifier/tests/fixtures/compiler-produced-cas/counterexamples/transformed-replacement/update-cell.edict new file mode 100644 index 000000000..bb364330b --- /dev/null +++ b/crates/echo-edict-provider-verifier/tests/fixtures/compiler-produced-cas/counterexamples/transformed-replacement/update-cell.edict @@ -0,0 +1,18 @@ +package examples.cas_echo@1; + +use lawpack cas.echo@1 digest "sha256:c5e61ed6aae005531a642287d00ed8bff56754ffec50fe0a2a419f9bc0cb28f9" as cell; + +type UpdatedCell = { + key: String, + message: String, +}; + +intent updateCell(input: cell.CompareAndSetInput) returns UpdatedCell + profile cell.compareAndSet + basis input.basis + budget <= cell.smallUpdateBudget +{ + let receipt: cell.UpdateReceipt = cell.compareAndSet({basis: input.basis, key: input.key, expected: input.expected, message: "forced"}) + else { staleExpected(existing) => cell.StaleExpected }; + return { key: receipt.key, message: input.message }; +} diff --git a/crates/echo-edict-provider-verifier/tests/fixtures/compiler-produced-cas/counterexamples/transformed-replacement/verification-report.cbor.hex b/crates/echo-edict-provider-verifier/tests/fixtures/compiler-produced-cas/counterexamples/transformed-replacement/verification-report.cbor.hex new file mode 100644 index 000000000..ab4cde0f2 --- /dev/null +++ b/crates/echo-edict-provider-verifier/tests/fixtures/compiler-produced-cas/counterexamples/transformed-replacement/verification-report.cbor.hex @@ -0,0 +1 @@ +a8676f7574636f6d65686163636570746564677061636b616765a2626964782165786563757461626c652d6f7065726174696f6e2d7061636b6167652e6563686f6664696765737482667368613235365820a64ce33a8dec08d87040f507144f0dd9b7963d78d877713965658bcd09838f4f687461726765744972a26269646f6563686f2e7370616e2d69722f76316664696765737482667368613235365820f76704b6c9c3ecb8634f42143b6aa2fa6e2e009c692f1b3e378b1cab8d9a2bf96a61706956657273696f6e78296563686f2e6f7065726174696f6e2d7061636b6167652d76657269666965722d7265706f72742f76316d646961676e6f73746963416269a26269647465646963742e646961676e6f73746963732f7631666469676573748266736861323536582028fd72a98223153982ca084c29dbb1b2d430623967ab3b6db9d7fee668e614b96f646961676e6f737469634279746573407165786563757461626c655375626a656374a2656279746573590148a4677061636b616765a2626964782165786563757461626c652d6f7065726174696f6e2d7061636b6167652e6563686f6664696765737482667368613235365820a64ce33a8dec08d87040f507144f0dd9b7963d78d877713965658bcd09838f4f687461726765744972a26269646f6563686f2e7370616e2d69722f76316664696765737482667368613235365820f76704b6c9c3ecb8634f42143b6aa2fa6e2e009c692f1b3e378b1cab8d9a2bf96a61706956657273696f6e781a6563686f2e65786563757461626c652d7375626a6563742f7631781b6170706c69636174696f6e526573756c7450726f6a656374696f6ea2626964781e6578616d706c65732e6361735f6563686f40312e75706461746543656c6c6664696765737482667368613235365820f18f9a3d65491af531a6b8c4b84fd945daa5bcacb5df854874eec06fc606de7e697265666572656e6365a2626964781a6563686f2e65786563757461626c652d7375626a6563742f76316664696765737482667368613235365820f78f42dde3b566957358a82f1d07299487faef32e4d936f17e7370a39744238b781b6170706c69636174696f6e526573756c7450726f6a656374696f6ea2626964781e6578616d706c65732e6361735f6563686f40312e75706461746543656c6c6664696765737482667368613235365820f18f9a3d65491af531a6b8c4b84fd945daa5bcacb5df854874eec06fc606de7e diff --git a/crates/echo-edict-provider-verifier/tests/fixtures/compiler-produced-cas/seed/adapter.cbor b/crates/echo-edict-provider-verifier/tests/fixtures/compiler-produced-cas/seed/adapter.cbor new file mode 100644 index 000000000..24e2f546e Binary files /dev/null and b/crates/echo-edict-provider-verifier/tests/fixtures/compiler-produced-cas/seed/adapter.cbor differ diff --git a/crates/echo-edict-provider-verifier/tests/fixtures/compiler-produced-cas/seed/echo-operation-configuration.cbor b/crates/echo-edict-provider-verifier/tests/fixtures/compiler-produced-cas/seed/echo-operation-configuration.cbor new file mode 100644 index 000000000..84838ab0d Binary files /dev/null and b/crates/echo-edict-provider-verifier/tests/fixtures/compiler-produced-cas/seed/echo-operation-configuration.cbor differ diff --git a/crates/echo-edict-provider-verifier/tests/fixtures/compiler-produced-cas/seed/exports.cbor b/crates/echo-edict-provider-verifier/tests/fixtures/compiler-produced-cas/seed/exports.cbor new file mode 100644 index 000000000..0e1220cee --- /dev/null +++ b/crates/echo-edict-provider-verifier/tests/fixtures/compiler-produced-cas/seed/exports.cbor @@ -0,0 +1 @@ +¦etypesƒ¢jcoordinatex hello.echo@1.CreateGreetingInputjdefinitionx�Record,key:String,message:String>¢jcoordinatexhello.echo@1.GreetingReceiptjdefinitionx-Record>¢jcoordinatexhello.echo@1.ExistingGreetingjdefinitionxXRecord,message:String>geffects�ªiinputTypex hello.echo@1.CreateGreetingInputjcoordinatexhello.echo@1.createGreetingjoutputTypexhello.echo@1.GreetingReceiptlguardSupportõncostObligationxhello.echo@1.smallCreateBudgetneffectFailures¡malreadyExists¢kpayloadTypexhello.echo@1.ExistingGreetingnauthorityClassndomainMappableneffectKindHintfcreatenexecutionClassgruntimentypeParameters€sfootprintObligationx!hello.echo@1.greetingKeyFootprinticonstants€lobstructions�£jcoordinatexhello.echo@1.AlreadyExistsmpayloadSchemaxhello.echo@1.ExistingGreetingnauthorityClassndomainMappablempureFunctions€qoperationProfiles¡xhello.echo@1.createGreeting¢mopticTemplate¥iopticKindsaffectReintegrationlboundaryKindfaffectmsupportPolicyxhello.echo@1.directSupportolossDispositionuhello.echo@1.losslesssapertureRequirement¢crefx!hello.echo@1.greetingKeyFootprintdkindxabstractFootprintObligationoeffectPredicatex!hello.echo@1.createGreetingEffect \ No newline at end of file diff --git a/crates/echo-edict-provider-verifier/tests/fixtures/compiler-produced-cas/seed/manifest.cbor b/crates/echo-edict-provider-verifier/tests/fixtures/compiler-produced-cas/seed/manifest.cbor new file mode 100644 index 000000000..d715900f0 Binary files /dev/null and b/crates/echo-edict-provider-verifier/tests/fixtures/compiler-produced-cas/seed/manifest.cbor differ diff --git a/crates/echo-edict-provider-verifier/tests/fixtures/compiler-produced-cas/update-cell.edict.in b/crates/echo-edict-provider-verifier/tests/fixtures/compiler-produced-cas/update-cell.edict.in new file mode 100644 index 000000000..3efedfc1f --- /dev/null +++ b/crates/echo-edict-provider-verifier/tests/fixtures/compiler-produced-cas/update-cell.edict.in @@ -0,0 +1,18 @@ +package examples.cas_echo@1; + +use lawpack cas.echo@1 digest "sha256:LAWPACK_DIGEST" as cell; + +type UpdatedCell = { + key: String, + message: String, +}; + +intent updateCell(input: cell.CompareAndSetInput) returns UpdatedCell + profile cell.compareAndSet + basis input.basis + budget <= cell.smallUpdateBudget +{ + let receipt: cell.UpdateReceipt = cell.compareAndSet(input) + else { staleExpected(existing) => cell.StaleExpected }; + return { key: receipt.key, message: input.message }; +} 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 555e4dcf6..43762f5e1 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 74fe228fd..db3a02611 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 2a01bb289..892a7fb8a 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: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 +{"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:16e8b1aa45f77bf2f69f538e7120cfb398d85525fdc6947a8cdd86d7b317ea2e"}],"generationInputDigest":"sha256:0c07274803d465ec212c313f74dd46396851f43019fa0f97da9571a167c84587","generator":{"coordinate":"echo-wesley-gen.provider-artifact-generator@1","digest":"sha256:2a49f1ba9ff4b83a9542aeb8a64bfc642b543e033aad61b855b7d429cf333e37","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 b960179af..1663cece1 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: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 +{"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:16e8b1aa45f77bf2f69f538e7120cfb398d85525fdc6947a8cdd86d7b317ea2e"}],"generationInputDigest":"sha256:0c07274803d465ec212c313f74dd46396851f43019fa0f97da9571a167c84587","generator":{"coordinate":"echo-wesley-gen.provider-artifact-generator@1","digest":"sha256:2a49f1ba9ff4b83a9542aeb8a64bfc642b543e033aad61b855b7d429cf333e37","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:e6158f9eae9e1a57f3b17b4b50aae47ab1c82de94fbc040e6fd3347193a55b3f","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/primary/schema.echo-provider-artifacts.cddl b/crates/echo-wesley-gen/assets/v1/edict-provider/package/v1/generated/primary/schema.echo-provider-artifacts.cddl index a3f6f87fa..a510adbed 100644 --- a/crates/echo-wesley-gen/assets/v1/edict-provider/package/v1/generated/primary/schema.echo-provider-artifacts.cddl +++ b/crates/echo-wesley-gen/assets/v1/edict-provider/package/v1/generated/primary/schema.echo-provider-artifacts.cddl @@ -1226,6 +1226,7 @@ echo-compiler-produced-pure-budget = { } echo-operation-application-result-projection = { + ? "application_input_expected_value_digest_path": [* echo-nonempty-tstr], "application_input_node_key_path": [* echo-nonempty-tstr], "application_input_replacement_path": [* echo-nonempty-tstr], "artifact_bytes": bstr, @@ -1266,6 +1267,7 @@ echo-operation-semantic-closure = { echo-operation-lowering-configuration = echo-attachment-create-if-absent-lowering-configuration / + echo-attachment-compare-and-set-lowering-configuration / echo-compiler-produced-bounded-pure-lowering-configuration / echo-compiler-produced-bounded-read-lowering-configuration @@ -1301,6 +1303,27 @@ echo-attachment-create-if-absent-lowering-configuration = { requiredNodeTypeProfile: echo-nonempty-tstr, } +echo-attachment-compare-and-set-lowering-configuration = { + apiVersion: "echo.operation-lowering-configuration/v1", + authorityProfile: echo-nonempty-tstr, + budgetCeiling: { + readBytes: 64..18446744073709551615, + steps: 4..18446744073709551615, + writeBytes: 64..18446744073709551615, + }, + invocationBinding: { + expectedValueDigestField: echo-nonempty-tstr, + nodeIdDerivation: "sha256-utf8/v1", + nodeKeyField: echo-nonempty-tstr, + replacementField: echo-nonempty-tstr, + warpIdSource: "action-lane/v1", + }, + maxReplacementBytes: 1..18446744073709551615, + programKind: "anchored-node-attachment-compare-and-set/v1", + requiredAttachmentTypeProfile: echo-nonempty-tstr, + requiredNodeTypeProfile: echo-nonempty-tstr, +} + echo-operation-package-verifier-report = { apiVersion: "echo.operation-package-verifier-report/v1", applicationResultProjection: resource-ref, 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 ffda753cd..f1c694828 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:21e5267310b6b4c06bf24a814d76790cbd58a4a9dcc33205dddd6fc1937594dd" + "digest": "sha256:e1de94ad00e3297688199a6f45d26ef86fe3a270529c006bbbd3d8bb6fc40738" }, "artifacts": [ { @@ -21,7 +21,7 @@ }, "generator": { "coordinate": "echo-wesley-gen.provider-artifact-generator@1", - "digest": "sha256:cb9c6569a8f3c0ed3a0bd500151a3673f7f03b6f9d06d3b7edf227760fa895df" + "digest": "sha256:2a49f1ba9ff4b83a9542aeb8a64bfc642b543e033aad61b855b7d429cf333e37" } } }, @@ -40,7 +40,7 @@ }, "generator": { "coordinate": "echo-wesley-gen.provider-artifact-generator@1", - "digest": "sha256:cb9c6569a8f3c0ed3a0bd500151a3673f7f03b6f9d06d3b7edf227760fa895df" + "digest": "sha256:2a49f1ba9ff4b83a9542aeb8a64bfc642b543e033aad61b855b7d429cf333e37" } } }, @@ -59,7 +59,7 @@ }, "generator": { "coordinate": "echo-wesley-gen.provider-artifact-generator@1", - "digest": "sha256:cb9c6569a8f3c0ed3a0bd500151a3673f7f03b6f9d06d3b7edf227760fa895df" + "digest": "sha256:2a49f1ba9ff4b83a9542aeb8a64bfc642b543e033aad61b855b7d429cf333e37" } } }, @@ -78,7 +78,7 @@ }, "generator": { "coordinate": "echo-wesley-gen.provider-artifact-generator@1", - "digest": "sha256:cb9c6569a8f3c0ed3a0bd500151a3673f7f03b6f9d06d3b7edf227760fa895df" + "digest": "sha256:2a49f1ba9ff4b83a9542aeb8a64bfc642b543e033aad61b855b7d429cf333e37" } } }, @@ -87,13 +87,13 @@ "artifactKind": "lowerer", "resource": { "coordinate": "echo.dpo.lowerer/component@1", - "digest": "sha256:dc3edc7c4f194bd2fdd2b5a1ecc79f8fb7a24a5a31294a1f1fc30369c053b8d4" + "digest": "sha256:80e426cc277a4c5e78eab374b68144c8f3e34b6afb93eb991789ce2e5ee7b9a4" }, "source": { "kind": "component", "component": { "coordinate": "echo.dpo.lowerer/component@1", - "digest": "sha256:dc3edc7c4f194bd2fdd2b5a1ecc79f8fb7a24a5a31294a1f1fc30369c053b8d4" + "digest": "sha256:80e426cc277a4c5e78eab374b68144c8f3e34b6afb93eb991789ce2e5ee7b9a4" } } }, @@ -102,7 +102,7 @@ "artifactKind": "generationProvenance", "resource": { "coordinate": "echo.edict-provider-generation-provenance@1", - "digest": "sha256:2b27a04863c3660e8ce716cabfd63fd047d3f75a884e28d4222de5564c254142" + "digest": "sha256:764d213f3eff1dd9bf922c7c4d0a75334bf1fd11f3080139493f796058c79762" }, "source": { "kind": "generated", @@ -112,7 +112,7 @@ }, "generator": { "coordinate": "echo-wesley-gen.provider-artifact-generator@1", - "digest": "sha256:cb9c6569a8f3c0ed3a0bd500151a3673f7f03b6f9d06d3b7edf227760fa895df" + "digest": "sha256:2a49f1ba9ff4b83a9542aeb8a64bfc642b543e033aad61b855b7d429cf333e37" } } }, @@ -121,7 +121,7 @@ "artifactKind": "reviewArtifact", "resource": { "coordinate": "echo.edict-provider-generation-review@1", - "digest": "sha256:8000857ec83afd059b0747376c01699b8c97052c84ff6a69d3e7eed407910a8a" + "digest": "sha256:cc3d858a150c0b56424d36ef8efad040106edb6af1ac05a20aa619542e6dbc94" }, "source": { "kind": "generated", @@ -131,7 +131,7 @@ }, "generator": { "coordinate": "echo-wesley-gen.provider-artifact-generator@1", - "digest": "sha256:cb9c6569a8f3c0ed3a0bd500151a3673f7f03b6f9d06d3b7edf227760fa895df" + "digest": "sha256:2a49f1ba9ff4b83a9542aeb8a64bfc642b543e033aad61b855b7d429cf333e37" } } }, @@ -140,7 +140,7 @@ "artifactKind": "artifactSchema", "resource": { "coordinate": "echo.provider-artifacts.cddl@1", - "digest": "sha256:9078b84c47f2e5b722f8fea56cd0bca585cfd150c8114c22994b67cf19f6d9ab" + "digest": "sha256:16e8b1aa45f77bf2f69f538e7120cfb398d85525fdc6947a8cdd86d7b317ea2e" }, "source": { "kind": "generated", @@ -150,7 +150,7 @@ }, "generator": { "coordinate": "echo-wesley-gen.provider-artifact-generator@1", - "digest": "sha256:cb9c6569a8f3c0ed3a0bd500151a3673f7f03b6f9d06d3b7edf227760fa895df" + "digest": "sha256:2a49f1ba9ff4b83a9542aeb8a64bfc642b543e033aad61b855b7d429cf333e37" } } }, @@ -169,7 +169,7 @@ }, "generator": { "coordinate": "echo-wesley-gen.provider-artifact-generator@1", - "digest": "sha256:cb9c6569a8f3c0ed3a0bd500151a3673f7f03b6f9d06d3b7edf227760fa895df" + "digest": "sha256:2a49f1ba9ff4b83a9542aeb8a64bfc642b543e033aad61b855b7d429cf333e37" } } }, @@ -178,13 +178,13 @@ "artifactKind": "verifier", "resource": { "coordinate": "echo.dpo.verifier/component@1", - "digest": "sha256:23d1d256ec66138697f93babcd2be57a4f56de1eb2d1fb6910e5aef881c4dd0e" + "digest": "sha256:87649ace3aa1ab21c45cc4ed5c90051583dd08b48518cdca61dacde72783440a" }, "source": { "kind": "component", "component": { "coordinate": "echo.dpo.verifier/component@1", - "digest": "sha256:23d1d256ec66138697f93babcd2be57a4f56de1eb2d1fb6910e5aef881c4dd0e" + "digest": "sha256:87649ace3aa1ab21c45cc4ed5c90051583dd08b48518cdca61dacde72783440a" } } } diff --git a/crates/echo-wesley-gen/src/provider_artifacts.rs b/crates/echo-wesley-gen/src/provider_artifacts.rs index cf05c9730..dc41a81e5 100644 --- a/crates/echo-wesley-gen/src/provider_artifacts.rs +++ b/crates/echo-wesley-gen/src/provider_artifacts.rs @@ -294,6 +294,7 @@ echo-compiler-produced-pure-budget = { } echo-operation-application-result-projection = { + ? "application_input_expected_value_digest_path": [* echo-nonempty-tstr], "application_input_node_key_path": [* echo-nonempty-tstr], "application_input_replacement_path": [* echo-nonempty-tstr], "artifact_bytes": bstr, @@ -334,6 +335,7 @@ echo-operation-semantic-closure = { echo-operation-lowering-configuration = echo-attachment-create-if-absent-lowering-configuration / + echo-attachment-compare-and-set-lowering-configuration / echo-compiler-produced-bounded-pure-lowering-configuration / echo-compiler-produced-bounded-read-lowering-configuration @@ -369,6 +371,27 @@ echo-attachment-create-if-absent-lowering-configuration = { requiredNodeTypeProfile: echo-nonempty-tstr, } +echo-attachment-compare-and-set-lowering-configuration = { + apiVersion: "echo.operation-lowering-configuration/v1", + authorityProfile: echo-nonempty-tstr, + budgetCeiling: { + readBytes: 64..18446744073709551615, + steps: 4..18446744073709551615, + writeBytes: 64..18446744073709551615, + }, + invocationBinding: { + expectedValueDigestField: echo-nonempty-tstr, + nodeIdDerivation: "sha256-utf8/v1", + nodeKeyField: echo-nonempty-tstr, + replacementField: echo-nonempty-tstr, + warpIdSource: "action-lane/v1", + }, + maxReplacementBytes: 1..18446744073709551615, + programKind: "anchored-node-attachment-compare-and-set/v1", + requiredAttachmentTypeProfile: echo-nonempty-tstr, + requiredNodeTypeProfile: echo-nonempty-tstr, +} + echo-operation-package-verifier-report = { apiVersion: "echo.operation-package-verifier-report/v1", applicationResultProjection: resource-ref, diff --git a/crates/echo-wesley-gen/tests/provider_cas_schema.rs b/crates/echo-wesley-gen/tests/provider_cas_schema.rs new file mode 100644 index 000000000..06b6cf4db --- /dev/null +++ b/crates/echo-wesley-gen/tests/provider_cas_schema.rs @@ -0,0 +1,166 @@ +// SPDX-License-Identifier: Apache-2.0 +// © James Ross Ω FLYING•ROBOTS +#![allow(clippy::expect_used, clippy::panic)] +//! Projected CAS configuration and result-projection schema witnesses. + +use echo_wesley_gen::provider_artifacts::{ + generate_provider_primary_artifacts_v1, ProviderPrimaryArtifactsV1, +}; +use echo_wesley_gen::provider_canonical::{encode_canonical_cbor_v1, CanonicalValueV1}; +use echo_wesley_gen::provider_contract_pack::{ + admit_provider_contract_pack_v1, AdmittedProviderContractPackV1, +}; +use echo_wesley_gen::provider_generation::{ + build_provider_generation_input_v1, ProviderGenerationInputV1, +}; + +const SOURCE: &[u8] = include_bytes!("../assets/v1/edict-provider/echo-provider-semantics-v1.json"); +const SETTINGS: &[u8] = include_bytes!("../assets/v1/edict-provider/generation-settings-v1.json"); +const CONTRACT_CDDL: &[u8] = + include_bytes!("../assets/v1/edict-provider/contracts/v1/edict-provider-contracts.cddl"); +const CONTRACT_MANIFEST: &[u8] = + include_bytes!("../assets/v1/edict-provider/contracts/v1/manifest.json"); + +fn admitted_pack() -> AdmittedProviderContractPackV1 { + admit_provider_contract_pack_v1(CONTRACT_CDDL, CONTRACT_MANIFEST) + .expect("checked Edict provider contract pack is admitted") +} + +fn build_input(source: &[u8], pack: &AdmittedProviderContractPackV1) -> ProviderGenerationInputV1 { + build_provider_generation_input_v1(source, pack, SETTINGS) + .expect("checked provider generation input builds") +} + +fn generate( + source: &[u8], + pack: &AdmittedProviderContractPackV1, +) -> (ProviderGenerationInputV1, ProviderPrimaryArtifactsV1) { + let input = build_input(source, pack); + let artifacts = generate_provider_primary_artifacts_v1(&input, pack) + .expect("checked primary provider artifacts generate"); + (input, artifacts) +} + +fn text(value: &str) -> CanonicalValueV1 { + CanonicalValueV1::Text(value.to_owned()) +} +fn map(entries: Vec<(&str, CanonicalValueV1)>) -> CanonicalValueV1 { + CanonicalValueV1::Map( + entries + .into_iter() + .map(|(key, value)| (text(key), value)) + .collect(), + ) +} + +fn configuration(expected: bool, steps: i128) -> CanonicalValueV1 { + let mut binding = vec![ + ("nodeIdDerivation", text("sha256-utf8/v1")), + ("nodeKeyField", text("key")), + ("replacementField", text("value")), + ("warpIdSource", text("action-lane/v1")), + ]; + if expected { + binding.push(("expectedValueDigestField", text("expected"))); + } + map(vec![ + ( + "apiVersion", + text("echo.operation-lowering-configuration/v1"), + ), + ( + "programKind", + text("anchored-node-attachment-compare-and-set/v1"), + ), + ("authorityProfile", text("test.authority/v1")), + ("requiredNodeTypeProfile", text("test.node/v1")), + ("requiredAttachmentTypeProfile", text("test.atom/v1")), + ("maxReplacementBytes", CanonicalValueV1::Integer(256)), + ( + "budgetCeiling", + map(vec![ + ("steps", CanonicalValueV1::Integer(steps)), + ("readBytes", CanonicalValueV1::Integer(1024)), + ("writeBytes", CanonicalValueV1::Integer(1024)), + ]), + ), + ("invocationBinding", map(binding)), + ]) +} + +#[test] +fn cas_configuration_requires_digest_binding_and_four_steps() { + let pack = admitted_pack(); + let (_, generated) = generate(SOURCE, &pack); + let bytes = encode_canonical_cbor_v1(&configuration(true, 4)).expect("canonical configuration"); + generated + .schema() + .validate_root_bytes("echo-operation-lowering-configuration", &bytes) + .expect("valid CAS configuration"); + for value in [configuration(false, 4), configuration(true, 3)] { + let bytes = encode_canonical_cbor_v1(&value).expect("canonical invalid configuration"); + let error = generated + .schema() + .validate_root_bytes("echo-operation-lowering-configuration", &bytes) + .expect_err("invalid CAS configuration"); + assert_eq!(error.kind(), echo_wesley_gen::provider_artifacts::ProviderArtifactGenerationErrorKind::OwningRootRejected); + } +} + +#[test] +fn projected_cas_schema_accepts_digest_path_without_changing_create_shape() { + let pack = admitted_pack(); + let (_, generated) = generate(SOURCE, &pack); + let mut fields = vec![ + ( + "application_input_node_key_path", + CanonicalValueV1::Array(vec![text("key")]), + ), + ( + "application_input_replacement_path", + CanonicalValueV1::Array(vec![text("value")]), + ), + ("artifact_bytes", CanonicalValueV1::Bytes(vec![0xa0])), + ("artifact_identity", CanonicalValueV1::Bytes(vec![0; 32])), + ( + "runtime_expression", + map(vec![ + ("kind", text("source")), + ("path", CanonicalValueV1::Array(vec![text("key")])), + ("source", map(vec![("kind", text("applicationInput"))])), + ]), + ), + ]; + for cas in [false, true] { + if cas { + fields.push(( + "application_input_expected_value_digest_path", + CanonicalValueV1::Array(vec![text("expected")]), + )); + } + let bytes = + encode_canonical_cbor_v1(&map(fields.clone())).expect("canonical projection shape"); + generated + .schema() + .validate_root_bytes("echo-operation-application-result-projection", &bytes) + .expect("valid projection shape"); + } + for segment in [text(""), CanonicalValueV1::Integer(1)] { + let mut invalid_fields = fields.clone(); + let (_, path) = invalid_fields + .iter_mut() + .find(|(key, _)| *key == "application_input_expected_value_digest_path") + .expect("CAS digest path"); + *path = CanonicalValueV1::Array(vec![segment]); + let bytes = + encode_canonical_cbor_v1(&map(invalid_fields)).expect("canonical malformed projection"); + let error = generated + .schema() + .validate_root_bytes("echo-operation-application-result-projection", &bytes) + .expect_err("malformed digest-path segment refuses"); + assert_eq!( + error.kind(), + echo_wesley_gen::provider_artifacts::ProviderArtifactGenerationErrorKind::OwningRootRejected + ); + } +} diff --git a/crates/echo-wesley-gen/tests/provider_package.rs b/crates/echo-wesley-gen/tests/provider_package.rs index 30dfe86aa..623c38d23 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 = - "21e5267310b6b4c06bf24a814d76790cbd58a4a9dcc33205dddd6fc1937594dd"; + "e1de94ad00e3297688199a6f45d26ef86fe3a270529c006bbbd3d8bb6fc40738"; const OTHER_PACKAGE_ARTIFACT_SHA256: &str = "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"; diff --git a/crates/echo-wesley-gen/tests/provider_package_corpus.rs b/crates/echo-wesley-gen/tests/provider_package_corpus.rs index 1f64abbaa..e1a570539 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:21e5267310b6b4c06bf24a814d76790cbd58a4a9dcc33205dddd6fc1937594dd"; + "sha256:e1de94ad00e3297688199a6f45d26ef86fe3a270529c006bbbd3d8bb6fc40738"; const EXPECTED_MANIFEST_RAW_SHA256: &str = - "821c54b6d16f0a895769630fa6adf0401cef4578e5439cea79162d5e9c20b9b6"; + "0dfdef1d409103da02977bf51123dc29095812fddb3b8fb05358b33a52dff372"; const PACKAGE_PATHS: [&str; 25] = [ "components/lowerer.echo-dpo.component.wasm", diff --git a/crates/warp-core/src/echo_operation.rs b/crates/warp-core/src/echo_operation.rs index e145dcd58..20ea78007 100644 --- a/crates/warp-core/src/echo_operation.rs +++ b/crates/warp-core/src/echo_operation.rs @@ -50,6 +50,8 @@ use crate::{ const PACKAGE_SCHEMA: &str = "echo.operation-package/v1"; const PROGRAM_SCHEMA: &str = "echo.operation-program/v1"; const INVOCATION_SCHEMA: &str = "echo.operation-invocation/v1"; +const PROJECTED_CAS_INVOCATION_SCHEMA: &str = + "echo.operation-invocation.anchored-node-alpha-cas-projected/v1"; const PROGRAM_KIND: &str = "anchored-node-attachment-compare-and-set/v1"; const FOOTPRINT_CONTRACT: &str = "anchored-node-alpha-exact/v1"; const INPUT_SCHEMA: &str = "echo.operation.input.anchored-node-alpha-cas/v1"; @@ -1124,27 +1126,42 @@ pub struct EchoOperationApplicationResultProjectionV1 { max_output_bytes: u64, application_input_node_key_path: Vec, application_input_replacement_path: Vec, + application_input_expected_value_digest_path: Option>, runtime_expression: EchoOperationResultExpressionV1, } impl EchoOperationApplicationResultProjectionV1 { fn from_value(value: CanonicalValueV1) -> Result { - let mut fields = exact_text_map( - value, - &[ - "application_input_node_key_path", - "application_input_replacement_path", - "artifact_bytes", - "artifact_identity", - "runtime_expression", - ], - )?; + let has_expected_digest = matches!(&value, CanonicalValueV1::Map(entries) if entries.iter().any(|(key, _)| key == &text_value("application_input_expected_value_digest_path"))); + let mut names = vec![ + "application_input_node_key_path", + "application_input_replacement_path", + "artifact_bytes", + "artifact_identity", + "runtime_expression", + ]; + if has_expected_digest { + names.push("application_input_expected_value_digest_path"); + } + let mut fields = exact_text_map(value, &names)?; + let expected_digest_path = if has_expected_digest { + Some(take_projection_path( + &mut fields, + "application_input_expected_value_digest_path", + )?) + } else { + None + }; let artifact_bytes = take_bytes(&mut fields, "artifact_bytes")?; let artifact_identity = take_hash(&mut fields, "artifact_identity")?; let node_key_path = take_projection_path(&mut fields, "application_input_node_key_path")?; let replacement_path = take_projection_path(&mut fields, "application_input_replacement_path")?; - if node_key_path == replacement_path { + if node_key_path == replacement_path + || expected_digest_path + .as_ref() + .is_some_and(|path| path == &node_key_path || path == &replacement_path) + { return Err(invalid_structure( "result projection input bindings must be distinct", )); @@ -1169,12 +1186,13 @@ impl EchoOperationApplicationResultProjectionV1 { max_output_bytes, application_input_node_key_path: node_key_path, application_input_replacement_path: replacement_path, + application_input_expected_value_digest_path: expected_digest_path, runtime_expression, }) } fn to_value(&self) -> CanonicalValueV1 { - map_value([ + let mut fields = vec![ ( "application_input_node_key_path", projection_path_value(&self.application_input_node_key_path), @@ -1189,7 +1207,19 @@ impl EchoOperationApplicationResultProjectionV1 { ), ("artifact_identity", hash_value(self.artifact_identity)), ("runtime_expression", self.runtime_expression.to_value()), - ]) + ]; + if let Some(path) = &self.application_input_expected_value_digest_path { + fields.push(( + "application_input_expected_value_digest_path", + projection_path_value(path), + )); + } + CanonicalValueV1::Map( + fields + .into_iter() + .map(|(key, value)| (text_value(key), value)) + .collect(), + ) } /// Returns the exact compiler-owned projection identity. @@ -1209,6 +1239,7 @@ impl EchoOperationApplicationResultProjectionV1 { canonical_application_input_bytes: &[u8], node: NodeKey, replacement_bytes: &[u8], + kind: EchoOperationInvocationKindV1, ) -> Result<(), EchoOperationArtifactErrorV1> { let application_input = decode_canonical_cbor_v1(canonical_application_input_bytes).map_err(canonical_error)?; @@ -1245,7 +1276,35 @@ impl EchoOperationApplicationResultProjectionV1 { "application input replacement binding disagrees with invocation bytes", )); } - Ok(()) + self.validate_expected_digest_binding(&application_input, kind) + } + + fn validate_expected_digest_binding( + &self, + application_input: &CanonicalValueV1, + kind: EchoOperationInvocationKindV1, + ) -> Result<(), EchoOperationArtifactErrorV1> { + match (&self.application_input_expected_value_digest_path, kind) { + (None, EchoOperationInvocationKindV1::AnchoredNodeAttachmentCreateIfAbsent) => Ok(()), + ( + Some(path), + EchoOperationInvocationKindV1::AnchoredNodeAttachmentCompareAndSet { + expected_value_digest, + }, + ) => { + if value_at_projection_path(application_input, path)? + != &hash_value(expected_value_digest) + { + return Err(invalid_structure( + "application input expected digest differs from invocation precondition", + )); + } + Ok(()) + } + _ => Err(invalid_structure( + "expected digest binding does not match invocation program", + )), + } } fn evaluate( @@ -2080,6 +2139,21 @@ impl ExecutableOperationPackageV1 { "unsupported interpreter or intrinsic profile identity", )); } + if let Some(projection) = &self.application_result_projection { + let requires_digest = matches!( + self.program, + EchoOperationProgramV1::AnchoredNodeAttachmentCompareAndSet { .. } + ); + if projection + .application_input_expected_value_digest_path + .is_some() + != requires_digest + { + return Err(invalid_structure( + "expected digest binding does not match the package program", + )); + } + } if self .application_result_projection .as_ref() @@ -2939,8 +3013,14 @@ impl EchoOperationInvocationV1 { EchoOperationInvocationKindV1::AnchoredNodeAttachmentCompareAndSet { expected_value_digest, } => { - let mut fields = Vec::from(common(INVOCATION_SCHEMA)); + let schema = if self.application_input_bytes.is_some() { + PROJECTED_CAS_INVOCATION_SCHEMA + } else { + INVOCATION_SCHEMA + }; + let mut fields = Vec::from(common(schema)); fields.push(("expected_value_digest", hash_value(expected_value_digest))); + self.append_application_input(&mut fields)?; CanonicalValueV1::Map( fields .into_iter() @@ -2959,27 +3039,7 @@ impl EchoOperationInvocationV1 { "absence_precondition", text_value(CREATE_ABSENCE_PRECONDITION), )); - if let Some(application_input_bytes) = &self.application_input_bytes { - if application_input_bytes.len() > MAX_APPLICATION_INPUT_BYTES { - return Err(invalid_structure( - "application input exceeds the projected invocation byte ceiling", - )); - } - let application_input = decode_canonical_cbor_v1(application_input_bytes) - .map_err(canonical_error)?; - if encode_canonical_cbor_v1(&application_input).map_err(canonical_error)? - != *application_input_bytes - { - return Err(artifact_error( - EchoOperationArtifactErrorKindV1::NonCanonical, - "application input did not reproduce the exact invocation bytes", - )); - } - fields.push(( - "application_input_bytes", - CanonicalValueV1::Bytes(application_input_bytes.clone()), - )); - } + self.append_application_input(&mut fields)?; CanonicalValueV1::Map( fields .into_iter() @@ -2991,6 +3051,34 @@ impl EchoOperationInvocationV1 { encode_canonical_cbor_v1(&value).map_err(canonical_error) } + fn append_application_input( + &self, + fields: &mut Vec<(&str, CanonicalValueV1)>, + ) -> Result<(), EchoOperationArtifactErrorV1> { + if let Some(application_input_bytes) = &self.application_input_bytes { + if application_input_bytes.len() > MAX_APPLICATION_INPUT_BYTES { + return Err(invalid_structure( + "application input exceeds the projected invocation byte ceiling", + )); + } + let application_input = + decode_canonical_cbor_v1(application_input_bytes).map_err(canonical_error)?; + if encode_canonical_cbor_v1(&application_input).map_err(canonical_error)? + != *application_input_bytes + { + return Err(artifact_error( + EchoOperationArtifactErrorKindV1::NonCanonical, + "application input did not reproduce the exact invocation bytes", + )); + } + fields.push(( + "application_input_bytes", + CanonicalValueV1::Bytes(application_input_bytes.clone()), + )); + } + Ok(()) + } + pub(crate) fn from_canonical_bytes(bytes: &[u8]) -> Result { let value = decode_canonical_cbor_v1(bytes).map_err(canonical_error)?; let schema = match &value { @@ -3060,6 +3148,23 @@ impl EchoOperationInvocationV1 { true, false, ), + PROJECTED_CAS_INVOCATION_SCHEMA => ( + &[ + "application_input_bytes", + "authority_grant_identity", + "delegated_budget", + "evaluation_basis", + "expected_value_digest", + "node_id", + "operation_coordinate", + "package_id", + "replacement_bytes", + "schema", + "warp_id", + ][..], + false, + true, + ), PROJECTED_CREATE_INVOCATION_SCHEMA => ( &[ "absence_precondition", @@ -3083,8 +3188,10 @@ impl EchoOperationInvocationV1 { require_text( &mut fields, "schema", - if projected { + if projected && create_if_absent { PROJECTED_CREATE_INVOCATION_SCHEMA + } else if projected { + PROJECTED_CAS_INVOCATION_SCHEMA } else if create_if_absent { CREATE_INVOCATION_SCHEMA } else { @@ -3371,6 +3478,7 @@ fn admit_invocation_static_v1<'a>( application_input_bytes, invocation.node, &invocation.replacement_bytes, + invocation.kind, ) .map_err(|error| { invocation_admission_error( @@ -7558,6 +7666,180 @@ mod tests { ) } + #[test] + fn package_admission_rejects_projection_digest_binding_for_the_wrong_program() { + let (installed, _, _, _, _, _) = projected_create_fixture(1_024); + let create = + ExecutableOperationPackageV1::from_canonical_bytes(installed.canonical_package_bytes()) + .expect("valid create package"); + for cas in [false, true] { + let mut package = if cas { + ExecutableOperationPackageV1::new( + create.operation_coordinate.clone(), + create.obstruction_coordinate.clone(), + create.semantic_closure.clone(), + echo_operation_target_profile_identity_v1(), + create.authority_profile_identity, + create.budget_ceiling, + EchoOperationProgramV1::anchored_node_attachment_compare_and_set( + crate::make_type_id("projected-created-node"), + crate::make_type_id("projected-created-atom"), + 1_024, + ), + ) + } else { + create.clone() + }; + let mut projection = create + .application_result_projection + .clone() + .expect("projection"); + projection.application_input_expected_value_digest_path = + cas.then(|| vec!["expected".to_owned()]); + package.application_result_projection = Some(projection); + let valid = package.to_canonical_bytes().expect("valid package encodes"); + let policy_for = |bytes: &[u8]| { + EchoOperationAdmissionPolicyV1::exact( + echo_operation_package_id_v1(bytes), + package.operation_coordinate.clone(), + package.authority_profile_identity, + package.budget_ceiling, + ) + }; + admit_package_v1(&policy_for(&valid), valid.clone()) + .expect("matching projection admits"); + let CanonicalValueV1::Map(mut fields) = + decode_canonical_cbor_v1(&valid).expect("package") + else { + panic!("package map") + }; + let (_, CanonicalValueV1::Map(projection)) = fields + .iter_mut() + .find(|(key, _)| key == &text_value("application_result_projection")) + .expect("projection field") + else { + panic!("projection map") + }; + let key = text_value("application_input_expected_value_digest_path"); + if cas { + projection.retain(|(name, _)| name != &key); + } else { + projection.push((key, projection_path_value(&["expected".to_owned()]))); + } + let invalid = encode_canonical_cbor_v1(&CanonicalValueV1::Map(fields)) + .expect("invalid package encodes"); + let error = admit_package_v1(&policy_for(&invalid), invalid) + .expect_err("program-incompatible projection must refuse before installation"); + assert_eq!( + error.kind(), + EchoOperationAdmissionErrorKindV1::ArtifactInvalid + ); + assert_eq!( + error.artifact().expect("artifact failure").kind(), + EchoOperationArtifactErrorKindV1::InvalidStructure + ); + } + } + + #[test] + fn projected_cas_admission_requires_an_expected_digest_binding() { + let (mut installed, _, _, policy, mut invocation, _) = projected_create_fixture(1_024); + invocation.delegated_budget = EchoOperationBudgetV1::new(8, 1_024, 1_024); + installed.program = EchoOperationProgramV1::anchored_node_attachment_compare_and_set( + crate::make_type_id("projected-created-node"), + crate::make_type_id("projected-created-atom"), + 1_024, + ); + invocation.kind = EchoOperationInvocationKindV1::AnchoredNodeAttachmentCompareAndSet { + expected_value_digest: digest(212), + }; + let bytes = invocation.to_canonical_bytes().expect("CAS encodes"); + let error = admit_invocation_static_v1(Some(&installed), policy, &bytes) + .expect_err("projected CAS must bind its expected digest"); + assert_eq!( + error.kind(), + EchoOperationInvocationAdmissionErrorKindV1::ApplicationInputMismatch + ); + } + + #[test] + fn projected_cas_admission_rejects_substituted_expected_digest() { + let (mut installed, _, _, policy, mut invocation, _) = projected_create_fixture(1_024); + invocation.delegated_budget = EchoOperationBudgetV1::new(8, 1_024, 1_024); + installed.program = EchoOperationProgramV1::anchored_node_attachment_compare_and_set( + crate::make_type_id("projected-created-node"), + crate::make_type_id("projected-created-atom"), + 1_024, + ); + let mut projection_value = installed + .application_result_projection + .as_ref() + .expect("projection") + .to_value(); + let CanonicalValueV1::Map(ref mut fields) = projection_value else { + panic!("projection is a map"); + }; + fields.push(( + text_value("application_input_expected_value_digest_path"), + projection_path_value(&["expected".to_owned()]), + )); + installed.application_result_projection = Some( + EchoOperationApplicationResultProjectionV1::from_value(projection_value) + .expect("CAS digest projection decodes"), + ); + invocation.application_input_bytes = Some( + encode_canonical_cbor_v1(&map_value([ + ("key", text_value("fixture-key")), + ("message", text_value("fixture-message")), + ("expected", hash_value(digest(212))), + ])) + .expect("input encodes"), + ); + invocation.kind = EchoOperationInvocationKindV1::AnchoredNodeAttachmentCompareAndSet { + expected_value_digest: digest(212), + }; + admit_invocation_static_v1( + Some(&installed), + policy, + &invocation.to_canonical_bytes().expect("CAS encodes"), + ) + .expect("exact expected digest admits at the static boundary"); + invocation.kind = EchoOperationInvocationKindV1::AnchoredNodeAttachmentCompareAndSet { + expected_value_digest: digest(213), + }; + let error = admit_invocation_static_v1( + Some(&installed), + policy, + &invocation.to_canonical_bytes().expect("CAS encodes"), + ) + .expect_err("substituted expected digest refuses"); + assert_eq!( + error.kind(), + EchoOperationInvocationAdmissionErrorKindV1::ApplicationInputMismatch + ); + } + + #[test] + fn projected_cas_invocation_preserves_application_input_and_identity() { + let (_, _, _, _, mut invocation, application_input) = projected_create_fixture(1_024); + invocation.kind = EchoOperationInvocationKindV1::AnchoredNodeAttachmentCompareAndSet { + expected_value_digest: digest(212), + }; + let encoded = invocation + .to_canonical_bytes() + .expect("projected CAS encodes"); + let decoded = EchoOperationInvocationV1::from_canonical_bytes(&encoded) + .expect("projected CAS decodes"); + assert_eq!(decoded.application_input_bytes, Some(application_input)); + assert_eq!(decoded.kind, invocation.kind); + let projected_id = invocation.identity().expect("projected identity"); + invocation.application_input_bytes = None; + assert_ne!( + projected_id, + invocation.identity().expect("legacy identity") + ); + } + #[test] fn projected_invocation_rejects_missing_or_rebound_application_input() { let (installed, state, basis, policy, invocation, _) = projected_create_fixture(1_024); diff --git a/crates/warp-core/tests/edict_projected_cas_tests.rs b/crates/warp-core/tests/edict_projected_cas_tests.rs new file mode 100644 index 000000000..2ae50ff4f --- /dev/null +++ b/crates/warp-core/tests/edict_projected_cas_tests.rs @@ -0,0 +1,623 @@ +// SPDX-License-Identifier: Apache-2.0 +// © James Ross Ω FLYING•ROBOTS +//! Public scheduler and recovery witnesses for an actual Edict compiler output. +#![cfg(all(feature = "native_rule_bootstrap", feature = "trusted_runtime"))] +#![allow(clippy::expect_used, clippy::panic)] + +use bytes::Bytes; +use echo_edict_canonical::{ + decode_canonical_cbor_v1, encode_canonical_cbor_v1, CanonicalValueV1 as Value, +}; +use sha2::{Digest, Sha256}; +use std::{ + fs, + path::PathBuf, + sync::atomic::{AtomicU64, Ordering}, +}; +use warp_core::{ + echo_operation_action_envelope_v1, echo_operation_anchored_node_application_basis_v1, + echo_operation_atom_value_digest_v1, echo_operation_package_id_v1, make_head_id, make_node_id, + make_type_id, make_warp_id, AtomPayload, AttachmentValue, EchoOperationActionOutcomeV1, + EchoOperationAdmissionPolicyV1, EchoOperationBudgetV1, + EchoOperationInvocationAdmissionErrorKindV1, EchoOperationInvocationAdmissionPolicyV1, + EchoOperationInvocationV1, EchoOperationObstructionKindV1, EngineBuilder, GraphStore, + InboxPolicy, IngressTarget, NodeId, NodeKey, NodeRecord, PlaybackMode, SchedulerKind, + TrustedRuntimeHost, TrustedRuntimeWalConfig, TypeId, WorldlineId, WorldlineRuntime, + WorldlineState, WriterHead, WriterHeadKey, +}; + +const OPERATION: &str = "examples.cas_echo@1.updateCell"; +const KEY: &str = "cell"; +const INITIAL: &[u8] = b"initial"; +const GRANT: [u8; 32] = [73; 32]; +static TEMP_COUNTER: AtomicU64 = AtomicU64::new(0); + +struct WalDir(PathBuf); +impl WalDir { + fn new() -> Self { + for _ in 0..1024 { + let path = std::env::temp_dir().join(format!( + "edict-cas-{}-{}", + std::process::id(), + TEMP_COUNTER.fetch_add(1, Ordering::Relaxed) + )); + match fs::create_dir(&path) { + Ok(()) => return Self(path), + Err(error) if error.kind() == std::io::ErrorKind::AlreadyExists => {} + Err(error) => panic!("create owned WAL directory: {error}"), + } + } + panic!("unique WAL directory unavailable"); + } +} +impl Drop for WalDir { + fn drop(&mut self) { + fs::remove_dir_all(&self.0).expect("remove owned WAL evidence after test"); + } +} + +fn package() -> Vec { + let bytes = hex::decode(include_str!("../../echo-edict-provider-verifier/tests/fixtures/compiler-produced-cas/built/executable-operation-package.cbor.hex").trim()).expect("compiler output hex"); + assert_eq!( + hex::encode(Sha256::digest(&bytes)), + "4d671f8d9515c7d60c82e938103e59231eecab3c60782874f2d6fc2ff27c0d17" + ); + bytes +} +fn profile(label: &str) -> [u8; 32] { + let mut hash = blake3::Hasher::new(); + hash.update(b"echo:operation-profile:v1\0"); + hash.update(&(label.len() as u64).to_le_bytes()); + hash.update(label.as_bytes()); + hash.finalize().into() +} +fn attachment_type() -> TypeId { + TypeId(profile("cas.echo.attachment.greeting-message/v1")) +} +fn budget() -> EchoOperationBudgetV1 { + EchoOperationBudgetV1::new(16, 1024, 320) +} +fn policy() -> EchoOperationInvocationAdmissionPolicyV1 { + EchoOperationInvocationAdmissionPolicyV1::new( + profile("cas.echo.authority.local-demo/v1"), + GRANT, + budget(), + ) +} +struct Fixture { + host: TrustedRuntimeHost, + head: WriterHeadKey, + node: NodeKey, +} +impl Fixture { + fn new(present: bool) -> Self { + let warp_id = make_warp_id("edict-cas-public-witness"); + let root = make_node_id("edict-cas-public-witness-root"); + let node = NodeKey { + warp_id, + local_id: NodeId(Sha256::digest(KEY.as_bytes()).into()), + }; + let mut store = GraphStore::new(warp_id); + store.insert_node( + root, + NodeRecord { + ty: make_type_id("cas-witness-root"), + }, + ); + if present { + store.insert_node( + node.local_id, + NodeRecord { + ty: TypeId(profile("cas.echo.node.greeting/v1")), + }, + ); + store.set_node_attachment( + node.local_id, + Some(AttachmentValue::Atom(AtomPayload::new( + attachment_type(), + Bytes::from_static(INITIAL), + ))), + ); + } + let worldline_id = WorldlineId::from_bytes([67; 32]); + let head = WriterHeadKey { + worldline_id, + head_id: make_head_id("edict-cas-public-witness-head"), + }; + let mut runtime = WorldlineRuntime::new(); + runtime + .register_worldline( + worldline_id, + WorldlineState::from_root_store(store, root).expect("root state"), + ) + .expect("worldline"); + runtime + .register_writer_head(WriterHead::with_routing( + head, + PlaybackMode::Play, + InboxPolicy::AcceptAll, + None, + true, + )) + .expect("head"); + let mut engine_store = GraphStore::default(); + engine_store.insert_node( + root, + NodeRecord { + ty: make_type_id("cas-witness-engine-root"), + }, + ); + let engine = EngineBuilder::new(engine_store, root) + .scheduler(SchedulerKind::Radix) + .workers(1) + .build(); + Self { + host: TrustedRuntimeHost::new(runtime, engine).expect("host"), + head, + node, + } + } + fn install(&mut self) { + let bytes = package(); + let admitted = self + .host + .admit_echo_operation_package_v1( + &EchoOperationAdmissionPolicyV1::exact( + echo_operation_package_id_v1(&bytes), + OPERATION, + profile("cas.echo.authority.local-demo/v1"), + budget(), + ), + bytes, + ) + .expect("actual compiler package admitted"); + self.host + .install_admitted_echo_operation_package_v1(admitted) + .expect("install"); + } + fn enable_wal(&mut self, dir: &WalDir) { + self.host + .enable_runtime_wal(TrustedRuntimeWalConfig::filesystem(&dir.0)) + .expect("activate or recover WAL"); + } + fn value(&self) -> &[u8] { + let state = self + .host + .runtime() + .worldlines() + .get(&self.head.worldline_id) + .expect("worldline") + .state(); + let store = state.store(&self.node.warp_id).expect("store"); + assert_eq!( + store.node(&self.node.local_id).expect("target node").ty, + TypeId(profile("cas.echo.node.greeting/v1")) + ); + let Some(AttachmentValue::Atom(atom)) = store.node_attachment(&self.node.local_id) else { + panic!("typed target atom missing") + }; + assert_eq!(atom.type_id, attachment_type()); + &atom.bytes + } + fn invocation( + &self, + current: &[u8], + expected: [u8; 32], + input_expected: [u8; 32], + replacement: &str, + ) -> Vec { + let basis = self + .host + .echo_operation_evaluation_basis_v1( + self.head, + echo_operation_anchored_node_application_basis_v1( + self.node, + attachment_type(), + current, + ), + ) + .expect("current causal basis"); + let legacy = EchoOperationInvocationV1::anchored_node_attachment_compare_and_set( + echo_operation_package_id_v1(&package()), + OPERATION, + basis, + GRANT, + budget(), + self.node, + expected, + replacement.as_bytes().to_vec(), + ) + .to_canonical_bytes() + .expect("invocation"); + // Encode the public projected wire form as a client would. The package + // itself is the exact compiler output, with no test-side rewriting. + let Value::Map(mut fields) = + decode_canonical_cbor_v1(&legacy).expect("canonical invocation") + else { + panic!("invocation map") + }; + for (key, value) in &mut fields { + if key == &Value::Text("schema".into()) { + *value = Value::Text( + "echo.operation-invocation.anchored-node-alpha-cas-projected/v1".into(), + ); + } + } + let input = encode_canonical_cbor_v1(&map(&[ + ("basis", Value::Text("explicit".into())), + ("key", Value::Text(KEY.into())), + ("expected", Value::Bytes(input_expected.to_vec())), + ("message", Value::Text(replacement.into())), + ])) + .expect("input"); + fields.push(( + Value::Text("application_input_bytes".into()), + Value::Bytes(input), + )); + encode_canonical_cbor_v1(&Value::Map(fields)).expect("projected invocation") + } +} +fn map(fields: &[(&str, Value)]) -> Value { + Value::Map( + fields + .iter() + .map(|(key, value)| (Value::Text((*key).into()), value.clone())) + .collect(), + ) +} +fn initial_digest() -> [u8; 32] { + echo_operation_atom_value_digest_v1(attachment_type(), INITIAL) +} + +#[test] +fn compiler_cas_recovers_pending_action_committed_result_and_stale_obstruction() { + let dir = WalDir::new(); + let mut accepted = Fixture::new(true); + accepted.enable_wal(&dir); + accepted.install(); + let invocation = accepted.invocation(INITIAL, initial_digest(), initial_digest(), "updated"); + let envelope = echo_operation_action_envelope_v1( + IngressTarget::ExactHead { key: accepted.head }, + invocation, + ) + .expect("Action envelope"); + let submission = accepted + .host + .app() + .submit_intent_with_runtime_wal_ack(envelope) + .expect("durable Action acknowledgement") + .submission_id; + assert_eq!(accepted.value(), INITIAL); + let wal = accepted + .host + .runtime_wal() + .expect("WAL") + .recover_read_only() + .expect("read accepted WAL"); + assert!(wal + .witnessed_submissions + .records() + .iter() + .any(|record| record.submission.submission_id == submission)); + assert!(wal.provenance_entries.is_empty()); + assert!(wal.receipt_correlations.is_empty()); + drop(accepted); + + let mut pending = Fixture::new(true); + pending.enable_wal(&dir); + assert_eq!( + pending.host.runtime().pending_witnessed_submission_count(), + 1 + ); + assert!(pending + .host + .engine() + .installed_echo_operation_package_v1(echo_operation_package_id_v1(&package())) + .is_some()); + assert!(pending + .host + .echo_operation_action_outcome_v1(&submission) + .is_none()); + assert_eq!(pending.value(), INITIAL); + pending + .host + .install_echo_operation_action_admission_policy_v1(policy()); + let steps = pending + .host + .tick_once() + .expect("scheduler evaluates recovered CAS"); + assert_eq!(steps.len(), 1); + assert_eq!(steps[0].admitted_count, 1); + assert_eq!(pending.value(), b"updated"); + let Some(EchoOperationActionOutcomeV1::Committed(receipt)) = + pending.host.echo_operation_action_outcome_v1(&submission) + else { + panic!("CAS must commit") + }; + assert_eq!(receipt.worldline_tick_after().as_u64(), 1); + assert!(receipt.commit_global_tick().is_some()); + let result = receipt + .committed_application_result() + .expect("typed result") + .canonical_bytes() + .to_vec(); + assert_eq!( + decode_canonical_cbor_v1(&result).expect("result CBOR"), + decode_canonical_cbor_v1( + &encode_canonical_cbor_v1(&map(&[ + ("key", Value::Text(KEY.into())), + ("message", Value::Text("updated".into())) + ])) + .expect("expected result") + ) + .expect("expected CBOR") + ); + let receipt_digest = receipt.digest(); + let commit_id = receipt.commit_id(); + drop(pending); + + let mut recovered = Fixture::new(true); + recovered.enable_wal(&dir); + assert_eq!(recovered.value(), b"updated"); + assert_eq!( + recovered + .host + .runtime() + .pending_witnessed_submission_count(), + 0 + ); + let Some(EchoOperationActionOutcomeV1::Committed(receipt)) = + recovered.host.echo_operation_action_outcome_v1(&submission) + else { + panic!("committed outcome recovers") + }; + assert_eq!(receipt.digest(), receipt_digest); + assert_eq!(receipt.commit_id(), commit_id); + assert_eq!( + receipt + .committed_application_result() + .expect("recovered typed result") + .canonical_bytes(), + result + ); + // Keep the causal/application basis fresh, but the expected value stale. + // This must reach CAS evaluation rather than fail an unrelated basis check. + let stale = recovered.invocation( + b"updated", + initial_digest(), + initial_digest(), + "should not write", + ); + recovered + .host + .admit_echo_operation_invocation_v1(&policy(), &stale) + .expect("stale precondition passes static admission"); + recovered + .host + .install_echo_operation_action_admission_policy_v1(policy()); + let envelope = echo_operation_action_envelope_v1( + IngressTarget::ExactHead { + key: recovered.head, + }, + stale, + ) + .expect("stale envelope"); + let stale_submission = recovered + .host + .app() + .submit_intent_with_runtime_wal_ack(envelope) + .expect("accept stale Action") + .submission_id; + let steps = recovered.host.tick_once().expect("decide stale Action"); + assert_eq!(steps.len(), 1); + assert_eq!(steps[0].admitted_count, 1); + let Some(EchoOperationActionOutcomeV1::Obstructed(obstruction)) = recovered + .host + .echo_operation_action_outcome_v1(&stale_submission) + else { + panic!("stale CAS must obstruct") + }; + assert_eq!( + obstruction.kind(), + EchoOperationObstructionKindV1::PreconditionMismatch + ); + assert_eq!(recovered.value(), b"updated"); + drop(recovered); + let mut recovered_again = Fixture::new(true); + recovered_again.enable_wal(&dir); + assert_eq!(recovered_again.value(), b"updated"); + let Some(EchoOperationActionOutcomeV1::Obstructed(obstruction)) = recovered_again + .host + .echo_operation_action_outcome_v1(&stale_submission) + else { + panic!("obstruction recovers") + }; + assert_eq!( + obstruction.kind(), + EchoOperationObstructionKindV1::PreconditionMismatch + ); +} + +#[test] +fn compiler_cas_refuses_substituted_digest_and_missing_node_without_mutation() { + let mut fixture = Fixture::new(true); + fixture.install(); + let substituted = fixture.invocation(INITIAL, initial_digest(), [99; 32], "updated"); + assert_eq!( + fixture + .host + .admit_echo_operation_invocation_v1(&policy(), &substituted) + .expect_err("substituted digest refused") + .kind(), + EchoOperationInvocationAdmissionErrorKindV1::ApplicationInputMismatch + ); + assert_eq!(fixture.value(), INITIAL); + let wrong_basis = + fixture.invocation(b"different", initial_digest(), initial_digest(), "updated"); + assert_eq!( + fixture + .host + .admit_echo_operation_invocation_v1(&policy(), &wrong_basis) + .expect_err("uncorroborated application basis refused") + .kind(), + EchoOperationInvocationAdmissionErrorKindV1::BasisMismatch + ); + let valid = fixture.invocation(INITIAL, initial_digest(), initial_digest(), "updated"); + let other_grant = EchoOperationInvocationAdmissionPolicyV1::new( + profile("cas.echo.authority.local-demo/v1"), + [74; 32], + budget(), + ); + assert_eq!( + fixture + .host + .admit_echo_operation_invocation_v1(&other_grant, &valid) + .expect_err("ungranted authority refused") + .kind(), + EchoOperationInvocationAdmissionErrorKindV1::AuthorityGrantMismatch + ); + assert_eq!(fixture.value(), INITIAL); + let mut missing = Fixture::new(false); + missing.install(); + let invocation = missing.invocation(INITIAL, initial_digest(), initial_digest(), "updated"); + assert_eq!( + missing + .host + .admit_echo_operation_invocation_v1(&policy(), &invocation) + .expect_err("missing node refused") + .kind(), + EchoOperationInvocationAdmissionErrorKindV1::BasisMismatch + ); + assert!(missing + .host + .runtime() + .worldlines() + .get(&missing.head.worldline_id) + .expect("worldline") + .state() + .store(&missing.node.warp_id) + .expect("store") + .node(&missing.node.local_id) + .is_none()); +} + +#[test] +fn compiler_cas_obstructs_oversized_replacement_without_mutation() { + let mut fixture = Fixture::new(true); + fixture.install(); + fixture + .host + .install_echo_operation_action_admission_policy_v1(policy()); + let invocation = fixture.invocation( + INITIAL, + initial_digest(), + initial_digest(), + &"x".repeat(257), + ); + let envelope = echo_operation_action_envelope_v1( + IngressTarget::ExactHead { key: fixture.head }, + invocation, + ) + .expect("envelope"); + let submission = fixture + .host + .app() + .submit_intent(envelope) + .expect("accept Action") + .submission_id; + let steps = fixture + .host + .tick_once() + .expect("decide oversized replacement"); + assert_eq!(steps.len(), 1); + let Some(EchoOperationActionOutcomeV1::Obstructed(obstruction)) = + fixture.host.echo_operation_action_outcome_v1(&submission) + else { + panic!("oversized replacement obstructs") + }; + assert_eq!( + obstruction.kind(), + EchoOperationObstructionKindV1::ReplacementTooLarge + ); + assert_eq!(fixture.value(), INITIAL); +} + +#[test] +fn compiler_cas_accepts_replacement_at_exact_declared_bound() { + let mut fixture = Fixture::new(true); + fixture.install(); + fixture + .host + .install_echo_operation_action_admission_policy_v1(policy()); + let replacement = "x".repeat(256); + let invocation = fixture.invocation(INITIAL, initial_digest(), initial_digest(), &replacement); + let envelope = echo_operation_action_envelope_v1( + IngressTarget::ExactHead { key: fixture.head }, + invocation, + ) + .expect("bounded envelope"); + let submission = fixture + .host + .app() + .submit_intent(envelope) + .expect("accept boundary Action") + .submission_id; + let steps = fixture.host.tick_once().expect("execute boundary Action"); + assert_eq!(steps.len(), 1); + assert!(matches!( + fixture.host.echo_operation_action_outcome_v1(&submission), + Some(EchoOperationActionOutcomeV1::Committed(_)) + )); + assert_eq!(fixture.value(), replacement.as_bytes()); +} + +#[test] +fn compiler_cas_refuses_aliased_projection_before_installation() { + let fixture = Fixture::new(true); + let Value::Map(mut fields) = decode_canonical_cbor_v1(&package()).expect("compiler package") + else { + panic!("package map") + }; + let (_, Value::Map(projection)) = fields + .iter_mut() + .find(|(key, _)| key == &Value::Text("application_result_projection".into())) + .expect("projection") + else { + panic!("projection map") + }; + let (_, expected) = projection + .iter_mut() + .find(|(key, _)| key == &Value::Text("application_input_expected_value_digest_path".into())) + .expect("digest binding"); + *expected = Value::Array(vec![Value::Text("key".into())]); + let bytes = encode_canonical_cbor_v1(&Value::Map(fields)).expect("mutated package"); + let id = echo_operation_package_id_v1(&bytes); + // Pin the mutated identity so refusal proves structural validation, not + // merely a mismatch against the original package's hash. + let error = fixture + .host + .admit_echo_operation_package_v1( + &EchoOperationAdmissionPolicyV1::exact( + id, + OPERATION, + profile("cas.echo.authority.local-demo/v1"), + budget(), + ), + bytes, + ) + .expect_err("aliased projection refused"); + assert_eq!( + error.kind(), + warp_core::EchoOperationAdmissionErrorKindV1::ArtifactInvalid + ); + assert_eq!( + error.artifact().expect("artifact error").kind(), + warp_core::EchoOperationArtifactErrorKindV1::InvalidStructure + ); + assert!(fixture + .host + .engine() + .installed_echo_operation_package_v1(id) + .is_none()); + assert_eq!(fixture.value(), INITIAL); +} diff --git a/docs/architecture/application-contract-hosting.md b/docs/architecture/application-contract-hosting.md index 19473ff8f..734b9afaa 100644 --- a/docs/architecture/application-contract-hosting.md +++ b/docs/architecture/application-contract-hosting.md @@ -165,6 +165,48 @@ tests, but it is not the application lifecycle. The create-if-absent profile creates a node and its alpha attachment. It does not create a skeleton edge from the lane root. A detached cell therefore remains outside the reachable-state root hash. Equal roots do not prove equal stores or the absence of a detached write. The retained tick patch and commit identity bind that write; duplicate checks also compare the typed target-value digest. This preserves the [Merkle commit law](../spec/merkle-commit.md). +The native invocation codec has a distinct projected compare-and-set form that +retains canonical application input in the invocation identity. Static admission +requires an `application_input_expected_value_digest_path` in the installed +projection and exact equality between its 32-byte input value and the invocation +precondition. Create projections omit this path. Package admission checks this +program/path relationship before installation: a projected compare-and-set +package without the path, or a create package with it, returns `ArtifactInvalid` +with `InvalidStructure`. Legacy packages without a result projection remain +supported. Invocation admission also checks the relationship and exact digest. +Missing or substituted invocation bindings +return `ApplicationInputMismatch`; legacy invocation encodings remain unchanged. +Both mutation profiles require the effect argument to be the declared application +input directly. The lowerer and independent verifier each check the canonical +`arg.0` reference, its declared type and alpha name, and its unique local +declaration. They refuse transformed records and other local references with +`UnsupportedSemantics`; these bounded profiles do not evaluate arbitrary effect +arguments. This prevents a package from silently replacing an authored argument +with the caller's input fields. The compiler fixture retains the previously +accepted `message: "forced"` counterexample as historical failing evidence; the +corrected components refuse that source without publishing package artifacts, +while the direct-input control produces the same package and report bytes. + +The native lowerer and independent verifier select the CAS program from an exact +`continuum.profile.write/v1` effect with the CAS target intrinsic and `replace` +write class. Its configuration requires a distinct `expectedValueDigestField` +and at least four steps; the package binds the corresponding input path and +CAS-specific profile identities. The verifier reconstructs these bytes and +rejects a substituted digest path. Native refusal witnesses use synthetic semantic closures. The generated schema +and reproducible checked components also admit a real public Edict compiler build; +its exact source, executable package and accepted report are retained under +`crates/echo-edict-provider-verifier/tests/fixtures/compiler-produced-cas/`. +`crates/warp-core/tests/edict_projected_cas_tests.rs` exercises those exact package +bytes through durable Action intake, pending-Action recovery, scheduler-owned +update, and fresh-host recovery of the committed value and typed result. A fresh +causal basis with a stale expected digest produces `PreconditionMismatch`; that +noncommitted outcome also survives recovery without changing the target value. +Separate witnesses refuse substituted input digests, missing target nodes and +oversized replacements. The target is detached, so these checks inspect its typed +atom and receipt rather than treating an unchanged reachable-state root as proof +of no write. The public CLI runner still selects the create-if-absent profile; +this CAS lifecycle witness uses the trusted host and Action APIs directly. + The generic operation runner distinguishes missing Action outcomes, typed obstructions, and footprint conflicts in its error messages. Both the first Action and unexpected duplicate outcomes use bounded summaries and omit invocation data. Opted-in Rust backtraces remain separate diagnostic output. This diagnostic boundary does not change retained obstruction encoding. The external-provider schema additionally admits one exact zero-choice 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 555e4dcf6..43762f5e1 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 74fe228fd..db3a02611 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/generated/v1/evidence/provenance.provider-generation.json b/schemas/edict-provider/generated/v1/evidence/provenance.provider-generation.json index 2a01bb289..892a7fb8a 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: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 +{"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:16e8b1aa45f77bf2f69f538e7120cfb398d85525fdc6947a8cdd86d7b317ea2e"}],"generationInputDigest":"sha256:0c07274803d465ec212c313f74dd46396851f43019fa0f97da9571a167c84587","generator":{"coordinate":"echo-wesley-gen.provider-artifact-generator@1","digest":"sha256:2a49f1ba9ff4b83a9542aeb8a64bfc642b543e033aad61b855b7d429cf333e37","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 b960179af..1663cece1 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: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 +{"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:16e8b1aa45f77bf2f69f538e7120cfb398d85525fdc6947a8cdd86d7b317ea2e"}],"generationInputDigest":"sha256:0c07274803d465ec212c313f74dd46396851f43019fa0f97da9571a167c84587","generator":{"coordinate":"echo-wesley-gen.provider-artifact-generator@1","digest":"sha256:2a49f1ba9ff4b83a9542aeb8a64bfc642b543e033aad61b855b7d429cf333e37","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:e6158f9eae9e1a57f3b17b4b50aae47ab1c82de94fbc040e6fd3347193a55b3f","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/primary/schema.echo-provider-artifacts.cddl b/schemas/edict-provider/generated/v1/primary/schema.echo-provider-artifacts.cddl index a3f6f87fa..a510adbed 100644 --- a/schemas/edict-provider/generated/v1/primary/schema.echo-provider-artifacts.cddl +++ b/schemas/edict-provider/generated/v1/primary/schema.echo-provider-artifacts.cddl @@ -1226,6 +1226,7 @@ echo-compiler-produced-pure-budget = { } echo-operation-application-result-projection = { + ? "application_input_expected_value_digest_path": [* echo-nonempty-tstr], "application_input_node_key_path": [* echo-nonempty-tstr], "application_input_replacement_path": [* echo-nonempty-tstr], "artifact_bytes": bstr, @@ -1266,6 +1267,7 @@ echo-operation-semantic-closure = { echo-operation-lowering-configuration = echo-attachment-create-if-absent-lowering-configuration / + echo-attachment-compare-and-set-lowering-configuration / echo-compiler-produced-bounded-pure-lowering-configuration / echo-compiler-produced-bounded-read-lowering-configuration @@ -1301,6 +1303,27 @@ echo-attachment-create-if-absent-lowering-configuration = { requiredNodeTypeProfile: echo-nonempty-tstr, } +echo-attachment-compare-and-set-lowering-configuration = { + apiVersion: "echo.operation-lowering-configuration/v1", + authorityProfile: echo-nonempty-tstr, + budgetCeiling: { + readBytes: 64..18446744073709551615, + steps: 4..18446744073709551615, + writeBytes: 64..18446744073709551615, + }, + invocationBinding: { + expectedValueDigestField: echo-nonempty-tstr, + nodeIdDerivation: "sha256-utf8/v1", + nodeKeyField: echo-nonempty-tstr, + replacementField: echo-nonempty-tstr, + warpIdSource: "action-lane/v1", + }, + maxReplacementBytes: 1..18446744073709551615, + programKind: "anchored-node-attachment-compare-and-set/v1", + requiredAttachmentTypeProfile: echo-nonempty-tstr, + requiredNodeTypeProfile: echo-nonempty-tstr, +} + echo-operation-package-verifier-report = { apiVersion: "echo.operation-package-verifier-report/v1", applicationResultProjection: resource-ref, 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 555e4dcf6..43762f5e1 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 74fe228fd..db3a02611 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 2a01bb289..892a7fb8a 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: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 +{"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:16e8b1aa45f77bf2f69f538e7120cfb398d85525fdc6947a8cdd86d7b317ea2e"}],"generationInputDigest":"sha256:0c07274803d465ec212c313f74dd46396851f43019fa0f97da9571a167c84587","generator":{"coordinate":"echo-wesley-gen.provider-artifact-generator@1","digest":"sha256:2a49f1ba9ff4b83a9542aeb8a64bfc642b543e033aad61b855b7d429cf333e37","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 b960179af..1663cece1 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: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 +{"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:16e8b1aa45f77bf2f69f538e7120cfb398d85525fdc6947a8cdd86d7b317ea2e"}],"generationInputDigest":"sha256:0c07274803d465ec212c313f74dd46396851f43019fa0f97da9571a167c84587","generator":{"coordinate":"echo-wesley-gen.provider-artifact-generator@1","digest":"sha256:2a49f1ba9ff4b83a9542aeb8a64bfc642b543e033aad61b855b7d429cf333e37","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:e6158f9eae9e1a57f3b17b4b50aae47ab1c82de94fbc040e6fd3347193a55b3f","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/primary/schema.echo-provider-artifacts.cddl b/schemas/edict-provider/package/v1/generated/primary/schema.echo-provider-artifacts.cddl index a3f6f87fa..a510adbed 100644 --- a/schemas/edict-provider/package/v1/generated/primary/schema.echo-provider-artifacts.cddl +++ b/schemas/edict-provider/package/v1/generated/primary/schema.echo-provider-artifacts.cddl @@ -1226,6 +1226,7 @@ echo-compiler-produced-pure-budget = { } echo-operation-application-result-projection = { + ? "application_input_expected_value_digest_path": [* echo-nonempty-tstr], "application_input_node_key_path": [* echo-nonempty-tstr], "application_input_replacement_path": [* echo-nonempty-tstr], "artifact_bytes": bstr, @@ -1266,6 +1267,7 @@ echo-operation-semantic-closure = { echo-operation-lowering-configuration = echo-attachment-create-if-absent-lowering-configuration / + echo-attachment-compare-and-set-lowering-configuration / echo-compiler-produced-bounded-pure-lowering-configuration / echo-compiler-produced-bounded-read-lowering-configuration @@ -1301,6 +1303,27 @@ echo-attachment-create-if-absent-lowering-configuration = { requiredNodeTypeProfile: echo-nonempty-tstr, } +echo-attachment-compare-and-set-lowering-configuration = { + apiVersion: "echo.operation-lowering-configuration/v1", + authorityProfile: echo-nonempty-tstr, + budgetCeiling: { + readBytes: 64..18446744073709551615, + steps: 4..18446744073709551615, + writeBytes: 64..18446744073709551615, + }, + invocationBinding: { + expectedValueDigestField: echo-nonempty-tstr, + nodeIdDerivation: "sha256-utf8/v1", + nodeKeyField: echo-nonempty-tstr, + replacementField: echo-nonempty-tstr, + warpIdSource: "action-lane/v1", + }, + maxReplacementBytes: 1..18446744073709551615, + programKind: "anchored-node-attachment-compare-and-set/v1", + requiredAttachmentTypeProfile: echo-nonempty-tstr, + requiredNodeTypeProfile: echo-nonempty-tstr, +} + echo-operation-package-verifier-report = { apiVersion: "echo.operation-package-verifier-report/v1", applicationResultProjection: resource-ref, diff --git a/schemas/edict-provider/package/v1/provider-manifest.echo.json b/schemas/edict-provider/package/v1/provider-manifest.echo.json index ffda753cd..f1c694828 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:21e5267310b6b4c06bf24a814d76790cbd58a4a9dcc33205dddd6fc1937594dd" + "digest": "sha256:e1de94ad00e3297688199a6f45d26ef86fe3a270529c006bbbd3d8bb6fc40738" }, "artifacts": [ { @@ -21,7 +21,7 @@ }, "generator": { "coordinate": "echo-wesley-gen.provider-artifact-generator@1", - "digest": "sha256:cb9c6569a8f3c0ed3a0bd500151a3673f7f03b6f9d06d3b7edf227760fa895df" + "digest": "sha256:2a49f1ba9ff4b83a9542aeb8a64bfc642b543e033aad61b855b7d429cf333e37" } } }, @@ -40,7 +40,7 @@ }, "generator": { "coordinate": "echo-wesley-gen.provider-artifact-generator@1", - "digest": "sha256:cb9c6569a8f3c0ed3a0bd500151a3673f7f03b6f9d06d3b7edf227760fa895df" + "digest": "sha256:2a49f1ba9ff4b83a9542aeb8a64bfc642b543e033aad61b855b7d429cf333e37" } } }, @@ -59,7 +59,7 @@ }, "generator": { "coordinate": "echo-wesley-gen.provider-artifact-generator@1", - "digest": "sha256:cb9c6569a8f3c0ed3a0bd500151a3673f7f03b6f9d06d3b7edf227760fa895df" + "digest": "sha256:2a49f1ba9ff4b83a9542aeb8a64bfc642b543e033aad61b855b7d429cf333e37" } } }, @@ -78,7 +78,7 @@ }, "generator": { "coordinate": "echo-wesley-gen.provider-artifact-generator@1", - "digest": "sha256:cb9c6569a8f3c0ed3a0bd500151a3673f7f03b6f9d06d3b7edf227760fa895df" + "digest": "sha256:2a49f1ba9ff4b83a9542aeb8a64bfc642b543e033aad61b855b7d429cf333e37" } } }, @@ -87,13 +87,13 @@ "artifactKind": "lowerer", "resource": { "coordinate": "echo.dpo.lowerer/component@1", - "digest": "sha256:dc3edc7c4f194bd2fdd2b5a1ecc79f8fb7a24a5a31294a1f1fc30369c053b8d4" + "digest": "sha256:80e426cc277a4c5e78eab374b68144c8f3e34b6afb93eb991789ce2e5ee7b9a4" }, "source": { "kind": "component", "component": { "coordinate": "echo.dpo.lowerer/component@1", - "digest": "sha256:dc3edc7c4f194bd2fdd2b5a1ecc79f8fb7a24a5a31294a1f1fc30369c053b8d4" + "digest": "sha256:80e426cc277a4c5e78eab374b68144c8f3e34b6afb93eb991789ce2e5ee7b9a4" } } }, @@ -102,7 +102,7 @@ "artifactKind": "generationProvenance", "resource": { "coordinate": "echo.edict-provider-generation-provenance@1", - "digest": "sha256:2b27a04863c3660e8ce716cabfd63fd047d3f75a884e28d4222de5564c254142" + "digest": "sha256:764d213f3eff1dd9bf922c7c4d0a75334bf1fd11f3080139493f796058c79762" }, "source": { "kind": "generated", @@ -112,7 +112,7 @@ }, "generator": { "coordinate": "echo-wesley-gen.provider-artifact-generator@1", - "digest": "sha256:cb9c6569a8f3c0ed3a0bd500151a3673f7f03b6f9d06d3b7edf227760fa895df" + "digest": "sha256:2a49f1ba9ff4b83a9542aeb8a64bfc642b543e033aad61b855b7d429cf333e37" } } }, @@ -121,7 +121,7 @@ "artifactKind": "reviewArtifact", "resource": { "coordinate": "echo.edict-provider-generation-review@1", - "digest": "sha256:8000857ec83afd059b0747376c01699b8c97052c84ff6a69d3e7eed407910a8a" + "digest": "sha256:cc3d858a150c0b56424d36ef8efad040106edb6af1ac05a20aa619542e6dbc94" }, "source": { "kind": "generated", @@ -131,7 +131,7 @@ }, "generator": { "coordinate": "echo-wesley-gen.provider-artifact-generator@1", - "digest": "sha256:cb9c6569a8f3c0ed3a0bd500151a3673f7f03b6f9d06d3b7edf227760fa895df" + "digest": "sha256:2a49f1ba9ff4b83a9542aeb8a64bfc642b543e033aad61b855b7d429cf333e37" } } }, @@ -140,7 +140,7 @@ "artifactKind": "artifactSchema", "resource": { "coordinate": "echo.provider-artifacts.cddl@1", - "digest": "sha256:9078b84c47f2e5b722f8fea56cd0bca585cfd150c8114c22994b67cf19f6d9ab" + "digest": "sha256:16e8b1aa45f77bf2f69f538e7120cfb398d85525fdc6947a8cdd86d7b317ea2e" }, "source": { "kind": "generated", @@ -150,7 +150,7 @@ }, "generator": { "coordinate": "echo-wesley-gen.provider-artifact-generator@1", - "digest": "sha256:cb9c6569a8f3c0ed3a0bd500151a3673f7f03b6f9d06d3b7edf227760fa895df" + "digest": "sha256:2a49f1ba9ff4b83a9542aeb8a64bfc642b543e033aad61b855b7d429cf333e37" } } }, @@ -169,7 +169,7 @@ }, "generator": { "coordinate": "echo-wesley-gen.provider-artifact-generator@1", - "digest": "sha256:cb9c6569a8f3c0ed3a0bd500151a3673f7f03b6f9d06d3b7edf227760fa895df" + "digest": "sha256:2a49f1ba9ff4b83a9542aeb8a64bfc642b543e033aad61b855b7d429cf333e37" } } }, @@ -178,13 +178,13 @@ "artifactKind": "verifier", "resource": { "coordinate": "echo.dpo.verifier/component@1", - "digest": "sha256:23d1d256ec66138697f93babcd2be57a4f56de1eb2d1fb6910e5aef881c4dd0e" + "digest": "sha256:87649ace3aa1ab21c45cc4ed5c90051583dd08b48518cdca61dacde72783440a" }, "source": { "kind": "component", "component": { "coordinate": "echo.dpo.verifier/component@1", - "digest": "sha256:23d1d256ec66138697f93babcd2be57a4f56de1eb2d1fb6910e5aef881c4dd0e" + "digest": "sha256:87649ace3aa1ab21c45cc4ed5c90051583dd08b48518cdca61dacde72783440a" } } } diff --git a/xtask/src/provider_lowerer_component.rs b/xtask/src/provider_lowerer_component.rs index 86c4e15c3..69b8cb891 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 = - "dc3edc7c4f194bd2fdd2b5a1ecc79f8fb7a24a5a31294a1f1fc30369c053b8d4"; + "80e426cc277a4c5e78eab374b68144c8f3e34b6afb93eb991789ce2e5ee7b9a4"; 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 = - "23d1d256ec66138697f93babcd2be57a4f56de1eb2d1fb6910e5aef881c4dd0e"; + "87649ace3aa1ab21c45cc4ed5c90051583dd08b48518cdca61dacde72783440a"; #[derive(Clone, Copy, Debug, Eq, PartialEq)] struct ProviderComponentSpec {