Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ versions still track specification maturity rather than a released product.

### Changed

- Added explicitly selected `echo.span-ir/v2` ordered Target IR for interleaved
effects, pure bindings, and dependent guards. The canonical artifact binds an
exact instruction order and rejects malformed dependency references. Legacy
v1 artifacts retain their byte identities and pre-step restrictions. The CLI
exposes the new order and its binding payloads. This compiler capability does
not add Echo execution support or change existing consumer producer pins.

- Avoided exponential repeated byte-length validation through nested conditional
predicates while retaining inner local-authority refusal.

Expand Down
20 changes: 20 additions & 0 deletions crates/edict-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1378,6 +1378,26 @@ fn target_ir_intent_review(intent: &TargetIrIntent) -> Value {
"basis",
intent.basis.as_ref().map(core_expr_review),
);
insert_optional_review_field(
&mut review,
"executionOrder",
intent.execution_order.as_ref().map(|order| json!(order)),
);
if !intent.pure_bindings.is_empty() {
insert_optional_review_field(
&mut review,
"pureBindings",
Some(json!(intent
.pure_bindings
.iter()
.map(|binding| json!({
"id": binding.id,
"binding": local_ref_review(&binding.binding),
"value": core_expr_review(&binding.value),
}))
.collect::<Vec<_>>())),
);
}
review
}

Expand Down
33 changes: 33 additions & 0 deletions crates/edict-cli/tests/jsonl_cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,39 @@ 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_ordered_effect_binding_and_guard_without_hoisting() {
let source = ECHO_SOURCE.replace(
" return { id: input.id };",
" let observed: String<max=16> = receipt.id;\n require observed != \"\" else domain.WriteRejected;\n return { id: observed };",
);
let mut settings = projection_settings(["core", "targetIr", "digests"]);
settings["target"]["irDomain"] = json!("echo.span-ir/v2");
let output = run_edict(&jsonl([
settings,
json!({
"schema": "edict.compiler.input/v1", "type": "compilerInput",
"kind": "source", "name": "ordered.edict", "source": source,
}),
]));
let stdout = assert_successful_projection_output(&output);
let target = record_of_type(&stdout, "targetIr");
assert_eq!(target["state"], "available");
assert_eq!(target["domain"], "echo.span-ir/v2");
let intent = &target["review"]["intents"]["replaceThing"];
assert_eq!(
intent["executionOrder"],
json!([
"replaceThing.step.0",
"replaceThing.binding.0",
"replaceThing.require.0",
])
);
assert_eq!(intent["pureBindings"][0]["id"], "replaceThing.binding.0");
assert_eq!(intent["steps"][0]["id"], "replaceThing.step.0");
assert_eq!(intent["requirements"][0]["id"], "replaceThing.require.0");
}

#[test]
fn project_exposes_external_requests_as_non_callable_review_data() {
let output = run_edict(&jsonl([
Expand Down
69 changes: 68 additions & 1 deletion crates/edict-provider-schema/src/recursive_dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -541,7 +541,53 @@ fn required_text_literals<'a>(
node: &'a Node,
rules: &'a RulesByName,
) -> Option<Vec<(&'a str, &'a str)>> {
let Node::Map(map) = terminal_node(node, rules)? else {
required_text_literals_cached(node, rules, 0, &mut BTreeMap::new())
}

type DiscriminatorLiterals<'a> = Vec<(&'a str, &'a str)>;
type DiscriminatorCache<'a> = BTreeMap<(&'a str, usize), Option<DiscriminatorLiterals<'a>>>;

fn required_text_literals_cached<'a>(
node: &'a Node,
rules: &'a RulesByName,
depth: usize,
cache: &mut DiscriminatorCache<'a>,
) -> Option<DiscriminatorLiterals<'a>> {
if depth > PROVIDER_SCHEMA_VALIDATION_MAX_NESTING_DEPTH {
return None;
}
if let Node::Rule(rule) = node {
if !rule.generic_args.is_empty() {
return None;
}
let key = (rule.name.as_str(), depth);
if let Some(literals) = cache.get(&key) {
return literals.clone();
}
let literals = required_text_literals_at_depth(node, rules, depth, cache);
cache.insert(key, literals.clone());
return literals;
}
required_text_literals_at_depth(node, rules, depth, cache)
}

fn required_text_literals_at_depth<'a>(
node: &'a Node,
rules: &'a RulesByName,
depth: usize,
cache: &mut DiscriminatorCache<'a>,
) -> Option<DiscriminatorLiterals<'a>> {
let node = terminal_node(node, rules)?;
if let Node::Choice(choice) = node {
let (first, rest) = choice.options.split_first()?;
let mut shared = required_text_literals_cached(first, rules, depth + 1, cache)?;
for option in rest {
let required = required_text_literals_cached(option, rules, depth + 1, cache)?;
shared.retain(|literal| required.contains(literal));
}
return Some(shared);
}
let Node::Map(map) = node else {
return Some(Vec::new());
};
let mut literals = Vec::new();
Expand Down Expand Up @@ -1101,6 +1147,27 @@ mod tests {
use super::*;
use cddl_cat::flatten::flatten_from_str;

#[test]
fn shared_discriminator_rules_do_not_expand_exponentially() {
use std::fmt::Write;

const LAYERS: usize = 40;
let mut schema = format!("layer{LAYERS} = {{kind: \"leaf\"}}\n");
for index in 0..LAYERS {
let next = index + 1;
writeln!(schema, "layer{index} = layer{next} / layer{next}").expect("write schema");
}
let rules = flatten_from_str(&schema).expect("shared schema compiles");
let mut cache = BTreeMap::new();
let required = required_text_literals_cached(&rules["layer0"].node, &rules, 0, &mut cache);
assert_eq!(required, Some(vec![("kind", "leaf")]));
assert_eq!(
cache.len(),
LAYERS,
"each shared rule/depth pair is computed once"
);
}

const TAGGED_RECURSION: &str = r#"
root = leaf / left / right
leaf = { kind: "leaf", value: uint }
Expand Down
39 changes: 39 additions & 0 deletions crates/edict-provider-schema/tests/provider_contract_pack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,44 @@ fn target_ir_root_matches_reference_encoder() {
);
}

