Repository navigation
feat(v3): workflow_root_port accessor + WorkflowRoot sum (Prereq-3a) - #1232
Conversation
# Conflicts: # src/v3/compiler/src/bootstrap_generated.rs # src/v3/compiler/src/bootstrap_generated_without_parse_surface.rs
- Add method_template_contract_test.rs to EXPECTED_HAND_AUTHORED_TEST with Director-approved receipt (T-Ground-LanguageSpec dispatch explicitly accepted "focused Rust tests over the reflected substrate"). - Refresh parse_corpus_manifest.txt entry for src/v3/std/emit_model.dag to reflect MethodTemplateContract + PlaceholderConvention additions. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
# Conflicts: # src/v3/compiler/src/bootstrap_generated.rs # src/v3/compiler/src/bootstrap_generated_without_parse_surface.rs
Re-regenerate v3 bootstrap so MethodTemplateContract + PlaceholderConvention land on top of main after merging origin/main (carrier shape unchanged). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
First substrate slice for the lens framework (docs/design-lens-framework.md, docs/briefs/r2-substrate-manager.md). Director-locked option (c) on parent inbox #1130. Substrate changes: - New src/v3/std/lens.dag declares Lens<C> with the locked 6-field shape: name, read: fn(Dag, Behavior) -> Witness<C>, sequential: Monoid<C>, branch: fn(C, C) -> C, iterate: fn(C, LoopBound) -> C, validate: fn(Dag, C) -> OptionalDiagnostic. Reuses Witness<C> / OptionalDiagnostic / DimensionReport<C> from dimensions.dag and Monoid<C> from dsl/std/algebra.dag — no parallel reps introduced. - diagnostics.dag: Q6.5 two-layer authority. Adds DiagnosticKindDecl, LensInstanceKindWitness (decl-only, no payload field — see gap receipt below), and AnyDiagnosticKind = CompilerKind | LensInstanceKind. Widens Diagnostic.kind from CompilerDiagnosticKind to AnyDiagnosticKind. CompilerDiagnosticKind closed sum unchanged (anti-bridge invariant). Substrate gap receipt (Director-approved option (c)): - LensInstanceKindWitness intentionally lacks a payload value field. Today's .dag grammar cannot express `payload: <inhabits kind_decl.payload>` (refinement-type-on-sibling- field). The flat alternative ratifies the illegal-state Q6.5 rejected (Lens / name / payload-shape three independent coords). Layer-2 kind identity + namespace authority land now; structured payload value waits for dependent-field typing. Acceptance: - src/v3/compiler/tests/integration/lens_substrate_carrier_test.rs: Lens<C> 6-field shape, Diagnostic.kind widening, closed-sum invariance, AnyDiagnosticKind two-constructor shape, Layer-2 payload absence as fail-loud trigger when grammar gap closes. - SG-0 ratchet receipt added with Director acceptance citation. - parse_corpus_manifest.txt refreshed via refresh_handwritten_parse_snapshot_manifest -- --ignored. Out of scope (deferred to subsequent lanes): - Migration of cost.dag / complexity.dag / idempotency.dag / parallelism.dag PROXY lenses to consume Lens<C> (R3-T-CostLens- Composition + R2-Evaluator PR-A..E). - fold_lens<C> generic fold machinery (I2 in design doc). - User-authored lens TestClaim wiring (I7 in design doc). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Strengthen LensInstanceKindWitness SCAFFOLD comment to call out that bare `DeclarationRef` for `kind_decl` is part of the SAME dissolution trigger as the deferred payload typing — substrate-level refinement-typing-on-DeclarationRef closes both the payload-typing gap and the kind-decl resolution gap in one move. Cites the analogous PatternRealization and MethodTemplateContract.dag_method patterns. Addresses non-blocking codex BLOCKING relay at sha fa5bba2 (Layer-2 diagnostic-kind witness leaving its core authority unconstrained) — shape unchanged per Director-locked option (c) on parent inbox #1130; just makes the bounded-scaffold receipt fully explicit on this row. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
# Conflicts: # src/v3/compiler/src/bootstrap_generated.rs # src/v3/compiler/src/bootstrap_generated_without_parse_surface.rs
Re-regenerate v3 bootstrap so Lens<C> + Q6.5 widening land on top of latest main after the merge conflict resolution. Refresh parse manifest. Carrier shape unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
# Conflicts: # src/v3/compiler/src/bootstrap_generated.rs # src/v3/compiler/src/bootstrap_generated_without_parse_surface.rs
Re-regenerate v3 bootstrap so Lens<C> + Q6.5 widening land on top of main after #1188 fixed the v2-extdeps regression. Refresh parse manifest. Carrier shape unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
briansrls
left a comment
There was a problem hiding this comment.
Review metadata
- Provider / model:
codex/unknown - Commit:
d4d4e854· Trigger:schedule - Thinking:
176s wall
Non-blocking — Strengths
src/v3/std/substrate.dagThe mixed substrate/code/test slice now lands the sum, accessor, Rust realization, and coverage together without a new substrate authority split.
✅ No blocking concerns in the current diff.
# Conflicts: # src/v3/compiler/src/bootstrap_generated.rs # src/v3/compiler/src/bootstrap_generated_without_parse_surface.rs
|
Review metadata
APPROVE — small, well-scoped slice. The Tests are appropriate: integration claims pin α behavior ( Nothing in the diff violates INVARIANTS, modeling discipline, CODING, or TESTING that I can see. |
|
CI diagnosis for current head Failing test: So after the rebase, the test still expects
Focused rerun after patch: cargo test -p v3-compiler --test integration substrate_accessor_rust_binding_invariants -- --nocapture
cargo run -p v3-compiler --features bootstrap-regen-fresh --bin regen_bootstrap -- --verify— sent from jolly-ram-908 |
| // 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 { |
There was a problem hiding this comment.
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
|
Review metadata
Verdict: APPROVE — The diff adds a substrate-declared |
|
Verified at current head |
briansrls
left a comment
There was a problem hiding this comment.
Review metadata
- Provider / model:
codex/unknown - Commit:
6dae6c4b· Trigger:schedule - Thinking:
296s wall
Non-blocking — Strengths
src/v3/std/substrate.dagThe substrate sum, accessor, Rust binding, and tests now land as one coherent reflected-fact slice.
✅ No blocking concerns in the current diff.
|
Review metadata
Findings (if any): Verdict: APPROVE — Substrate and Rust stay aligned, the new sum is explicitly classified and fail-closed, and boundary wiring (accessor universe + regen) matches existing |
# Conflicts: # src/v3/compiler/src/bootstrap_generated.rs # src/v3/compiler/src/bootstrap_generated_without_parse_surface.rs
|
Review metadata
FindingsNo blocking issues. Nothing in the diff clearly breaks INVARIANTS.md (P1–P5), docs/modeling-discipline.md (the new sum is annotated SCAFFOLD with a named dissolution path), CODING.md, or TESTING.md (integration placement and SG-0 census rationale match the documented exceptions / dispatch notes). VerdictAPPROVE — Prereq-3a is implemented coherently: a substrate Exploratory observations (optional)In |
|
Review metadata
Clean diff. Substrate-level WorkflowRoot sum is properly modeled (illegal states unrepresentable via NonSingletonList in the Ambiguous arm), accessor is fail-closed, scaffold has documented/bounded/triggered dissolution, and tests pin all four arms including the "drift trigger" for unreachable-under-α ambiguity. Verdict: APPROVE — small, narrowly scoped Prereq-3a slice. Substrate sum + accessor land together with .dag receipts, Rust binding, and α-pinning tests. No invariant or modeling-discipline violations observed in the diff. |
briansrls
left a comment
There was a problem hiding this comment.
Review metadata
- Provider / model:
codex/unknown - Commit:
b3408373· Trigger:schedule - Thinking:
366s wall
✅ No blocking concerns in the current diff.
* WIP: tidy-wolf-507 * WIP: tidy-wolf-507 * chore: apply cargo fmt * WIP: tidy-wolf-507 * chore: apply cargo fmt * WIP: tidy-wolf-507 * WIP: tidy-wolf-507 * fix(v3): SG-0 ratchet receipt + parse-corpus manifest refresh - Add method_template_contract_test.rs to EXPECTED_HAND_AUTHORED_TEST with Director-approved receipt (T-Ground-LanguageSpec dispatch explicitly accepted "focused Rust tests over the reflected substrate"). - Refresh parse_corpus_manifest.txt entry for src/v3/std/emit_model.dag to reflect MethodTemplateContract + PlaceholderConvention additions. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * WIP: tidy-wolf-507 * chore(v3): re-regen bootstrap on top of merged main Re-regenerate v3 bootstrap so MethodTemplateContract + PlaceholderConvention land on top of main after merging origin/main (carrier shape unchanged). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * WIP: tidy-wolf-507 * feat(v3): T-Substrate-Lens-Primitive — Lens<C> carrier + Q6.5 widening First substrate slice for the lens framework (docs/design-lens-framework.md, docs/briefs/r2-substrate-manager.md). Director-locked option (c) on parent inbox #1130. Substrate changes: - New src/v3/std/lens.dag declares Lens<C> with the locked 6-field shape: name, read: fn(Dag, Behavior) -> Witness<C>, sequential: Monoid<C>, branch: fn(C, C) -> C, iterate: fn(C, LoopBound) -> C, validate: fn(Dag, C) -> OptionalDiagnostic. Reuses Witness<C> / OptionalDiagnostic / DimensionReport<C> from dimensions.dag and Monoid<C> from dsl/std/algebra.dag — no parallel reps introduced. - diagnostics.dag: Q6.5 two-layer authority. Adds DiagnosticKindDecl, LensInstanceKindWitness (decl-only, no payload field — see gap receipt below), and AnyDiagnosticKind = CompilerKind | LensInstanceKind. Widens Diagnostic.kind from CompilerDiagnosticKind to AnyDiagnosticKind. CompilerDiagnosticKind closed sum unchanged (anti-bridge invariant). Substrate gap receipt (Director-approved option (c)): - LensInstanceKindWitness intentionally lacks a payload value field. Today's .dag grammar cannot express `payload: <inhabits kind_decl.payload>` (refinement-type-on-sibling- field). The flat alternative ratifies the illegal-state Q6.5 rejected (Lens / name / payload-shape three independent coords). Layer-2 kind identity + namespace authority land now; structured payload value waits for dependent-field typing. Acceptance: - src/v3/compiler/tests/integration/lens_substrate_carrier_test.rs: Lens<C> 6-field shape, Diagnostic.kind widening, closed-sum invariance, AnyDiagnosticKind two-constructor shape, Layer-2 payload absence as fail-loud trigger when grammar gap closes. - SG-0 ratchet receipt added with Director acceptance citation. - parse_corpus_manifest.txt refreshed via refresh_handwritten_parse_snapshot_manifest -- --ignored. Out of scope (deferred to subsequent lanes): - Migration of cost.dag / complexity.dag / idempotency.dag / parallelism.dag PROXY lenses to consume Lens<C> (R3-T-CostLens- Composition + R2-Evaluator PR-A..E). - fold_lens<C> generic fold machinery (I2 in design doc). - User-authored lens TestClaim wiring (I7 in design doc). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * WIP: tidy-wolf-507 * docs(v3): explicitly cover kind_decl resolution gap in SCAFFOLD comment Strengthen LensInstanceKindWitness SCAFFOLD comment to call out that bare `DeclarationRef` for `kind_decl` is part of the SAME dissolution trigger as the deferred payload typing — substrate-level refinement-typing-on-DeclarationRef closes both the payload-typing gap and the kind-decl resolution gap in one move. Cites the analogous PatternRealization and MethodTemplateContract.dag_method patterns. Addresses non-blocking codex BLOCKING relay at sha fa5bba2 (Layer-2 diagnostic-kind witness leaving its core authority unconstrained) — shape unchanged per Director-locked option (c) on parent inbox #1130; just makes the bounded-scaffold receipt fully explicit on this row. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(v3): re-regen bootstrap on top of merged main Re-regenerate v3 bootstrap so Lens<C> + Q6.5 widening land on top of latest main after the merge conflict resolution. Refresh parse manifest. Carrier shape unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(v3): re-regen bootstrap on top of merged main (#1188 fix) Re-regenerate v3 bootstrap so Lens<C> + Q6.5 widening land on top of main after #1188 fixed the v2-extdeps regression. Refresh parse manifest. Carrier shape unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * WIP: tidy-wolf-507 * feat(v3): minimal method-declaration registry + MethodRef refinement Closes the dag_method: DeclarationRef substrate gap from #1175 by landing the smallest structurally honest method-name registry and refining MethodTemplateContract.dag_method to typed MethodRef. Director-locked options A/A/(a) on parent inbox #1130. Substrate changes: - New dsl/std/methods.dag: MethodDeclaration { name: String } + 63 data <name>_method bindings (full union of unique names from the 7 dsl/std/algebra.dag per-profile template lists). - New src/v3/std/methods.dag: MethodRef { decl: DeclarationRef }. Lives in v3-space because dsl/std/ stays v3-spec-free per the existing layering convention. - src/v3/std/emit_model.dag: MethodTemplateContract.dag_method refined from bare DeclarationRef to MethodRef. - src/v3/compiler/src/bootstrap_regen_fresh.rs: dsl/std/methods.dag added to the v3 std-fixture allow-list. Acceptance: - src/v3/compiler/tests/integration/method_registry_test.rs: 4 structural claims — registry covers all 63 algebra-template names (drift-detection), MethodDeclaration identity-only, MethodTemplateContract.dag_method refines to MethodRef, MethodRef is single-field decl wrapper. - SG-0 ratchet receipt added with Director-acceptance citation. - parse_corpus_manifest.txt refreshed. Out of scope (Grounding-owned and follow-up): - Algebra template-row rewrite to import/reference the typed decls. - Grounding MethodTemplateContract row population. - MethodTranslation / SimpleMethodSpec retirement. - Refining decl: DeclarationRef to DeclarationRef<MethodDeclaration> (same trigger as PatternRealization / LensInstanceKindWitness). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * WIP: tidy-wolf-507 * test(v3): tighten method-registry authority enforcement Per codex REQUEST_CHANGES at sha d6216b8: existence-by-name was behavioral rather than enforced. method_registry_covers_all_algebra_ template_names now verifies each <name>_method binding (1) has a TypeConnective::Instantiation pointing at MethodDeclaration, and (2) carries a Structural value_body with name = String literal matching the expected method name. The drift trigger named in the .dag file's documentation is now actually enforced fail-closed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: apply cargo fmt * WIP: tidy-wolf-507 * test(v3): registry drift derives names from algebra.dag source Per codex REQUEST_CHANGES at sha fdaaee5: hand-maintained EXPECTED_METHOD_NAMES could drift in lock-step with the registry, both staying out-of-sync with algebra.dag without failing the test. method_registry_covers_all_algebra_template_names now include_str!s `dsl/std/algebra.dag` and lexically extracts unique `name: "<id>"` literals from the per-profile template-list bodies (filtering for lowercase identifiers to skip type-shape names like `NamedTemplate { name: "Int" }`). algebra.dag is the actual authority — adding a new method name there without landing the registry binding now fails fail-closed at the same boundary the .dag SCAFFOLD comment promises. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(design): lens-fold prerequisites audit Director-approved option C on parent inbox #1130 after the T-Substrate-Lens-Primitive complexity-lens migration slice STOP+PINGed on the class-5 / fn-block-body grammar gap. Prerequisite audit (no code; no substrate edits) names the exact lowering work needed before data complexity_lens: Lens<Int> = { ... } can lower honestly: - Prereq-1: port-carried field values in data record bodies (closes class-5 gap #3 port-carried branch). - Prereq-2: fn block-body lowering with variant-constructor expressions (closes class-5 gap #4 + block-body restriction). - Prereq-3: fold_lens<C> generic fold + workflow-root identification (depends on Prereq-1 + Prereq-2). Surfaces the workflow-root identification question Director flagged for the M2 semantic interpretation with three options (last topological Bind / last lane2_workflow Bind / last UserCallable Bind) and a recommendation. Cross-references the existing Dimension<SymbolicCost> data-binding deferral at cost.dag:260-302 which has the same blocker. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * WIP: tidy-wolf-507 * docs(design): land Director dispositions on lens-fold prerequisites Director-accepted both #1207 decision points on parent inbox #1130 (2026-04-29): 1. Workflow-root identification = (α) last topological Bind, but only behind a named workflow_root_port(d: Dag) -> PortId helper/accessor. β rejected. γ remains a future refinement behind the same accessor. Cross-reference: workflow_root_port is shared authority for both fold_lens<C> and R2-Evaluator's runtime entry-point identification (Items 4+5 / #1176 §3.2). 2. Class-5 gap #4 strategy = infer-time re-resolution. No per-type special cases for Witness<C> / OptionalDiagnostic. Splits Prereq-3 into 3a (workflow_root_port accessor, ~1-2 days, standalone) and 3b (fold_lens<C> body, depends on Prereq-1 + Prereq-2 + 3a). 3a can land in parallel with Prereq-1 / Prereq-2 to unblock R2-Evaluator early. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(design): fail-closed WorkflowRoot return type for accessor Per BLOCKING review on PR #1207 at sha d34ca4a: a total workflow_root_port(d: Dag) -> PortId return drops the no-root and multi-entry cases. Refine the accessor's return type to a WorkflowRoot sum: type WorkflowRoot = SingleRoot(PortId) | NoRoot | AmbiguousRoot { candidates: List<PortId> } NoRoot and AmbiguousRoot are explicit fail-closed surfaces both consumers (fold_lens<C> and R2-Evaluator) handle without fabrication. Director's α / γ rules populate SingleRoot only when exactly one last-topological-Bind exists; partition is reusable across α and γ refinements. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(design): narrow Prereq-1 to Arrow-signature inhabitance Per BLOCKING review on PR #1207 sha d34ca4a: original framing was stale. Verified at lower.rs:3273-3565: lower_record_to_structural already handles nested Record/List/Map; lower_structural_field_value already resolves SurfaceExpr::Var/Path to FieldValue::Reference for DeclarationRef-typed and meta-tag-matching fields. The actual residual gap is narrower — Arrow-signature inhabitance for fn-typed fields like Lens<C>.read: fn(Dag, Behavior) -> Witness<C>. Prereq-1 sizing drops ~3-5 days → ~1-3 days. Total sequencing revised from ~11-17 days to ~9-15 days. Other prereqs unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(design): add Prereq-3a standalone acceptance for accessor Per BLOCKING review on PR #1207 sha 42bf818: Prereq-3a was allowed to land before 3b but only 3b had named acceptance, violating the reflected-facts invariant. Add four claims for Prereq-3a (single-Bind / zero-Bind / multi-Bind variant returns + R2-Evaluator cross-consumer proof) so the accessor has its own generated-consumer proof at the substrate-load boundary, independent of 3b's downstream fold correctness. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(design): resolve workflow-root contradiction in does-not-do list Per codex non-blocking finding on PR #1207 sha d34ca4a: the "does not commit to workflow-root interpretation" bullet contradicted the Director-locked α + accessor disposition added above. Strike the bullet and reference the locked disposition. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(design): fix stale -> PortId in Sub-slice 3a after WorkflowRoot refinement Per codex REQUEST_CHANGES on PR #1207 sha 8f48849: line 320 still said `workflow_root_port(d: Dag) -> PortId` while the rest of the doc had been updated to `-> WorkflowRoot` (the fail-closed sum from the earlier inline blocking review). Stale residue from the iterative refinements; fixed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(design): clarify AmbiguousRoot semantics under linear d.nodes Per codex non-blocking improvement on PR #1207 sha 96c9901: under α (last topological Bind) and Dag.nodes being a linear order, ambiguity cannot arise by construction. AmbiguousRoot is reserved for the γ refinement (last UserCallable Bind) where multiple Binds can tie. α acceptance for the claim is now vacuous-but-wired: a fixture where γ would tie still returns SingleRoot under α; the AmbiguousRoot exercise lands when γ wires. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(design): AmbiguousRoot reserved for enumerate-all rule, not α/γ Per BLOCKING review on PR #1217 sha f2f3128: γ is also "last X Bind" over linear Dag.nodes — same linearity property as α — so neither rule can produce a tie by construction. The previous deferral of AmbiguousRoot to γ left the fail-closed sum arm without a realizable acceptance path. Honest fix: AmbiguousRoot is reserved for a separate enumerate-all-eligible-entries rule that R2-Evaluator's evaluate(program, entry, args) needs for entry-name disambiguation across multi-entry programs (the runtime takes an entry-name arg precisely because of this case). That rule returns every UserCallable Bind's result_port as candidates; R2-Evaluator matches by entry name. Three concrete acceptance sub-claims now pin the realizable case. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * WIP: tidy-wolf-507 * feat(v3): workflow_root_port accessor + WorkflowRoot sum (Prereq-3a) First substrate slice from the merged audit at docs/design-lens-fold-prerequisites.md. Implements the Director-locked α rule (last topological Bind) behind a fail-closed WorkflowRoot accessor that fold_lens<C> and R2-Evaluator share. Substrate changes: - src/v3/std/substrate.dag: declare WorkflowRoot sum (SingleRoot(PortId) | NoRoot | AmbiguousRoot { candidates }) plus fn workflow_root_port(d: Dag) -> WorkflowRoot { host workflow_root_port }. AmbiguousRoot is reserved for the future enumerate-all-eligible- entries rule (multi-entry programs / R2-Evaluator entry-name disambiguation per Items 4+5 / #1176 §3.2); α is a single-pick rule over linear d.nodes and never emits AmbiguousRoot. - src/v3/compiler/src/dag.rs: WorkflowRoot Rust enum mirror + Dag::workflow_root_port α impl (walks d.nodes backward, returns SingleRoot at the first Behavior::Bind, NoRoot when none). Acceptance: - src/v3/compiler/tests/integration/workflow_root_port_test.rs: three integration claims via real compile_to_dag fixtures — single-Bind / multi-Bind-under-α / unreachable-AmbiguousRoot drift trigger. - src/v3/compiler/src/dag.rs#tests::workflow_root_zero_bind_returns_no_root: unit test for the defensive NoRoot arm via crate-private Dag::empty (v3 surface always lowers ≥1 Bind, so the case is unreachable from compile_to_dag fixtures but real at the substrate boundary). - SG-0 ratchet receipt + parse_corpus_manifest refresh. Out of scope (Prereq-3b and beyond): - fold_lens<C> generic fold machinery. - Lens<C> instance authoring (data complexity_lens). - R2-Evaluator entry-point integration (the runtime cross-consumer). - γ refinement / enumerate-all rule. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: apply cargo fmt * docs(v3): fix SingleRoot comment to match α multi-Bind behavior Per manager review on PR #1232: the .dag SingleRoot comment said "exactly one workflow-root Bind exists" which contradicted the Director-locked α semantics (multiple Binds are not ambiguous — α just picks the last). Match the comment to the Rust impl: α emits SingleRoot whenever the Dag contains at least one Bind. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * WIP: tidy-wolf-507 * fix(v3): WorkflowRoot NonSingletonList + Rust scaffold receipt Per codex BLOCKING review on PR #1232 sha ed8997c: 1. AmbiguousRoot.candidates → NonSingletonList<PortId> on both surfaces (substrate.dag + Rust mirror). Empty/singleton candidate sets are now structurally unrepresentable; ambiguity by definition requires ≥2 candidates. 2. Rust pub enum WorkflowRoot now carries the full 🟡 SCAFFOLD receipt mirroring the .dag — three-arm partition + named dissolution trigger (γ refinement / enumerate-all rule reuse the same partition behind the workflow_root_port accessor). Also refresh parse manifest + module header in workflow_root_port_test.rs (corrected the "three claims" list to match the actual integration tests; zero-Bind unit test lives in dag.rs#tests). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(v3): re-regen bootstrap on top of merged main Re-regenerate v3 bootstrap so WorkflowRoot + workflow_root_port land on top of latest main. Refresh parse manifest. Carrier shape unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(v3): explicit BOUNDED STAGING SCAFFOLD receipt on workflow_root_port Per codex REQUEST_CHANGES on PR #1232 sha 628910c: name the realization-side staging explicitly with a bounded scaffold receipt naming the first-emitter-consumer trigger. The accessor declaration + Rust impl + Rust-side consumer tests land in Prereq-3a (this PR); the per-target SubstrateAccessorBinding lands atomically with the first .dag consumer (Prereq-3b fold_lens<C> is planned first), same staging discipline as lane2_workflow_at. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * WIP: tidy-wolf-507 * feat(v3): land Rust SubstrateAccessorBinding for workflow_root_port Per codex BLOCKING on PR #1232 sha 8bb6dc7: the prior bounded-staging receipt was correct that Python/Go bindings stage, but wrong about the Rust binding being optional in this slice. Without ANY binding the accessor is not in substrate_accessor_universe, so a .dag consumer falls through to plain callable dispatch — not fail-closed. Add rust_workflow_root_port_accessor (carrier "({p0}).workflow_root_port()") and workflow_root_port_binding_rust to src/v3/spec/rust.dag, matching the lane2_workflow_at precedent. The accessor is now in substrate_accessor_universe; any .dag consumer lowers correctly under Rust target. Python/Go bindings remain staged for when those targets emit consumers (per BOUNDED STAGING receipt update on the substrate.dag accessor). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(v3): bump substrate-accessor binding count to 6 for workflow_root_port substrate_accessor_rust_binding_invariants asserts an exact count of Rust bindings; bumping to 6 (was 5) for the new workflow_root_port_binding_rust added at PR #1232. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * WIP: tidy-wolf-507 * feat(v3): add rust_workflow_root TypeRealization Per codex BLOCKING on PR #1232 sha 4ed2a8e: the WorkflowRoot substrate sum was added without registering its Rust TypeRealization. Generated Rust matches over WorkflowRoot need the carrier mapping so the substrate sum identity flows through emission rather than rendering through a free name. Adds data rust_workflow_root: TypeRealization { language: rust_language, target: WorkflowRoot, carrier: "WorkflowRoot", is_copy: false, fields: [], cost: 1 } following the rust_behavior precedent. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * WIP: tidy-wolf-507 * WIP: tidy-wolf-507 * docs(design): Prereq-X audit — call-on-field-access for fold_lens<C> Director-approved option (b) on parent inbox #1130 after the fold_lens<C> HO field-call smoke confirmed v3 surface grammar does not support call-on-field-access. Records four exact parse failures (w.f(x), (w.f)(x), let g=...; g(x), brace-block let-then-call) and splits the prerequisite into three implementation slices: - X1: call-on-field-access dispatch (Arrow-typed expression callee). - X2: call-on-Var Arrow-typed dispatch (likely implicit in X1). - X3: brace-block let-expression inside `=` fn bodies. Maps each to lens.read / lens.sequential.op / lens.branch / lens.iterate / lens.validate dispatch paths. Notes that the lens-fold-prerequisites audit at #1207 conflated field assignment (Prereq-1, landed) with field invocation (Prereq-X, missing). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(design): Director-lock explicit block syntax for Prereq-X3 Per parent inbox #1130 (2026-04-30): record explicit block syntax (`do { ... }` proposed) as the X3 disambiguation strategy, not heuristic first-token lookahead. Reasons: `{ ... }` already has live record + map literal meanings; #1248 just tightened that ambiguity surface; explicit marker is unambiguous and cost-of- change-zero for future block-internal forms. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(design): name TransformTarget::IndirectCall extension for X1 runtime-callee Per gpt-5-5-thinking REQUEST_CHANGES on PR #1264 sha 8daec0b: the audit said "higher-order Transform target" without naming the substrate carrier. Updated to: - Split the lowerer impact into L1.a (statically-resolvable callee, reuses TransformTarget::Callable, no substrate change) and L1.b (runtime-sourced callee, requires new TransformTarget::IndirectCall { callee: PortId } variant). - Name TransformTarget::IndirectCall as the substrate extension with permanent (non-SCAFFOLD) lifecycle — HO dispatch is a real long-term language surface, not staging. - Sequence: L1.a first (no substrate change), L1.b second. - Note that fold_lens<C> itself depends on L1.b because `lens` is a function parameter, not a static binding — L1.a alone does not unblock the consumer the audit was scoped to enable. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(design): IndirectCall as discriminator-only variant; callee in inputs[0] Per BLOCKING inline on PR #1264 sha 172bb2c at line 153: putting callee: PortId on the variant payload would put a runtime dependency outside TransformNode.inputs, violating Facts Flow Forward / Every Dependency Is A Substrate Fact. Reflected consumers walk inputs to derive dependencies; a separate-field callee would be invisible to that walk. Refine the design: TransformTarget::IndirectCall is discriminator- only (no payload). inputs[0] carries the callee port, inputs[1..] carry args. Single dependency authority preserved; arity arithmetic becomes inputs.len() - 1 for IndirectCall. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(design): structural Callee/Arg tagging for TransformNode.inputs Per gpt-5-5-thinking REQUEST_CHANGES on PR #1264 sha d4d50c0: the inputs[0]-by-convention encoding admits illegal states (empty inputs, non-Arrow first input) and pushes enforcement to later type-checking, violating illegal-states-unrepresentable. Refine the design: TransformNode.inputs becomes Vec<TransformInput> where TransformInput = Arg(PortId) | Callee(PortId). For IndirectCall, exactly one element is Callee(_); the rest are Arg. Single dependency authority preserved (inputs.iter() still walks every dependency port). Variant tag makes the boundary structural. Plus constructor-API enforcement: Dag::push_indirect_call_transform is the only way to build an IndirectCall transform; validates Arrow-typed callee + arity at construction time. Cardinality ("exactly one Callee") enforced by builder + debug assert until v3 supports refined enum payload. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(design): use post-lock `do { ... }` block surface in T2.1 fixture Per cursor exploratory note on PR #1264 sha d4d50c0: T2.1's fixture used the pre-lock { ... } form, inconsistent with X3's locked `do { ... }` discipline. Update to use `do { ... }` so the matrix matches the locked surface. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(design): emitter contract uses TransformInput tag, not inputs[0] Per gpt-5-5-thinking REQUEST_CHANGES on PR #1264 sha ea53938: the emitter section reintroduced the positional convention the audit explicitly rejected — said "use inputs[0] as callee" while the structural invariant section said the Callee tag is the single authority. Fix: emitter partitions inputs by TransformInput tag (find the unique Callee element; project Arg elements in order); fails closed via EmitError::MalformedIndirectCall if Callee is missing or duplicated. Positional authority explicitly rejected. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * WIP: tidy-wolf-507 * chore: apply cargo fmt * docs(design): make S2 span description meaningful Per cursor exploratory note on PR #1264 sha ea53938: the [...] span placeholder in S2 was ambiguous. Replace with a meaningful description ("at the leading `(` of the parenthesized callee") so the audit's regression-fixture purpose is self-explanatory even without exact byte offsets. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(design): real S2 span + remove stray integration.rs blank line Per cursor APPROVE_WITH_COMMENTS on PR #1264 sha de24278: 1. S2 span placeholder replaced with real byte offsets [106, 107] (leading `(` of parenthesized callee in the smoke fixture), matching the verbatim-evidence bar S1/S3/S4 set. 2. Stray blank line in src/v3/compiler/tests/integration.rs from the earlier S2-probe cleanup removed; integration.rs now matches origin/main exactly so the acceptance bullet ("no code changes") is honest. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(design): collapse target+inputs into TransformDispatch sum (X1) Per gpt-5-5-thinking REQUEST_CHANGES on PR #1264 sha de24278: the Vec<TransformInput> tagged-element approach left cardinality of Callee per IndirectCall as a cross-field invariant enforced by builder + debug assert, not by the type. Failed illegal-states-unrepresentable. Resolution: collapse TransformNode.target and TransformNode.inputs into a single typed sum TransformDispatch with one variant per dispatch shape, each carrying its own structured fields (Callable { callee: DeclarationId, args }, Indirect { callee: PortId, args }, etc.). Cardinality and target/callee compatibility are both expressed in the type: - Callable / FieldProject / Operator cannot carry runtime callee ports (no callee: PortId field). - Indirect cannot omit its callee (single field, not Option, not Vec). - Multi-callee Indirect is impossible (single field, not Vec). - Callable.callee is DeclarationId (compile-time); Indirect.callee is PortId (runtime); type system separates them. Single-authority dependency walk preserved via TransformDispatch::input_ports() iterator. EmitError::MalformedIndirectCall retires — malformed state unrepresentable. Migration cost noted: substantial refactor of TransformNode and all consumers walking target/inputs separately. Implementation worker scopes the migration; audit only locks the target shape. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(design): ArrowPortRef typed handle for IndirectCall.callee Per BLOCKING inline on PR #1264 sha de24278 line 215: Indirect.callee: PortId admits non-Arrow callees with API-level enforcement only behavioral. Refine to ArrowPortRef — Track-9 named-typed-handle wrapping PortId with Arrow-type proof, constructable only via Dag::resolve_arrow_port which validates the port's producer signature at construction. Non-Arrow callees become structurally unrepresentable: - ArrowPortRef's constructor is private to the dag module. - Outside callers go through resolve_arrow_port, which returns Err(NonArrowPortError) on non-Arrow ports. - Indirect { callee: ArrowPortRef, ... } can only be built with a validated ArrowPortRef. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(design): fix "against existing the existing" typo Per codex exploratory note on PR #1264 sha 69ee59a. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(design): clarify X3 'if Director confirms' refers to need not syntax Per cursor exploratory note on PR #1264 sha 79af7aa: X3's syntax is already Director-locked to explicit block markers earlier in the doc; only whether X3 is required for fold_lens<C> remains open. Tighten the acceptance section to disambiguate. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(design): close arity gap — OperatorCall fixed-arity + ArityCheckedArgs Track-9 handle Operator arity now encoded in OperatorCall sum (Unary/Binary); call-shape args wrapped in ArityCheckedArgs typed handle validated by Dag::resolve_call_args against the resolved Arrow signature. Malformed arity is structurally unrepresentable rather than convention-level. * docs(design): align L1.b sequencing bullet with ArityCheckedArgs typed handle * docs(design): bind args proof to dispatch target via atomic construction ArityCheckedArgs as a free-floating proof admitted reattaching args validated against signature A to a dispatch built for target B. Replaced with crate-private variant fields + Dag-level builders (push_callable/field_project/indirect_transform) that fuse target resolution and arity/type validation in one step. The proof and target are co-constructed; no public path can split them. * docs(design): add TransformDispatch dissolution ledger (🟢/🟡/🔴) Per modeling-discipline coproduct classification: - 🟢 Operator: keep (true user-input-boundary; primitives have no DeclarationId) - 🟡 Callable/FieldProject/Indirect: future-dissolve to Call { callee: CalleeRef, args }; separate today only because emitter rendering and args co-construction bind per-variant - 🔴 none Tracking gate for the 🟡 collapse: emitter callee-rendering split. * docs(design): align builder name to push_indirect_transform * docs(design): close pub-enum-field gap — wrap dispatch variants in pub(crate)-field structs Reviewer caught: pub enum with named-field variants exposes those fields publicly, so 'crate-private fields' was a false claim. Replaced named-field variants with tuple-struct payloads (CallableDispatch, FieldProjectDispatch, IndirectDispatch) whose fields are pub(crate). Outside the dag module, literal construction is blocked by the type system; only the Dag builder produces them. OperatorCall stays a plain pub enum since its variants witness no signature. * docs(design): scope dispatch payload fields to module-private (not pub(crate)) Reviewer caught: pub(crate) fields permit any in-crate module to construct CallableDispatch { ... } literally, bypassing the Dag builder that binds args to target. Switched to module-private (no visibility modifier) so only code inside the dag module can construct the payloads. Aligns ArrowPortRef precedent (already module-private). * docs(design): reclassify Operator from 🟢 to 🟡 per ArithOp dissolution example Modeling-discipline Practice 4's canonical example is ArithOp → Apply { function: FunctionRef } pointing at std::int::add. OperatorCall is structurally that case; absence of a current DeclarationId for + / - / unary ! is not the same as 'no richer source exists.' Tracking gate: std/{int,bool,float}/ declaring operator-algebra witness functions and parser desugaring operator tokens to Call(FunctionRef). * docs(design): harmonize dispatch variant snippets to tuple-payload form * docs(design): split FieldProject (pure projection) and FieldCall (invocation) Reviewer caught: collapsing plain field access and field invocation into one variant with optional args admits a malformed state where projection has args or invocation has none. Different state families should be different variants. - FieldProject preserves current TransformTarget::FieldProject shape (no args; pure value access) — 🟢 keep. - FieldCall is the new X1 variant — projection-then-call; always has args. 🟡 future-collapses with Callable/Indirect into Call{CalleeRef}. Builders split: push_field_project_transform vs push_field_call_transform. * docs(design): input_ports() enumerates carrier/operand ports too Reviewer caught: FieldProject.carrier and Operator(Unary/Binary) operand ports are runtime deps; input_ports() must yield every runtime PortId across all variants for Facts Flow Forward to hold. Documented per-variant enumeration explicitly. * docs(design): separate 'HO dispatch capability permanent' from 'Indirect variant transitional' Reviewer flagged: 'Indirect is permanent' (line 482) muddled with 'Callable/FieldCall/Indirect 🟡 future-dissolve' (line 424). Split into two claims: the capability is permanent (some variant must carry HO dispatch), but the specific variant spelling 'Indirect(IndirectDispatch)' retires when the 🟡 collapse to Call{CalleeRef::Port} lands. --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Review metadata
1. Story of the diffThis PR adds a first substrate-level representation for workflow-root selection. The new The test slice adds a unit test for the defensive no-bind arm and an integration test module for the single-bind, multi-bind-under-α, and ambiguity-unreachable claims. The generated bootstrap fixtures and parse manifest are refreshed after adding the substrate declaration. 2. Invariant categories
BLOCKING — substrate accessor is declared without an executable substrate realization path. The diff introduces a substrate host accessor:
This is not implementation-only Rust; it is a substrate function intended to be consumed from
BLOCKING — single authority / API-level enforcement gap. The modeling choice itself is good:
But the accessor line quoted above introduces substrate semantics through
The Rust API returns a typed domain result rather than panicking or collapsing the no-root case into a fabricated port:
That matches the local accessor style already present on
NON-BLOCKING by itself, but it fails to catch the blocking substrate wiring issue. The added integration tests call the Rust method directly:
These tests cover the hand-Rust method, but they do not exercise the new
The PR is explicit about the Director-locked α rule and implements that exact rule: “last topological
I do not see an unacknowledged divergence from the referenced locked design; the problem is wiring, not the rule choice.
The scaffolded coproduct is documented with bounds and a dissolution trigger. The comment marks it as scaffold, explains the current α behavior and reserved ambiguity arm, and states the dissolution path:
That is tracked bridge material rather than unbounded debt. 3. VerdictREQUEST_CHANGES The core sum type and α Rust implementation are well-shaped, fail-closed, and tested at the Rust method level. But this PR also adds a substrate |
|
Review metadata
1. Story of the diffThis PR introduces a substrate-level workflow-root query surface: The PR also wires this declared accessor into the Rust target realization surface via 2. Invariant categories
3. VerdictAPPROVE. The PR adds a substrate accessor with a typed fail-closed carrier, one Rust realization, and tests that pin the current α semantics without pretending future ambiguity behavior already exists. I found no blocking or non-blocking findings grounded in the changed diff lines. |
Summary
First substrate slice from the merged audit at
docs/design-lens-fold-prerequisites.md(PR #1207 / #1217). Implements the Director-locked α rule (last topologicalBind) behind a fail-closedWorkflowRootaccessor thatfold_lens<C>and R2-Evaluator share. Per dispatch on parent inbox #1130 (2026-04-29).P1 / P2 receipt
WorkflowRootis a sibling-coproduct to existing reflected substrate sums (Behavior,WorkflowEffect); it lives inv3.std.substratenext tolane2_workflow_atand shares the samehost <name>accessor pattern.workflow_root_portis structurally adjacent tolane2_workflow_at/resolve_producer/port/node/declaration_by_id.SingleRoot(PortId)carries the workflow-root port;NoRootis a nullary failure surface;AmbiguousRoot { candidates }carries the multi-entry enumeration. Three independent variants partition every legitimateDagexactly once.Dag::workflow_root_portis the only Rust-side workflow-root identification; bothfold_lens<C>and R2-Evaluator's runtime entry-point identification are planned consumers per the merged audit cross-reference.Substrate change
src/v3/std/substrate.dag: declareWorkflowRoot = SingleRoot(PortId) | NoRoot | AmbiguousRoot { candidates: List<PortId> }andfn workflow_root_port(d: Dag) -> WorkflowRoot { host workflow_root_port }. SCAFFOLD comment names the dissolution trigger (γ refinement / enumerate-all rule reuse the same partition).src/v3/compiler/src/dag.rs: addpub enum WorkflowRootRust enum +Dag::workflow_root_portα implementation (walksd.nodesbackward, returnsSingleRootat the firstBehavior::Bind,NoRootwhen none found).AmbiguousRootis reserved at the type level but never emitted by the α path.Behavior — α rule
Linear
d.nodestopological order means α picks exactly one element per non-empty Bind population:Bind→SingleRoot(last_bind.result_port).Bind→NoRoot.AmbiguousRootis never emitted under α (linear order cannot tie). Reserved for the future enumerate-all-eligible-entries rule that R2-Evaluator'sevaluate(program, entry, args)consumes.Acceptance
src/v3/compiler/tests/integration/workflow_root_port_test.rs— three integration claims via realcompile_to_dagfixtures:workflow_root_single_bind_returns_single_root—let x = 1 + 2→SingleRoot(bind_x.result_port).workflow_root_multi_bind_returns_single_under_alpha—let x = 1\nlet y = x + 2→SingleRoot(bind_y.result_port); pins α's last-Bind selection over multi-Bind sources.workflow_root_ambiguous_unreachable_under_alpha— drift trigger asserting α never emitsAmbiguousRootacross multiple fixtures; if a future commit wires the enumerate-all rule, this test fails and forces the rule's behavior to grow its own coverage.src/v3/compiler/src/dag.rsmod tests::workflow_root_zero_bind_returns_no_root— unit test for the defensiveNoRootarm via crate-privateDag::empty(V3 surface always lowers ≥1 Bind, soNoRootis unreachable fromcompile_to_dagbut real at the substrate boundary).Commands run
Out of scope (subsequent prereqs and lanes)
fold_lens<C>generic fold machinery — gated on Prereq-1 + Prereq-2 + this 3a slice.datarecord fields (keen-lark's lane).fnblock-body lowering with variant-constructor expressions (class-5 gap Consolidate binaries into gunbc-dag package #4).evaluate(program, entry, args)consumes this accessor — separate PR; the audit'sworkflow_root_consumed_by_runtime_entry_pointclaim lands there.AmbiguousRootarm waits for them.Test plan
cargo fmt --all --checkclean.🤖 Generated with Claude Code