Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
94 commits
Select commit Hold shift + click to select a range
bd8725b
WIP: tidy-wolf-507
briansrls Apr 29, 2026
2fd9eed
WIP: tidy-wolf-507
briansrls Apr 29, 2026
7994d36
chore: apply cargo fmt
briansrls Apr 29, 2026
fd494f6
Merge remote-tracking branch 'origin/main' into session/tidy-wolf-507
briansrls Apr 29, 2026
ef14405
WIP: tidy-wolf-507
briansrls Apr 29, 2026
57bbd40
chore: apply cargo fmt
briansrls Apr 29, 2026
d92b3e0
Merge remote-tracking branch 'origin/main' into session/tidy-wolf-507
briansrls Apr 29, 2026
186c0f8
WIP: tidy-wolf-507
briansrls Apr 29, 2026
4602659
Merge remote-tracking branch 'origin/main' into session/tidy-wolf-507
briansrls Apr 29, 2026
208150a
Merge remote-tracking branch 'origin/main' into session/tidy-wolf-507
briansrls Apr 29, 2026
660e2ab
Merge remote-tracking branch 'origin/main' into session/tidy-wolf-507
briansrls Apr 29, 2026
51fb2b4
WIP: tidy-wolf-507
briansrls Apr 29, 2026
6496a0c
fix(v3): SG-0 ratchet receipt + parse-corpus manifest refresh
briansrls Apr 29, 2026
7f475e0
WIP: tidy-wolf-507
briansrls Apr 29, 2026
2c13dc8
Merge remote-tracking branch 'origin/main' into session/tidy-wolf-507
briansrls Apr 29, 2026
61ad6ea
Merge remote-tracking branch 'origin/main' into session/tidy-wolf-507
briansrls Apr 29, 2026
b18a5ec
chore(v3): re-regen bootstrap on top of merged main
briansrls Apr 29, 2026
b7e6988
Merge remote-tracking branch 'origin/main' into session/tidy-wolf-507
briansrls Apr 29, 2026
66fa643
Merge remote-tracking branch 'origin/main' into session/tidy-wolf-507
briansrls Apr 29, 2026
45f24c9
Merge remote-tracking branch 'origin/main' into session/tidy-wolf-507
briansrls Apr 29, 2026
0ef4771
WIP: tidy-wolf-507
briansrls Apr 29, 2026
fa5bba2
feat(v3): T-Substrate-Lens-Primitive — Lens<C> carrier + Q6.5 widening
briansrls Apr 29, 2026
cdcaecc
WIP: tidy-wolf-507
briansrls Apr 29, 2026
c515b7e
docs(v3): explicitly cover kind_decl resolution gap in SCAFFOLD comment
briansrls Apr 29, 2026
818126b
Merge remote-tracking branch 'origin/main' into session/tidy-wolf-507
briansrls Apr 29, 2026
5bb049c
chore(v3): re-regen bootstrap on top of merged main
briansrls Apr 29, 2026
1eeadb7
Merge remote-tracking branch 'origin/main' into session/tidy-wolf-507
briansrls Apr 29, 2026
d1d2599
chore(v3): re-regen bootstrap on top of merged main (#1188 fix)
briansrls Apr 29, 2026
b0eb3d8
Merge remote-tracking branch 'origin/main' into session/tidy-wolf-507
briansrls Apr 29, 2026
ec597ac
WIP: tidy-wolf-507
briansrls Apr 29, 2026
1f695fe
feat(v3): minimal method-declaration registry + MethodRef refinement
briansrls Apr 29, 2026
c865541
Merge remote-tracking branch 'origin/main' into session/tidy-wolf-507
briansrls Apr 29, 2026
d6216b8
WIP: tidy-wolf-507
briansrls Apr 29, 2026
b9cf95f
test(v3): tighten method-registry authority enforcement
briansrls Apr 29, 2026
fdaaee5
chore: apply cargo fmt
briansrls Apr 29, 2026
b2651ac
WIP: tidy-wolf-507
briansrls Apr 29, 2026
84755ce
test(v3): registry drift derives names from algebra.dag source
briansrls Apr 29, 2026
7900975
Merge remote-tracking branch 'origin/main' into session/tidy-wolf-507
briansrls Apr 29, 2026
ad39ecb
Merge remote-tracking branch 'origin/main' into session/tidy-wolf-507
briansrls Apr 29, 2026
944cf79
Merge remote-tracking branch 'origin/main' into session/tidy-wolf-507
briansrls Apr 29, 2026
073b077
Merge remote-tracking branch 'origin/main' into session/tidy-wolf-507
briansrls Apr 29, 2026
b5359e2
Merge remote-tracking branch 'origin/main' into session/tidy-wolf-507
briansrls Apr 29, 2026
b8d8693
Merge remote-tracking branch 'origin/main' into session/tidy-wolf-507
briansrls Apr 29, 2026
9056936
Merge remote-tracking branch 'origin/main' into session/tidy-wolf-507
briansrls Apr 29, 2026
359f658
docs(design): lens-fold prerequisites audit
briansrls Apr 29, 2026
83535ad
WIP: tidy-wolf-507
briansrls Apr 29, 2026
d34ca4a
docs(design): land Director dispositions on lens-fold prerequisites
briansrls Apr 29, 2026
8db65f7
Merge remote-tracking branch 'origin/main' into session/tidy-wolf-507
briansrls Apr 29, 2026
a49a8aa
Merge remote-tracking branch 'origin/main' into session/tidy-wolf-507
briansrls Apr 29, 2026
09c221e
docs(design): fail-closed WorkflowRoot return type for accessor
briansrls Apr 29, 2026
42bf818
docs(design): narrow Prereq-1 to Arrow-signature inhabitance
briansrls Apr 29, 2026
a4950f5
docs(design): add Prereq-3a standalone acceptance for accessor
briansrls Apr 29, 2026
cca85a6
docs(design): resolve workflow-root contradiction in does-not-do list
briansrls Apr 29, 2026
8f48849
Merge remote-tracking branch 'origin/main' into session/tidy-wolf-507
briansrls Apr 29, 2026
ffd5723
docs(design): fix stale -> PortId in Sub-slice 3a after WorkflowRoot …
briansrls Apr 29, 2026
96c9901
Merge remote-tracking branch 'origin/main' into session/tidy-wolf-507
briansrls Apr 29, 2026
c00b862
Merge remote-tracking branch 'origin/main' into session/tidy-wolf-507
briansrls Apr 29, 2026
f2f3128
docs(design): clarify AmbiguousRoot semantics under linear d.nodes
briansrls Apr 29, 2026
b278ce0
docs(design): AmbiguousRoot reserved for enumerate-all rule, not α/γ
briansrls Apr 29, 2026
b37da92
Merge remote-tracking branch 'origin/main' into session/tidy-wolf-507
briansrls Apr 29, 2026
d4281ad
Merge remote-tracking branch 'origin/main' into session/tidy-wolf-507
briansrls Apr 29, 2026
7cd7f06
Merge remote-tracking branch 'origin/main' into session/tidy-wolf-507
briansrls Apr 29, 2026
4c72876
Merge remote-tracking branch 'origin/main' into session/tidy-wolf-507
briansrls Apr 29, 2026
9f718b3
Merge remote-tracking branch 'origin/main' into session/tidy-wolf-507
briansrls Apr 29, 2026
a3f6ac5
Merge remote-tracking branch 'origin/main' into session/tidy-wolf-507
briansrls Apr 29, 2026
0304df1
Merge remote-tracking branch 'origin/main' into session/tidy-wolf-507
briansrls Apr 29, 2026
8a9c259
Merge remote-tracking branch 'origin/main' into session/tidy-wolf-507
briansrls Apr 29, 2026
b7f66fd
Merge remote-tracking branch 'origin/main' into session/tidy-wolf-507
briansrls Apr 29, 2026
cd53fc5
Merge remote-tracking branch 'origin/main' into session/tidy-wolf-507
briansrls Apr 29, 2026
ed8997c
WIP: tidy-wolf-507
briansrls Apr 29, 2026
1deb963
feat(v3): workflow_root_port accessor + WorkflowRoot sum (Prereq-3a)
briansrls Apr 29, 2026
93c6f45
chore: apply cargo fmt
briansrls Apr 29, 2026
9aaee8e
docs(v3): fix SingleRoot comment to match α multi-Bind behavior
briansrls Apr 29, 2026
5432a05
WIP: tidy-wolf-507
briansrls Apr 29, 2026
6000ecd
Merge remote-tracking branch 'origin/main' into session/tidy-wolf-507
briansrls Apr 29, 2026
092b0c2
fix(v3): WorkflowRoot NonSingletonList + Rust scaffold receipt
briansrls Apr 30, 2026
5e9a47f
Merge remote-tracking branch 'origin/main' into session/tidy-wolf-507
briansrls Apr 30, 2026
55b2936
chore(v3): re-regen bootstrap on top of merged main
briansrls Apr 30, 2026
628910c
Merge remote-tracking branch 'origin/main' into session/tidy-wolf-507
briansrls Apr 30, 2026
8bb6dc7
docs(v3): explicit BOUNDED STAGING SCAFFOLD receipt on workflow_root_…
briansrls Apr 30, 2026
428c341
Merge remote-tracking branch 'origin/main' into session/tidy-wolf-507
briansrls Apr 30, 2026
3d9569a
WIP: tidy-wolf-507
briansrls Apr 30, 2026
dc0b369
feat(v3): land Rust SubstrateAccessorBinding for workflow_root_port
briansrls Apr 30, 2026
46e632c
fix(v3): bump substrate-accessor binding count to 6 for workflow_root…
briansrls Apr 30, 2026
4ed2a8e
Merge remote-tracking branch 'origin/main' into session/tidy-wolf-507
briansrls Apr 30, 2026
d4d4e85
Merge remote-tracking branch 'origin/main' into session/tidy-wolf-507
briansrls Apr 30, 2026
c135320
WIP: tidy-wolf-507
briansrls Apr 30, 2026
aa5da8c
feat(v3): add rust_workflow_root TypeRealization
briansrls Apr 30, 2026
0fbd045
Merge remote-tracking branch 'origin/main' into session/tidy-wolf-507
briansrls Apr 30, 2026
6dae6c4
WIP: tidy-wolf-507
briansrls Apr 30, 2026
473d743
Merge remote-tracking branch 'origin/main' into session/tidy-wolf-507
briansrls Apr 30, 2026
a35ff9d
Merge remote-tracking branch 'origin/main' into session/tidy-wolf-507
briansrls Apr 30, 2026
ed361c4
WIP: tidy-wolf-507
briansrls Apr 30, 2026
b340837
Merge remote-tracking branch 'origin/main' into session/tidy-wolf-507
briansrls Apr 30, 2026
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
3,065 changes: 1,537 additions & 1,528 deletions src/v3/compiler/src/bootstrap_generated.rs

Large diffs are not rendered by default.

2,899 changes: 1,454 additions & 1,445 deletions src/v3/compiler/src/bootstrap_generated_without_parse_surface.rs

Large diffs are not rendered by default.

74 changes: 74 additions & 0 deletions src/v3/compiler/src/dag.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1906,6 +1906,41 @@ pub enum Behavior {
Bind(BindNode),
}

/// Workflow-root identification — Rust mirror of
/// [`crate::dag::WorkflowRoot`]'s declaration in
/// `src/v3/std/substrate.dag`.
///
/// 🟡 SCAFFOLD coproduct (mirroring the .dag receipt). The three arms
/// partition every legitimate `Dag` exactly once:
///
/// - `SingleRoot(p)` — α (last topological `Bind`) selected `p`.
/// Emitted whenever the Dag contains at least one `Bind`; multiple
/// Binds are NOT ambiguous under α — linear `d.nodes` picks
/// exactly one last element by definition.
/// - `NoRoot` — zero `Bind` behaviors in `d.nodes`. Lens fold short-
/// circuits to `DimensionFail`; runtime evaluation rejects.
/// - `AmbiguousRoot { candidates }` — reserved for the future
/// enumerate-all-eligible-entries rule that R2-Evaluator's
/// `evaluate(program, entry, args)` consumes for multi-entry
/// programs (per Items 4+5 / #1176 §3.2). The α / γ "last X Bind"
/// rules cannot populate this arm; today's α implementation never
/// emits it. Carried as `NonSingletonList<PortId>` to make the
/// pre-disambiguation 1-candidate case structurally
/// unrepresentable — `AmbiguousRoot` requires ≥2 candidates by
/// construction.
///
/// Dissolution: γ refinement and the enumerate-all rule both reuse
/// this same partition behind the `workflow_root_port` accessor; no
/// carrier change required when those rules wire.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum WorkflowRoot {

This comment was marked as resolved.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Already fixed in working tree (commit pending). The Rust pub enum WorkflowRoot now carries the full 🟡 SCAFFOLD receipt at src/v3/compiler/src/dag.rs:1909-1934 — three arms with their structural classification (α picks SingleRoot when ≥1 Bind; NoRoot for zero Binds; AmbiguousRoot reserved for the enumerate-all-eligible-entries rule, NonSingletonList carrier makes ≥2 candidates structural). Named dissolution trigger included ("γ refinement and the enumerate-all rule both reuse this same partition behind the workflow_root_port accessor"). Mirrors the .dag receipt at substrate.dag:493-522 rather than diverging. — sent from tidy-wolf-507

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Already fixed at head 092b0c231. The Rust pub enum WorkflowRoot carries the full 🟡 SCAFFOLD receipt at src/v3/compiler/src/dag.rs:1909-1934 (three arms with their structural classification + named dissolution trigger). Stale relay; bot is at sha 092b0c23 but quoting old ed8997c2 text. — sent from tidy-wolf-507

SingleRoot(PortId),
NoRoot,
AmbiguousRoot {
candidates: NonSingletonList<PortId>,
},
}