#[test]
fn ordered_target_ir_schema_requires_explicit_execution_order() {
let pack = assemble(canonical_target_profile_contract_resources());
let mut artifact = representative_target_ir("echo.dpo@1");
artifact.domain = "echo.span-ir/v2".to_owned();
artifact
.intents
.get_mut("apply")
.expect("intent")
.execution_order = Some(vec![
"apply.require.0".to_owned(),
"apply.require.1".to_owned(),
]);
let value = encode_target_ir_value(&artifact);
pack.validate_domain(TARGET_IR_ARTIFACT_DIGEST_DOMAIN, &value)
.expect("v2 schema agrees with encoder");
let mut missing = value.clone();
let intents = map_value_mut(&mut missing, "intents");
remove_map_field(map_value_mut(intents, "apply"), "executionOrder");
assert_eq!(
pack.validate_domain(TARGET_IR_ARTIFACT_DIGEST_DOMAIN, &missing),
Err(ProviderArtifactSchemaValidationErrorKind::SchemaMismatch)
);
let mut downgrade = value;
*map_value_mut(&mut downgrade, "domain") = CanonicalValue::Text("echo.span-ir/v1".to_owned());
assert_eq!(
pack.validate_domain(TARGET_IR_ARTIFACT_DIGEST_DOMAIN, &downgrade),
Err(ProviderArtifactSchemaValidationErrorKind::SchemaMismatch)
);

let mut missing_closure = encode_target_ir_value(&artifact);
remove_map_field(&mut missing_closure, "semanticClosure");
assert_eq!(
pack.validate_domain(TARGET_IR_ARTIFACT_DIGEST_DOMAIN, &missing_closure),
Err(ProviderArtifactSchemaValidationErrorKind::SchemaMismatch)
);
}

#[test]
fn target_ir_root_accepts_only_closed_nonempty_pure_bindings() {
let pack = assemble(canonical_target_profile_contract_resources());
Expand Down Expand Up @@ -923,6 +961,7 @@ fn representative_target_ir(coordinate: &str) -> TargetIrArtifact {
intents: BTreeMap::from([(
"apply".to_owned(),
TargetIrIntent {
execution_order: None,
operation_profile: "example.operation/v1".to_owned(),
basis: Some(CoreExpr::Const(CoreValue::String(
"example.basis@1".to_owned(),
Expand Down
39 changes: 39 additions & 0 deletions crates/edict-provider-schema/tests/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -665,6 +665,45 @@ fn checked_target_ir_root_constructs_through_required_key_dispatch() {
.expect("the checked Target IR compatibility union must construct");
}

#[test]
fn nested_tagged_families_keep_required_key_dispatch_bounded_and_exact() {
let schema = br#"
generated-artifact = phased / ordered
phased = phased-closed / phased-legacy
ordered = ordered-closed / ordered-legacy
phased-closed = {kind: "phased", closure: uint, ? child: generated-artifact}
phased-legacy = {kind: "phased", value: uint, ? child: generated-artifact}
ordered-closed = {kind: "ordered", closure: uint, order: [* uint], ? child: generated-artifact}
ordered-legacy = {kind: "ordered", value: uint, order: [* uint], ? child: generated-artifact}
"#;
let registry = registry_with_generated_schema(schema).expect("nested families compile");
let phased = map(&[
("kind", CanonicalValue::Text("phased".to_owned())),
("value", CanonicalValue::Integer(1)),
]);
let ordered = map(&[
("kind", CanonicalValue::Text("ordered".to_owned())),
("closure", CanonicalValue::Integer(1)),
("order", CanonicalValue::Array(Vec::new())),
("child", phased.clone()),
]);
for value in [phased, ordered] {
registry
.validate_canonical_value("runtime.generated-artifact/v1", &value)
.expect("valid selected family");
}
for kind in ["ordered", "unknown"] {
let missing_order = map(&[
("kind", CanonicalValue::Text(kind.to_owned())),
("value", CanonicalValue::Integer(1)),
]);
assert_eq!(
registry.validate_canonical_value("runtime.generated-artifact/v1", &missing_order),
Err(ProviderArtifactSchemaValidationErrorKind::SchemaMismatch)
);
}
}

#[test]
fn recursive_map_discriminator_dispatch_rejects_invalid_discriminators() {
let registry = registry_with_generated_schema(DISCRIMINATED_RECURSIVE_SCHEMA)
Expand Down
30 changes: 28 additions & 2 deletions crates/edict-syntax/src/canonical.rs
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,12 @@ pub fn decode_canonical_cbor(bytes: &[u8]) -> Result<CanonicalValue, CanonicalEr
}

fn target_ir_artifact_value(artifact: &TargetIrArtifact) -> Result<CanonicalValue, CanonicalError> {
if !crate::target_ir::execution_order::artifact_has_valid_execution_order(artifact) {
return Err(CanonicalError::new(
CanonicalErrorKind::UnsupportedValue,
"Target IR execution order is invalid for its selected domain",
));
}
if artifact.source_core_coordinate.is_empty() {
return Err(CanonicalError::new(
CanonicalErrorKind::UnsupportedValue,
Expand Down Expand Up @@ -542,7 +548,16 @@ fn target_ir_artifact_value(artifact: &TargetIrArtifact) -> Result<CanonicalValu
}
}
let mut entries = vec![
("kind", text("targetIrArtifact")),
(
"kind",
text(
if artifact.domain == crate::target_ir::ECHO_ORDERED_SPAN_IR_DOMAIN {
"orderedTargetIrArtifact"
} else {
"targetIrArtifact"
},
),
),
("domain", text(&artifact.domain)),
(
"targetProfile",
Expand Down Expand Up @@ -649,7 +664,7 @@ fn target_ir_resource_ref_value(resource: &ResourceRef) -> Result<CanonicalValue
]))
}

fn target_ir_intent_value(intent: &TargetIrIntent) -> Result<CanonicalValue, CanonicalError> {
fn validate_target_ir_intent_identities(intent: &TargetIrIntent) -> Result<(), CanonicalError> {
let mut binding_ids = BTreeSet::new();
for binding in &intent.pure_bindings {
if binding.id.is_empty() || !binding_ids.insert(binding.id.as_str()) {
Expand Down Expand Up @@ -702,6 +717,11 @@ fn target_ir_intent_value(intent: &TargetIrIntent) -> Result<CanonicalValue, Can
));
}
}
Ok(())
}

fn target_ir_intent_value(intent: &TargetIrIntent) -> Result<CanonicalValue, CanonicalError> {
validate_target_ir_intent_identities(intent)?;
let mut entries = vec![
("operationProfile", text(&intent.operation_profile)),
(
Expand All @@ -722,6 +742,12 @@ fn target_ir_intent_value(intent: &TargetIrIntent) -> Result<CanonicalValue, Can
),
("result", core_expr_value(&intent.result)?),
];
if let Some(order) = &intent.execution_order {
entries.push((
"executionOrder",
CanonicalValue::Array(order.iter().map(|id| text(id)).collect()),
));
}
if let Some(basis) = &intent.basis {
entries.push(("basis", core_expr_value(basis)?));
}
Expand Down
4 changes: 2 additions & 2 deletions crates/edict-syntax/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -230,8 +230,8 @@ pub use target_ir::{
TargetIrExternalActionRequest, TargetIrIntent, TargetIrLoweringFacts, TargetIrPureBinding,
TargetIrRequireFailure, TargetIrRequirement, TargetIrSemanticClosure, TargetIrStep,
TargetLoweringFailure, TargetLoweringFailureKind, TargetLoweringReport, TargetLoweringStatus,
TargetPureFunctionFact, ECHO_DPO_TARGET_PROFILE, ECHO_SPAN_IR_DOMAIN,
GITWARP_COMMIT_REDUCER_IR_DOMAIN, GITWARP_REF_CRDT_TARGET_PROFILE,
TargetPureFunctionFact, ECHO_DPO_TARGET_PROFILE, ECHO_ORDERED_SPAN_IR_DOMAIN,
ECHO_SPAN_IR_DOMAIN, GITWARP_COMMIT_REDUCER_IR_DOMAIN, GITWARP_REF_CRDT_TARGET_PROFILE,
};
pub use target_profile::{
validate_target_profile_manifest, TargetProfileConformanceFailure,
Expand Down
Loading
Loading