Skip to content
Closed
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
16 changes: 13 additions & 3 deletions core/daglang/daglang-driver/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,11 @@ pub fn compile_from_module_graph_with_options(
mut module_graph: ModuleGraph,
options: CompileOptions,
) -> Result<CompileOutput, CompileError> {
include_profile_modules(&mut module_graph, &context.roots, options.profile.is_some())?;
include_profile_modules(
&mut module_graph,
&context.roots,
options.profile.as_deref(),
)?;
let callable_scope_result = callable_scope_for_context(context, &module_graph)?;
let (callable_scope, entry_module_name) = match callable_scope_result {
Some((scope, entry)) => (Some(scope), Some(entry)),
Expand Down Expand Up @@ -1092,7 +1096,7 @@ fn discover_target_module_graph_for_context(
fn include_profile_modules(
module_graph: &mut ModuleGraph,
roots: &[PathBuf],
include_bound_services: bool,
active_profile: Option<&str>,
) -> Result<(), CompileError> {
let mut seed_files = Vec::<PathBuf>::new();
for root in roots {
Expand All @@ -1110,7 +1114,7 @@ fn include_profile_modules(
return Ok(());
}

if include_bound_services {
if active_profile.is_some() {
let canonical_roots = daglang_resolve::canonicalize_roots(roots);
let mut implementation_modules = Vec::<PathBuf>::new();
for profile_file in &seed_files {
Expand All @@ -1120,6 +1124,12 @@ fn include_profile_modules(
let Item::ProfileDef(def) = &item.node else {
continue;
};
// Only include implementation modules for the active profile,
// not all profiles. This avoids loading transport-incomplete
// providers that aren't used by the active profile.
if active_profile != Some(def.name.as_str()) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Match active profile against fully qualified names

include_profile_modules now filters binds with active_profile != Some(def.name.as_str()), which only matches profile aliases (e.g. local) and never matches fully qualified names (e.g. profiles.sdlc.local). This regresses the disambiguation path used when aliases collide: in this repo both dsl/profiles/gist.dag and dsl/profiles/sdlc.dag define profile local, and lowerer diagnostics explicitly require fully qualified names for ambiguous aliases. With a fully qualified --profile, this filter skips the active profile's implementation modules, so bound service endpoints are not loaded and profile compilation fails for valid profiles.

Useful? React with 👍 / 👎.

continue;
}
for bind in &def.binds {
if let Some(path) = resolve_profile_bind_implementation_module_path(
roots,
Expand Down
28 changes: 23 additions & 5 deletions core/daglang/daglang-lower/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -921,7 +921,7 @@ enum ProfileConfigValue {

fn collect_profile_binding_registry(
project: &TypedProject,
require_implementation_resolution: bool,
active_profile: Option<&str>,
) -> Result<ProfileBindingRegistry, LowerError> {
let mut interface_registry = NameAliasRegistry::default();
let mut service_registry = NameAliasRegistry::default();
Expand Down Expand Up @@ -986,7 +986,10 @@ fn collect_profile_binding_registry(
})
}
NameResolution::Missing => {
if require_implementation_resolution {
// Only require implementation resolution for the active profile.
// Non-active profiles may reference modules that weren't loaded.
let is_active = active_profile == Some(def.name.as_str());
if is_active {
return Err(LowerError::InvalidProfileBinding {
profile: profile_full.clone(),
detail: format!(
Expand Down Expand Up @@ -1992,7 +1995,7 @@ fn lower_typed_project_with_callable_scope(
entry_module,
&data_values,
);
let profile_registry = collect_profile_binding_registry(project, active_profile.is_some())?;
let profile_registry = collect_profile_binding_registry(project, active_profile)?;
let active_profile_bindings =
resolve_active_profile_bindings(&profile_registry, active_profile)?;
let profile_bound_interfaces = collect_profile_bound_interface_names(&profile_registry);
Expand Down Expand Up @@ -5012,6 +5015,10 @@ fn derive_service_call_metadata(
Some(TransportBinding::Shell { .. }) => ServiceTransportClass::ShellLocal,
Some(TransportBinding::File { .. }) => ServiceTransportClass::FileBoundary,
Some(TransportBinding::Local) => ServiceTransportClass::LocalDirect,
// Services implementing interfaces that have no transport block get
// InterfaceStub transport. This allows stub providers (unit_test profile)
// to compile without explicit transport declarations.
None if service.implements.is_some() => ServiceTransportClass::InterfaceStub,
None => ServiceTransportClass::Unknown,
};
let mut permissions = operation.permissions.clone();
Expand Down Expand Up @@ -5144,6 +5151,17 @@ fn derive_operation_spec(
ServiceTransportClass::LocalDirect => {
Some(ServiceOperationSpec::Local(derive_local_spec(operation)))
}
ServiceTransportClass::InterfaceStub => {
// Services implementing interfaces with no transport block.
// Use the service name as the interface name (from `: InterfaceName` syntax).
Some(ServiceOperationSpec::InterfaceStub {
interface: service
.implements
.clone()
.unwrap_or_else(|| service.name.clone()),
capability: operation.name.clone(),
})
}
_ => None,
}
}
Expand Down Expand Up @@ -5615,12 +5633,12 @@ fn collect_required_service_call_keys(

fn service_prepare_ports(operation: &OperationDef, metadata: &ServiceCallMetadata) -> Vec<Port> {
let declared_inputs = match metadata.spec.as_ref() {
Some(spec) => spec
Some(spec) if !spec.input_fields().is_empty() => spec
.input_fields()
.iter()
.map(|field| (field.name.clone(), field.type_id.clone()))
.collect::<Vec<_>>(),
None => operation
_ => operation
.inputs
.iter()
.map(|field| {
Expand Down
8 changes: 8 additions & 0 deletions dsl/services/github/pull_request.dag
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
module services.github.pull_request

service github.PullRequest {
config { endpoint: "https://api.github.com", auth: BearerToken }

operation Create {
input {
Expand All @@ -28,6 +29,7 @@ service github.PullRequest {
node_id: String from "node_id"
head_sha: String from "head.sha"
}
transport rest { method: POST, path: "/repos/\{owner\}/\{repo\}/pulls" }
}

operation Get {
Expand All @@ -47,6 +49,7 @@ service github.PullRequest {
mergeable: Bool? from "mergeable"
merged: Bool from "merged"
}
transport rest { method: GET, path: "/repos/\{owner\}/\{repo\}/pulls/\{number\}" }
}

operation AddComment {
Expand All @@ -60,6 +63,7 @@ service github.PullRequest {
id: Int from "id"
url: String from "html_url"
}
transport rest { method: POST, path: "/repos/\{owner\}/\{repo\}/issues/\{number\}/comments" }
}

operation Merge {
Expand All @@ -76,6 +80,7 @@ service github.PullRequest {
sha: String from "sha"
message: String from "message"
}
transport rest { method: PUT, path: "/repos/\{owner\}/\{repo\}/pulls/\{number\}/merge" }
}

operation ListFiles {
Expand All @@ -88,6 +93,7 @@ service github.PullRequest {
output {
files: Json
}
transport rest { method: GET, path: "/repos/\{owner\}/\{repo\}/pulls/\{number\}/files" }
}

operation RequestReview {
Expand All @@ -101,6 +107,7 @@ service github.PullRequest {
output {
url: String from "html_url"
}
transport rest { method: POST, path: "/repos/\{owner\}/\{repo\}/pulls/\{number\}/requested_reviewers" }
}

operation ListReviews {
Expand All @@ -112,6 +119,7 @@ service github.PullRequest {
output {
reviews: Json
}
transport rest { method: GET, path: "/repos/\{owner\}/\{repo\}/pulls/\{number\}/reviews" }
}
}

Expand Down
3 changes: 3 additions & 0 deletions dsl/services/llm/openai.dag
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
module services.llm.openai

service llm.OpenAI {
config { endpoint: "https://api.openai.com", auth: BearerToken }

operation ChatCompletion {
input {
Expand All @@ -21,6 +22,7 @@ service llm.OpenAI {
prompt_tokens: Int from "usage/prompt_tokens"
completion_tokens: Int from "usage/completion_tokens"
}
transport rest { method: POST, path: "/v1/chat/completions" }
}

operation Responses {
Expand All @@ -36,6 +38,7 @@ service llm.OpenAI {
output_tokens: Int from "usage/output_tokens"
input_tokens: Int from "usage/input_tokens"
}
transport rest { method: POST, path: "/v1/responses" }
}
}

Expand Down
7 changes: 7 additions & 0 deletions gunbc-dag/src/dsl_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -358,4 +358,11 @@ mod tests {
"runtime CI graph should not include pipeline metadata nodes"
);
}

#[test]
fn builds_sdlc_pipeline_with_unit_test_profile() {
let dag = build_dsl_graph_with_profile("pipelines/sdlc.dag", "unit_test")
.expect("SDLC pipeline should compile with unit_test profile");
assert!(!dag.nodes.is_empty());
}
}
2 changes: 1 addition & 1 deletion tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ L4+ needs transport on all services the local profile touches — BT6 handles th

| # | ID | Task | Level | Size | Status | Deps |
|---|-----|------|-------|------|--------|------|
| 1 | BT1 | **Compile SDLC pipeline.** `build_dsl_graph_with_profile("pipelines/sdlc.dag", "unit_test")` succeeds. Fix any resolve.rs gaps inline. | L0 | S | Pending | — |
| 1 | BT1 | **Compile SDLC pipeline.** `build_dsl_graph_with_profile("pipelines/sdlc.dag", "unit_test")` succeeds. Fix any resolve.rs gaps inline. | L0 | S | Done | — |
| 2 | BT2 | **Pipeline wiring.** Fill 3 empty stages in `workflows/sdlc.dag`: wire `intake`, `worker`, `report`. Decide: delete `sdlc_dispatch_runtime.dag` or fill with real policy. | L0 | M | Pending | BT1 |
| 3 | BT3 | **Hermetic scenario test.** unit_test profile, DryRun, full idea→done with stubs. | L1 | M | Pending | BT2 |
| 4 | BT4 | **Per-stage handler tests.** 8 handlers individually with mocked interfaces. | L2 | M | Pending | BT2 |
Expand Down
Loading