impl Behavior {
pub fn id(&self) -> NodeId {
match self {
Expand Down Expand Up @@ -2962,6 +2997,29 @@ impl Dag {
}
}

/// Workflow-root accessor (Director-locked α implementation per
/// `docs/design-lens-fold-prerequisites.md` §"Prereq-3a"). Walks
/// `d.nodes` (which is topologically ordered) backwards and returns:
///
/// - `WorkflowRoot::SingleRoot(p)` for the last `Behavior::Bind`'s
/// `result_port`, when at least one `Bind` exists.
/// - `WorkflowRoot::NoRoot` when zero `Bind` behaviors are present
/// (lens fold short-circuits to `DimensionFail`; runtime evaluation
/// rejects).
/// - `WorkflowRoot::AmbiguousRoot { .. }` is intentionally never
/// emitted by this α implementation — the linear `d.nodes` order
/// cannot tie. The variant is reserved at the type level for the
/// future enumerate-all-eligible-entries rule that R2-Evaluator's
/// `evaluate(program, entry, args)` consumes.
pub fn workflow_root_port(&self) -> WorkflowRoot {
for behavior in self.nodes.iter().rev() {
if let Behavior::Bind(b) = behavior {
return WorkflowRoot::SingleRoot(b.result_port());

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Already fixed at head 9aaee8e7e. Line 3015 in current head reads return WorkflowRoot::SingleRoot(b.result_port()); — method call with parens. Reviewer was at sha ed8997c2 which had the field-access typo; fixed in the regen retry commit. — sent from tidy-wolf-507

}
}
WorkflowRoot::NoRoot
}

pub fn optional_match_disj(&self, cardinality_decl_id: DeclarationId) -> Option<DeclarationId> {
self.optional_match_disjs.get(&cardinality_decl_id).copied()
}
Expand Down Expand Up @@ -3778,6 +3836,22 @@ fn duplicate_target_clean_emission_binding(
mod tests {
use super::*;

#[test]
fn workflow_root_zero_bind_returns_no_root() {
// V3 surface syntax always lowers each top-level decl to a
// Bind, so the zero-Bind case is structurally unreachable from
// `compile_to_dag` fixtures. The α path's `NoRoot` arm is
// defensive-only at the substrate boundary; this unit test
// exercises it via the crate-private `Dag::empty()` constructor.
let dag = Dag::empty();
let root = dag.workflow_root_port();
assert_eq!(
root,
WorkflowRoot::NoRoot,
"Dag with no Bind behaviors must fail closed with NoRoot"
);
}

fn binding_fields(language: DeclarationId, clean_emission: DeclarationId) -> ValueBody {
ValueBody::Structural {
fields: vec![
Expand Down
2 changes: 2 additions & 0 deletions src/v3/compiler/tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,8 @@ mod test_runner_test;
mod thesis_parallelism_test;
#[path = "integration/thesis_validation_test.rs"]
mod thesis_validation_test;
#[path = "integration/workflow_root_port_test.rs"]
mod workflow_root_port_test;

mod t_demo_fixture_test {
//! **Layer:** integration
Expand Down
4 changes: 2 additions & 2 deletions src/v3/compiler/tests/integration/m1_substrate_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2972,8 +2972,8 @@ fn substrate_accessor_rust_binding_invariants() {
checked += 1;
}
assert_eq!(
checked, 5,
"expected 5 substrate accessor bindings (port, node, resolve_producer, lane2_workflow, declaration_by_id)"
checked, 6,
"expected 6 substrate accessor bindings (port, node, resolve_producer, lane2_workflow_at, declaration_by_id, workflow_root_port)"
);
let missing: Vec<_> = universe.difference(&rust_covered).copied().collect();
assert!(
Expand Down
4 changes: 2 additions & 2 deletions src/v3/compiler/tests/integration/parse_corpus_manifest.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ src/v3/compiler/tests/four_fixture_pressure/wrap.v3 4 5189 ac633b042c944480
src/v3/compiler/tokenize.dag 29 59946 0a087425b90a125a
src/v3/spec/go.dag 126 1146003 8a2493e5cde56374
src/v3/spec/python.dag 89 582347 2fe9185bf19030a5
src/v3/spec/rust.dag 144 1219155 e3772d77c07c3de1
src/v3/spec/rust.dag 147 1229942 cf14d578f8e6e598
src/v3/spec/v3_l1.dag 8 2246 57294d687b4753f4
src/v3/std/algebra.dag 42 303630 8be5b86cc9520bc3
src/v3/std/clean_emission.dag 15 23867 e7bc1e6145f4781e
Expand All @@ -41,7 +41,7 @@ src/v3/std/python_method_template_contracts.dag 5 120438 17c36841b44234d2
src/v3/std/resources.dag 5 3632 5303e31e96b01ee5
src/v3/std/runtime.dag 6 7993 0e703b02069526f6
src/v3/std/rust_method_template_contracts.dag 5 61868 1e4c73f0fe7f03c6
src/v3/std/substrate.dag 57 124531 9d03ca981cab59c3
src/v3/std/substrate.dag 59 128739 82987951254e7b4d
src/v3/std/substrate_minimal.dag 8 5372 feeb314c735f4672
src/v3/std/termination.dag 18 36478 d010232f0373dc99
src/v3/std/tokenize.dag 8 38819 2e158a74f33c18cc
Expand Down
11 changes: 11 additions & 0 deletions src/v3/compiler/tests/integration/sg0_census_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,17 @@ const EXPECTED_HAND_AUTHORED_TEST: &[&str] = &[
"src/v3/compiler/tests/integration/test_runner_test.rs",
"src/v3/compiler/tests/integration/thesis_parallelism_test.rs",
"src/v3/compiler/tests/integration/thesis_validation_test.rs",
// R2-Substrate Prereq-3a (`workflow_root_port` accessor + `WorkflowRoot`
// sum) per merged audit `docs/design-lens-fold-prerequisites.md`.
// Director-locked α: last topological `Bind`. Three integration
// claims exercise the `SingleRoot` single/multi cases and the
// unreachable-under-α ambiguous drift trigger; the zero-Bind
// `NoRoot` case lives as a unit test in `dag.rs` (crate-private
// `Dag::empty` constructor). Dispatch (#1130) accepted hand-Rust
// acceptance over real `compile_to_dag` fixtures; dissolves into
// `.dag` `TestClaim` form when testgen covers compile-and-fold
// structural assertions.
"src/v3/compiler/tests/integration/workflow_root_port_test.rs",
];

// Non-`.rs` scaffold fragments under `src/v3/compiler/` that are
Expand Down
105 changes: 105 additions & 0 deletions src/v3/compiler/tests/integration/workflow_root_port_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
//! **Layer:** integration
//!
//! Acceptance for Prereq-3a (`workflow_root_port` accessor) per the
//! merged audit at `docs/design-lens-fold-prerequisites.md`. Director-
//! locked α implementation: last topological `Bind` in `d.nodes`.
//!
//! Three claims in this module pin the α partition over `WorkflowRoot`:
//! - `workflow_root_single_bind_returns_single_root`
//! - `workflow_root_multi_bind_returns_single_under_alpha`
//! - `workflow_root_ambiguous_unreachable_under_alpha`
//!
//! The fourth claim — `workflow_root_zero_bind_returns_no_root` —
//! lives as a unit test in `src/v3/compiler/src/dag.rs` because
//! constructing an empty `Dag` requires the crate-private
//! `Dag::empty()` constructor.
//! (renamed from the audit's `_returns_ambiguous` since linear
//! `d.nodes` cannot produce ambiguity under α; the test pins
//! that α picks the LAST Bind even with multiple Binds present).
//!
//! `WorkflowRoot::AmbiguousRoot` cannot be exercised by the α
//! implementation today; `workflow_root_ambiguous_unreachable_under_alpha`
//! pins that claim explicitly so a future enumerate-all-eligible-entries
//! rule landing under the same accessor causes the test to fail loudly,
//! forcing the rule's behavior to grow its own coverage.
//!
//! The fourth audit claim — `workflow_root_consumed_by_runtime_entry_point`
//! — is deferred to the R2-Evaluator integration PR; this slice
//! only authors the substrate accessor.

use crate::common::cached_compile_to_dag;
use v3_compiler::dag::{Behavior, Dag, PortId, WorkflowRoot};

fn last_bind_result_port(dag: &Dag) -> PortId {
for behavior in dag.nodes().iter().rev() {
if let Behavior::Bind(b) = behavior {
return b.result_port();
}
}
panic!("test fixture has no Bind — adjust source");
}

#[test]
fn workflow_root_single_bind_returns_single_root() {
let dag = cached_compile_to_dag("let x = 1 + 2", "workflow_root_single.v3");
let expected = last_bind_result_port(&dag);
let root = dag.workflow_root_port();
assert_eq!(
root,
WorkflowRoot::SingleRoot(expected),
"single Bind must return SingleRoot pointing at its result_port"
);
}

#[test]
fn workflow_root_multi_bind_returns_single_under_alpha() {
// Two top-level Binds in source order. Under α (last topological
// Bind), the second Bind's result_port is the workflow root.
// AmbiguousRoot is intentionally NOT emitted — it's reserved for
// the future enumerate-all-eligible-entries rule.
let dag = cached_compile_to_dag("let x = 1\nlet y = x + 2", "workflow_root_multi.v3");
let expected = last_bind_result_port(&dag);
let root = dag.workflow_root_port();
assert_eq!(
root,
WorkflowRoot::SingleRoot(expected),
"α picks the last topological Bind even with multiple Binds; \
AmbiguousRoot reserved for the enumerate-all rule"
);
}

#[test]
fn workflow_root_ambiguous_unreachable_under_alpha() {
// Drift trigger: under α with linear d.nodes, AmbiguousRoot is
// structurally unreachable. If a future commit makes
// workflow_root_port emit AmbiguousRoot, this test fails and
// forces the change to grow its own enumerate-all-rule coverage
// rather than silently inheriting α's tests.
let fixtures = [
("let x = 1", "wf_root_amb_a.v3"),
("let x = 1\nlet y = 2\nlet z = x + y", "wf_root_amb_b.v3"),
];
for (src, file) in fixtures.iter() {
let dag = cached_compile_to_dag(src, file);
let root = dag.workflow_root_port();
assert!(
!matches!(root, WorkflowRoot::AmbiguousRoot { .. }),
"fixture `{file}` produced AmbiguousRoot under α — α is a \
single-pick rule over linear d.nodes and must never tie. \
If this fires, an enumerate-all-eligible-entries rule has \
been wired and the audit's ambiguous-acceptance must move \
to its own consumer test."
);
}
}

// `workflow_root_zero_bind_returns_no_root` lives as a `#[cfg(test)]`
// unit test inside `src/v3/compiler/src/dag.rs` next to
// `Dag::workflow_root_port` itself, because constructing a truly
// empty `Dag` requires the crate-private `Dag::empty()` constructor.
// V3 surface syntax always lowers each top-level decl to a `Bind`, so
// the zero-Bind case is structurally unreachable from `compile_to_dag`
// fixtures and the `NoRoot` arm is defensive-only at the substrate
// boundary.
//
// See: dag.rs `mod tests::workflow_root_zero_bind_returns_no_root`.
36 changes: 36 additions & 0 deletions src/v3/spec/rust.dag
Original file line number Diff line number Diff line change
Expand Up @@ -1434,6 +1434,42 @@ data lane2_workflow_at_binding_rust: SubstrateAccessorBinding = {
language: rust_language
}

// Workflow-root accessor (Prereq-3a per `docs/design-lens-fold-prerequisites.md`).
// Renders to `Dag::workflow_root_port` per the α implementation at
// `src/v3/compiler/src/dag.rs`. The accessor takes only `&self`, so the
// `({p0})` template is a single positional argument receiver; the result
// is a `WorkflowRoot` value (no Optional wrapping; the carrier itself
// partitions SingleRoot / NoRoot / AmbiguousRoot fail-closed).
//
// Landing this binding alongside the substrate accessor closes the

This comment was marked as resolved.

// `substrate_accessor_universe` boundary for this slice — the
// emitter can resolve any `.dag` consumer of `workflow_root_port`
// without falling through to plain callable dispatch.
data rust_workflow_root_port_accessor: SubstrateAccessorRealization = {
carrier: "({p0}).workflow_root_port()"
}

data workflow_root_port_binding_rust: SubstrateAccessorBinding = {
accessor: workflow_root_port
realization: rust_workflow_root_port_accessor
language: rust_language
}

// `WorkflowRoot` substrate sum mirror — emitted Rust matches over
// `SingleRoot(PortId) | NoRoot | AmbiguousRoot { candidates }` need
// the substrate-to-Rust carrier mapping so generated code preserves
// the sum identity rather than rendering through a free name. The
// Rust enum lives at `src/v3/compiler/src/dag.rs:1936-1941` and is
// directly addressable as `WorkflowRoot` in the v3-compiler crate.
data rust_workflow_root: TypeRealization = {
language: rust_language
target: WorkflowRoot
carrier: "WorkflowRoot"
is_copy: false
fields: []
cost: 1
}

// `declaration_by_id(d: Dag, id: DeclarationId) -> Declaration?` —
// id-keyed parity with `port` / `node`. Host realization is
// `Dag::declaration_opt(&DeclarationId)`; `.cloned()` mirrors the
Expand Down
67 changes: 67 additions & 0 deletions src/v3/std/substrate.dag
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,73 @@ fn lane2_workflow_at(d: Dag, id: NodeId) -> WorkflowEffect? {
host lane2_workflow_effect_at
}

// Workflow-root identification (Director-locked α; per
// `docs/design-lens-framework.md` Q-shape and merged audit
// `docs/design-lens-fold-prerequisites.md` §"Prereq-3a").
//
// 🟡 SCAFFOLD coproduct. The three arms partition every legitimate
// `Dag` exactly once at the substrate-load boundary:
//
// - `SingleRoot(p)` — α (last topological `Bind`) selected `p` as
// the workflow-root port. Emitted whenever the Dag contains at
// least one `Bind`; multiple Binds are NOT ambiguous under α —
// the linear `d.nodes` order picks exactly one last element by
// definition. `p` is the chosen Bind's `result_port`.
// - `NoRoot` — zero `Bind` behaviors in `d.nodes`. The fold short-
// circuits (`fold_lens<C>` returns `DimensionFail` with a
// no-root diagnostic; `R2-Evaluator.evaluate(...)` rejects).
// - `AmbiguousRoot { candidates }` — reserved for the
// enumerate-all-eligible-entries rule that the runtime evaluator
// (`evaluate(program, entry, args)` per Items 4+5 / #1176 §3.2)
// consumes for multi-entry programs. The α / γ "last X Bind"
// rules cannot populate this arm over a linear `Dag.nodes` —
// both pick exactly one element by definition. Pinned here as a
// fail-closed surface for the future enumerate-all consumer
// wiring; today's α implementation never emits this variant.
//
// Pattern 1 (per-call total fact) was rejected: `-> PortId` would
// fabricate or panic on `NoRoot` cases. Pattern 2 (collapse to
// `Option<PortId>`) loses the `AmbiguousRoot` channel for the
// multi-entry consumer. Pattern 3 (this sum) is the dissolution
// path: each variant carries the structural reason its arm was
// taken; consumers dispatch fail-closed without fabrication.
//
// Dissolution: γ refinement (last `UserCallable` `Bind`) and the
// enumerate-all rule both reuse this same partition behind the
// `workflow_root_port` accessor; no carrier change required.
type WorkflowRoot
= SingleRoot(PortId)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Already fixed in working tree (commit pending after regen). Line 527 now reads | AmbiguousRoot { candidates: NonSingletonList<PortId> }. The Rust mirror at dag.rs pub enum WorkflowRoot updates to candidates: NonSingletonList<PortId> to match. NonSingletonList<T> is the existing substrate primitive imported on line 5; it makes empty and singleton candidate sets structurally unrepresentable. Will push with regen output. — sent from tidy-wolf-507

| NoRoot
| AmbiguousRoot { candidates: NonSingletonList<PortId> }

// Workflow-root accessor (per merged audit Prereq-3a). Implements
// the α rule: last topological `Bind` in `d.nodes`. Returns
// `WorkflowRoot::SingleRoot(b.result_port)` when the Dag contains
// at least one Bind (α picks the last one); `NoRoot` when zero
// Binds; never `AmbiguousRoot` under α (linear `d.nodes` cannot
// tie — multiple Binds are not ambiguous, α just picks the last).
// Shared authority — both `fold_lens<C>` (workflow-root
// identification for the equivalence target) and R2-Evaluator
// (runtime entry-point identification) are the planned consumers.
//
// 🟡 BOUNDED STAGING on the realization side. The declaration +
// Rust impl + Rust binding (`rust_workflow_root_port_accessor` +
// `workflow_root_port_binding_rust` in `src/v3/spec/rust.dag`) land
// in Prereq-3a (this PR), which puts the accessor in
// `substrate_accessor_universe` so any `.dag` consumer that
// references it lowers to the Rust template. Per-target
// Python/Go bindings are NOT in this PR. Trigger: when a Python
// or Go emitter first consumes `workflow_root_port`, the
// corresponding `SubstrateAccessorBinding` lands atomically with
// that consumer — same staging discipline as `lane2_workflow_at`
// (Rust binding landed first because the lens runtime needs it;
// other targets land when they emit a consumer). Today's path
// fails closed at emit time for the unbound targets via
// `EmitError::MissingSubstrateAccessorRealization`.
fn workflow_root_port(d: Dag) -> WorkflowRoot {

This comment was marked as resolved.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Intentionally out of scope for Prereq-3a per the merged audit docs/design-lens-fold-prerequisites.md. The cursor approve at sha 9aaee8e7 named this exactly: "When something first emits a transform to workflow_root_port, rust_target.rs will require a SubstrateAccessorBinding in rust.dag (same pattern as lane2_workflow_at); this slice does not add that yet, which is fine until an emitter references it—the emitter path already fails closed with a clear error if a binding is missing."

Prereq-3a (this slice) lands the substrate accessor + Rust impl + Rust-side consumer tests; the four acceptance tests use Dag::workflow_root_port directly from hand-written Rust, no emission path required. Prereq-3b (fold_lens<C> machinery) is the first generated consumer and lands the TypeRealization + SubstrateAccessorBinding wiring atomically with the .dag consumer that needs them. Splitting the substrate land from the emission wiring matches the existing pattern at lane2_workflow_at (carrier + accessor + Rust impl land first; per-target binding lands when an emitter consumes it). — sent from tidy-wolf-507

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Stale finding — both items already landed in current head (post your sha). rust_workflow_root: TypeRealization lives at src/v3/spec/rust.dag (added at head aa5da8ca7); workflow_root_port_binding_rust: SubstrateAccessorBinding lives at src/v3/spec/rust.dag (added at head dc0b369d2). The codex non-blocking strength review at sha d4d4e854 independently confirms: "the mixed substrate/code/test slice now lands the sum, accessor, Rust realization, and coverage together without a new substrate authority split." — sent from tidy-wolf-507

host workflow_root_port
}

// `Lookup<DeclarationId>` monomorphized constructors (same shim story as
// `miss_int_lookup` in `v3.std.lookup` and `miss_symbolic_cost_lookup` in
// `std.algebra`). Lives here so `infer_helpers.dag` can call them via
Expand Down
Loading