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
4 changes: 1 addition & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,9 @@ jobs:
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/

key: cargo-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }}-${{ github.sha }}
key: cargo-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
cargo-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }}-
cargo-${{ runner.os }}-

- name: Run CI Pipeline
Expand Down
5 changes: 5 additions & 0 deletions core/daglang/daglang-cli/src/compile/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ pub fn render_expand(dag: &Dag<LoweredOp>) -> String {
}) => {
format!("callable::{kind:?} {module}.{name}")
}
gunbc_ir::node::NodeBody::Opaque(LoweredOp::Primitive {
module, name, kind, ..
}) => {
format!("primitive::{kind:?} {module}.{name}")
}
gunbc_ir::node::NodeBody::Opaque(LoweredOp::Collection {
module,
callable,
Expand Down
21 changes: 4 additions & 17 deletions core/daglang/daglang-cli/src/compile/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -466,7 +466,7 @@ fn resolve_lowered_dag_maps_makegen_nodes_to_dyn_ops() {
}

#[test]
fn resolve_lowered_dag_defers_unknown_callable_module() {
fn resolve_lowered_dag_unknown_callable_module_fails_closed() {
let mut dag = Dag::new();
dag.add_node(Node::opaque(
"sample::unknown",
Expand All @@ -483,23 +483,10 @@ fn resolve_lowered_dag_defers_unknown_callable_module() {
},
));

let resolved = resolve_lowered_dag(&dag).expect("unknown modules should defer");
assert_eq!(resolved.nodes.len(), 1);
let debug = format!("{:?}", resolved.nodes[0].body);
assert!(
debug.contains("DeferredCallableOp"),
"expected deferred callable fallback, got {debug}"
);
// Deferred callables are passthrough: inputs forwarded, output ports populated.
let NodeBody::Opaque(op) = &resolved.nodes[0].body else {
panic!("unknown callable fixture should not contain subdag nodes")
};
let outputs = op
.execute(HashMap::new())
.expect("deferred callable should pass through");
let error = resolve_lowered_dag(&dag).expect_err("unknown modules should fail closed");
assert!(
outputs.contains_key("out"),
"deferred callable should populate declared output ports"
error.to_string().contains("unknown callable"),
"unexpected error: {error}"
);
}

Expand Down
14 changes: 7 additions & 7 deletions core/daglang/daglang-cli/tests/snapshots/makegen_expand.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,48 +12,48 @@ Nodes:
* __deps: Any (0..*)
outputs:
* written: Bool (1)
- prepare_read_makegen [callable::Pattern tools.makegen.content_upsert::prepare_read_makegen]
- prepare_read_makegen [primitive::IoPrepareFileRead tools.makegen.content_upsert::prepare_read_makegen]
inputs:
* path: String (1)
* res:file:Makefile: FilesystemHandle (1)
outputs:
* request: TransportRequest (1)
* skip: Bool (1)
- execute_read_makegen [callable::Pattern tools.makegen.content_upsert::execute_read_makegen]
- execute_read_makegen [primitive::IoExecuteFileRead tools.makegen.content_upsert::execute_read_makegen]
inputs:
* request: TransportRequest (1)
* skip: Bool (1)
outputs:
* response: TransportResponse (1)
- compare_makegen_content [callable::Pattern tools.makegen.content_upsert::compare_makegen_content]
- compare_makegen_content [primitive::CompareEquality tools.makegen.content_upsert::compare_makegen_content]
inputs:
* expected_content: String (1)
* response: TransportResponse (1)
outputs:
* fresh: Bool (1)
* skip: Bool (1)
- prepare_write_makegen [callable::Pattern tools.makegen.content_upsert::prepare_write_makegen]
- prepare_write_makegen [primitive::IoPrepareFileWrite tools.makegen.content_upsert::prepare_write_makegen]
inputs:
* content: String (1)
* path: String (1)
outputs:
* request: TransportRequest (1)
- execute_makegen_transport [callable::Pattern tools.makegen.content_upsert::execute_makegen_transport]
- execute_makegen_transport [primitive::IoExecuteFileWrite tools.makegen.content_upsert::execute_makegen_transport]
inputs:
* request: TransportRequest (1)
* skip: Bool (1)
* res:file: FilesystemHandle (1)
outputs:
* response: TransportResponse (1)
- param_source_tools_makegen_makegen_path [callable::Pattern tools.makegen.call_param_source::makegen::path]
- param_source_tools_makegen_makegen_path [primitive::CallParamSource { callable: "makegen", param: "path" } tools.makegen.call_param_source::makegen::path]
inputs:
* path: String (1)
outputs:
* path: String (1)
- load_registry [callable::Pattern tools.makegen.load_registry]
outputs:
* registry: ToolRegistry (1)
- fs_env [callable::Pattern tools.makegen.fs_env]
- fs_env [primitive::FsEnv tools.makegen.fs_env]
outputs:
* FilesystemHandle: FilesystemHandle (1)
Edges:
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"progress_manifest":{"capture_modes":{"compare_makegen_content":"captured","execute_makegen_transport":"captured","execute_read_makegen":"captured","fs_env":"captured","load_registry":"captured","param_source_tools_makegen_makegen_path":"captured","prepare_read_makegen":"captured","prepare_write_makegen":"captured","tools.makegen::makegen":"captured","tools.makegen::render_makefile":"captured"},"interactive_nodes":[],"labels":{"compare_makegen_content":"tools.makegen.content_upsert::compare_makegen_content","execute_makegen_transport":"tools.makegen.content_upsert::execute_makegen_transport","execute_read_makegen":"tools.makegen.content_upsert::execute_read_makegen","fs_env":"tools.makegen.fs_env","load_registry":"tools.makegen.load_registry","param_source_tools_makegen_makegen_path":"tools.makegen.call_param_source::makegen::path","prepare_read_makegen":"tools.makegen.content_upsert::prepare_read_makegen","prepare_write_makegen":"tools.makegen.content_upsert::prepare_write_makegen","tools.makegen::makegen":"tools.makegen.makegen","tools.makegen::render_makefile":"tools.makegen.render_makefile"},"parallel_groups":[{"depth":0,"nodes":["fs_env","load_registry","param_source_tools_makegen_makegen_path"]},{"depth":1,"nodes":["prepare_read_makegen","tools.makegen::render_makefile"]},{"depth":2,"nodes":["execute_read_makegen","prepare_write_makegen"]},{"depth":3,"nodes":["compare_makegen_content"]},{"depth":4,"nodes":["execute_makegen_transport"]},{"depth":5,"nodes":["tools.makegen::makegen"]}],"resources":{},"scatter_points":[],"schema_version":1,"stage_groups":[],"subdag_boundaries":[],"topology":[{"depth":0,"id":"fs_env"},{"depth":0,"id":"load_registry"},{"depth":0,"id":"param_source_tools_makegen_makegen_path"},{"depth":1,"id":"prepare_read_makegen"},{"depth":1,"id":"tools.makegen::render_makefile"},{"depth":2,"id":"execute_read_makegen"},{"depth":2,"id":"prepare_write_makegen"},{"depth":3,"id":"compare_makegen_content"},{"depth":4,"id":"execute_makegen_transport"},{"depth":5,"id":"tools.makegen::makegen"}],"total_nodes":10},"test_obligations":{"dry_run_completion_required":true,"interface_contract_verification_targets":0,"pure_node_determinism_targets":8,"resource_acquire_targets":0,"resource_provide_targets":0,"resource_release_targets":0,"service_param_source_targets":1,"service_transport_execute_targets":0,"service_transport_external_targets":0,"service_transport_hermetic_targets":0,"service_transport_idempotent_targets":0,"service_transport_parse_targets":0,"service_transport_permission_scoped_targets":0,"service_transport_prepare_targets":0,"service_transport_readonly_targets":0,"total_obligations":10,"transport_execution_targets":2}}
{"progress_manifest":{"capture_modes":{"compare_makegen_content":"captured","execute_makegen_transport":"captured","execute_read_makegen":"captured","fs_env":"captured","load_registry":"captured","param_source_tools_makegen_makegen_path":"captured","prepare_read_makegen":"captured","prepare_write_makegen":"captured","tools.makegen::makegen":"captured","tools.makegen::render_makefile":"captured"},"interactive_nodes":[],"labels":{"compare_makegen_content":"tools.makegen.content_upsert::compare_makegen_content","execute_makegen_transport":"tools.makegen.content_upsert::execute_makegen_transport","execute_read_makegen":"tools.makegen.content_upsert::execute_read_makegen","fs_env":"tools.makegen.fs_env","load_registry":"tools.makegen.load_registry","param_source_tools_makegen_makegen_path":"tools.makegen.call_param_source::makegen::path","prepare_read_makegen":"tools.makegen.content_upsert::prepare_read_makegen","prepare_write_makegen":"tools.makegen.content_upsert::prepare_write_makegen","tools.makegen::makegen":"tools.makegen.makegen","tools.makegen::render_makefile":"tools.makegen.render_makefile"},"parallel_groups":[{"depth":0,"nodes":["fs_env","load_registry","param_source_tools_makegen_makegen_path"]},{"depth":1,"nodes":["prepare_read_makegen","tools.makegen::render_makefile"]},{"depth":2,"nodes":["execute_read_makegen","prepare_write_makegen"]},{"depth":3,"nodes":["compare_makegen_content"]},{"depth":4,"nodes":["execute_makegen_transport"]},{"depth":5,"nodes":["tools.makegen::makegen"]}],"resources":{},"scatter_points":[],"schema_version":1,"stage_groups":[],"subdag_boundaries":[],"topology":[{"depth":0,"id":"fs_env"},{"depth":0,"id":"load_registry"},{"depth":0,"id":"param_source_tools_makegen_makegen_path"},{"depth":1,"id":"prepare_read_makegen"},{"depth":1,"id":"tools.makegen::render_makefile"},{"depth":2,"id":"execute_read_makegen"},{"depth":2,"id":"prepare_write_makegen"},{"depth":3,"id":"compare_makegen_content"},{"depth":4,"id":"execute_makegen_transport"},{"depth":5,"id":"tools.makegen::makegen"}],"total_nodes":10},"test_obligations":{"dry_run_completion_required":true,"interface_contract_verification_targets":1,"pure_node_determinism_targets":8,"resource_acquire_targets":0,"resource_provide_targets":1,"resource_release_targets":0,"service_param_source_targets":1,"service_transport_execute_targets":2,"service_transport_external_targets":2,"service_transport_hermetic_targets":0,"service_transport_idempotent_targets":0,"service_transport_parse_targets":0,"service_transport_permission_scoped_targets":0,"service_transport_prepare_targets":2,"service_transport_readonly_targets":0,"total_obligations":10,"transport_execution_targets":2}}
10 changes: 5 additions & 5 deletions core/daglang/daglang-cli/tests/workflow_fixtures/s1_makegen.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,19 @@
"error_contains": null,
"expected_json": {
"dry_run_completion_required": true,
"interface_contract_verification_targets": 0,
"interface_contract_verification_targets": 1,
"pure_node_determinism_targets": 8,
"resource_acquire_targets": 0,
"resource_provide_targets": 0,
"resource_provide_targets": 1,
"resource_release_targets": 0,
"service_param_source_targets": 1,
"service_transport_execute_targets": 0,
"service_transport_external_targets": 0,
"service_transport_execute_targets": 2,
"service_transport_external_targets": 2,
"service_transport_hermetic_targets": 0,
"service_transport_idempotent_targets": 0,
"service_transport_parse_targets": 0,
"service_transport_permission_scoped_targets": 0,
"service_transport_prepare_targets": 0,
"service_transport_prepare_targets": 2,
"service_transport_readonly_targets": 0,
"total_obligations": 10,
"transport_execution_targets": 2
Expand Down
19 changes: 12 additions & 7 deletions core/daglang/daglang-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,9 @@ fn derive_node_labels(nodes: &[Node<LoweredOp>]) -> BTreeMap<String, String> {
gunbc_ir::node::NodeBody::Opaque(LoweredOp::Callable { module, name, .. }) => {
format!("{module}.{name}")
}
gunbc_ir::node::NodeBody::Opaque(LoweredOp::Primitive { module, name, .. }) => {
format!("{module}.{name}")
}
gunbc_ir::node::NodeBody::Opaque(LoweredOp::Collection {
module,
callable,
Expand Down Expand Up @@ -519,9 +522,7 @@ fn derive_interactive_nodes(nodes: &[Node<LoweredOp>]) -> Vec<String> {
Some(LoweredOp::Callable {
is_interactive: true,
..
}) => {
Some(node.id.0.clone())
}
}) => Some(node.id.0.clone()),
_ => None,
})
.collect::<Vec<_>>();
Expand Down Expand Up @@ -579,9 +580,9 @@ fn derive_module_metadata(nodes: &[Node<LoweredOp>]) -> Vec<ModuleMetadata> {
continue;
};
let (module, is_pipeline) = match op {
LoweredOp::Callable { module, .. } | LoweredOp::Collection { module, .. } => {
(module, false)
}
LoweredOp::Callable { module, .. }
| LoweredOp::Primitive { module, .. }
| LoweredOp::Collection { module, .. } => (module, false),
LoweredOp::Pipeline { module, .. } => (module, true),
};
let entry = by_module
Expand Down Expand Up @@ -1235,7 +1236,11 @@ mod tests {
Some(CaptureMode::Passthrough)
);
assert_eq!(
artifacts.manifest.capture_modes.get("transport_node").cloned(),
artifacts
.manifest
.capture_modes
.get("transport_node")
.cloned(),
Some(CaptureMode::Captured)
);
}
Expand Down
118 changes: 111 additions & 7 deletions core/daglang/daglang-emit/src/computation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ pub enum PureBody {
/// Hardcoded value (e.g., LoadRegistry, FsEnv configuration).
Literal(serde_json::Value),

/// Build a transport request payload from step inputs.
PrepareTransport { kind: TransportKind },

/// String interpolation: fill variables into a pattern.
/// `pattern` uses `{var}` placeholders; `vars` lists the names to substitute.
Template { pattern: String, vars: Vec<String> },
Expand Down Expand Up @@ -265,7 +268,9 @@ pub enum CollectionOpKind {
// Classification (A1.3)
// ===========================================================================

use daglang_lower::{LoweredOp, ObligationCategory, ServiceTransportClass};
use daglang_lower::{
LoweredOp, ObligationCategory, PrimitiveLiteral, PrimitiveOpKind, ServiceTransportClass,
};
use gunbc_ir::node::{Node, NodeBody};
use gunbc_ir::Port;

Expand Down Expand Up @@ -316,6 +321,9 @@ pub fn classify_computation(node: &Node<LoweredOp>) -> Result<Computation, Class
strategy: AggregateKind::Concat,
},
}),
LoweredOp::Primitive { module, name, kind } => {
classify_primitive(module, name, kind, inputs, outputs)
}
LoweredOp::Callable {
module,
name,
Expand All @@ -333,6 +341,94 @@ pub fn classify_computation(node: &Node<LoweredOp>) -> Result<Computation, Class
}
}

