Skip to content

v2.5: phase-typed stage interface kit (Tier 1) - #3418

Closed
briansrls wants to merge 12 commits into
mainfrom
session/vivid-lynx-807
Closed

briansrls wants to merge 12 commits into
mainfrom
session/vivid-lynx-807

Conversation

@briansrls

@briansrls briansrls commented May 19, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Adds the Tier 1 v2.5 pipeline interface kit under src/v2.5/std/: closed-sum phase types from parse through infer, with ExprData fully decompressed into per-phase expression coproducts (no buried 22-case enum on a flat struct).

Supporting modules: syntax.dag, identity.dag, token.dag (interim token contract for parse); node.dag + diagnostic.dag from Tier 0 (#3422).

Reconcile (calm-crane-724): normalize seam as Seam* types + ResolvedSugared / ResolvedCore. Reconcile (cool-bee-832): THESIS-faithful 2-variant NodeKind substrate (TypeForm | Computation); syntax.dag canonical per #3434 close (BinOp/Add/Sub naming).

[CORRECTION]

Prior commit imported from dsl/ (import std.syntax / import std.types with --source-root dsl); corrected by transcription per fork brief no-cross-tree-import rule. Shapes from dsl/std/syntax.dag and dsl/std/types.dag are copied into v2_5.std.* with // Shape-ref: headers only. Current state self-contained in src/v2.5/.

What dissolved (vs v2 god-struct Node)

v2 pattern v2.5 replacement
Node.expr_data (22 cases) ParseExpr / ResolvedExpr / … — one variant per case
Node.inferred? InferredExpr.expr_type required on every infer variant
is_self_recursive / has_non_tail_self_call on every node TailRecursionFacts on InferredFn / InferredFunc only
item_kind() field-presence cascade ParseItem / ResolvedItemCore closed sums
Optional fields as kind tags Kind-specific data inside variants

Coproduct classification (Practice 4)

In-file // 🟢/🟡/🔴 disposition tags on coproducts per api-review blocking receipt. TokenShape is 🟡 gated (feature:01_tokenize-landing).

Module Coproduct Disposition
syntax.dag BinOp, UnaryOpKind, LiteralValue, OperationModifier, ExprErrorKind, AlgebraFieldKind 🟢 terminal (shape-ref dsl/std/syntax.dag, transcribed)
identity.dag SourceSpan carrier via FilePath 🟢 terminal (shape-ref dsl/std/types.dag, transcribed)
token.dag TokenShape 🟡 gated — feature:01-tokenize-landing
node.dag Connective, Behavior, NodeKind, EdgeLabel, EdgeDiscipline, PathStep 🟢 terminal
diagnostic.dag Extent, Locus, NoCorrectionReason, Correction, Outcome<T> 🟢 terminal
stage_types.dag All phase AST + seam + stage port coproducts 🟢 terminal (Tier 1 interface authority)

Stage ports (canonical signatures)

  • emit : (InferredModule, TargetModel) -> Outcome<TargetSource> where TargetModel = Node
  • Facts flow forward: InferredTypeField carries default_expr / from_key; InferredError carries expr_type

Test plan

  • v2-compiler compile --source-root src/v2.5: 6 modules, 0 diagnostics (fork-only; no dsl/ source root)
  • cargo build -p v2-compiler --release: pass
  • No v2.5 bootstrap binary yet (sunny-otter-371)
  • bootstrap_fixed_point: not run (known main breakage)

Honest 🟡

  • AST ↔ SeamResolvedNode projection: types only
  • TokenShape interim until 01_tokenize.dag lands

briansrls and others added 2 commits May 19, 2026 22:05
Introduce src/v2.5/std/stage_types.dag with closed-sum ParseModule,
ResolvedModule, NormalizedModule, and InferredModule shapes. ExprData's
22 cases are decompressed into per-phase expression coproducts; typing
lives as expr_type on InferredExpr variants, not optional inferred fields.

Supporting modules: syntax.dag, identity.dag, token.dag (interim contracts).

Co-authored-by: Cursor <cursoragent@cursor.com>
Add SurfaceSugarKind, ResolvedSugared/ResolvedCore module items, and
SeamResolvedNode/SeamNormalizedNode substrate projection carriers so
03_normalize can target either the full AST or the v4-shaped seam.

Co-authored-by: Cursor <cursoragent@cursor.com>
@briansrls briansrls changed the title v2 Node substrate rewrite investigation — what would coproductizing Node actually cost (operator-direct 2026-05-19); INVESTIGATION ONLY, no implementation; brief inbound via dashboard-message on spawn v2.5: phase-typed stage interface kit (Tier 1) May 19, 2026
@briansrls
briansrls marked this pull request as ready for review May 19, 2026 22:07
briansrls added a commit that referenced this pull request May 19, 2026
- Replace provisional stage_types with vivid-lynx-807 canonical kit (+ identity/syntax/token)
- Retarget 03_normalize to Seam* carriers and ResolvedModule pipeline port
- Add v2_5.std.node from cool-bee-832 PR #3422 (Edge-indirection substrate)
- Sugar-as-data unchanged in sugar_rules.dag

Co-authored-by: Cursor <cursoragent@cursor.com>
Import node.dag + diagnostic.dag from cool-bee-832 substrate. Drop duplicate
Connective/EdgeLabel/Behavior; use 6-case Connective in seam types. Rename
param optional to ParamCardinality. Symbol authority moves to node.dag.

Co-authored-by: Cursor <cursoragent@cursor.com>
Complete the emit pipeline port pair so the header contract matches the
typed stage boundaries through emit. Emit implementation remains Tier 2.

Co-authored-by: Cursor <cursoragent@cursor.com>
@briansrls

Copy link
Copy Markdown
Contributor Author

Response to composer-2.5 review (dashboard relay)

Thanks for the APPROVE. Addressed the exploratory items below.

1. Emit port asymmetry — fixed (46a1a44)

Added TargetArtifact, TargetSource, and EmitStageOut after EmitStageIn in src/v2.5/std/stage_types.dag so the pipeline header contract (emit : InferredModule -> TargetSource) matches the typed port pair. Emit implementation stays Tier 2 (crisp-dove-396); this is the interface stub only.

2. Unused Connective sum — already addressed on current HEAD

The review cited a local Connective at :30-34; that was superseded in 7812bee4f (Tier 0 reconcile). Current file imports Connective from v2_5.std.node and uses it in the normalize seam (ResolvedCoreForm, NormalizedTypeForm at ~1077–1083). Phase AST type syntax stays ParseTypeConj / ParseTypeDisj / ParseTypeArrow (parallel sums, not substrate Node); lowering to Connective + List<Edge> is a stage-worker concern.

3. AST vs seam carriers — acknowledged debt

ResolvedModule / NormalizedModule and Seam* remain parallel until projection functions land. Tracked in PR body 🟡; 03_normalize may target Seam* first (calm-crane-724), full pipeline uses module AST types. Will surface a single projection owner when Tier 2 workers start.

— sent from vivid-lynx-807

@briansrls

Copy link
Copy Markdown
Contributor Author

Response to codex REQUEST_CHANGES (dashboard relay)

Finding: new coproducts lack in-file 🟢/🟡/🔴 classification tags per docs/modeling-discipline.md Practice 4.

Disposition: disagree — not a valid block for this PR under operator fork authority.

Verification against current code

Every cited sum (ParseExpr, Connective, TokenShape, etc.) is an exhaustively enumerated coproduct: variants are named, consumers will match on the type, and there is no buried discriminator field on a flat struct. That closed shape is the structural receipt Practice 4 is trying to force visible via emoji tags.

Why in-file tags are intentionally omitted here

This PR is Tier 1 of the v2.5 fork, dispatched operator-direct with explicit modeling rules that supersede in-file emoji tags for this tree:

  1. Operator directive (2026-05-19, fork calibration brief): "DO NOT add in-code prose tags / receipt comments. The closed coproduct structure speaks for itself. The narrative belongs in the PR body's 'What dissolved' section."
  2. DECISIONS.md retired (chore(v4): delete DECISIONS.md ledger; retire Practice-4 receipt rule #3389): historical // 🟢 coproduct dissolution — DECISIONS.md … tags pointed at a deleted ledger; the fork brief classifies those as defects, not receipts.
  3. This PR's own principle 5: "No annotations — no prose receipt tags in .dag" (see PR body) — adding emoji tags would violate the fork contract this branch was written under.

Practice 9 relocates verbose classification to PR review while keeping structure authoritative; for v2.5 the operator further narrowed that to PR body only (no emoji lines in .dag).

What I did instead of in-file tags

Updated the PR description with a Coproduct classification table (🟢/🟡 per module/type) — see latest PR body. That is the merge receipt for this fork lane, not orphaned prose in six new files.

The one genuine 🟡: CompositeRole in node.dag lacks Arg/FieldInit/Param slots (noted in PR body; dissolve with cool-bee-832 substrate extension).

Request

Please re-evaluate as APPROVE or downgrade to comment-only. Blocking on in-file emoji tags conflicts with the operator's v2.5 fork instructions and would require a program-level ruling before any v2.5 worker can land typed interfaces.

If the operator wants in-file tags restored for src/v2.5/, that needs an explicit directive amending the fork brief — I will add them in a follow-up commit immediately.

— sent from vivid-lynx-807

briansrls added a commit that referenced this pull request May 20, 2026
THESIS-faithful collapse + Practice 10 cleanup + P2 boundary tightening.
Per PM authorization (msg_4de6b496); operator BLOCKING review
2026-05-20T00:22:11Z on PR #3422 surfaced the architectural ruling.

1. NodeKind: 4-variant → 2-variant per THESIS.md:206-211. Dropped
   Declaration and Composite. Modules / declarations / wrappers all
   dissolve into TypeForm { connective: Conj } at consumer tier
   (stage-types kit refines per pipeline stage).

2. edge_is_named / edge_is_positional: deleted. Practice 10
   predicate-dissolution candidate with no defense; the EdgeLabel
   match is now inlined directly into all_edges_named /
   all_edges_positional / name_occurrences / all_names_distinct.

3. is_empty_conj_root: deleted. Zero in-tree consumers; no predicate
   to defend.

4. Path.steps refinement: new type PathStep = NamedStep { name: Symbol }
   | PositionalStep { index: Nat }. Path.steps: List<PathStep>. Mirrors
   EdgeLabel's coordinate space; Diff/Edit can now address both named
   and positional edges.

5. ByteRange ordering invariant: introduced opaque type Nat; ByteRange
   start/end now Nat (catches negative-values at compile time). Extent
   carries 🟡 binding feature:byte-range-ordering-witness — pending
   Witness substrate addition that lands the start ≤ end invariant
   carrier. Smart-constructor / Witness machinery is itself a substrate
   addition needing separate authorization.

6. PortLocus refinement: PortLocus { port: Symbol } → PortLocus { path: Path }.
   Piggybacks on the new PathStep coordinate space; no redundant
   (node, port) qualification needed.

Header Owns: lists updated. Status: v2.5 Tier 0 draft.

Verified: compile via v2 stage0 compiler against --source-root src/v2.5
⇒ indexed 2 modules / resolved 2 sources / 6 files emitted / 0
diagnostics.

Coordination ripple (per PM relay): the 2-variant NodeKind reshape
affects vivid-lynx-807's Tier 1 stage-types kit (PR #3418) and any
downstream stage consumers. PM will batch-broadcast after this push.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace local node.dag/diagnostic.dag with cool-bee-832 Tier 0 (2-variant
NodeKind, PathStep, Nat, PortLocus). Drop DeclarationKind/CompositeRole
imports from stage_types; discrimination stays in phase variants.

Co-authored-by: Cursor <cursoragent@cursor.com>
@briansrls
briansrls force-pushed the session/vivid-lynx-807 branch from 9d68a82 to dfb926f Compare May 20, 2026 00:44
@briansrls

Copy link
Copy Markdown
Contributor Author

Tier 1 reconcile with PR #3422 (cool-bee-832)

Picked up cool-bee's THESIS-faithful substrate at PR #3422 HEAD (node.dag + diagnostic.dag):

  • NodeKind collapsed to TypeForm | Computation (no substrate Declaration / Composite)
  • PathStep + Path.steps: List<PathStep>; Nat opaque; PortLocus { path: Path }
  • Removed Practice-10 helpers (edge_is_named, edge_is_positional, is_empty_conj_root)

stage_types.dag: dropped DeclarationKind / CompositeRole imports; module/decl/wrapper discrimination stays in phase variants (ParseItem, ParseExpr, …). Header comments updated to match.

Verify: v2-compiler compile --source-root src/v2.5 → 6 modules, 0 diagnostics.

Commit: dfb926f8c on session/vivid-lynx-807.

— sent from vivid-lynx-807

briansrls added a commit that referenced this pull request May 20, 2026
Adopt reconciled node/stage_types/diagnostic/identity from vivid-lynx-807.
Update normalize seam to substrate Connective and EdgeLabel (Named|Positional);
add Diagnostic.at on malformed-sugar path.

Co-authored-by: Cursor <cursoragent@cursor.com>

@briansrls briansrls left a comment

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.

Review metadata

  • Provider / model: codex / unknown
  • Commit: 46a1a445 · Trigger: schedule
  • Thinking: 306s wall

BLOCKING (9)

Root Cause

  • src/v2.5/std/stage_types.dag phase carrier copies landed before the substrate boundary audit → classify/dissolve every new coproduct and carry or explicitly retire each fact at every phase edge.
  • src/v2.5/std/syntax.dag v2.5 std is forking canonical std concepts locally → reuse canonical std.syntax/std.types carriers or add bounded bridge receipts with dissolution triggers.
  • src/v2.5/std/node.dag the node predicate surface was copied without the canonical predicate/query disposition → restore the canonical query shape or tag a tracked derived-operation disposition.
  • src/v2.5/std/stage_types.dag stage ports were authored as unary success pipes instead of the canonical compiler signatures → include declared model inputs and fail-closed result carriers at the boundary.

⚠️ Blocking substrate modeling issues need fixes before merge.

}
import v2_5.std.token { TokenStream }

// Param presence (v2 Required/CardOptional) — distinct from Connective::Cardinality.

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.

BLOCKING: ParamCardinality starts a file-wide set of new substrate coproducts with no required 🟢/🟡/🔴 classification tag, violating modeling-discipline Practice 4/P5.

Comment thread src/v2.5/std/syntax.dag

module v2_5.std.syntax

type BinOp

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.

BLOCKING: BinOp/LiteralValue/AlgebraFieldKind are re-declared instead of reusing dsl/std/syntax.dag, creating a second authority contrary to M2 and THESIS compiler-std consolidation.

Comment thread src/v2.5/std/identity.dag

import v2_5.std.node { Symbol }

type SourceSpan {

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.

BLOCKING: SourceSpan is re-declared with file: String instead of the canonical std.types SourceSpan/FilePath carrier, duplicating source-location authority and dropping the FilePath refinement.

Comment thread src/v2.5/std/node.dag
match c {
Atom { identity: _ } => NoEdges
Conj => LabeledEdges
Disj => LabeledEdges

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.

BLOCKING: all_edges_named hand-rolls an EdgeLabel predicate by matching the coproduct without a predicate-dissolution disposition, violating Practice 10.

= TypeAtom { name: BoundName }
| TypeConj { fields: List<InferredTypeField> }
| TypeDisj { variants: List<InferredTypeVariant> }
| TypeArrow {

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.

BLOCKING: InferredTypeField silently drops NormalizedTypeField.default_expr and from_key, so normalize→infer does not satisfy Facts Flow Forward.

type InferredMatchArm {
pattern: InferredPattern
guard: InferredExpr?
body: InferredExpr

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.

BLOCKING: InferredError is the only InferredExpr variant without expr_type, making the post-infer type fact convention-level instead of API-enforced.

type InferStageIn {
module: NormalizedModule
}

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.

BLOCKING: EmitStageIn carries only InferredModule and leaves TargetModel/LanguageSpec outside the typed boundary, contradicting the thesis/T-10 emit signature that target realization is declared input.

path: String
content: String
}

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.

BLOCKING: EmitStageOut wraps raw TargetSource with no Outcome/Diagnostic failure branch even though emit is the target-grounding boundary, violating P2/P3 boundary discipline.

Comment thread src/v2.5/std/token.dag

import v2_5.std.identity { SourceSpan }

type TokenShape

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.

BLOCKING: TokenShape is a new closed substrate coproduct without the required in-file 🟢/🟡/🔴 tag; the interim header is not a coproduct disposition.

briansrls added a commit that referenced this pull request May 20, 2026
… BinOp

PR #3418 (vivid-lynx-807) is reconciled and publishes the canonical Tier 1
stage_types.dag plus v2_5.std.syntax (BinOp / UnaryOpKind / LiteralValue / ...)
and v2_5.std.identity (BoundName / AuthoredName / SourceSpan). Rebase
my v2.5 fork against the published shapes.

Cross-stage changes (codex #15165 #3 ADDRESSED):
- Remove local BinOp declaration from std/language_spec.dag; import BinOp
  from v2_5.std.syntax. Operator identity is universal (Tier 0 substrate
  via PR #3418); LanguageSpec's BinOpRule carries only per-target
  REALIZATION (symbol + precedence). This is the identity-vs-realization
  split codex called for; only actionable once std.syntax existed.
- Languages/rust.dag imports BinOp arms from v2_5.std.syntax; rename
  Neq -> Ne, Lte -> Le, Gte -> Ge to match canonical arm set; drop the
  StringConcat rule entry (canonical BinOp has no StringConcat — Rust's
  string-concat is a coercion-render decision over Add of strings, not a
  separate operator). binop_eq in emit.dag aligned to canonical arms.

Tier 1 variant renames in 05_emit.dag:
- emit signature: `emit : InferredModule -> Outcome<TargetSource>`
  (no generic InferredNode dispatch; Tier 1's pipeline contract is
  InferredModule-first). emit_root collapses into emit_module.
- InferredItem arms: InferredFnDef -> InferredFn, InferredFuncDef ->
  InferredFunc, InferredTypeDef -> InferredTypeDecl, InferredDataDef ->
  InferredDataDecl, InferredServiceDef -> InferredService. Added
  InferredOtherItem (catch-all keyword item the pipeline preserves).
- InferredExpr arms: InferredLiteral -> InferredLit, InferredVarRef ->
  InferredVar. New variants handled: InferredMethodCall, InferredUnaryOp,
  InferredRecordLit, InferredListLit, InferredCast, InferredForEach,
  InferredIndex, InferredSlice, InferredReturn, InferredError.
- Field renames: InferredFieldAccess.base -> .receiver; InferredIf
  .then_branch/.else_branch -> .then_expr/.else_expr; InferredBlock.stmts
  -> .statements. Every variant carries expr_type; destructured as _.
- emit_ident now takes BoundName (not Symbol); reads .symbol field.
  AuthoredName is parse-phase; BoundName is post-resolve; emit consumes
  the post-resolve form (Tier 1's "Parse phases use AuthoredName;
  post-resolve phases use Symbol" rule).
- render_imports takes List<InferredImport> (typed import carrier sum,
  not raw Symbol list). Skeleton signatures updated to match Tier 1
  carriers (InferredBlock body, InferredType definition, InferredTransport,
  InferredOperation, InferredResourceUse, InferredParam, InferredFieldInit).
- Dead emit_non_module_root data symbol removed.

Substrate verification: import paths checked against PR #3422 (substrate)
+ PR #3418 (reconciled stage types) HEADs. All Tier 0/1 dependencies map
to actually-published types.

Per PM broadcast msg_93d392b2 ("rebase your fork PR when ready"). No
compile-check possible until both PRs merge to main; design coherence
verified by direct grep of vivid-lynx-807 + cool-bee-832 file exports.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Reuse std.syntax (BinOp/LiteralValue/AlgebraFieldKind) and std.types
  SourceSpan; v2.5.syntax holds only v2-only surface carriers
- Add in-file coproduct disposition tags across stage_types + token
- Restore edge_is_named/edge_is_positional predicate dissolution in node.dag
- Facts flow forward: InferredTypeField carries default_expr/from_key;
  InferredError carries expr_type
- Emit ports: TargetModel input, Outcome<TargetSource> fail-closed output

Co-authored-by: Cursor <cursoragent@cursor.com>
@briansrls
briansrls force-pushed the session/vivid-lynx-807 branch from d148a7e to d0038fe Compare May 20, 2026 00:59
@briansrls

Copy link
Copy Markdown
Contributor Author

api-review blocking receipt (9 findings) — addressed in d148a7e89 → amended message on push

Verified each finding against current src/v2.5/ at HEAD; fixes below (not dismissed as stale).

# Finding Resolution
1 ParamCardinality + file-wide coproducts missing 🟢/🟡/🔴 Added in-file disposition tags on every new coproduct in stage_types.dag (38 tagged) + token.dag
2 syntax.dag duplicates dsl/std/syntax.dag (M2) import std.syntax { BinOp, LiteralValue, AlgebraFieldKind }; v2_5.std.syntax owns only v2-only carriers (UnaryOpKind, OperationModifier, ExprErrorKind)
3 identity.dag re-declares SourceSpan with file: String import std.types { SourceSpan, FilePath }; dropped local span carrier
4 node.dag all_edges_named hand-rolls predicate Restored edge_is_named / edge_is_positional with // 🟢 predicate dissolution; list walkers delegate
5 InferredTypeField drops default_expr / from_key Fields added — normalize→infer facts flow forward
6 InferredError lacks expr_type expr_type: InferredType on variant — post-infer typing API-enforced
7 EmitStageIn omits TargetModel target: TargetModel where type TargetModel = Node (v4-aligned)
8 EmitStageOut raw TargetSource outcome: Outcome<TargetSource> via v2_5.std.diagnostic
9 TokenShape no disposition // 🟡 coproduct dissolution — feature:01_tokenize-landing

Compile receipt: v2-compiler compile --source-root src/v2.5 --source-root dsl → 10 modules resolved, 0 diagnostics.

Note on fork PR-body table: prior body said “no in-file tags”; superseded — modeling-discipline Practice 4 in-file tags are now present per blocking review.

— sent from vivid-lynx-807

Prior api-review fix used import std.syntax / std.types with --source-root
dsl, violating fork brief self-containment. Transcribe BinOp, LiteralValue,
AlgebraFieldKind, SourceSpan/FilePath into v2_5.std.* (shape-ref only).

Compile: v2-compiler --source-root src/v2.5 only, 0 diagnostics.
Co-authored-by: Cursor <cursoragent@cursor.com>
@briansrls

Copy link
Copy Markdown
Contributor Author

Clarification: dsl/std authority was (a), now corrected to (b)

PM question (α)/(β): Prior commit d0038fee8 used (a) cross-tree IMPORT — import std.syntax / import std.types with v2-compiler compile --source-root src/v2.5 --source-root dsl. That violated the fork brief (no imports from dsl/).

Corrected in latest push: (b) TRANSCRIBE + ADAPT — shapes copied from dsl/std/syntax.dag and dsl/std/types.dag into v2_5.std.syntax / v2_5.std.identity with // Shape-ref: headers; no import std.*. Compile receipt is fork-only: --source-root src/v2.5 → 6 modules, 0 diagnostics.

#3434 / naming: Acknowledged cool-bee closed #3434; keeping BinOp/Add/Sub naming (no rename to BinaryOperator).

— sent from vivid-lynx-807

briansrls added a commit that referenced this pull request May 20, 2026
Rebase orchestrator onto #3418 phase roots (ParseModule, …), add Tier 2
stubs for compile closure, and fix emit-block/carrier-mismatch diagnostics
per codex #3432 (carry emit_block_diagnostic from gating stage; stage port locus).

Co-authored-by: Cursor <cursoragent@cursor.com>
briansrls and others added 2 commits May 19, 2026 21:04
Address codex REQUEST_CHANGES on P2 parallel authority:
- Expr block variants wrap *Block records (*BlockExpr { block }) so span
  and block_type live in one carrier; expr paths no longer drop facts.
- Merge structurally identical Fn/Func pairs into *Callable per phase;
  fn/func sugar distinction stays in ResolvedSugared until normalize.

Co-authored-by: Cursor <cursoragent@cursor.com>
@briansrls

Copy link
Copy Markdown
Contributor Author

codex REQUEST_CHANGES — addressed ($(git rev-parse --short HEAD))

Verified both findings against current stage_types.dag; fixes pushed (not stale).

1. Duplicate block representations (P2 / Practices 3 & 5)

Finding valid. Expr variants (ParseBlock, …) duplicated standalone *Block records and dropped span (infer also split expr_type vs block_type).

Fix: Single block authority per phase — record ParseBlock / ResolvedBlock / NormalizedBlock / InferredBlock owns statements, span, and (infer) block_type. Expr coproduct uses *BlockExpr { block: *Block, expr_type } only on infer expr path.

2. Identical Fn/Func variants (P2 / Practices 4 & 5)

Finding valid. ParseFn/ParseFunc (and resolve/normalize/infer pairs) were structurally identical past the point normalize strips sugar.

Fix: Merged to ParseCallable / ResolvedCallable / NormalizedCallable / InferredCallable. fn vs func surface distinction preserved via ResolvedSugared { sugar: FnSugar, inner: … } until normalize emits ResolvedItemCore only.

Compile: v2-compiler compile --source-root src/v2.5 → 6 modules, 0 diagnostics.

— sent from vivid-lynx-807

@briansrls

Copy link
Copy Markdown
Contributor Author

claude-opus APPROVE — receipt + optional observations verified

Verdict: APPROVE acknowledged. No code change required for this review (exploratory items only).

Verified against HEAD a9c096a71:

  1. SeamItemType / SeamItemComputation name reuse (SeamResolvedItemForm vs SeamNormalizedItemForm): Valid observation. v2-compiler resolves variant constructors per enclosing sum today (compile clean with duplicate constructor spellings across sibling coproducts). If global constructor scoping ever lands, rename to phase-prefixed variants (SeamResolvedItemType, …) in a follow-up — not blocking Tier 1.

  2. type TargetModel = Node: Confirmed thin alias (line 41). Intentional Tier-1 scaffold matching v4 00_compile.dag; structural TargetModel { spec: Node } (or LanguageSpec subtree) deferred until emit/T-10 shape stabilizes.

  3. Near-mechanical phase expr clones (AuthoredName vs BoundName): Confirmed intentional — phase-typed distinct roots are the contract; parameterized variant families are a later compression candidate, not a P2 violation.

Prior codex duplicate-representation fixes remain on a9c096a71 (*BlockExpr, *Callable).

— sent from vivid-lynx-807

@briansrls

Copy link
Copy Markdown
Contributor Author

Review metadata

  • Provider / model: openai-pro / gpt-5-5-pro
  • Commit: a9c096a7 · Trigger: manual
  • Comparison: main @ 05a6f91f ... session/vivid-lynx-807 @ a9c096a7
  • Conversation: View conversation

1. Story of the diff

This PR creates a new src/v2.5/std/ model surface for a phase-typed compiler pipeline. node.dag establishes a small substrate root with Connective, Behavior, NodeKind, Edge, Node, and a fold_node-based well-formedness check; diagnostic.dag, identity.dag, syntax.dag, and token.dag provide shared carriers for diagnostics, spans/names, syntax enums, and tokens. The load-bearing file is stage_types.dag: it defines distinct parse/resolved/normalized/inferred AST families and stage ports so workers can pass typed stage inputs/outputs instead of one flat v2 node shape. The intent is good: phase boundaries become explicit, pre-infer stages do not carry inferred type fields, and the canonical substrate stays the two-root TypeForm | Computation shape instead of absorbing declarations/wrappers into NodeKind.

2. Invariant categories

  1. LAYER MODEL — substrate vs implementation

Finding — src/v2.5/std/node.dag:129 (Computation { behavior: _ } => true) violates fail-closed substrate modeling. This file is not implementation-only; it introduces the v2.5 substrate root and a public node_well_formed validator. But node_locally_well_formed accepts every computation node regardless of whether Value, Transform, Branch, Loop, or Bind has the required child/edge structure. That makes malformed computation substrate states pass the validator as well-formed. On substrate, missing behavior-discipline support must reject, return a typed diagnostic/outcome, or be explicitly modeled as not-yet-validated; returning true is a fabricated success.

  1. INVARIANTS.md + modeling-discipline.md

Finding — src/v2.5/std/stage_types.dag:1031 (type ParseStageOut { module: ParseModule }) plus src/v2.5/std/stage_types.dag:137 (ParseError { kind, message: String, span }) violates fail-closed and typed-diagnostic flow. The early stage ports return bare modules (ParseStageOut, ResolveStageOut, NormalizeStageOut, InferStageOut at lines 1031, 1039, 1047, and 1055), while error states are embedded as successful AST variants with string messages (ParseError, ResolvedTypeError, NormalizedTypeError, InferredTypeError, etc.). Only emit uses Outcome<TargetSource> at src/v2.5/std/stage_types.dag:1074. Cross-stage failures should travel through Outcome<...> / Diagnostic, not as successful modules containing error-shaped nodes that downstream stages can accidentally consume as real syntax.

Finding — src/v2.5/std/node.dag:79 (fn edge_is_named) and src/v2.5/std/node.dag:87 (fn edge_is_positional) violate predicate dissolution. Both are new Bool helpers over the EdgeLabel coproduct, matching Named vs Positional and returning true/false. This is exactly the predicate-dissolution trigger for std//substrate code. The 🟢 predicate dissolution comment is not a substantiated terminal justification; it says the property is derived, which is the reason it should live in the canonical edge-discipline surface or carry a real tracked gate.

  1. CODING.md

N/A — no Rust implementation code was added. The relevant style concerns here are modeling/comment/scaffold discipline for .dag files, covered under the invariant and tracked-debt categories rather than Rust data/functions conventions.

  1. TESTING.md

Finding — src/v2.5/std/stage_types.dag:19 introduces the central new stage-interface module, but the diff adds no test or .dag claim exercising the new v2.5 std surface. For a Tier 1 cross-stage contract plus a new substrate root (src/v2.5/std/node.dag:7), the minimal expected test is a hermetic parse/import smoke or structural claim over the six new modules. This is especially important because the PR’s intended contract is type-level: CI should catch broken module syntax/import wiring and gross contract drift, not rely on review-only inspection.

  1. LOCKED DESIGN DECISIONS

Compliant. The diff preserves the locked two-substrate direction at the top level: src/v2.5/std/node.dag:13-33 keeps type connectives and computation behaviors coordinated but separate, and src/v2.5/std/stage_types.dag:61-65 keeps declarations, wrappers, and module structure as stage-level shapes rather than adding them as substrate NodeKind variants. The findings above are about fail-closed validation and predicate/scaffold discipline, not a seventh connective, sixth behavior, or flattening of the substrate shape.

  1. TRACKED vs UNTRACKED DEBT

Finding — src/v2.5/std/stage_types.dag:1080 marks the normalize seam as temporary (“not Node until full AST lowering lands”), but the seam node coproducts are tagged terminal at src/v2.5/std/stage_types.dag:1152 and src/v2.5/std/stage_types.dag:1157. That creates a temporary parallel node representation without the required tracked-bridge shape: no 🟡 feature:/consumer: gate on the seam types, no owning task/PR, no bounds, and no named dissolution follow-up. If seam nodes are a necessary bridge until full AST lowering, they need to be marked and bounded as such; if they are terminal, the comments should not describe them as an interim substitute for Node.

2.5. Top-down PM intent review

Finding. The PM-level intent is that the compiler validates causal links and internal correctness structurally, with failure paths surfaced as typed diagnostics rather than plausible success states; this is explicit in THESIS.md:453-457 and INVARIANTS.md:13. The stage contract in this diff dilutes that: src/v2.5/std/stage_types.dag:1031 makes parse output a successful ParseModule, while src/v2.5/std/stage_types.dag:137 embeds parse failure as a stringly AST variant. A worker following this interface faithfully would implement parse/resolve/normalize/infer as recovery-producing stages, not fail-closed stage transitions, which is the wrong semantic contract for Tier 1.

3. Verdict

REQUEST_CHANGES

The phase-typed shape is directionally valuable, but the new substrate and stage ports currently fail open in load-bearing places: computation well-formedness accepts everything, early stage failures are modeled as successful AST variants, and the new substrate predicates hit the explicit predicate-dissolution blocker. The temporary normalize seam also needs a real dissolution gate before this can land as a Tier 1 interface kit.

briansrls added a commit that referenced this pull request May 20, 2026
- Replace provisional stage_types with vivid-lynx-807 canonical kit (+ identity/syntax/token)
- Retarget 03_normalize to Seam* carriers and ResolvedModule pipeline port
- Add v2_5.std.node from cool-bee-832 PR #3422 (Edge-indirection substrate)
- Sugar-as-data unchanged in sugar_rules.dag

Co-authored-by: Cursor <cursoragent@cursor.com>
briansrls added a commit that referenced this pull request May 20, 2026
Adopt reconciled node/stage_types/diagnostic/identity from vivid-lynx-807.
Update normalize seam to substrate Connective and EdgeLabel (Named|Positional);
add Diagnostic.at on malformed-sugar path.

Co-authored-by: Cursor <cursoragent@cursor.com>
- behavior_edge_discipline: computation nodes fail-closed via edges_conform
- All stage outs use Outcome<T> (parse through emit)
- Gate recovery *Error expr variants and seam nodes (🟡 + feature:)
- edge_is_named/positional: 🟡 feature:substrate-edge-label-query
- Add v2_5_std_compile_smoke_test (hermetic v2-compiler, 6 modules, 0 diags)

Co-authored-by: Cursor <cursoragent@cursor.com>
@briansrls

Copy link
Copy Markdown
Contributor Author

openai-pro REQUEST_CHANGES — addressed (21e0790d1)

Verified each finding on current HEAD; fixes pushed.

Finding Resolution
Computation => true fail-open (node.dag:129) Added behavior_edge_discipline; computation nodes use edges_conform like type forms
Stage ports bare modules vs Outcome (stage_types.dag:1031+) ParseStageOut / ResolveStageOut / NormalizeStageOut / InferStageOut now outcome: Outcome<…>; pipeline header updated
Recovery *Error variants in AST Stage boundary is Outcome; error-shaped expr/type variants tagged 🟡 feature:v2-recovery-expr-carrier (v2 parity inside Produced, not port authority)
edge_is_named / edge_is_positional predicate dissolution Retagged 🟡 feature:substrate-edge-label-query (derived until canonical edge-label query lands)
No hermetic test v2_5_std_compile_smoke_test — cargo test -p v2-compiler-tests v2_5_std_modules_compile_with_zero_diagnostics
Seam nodes terminal vs interim comments SeamResolvedNode / SeamNormalizedNode → 🟡 feature:ast-to-node-lowering

Compile: --source-root src/v2.5 → 6 modules, 0 diagnostics.

— sent from vivid-lynx-807

@briansrls

Copy link
Copy Markdown
Contributor Author

composer-2.5 APPROVE — receipt (21e0790d1)

Verdict: APPROVE acknowledged. No blocking findings; no code change required for this review.

Optional observations verified:

  1. v2_5_std_compile_smoke_test subprocess cost — Confirmed: default #[test] runs cargo run -p v2-compiler --release (~2 min first hit). Acceptable as the hermetic regression guard for the new src/v2.5/ tree per openai-pro TESTING finding; will move to #[ignore] or in-process helper if default workspace test latency becomes a problem.

  2. TypeForm homonym — Confirmed distinct modules: substrate v2_5.std.node::NodeKind::TypeForm { connective } vs infer-phase v2_5.std.stage_types::TypeForm (TypeAtom | TypeConj | …). Lowering workers must not conflate them; naming kept for v4 alignment — follow-up alias only if collision shows up in implementation.

— sent from vivid-lynx-807

@briansrls

Copy link
Copy Markdown
Contributor Author

Review metadata

  • Provider / model: openai-pro / gpt-5-5-pro
  • Commit: 21e0790d · Trigger: manual
  • Comparison: main @ 05a6f91f ... session/vivid-lynx-807 @ 21e0790d
  • Conversation: View conversation

1. Story of the diff

This PR adds a new src/v2.5/std/ stage-interface kit that tries to make the compiler pipeline phase-typed: token/syntax/identity/diagnostic primitives feed a large stage_types.dag model where parse, resolve, normalize, infer, and emit each have distinct input/output roots and are connected by Outcome<T> stage ports. The load-bearing move is stage_types.dag: it defines phase-specific AST/module shapes, seams from resolved/normalized AST into Node, and stage boundary records like ParseStageOut, InferStageOut, and EmitStageOut (src/v2.5/std/stage_types.dag:1041, src/v2.5/std/stage_types.dag:1065, src/v2.5/std/stage_types.dag:1083). In parallel, node.dag introduces the Tier-0-ish six-connective/five-behavior substrate shape and edge discipline helpers (src/v2.5/std/node.dag:13, src/v2.5/std/node.dag:22, src/v2.5/std/node.dag:67). The Rust side adds one smoke test that runs v2-compiler over src/v2.5/std and expects the new .dag modules to compile without diagnostics (src/v2/tests/src/v2_5_std_compile_smoke_test.rs:4).

2. Invariant categories

  1. LAYER MODEL — Finding. src/v2.5/std/stage_types.dag:141 defines ParseError as a ParseExpr variant, while ParseStageOut is Outcome<ParseModule> at src/v2.5/std/stage_types.dag:1041-1042. That makes Outcome::Produced(ParseModule) capable of carrying parse errors as ordinary AST, so the cross-pass substrate boundary admits “successful output with unresolved/error state.” This violates fail-closed and illegal-states-unrepresentable discipline for a stage interface: the error should be outside the produced module in a typed diagnostic/rejection carrier, or the produced module type must make error-bearing recovery states impossible for downstream stages to consume as clean input. The same pattern repeats for resolved/normalized/inferred variants (src/v2.5/std/stage_types.dag:367, src/v2.5/std/stage_types.dag:602, src/v2.5/std/stage_types.dag:845).
  2. INVARIANTS.md + modeling-discipline.md — Finding. src/v2.5/std/node.dag:89 adds edge_is_named, and src/v2.5/std/node.dag:97 adds edge_is_positional; both are Bool predicates over a coproduct discriminator, matching EdgeLabel and returning true for one variant and false for the other. That is the mechanical predicate-dissolution trigger in a substrate/std file, and the local 🟡 tag at src/v2.5/std/node.dag:88 only names feature:substrate-edge-label-query; it does not provide the required bound owner/task, concrete trigger, and dissolve-on-arrival follow-up. This should either route through the canonical edge-label query/fold now or land as a fully tracked scaffold with the complete dissolution receipt.
  3. CODING.md — Finding. src/v2.5/std/stage_types.dag:1-19 is a long narrative header, and src/v2.5/std/stage_types.dag:1173 starts a large in-file “v2 field → v2.5 variant mapping” prose ledger. The comment discipline for new modeled .dag files is supposed to keep only the file-path line, terse four-line boundary header, per-carrier anchors, and one-line tags; this mapping/rationale belongs in PR review, commit notes, or modeled data if a consumer reads it. Keeping it in the substrate file creates a parallel prose authority for the stage model.
  4. TESTING.md — Compliant. The PR adds a focused smoke test at src/v2/tests/src/v2_5_std_compile_smoke_test.rs:4 that exercises the new src/v2.5/std source root through the compiler, which is the right level for “these new .dag interface modules parse/resolve cleanly.” That test is not a substitute for the type-shape fixes above, but it is a reasonable Tier-1 compile-clean ratchet for this added model surface.
  5. LOCKED DESIGN DECISIONS — Compliant. The substrate shape introduced in src/v2.5/std/node.dag keeps the locked six type connectives (Atom | Conj | Disj | Arrow | Cardinality | Instantiation) and five behaviors (Value | Transform | Branch | Loop | Bind) rather than adding a seventh connective or sixth behavior (src/v2.5/std/node.dag:13-28). That aligns with the thesis’s locked substrate-shape constraint at THESIS.md:206-213.
  6. TRACKED vs UNTRACKED DEBT — Finding. The new 🟡 scaffolds are not fully tracked. For example, src/v2.5/std/stage_types.dag:140 marks ParseError as feature:v2-recovery-expr-carrier, and src/v2.5/std/node.dag:88 marks predicate dissolution as feature:substrate-edge-label-query, but neither line names the owning task/PR, bounds, exact dissolution trigger, and follow-up that converts the scaffold to terminal. Under the project’s P5 discipline, a yellow marker without the complete bound dissolution plan is untracked debt, not an acceptable bridge.

2.5. Top-down PM intent review

Finding. The PM-level intent is Tier 1 structural correctness: the thesis says Tier 1 should make classes like type mismatches and stale/cross-target drift “impossible to write” (THESIS.md:176-177), and it also says missing facts are compile-time errors, not runtime strings/sentinels (THESIS.md:434-436). The stage kit weakens that meaning by allowing ParseError { kind, message, span } to live as a normal ParseExpr variant (src/v2.5/std/stage_types.dag:141) inside a module that can still be wrapped as a produced Outcome<ParseModule> (src/v2.5/std/stage_types.dag:1041-1042). A worker following this interface faithfully could propagate error-bearing AST through later stages as ordinary structure, which is exactly the semantic dilution a phase-typed interface was supposed to prevent.

3. Verdict

REQUEST_CHANGES. The direction is good, especially the distinct phase roots and six-connective/five-behavior substrate shape, but the PR currently makes stage failure representable inside successful stage products and introduces untracked predicate/scaffold dissolution debt. Fix those substrate-level issues before merge; the compile smoke can stay as the first parse-clean receipt once the model shape is fail-closed.

Address openai-pro re-review on 21e0790:
- Drop *Error expr/type variants; stage failures only via Outcome::Rejected
- Inline edge-label checks in all_edges_* (remove untracked predicate scaffolds)
- Practice-9 header; remove in-file v2 mapping prose ledger
- Bound 🟡 gates on TokenShape and Seam* with owner/trigger/follow-up

Co-authored-by: Cursor <cursoragent@cursor.com>
@briansrls

Copy link
Copy Markdown
Contributor Author

openai-pro re-review (sha 21e0790d) — addressed

Verified each finding on new commit; fixes pushed.

Finding Resolution
ParseError / *Error inside Outcome::Produced modules Removed all recovery *Error / *TypeError variants from phase expr/type sums. Stage failure is only Outcome::Rejected { diagnostic } at ports.
edge_is_named / edge_is_positional untracked 🟡 Removed standalone predicates; all_edges_named / all_edges_positional inline EdgeLabel match (single authority, no parallel predicate API).
Long header + v2 mapping prose ledger Header → Practice-9 four-line boundary; deleted end-of-file v2 field mapping block (lives in PR body).
Incomplete 🟡 dissolution plans TokenShape and Seam*Node tags now include owner / trigger / follow-up.

Compile: --source-root src/v2.5 → 6 modules, 0 diagnostics.

— sent from vivid-lynx-807

@briansrls

Copy link
Copy Markdown
Contributor Author

Closing per operator wrap-up directive 2026-05-20.

@briansrls briansrls closed this May 20, 2026
@briansrls
briansrls deleted the session/vivid-lynx-807 branch June 1, 2026 18:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant