From f220b77e4cbba1a572df83f3ca85c0bd962cd1b9 Mon Sep 17 00:00:00 2001 From: James Ross Date: Wed, 29 Jul 2026 15:38:26 -0700 Subject: [PATCH 01/11] test: define typed external action requests --- .../tests/external_action_requests.rs | 436 ++++++++++++++++++ .../external-action-requests/test-plan.md | 75 +++ 2 files changed, 511 insertions(+) create mode 100644 crates/edict-syntax/tests/external_action_requests.rs create mode 100644 docs/topics/external-action-requests/test-plan.md diff --git a/crates/edict-syntax/tests/external_action_requests.rs b/crates/edict-syntax/tests/external_action_requests.rs new file mode 100644 index 0000000..1cbbd9e --- /dev/null +++ b/crates/edict-syntax/tests/external_action_requests.rs @@ -0,0 +1,436 @@ +//! RED contract for typed external-action request values. +//! +//! The tests use only public compiler surfaces so the first failure is the +//! absence of request syntax/semantics, not a test compile error. + +use std::collections::BTreeSet; + +use edict_syntax::{ + compile_to_core, decode_canonical_cbor, digest_core_module, encode_core_module, + encode_target_ir_artifact, lower_to_target_ir, parse_module, CanonicalValue, CompilerContext, + CompilerErrorKind, CoreBudget, ResourceRef, TargetIrLoweringFacts, TargetLoweringStatus, + ECHO_DPO_TARGET_PROFILE, ECHO_SPAN_IR_DOMAIN, +}; + +const OPERATION_DIGEST: char = 'a'; +const INPUT_SCHEMA_DIGEST: char = 'b'; +const SETTLEMENT_SCHEMA_DIGEST: char = 'c'; +const RECONCILIATION_DIGEST: char = 'd'; +const TARGET_PROFILE_DIGEST: char = 'e'; +const PROPERTY_SEED: u64 = 0x4558_5452_4551_0001; + +fn digest(hex: char) -> String { + format!("sha256:{}", hex.to_string().repeat(64)) +} + +fn context() -> CompilerContext { + CompilerContext::new() + .with_operation_profile("workspace.read", "continuum.profile.read-only/v1") + .with_budget( + "workspace.tiny", + CoreBudget { + max_steps: 512, + max_allocated_bytes: 256 * 1024, + max_output_bytes: 128 * 1024, + }, + ) +} + +fn target_facts() -> TargetIrLoweringFacts { + TargetIrLoweringFacts { + target_profile: ResourceRef { + coordinate: ECHO_DPO_TARGET_PROFILE.to_owned(), + digest: Some(digest(TARGET_PROFILE_DIGEST)), + }, + target_ir_domain: ECHO_SPAN_IR_DOMAIN.to_owned(), + operation_profiles: vec!["continuum.profile.read-only/v1".to_owned()], + obstruction_coordinates: Vec::new(), + effect_lowerings: Vec::new(), + } +} + +fn request_source( + capability_coordinate: &str, + operation_alias: &str, + operation_digest: &str, + input_schema_digest: &str, + settlement_schema_digest: &str, + input_expr: &str, + authority_expr: &str, + basis_expr: &str, + max_settlement_bytes: &str, + max_attempts: &str, + reconciliation_digest: &str, +) -> String { + format!( + r#"package examples.workspace_observer@1; + +use capability {capability_coordinate} digest "{operation_digest}" as snapshot; + +type ObserveInput = {{ + payload: Bytes, + alternatePayload: Bytes, + scope: Bytes, + alternateScope: Bytes, + basis: Bytes, + alternateBasis: Bytes, + maxSettlementBytes: U64, + alternateMaxSettlementBytes: U64, + maxAttempts: U32, +}}; + +intent observe(input: ObserveInput) returns ExternalActionRequest> + profile workspace.read + basis input.basis + budget <= workspace.tiny +{{ + request pending: ExternalActionRequest> = + {operation_alias}({input_expr}) + input schema workspace.snapshot.input@1 digest "{input_schema_digest}" + settlement schema workspace.snapshot.settlement@1 digest "{settlement_schema_digest}" + authority {authority_expr} + basis {basis_expr} + budget maxSettlementBytes {max_settlement_bytes} maxAttempts {max_attempts} + reconcile workspace.snapshot.reconcile@1 digest "{reconciliation_digest}"; + return pending; +}} +"# + ) +} + +fn baseline_source() -> String { + request_source( + "workspace.snapshot.observe@1", + "snapshot", + &digest(OPERATION_DIGEST), + &digest(INPUT_SCHEMA_DIGEST), + &digest(SETTLEMENT_SCHEMA_DIGEST), + "input.payload", + "input.scope", + "input.basis", + "input.maxSettlementBytes", + "input.maxAttempts", + &digest(RECONCILIATION_DIGEST), + ) +} + +fn compile_source(source: &str) -> edict_syntax::CoreModule { + let module = parse_module(source).expect("external-action source parses"); + compile_to_core(&module, &context()).expect("external-action source compiles") +} + +fn lower_source(source: &str) -> (edict_syntax::CoreModule, edict_syntax::TargetIrArtifact) { + let core = compile_source(source); + let report = lower_to_target_ir(&core, &target_facts()); + assert_eq!(report.status, TargetLoweringStatus::Lowered); + assert_eq!(report.failures, Vec::new()); + (core, report.artifact.expect("external-action Target IR")) +} + +fn map_field<'a>(value: &'a CanonicalValue, field: &str) -> &'a CanonicalValue { + let CanonicalValue::Map(entries) = value else { + panic!("expected map while looking up {field:?}, got {value:?}"); + }; + entries + .iter() + .find_map(|(key, value)| match key { + CanonicalValue::Text(key) if key == field => Some(value), + _ => None, + }) + .unwrap_or_else(|| panic!("missing canonical field {field:?}")) +} + +fn text_field<'a>(value: &'a CanonicalValue, field: &str) -> &'a str { + let CanonicalValue::Text(value) = map_field(value, field) else { + panic!("canonical field {field:?} is not text"); + }; + value +} + +fn array_field<'a>(value: &'a CanonicalValue, field: &str) -> &'a [CanonicalValue] { + let CanonicalValue::Array(values) = map_field(value, field) else { + panic!("canonical field {field:?} is not an array"); + }; + values +} + +fn core_request_node(core: &edict_syntax::CoreModule) -> CanonicalValue { + let encoded = encode_core_module(core).expect("Core request encodes"); + let value = decode_canonical_cbor(&encoded).expect("Core request decodes"); + let intent = map_field(map_field(&value, "intents"), "observe"); + array_field(map_field(intent, "body"), "nodes")[0].clone() +} + +fn target_request_values(target: &edict_syntax::TargetIrArtifact) -> Vec { + let encoded = encode_target_ir_artifact(target).expect("Target IR request encodes"); + let value = decode_canonical_cbor(&encoded).expect("Target IR request decodes"); + let intent = map_field(map_field(&value, "intents"), "observe"); + array_field(intent, "externalActionRequests").to_vec() +} + +#[test] +fn workspace_observation_request_compiles_as_non_callable_data() { + let (core, target) = lower_source(&baseline_source()); + let core_value = decode_canonical_cbor(&encode_core_module(&core).expect("Core encodes")) + .expect("Core CBOR"); + let imports = array_field(&core_value, "imports"); + assert_eq!(imports.len(), 1); + assert_eq!(text_field(&imports[0], "kind"), "capability"); + assert_eq!( + text_field(map_field(&imports[0], "ref"), "id"), + "workspace.snapshot.observe@1" + ); + + let request = core_request_node(&core); + assert_eq!(text_field(&request, "kind"), "externalActionRequest"); + assert_eq!(text_field(&request, "inputType"), "Bytes"); + assert_eq!(text_field(&request, "settlementType"), "Bytes"); + assert_eq!(text_field(&request, "state"), "awaitingSettlement"); + assert_eq!( + text_field(&request, "settlementAdmission"), + "schemaRequired" + ); + + let target_bytes = encode_target_ir_artifact(&target).expect("Target IR encodes"); + let target_value = decode_canonical_cbor(&target_bytes).expect("Target IR CBOR"); + let intent = map_field(map_field(&target_value, "intents"), "observe"); + assert_eq!(array_field(intent, "steps"), []); + let requests = array_field(intent, "externalActionRequests"); + assert_eq!(requests.len(), 1); + assert_eq!( + text_field(&requests[0], "settlementAdmission"), + "schemaRequired" + ); + assert!(!target_bytes + .windows("targetIntrinsic".len()) + .any(|window| window == b"targetIntrinsic")); +} + +#[test] +fn undeclared_or_floating_operation_families_fail_closed() { + let undeclared = baseline_source().replace("snapshot(input.payload)", "missing(input.payload)"); + let module = parse_module(&undeclared).expect("undeclared operation source parses"); + let errors = compile_to_core(&module, &context()).expect_err("undeclared operation rejects"); + assert_eq!(errors[0].kind, CompilerErrorKind::MissingContextFact); + + let floating = + baseline_source().replace(&format!(" digest \"{}\"", digest(OPERATION_DIGEST)), ""); + let module = parse_module(&floating).expect("floating capability source parses"); + let core = compile_to_core(&module, &context()).expect("floating source reaches Core"); + assert_eq!( + encode_core_module(&core) + .expect_err("floating operation cannot become canonical") + .kind(), + edict_syntax::CanonicalErrorKind::UnresolvedDigest + ); +} + +#[test] +fn capability_import_cannot_be_called_as_an_effect() { + let source = format!( + r#"package examples.direct_call@1; +use capability workspace.snapshot.observe@1 digest "{}" as snapshot; +type Input = {{ payload: Bytes, }}; +intent observe(input: Input) returns Bytes + profile workspace.read + basis none + budget <= workspace.tiny {{ + let output: Bytes = snapshot(input.payload) + else {{ rejected(reason) => workspace.Rejected }}; + return output; +}} +"#, + digest(OPERATION_DIGEST) + ); + let module = parse_module(&source).expect("direct call source parses"); + let errors = compile_to_core(&module, &context()).expect_err("direct call rejects"); + assert_eq!(errors[0].kind, CompilerErrorKind::MissingContextFact); +} + +#[test] +fn ambient_operation_families_are_not_requestable() { + for coordinate in [ + "filesystem.read@1", + "process.spawn@1", + "network.fetch@1", + "git.push@1", + "github.open_pull_request@1", + "model.invoke@1", + "shell.command@1", + ] { + let source = request_source( + coordinate, + "snapshot", + &digest(OPERATION_DIGEST), + &digest(INPUT_SCHEMA_DIGEST), + &digest(SETTLEMENT_SCHEMA_DIGEST), + "input.payload", + "input.scope", + "input.basis", + "input.maxSettlementBytes", + "input.maxAttempts", + &digest(RECONCILIATION_DIGEST), + ); + let module = parse_module(&source).expect("ambient-family source parses structurally"); + let errors = + compile_to_core(&module, &context()).expect_err("ambient operation family rejects"); + assert_eq!( + errors[0].kind, + CompilerErrorKind::UnsupportedSourceShape, + "{coordinate}" + ); + } +} + +#[test] +fn dynamic_admission_values_survive_without_compile_time_execution() { + let core = compile_source(&baseline_source()); + let request = core_request_node(&core); + assert_eq!( + text_field(map_field(&request, "authorityScope"), "field"), + "scope" + ); + assert_eq!(text_field(map_field(&request, "basis"), "field"), "basis"); + let budget = map_field(&request, "budget"); + assert_eq!( + text_field(map_field(budget, "maxSettlementBytes"), "field"), + "maxSettlementBytes" + ); + assert_eq!( + text_field(map_field(budget, "maxAttempts"), "field"), + "maxAttempts" + ); +} + +#[test] +fn request_artifacts_are_reproducible() { + let source = baseline_source(); + let (left_core, left_target) = lower_source(&source); + let (right_core, right_target) = lower_source(&source); + assert_eq!( + encode_core_module(&left_core).expect("left Core"), + encode_core_module(&right_core).expect("right Core") + ); + assert_eq!( + encode_target_ir_artifact(&left_target).expect("left Target IR"), + encode_target_ir_artifact(&right_target).expect("right Target IR") + ); +} + +#[test] +fn every_request_authority_field_moves_core_identity() { + let baseline = baseline_source(); + let baseline_digest = digest_core_module(&compile_source(&baseline)).expect("baseline digest"); + let mutations = [ + baseline.replace(&digest(OPERATION_DIGEST), &digest('1')), + baseline.replace(&digest(INPUT_SCHEMA_DIGEST), &digest('2')), + baseline.replace(&digest(SETTLEMENT_SCHEMA_DIGEST), &digest('3')), + baseline.replace("authority input.scope", "authority input.alternateScope"), + baseline.replace("basis input.basis", "basis input.alternateBasis"), + baseline.replace( + "maxSettlementBytes input.maxSettlementBytes", + "maxSettlementBytes input.alternateMaxSettlementBytes", + ), + baseline.replace( + "snapshot(input.payload)", + "snapshot(input.alternatePayload)", + ), + baseline.replace(&digest(RECONCILIATION_DIGEST), &digest('4')), + ]; + for mutation in mutations { + assert_ne!( + digest_core_module(&compile_source(&mutation)).expect("mutated digest"), + baseline_digest + ); + } +} + +#[test] +fn fixed_seed_request_identity_corpus_is_deterministic() { + let mut state = PROPERTY_SEED; + let mut observed = BTreeSet::new(); + for _ in 0..32 { + state = state + .wrapping_mul(6_364_136_223_846_793_005) + .wrapping_add(1_442_695_040_888_963_407); + let mut hex = format!("{state:016x}"); + hex = hex.repeat(4); + let operation_digest = format!("sha256:{hex}"); + let source = request_source( + "workspace.snapshot.observe@1", + "snapshot", + &operation_digest, + &digest(INPUT_SCHEMA_DIGEST), + &digest(SETTLEMENT_SCHEMA_DIGEST), + "input.payload", + "input.scope", + "input.basis", + "input.maxSettlementBytes", + "input.maxAttempts", + &digest(RECONCILIATION_DIGEST), + ); + let first = digest_core_module(&compile_source(&source)).expect("first property digest"); + let second = digest_core_module(&compile_source(&source)).expect("second property digest"); + assert_eq!(first, second); + assert!(observed.insert(first)); + } + assert_eq!(observed.len(), 32); +} + +#[test] +fn sixty_four_requests_remain_bounded_non_callable_data() { + let mut requests = String::new(); + for index in 0..64 { + requests.push_str(&format!( + r#" request pending{index}: ExternalActionRequest> = + snapshot(input.payload) + input schema workspace.snapshot.input@1 digest "{}" + settlement schema workspace.snapshot.settlement@1 digest "{}" + authority input.scope + basis input.basis + budget maxSettlementBytes input.maxSettlementBytes maxAttempts input.maxAttempts + reconcile workspace.snapshot.reconcile@1 digest "{}"; +"#, + digest(INPUT_SCHEMA_DIGEST), + digest(SETTLEMENT_SCHEMA_DIGEST), + digest(RECONCILIATION_DIGEST), + )); + } + let source = format!( + r#"package examples.workspace_stress@1; +use capability workspace.snapshot.observe@1 digest "{}" as snapshot; +type ObserveInput = {{ + payload: Bytes, + scope: Bytes, + basis: Bytes, + maxSettlementBytes: U64, + maxAttempts: U32, +}}; +intent observe(input: ObserveInput) returns ExternalActionRequest> + profile workspace.read + basis input.basis + budget <= workspace.tiny +{{ +{requests} return pending63; +}} +"#, + digest(OPERATION_DIGEST), + ); + let (core, target) = lower_source(&source); + let core_value = + decode_canonical_cbor(&encode_core_module(&core).expect("stress Core encodes")) + .expect("stress Core decodes"); + let core_intent = map_field(map_field(&core_value, "intents"), "observe"); + assert_eq!( + array_field(map_field(core_intent, "body"), "nodes").len(), + 64 + ); + assert_eq!(target_request_values(&target).len(), 64); + + let target_value = decode_canonical_cbor( + &encode_target_ir_artifact(&target).expect("stress Target IR encodes"), + ) + .expect("stress Target IR decodes"); + let target_intent = map_field(map_field(&target_value, "intents"), "observe"); + assert_eq!(array_field(target_intent, "steps"), []); +} diff --git a/docs/topics/external-action-requests/test-plan.md b/docs/topics/external-action-requests/test-plan.md new file mode 100644 index 0000000..443aaf2 --- /dev/null +++ b/docs/topics/external-action-requests/test-plan.md @@ -0,0 +1,75 @@ +# External-Action Requests Test Plan + +Status: planned contract for issue #172. + +## Scope + +In scope: + +- digest-locked `capability` imports that name requestable external operation + families; +- source-authored external-action request declarations; +- deterministic Core and Target IR representation of request data; +- package-closure binding for operation, schema, and reconciliation resources; +- explicit awaiting-settlement and schema-admission posture; +- compile-time denial of undeclared or directly callable operation families; +- omission of filesystem, process, network, Git, GitHub, model, and shell + authority from compiler and provider interfaces. + +Out of scope: + +- performing external actions; +- adapter selection or authorization; +- live path, ref, basis, budget, or settlement admission; +- stack suspension, continuations, or `async` host frames; +- general-purpose effects or ambient I/O. + +## Requirements + +| ID | Status | Requirement | Source | +| --- | --- | --- | --- | +| EXTREQ-REQ-001 | planned | Source syntax binds each requestable operation through one digest-locked `capability` import and carries exact input-schema, settlement-schema, authority-scope, basis, budget, input, and reconciliation-law values. | issue #172 | +| EXTREQ-REQ-002 | planned | Core represents external-action requests as data with compiler-owned binding identity, typed input and settlement coordinates, explicit `awaitingSettlement` state, and required schema admission. | issue #172 | +| EXTREQ-REQ-003 | planned | Target IR preserves external-action requests separately from callable target steps and target intrinsics. | issue #172 | +| EXTREQ-REQ-004 | planned | Core and Target IR semantic closure bind the exact capability resource; undeclared operations and floating capability imports fail closed. | issue #172 | +| EXTREQ-REQ-005 | planned | Equivalent admitted source and compiler facts produce byte-identical Core and Target IR; every request authority or meaning mutation moves the corresponding digest. | issue #172 | +| EXTREQ-REQ-006 | planned | Runtime-valued authority scope, basis, and budget expressions survive compilation for Echo admission; Edict performs no external action while compiling or lowering them. | issue #172 | +| EXTREQ-REQ-007 | planned | Raw filesystem, process, network, Git, GitHub, model, and shell operation families are outside the requestable capability vocabulary. | issue #172 | +| EXTREQ-REQ-008 | planned | Request construction remains bounded under a fixed-seed mutation corpus and a 64-request stress module. | issue #172 | + +## Fixtures + +| Fixture | Purpose | Oracle | +| --- | --- | --- | +| In-test `workspace.snapshot.observe@1` source | First bounded read-only external request. | Public parser, compiler, canonical encoders, and Target IR lowerer preserve the exact request contract. | +| Fixed seed `0x4558_5452_4551_0001` | Determinism and mutation corpus. | Repeated compilation is byte-identical and distinct capability identities produce distinct Core identities. | +| 64-request generated module | Bounded stress case. | All 64 requests survive Core and Target IR without becoming callable steps. | + +## Cases + +| ID | Status | Category | Requirement | Oracle | Evidence | Notes | +| --- | --- | --- | --- | --- | --- | --- | +| EXTREQ-TP-001 | planned | Golden path | EXTREQ-REQ-001, EXTREQ-REQ-002, EXTREQ-REQ-003 | One workspace observation request parses, compiles, and lowers with exact structured fields, awaiting posture, and zero callable target steps. | `workspace_observation_request_compiles_as_non_callable_data` | The target owns later admission and execution. | +| EXTREQ-TP-002 | planned | Closure guard | EXTREQ-REQ-004 | Missing operation import, floating capability digest, and operation-alias substitution reject with stable compiler or canonical error kinds. | `undeclared_or_floating_operation_families_fail_closed` | A source alias is not authority by itself. | +| EXTREQ-TP-003 | planned | Authority guard | EXTREQ-REQ-003, EXTREQ-REQ-007 | Direct invocation of a capability import fails, and raw ambient operation families are unrepresentable. | `capability_import_cannot_be_called_as_an_effect`, `ambient_operation_families_are_not_requestable` | Request authority and performance authority remain distinct. | +| EXTREQ-TP-004 | planned | Dynamic boundary | EXTREQ-REQ-006 | Scope, basis, maximum settlement bytes, and attempt count remain Core expressions sourced from admitted input. | `dynamic_admission_values_survive_without_compile_time_execution` | Echo owns value-instance admission. | +| EXTREQ-TP-005 | planned | Determinism | EXTREQ-REQ-005 | Repeated compilation and lowering produce identical canonical bytes and digests. | `request_artifacts_are_reproducible` | No clock, randomness, filesystem, or network input enters the compiler. | +| EXTREQ-TP-006 | planned | Mutation sensitivity | EXTREQ-REQ-005 | Operation, input schema, settlement schema, authority scope, basis, budget, input, and reconciliation mutations move Core identity. | `every_request_authority_field_moves_core_identity` | Each field is request-authoritative. | +| EXTREQ-TP-007 | planned | Property | EXTREQ-REQ-005, EXTREQ-REQ-008 | A fixed-seed 32-case capability-digest corpus is reproducible and collision-free within the corpus. | `fixed_seed_request_identity_corpus_is_deterministic` | Seed is recorded in the fixture table. | +| EXTREQ-TP-008 | planned | Stress | EXTREQ-REQ-008 | A generated module with 64 request declarations emits 64 Core requests and 64 Target IR requests with zero callable steps. | `sixty_four_requests_remain_bounded_non_callable_data` | Bound is fixed for CI. | + +## Determinism Obligations + +- Tests use no filesystem discovery, network access, clock, environment, or + randomness. +- The property corpus uses the recorded fixed seed and a local deterministic + generator. +- Canonical comparisons assert decoded structured fields and exact bytes, not + diagnostics or log text. +- Stress cardinality is fixed at 64. + +## Open Gaps + +- Echo admission of compiler-emitted request values. +- A concrete workspace-observation adapter and settlement witness. +- Basis-bound validated patch application. From 1fd153bc600edf027452772fe498a44c63f55be0 Mon Sep 17 00:00:00 2001 From: James Ross Date: Wed, 29 Jul 2026 15:50:26 -0700 Subject: [PATCH 02/11] feat: declare typed external action requests --- crates/edict-syntax/src/ast.rs | 24 +- crates/edict-syntax/src/canonical.rs | 182 ++++++++++- crates/edict-syntax/src/compiler.rs | 292 +++++++++++++++++- crates/edict-syntax/src/contract_bundle.rs | 16 + crates/edict-syntax/src/core_ir.rs | 25 ++ crates/edict-syntax/src/lib.rs | 16 +- crates/edict-syntax/src/parser.rs | 77 ++++- crates/edict-syntax/src/result_projection.rs | 8 +- crates/edict-syntax/src/semantic.rs | 19 ++ crates/edict-syntax/src/target_ir.rs | 103 +++++- crates/edict-syntax/tests/contract_bundle.rs | 1 + .../tests/external_action_requests.rs | 36 +++ .../tests/parse_review_regressions.rs | 14 +- docs/abi/edict-core.cddl | 31 +- docs/abi/edict-target-ir.cddl | 19 ++ 15 files changed, 805 insertions(+), 58 deletions(-) diff --git a/crates/edict-syntax/src/ast.rs b/crates/edict-syntax/src/ast.rs index a2c73e6..d73e930 100644 --- a/crates/edict-syntax/src/ast.rs +++ b/crates/edict-syntax/src/ast.rs @@ -29,7 +29,7 @@ pub enum ImportKind { Lawpack, Target, Core, - /// `use capability ... as ...` — present in product sketches; rejected by v1. + /// Digest-bound external operation family available only to `request`. Capability, } @@ -47,6 +47,14 @@ pub struct Import { pub span: Span, } +/// A digest-locked package resource carried by source syntax. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct DigestLockedPackageRef { + pub package: PackageRef, + pub digest: String, + pub span: Span, +} + /// A top-level declaration. #[derive(Debug, Clone, PartialEq, Eq)] pub enum Decl { @@ -205,6 +213,20 @@ pub enum Stmt { els: Option, span: Span, }, + /// Construct one typed external-action request value without performing it. + ExternalActionRequest { + name: String, + request_type: TypeRef, + operation: Expr, + input_schema: DigestLockedPackageRef, + settlement_schema: DigestLockedPackageRef, + authority_scope: Expr, + basis: Expr, + max_settlement_bytes: Expr, + max_attempts: Expr, + reconciliation_law: DigestLockedPackageRef, + span: Span, + }, /// `require predicate else ;` (always carries `else`). Require { predicate: Expr, diff --git a/crates/edict-syntax/src/canonical.rs b/crates/edict-syntax/src/canonical.rs index 53891ca..24cc351 100644 --- a/crates/edict-syntax/src/canonical.rs +++ b/crates/edict-syntax/src/canonical.rs @@ -13,12 +13,13 @@ use sha2::{Digest, Sha256}; use crate::core_ir::{ is_lowercase_sha256_review_digest, parse_core_integer, CompareOp, CoreBlock, CoreBudget, - CoreExpr, CoreImport, CoreIntent, CoreModule, CoreNode, CoreObstructionArm, CorePredicate, - CoreType, CoreValue, InputConstraint, InputConstraintSource, LocalRef, ResourceRef, + CoreExpr, CoreExternalActionBudget, CoreImport, CoreImportKind, CoreIntent, CoreModule, + CoreNode, CoreObstructionArm, CorePredicate, CoreType, CoreValue, InputConstraint, + InputConstraintSource, LocalRef, ResourceRef, }; use crate::target_ir::{ - TargetIrArtifact, TargetIrIntent, TargetIrRequireFailure, TargetIrRequirement, - TargetIrSemanticClosure, TargetIrStep, + TargetIrArtifact, TargetIrExternalActionRequest, TargetIrIntent, TargetIrRequireFailure, + TargetIrRequirement, TargetIrSemanticClosure, TargetIrStep, }; /// Canonical encoding profile for Core artifacts. @@ -492,17 +493,43 @@ fn target_ir_artifact_value(artifact: &TargetIrArtifact) -> Result::new(); + for resource in &closure.capabilities { + if let Some(prior) = capabilities.get(resource.coordinate.as_str()) { + if *prior != resource { + return Err(CanonicalError::new( + CanonicalErrorKind::UnsupportedValue, + format!( + "Target IR capability coordinate `{}` is bound to conflicting resources", + resource.coordinate + ), + )); + } + } else { + capabilities.insert(resource.coordinate.as_str(), resource); + } + } + let mut entries = vec![ ( "sourceCore", target_ir_resource_ref_value(&closure.source_core)?, @@ -567,7 +610,14 @@ fn target_ir_semantic_closure_value( "lawpacks", sorted_array_results(lawpacks.into_values().map(target_ir_resource_ref_value))?, ), - ])) + ]; + if !capabilities.is_empty() { + entries.push(( + "capabilities", + sorted_array_results(capabilities.into_values().map(target_ir_resource_ref_value))?, + )); + } + Ok(map(entries)) } fn target_ir_resource_ref_value(resource: &ResourceRef) -> Result { @@ -619,9 +669,53 @@ fn target_ir_intent_value(intent: &TargetIrIntent) -> Result Result { + Ok(map([ + ("id", text(&request.id)), + ("binding", local_ref_value(&request.binding)), + ( + "operation", + target_ir_resource_ref_value(&request.operation)?, + ), + ("inputType", text(&request.input_type)), + ("settlementType", text(&request.settlement_type)), + ( + "inputSchema", + target_ir_resource_ref_value(&request.input_schema)?, + ), + ( + "settlementSchema", + target_ir_resource_ref_value(&request.settlement_schema)?, + ), + ("input", core_expr_value(&request.input)?), + ("authorityScope", core_expr_value(&request.authority_scope)?), + ("basis", core_expr_value(&request.basis)?), + ("budget", external_action_budget_value(&request.budget)?), + ( + "reconciliationLaw", + target_ir_resource_ref_value(&request.reconciliation_law)?, + ), + ("state", text("awaitingSettlement")), + ("settlementAdmission", text("schemaRequired")), + ])) +} + fn target_ir_requirement_value( requirement: &TargetIrRequirement, ) -> Result { @@ -673,6 +767,34 @@ fn target_ir_step_value(step: &TargetIrStep) -> Result Result { + let capability_imports = module + .imports + .iter() + .filter(|import| import.kind == CoreImportKind::Capability) + .map(|import| &import.resource) + .collect::>(); + for request in module + .intents + .values() + .flat_map(|intent| &intent.body.nodes) + .filter_map(|node| match node { + CoreNode::ExternalActionRequest { operation, .. } => Some(operation), + CoreNode::Let { .. } | CoreNode::Require { .. } | CoreNode::Effect { .. } => None, + }) + { + if !capability_imports + .iter() + .any(|capability| *capability == request) + { + return Err(CanonicalError::new( + CanonicalErrorKind::UnsupportedValue, + format!( + "Core request operation `{}` is absent from the capability imports", + request.coordinate + ), + )); + } + } Ok(map([ ("apiVersion", text(&module.api_version)), ("coordinate", text(&module.coordinate)), @@ -807,6 +929,10 @@ fn core_type_value(ty: &CoreType) -> CanonicalValue { CoreType::CapabilityRef { item } => { map([("kind", text("CapabilityRef")), ("item", text(item))]) } + CoreType::ExternalActionRequest { settlement } => map([ + ("kind", text("ExternalActionRequest")), + ("settlement", text(settlement)), + ]), } } @@ -900,9 +1026,49 @@ fn core_node_value(node: &CoreNode) -> Result { }))?, ), ])), + CoreNode::ExternalActionRequest { + binding, + operation, + input_type, + settlement_type, + input_schema, + settlement_schema, + input, + authority_scope, + basis, + budget, + reconciliation_law, + } => Ok(map([ + ("kind", text("externalActionRequest")), + ("binding", local_ref_value(binding)), + ("operation", resource_ref_value(operation)?), + ("inputType", text(input_type)), + ("settlementType", text(settlement_type)), + ("inputSchema", resource_ref_value(input_schema)?), + ("settlementSchema", resource_ref_value(settlement_schema)?), + ("input", core_expr_value(input)?), + ("authorityScope", core_expr_value(authority_scope)?), + ("basis", core_expr_value(basis)?), + ("budget", external_action_budget_value(budget)?), + ("reconciliationLaw", resource_ref_value(reconciliation_law)?), + ("state", text("awaitingSettlement")), + ("settlementAdmission", text("schemaRequired")), + ])), } } +fn external_action_budget_value( + budget: &CoreExternalActionBudget, +) -> Result { + Ok(map([ + ( + "maxSettlementBytes", + core_expr_value(&budget.max_settlement_bytes)?, + ), + ("maxAttempts", core_expr_value(&budget.max_attempts)?), + ])) +} + fn core_require_failure_arm_value( arm: &crate::core_ir::CoreRequireFailureArm, ) -> Result { diff --git a/crates/edict-syntax/src/compiler.rs b/crates/edict-syntax/src/compiler.rs index b8d8feb..ea84684 100644 --- a/crates/edict-syntax/src/compiler.rs +++ b/crates/edict-syntax/src/compiler.rs @@ -6,15 +6,17 @@ use std::collections::{BTreeMap, BTreeSet}; use crate::ast::{ - BinOp, Block, BoundRef, Decl, ElseClause, Expr, FieldDecl, Import, ImportKind, IntentClause, - IntentDecl, Module, ObstructionArm, ObstructionHandler, ObstructionTarget, RecordEntry, - RequireElseArm, ScalarRefine, Stmt, TypeDecl, TypeExpr, TypeRef, UnOp, YieldBlock, + BinOp, Block, BoundRef, Decl, DigestLockedPackageRef, ElseClause, Expr, FieldDecl, Import, + ImportKind, IntentClause, IntentDecl, Module, ObstructionArm, ObstructionHandler, + ObstructionTarget, RecordEntry, RequireElseArm, ScalarRefine, Stmt, TypeDecl, TypeExpr, + TypeRef, UnOp, YieldBlock, }; use crate::core_ir::{ - parse_core_integer, CompareOp, CoreBlock, CoreBudget, CoreExpr, CoreImport, CoreImportKind, - CoreIntent, CoreModule, CoreNode, CoreObstructionArm, CoreObstructionReason, CorePredicate, - CoreRequireFailureArm, CoreType, CoreValue, InputConstraint, InputConstraintSource, LocalRef, - ResourceRef, CORE_API_VERSION, CORE_APPLICATION_INPUT_LOCAL_ID, + is_lowercase_sha256_review_digest, parse_core_integer, CompareOp, CoreBlock, CoreBudget, + CoreExpr, CoreExternalActionBudget, CoreImport, CoreImportKind, CoreIntent, CoreModule, + CoreNode, CoreObstructionArm, CoreObstructionReason, CorePredicate, CoreRequireFailureArm, + CoreType, CoreValue, InputConstraint, InputConstraintSource, LocalRef, ResourceRef, + CORE_API_VERSION, CORE_APPLICATION_INPUT_LOCAL_ID, }; use crate::lowerability::WriteClass; use crate::semantic::validate_surface; @@ -281,18 +283,10 @@ pub fn lower_core(typed: &TypedModule) -> Result> }) } -fn resolve_imports(imports: &[Import], errors: &mut Vec) -> Vec { +fn resolve_imports(imports: &[Import], _errors: &mut Vec) -> Vec { let mut out = Vec::new(); for import in imports { let Some(kind) = core_import_kind(import.kind) else { - if import.kind == ImportKind::Capability { - errors.push(error( - CompilerStage::Resolve, - CompilerErrorKind::UnsupportedSourceShape, - "capability imports are not supported by the v1 compiler spine", - import.span, - )); - } continue; }; let Some(package) = &import.package else { @@ -315,7 +309,8 @@ fn core_import_kind(kind: ImportKind) -> Option { ImportKind::Lawpack => Some(CoreImportKind::Lawpack), ImportKind::Target => Some(CoreImportKind::Target), ImportKind::Core => Some(CoreImportKind::Core), - ImportKind::Shape | ImportKind::Capability => None, + ImportKind::Capability => Some(CoreImportKind::Capability), + ImportKind::Shape => None, } } @@ -394,6 +389,7 @@ enum TypeKind { String { max: u64, canonical: String }, Bytes { max: u64 }, Record(BTreeMap), + ExternalActionRequest { settlement: Box }, } impl TypeShape { @@ -414,6 +410,19 @@ impl TypeShape { .map(|(name, shape)| (name.clone(), shape.coord.clone())) .collect(), }, + TypeKind::ExternalActionRequest { settlement } => CoreType::ExternalActionRequest { + settlement: settlement.coord.clone(), + }, + } + } + + fn value_type_coord(&self) -> String { + match &self.kind { + TypeKind::Bool => "Bool".to_owned(), + TypeKind::Int { width } => width.clone(), + TypeKind::String { max, canonical } => string_type_coord(*max, canonical), + TypeKind::Bytes { max } => bytes_type_coord(*max), + TypeKind::Record(_) | TypeKind::ExternalActionRequest { .. } => self.coord.clone(), } } } @@ -564,6 +573,17 @@ impl<'a> TypeChecker<'a> { None } } + TypeRef::Named { path, args } + if path.as_slice() == ["ExternalActionRequest"] && args.len() == 1 => + { + let settlement = self.type_ref_shape(&args[0], span, None)?; + Some(TypeShape { + coord: format!("edict.external-action.request/v1<{}>", settlement.coord), + kind: TypeKind::ExternalActionRequest { + settlement: Box::new(settlement), + }, + }) + } TypeRef::StringTy(Some(refine)) => self.string_shape(refine, span, coord_hint), TypeRef::BytesTy(Some(bound)) => self.bytes_shape(bound, span, coord_hint), TypeRef::BytesTy(None) @@ -801,6 +821,34 @@ impl<'a> TypeChecker<'a> { self.unsupported_stmt(*span, "effect statement"); } } + Stmt::ExternalActionRequest { + name, + request_type, + operation, + input_schema, + settlement_schema, + authority_scope, + basis, + max_settlement_bytes, + max_attempts, + reconciliation_law, + span, + } => self.check_external_action_request( + name, + request_type, + operation, + input_schema, + settlement_schema, + authority_scope, + basis, + max_settlement_bytes, + max_attempts, + reconciliation_law, + *span, + env, + locals, + state, + ), Stmt::Require { predicate, arm, .. } => { self.check_require_stmt(predicate, arm, env, state); } @@ -811,6 +859,187 @@ impl<'a> TypeChecker<'a> { } } + #[allow(clippy::too_many_arguments)] + fn check_external_action_request( + &mut self, + name: &str, + request_type: &TypeRef, + operation: &Expr, + input_schema: &DigestLockedPackageRef, + settlement_schema: &DigestLockedPackageRef, + authority_scope: &Expr, + basis: &Expr, + max_settlement_bytes: &Expr, + max_attempts: &Expr, + reconciliation_law: &DigestLockedPackageRef, + span: Span, + env: &mut BTreeMap, + locals: &mut Vec, + state: &mut BodyState, + ) { + let Some(request_shape) = self.type_ref_shape(request_type, span, None) else { + return; + }; + let TypeKind::ExternalActionRequest { settlement } = &request_shape.kind else { + self.errors.push(error( + CompilerStage::TypeCheck, + CompilerErrorKind::TypeMismatch, + "request binding type must be ExternalActionRequest", + span, + )); + return; + }; + let settlement_type = settlement.coord.clone(); + let Some((operation_resource, input)) = + self.check_external_action_operation(operation, env, span) + else { + return; + }; + if ambient_operation_family(&operation_resource.coordinate) { + self.errors.push(error( + CompilerStage::TypeCheck, + CompilerErrorKind::UnsupportedSourceShape, + format!( + "ambient operation family `{}` is not requestable", + operation_resource.coordinate + ), + span, + )); + return; + } + let Some(authority_scope) = self.check_expr(authority_scope, env) else { + return; + }; + let Some(basis) = self.check_expr(basis, env) else { + return; + }; + let settlement_budget_shape = integer_shape("U64"); + let Some(max_settlement_bytes) = self.check_expr_with_expected( + max_settlement_bytes, + env, + Some(&settlement_budget_shape), + ) else { + return; + }; + let attempt_budget_shape = integer_shape("U32"); + let Some(max_attempts) = + self.check_expr_with_expected(max_attempts, env, Some(&attempt_budget_shape)) + else { + return; + }; + let Some(input_schema) = self.request_resource(input_schema) else { + return; + }; + let Some(settlement_schema) = self.request_resource(settlement_schema) else { + return; + }; + let Some(reconciliation_law) = self.request_resource(reconciliation_law) else { + return; + }; + let local = next_local(&mut state.local_index, request_shape.coord.clone()); + state.nodes.push(CoreNode::ExternalActionRequest { + binding: local.clone(), + operation: operation_resource, + input_type: input.ty.value_type_coord(), + settlement_type, + input_schema, + settlement_schema, + input: input.expr, + authority_scope: authority_scope.expr, + basis: basis.expr, + budget: CoreExternalActionBudget { + max_settlement_bytes: max_settlement_bytes.expr, + max_attempts: max_attempts.expr, + }, + reconciliation_law, + }); + locals.push(local.clone()); + env.insert(name.to_owned(), (local, request_shape)); + } + + fn check_external_action_operation( + &mut self, + operation: &Expr, + env: &BTreeMap, + span: Span, + ) -> Option<(ResourceRef, TypedValue)> { + let Expr::Call { + callee, + type_args, + args, + .. + } = operation + else { + self.unsupported_stmt(span, "external-action request operation"); + return None; + }; + if !type_args.is_empty() { + self.errors.push(error( + CompilerStage::TypeCheck, + CompilerErrorKind::UnsupportedSourceShape, + "external-action request operation does not accept type arguments", + span, + )); + return None; + } + let Expr::Ident { name: alias, .. } = callee.as_ref() else { + self.errors.push(error( + CompilerStage::TypeCheck, + CompilerErrorKind::UnsupportedSourceShape, + "external-action request operation must name one capability import alias", + span, + )); + return None; + }; + let Some(operation_resource) = self + .resolved + .imports + .iter() + .find(|import| { + import.kind == CoreImportKind::Capability + && import.alias.as_deref() == Some(alias.as_str()) + }) + .map(|import| import.resource.clone()) + else { + self.errors.push(error( + CompilerStage::TypeCheck, + CompilerErrorKind::MissingContextFact, + format!( + "external-action operation `{alias}` has no digest-locked capability import" + ), + span, + )); + return None; + }; + let [input] = args.as_slice() else { + self.errors.push(error( + CompilerStage::TypeCheck, + CompilerErrorKind::UnsupportedSourceShape, + "external-action request operation accepts exactly one input value", + span, + )); + return None; + }; + let input = self.check_expr(input, env)?; + Some((operation_resource, input)) + } + + fn request_resource(&mut self, resource: &DigestLockedPackageRef) -> Option { + if !is_lowercase_sha256_review_digest(&resource.digest) { + self.errors.push(error( + CompilerStage::TypeCheck, + CompilerErrorKind::UnsupportedSourceShape, + "external-action resource digest must use lowercase sha256 review rendering", + resource.span, + )); + return None; + } + Some(ResourceRef { + coordinate: package_coordinate(&resource.package.path, &resource.package.version), + digest: Some(resource.digest.clone()), + }) + } + fn check_require_stmt( &mut self, predicate: &Expr, @@ -1404,6 +1633,20 @@ impl<'a> TypeChecker<'a> { } } Stmt::Effect { call, span, .. } => self.check_effect_profile(intent, call, *span), + Stmt::ExternalActionRequest { + operation, + authority_scope, + basis, + max_settlement_bytes, + max_attempts, + .. + } => { + self.check_known_effect_profiles(intent, operation) + && self.check_known_effect_profiles(intent, authority_scope) + && self.check_known_effect_profiles(intent, basis) + && self.check_known_effect_profiles(intent, max_settlement_bytes) + && self.check_known_effect_profiles(intent, max_attempts) + } Stmt::Require { predicate, .. } | Stmt::Guarantee { predicate, .. } | Stmt::Assert { predicate, .. } => self.check_known_effect_profiles(intent, predicate), @@ -1859,10 +2102,25 @@ fn compatible(expected: &TypeShape, actual: &TypeShape) -> bool { (TypeKind::Bool, TypeKind::Bool) => true, (TypeKind::Int { width: expected }, TypeKind::Int { width: actual }) => expected == actual, (TypeKind::Bytes { max: expected }, TypeKind::Bytes { max: actual }) => actual <= expected, + ( + TypeKind::ExternalActionRequest { + settlement: expected, + }, + TypeKind::ExternalActionRequest { settlement: actual }, + ) => compatible(expected, actual), _ => false, } } +fn ambient_operation_family(coordinate: &str) -> bool { + coordinate.split(['.', '@']).next().is_some_and(|root| { + matches!( + root, + "filesystem" | "process" | "network" | "git" | "github" | "model" | "shell" + ) + }) +} + fn comparable(left: &TypeShape, right: &TypeShape) -> bool { compatible(left, right) || compatible(right, left) } diff --git a/crates/edict-syntax/src/contract_bundle.rs b/crates/edict-syntax/src/contract_bundle.rs index f6e071b..4c62442 100644 --- a/crates/edict-syntax/src/contract_bundle.rs +++ b/crates/edict-syntax/src/contract_bundle.rs @@ -603,6 +603,13 @@ fn corroborate_target_ir_semantic_closure( "Target IR source Core identity does not match the supplied Core module", )); } + if !actual.capabilities.is_empty() { + return Err(ContractBundleAssemblyError::new( + ContractBundleAssemblyErrorKind::TargetIrSourceMismatch, + "target_ir_artifact.semantic_closure.capabilities", + "Target IR capability closure does not match the supplied Core module", + )); + } canonical_resource_set(&actual.lawpacks) } (Some(expected), Some(actual)) => { @@ -622,6 +629,15 @@ fn corroborate_target_ir_semantic_closure( "Target IR lawpack closure does not match the supplied Core module", )); } + if canonical_resource_set(&actual.capabilities) + != canonical_resource_set(&expected.capabilities) + { + return Err(ContractBundleAssemblyError::new( + ContractBundleAssemblyErrorKind::TargetIrSourceMismatch, + "target_ir_artifact.semantic_closure.capabilities", + "Target IR capability closure does not match the supplied Core module", + )); + } canonical_resource_set(&expected.lawpacks) } }; diff --git a/crates/edict-syntax/src/core_ir.rs b/crates/edict-syntax/src/core_ir.rs index 3a458e9..b4d9e9d 100644 --- a/crates/edict-syntax/src/core_ir.rs +++ b/crates/edict-syntax/src/core_ir.rs @@ -39,6 +39,7 @@ pub enum CoreImportKind { Lawpack, Target, Core, + Capability, } impl CoreImportKind { @@ -48,6 +49,7 @@ impl CoreImportKind { Self::Lawpack => "lawpack", Self::Target => "target", Self::Core => "core", + Self::Capability => "capability", } } } @@ -147,6 +149,9 @@ pub enum CoreType { CapabilityRef { item: String, }, + ExternalActionRequest { + settlement: String, + }, } /// Alpha-stable local reference. @@ -276,6 +281,26 @@ pub enum CoreNode { input: CoreExpr, obstruction_map: BTreeMap, }, + ExternalActionRequest { + binding: LocalRef, + operation: ResourceRef, + input_type: String, + settlement_type: String, + input_schema: ResourceRef, + settlement_schema: ResourceRef, + input: CoreExpr, + authority_scope: CoreExpr, + basis: CoreExpr, + budget: CoreExternalActionBudget, + reconciliation_law: ResourceRef, + }, +} + +/// Runtime-valued bounds carried by a typed external-action request. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct CoreExternalActionBudget { + pub max_settlement_bytes: CoreExpr, + pub max_attempts: CoreExpr, } /// Core disposition for a failed `require` predicate. diff --git a/crates/edict-syntax/src/lib.rs b/crates/edict-syntax/src/lib.rs index 0d6db55..2ae94f0 100644 --- a/crates/edict-syntax/src/lib.rs +++ b/crates/edict-syntax/src/lib.rs @@ -131,10 +131,10 @@ pub use contract_bundle::{ SourceArtifactRef, SuppliedDigest, SuppliedTargetIrResource, CONTRACT_BUNDLE_API_VERSION, }; pub use core_ir::{ - CompareOp, CoreBlock, CoreBudget, CoreExpr, CoreImport, CoreImportKind, CoreIntent, CoreModule, - CoreNode, CoreObstructionArm, CoreObstructionReason, CorePredicate, CoreRequireFailureArm, - CoreType, CoreValue, InputConstraint, InputConstraintSource, LocalRef, ResourceRef, - CORE_API_VERSION, + CompareOp, CoreBlock, CoreBudget, CoreExpr, CoreExternalActionBudget, CoreImport, + CoreImportKind, CoreIntent, CoreModule, CoreNode, CoreObstructionArm, CoreObstructionReason, + CorePredicate, CoreRequireFailureArm, CoreType, CoreValue, InputConstraint, + InputConstraintSource, LocalRef, ResourceRef, CORE_API_VERSION, }; pub use highlight::{highlight_source, HighlightRole, HighlightToken}; pub use lawpack::{ @@ -207,10 +207,10 @@ pub use result_projection::{ }; pub use semantic::{validate_module, validate_surface, SemanticError, SemanticErrorKind}; pub use target_ir::{ - lower_to_target_ir, TargetEffectLowering, TargetIrArtifact, TargetIrIntent, - TargetIrLoweringFacts, TargetIrRequireFailure, TargetIrRequirement, TargetIrSemanticClosure, - TargetIrStep, TargetLoweringFailure, TargetLoweringFailureKind, TargetLoweringReport, - TargetLoweringStatus, ECHO_DPO_TARGET_PROFILE, ECHO_SPAN_IR_DOMAIN, + lower_to_target_ir, TargetEffectLowering, TargetIrArtifact, TargetIrExternalActionRequest, + TargetIrIntent, TargetIrLoweringFacts, TargetIrRequireFailure, TargetIrRequirement, + TargetIrSemanticClosure, TargetIrStep, TargetLoweringFailure, TargetLoweringFailureKind, + TargetLoweringReport, TargetLoweringStatus, ECHO_DPO_TARGET_PROFILE, ECHO_SPAN_IR_DOMAIN, GITWARP_COMMIT_REDUCER_IR_DOMAIN, GITWARP_REF_CRDT_TARGET_PROFILE, }; pub use target_profile::{ diff --git a/crates/edict-syntax/src/parser.rs b/crates/edict-syntax/src/parser.rs index a5d1b7e..152e9ec 100644 --- a/crates/edict-syntax/src/parser.rs +++ b/crates/edict-syntax/src/parser.rs @@ -4,10 +4,11 @@ //! text so they remain usable as member names after `.`. use crate::ast::{ - BinOp, Block, BoundRef, ContinueObstructedArm, Decl, ElseClause, EnumDecl, Expr, - FieldConstraint, FieldDecl, Import, ImportKind, IntentClause, IntentDecl, MatchArm, Module, - ObstructionArm, ObstructionHandler, ObstructionTarget, PackageRef, Param, RecordEntry, - RequireElseArm, ScalarRefine, Stmt, TypeDecl, TypeExpr, TypeRef, UnOp, VariantCase, YieldBlock, + BinOp, Block, BoundRef, ContinueObstructedArm, Decl, DigestLockedPackageRef, ElseClause, + EnumDecl, Expr, FieldConstraint, FieldDecl, Import, ImportKind, IntentClause, IntentDecl, + MatchArm, Module, ObstructionArm, ObstructionHandler, ObstructionTarget, PackageRef, Param, + RecordEntry, RequireElseArm, ScalarRefine, Stmt, TypeDecl, TypeExpr, TypeRef, UnOp, + VariantCase, YieldBlock, }; use crate::token::{lex, Span, Token, TokenKind}; @@ -135,6 +136,7 @@ pub(crate) fn is_keyword(s: &str) -> bool { | "budget" | "where" | "let" + | "request" | "return" | "require" | "guarantee" @@ -190,6 +192,7 @@ fn stmt_contains_return(stmt: &Stmt) -> bool { Stmt::For { body, .. } => block_contains_return(body), Stmt::Let { .. } | Stmt::Effect { .. } + | Stmt::ExternalActionRequest { .. } | Stmt::Require { .. } | Stmt::Guarantee { .. } | Stmt::Assert { .. } => false, @@ -487,12 +490,9 @@ impl Parser { } else if self.eat_kw("core") { ImportKind::Core } else if self.eat_kw("capability") { - return self.err_kind( - ParseErrorKind::UnsupportedSyntax, - "`use capability` is not accepted in Edict v1", - ); + ImportKind::Capability } else { - return self.err("expected import kind (shape|lawpack|target|core)"); + return self.err("expected import kind (shape|lawpack|target|core|capability)"); }; let (package, shape_path) = if kind == ImportKind::Shape { @@ -893,6 +893,8 @@ impl Parser { els, span: Span::new(start, self.prev_end()), }) + } else if self.eat_kw("request") { + self.external_action_request_stmt(start) } else if self.eat_kw("return") { let value = self.expr()?; self.expect(&TokenKind::Semi)?; @@ -953,6 +955,63 @@ impl Parser { } } + fn external_action_request_stmt(&mut self, start: usize) -> Result { + let name = self.binder()?; + self.expect(&TokenKind::Colon)?; + let request_type = self.type_ref()?; + self.expect(&TokenKind::Eq)?; + let operation = self.expr()?; + if !is_call_expr(&operation) { + return self.err_kind( + ParseErrorKind::NonCallEffect, + "external-action request operation must be a call expression", + ); + } + self.expect_kw("input")?; + self.expect_kw("schema")?; + let input_schema = self.digest_locked_package_ref()?; + self.expect_kw("settlement")?; + self.expect_kw("schema")?; + let settlement_schema = self.digest_locked_package_ref()?; + self.expect_kw("authority")?; + let authority_scope = self.expr()?; + self.expect_kw("basis")?; + let basis = self.expr()?; + self.expect_kw("budget")?; + self.expect_kw("maxSettlementBytes")?; + let max_settlement_bytes = self.expr()?; + self.expect_kw("maxAttempts")?; + let max_attempts = self.expr()?; + self.expect_kw("reconcile")?; + let reconciliation_law = self.digest_locked_package_ref()?; + self.expect(&TokenKind::Semi)?; + Ok(Stmt::ExternalActionRequest { + name, + request_type, + operation, + input_schema, + settlement_schema, + authority_scope, + basis, + max_settlement_bytes, + max_attempts, + reconciliation_law, + span: Span::new(start, self.prev_end()), + }) + } + + fn digest_locked_package_ref(&mut self) -> Result { + let start = self.peek_span().start; + let package = self.package_ref()?; + self.expect_kw("digest")?; + let digest = self.digest_lit()?; + Ok(DigestLockedPackageRef { + package, + digest, + span: Span::new(start, self.prev_end()), + }) + } + /// The right-hand side of a `let`: either an ordinary expression, or the /// effectful branch-yield form (legal *only* here). Both start with `if`, /// so we disambiguate on what follows the predicate: `then` is the pure diff --git a/crates/edict-syntax/src/result_projection.rs b/crates/edict-syntax/src/result_projection.rs index c29b042..ffd3243 100644 --- a/crates/edict-syntax/src/result_projection.rs +++ b/crates/edict-syntax/src/result_projection.rs @@ -519,7 +519,9 @@ fn resolve_capability_sources( input, .. } => Some((binding, effect, input)), - CoreNode::Let { .. } | CoreNode::Require { .. } => None, + CoreNode::Let { .. } + | CoreNode::Require { .. } + | CoreNode::ExternalActionRequest { .. } => None, }) .collect::>(); if core_effects.len() != target_intent.steps.len() { @@ -818,6 +820,10 @@ fn types_are_compatible(core: &CoreModule, left: &str, right: &str, depth: usize | (CoreType::CapabilityRef { item: left }, CoreType::CapabilityRef { item: right }) => { types_are_compatible(core, left, right, depth + 1) } + ( + CoreType::ExternalActionRequest { settlement: left }, + CoreType::ExternalActionRequest { settlement: right }, + ) => types_are_compatible(core, left, right, depth + 1), ( CoreType::List { item: left, diff --git a/crates/edict-syntax/src/semantic.rs b/crates/edict-syntax/src/semantic.rs index db1fe53..586f43c 100644 --- a/crates/edict-syntax/src/semantic.rs +++ b/crates/edict-syntax/src/semantic.rs @@ -366,6 +366,25 @@ fn validate_stmt(stmt: &Stmt, names: &mut NameEnv, errors: &mut Vec { + names.bind(name, *span, errors); + validate_type_ref(request_type, *span, errors); + validate_expr(operation, names, errors); + validate_expr(authority_scope, names, errors); + validate_expr(basis, names, errors); + validate_expr(max_settlement_bytes, names, errors); + validate_expr(max_attempts, names, errors); + } Stmt::Require { predicate, arm, .. } => { validate_expr(predicate, names, errors); validate_require_else_arm(arm, names, errors); diff --git a/crates/edict-syntax/src/target_ir.rs b/crates/edict-syntax/src/target_ir.rs index 65e59fd..f8d6e9a 100644 --- a/crates/edict-syntax/src/target_ir.rs +++ b/crates/edict-syntax/src/target_ir.rs @@ -7,9 +7,9 @@ use std::collections::{BTreeMap, BTreeSet}; use crate::core_ir::{ - is_lowercase_sha256_review_digest, CoreBudget, CoreExpr, CoreImportKind, CoreIntent, - CoreModule, CoreNode, CoreObstructionArm, CoreObstructionReason, CorePredicate, - CoreRequireFailureArm, InputConstraint, LocalRef, ResourceRef, CORE_API_VERSION, + is_lowercase_sha256_review_digest, CoreBudget, CoreExpr, CoreExternalActionBudget, + CoreImportKind, CoreIntent, CoreModule, CoreNode, CoreObstructionArm, CoreObstructionReason, + CorePredicate, CoreRequireFailureArm, InputConstraint, LocalRef, ResourceRef, CORE_API_VERSION, }; use crate::digest_core_module; use crate::lowerability::{LowerabilityEffectStatus, LowerabilityReport, LowerabilityStatus}; @@ -203,6 +203,7 @@ pub struct TargetIrArtifact { pub struct TargetIrSemanticClosure { pub source_core: ResourceRef, pub lawpacks: Vec, + pub capabilities: Vec, } #[derive(Debug, Clone, PartialEq, Eq)] @@ -213,6 +214,7 @@ pub struct TargetIrIntent { pub core_evaluation_budget: CoreBudget, pub requirements: Vec, pub steps: Vec, + pub external_action_requests: Vec, pub result: CoreExpr, } @@ -240,6 +242,23 @@ pub struct TargetIrStep { pub obstruction_arms: BTreeMap, } +/// A deterministic external-action request preserved as data for Echo admission. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct TargetIrExternalActionRequest { + pub id: String, + pub binding: LocalRef, + pub operation: ResourceRef, + pub input_type: String, + pub settlement_type: String, + pub input_schema: ResourceRef, + pub settlement_schema: ResourceRef, + pub input: CoreExpr, + pub authority_scope: CoreExpr, + pub basis: CoreExpr, + pub budget: CoreExternalActionBudget, + pub reconciliation_law: ResourceRef, +} + #[must_use] pub fn lower_to_target_ir( core: &CoreModule, @@ -346,6 +365,7 @@ pub(crate) fn semantic_closure_for_core( core: &CoreModule, ) -> Result, TargetLoweringFailure> { let mut lawpacks = BTreeMap::::new(); + let mut capabilities = BTreeMap::::new(); for resource in core .imports .iter() @@ -380,8 +400,42 @@ pub(crate) fn semantic_closure_for_core( lawpacks.insert(resource.coordinate.clone(), resource.clone()); } } + for resource in core + .imports + .iter() + .filter(|import| import.kind == CoreImportKind::Capability) + .map(|import| &import.resource) + { + if !resource + .digest + .as_deref() + .is_some_and(is_lowercase_sha256_review_digest) + { + return Err(TargetLoweringFailure { + kind: TargetLoweringFailureKind::UndigestedCoreImport, + intent: None, + node_index: None, + detail: resource.coordinate.clone(), + }); + } + if let Some(prior) = capabilities.get(&resource.coordinate) { + if prior != resource { + return Err(TargetLoweringFailure { + kind: TargetLoweringFailureKind::InvalidCoreIdentity, + intent: None, + node_index: None, + detail: format!( + "capability coordinate `{}` is bound to conflicting resources", + resource.coordinate + ), + }); + } + } else { + capabilities.insert(resource.coordinate.clone(), resource.clone()); + } + } let has_explicit_basis = core.intents.values().any(|intent| intent.basis.is_some()); - if !has_explicit_basis && lawpacks.is_empty() { + if !has_explicit_basis && lawpacks.is_empty() && capabilities.is_empty() { return Ok(None); } @@ -397,6 +451,7 @@ pub(crate) fn semantic_closure_for_core( digest: Some(digest.to_review_string()), }, lawpacks: lawpacks.into_values().collect(), + capabilities: capabilities.into_values().collect(), })) } @@ -505,7 +560,11 @@ fn lower_intent( for (node_index, node) in intent.body.nodes.iter().enumerate() { lower_node(intent_name, node_index, node, context, &mut state, failures); } - if state.requirements.is_empty() && state.steps.is_empty() && intent.body.nodes.is_empty() { + if state.requirements.is_empty() + && state.steps.is_empty() + && state.external_action_requests.is_empty() + && intent.body.nodes.is_empty() + { failures.push(TargetLoweringFailure { kind: TargetLoweringFailureKind::NoTargetSteps, intent: Some(intent_name.to_owned()), @@ -521,6 +580,7 @@ fn lower_intent( core_evaluation_budget: intent.core_evaluation_budget.clone(), requirements: state.requirements, steps: state.steps, + external_action_requests: state.external_action_requests, result: intent.body.result.clone(), } } @@ -535,6 +595,7 @@ struct TargetLoweringContext<'a> { struct IntentLoweringState { requirements: Vec, steps: Vec, + external_action_requests: Vec, step_outputs: BTreeSet, } @@ -571,6 +632,38 @@ fn lower_node( state.step_outputs.insert(binding.id.clone()); } } + CoreNode::ExternalActionRequest { + binding, + operation, + input_type, + settlement_type, + input_schema, + settlement_schema, + input, + authority_scope, + basis, + budget, + reconciliation_law, + } => state + .external_action_requests + .push(TargetIrExternalActionRequest { + id: format!( + "{}.request.{}", + intent_name, + state.external_action_requests.len() + ), + binding: binding.clone(), + operation: operation.clone(), + input_type: input_type.clone(), + settlement_type: settlement_type.clone(), + input_schema: input_schema.clone(), + settlement_schema: settlement_schema.clone(), + input: input.clone(), + authority_scope: authority_scope.clone(), + basis: basis.clone(), + budget: budget.clone(), + reconciliation_law: reconciliation_law.clone(), + }), CoreNode::Let { .. } => failures.push(TargetLoweringFailure { kind: TargetLoweringFailureKind::UnsupportedCoreNode, intent: Some(intent_name.to_owned()), diff --git a/crates/edict-syntax/tests/contract_bundle.rs b/crates/edict-syntax/tests/contract_bundle.rs index 13f724e..77be4a4 100644 --- a/crates/edict-syntax/tests/contract_bundle.rs +++ b/crates/edict-syntax/tests/contract_bundle.rs @@ -498,6 +498,7 @@ mod contract_bundle_assembly { .iter() .map(DigestLockedResource::to_resource_ref) .collect(), + capabilities: Vec::new(), }); input } diff --git a/crates/edict-syntax/tests/external_action_requests.rs b/crates/edict-syntax/tests/external_action_requests.rs index 1cbbd9e..de6e105 100644 --- a/crates/edict-syntax/tests/external_action_requests.rs +++ b/crates/edict-syntax/tests/external_action_requests.rs @@ -193,10 +193,21 @@ fn workspace_observation_request_compiles_as_non_callable_data() { let target_bytes = encode_target_ir_artifact(&target).expect("Target IR encodes"); let target_value = decode_canonical_cbor(&target_bytes).expect("Target IR CBOR"); + let closure = map_field(&target_value, "semanticClosure"); + let capabilities = array_field(closure, "capabilities"); + assert_eq!(capabilities.len(), 1); + assert_eq!( + text_field(&capabilities[0], "id"), + "workspace.snapshot.observe@1" + ); let intent = map_field(map_field(&target_value, "intents"), "observe"); assert_eq!(array_field(intent, "steps"), []); let requests = array_field(intent, "externalActionRequests"); assert_eq!(requests.len(), 1); + assert_eq!( + text_field(map_field(&requests[0], "operation"), "id"), + "workspace.snapshot.observe@1" + ); assert_eq!( text_field(&requests[0], "settlementAdmission"), "schemaRequired" @@ -225,6 +236,31 @@ fn undeclared_or_floating_operation_families_fail_closed() { ); } +#[test] +fn request_operation_must_remain_in_core_and_target_capability_closure() { + let (mut core, mut target) = lower_source(&baseline_source()); + core.imports.clear(); + assert_eq!( + encode_core_module(&core) + .expect_err("Core request without capability import rejects") + .kind(), + edict_syntax::CanonicalErrorKind::UnsupportedValue + ); + + target + .semantic_closure + .as_mut() + .expect("request-bearing Target IR has a closure") + .capabilities + .clear(); + assert_eq!( + encode_target_ir_artifact(&target) + .expect_err("Target IR request without capability closure rejects") + .kind(), + edict_syntax::CanonicalErrorKind::UnsupportedValue + ); +} + #[test] fn capability_import_cannot_be_called_as_an_effect() { let source = format!( diff --git a/crates/edict-syntax/tests/parse_review_regressions.rs b/crates/edict-syntax/tests/parse_review_regressions.rs index 7a89754..3beceeb 100644 --- a/crates/edict-syntax/tests/parse_review_regressions.rs +++ b/crates/edict-syntax/tests/parse_review_regressions.rs @@ -5,7 +5,9 @@ mod common; use common::{body, intent_of}; -use edict_syntax::ast::{BinOp, BoundRef, Decl, Expr, RequireElseArm, Stmt, TypeExpr, TypeRef}; +use edict_syntax::ast::{ + BinOp, BoundRef, Decl, Expr, ImportKind, RequireElseArm, Stmt, TypeExpr, TypeRef, +}; use edict_syntax::token::IntSuffix; use edict_syntax::{parse_module, ParseErrorKind}; @@ -61,11 +63,11 @@ fn reserved_keywords_are_rejected_as_import_aliases() { } #[test] -fn capability_imports_are_rejected_in_v1() { - reject_kind( - "package a.b@1;\nuse capability c.d@1 as c;", - ParseErrorKind::UnsupportedSyntax, - ); +fn capability_imports_parse_as_external_operation_references() { + let source = format!("package a.b@1;\nuse capability c.d@1 digest \"{ZERO_DIGEST}\" as c;"); + let module = parse_module(&source).expect("capability import parses"); + assert_eq!(module.imports[0].kind, ImportKind::Capability); + assert_eq!(module.imports[0].digest.as_deref(), Some(ZERO_DIGEST)); } #[test] diff --git a/docs/abi/edict-core.cddl b/docs/abi/edict-core.cddl index 6ef395e..21cf2a0 100644 --- a/docs/abi/edict-core.cddl +++ b/docs/abi/edict-core.cddl @@ -15,7 +15,7 @@ core-module = { } core-import = { - kind: "lawpack" / "target" / "core", + kind: "lawpack" / "target" / "core" / "capability", ref: resource-ref, ? alias: tstr, } @@ -24,7 +24,7 @@ core-import = { core-type = core-scalar-type / core-record-type / core-variant-type / core-option-type / core-list-type / core-map-type / - core-capability-ref-type + core-capability-ref-type / core-external-action-request-type core-scalar-type = core-bool-type / core-int-type / core-string-type / core-bytes-type / core-unit-type @@ -73,6 +73,10 @@ core-capability-ref-type = { kind: "CapabilityRef", item: core-type-ref, } +core-external-action-request-type = { + kind: "ExternalActionRequest", + settlement: core-type-ref, +} ; core-type-ref is defined in edict-common.cddl and assembled with this schema. @@ -237,7 +241,8 @@ core-block = { result: core-expr, } -core-node = let-node / require-node / effect-node / guard-node / branch-node / +core-node = let-node / require-node / effect-node / + external-action-request-node / guard-node / branch-node / for-node / match-node / proof-obligation-node let-node = { @@ -271,6 +276,26 @@ effect-node = { input: core-expr, obstructionMap: { * failure-ident => obstruction-arm }, } +external-action-request-node = { + kind: "externalActionRequest", + binding: local-ref, + operation: resource-ref, + inputType: core-type-ref, + settlementType: core-type-ref, + inputSchema: resource-ref, + settlementSchema: resource-ref, + input: core-expr, + authorityScope: core-expr, + basis: core-expr, + budget: external-action-budget, + reconciliationLaw: resource-ref, + state: "awaitingSettlement", + settlementAdmission: "schemaRequired", +} +external-action-budget = { + maxSettlementBytes: core-expr, + maxAttempts: core-expr, +} obstruction-arm = { binder: local-ref, value: core-expr, diff --git a/docs/abi/edict-target-ir.cddl b/docs/abi/edict-target-ir.cddl index e78c4e5..e673446 100644 --- a/docs/abi/edict-target-ir.cddl +++ b/docs/abi/edict-target-ir.cddl @@ -41,6 +41,7 @@ target-ir-legacy-artifact = { target-ir-semantic-closure = { sourceCore: target-ir-resource-ref, lawpacks: [* target-ir-resource-ref], + ? capabilities: [* target-ir-resource-ref], } target-ir-intent = { @@ -58,6 +59,7 @@ target-ir-intent-common = ( coreEvaluationBudget: core-budget, requirements: [* target-ir-requirement], steps: [* target-ir-step], + ? externalActionRequests: [* target-ir-external-action-request], result: core-expr, ) @@ -76,3 +78,20 @@ target-ir-step = { obstructionFailures: [* failure-ident], obstructionArms: { * failure-ident => obstruction-arm }, } + +target-ir-external-action-request = { + id: tstr, + binding: local-ref, + operation: target-ir-resource-ref, + inputType: core-type-ref, + settlementType: core-type-ref, + inputSchema: target-ir-resource-ref, + settlementSchema: target-ir-resource-ref, + input: core-expr, + authorityScope: core-expr, + basis: core-expr, + budget: external-action-budget, + reconciliationLaw: target-ir-resource-ref, + state: "awaitingSettlement", + settlementAdmission: "schemaRequired", +} From e0a5b0df951691658ceeb4a318a14c2307dd74da Mon Sep 17 00:00:00 2001 From: James Ross Date: Wed, 29 Jul 2026 15:56:38 -0700 Subject: [PATCH 03/11] docs: define external action request boundary --- ARCHITECTURE.md | 12 ++- README.md | 5 + ROADMAP.md | 28 +++++ crates/edict-syntax/src/lib.rs | 3 + docs/REQUIREMENTS.md | 1 + docs/SPEC_edict-language-v1.md | 55 +++++++++- docs/topics/README.md | 3 + docs/topics/core-ir/README.md | 19 +++- docs/topics/core-ir/test-plan.md | 2 + .../topics/external-action-requests/README.md | 101 ++++++++++++++++++ .../external-action-requests/test-plan.md | 39 +++---- docs/topics/providers/README.md | 5 + docs/topics/providers/test-plan.md | 2 +- docs/topics/syntax/README.md | 13 ++- docs/topics/syntax/test-plan.md | 7 +- docs/topics/target-ir/README.md | 46 ++++---- docs/topics/target-ir/test-plan.md | 2 + 17 files changed, 287 insertions(+), 56 deletions(-) create mode 100644 docs/topics/external-action-requests/README.md diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 2fe856b..d0d0fc1 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -36,7 +36,8 @@ exports: - source/surface semantic validation; - compiler context facts and authority-fact loading; - source-to-Core compiler spine for the current supported subset; -- Core IR data structures; +- Core IR data structures, including non-callable typed external-action request + values; - depth-bounded canonical-CBOR encoding/decoding plus canonical Core, Target IR, bundle-layer encoders and digest helpers; - target-profile conformance checks; @@ -64,15 +65,15 @@ Module map: | `ast` | Source-level syntax tree types. | | `semantic` | Surface validation that does not require import resolution or target facts. | | `authority_facts` | File-backed compiler context facts for profiles, budgets, write classes, and source identity. | -| `compiler` | Resolve, type-check, and lower the supported source subset to Core IR. | -| `core_ir` | Runtime-neutral Core module, intent, expression, budget, import, and obstruction data. | +| `compiler` | Resolve, type-check, and lower the supported source subset to Core IR, including typed external-action request construction without execution authority. | +| `core_ir` | Runtime-neutral Core module, intent, expression, budget, import, obstruction, and external-action request data. | | `canonical` | Canonical value model, depth-bounded canonical CBOR encoder/decoder, digest frames, and reviewed golden digest helpers. | | `target_profile` | Runtime-neutral target-profile manifest conformance. | | `lowerability` | Checks whether Core requirements can be satisfied natively, by a direct adapter, or not at all. | | `provider` | Runtime-neutral provider manifest and generated/component provenance envelope validation. | | `provider_invocation` | Pure host-contract, explicitly injected owning-schema, WIT-shaped request/result, canonical artifact, limit, and sealed output-manifest validation. | | `provider_lowering` | Explicit in-process compatibility adapters over the current built-in target lowerers. | -| `target_ir` | Current Echo and git-warp Target IR artifact construction from Core plus lowering facts. | +| `target_ir` | Current Echo and git-warp Target IR artifact construction from Core plus lowering facts, preserving external requests outside callable target steps. | | `contract_bundle` | Participant-neutral bundle assembly, bundle digest preimages, validation, and assurance evidence binding. | | `admission` | Edict-owned Gate C request/receipt shape and binding validation without participant policy execution. | | `highlight` | Lexical highlight roles consumed by editor tooling. | @@ -172,6 +173,7 @@ source text -> compiler context facts -> compiler spine -> Core IR + -> typed external-action request data -> canonical Core bytes and digest -> lowerability + target facts -> direct lowering or built-in lowerer compatibility adapter @@ -220,6 +222,8 @@ Use these rules when placing new code: This workspace does not yet implement: - target runtime execution; +- external-action request admission, adapter execution, or settlement + resumption; - participant admission execution; - participant policy evaluation; - trusted lawpack or target-profile authorship; diff --git a/README.md b/README.md index 1b63740..df49778 100644 --- a/README.md +++ b/README.md @@ -575,6 +575,9 @@ What exists today: `let ... = effect(arg) else { failure(binder) => Obstruction }` source shape lowers through file-backed authority facts into typed Core with a semantic effect node and deterministic obstruction map +- Typed external-action request construction: digest-locked operation families, + schemas, scope, basis, budgets, and reconciliation law lower as non-callable + Core and Target IR data with exact capability closure - Reference `edict.canonical-cbor/v1` Core encoder and canonical byte validation path for the current in-memory Core module model - Reviewed Core golden bytes and exact `edict.core.module/v1` digest fixture for @@ -638,6 +641,8 @@ What doesn't exist yet: participant acceptance policy - Target-runtime execution, Echo verifier reports, git-warp commit object creation, or git-warp CRDT reducer verification +- External-action admission, adapter execution, settlement witnessing, or + settlement-driven resumption - Canonical bytes for full `ContractBundleManifest` values - Full admission execution tooling - Participant policy evaluation, capability delegation, and revocation logic diff --git a/ROADMAP.md b/ROADMAP.md index aee9202..d0e0135 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -723,6 +723,33 @@ Edict owns deterministic validation of fact provenance, digest binding, review references, and artifact shape. Continuum and participants own trust policy, identity, delegation, revocation, and acceptance decisions. +## Parallel Integration Track: Typed External Requests + +Milestone: `Typed External Requests` (#17) + +Primary issue: #172 + +Roadmap label: `roadmap-a1` + +This cross-repository track follows Echo's durable external-action protocol and +precedes Hello Echo's first bounded workspace observation: + +```text +Echo #694 durable request and settlement protocol + -> Edict #172 typed request values + -> Hello Echo #10 observed workspace settlement proof +``` + +Edict owns deterministic request construction, exact operation/schema/law +identity, capability closure, and non-callable Core/Target IR representation. +Echo owns request admission, durability, adapter coordination, settlement, +recovery, and replay. Adapters alone own external authority. + +The milestone closes when request source, Core, Target IR, canonical identity, +negative authority cases, and provider-seam non-authority are implemented and +validated. It does not add direct I/O, general algebraic effects, native stack +suspension, writable model authority, or the autonomous delivery loop. + ## v2-design - Future Design Track Milestone: `v2-design` @@ -762,6 +789,7 @@ Milestones: - `v0.13.0-alpha.1`: planned, issues TBD - `v0.14.0-alpha.1`: planned, issues TBD - `v0.15.0-alpha.1`: planned, issues TBD +- `Typed External Requests`: #172 - `v2-design`: #4 Alpha-train release labels: diff --git a/crates/edict-syntax/src/lib.rs b/crates/edict-syntax/src/lib.rs index 2ae94f0..7267a67 100644 --- a/crates/edict-syntax/src/lib.rs +++ b/crates/edict-syntax/src/lib.rs @@ -332,4 +332,7 @@ mod topic_shelf_doctests { #[doc = include_str!("../../../docs/topics/developer-tooling/README.md")] pub struct DeveloperToolingTopicDocs; + + #[doc = include_str!("../../../docs/topics/external-action-requests/README.md")] + pub struct ExternalActionRequestsTopicDocs; } diff --git a/docs/REQUIREMENTS.md b/docs/REQUIREMENTS.md index a0310e6..5e2fc27 100644 --- a/docs/REQUIREMENTS.md +++ b/docs/REQUIREMENTS.md @@ -67,6 +67,7 @@ but owned by a follow-up issue; no fixtures until its dependency lands). | EDICT-LANG-BUDGET-SPLIT-001 | Core/target/admitted budget split; target dims not in Core | Language | `lang/budget/split` | `lang/budget/target-dim-in-core` | spec | | EDICT-LANG-PROFILE-001 | `edict.language/v1` vs `edict.implementation/minimal-v1` capability flags | Language | `lang/profile/minimal` | `lang/profile/undeclared-capability` | spec | | EDICT-LANG-CAPREF-001 | `CapabilityRef` carries receipt digest only; inert until admitted | Language | `lang/capref/inert` | `lang/capref/ambient-authority` | spec | +| EDICT-LANG-EXTERNAL-REQUEST-001 | External boundary crossings are typed deterministic request values; exact operation capability closure is required and no ambient world authority enters Edict or the provider seam | Language/Core/Target | `crates/edict-syntax/tests/external_action_requests.rs` | `crates/edict-syntax/tests/external_action_requests.rs` | impl | | EDICT-LANG-READONLY-001 | Read-only inferred; executionClass (proofOnly/runtime) orthogonal to writeClass; runtime read is allowed | Language/Lawpack | `lang/readonly/runtime-read` | `lang/readonly/hidden-append` | spec | | EDICT-OPTIC-SOURCE-001 | Each optic field has one deterministic source (basis clause / profile template / coordinate / footprint) | Language | `optic/source/basis-clause` | `optic/source/freeform-support` | spec | | EDICT-DIGEST-WIRE-001 | Canonical digest is typed `[algorithm, bytes]`, never a hex string; hex only in review JSON | Bundle/ABI | `abi/digest/typed-pair` | `abi/digest/hex-string` | spec | diff --git a/docs/SPEC_edict-language-v1.md b/docs/SPEC_edict-language-v1.md index d5cebda..74cbcec 100644 --- a/docs/SPEC_edict-language-v1.md +++ b/docs/SPEC_edict-language-v1.md @@ -1339,7 +1339,8 @@ package-decl = "package" , package-ref , ";" ; import-decl = shape-import | lawpack-import | target-import - | core-import ; + | core-import + | capability-import ; shape-import = "use" , "shape" , string-lit , digest-clause? , "as" , ident , ";" ; @@ -1349,6 +1350,8 @@ target-import = "use" , "target" , package-ref , digest-clause? , "as" , ident , ";" ; core-import = "use" , "core" , package-ref , digest-clause? , "as" , ident , ";" ; +capability-import = "use" , "capability" , package-ref , digest-clause , + "as" , ident , ";" ; digest-clause = "digest" , digest-lit ; declaration = type-decl @@ -1383,6 +1386,7 @@ type-ref = qual-ident , type-args? | "Bytes" , bytes-refine? | "Option" , "<" , type-ref , ">" | "CapabilityRef" , "<" , type-ref , ">" + | "ExternalActionRequest" , "<" , type-ref , ">" | "List" , "<" , type-ref , "," , "max" , "=" , bound-ref , ">" | "Map" , "<" , type-ref , "," , type-ref , "," , "max" , "=" , bound-ref , ">" ; @@ -1420,6 +1424,7 @@ param = ident , ":" , type-ref ; block = "{" , statement* , "}" ; statement = let-stmt + | external-action-request-stmt | assert-stmt | require-stmt | guarantee-stmt @@ -1433,6 +1438,19 @@ let-stmt = "let" , ident , type-annotation? , "=" , let-rhs , effect-else-clause? , ";" ; let-rhs = expr | effect-branch-expr ; type-annotation = ":" , type-ref ; +external-action-request-stmt = + "request" , ident , ":" , + "ExternalActionRequest" , "<" , type-ref , ">" , "=" , + call-expr , + "input" , "schema" , digest-locked-package-ref , + "settlement" , "schema" , digest-locked-package-ref , + "authority" , expr , + "basis" , expr , + "budget" , + "maxSettlementBytes" , expr , + "maxAttempts" , expr , + "reconcile" , digest-locked-package-ref , ";" ; +digest-locked-package-ref = package-ref , digest-clause ; assert-stmt = "assert" , predicate , ";" ; require-stmt = "require" , predicate , obstruction-clause , ";" ; guarantee-stmt = "guarantee" , predicate , obstruction-clause? , ";" ; @@ -1928,6 +1946,41 @@ diagnostic that the compiler can force authors to handle. `hash` is a source-level helper, not the artifact hash primitive. The label must be a string literal so digest domains are stable and reviewable. +## External-Action Requests + +Edict expresses an external boundary crossing as deterministic data, not as an +effect performed by the compiler, lowerer, provider, or model +(`EDICT-LANG-EXTERNAL-REQUEST-001`). + +A `use capability` import binds one digest-locked, domain-specific external +operation family. Its alias is legal only as the single-argument operation call +inside an `external-action-request-stmt`. It is not a semantic-effect import and +cannot be invoked from `let` or effect-statement position. Raw filesystem, +process, network, Git, GitHub, model, and shell operation families are not +requestable language capabilities. + +Every request carries: + +- an input value and its exact schema; +- an exact settlement schema; +- runtime-valued authority scope and basis expressions; +- runtime-valued maximum settlement bytes and attempt bounds; +- an exact reconciliation law; +- a typed `ExternalActionRequest` result. + +Core and Target IR preserve the request as a separate node/collection with +fixed `awaitingSettlement` and `schemaRequired` posture. The exact operation +resource remains in the digest-bound capability closure. Target lowering must +not translate the request into a callable target intrinsic or provider import. + +Execution is outside Edict. Echo admits a durable request before an authorized +adapter touches the world, admits a schema-valid settlement afterward, and +resumes deterministic interpretation from history. Waiting is explicit program +state keyed by request identity; no native stack, continuation, callback, or +`async` host frame is serialized. Runtime path, ref, basis, budget, adapter, and +settlement checks fail closed at Echo admission rather than pretending dynamic +facts are compile-time properties. + ## Effects And Footprints An effect is a typed, imported operation emitted by a target intrinsic or diff --git a/docs/topics/README.md b/docs/topics/README.md index ebb99e1..1a8ce88 100644 --- a/docs/topics/README.md +++ b/docs/topics/README.md @@ -52,6 +52,9 @@ cargo xtask verify - [Developer Tooling](./developer-tooling/README.md): editor-facing source highlighting roles, Tree-sitter grammar source, TextMate grammar scopes, and fixture-backed tooling behavior. +- [External-Action Requests](./external-action-requests/README.md): + deterministic typed requests for Echo-coordinated external effects, with + digest-locked capability closure and no ambient execution authority. - [Fixtures](./fixtures/README.md): shared executable fixture corpus and reviewed Core golden artifact contract. - [Lawpacks](./lawpacks/README.md): lawpack import, direct-adapter, bundle diff --git a/docs/topics/core-ir/README.md b/docs/topics/core-ir/README.md index 75f7a71..5307aca 100644 --- a/docs/topics/core-ir/README.md +++ b/docs/topics/core-ir/README.md @@ -38,11 +38,12 @@ outside the schema under `fixtures/core/canonical/`. [COREIR-REQ-007] capabilities. Imports are digest-locked `resource-ref` values, but the Core module does not contain its own self-hash field. [COREIR-REQ-001] [COREIR-REQ-007] -- Core types cover bounded scalars, records, variants, options, lists, maps, and - capability references. Runtime-sized collections remain explicitly bounded at - the Core schema boundary. Integer type and value identity retains exact width - and signedness, and byte payloads carry an explicit maximum. [COREIR-REQ-002] - [COREIR-REQ-019] +- Core types cover bounded scalars, records, variants, options, lists, maps, + capability references, and typed external-action requests. Runtime-sized + collections remain explicitly bounded at the Core schema boundary. Integer + type and value identity retains exact width and signedness, and byte payloads + carry an explicit maximum. [COREIR-REQ-002] [COREIR-REQ-019] + [COREIR-REQ-020] - Core expressions and predicates are separate schema families. Expressions compute values; predicates express boolean obligations and input constraints. [COREIR-REQ-003] @@ -54,6 +55,11 @@ outside the schema under `fixtures/core/canonical/`. [COREIR-REQ-007] deterministic obstruction map. They also represent `require` nodes with distinct terminal and preserved-obstruction failure arms. [COREIR-REQ-015] [COREIR-REQ-016] +- External-action requests are distinct Core nodes, not semantic effects. They + bind exact operation, schema, scope, basis, budget, and reconciliation data + plus fixed awaiting-settlement posture. Canonical encoding requires the + operation resource to remain in the module's capability imports. + [COREIR-REQ-020] - Local references are alpha-stable: each `local-ref` carries a compiler-owned `id`, normalized `alphaName`, and type reference. Source binder spelling is not identity. [COREIR-REQ-005] @@ -95,6 +101,9 @@ outside the schema under `fixtures/core/canonical/`. [COREIR-REQ-007] sensitivity, and source alpha-renaming invariance. [COREIR-REQ-005] [COREIR-REQ-012] [COREIR-REQ-013] [COREIR-REQ-014] [COREIR-REQ-015] [COREIR-REQ-016] [COREIR-REQ-017] +- External request coverage proves byte reproducibility, mutation sensitivity, + exact capability closure, a fixed-seed 32-case identity corpus, and a bounded + 64-request module. [COREIR-REQ-020] ## Deferred diff --git a/docs/topics/core-ir/test-plan.md b/docs/topics/core-ir/test-plan.md index 55611a1..fc3b43c 100644 --- a/docs/topics/core-ir/test-plan.md +++ b/docs/topics/core-ir/test-plan.md @@ -52,6 +52,7 @@ Out of scope: | COREIR-REQ-017 | implemented | Canonical encoding and decoding accept values through the public `MAX_CANONICAL_NESTING_DEPTH` of 128 nested containers and reject the next level with stable `CanonicalErrorKind::NestingLimitExceeded`, without changing canonical Core bytes or digests below the limit. | issue #146, crates/edict-syntax/src/canonical.rs | | COREIR-REQ-018 | implemented | A Core intent may bind one optional typed basis expression. The expression participates in canonical bytes and digest when present, while the absent representation preserves existing `basis none` Core bytes. | docs/SPEC_edict-language-v1.md, docs/abi/edict-core.cddl | | COREIR-REQ-019 | implemented | Core integer type and value identity preserves exact supported width and signedness, and canonical encoding rejects values outside the declared integer domain. | docs/SPEC_edict-language-v1.md, EDICT-LANG-INTLIT-001 | +| COREIR-REQ-020 | implemented | Core represents typed external-action requests as deterministic non-callable data and requires each request operation to remain in the exact digest-locked capability-import closure before canonical identity exists. | issue #172, docs/abi/edict-core.cddl | ## Fixtures @@ -100,6 +101,7 @@ Out of scope: | COREIR-TP-025 | implemented | Canonical validation | COREIR-REQ-017 | Encoding and decoding accept exactly 128 nested empty arrays or maps and reject the 129th container even when it has no child value that would otherwise cross the depth guard. | canonical_nesting_limit_counts_empty_terminal_containers | crates/edict-syntax/src/canonical.rs | Code Lawyer regression for PR #150; prevents empty terminal containers from bypassing the public container-count bound. | | COREIR-TP-026 | implemented | Canonical encoding | COREIR-REQ-012, COREIR-REQ-014, COREIR-REQ-018 | Adding or changing an explicit Core basis expression changes canonical Core bytes and digest; the reference-encoded operation Core satisfies the published schema, while omitting basis retains the reviewed `basis none` golden bytes and digest. | explicit_basis_and_semantic_input_mutations_move_target_identity, core_root_accepts_reference_encoded_operation_basis, reviewed_core_golden_bytes_match_executable_encoder, reviewed_core_digest_matches_exact_fixture | fixtures/lang/operations/explicit-basis-u64.edict, fixtures/core/canonical/bounded-hello.core.cbor | Proves basis is semantic while retaining provider-v1 compatibility. | | COREIR-TP-027 | implemented | Canonical validation | COREIR-REQ-012, COREIR-REQ-019 | `U64::MAX` and the signed fixed-width minima canonicalize under their exact declared domains; an out-of-range value or a value incompatible with its declared width rejects before canonical identity exists, and the reference-encoded exact-width operation satisfies the published schema. | operation_prerequisite_fixture_preserves_fixed_width_basis_and_lawpack_closure, signed_fixed_width_minima_preserve_exact_domains, out_of_range_u64_and_cross_width_values_reject_before_core, canonical_core_rejects_values_outside_their_declared_integer_domain, core_root_accepts_reference_encoded_operation_basis | fixtures/lang/operations/explicit-basis-u64.edict | Prevents host-width and GraphQL-width narrowing from entering Core. | +| COREIR-TP-028 | implemented | External request | COREIR-REQ-012, COREIR-REQ-020 | Request fields survive canonical Core encoding, repeated compilation is byte-identical, authority mutations move identity, and removing the exact capability import makes the artifact unencodable. | workspace_observation_request_compiles_as_non_callable_data, request_artifacts_are_reproducible, every_request_authority_field_moves_core_identity, request_operation_must_remain_in_core_and_target_capability_closure | crates/edict-syntax/tests/external_action_requests.rs | Request construction performs no external effect. | ## Determinism Obligations diff --git a/docs/topics/external-action-requests/README.md b/docs/topics/external-action-requests/README.md new file mode 100644 index 0000000..72ff6a9 --- /dev/null +++ b/docs/topics/external-action-requests/README.md @@ -0,0 +1,101 @@ +# External-Action Requests + +Status: current HEAD contract. + +Edict can construct a typed request for one external operation without +performing that operation. The request is deterministic compiler data. Echo +later admits and records it, an authorized adapter performs the boundary +crossing, and a schema-validated settlement may resume the program. This topic +owns request construction only. + +## Source Surface + +A requestable operation family enters source through a digest-locked +`capability` import: + +```edict +use capability workspace.snapshot.observe@1 + digest "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + as snapshot; +``` + +The alias is callable only in a `request` statement: + +```edict +request pending: ExternalActionRequest> = + snapshot(input.payload) + input schema workspace.snapshot.input@1 digest "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" + settlement schema workspace.snapshot.settlement@1 digest "sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" + authority input.scope + basis input.basis + budget maxSettlementBytes input.maxSettlementBytes maxAttempts input.maxAttempts + reconcile workspace.snapshot.reconcile@1 digest "sha256:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"; +``` + +The operation takes one typed input. Scope, basis, and both budgets are ordinary +typed expressions, so their runtime values survive compilation for Echo +admission. Schemas and the reconciliation law are exact digest-locked +resources. [EXTREQ-REQ-001] [EXTREQ-REQ-006] + +## Core And Target IR + +Core emits `ExternalActionRequest` as a distinct node containing: + +- compiler-owned binding identity; +- exact operation resource; +- input and settlement type coordinates; +- exact input and settlement schemas; +- input, authority-scope, and basis expressions; +- maximum settlement bytes and attempt expressions; +- exact reconciliation law; +- fixed `awaitingSettlement` state and `schemaRequired` settlement posture. + +The operation resource must equal one `capability` import. Canonical Core +encoding fails closed when that closure is missing. [EXTREQ-REQ-002] +[EXTREQ-REQ-004] + +Target lowering copies the node into `externalActionRequests`. It never turns +the operation into a target step or `targetIntrinsic`. The Target IR semantic +closure binds the exact capability resource alongside the source Core and any +lawpacks; canonical Target IR encoding refuses a request whose operation is +absent from that closure. [EXTREQ-REQ-003] [EXTREQ-REQ-004] + +Equivalent source and compiler facts produce byte-identical Core and Target IR. +Every operation, schema, input, scope, basis, budget, or reconciliation change +participates in canonical identity. [EXTREQ-REQ-005] + +## Authority Boundary + +Request authority is not performance authority: + +- a capability alias cannot be invoked as an ordinary semantic effect; +- raw `filesystem`, `process`, `network`, `git`, `github`, `model`, and `shell` + operation families are rejected; +- compiling and lowering perform no I/O; +- the compiler/provider component interface gains no callable import; +- dynamic path, ref, basis, budget, adapter, and settlement constraints remain + Echo admission obligations. + +The first admitted family is domain-specific +`workspace.snapshot.observe@1`, not ambient filesystem access. +[EXTREQ-REQ-003] [EXTREQ-REQ-006] [EXTREQ-REQ-007] + +## Waiting And Settlement + +The request binding and Target IR request id identify explicit +`awaitingSettlement` program state. No native stack, continuation, callback, or +`async` host frame is serialized. This Edict slice does not define settlement +execution or resumption; Echo owns durable request, claim, settlement, recovery, +and replay semantics. + +## Deferred + +- Echo admission and durable settlement of the compiler-emitted request; +- bounded workspace-observation adapter execution; +- settlement-driven deterministic resumption; +- basis-bound validated patch application; +- Git, GitHub, process, network, timer, and model adapters; +- the autonomous delivery loop. + +The verification matrix, fixed seed, and stress bound are in +[test-plan.md](./test-plan.md). diff --git a/docs/topics/external-action-requests/test-plan.md b/docs/topics/external-action-requests/test-plan.md index 443aaf2..dc01a56 100644 --- a/docs/topics/external-action-requests/test-plan.md +++ b/docs/topics/external-action-requests/test-plan.md @@ -1,6 +1,6 @@ # External-Action Requests Test Plan -Status: planned contract for issue #172. +Status: current verification design for issue #172. ## Scope @@ -28,14 +28,14 @@ Out of scope: | ID | Status | Requirement | Source | | --- | --- | --- | --- | -| EXTREQ-REQ-001 | planned | Source syntax binds each requestable operation through one digest-locked `capability` import and carries exact input-schema, settlement-schema, authority-scope, basis, budget, input, and reconciliation-law values. | issue #172 | -| EXTREQ-REQ-002 | planned | Core represents external-action requests as data with compiler-owned binding identity, typed input and settlement coordinates, explicit `awaitingSettlement` state, and required schema admission. | issue #172 | -| EXTREQ-REQ-003 | planned | Target IR preserves external-action requests separately from callable target steps and target intrinsics. | issue #172 | -| EXTREQ-REQ-004 | planned | Core and Target IR semantic closure bind the exact capability resource; undeclared operations and floating capability imports fail closed. | issue #172 | -| EXTREQ-REQ-005 | planned | Equivalent admitted source and compiler facts produce byte-identical Core and Target IR; every request authority or meaning mutation moves the corresponding digest. | issue #172 | -| EXTREQ-REQ-006 | planned | Runtime-valued authority scope, basis, and budget expressions survive compilation for Echo admission; Edict performs no external action while compiling or lowering them. | issue #172 | -| EXTREQ-REQ-007 | planned | Raw filesystem, process, network, Git, GitHub, model, and shell operation families are outside the requestable capability vocabulary. | issue #172 | -| EXTREQ-REQ-008 | planned | Request construction remains bounded under a fixed-seed mutation corpus and a 64-request stress module. | issue #172 | +| EXTREQ-REQ-001 | implemented | Source syntax binds each requestable operation through one digest-locked `capability` import and carries exact input-schema, settlement-schema, authority-scope, basis, budget, input, and reconciliation-law values. | issue #172 | +| EXTREQ-REQ-002 | implemented | Core represents external-action requests as data with compiler-owned binding identity, typed input and settlement coordinates, explicit `awaitingSettlement` state, and required schema admission. | issue #172 | +| EXTREQ-REQ-003 | implemented | Target IR preserves external-action requests separately from callable target steps and target intrinsics. | issue #172 | +| EXTREQ-REQ-004 | implemented | Core and Target IR semantic closure bind the exact capability resource; undeclared operations and floating capability imports fail closed. | issue #172 | +| EXTREQ-REQ-005 | implemented | Equivalent admitted source and compiler facts produce byte-identical Core and Target IR; every request authority or meaning mutation moves the corresponding digest. | issue #172 | +| EXTREQ-REQ-006 | implemented | Runtime-valued authority scope, basis, and budget expressions survive compilation for Echo admission; Edict performs no external action while compiling or lowering them. | issue #172 | +| EXTREQ-REQ-007 | implemented | Raw filesystem, process, network, Git, GitHub, model, and shell operation families are outside the requestable capability vocabulary. | issue #172 | +| EXTREQ-REQ-008 | implemented | Request construction remains bounded under a fixed-seed mutation corpus and a 64-request stress module. | issue #172 | ## Fixtures @@ -47,16 +47,17 @@ Out of scope: ## Cases -| ID | Status | Category | Requirement | Oracle | Evidence | Notes | -| --- | --- | --- | --- | --- | --- | --- | -| EXTREQ-TP-001 | planned | Golden path | EXTREQ-REQ-001, EXTREQ-REQ-002, EXTREQ-REQ-003 | One workspace observation request parses, compiles, and lowers with exact structured fields, awaiting posture, and zero callable target steps. | `workspace_observation_request_compiles_as_non_callable_data` | The target owns later admission and execution. | -| EXTREQ-TP-002 | planned | Closure guard | EXTREQ-REQ-004 | Missing operation import, floating capability digest, and operation-alias substitution reject with stable compiler or canonical error kinds. | `undeclared_or_floating_operation_families_fail_closed` | A source alias is not authority by itself. | -| EXTREQ-TP-003 | planned | Authority guard | EXTREQ-REQ-003, EXTREQ-REQ-007 | Direct invocation of a capability import fails, and raw ambient operation families are unrepresentable. | `capability_import_cannot_be_called_as_an_effect`, `ambient_operation_families_are_not_requestable` | Request authority and performance authority remain distinct. | -| EXTREQ-TP-004 | planned | Dynamic boundary | EXTREQ-REQ-006 | Scope, basis, maximum settlement bytes, and attempt count remain Core expressions sourced from admitted input. | `dynamic_admission_values_survive_without_compile_time_execution` | Echo owns value-instance admission. | -| EXTREQ-TP-005 | planned | Determinism | EXTREQ-REQ-005 | Repeated compilation and lowering produce identical canonical bytes and digests. | `request_artifacts_are_reproducible` | No clock, randomness, filesystem, or network input enters the compiler. | -| EXTREQ-TP-006 | planned | Mutation sensitivity | EXTREQ-REQ-005 | Operation, input schema, settlement schema, authority scope, basis, budget, input, and reconciliation mutations move Core identity. | `every_request_authority_field_moves_core_identity` | Each field is request-authoritative. | -| EXTREQ-TP-007 | planned | Property | EXTREQ-REQ-005, EXTREQ-REQ-008 | A fixed-seed 32-case capability-digest corpus is reproducible and collision-free within the corpus. | `fixed_seed_request_identity_corpus_is_deterministic` | Seed is recorded in the fixture table. | -| EXTREQ-TP-008 | planned | Stress | EXTREQ-REQ-008 | A generated module with 64 request declarations emits 64 Core requests and 64 Target IR requests with zero callable steps. | `sixty_four_requests_remain_bounded_non_callable_data` | Bound is fixed for CI. | +| ID | Status | Category | Requirement | Oracle | Evidence | Fixtures | Notes | +| --- | --- | --- | --- | --- | --- | --- | --- | +| EXTREQ-TP-001 | implemented | Golden path | EXTREQ-REQ-001, EXTREQ-REQ-002, EXTREQ-REQ-003 | One workspace observation request parses, compiles, and lowers with exact structured fields, awaiting posture, and zero callable target steps. | workspace_observation_request_compiles_as_non_callable_data | crates/edict-syntax/tests/external_action_requests.rs | The target owns later admission and execution. | +| EXTREQ-TP-002 | implemented | Closure guard | EXTREQ-REQ-004 | Missing operation import, floating capability digest, and operation-alias substitution reject with stable compiler or canonical error kinds. | undeclared_or_floating_operation_families_fail_closed | crates/edict-syntax/tests/external_action_requests.rs | A source alias is not authority by itself. | +| EXTREQ-TP-003 | implemented | Authority guard | EXTREQ-REQ-003, EXTREQ-REQ-007 | Direct invocation of a capability import fails, and raw ambient operation families are unrepresentable. | capability_import_cannot_be_called_as_an_effect, ambient_operation_families_are_not_requestable | crates/edict-syntax/tests/external_action_requests.rs | Request authority and performance authority remain distinct. | +| EXTREQ-TP-004 | implemented | Dynamic boundary | EXTREQ-REQ-006 | Scope, basis, maximum settlement bytes, and attempt count remain Core expressions sourced from admitted input. | dynamic_admission_values_survive_without_compile_time_execution | crates/edict-syntax/tests/external_action_requests.rs | Echo owns value-instance admission. | +| EXTREQ-TP-005 | implemented | Determinism | EXTREQ-REQ-005 | Repeated compilation and lowering produce identical canonical bytes and digests. | request_artifacts_are_reproducible | crates/edict-syntax/tests/external_action_requests.rs | No clock, randomness, filesystem, or network input enters the compiler. | +| EXTREQ-TP-006 | implemented | Mutation sensitivity | EXTREQ-REQ-005 | Operation, input schema, settlement schema, authority scope, basis, budget, input, and reconciliation mutations move Core identity. | every_request_authority_field_moves_core_identity | crates/edict-syntax/tests/external_action_requests.rs | Each field is request-authoritative. | +| EXTREQ-TP-007 | implemented | Property | EXTREQ-REQ-005, EXTREQ-REQ-008 | A fixed-seed 32-case capability-digest corpus is reproducible and collision-free within the corpus. | fixed_seed_request_identity_corpus_is_deterministic | crates/edict-syntax/tests/external_action_requests.rs | Seed is recorded in the fixture table. | +| EXTREQ-TP-008 | implemented | Stress | EXTREQ-REQ-008 | A generated module with 64 request declarations emits 64 Core requests and 64 Target IR requests with zero callable steps. | sixty_four_requests_remain_bounded_non_callable_data | crates/edict-syntax/tests/external_action_requests.rs | Bound is fixed for CI. | +| EXTREQ-TP-009 | implemented | Closure guard | EXTREQ-REQ-004 | Canonical Core and Target IR encoding reject request operations removed from their exact capability closure. | request_operation_must_remain_in_core_and_target_capability_closure | crates/edict-syntax/tests/external_action_requests.rs | Manual artifact construction cannot bypass compiler-owned closure. | ## Determinism Obligations diff --git a/docs/topics/providers/README.md b/docs/topics/providers/README.md index 9f62eab..74813e9 100644 --- a/docs/topics/providers/README.md +++ b/docs/topics/providers/README.md @@ -128,6 +128,11 @@ callable or unknown import. The generated component's exact type-only conveys types rather than a host capability. No WASI linker or callable host function is installed. +Source `use capability` declarations and Core external-action request nodes do +not alter this provider boundary. They are canonical request data passed through +Core and Target IR; they do not become component imports, host functions, or +provider-owned execution authority. + The host owns one immutable Wasmtime engine and creates a fresh store for every invocation. The prepared component, opaque validated request proof, and concrete schema registry must share one manifest and validator authority, and a prepared diff --git a/docs/topics/providers/test-plan.md b/docs/topics/providers/test-plan.md index 4e54f2c..10085aa 100644 --- a/docs/topics/providers/test-plan.md +++ b/docs/topics/providers/test-plan.md @@ -63,7 +63,7 @@ Out of scope: | PROVIDERS-REQ-019 | implemented | Every artifact domain admitted by one provider manifest is bound exactly once to a digest-locked generated schema artifact, schema format, and root rule. Missing, duplicate, ambiguous, unsupported, or provenance-invalid bindings reject before component invocation. | issue #145, EDICT-ABI-PROVIDER-AUTHORITY-001 | | PROVIDERS-REQ-020 | implemented | The concrete provider artifact-schema registry is constructed completely from a validated manifest plus the exact manifest-bound closure of explicit in-memory schema bytes. Construction verifies schema digests, compiles and totality-checks every selected root before invocation, proves required-domain closure, and performs no discovery, filesystem, network, environment, clock, randomness, or mutable-global access. | issue #145, EDICT-ABI-PROVIDER-AUTHORITY-001 | | PROVIDERS-REQ-021 | implemented | Every registered domain performs real schema-instance validation over canonical CBOR. Unsupported domains and schema mismatches remain stable structured failures, and construction and validation results are independent of input insertion order. | issue #145, EDICT-ABI-PROVIDER-AUTHORITY-001 | -| PROVIDERS-REQ-022 | implemented | The Wasmtime host uses one explicitly configured component-model engine and a fresh store for every invocation. It permits only the frozen protocol's type-only instance import and registers no callable WASI, filesystem, network, environment, clock, randomness, registry, or other capability-bearing import. | issue #145, EDICT-ABI-PROVIDER-HOST-001 | +| PROVIDERS-REQ-022 | implemented | The Wasmtime host uses one explicitly configured component-model engine and a fresh store for every invocation. It permits only the frozen protocol's type-only instance import and registers no callable WASI, filesystem, network, environment, clock, randomness, registry, or other capability-bearing import. Source external-action capability references remain canonical request data and never become provider imports. | issue #145, issue #172, EDICT-ABI-PROVIDER-HOST-001 | | PROVIDERS-REQ-023 | implemented | The host accepts only opaque validated request proofs, preflights an exact WIT-logical request byte bound and request-authorized output ceiling, enforces explicit fuel, lifting, and store resource limits, invokes the typed frozen lowerer or verifier export, and exposes an artifact only after the pure response validator admits the complete result. | issue #145, issue #146, EDICT-ABI-PROVIDER-HOST-001 | | PROVIDERS-REQ-024 | implemented | Digest, contract, decode, instantiation, fuel, resource, input, output, diagnostic, response-lifting, trap, malformed-response, response-validation, and host-invariant failures use stable host-owned kinds. Wasmtime implementation details remain bounded diagnostics and never enter Edict's public failure identity. | issue #145, EDICT-ABI-PROVIDER-HOST-001 | | PROVIDERS-REQ-025 | implemented | Provider replay executes the same prepared component, validated request, concrete schema authority, and limits twice through independent fresh stores. Exact sealed outcomes must be equal; host failures compare by stable kind, phase, and structured validation report while excluding opaque engine diagnostics. A disposition, completed-outcome, or stable-failure-identity difference returns a dedicated structured replay mismatch. | issue #147 | diff --git a/docs/topics/syntax/README.md b/docs/topics/syntax/README.md index 82363dc..b2a372b 100644 --- a/docs/topics/syntax/README.md +++ b/docs/topics/syntax/README.md @@ -36,8 +36,9 @@ programs, prove bounds, or itself lower to Core IR. [SYNTAX-REQ-001] - A module starts with one `package` declaration, then zero or more imports, then declarations. Package and import versions preserve source-significant version spelling, including `_beta` style labels. [SYNTAX-REQ-002] -- Supported imports are `shape`, `lawpack`, `target`, and `core`; `capability` - import syntax is rejected in minimal-v1. Digest clauses accept only +- Supported imports are `shape`, `lawpack`, `target`, `core`, and `capability`. + A capability import names a digest-bound external operation family for + `request`; it is not a callable effect import. Digest clauses accept only `sha256:` plus 64 hex characters. [SYNTAX-REQ-003] - Type declarations parse record types, refined `String`, max-only `Bytes`, `Option`, `CapabilityRef`, bounded `List`, bounded `Map`, enum declarations, @@ -47,9 +48,11 @@ programs, prove bounds, or itself lower to Core IR. [SYNTAX-REQ-001] - Intent declarations parse parameters, return type, clause surface, statement blocks, and expression bodies. Clause requiredness is semantic validation, not parser validation. [SYNTAX-REQ-006] -- Statements parse `let`, `return`, `require`, `guarantee`, `assert`, effect - call statements, `if` / `else if` / `else`, and bounded `for`. Effect - positions must be calls. [SYNTAX-REQ-007] +- Statements parse `let`, `request`, `return`, `require`, `guarantee`, `assert`, + effect call statements, `if` / `else if` / `else`, and bounded `for`. + External-action requests carry exact schemas, scope, basis, budgets, and + reconciliation law. Effect and request operation positions must be calls. + [SYNTAX-REQ-007] - Expressions parse the full Phase 1 precedence chain, calls, type-calls, field access, record literals, booleans, digest literals, pure ternary `if ... then ... else`, branch-yield conditional effects in `let` right-hand diff --git a/docs/topics/syntax/test-plan.md b/docs/topics/syntax/test-plan.md index a654311..001c542 100644 --- a/docs/topics/syntax/test-plan.md +++ b/docs/topics/syntax/test-plan.md @@ -29,11 +29,11 @@ Out of scope: | --- | --- | --- | --- | | SYNTAX-REQ-001 | implemented | `parse_module` returns source AST only; later phases own semantic validation and lowering. | crates/edict-syntax/src/lib.rs | | SYNTAX-REQ-002 | implemented | Module/package/import coordinates parse and preserve source-significant version spelling. | docs/SPEC_edict-language-v1.md | -| SYNTAX-REQ-003 | implemented | Imports parse supported kinds, validate digest literals, and reject minimal-v1 unsupported `capability`. | docs/SPEC_edict-language-v1.md | +| SYNTAX-REQ-003 | implemented | Imports parse supported kinds, including digest-bound external-operation `capability` references, and validate digest literals. | docs/SPEC_edict-language-v1.md | | SYNTAX-REQ-004 | implemented | Type declarations parse Phase 1 type surface and reject empty enum declarations. | docs/SPEC_edict-language-v1.md | | SYNTAX-REQ-005 | implemented | Integer suffixes remain source-significant in expression literals and static bounds. | docs/SPEC_edict-language-v1.md | | SYNTAX-REQ-006 | implemented | Intent declaration syntax parses parameters, return type, clauses, and blocks; semantic requiredness is deferred. | docs/SPEC_edict-language-v1.md | -| SYNTAX-REQ-007 | implemented | Statement syntax includes effect call positions, guards, control flow, and bounded loops. | docs/SPEC_edict-language-v1.md | +| SYNTAX-REQ-007 | implemented | Statement syntax includes external-action requests, effect call positions, guards, control flow, and bounded loops. | docs/SPEC_edict-language-v1.md | | SYNTAX-REQ-008 | implemented | Expression syntax includes precedence, records, literals, conditionals, variants, and match. | docs/SPEC_edict-language-v1.md | | SYNTAX-REQ-009 | implemented | Reserved keywords reject in bare-name positions while remaining legal after `.`. | docs/SPEC_edict-language-v1.md | | SYNTAX-REQ-010 | implemented | Negative tests assert stable error kinds, not diagnostic prose or incidental output. | crates/edict-syntax/src/parser.rs | @@ -56,7 +56,7 @@ Out of scope: | --- | --- | --- | --- | --- | --- | --- | --- | | SYNTAX-TP-001 | implemented | Golden path | SYNTAX-REQ-001 | Public API returns a `Module` with expected package/declaration shape. | bounded_hello_parses | fixtures/lang/bounds/bounded-hello.edict | Source AST, not Core IR. | | SYNTAX-TP-002 | implemented | Golden path | SYNTAX-REQ-002 | Exact version strings match source spelling. | multi_part_package_version, package_versions_preserve_underscores, import_versions_preserve_underscore_labels | - | Covers `_beta` labels. | -| SYNTAX-TP-003 | implemented | Error handling | SYNTAX-REQ-003 | Invalid digest and unsupported import syntax produce stable error kinds. | import_digest_literals_are_validated, capability_imports_are_rejected_in_v1 | - | Digest oracle is fixed `sha256:` length/hex rule. | +| SYNTAX-TP-003 | implemented | Import boundary | SYNTAX-REQ-003 | Invalid digests reject with a stable error kind, while a digest-bound capability import preserves its distinct AST kind and digest. | import_digest_literals_are_validated, capability_imports_parse_as_external_operation_references | - | Capability import is request authority, not performance authority. | | SYNTAX-TP-004 | implemented | Golden path | SYNTAX-REQ-004 | Type AST nodes match expected records, bytes, variants, enums, and bounds. | bounded_hello_parses, bytes_accept_coordinate_bounds, enum_decl_parses, variant_type_with_and_without_payloads_parses | fixtures/lang/bounds/bounded-hello.edict | Structural AST equality. | | SYNTAX-TP-005 | implemented | Error handling | SYNTAX-REQ-004 | Empty enums reject with `ParseErrorKind::EmptyEnum`. | empty_enum_and_empty_obstruction_maps_reject | - | Parser-level syntactic emptiness. | | SYNTAX-TP-006 | implemented | Edge case | SYNTAX-REQ-005 | Integer suffix is preserved in `Expr::Int` and `BoundRef::Int`. | typed_integer_suffix, bound_integer_suffixes_are_preserved | - | Source-significant suffix oracle. | @@ -77,6 +77,7 @@ Out of scope: | SYNTAX-TP-021 | implemented | Semantic validation | SYNTAX-REQ-011 | The source/surface validator returns stable diagnostic kinds for context-free source errors and accepts unresolved downstream facts. | validate_module_remains_surface_stage_compatibility_alias, surface_validation_defers_import_and_name_resolution, surface_validation_defers_contextual_typing_and_loop_bound_proof, surface_validation_defers_obstruction_exhaustiveness | - | Owned by the semantic-validation shelf. | | SYNTAX-TP-022 | planned | Golden artifact | SYNTAX-REQ-012 | Full source lowering emits byte-stable canonical artifacts. | - | - | Initial Core golden artifacts exist in the Core IR shelf; full source-language coverage remains planned. | | SYNTAX-TP-023 | implemented | Syntax guard | SYNTAX-REQ-013 | `else continue obstructed { reason: ... }` parses as a distinct require-else arm, while `else continueInObstructedStrand(...)` remains a terminal obstruction target. | continue_obstructed_source_arm_parses, helper_shaped_continue_in_obstructed_strand_is_terminal, stale_basis_obstruction_strand_fixture_parses | crates/edict-syntax/tests/parse_review_regressions.rs, fixtures/obstruction-strands/v0/stale-basis/source.edict | Prevents hidden control-flow semantics in ordinary obstruction constructors. | +| SYNTAX-TP-024 | implemented | Golden path | SYNTAX-REQ-003, SYNTAX-REQ-007 | A request statement parses its typed binding, single operation call, schemas, authority scope, basis, two budgets, and reconciliation resource before source-to-Core compilation. | workspace_observation_request_compiles_as_non_callable_data | crates/edict-syntax/tests/external_action_requests.rs | Execution and settlement remain outside the parser. | ## Determinism Obligations diff --git a/docs/topics/target-ir/README.md b/docs/topics/target-ir/README.md index e01e5d6..7d80d78 100644 --- a/docs/topics/target-ir/README.md +++ b/docs/topics/target-ir/README.md @@ -14,8 +14,8 @@ The current target IR implementation is deliberately narrow: - selected target profile: `echo.dpo@1` or `gitwarp.ref_crdt@1`; - selected Target IR artifact domain: `echo.span-ir/v1` or `gitwarp.commit-reducer-ir/v1`; -- selected source/Core shape: the first supported effectful Core effect node - and Echo `require` guard requirements; +- selected source/Core shape: the first supported effectful Core effect node, + Echo `require` guard requirements, and typed external-action request data; - selected outcome: a deterministic target-owned review artifact with canonical `edict.canonical-cbor/v1` bytes and a reviewed `edict.target-ir.artifact/v1` digest; @@ -24,7 +24,7 @@ The current target IR implementation is deliberately narrow: The `edict_syntax` crate exposes `lower_to_target_ir`, `TargetIrLoweringFacts`, `TargetLoweringReport`, `TargetIrArtifact`, -`TargetIrSemanticClosure`, +`TargetIrSemanticClosure`, `TargetIrExternalActionRequest`, `encode_target_ir_artifact`, `digest_target_ir_artifact`, and stable `TargetLoweringFailureKind` values. The lowerer consumes an already-built `CoreModule` and explicit target-lowering facts supplied by the caller. It does @@ -80,6 +80,12 @@ becomes a deterministic Target IR step that records: - the structured Core input expression; - sorted obstruction failure keys and their structured obstruction arm values. +Each supported Core external-action request instead becomes one +`externalActionRequests` entry. It preserves the operation, input and settlement +types, schemas, input, scope, basis, budgets, reconciliation law, compiler-owned +binding, deterministic request id, and fixed awaiting-settlement posture. It +does not emit a target step or target intrinsic. + For the supported Echo slice, each supported Core `require` node before any target step becomes a deterministic Target IR requirement that records: @@ -107,14 +113,15 @@ expression for the supported slice. This records authored basis, preconditions, evaluation limits, guard dispositions, and success-output semantics without resolving a runtime basis, executing Echo, or admitting a bundle. -When any intent has an explicit basis or the Core module imports a lawpack, the -artifact carries a `TargetIrSemanticClosure`. The closure binds the exact -canonical Core coordinate/digest and a coordinate-keyed, lowercase -digest-locked lawpack set. Equivalent lawpack order and duplicate identical -references canonicalize to the same Target IR identity; conflicting resources, -an empty source Core coordinate, an unidentifiable Core, or a basis-bearing -artifact without its closure rejects before Target IR identity exists. -[TIR-REQ-015] +When any intent has an explicit basis, the Core module imports a lawpack, or the +Core module imports a requestable capability, the artifact carries a +`TargetIrSemanticClosure`. The closure binds the exact canonical Core +coordinate/digest plus coordinate-keyed, lowercase digest-locked lawpack and +capability sets. Equivalent resource order and duplicate identical references +canonicalize to the same Target IR identity. Conflicting resources, an empty +source Core coordinate, an unidentifiable Core, a basis-bearing artifact without +its closure, or a request operation absent from the capability closure rejects +before Target IR identity exists. [TIR-REQ-015] [TIR-REQ-017] Canonical Target IR uses an intentional artifact-envelope value model rather than Rust struct serialization. The reviewed digest is SHA-256 over canonical @@ -128,10 +135,11 @@ The canonical value includes the artifact's own domain, digest-locked target profile resource, non-empty source Core coordinate, optional semantic closure, sorted intent map, optional explicit basis expressions, input constraints, Core evaluation budget, source-ordered requirements, requirement predicates and -failure dispositions, source-ordered target steps, sorted obstruction failure -keys and arms, and structured Core result expression. Target profile and -semantic-closure digests are strict artifact references: missing digests and -non-lowercase `sha256:<64 hex>` review strings reject before hashing. +failure dispositions, source-ordered target steps, source-ordered external +requests, sorted obstruction failure keys and arms, and structured Core result +expression. Target profile and semantic-closure digests are strict artifact +references: missing digests and non-lowercase `sha256:<64 hex>` review strings +reject before hashing. Reviewed Echo and git-warp Target IR byte/digest goldens live under `fixtures/target-ir/canonical/`. `cargo xtask target-ir-goldens --check` @@ -173,14 +181,15 @@ with an unsupported ABI rejects with floating imports rejects with `TargetLoweringFailureKind::UndigestedCoreImport`. Supplying unsupported Core capability flags rejects with `TargetLoweringFailureKind::UnsupportedCoreCapability`. Supplying Core nodes -outside the supported effect and Echo requirement shapes rejects with +outside the supported effect, external-request, and Echo requirement shapes +rejects with `TargetLoweringFailureKind::UnsupportedCoreNode`. Supplying a target-specific Core feature that the selected target does not support rejects with `TargetLoweringFailureKind::UnsupportedTargetFeature`. Missing or ambiguous effect lowering facts, non-Echo target intrinsics, missing operation-profile support, and obstruction keys absent from the selected target facts also reject -before any artifact is emitted. A Core intent with no target-owned requirements -or steps, or a Core module with no intents, rejects with +before any artifact is emitted. A Core intent with no target-owned requirements, +steps, or external requests, or a Core module with no intents, rejects with `TargetLoweringFailureKind::NoTargetSteps`. Duplicate target-lowering facts are ambiguous only when they match an effect used by the Core module being lowered; unrelated duplicate facts do not block the supported artifact. @@ -196,6 +205,7 @@ The following are not implemented by this slice: - git-warp runtime execution, commit object creation, and CRDT reducer verification; - Echo runtime receipts for first-class resumable obstruction strands; +- Echo request admission, adapter execution, and settlement resumption; - additional target profiles beyond Echo and git-warp; - v2 chained or composite adapter resolution. diff --git a/docs/topics/target-ir/test-plan.md b/docs/topics/target-ir/test-plan.md index 7b6e8f0..51a0e28 100644 --- a/docs/topics/target-ir/test-plan.md +++ b/docs/topics/target-ir/test-plan.md @@ -53,6 +53,7 @@ Out of scope: | TIR-REQ-014 | implemented | `edict-target-ir.cddl` defines `target-ir-artifact` from the executable canonical value shape and is published in the self-contained provider contract pack. | issue #161, EDICT-ABI-PROVIDER-CONTRACT-PACK-001 | | TIR-REQ-015 | implemented | Target IR preserves an explicit Core basis expression and, when an operation carries an explicit basis or lawpack import, binds a semantic closure containing the exact source Core digest and the sorted digest-locked lawpack resource set. Existing no-basis/no-lawpack Target IR bytes remain unchanged. | docs/SPEC_edict-language-v1.md, docs/design/canonical-target-ir-v0.11.md | | TIR-REQ-016 | implemented | A validated direct lawpack adapter derives the exact target profile, Target IR domain, operation-profile support, effect intrinsic, and named-failure support used for Target IR lowering; callers do not hand-assemble `TargetIrLoweringFacts`. | issue #169, docs/abi/edict-lawpack-adapter.cddl | +| TIR-REQ-017 | implemented | Target IR preserves typed external-action requests in a separate non-callable collection and binds each exact request operation in the semantic capability closure. | issue #172, docs/abi/edict-target-ir.cddl | ## Fixtures @@ -105,6 +106,7 @@ Out of scope: | TIR-TP-032 | implemented | Mutation sensitivity | TIR-REQ-008, TIR-REQ-015 | Changing the basis expression, source Core meaning, lawpack coordinate, or lawpack digest moves the Target IR digest, while equivalent lawpack construction order does not. | explicit_basis_and_semantic_input_mutations_move_target_identity, semantic_closure_lawpack_set_is_order_invariant | fixtures/lang/operations/explicit-basis-u64.edict | Prevents Target IR identity from floating over operation semantic inputs. | | TIR-TP-033 | implemented | Compatibility | TIR-REQ-009, TIR-REQ-015 | Existing reviewed Echo and git-warp fixtures with `basis none` and no lawpack import retain byte-identical Target IR and digest goldens. | target_ir_goldens_match_executable_encoder | fixtures/target-ir/canonical/echo-effectful.target-ir.cbor, fixtures/target-ir/canonical/gitwarp-append.target-ir.cbor | Provider-v1 compatibility artifacts remain stable alongside the new operation prerequisites. | | TIR-TP-034 | implemented | Integration | TIR-REQ-001, TIR-REQ-002, TIR-REQ-015, TIR-REQ-016 | Hello Echo lowers from exact source and lawpack resources through the selected direct adapter into `echo.span-ir/v1`, preserving the explicit basis, create effect, create-if-absent intrinsic, typed failure arm, and semantic closure. | hello_echo_source_compiles_to_echo_target_ir_from_exact_lawpack_adapter | fixtures/lawpack/hello-echo/README.md | Target IR remains distinct from the later Echo executable package. | +| TIR-TP-035 | implemented | External request | TIR-REQ-001, TIR-REQ-006, TIR-REQ-008, TIR-REQ-017 | Workspace observation requests lower into `externalActionRequests` with exact capability closure and zero callable target steps or intrinsics; removing the operation from the closure makes canonical encoding fail closed. | workspace_observation_request_compiles_as_non_callable_data, request_operation_must_remain_in_core_and_target_capability_closure | crates/edict-syntax/tests/external_action_requests.rs | Echo owns later request admission, execution, and settlement. | ## Determinism Obligations From 0fb8b9fa7a0d1f0e65ef20b27b123984839877c8 Mon Sep 17 00:00:00 2001 From: James Ross Date: Wed, 29 Jul 2026 15:56:54 -0700 Subject: [PATCH 04/11] docs: record typed external action requests --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 47ce11d..3345354 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,13 @@ versions still track specification maturity rather than a released product. ### Added +- Added typed external-action request values without adding external execution + authority to Edict. Digest-locked capability imports and `request` statements + preserve exact operation, schema, scope, basis, budget, input, reconciliation, + and awaiting-settlement data through canonical Core and Target IR. Operation + families remain bound in the semantic capability closure, raw ambient + filesystem/process/network/Git/GitHub/model/shell families reject, target + steps remain non-callable, and the provider seam gains no host import. - Added the compiler-owned `edict.result-projection/v1` artifact for preserving typed application results across the Echo target boundary. The bounded, canonical projection names only declared application input and From 905ae24959612111efdd5607706134f7b1ef8f61 Mon Sep 17 00:00:00 2001 From: James Ross Date: Wed, 29 Jul 2026 15:59:51 -0700 Subject: [PATCH 05/11] fix: isolate request capabilities from effects --- crates/edict-syntax/src/compiler.rs | 25 +++++++++++++++++++ .../tests/external_action_requests.rs | 10 +++++--- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/crates/edict-syntax/src/compiler.rs b/crates/edict-syntax/src/compiler.rs index ea84684..ea57306 100644 --- a/crates/edict-syntax/src/compiler.rs +++ b/crates/edict-syntax/src/compiler.rs @@ -1325,6 +1325,17 @@ impl<'a> TypeChecker<'a> { } fn check_effect_profile(&mut self, intent: &ResolvedIntent, call: &Expr, span: Span) -> bool { + if let Some(alias) = self.capability_import_alias_for_call(call) { + self.errors.push(error( + CompilerStage::TypeCheck, + CompilerErrorKind::UnsupportedSourceShape, + format!( + "external-action capability `{alias}` is request data, not a semantic effect" + ), + span, + )); + return false; + } let Some(effect) = effect_coordinate(call) else { self.unsupported_stmt(span, "effect call"); return false; @@ -1366,6 +1377,20 @@ impl<'a> TypeChecker<'a> { } } + fn capability_import_alias_for_call(&self, call: &Expr) -> Option { + let Expr::Call { callee, .. } = call else { + return None; + }; + let alias = plain_path_root(callee)?; + self.resolved + .imports + .iter() + .any(|import| { + import.kind == CoreImportKind::Capability && import.alias.as_deref() == Some(alias) + }) + .then(|| alias.to_owned()) + } + fn effect_binding_shape(&mut self, ty: Option<&TypeRef>, span: Span) -> Option { let Some(ty) = ty else { self.errors.push(error( diff --git a/crates/edict-syntax/tests/external_action_requests.rs b/crates/edict-syntax/tests/external_action_requests.rs index de6e105..8c0da1d 100644 --- a/crates/edict-syntax/tests/external_action_requests.rs +++ b/crates/edict-syntax/tests/external_action_requests.rs @@ -9,7 +9,7 @@ use edict_syntax::{ compile_to_core, decode_canonical_cbor, digest_core_module, encode_core_module, encode_target_ir_artifact, lower_to_target_ir, parse_module, CanonicalValue, CompilerContext, CompilerErrorKind, CoreBudget, ResourceRef, TargetIrLoweringFacts, TargetLoweringStatus, - ECHO_DPO_TARGET_PROFILE, ECHO_SPAN_IR_DOMAIN, + WriteClass, ECHO_DPO_TARGET_PROFILE, ECHO_SPAN_IR_DOMAIN, }; const OPERATION_DIGEST: char = 'a'; @@ -279,8 +279,12 @@ intent observe(input: Input) returns Bytes digest(OPERATION_DIGEST) ); let module = parse_module(&source).expect("direct call source parses"); - let errors = compile_to_core(&module, &context()).expect_err("direct call rejects"); - assert_eq!(errors[0].kind, CompilerErrorKind::MissingContextFact); + let permissive_effect_facts = context() + .with_operation_profile_write_classes("workspace.read", [WriteClass::Read]) + .with_effect_write_class("snapshot", WriteClass::Read); + let errors = compile_to_core(&module, &permissive_effect_facts) + .expect_err("capability alias rejects despite matching effect facts"); + assert_eq!(errors[0].kind, CompilerErrorKind::UnsupportedSourceShape); } #[test] From f9a6abf8a703161516742da661ae8edb10777a63 Mon Sep 17 00:00:00 2001 From: James Ross Date: Wed, 29 Jul 2026 16:03:48 -0700 Subject: [PATCH 06/11] fix: require request semantic closure --- .../tests/provider_contract_pack.rs | 87 +++++++++++++++++-- docs/abi/edict-target-ir.cddl | 2 +- docs/topics/providers/test-plan.md | 2 +- docs/topics/target-ir/test-plan.md | 2 +- 4 files changed, 85 insertions(+), 8 deletions(-) diff --git a/crates/edict-provider-schema/tests/provider_contract_pack.rs b/crates/edict-provider-schema/tests/provider_contract_pack.rs index 45ae88b..b08e845 100644 --- a/crates/edict-provider-schema/tests/provider_contract_pack.rs +++ b/crates/edict-provider-schema/tests/provider_contract_pack.rs @@ -14,9 +14,10 @@ use edict_syntax::{ canonical_target_profile_contract_resources, compile_to_core, decode_canonical_cbor, digest_target_profile_contract_resource, encode_core_module, encode_result_projection, encode_target_ir_artifact, parse_module, CanonicalValue, CompilerContext, CoreBudget, CoreExpr, - CoreObstructionReason, CorePredicate, CoreValue, ProviderArtifactSchemaValidationErrorKind, - ResourceRef, ResultProjection, ResultProjectionExpr, ResultProjectionSource, TargetIrArtifact, - TargetIrIntent, TargetIrRequireFailure, TargetIrRequirement, TargetIrSemanticClosure, + CoreExternalActionBudget, CoreObstructionReason, CorePredicate, CoreValue, LocalRef, + ProviderArtifactSchemaValidationErrorKind, ResourceRef, ResultProjection, ResultProjectionExpr, + ResultProjectionSource, TargetIrArtifact, TargetIrExternalActionRequest, TargetIrIntent, + TargetIrRequireFailure, TargetIrRequirement, TargetIrSemanticClosure, TargetProfileContractResource, WriteClass, AUTHORITY_FACTS_API_VERSION, CORE_MODULE_DIGEST_DOMAIN, PROVIDER_LAWPACK_ARTIFACT_DOMAIN, RESULT_PROJECTION_DIGEST_DOMAIN, TARGET_IR_ARTIFACT_DIGEST_DOMAIN, TARGET_PROFILE_API_VERSION, @@ -197,6 +198,17 @@ fn target_ir_root_matches_reference_encoder() { pack.validate_domain(TARGET_IR_ARTIFACT_DIGEST_DOMAIN, &encoded_requirements) .expect("encoder output with both requirement dispositions satisfies the root"); + let encoded_request = encoded_target_ir_with_external_request(); + pack.validate_domain(TARGET_IR_ARTIFACT_DIGEST_DOMAIN, &encoded_request) + .expect("encoder output with an external request satisfies the closed root"); + let mut request_without_closure = encoded_request; + remove_map_field(&mut request_without_closure, "semanticClosure"); + assert_eq!( + pack.validate_domain(TARGET_IR_ARTIFACT_DIGEST_DOMAIN, &request_without_closure), + Err(ProviderArtifactSchemaValidationErrorKind::SchemaMismatch), + "external requests cannot validate through the legacy closure-free root" + ); + let mut missing_semantic_closure = encoded_target_ir_with_coordinate("example.target@1"); remove_map_field(&mut missing_semantic_closure, "semanticClosure"); assert_eq!( @@ -708,6 +720,66 @@ fn encoded_target_ir_with_requirements() -> CanonicalValue { } fn encoded_target_ir_with_coordinate(coordinate: &str) -> CanonicalValue { + encode_target_ir_value(representative_target_ir(coordinate)) +} + +fn encoded_target_ir_with_external_request() -> CanonicalValue { + let mut artifact = representative_target_ir("example.target-profile@1"); + let operation = ResourceRef { + coordinate: "workspace.snapshot.observe@1".to_owned(), + digest: Some(format!("sha256:{}", "4".repeat(64))), + }; + artifact + .semantic_closure + .as_mut() + .expect("representative Target IR is closed") + .capabilities + .push(operation.clone()); + artifact + .intents + .get_mut("apply") + .expect("representative intent") + .external_action_requests + .push(TargetIrExternalActionRequest { + id: "apply.request.0".to_owned(), + binding: LocalRef { + id: "local.0".to_owned(), + alpha_name: "$local0".to_owned(), + ty: "edict.external-action.request/v1>".to_owned(), + }, + operation, + input_type: "Bytes".to_owned(), + settlement_type: "Bytes".to_owned(), + input_schema: ResourceRef { + coordinate: "workspace.snapshot.input@1".to_owned(), + digest: Some(format!("sha256:{}", "5".repeat(64))), + }, + settlement_schema: ResourceRef { + coordinate: "workspace.snapshot.settlement@1".to_owned(), + digest: Some(format!("sha256:{}", "6".repeat(64))), + }, + input: CoreExpr::Const(CoreValue::Bytes(vec![1, 2, 3])), + authority_scope: CoreExpr::Const(CoreValue::Bytes(vec![4, 5])), + basis: CoreExpr::Const(CoreValue::Bytes(vec![6, 7])), + budget: CoreExternalActionBudget { + max_settlement_bytes: CoreExpr::Const(CoreValue::Int { + width: "U64".to_owned(), + value: "64".to_owned(), + }), + max_attempts: CoreExpr::Const(CoreValue::Int { + width: "U32".to_owned(), + value: "3".to_owned(), + }), + }, + reconciliation_law: ResourceRef { + coordinate: "workspace.snapshot.reconcile@1".to_owned(), + digest: Some(format!("sha256:{}", "7".repeat(64))), + }, + }); + encode_target_ir_value(artifact) +} + +fn representative_target_ir(coordinate: &str) -> TargetIrArtifact { let terminal_reason = CoreObstructionReason { kind: "example.Terminal".to_owned(), payload: BTreeMap::from([( @@ -722,7 +794,7 @@ fn encoded_target_ir_with_coordinate(coordinate: &str) -> CanonicalValue { CoreExpr::Const(CoreValue::String("preserved".to_owned())), )]), }; - let artifact = TargetIrArtifact { + TargetIrArtifact { domain: "example.target-ir/v1".to_owned(), target_profile: ResourceRef { coordinate: coordinate.to_owned(), @@ -738,6 +810,7 @@ fn encoded_target_ir_with_coordinate(coordinate: &str) -> CanonicalValue { coordinate: "example.lawpack@1".to_owned(), digest: Some(format!("sha256:{}", "3".repeat(64))), }], + capabilities: Vec::new(), }), intents: BTreeMap::from([( "apply".to_owned(), @@ -769,10 +842,14 @@ fn encoded_target_ir_with_coordinate(coordinate: &str) -> CanonicalValue { }, ], steps: Vec::new(), + external_action_requests: Vec::new(), result: CoreExpr::Const(CoreValue::Null), }, )]), - }; + } +} + +fn encode_target_ir_value(artifact: TargetIrArtifact) -> CanonicalValue { let bytes = encode_target_ir_artifact(&artifact).expect("representative Target IR encodes"); decode_canonical_cbor(&bytes).expect("encoded representative Target IR is canonical") } diff --git a/docs/abi/edict-target-ir.cddl b/docs/abi/edict-target-ir.cddl index e673446..62f8a6d 100644 --- a/docs/abi/edict-target-ir.cddl +++ b/docs/abi/edict-target-ir.cddl @@ -47,6 +47,7 @@ target-ir-semantic-closure = { target-ir-intent = { target-ir-intent-common, ? basis: core-expr, + ? externalActionRequests: [* target-ir-external-action-request], } target-ir-legacy-intent = { @@ -59,7 +60,6 @@ target-ir-intent-common = ( coreEvaluationBudget: core-budget, requirements: [* target-ir-requirement], steps: [* target-ir-step], - ? externalActionRequests: [* target-ir-external-action-request], result: core-expr, ) diff --git a/docs/topics/providers/test-plan.md b/docs/topics/providers/test-plan.md index 10085aa..0c14253 100644 --- a/docs/topics/providers/test-plan.md +++ b/docs/topics/providers/test-plan.md @@ -135,7 +135,7 @@ Out of scope: | PROVIDERS-TP-039 | implemented | Failure matrix | PROVIDERS-REQ-028 | Unsupported protocol and duplicate request roles reject before proof construction; noncanonical output, wrong output domain, duplicate returned roles, undeclared output, path traversal, malformed WIT, trap, and fuel exhaustion retain their stable layer-owned failure kinds and the test process continues to a conforming invocation. | requests_reject_unsupported_protocol_version, semantic_input_closure_and_role_order_are_exact, provider_output_failure_matrix_preserves_stable_validation_kinds, malformed_canonical_abi_result_is_not_a_guest_trap_or_envelope_failure, fuel_resource_and_guest_traps_remain_distinct | crates/edict-syntax/tests/provider_invocation.rs, crates/edict-provider-host-wasmtime/tests/invocation.rs | Pure-envelope cases remain authoritative for invalid requests that cannot cross the opaque validated-request boundary; host cases prove guest and transport failures remain contained. | | PROVIDERS-TP-040 | implemented | Generated provenance routing | PROVIDERS-REQ-029 | A manifest fixture containing a digest-locked generated `generationProvenance` artifact round-trips and validates; changing that entry to component provenance rejects with `GeneratedRoleRequiresGeneratedSource`. | generation_provenance_is_generated_provider_metadata | fixtures/providers/echo-generated/provider-manifest.json, crates/edict-syntax/tests/provider.rs | Edict validates only the generic routing and provenance envelope; the provider owns the provenance schema and evidence semantics. | | PROVIDERS-TP-041 | implemented | Contract publication | PROVIDERS-REQ-030 | Repeated in-memory assembly yields byte-identical self-contained CDDL and manifest bytes with one sorted binding per published provider contract, and unresolved external rules reject explicitly. | contract_pack_is_self_contained_and_repeatable, assembly_rejects_missing_invalid_uncompilable_and_incomplete_schema_fragments | fixtures/provider-contracts/v1/edict-provider-contracts.cddl, fixtures/provider-contracts/v1/manifest.json, crates/edict-provider-schema/tests/provider_contract_pack.rs | The generated pack is transport-neutral contract authority, not a provider package or registry lookup mechanism. | -| PROVIDERS-TP-042 | implemented | Schema fidelity | PROVIDERS-REQ-030 | Every published root accepts a reference canonical value and rejects a representative structural mutation; `target-ir-artifact` accepts bytes emitted by the executable Target IR encoder. | target_ir_root_matches_reference_encoder, every_published_root_validates_reference_and_rejects_mutation | fixtures/target-ir/canonical/echo-effectful.target-ir.cbor, crates/edict-provider-schema/tests/provider_contract_pack.rs | Instance validation uses the generated self-contained document rather than recognizing domain names. | +| PROVIDERS-TP-042 | implemented | Schema fidelity | PROVIDERS-REQ-030 | Every published root accepts a reference canonical value and rejects a representative structural mutation; `target-ir-artifact` accepts executable encoder output carrying an exact capability-closed external request and rejects that request when injected into the closure-free legacy artifact. | target_ir_root_matches_reference_encoder, every_published_root_validates_reference_and_rejects_mutation | fixtures/target-ir/canonical/echo-effectful.target-ir.cbor, crates/edict-provider-schema/tests/provider_contract_pack.rs | Instance validation uses the generated self-contained document rather than recognizing domain names. The provider schema cannot revive an authority shape refused by the canonical Target IR encoder. | | PROVIDERS-TP-043 | implemented | Authority boundary | PROVIDERS-REQ-030 | Missing, duplicate, unknown, reordered-conflicting, byte-tampered, digest-tampered, or provenance-tampered pack members reject with stable structured kinds and yield no partial authority. | contract_pack_rejects_missing_duplicate_and_tampered_members, manifest_validation_recomputes_schema_and_resource_identity | crates/edict-provider-schema/tests/provider_contract_pack.rs | Recomputing a digest over replacement bytes does not grant authority to replace an Edict-owned contract. | | PROVIDERS-TP-044 | implemented | Drift gate | PROVIDERS-REQ-030 | Check mode reproduces exact CDDL and manifest fixtures and fails on either mismatch without rewriting files. | provider_contract_pack_goldens_match_executable_contract, provider_contract_pack_check_rejects_drift_without_rewriting | fixtures/provider-contracts/v1/edict-provider-contracts.cddl, fixtures/provider-contracts/v1/manifest.json, xtask/src/provider_contract_pack.rs, xtask/src/tests.rs | Write mode is an explicit review action; `cargo xtask verify` uses check mode only. | | PROVIDERS-TP-045 | implemented | Drift recovery | PROVIDERS-REQ-030 | A contract-pack drift failure identifies the exact supported regeneration command. | provider_contract_pack_check_rejects_drift_without_rewriting | xtask/src/provider_contract_pack.rs, xtask/src/tests.rs | The diagnostic does not direct users to the unrelated `*-goldens` command family. | diff --git a/docs/topics/target-ir/test-plan.md b/docs/topics/target-ir/test-plan.md index 51a0e28..ccc4db4 100644 --- a/docs/topics/target-ir/test-plan.md +++ b/docs/topics/target-ir/test-plan.md @@ -100,7 +100,7 @@ Out of scope: | TIR-TP-026 | implemented | Boundary guard | TIR-REQ-011, TIR-REQ-012 | A Target IR requirement after an emitted target step rejects with a stable target-feature failure kind and no artifact, with a more specific detail when it reads an earlier step output. | requirement_after_target_step_rejects_with_stable_feature_kind, requirement_that_reads_step_output_rejects_with_stable_feature_kind | crates/edict-syntax/tests/target_ir.rs | Intent-level requirements are pre-step guards until the artifact model owns ordered or step-attached guards. | | TIR-TP-027 | implemented | Integration | TIR-REQ-013 | Built-in Echo and git-warp lowerer adapters return the same artifacts, canonical bytes, and digests as direct lowering for identical Core and facts. | builtin_echo_lowerer_matches_direct_target_ir, builtin_gitwarp_lowerer_matches_direct_target_ir | crates/edict-syntax/tests/provider_lowering.rs | No Target IR golden moves when the invocation path changes. | | TIR-TP-028 | implemented | Boundary guard | TIR-REQ-013 | Matched-profile target and target-profile-digest failures pass through unchanged, while cross-profile lowerer selection rejects with a stable compatibility failure before invocation. | builtin_lowerers_preserve_structured_lowering_failures, builtin_lowerers_preserve_target_profile_digest_failures, builtin_lowerers_reject_mismatched_target_profiles | crates/edict-syntax/tests/provider_lowering.rs | Lowerer selection compatibility remains distinct from target semantic refusal; coordinate matching does not bypass target artifact validation. | -| TIR-TP-029 | implemented | Schema fidelity | TIR-REQ-014 | Canonical Echo and git-warp Target IR bytes plus encoder output containing both requirement dispositions satisfy `target-ir-artifact`; null, missing envelope fields, and malformed nested Target IR values reject through the same compiled root. | target_ir_root_matches_reference_encoder, every_published_root_validates_reference_and_rejects_mutation, target_ir_goldens_match_executable_encoder | docs/abi/edict-target-ir.cddl, fixtures/target-ir/canonical/echo-effectful.target-ir.cbor, fixtures/target-ir/canonical/gitwarp-append.target-ir.cbor, crates/edict-provider-schema/tests/provider_contract_pack.rs, xtask/src/tests.rs | The schema is derived from the canonical encoder contract, not target-specific runtime semantics. | +| TIR-TP-029 | implemented | Schema fidelity | TIR-REQ-014, TIR-REQ-017 | Canonical Echo and git-warp Target IR bytes plus encoder output containing both requirement dispositions or an exact capability-closed external request satisfy `target-ir-artifact`; null, missing envelope fields, malformed nested Target IR values, and an external request injected into the closure-free legacy artifact reject through the same compiled root. | target_ir_root_matches_reference_encoder, every_published_root_validates_reference_and_rejects_mutation, target_ir_goldens_match_executable_encoder | docs/abi/edict-target-ir.cddl, fixtures/target-ir/canonical/echo-effectful.target-ir.cbor, fixtures/target-ir/canonical/gitwarp-append.target-ir.cbor, crates/edict-provider-schema/tests/provider_contract_pack.rs, xtask/src/tests.rs | The schema is derived from the canonical encoder contract, including the rule that external requests require exact semantic authority rather than the legacy compatibility shape. | | TIR-TP-030 | implemented | Schema fidelity | TIR-REQ-014 | A target-profile coordinate containing only LF is accepted by both the canonical Target IR encoder and the published `target-ir-artifact` root. | target_ir_root_accepts_encoder_valid_line_feed_coordinate | crates/edict-provider-schema/tests/provider_contract_pack.rs | The CDDL nonempty constraint cannot use a regex whose wildcard excludes line terminators accepted by the encoder. | | TIR-TP-031 | implemented | Integration | TIR-REQ-001, TIR-REQ-015 | Compiling and lowering the operation prerequisite fixture preserves the exact Core basis expression and binds the computed Core digest plus exact lawpack coordinate/digest in Target IR; an empty Core coordinate, unidentifiable or noncanonical resource, coordinate conflict, internally contradictory closure, or missing closure on a basis-bearing artifact refuses without an artifact identity. The canonical encoder and published CDDL root reject an empty source Core coordinate, and the CDDL root independently rejects the externally constructed missing-closure shape. | operation_prerequisite_fixture_preserves_fixed_width_basis_and_lawpack_closure, target_lowering_refuses_an_unidentifiable_semantic_closure, legacy_target_ir_encoder_rejects_an_empty_source_core_coordinate, invalid_or_conflicting_lawpack_resources_refuse_before_target_artifact, semantic_closure_cannot_substitute_a_different_source_core_coordinate, explicit_basis_target_ir_cannot_drop_its_semantic_closure, target_ir_root_matches_reference_encoder | fixtures/lang/operations/explicit-basis-u64.edict | The target artifact binds semantic inputs but remains distinct from package admission or runtime execution. | | TIR-TP-032 | implemented | Mutation sensitivity | TIR-REQ-008, TIR-REQ-015 | Changing the basis expression, source Core meaning, lawpack coordinate, or lawpack digest moves the Target IR digest, while equivalent lawpack construction order does not. | explicit_basis_and_semantic_input_mutations_move_target_identity, semantic_closure_lawpack_set_is_order_invariant | fixtures/lang/operations/explicit-basis-u64.edict | Prevents Target IR identity from floating over operation semantic inputs. | From 00bb1fdd0692cd66c2b7568c1677d6a79ad79358 Mon Sep 17 00:00:00 2001 From: James Ross Date: Wed, 29 Jul 2026 16:06:39 -0700 Subject: [PATCH 07/11] test: reject request closure substitution --- crates/edict-syntax/tests/contract_bundle.rs | 24 +++++++++++++++++++ docs/topics/contract-bundles/test-plan.md | 4 ++-- .../external-action-requests/test-plan.md | 1 + 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/crates/edict-syntax/tests/contract_bundle.rs b/crates/edict-syntax/tests/contract_bundle.rs index 77be4a4..7e134e7 100644 --- a/crates/edict-syntax/tests/contract_bundle.rs +++ b/crates/edict-syntax/tests/contract_bundle.rs @@ -851,6 +851,30 @@ mod contract_bundle_assembly { assert_eq!(err.field(), "target_ir_artifact.semantic_closure.lawpacks"); } + #[test] + fn assembly_from_target_ir_rejects_artifact_capability_substitution() { + let mut input = assembly_from_target_ir_input(); + input + .target_ir_artifact + .semantic_closure + .as_mut() + .expect("lawpack-bearing fixture has a closure") + .capabilities + .push(digest_locked("workspace.snapshot.observe@1", 'e')); + + let err = assemble_contract_bundle_from_target_ir(input) + .expect_err("artifact capabilities must equal the supplied Core closure"); + + assert_eq!( + err.kind(), + ContractBundleAssemblyErrorKind::TargetIrSourceMismatch + ); + assert_eq!( + err.field(), + "target_ir_artifact.semantic_closure.capabilities" + ); + } + #[test] fn assembly_from_target_ir_preserves_canonical_core_failure() { let mut input = assembly_from_target_ir_input(); diff --git a/docs/topics/contract-bundles/test-plan.md b/docs/topics/contract-bundles/test-plan.md index 5c1f093..3ae4c73 100644 --- a/docs/topics/contract-bundles/test-plan.md +++ b/docs/topics/contract-bundles/test-plan.md @@ -43,7 +43,7 @@ Out of scope: | BUNDLE-REQ-005 | implemented | HOLMES, Watson, and Moriarty evidence entries are optional in the typed bundle; when present, each entry must bind to the manifest's selected bundle subject digest, target profile digest, and target IR digest. | issue #1, docs/GUIDE_edict-assurance-transparency.md | | BUNDLE-REQ-006 | implemented | Admission artifacts remain out of the participant-neutral contract bundle manifest; non-empty admission references are rejected. | docs/SPEC_continuum-contract-bundle-v1.md | | BUNDLE-REQ-007 | implemented | The typed bundle pins `canonicalization_profile.coordinate` to `edict.canonical-cbor/v1`. | docs/SPEC_continuum-contract-bundle-v1.md | -| BUNDLE-REQ-008 | implemented | The crate can assemble a `ContractBundleManifest`, computing `semanticBundleDigest` and `releaseBundleDigest` per the exact spec preimages from digest-locked references, with `coreIrDigest` computed from a real compiled Core module and `targetIrDigest` either supplied as a typed reference or computed from a real `TargetIrArtifact`; the real-artifact path corroborates the Target IR semantic closure against the exact supplied Core and explicit bundle lawpacks, canonicalizes the lawpack set, and preserves malformed-Core canonical failure taxonomy; the assembled manifest validates. | docs/SPEC_continuum-contract-bundle-v1.md, docs/design/contract-bundle-assembly-v0.11.md, docs/design/canonical-target-ir-v0.11.md | +| BUNDLE-REQ-008 | implemented | The crate can assemble a `ContractBundleManifest`, computing `semanticBundleDigest` and `releaseBundleDigest` per the exact spec preimages from digest-locked references, with `coreIrDigest` computed from a real compiled Core module and `targetIrDigest` either supplied as a typed reference or computed from a real `TargetIrArtifact`; the real-artifact path corroborates the Target IR semantic closure against the exact supplied Core, capability imports, and explicit bundle lawpacks, canonicalizes resource sets, and preserves malformed-Core canonical failure taxonomy; the assembled manifest validates. | docs/SPEC_continuum-contract-bundle-v1.md, docs/design/contract-bundle-assembly-v0.11.md, docs/design/canonical-target-ir-v0.11.md | | BUNDLE-REQ-009 | implemented | Direct and built-in-lowerer Target IR artifacts produce identical semantic and release bundle identities under identical explicit assembly inputs; lowerer identity remains release-only. | issue #140, docs/design/provider-artifact-pipeline-alpha.md | | BUNDLE-REQ-010 | implemented | When Core does not declare a semantic closure, the computed-Target-IR assembly path may accept a provider-supplied closure only when it binds the exact supplied Core and its canonical lawpack set equals the explicitly supplied bundle lawpack set; missing or substituted bindings remain rejected. | docs/topics/contract-bundles/README.md | @@ -68,7 +68,7 @@ Out of scope: | BUNDLE-TP-007 | implemented | Boundary guard | BUNDLE-REQ-001 | Removing the release-only build-provenance digest returns `InvalidArtifactReference` on `build_provenance`. | release_bundle_inputs_must_be_digest_locked | crates/edict-syntax/tests/contract_bundle.rs | Proves release digest preimage inputs are represented by the typed manifest. | | BUNDLE-TP-008 | implemented | Boundary guard | BUNDLE-REQ-007 | Changing `canonicalization_profile.coordinate` returns `UnsupportedCanonicalizationProfile`. | canonicalization_profile_must_be_the_v1_cbor_profile | crates/edict-syntax/tests/contract_bundle.rs | Pins the v1 bundle to the canonical CBOR profile. | | BUNDLE-TP-009 | implemented | Golden path | BUNDLE-REQ-001 | Empty lawpack, generated-artifact, and conformance-corpus lists remain valid because optional lists bind what is present without creating non-empty obligations. | optional_artifact_lists_may_be_empty | crates/edict-syntax/tests/contract_bundle.rs | Source artifacts remain the required artifact set. | -| BUNDLE-TP-010 | implemented | Golden path and substitution boundary | BUNDLE-REQ-008 | Assembling a bundle from a real compiled Core module plus supplied digest-locked references produces a manifest that `validate_contract_bundle_manifest` returns `Valid` for; the real-Target-IR path computes `targetIrDigest`, rejects source-coordinate, source-Core-digest, missing, mismatched-provider, artifact-lawpack, and bundle-lawpack substitutions, preserves malformed-Core canonical failures, canonicalizes equivalent bundle lawpack order and duplicates, reports invalid embedded target-profile digests with stable fields, and the checked-in bundle digest golden matches regenerated assembler output. | assembled_bundle_from_real_core_validates, assembled_bundle_from_real_target_ir_computes_target_ir_digest, assembly_from_target_ir_rejects_mismatched_core_source, assembly_from_target_ir_rejects_core_digest_substitution, assembly_from_target_ir_rejects_stripped_lawpack_only_closure, assembly_from_target_ir_rejects_mismatched_provider_semantic_closure, assembly_from_target_ir_rejects_artifact_lawpack_substitution, assembly_from_target_ir_rejects_lawpack_set_substitution, assembly_from_target_ir_preserves_canonical_core_failure, assembly_from_target_ir_canonicalizes_bundle_lawpacks, assembly_from_target_ir_rejects_invalid_target_profile_digest_with_stable_field, assembly_rejects_inputs_that_would_not_validate, bundle_digest_goldens_match_assembly | crates/edict-syntax/tests/contract_bundle.rs, fixtures/bundle/assembly/bounded-hello.bundle-digests.txt | The computed-artifact path recomputes semantic closure from Core when Core declares it and otherwise corroborates an exact provider-supplied closure against explicit bundle lawpacks; standalone canonical Target IR validation remains self-validation and does not reconstruct erased source dependencies. `cargo xtask bundle-goldens --check` guards golden drift. | +| BUNDLE-TP-010 | implemented | Golden path and substitution boundary | BUNDLE-REQ-008 | Assembling a bundle from a real compiled Core module plus supplied digest-locked references produces a manifest that `validate_contract_bundle_manifest` returns `Valid` for; the real-Target-IR path computes `targetIrDigest`, rejects source-coordinate, source-Core-digest, missing, mismatched-provider, artifact-lawpack, artifact-capability, and bundle-lawpack substitutions, preserves malformed-Core canonical failures, canonicalizes equivalent bundle lawpack order and duplicates, reports invalid embedded target-profile digests with stable fields, and the checked-in bundle digest golden matches regenerated assembler output. | assembled_bundle_from_real_core_validates, assembled_bundle_from_real_target_ir_computes_target_ir_digest, assembly_from_target_ir_rejects_mismatched_core_source, assembly_from_target_ir_rejects_core_digest_substitution, assembly_from_target_ir_rejects_stripped_lawpack_only_closure, assembly_from_target_ir_rejects_mismatched_provider_semantic_closure, assembly_from_target_ir_rejects_artifact_lawpack_substitution, assembly_from_target_ir_rejects_artifact_capability_substitution, assembly_from_target_ir_rejects_lawpack_set_substitution, assembly_from_target_ir_preserves_canonical_core_failure, assembly_from_target_ir_canonicalizes_bundle_lawpacks, assembly_from_target_ir_rejects_invalid_target_profile_digest_with_stable_field, assembly_rejects_inputs_that_would_not_validate, bundle_digest_goldens_match_assembly | crates/edict-syntax/tests/contract_bundle.rs, fixtures/bundle/assembly/bounded-hello.bundle-digests.txt | The computed-artifact path recomputes semantic closure from Core when Core declares it and otherwise corroborates an exact provider-supplied closure against explicit bundle lawpacks; standalone canonical Target IR validation remains self-validation and does not reconstruct erased source dependencies. `cargo xtask bundle-goldens --check` guards golden drift. | | BUNDLE-TP-011 | implemented | Boundary guard | BUNDLE-REQ-008 | Changing any semantic-layer preimage component (Core digest, supplied `targetIrDigest` reference, target profile, lawpack, generated artifact, conformance corpus, verifier report, `sourceProfileSemanticFactsDigest`, `canonicalizationProfileDigest`, or semantic compile options) changes both `semanticBundleDigest` and `releaseBundleDigest`. | semantic_preimage_mutations_change_semantic_and_release_digests | crates/edict-syntax/tests/contract_bundle.rs | Semantic-layer mutation propagates to both digests. | | BUNDLE-TP-012 | implemented | Boundary guard | BUNDLE-REQ-008 | Changing any release-only preimage component (provenance-only source digest or logical path, compiler/lowerer/verifier identity with produced artifacts unchanged, nonsemantic compile options, build provenance, or compile explanation) changes `releaseBundleDigest` only, leaving `semanticBundleDigest` unchanged. | release_only_preimage_mutations_leave_semantic_digest_unchanged | crates/edict-syntax/tests/contract_bundle.rs | Honors the semantic/release split; diagnostic and provenance-only changes do not move semantic identity. | | BUNDLE-TP-013 | implemented | Boundary guard | BUNDLE-REQ-008 | The assembly input API distinguishes the computed Core digest from supplied references by type, rejects non-lowercase supplied digest references, and uses the supplied `targetIrDigest` as the single source of truth for both the manifest and semantic preimage. | assembly_rejects_uppercase_supplied_target_ir_digest, assembly_rejects_uppercase_supplied_artifact_digest, target_ir_digest_is_single_source_of_truth | crates/edict-syntax/tests/contract_bundle.rs | The separate computed Target IR path is covered by BUNDLE-TP-010 and TIR-TP-022. | diff --git a/docs/topics/external-action-requests/test-plan.md b/docs/topics/external-action-requests/test-plan.md index dc01a56..40609ee 100644 --- a/docs/topics/external-action-requests/test-plan.md +++ b/docs/topics/external-action-requests/test-plan.md @@ -58,6 +58,7 @@ Out of scope: | EXTREQ-TP-007 | implemented | Property | EXTREQ-REQ-005, EXTREQ-REQ-008 | A fixed-seed 32-case capability-digest corpus is reproducible and collision-free within the corpus. | fixed_seed_request_identity_corpus_is_deterministic | crates/edict-syntax/tests/external_action_requests.rs | Seed is recorded in the fixture table. | | EXTREQ-TP-008 | implemented | Stress | EXTREQ-REQ-008 | A generated module with 64 request declarations emits 64 Core requests and 64 Target IR requests with zero callable steps. | sixty_four_requests_remain_bounded_non_callable_data | crates/edict-syntax/tests/external_action_requests.rs | Bound is fixed for CI. | | EXTREQ-TP-009 | implemented | Closure guard | EXTREQ-REQ-004 | Canonical Core and Target IR encoding reject request operations removed from their exact capability closure. | request_operation_must_remain_in_core_and_target_capability_closure | crates/edict-syntax/tests/external_action_requests.rs | Manual artifact construction cannot bypass compiler-owned closure. | +| EXTREQ-TP-010 | implemented | Bundle boundary | EXTREQ-REQ-004 | Contract-bundle assembly rejects a digest-locked Target IR capability absent from the supplied Core closure. | assembly_from_target_ir_rejects_artifact_capability_substitution | crates/edict-syntax/tests/contract_bundle.rs | A self-consistent target artifact cannot substitute source-owned request authority. | ## Determinism Obligations From 59b309194ace2952269f9ac180f11460e329a6c5 Mon Sep 17 00:00:00 2001 From: James Ross Date: Wed, 29 Jul 2026 16:11:19 -0700 Subject: [PATCH 08/11] refactor: bound external request variants --- .../tests/provider_contract_pack.rs | 8 +- crates/edict-syntax/src/ast.rs | 8 +- crates/edict-syntax/src/canonical.rs | 5 +- crates/edict-syntax/src/compiler.rs | 8 +- crates/edict-syntax/src/core_ir.rs | 6 +- crates/edict-syntax/src/parser.rs | 8 +- crates/edict-syntax/src/target_ir.rs | 6 +- .../tests/external_action_requests.rs | 127 ++++++++++-------- 8 files changed, 95 insertions(+), 81 deletions(-) diff --git a/crates/edict-provider-schema/tests/provider_contract_pack.rs b/crates/edict-provider-schema/tests/provider_contract_pack.rs index b08e845..b1efcad 100644 --- a/crates/edict-provider-schema/tests/provider_contract_pack.rs +++ b/crates/edict-provider-schema/tests/provider_contract_pack.rs @@ -720,7 +720,7 @@ fn encoded_target_ir_with_requirements() -> CanonicalValue { } fn encoded_target_ir_with_coordinate(coordinate: &str) -> CanonicalValue { - encode_target_ir_value(representative_target_ir(coordinate)) + encode_target_ir_value(&representative_target_ir(coordinate)) } fn encoded_target_ir_with_external_request() -> CanonicalValue { @@ -776,7 +776,7 @@ fn encoded_target_ir_with_external_request() -> CanonicalValue { digest: Some(format!("sha256:{}", "7".repeat(64))), }, }); - encode_target_ir_value(artifact) + encode_target_ir_value(&artifact) } fn representative_target_ir(coordinate: &str) -> TargetIrArtifact { @@ -849,8 +849,8 @@ fn representative_target_ir(coordinate: &str) -> TargetIrArtifact { } } -fn encode_target_ir_value(artifact: TargetIrArtifact) -> CanonicalValue { - let bytes = encode_target_ir_artifact(&artifact).expect("representative Target IR encodes"); +fn encode_target_ir_value(artifact: &TargetIrArtifact) -> CanonicalValue { + let bytes = encode_target_ir_artifact(artifact).expect("representative Target IR encodes"); decode_canonical_cbor(&bytes).expect("encoded representative Target IR is canonical") } diff --git a/crates/edict-syntax/src/ast.rs b/crates/edict-syntax/src/ast.rs index d73e930..7c34203 100644 --- a/crates/edict-syntax/src/ast.rs +++ b/crates/edict-syntax/src/ast.rs @@ -220,10 +220,10 @@ pub enum Stmt { operation: Expr, input_schema: DigestLockedPackageRef, settlement_schema: DigestLockedPackageRef, - authority_scope: Expr, - basis: Expr, - max_settlement_bytes: Expr, - max_attempts: Expr, + authority_scope: Box, + basis: Box, + max_settlement_bytes: Box, + max_attempts: Box, reconciliation_law: DigestLockedPackageRef, span: Span, }, diff --git a/crates/edict-syntax/src/canonical.rs b/crates/edict-syntax/src/canonical.rs index 24cc351..5e4e090 100644 --- a/crates/edict-syntax/src/canonical.rs +++ b/crates/edict-syntax/src/canonical.rs @@ -782,10 +782,7 @@ fn core_module_value(module: &CoreModule) -> Result None, }) { - if !capability_imports - .iter() - .any(|capability| *capability == request) - { + if !capability_imports.contains(&request) { return Err(CanonicalError::new( CanonicalErrorKind::UnsupportedValue, format!( diff --git a/crates/edict-syntax/src/compiler.rs b/crates/edict-syntax/src/compiler.rs index ea57306..ecb3f20 100644 --- a/crates/edict-syntax/src/compiler.rs +++ b/crates/edict-syntax/src/compiler.rs @@ -945,12 +945,12 @@ impl<'a> TypeChecker<'a> { input_schema, settlement_schema, input: input.expr, - authority_scope: authority_scope.expr, - basis: basis.expr, - budget: CoreExternalActionBudget { + authority_scope: Box::new(authority_scope.expr), + basis: Box::new(basis.expr), + budget: Box::new(CoreExternalActionBudget { max_settlement_bytes: max_settlement_bytes.expr, max_attempts: max_attempts.expr, - }, + }), reconciliation_law, }); locals.push(local.clone()); diff --git a/crates/edict-syntax/src/core_ir.rs b/crates/edict-syntax/src/core_ir.rs index b4d9e9d..0276d2a 100644 --- a/crates/edict-syntax/src/core_ir.rs +++ b/crates/edict-syntax/src/core_ir.rs @@ -289,9 +289,9 @@ pub enum CoreNode { input_schema: ResourceRef, settlement_schema: ResourceRef, input: CoreExpr, - authority_scope: CoreExpr, - basis: CoreExpr, - budget: CoreExternalActionBudget, + authority_scope: Box, + basis: Box, + budget: Box, reconciliation_law: ResourceRef, }, } diff --git a/crates/edict-syntax/src/parser.rs b/crates/edict-syntax/src/parser.rs index 152e9ec..c83fa59 100644 --- a/crates/edict-syntax/src/parser.rs +++ b/crates/edict-syntax/src/parser.rs @@ -991,10 +991,10 @@ impl Parser { operation, input_schema, settlement_schema, - authority_scope, - basis, - max_settlement_bytes, - max_attempts, + authority_scope: Box::new(authority_scope), + basis: Box::new(basis), + max_settlement_bytes: Box::new(max_settlement_bytes), + max_attempts: Box::new(max_attempts), reconciliation_law, span: Span::new(start, self.prev_end()), }) diff --git a/crates/edict-syntax/src/target_ir.rs b/crates/edict-syntax/src/target_ir.rs index f8d6e9a..c62ea63 100644 --- a/crates/edict-syntax/src/target_ir.rs +++ b/crates/edict-syntax/src/target_ir.rs @@ -659,9 +659,9 @@ fn lower_node( input_schema: input_schema.clone(), settlement_schema: settlement_schema.clone(), input: input.clone(), - authority_scope: authority_scope.clone(), - basis: basis.clone(), - budget: budget.clone(), + authority_scope: authority_scope.as_ref().clone(), + basis: basis.as_ref().clone(), + budget: budget.as_ref().clone(), reconciliation_law: reconciliation_law.clone(), }), CoreNode::Let { .. } => failures.push(TargetLoweringFailure { diff --git a/crates/edict-syntax/tests/external_action_requests.rs b/crates/edict-syntax/tests/external_action_requests.rs index 8c0da1d..019fe8d 100644 --- a/crates/edict-syntax/tests/external_action_requests.rs +++ b/crates/edict-syntax/tests/external_action_requests.rs @@ -3,7 +3,7 @@ //! The tests use only public compiler surfaces so the first failure is the //! absence of request syntax/semantics, not a test compile error. -use std::collections::BTreeSet; +use std::{collections::BTreeSet, fmt::Write as _}; use edict_syntax::{ compile_to_core, decode_canonical_cbor, digest_core_module, encode_core_module, @@ -49,19 +49,34 @@ fn target_facts() -> TargetIrLoweringFacts { } } -fn request_source( - capability_coordinate: &str, - operation_alias: &str, - operation_digest: &str, - input_schema_digest: &str, - settlement_schema_digest: &str, - input_expr: &str, - authority_expr: &str, - basis_expr: &str, - max_settlement_bytes: &str, - max_attempts: &str, - reconciliation_digest: &str, -) -> String { +struct RequestSource<'a> { + capability_coordinate: &'a str, + operation_alias: &'a str, + operation_digest: &'a str, + input_schema_digest: &'a str, + settlement_schema_digest: &'a str, + input_expr: &'a str, + authority_expr: &'a str, + basis_expr: &'a str, + max_settlement_bytes: &'a str, + max_attempts: &'a str, + reconciliation_digest: &'a str, +} + +fn request_source(request: &RequestSource<'_>) -> String { + let RequestSource { + capability_coordinate, + operation_alias, + operation_digest, + input_schema_digest, + settlement_schema_digest, + input_expr, + authority_expr, + basis_expr, + max_settlement_bytes, + max_attempts, + reconciliation_digest, + } = request; format!( r#"package examples.workspace_observer@1; @@ -99,19 +114,19 @@ intent observe(input: ObserveInput) returns ExternalActionRequest String { - request_source( - "workspace.snapshot.observe@1", - "snapshot", - &digest(OPERATION_DIGEST), - &digest(INPUT_SCHEMA_DIGEST), - &digest(SETTLEMENT_SCHEMA_DIGEST), - "input.payload", - "input.scope", - "input.basis", - "input.maxSettlementBytes", - "input.maxAttempts", - &digest(RECONCILIATION_DIGEST), - ) + request_source(&RequestSource { + capability_coordinate: "workspace.snapshot.observe@1", + operation_alias: "snapshot", + operation_digest: &digest(OPERATION_DIGEST), + input_schema_digest: &digest(INPUT_SCHEMA_DIGEST), + settlement_schema_digest: &digest(SETTLEMENT_SCHEMA_DIGEST), + input_expr: "input.payload", + authority_expr: "input.scope", + basis_expr: "input.basis", + max_settlement_bytes: "input.maxSettlementBytes", + max_attempts: "input.maxAttempts", + reconciliation_digest: &digest(RECONCILIATION_DIGEST), + }) } fn compile_source(source: &str) -> edict_syntax::CoreModule { @@ -298,19 +313,19 @@ fn ambient_operation_families_are_not_requestable() { "model.invoke@1", "shell.command@1", ] { - let source = request_source( - coordinate, - "snapshot", - &digest(OPERATION_DIGEST), - &digest(INPUT_SCHEMA_DIGEST), - &digest(SETTLEMENT_SCHEMA_DIGEST), - "input.payload", - "input.scope", - "input.basis", - "input.maxSettlementBytes", - "input.maxAttempts", - &digest(RECONCILIATION_DIGEST), - ); + let source = request_source(&RequestSource { + capability_coordinate: coordinate, + operation_alias: "snapshot", + operation_digest: &digest(OPERATION_DIGEST), + input_schema_digest: &digest(INPUT_SCHEMA_DIGEST), + settlement_schema_digest: &digest(SETTLEMENT_SCHEMA_DIGEST), + input_expr: "input.payload", + authority_expr: "input.scope", + basis_expr: "input.basis", + max_settlement_bytes: "input.maxSettlementBytes", + max_attempts: "input.maxAttempts", + reconciliation_digest: &digest(RECONCILIATION_DIGEST), + }); let module = parse_module(&source).expect("ambient-family source parses structurally"); let errors = compile_to_core(&module, &context()).expect_err("ambient operation family rejects"); @@ -396,19 +411,19 @@ fn fixed_seed_request_identity_corpus_is_deterministic() { let mut hex = format!("{state:016x}"); hex = hex.repeat(4); let operation_digest = format!("sha256:{hex}"); - let source = request_source( - "workspace.snapshot.observe@1", - "snapshot", - &operation_digest, - &digest(INPUT_SCHEMA_DIGEST), - &digest(SETTLEMENT_SCHEMA_DIGEST), - "input.payload", - "input.scope", - "input.basis", - "input.maxSettlementBytes", - "input.maxAttempts", - &digest(RECONCILIATION_DIGEST), - ); + let source = request_source(&RequestSource { + capability_coordinate: "workspace.snapshot.observe@1", + operation_alias: "snapshot", + operation_digest: &operation_digest, + input_schema_digest: &digest(INPUT_SCHEMA_DIGEST), + settlement_schema_digest: &digest(SETTLEMENT_SCHEMA_DIGEST), + input_expr: "input.payload", + authority_expr: "input.scope", + basis_expr: "input.basis", + max_settlement_bytes: "input.maxSettlementBytes", + max_attempts: "input.maxAttempts", + reconciliation_digest: &digest(RECONCILIATION_DIGEST), + }); let first = digest_core_module(&compile_source(&source)).expect("first property digest"); let second = digest_core_module(&compile_source(&source)).expect("second property digest"); assert_eq!(first, second); @@ -421,7 +436,8 @@ fn fixed_seed_request_identity_corpus_is_deterministic() { fn sixty_four_requests_remain_bounded_non_callable_data() { let mut requests = String::new(); for index in 0..64 { - requests.push_str(&format!( + write!( + requests, r#" request pending{index}: ExternalActionRequest> = snapshot(input.payload) input schema workspace.snapshot.input@1 digest "{}" @@ -434,7 +450,8 @@ fn sixty_four_requests_remain_bounded_non_callable_data() { digest(INPUT_SCHEMA_DIGEST), digest(SETTLEMENT_SCHEMA_DIGEST), digest(RECONCILIATION_DIGEST), - )); + ) + .expect("writing to a String cannot fail"); } let source = format!( r#"package examples.workspace_stress@1; From 4a95f6aea06f2b7ddafdaaac42173f8b91f747eb Mon Sep 17 00:00:00 2001 From: James Ross Date: Wed, 29 Jul 2026 16:11:26 -0700 Subject: [PATCH 09/11] fix: project typed external requests --- crates/edict-cli/src/main.rs | 72 ++++++++++++- crates/edict-cli/tests/jsonl_cli.rs | 101 +++++++++++++++++- docs/topics/cli/test-plan.md | 1 + .../external-action-requests/test-plan.md | 1 + 4 files changed, 170 insertions(+), 5 deletions(-) diff --git a/crates/edict-cli/src/main.rs b/crates/edict-cli/src/main.rs index 7bffee1..8da9ac2 100644 --- a/crates/edict-cli/src/main.rs +++ b/crates/edict-cli/src/main.rs @@ -17,9 +17,9 @@ use edict_syntax::{ CoreBudget, CoreExpr, CoreImport, CoreIntent, CoreNode, CoreObstructionArm, CoreObstructionReason, CorePredicate, CoreRequireFailureArm, CoreType, CoreValue, HighlightRole, InputConstraint, InputConstraintSource, ParseError, ResourceRef, SemanticError, - Span, TargetEffectLowering, TargetIrArtifact, TargetIrIntent, TargetIrLoweringFacts, - TargetIrRequireFailure, TargetIrRequirement, TargetIrSemanticClosure, TargetIrStep, - TargetLoweringFailure, TargetLoweringFailureKind, WriteClass, + Span, TargetEffectLowering, TargetIrArtifact, TargetIrExternalActionRequest, TargetIrIntent, + TargetIrLoweringFacts, TargetIrRequireFailure, TargetIrRequirement, TargetIrSemanticClosure, + TargetIrStep, TargetLoweringFailure, TargetLoweringFailureKind, WriteClass, }; use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; @@ -1261,6 +1261,11 @@ fn target_ir_semantic_closure_review(closure: &TargetIrSemanticClosure) -> Value .iter() .map(resource_ref_review) .collect::>(), + "capabilities": closure + .capabilities + .iter() + .map(resource_ref_review) + .collect::>(), }) } @@ -1279,6 +1284,11 @@ fn target_ir_intent_review(intent: &TargetIrIntent) -> Value { .map(target_ir_requirement_review) .collect::>(), "steps": intent.steps.iter().map(target_ir_step_review).collect::>(), + "externalActionRequests": intent + .external_action_requests + .iter() + .map(target_ir_external_action_request_review) + .collect::>(), "result": core_expr_review(&intent.result), }); insert_optional_review_field( @@ -1331,6 +1341,28 @@ fn target_ir_step_review(step: &TargetIrStep) -> Value { }) } +fn target_ir_external_action_request_review(request: &TargetIrExternalActionRequest) -> Value { + json!({ + "id": request.id, + "binding": local_ref_review(&request.binding), + "operation": resource_ref_review(&request.operation), + "inputType": request.input_type, + "settlementType": request.settlement_type, + "inputSchema": resource_ref_review(&request.input_schema), + "settlementSchema": resource_ref_review(&request.settlement_schema), + "input": core_expr_review(&request.input), + "authorityScope": core_expr_review(&request.authority_scope), + "basis": core_expr_review(&request.basis), + "budget": { + "maxSettlementBytes": core_expr_review(&request.budget.max_settlement_bytes), + "maxAttempts": core_expr_review(&request.budget.max_attempts), + }, + "reconciliationLaw": resource_ref_review(&request.reconciliation_law), + "state": "awaitingSettlement", + "settlementAdmission": "schemaRequired", + }) +} + fn core_import_review(import: &CoreImport) -> Value { json!({ "kind": import.kind.as_str(), @@ -1362,6 +1394,9 @@ fn core_type_review(ty: &CoreType) -> Value { json!({ "kind": "map", "key": key, "value": value, "max": max }) } CoreType::CapabilityRef { item } => json!({ "kind": "capabilityRef", "item": item }), + CoreType::ExternalActionRequest { settlement } => { + json!({ "kind": "externalActionRequest", "settlement": settlement }) + } } } @@ -1423,6 +1458,37 @@ fn core_node_review(node: &CoreNode) -> Value { .map(|(name, arm)| (name.clone(), obstruction_arm_review(arm))) .collect::>(), }), + CoreNode::ExternalActionRequest { + binding, + operation, + input_type, + settlement_type, + input_schema, + settlement_schema, + input, + authority_scope, + basis, + budget, + reconciliation_law, + } => json!({ + "kind": "externalActionRequest", + "binding": local_ref_review(binding), + "operation": resource_ref_review(operation), + "inputType": input_type, + "settlementType": settlement_type, + "inputSchema": resource_ref_review(input_schema), + "settlementSchema": resource_ref_review(settlement_schema), + "input": core_expr_review(input), + "authorityScope": core_expr_review(authority_scope), + "basis": core_expr_review(basis), + "budget": { + "maxSettlementBytes": core_expr_review(&budget.max_settlement_bytes), + "maxAttempts": core_expr_review(&budget.max_attempts), + }, + "reconciliationLaw": resource_ref_review(reconciliation_law), + "state": "awaitingSettlement", + "settlementAdmission": "schemaRequired", + }), } } diff --git a/crates/edict-cli/tests/jsonl_cli.rs b/crates/edict-cli/tests/jsonl_cli.rs index e91d6c0..516eec6 100644 --- a/crates/edict-cli/tests/jsonl_cli.rs +++ b/crates/edict-cli/tests/jsonl_cli.rs @@ -49,6 +49,36 @@ intent replaceThing(input: Input) } "#; +const EXTERNAL_REQUEST_SOURCE: &str = r#"package demo.external_request@1; + +use capability workspace.snapshot.observe@1 digest "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" as snapshot; + +type Input = { + payload: Bytes, + scope: Bytes, + basis: Bytes, + maxSettlementBytes: U64, + maxAttempts: U32, +}; + +intent observe(input: Input) + returns ExternalActionRequest> + profile hello.readOnly + basis input.basis + budget <= hello.tinyBudget +{ + request pending: ExternalActionRequest> = + snapshot(input.payload) + input schema workspace.snapshot.input@1 digest "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" + settlement schema workspace.snapshot.settlement@1 digest "sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" + authority input.scope + basis input.basis + budget maxSettlementBytes input.maxSettlementBytes maxAttempts input.maxAttempts + reconcile workspace.snapshot.reconcile@1 digest "sha256:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"; + return pending; +} +"#; + const ECHO_TARGET_PROFILE_DIGEST: &str = "sha256:1111111111111111111111111111111111111111111111111111111111111111"; @@ -245,6 +275,67 @@ fn project_accepts_dirty_source_and_emits_syntax_core_target_ir_projection() { assert_available_target_ir_projection(&stdout, &expected_target_digest); } +#[test] +fn project_exposes_external_requests_as_non_callable_review_data() { + let output = run_edict(&jsonl([ + projection_settings(["diagnostics", "core", "targetIr"]), + json!({ + "schema": "edict.compiler.input/v1", + "type": "compilerInput", + "kind": "source", + "name": "unsaved/external-request.edict", + "source": EXTERNAL_REQUEST_SOURCE, + }), + ])); + + let stdout = assert_successful_projection_output(&output); + assert_empty_projection_diagnostics(&stdout); + + let core = record_of_type(&stdout, "core"); + assert_eq!(core.get("state").and_then(Value::as_str), Some("available")); + assert_eq!( + core.pointer("/review/intents/observe/body/nodes/0/kind") + .and_then(Value::as_str), + Some("externalActionRequest") + ); + assert_eq!( + core.pointer("/review/intents/observe/body/nodes/0/operation/coordinate") + .and_then(Value::as_str), + Some("workspace.snapshot.observe@1") + ); + + let target = record_of_type(&stdout, "targetIr"); + assert_eq!( + target.get("state").and_then(Value::as_str), + Some("available") + ); + assert_eq!( + target + .pointer("/review/semanticClosure/capabilities/0/coordinate") + .and_then(Value::as_str), + Some("workspace.snapshot.observe@1") + ); + assert_eq!( + target + .pointer("/review/intents/observe/externalActionRequests/0/operation/coordinate") + .and_then(Value::as_str), + Some("workspace.snapshot.observe@1") + ); + assert_eq!( + target + .pointer("/review/intents/observe/externalActionRequests/0/state") + .and_then(Value::as_str), + Some("awaitingSettlement") + ); + assert_eq!( + target + .pointer("/review/intents/observe/steps") + .and_then(Value::as_array) + .map(Vec::len), + Some(0) + ); +} + #[test] fn project_invalid_source_emits_diagnostics_without_process_failure() { let output = run_edict(&jsonl([ @@ -1048,7 +1139,10 @@ fn projection_settings(emit: [&str; N]) -> Value { "coordinate": "echo.dpo@1", "profileDigest": ECHO_TARGET_PROFILE_DIGEST, "irDomain": "echo.span-ir/v1", - "operationProfiles": ["continuum.profile.write/v1"], + "operationProfiles": [ + "continuum.profile.read-only/v1", + "continuum.profile.write/v1" + ], "obstructionCoordinates": ["rejected"], "effectLowerings": [ { @@ -1356,7 +1450,10 @@ fn projection_target_facts() -> TargetIrLoweringFacts { digest: Some(ECHO_TARGET_PROFILE_DIGEST.to_owned()), }, target_ir_domain: ECHO_SPAN_IR_DOMAIN.to_owned(), - operation_profiles: vec!["continuum.profile.write/v1".to_owned()], + operation_profiles: vec![ + "continuum.profile.read-only/v1".to_owned(), + "continuum.profile.write/v1".to_owned(), + ], obstruction_coordinates: vec!["rejected".to_owned()], effect_lowerings: vec![TargetEffectLowering { effect: "target.replace".to_owned(), diff --git a/docs/topics/cli/test-plan.md b/docs/topics/cli/test-plan.md index 20563d7..422bb38 100644 --- a/docs/topics/cli/test-plan.md +++ b/docs/topics/cli/test-plan.md @@ -99,6 +99,7 @@ Out of scope: | CLI-TP-026 | implemented | Schema guard | CLI-REQ-003 | The compiler settings schema rejects empty `project` emit lists. | compiler_settings_schema_declares_jsonl_contract | docs/schemas/edict.compiler-settings.v1.schema.json | The schema and runtime both require at least one requested projection slot. | | CLI-TP-027 | implemented | Build request dispatch | CLI-REQ-015 | A `build` settings record is accepted without compiler input records and reaches application-config loading; a missing application config fails as a structured `ApplicationConfigReadFailed` build diagnostic rather than as invalid settings or missing compiler input. | build_accepts_application_request_without_compiler_input_records | crates/edict-cli/tests/jsonl_cli.rs | Covers the public request shape and dispatch boundary only. | | CLI-TP-028 | planned | Verified application build | CLI-REQ-015 | A repository-owned provider-component fixture builds one exact source and complete lawpack closure, independently accepts the package, and publishes the canonical package/report pair. | - | - | The standalone external Hello Echo build is a green integration witness; this planned case makes the full crossing reproducible inside Edict's own automated suite. | +| CLI-TP-029 | implemented | External request projection | CLI-REQ-013 | A `project` request carrying exact external-action source emits Core and Target IR review data with the capability resource, complete request payload, explicit awaiting-settlement posture, capability semantic closure, and zero callable target steps. | project_exposes_external_requests_as_non_callable_review_data | crates/edict-cli/tests/jsonl_cli.rs | Review JSON exposes compiler data only; the CLI performs no external action. | ## Determinism Obligations diff --git a/docs/topics/external-action-requests/test-plan.md b/docs/topics/external-action-requests/test-plan.md index 40609ee..55f634a 100644 --- a/docs/topics/external-action-requests/test-plan.md +++ b/docs/topics/external-action-requests/test-plan.md @@ -59,6 +59,7 @@ Out of scope: | EXTREQ-TP-008 | implemented | Stress | EXTREQ-REQ-008 | A generated module with 64 request declarations emits 64 Core requests and 64 Target IR requests with zero callable steps. | sixty_four_requests_remain_bounded_non_callable_data | crates/edict-syntax/tests/external_action_requests.rs | Bound is fixed for CI. | | EXTREQ-TP-009 | implemented | Closure guard | EXTREQ-REQ-004 | Canonical Core and Target IR encoding reject request operations removed from their exact capability closure. | request_operation_must_remain_in_core_and_target_capability_closure | crates/edict-syntax/tests/external_action_requests.rs | Manual artifact construction cannot bypass compiler-owned closure. | | EXTREQ-TP-010 | implemented | Bundle boundary | EXTREQ-REQ-004 | Contract-bundle assembly rejects a digest-locked Target IR capability absent from the supplied Core closure. | assembly_from_target_ir_rejects_artifact_capability_substitution | crates/edict-syntax/tests/contract_bundle.rs | A self-consistent target artifact cannot substitute source-owned request authority. | +| EXTREQ-TP-011 | implemented | Public projection | EXTREQ-REQ-002, EXTREQ-REQ-003, EXTREQ-REQ-004 | The public CLI review projection carries the complete Core and Target IR request, exact capability closure, awaiting-settlement posture, and no callable target step. | project_exposes_external_requests_as_non_callable_review_data | crates/edict-cli/tests/jsonl_cli.rs | Projection is review data, not execution authority. | ## Determinism Obligations From 76e9f37527d8ab6bd361ee2722586df03b24dae8 Mon Sep 17 00:00:00 2001 From: James Ross Date: Wed, 29 Jul 2026 16:12:29 -0700 Subject: [PATCH 10/11] chore: regenerate provider contract pack --- .../v1/edict-provider-contracts.cddl | 50 +++++++++++++++++-- fixtures/provider-contracts/v1/manifest.json | 4 +- 2 files changed, 49 insertions(+), 5 deletions(-) diff --git a/fixtures/provider-contracts/v1/edict-provider-contracts.cddl b/fixtures/provider-contracts/v1/edict-provider-contracts.cddl index 5df2ec6..f309b20 100644 --- a/fixtures/provider-contracts/v1/edict-provider-contracts.cddl +++ b/fixtures/provider-contracts/v1/edict-provider-contracts.cddl @@ -102,7 +102,7 @@ core-module = { } core-import = { - kind: "lawpack" / "target" / "core", + kind: "lawpack" / "target" / "core" / "capability", ref: resource-ref, ? alias: tstr, } @@ -111,7 +111,7 @@ core-import = { core-type = core-scalar-type / core-record-type / core-variant-type / core-option-type / core-list-type / core-map-type / - core-capability-ref-type + core-capability-ref-type / core-external-action-request-type core-scalar-type = core-bool-type / core-int-type / core-string-type / core-bytes-type / core-unit-type @@ -160,6 +160,10 @@ core-capability-ref-type = { kind: "CapabilityRef", item: core-type-ref, } +core-external-action-request-type = { + kind: "ExternalActionRequest", + settlement: core-type-ref, +} ; core-type-ref is defined in edict-common.cddl and assembled with this schema. @@ -324,7 +328,8 @@ core-block = { result: core-expr, } -core-node = let-node / require-node / effect-node / guard-node / branch-node / +core-node = let-node / require-node / effect-node / + external-action-request-node / guard-node / branch-node / for-node / match-node / proof-obligation-node let-node = { @@ -358,6 +363,26 @@ effect-node = { input: core-expr, obstructionMap: { * failure-ident => obstruction-arm }, } +external-action-request-node = { + kind: "externalActionRequest", + binding: local-ref, + operation: resource-ref, + inputType: core-type-ref, + settlementType: core-type-ref, + inputSchema: resource-ref, + settlementSchema: resource-ref, + input: core-expr, + authorityScope: core-expr, + basis: core-expr, + budget: external-action-budget, + reconciliationLaw: resource-ref, + state: "awaitingSettlement", + settlementAdmission: "schemaRequired", +} +external-action-budget = { + maxSettlementBytes: core-expr, + maxAttempts: core-expr, +} obstruction-arm = { binder: local-ref, value: core-expr, @@ -872,11 +897,13 @@ target-ir-legacy-artifact = { target-ir-semantic-closure = { sourceCore: target-ir-resource-ref, lawpacks: [* target-ir-resource-ref], + ? capabilities: [* target-ir-resource-ref], } target-ir-intent = { target-ir-intent-common, ? basis: core-expr, + ? externalActionRequests: [* target-ir-external-action-request], } target-ir-legacy-intent = { @@ -907,3 +934,20 @@ target-ir-step = { obstructionFailures: [* failure-ident], obstructionArms: { * failure-ident => obstruction-arm }, } + +target-ir-external-action-request = { + id: tstr, + binding: local-ref, + operation: target-ir-resource-ref, + inputType: core-type-ref, + settlementType: core-type-ref, + inputSchema: target-ir-resource-ref, + settlementSchema: target-ir-resource-ref, + input: core-expr, + authorityScope: core-expr, + basis: core-expr, + budget: external-action-budget, + reconciliationLaw: target-ir-resource-ref, + state: "awaitingSettlement", + settlementAdmission: "schemaRequired", +} diff --git a/fixtures/provider-contracts/v1/manifest.json b/fixtures/provider-contracts/v1/manifest.json index 0106222..ee9c0bc 100644 --- a/fixtures/provider-contracts/v1/manifest.json +++ b/fixtures/provider-contracts/v1/manifest.json @@ -3,8 +3,8 @@ "coordinate": "edict.provider-contract-pack.cddl@1", "license": "Apache-2.0", "schema": { - "bytesHex": "3b20535044582d4c6963656e73652d4964656e7469666965723a204170616368652d322e300a3b2065646963742d70726f76696465722d636f6e7472616374732e6364646c0a3b2047656e6572617465642066726f6d2045646963742d6f776e65642041424920667261676d656e74732e20444f204e4f5420454449542e0a0a3b202d2d2d2065646963742d636f6d6d6f6e2e6364646c202d2d2d0a3b2065646963742d636f6d6d6f6e2e6364646c0a3b20536861726564204344444c20747970657320666f722074686520456469637420414249732c20646566696e6564204f4e4345206865726520736f20746865792063616e6e6f742064726966740a3b202845444943542d4142492d4e4f4455502d303031292e2054776f2067726f7570733a0a3b2020202d207265736f757263652d7265662c207368613235362d6469676573742c206566666563742d6661696c7572652d626f64792c206566666563742d6b696e642c20617574686f726974792d636c6173732c0a3b2020202020636f72652d747970652d7265663a20617373656d626c656420776974682065646963742d7461726765742d70726f66696c652e6364646c20616e640a3b202020202065646963742d6c61777061636b2e6364646c20627920746865206275696c643b2074686f736520736368656d617320646f206e6f74207265646566696e65207468656d2e0a3b2020202d206f7065726174696f6e2d70726f66696c652c206f707469632d74656d706c6174652c2061706572747572652d726571756972656d656e7420616e6420746865697220726566733a0a3b2020202020636f6e73756d65642062792074686520436f72652f6f70746963206c61796572202865646963742d636f72652e6364646c2920616e64207265666572656e636564206279207468650a3b20202020206c616e67756167652f7461726765742d70726f66696c652073706563732e0a3b20417574686f7269746174697665206279746520656e636f64696e673a2065646963742e63616e6f6e6963616c2d63626f722f76312e0a0a3b2041206e6f726d617469766520737562636f6d706f6e656e74207265666572656e636564206279206964656e7469747920706c7573206469676573742e204d616e696665737473206e657665720a3b20656d626564207468656972206f776e2073656c662d64696765737420696e20746865697220707265696d616765202845444943542d434f52452d53454c46484153482d303031292e0a7265736f757263652d726566203d207b2069643a20747374722c206469676573743a207368613235362d646967657374207d0a0a3b20446967657374732061726520617574686f726974617469766520617320747970656420627974652076616c7565732c206e657665722068657820737472696e67732e20526576696577204a534f4e0a3b2072656e64657273207468697320617320227368613235363a3c3634206c6f77657263617365206865783e22202845444943542d4449474553542d574952452d303031292e0a7368613235362d646967657374203d205b20616c676f726974686d3a2022736861323536222c2062797465733a2062737472202e73697a65203332205d0a0a3b2041206e616d6564206c6f772d6c6576656c206661696c75726520616e206566666563742063616e2072616973652e2054686520736f75726365206f62737472756374696f6e206d61700a3b2062696e64732069742028627920636f6f7264696e6174652920616e6420636f6e73747275637473206120747970656420646f6d61696e206f62737472756374696f6e2066726f6d206974730a3b207061796c6f6164202845444943542d4142492d4641494c5552452d4e414d45442d303031292e0a3b20416e20656666656374277320606566666563744661696c7572657360206c697374204d555354206861766520756e697175652060636f6f7264696e61746560733a2073696e6365207468650a3b206f62737472756374696f6e206d6170206973206b6579656420627920636f6f7264696e6174652c2074776f206661696c757265732073686172696e67206120636f6f7264696e61746520286576656e0a3b207769746820646966666572656e7420617574686f72697479436c6173732f7061796c6f61645479706529206d616b652065786861757374697665206d617070696e6720616e642062696e6465720a3b20747970696e6720616d626967756f757320616e64206172652072656a6563746564202845444943542d4142492d4641494c5552452d554e495155452d303031292e0a3b0a3b2045666665637473206361727279207468656972206661696c757265732061732061206d617020607b206661696c7572652d6964656e74203d3e206566666563742d6661696c7572652d626f6479207d600a3b202873656520746865207461726765742f6c61777061636b2065666665637420736368656d6173292e20546865206661696c75726520636f6f7264696e61746520697320746865206d61700a3b204b45592c20736f206974206973206e6f7420726570656174656420696e2074686520626f647920616e642063616e6e6f74206469736167726565207769746820746865206b65792e0a6566666563742d6661696c7572652d626f6479203d207b0a2020617574686f72697479436c6173733a20617574686f726974792d636c6173732c0a20207061796c6f6164547970653a20636f72652d747970652d7265662c202020202020202020202020203b2074797065642c20626f756e64656420286d617920626520656d707479207265636f7264290a7d0a0a3b2041206661696c75726520636f6f7264696e617465206d7573742062652061206261726520456469637420606964656e746020286c65747465722f756e64657273636f7265207468656e0a3b206c6574746572732f6469676974732f756e64657273636f7265732920414e44206d757374206e6f742062652061207265736572766564206b6579776f72642028652e672e2060656c7365602c0a3b20606261736973602c20607768657265602c206072657175697265602c2060666f72602c2060696660292e2054686520736f75726365206f62737472756374696f6e2d6d6170204c4853206f6e6c790a3b20616363657074732061206e6f6e2d6b6579776f726420606964656e74602c20736f20612068797068656e2f646f742f6b6579776f726420636f6f7264696e61746520776f756c642062650a3b204142492d76616c69642079657420696d706f737369626c6520746f206d617020657868617573746976656c7920696e20736f757263652e20546865207265676578206361707475726573207468650a3b206c65786963616c2073686170653b206b6579776f7264206578636c7573696f6e20697320616e206164646974696f6e616c2076616c69646174696f6e2072756c650a3b202845444943542d4142492d4641494c5552452d4944454e542d303031292e0a6661696c7572652d6964656e74203d2074737472202e72656765787020225b412d5a612d7a5f5d5b412d5a612d7a302d395f5d2a220a0a6566666563742d6b696e64203d20227265616422202f202263726561746522202f2022656e7375726522202f20227265706c61636522202f202264656c65746522202f0a202020202020202020202020202022617070656e6422202f202272656475636522202f202273656d616e7469632e656d697422202f2022637573746f6d220a0a617574686f726974792d636c617373203d2022646f6d61696e4d61707061626c6522202f20227061727469636970616e744f776e656422202f2022696e746567726974794661756c7422202f0a202020202020202020202020202020202020227265736f757263654661756c7422202f2022696e7465726e616c4661756c74220a0a636f72652d747970652d726566203d20747374722020203b2063616e6f6e6963616c20436f7265207479706520636f6f7264696e6174650a0a3b20416e206f7065726174696f6e2070726f66696c6520737570706c69657320746865206f707469632074656d706c617465206120436f726520696e74656e74207265736f6c766573206974730a3b206f707469634b696e642f626f756e646172794b696e642f737570706f7274506f6c6963792f6c6f7373446973706f736974696f6e2066726f6d2e205461726765742070726f66696c657320616e640a3b206c61777061636b73207075626c6973682074686573652061732061206d617020607b20636f6f7264696e617465203d3e206f7065726174696f6e2d70726f66696c65207d602c20736f207468650a3b20636f6f7264696e61746520697320746865204b45592c206e6f7420612076616c7565206669656c64202845444943542d4f505449432d54454d504c4154452d4f574e45522d3030312c0a3b2045444943542d4142492d4f5050524f46494c452d554e495155452d303031292e0a6f7065726174696f6e2d70726f66696c65203d207b0a20206f7074696354656d706c6174653a206f707469632d74656d706c6174652c0a20206566666563745072656469636174653a20747374722c20202020202020202020203b20636f6f7264696e617465206f6620746865206f7065726174696f6e2d6d6f6465207072656469636174650a7d0a0a6f707469632d74656d706c617465203d207b0a20206f707469634b696e643a2022726576656c6174696f6e22202f20226166666563745265696e746567726174696f6e222c0a2020626f756e646172794b696e643a202270726f6a656374696f6e22202f2022616666656374222c0a2020737570706f7274506f6c6963793a20747374722c202020202020202020202020203b2063616e6f6e6963616c20737570706f72742d706f6c69637920636f6f7264696e6174650a20206c6f7373446973706f736974696f6e3a20747374722c20202020202020202020203b2063616e6f6e6963616c206c6f73732d646973706f736974696f6e20636f6f7264696e6174650a20203f20626173697354656d706c6174653a20747374722c20202020202020202020203b206f7074696f6e616c206469676573742d6c6f636b65642062617369732074656d706c61746520636f6f72640a20203b2074686520617065727475726520726571756972656d656e7420746869732074656d706c61746520737570706c6965732e205265717569726564207768656e207468652074656d706c6174650a20203b2069732074686520736f75726365206f66206120436f7265206f707469632773206170657274757265526571756972656d656e742028692e652e2074686520696e74656e7420686173206e6f0a20203b20736f757263652060666f6f747072696e74203c3d202e2e2e60292c2073696e6365206170657274757265526571756972656d656e74206973206d616e6461746f727920696e20436f72650a20203b202845444943542d4f505449432d41504552545552452d5245462d303031292e0a20203f206170657274757265526571756972656d656e743a2061706572747572652d726571756972656d656e742c0a7d0a0a3b206170657274757265526571756972656d656e742069732061207479706564207265666572656e63652c206e65766572206120667265652d666f726d20737472696e672e2041207265766965770a3b2072656e646572696e67206d61792073686f772069747320636f6f7264696e617465202845444943542d4f505449432d41504552545552452d5245462d303031292e0a61706572747572652d726571756972656d656e74203d20666f6f747072696e742d6365696c696e672d726566202f2061627374726163742d666f6f747072696e742d6f626c69676174696f6e2d7265660a666f6f747072696e742d6365696c696e672d726566203d207b206b696e643a2022666f6f747072696e744365696c696e67222c207265663a2074737472207d0a61627374726163742d666f6f747072696e742d6f626c69676174696f6e2d726566203d207b206b696e643a20226162737472616374466f6f747072696e744f626c69676174696f6e222c207265663a2074737472207d0a0a3b202d2d2d2065646963742d636f72652e6364646c202d2d2d0a3b2065646963742d636f72652e6364646c0a3b204e6f726d617469766520736368656d6120666f722074686520456469637420436f72652076312073656d616e746963206d6f64656c2e0a3b0a3b2053636f706520626f756e646172793a20746869732066696c6520646566696e657320436f7265206d65616e696e6720616e6420736368656d61207368617065206f6e6c792e20497420646f65730a3b206e6f7420646566696e6520612063616e6f6e6963616c20656e636f6465722c20436f7265206d6f64756c652068617368206669656c64732c20686173682066697874757265732c207461726765740a3b206c6f776572696e672c2061646d697373696f6e2062756e646c65732c206f72207461726765742d6f776e65642049522e0a0a636f72652d6d6f64756c65203d207b0a202061706956657273696f6e3a202265646963742e636f72652f7631222c0a2020636f6f7264696e6174653a20747374722c0a2020696d706f7274733a205b2a20636f72652d696d706f72745d2c0a202074797065733a207b202a2074737472203d3e20636f72652d74797065207d2c0a2020696e74656e74733a207b202b2074737472203d3e20636f72652d696e74656e74207d2c0a20207265717569726564436f72654361706162696c69746965733a205b2a20747374725d2c0a7d0a0a636f72652d696d706f7274203d207b0a20206b696e643a20226c61777061636b22202f202274617267657422202f2022636f7265222c0a20207265663a207265736f757263652d7265662c0a20203f20616c6961733a20747374722c0a7d0a0a3b202d2d2d207479706573202d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d0a0a636f72652d74797065203d20636f72652d7363616c61722d74797065202f20636f72652d7265636f72642d74797065202f20636f72652d76617269616e742d74797065202f0a202020202020202020202020636f72652d6f7074696f6e2d74797065202f20636f72652d6c6973742d74797065202f20636f72652d6d61702d74797065202f0a202020202020202020202020636f72652d6361706162696c6974792d7265662d747970650a0a636f72652d7363616c61722d74797065203d20636f72652d626f6f6c2d74797065202f20636f72652d696e742d74797065202f20636f72652d737472696e672d74797065202f0a20202020202020202020202020202020202020636f72652d62797465732d74797065202f20636f72652d756e69742d747970650a0a636f72652d626f6f6c2d74797065203d207b206b696e643a2022426f6f6c22207d0a636f72652d756e69742d74797065203d207b206b696e643a2022556e697422207d0a636f72652d696e742d74797065203d207b0a20206b696e643a202249363422202f202255363422202f202249333222202f202255333222202f202249313622202f202255313622202f2022493822202f20225538222c0a7d0a636f72652d737472696e672d74797065203d207b0a20206b696e643a2022537472696e67222c0a20206d61783a2075696e742c0a202063616e6f6e6963616c3a2022756e69636f64652d7363616c61722d6e666322202f20227261772d75746638222c0a7d0a636f72652d62797465732d74797065203d207b0a20206b696e643a20224279746573222c0a20206d61783a2075696e742c0a7d0a636f72652d7265636f72642d74797065203d207b0a20206b696e643a20225265636f7264222c0a20206669656c64733a207b202a2074737472203d3e20636f72652d747970652d726566207d2c0a7d0a636f72652d76617269616e742d74797065203d207b0a20206b696e643a202256617269616e74222c0a202063617365733a207b202b2074737472203d3e2076617269616e742d636173652d626f6479207d2c0a7d0a76617269616e742d636173652d626f6479203d207b0a20203f207061796c6f61643a20636f72652d747970652d7265662c0a7d0a636f72652d6f7074696f6e2d74797065203d207b0a20206b696e643a20224f7074696f6e222c0a20206974656d3a20636f72652d747970652d7265662c0a7d0a636f72652d6c6973742d74797065203d207b0a20206b696e643a20224c697374222c0a20206974656d3a20636f72652d747970652d7265662c0a20206d61783a2075696e742c0a7d0a636f72652d6d61702d74797065203d207b0a20206b696e643a20224d6170222c0a20206b65793a20636f72652d747970652d7265662c0a202076616c75653a20636f72652d747970652d7265662c0a20206d61783a2075696e742c0a7d0a636f72652d6361706162696c6974792d7265662d74797065203d207b0a20206b696e643a20224361706162696c697479526566222c0a20206974656d3a20636f72652d747970652d7265662c0a7d0a0a3b20636f72652d747970652d72656620697320646566696e656420696e2065646963742d636f6d6d6f6e2e6364646c20616e6420617373656d626c65642077697468207468697320736368656d612e0a0a3b202d2d2d207265666572656e63657320616e642076616c756573202d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d0a0a3b204c6f63616c206964656e7469747920697320616c7068612d737461626c652e20606964602069732074686520636f6d70696c65722d6f776e6564206c6f63616c20636f6f7264696e6174653b0a3b2060616c7068614e616d656020697320746865206e6f726d616c697a65642068756d616e2f6465627567206e616d652e20536f757263652062696e646572207370656c6c696e67206973206e6f740a3b206964656e746974792e0a6c6f63616c2d726566203d207b0a202069643a20747374722c0a2020616c7068614e616d653a20747374722c0a2020747970653a20636f72652d747970652d7265662c0a7d0a0a636f72652d76616c7565203d20636f72652d6e756c6c2d76616c7565202f20636f72652d626f6f6c2d76616c7565202f20636f72652d696e742d76616c7565202f0a20202020202020202020202020636f72652d737472696e672d76616c7565202f20636f72652d62797465732d76616c7565202f20636f72652d7265636f72642d76616c7565202f0a20202020202020202020202020636f72652d76617269616e742d76616c7565202f20636f72652d6c6973742d76616c7565202f20636f72652d6d61702d76616c7565202f0a20202020202020202020202020636f72652d6361706162696c6974792d76616c75650a0a636f72652d6e756c6c2d76616c7565203d207b206b696e643a20226e756c6c22207d0a636f72652d626f6f6c2d76616c7565203d207b206b696e643a2022626f6f6c222c2076616c75653a20626f6f6c207d0a636f72652d696e742d76616c7565203d207b206b696e643a2022696e74222c2077696474683a20747374722c2076616c75653a20696e74207d0a636f72652d737472696e672d76616c7565203d207b206b696e643a2022737472696e67222c2076616c75653a2074737472207d0a636f72652d62797465732d76616c7565203d207b206b696e643a20226279746573222c2076616c75653a2062737472207d0a636f72652d7265636f72642d76616c7565203d207b206b696e643a20227265636f7264222c206669656c64733a207b202a2074737472203d3e20636f72652d76616c7565207d207d0a636f72652d76617269616e742d76616c7565203d207b0a20206b696e643a202276617269616e74222c0a2020747970653a20636f72652d747970652d7265662c0a2020636173653a20747374722c0a20203f207061796c6f61643a20636f72652d76616c75652c0a7d0a636f72652d6c6973742d76616c7565203d207b206b696e643a20226c697374222c2076616c7565733a205b2a20636f72652d76616c75655d207d0a636f72652d6d61702d76616c7565203d207b206b696e643a20226d6170222c20656e74726965733a205b2a205b6b65793a20636f72652d76616c75652c2076616c75653a20636f72652d76616c75655d5d207d0a636f72652d6361706162696c6974792d76616c7565203d207b0a20206b696e643a20226361706162696c697479222c0a2020726563656970743a207368613235362d6469676573742c0a7d0a0a3b2045646963742d617574686f72656420707572652068656c70657273207573652061207075726520436f72652066756e6374696f6e20626f64792e2054686520626f64792063616e2062696e640a3b20707572652065787072657373696f6e7320616e642072657475726e20616e2065787072657373696f6e2c206275742069742063616e6e6f7420636f6e7461696e20436f7265206566666563742c0a3b2067756172642c206272616e63682c206c6f6f702c206d617463682d6e6f64652c206f722070726f6f662d6f626c69676174696f6e206e6f6465732e0a636f72652d666e2d626f6479203d207b0a2020706172616d733a205b2a206c6f63616c2d7265665d2c0a2020626f64793a20636f72652d707572652d626c6f636b2c0a7d0a0a636f72652d707572652d626c6f636b203d207b0a20206c6f63616c733a205b2a206c6f63616c2d7265665d2c0a202062696e64696e67733a205b2a20707572652d6c65742d6e6f64655d2c0a2020726573756c743a20636f72652d657870722c0a7d0a0a707572652d6c65742d6e6f6465203d207b0a20206b696e643a20226c6574222c0a202062696e64696e673a206c6f63616c2d7265662c0a202076616c75653a20636f72652d657870722c0a7d0a0a3b202d2d2d2065787072657373696f6e7320616e642070726564696361746573202d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d0a0a636f72652d65787072203d206c6f63616c2d65787072202f20636f6e73742d65787072202f207265636f72642d65787072202f206669656c642d65787072202f0a20202020202020202020202076617269616e742d65787072202f206d617463682d65787072202f2063616c6c2d65787072202f206c6973742d65787072202f206d61702d65787072202f0a20202020202020202020202069662d657870720a0a6c6f63616c2d65787072203d207b206b696e643a20226c6f63616c222c207265663a206c6f63616c2d726566207d0a636f6e73742d65787072203d207b206b696e643a2022636f6e7374222c2076616c75653a20636f72652d76616c7565207d0a7265636f72642d65787072203d207b206b696e643a20227265636f7264222c206669656c64733a207b202a2074737472203d3e20636f72652d65787072207d207d0a6669656c642d65787072203d207b206b696e643a20226669656c64222c20626173653a20636f72652d657870722c206669656c643a2074737472207d0a76617269616e742d65787072203d207b0a20206b696e643a202276617269616e74222c0a2020747970653a20636f72652d747970652d7265662c0a2020636173653a20747374722c0a20203f207061796c6f61643a20636f72652d657870722c0a7d0a6d617463682d65787072203d207b0a20206b696e643a20226d61746368222c0a20207363727574696e65653a20636f72652d657870722c0a202061726d733a205b2b206d617463682d61726d5d2c0a7d0a6d617463682d61726d203d207b0a2020636173653a20747374722c0a20203f2062696e6465723a206c6f63616c2d7265662c0a2020626f64793a20636f72652d657870722c0a7d0a63616c6c2d65787072203d207b0a20206b696e643a202263616c6c222c0a202063616c6c65653a20747374722c0a202074797065417267733a205b2a20636f72652d747970652d7265665d2c0a2020617267733a205b2a20636f72652d657870725d2c0a7d0a6c6973742d65787072203d207b206b696e643a20226c697374222c2076616c7565733a205b2a20636f72652d657870725d207d0a6d61702d65787072203d207b206b696e643a20226d6170222c20656e74726965733a205b2a205b6b65793a20636f72652d657870722c2076616c75653a20636f72652d657870725d5d207d0a69662d65787072203d207b0a20206b696e643a20226966222c0a20207072656469636174653a20636f72652d7072656469636174652c0a20207468656e3a20636f72652d657870722c0a2020656c73653a20636f72652d657870722c0a7d0a0a636f72652d707265646963617465203d20747275652d707265646963617465202f2066616c73652d707265646963617465202f206e6f742d707265646963617465202f0a2020202020202020202020202020202020616c6c2d707265646963617465202f20616e792d707265646963617465202f20636f6d706172652d707265646963617465202f0a202020202020202020202020202020202063616c6c2d707265646963617465202f206f62737472756374696f6e2d7072656469636174650a0a747275652d707265646963617465203d207b206b696e643a20227472756522207d0a66616c73652d707265646963617465203d207b206b696e643a202266616c736522207d0a6e6f742d707265646963617465203d207b206b696e643a20226e6f74222c2076616c75653a20636f72652d707265646963617465207d0a616c6c2d707265646963617465203d207b206b696e643a2022616c6c222c2076616c7565733a205b2b20636f72652d7072656469636174655d207d0a616e792d707265646963617465203d207b206b696e643a2022616e79222c2076616c7565733a205b2b20636f72652d7072656469636174655d207d0a636f6d706172652d707265646963617465203d207b0a20206b696e643a2022636f6d70617265222c0a20206f703a20223d3d22202f2022213d22202f20223c22202f20223c3d22202f20223e22202f20223e3d222c0a20206c6566743a20636f72652d657870722c0a202072696768743a20636f72652d657870722c0a7d0a63616c6c2d707265646963617465203d207b0a20206b696e643a202263616c6c222c0a20207072656469636174653a20747374722c0a2020617267733a205b2a20636f72652d657870725d2c0a7d0a6f62737472756374696f6e2d707265646963617465203d207b0a20206b696e643a20226f62737472756374696f6e222c0a2020636f6f7264696e6174653a206661696c7572652d6964656e742c0a20207061796c6f61643a20636f72652d657870722c0a7d0a0a696e7075742d636f6e73747261696e74203d207b0a2020636f6f7264696e6174653a20747374722c0a2020736f757263653a2022776865726522202f2022636f6d70696c6572222c0a20207072656469636174653a20636f72652d7072656469636174652c0a7d0a0a3b202d2d2d20696e74656e74732c20626c6f636b732c20616e64206e6f646573202d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d0a0a636f72652d696e74656e74203d207b0a2020696e7075743a20636f72652d747970652d7265662c0a20206f75747075743a20636f72652d747970652d7265662c0a202072657175697265644f7065726174696f6e50726f66696c653a20747374722c0a20203f2062617369733a20636f72652d657870722c0a2020696e707574436f6e73747261696e74733a205b2a20696e7075742d636f6e73747261696e745d2c0a2020636f72654576616c756174696f6e4275646765743a20636f72652d6275646765742c0a2020626f64793a20636f72652d626c6f636b2c0a20203f206f707469633a20636f72652d6f707469632c0a7d0a0a636f72652d627564676574203d207b0a20206d617853746570733a2075696e742c0a20206d6178416c6c6f636174656442797465733a2075696e742c0a20206d61784f757470757442797465733a2075696e742c0a7d0a0a636f72652d6f70746963203d207b0a20206f707469634b696e643a2022726576656c6174696f6e22202f20226166666563745265696e746567726174696f6e222c0a2020626f756e646172794b696e643a202270726f6a656374696f6e22202f2022616666656374222c0a20206170657274757265526571756972656d656e743a2061706572747572652d726571756972656d656e742c0a2020737570706f7274506f6c6963793a20747374722c0a20206c6f7373446973706f736974696f6e3a20747374722c0a7d0a0a636f72652d626c6f636b203d207b0a20206c6f63616c733a205b2a206c6f63616c2d7265665d2c0a20206e6f6465733a205b2a20636f72652d6e6f64655d2c0a2020726573756c743a20636f72652d657870722c0a7d0a0a636f72652d6e6f6465203d206c65742d6e6f6465202f20726571756972652d6e6f6465202f206566666563742d6e6f6465202f2067756172642d6e6f6465202f206272616e63682d6e6f6465202f0a202020202020202020202020666f722d6e6f6465202f206d617463682d6e6f6465202f2070726f6f662d6f626c69676174696f6e2d6e6f64650a0a6c65742d6e6f6465203d207b0a20206b696e643a20226c6574222c0a202062696e64696e673a206c6f63616c2d7265662c0a202076616c75653a20636f72652d657870722c0a7d0a726571756972652d6e6f6465203d207b0a20206b696e643a202272657175697265222c0a20207072656469636174653a20636f72652d7072656469636174652c0a20206f6e4661696c7572653a20726571756972652d6661696c7572652d61726d2c0a7d0a726571756972652d6661696c7572652d61726d203d207465726d696e616c2d726571756972652d6661696c757265202f0a20202020202020202020202020202020202020202020636f6e74696e75652d6f6273747275637465642d726571756972652d6661696c7572650a7465726d696e616c2d726571756972652d6661696c757265203d207b0a20206b696e643a20227465726d696e616c222c0a2020726561736f6e3a206f62737472756374696f6e2d726561736f6e2c0a7d0a636f6e74696e75652d6f6273747275637465642d726571756972652d6661696c757265203d207b0a20206b696e643a2022636f6e74696e75654f627374727563746564222c0a2020726561736f6e3a206f62737472756374696f6e2d726561736f6e2c0a7d0a6f62737472756374696f6e2d726561736f6e203d207b0a2020726561736f6e4b696e643a20747374722c0a20207061796c6f61643a207b202a2074737472203d3e20636f72652d65787072207d2c0a7d0a6566666563742d6e6f6465203d207b0a20206b696e643a2022656666656374222c0a202062696e64696e673a206c6f63616c2d7265662c0a20206566666563743a20747374722c0a2020696e7075743a20636f72652d657870722c0a20206f62737472756374696f6e4d61703a207b202a206661696c7572652d6964656e74203d3e206f62737472756374696f6e2d61726d207d2c0a7d0a6f62737472756374696f6e2d61726d203d207b0a202062696e6465723a206c6f63616c2d7265662c0a202076616c75653a20636f72652d657870722c0a7d0a67756172642d6e6f6465203d207b0a20206b696e643a20226775617264222c0a20207072656469636174653a20636f72652d7072656469636174652c0a20206f62737472756374696f6e3a20636f72652d657870722c0a7d0a6272616e63682d6e6f6465203d207b0a20206b696e643a20226272616e6368222c0a20207072656469636174653a20636f72652d7072656469636174652c0a20207468656e3a20636f72652d626c6f636b2c0a2020656c73653a20636f72652d626c6f636b2c0a7d0a666f722d6e6f6465203d207b0a20206b696e643a2022666f72222c0a202062696e6465723a206c6f63616c2d7265662c0a2020697465723a20636f72652d657870722c0a2020626f756e643a20636f72652d626f756e642c0a2020626f64793a20636f72652d626c6f636b2c0a7d0a6d617463682d6e6f6465203d207b0a20206b696e643a20226d61746368222c0a20207363727574696e65653a20636f72652d657870722c0a202061726d733a205b2b206d617463682d626c6f636b2d61726d5d2c0a7d0a6d617463682d626c6f636b2d61726d203d207b0a2020636173653a20747374722c0a20203f2062696e6465723a206c6f63616c2d7265662c0a2020626f64793a20636f72652d626c6f636b2c0a7d0a70726f6f662d6f626c69676174696f6e2d6e6f6465203d207b0a20206b696e643a202270726f6f66222c0a2020636f6f7264696e6174653a20747374722c0a20207072656469636174653a20636f72652d7072656469636174652c0a7d0a0a636f72652d626f756e64203d206c69746572616c2d626f756e64202f20636f6f7264696e6174652d626f756e640a6c69746572616c2d626f756e64203d207b206b696e643a20226c69746572616c222c2076616c75653a2075696e74207d0a636f6f7264696e6174652d626f756e64203d207b206b696e643a2022636f6f7264696e617465222c207265663a2074737472207d0a0a3b20536861726564207265736f757263652d7265662c207368613235362d6469676573742c206661696c7572652d6964656e742c2061706572747572652d726571756972656d656e742c20616e640a3b20636f72652d747970652d7265662061726520646566696e6564206f6e636520696e2065646963742d636f6d6d6f6e2e6364646c2e0a0a3b202d2d2d2065646963742d6c61777061636b2e6364646c202d2d2d0a3b2065646963742d6c61777061636b2e6364646c0a3b2043616e6f6e6963616c20736368656d6120666f7220746865204564696374206c61777061636b206d616e696665737420616e64206578706f727420737572666163652e0a3b20417574686f7269746174697665206279746520656e636f64696e673a2065646963742e63616e6f6e6963616c2d63626f722f76312e204a534f4e20696e207468652070726f73652073706563730a3b2069732061207265766965772072656e646572696e672067656e6572617465642066726f6d207468697320736368656d61202845444943542d4142492d4e4f4455502d303031292e0a0a6c61777061636b2d6d616e6966657374203d207b0a202061706956657273696f6e3a202265646963742e6c61777061636b2f7631222c0a202069643a20747374722c0a202076657273696f6e3a20747374722c0a20206163636570746564436f72654162693a205b2b20747374725d2c0a2020646570656e64656e636965733a205b2a206c61777061636b2d6465705d2c202020202020202020203b20616379636c69632c206469676573742d6c6f636b6564202845444943542d4c41575041434b2d4441472d303031290a20206578706f7274733a207265736f757263652d7265662c0a20203f2074617267657441646170746572733a205b2b207461726765742d616461707465725d2c2020203b207265717569726564206f6e6c7920696620616e792072756e74696d6520656666656374206578697374730a20203f2068656c706572436f6d706f6e656e743a2065786563757461626c652d636f6d706f6e656e742c203b2065786563757461626c652068656c70657273206361727279207468656972206f776e2073616e64626f782b6675656c0a202076657269666965723a2076657269666965722c202020202020202020202020202020202020202020203b20636c61737369666965643a206465636c61726174697665206f722065786563757461626c650a2020636f6d7061746962696c6974793a207265736f757263652d7265662c0a2020636f6e666f726d616e636546697874757265436f727075733a207265736f757263652d7265662c0a7d0a0a3b2041207665726966696572206973206569746865722061206465636c617261746976652072756c6573657420286e6f2072756e74696d6529206f7220616e2065786563757461626c650a3b20636f6d706f6e656e742e20416e2065786563757461626c65207665726966696572204d55535420636172727920697473206f776e2073616e64626f7820616e64206675656c206d6f64656c2c0a3b20736f2074686520736368656d6120656e666f726365732074686174206e6f2065786563757461626c6520636f6d706f6e656e74206973206c65667420756e626f756e6465640a3b202845444943542d4142492d56455249464945522d424f554e442d303031292e0a7665726966696572203d206465636c617261746976652d7665726966696572202f2065786563757461626c652d76657269666965720a6465636c617261746976652d7665726966696572203d207b20636c6173733a20226465636c61726174697665222c2072756c657365743a207265736f757263652d726566207d0a65786563757461626c652d7665726966696572203d207b0a2020636c6173733a202265786563757461626c65222c0a2020636f6d706f6e656e743a207265736f757263652d7265662c0a202073616e64626f783a207265736f757263652d7265662c0a20206675656c4d6f64656c3a207265736f757263652d7265662c0a7d0a0a3b20416e792065786563757461626c6520636f6d706f6e656e7420697320626f756e64656420627920697473206f776e2073616e64626f78202b206675656c206d6f64656c2e0a65786563757461626c652d636f6d706f6e656e74203d207b0a2020636f6d706f6e656e743a207265736f757263652d7265662c0a202073616e64626f783a207265736f757263652d7265662c0a20206675656c4d6f64656c3a207265736f757263652d7265662c0a7d0a0a6c61777061636b2d646570203d207b2069643a20747374722c2076657273696f6e3a20747374722c206469676573743a207368613235362d646967657374207d0a0a3b20416461707465722073656c656374696f6e206b65797320534f4c454c59206f666620746865206469676573742d6c6f636b65642060616363657074656454617267657450726f66696c65600a3b20286974732060696460206973207468652070726f66696c652069643b206974732060646967657374602070696e73207468652065786163742070726f66696c652f76657273696f6e292e2054686572650a3b20617265206e6f20696e646570656e64656e7420646973706c617920737472696e6773207468617420636f756c64206469736167726565207769746820746865206c6f636b2c20736f20610a3b207265736f6c7665722063616e6e6f742062696e6420616e206164617074657220746f206f6e6520746172676574207768696c6520746865206c6f636b2070726f76657320616e6f746865720a3b202845444943542d4c41575041434b2d414441505445522d54415247455449522d303031292e0a7461726765742d61646170746572203d207b0a2020616363657074656454617267657450726f66696c653a207265736f757263652d7265662c202020203b206469676573742d6c6f636b65642c20617574686f72697461746976652073656c6563746f720a2020616363657074656454617267657449723a207265736f757263652d7265662c2020202020202020203b206469676573742d6c6f636b65640a2020616461707465723a207265736f757263652d7265662c0a7d0a0a3b20536861726564207479706573207265736f757263652d72656620616e64207368613235362d6469676573742061726520646566696e6564206f6e636520696e0a3b2065646963742d636f6d6d6f6e2e6364646c20616e6420617373656d626c65642077697468207468697320736368656d61202845444943542d4142492d4e4f4455502d303031292e0a0a3b202d2d2d206578706f72742073757266616365202d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d0a0a6c61777061636b2d6578706f727473203d207b0a202074797065733a205b2a206578706f727465642d747970655d2c0a2020636f6e7374616e74733a205b2a206578706f727465642d636f6e7374616e745d2c0a20207075726546756e6374696f6e733a205b2a20707572652d66756e6374696f6e5d2c0a2020656666656374733a205b2a2073656d616e7469632d6566666563745d2c0a20206f62737472756374696f6e733a205b2a206f62737472756374696f6e2d6465665d2c0a20203b206b65796564206279206f7065726174696f6e2d70726f66696c6520636f6f7264696e61746520e2869220756e697175656e65737320656e666f726365640a20203b202845444943542d4142492d4f5050524f46494c452d554e495155452d303031290a20203b206f7065726174696f6e2d70726f66696c65207265636f7264732074686973206c61777061636b206578706f72747320286f707469632074656d706c6174657320746861740a20203b2060696d706c656d656e7473602f6070726f66696c656020636c6175736573207265736f6c766520616761696e7374292e206f7065726174696f6e2d70726f66696c652069730a20203b20646566696e656420696e2065646963742d636f6d6d6f6e2e6364646c202845444943542d4142492d4f5050524f46494c452d534c4f542d303031292e0a20206f7065726174696f6e50726f66696c65733a207b202a2074737472203d3e206f7065726174696f6e2d70726f66696c65207d2c20203b206b6579656420627920636f6f7264696e6174650a7d0a0a6578706f727465642d7479706520202020203d207b20636f6f7264696e6174653a20747374722c20646566696e6974696f6e3a20636f72652d747970652d726566207d0a6578706f727465642d636f6e7374616e74203d207b20636f6f7264696e6174653a20747374722c20747970653a20636f72652d747970652d7265662c2076616c75653a20616e79207d0a0a3b204120707572652068656c7065722069732061206469736372696d696e6174656420756e696f6e2062792060736f75726365602c20736f2074686520736368656d6120697473656c660a3b2067756172616e7465657320616e20696d706c656d656e746174696f6e20657869737473202845444943542d4c41575041434b2d505552452d494d504c2d303031293a0a3b2020202d20226564696374223a20617574686f72656420696e2045646963742f436f72653b2074686520436f726520626f6479206973206361727269656420696e6c696e6520286861736865640a3b20202020207769746820746865206578706f72742073757266616365292e2054686520736368656d61207265717569726573207468652060626f647960206669656c642e0a3b2020202d2022636f6d706f6e656e74223a20696d706c656d656e746564206f7574736964652045646963743b2063617272696573206e6f20696e6c696e6520626f647920616e6420696e73746561640a3b20202020206361727269657320697473206f776e206469676573742d6c6f636b65642060696d706c656d656e746174696f6e60202873616e64626f78202b206675656c292e20497420646f65730a3b20202020206e6f7420646570656e64206f6e20746865206f7074696f6e616c206d616e69666573742d6c6576656c2068656c706572436f6d706f6e656e742e0a707572652d66756e6374696f6e203d2065646963742d707572652d66756e6374696f6e202f20636f6d706f6e656e742d707572652d66756e6374696f6e0a0a707572652d66756e6374696f6e2d636f6d6d6f6e203d20280a2020636f6f7264696e6174653a20747374722c0a202074797065506172616d65746572733a205b2a20747374725d2c0a2020706172616d6574657254797065733a205b2a20636f72652d747970652d7265665d2c2020202020203b20616c6c20626f756e6465640a202072657475726e547970653a20636f72652d747970652d7265662c20202020202020202020202020203b20626f756e6465640a2020636f737454656d706c6174653a20747374722c0a202064657465726d696e69736d436c6173733a2022746f74616c22202f2022746f74616c2d776974682d74797065642d646961676e6f73746963222c0a290a0a65646963742d707572652d66756e6374696f6e203d207b0a2020707572652d66756e6374696f6e2d636f6d6d6f6e2c0a2020736f757263653a20226564696374222c0a2020626f64793a20636f72652d666e2d626f64792c2020202020202020202020202020202020202020203b20696e6c696e652c20686173682d7369676e69666963616e740a7d0a0a636f6d706f6e656e742d707572652d66756e6374696f6e203d207b0a2020707572652d66756e6374696f6e2d636f6d6d6f6e2c0a2020736f757263653a2022636f6d706f6e656e74222c0a20203b20746865206469676573742d6c6f636b656420636f6d706f6e656e7420696d706c656d656e74696e6720746869732068656c7065722e2052657175697265642061742074686520736368656d610a20203b206c6576656c20736f206120636f6d706f6e656e742068656c7065722063616e206e657665722076616c696461746520776974686f7574206120686173682d626f756e642c0a20203b2073616e64626f782b6675656c2d64657363726962656420696d706c656d656e746174696f6e202845444943542d4c41575041434b2d505552452d494d504c2d303031292e0a2020696d706c656d656e746174696f6e3a2065786563757461626c652d636f6d706f6e656e742c0a7d0a0a3b20636f72652d666e2d626f647920697320646566696e65642062792065646963742d636f72652e6364646c20616e6420617373656d626c656420776974682074686973206c61777061636b0a3b20736368656d612e2049742069732061207075726520436f72652066756e6374696f6e20626f64792c206e6f7420616e206566666563742d63617061626c6520636f72652d626c6f636b2e0a0a73656d616e7469632d656666656374203d207b0a2020636f6f7264696e6174653a20747374722c0a202074797065506172616d65746572733a205b2a20747374725d2c0a2020696e707574547970653a20636f72652d747970652d7265662c2020202020202020202020202020203b20626f756e6465640a20206f7574707574547970653a20636f72652d747970652d7265662c20202020202020202020202020203b20626f756e6465640a2020657865637574696f6e436c6173733a202270726f6f664f6e6c7922202f202272756e74696d65222c2020203b206f7274686f676f6e616c20746f207772697465436c6173730a20206566666563744b696e6448696e743a206566666563742d6b696e642c0a2020666f6f747072696e744f626c69676174696f6e3a20747374722c0a2020636f73744f626c69676174696f6e3a20747374722c0a20206566666563744661696c757265733a207b202a206661696c7572652d6964656e74203d3e206566666563742d6661696c7572652d626f6479207d2c20203b206b6579656420627920636f6f7264696e6174653b20756e697175650a20206775617264537570706f72743a20626f6f6c2c0a7d0a0a6f62737472756374696f6e2d646566203d207b0a2020636f6f7264696e6174653a20747374722c0a2020617574686f72697479436c6173733a20617574686f726974792d636c6173732c0a20207061796c6f6164536368656d613a20636f72652d747970652d7265662c20202020202020202020203b2074797065642c20626f756e64656420286d617920626520656d707479207265636f7264290a7d0a0a3b206566666563742d6661696c7572652d626f64792c206566666563742d6b696e642c20617574686f726974792d636c6173732c20616e6420636f72652d747970652d7265662061726520646566696e65640a3b206f6e636520696e2065646963742d636f6d6d6f6e2e6364646c20616e6420617373656d626c65642077697468207468697320736368656d61202845444943542d4142492d4e4f4455502d303031292e0a0a3b202d2d2d2065646963742d6c61777061636b2d616461707465722e6364646c202d2d2d0a3b2065646963742d6c61777061636b2d616461707465722e6364646c0a3b2043616e6f6e6963616c20736368656d6120666f72206f6e6520646972656374206465636c61726174697665206c61777061636b2074617267657420616461707465722e0a3b20417574686f7269746174697665206279746520656e636f64696e673a2065646963742e63616e6f6e6963616c2d63626f722f76312e0a3b0a3b2054686520656e636c6f73696e67206c61777061636b206d616e69666573742073656c6563747320746865206578616374207461726765742070726f66696c652c207461726765742049522c0a3b20616e642061646170746572207265736f75726365206469676573742e2054686f7365206964656e74697469657320617265206e6f7420726570656174656420686572652e0a0a6c61777061636b2d61646170746572203d207b0a202061706956657273696f6e3a202265646963742e6c61777061636b2d616461707465722f7631222c0a2020636c6173733a20226465636c61726174697665222c0a20206f7065726174696f6e50726f66696c65733a207b0a202020202a2074737472203d3e206c61777061636b2d616461707465722d6f7065726174696f6e2d70726f66696c650a20207d2c0a2020656666656374496d706c656d656e746174696f6e733a207b0a202020202a2074737472203d3e206c61777061636b2d616461707465722d6566666563740a20207d2c0a2020627564676574733a207b0a202020202a2074737472203d3e206c61777061636b2d616461707465722d6275646765740a20207d2c0a7d0a0a3b204b657973206172652063616e6f6e6963616c206c61777061636b206f7065726174696f6e2d70726f66696c6520636f6f7264696e617465732e0a6c61777061636b2d616461707465722d6f7065726174696f6e2d70726f66696c65203d207b0a2020636f72653a20747374722c0a202073656d616e746963456666656374733a205b2b20747374725d2c0a7d0a0a3b204b657973206172652063616e6f6e6963616c206c61777061636b2073656d616e7469632d65666665637420636f6f7264696e617465732e20466f6f747072696e742c20636f73742c20616e640a3b206661696c757265206669656c6473206d7573742065786163746c792064697363686172676520746865206d61746368696e67206578706f72746564206566666563742e0a6c61777061636b2d616461707465722d656666656374203d207b0a2020746172676574496e7472696e7369633a20747374722c0a2020746172676574436f6e66696775726174696f6e3a207265736f757263652d7265662c0a20207772697465436c6173733a206c61777061636b2d616461707465722d77726974652d636c6173732c0a2020666f6f747072696e744f626c69676174696f6e3a20747374722c0a2020636f73744f626c69676174696f6e3a20747374722c0a20206661696c7572654d617070696e67733a207b202a206661696c7572652d6964656e74203d3e2074737472207d2c0a7d0a0a3b204b657973206172652063616e6f6e6963616c206578706f7274656420636f73742d6f626c69676174696f6e20636f6f7264696e617465732e0a6c61777061636b2d616461707465722d627564676574203d207b0a20206d617853746570733a2075696e742c0a20206d6178416c6c6f636174656442797465733a2075696e742c0a20206d61784f757470757442797465733a2075696e742c0a7d0a0a6c61777061636b2d616461707465722d77726974652d636c617373203d20226e6f6e6522202f20227265616422202f202263726561746522202f2022656e7375726522202f0a20202020202020202020202020202020202020202020202020202020202022617070656e6422202f20227265706c61636522202f202264656c65746522202f2022637573746f6d220a0a3b206661696c7572652d6964656e7420697320646566696e656420696e2065646963742d636f6d6d6f6e2e6364646c2e0a0a3b202d2d2d2065646963742d7461726765742d70726f66696c652e6364646c202d2d2d0a3b2065646963742d7461726765742d70726f66696c652e6364646c0a3b2043616e6f6e6963616c20736368656d6120666f7220746865204564696374207461726765742070726f66696c65206d616e69666573742e0a3b20417574686f7269746174697665206279746520656e636f64696e673a2065646963742e63616e6f6e6963616c2d63626f722f76310a3b202873656520535045435f636f6e74696e75756d2d636f6e74726163742d62756e646c652d76312e6d64292e204a534f4e20696e207468652070726f736520737065637320697320610a3b207265766965772072656e646572696e672067656e6572617465642066726f6d207468697320736368656d613b2074686973204344444c206973207468652073696e676c6520736f757263650a3b206f66207472757468202845444943542d4142492d4e4f4455502d303031292e0a0a7461726765742d70726f66696c652d6d616e6966657374203d207b0a202061706956657273696f6e3a202265646963742e7461726765742d70726f66696c652f7631222c0a202069643a20747374722c202020202020202020202020202020202020202020202020203b20652e672e20226563686f2e64706f220a202076657273696f6e3a20747374722c20202020202020202020202020202020202020203b20652e672e202231220a20206163636570746564436f72654162693a205b2b20747374725d2c20202020202020203b20652e672e205b2265646963742e636f72652f7631225d0a0a2020696e7472696e736963733a207265736f757263652d7265662c0a2020696e7472696e7369634e616d6573706163653a20747374722c0a20203b207075626c697368657320746869732070726f66696c652773206f7065726174696f6e2d70726f66696c65207265636f72647320286f707469632074656d706c6174657320746861740a20203b206070726f66696c65602f60696d706c656d656e74736020636c6175736573207265736f6c766520616761696e7374292e205265666572656e63657320616e0a20203b206f7065726174696f6e2d70726f66696c65732d646f63756d656e74202845444943542d4142492d4f5050524f46494c452d534c4f542d303031292e0a20206f7065726174696f6e50726f66696c65733a207265736f757263652d7265662c0a2020666f6f747072696e74416c67656272613a207265736f757263652d7265662c0a2020636f7374416c67656272613a207265736f757263652d7265662c0a202074617267657449723a207265736f757263652d7265662c0a20206f62737472756374696f6e5461786f6e6f6d793a207265736f757263652d7265662c0a202076657269666965723a207265736f757263652d7265662c0a20206c6f77657265723a207265736f757263652d7265662c0a202073616e64626f783a207265736f757263652d7265662c0a20206675656c4d6f64656c3a207265736f757263652d7265662c0a0a20203b206669656c647320746865206c616e67756167652073706563207265717569726573206f662065766572792070726f66696c650a202062756e646c6550726f66696c653a207265736f757263652d7265662c0a202067656e657261746564417274696661637450726f66696c65733a205b2a207265736f757263652d7265665d2c0a202063616e6f6e6963616c456e636f64696e6752756c65733a207265736f757263652d7265662c0a20203b20412070726f66696c65207468617420616363657074732074686520646972656374206465636c61726174697665206c61777061636b2d6164617074657220414249206e616d65732069740a20203b2065786163746c79206f6e63652e2050726f66696c6573207468617420646f206e6f7420636f6e73756d65206c61777061636b206164617074657273206c6561766520746869730a20203b206f7074696f6e616c20736c6f7420616273656e74206f7220656d7074792e0a20203f2061636365707465644c61777061636b416461707465724162693a205b5d202f205b2265646963742e6c61777061636b2d616461707465722f7631225d2c0a2020646961676e6f737469634162693a207265736f757263652d7265662c0a0a20203b206170706c69636174696f6e20646f637472696e650a20206170706c69636174696f6e4d6f64656c3a202261746f6d6963222c0a202072656164436f6e73697374656e63793a20226170706c69636174696f6e2d736e617073686f7422202f20747374722c0a202067756172644576616c756174696f6e3a2022707265636f6d6d69742d61746f6d696322202f20747374722c0a20206f62737472756374696f6e526f6c6c6261636b3a20226e6f2d76697369626c652d6566666563747322202f20747374722c0a20206d756c74695461726765743a20626f6f6c2c0a20203b207768657468657220746865207461726765742063616e206576616c7561746520707265636f6d6d697420706f7374636f6e646974696f6e20286067756172616e746565602920636865636b730a20203b20696e73696465207468652061746f6d6963206170706c69636174696f6e20756e6974202845444943542d5441524745542d504f5354434f4e442d303031290a2020706f7374636f6e646974696f6e537570706f72743a20626f6f6c2c0a0a202064657465726d696e6973746963457865637574696f6e3a207265736f757263652d7265662c0a2020636f6e666f726d616e636546697874757265436f727075733a207265736f757263652d7265662c0a7d0a0a3b20536861726564207479706573207265736f757263652d72656620616e64207368613235362d6469676573742061726520646566696e6564206f6e636520696e0a3b2065646963742d636f6d6d6f6e2e6364646c20616e6420617373656d626c65642077697468207468697320736368656d6120627920746865206275696c640a3b202845444943542d4142492d4e4f4455502d303031292e205468657920617265206e6f74207265646566696e656420686572652e0a0a3b202d2d2d20696e7472696e736963207369676e6174757265202d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d0a3b20546865206172746966616374207265666572656e63656420627920746865206d616e696665737427732060696e7472696e7369637360207265736f757263652d726566206973207468650a3b20696e7472696e7369632d7369676e617475726520636f7270757320646f63756d656e742062656c6f772e20497473206c61796f757420697320666978656420736f2074776f0a3b20696e646570656e64656e742070726f66696c65732076616c69646174652f686173682074686520636f72707573206964656e746963616c6c790a3b202845444943542d4142492d494e5452494e534943532d444f432d303031292e0a0a3b20696e7472696e736963732069732061204d4150206b6579656420627920636f6f7264696e6174652c20736f2074686520736368656d6120697473656c6620656e666f726365730a3b20636f6f7264696e61746520756e697175656e6573732e20412070726f766964657220726563656976657320746865207265736f6c76656420636f7270757320617320610a3b206469676573742d626f756e642073656d616e74696320696e70757420616e64207265736f6c76657320636f6f7264696e617465732077697468696e20746861742061727469666163742e0a3b2045616368206d6170206b6579204d55535420657175616c20697473207265636f726427732060636f6f7264696e61746560206669656c640a3b202845444943542d4142492d494e5452494e5349432d554e495155452d303031292e0a696e7472696e736963732d646f63756d656e74203d207b0a202061706956657273696f6e3a202265646963742e7461726765742d70726f66696c652e696e7472696e736963732f7631222c0a2020696e7472696e736963733a207b202a2074737472203d3e20696e7472696e736963207d2c0a7d0a0a3b20546865206172746966616374207265666572656e63656420627920746865206d616e6966657374277320606f7065726174696f6e50726f66696c657360207265736f757263652d7265662e0a3b206f7065726174696f6e2d70726f66696c65202f206f707469632d74656d706c6174652061726520646566696e656420696e2065646963742d636f6d6d6f6e2e6364646c2e204b657965642062790a3b20636f6f7264696e61746520736f207265736f6c7574696f6e2063616e2774207069636b206265747765656e2074776f2073616d652d636f6f7264696e6174652070726f66696c65730a3b202845444943542d4142492d4f5050524f46494c452d534c4f542d3030312c2045444943542d4142492d4f5050524f46494c452d554e495155452d303031292e0a6f7065726174696f6e2d70726f66696c65732d646f63756d656e74203d207b0a202061706956657273696f6e3a202265646963742e7461726765742d70726f66696c652e6f7065726174696f6e2d70726f66696c65732f7631222c0a202070726f66696c65733a207b202a2074737472203d3e206f7065726174696f6e2d70726f66696c65207d2c0a7d0a0a3b2041207479706564207072652d6c6f776572696e67207175657374696f6e20746861742063616e2062652070726f706f73656420627920576174736f6e206f7220616e206167656e7420616e640a3b20636865636b65642062792074686520636f6d70696c65722e2049742069732063616e6f6e6963616c2d43424f5220656e636f64656420756e6465720a3b206065646963742e6c6f776572696e672d726571756972656d656e74732f7631603b2074686520636f6d70696c657220636865636b7320746869732061727469666163742c206e6f74207468650a3b2070726f736520746861742070726f64756365642069742e0a6c6f776572696e672d726571756972656d656e7473203d207b0a202061706956657273696f6e3a202265646963742e6c6f776572696e672d726571756972656d656e74732f7631222c0a20206f7065726174696f6e50726f66696c653a20747374722c0a202073656d616e746963456666656374733a205b2a2073656d616e7469632d6566666563742d726571756972656d656e745d2c0a202072657175697265645772697465436c61737365733a205b2a2077726974652d636c6173735d2c0a202067756172644b696e64733a205b2a2067756172642d6b696e645d2c0a202061746f6d69636974793a2061746f6d69636974792d726571756972656d656e742c0a2020706f7374636f6e646974696f6e537570706f72743a20626f6f6c2c0a20206f62737472756374696f6e436f6f7264696e617465733a205b2a20747374725d2c0a2020666f6f747072696e744f626c69676174696f6e733a205b2a20747374725d2c0a2020636f73744f626c69676174696f6e733a205b2a20747374725d2c0a20206f70746963436f6e74726163743a20747374722c0a7d0a0a73656d616e7469632d6566666563742d726571756972656d656e74203d207b0a2020636f6f7264696e6174653a20747374722c0a20207772697465436c6173733a2077726974652d636c6173732c0a202067756172644b696e64733a205b2a2067756172642d6b696e645d2c0a20206f62737472756374696f6e436f6f7264696e617465733a205b2a20747374725d2c0a2020666f6f747072696e744f626c69676174696f6e733a205b2a20747374725d2c0a2020636f73744f626c69676174696f6e733a205b2a20747374725d2c0a7d0a0a77726974652d636c617373203d20226e6f6e6522202f20227265616422202f202263726561746522202f2022656e7375726522202f2022617070656e6422202f0a2020202020202020202020202020227265706c61636522202f202264656c65746522202f20747374720a67756172642d6b696e64203d2022707265636f6d6d69742d61746f6d696322202f20747374720a61746f6d69636974792d726571756972656d656e74203d202261746f6d696322202f20747374720a0a3b20412067656e75696e6520756e696f6e3a207075726520636f6e7374727563746f7273206361727279206e6f20656666656374206b696e64206f72206661696c757265733b206566666563740a3b20696e7472696e73696373206d757374202845444943542d5441524745542d494e5452494e5349432d434c4153532d303031292e2054686520736368656d6120656e666f7263657320746869732c0a3b206e6f74206120636f6d6d656e742e0a0a3b2054686520696e7472696e736963277320636f6f7264696e6174652069732074686520696e7472696e73696373206d6170204b45592c206e6f7420612076616c7565206669656c642c20736f207468650a3b206b657920616e6420636f6f7264696e6174652063616e206e65766572206469736167726565202845444943542d4142492d494e5452494e5349432d554e495155452d303031292e0a696e7472696e736963203d20707572652d696e7472696e736963202f206566666563742d696e7472696e7369630a0a707572652d696e7472696e736963203d207b0a2020696e7472696e736963436c6173733a202270757265222c0a202074797065506172616d65746572733a205b2a20747374725d2c0a2020617267756d656e7454797065733a205b2a20636f72652d747970652d7265665d2c0a202072657475726e547970653a20636f72652d747970652d7265662c0a20206775617264537570706f72743a2066616c73652c0a2020666f6f747072696e7454656d706c6174653a20747374722c0a2020636f737454656d706c6174653a20747374722c0a20207772697465436c6173733a20226e6f6e65222c0a7d0a0a6566666563742d696e7472696e736963203d207b0a2020696e7472696e736963436c6173733a2022656666656374222c0a202074797065506172616d65746572733a205b2a20747374725d2c0a2020617267756d656e7454797065733a205b2a20636f72652d747970652d7265665d2c0a202072657475726e547970653a20636f72652d747970652d7265662c0a20206566666563744b696e643a206566666563742d6b696e642c0a20203b206d6170206b65796564206279206661696c75726520636f6f7264696e61746520286661696c7572652d6964656e74293b20746865206661696c75726520636f6f7264696e6174652069730a20203b20746865206b65792c206e6f7420612076616c7565206669656c642c20736f20756e697175656e657373206973207374727563747572616c0a20203b202845444943542d4142492d4641494c5552452d554e495155452d303031292e0a20206566666563744661696c757265733a207b202a206661696c7572652d6964656e74203d3e206566666563742d6661696c7572652d626f6479207d2c0a20206775617264537570706f72743a20626f6f6c2c0a2020666f6f747072696e7454656d706c6174653a20747374722c0a2020636f737454656d706c6174653a20747374722c0a20207772697465436c6173733a20227265616422202f202263726561746522202f2022656e7375726522202f2022617070656e6422202f20227265706c61636522202f0a20202020202020202020202020202264656c65746522202f2022637573746f6d222c0a202063616e5061727469636970617465496e41746f6d696347756172643a20626f6f6c2c0a7d0a0a3b206566666563742d6661696c7572652d626f64792c206566666563742d6b696e642c20617574686f726974792d636c6173732c20616e6420636f72652d747970652d7265662061726520646566696e65640a3b206f6e636520696e2065646963742d636f6d6d6f6e2e6364646c20616e6420617373656d626c65642077697468207468697320736368656d61202845444943542d4142492d4e4f4455502d303031292e0a0a3b202d2d2d2065646963742d617574686f726974792d66616374732e6364646c202d2d2d0a3b2065646963742d617574686f726974792d66616374732e6364646c0a3b2043616e6f6e6963616c20736368656d6120666f722074686520666972737420636f6d70696c65722d636f6e7465787420617574686f726974792d666163747320646f63756d656e742e0a3b20417574686f7269746174697665206279746520656e636f64696e673a2065646963742e63616e6f6e6963616c2d63626f722f76312e0a3b0a3b205468697320736368656d6120697320617373656d626c656420776974682065646963742d636f6d6d6f6e2e6364646c20736f20736f757263652e6469676573742075736573207468650a3b20736861726564207368613235362d6469676573742074797065642076616c75652e204a534f4e2069732061207265766965772f696e7075742072656e646572696e673a206974730a3b20607368613235363a3c3634206865783e6020736f75726365206469676573742069732070726f6a656374656420746f205b60736861323536602c203332207261772062797465735d206f6e0a3b2074686520776972652c20616e64206974732066616374206172726179732070726f6a65637420746f2074686520636f6f7264696e6174652d6b65796564206d6170732062656c6f772e0a0a617574686f726974792d6661637473203d207b0a202061706956657273696f6e3a202265646963742e617574686f726974792d66616374732f7631222c0a2020736f757263653a20617574686f726974792d666163742d736f757263652c0a20206f7065726174696f6e50726f66696c65733a207b202a2074737472203d3e20617574686f726974792d6f7065726174696f6e2d70726f66696c652d66616374207d2c0a20206566666563745772697465436c61737365733a207b202a2074737472203d3e20617574686f726974792d77726974652d636c617373207d2c0a2020627564676574733a207b202a2074737472203d3e20617574686f726974792d6275646765742d66616374207d2c0a7d0a0a617574686f726974792d666163742d736f75726365203d207b0a20206b696e643a20226c61777061636b22202f202274617267657450726f66696c65222c0a2020636f6f7264696e6174653a20747374722c0a20206469676573743a207368613235362d6469676573742c0a7d0a0a3b20546865206d6170206b65792069732074686520736f75726365206f7065726174696f6e2d70726f66696c6520636f6f7264696e6174652e204974206973206e6f7420726570656174656420696e0a3b207468652076616c75652c20736f2061206b657920616e6420656d62656464656420636f6f7264696e6174652063616e6e6f742064697361677265652e20416c6c6f7765642077726974650a3b20636c61737365732061726520612063616e6f6e6963616c206d61702d7365743a2074686520636c6173732069732074686520756e69717565206b657920616e64206e756c6c206973207468650a3b20756e6974206d61726b65722e2043616e6f6e6963616c2043424f52206669786573206b6579206f7264657220776974686f75742061207365636f6e64206f72646572696e672072756c652e0a617574686f726974792d6f7065726174696f6e2d70726f66696c652d66616374203d207b0a2020636f72653a20747374722c0a2020616c6c6f7765645772697465436c61737365733a207b202a20617574686f726974792d77726974652d636c617373203d3e206e756c6c207d2c0a7d0a0a3b20546865206566666563745772697465436c6173736573206d6170206b6579206973207468652073656d616e7469632065666665637420636f6f7264696e6174652e2054686520627564676574730a3b206d6170206b65792069732074686520736f757263652062756467657420636f6f7264696e6174652e2043616e6f6e6963616c2043424f52206d61702d6b657920756e697175656e6573730a3b206d616b6573206475706c6963617465206661637420636f6f7264696e61746573207374727563747572616c6c7920756e726570726573656e7461626c652e0a617574686f726974792d6275646765742d66616374203d207b0a20206d617853746570733a2075696e742c0a20206d6178416c6c6f636174656442797465733a2075696e742c0a20206d61784f757470757442797465733a2075696e742c0a7d0a0a3b20417574686f726974794661637473446f63756d656e7420763120696e74656e74696f6e616c6c792061636365707473206f6e6c792074686520777269746520636c6173736573207468650a3b2063757272656e7420636f6d70696c6572206d6f64656c2063616e20636f6e73756d652e2060637573746f6d602069732074686520736f6c6520763120637573746f6d207370656c6c696e673b0a3b20617262697472617279207461726765742d70726f66696c6520657874656e73696f6e20737472696e677320646f206e6f7420656e746572207468697320636f6d70696c657220706174682e0a617574686f726974792d77726974652d636c617373203d20226e6f6e6522202f20227265616422202f202263726561746522202f2022656e7375726522202f2022617070656e6422202f0a202020202020202020202020202020202020202020202020227265706c61636522202f202264656c65746522202f2022637573746f6d220a0a3b202d2d2d2065646963742d726573756c742d70726f6a656374696f6e2e6364646c202d2d2d0a3b20535044582d4c6963656e73652d4964656e7469666965723a204170616368652d322e300a3b2065646963742d726573756c742d70726f6a656374696f6e2e6364646c0a3b2043616e6f6e6963616c20736368656d6120666f7220636f6d70696c65722d6f776e6564206170706c69636174696f6e2d726573756c742070726f6a656374696f6e732e0a3b20417574686f7269746174697665206279746520656e636f64696e673a2065646963742e63616e6f6e6963616c2d63626f722f76312e0a0a726573756c742d70726f6a656374696f6e203d207b0a2020736368656d613a202265646963742e726573756c742d70726f6a656374696f6e2f7631222c0a20206f7065726174696f6e436f6f7264696e6174653a20626f756e6465642d70726f6a656374696f6e2d746578742c0a20206f7574707574547970653a20626f756e6465642d70726f6a656374696f6e2d746578742c0a20206d61784f757470757442797465733a2075696e74202e677420302c0a202065787072657373696f6e3a20726573756c742d70726f6a656374696f6e2d657870722c0a7d0a0a726573756c742d70726f6a656374696f6e2d65787072203d20726573756c742d70726f6a656374696f6e2d7265636f7264202f20726573756c742d70726f6a656374696f6e2d736f757263650a0a726573756c742d70726f6a656374696f6e2d7265636f7264203d207b0a20206b696e643a20227265636f7264222c0a20203b2054686520726f6f74207265636f726420636f756e7473206173206f6e65206f66207468652052757374206465636f6465722773203235362065787072657373696f6e206e6f6465732e0a20203b204e657374656420616767726567617465206e6f646520636f756e742072656d61696e7320616e20617574686f7269746174697665206465636f64657220636865636b2e0a20206669656c64733a207b20302a32353520626f756e6465642d70726f6a656374696f6e2d74657874203d3e20726573756c742d70726f6a656374696f6e2d65787072207d2c0a7d0a0a726573756c742d70726f6a656374696f6e2d736f75726365203d207b0a20206b696e643a2022736f75726365222c0a2020736f757263653a20726573756c742d70726f6a656374696f6e2d736f757263652d6b696e642c0a20203b204d617463686573204d41585f524553554c545f50524f4a454354494f4e5f504154485f5345474d454e545320696e2065646963742d73796e7461782e0a2020706174683a205b302a333220626f756e6465642d70726f6a656374696f6e2d746578745d2c0a7d0a0a726573756c742d70726f6a656374696f6e2d736f757263652d6b696e64203d0a20207b206b696e643a20226170706c69636174696f6e496e70757422207d202f0a20207b0a202020206b696e643a20226361706162696c697479526573756c74222c0a202020207374657049643a20626f756e6465642d70726f6a656374696f6e2d746578742c0a20207d0a0a626f756e6465642d70726f6a656374696f6e2d74657874203d2074737472202e73697a652028312e2e31303234290a0a3b202d2d2d2065646963742d7461726765742d69722e6364646c202d2d2d0a3b20535044582d4c6963656e73652d4964656e7469666965723a204170616368652d322e300a3b2065646963742d7461726765742d69722e6364646c0a3b2043616e6f6e6963616c20736368656d6120666f72207468652045646963742d6f776e65642054617267657420495220617274696661637420656e76656c6f70652e0a3b20417574686f7269746174697665206279746520656e636f64696e673a2065646963742e63616e6f6e6963616c2d63626f722f76312e0a3b0a3b205468697320736368656d6120697320617373656d626c656420776974682065646963742d636f6d6d6f6e2e6364646c20616e642065646963742d636f72652e6364646c2e2049740a3b2064656c696265726174656c792072657573657320436f72652065787072657373696f6e732c20707265646963617465732c20627564676574732c206c6f63616c207265666572656e6365732c0a3b206f62737472756374696f6e20726561736f6e732c20616e64206f62737472756374696f6e2061726d7320736f2074686520736368656d61206d617463686573207468652076616c75650a3b20656d6974746564206279207468652063616e6f6e6963616c2054617267657420495220656e636f64657220726174686572207468616e20726573746174696e672074686f73652074797065732e0a3b2049742064657363726962657320746865207374727563747572616c207368617065206f662076616c6964206c6f776572696e672d70726f6475636564206172746966616374732e205468650a3b206c6f776572696e6720616e6420656e636f64657220636f6e7472616374732073657061726174656c7920656e666f7263652073656d616e746963206964656e7469666965722072756c65730a3b20616e642063616e6f6e6963616c206f72646572696e672f64656475706c69636174696f6e20666f72207365742d6c696b652076616c7565732e0a0a3b2054617267657420495220656e636f64696e672072656a6563747320616e20656d707479207461726765742d70726f66696c6520636f6f7264696e617465206265666f72652062797465730a3b2065786973742c20736f207468697320726f6f74207469676874656e732074686520736861726564207374727563747572616c207265736f757263652d726566206163636f7264696e676c792e0a7461726765742d69722d7265736f757263652d726566203d207b0a202069643a2074737472202e7265676578702022283f73292e2b222c0a20206469676573743a207368613235362d6469676573742c0a7d0a0a7461726765742d69722d6172746966616374203d207461726765742d69722d636c6f7365642d6172746966616374202f207461726765742d69722d6c65676163792d61727469666163740a0a7461726765742d69722d61727469666163742d636f6d6d6f6e203d20280a20206b696e643a202274617267657449724172746966616374222c0a2020646f6d61696e3a20747374722c0a202074617267657450726f66696c653a207461726765742d69722d7265736f757263652d7265662c0a2020736f75726365436f7265436f6f7264696e6174653a2074737472202e7265676578702022283f73292e2b222c0a290a0a7461726765742d69722d636c6f7365642d6172746966616374203d207b0a20207461726765742d69722d61727469666163742d636f6d6d6f6e2c0a202073656d616e746963436c6f737572653a207461726765742d69722d73656d616e7469632d636c6f737572652c0a2020696e74656e74733a207b202a2074737472203d3e207461726765742d69722d696e74656e74207d2c0a7d0a0a7461726765742d69722d6c65676163792d6172746966616374203d207b0a20207461726765742d69722d61727469666163742d636f6d6d6f6e2c0a2020696e74656e74733a207b202a2074737472203d3e207461726765742d69722d6c65676163792d696e74656e74207d2c0a7d0a0a7461726765742d69722d73656d616e7469632d636c6f73757265203d207b0a2020736f75726365436f72653a207461726765742d69722d7265736f757263652d7265662c0a20206c61777061636b733a205b2a207461726765742d69722d7265736f757263652d7265665d2c0a7d0a0a7461726765742d69722d696e74656e74203d207b0a20207461726765742d69722d696e74656e742d636f6d6d6f6e2c0a20203f2062617369733a20636f72652d657870722c0a7d0a0a7461726765742d69722d6c65676163792d696e74656e74203d207b0a20207461726765742d69722d696e74656e742d636f6d6d6f6e2c0a7d0a0a7461726765742d69722d696e74656e742d636f6d6d6f6e203d20280a20206f7065726174696f6e50726f66696c653a20747374722c0a2020696e707574436f6e73747261696e74733a205b2a20696e7075742d636f6e73747261696e745d2c0a2020636f72654576616c756174696f6e4275646765743a20636f72652d6275646765742c0a2020726571756972656d656e74733a205b2a207461726765742d69722d726571756972656d656e745d2c0a202073746570733a205b2a207461726765742d69722d737465705d2c0a2020726573756c743a20636f72652d657870722c0a290a0a7461726765742d69722d726571756972656d656e74203d207b0a202069643a20747374722c0a20207072656469636174653a20636f72652d7072656469636174652c0a20206f6e4661696c7572653a20726571756972652d6661696c7572652d61726d2c0a7d0a0a7461726765742d69722d73746570203d207b0a202069643a20747374722c0a202062696e64696e673a206c6f63616c2d7265662c0a20206566666563743a20747374722c0a2020746172676574496e7472696e7369633a20747374722c0a2020696e7075743a20636f72652d657870722c0a20206f62737472756374696f6e4661696c757265733a205b2a206661696c7572652d6964656e745d2c0a20206f62737472756374696f6e41726d733a207b202a206661696c7572652d6964656e74203d3e206f62737472756374696f6e2d61726d207d2c0a7d0a", - "rawSha256": "ff5405708185e0bcf33dac263000fcb772c79e21145ad69da7cad0692e1a9552" + "bytesHex": "3b20535044582d4c6963656e73652d4964656e7469666965723a204170616368652d322e300a3b2065646963742d70726f76696465722d636f6e7472616374732e6364646c0a3b2047656e6572617465642066726f6d2045646963742d6f776e65642041424920667261676d656e74732e20444f204e4f5420454449542e0a0a3b202d2d2d2065646963742d636f6d6d6f6e2e6364646c202d2d2d0a3b2065646963742d636f6d6d6f6e2e6364646c0a3b20536861726564204344444c20747970657320666f722074686520456469637420414249732c20646566696e6564204f4e4345206865726520736f20746865792063616e6e6f742064726966740a3b202845444943542d4142492d4e4f4455502d303031292e2054776f2067726f7570733a0a3b2020202d207265736f757263652d7265662c207368613235362d6469676573742c206566666563742d6661696c7572652d626f64792c206566666563742d6b696e642c20617574686f726974792d636c6173732c0a3b2020202020636f72652d747970652d7265663a20617373656d626c656420776974682065646963742d7461726765742d70726f66696c652e6364646c20616e640a3b202020202065646963742d6c61777061636b2e6364646c20627920746865206275696c643b2074686f736520736368656d617320646f206e6f74207265646566696e65207468656d2e0a3b2020202d206f7065726174696f6e2d70726f66696c652c206f707469632d74656d706c6174652c2061706572747572652d726571756972656d656e7420616e6420746865697220726566733a0a3b2020202020636f6e73756d65642062792074686520436f72652f6f70746963206c61796572202865646963742d636f72652e6364646c2920616e64207265666572656e636564206279207468650a3b20202020206c616e67756167652f7461726765742d70726f66696c652073706563732e0a3b20417574686f7269746174697665206279746520656e636f64696e673a2065646963742e63616e6f6e6963616c2d63626f722f76312e0a0a3b2041206e6f726d617469766520737562636f6d706f6e656e74207265666572656e636564206279206964656e7469747920706c7573206469676573742e204d616e696665737473206e657665720a3b20656d626564207468656972206f776e2073656c662d64696765737420696e20746865697220707265696d616765202845444943542d434f52452d53454c46484153482d303031292e0a7265736f757263652d726566203d207b2069643a20747374722c206469676573743a207368613235362d646967657374207d0a0a3b20446967657374732061726520617574686f726974617469766520617320747970656420627974652076616c7565732c206e657665722068657820737472696e67732e20526576696577204a534f4e0a3b2072656e64657273207468697320617320227368613235363a3c3634206c6f77657263617365206865783e22202845444943542d4449474553542d574952452d303031292e0a7368613235362d646967657374203d205b20616c676f726974686d3a2022736861323536222c2062797465733a2062737472202e73697a65203332205d0a0a3b2041206e616d6564206c6f772d6c6576656c206661696c75726520616e206566666563742063616e2072616973652e2054686520736f75726365206f62737472756374696f6e206d61700a3b2062696e64732069742028627920636f6f7264696e6174652920616e6420636f6e73747275637473206120747970656420646f6d61696e206f62737472756374696f6e2066726f6d206974730a3b207061796c6f6164202845444943542d4142492d4641494c5552452d4e414d45442d303031292e0a3b20416e20656666656374277320606566666563744661696c7572657360206c697374204d555354206861766520756e697175652060636f6f7264696e61746560733a2073696e6365207468650a3b206f62737472756374696f6e206d6170206973206b6579656420627920636f6f7264696e6174652c2074776f206661696c757265732073686172696e67206120636f6f7264696e61746520286576656e0a3b207769746820646966666572656e7420617574686f72697479436c6173732f7061796c6f61645479706529206d616b652065786861757374697665206d617070696e6720616e642062696e6465720a3b20747970696e6720616d626967756f757320616e64206172652072656a6563746564202845444943542d4142492d4641494c5552452d554e495155452d303031292e0a3b0a3b2045666665637473206361727279207468656972206661696c757265732061732061206d617020607b206661696c7572652d6964656e74203d3e206566666563742d6661696c7572652d626f6479207d600a3b202873656520746865207461726765742f6c61777061636b2065666665637420736368656d6173292e20546865206661696c75726520636f6f7264696e61746520697320746865206d61700a3b204b45592c20736f206974206973206e6f7420726570656174656420696e2074686520626f647920616e642063616e6e6f74206469736167726565207769746820746865206b65792e0a6566666563742d6661696c7572652d626f6479203d207b0a2020617574686f72697479436c6173733a20617574686f726974792d636c6173732c0a20207061796c6f6164547970653a20636f72652d747970652d7265662c202020202020202020202020203b2074797065642c20626f756e64656420286d617920626520656d707479207265636f7264290a7d0a0a3b2041206661696c75726520636f6f7264696e617465206d7573742062652061206261726520456469637420606964656e746020286c65747465722f756e64657273636f7265207468656e0a3b206c6574746572732f6469676974732f756e64657273636f7265732920414e44206d757374206e6f742062652061207265736572766564206b6579776f72642028652e672e2060656c7365602c0a3b20606261736973602c20607768657265602c206072657175697265602c2060666f72602c2060696660292e2054686520736f75726365206f62737472756374696f6e2d6d6170204c4853206f6e6c790a3b20616363657074732061206e6f6e2d6b6579776f726420606964656e74602c20736f20612068797068656e2f646f742f6b6579776f726420636f6f7264696e61746520776f756c642062650a3b204142492d76616c69642079657420696d706f737369626c6520746f206d617020657868617573746976656c7920696e20736f757263652e20546865207265676578206361707475726573207468650a3b206c65786963616c2073686170653b206b6579776f7264206578636c7573696f6e20697320616e206164646974696f6e616c2076616c69646174696f6e2072756c650a3b202845444943542d4142492d4641494c5552452d4944454e542d303031292e0a6661696c7572652d6964656e74203d2074737472202e72656765787020225b412d5a612d7a5f5d5b412d5a612d7a302d395f5d2a220a0a6566666563742d6b696e64203d20227265616422202f202263726561746522202f2022656e7375726522202f20227265706c61636522202f202264656c65746522202f0a202020202020202020202020202022617070656e6422202f202272656475636522202f202273656d616e7469632e656d697422202f2022637573746f6d220a0a617574686f726974792d636c617373203d2022646f6d61696e4d61707061626c6522202f20227061727469636970616e744f776e656422202f2022696e746567726974794661756c7422202f0a202020202020202020202020202020202020227265736f757263654661756c7422202f2022696e7465726e616c4661756c74220a0a636f72652d747970652d726566203d20747374722020203b2063616e6f6e6963616c20436f7265207479706520636f6f7264696e6174650a0a3b20416e206f7065726174696f6e2070726f66696c6520737570706c69657320746865206f707469632074656d706c617465206120436f726520696e74656e74207265736f6c766573206974730a3b206f707469634b696e642f626f756e646172794b696e642f737570706f7274506f6c6963792f6c6f7373446973706f736974696f6e2066726f6d2e205461726765742070726f66696c657320616e640a3b206c61777061636b73207075626c6973682074686573652061732061206d617020607b20636f6f7264696e617465203d3e206f7065726174696f6e2d70726f66696c65207d602c20736f207468650a3b20636f6f7264696e61746520697320746865204b45592c206e6f7420612076616c7565206669656c64202845444943542d4f505449432d54454d504c4154452d4f574e45522d3030312c0a3b2045444943542d4142492d4f5050524f46494c452d554e495155452d303031292e0a6f7065726174696f6e2d70726f66696c65203d207b0a20206f7074696354656d706c6174653a206f707469632d74656d706c6174652c0a20206566666563745072656469636174653a20747374722c20202020202020202020203b20636f6f7264696e617465206f6620746865206f7065726174696f6e2d6d6f6465207072656469636174650a7d0a0a6f707469632d74656d706c617465203d207b0a20206f707469634b696e643a2022726576656c6174696f6e22202f20226166666563745265696e746567726174696f6e222c0a2020626f756e646172794b696e643a202270726f6a656374696f6e22202f2022616666656374222c0a2020737570706f7274506f6c6963793a20747374722c202020202020202020202020203b2063616e6f6e6963616c20737570706f72742d706f6c69637920636f6f7264696e6174650a20206c6f7373446973706f736974696f6e3a20747374722c20202020202020202020203b2063616e6f6e6963616c206c6f73732d646973706f736974696f6e20636f6f7264696e6174650a20203f20626173697354656d706c6174653a20747374722c20202020202020202020203b206f7074696f6e616c206469676573742d6c6f636b65642062617369732074656d706c61746520636f6f72640a20203b2074686520617065727475726520726571756972656d656e7420746869732074656d706c61746520737570706c6965732e205265717569726564207768656e207468652074656d706c6174650a20203b2069732074686520736f75726365206f66206120436f7265206f707469632773206170657274757265526571756972656d656e742028692e652e2074686520696e74656e7420686173206e6f0a20203b20736f757263652060666f6f747072696e74203c3d202e2e2e60292c2073696e6365206170657274757265526571756972656d656e74206973206d616e6461746f727920696e20436f72650a20203b202845444943542d4f505449432d41504552545552452d5245462d303031292e0a20203f206170657274757265526571756972656d656e743a2061706572747572652d726571756972656d656e742c0a7d0a0a3b206170657274757265526571756972656d656e742069732061207479706564207265666572656e63652c206e65766572206120667265652d666f726d20737472696e672e2041207265766965770a3b2072656e646572696e67206d61792073686f772069747320636f6f7264696e617465202845444943542d4f505449432d41504552545552452d5245462d303031292e0a61706572747572652d726571756972656d656e74203d20666f6f747072696e742d6365696c696e672d726566202f2061627374726163742d666f6f747072696e742d6f626c69676174696f6e2d7265660a666f6f747072696e742d6365696c696e672d726566203d207b206b696e643a2022666f6f747072696e744365696c696e67222c207265663a2074737472207d0a61627374726163742d666f6f747072696e742d6f626c69676174696f6e2d726566203d207b206b696e643a20226162737472616374466f6f747072696e744f626c69676174696f6e222c207265663a2074737472207d0a0a3b202d2d2d2065646963742d636f72652e6364646c202d2d2d0a3b2065646963742d636f72652e6364646c0a3b204e6f726d617469766520736368656d6120666f722074686520456469637420436f72652076312073656d616e746963206d6f64656c2e0a3b0a3b2053636f706520626f756e646172793a20746869732066696c6520646566696e657320436f7265206d65616e696e6720616e6420736368656d61207368617065206f6e6c792e20497420646f65730a3b206e6f7420646566696e6520612063616e6f6e6963616c20656e636f6465722c20436f7265206d6f64756c652068617368206669656c64732c20686173682066697874757265732c207461726765740a3b206c6f776572696e672c2061646d697373696f6e2062756e646c65732c206f72207461726765742d6f776e65642049522e0a0a636f72652d6d6f64756c65203d207b0a202061706956657273696f6e3a202265646963742e636f72652f7631222c0a2020636f6f7264696e6174653a20747374722c0a2020696d706f7274733a205b2a20636f72652d696d706f72745d2c0a202074797065733a207b202a2074737472203d3e20636f72652d74797065207d2c0a2020696e74656e74733a207b202b2074737472203d3e20636f72652d696e74656e74207d2c0a20207265717569726564436f72654361706162696c69746965733a205b2a20747374725d2c0a7d0a0a636f72652d696d706f7274203d207b0a20206b696e643a20226c61777061636b22202f202274617267657422202f2022636f726522202f20226361706162696c697479222c0a20207265663a207265736f757263652d7265662c0a20203f20616c6961733a20747374722c0a7d0a0a3b202d2d2d207479706573202d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d0a0a636f72652d74797065203d20636f72652d7363616c61722d74797065202f20636f72652d7265636f72642d74797065202f20636f72652d76617269616e742d74797065202f0a202020202020202020202020636f72652d6f7074696f6e2d74797065202f20636f72652d6c6973742d74797065202f20636f72652d6d61702d74797065202f0a202020202020202020202020636f72652d6361706162696c6974792d7265662d74797065202f20636f72652d65787465726e616c2d616374696f6e2d726571756573742d747970650a0a636f72652d7363616c61722d74797065203d20636f72652d626f6f6c2d74797065202f20636f72652d696e742d74797065202f20636f72652d737472696e672d74797065202f0a20202020202020202020202020202020202020636f72652d62797465732d74797065202f20636f72652d756e69742d747970650a0a636f72652d626f6f6c2d74797065203d207b206b696e643a2022426f6f6c22207d0a636f72652d756e69742d74797065203d207b206b696e643a2022556e697422207d0a636f72652d696e742d74797065203d207b0a20206b696e643a202249363422202f202255363422202f202249333222202f202255333222202f202249313622202f202255313622202f2022493822202f20225538222c0a7d0a636f72652d737472696e672d74797065203d207b0a20206b696e643a2022537472696e67222c0a20206d61783a2075696e742c0a202063616e6f6e6963616c3a2022756e69636f64652d7363616c61722d6e666322202f20227261772d75746638222c0a7d0a636f72652d62797465732d74797065203d207b0a20206b696e643a20224279746573222c0a20206d61783a2075696e742c0a7d0a636f72652d7265636f72642d74797065203d207b0a20206b696e643a20225265636f7264222c0a20206669656c64733a207b202a2074737472203d3e20636f72652d747970652d726566207d2c0a7d0a636f72652d76617269616e742d74797065203d207b0a20206b696e643a202256617269616e74222c0a202063617365733a207b202b2074737472203d3e2076617269616e742d636173652d626f6479207d2c0a7d0a76617269616e742d636173652d626f6479203d207b0a20203f207061796c6f61643a20636f72652d747970652d7265662c0a7d0a636f72652d6f7074696f6e2d74797065203d207b0a20206b696e643a20224f7074696f6e222c0a20206974656d3a20636f72652d747970652d7265662c0a7d0a636f72652d6c6973742d74797065203d207b0a20206b696e643a20224c697374222c0a20206974656d3a20636f72652d747970652d7265662c0a20206d61783a2075696e742c0a7d0a636f72652d6d61702d74797065203d207b0a20206b696e643a20224d6170222c0a20206b65793a20636f72652d747970652d7265662c0a202076616c75653a20636f72652d747970652d7265662c0a20206d61783a2075696e742c0a7d0a636f72652d6361706162696c6974792d7265662d74797065203d207b0a20206b696e643a20224361706162696c697479526566222c0a20206974656d3a20636f72652d747970652d7265662c0a7d0a636f72652d65787465726e616c2d616374696f6e2d726571756573742d74797065203d207b0a20206b696e643a202245787465726e616c416374696f6e52657175657374222c0a2020736574746c656d656e743a20636f72652d747970652d7265662c0a7d0a0a3b20636f72652d747970652d72656620697320646566696e656420696e2065646963742d636f6d6d6f6e2e6364646c20616e6420617373656d626c65642077697468207468697320736368656d612e0a0a3b202d2d2d207265666572656e63657320616e642076616c756573202d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d0a0a3b204c6f63616c206964656e7469747920697320616c7068612d737461626c652e20606964602069732074686520636f6d70696c65722d6f776e6564206c6f63616c20636f6f7264696e6174653b0a3b2060616c7068614e616d656020697320746865206e6f726d616c697a65642068756d616e2f6465627567206e616d652e20536f757263652062696e646572207370656c6c696e67206973206e6f740a3b206964656e746974792e0a6c6f63616c2d726566203d207b0a202069643a20747374722c0a2020616c7068614e616d653a20747374722c0a2020747970653a20636f72652d747970652d7265662c0a7d0a0a636f72652d76616c7565203d20636f72652d6e756c6c2d76616c7565202f20636f72652d626f6f6c2d76616c7565202f20636f72652d696e742d76616c7565202f0a20202020202020202020202020636f72652d737472696e672d76616c7565202f20636f72652d62797465732d76616c7565202f20636f72652d7265636f72642d76616c7565202f0a20202020202020202020202020636f72652d76617269616e742d76616c7565202f20636f72652d6c6973742d76616c7565202f20636f72652d6d61702d76616c7565202f0a20202020202020202020202020636f72652d6361706162696c6974792d76616c75650a0a636f72652d6e756c6c2d76616c7565203d207b206b696e643a20226e756c6c22207d0a636f72652d626f6f6c2d76616c7565203d207b206b696e643a2022626f6f6c222c2076616c75653a20626f6f6c207d0a636f72652d696e742d76616c7565203d207b206b696e643a2022696e74222c2077696474683a20747374722c2076616c75653a20696e74207d0a636f72652d737472696e672d76616c7565203d207b206b696e643a2022737472696e67222c2076616c75653a2074737472207d0a636f72652d62797465732d76616c7565203d207b206b696e643a20226279746573222c2076616c75653a2062737472207d0a636f72652d7265636f72642d76616c7565203d207b206b696e643a20227265636f7264222c206669656c64733a207b202a2074737472203d3e20636f72652d76616c7565207d207d0a636f72652d76617269616e742d76616c7565203d207b0a20206b696e643a202276617269616e74222c0a2020747970653a20636f72652d747970652d7265662c0a2020636173653a20747374722c0a20203f207061796c6f61643a20636f72652d76616c75652c0a7d0a636f72652d6c6973742d76616c7565203d207b206b696e643a20226c697374222c2076616c7565733a205b2a20636f72652d76616c75655d207d0a636f72652d6d61702d76616c7565203d207b206b696e643a20226d6170222c20656e74726965733a205b2a205b6b65793a20636f72652d76616c75652c2076616c75653a20636f72652d76616c75655d5d207d0a636f72652d6361706162696c6974792d76616c7565203d207b0a20206b696e643a20226361706162696c697479222c0a2020726563656970743a207368613235362d6469676573742c0a7d0a0a3b2045646963742d617574686f72656420707572652068656c70657273207573652061207075726520436f72652066756e6374696f6e20626f64792e2054686520626f64792063616e2062696e640a3b20707572652065787072657373696f6e7320616e642072657475726e20616e2065787072657373696f6e2c206275742069742063616e6e6f7420636f6e7461696e20436f7265206566666563742c0a3b2067756172642c206272616e63682c206c6f6f702c206d617463682d6e6f64652c206f722070726f6f662d6f626c69676174696f6e206e6f6465732e0a636f72652d666e2d626f6479203d207b0a2020706172616d733a205b2a206c6f63616c2d7265665d2c0a2020626f64793a20636f72652d707572652d626c6f636b2c0a7d0a0a636f72652d707572652d626c6f636b203d207b0a20206c6f63616c733a205b2a206c6f63616c2d7265665d2c0a202062696e64696e67733a205b2a20707572652d6c65742d6e6f64655d2c0a2020726573756c743a20636f72652d657870722c0a7d0a0a707572652d6c65742d6e6f6465203d207b0a20206b696e643a20226c6574222c0a202062696e64696e673a206c6f63616c2d7265662c0a202076616c75653a20636f72652d657870722c0a7d0a0a3b202d2d2d2065787072657373696f6e7320616e642070726564696361746573202d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d0a0a636f72652d65787072203d206c6f63616c2d65787072202f20636f6e73742d65787072202f207265636f72642d65787072202f206669656c642d65787072202f0a20202020202020202020202076617269616e742d65787072202f206d617463682d65787072202f2063616c6c2d65787072202f206c6973742d65787072202f206d61702d65787072202f0a20202020202020202020202069662d657870720a0a6c6f63616c2d65787072203d207b206b696e643a20226c6f63616c222c207265663a206c6f63616c2d726566207d0a636f6e73742d65787072203d207b206b696e643a2022636f6e7374222c2076616c75653a20636f72652d76616c7565207d0a7265636f72642d65787072203d207b206b696e643a20227265636f7264222c206669656c64733a207b202a2074737472203d3e20636f72652d65787072207d207d0a6669656c642d65787072203d207b206b696e643a20226669656c64222c20626173653a20636f72652d657870722c206669656c643a2074737472207d0a76617269616e742d65787072203d207b0a20206b696e643a202276617269616e74222c0a2020747970653a20636f72652d747970652d7265662c0a2020636173653a20747374722c0a20203f207061796c6f61643a20636f72652d657870722c0a7d0a6d617463682d65787072203d207b0a20206b696e643a20226d61746368222c0a20207363727574696e65653a20636f72652d657870722c0a202061726d733a205b2b206d617463682d61726d5d2c0a7d0a6d617463682d61726d203d207b0a2020636173653a20747374722c0a20203f2062696e6465723a206c6f63616c2d7265662c0a2020626f64793a20636f72652d657870722c0a7d0a63616c6c2d65787072203d207b0a20206b696e643a202263616c6c222c0a202063616c6c65653a20747374722c0a202074797065417267733a205b2a20636f72652d747970652d7265665d2c0a2020617267733a205b2a20636f72652d657870725d2c0a7d0a6c6973742d65787072203d207b206b696e643a20226c697374222c2076616c7565733a205b2a20636f72652d657870725d207d0a6d61702d65787072203d207b206b696e643a20226d6170222c20656e74726965733a205b2a205b6b65793a20636f72652d657870722c2076616c75653a20636f72652d657870725d5d207d0a69662d65787072203d207b0a20206b696e643a20226966222c0a20207072656469636174653a20636f72652d7072656469636174652c0a20207468656e3a20636f72652d657870722c0a2020656c73653a20636f72652d657870722c0a7d0a0a636f72652d707265646963617465203d20747275652d707265646963617465202f2066616c73652d707265646963617465202f206e6f742d707265646963617465202f0a2020202020202020202020202020202020616c6c2d707265646963617465202f20616e792d707265646963617465202f20636f6d706172652d707265646963617465202f0a202020202020202020202020202020202063616c6c2d707265646963617465202f206f62737472756374696f6e2d7072656469636174650a0a747275652d707265646963617465203d207b206b696e643a20227472756522207d0a66616c73652d707265646963617465203d207b206b696e643a202266616c736522207d0a6e6f742d707265646963617465203d207b206b696e643a20226e6f74222c2076616c75653a20636f72652d707265646963617465207d0a616c6c2d707265646963617465203d207b206b696e643a2022616c6c222c2076616c7565733a205b2b20636f72652d7072656469636174655d207d0a616e792d707265646963617465203d207b206b696e643a2022616e79222c2076616c7565733a205b2b20636f72652d7072656469636174655d207d0a636f6d706172652d707265646963617465203d207b0a20206b696e643a2022636f6d70617265222c0a20206f703a20223d3d22202f2022213d22202f20223c22202f20223c3d22202f20223e22202f20223e3d222c0a20206c6566743a20636f72652d657870722c0a202072696768743a20636f72652d657870722c0a7d0a63616c6c2d707265646963617465203d207b0a20206b696e643a202263616c6c222c0a20207072656469636174653a20747374722c0a2020617267733a205b2a20636f72652d657870725d2c0a7d0a6f62737472756374696f6e2d707265646963617465203d207b0a20206b696e643a20226f62737472756374696f6e222c0a2020636f6f7264696e6174653a206661696c7572652d6964656e742c0a20207061796c6f61643a20636f72652d657870722c0a7d0a0a696e7075742d636f6e73747261696e74203d207b0a2020636f6f7264696e6174653a20747374722c0a2020736f757263653a2022776865726522202f2022636f6d70696c6572222c0a20207072656469636174653a20636f72652d7072656469636174652c0a7d0a0a3b202d2d2d20696e74656e74732c20626c6f636b732c20616e64206e6f646573202d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d0a0a636f72652d696e74656e74203d207b0a2020696e7075743a20636f72652d747970652d7265662c0a20206f75747075743a20636f72652d747970652d7265662c0a202072657175697265644f7065726174696f6e50726f66696c653a20747374722c0a20203f2062617369733a20636f72652d657870722c0a2020696e707574436f6e73747261696e74733a205b2a20696e7075742d636f6e73747261696e745d2c0a2020636f72654576616c756174696f6e4275646765743a20636f72652d6275646765742c0a2020626f64793a20636f72652d626c6f636b2c0a20203f206f707469633a20636f72652d6f707469632c0a7d0a0a636f72652d627564676574203d207b0a20206d617853746570733a2075696e742c0a20206d6178416c6c6f636174656442797465733a2075696e742c0a20206d61784f757470757442797465733a2075696e742c0a7d0a0a636f72652d6f70746963203d207b0a20206f707469634b696e643a2022726576656c6174696f6e22202f20226166666563745265696e746567726174696f6e222c0a2020626f756e646172794b696e643a202270726f6a656374696f6e22202f2022616666656374222c0a20206170657274757265526571756972656d656e743a2061706572747572652d726571756972656d656e742c0a2020737570706f7274506f6c6963793a20747374722c0a20206c6f7373446973706f736974696f6e3a20747374722c0a7d0a0a636f72652d626c6f636b203d207b0a20206c6f63616c733a205b2a206c6f63616c2d7265665d2c0a20206e6f6465733a205b2a20636f72652d6e6f64655d2c0a2020726573756c743a20636f72652d657870722c0a7d0a0a636f72652d6e6f6465203d206c65742d6e6f6465202f20726571756972652d6e6f6465202f206566666563742d6e6f6465202f0a20202020202020202020202065787465726e616c2d616374696f6e2d726571756573742d6e6f6465202f2067756172642d6e6f6465202f206272616e63682d6e6f6465202f0a202020202020202020202020666f722d6e6f6465202f206d617463682d6e6f6465202f2070726f6f662d6f626c69676174696f6e2d6e6f64650a0a6c65742d6e6f6465203d207b0a20206b696e643a20226c6574222c0a202062696e64696e673a206c6f63616c2d7265662c0a202076616c75653a20636f72652d657870722c0a7d0a726571756972652d6e6f6465203d207b0a20206b696e643a202272657175697265222c0a20207072656469636174653a20636f72652d7072656469636174652c0a20206f6e4661696c7572653a20726571756972652d6661696c7572652d61726d2c0a7d0a726571756972652d6661696c7572652d61726d203d207465726d696e616c2d726571756972652d6661696c757265202f0a20202020202020202020202020202020202020202020636f6e74696e75652d6f6273747275637465642d726571756972652d6661696c7572650a7465726d696e616c2d726571756972652d6661696c757265203d207b0a20206b696e643a20227465726d696e616c222c0a2020726561736f6e3a206f62737472756374696f6e2d726561736f6e2c0a7d0a636f6e74696e75652d6f6273747275637465642d726571756972652d6661696c757265203d207b0a20206b696e643a2022636f6e74696e75654f627374727563746564222c0a2020726561736f6e3a206f62737472756374696f6e2d726561736f6e2c0a7d0a6f62737472756374696f6e2d726561736f6e203d207b0a2020726561736f6e4b696e643a20747374722c0a20207061796c6f61643a207b202a2074737472203d3e20636f72652d65787072207d2c0a7d0a6566666563742d6e6f6465203d207b0a20206b696e643a2022656666656374222c0a202062696e64696e673a206c6f63616c2d7265662c0a20206566666563743a20747374722c0a2020696e7075743a20636f72652d657870722c0a20206f62737472756374696f6e4d61703a207b202a206661696c7572652d6964656e74203d3e206f62737472756374696f6e2d61726d207d2c0a7d0a65787465726e616c2d616374696f6e2d726571756573742d6e6f6465203d207b0a20206b696e643a202265787465726e616c416374696f6e52657175657374222c0a202062696e64696e673a206c6f63616c2d7265662c0a20206f7065726174696f6e3a207265736f757263652d7265662c0a2020696e707574547970653a20636f72652d747970652d7265662c0a2020736574746c656d656e74547970653a20636f72652d747970652d7265662c0a2020696e707574536368656d613a207265736f757263652d7265662c0a2020736574746c656d656e74536368656d613a207265736f757263652d7265662c0a2020696e7075743a20636f72652d657870722c0a2020617574686f7269747953636f70653a20636f72652d657870722c0a202062617369733a20636f72652d657870722c0a20206275646765743a2065787465726e616c2d616374696f6e2d6275646765742c0a20207265636f6e63696c696174696f6e4c61773a207265736f757263652d7265662c0a202073746174653a20226177616974696e67536574746c656d656e74222c0a2020736574746c656d656e7441646d697373696f6e3a2022736368656d615265717569726564222c0a7d0a65787465726e616c2d616374696f6e2d627564676574203d207b0a20206d6178536574746c656d656e7442797465733a20636f72652d657870722c0a20206d6178417474656d7074733a20636f72652d657870722c0a7d0a6f62737472756374696f6e2d61726d203d207b0a202062696e6465723a206c6f63616c2d7265662c0a202076616c75653a20636f72652d657870722c0a7d0a67756172642d6e6f6465203d207b0a20206b696e643a20226775617264222c0a20207072656469636174653a20636f72652d7072656469636174652c0a20206f62737472756374696f6e3a20636f72652d657870722c0a7d0a6272616e63682d6e6f6465203d207b0a20206b696e643a20226272616e6368222c0a20207072656469636174653a20636f72652d7072656469636174652c0a20207468656e3a20636f72652d626c6f636b2c0a2020656c73653a20636f72652d626c6f636b2c0a7d0a666f722d6e6f6465203d207b0a20206b696e643a2022666f72222c0a202062696e6465723a206c6f63616c2d7265662c0a2020697465723a20636f72652d657870722c0a2020626f756e643a20636f72652d626f756e642c0a2020626f64793a20636f72652d626c6f636b2c0a7d0a6d617463682d6e6f6465203d207b0a20206b696e643a20226d61746368222c0a20207363727574696e65653a20636f72652d657870722c0a202061726d733a205b2b206d617463682d626c6f636b2d61726d5d2c0a7d0a6d617463682d626c6f636b2d61726d203d207b0a2020636173653a20747374722c0a20203f2062696e6465723a206c6f63616c2d7265662c0a2020626f64793a20636f72652d626c6f636b2c0a7d0a70726f6f662d6f626c69676174696f6e2d6e6f6465203d207b0a20206b696e643a202270726f6f66222c0a2020636f6f7264696e6174653a20747374722c0a20207072656469636174653a20636f72652d7072656469636174652c0a7d0a0a636f72652d626f756e64203d206c69746572616c2d626f756e64202f20636f6f7264696e6174652d626f756e640a6c69746572616c2d626f756e64203d207b206b696e643a20226c69746572616c222c2076616c75653a2075696e74207d0a636f6f7264696e6174652d626f756e64203d207b206b696e643a2022636f6f7264696e617465222c207265663a2074737472207d0a0a3b20536861726564207265736f757263652d7265662c207368613235362d6469676573742c206661696c7572652d6964656e742c2061706572747572652d726571756972656d656e742c20616e640a3b20636f72652d747970652d7265662061726520646566696e6564206f6e636520696e2065646963742d636f6d6d6f6e2e6364646c2e0a0a3b202d2d2d2065646963742d6c61777061636b2e6364646c202d2d2d0a3b2065646963742d6c61777061636b2e6364646c0a3b2043616e6f6e6963616c20736368656d6120666f7220746865204564696374206c61777061636b206d616e696665737420616e64206578706f727420737572666163652e0a3b20417574686f7269746174697665206279746520656e636f64696e673a2065646963742e63616e6f6e6963616c2d63626f722f76312e204a534f4e20696e207468652070726f73652073706563730a3b2069732061207265766965772072656e646572696e672067656e6572617465642066726f6d207468697320736368656d61202845444943542d4142492d4e4f4455502d303031292e0a0a6c61777061636b2d6d616e6966657374203d207b0a202061706956657273696f6e3a202265646963742e6c61777061636b2f7631222c0a202069643a20747374722c0a202076657273696f6e3a20747374722c0a20206163636570746564436f72654162693a205b2b20747374725d2c0a2020646570656e64656e636965733a205b2a206c61777061636b2d6465705d2c202020202020202020203b20616379636c69632c206469676573742d6c6f636b6564202845444943542d4c41575041434b2d4441472d303031290a20206578706f7274733a207265736f757263652d7265662c0a20203f2074617267657441646170746572733a205b2b207461726765742d616461707465725d2c2020203b207265717569726564206f6e6c7920696620616e792072756e74696d6520656666656374206578697374730a20203f2068656c706572436f6d706f6e656e743a2065786563757461626c652d636f6d706f6e656e742c203b2065786563757461626c652068656c70657273206361727279207468656972206f776e2073616e64626f782b6675656c0a202076657269666965723a2076657269666965722c202020202020202020202020202020202020202020203b20636c61737369666965643a206465636c61726174697665206f722065786563757461626c650a2020636f6d7061746962696c6974793a207265736f757263652d7265662c0a2020636f6e666f726d616e636546697874757265436f727075733a207265736f757263652d7265662c0a7d0a0a3b2041207665726966696572206973206569746865722061206465636c617261746976652072756c6573657420286e6f2072756e74696d6529206f7220616e2065786563757461626c650a3b20636f6d706f6e656e742e20416e2065786563757461626c65207665726966696572204d55535420636172727920697473206f776e2073616e64626f7820616e64206675656c206d6f64656c2c0a3b20736f2074686520736368656d6120656e666f726365732074686174206e6f2065786563757461626c6520636f6d706f6e656e74206973206c65667420756e626f756e6465640a3b202845444943542d4142492d56455249464945522d424f554e442d303031292e0a7665726966696572203d206465636c617261746976652d7665726966696572202f2065786563757461626c652d76657269666965720a6465636c617261746976652d7665726966696572203d207b20636c6173733a20226465636c61726174697665222c2072756c657365743a207265736f757263652d726566207d0a65786563757461626c652d7665726966696572203d207b0a2020636c6173733a202265786563757461626c65222c0a2020636f6d706f6e656e743a207265736f757263652d7265662c0a202073616e64626f783a207265736f757263652d7265662c0a20206675656c4d6f64656c3a207265736f757263652d7265662c0a7d0a0a3b20416e792065786563757461626c6520636f6d706f6e656e7420697320626f756e64656420627920697473206f776e2073616e64626f78202b206675656c206d6f64656c2e0a65786563757461626c652d636f6d706f6e656e74203d207b0a2020636f6d706f6e656e743a207265736f757263652d7265662c0a202073616e64626f783a207265736f757263652d7265662c0a20206675656c4d6f64656c3a207265736f757263652d7265662c0a7d0a0a6c61777061636b2d646570203d207b2069643a20747374722c2076657273696f6e3a20747374722c206469676573743a207368613235362d646967657374207d0a0a3b20416461707465722073656c656374696f6e206b65797320534f4c454c59206f666620746865206469676573742d6c6f636b65642060616363657074656454617267657450726f66696c65600a3b20286974732060696460206973207468652070726f66696c652069643b206974732060646967657374602070696e73207468652065786163742070726f66696c652f76657273696f6e292e2054686572650a3b20617265206e6f20696e646570656e64656e7420646973706c617920737472696e6773207468617420636f756c64206469736167726565207769746820746865206c6f636b2c20736f20610a3b207265736f6c7665722063616e6e6f742062696e6420616e206164617074657220746f206f6e6520746172676574207768696c6520746865206c6f636b2070726f76657320616e6f746865720a3b202845444943542d4c41575041434b2d414441505445522d54415247455449522d303031292e0a7461726765742d61646170746572203d207b0a2020616363657074656454617267657450726f66696c653a207265736f757263652d7265662c202020203b206469676573742d6c6f636b65642c20617574686f72697461746976652073656c6563746f720a2020616363657074656454617267657449723a207265736f757263652d7265662c2020202020202020203b206469676573742d6c6f636b65640a2020616461707465723a207265736f757263652d7265662c0a7d0a0a3b20536861726564207479706573207265736f757263652d72656620616e64207368613235362d6469676573742061726520646566696e6564206f6e636520696e0a3b2065646963742d636f6d6d6f6e2e6364646c20616e6420617373656d626c65642077697468207468697320736368656d61202845444943542d4142492d4e4f4455502d303031292e0a0a3b202d2d2d206578706f72742073757266616365202d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d0a0a6c61777061636b2d6578706f727473203d207b0a202074797065733a205b2a206578706f727465642d747970655d2c0a2020636f6e7374616e74733a205b2a206578706f727465642d636f6e7374616e745d2c0a20207075726546756e6374696f6e733a205b2a20707572652d66756e6374696f6e5d2c0a2020656666656374733a205b2a2073656d616e7469632d6566666563745d2c0a20206f62737472756374696f6e733a205b2a206f62737472756374696f6e2d6465665d2c0a20203b206b65796564206279206f7065726174696f6e2d70726f66696c6520636f6f7264696e61746520e2869220756e697175656e65737320656e666f726365640a20203b202845444943542d4142492d4f5050524f46494c452d554e495155452d303031290a20203b206f7065726174696f6e2d70726f66696c65207265636f7264732074686973206c61777061636b206578706f72747320286f707469632074656d706c6174657320746861740a20203b2060696d706c656d656e7473602f6070726f66696c656020636c6175736573207265736f6c766520616761696e7374292e206f7065726174696f6e2d70726f66696c652069730a20203b20646566696e656420696e2065646963742d636f6d6d6f6e2e6364646c202845444943542d4142492d4f5050524f46494c452d534c4f542d303031292e0a20206f7065726174696f6e50726f66696c65733a207b202a2074737472203d3e206f7065726174696f6e2d70726f66696c65207d2c20203b206b6579656420627920636f6f7264696e6174650a7d0a0a6578706f727465642d7479706520202020203d207b20636f6f7264696e6174653a20747374722c20646566696e6974696f6e3a20636f72652d747970652d726566207d0a6578706f727465642d636f6e7374616e74203d207b20636f6f7264696e6174653a20747374722c20747970653a20636f72652d747970652d7265662c2076616c75653a20616e79207d0a0a3b204120707572652068656c7065722069732061206469736372696d696e6174656420756e696f6e2062792060736f75726365602c20736f2074686520736368656d6120697473656c660a3b2067756172616e7465657320616e20696d706c656d656e746174696f6e20657869737473202845444943542d4c41575041434b2d505552452d494d504c2d303031293a0a3b2020202d20226564696374223a20617574686f72656420696e2045646963742f436f72653b2074686520436f726520626f6479206973206361727269656420696e6c696e6520286861736865640a3b20202020207769746820746865206578706f72742073757266616365292e2054686520736368656d61207265717569726573207468652060626f647960206669656c642e0a3b2020202d2022636f6d706f6e656e74223a20696d706c656d656e746564206f7574736964652045646963743b2063617272696573206e6f20696e6c696e6520626f647920616e6420696e73746561640a3b20202020206361727269657320697473206f776e206469676573742d6c6f636b65642060696d706c656d656e746174696f6e60202873616e64626f78202b206675656c292e20497420646f65730a3b20202020206e6f7420646570656e64206f6e20746865206f7074696f6e616c206d616e69666573742d6c6576656c2068656c706572436f6d706f6e656e742e0a707572652d66756e6374696f6e203d2065646963742d707572652d66756e6374696f6e202f20636f6d706f6e656e742d707572652d66756e6374696f6e0a0a707572652d66756e6374696f6e2d636f6d6d6f6e203d20280a2020636f6f7264696e6174653a20747374722c0a202074797065506172616d65746572733a205b2a20747374725d2c0a2020706172616d6574657254797065733a205b2a20636f72652d747970652d7265665d2c2020202020203b20616c6c20626f756e6465640a202072657475726e547970653a20636f72652d747970652d7265662c20202020202020202020202020203b20626f756e6465640a2020636f737454656d706c6174653a20747374722c0a202064657465726d696e69736d436c6173733a2022746f74616c22202f2022746f74616c2d776974682d74797065642d646961676e6f73746963222c0a290a0a65646963742d707572652d66756e6374696f6e203d207b0a2020707572652d66756e6374696f6e2d636f6d6d6f6e2c0a2020736f757263653a20226564696374222c0a2020626f64793a20636f72652d666e2d626f64792c2020202020202020202020202020202020202020203b20696e6c696e652c20686173682d7369676e69666963616e740a7d0a0a636f6d706f6e656e742d707572652d66756e6374696f6e203d207b0a2020707572652d66756e6374696f6e2d636f6d6d6f6e2c0a2020736f757263653a2022636f6d706f6e656e74222c0a20203b20746865206469676573742d6c6f636b656420636f6d706f6e656e7420696d706c656d656e74696e6720746869732068656c7065722e2052657175697265642061742074686520736368656d610a20203b206c6576656c20736f206120636f6d706f6e656e742068656c7065722063616e206e657665722076616c696461746520776974686f7574206120686173682d626f756e642c0a20203b2073616e64626f782b6675656c2d64657363726962656420696d706c656d656e746174696f6e202845444943542d4c41575041434b2d505552452d494d504c2d303031292e0a2020696d706c656d656e746174696f6e3a2065786563757461626c652d636f6d706f6e656e742c0a7d0a0a3b20636f72652d666e2d626f647920697320646566696e65642062792065646963742d636f72652e6364646c20616e6420617373656d626c656420776974682074686973206c61777061636b0a3b20736368656d612e2049742069732061207075726520436f72652066756e6374696f6e20626f64792c206e6f7420616e206566666563742d63617061626c6520636f72652d626c6f636b2e0a0a73656d616e7469632d656666656374203d207b0a2020636f6f7264696e6174653a20747374722c0a202074797065506172616d65746572733a205b2a20747374725d2c0a2020696e707574547970653a20636f72652d747970652d7265662c2020202020202020202020202020203b20626f756e6465640a20206f7574707574547970653a20636f72652d747970652d7265662c20202020202020202020202020203b20626f756e6465640a2020657865637574696f6e436c6173733a202270726f6f664f6e6c7922202f202272756e74696d65222c2020203b206f7274686f676f6e616c20746f207772697465436c6173730a20206566666563744b696e6448696e743a206566666563742d6b696e642c0a2020666f6f747072696e744f626c69676174696f6e3a20747374722c0a2020636f73744f626c69676174696f6e3a20747374722c0a20206566666563744661696c757265733a207b202a206661696c7572652d6964656e74203d3e206566666563742d6661696c7572652d626f6479207d2c20203b206b6579656420627920636f6f7264696e6174653b20756e697175650a20206775617264537570706f72743a20626f6f6c2c0a7d0a0a6f62737472756374696f6e2d646566203d207b0a2020636f6f7264696e6174653a20747374722c0a2020617574686f72697479436c6173733a20617574686f726974792d636c6173732c0a20207061796c6f6164536368656d613a20636f72652d747970652d7265662c20202020202020202020203b2074797065642c20626f756e64656420286d617920626520656d707479207265636f7264290a7d0a0a3b206566666563742d6661696c7572652d626f64792c206566666563742d6b696e642c20617574686f726974792d636c6173732c20616e6420636f72652d747970652d7265662061726520646566696e65640a3b206f6e636520696e2065646963742d636f6d6d6f6e2e6364646c20616e6420617373656d626c65642077697468207468697320736368656d61202845444943542d4142492d4e4f4455502d303031292e0a0a3b202d2d2d2065646963742d6c61777061636b2d616461707465722e6364646c202d2d2d0a3b2065646963742d6c61777061636b2d616461707465722e6364646c0a3b2043616e6f6e6963616c20736368656d6120666f72206f6e6520646972656374206465636c61726174697665206c61777061636b2074617267657420616461707465722e0a3b20417574686f7269746174697665206279746520656e636f64696e673a2065646963742e63616e6f6e6963616c2d63626f722f76312e0a3b0a3b2054686520656e636c6f73696e67206c61777061636b206d616e69666573742073656c6563747320746865206578616374207461726765742070726f66696c652c207461726765742049522c0a3b20616e642061646170746572207265736f75726365206469676573742e2054686f7365206964656e74697469657320617265206e6f7420726570656174656420686572652e0a0a6c61777061636b2d61646170746572203d207b0a202061706956657273696f6e3a202265646963742e6c61777061636b2d616461707465722f7631222c0a2020636c6173733a20226465636c61726174697665222c0a20206f7065726174696f6e50726f66696c65733a207b0a202020202a2074737472203d3e206c61777061636b2d616461707465722d6f7065726174696f6e2d70726f66696c650a20207d2c0a2020656666656374496d706c656d656e746174696f6e733a207b0a202020202a2074737472203d3e206c61777061636b2d616461707465722d6566666563740a20207d2c0a2020627564676574733a207b0a202020202a2074737472203d3e206c61777061636b2d616461707465722d6275646765740a20207d2c0a7d0a0a3b204b657973206172652063616e6f6e6963616c206c61777061636b206f7065726174696f6e2d70726f66696c6520636f6f7264696e617465732e0a6c61777061636b2d616461707465722d6f7065726174696f6e2d70726f66696c65203d207b0a2020636f72653a20747374722c0a202073656d616e746963456666656374733a205b2b20747374725d2c0a7d0a0a3b204b657973206172652063616e6f6e6963616c206c61777061636b2073656d616e7469632d65666665637420636f6f7264696e617465732e20466f6f747072696e742c20636f73742c20616e640a3b206661696c757265206669656c6473206d7573742065786163746c792064697363686172676520746865206d61746368696e67206578706f72746564206566666563742e0a6c61777061636b2d616461707465722d656666656374203d207b0a2020746172676574496e7472696e7369633a20747374722c0a2020746172676574436f6e66696775726174696f6e3a207265736f757263652d7265662c0a20207772697465436c6173733a206c61777061636b2d616461707465722d77726974652d636c6173732c0a2020666f6f747072696e744f626c69676174696f6e3a20747374722c0a2020636f73744f626c69676174696f6e3a20747374722c0a20206661696c7572654d617070696e67733a207b202a206661696c7572652d6964656e74203d3e2074737472207d2c0a7d0a0a3b204b657973206172652063616e6f6e6963616c206578706f7274656420636f73742d6f626c69676174696f6e20636f6f7264696e617465732e0a6c61777061636b2d616461707465722d627564676574203d207b0a20206d617853746570733a2075696e742c0a20206d6178416c6c6f636174656442797465733a2075696e742c0a20206d61784f757470757442797465733a2075696e742c0a7d0a0a6c61777061636b2d616461707465722d77726974652d636c617373203d20226e6f6e6522202f20227265616422202f202263726561746522202f2022656e7375726522202f0a20202020202020202020202020202020202020202020202020202020202022617070656e6422202f20227265706c61636522202f202264656c65746522202f2022637573746f6d220a0a3b206661696c7572652d6964656e7420697320646566696e656420696e2065646963742d636f6d6d6f6e2e6364646c2e0a0a3b202d2d2d2065646963742d7461726765742d70726f66696c652e6364646c202d2d2d0a3b2065646963742d7461726765742d70726f66696c652e6364646c0a3b2043616e6f6e6963616c20736368656d6120666f7220746865204564696374207461726765742070726f66696c65206d616e69666573742e0a3b20417574686f7269746174697665206279746520656e636f64696e673a2065646963742e63616e6f6e6963616c2d63626f722f76310a3b202873656520535045435f636f6e74696e75756d2d636f6e74726163742d62756e646c652d76312e6d64292e204a534f4e20696e207468652070726f736520737065637320697320610a3b207265766965772072656e646572696e672067656e6572617465642066726f6d207468697320736368656d613b2074686973204344444c206973207468652073696e676c6520736f757263650a3b206f66207472757468202845444943542d4142492d4e4f4455502d303031292e0a0a7461726765742d70726f66696c652d6d616e6966657374203d207b0a202061706956657273696f6e3a202265646963742e7461726765742d70726f66696c652f7631222c0a202069643a20747374722c202020202020202020202020202020202020202020202020203b20652e672e20226563686f2e64706f220a202076657273696f6e3a20747374722c20202020202020202020202020202020202020203b20652e672e202231220a20206163636570746564436f72654162693a205b2b20747374725d2c20202020202020203b20652e672e205b2265646963742e636f72652f7631225d0a0a2020696e7472696e736963733a207265736f757263652d7265662c0a2020696e7472696e7369634e616d6573706163653a20747374722c0a20203b207075626c697368657320746869732070726f66696c652773206f7065726174696f6e2d70726f66696c65207265636f72647320286f707469632074656d706c6174657320746861740a20203b206070726f66696c65602f60696d706c656d656e74736020636c6175736573207265736f6c766520616761696e7374292e205265666572656e63657320616e0a20203b206f7065726174696f6e2d70726f66696c65732d646f63756d656e74202845444943542d4142492d4f5050524f46494c452d534c4f542d303031292e0a20206f7065726174696f6e50726f66696c65733a207265736f757263652d7265662c0a2020666f6f747072696e74416c67656272613a207265736f757263652d7265662c0a2020636f7374416c67656272613a207265736f757263652d7265662c0a202074617267657449723a207265736f757263652d7265662c0a20206f62737472756374696f6e5461786f6e6f6d793a207265736f757263652d7265662c0a202076657269666965723a207265736f757263652d7265662c0a20206c6f77657265723a207265736f757263652d7265662c0a202073616e64626f783a207265736f757263652d7265662c0a20206675656c4d6f64656c3a207265736f757263652d7265662c0a0a20203b206669656c647320746865206c616e67756167652073706563207265717569726573206f662065766572792070726f66696c650a202062756e646c6550726f66696c653a207265736f757263652d7265662c0a202067656e657261746564417274696661637450726f66696c65733a205b2a207265736f757263652d7265665d2c0a202063616e6f6e6963616c456e636f64696e6752756c65733a207265736f757263652d7265662c0a20203b20412070726f66696c65207468617420616363657074732074686520646972656374206465636c61726174697665206c61777061636b2d6164617074657220414249206e616d65732069740a20203b2065786163746c79206f6e63652e2050726f66696c6573207468617420646f206e6f7420636f6e73756d65206c61777061636b206164617074657273206c6561766520746869730a20203b206f7074696f6e616c20736c6f7420616273656e74206f7220656d7074792e0a20203f2061636365707465644c61777061636b416461707465724162693a205b5d202f205b2265646963742e6c61777061636b2d616461707465722f7631225d2c0a2020646961676e6f737469634162693a207265736f757263652d7265662c0a0a20203b206170706c69636174696f6e20646f637472696e650a20206170706c69636174696f6e4d6f64656c3a202261746f6d6963222c0a202072656164436f6e73697374656e63793a20226170706c69636174696f6e2d736e617073686f7422202f20747374722c0a202067756172644576616c756174696f6e3a2022707265636f6d6d69742d61746f6d696322202f20747374722c0a20206f62737472756374696f6e526f6c6c6261636b3a20226e6f2d76697369626c652d6566666563747322202f20747374722c0a20206d756c74695461726765743a20626f6f6c2c0a20203b207768657468657220746865207461726765742063616e206576616c7561746520707265636f6d6d697420706f7374636f6e646974696f6e20286067756172616e746565602920636865636b730a20203b20696e73696465207468652061746f6d6963206170706c69636174696f6e20756e6974202845444943542d5441524745542d504f5354434f4e442d303031290a2020706f7374636f6e646974696f6e537570706f72743a20626f6f6c2c0a0a202064657465726d696e6973746963457865637574696f6e3a207265736f757263652d7265662c0a2020636f6e666f726d616e636546697874757265436f727075733a207265736f757263652d7265662c0a7d0a0a3b20536861726564207479706573207265736f757263652d72656620616e64207368613235362d6469676573742061726520646566696e6564206f6e636520696e0a3b2065646963742d636f6d6d6f6e2e6364646c20616e6420617373656d626c65642077697468207468697320736368656d6120627920746865206275696c640a3b202845444943542d4142492d4e4f4455502d303031292e205468657920617265206e6f74207265646566696e656420686572652e0a0a3b202d2d2d20696e7472696e736963207369676e6174757265202d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d0a3b20546865206172746966616374207265666572656e63656420627920746865206d616e696665737427732060696e7472696e7369637360207265736f757263652d726566206973207468650a3b20696e7472696e7369632d7369676e617475726520636f7270757320646f63756d656e742062656c6f772e20497473206c61796f757420697320666978656420736f2074776f0a3b20696e646570656e64656e742070726f66696c65732076616c69646174652f686173682074686520636f72707573206964656e746963616c6c790a3b202845444943542d4142492d494e5452494e534943532d444f432d303031292e0a0a3b20696e7472696e736963732069732061204d4150206b6579656420627920636f6f7264696e6174652c20736f2074686520736368656d6120697473656c6620656e666f726365730a3b20636f6f7264696e61746520756e697175656e6573732e20412070726f766964657220726563656976657320746865207265736f6c76656420636f7270757320617320610a3b206469676573742d626f756e642073656d616e74696320696e70757420616e64207265736f6c76657320636f6f7264696e617465732077697468696e20746861742061727469666163742e0a3b2045616368206d6170206b6579204d55535420657175616c20697473207265636f726427732060636f6f7264696e61746560206669656c640a3b202845444943542d4142492d494e5452494e5349432d554e495155452d303031292e0a696e7472696e736963732d646f63756d656e74203d207b0a202061706956657273696f6e3a202265646963742e7461726765742d70726f66696c652e696e7472696e736963732f7631222c0a2020696e7472696e736963733a207b202a2074737472203d3e20696e7472696e736963207d2c0a7d0a0a3b20546865206172746966616374207265666572656e63656420627920746865206d616e6966657374277320606f7065726174696f6e50726f66696c657360207265736f757263652d7265662e0a3b206f7065726174696f6e2d70726f66696c65202f206f707469632d74656d706c6174652061726520646566696e656420696e2065646963742d636f6d6d6f6e2e6364646c2e204b657965642062790a3b20636f6f7264696e61746520736f207265736f6c7574696f6e2063616e2774207069636b206265747765656e2074776f2073616d652d636f6f7264696e6174652070726f66696c65730a3b202845444943542d4142492d4f5050524f46494c452d534c4f542d3030312c2045444943542d4142492d4f5050524f46494c452d554e495155452d303031292e0a6f7065726174696f6e2d70726f66696c65732d646f63756d656e74203d207b0a202061706956657273696f6e3a202265646963742e7461726765742d70726f66696c652e6f7065726174696f6e2d70726f66696c65732f7631222c0a202070726f66696c65733a207b202a2074737472203d3e206f7065726174696f6e2d70726f66696c65207d2c0a7d0a0a3b2041207479706564207072652d6c6f776572696e67207175657374696f6e20746861742063616e2062652070726f706f73656420627920576174736f6e206f7220616e206167656e7420616e640a3b20636865636b65642062792074686520636f6d70696c65722e2049742069732063616e6f6e6963616c2d43424f5220656e636f64656420756e6465720a3b206065646963742e6c6f776572696e672d726571756972656d656e74732f7631603b2074686520636f6d70696c657220636865636b7320746869732061727469666163742c206e6f74207468650a3b2070726f736520746861742070726f64756365642069742e0a6c6f776572696e672d726571756972656d656e7473203d207b0a202061706956657273696f6e3a202265646963742e6c6f776572696e672d726571756972656d656e74732f7631222c0a20206f7065726174696f6e50726f66696c653a20747374722c0a202073656d616e746963456666656374733a205b2a2073656d616e7469632d6566666563742d726571756972656d656e745d2c0a202072657175697265645772697465436c61737365733a205b2a2077726974652d636c6173735d2c0a202067756172644b696e64733a205b2a2067756172642d6b696e645d2c0a202061746f6d69636974793a2061746f6d69636974792d726571756972656d656e742c0a2020706f7374636f6e646974696f6e537570706f72743a20626f6f6c2c0a20206f62737472756374696f6e436f6f7264696e617465733a205b2a20747374725d2c0a2020666f6f747072696e744f626c69676174696f6e733a205b2a20747374725d2c0a2020636f73744f626c69676174696f6e733a205b2a20747374725d2c0a20206f70746963436f6e74726163743a20747374722c0a7d0a0a73656d616e7469632d6566666563742d726571756972656d656e74203d207b0a2020636f6f7264696e6174653a20747374722c0a20207772697465436c6173733a2077726974652d636c6173732c0a202067756172644b696e64733a205b2a2067756172642d6b696e645d2c0a20206f62737472756374696f6e436f6f7264696e617465733a205b2a20747374725d2c0a2020666f6f747072696e744f626c69676174696f6e733a205b2a20747374725d2c0a2020636f73744f626c69676174696f6e733a205b2a20747374725d2c0a7d0a0a77726974652d636c617373203d20226e6f6e6522202f20227265616422202f202263726561746522202f2022656e7375726522202f2022617070656e6422202f0a2020202020202020202020202020227265706c61636522202f202264656c65746522202f20747374720a67756172642d6b696e64203d2022707265636f6d6d69742d61746f6d696322202f20747374720a61746f6d69636974792d726571756972656d656e74203d202261746f6d696322202f20747374720a0a3b20412067656e75696e6520756e696f6e3a207075726520636f6e7374727563746f7273206361727279206e6f20656666656374206b696e64206f72206661696c757265733b206566666563740a3b20696e7472696e73696373206d757374202845444943542d5441524745542d494e5452494e5349432d434c4153532d303031292e2054686520736368656d6120656e666f7263657320746869732c0a3b206e6f74206120636f6d6d656e742e0a0a3b2054686520696e7472696e736963277320636f6f7264696e6174652069732074686520696e7472696e73696373206d6170204b45592c206e6f7420612076616c7565206669656c642c20736f207468650a3b206b657920616e6420636f6f7264696e6174652063616e206e65766572206469736167726565202845444943542d4142492d494e5452494e5349432d554e495155452d303031292e0a696e7472696e736963203d20707572652d696e7472696e736963202f206566666563742d696e7472696e7369630a0a707572652d696e7472696e736963203d207b0a2020696e7472696e736963436c6173733a202270757265222c0a202074797065506172616d65746572733a205b2a20747374725d2c0a2020617267756d656e7454797065733a205b2a20636f72652d747970652d7265665d2c0a202072657475726e547970653a20636f72652d747970652d7265662c0a20206775617264537570706f72743a2066616c73652c0a2020666f6f747072696e7454656d706c6174653a20747374722c0a2020636f737454656d706c6174653a20747374722c0a20207772697465436c6173733a20226e6f6e65222c0a7d0a0a6566666563742d696e7472696e736963203d207b0a2020696e7472696e736963436c6173733a2022656666656374222c0a202074797065506172616d65746572733a205b2a20747374725d2c0a2020617267756d656e7454797065733a205b2a20636f72652d747970652d7265665d2c0a202072657475726e547970653a20636f72652d747970652d7265662c0a20206566666563744b696e643a206566666563742d6b696e642c0a20203b206d6170206b65796564206279206661696c75726520636f6f7264696e61746520286661696c7572652d6964656e74293b20746865206661696c75726520636f6f7264696e6174652069730a20203b20746865206b65792c206e6f7420612076616c7565206669656c642c20736f20756e697175656e657373206973207374727563747572616c0a20203b202845444943542d4142492d4641494c5552452d554e495155452d303031292e0a20206566666563744661696c757265733a207b202a206661696c7572652d6964656e74203d3e206566666563742d6661696c7572652d626f6479207d2c0a20206775617264537570706f72743a20626f6f6c2c0a2020666f6f747072696e7454656d706c6174653a20747374722c0a2020636f737454656d706c6174653a20747374722c0a20207772697465436c6173733a20227265616422202f202263726561746522202f2022656e7375726522202f2022617070656e6422202f20227265706c61636522202f0a20202020202020202020202020202264656c65746522202f2022637573746f6d222c0a202063616e5061727469636970617465496e41746f6d696347756172643a20626f6f6c2c0a7d0a0a3b206566666563742d6661696c7572652d626f64792c206566666563742d6b696e642c20617574686f726974792d636c6173732c20616e6420636f72652d747970652d7265662061726520646566696e65640a3b206f6e636520696e2065646963742d636f6d6d6f6e2e6364646c20616e6420617373656d626c65642077697468207468697320736368656d61202845444943542d4142492d4e4f4455502d303031292e0a0a3b202d2d2d2065646963742d617574686f726974792d66616374732e6364646c202d2d2d0a3b2065646963742d617574686f726974792d66616374732e6364646c0a3b2043616e6f6e6963616c20736368656d6120666f722074686520666972737420636f6d70696c65722d636f6e7465787420617574686f726974792d666163747320646f63756d656e742e0a3b20417574686f7269746174697665206279746520656e636f64696e673a2065646963742e63616e6f6e6963616c2d63626f722f76312e0a3b0a3b205468697320736368656d6120697320617373656d626c656420776974682065646963742d636f6d6d6f6e2e6364646c20736f20736f757263652e6469676573742075736573207468650a3b20736861726564207368613235362d6469676573742074797065642076616c75652e204a534f4e2069732061207265766965772f696e7075742072656e646572696e673a206974730a3b20607368613235363a3c3634206865783e6020736f75726365206469676573742069732070726f6a656374656420746f205b60736861323536602c203332207261772062797465735d206f6e0a3b2074686520776972652c20616e64206974732066616374206172726179732070726f6a65637420746f2074686520636f6f7264696e6174652d6b65796564206d6170732062656c6f772e0a0a617574686f726974792d6661637473203d207b0a202061706956657273696f6e3a202265646963742e617574686f726974792d66616374732f7631222c0a2020736f757263653a20617574686f726974792d666163742d736f757263652c0a20206f7065726174696f6e50726f66696c65733a207b202a2074737472203d3e20617574686f726974792d6f7065726174696f6e2d70726f66696c652d66616374207d2c0a20206566666563745772697465436c61737365733a207b202a2074737472203d3e20617574686f726974792d77726974652d636c617373207d2c0a2020627564676574733a207b202a2074737472203d3e20617574686f726974792d6275646765742d66616374207d2c0a7d0a0a617574686f726974792d666163742d736f75726365203d207b0a20206b696e643a20226c61777061636b22202f202274617267657450726f66696c65222c0a2020636f6f7264696e6174653a20747374722c0a20206469676573743a207368613235362d6469676573742c0a7d0a0a3b20546865206d6170206b65792069732074686520736f75726365206f7065726174696f6e2d70726f66696c6520636f6f7264696e6174652e204974206973206e6f7420726570656174656420696e0a3b207468652076616c75652c20736f2061206b657920616e6420656d62656464656420636f6f7264696e6174652063616e6e6f742064697361677265652e20416c6c6f7765642077726974650a3b20636c61737365732061726520612063616e6f6e6963616c206d61702d7365743a2074686520636c6173732069732074686520756e69717565206b657920616e64206e756c6c206973207468650a3b20756e6974206d61726b65722e2043616e6f6e6963616c2043424f52206669786573206b6579206f7264657220776974686f75742061207365636f6e64206f72646572696e672072756c652e0a617574686f726974792d6f7065726174696f6e2d70726f66696c652d66616374203d207b0a2020636f72653a20747374722c0a2020616c6c6f7765645772697465436c61737365733a207b202a20617574686f726974792d77726974652d636c617373203d3e206e756c6c207d2c0a7d0a0a3b20546865206566666563745772697465436c6173736573206d6170206b6579206973207468652073656d616e7469632065666665637420636f6f7264696e6174652e2054686520627564676574730a3b206d6170206b65792069732074686520736f757263652062756467657420636f6f7264696e6174652e2043616e6f6e6963616c2043424f52206d61702d6b657920756e697175656e6573730a3b206d616b6573206475706c6963617465206661637420636f6f7264696e61746573207374727563747572616c6c7920756e726570726573656e7461626c652e0a617574686f726974792d6275646765742d66616374203d207b0a20206d617853746570733a2075696e742c0a20206d6178416c6c6f636174656442797465733a2075696e742c0a20206d61784f757470757442797465733a2075696e742c0a7d0a0a3b20417574686f726974794661637473446f63756d656e7420763120696e74656e74696f6e616c6c792061636365707473206f6e6c792074686520777269746520636c6173736573207468650a3b2063757272656e7420636f6d70696c6572206d6f64656c2063616e20636f6e73756d652e2060637573746f6d602069732074686520736f6c6520763120637573746f6d207370656c6c696e673b0a3b20617262697472617279207461726765742d70726f66696c6520657874656e73696f6e20737472696e677320646f206e6f7420656e746572207468697320636f6d70696c657220706174682e0a617574686f726974792d77726974652d636c617373203d20226e6f6e6522202f20227265616422202f202263726561746522202f2022656e7375726522202f2022617070656e6422202f0a202020202020202020202020202020202020202020202020227265706c61636522202f202264656c65746522202f2022637573746f6d220a0a3b202d2d2d2065646963742d726573756c742d70726f6a656374696f6e2e6364646c202d2d2d0a3b20535044582d4c6963656e73652d4964656e7469666965723a204170616368652d322e300a3b2065646963742d726573756c742d70726f6a656374696f6e2e6364646c0a3b2043616e6f6e6963616c20736368656d6120666f7220636f6d70696c65722d6f776e6564206170706c69636174696f6e2d726573756c742070726f6a656374696f6e732e0a3b20417574686f7269746174697665206279746520656e636f64696e673a2065646963742e63616e6f6e6963616c2d63626f722f76312e0a0a726573756c742d70726f6a656374696f6e203d207b0a2020736368656d613a202265646963742e726573756c742d70726f6a656374696f6e2f7631222c0a20206f7065726174696f6e436f6f7264696e6174653a20626f756e6465642d70726f6a656374696f6e2d746578742c0a20206f7574707574547970653a20626f756e6465642d70726f6a656374696f6e2d746578742c0a20206d61784f757470757442797465733a2075696e74202e677420302c0a202065787072657373696f6e3a20726573756c742d70726f6a656374696f6e2d657870722c0a7d0a0a726573756c742d70726f6a656374696f6e2d65787072203d20726573756c742d70726f6a656374696f6e2d7265636f7264202f20726573756c742d70726f6a656374696f6e2d736f757263650a0a726573756c742d70726f6a656374696f6e2d7265636f7264203d207b0a20206b696e643a20227265636f7264222c0a20203b2054686520726f6f74207265636f726420636f756e7473206173206f6e65206f66207468652052757374206465636f6465722773203235362065787072657373696f6e206e6f6465732e0a20203b204e657374656420616767726567617465206e6f646520636f756e742072656d61696e7320616e20617574686f7269746174697665206465636f64657220636865636b2e0a20206669656c64733a207b20302a32353520626f756e6465642d70726f6a656374696f6e2d74657874203d3e20726573756c742d70726f6a656374696f6e2d65787072207d2c0a7d0a0a726573756c742d70726f6a656374696f6e2d736f75726365203d207b0a20206b696e643a2022736f75726365222c0a2020736f757263653a20726573756c742d70726f6a656374696f6e2d736f757263652d6b696e642c0a20203b204d617463686573204d41585f524553554c545f50524f4a454354494f4e5f504154485f5345474d454e545320696e2065646963742d73796e7461782e0a2020706174683a205b302a333220626f756e6465642d70726f6a656374696f6e2d746578745d2c0a7d0a0a726573756c742d70726f6a656374696f6e2d736f757263652d6b696e64203d0a20207b206b696e643a20226170706c69636174696f6e496e70757422207d202f0a20207b0a202020206b696e643a20226361706162696c697479526573756c74222c0a202020207374657049643a20626f756e6465642d70726f6a656374696f6e2d746578742c0a20207d0a0a626f756e6465642d70726f6a656374696f6e2d74657874203d2074737472202e73697a652028312e2e31303234290a0a3b202d2d2d2065646963742d7461726765742d69722e6364646c202d2d2d0a3b20535044582d4c6963656e73652d4964656e7469666965723a204170616368652d322e300a3b2065646963742d7461726765742d69722e6364646c0a3b2043616e6f6e6963616c20736368656d6120666f72207468652045646963742d6f776e65642054617267657420495220617274696661637420656e76656c6f70652e0a3b20417574686f7269746174697665206279746520656e636f64696e673a2065646963742e63616e6f6e6963616c2d63626f722f76312e0a3b0a3b205468697320736368656d6120697320617373656d626c656420776974682065646963742d636f6d6d6f6e2e6364646c20616e642065646963742d636f72652e6364646c2e2049740a3b2064656c696265726174656c792072657573657320436f72652065787072657373696f6e732c20707265646963617465732c20627564676574732c206c6f63616c207265666572656e6365732c0a3b206f62737472756374696f6e20726561736f6e732c20616e64206f62737472756374696f6e2061726d7320736f2074686520736368656d61206d617463686573207468652076616c75650a3b20656d6974746564206279207468652063616e6f6e6963616c2054617267657420495220656e636f64657220726174686572207468616e20726573746174696e672074686f73652074797065732e0a3b2049742064657363726962657320746865207374727563747572616c207368617065206f662076616c6964206c6f776572696e672d70726f6475636564206172746966616374732e205468650a3b206c6f776572696e6720616e6420656e636f64657220636f6e7472616374732073657061726174656c7920656e666f7263652073656d616e746963206964656e7469666965722072756c65730a3b20616e642063616e6f6e6963616c206f72646572696e672f64656475706c69636174696f6e20666f72207365742d6c696b652076616c7565732e0a0a3b2054617267657420495220656e636f64696e672072656a6563747320616e20656d707479207461726765742d70726f66696c6520636f6f7264696e617465206265666f72652062797465730a3b2065786973742c20736f207468697320726f6f74207469676874656e732074686520736861726564207374727563747572616c207265736f757263652d726566206163636f7264696e676c792e0a7461726765742d69722d7265736f757263652d726566203d207b0a202069643a2074737472202e7265676578702022283f73292e2b222c0a20206469676573743a207368613235362d6469676573742c0a7d0a0a7461726765742d69722d6172746966616374203d207461726765742d69722d636c6f7365642d6172746966616374202f207461726765742d69722d6c65676163792d61727469666163740a0a7461726765742d69722d61727469666163742d636f6d6d6f6e203d20280a20206b696e643a202274617267657449724172746966616374222c0a2020646f6d61696e3a20747374722c0a202074617267657450726f66696c653a207461726765742d69722d7265736f757263652d7265662c0a2020736f75726365436f7265436f6f7264696e6174653a2074737472202e7265676578702022283f73292e2b222c0a290a0a7461726765742d69722d636c6f7365642d6172746966616374203d207b0a20207461726765742d69722d61727469666163742d636f6d6d6f6e2c0a202073656d616e746963436c6f737572653a207461726765742d69722d73656d616e7469632d636c6f737572652c0a2020696e74656e74733a207b202a2074737472203d3e207461726765742d69722d696e74656e74207d2c0a7d0a0a7461726765742d69722d6c65676163792d6172746966616374203d207b0a20207461726765742d69722d61727469666163742d636f6d6d6f6e2c0a2020696e74656e74733a207b202a2074737472203d3e207461726765742d69722d6c65676163792d696e74656e74207d2c0a7d0a0a7461726765742d69722d73656d616e7469632d636c6f73757265203d207b0a2020736f75726365436f72653a207461726765742d69722d7265736f757263652d7265662c0a20206c61777061636b733a205b2a207461726765742d69722d7265736f757263652d7265665d2c0a20203f206361706162696c69746965733a205b2a207461726765742d69722d7265736f757263652d7265665d2c0a7d0a0a7461726765742d69722d696e74656e74203d207b0a20207461726765742d69722d696e74656e742d636f6d6d6f6e2c0a20203f2062617369733a20636f72652d657870722c0a20203f2065787465726e616c416374696f6e52657175657374733a205b2a207461726765742d69722d65787465726e616c2d616374696f6e2d726571756573745d2c0a7d0a0a7461726765742d69722d6c65676163792d696e74656e74203d207b0a20207461726765742d69722d696e74656e742d636f6d6d6f6e2c0a7d0a0a7461726765742d69722d696e74656e742d636f6d6d6f6e203d20280a20206f7065726174696f6e50726f66696c653a20747374722c0a2020696e707574436f6e73747261696e74733a205b2a20696e7075742d636f6e73747261696e745d2c0a2020636f72654576616c756174696f6e4275646765743a20636f72652d6275646765742c0a2020726571756972656d656e74733a205b2a207461726765742d69722d726571756972656d656e745d2c0a202073746570733a205b2a207461726765742d69722d737465705d2c0a2020726573756c743a20636f72652d657870722c0a290a0a7461726765742d69722d726571756972656d656e74203d207b0a202069643a20747374722c0a20207072656469636174653a20636f72652d7072656469636174652c0a20206f6e4661696c7572653a20726571756972652d6661696c7572652d61726d2c0a7d0a0a7461726765742d69722d73746570203d207b0a202069643a20747374722c0a202062696e64696e673a206c6f63616c2d7265662c0a20206566666563743a20747374722c0a2020746172676574496e7472696e7369633a20747374722c0a2020696e7075743a20636f72652d657870722c0a20206f62737472756374696f6e4661696c757265733a205b2a206661696c7572652d6964656e745d2c0a20206f62737472756374696f6e41726d733a207b202a206661696c7572652d6964656e74203d3e206f62737472756374696f6e2d61726d207d2c0a7d0a0a7461726765742d69722d65787465726e616c2d616374696f6e2d72657175657374203d207b0a202069643a20747374722c0a202062696e64696e673a206c6f63616c2d7265662c0a20206f7065726174696f6e3a207461726765742d69722d7265736f757263652d7265662c0a2020696e707574547970653a20636f72652d747970652d7265662c0a2020736574746c656d656e74547970653a20636f72652d747970652d7265662c0a2020696e707574536368656d613a207461726765742d69722d7265736f757263652d7265662c0a2020736574746c656d656e74536368656d613a207461726765742d69722d7265736f757263652d7265662c0a2020696e7075743a20636f72652d657870722c0a2020617574686f7269747953636f70653a20636f72652d657870722c0a202062617369733a20636f72652d657870722c0a20206275646765743a2065787465726e616c2d616374696f6e2d6275646765742c0a20207265636f6e63696c696174696f6e4c61773a207461726765742d69722d7265736f757263652d7265662c0a202073746174653a20226177616974696e67536574746c656d656e74222c0a2020736574746c656d656e7441646d697373696f6e3a2022736368656d615265717569726564222c0a7d0a", + "rawSha256": "4936a5c13f61647f710c99cbdfb347ef180d9ccf4b9db75bfcee68e394fdc480" }, "contracts": [ {"contract": "authority-facts", "rootRule": "authority-facts"}, From 6bb6552929f75768fa5815c53a8c56524ec469a0 Mon Sep 17 00:00:00 2001 From: James Ross Date: Wed, 29 Jul 2026 16:45:07 -0700 Subject: [PATCH 11/11] fix: close external request authority gaps --- ARCHITECTURE.md | 3 +- CHANGELOG.md | 10 +- EDICT.md | 4 +- README.md | 7 +- crates/edict-cli/src/main.rs | 1 + crates/edict-cli/tests/jsonl_cli.rs | 41 ++++- .../tests/provider_contract_pack.rs | 20 ++- crates/edict-syntax/src/canonical.rs | 75 +++++---- crates/edict-syntax/src/compiler.rs | 77 +++++++-- crates/edict-syntax/src/contract_bundle.rs | 46 +++--- crates/edict-syntax/src/highlight.rs | 1 + crates/edict-syntax/src/parser.rs | 8 +- crates/edict-syntax/src/target_ir.rs | 91 ++++------- .../tests/external_action_requests.rs | 146 ++++++++++++++++-- crates/edict-syntax/tests/highlighting.rs | 8 + docs/REQUIREMENTS.md | 2 +- docs/topics/core-ir/canonical-encoding.md | 4 +- docs/topics/core-ir/test-plan.md | 5 +- docs/topics/developer-tooling/test-plan.md | 4 +- .../topics/external-action-requests/README.md | 7 +- .../external-action-requests/test-plan.md | 13 +- docs/topics/providers/README.md | 8 +- docs/topics/providers/test-plan.md | 2 +- docs/topics/syntax/test-plan.md | 2 +- docs/topics/target-ir/test-plan.md | 8 +- editors/vscode/syntaxes/edict.tmLanguage.json | 2 +- fixtures/core/canonical/README.md | 8 +- .../canonical/workspace-snapshot.core.cbor | Bin 0 -> 2672 bytes .../canonical/workspace-snapshot.core.sha256 | 1 + .../external-actions/workspace-snapshot.edict | 35 +++++ .../workspace-snapshot.target-ir.cbor | Bin 0 -> 2152 bytes .../workspace-snapshot.target-ir.sha256 | 1 + grammars/textmate/edict.tmLanguage.json | 2 +- xtask/src/goldens.rs | 66 +++++++- xtask/src/tests.rs | 12 +- 35 files changed, 535 insertions(+), 185 deletions(-) create mode 100644 fixtures/core/canonical/workspace-snapshot.core.cbor create mode 100644 fixtures/core/canonical/workspace-snapshot.core.sha256 create mode 100644 fixtures/lang/external-actions/workspace-snapshot.edict create mode 100644 fixtures/target-ir/canonical/workspace-snapshot.target-ir.cbor create mode 100644 fixtures/target-ir/canonical/workspace-snapshot.target-ir.sha256 diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index d0d0fc1..6a6c6d7 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -172,8 +172,7 @@ source text -> semantic surface validation -> compiler context facts -> compiler spine - -> Core IR - -> typed external-action request data + -> Core IR (including typed external-action request data) -> canonical Core bytes and digest -> lowerability + target facts -> direct lowering or built-in lowerer compatibility adapter diff --git a/CHANGELOG.md b/CHANGELOG.md index 3345354..df8c129 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,9 +29,13 @@ versions still track specification maturity rather than a released product. authority to Edict. Digest-locked capability imports and `request` statements preserve exact operation, schema, scope, basis, budget, input, reconciliation, and awaiting-settlement data through canonical Core and Target IR. Operation - families remain bound in the semantic capability closure, raw ambient - filesystem/process/network/Git/GitHub/model/shell families reject, target - steps remain non-callable, and the provider seam gains no host import. + families remain bound in the semantic capability closure and the current + allowlist admits only the domain-specific `workspace` root. Floating + capabilities, empty request resources, duplicate request ids, capability + aliases outside request position, and raw ambient operation families reject + with stable kinds. Target steps remain non-callable, the provider seam gains + no host import, and checked Core/Target request goldens bind the complete + public request identity. - Added the compiler-owned `edict.result-projection/v1` artifact for preserving typed application results across the Echo target boundary. The bounded, canonical projection names only declared application input and diff --git a/EDICT.md b/EDICT.md index 53c3b82..3537613 100644 --- a/EDICT.md +++ b/EDICT.md @@ -1267,9 +1267,9 @@ intent (input: ) | Family | Codes | | --- | --- | -| Parser (19) | `Lex`, `ExpectedToken`, `ExpectedKeyword`, `ExpectedIdentifier`, `ExpectedExpression`, `InvalidInteger`, `InvalidDigest`, `InvalidVersion`, `ReservedKeyword`, `UnsupportedSyntax`, `InvalidName`, `EmptyEnum`, `EmptyObstructionMap`, `EmptyMatch`, `MissingRequiredField`, `DuplicateField`, `NonCallEffect`, `ReturnInYieldBlock`, `InvalidTypeCall` | +| Parser (20) | `Lex`, `ExpectedToken`, `ExpectedKeyword`, `ExpectedIdentifier`, `ExpectedExpression`, `InvalidInteger`, `InvalidDigest`, `InvalidVersion`, `ReservedKeyword`, `UnsupportedSyntax`, `InvalidName`, `EmptyEnum`, `EmptyObstructionMap`, `EmptyMatch`, `MissingRequiredField`, `DuplicateField`, `NonCallEffect`, `NonCallExternalActionOperation`, `ReturnInYieldBlock`, `InvalidTypeCall` | | Semantic (7) | `UnboundedScalar`, `MissingOperationMode`, `MissingBudget`, `MissingBasis`, `DuplicateIntentClause`, `DuplicateName`, `ShadowedName` | -| Compiler (10) | `SurfaceValidation`, `MissingContextFact`, `UnsupportedSourceShape`, `UnresolvedType`, `UnknownField`, `TypeMismatch`, `ExpectedPredicate`, `ProfileEffectMismatch`, `DuplicateObstructionFailure`, `DuplicateObstructionPayloadField` | +| Compiler (11) | `SurfaceValidation`, `MissingContextFact`, `UnsupportedSourceShape`, `UnresolvedType`, `UnknownField`, `TypeMismatch`, `ExpectedPredicate`, `ProfileEffectMismatch`, `UnrequestableExternalOperation`, `DuplicateObstructionFailure`, `DuplicateObstructionPayloadField` | | CLI exit codes | `0` ok · `1` compiler/validation diagnostics · `2` invalid CLI input | ### D.5 Digest domains diff --git a/README.md b/README.md index df49778..87f6d5d 100644 --- a/README.md +++ b/README.md @@ -581,7 +581,8 @@ What exists today: - Reference `edict.canonical-cbor/v1` Core encoder and canonical byte validation path for the current in-memory Core module model - Reviewed Core golden bytes and exact `edict.core.module/v1` digest fixture for - the initial pure local-record Core artifact + the initial pure local-record Core artifact and typed workspace-snapshot + external request - Typed v1 target-profile manifest conformance for runtime-neutral profile validation, including `echo.dpo@1` and `kv.transactional@1` shaped profiles - Typed v1 lowerability checks for `LoweringRequirements` against explicit @@ -608,8 +609,8 @@ What exists today: through the binary - Published `v0.11.0-alpha.1` release notes for contract-bundle assembly and canonical Target IR artifact bytes/digests: semantic/release bundle digest - goldens, Echo/git-warp Target IR byte/digest goldens, and computed - `targetIrDigest` bundle assembly from real `TargetIrArtifact` values + goldens, Echo/git-warp/workspace-request Target IR byte/digest goldens, and + computed `targetIrDigest` bundle assembly from real `TargetIrArtifact` values - Published `v0.8.0-alpha.1` release notes for the minimal effectful compiler-spine alpha - Published `v0.7.0-alpha.1` release notes for the file-backed diff --git a/crates/edict-cli/src/main.rs b/crates/edict-cli/src/main.rs index 8da9ac2..ad4244e 100644 --- a/crates/edict-cli/src/main.rs +++ b/crates/edict-cli/src/main.rs @@ -1189,6 +1189,7 @@ fn compiler_error_kind_name(kind: CompilerErrorKind) -> &'static str { CompilerErrorKind::TypeMismatch => "TypeMismatch", CompilerErrorKind::ExpectedPredicate => "ExpectedPredicate", CompilerErrorKind::ProfileEffectMismatch => "ProfileEffectMismatch", + CompilerErrorKind::UnrequestableExternalOperation => "UnrequestableExternalOperation", CompilerErrorKind::DuplicateObstructionFailure => "DuplicateObstructionFailure", CompilerErrorKind::DuplicateObstructionPayloadField => "DuplicateObstructionPayloadField", } diff --git a/crates/edict-cli/tests/jsonl_cli.rs b/crates/edict-cli/tests/jsonl_cli.rs index 516eec6..9d9892f 100644 --- a/crates/edict-cli/tests/jsonl_cli.rs +++ b/crates/edict-cli/tests/jsonl_cli.rs @@ -303,6 +303,10 @@ fn project_exposes_external_requests_as_non_callable_review_data() { .and_then(Value::as_str), Some("workspace.snapshot.observe@1") ); + assert_external_request_review( + core.pointer("/review/intents/observe/body/nodes/0") + .expect("Core request review exists"), + ); let target = record_of_type(&stdout, "targetIr"); assert_eq!( @@ -321,11 +325,10 @@ fn project_exposes_external_requests_as_non_callable_review_data() { .and_then(Value::as_str), Some("workspace.snapshot.observe@1") ); - assert_eq!( + assert_external_request_review( target - .pointer("/review/intents/observe/externalActionRequests/0/state") - .and_then(Value::as_str), - Some("awaitingSettlement") + .pointer("/review/intents/observe/externalActionRequests/0") + .expect("Target IR request review exists"), ); assert_eq!( target @@ -336,6 +339,36 @@ fn project_exposes_external_requests_as_non_callable_review_data() { ); } +fn assert_external_request_review(request: &Value) { + for (pointer, expected) in [ + ("/operation/coordinate", "workspace.snapshot.observe@1"), + ("/inputType", "Bytes"), + ("/settlementType", "Bytes"), + ("/inputSchema/coordinate", "workspace.snapshot.input@1"), + ( + "/settlementSchema/coordinate", + "workspace.snapshot.settlement@1", + ), + ("/input/field", "payload"), + ("/authorityScope/field", "scope"), + ("/basis/field", "basis"), + ("/budget/maxSettlementBytes/field", "maxSettlementBytes"), + ("/budget/maxAttempts/field", "maxAttempts"), + ( + "/reconciliationLaw/coordinate", + "workspace.snapshot.reconcile@1", + ), + ("/state", "awaitingSettlement"), + ("/settlementAdmission", "schemaRequired"), + ] { + assert_eq!( + request.pointer(pointer).and_then(Value::as_str), + Some(expected), + "{pointer}" + ); + } +} + #[test] fn project_invalid_source_emits_diagnostics_without_process_failure() { let output = run_edict(&jsonl([ diff --git a/crates/edict-provider-schema/tests/provider_contract_pack.rs b/crates/edict-provider-schema/tests/provider_contract_pack.rs index b1efcad..7b7032e 100644 --- a/crates/edict-provider-schema/tests/provider_contract_pack.rs +++ b/crates/edict-provider-schema/tests/provider_contract_pack.rs @@ -35,6 +35,8 @@ const RESULT_PROJECTION_CDDL: &[u8] = const TARGET_IR_CDDL: &[u8] = include_bytes!("../../../docs/abi/edict-target-ir.cddl"); const CORE_FIXTURE: &[u8] = include_bytes!("../../../fixtures/core/canonical/bounded-hello.core.cbor"); +const EXTERNAL_REQUEST_CORE_FIXTURE: &[u8] = + include_bytes!("../../../fixtures/core/canonical/workspace-snapshot.core.cbor"); const AUTHORITY_FACTS_FIXTURE: &[u8] = include_bytes!( "../../../fixtures/authority-facts/canonical/example-effectful.authority-facts.cbor" ); @@ -42,6 +44,8 @@ const TARGET_IR_FIXTURE: &[u8] = include_bytes!("../../../fixtures/target-ir/canonical/echo-effectful.target-ir.cbor"); const ALTERNATE_TARGET_IR_FIXTURE: &[u8] = include_bytes!("../../../fixtures/target-ir/canonical/gitwarp-append.target-ir.cbor"); +const EXTERNAL_REQUEST_TARGET_IR_FIXTURE: &[u8] = + include_bytes!("../../../fixtures/target-ir/canonical/workspace-snapshot.target-ir.cbor"); const LAWPACK_ADAPTER_FIXTURE: &[u8] = include_bytes!("../../../fixtures/lawpack/hello-echo/adapter.cbor"); const RESULT_PROJECTION_FIXTURE: &[u8] = @@ -186,10 +190,24 @@ fn every_published_root_validates_reference_and_rejects_mutation() { ); } +#[test] +fn core_root_matches_reference_encoder() { + let pack = assemble(canonical_target_profile_contract_resources()); + for fixture in [CORE_FIXTURE, EXTERNAL_REQUEST_CORE_FIXTURE] { + let value = decode_canonical_cbor(fixture).expect("reviewed Core is canonical"); + pack.validate_domain(CORE_MODULE_DIGEST_DOMAIN, &value) + .expect("reviewed Core satisfies the Edict-owned root"); + } +} + #[test] fn target_ir_root_matches_reference_encoder() { let pack = assemble(canonical_target_profile_contract_resources()); - for fixture in [TARGET_IR_FIXTURE, ALTERNATE_TARGET_IR_FIXTURE] { + for fixture in [ + TARGET_IR_FIXTURE, + ALTERNATE_TARGET_IR_FIXTURE, + EXTERNAL_REQUEST_TARGET_IR_FIXTURE, + ] { let value = decode_canonical_cbor(fixture).expect("reviewed Target IR is canonical"); pack.validate_domain(TARGET_IR_ARTIFACT_DIGEST_DOMAIN, &value) .expect("reviewed Target IR satisfies the Edict-owned root"); diff --git a/crates/edict-syntax/src/canonical.rs b/crates/edict-syntax/src/canonical.rs index 5e4e090..b45e8d4 100644 --- a/crates/edict-syntax/src/canonical.rs +++ b/crates/edict-syntax/src/canonical.rs @@ -569,38 +569,8 @@ fn target_ir_artifact_value(artifact: &TargetIrArtifact) -> Result Result { - let mut lawpacks = BTreeMap::<&str, &ResourceRef>::new(); - for resource in &closure.lawpacks { - if let Some(prior) = lawpacks.get(resource.coordinate.as_str()) { - if *prior != resource { - return Err(CanonicalError::new( - CanonicalErrorKind::UnsupportedValue, - format!( - "Target IR lawpack coordinate `{}` is bound to conflicting resources", - resource.coordinate - ), - )); - } - } else { - lawpacks.insert(resource.coordinate.as_str(), resource); - } - } - let mut capabilities = BTreeMap::<&str, &ResourceRef>::new(); - for resource in &closure.capabilities { - if let Some(prior) = capabilities.get(resource.coordinate.as_str()) { - if *prior != resource { - return Err(CanonicalError::new( - CanonicalErrorKind::UnsupportedValue, - format!( - "Target IR capability coordinate `{}` is bound to conflicting resources", - resource.coordinate - ), - )); - } - } else { - capabilities.insert(resource.coordinate.as_str(), resource); - } - } + let lawpacks = target_ir_resource_set(&closure.lawpacks, "lawpack")?; + let capabilities = target_ir_resource_set(&closure.capabilities, "capability")?; let mut entries = vec![ ( "sourceCore", @@ -620,6 +590,29 @@ fn target_ir_semantic_closure_value( Ok(map(entries)) } +fn target_ir_resource_set<'a>( + resources: &'a [ResourceRef], + field_name: &str, +) -> Result, CanonicalError> { + let mut indexed = BTreeMap::new(); + for resource in resources { + if let Some(prior) = indexed.get(resource.coordinate.as_str()) { + if *prior != resource { + return Err(CanonicalError::new( + CanonicalErrorKind::UnsupportedValue, + format!( + "Target IR {field_name} coordinate `{}` is bound to conflicting resources", + resource.coordinate + ), + )); + } + } else { + indexed.insert(resource.coordinate.as_str(), resource); + } + } + Ok(indexed) +} + fn target_ir_resource_ref_value(resource: &ResourceRef) -> Result { if resource.coordinate.is_empty() { return Err(CanonicalError::new( @@ -646,6 +639,18 @@ fn target_ir_resource_ref_value(resource: &ResourceRef) -> Result Result { + let mut request_ids = BTreeSet::new(); + for request in &intent.external_action_requests { + if !request_ids.insert(request.id.as_str()) { + return Err(CanonicalError::new( + CanonicalErrorKind::UnsupportedValue, + format!( + "Target IR external-action request id `{}` is duplicated", + request.id + ), + )); + } + } let mut entries = vec![ ("operationProfile", text(&intent.operation_profile)), ( @@ -832,6 +837,12 @@ fn core_import_value(import: &CoreImport) -> Result Result { + if resource.coordinate.is_empty() { + return Err(CanonicalError::new( + CanonicalErrorKind::UnsupportedValue, + "Core resource coordinate is empty", + )); + } let mut entries = vec![("id", text(&resource.coordinate))]; let Some(digest) = &resource.digest else { return Err(CanonicalError::new( diff --git a/crates/edict-syntax/src/compiler.rs b/crates/edict-syntax/src/compiler.rs index ecb3f20..ce9ed8c 100644 --- a/crates/edict-syntax/src/compiler.rs +++ b/crates/edict-syntax/src/compiler.rs @@ -42,6 +42,7 @@ pub enum CompilerErrorKind { TypeMismatch, ExpectedPredicate, ProfileEffectMismatch, + UnrequestableExternalOperation, DuplicateObstructionFailure, DuplicateObstructionPayloadField, } @@ -203,7 +204,7 @@ pub fn resolve_module( ) -> Result> { let mut errors = Vec::new(); let coordinate = package_coordinate(&module.package.path, &module.package.version); - let imports = resolve_imports(&module.imports, &mut errors); + let imports = resolve_imports(&module.imports); let mut types = Vec::new(); let mut intents = Vec::new(); @@ -283,7 +284,7 @@ pub fn lower_core(typed: &TypedModule) -> Result> }) } -fn resolve_imports(imports: &[Import], _errors: &mut Vec) -> Vec { +fn resolve_imports(imports: &[Import]) -> Vec { let mut out = Vec::new(); for import in imports { let Some(kind) = core_import_kind(import.kind) else { @@ -895,12 +896,12 @@ impl<'a> TypeChecker<'a> { else { return; }; - if ambient_operation_family(&operation_resource.coordinate) { + if !requestable_operation_family(&operation_resource.coordinate) { self.errors.push(error( CompilerStage::TypeCheck, - CompilerErrorKind::UnsupportedSourceShape, + CompilerErrorKind::UnrequestableExternalOperation, format!( - "ambient operation family `{}` is not requestable", + "external operation family `{}` is not requestable", operation_resource.coordinate ), span, @@ -998,6 +999,11 @@ impl<'a> TypeChecker<'a> { .find(|import| { import.kind == CoreImportKind::Capability && import.alias.as_deref() == Some(alias.as_str()) + && import + .resource + .digest + .as_deref() + .is_some_and(is_lowercase_sha256_review_digest) }) .map(|import| import.resource.clone()) else { @@ -1089,6 +1095,9 @@ impl<'a> TypeChecker<'a> { target: &ObstructionTarget, env: &BTreeMap, ) -> Option { + if self.reject_capability_obstruction_root(&target.coordinate, target.span) { + return None; + } let payload = match &target.payload { Some(Expr::Record { entries, .. }) => { self.check_reason_payload(entries, env, ReasonPayloadMode::PreserveReasonField)? @@ -1126,6 +1135,15 @@ impl<'a> TypeChecker<'a> { return None; }; if let Some(root) = plain_path_root(reason) { + if self.capability_import_alias(root) { + self.errors.push(error( + CompilerStage::TypeCheck, + CompilerErrorKind::UnsupportedSourceShape, + "capability imports are legal only as request operations", + span, + )); + return None; + } if env.contains_key(root) { self.errors.push(error( CompilerStage::TypeCheck, @@ -1520,6 +1538,9 @@ impl<'a> TypeChecker<'a> { } fn check_obstruction_target(&mut self, target: &ObstructionTarget) -> Option { + if self.reject_capability_obstruction_root(&target.coordinate, target.span) { + return None; + } if target.payload.is_some() { self.errors.push(error( CompilerStage::TypeCheck, @@ -1536,6 +1557,29 @@ impl<'a> TypeChecker<'a> { }) } + fn reject_capability_obstruction_root(&mut self, coordinate: &[String], span: Span) -> bool { + if coordinate + .first() + .is_some_and(|root| self.capability_import_alias(root)) + { + self.errors.push(error( + CompilerStage::TypeCheck, + CompilerErrorKind::UnsupportedSourceShape, + "capability imports are legal only as request operations", + span, + )); + true + } else { + false + } + } + + fn capability_import_alias(&self, alias: &str) -> bool { + self.resolved.imports.iter().any(|import| { + import.kind == CoreImportKind::Capability && import.alias.as_deref() == Some(alias) + }) + } + fn check_known_effect_profiles(&mut self, intent: &ResolvedIntent, expr: &Expr) -> bool { let mut accepted = true; if let Some(effect) = effect_coordinate(expr) { @@ -1666,11 +1710,12 @@ impl<'a> TypeChecker<'a> { max_attempts, .. } => { - self.check_known_effect_profiles(intent, operation) - && self.check_known_effect_profiles(intent, authority_scope) - && self.check_known_effect_profiles(intent, basis) - && self.check_known_effect_profiles(intent, max_settlement_bytes) - && self.check_known_effect_profiles(intent, max_attempts) + let mut accepted = self.check_known_effect_profiles(intent, operation); + accepted &= self.check_known_effect_profiles(intent, authority_scope); + accepted &= self.check_known_effect_profiles(intent, basis); + accepted &= self.check_known_effect_profiles(intent, max_settlement_bytes); + accepted &= self.check_known_effect_profiles(intent, max_attempts); + accepted } Stmt::Require { predicate, .. } | Stmt::Guarantee { predicate, .. } @@ -2137,13 +2182,11 @@ fn compatible(expected: &TypeShape, actual: &TypeShape) -> bool { } } -fn ambient_operation_family(coordinate: &str) -> bool { - coordinate.split(['.', '@']).next().is_some_and(|root| { - matches!( - root, - "filesystem" | "process" | "network" | "git" | "github" | "model" | "shell" - ) - }) +fn requestable_operation_family(coordinate: &str) -> bool { + coordinate + .split(['.', '@']) + .next() + .is_some_and(|root| root.eq_ignore_ascii_case("workspace")) } fn comparable(left: &TypeShape, right: &TypeShape) -> bool { diff --git a/crates/edict-syntax/src/contract_bundle.rs b/crates/edict-syntax/src/contract_bundle.rs index 4c62442..9a31645 100644 --- a/crates/edict-syntax/src/contract_bundle.rs +++ b/crates/edict-syntax/src/contract_bundle.rs @@ -620,24 +620,18 @@ fn corroborate_target_ir_semantic_closure( "Target IR source Core identity does not match the supplied Core module", )); } - if canonical_resource_set(&actual.lawpacks) - != canonical_resource_set(&expected.lawpacks) - { - return Err(ContractBundleAssemblyError::new( - ContractBundleAssemblyErrorKind::TargetIrSourceMismatch, - "target_ir_artifact.semantic_closure.lawpacks", - "Target IR lawpack closure does not match the supplied Core module", - )); - } - if canonical_resource_set(&actual.capabilities) - != canonical_resource_set(&expected.capabilities) - { - return Err(ContractBundleAssemblyError::new( - ContractBundleAssemblyErrorKind::TargetIrSourceMismatch, - "target_ir_artifact.semantic_closure.capabilities", - "Target IR capability closure does not match the supplied Core module", - )); - } + require_matching_resource_set( + &actual.lawpacks, + &expected.lawpacks, + "target_ir_artifact.semantic_closure.lawpacks", + "Target IR lawpack closure does not match the supplied Core module", + )?; + require_matching_resource_set( + &actual.capabilities, + &expected.capabilities, + "target_ir_artifact.semantic_closure.capabilities", + "Target IR capability closure does not match the supplied Core module", + )?; canonical_resource_set(&expected.lawpacks) } }; @@ -675,6 +669,22 @@ fn canonical_resource_set(resources: &[ResourceRef]) -> Vec<(String, Option Result<(), ContractBundleAssemblyError> { + if canonical_resource_set(actual) != canonical_resource_set(expected) { + return Err(ContractBundleAssemblyError::new( + ContractBundleAssemblyErrorKind::TargetIrSourceMismatch, + field, + message, + )); + } + Ok(()) +} + fn target_profile_from_target_ir_artifact( target_profile: &ResourceRef, ) -> Result { diff --git a/crates/edict-syntax/src/highlight.rs b/crates/edict-syntax/src/highlight.rs index 1ed34b1..28b3f7d 100644 --- a/crates/edict-syntax/src/highlight.rs +++ b/crates/edict-syntax/src/highlight.rs @@ -117,6 +117,7 @@ fn is_highlight_keyword(text: &str) -> bool { | "where" | "let" | "return" + | "request" | "require" | "guarantee" | "assert" diff --git a/crates/edict-syntax/src/parser.rs b/crates/edict-syntax/src/parser.rs index c83fa59..0cb624c 100644 --- a/crates/edict-syntax/src/parser.rs +++ b/crates/edict-syntax/src/parser.rs @@ -32,6 +32,7 @@ pub enum ParseErrorKind { MissingRequiredField, DuplicateField, NonCallEffect, + NonCallExternalActionOperation, ReturnInYieldBlock, InvalidTypeCall, } @@ -64,6 +65,7 @@ impl ParseErrorKind { ParseErrorKind::MissingRequiredField => "MissingRequiredField", ParseErrorKind::DuplicateField => "DuplicateField", ParseErrorKind::NonCallEffect => "NonCallEffect", + ParseErrorKind::NonCallExternalActionOperation => "NonCallExternalActionOperation", ParseErrorKind::ReturnInYieldBlock => "ReturnInYieldBlock", ParseErrorKind::InvalidTypeCall => "InvalidTypeCall", } @@ -963,7 +965,7 @@ impl Parser { let operation = self.expr()?; if !is_call_expr(&operation) { return self.err_kind( - ParseErrorKind::NonCallEffect, + ParseErrorKind::NonCallExternalActionOperation, "external-action request operation must be a call expression", ); } @@ -1680,6 +1682,10 @@ mod parse_error_kind_codes { (ParseErrorKind::MissingRequiredField, "MissingRequiredField"), (ParseErrorKind::DuplicateField, "DuplicateField"), (ParseErrorKind::NonCallEffect, "NonCallEffect"), + ( + ParseErrorKind::NonCallExternalActionOperation, + "NonCallExternalActionOperation", + ), (ParseErrorKind::ReturnInYieldBlock, "ReturnInYieldBlock"), (ParseErrorKind::InvalidTypeCall, "InvalidTypeCall"), ]; diff --git a/crates/edict-syntax/src/target_ir.rs b/crates/edict-syntax/src/target_ir.rs index c62ea63..0862dab 100644 --- a/crates/edict-syntax/src/target_ir.rs +++ b/crates/edict-syntax/src/target_ir.rs @@ -364,46 +364,39 @@ fn lower_result_projections( pub(crate) fn semantic_closure_for_core( core: &CoreModule, ) -> Result, TargetLoweringFailure> { - let mut lawpacks = BTreeMap::::new(); - let mut capabilities = BTreeMap::::new(); - for resource in core - .imports - .iter() - .filter(|import| import.kind == CoreImportKind::Lawpack) - .map(|import| &import.resource) - { - if !resource - .digest - .as_deref() - .is_some_and(is_lowercase_sha256_review_digest) - { - return Err(TargetLoweringFailure { - kind: TargetLoweringFailureKind::UndigestedCoreImport, - intent: None, - node_index: None, - detail: resource.coordinate.clone(), - }); - } - if let Some(prior) = lawpacks.get(&resource.coordinate) { - if prior != resource { - return Err(TargetLoweringFailure { - kind: TargetLoweringFailureKind::InvalidCoreIdentity, - intent: None, - node_index: None, - detail: format!( - "lawpack coordinate `{}` is bound to conflicting resources", - resource.coordinate - ), - }); - } - } else { - lawpacks.insert(resource.coordinate.clone(), resource.clone()); - } + let lawpacks = digest_locked_import_set(core, CoreImportKind::Lawpack, "lawpack")?; + let capabilities = digest_locked_import_set(core, CoreImportKind::Capability, "capability")?; + let has_explicit_basis = core.intents.values().any(|intent| intent.basis.is_some()); + if !has_explicit_basis && lawpacks.is_empty() && capabilities.is_empty() { + return Ok(None); } + + let digest = digest_core_module(core).map_err(|error| TargetLoweringFailure { + kind: TargetLoweringFailureKind::InvalidCoreIdentity, + intent: None, + node_index: None, + detail: error.to_string(), + })?; + Ok(Some(TargetIrSemanticClosure { + source_core: ResourceRef { + coordinate: core.coordinate.clone(), + digest: Some(digest.to_review_string()), + }, + lawpacks: lawpacks.into_values().collect(), + capabilities: capabilities.into_values().collect(), + })) +} + +fn digest_locked_import_set( + core: &CoreModule, + kind: CoreImportKind, + field_name: &str, +) -> Result, TargetLoweringFailure> { + let mut resources = BTreeMap::new(); for resource in core .imports .iter() - .filter(|import| import.kind == CoreImportKind::Capability) + .filter(|import| import.kind == kind) .map(|import| &import.resource) { if !resource @@ -418,41 +411,23 @@ pub(crate) fn semantic_closure_for_core( detail: resource.coordinate.clone(), }); } - if let Some(prior) = capabilities.get(&resource.coordinate) { + if let Some(prior) = resources.get(&resource.coordinate) { if prior != resource { return Err(TargetLoweringFailure { kind: TargetLoweringFailureKind::InvalidCoreIdentity, intent: None, node_index: None, detail: format!( - "capability coordinate `{}` is bound to conflicting resources", + "{field_name} coordinate `{}` is bound to conflicting resources", resource.coordinate ), }); } } else { - capabilities.insert(resource.coordinate.clone(), resource.clone()); + resources.insert(resource.coordinate.clone(), resource.clone()); } } - let has_explicit_basis = core.intents.values().any(|intent| intent.basis.is_some()); - if !has_explicit_basis && lawpacks.is_empty() && capabilities.is_empty() { - return Ok(None); - } - - let digest = digest_core_module(core).map_err(|error| TargetLoweringFailure { - kind: TargetLoweringFailureKind::InvalidCoreIdentity, - intent: None, - node_index: None, - detail: error.to_string(), - })?; - Ok(Some(TargetIrSemanticClosure { - source_core: ResourceRef { - coordinate: core.coordinate.clone(), - digest: Some(digest.to_review_string()), - }, - lawpacks: lawpacks.into_values().collect(), - capabilities: capabilities.into_values().collect(), - })) + Ok(resources) } fn validate_target_selection( diff --git a/crates/edict-syntax/tests/external_action_requests.rs b/crates/edict-syntax/tests/external_action_requests.rs index 019fe8d..9ba8793 100644 --- a/crates/edict-syntax/tests/external_action_requests.rs +++ b/crates/edict-syntax/tests/external_action_requests.rs @@ -8,8 +8,8 @@ use std::{collections::BTreeSet, fmt::Write as _}; use edict_syntax::{ compile_to_core, decode_canonical_cbor, digest_core_module, encode_core_module, encode_target_ir_artifact, lower_to_target_ir, parse_module, CanonicalValue, CompilerContext, - CompilerErrorKind, CoreBudget, ResourceRef, TargetIrLoweringFacts, TargetLoweringStatus, - WriteClass, ECHO_DPO_TARGET_PROFILE, ECHO_SPAN_IR_DOMAIN, + CompilerErrorKind, CoreBudget, CoreNode, ResourceRef, TargetIrLoweringFacts, + TargetLoweringStatus, WriteClass, ECHO_DPO_TARGET_PROFILE, ECHO_SPAN_IR_DOMAIN, }; const OPERATION_DIGEST: char = 'a'; @@ -92,6 +92,7 @@ type ObserveInput = {{ maxSettlementBytes: U64, alternateMaxSettlementBytes: U64, maxAttempts: U32, + alternateMaxAttempts: U32, }}; intent observe(input: ObserveInput) returns ExternalActionRequest> @@ -242,12 +243,11 @@ fn undeclared_or_floating_operation_families_fail_closed() { let floating = baseline_source().replace(&format!(" digest \"{}\"", digest(OPERATION_DIGEST)), ""); let module = parse_module(&floating).expect("floating capability source parses"); - let core = compile_to_core(&module, &context()).expect("floating source reaches Core"); assert_eq!( - encode_core_module(&core) - .expect_err("floating operation cannot become canonical") - .kind(), - edict_syntax::CanonicalErrorKind::UnresolvedDigest + compile_to_core(&module, &context()) + .expect_err("floating operation rejects during compilation")[0] + .kind, + CompilerErrorKind::MissingContextFact ); } @@ -276,6 +276,65 @@ fn request_operation_must_remain_in_core_and_target_capability_closure() { ); } +#[test] +fn request_resource_coordinates_must_be_nonempty() { + let core = compile_source(&baseline_source()); + + for field in ["inputSchema", "settlementSchema", "reconciliationLaw"] { + let mut changed = core.clone(); + let request = changed + .intents + .get_mut("observe") + .expect("observe intent exists") + .body + .nodes + .first_mut() + .expect("request node exists"); + let CoreNode::ExternalActionRequest { + input_schema, + settlement_schema, + reconciliation_law, + .. + } = request + else { + panic!("first node is an external-action request"); + }; + match field { + "inputSchema" => input_schema.coordinate.clear(), + "settlementSchema" => settlement_schema.coordinate.clear(), + "reconciliationLaw" => reconciliation_law.coordinate.clear(), + _ => unreachable!("bounded field corpus"), + } + + assert_eq!( + encode_core_module(&changed) + .expect_err("empty request resource coordinate rejects") + .kind(), + edict_syntax::CanonicalErrorKind::UnsupportedValue, + "{field}" + ); + } +} + +#[test] +fn duplicate_target_request_ids_reject_before_identity() { + let (_, mut target) = lower_source(&baseline_source()); + let intent = target + .intents + .get_mut("observe") + .expect("observe intent exists"); + intent + .external_action_requests + .push(intent.external_action_requests[0].clone()); + + assert_eq!( + encode_target_ir_artifact(&target) + .expect_err("duplicate target request ids reject") + .kind(), + edict_syntax::CanonicalErrorKind::UnsupportedValue + ); +} + #[test] fn capability_import_cannot_be_called_as_an_effect() { let source = format!( @@ -302,16 +361,60 @@ intent observe(input: Input) returns Bytes assert_eq!(errors[0].kind, CompilerErrorKind::UnsupportedSourceShape); } +#[test] +fn capability_import_cannot_be_used_as_an_obstruction_coordinate() { + let require_source = baseline_source().replace( + "{\n request pending:", + "{\n require false else snapshot;\n request pending:", + ); + let module = parse_module(&require_source).expect("capability-require source parses"); + let errors = compile_to_core(&module, &context()) + .expect_err("capability alias in obstruction position rejects"); + assert_eq!(errors[0].kind, CompilerErrorKind::UnsupportedSourceShape); + + let effect_source = format!( + r#"package examples.obstruction_alias@1; +use capability workspace.snapshot.observe@1 digest "{}" as snapshot; +type Input = {{ payload: Bytes, }}; +type Output = {{ payload: Bytes, }}; +intent observe(input: Input) returns Output + profile workspace.read + basis none + budget <= workspace.tiny {{ + let output: Output = target.read(input.payload) + else {{ rejected(reason) => snapshot }}; + return output; +}} +"#, + digest(OPERATION_DIGEST) + ); + let module = parse_module(&effect_source).expect("capability-effect-obstruction source parses"); + let effect_context = context() + .with_operation_profile_write_classes("workspace.read", [WriteClass::Read]) + .with_effect_write_class("target.read", WriteClass::Read); + let errors = compile_to_core(&module, &effect_context) + .expect_err("capability alias in effect obstruction position rejects"); + assert_eq!(errors[0].kind, CompilerErrorKind::UnsupportedSourceShape); +} + #[test] fn ambient_operation_families_are_not_requestable() { for coordinate in [ "filesystem.read@1", + "Filesystem.read@1", "process.spawn@1", "network.fetch@1", + "NETWORK.fetch@1", "git.push@1", "github.open_pull_request@1", "model.invoke@1", "shell.command@1", + "fs.read@1", + "net.fetch@1", + "http.get@1", + "exec.command@1", + "gh.open_pull_request@1", + "calendar.observe@1", ] { let source = request_source(&RequestSource { capability_coordinate: coordinate, @@ -331,12 +434,19 @@ fn ambient_operation_families_are_not_requestable() { compile_to_core(&module, &context()).expect_err("ambient operation family rejects"); assert_eq!( errors[0].kind, - CompilerErrorKind::UnsupportedSourceShape, + CompilerErrorKind::UnrequestableExternalOperation, "{coordinate}" ); } } +#[test] +fn non_call_request_operation_has_a_request_specific_parse_kind() { + let source = baseline_source().replace("snapshot(input.payload)", "input.payload"); + let error = parse_module(&source).expect_err("non-call request operation rejects"); + assert_eq!(error.kind.code(), "NonCallExternalActionOperation"); +} + #[test] fn dynamic_admission_values_survive_without_compile_time_execution() { let core = compile_source(&baseline_source()); @@ -373,9 +483,12 @@ fn request_artifacts_are_reproducible() { } #[test] -fn every_request_authority_field_moves_core_identity() { +fn every_request_authority_field_moves_core_and_target_identity() { let baseline = baseline_source(); - let baseline_digest = digest_core_module(&compile_source(&baseline)).expect("baseline digest"); + let (baseline_core, baseline_target) = lower_source(&baseline); + let baseline_core_digest = digest_core_module(&baseline_core).expect("baseline Core digest"); + let baseline_target_bytes = + encode_target_ir_artifact(&baseline_target).expect("baseline Target IR"); let mutations = [ baseline.replace(&digest(OPERATION_DIGEST), &digest('1')), baseline.replace(&digest(INPUT_SCHEMA_DIGEST), &digest('2')), @@ -386,6 +499,10 @@ fn every_request_authority_field_moves_core_identity() { "maxSettlementBytes input.maxSettlementBytes", "maxSettlementBytes input.alternateMaxSettlementBytes", ), + baseline.replace( + "maxAttempts input.maxAttempts", + "maxAttempts input.alternateMaxAttempts", + ), baseline.replace( "snapshot(input.payload)", "snapshot(input.alternatePayload)", @@ -393,9 +510,14 @@ fn every_request_authority_field_moves_core_identity() { baseline.replace(&digest(RECONCILIATION_DIGEST), &digest('4')), ]; for mutation in mutations { + let (mutated_core, mutated_target) = lower_source(&mutation); + assert_ne!( + digest_core_module(&mutated_core).expect("mutated Core digest"), + baseline_core_digest + ); assert_ne!( - digest_core_module(&compile_source(&mutation)).expect("mutated digest"), - baseline_digest + encode_target_ir_artifact(&mutated_target).expect("mutated Target IR"), + baseline_target_bytes ); } } diff --git a/crates/edict-syntax/tests/highlighting.rs b/crates/edict-syntax/tests/highlighting.rs index f6d2146..17e6b9b 100644 --- a/crates/edict-syntax/tests/highlighting.rs +++ b/crates/edict-syntax/tests/highlighting.rs @@ -50,3 +50,11 @@ fn highlight_source_emits_editor_roles_for_fixture() { assert_eq!(find_role(src, &tokens, "<="), HighlightRole::Operator); assert_eq!(find_role(src, &tokens, ";"), HighlightRole::Punctuation); } + +#[test] +fn request_statement_introducer_is_highlighted_as_a_keyword() { + let src = "request pending: ExternalActionRequest> = snapshot(input);"; + let tokens = highlight_source(src).expect("request source highlights"); + + assert_eq!(find_role(src, &tokens, "request"), HighlightRole::Keyword); +} diff --git a/docs/REQUIREMENTS.md b/docs/REQUIREMENTS.md index 5e2fc27..7b321b6 100644 --- a/docs/REQUIREMENTS.md +++ b/docs/REQUIREMENTS.md @@ -67,7 +67,7 @@ but owned by a follow-up issue; no fixtures until its dependency lands). | EDICT-LANG-BUDGET-SPLIT-001 | Core/target/admitted budget split; target dims not in Core | Language | `lang/budget/split` | `lang/budget/target-dim-in-core` | spec | | EDICT-LANG-PROFILE-001 | `edict.language/v1` vs `edict.implementation/minimal-v1` capability flags | Language | `lang/profile/minimal` | `lang/profile/undeclared-capability` | spec | | EDICT-LANG-CAPREF-001 | `CapabilityRef` carries receipt digest only; inert until admitted | Language | `lang/capref/inert` | `lang/capref/ambient-authority` | spec | -| EDICT-LANG-EXTERNAL-REQUEST-001 | External boundary crossings are typed deterministic request values; exact operation capability closure is required and no ambient world authority enters Edict or the provider seam | Language/Core/Target | `crates/edict-syntax/tests/external_action_requests.rs` | `crates/edict-syntax/tests/external_action_requests.rs` | impl | +| EDICT-LANG-EXTERNAL-REQUEST-001 | External boundary crossings are typed deterministic request values; exact operation capability closure is required and no ambient world authority enters Edict or the provider seam | Language/Core/Target | `fixtures/lang/external-actions/workspace-snapshot.edict`; `fixtures/core/canonical/workspace-snapshot.core.cbor`; `fixtures/core/canonical/workspace-snapshot.core.sha256`; `fixtures/target-ir/canonical/workspace-snapshot.target-ir.cbor`; `fixtures/target-ir/canonical/workspace-snapshot.target-ir.sha256`; `workspace_observation_request_compiles_as_non_callable_data` | `undeclared_or_floating_operation_families_fail_closed`; `ambient_operation_families_are_not_requestable`; `request_resource_coordinates_must_be_nonempty`; `duplicate_target_request_ids_reject_before_identity` | impl | | EDICT-LANG-READONLY-001 | Read-only inferred; executionClass (proofOnly/runtime) orthogonal to writeClass; runtime read is allowed | Language/Lawpack | `lang/readonly/runtime-read` | `lang/readonly/hidden-append` | spec | | EDICT-OPTIC-SOURCE-001 | Each optic field has one deterministic source (basis clause / profile template / coordinate / footprint) | Language | `optic/source/basis-clause` | `optic/source/freeform-support` | spec | | EDICT-DIGEST-WIRE-001 | Canonical digest is typed `[algorithm, bytes]`, never a hex string; hex only in review JSON | Bundle/ABI | `abi/digest/typed-pair` | `abi/digest/hex-string` | spec | diff --git a/docs/topics/core-ir/canonical-encoding.md b/docs/topics/core-ir/canonical-encoding.md index 4489efb..34927c6 100644 --- a/docs/topics/core-ir/canonical-encoding.md +++ b/docs/topics/core-ir/canonical-encoding.md @@ -174,7 +174,9 @@ public digest function and reviewed digest contract remain unchanged. The reviewed Core fixture pair is: - [`fixtures/core/canonical/bounded-hello.core.cbor`](../../../fixtures/core/canonical/bounded-hello.core.cbor); -- [`fixtures/core/canonical/bounded-hello.core.sha256`](../../../fixtures/core/canonical/bounded-hello.core.sha256). +- [`fixtures/core/canonical/bounded-hello.core.sha256`](../../../fixtures/core/canonical/bounded-hello.core.sha256); +- [`fixtures/core/canonical/workspace-snapshot.core.cbor`](../../../fixtures/core/canonical/workspace-snapshot.core.cbor); +- [`fixtures/core/canonical/workspace-snapshot.core.sha256`](../../../fixtures/core/canonical/workspace-snapshot.core.sha256). Both are generated from [`fixtures/lang/bounds/bounded-hello.edict`](../../../fixtures/lang/bounds/bounded-hello.edict) diff --git a/docs/topics/core-ir/test-plan.md b/docs/topics/core-ir/test-plan.md index fc3b43c..0bbb918 100644 --- a/docs/topics/core-ir/test-plan.md +++ b/docs/topics/core-ir/test-plan.md @@ -60,6 +60,7 @@ Out of scope: | --- | --- | --- | | docs/abi/edict-core.cddl | Normative Core semantic schema. | Required semantic declarations exist and forbidden byte/hash freeze fields are absent. | | fixtures/lang/bounds/bounded-hello.edict | Initial pure local-record source-to-Core fixture. | Compiled Core module canonicalizes deterministically and produces the reviewed Core golden artifacts. | +| fixtures/lang/external-actions/workspace-snapshot.edict | Typed external-request source-to-Core fixture. | Compiled Core preserves the complete request and exact capability closure in generated canonical bytes. | | fixtures/core/schema/accepted/core-module-minimal.fields | Accepted Core module field-shape fixture. | Required `core-module` fields are present and no unknown fields appear. | | fixtures/core/schema/accepted/core-intent-minimal.fields | Accepted Core intent field-shape fixture. | Required `core-intent` fields are present and no unknown fields appear. | | fixtures/core/schema/accepted/core-fn-body-minimal.fields | Accepted pure Core function body field-shape fixture. | Required `core-fn-body` fields are present and no unknown fields appear. | @@ -69,6 +70,8 @@ Out of scope: | fixtures/core/schema/rejected/core-fn-body-effect-node-field.fields | Rejected pure Core function body field-shape fixture. | Effect-capable `nodes` field rejects as non-Core helper body shape. | | fixtures/core/canonical/bounded-hello.core.cbor | Reviewed canonical Core byte fixture for the initial pure local-record source fixture. | Executable Core encoding exactly matches the checked-in byte fixture. | | fixtures/core/canonical/bounded-hello.core.sha256 | Exact reviewed Core module digest for the initial pure local-record source fixture. | Domain-separated executable Core digest exactly matches the checked-in review rendering. | +| fixtures/core/canonical/workspace-snapshot.core.cbor | Reviewed canonical Core byte fixture for the typed workspace-snapshot request. | `cargo xtask core-goldens --check` compares the checked-in bytes to executable regeneration. | +| fixtures/core/canonical/workspace-snapshot.core.sha256 | Exact reviewed Core module digest for the typed workspace-snapshot request. | `cargo xtask core-goldens --check` compares the checked-in review digest to executable regeneration. | ## Test Cases @@ -101,7 +104,7 @@ Out of scope: | COREIR-TP-025 | implemented | Canonical validation | COREIR-REQ-017 | Encoding and decoding accept exactly 128 nested empty arrays or maps and reject the 129th container even when it has no child value that would otherwise cross the depth guard. | canonical_nesting_limit_counts_empty_terminal_containers | crates/edict-syntax/src/canonical.rs | Code Lawyer regression for PR #150; prevents empty terminal containers from bypassing the public container-count bound. | | COREIR-TP-026 | implemented | Canonical encoding | COREIR-REQ-012, COREIR-REQ-014, COREIR-REQ-018 | Adding or changing an explicit Core basis expression changes canonical Core bytes and digest; the reference-encoded operation Core satisfies the published schema, while omitting basis retains the reviewed `basis none` golden bytes and digest. | explicit_basis_and_semantic_input_mutations_move_target_identity, core_root_accepts_reference_encoded_operation_basis, reviewed_core_golden_bytes_match_executable_encoder, reviewed_core_digest_matches_exact_fixture | fixtures/lang/operations/explicit-basis-u64.edict, fixtures/core/canonical/bounded-hello.core.cbor | Proves basis is semantic while retaining provider-v1 compatibility. | | COREIR-TP-027 | implemented | Canonical validation | COREIR-REQ-012, COREIR-REQ-019 | `U64::MAX` and the signed fixed-width minima canonicalize under their exact declared domains; an out-of-range value or a value incompatible with its declared width rejects before canonical identity exists, and the reference-encoded exact-width operation satisfies the published schema. | operation_prerequisite_fixture_preserves_fixed_width_basis_and_lawpack_closure, signed_fixed_width_minima_preserve_exact_domains, out_of_range_u64_and_cross_width_values_reject_before_core, canonical_core_rejects_values_outside_their_declared_integer_domain, core_root_accepts_reference_encoded_operation_basis | fixtures/lang/operations/explicit-basis-u64.edict | Prevents host-width and GraphQL-width narrowing from entering Core. | -| COREIR-TP-028 | implemented | External request | COREIR-REQ-012, COREIR-REQ-020 | Request fields survive canonical Core encoding, repeated compilation is byte-identical, authority mutations move identity, and removing the exact capability import makes the artifact unencodable. | workspace_observation_request_compiles_as_non_callable_data, request_artifacts_are_reproducible, every_request_authority_field_moves_core_identity, request_operation_must_remain_in_core_and_target_capability_closure | crates/edict-syntax/tests/external_action_requests.rs | Request construction performs no external effect. | +| COREIR-TP-028 | implemented | External request | COREIR-REQ-012, COREIR-REQ-014, COREIR-REQ-020 | Request fields survive canonical Core encoding, repeated compilation is byte-identical, authority mutations move Core and Target identity, removing the exact capability import makes the artifact unencodable, and the owner-generated request golden reproduces exact bytes and digest. | workspace_observation_request_compiles_as_non_callable_data, request_artifacts_are_reproducible, every_request_authority_field_moves_core_and_target_identity, request_operation_must_remain_in_core_and_target_capability_closure, core_goldens_match_executable_encoder | crates/edict-syntax/tests/external_action_requests.rs, fixtures/core/canonical/workspace-snapshot.core.cbor, fixtures/core/canonical/workspace-snapshot.core.sha256 | Request construction performs no external effect. | ## Determinism Obligations diff --git a/docs/topics/developer-tooling/test-plan.md b/docs/topics/developer-tooling/test-plan.md index a587f68..9240f71 100644 --- a/docs/topics/developer-tooling/test-plan.md +++ b/docs/topics/developer-tooling/test-plan.md @@ -48,7 +48,7 @@ Out of scope: | ID | Status | Kind | Requirement | Scenario | Evidence | Fixtures | Oracle | | --- | --- | --- | --- | --- | --- | --- | --- | -| DEVTOOLS-TP-001 | implemented | Golden path | DEVTOOLS-REQ-001 | Highlighting a representative source fixture emits stable roles and spans for editor adapters. | highlight_source_emits_editor_roles_for_fixture | fixtures/lang/tooling/highlight-smoke.edict | Comments, keywords, identifiers, type identifiers, strings, numbers, operators, and punctuation are classified distinctly; whitespace-only spans are not emitted. | +| DEVTOOLS-TP-001 | implemented | Golden path | DEVTOOLS-REQ-001 | Highlighting a representative source fixture emits stable roles and spans for editor adapters, and the `request` statement introducer is a keyword rather than an identifier. | highlight_source_emits_editor_roles_for_fixture, request_statement_introducer_is_highlighted_as_a_keyword | fixtures/lang/tooling/highlight-smoke.edict, crates/edict-syntax/tests/highlighting.rs | Comments, keywords, identifiers, type identifiers, strings, numbers, operators, and punctuation are classified distinctly; whitespace-only spans are not emitted. | | DEVTOOLS-TP-002 | implemented | Contract artifact | DEVTOOLS-REQ-002 | Tree-sitter grammar artifacts expose the current editor syntax surface and highlight roles. | tree_sitter_grammar_declares_current_editor_contract | grammars/tree-sitter-edict/grammar.js, grammars/tree-sitter-edict/queries/highlights.scm | Grammar rules cover package, imports, declarations, intent clauses, blocks, statements, match/call/type-call/record expressions, comments, strings, and numbers; highlight captures cover the public editor roles. | | DEVTOOLS-TP-003 | implemented | Corpus alignment | DEVTOOLS-REQ-003 | Tree-sitter corpus examples remain accepted Edict source under the reference parser. | tree_sitter_corpus_examples_match_reference_parser | grammars/tree-sitter-edict/test/corpus/current-subset.txt | Corpus cases are nonempty, unique, include expected trees, cover the accepted fixture families, and parse through `parse_module`. | | DEVTOOLS-TP-004 | implemented | Contract artifact | DEVTOOLS-REQ-002 | Tree-sitter keyword captures stay aligned with the public lexical highlighter keyword role. | tree_sitter_query_covers_public_keyword_roles | grammars/tree-sitter-edict/queries/highlights.scm | Every keyword lexeme emitted by `highlight_source` for current and reserved public keyword tokens is covered by the Tree-sitter keyword capture contract. | @@ -56,7 +56,7 @@ Out of scope: | DEVTOOLS-TP-006 | implemented | Corpus alignment | DEVTOOLS-REQ-003 | Tree-sitter accepts uppercase bare identifiers in positions where the reference parser accepts them. | tree_sitter_corpus_examples_match_reference_parser | grammars/tree-sitter-edict/test/corpus/current-subset.txt | Uppercase import aliases, intent names, parameters, local binders, and record shorthands parse without error and remain accepted by `parse_module`; `tree-sitter test` verifies the checked-in syntax tree. | | DEVTOOLS-TP-007 | implemented | Contract artifact | DEVTOOLS-REQ-002 | Tree-sitter operator and punctuation captures stay aligned with the public lexical highlighter roles. | tree_sitter_query_operator_and_punctuation_roles_match_public_highlighter | grammars/tree-sitter-edict/queries/highlights.scm | Query captures do not assign a lexeme to both operator and punctuation, and captured operator or punctuation lexemes match the role emitted by `highlight_source`. | | DEVTOOLS-TP-008 | implemented | Contract artifact | DEVTOOLS-REQ-004 | TextMate grammar declares the current editor contract for `.edict` lexical scopes. | textmate_grammar_declares_current_editor_contract | grammars/textmate/edict.tmLanguage.json | The grammar is valid JSON, names `source.edict`, registers `.edict`, includes comments, strings, numbers, keywords, types, operators, punctuation, and identifiers, and scopes line/block comments distinctly. | -| DEVTOOLS-TP-009 | implemented | Contract artifact | DEVTOOLS-REQ-004 | TextMate grammar keyword, operator, punctuation, type, and identifier patterns stay aligned with public highlighter roles. | textmate_grammar_covers_public_highlight_roles | grammars/textmate/edict.tmLanguage.json | Lexemes emitted by `highlight_source` as keywords, operators including `->`, punctuation, type identifiers, and identifiers are covered by the corresponding TextMate scope patterns. | +| DEVTOOLS-TP-009 | implemented | Contract artifact | DEVTOOLS-REQ-004 | TextMate grammar keyword, operator, punctuation, type, and identifier patterns stay aligned with public highlighter roles, including the `request` statement introducer. | textmate_grammar_covers_public_highlight_roles | grammars/textmate/edict.tmLanguage.json | Lexemes emitted by `highlight_source` as keywords, operators including `->`, punctuation, type identifiers, and identifiers are covered by the corresponding TextMate scope patterns. | | DEVTOOLS-TP-010 | implemented | Contract artifact | DEVTOOLS-REQ-004 | TextMate grammar number patterns stay aligned with public highlighter spans for package version labels. | textmate_grammar_scopes_public_number_spans_in_version_labels | grammars/textmate/edict.tmLanguage.json | In a valid version label such as `@1_beta`, a number scope exactly covers the public `Number` span emitted by `highlight_source`. | | DEVTOOLS-TP-011 | implemented | Contract artifact | DEVTOOLS-REQ-005 | VS Code/Cursor package registration stays aligned with the canonical TextMate grammar. | vscode_extension_declares_textmate_language_contract | editors/vscode/package.json, editors/vscode/syntaxes/edict.tmLanguage.json | The extension contributes language id `edict`, extension `.edict`, scope `source.edict`, and a vendored grammar identical to the canonical TextMate grammar artifact. | | DEVTOOLS-TP-012 | implemented | Contract artifact | DEVTOOLS-REQ-005 | VS Code/Cursor language configuration exposes Edict lexer comment and delimiter boundaries. | vscode_language_configuration_matches_lexer_boundaries | editors/vscode/package.json, editors/vscode/language-configuration.json | The language configuration declares `//`, `/* */`, `{}`, `[]`, and `()` boundaries consistent with the lexer and grammar artifacts. | diff --git a/docs/topics/external-action-requests/README.md b/docs/topics/external-action-requests/README.md index 72ff6a9..03ae27a 100644 --- a/docs/topics/external-action-requests/README.md +++ b/docs/topics/external-action-requests/README.md @@ -69,8 +69,11 @@ participates in canonical identity. [EXTREQ-REQ-005] Request authority is not performance authority: - a capability alias cannot be invoked as an ordinary semantic effect; -- raw `filesystem`, `process`, `network`, `git`, `github`, `model`, and `shell` - operation families are rejected; +- the current request-family allowlist contains only the domain-specific + `workspace` root used by `workspace.snapshot.observe@1`; +- raw `filesystem`, `process`, `network`, Git, GitHub, `model`, and `shell` + operation families, case variants, abbreviations, and unregistered roots are + rejected with `UnrequestableExternalOperation`; - compiling and lowering perform no I/O; - the compiler/provider component interface gains no callable import; - dynamic path, ref, basis, budget, adapter, and settlement constraints remain diff --git a/docs/topics/external-action-requests/test-plan.md b/docs/topics/external-action-requests/test-plan.md index 55f634a..96863f6 100644 --- a/docs/topics/external-action-requests/test-plan.md +++ b/docs/topics/external-action-requests/test-plan.md @@ -31,10 +31,10 @@ Out of scope: | EXTREQ-REQ-001 | implemented | Source syntax binds each requestable operation through one digest-locked `capability` import and carries exact input-schema, settlement-schema, authority-scope, basis, budget, input, and reconciliation-law values. | issue #172 | | EXTREQ-REQ-002 | implemented | Core represents external-action requests as data with compiler-owned binding identity, typed input and settlement coordinates, explicit `awaitingSettlement` state, and required schema admission. | issue #172 | | EXTREQ-REQ-003 | implemented | Target IR preserves external-action requests separately from callable target steps and target intrinsics. | issue #172 | -| EXTREQ-REQ-004 | implemented | Core and Target IR semantic closure bind the exact capability resource; undeclared operations and floating capability imports fail closed. | issue #172 | +| EXTREQ-REQ-004 | implemented | Core and Target IR semantic closure bind the exact capability resource; undeclared operations, floating capability imports, empty request resource coordinates, and duplicate Target request identities fail closed. | issue #172 | | EXTREQ-REQ-005 | implemented | Equivalent admitted source and compiler facts produce byte-identical Core and Target IR; every request authority or meaning mutation moves the corresponding digest. | issue #172 | | EXTREQ-REQ-006 | implemented | Runtime-valued authority scope, basis, and budget expressions survive compilation for Echo admission; Edict performs no external action while compiling or lowering them. | issue #172 | -| EXTREQ-REQ-007 | implemented | Raw filesystem, process, network, Git, GitHub, model, and shell operation families are outside the requestable capability vocabulary. | issue #172 | +| EXTREQ-REQ-007 | implemented | The request-family allowlist contains only the domain-specific `workspace` root; raw filesystem, process, network, Git, GitHub, model, shell, case-variant, abbreviation, and unregistered roots are outside the requestable capability vocabulary. | issue #172 | | EXTREQ-REQ-008 | implemented | Request construction remains bounded under a fixed-seed mutation corpus and a 64-request stress module. | issue #172 | ## Fixtures @@ -50,16 +50,19 @@ Out of scope: | ID | Status | Category | Requirement | Oracle | Evidence | Fixtures | Notes | | --- | --- | --- | --- | --- | --- | --- | --- | | EXTREQ-TP-001 | implemented | Golden path | EXTREQ-REQ-001, EXTREQ-REQ-002, EXTREQ-REQ-003 | One workspace observation request parses, compiles, and lowers with exact structured fields, awaiting posture, and zero callable target steps. | workspace_observation_request_compiles_as_non_callable_data | crates/edict-syntax/tests/external_action_requests.rs | The target owns later admission and execution. | -| EXTREQ-TP-002 | implemented | Closure guard | EXTREQ-REQ-004 | Missing operation import, floating capability digest, and operation-alias substitution reject with stable compiler or canonical error kinds. | undeclared_or_floating_operation_families_fail_closed | crates/edict-syntax/tests/external_action_requests.rs | A source alias is not authority by itself. | -| EXTREQ-TP-003 | implemented | Authority guard | EXTREQ-REQ-003, EXTREQ-REQ-007 | Direct invocation of a capability import fails, and raw ambient operation families are unrepresentable. | capability_import_cannot_be_called_as_an_effect, ambient_operation_families_are_not_requestable | crates/edict-syntax/tests/external_action_requests.rs | Request authority and performance authority remain distinct. | +| EXTREQ-TP-002 | implemented | Closure guard | EXTREQ-REQ-004 | Missing operation imports and floating capability digests reject during compilation; removing the exact capability from an already constructed Core or Target artifact rejects during canonical encoding. | undeclared_or_floating_operation_families_fail_closed, request_operation_must_remain_in_core_and_target_capability_closure | crates/edict-syntax/tests/external_action_requests.rs | A source alias is not authority by itself. | +| EXTREQ-TP-003 | implemented | Authority guard | EXTREQ-REQ-003, EXTREQ-REQ-007 | Direct invocation or obstruction-position use of a capability import fails; case variants, abbreviations, unregistered roots, and raw ambient operation families reject with stable compiler kinds. | capability_import_cannot_be_called_as_an_effect, capability_import_cannot_be_used_as_an_obstruction_coordinate, ambient_operation_families_are_not_requestable | crates/edict-syntax/tests/external_action_requests.rs | Request authority and performance authority remain distinct. | | EXTREQ-TP-004 | implemented | Dynamic boundary | EXTREQ-REQ-006 | Scope, basis, maximum settlement bytes, and attempt count remain Core expressions sourced from admitted input. | dynamic_admission_values_survive_without_compile_time_execution | crates/edict-syntax/tests/external_action_requests.rs | Echo owns value-instance admission. | | EXTREQ-TP-005 | implemented | Determinism | EXTREQ-REQ-005 | Repeated compilation and lowering produce identical canonical bytes and digests. | request_artifacts_are_reproducible | crates/edict-syntax/tests/external_action_requests.rs | No clock, randomness, filesystem, or network input enters the compiler. | -| EXTREQ-TP-006 | implemented | Mutation sensitivity | EXTREQ-REQ-005 | Operation, input schema, settlement schema, authority scope, basis, budget, input, and reconciliation mutations move Core identity. | every_request_authority_field_moves_core_identity | crates/edict-syntax/tests/external_action_requests.rs | Each field is request-authoritative. | +| EXTREQ-TP-006 | implemented | Mutation sensitivity | EXTREQ-REQ-005 | Operation, input schema, settlement schema, authority scope, basis, both budget fields, input, and reconciliation mutations move both Core and Target IR identity. | every_request_authority_field_moves_core_and_target_identity | crates/edict-syntax/tests/external_action_requests.rs | Each field is request-authoritative. | | EXTREQ-TP-007 | implemented | Property | EXTREQ-REQ-005, EXTREQ-REQ-008 | A fixed-seed 32-case capability-digest corpus is reproducible and collision-free within the corpus. | fixed_seed_request_identity_corpus_is_deterministic | crates/edict-syntax/tests/external_action_requests.rs | Seed is recorded in the fixture table. | | EXTREQ-TP-008 | implemented | Stress | EXTREQ-REQ-008 | A generated module with 64 request declarations emits 64 Core requests and 64 Target IR requests with zero callable steps. | sixty_four_requests_remain_bounded_non_callable_data | crates/edict-syntax/tests/external_action_requests.rs | Bound is fixed for CI. | | EXTREQ-TP-009 | implemented | Closure guard | EXTREQ-REQ-004 | Canonical Core and Target IR encoding reject request operations removed from their exact capability closure. | request_operation_must_remain_in_core_and_target_capability_closure | crates/edict-syntax/tests/external_action_requests.rs | Manual artifact construction cannot bypass compiler-owned closure. | | EXTREQ-TP-010 | implemented | Bundle boundary | EXTREQ-REQ-004 | Contract-bundle assembly rejects a digest-locked Target IR capability absent from the supplied Core closure. | assembly_from_target_ir_rejects_artifact_capability_substitution | crates/edict-syntax/tests/contract_bundle.rs | A self-consistent target artifact cannot substitute source-owned request authority. | | EXTREQ-TP-011 | implemented | Public projection | EXTREQ-REQ-002, EXTREQ-REQ-003, EXTREQ-REQ-004 | The public CLI review projection carries the complete Core and Target IR request, exact capability closure, awaiting-settlement posture, and no callable target step. | project_exposes_external_requests_as_non_callable_review_data | crates/edict-cli/tests/jsonl_cli.rs | Projection is review data, not execution authority. | +| EXTREQ-TP-012 | implemented | Canonical identity guard | EXTREQ-REQ-004 | Canonical Core encoding rejects empty request schema or reconciliation coordinates, and canonical Target IR encoding rejects duplicate request ids within one intent. | request_resource_coordinates_must_be_nonempty, duplicate_target_request_ids_reject_before_identity | crates/edict-syntax/tests/external_action_requests.rs | Waiting and settlement identity cannot be ambiguous or anonymous. | +| EXTREQ-TP-013 | implemented | Tooling guard | EXTREQ-REQ-001 | A non-call request operation has its own stable parser kind, and `request` is highlighted as a keyword. | non_call_request_operation_has_a_request_specific_parse_kind, request_statement_introducer_is_highlighted_as_a_keyword | crates/edict-syntax/tests/external_action_requests.rs, crates/edict-syntax/tests/highlighting.rs | Request syntax remains distinct from semantic effect syntax. | +| EXTREQ-TP-014 | implemented | Golden artifact | EXTREQ-REQ-002, EXTREQ-REQ-003, EXTREQ-REQ-004, EXTREQ-REQ-005 | The checked workspace-snapshot source reproduces exact compiler-owned Core and Target IR canonical bytes and domain-framed digests. | core_goldens_match_executable_encoder, target_ir_goldens_match_executable_encoder | fixtures/lang/external-actions/workspace-snapshot.edict, fixtures/core/canonical/workspace-snapshot.core.cbor, fixtures/core/canonical/workspace-snapshot.core.sha256, fixtures/target-ir/canonical/workspace-snapshot.target-ir.cbor, fixtures/target-ir/canonical/workspace-snapshot.target-ir.sha256 | Generated only through the owning xtask commands. | ## Determinism Obligations diff --git a/docs/topics/providers/README.md b/docs/topics/providers/README.md index 74813e9..a6fd724 100644 --- a/docs/topics/providers/README.md +++ b/docs/topics/providers/README.md @@ -128,10 +128,10 @@ callable or unknown import. The generated component's exact type-only conveys types rather than a host capability. No WASI linker or callable host function is installed. -Source `use capability` declarations and Core external-action request nodes do -not alter this provider boundary. They are canonical request data passed through -Core and Target IR; they do not become component imports, host functions, or -provider-owned execution authority. +Source `use capability` declarations establish digest-locked module-import +closure resources. Core external-action request nodes carry the canonical +request fields. Both pass through Core and Target IR without becoming component +imports, host functions, or provider-owned execution authority. The host owns one immutable Wasmtime engine and creates a fresh store for every invocation. The prepared component, opaque validated request proof, and concrete diff --git a/docs/topics/providers/test-plan.md b/docs/topics/providers/test-plan.md index 0c14253..a9a3cef 100644 --- a/docs/topics/providers/test-plan.md +++ b/docs/topics/providers/test-plan.md @@ -63,7 +63,7 @@ Out of scope: | PROVIDERS-REQ-019 | implemented | Every artifact domain admitted by one provider manifest is bound exactly once to a digest-locked generated schema artifact, schema format, and root rule. Missing, duplicate, ambiguous, unsupported, or provenance-invalid bindings reject before component invocation. | issue #145, EDICT-ABI-PROVIDER-AUTHORITY-001 | | PROVIDERS-REQ-020 | implemented | The concrete provider artifact-schema registry is constructed completely from a validated manifest plus the exact manifest-bound closure of explicit in-memory schema bytes. Construction verifies schema digests, compiles and totality-checks every selected root before invocation, proves required-domain closure, and performs no discovery, filesystem, network, environment, clock, randomness, or mutable-global access. | issue #145, EDICT-ABI-PROVIDER-AUTHORITY-001 | | PROVIDERS-REQ-021 | implemented | Every registered domain performs real schema-instance validation over canonical CBOR. Unsupported domains and schema mismatches remain stable structured failures, and construction and validation results are independent of input insertion order. | issue #145, EDICT-ABI-PROVIDER-AUTHORITY-001 | -| PROVIDERS-REQ-022 | implemented | The Wasmtime host uses one explicitly configured component-model engine and a fresh store for every invocation. It permits only the frozen protocol's type-only instance import and registers no callable WASI, filesystem, network, environment, clock, randomness, registry, or other capability-bearing import. Source external-action capability references remain canonical request data and never become provider imports. | issue #145, issue #172, EDICT-ABI-PROVIDER-HOST-001 | +| PROVIDERS-REQ-022 | implemented | The Wasmtime host uses one explicitly configured component-model engine and a fresh store for every invocation. It permits only the frozen protocol's type-only instance import and registers no callable WASI, filesystem, network, environment, clock, randomness, registry, or other capability-bearing import. Source external-action capability declarations remain digest-locked module-import closure resources, while Core request nodes carry canonical request data; neither becomes a provider import. | issue #145, issue #172, EDICT-ABI-PROVIDER-HOST-001 | | PROVIDERS-REQ-023 | implemented | The host accepts only opaque validated request proofs, preflights an exact WIT-logical request byte bound and request-authorized output ceiling, enforces explicit fuel, lifting, and store resource limits, invokes the typed frozen lowerer or verifier export, and exposes an artifact only after the pure response validator admits the complete result. | issue #145, issue #146, EDICT-ABI-PROVIDER-HOST-001 | | PROVIDERS-REQ-024 | implemented | Digest, contract, decode, instantiation, fuel, resource, input, output, diagnostic, response-lifting, trap, malformed-response, response-validation, and host-invariant failures use stable host-owned kinds. Wasmtime implementation details remain bounded diagnostics and never enter Edict's public failure identity. | issue #145, EDICT-ABI-PROVIDER-HOST-001 | | PROVIDERS-REQ-025 | implemented | Provider replay executes the same prepared component, validated request, concrete schema authority, and limits twice through independent fresh stores. Exact sealed outcomes must be equal; host failures compare by stable kind, phase, and structured validation report while excluding opaque engine diagnostics. A disposition, completed-outcome, or stable-failure-identity difference returns a dedicated structured replay mismatch. | issue #147 | diff --git a/docs/topics/syntax/test-plan.md b/docs/topics/syntax/test-plan.md index 001c542..a5ee9c2 100644 --- a/docs/topics/syntax/test-plan.md +++ b/docs/topics/syntax/test-plan.md @@ -77,7 +77,7 @@ Out of scope: | SYNTAX-TP-021 | implemented | Semantic validation | SYNTAX-REQ-011 | The source/surface validator returns stable diagnostic kinds for context-free source errors and accepts unresolved downstream facts. | validate_module_remains_surface_stage_compatibility_alias, surface_validation_defers_import_and_name_resolution, surface_validation_defers_contextual_typing_and_loop_bound_proof, surface_validation_defers_obstruction_exhaustiveness | - | Owned by the semantic-validation shelf. | | SYNTAX-TP-022 | planned | Golden artifact | SYNTAX-REQ-012 | Full source lowering emits byte-stable canonical artifacts. | - | - | Initial Core golden artifacts exist in the Core IR shelf; full source-language coverage remains planned. | | SYNTAX-TP-023 | implemented | Syntax guard | SYNTAX-REQ-013 | `else continue obstructed { reason: ... }` parses as a distinct require-else arm, while `else continueInObstructedStrand(...)` remains a terminal obstruction target. | continue_obstructed_source_arm_parses, helper_shaped_continue_in_obstructed_strand_is_terminal, stale_basis_obstruction_strand_fixture_parses | crates/edict-syntax/tests/parse_review_regressions.rs, fixtures/obstruction-strands/v0/stale-basis/source.edict | Prevents hidden control-flow semantics in ordinary obstruction constructors. | -| SYNTAX-TP-024 | implemented | Golden path | SYNTAX-REQ-003, SYNTAX-REQ-007 | A request statement parses its typed binding, single operation call, schemas, authority scope, basis, two budgets, and reconciliation resource before source-to-Core compilation. | workspace_observation_request_compiles_as_non_callable_data | crates/edict-syntax/tests/external_action_requests.rs | Execution and settlement remain outside the parser. | +| SYNTAX-TP-024 | implemented | Golden path | SYNTAX-REQ-003, SYNTAX-REQ-007 | A request statement parses its typed binding, single operation call, schemas, authority scope, basis, two budgets, and reconciliation resource before source-to-Core compilation. A non-call request operation rejects with `NonCallExternalActionOperation`, independently of semantic-effect call diagnostics. | workspace_observation_request_compiles_as_non_callable_data, non_call_request_operation_has_a_request_specific_parse_kind | crates/edict-syntax/tests/external_action_requests.rs | Execution and settlement remain outside the parser. | ## Determinism Obligations diff --git a/docs/topics/target-ir/test-plan.md b/docs/topics/target-ir/test-plan.md index ccc4db4..2d992c3 100644 --- a/docs/topics/target-ir/test-plan.md +++ b/docs/topics/target-ir/test-plan.md @@ -66,6 +66,8 @@ Out of scope: | fixtures/target-ir/canonical/echo-effectful.target-ir.sha256 | Reviewed Echo Target IR digest golden. | `cargo xtask target-ir-goldens --check` compares the checked-in review digest to executable regeneration. | | fixtures/target-ir/canonical/gitwarp-append.target-ir.cbor | Reviewed git-warp Target IR canonical byte golden. | `cargo xtask target-ir-goldens --check` compares the checked-in bytes to executable regeneration. | | fixtures/target-ir/canonical/gitwarp-append.target-ir.sha256 | Reviewed git-warp Target IR digest golden. | `cargo xtask target-ir-goldens --check` compares the checked-in review digest to executable regeneration. | +| fixtures/target-ir/canonical/workspace-snapshot.target-ir.cbor | Reviewed typed external-request Target IR canonical byte golden. | `cargo xtask target-ir-goldens --check` compares the checked-in bytes to executable regeneration. | +| fixtures/target-ir/canonical/workspace-snapshot.target-ir.sha256 | Reviewed typed external-request Target IR digest golden. | `cargo xtask target-ir-goldens --check` compares the checked-in review digest to executable regeneration. | | docs/abi/edict-target-ir.cddl | Edict-owned canonical Target IR artifact schema. | The self-contained provider contract pack compiles the root and validates both reviewed Target IR artifacts. | ## Test Cases @@ -92,7 +94,7 @@ Out of scope: | TIR-TP-018 | implemented | Determinism guard | TIR-REQ-008 | Equivalent Target IR construction order for maps, obstruction failures, and input constraints does not change canonical bytes or digest, while semantic list order for steps remains preserved. | target_ir_artifact_canonicalization_ignores_equivalent_construction_order, target_ir_step_order_changes_digest | crates/edict-syntax/tests/target_ir.rs | Prevents Rust construction order from becoming a cryptographic contract. | | TIR-TP-019 | implemented | Mutation sensitivity | TIR-REQ-008 | Target profile digest, source Core coordinate, intent name, effect coordinate, selected intrinsic, input expression, obstruction failure/arm, input constraint, budget, and result mutations each move the Target IR digest. | target_ir_digest_moves_for_artifact_semantic_mutations, target_ir_obstruction_arm_value_mutation_moves_digest | crates/edict-syntax/tests/target_ir.rs | Freezes the reviewed value shape without re-litigating lowering semantics. | | TIR-TP-020 | implemented | Boundary guard | TIR-REQ-008 | Canonical Target IR encoding rejects missing target-profile digests and non-lowercase digest review strings before hashing. | target_ir_encoder_rejects_unlocked_or_uppercase_target_profile_digest | crates/edict-syntax/tests/target_ir.rs | Target IR artifact references use the strict bundle-artifact digest policy. | -| TIR-TP-021 | implemented | Golden path | TIR-REQ-009 | `xtask target-ir-goldens --check` fails on drift and `--write` regenerates Echo and git-warp byte/digest golden fixtures from executable assembly. | target_ir_goldens_match_executable_encoder | xtask/src/goldens.rs, xtask/src/main.rs, xtask/src/tests.rs, fixtures/target-ir/canonical/echo-effectful.target-ir.cbor, fixtures/target-ir/canonical/echo-effectful.target-ir.sha256, fixtures/target-ir/canonical/gitwarp-append.target-ir.cbor, fixtures/target-ir/canonical/gitwarp-append.target-ir.sha256 | Golden scope is Target IR artifact bytes and digest review strings only. | +| TIR-TP-021 | implemented | Golden path | TIR-REQ-009, TIR-REQ-017 | `xtask target-ir-goldens --check` fails on drift and `--write` regenerates Echo, git-warp, and typed workspace-request byte/digest golden fixtures from executable assembly. | target_ir_goldens_match_executable_encoder | xtask/src/goldens.rs, xtask/src/main.rs, xtask/src/tests.rs, fixtures/target-ir/canonical/echo-effectful.target-ir.cbor, fixtures/target-ir/canonical/echo-effectful.target-ir.sha256, fixtures/target-ir/canonical/gitwarp-append.target-ir.cbor, fixtures/target-ir/canonical/gitwarp-append.target-ir.sha256, fixtures/target-ir/canonical/workspace-snapshot.target-ir.cbor, fixtures/target-ir/canonical/workspace-snapshot.target-ir.sha256 | Golden scope is Target IR artifact bytes and digest review strings only. | | TIR-TP-022 | implemented | Integration | TIR-REQ-010 | Bundle assembly from a real `TargetIrArtifact` computes `targetIrDigest`, writes that same digest into the manifest, and changes bundle digests when the Target IR artifact changes. | assembled_bundle_from_real_target_ir_computes_target_ir_digest | crates/edict-syntax/tests/contract_bundle.rs | Keeps generated Target IR and manifest `target_ir.digest` as one source of truth. | | TIR-TP-023 | implemented | Golden path | TIR-REQ-011 | Echo lowering emits explicit Target IR requirements for terminal and preserved-obstruction Core `require` arms while leaving effect steps unchanged. | echo_target_ir_contains_obstruction_requirement_payload, terminal_and_preserved_requirements_are_target_ir_distinct | crates/edict-syntax/tests/target_ir.rs | Target IR emission remains distinct from Echo acceptance or runtime execution. | | TIR-TP-024 | implemented | Mutation sensitivity | TIR-REQ-012 | Requirement reason kind, reason payload value, predicate, and terminal-vs-preserved disposition mutations move the Target IR digest. | target_ir_requirement_mutations_move_digest | crates/edict-syntax/tests/target_ir.rs | Prevents obstruction semantics from collapsing in canonical Target IR bytes. | @@ -100,13 +102,13 @@ Out of scope: | TIR-TP-026 | implemented | Boundary guard | TIR-REQ-011, TIR-REQ-012 | A Target IR requirement after an emitted target step rejects with a stable target-feature failure kind and no artifact, with a more specific detail when it reads an earlier step output. | requirement_after_target_step_rejects_with_stable_feature_kind, requirement_that_reads_step_output_rejects_with_stable_feature_kind | crates/edict-syntax/tests/target_ir.rs | Intent-level requirements are pre-step guards until the artifact model owns ordered or step-attached guards. | | TIR-TP-027 | implemented | Integration | TIR-REQ-013 | Built-in Echo and git-warp lowerer adapters return the same artifacts, canonical bytes, and digests as direct lowering for identical Core and facts. | builtin_echo_lowerer_matches_direct_target_ir, builtin_gitwarp_lowerer_matches_direct_target_ir | crates/edict-syntax/tests/provider_lowering.rs | No Target IR golden moves when the invocation path changes. | | TIR-TP-028 | implemented | Boundary guard | TIR-REQ-013 | Matched-profile target and target-profile-digest failures pass through unchanged, while cross-profile lowerer selection rejects with a stable compatibility failure before invocation. | builtin_lowerers_preserve_structured_lowering_failures, builtin_lowerers_preserve_target_profile_digest_failures, builtin_lowerers_reject_mismatched_target_profiles | crates/edict-syntax/tests/provider_lowering.rs | Lowerer selection compatibility remains distinct from target semantic refusal; coordinate matching does not bypass target artifact validation. | -| TIR-TP-029 | implemented | Schema fidelity | TIR-REQ-014, TIR-REQ-017 | Canonical Echo and git-warp Target IR bytes plus encoder output containing both requirement dispositions or an exact capability-closed external request satisfy `target-ir-artifact`; null, missing envelope fields, malformed nested Target IR values, and an external request injected into the closure-free legacy artifact reject through the same compiled root. | target_ir_root_matches_reference_encoder, every_published_root_validates_reference_and_rejects_mutation, target_ir_goldens_match_executable_encoder | docs/abi/edict-target-ir.cddl, fixtures/target-ir/canonical/echo-effectful.target-ir.cbor, fixtures/target-ir/canonical/gitwarp-append.target-ir.cbor, crates/edict-provider-schema/tests/provider_contract_pack.rs, xtask/src/tests.rs | The schema is derived from the canonical encoder contract, including the rule that external requests require exact semantic authority rather than the legacy compatibility shape. | +| TIR-TP-029 | implemented | Schema fidelity | TIR-REQ-014, TIR-REQ-017 | Canonical Echo, git-warp, and typed workspace-request Target IR bytes plus encoder output containing both requirement dispositions satisfy `target-ir-artifact`; null, missing envelope fields, malformed nested Target IR values, and an external request injected into the closure-free legacy artifact reject through the same compiled root. | target_ir_root_matches_reference_encoder, every_published_root_validates_reference_and_rejects_mutation, target_ir_goldens_match_executable_encoder | docs/abi/edict-target-ir.cddl, fixtures/target-ir/canonical/echo-effectful.target-ir.cbor, fixtures/target-ir/canonical/gitwarp-append.target-ir.cbor, fixtures/target-ir/canonical/workspace-snapshot.target-ir.cbor, crates/edict-provider-schema/tests/provider_contract_pack.rs, xtask/src/tests.rs | The schema is derived from the canonical encoder contract, including the rule that external requests require exact semantic authority rather than the legacy compatibility shape. | | TIR-TP-030 | implemented | Schema fidelity | TIR-REQ-014 | A target-profile coordinate containing only LF is accepted by both the canonical Target IR encoder and the published `target-ir-artifact` root. | target_ir_root_accepts_encoder_valid_line_feed_coordinate | crates/edict-provider-schema/tests/provider_contract_pack.rs | The CDDL nonempty constraint cannot use a regex whose wildcard excludes line terminators accepted by the encoder. | | TIR-TP-031 | implemented | Integration | TIR-REQ-001, TIR-REQ-015 | Compiling and lowering the operation prerequisite fixture preserves the exact Core basis expression and binds the computed Core digest plus exact lawpack coordinate/digest in Target IR; an empty Core coordinate, unidentifiable or noncanonical resource, coordinate conflict, internally contradictory closure, or missing closure on a basis-bearing artifact refuses without an artifact identity. The canonical encoder and published CDDL root reject an empty source Core coordinate, and the CDDL root independently rejects the externally constructed missing-closure shape. | operation_prerequisite_fixture_preserves_fixed_width_basis_and_lawpack_closure, target_lowering_refuses_an_unidentifiable_semantic_closure, legacy_target_ir_encoder_rejects_an_empty_source_core_coordinate, invalid_or_conflicting_lawpack_resources_refuse_before_target_artifact, semantic_closure_cannot_substitute_a_different_source_core_coordinate, explicit_basis_target_ir_cannot_drop_its_semantic_closure, target_ir_root_matches_reference_encoder | fixtures/lang/operations/explicit-basis-u64.edict | The target artifact binds semantic inputs but remains distinct from package admission or runtime execution. | | TIR-TP-032 | implemented | Mutation sensitivity | TIR-REQ-008, TIR-REQ-015 | Changing the basis expression, source Core meaning, lawpack coordinate, or lawpack digest moves the Target IR digest, while equivalent lawpack construction order does not. | explicit_basis_and_semantic_input_mutations_move_target_identity, semantic_closure_lawpack_set_is_order_invariant | fixtures/lang/operations/explicit-basis-u64.edict | Prevents Target IR identity from floating over operation semantic inputs. | | TIR-TP-033 | implemented | Compatibility | TIR-REQ-009, TIR-REQ-015 | Existing reviewed Echo and git-warp fixtures with `basis none` and no lawpack import retain byte-identical Target IR and digest goldens. | target_ir_goldens_match_executable_encoder | fixtures/target-ir/canonical/echo-effectful.target-ir.cbor, fixtures/target-ir/canonical/gitwarp-append.target-ir.cbor | Provider-v1 compatibility artifacts remain stable alongside the new operation prerequisites. | | TIR-TP-034 | implemented | Integration | TIR-REQ-001, TIR-REQ-002, TIR-REQ-015, TIR-REQ-016 | Hello Echo lowers from exact source and lawpack resources through the selected direct adapter into `echo.span-ir/v1`, preserving the explicit basis, create effect, create-if-absent intrinsic, typed failure arm, and semantic closure. | hello_echo_source_compiles_to_echo_target_ir_from_exact_lawpack_adapter | fixtures/lawpack/hello-echo/README.md | Target IR remains distinct from the later Echo executable package. | -| TIR-TP-035 | implemented | External request | TIR-REQ-001, TIR-REQ-006, TIR-REQ-008, TIR-REQ-017 | Workspace observation requests lower into `externalActionRequests` with exact capability closure and zero callable target steps or intrinsics; removing the operation from the closure makes canonical encoding fail closed. | workspace_observation_request_compiles_as_non_callable_data, request_operation_must_remain_in_core_and_target_capability_closure | crates/edict-syntax/tests/external_action_requests.rs | Echo owns later request admission, execution, and settlement. | +| TIR-TP-035 | implemented | External request | TIR-REQ-001, TIR-REQ-008, TIR-REQ-017 | Workspace observation requests lower into `externalActionRequests` with exact capability closure and zero callable target steps or intrinsics; removing the operation from the closure makes canonical encoding fail closed. | workspace_observation_request_compiles_as_non_callable_data, request_operation_must_remain_in_core_and_target_capability_closure | crates/edict-syntax/tests/external_action_requests.rs | Echo owns later request admission, execution, and settlement. | ## Determinism Obligations diff --git a/editors/vscode/syntaxes/edict.tmLanguage.json b/editors/vscode/syntaxes/edict.tmLanguage.json index 41c0db4..5847d8e 100644 --- a/editors/vscode/syntaxes/edict.tmLanguage.json +++ b/editors/vscode/syntaxes/edict.tmLanguage.json @@ -66,7 +66,7 @@ }, "keywords": { "name": "keyword.control.edict", - "match": "\\b(?:package|use|type|enum|variant|intent|returns|profile|implements|basis|footprint|budget|where|let|return|require|guarantee|assert|if|then|else|for|in|bounded|yield|match|shape|lawpack|target|core|capability|as|digest|fn|const|true|false)\\b" + "match": "\\b(?:package|use|type|enum|variant|intent|returns|profile|implements|basis|footprint|budget|where|let|return|request|require|guarantee|assert|if|then|else|for|in|bounded|yield|match|shape|lawpack|target|core|capability|as|digest|fn|const|true|false)\\b" }, "types": { "name": "entity.name.type.edict", diff --git a/fixtures/core/canonical/README.md b/fixtures/core/canonical/README.md index a447f84..d9a0ca7 100644 --- a/fixtures/core/canonical/README.md +++ b/fixtures/core/canonical/README.md @@ -3,7 +3,7 @@ This directory contains reviewed Core artifact fixtures generated from the executable compiler and canonical encoder. -## Current Fixture +## Current Fixtures `bounded-hello` is generated from [`fixtures/lang/bounds/bounded-hello.edict`](../../lang/bounds/bounded-hello.edict) @@ -20,6 +20,12 @@ Artifacts: - `bounded-hello.core.sha256`: review rendering of the `edict.core.module/v1` digest. +`workspace-snapshot` is generated from +[`fixtures/lang/external-actions/workspace-snapshot.edict`](../../lang/external-actions/workspace-snapshot.edict) +using the deterministic `workspace.read` profile and `workspace.tiny` budget +facts. Its `.core.cbor` and `.core.sha256` files bind every external-request +field and exact capability closure into reviewed Core identity. + The digest preimage is the canonical CBOR encoding of: ```text diff --git a/fixtures/core/canonical/workspace-snapshot.core.cbor b/fixtures/core/canonical/workspace-snapshot.core.cbor new file mode 100644 index 0000000000000000000000000000000000000000..62e941363e865e25d86724e1a39c89c4af55b564 GIT binary patch literal 2672 zcmd5;%}yIJ5RMQJ0jLEIoGP(R0^~#yKkA`URVpY|Z3H2 zMGQDKU!Y42Gg%Wdk|+))xRCnHC=$YFneWxRt)Lnv@H@bSQo&UF{Ek?W0hfek?I|*j z_najp+p`uDWleap(bo9;7_8-xfUx@mi=<9EoBA=^VTjO$RbK$`!22Li?J>ySi!^58 zVT<#PO0aC<9;50rdjf*^F7Dq_*a27;1=yT}HEKcWnubAjsK~ zuUW`Ha}_ct?Ad28REz~6SQK(Wm}Md^I~sLU8iO5HB|CeX)$BFHGJ8TbB;qcb64uV? zCicB9l+y;vLK_saTPI$3fzE~ZvftYFle~@N_NueBwb9wxDHRm2`B+Wn74iX`Zv}ZOqMIm^<%v z^;Knk^KPi}eWeg9HH9dFHP!ezVV!F{&_swv=9~GJT=;@7*W@XXP?P4@gk?&Sdo*Fj zpm2oo4R%(@G|1b7CeX%eXX5jrKI>r(ImdgUom>?3HW+9dy5j+c88 bO1us-fr+x`e>|(p, + scope: Bytes, + basis: Bytes, + maxSettlementBytes: U64, + maxAttempts: U32, +}; + +intent observe(input: ObserveInput) + returns ExternalActionRequest> + profile workspace.read + basis input.basis + budget <= workspace.tiny +{ + request pending: ExternalActionRequest> = + snapshot(input.payload) + input schema workspace.snapshot.input@1 + digest "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" + settlement schema workspace.snapshot.settlement@1 + digest "sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" + authority input.scope + basis input.basis + budget + maxSettlementBytes input.maxSettlementBytes + maxAttempts input.maxAttempts + reconcile workspace.snapshot.reconcile@1 + digest "sha256:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"; + return pending; +} diff --git a/fixtures/target-ir/canonical/workspace-snapshot.target-ir.cbor b/fixtures/target-ir/canonical/workspace-snapshot.target-ir.cbor new file mode 100644 index 0000000000000000000000000000000000000000..47198d0429f6159b21d68e6a7d35a41760fd0cd3 GIT binary patch literal 2152 zcmc&#F>ljA6pkuZ1{S1NrF7^7s!3YfsZ>n^LnYKA!hl%N+4pQ;bH2OWofBtk;WxmF zXi!x#@CRUKDmzOhCVm4863^!(u7uSr(B9x!7vKB7_r3SNJEk2bsIo*yV0Uy=TNaYQ zhEyhm2?;?gJ)?+NX8K;Q5iwywSo1!Tz5(5X6Yz;)W=Qb|jsgwg&}S4dnpdZGpdh;g z8A%ie+!^&qpL1>wt>9+Se;&`t3?X0@?BuwXV4l08^oTU%Y<+$@Av&SUQt$!d z+dyh~)FV7~r?yjywHU_8r=6!aJ5v+*zL#1Aa#ZJPGqqY5_nJIsXMv8kuhnY3ah^?= z=F=iebNC7C(j5su&fTkDarvBfm)|^*A_Fc7#qy(gn)C_7@-xxT8@w=h9f{(nx0+Z95TA6BSwI%Rb=pgRVPGiQE}N#6;+ zP=Pc45-MQMrYuvEY|?}ogZ382q`}@n*AH4; znpDFu=A@6eU&kEm8=2|=TIl*N*IO4}k$*Ms?C%e|582r_cH{c#@z2F~Z{IBZ@as3{ oPhNlddiVQ_Upyd+_$a7lU=AI^znu Result<(), String> { for case in CORE_GOLDEN_CASES { @@ -181,6 +188,7 @@ fn check_or_write_core_golden( fn core_golden_context() -> CompilerContext { CompilerContext::new() .with_operation_profile("hello.readOnly", "continuum.profile.read-only/v1") + .with_operation_profile("workspace.read", "continuum.profile.read-only/v1") .with_budget( "hello.tinyBudget", CoreBudget { @@ -189,6 +197,14 @@ fn core_golden_context() -> CompilerContext { max_output_bytes: 1024, }, ) + .with_budget( + "workspace.tiny", + CoreBudget { + max_steps: 512, + max_allocated_bytes: 256 * 1024, + max_output_bytes: 128 * 1024, + }, + ) } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -201,6 +217,7 @@ pub(crate) enum TargetIrGoldenMode { enum TargetIrGoldenKind { Echo, Gitwarp, + ExternalRequest, } #[derive(Debug)] @@ -221,6 +238,11 @@ const TARGET_IR_GOLDEN_CASES: &[TargetIrGoldenCase] = &[ bytes: "fixtures/target-ir/canonical/gitwarp-append.target-ir.cbor", digest: "fixtures/target-ir/canonical/gitwarp-append.target-ir.sha256", }, + TargetIrGoldenCase { + kind: TargetIrGoldenKind::ExternalRequest, + bytes: "fixtures/target-ir/canonical/workspace-snapshot.target-ir.cbor", + digest: "fixtures/target-ir/canonical/workspace-snapshot.target-ir.sha256", + }, ]; const TARGET_IR_ECHO_SOURCE: &str = "package a.b@1;\n\ @@ -250,6 +272,9 @@ const TARGET_IR_GITWARP_SOURCE: &str = "package a.git@1;\n\ return { id: receipt.id };\n\ }"; +const TARGET_IR_EXTERNAL_REQUEST_SOURCE: &str = + include_str!("../../fixtures/lang/external-actions/workspace-snapshot.edict"); + pub(crate) fn target_ir_goldens(root: &Path, mode: TargetIrGoldenMode) -> Result<(), String> { for case in TARGET_IR_GOLDEN_CASES { check_or_write_target_ir_golden(root, case, mode)?; @@ -302,6 +327,11 @@ fn target_ir_golden_artifact(case: &TargetIrGoldenCase) -> Result ( + TARGET_IR_EXTERNAL_REQUEST_SOURCE, + target_ir_external_request_context(), + target_ir_external_request_facts(), + ), }; let module = parse_module(source) .map_err(|err| format!("parse {:?} Target IR golden source: {err}", case.kind))?; @@ -346,6 +376,19 @@ fn target_ir_gitwarp_context() -> CompilerContext { ) } +fn target_ir_external_request_context() -> CompilerContext { + CompilerContext::new() + .with_operation_profile("workspace.read", "continuum.profile.read-only/v1") + .with_budget( + "workspace.tiny", + CoreBudget { + max_steps: 512, + max_allocated_bytes: 256 * 1024, + max_output_bytes: 128 * 1024, + }, + ) +} + fn target_ir_echo_facts() -> TargetIrLoweringFacts { TargetIrLoweringFacts { target_profile: ResourceRef { @@ -380,6 +423,19 @@ fn target_ir_gitwarp_facts() -> TargetIrLoweringFacts { } } +fn target_ir_external_request_facts() -> TargetIrLoweringFacts { + TargetIrLoweringFacts { + target_profile: ResourceRef { + coordinate: ECHO_DPO_TARGET_PROFILE.to_owned(), + digest: Some(digest_text('3')), + }, + target_ir_domain: ECHO_SPAN_IR_DOMAIN.to_owned(), + operation_profiles: vec!["continuum.profile.read-only/v1".to_owned()], + obstruction_coordinates: Vec::new(), + effect_lowerings: Vec::new(), + } +} + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub(crate) enum BundleGoldenMode { Check, diff --git a/xtask/src/tests.rs b/xtask/src/tests.rs index 4a5b1c5..f7efca2 100644 --- a/xtask/src/tests.rs +++ b/xtask/src/tests.rs @@ -22,9 +22,9 @@ use wit_parser::{ use super::contract_check::{check_links, check_topic, contract_check}; use super::goldens::{ - authority_facts_goldens, bundle_goldens, cli_binary_path_from_cargo_metadata, + authority_facts_goldens, bundle_goldens, cli_binary_path_from_cargo_metadata, core_goldens, target_ir_goldens, target_profile_resource_goldens, AuthorityFactsGoldenMode, BundleGoldenMode, - TargetIrGoldenMode, TargetProfileResourceGoldenMode, + CoreGoldenMode, TargetIrGoldenMode, TargetProfileResourceGoldenMode, }; use super::lawpack_goldens::{lawpack_goldens, LawpackGoldenMode}; use super::provider_contract_pack::{ @@ -57,6 +57,12 @@ fn bundle_digest_goldens_match_assembly() { .expect("bundle digest goldens match assembly output"); } +#[test] +fn core_goldens_match_executable_encoder() { + core_goldens(&repo_root().expect("repo root"), CoreGoldenMode::Check) + .expect("Core goldens match executable encoder output"); +} + #[test] fn target_ir_goldens_match_executable_encoder() { target_ir_goldens(&repo_root().expect("repo root"), TargetIrGoldenMode::Check) @@ -1031,7 +1037,7 @@ fn textmate_grammar_covers_public_highlight_roles() { let type_regex = textmate_repository_match(&grammar, "types"); let identifier_regex = textmate_repository_match(&grammar, "identifiers"); let source = "package use type enum variant intent returns profile implements basis \ - footprint budget where let return require guarantee assert if then else for in \ + footprint budget where let return request require guarantee assert if then else for in \ bounded yield match shape lawpack target core capability as digest fn const true \ false HelloInput input = == != < <= > >= + - * / % ! && || => -> :: ... ; : , . @ \ ( ) { } [ ] \"text\" 123";