fn classify_primitive(
_module: &str,
name: &str,
kind: &PrimitiveOpKind,
inputs: Vec<TypedPort>,
outputs: Vec<TypedPort>,
) -> Result<Computation, ClassifyError> {
match kind {
PrimitiveOpKind::FsEnv => {
let handle_type = outputs
.first()
.map(|p| p.abstract_type.clone())
.unwrap_or_else(|| "Unknown".to_string());
Ok(Computation::ResourceAcquire {
handle_type,
handle_value: name.to_string(),
})
}
PrimitiveOpKind::CallLiteralSource { literal } => Ok(Computation::Pure {
inputs,
outputs,
body: PureBody::Literal(primitive_literal_to_json(literal)),
}),
PrimitiveOpKind::IoPrepareFileRead => Ok(Computation::Pure {
inputs,
outputs,
body: PureBody::PrepareTransport {
kind: TransportKind::FileRead,
},
}),
PrimitiveOpKind::IoExecuteFileRead => Ok(Computation::Transport {
prepare: RequestSpec {
input_ports: inputs.iter().map(|p| p.name.clone()).collect(),
kind: RequestKind::FilePath {
path_port: "request".to_string(),
},
},
execute: TransportKind::FileRead,
parse: ResponseSpec {
output_ports: outputs.iter().map(|p| p.name.clone()).collect(),
kind: ResponseKind::RawContent,
},
}),
PrimitiveOpKind::CompareEquality => Ok(Computation::Pure {
inputs,
outputs,
body: PureBody::Compare {
left: "expected_content".to_string(),
right: "response".to_string(),
},
}),
PrimitiveOpKind::IoPrepareFileWrite => Ok(Computation::Pure {
inputs,
outputs,
body: PureBody::PrepareTransport {
kind: TransportKind::FileWrite,
},
}),
PrimitiveOpKind::IoExecuteFileWrite => Ok(Computation::Transport {
prepare: RequestSpec {
input_ports: inputs.iter().map(|p| p.name.clone()).collect(),
kind: RequestKind::FilePath {
path_port: "request".to_string(),
},
},
execute: TransportKind::FileWrite,
parse: ResponseSpec {
output_ports: outputs.iter().map(|p| p.name.clone()).collect(),
kind: ResponseKind::ExitStatus,
},
}),
PrimitiveOpKind::CallParamSource { .. } => Ok(Computation::Pure {
inputs,
outputs,
body: PureBody::Literal(serde_json::Value::Null),
}),
}
}

fn primitive_literal_to_json(literal: &PrimitiveLiteral) -> serde_json::Value {
match literal {
PrimitiveLiteral::String(value) => serde_json::Value::String(value.clone()),
PrimitiveLiteral::Int(value) => serde_json::Value::Number((*value).into()),
PrimitiveLiteral::Bool(value) => serde_json::Value::Bool(*value),
PrimitiveLiteral::Unit => serde_json::Value::Null,
}
}

// ---------------------------------------------------------------------------
// Internal helpers
// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -610,7 +706,9 @@ fn classify_content_upsert(
return Ok(Computation::Pure {
inputs,
outputs,
body: PureBody::Literal(serde_json::Value::String("prepare_file_read".to_string())),
body: PureBody::PrepareTransport {
kind: TransportKind::FileRead,
},
});
}

Expand Down Expand Up @@ -652,7 +750,9 @@ fn classify_content_upsert(
return Ok(Computation::Pure {
inputs,
outputs,
body: PureBody::Literal(serde_json::Value::String("prepare_file_write".to_string())),
body: PureBody::PrepareTransport {
kind: TransportKind::FileWrite,
},
});
}

Expand Down Expand Up @@ -785,11 +885,13 @@ mod tests {
matches!(
comp,
Computation::Pure {
body: PureBody::Literal(_),
body: PureBody::PrepareTransport {
kind: TransportKind::FileRead
},
..
}
),
"prepare_read should be Pure(Literal), got {comp:?}"
"prepare_read should prepare file-read transport request, got {comp:?}"
);
}

Expand Down Expand Up @@ -875,11 +977,13 @@ mod tests {
matches!(
comp,
Computation::Pure {
body: PureBody::Literal(_),
body: PureBody::PrepareTransport {
kind: TransportKind::FileWrite
},
..
}
),
"prepare_write should be Pure(Literal), got {comp:?}"
"prepare_write should prepare file-write transport request, got {comp:?}"
);
}

Expand Down
Loading