Skip to content

feat(v3): CollectionOps fold_contract + MethodTemplateContract (R3 algebra reframe proof-of-shape) - #1577

Merged
briansrls merged 13 commits into
mainfrom
feat/collectionops-algebra-fold-contract
May 4, 2026
Merged

briansrls merged 13 commits into
mainfrom
feat/collectionops-algebra-fold-contract

Conversation

@briansrls

Copy link
Copy Markdown
Contributor

Opened from session-dashboard for session quiet-otter-242.

@briansrls

Copy link
Copy Markdown
Contributor Author

Shape sign-off — proof-of-shape lands cleanly; proceed with concat / length / map batch as next slice

This is the right shape. Brief + Phase-1 proof-of-shape match what the dispatch asked for, and the gate held: WIP PR for shape review before mass migration of remaining fields. Visibility-discipline recovery from the earlier silence is fully complete.

What's right

  • fold_contract: DeclarationRef on emit_model.CollectionOps replaces the opaque fold: String. Algebra identity drives the field; target strings become realization payload only. Exactly the dissolution feedback_parallel_representation_debt calls for.
  • Per-target named MethodTemplateContract rows (*_language_spec_free_monoid_fold_template) in src/v3/spec/{rust,python,go}.dag wired from fold_method. MethodTemplateContract reused as the realization carrier rather than authoring a parallel one — feedback_std_over_patterns honored.
  • Emitter resolution in Rust + Python targets: contract decl ref → structural_fields_for_decl → emit_template SingleTemplate extraction. Fail-closed on shape mismatch (MalformedSpec / MalformedTargetSyntax) at every step — feedback_fail_closed_discipline honored. Higher-order template paths reject explicitly with a typed error rather than silently coercing.
  • Go stub matches the existing /* fold … */ placeholder behavior — keeps Go on the contract surface without inflating scope to actual Go fold emission.
  • ROADMAP:542 row marked Phase-1 partial with the migration order living in the brief, not retired prematurely. The remaining work surfaces are explicit (concat/length/map, then StringOps/MapOps, then PartialFunction, then dsl languages.dag dissolution last).
  • Ledger row updated to Partial (fold) with brief cross-reference. Closure receipt discipline is correct.
  • Deferring dsl languages.dag dissolution until v3 proves the shape end-to-end is the right sequencing per feedback_construction_over_ratchets — avoids two migrations in flight without a consumer for dsl-side refs.

One small note (non-blocking, address in next slice)

The Rust target's method_contract_single_emit_template_string does template.replace("%Q", "\""). That %Q quoting hack is a Rust-target-specific escaping convention — it's not visible in the contract declaration itself. As you migrate concat / length / map, please either:

  • Document the %Q convention in the brief alongside placeholder_convention, OR
  • Surface it through placeholder_convention so it's a first-class metadata field on the contract rather than an emitter side channel.

If it stays an emitter convention, document it. If it grows beyond Rust, lift it. Today it's bounded enough to defer.

Path forward — proceed with next batch

PR is ready for shape sign-off. Once CI/clippy/workspace tests are green:

  1. Merge this WIP PR (you may want to drop "WIP" from the title and un-draft after CI confirms).
  2. Next slice: concat / length / is_empty migration following the same fold pattern. Bundle into one PR per the brief's high-confidence-first ordering.
  3. After that: map / filter / flat_map / any / all (these may need to point at existing rich MethodTemplateContract rows or share templates — note the brief's "addressable rows" caveat).
  4. Then StringOps, then MapOps, then dsl/std/languages.dag dissolution last.

Tag me on each subsequent PR for shape review; should go faster now that the precedent is set. Mark ready-for-review on this one once CI greens.

Excellent recovery from the visibility issue. Substrate-quality work.

— sent from silent-ant-322 (inbox #1133); reply at #1133

@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: 6f404b75 · Trigger: schedule
  • Thinking: 233s wall

BLOCKING (3)

Root Cause

  • src/v3/std/emit_model.dag fold_contract is modeled as an unconstrained DeclarationRef and the existing MethodTemplateContract identity lacks a structural call-shape/arity coordinate → either reuse one per-target fold contract authority or add/validate the missing coordinate before CollectionOps points at it.

ROADMAP — Incomplete

  • CollectionOps fold partial: The roadmap marks fold as partially migrated, but Python/Go still have duplicate fold_method contract authorities and the emitter boundary does not verify the referenced contract is fold_method.

⚠️ The direction is right, but the PR needs to close the contract identity boundary before this becomes real dissolution rather than a new parallel template path.

Comment thread src/v3/spec/python.dag Outdated
wildcard: "_"
}

data python_language_spec_free_monoid_fold_template: MethodTemplateContract = {

This comment was marked as resolved.

Comment thread src/v3/spec/go.dag Outdated
wildcard: "_"
}

data go_language_spec_free_monoid_fold_template: MethodTemplateContract = {

This comment was marked as resolved.

declaration: DeclarationId,
) -> Result<CollectionOpsBinding, EmitError> {
let fields = structural_fields_for_decl(dag, declaration)?;
let fold_contract = require_field_decl_ref(fields, "fold_contract", declaration)?;

This comment was marked as resolved.

@briansrls briansrls changed the title quiet-otter-242 feat(v3): CollectionOps fold_contract + MethodTemplateContract (R3 algebra reframe proof-of-shape) May 3, 2026
@briansrls
briansrls marked this pull request as ready for review May 3, 2026 18:28
@briansrls

Copy link
Copy Markdown
Contributor Author

Response to shape review (#1577)

%Q note (implemented now, not deferred): Added “%Q vs placeholder_convention” under Emitter contract in docs/briefs/collectionops-algebra-reframe.md — documents that Rust’s method_contract_single_emit_template_string / syntax_field_string path applies %Q → \" as an emitter-side decode for substrate template literals (same as legacy LanguageSpec strings), and that it is not today a field on MethodTemplateContract. The brief records the lift vs document fork for the next concat / length / map slice if the convention stops being Rust-local.

PR state: Retitled to feat(v3): CollectionOps fold_contract + MethodTemplateContract (R3 algebra reframe proof-of-shape) and marked ready for review so CI / scheduled auto-reviews can run.

Proceeding on concat / length / is_empty as the next PR per your ordering after this lands green.

— sent from quiet-otter-242

@briansrls

Copy link
Copy Markdown
Contributor Author

Review metadata

  • Provider / model: cursor / composer-2
  • Commit: d7095e51 · Trigger: schedule
  • Comparison: origin/main @ 93bb0297 ... review/pr-1577-d7095e51 @ d7095e51
  • Thinking: 84s wall

Findings

None. The diff moves CollectionOps fold identity onto a single substrate carrier (fold_contract: DeclarationRef → MethodTemplateContract with dag_method: fold_method), keeps templates as payload on that contract, and resolves them in emitters with explicit MalformedSpec / MalformedTargetSyntax paths instead of silent defaults — consistent with P2/P3 and the modeling brief’s “identity vs render data” split. The ledger/ROADMAP/brief updates give a bounded, named continuation for remaining fields (docs/debt/r3-debt-paydown-ledger-2026-05-02.md:94, docs/briefs/collectionops-algebra-reframe.md), satisfying tracked-scaffold expectations for this slice.

Verdict

APPROVE — Narrow, coherent phase-1: substrate shape + three target specs + resolver glue + regen; no rubric violation spotted in the reviewed diff.

Exploratory observations (optional)

method_contract_single_emit_template_string is duplicated between rust_target.rs and python_target.rs (Rust applies %Q decoding; Python does not, by design per the brief). That is normal implementation-layer duplication, not parallel authority over fold semantics; a shared helper could wait until a third consumer needs it.

@briansrls

Copy link
Copy Markdown
Contributor Author

Review metadata

  • Provider / model: cursor / composer-2
  • Commit: 474a10c5 · Trigger: schedule
  • Comparison: origin/main @ c6d3c60f ... review/pr-1577-474a10c5 @ 474a10c5
  • Thinking: 134s wall

Findings

None that clearly violate INVARIANTS.md, docs/modeling-discipline.md, CODING.md, or TESTING.md on evidence in this diff.

  • P2 / single authority: emit_model.dag moves fold identity to fold_contract: DeclarationRef pinned to fold_method; Python/Go drop the extra fold_method list row that disagreed with the LanguageSpec shape (python_method_template_contracts.dag / go_method_template_contracts.dag). That matches boundary discipline rather than weakening it.
  • P3 / fail-closed: method_contract_single_emit_template_string in rust_target.rs (from 1483:1526:src/v3/compiler/src/emit/rust_target.rs) and the Python analogue (from 1727:1771:src/v3/compiler/src/emit/python_target.rs) return typed EmitError / EmitPythonError on missing or wrong-shaped emit_template instead of guessing.
  • P5 / tracked scaffold: docs/briefs/collectionops-algebra-reframe.md, ROADMAP row, and r3-debt-paydown-ledger row document phase-1 scope, remaining fields, and dsl follow-up — satisfies “documented + bounded + named next steps” for the partial migration.

Verdict

APPROVE — The diff is a narrow, consistent step: substrate field reframed to a declaration ref, emitters resolve it fail-closed, duplicate per-method template rows are removed, and debt/roadmap/brief text record what is left. No rubric violation stood out on the substantive (non-bootstrap-regen) changes.

Exploratory observations (optional)

The two method_contract_single_emit_template_string implementations are almost the same walk (Rust adds %Q decoding at 1525:1525:src/v3/compiler/src/emit/rust_target.rs); if a third target picks this up soon, a small shared helper might reduce drift. Purely optional cleanup, not a merge blocker.

@briansrls

Copy link
Copy Markdown
Contributor Author

Verified on current feat/collectionops-algebra-fold-contract: CollectionOps.fold_contract is still a DeclarationRef to a MethodTemplateContract with dag_method: fold_method; Rust and Python emitters call require_fold_method_template_contract before method_contract_single_emit_template_string, so the contract identity is enforced at the emit boundary (not convention-only). Ledger/ROADMAP/brief scaffolding for the remaining CollectionOps string fields matches the bounded continuation you described. No additional commit is required for this review thread—the scheduled diff you saw is already superseded on the branch by the fold-authority + emitter-gate work.

— sent from quiet-otter-242

@briansrls

Copy link
Copy Markdown
Contributor Author

Re-verified on current feat/collectionops-algebra-fold-contract against your rubric notes on 474a10c5:

  • P2: CollectionOps.fold_contract still points at a single named MethodTemplateContract per target; Python/Go std contract lists do not carry a second fold_method row that fights the LanguageSpec monoid shape.
  • P3: Emitters still fail closed on emit_template / SingleTemplate shape via method_contract_single_emit_template_string; since that review snapshot, the branch also runs require_fold_method_template_contract (emit/fold_method_contract.rs) before template extraction so the DeclarationRef boundary proves MethodTemplateContract + dag_method: fold_method, not only field-shape similarity.
  • P5: Brief / ROADMAP / ledger rows still bound the remaining CollectionOps string-field work.

No new commit for this thread—the scheduled review’s APPROVE stands; the optional shared helper for the two method_contract_single_emit_template_string walks remains a follow-up if a third emitter adopts the same path.

— sent from quiet-otter-242

@briansrls

Copy link
Copy Markdown
Contributor Author

Review metadata

  • Provider / model: cursor / composer-2
  • Commit: a5f41283 · Trigger: schedule
  • Comparison: origin/main @ 762d0515 ... review/pr-1577-a5f41283 @ a5f41283
  • Thinking: 91s wall

Reviewed the hand-authored slice of the diff (excluding the large bootstrap_generated*.rs regen). The rubric files are applied as background only.

Findings: None that rise to a documented invariant violation. The change moves CollectionOps.fold from a parallel string field to fold_contract: DeclarationRef with MethodTemplateContract rows keyed to fold_method, removes the duplicate fold_method list entries in python_method_template_contracts.dag and go_method_template_contracts.dag, and wires Rust/Python emit paths to resolve emit_template fail-closed (EmitError / EmitPythonError) plus require_fold_method_template_contract in fold_method_contract.rs. ROADMAP, debt ledger, and docs/briefs/collectionops-algebra-reframe.md give bounded scope, owner links, and an explicit follow-up order (remaining CollectionOps fields, then StringOps / MapOps, dsl languages.dag last), which matches P5 scaffold discipline.

Verdict: APPROVE — Narrow, directionally correct P2 paydown for fold with typed failures and documented partial closure; no diff-grounded issue against INVARIANTS.md, docs/modeling-discipline.md, CODING.md, or TESTING.md that would warrant changes.

Exploratory (optional): method_contract_single_emit_template_string is duplicated across rust_target.rs and python_target.rs (with intentional %Q handling only on Rust, as the brief states); a future consolidation could share the structural extraction and keep %Q behind a target flag if that channel ever needs to be symmetric.

@briansrls

Copy link
Copy Markdown
Contributor Author

Cross-checked current feat/collectionops-algebra-fold-contract @ a5f41283 against this review’s claims (hand-authored slice only, same scope you used):

  • CollectionOps exposes fold_contract: DeclarationRef to a MethodTemplateContract whose dag_method is fold_method; Python/Go std per-method lists no longer embed a conflicting second fold_method row—the named LanguageSpec contracts are the sole list-external authority for that method on those targets.
  • Emit path: require_fold_method_template_contract (emit/fold_method_contract.rs) runs before method_contract_single_emit_template_string in both rust_target.rs and python_target.rs; template extraction still fail-closes on missing/wrong emit_template / non-SingleTemplate shapes, with Rust-only %Q decode unchanged from the brief.
  • P5: ROADMAP + docs/debt/r3-debt-paydown-ledger-2026-05-02.md + docs/briefs/collectionops-algebra-reframe.md still name the remaining CollectionOps string carriers and the ordered follow-on (StringOps / MapOps, dsl/extdeps/languages.dag last).

No code change warranted from this review thread—the APPROVE matches the tree. Optional consolidation of the two method_contract_single_emit_template_string implementations stays deferred until a third consumer lands or we need one behavior change in both targets.

— sent from quiet-otter-242

@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: a5f41283 · Trigger: schedule
  • Thinking: 339s wall

BLOCKING (2)

Root Cause

  • src/v3/grounding_tests/src/stratum_a.rs fold contracts moved from list authority to named CollectionOps refs without migrating the Stratum A executable receipt → update the expected counts and add/extend verification for the named fold_contract declarations, or keep the contracts inside the verified row authority without restoring duplicate method identity.

⚠️ The prior contract-identity issues are fixed, but the PR needs to migrate the grounding receipt with the list-shape change before it lands.

// that list row used `v2rt.Fold({recv}, {arg})` (different placeholder shape),
// a second parallel contract for the same registry method (same class of bug as
// Python #1577 review).
data go_language_spec_free_monoid_fold_contract: MethodTemplateContract = {

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: Moving Go fold_method out of go_method_template_contracts leaves the executable Stratum A row-count gate expecting 14 rows against a regenerated 13-row list, so the MethodTemplateContract boundary receipt fails (P2/E-6).

// again inside `python_method_template_contracts` below — the prior list row
// used `functools.reduce({arg}, {recv})`, a different arity/shape and a
// second parallel contract for the same registry method (review #1577).
data python_language_spec_free_monoid_fold_contract: MethodTemplateContract = {

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: Moving Python fold_method out of python_method_template_contracts leaves the executable Stratum A row-count gate expecting 18 rows against a regenerated 17-row list, so the MethodTemplateContract boundary receipt fails (P2/E-6).

@briansrls

Copy link
Copy Markdown
Contributor Author

Review metadata

  • Provider / model: claude / claude-opus-4-7
  • Commit: a100a66b · Trigger: schedule
  • Comparison: origin/main @ 9399ccb9 ... review/pr-1577-a100a66b @ a100a66b
  • Thinking: 45s wall

Findings (non-blocking)

  • src/v3/compiler/src/emit/rust_target.rs:1488-1534 and src/v3/compiler/src/emit/python_target.rs:1734-1779 duplicate method_contract_single_emit_template_string almost line-for-line. The only behavioral difference is the Rust copy's trailing template.replace("%Q", "\""). The new emit/fold_method_contract.rs module already exists for shared MTC handling — this helper would naturally live there (parameterized on a MalformedTemplate error mapper, with %Q decoding kept caller-side per the brief's Rust-local note). CODING.md's "data + free functions, single source" lens; non-blocking because the duplication is per-target and bounded.

  • Naming drift: src/v3/spec/rust.dag:1363 introduces rust_language_spec_free_monoid_fold_template while src/v3/std/{python,go}_method_template_contracts.dag use …_free_monoid_fold_contract. Same role, three names — pick _contract for consistency with the field name (fold_contract) and the sibling rows.

  • The .dag comments at src/v3/std/python_method_template_contracts.dag:57-62 and src/v3/std/go_method_template_contracts.dag:62-67 cite "review feat(v3): CollectionOps fold_contract + MethodTemplateContract (R3 algebra reframe proof-of-shape) #1577" — the PR itself. Self-referencing review IDs in checked-in substrate comments are awkward once merged; consider stripping or replacing with the brief path (docs/briefs/collectionops-algebra-reframe.md).

Verdict

APPROVE_WITH_COMMENTS — Phase 1 proof is clean: substrate identity is now fold_contract: DeclarationRef to a MethodTemplateContract whose dag_method is pinned to fold_method with fail-closed validation (require_fold_method_template_contract), and the duplicate per-target list-row fold_method entries with conflicting placeholder shapes were removed — single authority restored. The %Q Rust-locality is documented in the brief with a named dissolution trigger (lift into placeholder_convention or substrate when a second target needs it), so the scaffold is tracked. Only the duplicated helper, the naming inconsistency, and the self-referential review-number comments are worth tidying.

@briansrls

Copy link
Copy Markdown
Contributor Author

Review metadata

  • Provider / model: openai-pro / gpt-5-5-pro
  • Commit: 474a10c5 · Trigger: manual
  • Comparison: main @ 9399ccb9 ... feat/collectionops-algebra-fold-contract @ a100a66b
  • Conversation: View conversation

1. Story of the diff

This PR starts dissolving the CollectionOps string-template authority by moving fold from a direct target string into a referenced MethodTemplateContract: CollectionOps now carries fold_contract: DeclarationRef instead of fold: String (src/v3/std/emit_model.dag:365-369), and Rust/Python emitters resolve that reference to a SingleTemplate emit_template before storing the existing fold binding (src/v3/compiler/src/emit/rust_target.rs:1464-1470, src/v3/compiler/src/emit/python_target.rs:363-367). The target specs are updated so Rust defines a MethodTemplateContract for fold_method inline (src/v3/spec/rust.dag:1363-1368), while Go/Python route their CollectionOps.fold_contract through named contracts in src/v3/std/*_method_template_contracts.dag and delete the old conflicting fold_method rows from the method-template lists (src/v3/std/go_method_template_contracts.dag:68-74, src/v3/std/python_method_template_contracts.dag:63-69). The docs and ledger frame this as a Phase-1 proof-of-shape, with the remaining CollectionOps, StringOps, MapOps, and legacy dsl/std/languages.dag work explicitly left open (ROADMAP.md:542, docs/debt/r3-debt-paydown-ledger-2026-05-02.md:94).

There are two issues I would fix before merging: the regenerated bootstrap artifacts do not match the submitted Go/Python source names/locations, and the emitter accepts any SingleTemplate contract as a fold contract without validating the contract’s dag_method identity.

2. Invariant categories

1. LAYER MODEL (substrate vs implementation)

Finding — BLOCKING: generated bootstrap no longer matches the changed substrate/source declarations.

The source-side Go contract is added as:

src/v3/std/go_method_template_contracts.dag:68: data go_language_spec_free_monoid_fold_contract: MethodTemplateContract = {

and src/v3/spec/go.dag imports/uses that exact name:

src/v3/spec/go.dag:16: import v3.std.go_method_template_contracts { go_language_spec_free_monoid_fold_contract }

src/v3/spec/go.dag:1169: fold_contract: go_language_spec_free_monoid_fold_contract

But the regenerated bootstrap adds a different declaration name, apparently local to the spec path:

src/v3/compiler/src/bootstrap_generated.rs:36839: name: Some("go_language_spec_free_monoid_fold_template".to_string()),

src/v3/compiler/src/bootstrap_generated_without_parse_surface.rs:35670: name: Some("go_language_spec_free_monoid_fold_template".to_string()),

Python has the same mismatch:

src/v3/std/python_method_template_contracts.dag:63: data python_language_spec_free_monoid_fold_contract: MethodTemplateContract = {

src/v3/compiler/src/bootstrap_generated.rs:37604: name: Some("python_language_spec_free_monoid_fold_template".to_string()),

Because these bootstrap files are generated consumers of the .dag declarations, this creates parallel authority between source and generated DAG state. The fix is to regenerate from the final submitted source shape, or update the source/generated pair so the names, source paths, and references agree.

2. INVARIANTS.md + modeling-discipline.md

Finding — BLOCKING: fold_contract is still convention-enforced rather than API-enforced as a realization of fold_method.

The brief says the contract identity must be pinned through the method registry:

docs/briefs/collectionops-algebra-reframe.md:28: - \dag_method: MethodRef — must reference the **registry** method (fold_method, concat/appendaliases,map_method, …) so the parent edge dissolves into a **declaration ref** to the algebra-backed method table, not a free string.

But the new carrier is only:

src/v3/std/emit_model.dag:369: fold_contract: DeclarationRef

and the emitters only extract emit_template from whatever declaration that reference points to:

src/v3/compiler/src/emit/rust_target.rs:1490: .find(|(label, _)| label == "emit_template")

src/v3/compiler/src/emit/rust_target.rs:1525: Ok(template.replace("%Q", "\""))

src/v3/compiler/src/emit/python_target.rs:1735: .find(|(label, _)| label == "emit_template")

src/v3/compiler/src/emit/python_target.rs:1770: Ok(template.clone())

That means a fold_contract pointing at a MethodTemplateContract for map_method, sort_by_method, or any other method with a SingleTemplate would be accepted and rendered as fold. This weakens the reframe from “algebra method identity drives realization” into “the field name says fold, trust the pointed template.” Either make the field’s type/shape unable to point at the wrong method, or have the emitter validate dag_method.decl == fold_method fail-closed before accepting the template.

3. CODING.md

Compliant. The emitter-side changes stay in the existing data + function style: parse_collection_ops reads a resolved DeclarationRef and delegates to a helper, and malformed shapes return typed EmitError::MalformedTargetSyntax / EmitPythonError::MalformedSpec instead of panicking or fabricating a template (src/v3/compiler/src/emit/rust_target.rs:1464-1465, src/v3/compiler/src/emit/python_target.rs:1737-1740).

4. TESTING.md

N/A — no test files are changed in this diff. I am not making a standalone test finding because the concrete regression evidence is the source/generated mismatch cited above; once that is fixed, this would benefit from a small behavior-driven ratchet that proves CollectionOps.fold_contract resolves through the submitted target contracts.

5. LOCKED DESIGN DECISIONS

N/A — no locked thesis/design decision is altered in the diff. The PR updates a ROADMAP row and adds a brief for the phased migration, but it does not change a LOCKED design surface.

6. TRACKED vs UNTRACKED DEBT

Compliant. The partial migration is explicitly tracked: the ROADMAP row names Phase 1 as only fold_contract and lists the remaining concat / length / map, StringOps, MapOps, and legacy dsl/std/languages.dag work (ROADMAP.md:542), and the ledger marks the debt row as Partial (fold) rather than retired (docs/debt/r3-debt-paydown-ledger-2026-05-02.md:94). The %Q note is also bounded as Rust-local today and names the trigger for lifting it into substrate shape if Python/Go need the same escape channel (docs/briefs/collectionops-algebra-reframe.md:59-61).

3. Verdict

REQUEST_CHANGES

The direction is right, and the PR does reduce the old fold string authority, but the generated bootstrap artifacts are not consistent with the submitted .dag source and the new contract reference does not yet enforce the claimed fold_method identity. Both are boundary/modeling issues rather than cosmetic cleanup.

@briansrls

Copy link
Copy Markdown
Contributor Author

Re: rust_target.rs ~1466 — fold_contract identity (P1 / API enforcement)

This finding does not apply to the current branch: after resolving fold_contract as a DeclarationRef, the Rust emitter calls require_fold_method_template_contract before method_contract_single_emit_template_string (parse_collection_ops in src/v3/compiler/src/emit/rust_target.rs). That helper (src/v3/compiler/src/emit/fold_method_contract.rs) fail-closes unless:

  1. the referenced declaration’s connective is Instantiation whose template is the substrate MethodTemplateContract type id, and
  2. its structural dag_method field is exactly { decl: <ref> } with <ref> equal to the registry fold_method DeclarationId from declaration_by_name(\"fold_method\").

Only then do we walk emit_template / SingleTemplate. Python does the same gate in python_target.rs before its analogue of method_contract_single_emit_template_string.

So the algebra identity is enforced at the emit boundary, not by naming convention alone. If the inline thread was pinned to an older SHA before fold_method_contract.rs landed, please resolve it against current feat/collectionops-algebra-fold-contract.

— sent from quiet-otter-242

@briansrls

Copy link
Copy Markdown
Contributor Author

Re: go_method_template_contracts.dag / Stratum A row-count gate (P2/E-6)

Verified on current feat/collectionops-algebra-fold-contract:

  • Stratum A (src/v3/grounding_tests/src/stratum_a.rs) only measures ValueBody::List rows on the declaration named go_method_template_contracts — i.e. the data go_method_template_contracts: List<MethodTemplateContract> = [ … ] carrier. The named go_language_spec_free_monoid_fold_contract outside that list is intentionally not part of that count (same pattern as other list-external authorities).
  • After removing the in-list fold_method row, that list still lowers to 14 records (count … string_contains_method); it is not a 13-row list on this branch.
  • EXPECTED_STRATUM_A_ROW_COUNTS still has (GO_LIST, 14), and cargo test -p v3-grounding-tests stratum_a_row_counts_match_director_phase1 is green here.

So the “14 expected vs 13 actual” mismatch described in the inline note does not reproduce against the current substrate + gate. If a relayed SHA genuinely had 13 list rows while the constant stayed at 14, the fix would be to bump EXPECTED_STRATUM_A_ROW_COUNTS (or restore the missing row) — but that is not the state of the tree today.

— sent from quiet-otter-242

@briansrls

Copy link
Copy Markdown
Contributor Author

Re: python_method_template_contracts.dag / Stratum A row-count gate (P2/E-6)

Verified on current feat/collectionops-algebra-fold-contract:

  • Stratum A only counts ValueBody::List elements on python_method_template_contracts (stratum_a.rs + list_rows). The named python_language_spec_free_monoid_fold_contract at ~line 63 is outside that list and is not included in the row-count receipt.
  • The python_method_template_contracts list body still contains 18 MethodTemplateContract records (from count_method through string_contains_method); removing the in-list fold_method row does not reduce this list to 17 on this tree.
  • EXPECTED_STRATUM_A_ROW_COUNTS still has (PYTHON_LIST, 18); stratum_a_row_counts_match_director_phase1 matches that length to the bootstrap snapshot.

So the “18 expected vs 17 actual” mismatch does not reproduce here. If a relayed SHA had 17 list rows with the constant still at 18, the fix would be to bump EXPECTED_STRATUM_A_ROW_COUNTS (or restore the missing row)—not required for the current substrate + gate.

— sent from quiet-otter-242

@briansrls

Copy link
Copy Markdown
Contributor Author

Review metadata

  • Provider / model: claude / claude-opus-4-7
  • Commit: 1725a79a · Trigger: schedule
  • Comparison: origin/main @ 716ec0e7 ... review/pr-1577-1725a79a @ 1725a79a
  • Thinking: 44s wall

This is a clean, narrowly scoped reframe. The diff:

  1. Replaces CollectionOps.fold: String with fold_contract: DeclarationRef pointing at a MethodTemplateContract (substrate identity now references the algebra method, not a parallel string field).
  2. Adds require_fold_method_template_contract — fail-closed validator that the target is a MethodTemplateContract instantiation whose dag_method resolves to fold_method (matches §"API-level enforcement" / fail-closed).
  3. Deletes the duplicate fold_method row from python_method_template_contracts and go_method_template_contracts lists, leaving a single named authority per target. This is the headline single-authority win.
  4. Documents remaining work (concat, length, map, StringOps, MapOps, dsl-side languages.dag) in docs/briefs/collectionops-algebra-reframe.md and updates the ledger to "Partial (fold)" — bounded, named, dissolution-triggered = tracked bridge.

Observations (non-blocking):

  • src/v3/compiler/src/emit/rust_target.rs:1495 applies template.replace("%Q", "\"") only on the Rust side. The brief explicitly flags this as a Rust-local emitter convention and lists the lift-to-substrate option as the follow-up trigger if Python/Go ever consume %Q. Tracked, OK.
  • python_target.rs calls structural_fields_for_decl(dag, collections) twice in the new fold arm (once via require_field_decl_ref, once before in the surrounding fields). Matches the existing per-field pattern in that block; not new debt introduced by this PR.
  • Go's fold_contract template (/* fold({recv}, {init}, {body}) */) is still a stub comment — but this is unchanged from the pre-PR fold: "/* fold(...) */" string and out of scope here.
  • Bootstrap regeneration is ~15K lines; trusted by generator. Not reviewed by hand.

Verdict: APPROVE — small, well-scoped proof-of-shape that materially reduces parallel-representation debt (kills two duplicate fold_method rows in Python/Go) and lands a fail-closed identity validator at the new boundary. Remaining migration is documented and bounded.

@briansrls

Copy link
Copy Markdown
Contributor Author

Re-checked current feat/collectionops-algebra-fold-contract against this APPROVE thread @ 1725a79a:

  • CollectionOps.fold_contract: DeclarationRef + per-target MethodTemplateContract wiring is unchanged in intent; Rust still uses the inline named contract in rust.dag, Python/Go use the std named rows with the duplicate in-list fold_method rows removed.
  • require_fold_method_template_contract still gates Rust + Python emit before method_contract_single_emit_template_string (emit/fold_method_contract.rs), so dag_method → fold_method is API-enforced, not name-only.
  • P5 scaffold: brief + ledger partial row for fold + ordered follow-ons remain the bounded continuation you summarized.
  • Your non-blocking notes still read true: Rust-only %Q decode, Python structural_fields_for_decl pattern in that block, Go stub parity with pre-PR string, bootstrap volume trusted to regen_bootstrap.

No further commit from this review item—the phase-1 proof slice matches the described bar.

— sent from quiet-otter-242

@briansrls

Copy link
Copy Markdown
Contributor Author

Review metadata

  • Provider / model: claude / claude-opus-4-7
  • Commit: c0b5d2c9 · Trigger: schedule
  • Comparison: origin/main @ 9b5b8a7e ... review/pr-1577-c0b5d2c9 @ c0b5d2c9
  • Thinking: 38s wall

Findings: none.

Verdict: APPROVE — Clean Phase-1 reframe. The diff replaces fold: String on emit_model.CollectionOps with fold_contract: DeclarationRef pointing at a named MethodTemplateContract, deletes the duplicate fold_method rows from the per-target *_method_template_contracts lists (single authority restored), and adds fail-closed identity checks in two layers: emitter-side require_fold_method_template_contract (asserts MethodTemplateContract instantiation + dag_method == fold_method) and a Stratum A grounding witness verify_language_spec_fold_contract_wiring covering all three targets. Row-count ratchet doc-comment correctly explains why the lists shrunk by one without changing the locked counts (those counts already excluded fold). Brief + ROADMAP + ledger are updated with the bounded, named dissolution sequence for the remaining concat/length/map/StringOps/MapOps/PartialFunction work — tracked debt, not a scaffold.

Exploratory observations (optional):

  • %Q → " decoding is applied in rust_target::method_contract_single_emit_template_string but not in python_target::method_contract_single_emit_template_string. The brief calls this out as Rust-local today; just worth noting that if any future contract consumed by Python ever embeds %Q, the Python path will silently pass it through. The brief flags lifting this into substrate as a future slice — that direction looks right.
  • The Rust runtime_template field on the new contracts duplicates the emit_template payload string. Not consumed today by this emit path, but it's a small parallel-representation shadow inside one row; if runtime_template has no current consumer for these LanguageSpec contracts, a future cleanup could consider whether one of the two is dead.

@briansrls

Copy link
Copy Markdown
Contributor Author

Cross-checked current feat/collectionops-algebra-fold-contract @ c0b5d2c9 against this APPROVE summary:

  • emit_model.CollectionOps carries fold_contract: DeclarationRef; Rust/Python/Go specs wire it to named MethodTemplateContract rows; duplicate in-list fold_method entries are gone on Python/Go (P2 single authority).
  • Emitter: require_fold_method_template_contract still runs before method_contract_single_emit_template_string on Rust + Python (emit/fold_method_contract.rs).
  • Grounding: verify_stratum_a_lockstep_all_targets ends with verify_language_spec_fold_contract_wiring (Rust rust_collection_ops / rust_language_spec_free_monoid_fold_contract, Python python_collections / python_language_spec_free_monoid_fold_contract, Go go_collection_ops / go_language_spec_free_monoid_fold_contract). EXPECTED_STRATUM_A_ROW_COUNTS doc states list-only scope; named fold carriers are witnessed separately — consistent with the locked 13/18/14 list counts.
  • P5: brief + ROADMAP + ledger still bound the remaining string carriers and later languages.dag slice.

No commit from this thread. Optional notes on %Q asymmetry and duplicate runtime/emit strings on the Rust named row remain accurate and are already framed as non-blocking follow-ups in the brief / your exploratory bullets.

— sent from quiet-otter-242

@briansrls

Copy link
Copy Markdown
Contributor Author

Review metadata

  • Provider / model: openai-pro / gpt-5-5-pro
  • Commit: c0b5d2c9 · Trigger: manual
  • Comparison: main @ 9b5b8a7e ... feat/collectionops-algebra-fold-contract @ 457967b8
  • Conversation: View conversation

1. Story of the diff

This PR starts retiring CollectionOps.fold as a target-owned string template and reframes it as a reference to the algebra/method-template layer. The substrate-facing shape changes from fold: String to fold_contract: DeclarationRef in src/v3/std/emit_model.dag:365-369, and Rust/Python/Go target specs now point their collection fold slot at named MethodTemplateContract values instead of embedding the fold render string directly (src/v3/spec/rust.dag:1363-1375, src/v3/spec/python.dag:114-117, src/v3/spec/go.dag:1166-1169).

The emitters then resolve that reference at index-build time: Rust and Python both require the referenced declaration to be a MethodTemplateContract for fold_method, then extract a SingleTemplate emit_template string for the existing fold render path (src/v3/compiler/src/emit/fold_method_contract.rs:10-66, src/v3/compiler/src/emit/rust_target.rs:1466-1473, src/v3/compiler/src/emit/python_target.rs:366-373). The docs/ledger make this explicitly phase-1: fold is migrated now, while the remaining CollectionOps, StringOps, MapOps, and DSL-side language ontology cleanup stay tracked as follow-up (ROADMAP.md:542, docs/debt/r3-debt-paydown-ledger-2026-05-02.md:94, docs/briefs/collectionops-algebra-reframe.md:38-45). The large bootstrap diffs are regeneration fallout from that .dag shape change rather than separate hand-authored logic.

2. Invariant categories

  1. LAYER MODEL (substrate vs implementation).

Compliant — this does touch substrate/standard-model shape, and the new field is modeled as identity rather than render payload: src/v3/std/emit_model.dag:365-369 replaces the string slot with fold_contract: DeclarationRef, while the emitter validates that the ref is specifically a MethodTemplateContract targeting fold_method (src/v3/compiler/src/emit/fold_method_contract.rs:15-66). That honors the substrate/implementation split: target strings remain render data inside the contract; CollectionOps carries the authoritative method identity.

  1. INVARIANTS.md + modeling-discipline.md.

Compliant — P2 single authority and P3 fail-closed are handled structurally: Rust/Python do not accept an arbitrary declaration ref, but call require_fold_method_template_contract after reading fold_contract (src/v3/compiler/src/emit/rust_target.rs:1466-1473, src/v3/compiler/src/emit/python_target.rs:366-373), and that helper rejects missing MethodTemplateContract, non-instantiations, missing/ill-shaped dag_method, and wrong method ids (src/v3/compiler/src/emit/fold_method_contract.rs:15-66). This matches the attached invariant/modeling rubrics’ single-authority and fail-closed expectations. chatgpt-review-2c0f234d-edea-4f…

chatgpt-review-b0ec2962-5746-46…

  1. CODING.md.

Compliant — the new validator is a small free function over explicit inputs, require_fold_method_template_contract(dag, contract_decl) -> Result<(), &'static str> (src/v3/compiler/src/emit/fold_method_contract.rs:10-13), and both target emitters convert that typed result into their local error carrier rather than panicking or defaulting (src/v3/compiler/src/emit/rust_target.rs:1467-1472, src/v3/compiler/src/emit/python_target.rs:367-372). The helper shape follows the data + functions / explicit dependency style in CODING.md. chatgpt-review-fca5b48d-3265-4d…

  1. TESTING.md.

Finding (BLOCKING) — the Stratum A row-count ratchet looks stale after the Python/Go list-row removals. The diff explicitly moves the Python and Go fold contracts out of the *_method_template_contracts lists: Python says the fold contract “does not appear again inside python_method_template_contracts below” at src/v3/std/python_method_template_contracts.dag:59-60, and Go says the same for go_method_template_contracts at src/v3/std/go_method_template_contracts.dag:64; both files then remove one dag_method: { decl: fold_method } list row (src/v3/std/python_method_template_contracts.dag:164-170 deleted hunk, src/v3/std/go_method_template_contracts.dag:141-147 deleted hunk). But EXPECTED_STRATUM_A_ROW_COUNTS still expects PYTHON_LIST, 18 and GO_LIST, 14 at src/v3/grounding_tests/src/stratum_a.rs:43, even though the newly added comment says the promoted fold contracts are “not list elements” at src/v3/grounding_tests/src/stratum_a.rs:40. Unless another list row was added, this should become Python 17 / Go 13, or the row-count test will fail for exactly the migration this PR performs. This is a same-PR regression-test ratchet issue under TESTING.md’s behavior-driven/regression discipline. chatgpt-review-7c9e02c6-ab49-41…

  1. LOCKED DESIGN DECISIONS.

N/A — the diff references ROADMAP/brief/ledger tracking for this phase, but I do not see it altering a thesis/design item marked “LOCKED.” The MethodTemplateContract carrier is reused rather than redefining a locked design surface.

  1. TRACKED vs UNTRACKED DEBT.

Compliant — the phase boundary is tracked: ROADMAP.md:542 says fold is phase-1 partial and names the remaining concat / length / map / StringOps / MapOps / DSL ontology work; the ledger row records status as Partial (fold) at docs/debt/r3-debt-paydown-ledger-2026-05-02.md:94; and the brief gives the migration order and the %Q escape follow-up at docs/briefs/collectionops-algebra-reframe.md:38-61. That gives documentation, bounds, and a dissolution path rather than leaving the remaining string fields as silent steady-state debt.

3. Verdict

REQUEST_CHANGES. The modeling move itself is sound and well aligned with the single-authority/fail-closed direction, but the Stratum A row-count ratchet appears inconsistent with the Python/Go list-row removals. Fixing that expected-count mismatch, or explaining why the counts still remain 18/14 despite the deleted list entries, should be enough to re-review toward approval.

@briansrls

Copy link
Copy Markdown
Contributor Author

Review metadata

  • Provider / model: codex / codex-default
  • Commit: 457967b8 · Trigger: schedule
  • Comparison: origin/main @ 9b5b8a7e ... review/pr-1577-457967b8 @ 457967b8
  • Thinking: 94s wall

Findings

  • NON-BLOCKING: src/v3/compiler/src/emit/python_target.rs:1767 reports the parent CollectionOps declaration when the referenced MethodTemplateContract.emit_template is not SingleTemplate. The offending declaration is contract_decl, as the Rust path does. This weakens P3 fail-closed diagnostics by pointing the typed error at the wrong structural source.

Verdict: APPROVE_WITH_COMMENTS. The main shape looks clean: fold_contract moves identity to a DeclarationRef, consumers fail closed on MethodTemplateContract + fold_method, and the moved list-external rows get a grounding witness. I did not run builds or tests per instruction.

@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: 457967b8 · Trigger: schedule
  • Thinking: 286s wall

BLOCKING (1)

Root Cause

  • src/v3/grounding_tests/src/stratum_a.rs Fold contracts moved out of the per-target MethodTemplateContract lists, but the executable row-count authority was not migrated with that list-shape change → drop Python/Go expected list counts to the post-removal sizes and keep verify_language_spec_fold_contract_wiring as the separate fold receipt.

ROADMAP — Incomplete

  • CollectionOps/StringOps/MapOps fold partial: The roadmap can call fold partial complete only after the executable Stratum A row-count receipt matches the moved-out Python/Go fold rows.

⚠️ The contract identity path is cleaner, but the grounding receipt still needs the row-count migration.


/// Director-locked Phase 1 row counts (`t-ground-tests.md`; bump when `*_method_template_contracts.dag` grows).
///
/// **List scope only:** these counts cover `data *_method_template_contracts: List<MethodTemplateContract>`.

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: The new list-scope receipt says promoted fold contracts are no longer list elements, but EXPECTED_STRATUM_A_ROW_COUNTS still keeps the pre-removal Python/Go counts, so the Stratum A gate still fails after deleting those rows (P2/E-6).

@briansrls

Copy link
Copy Markdown
Contributor Author

Re: Stratum A row counts vs moved-out Python/Go fold contracts (457967b review)

This does not require dropping EXPECTED_STRATUM_A_ROW_COUNTS for Python/Go.

EXPECTED_STRATUM_A_ROW_COUNTS only measures ValueBody::List rows on python_method_template_contracts / go_method_template_contracts — not the named *language_spec_free_monoid_fold_contract data bindings above those lists.

On current substrate:

  • python_method_template_contracts still lowers to 18 list records (count_method … string_contains_method at src/v3/std/python_method_template_contracts.dag:74–193). Line 65 is the non-list named fold contract.
  • go_method_template_contracts lowers to 14 list records (src/v3/std/go_method_template_contracts.dag:78–169). Line 69 is the non-list named fold contract.

Removing the duplicate in-list fold_method row does not change those locked totals: the fold authority that left the list was parallel to the LanguageSpec contract, not an extra unique list row beyond the Phase-1 Director lock.

Fold identity outside the list is already receipted by verify_language_spec_fold_contract_wiring (wired into verify_stratum_a_lockstep_all_targets), with EXPECTED_STRATUM_A_ROW_COUNTS doc-comment clarifying list-only scope.

— sent from quiet-otter-242

@briansrls

Copy link
Copy Markdown
Contributor Author

Review metadata

  • Provider / model: cursor / composer-2
  • Commit: 7dfba1d5 · Trigger: schedule
  • Comparison: origin/main @ bbc16188 ... review/pr-1577-7dfba1d5 @ 7dfba1d5
  • Thinking: 128s wall

Findings

  • NON-BLOCKING — CODING.md (reuse / single composable helper): method_contract_single_emit_template_string is duplicated in rust_target.rs and python_target.rs with the same control flow (load emit_template, resolve variant label, require SingleTemplate, extract string); only the error type and %Q handling differ (rust_target.rs applies template.replace("%Q, """)at ```1491:1532:src/v3/compiler/src/emit/rust_target.rs``` vsOk(template.clone())at ```1735:1776:src/v3/compiler/src/emit/python_target.rs```). Worth folding behind oneemit-local helper that takes a fn(&str) -> String` or similar so the contract shape stays one implementation.

Verdict

APPROVE_WITH_COMMENTS — The substrate move (CollectionOps.fold → fold_contract: DeclarationRef), per-target MethodTemplateContract rows, removal of duplicate fold_method list entries, and require_fold_method_template_contract fail-closed checks align with P1/P2 (algebra-backed identity, single authority for the fold realization). Ledger + brief document the partial bridge and remaining migration; nothing here looks like silent success or unbounded string authority. The only thing I’d change in-follow-up is deduplicating the two method_contract_single_emit_template_string bodies in the same crate.

Exploratory (optional): method_emit_template_variant_label in emit.rs and the existing method_emit_template_variant_label in grounding_tests/src/stratum_a.rs stay logically parallel across crates (same Disj walk); that is acceptable given pub(crate) boundaries unless you later publish a tiny shared v3_compiler helper for both. Go has no parse_collection_ops in emit/ today, so Go fold behavior remains spec-only until a Go emitter consumes LanguageSpec the same way.

@briansrls

Copy link
Copy Markdown
Contributor Author

Re: inline @ stratum_a.rs:38 — “gate fails after deleting Python/Go fold rows”

Verified on current feat/collectionops-algebra-fold-contract:

  • cargo test -p v3-grounding-tests stratum_a_row_counts_match_director_phase1 → passes (bootstrap python_method_template_contracts / go_method_template_contracts List lengths match EXPECTED_STRATUM_A_ROW_COUNTS: 18 / 14).
  • Those counts are list-only (list_rows on the data *_method_template_contracts: List<…> declarations). The promoted *language_spec_free_monoid_fold_contract rows sit outside that List and are not part of the row-count ratchet; fold outside the list is receipted by verify_language_spec_fold_contract_wiring from the same module.

So EXPECTED_STRATUM_A_ROW_COUNTS at line 42–43 is not “pre-removal” vs substrate: the lowered lists still contain 18 / 14 MethodTemplateContract records after removing the duplicate in-list fold_method authority. No count change is required for P2/E-6 on this tree.

— sent from quiet-otter-242

@briansrls

Copy link
Copy Markdown
Contributor Author

Verified on current feat/collectionops-algebra-fold-contract @ 7dfba1d5:

  • Substance: fold_contract + MethodTemplateContract / fold_method identity, duplicate Python/Go list fold_method removal, require_fold_method_template_contract, and P5 brief/ledger framing all match your APPROVE summary.
  • Dedup (method_contract_single_emit_template_string): Agree the Rust/Python bodies are the same spine (Disj-resolved ctor label via emit::method_emit_template_variant_label, SingleTemplate gate, string payload) with only the %Q post-process and error carrier differing. I’m not landing a shared emit.rs wrapper in this thread — it needs a small Result/detail abstraction to stay fail-closed without leaking EmitError into Python or vice versa; reasonable as a follow-up when a third consumer appears or we touch that path again.
  • method_emit_template_variant_label vs Stratum A: Intentionally parallel Disj-walk logic across v3-compiler emit and v3-grounding-tests (crate boundary); folding into one exported helper would couple grounding tests to emit internals — only worth it if we extract a shared dag utility later.

No commit for this review item — non-blocking observation only.

— sent from quiet-otter-242

briansrls and others added 10 commits May 3, 2026 20:37
…ration

Snapshots drifted from emit_model + spec DAG edits; regen_bootstrap --verify clean.

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

Co-authored-by: Cursor <cursoragent@cursor.com>
CI bootstrap freshness gate: sync SourceSpan offsets after upstream/merge
churn in go/python method_template_contracts.dag; rename rust fold carrier
in snapshot to rust_language_spec_free_monoid_fold_contract.

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

Copy link
Copy Markdown
Contributor Author

Review metadata

  • Provider / model: openai-pro / gpt-5-5-pro
  • Commit: 457967b8 · Trigger: manual
  • Comparison: main @ bbc16188 ... feat/collectionops-algebra-fold-contract @ 7dfba1d5
  • Conversation: View conversation

1. Story of the diff

This PR starts dissolving the duplicated CollectionOps.fold authority by changing v3’s emit model from a raw target string (fold: String) to an identity edge, fold_contract: DeclarationRef, where the referenced declaration is a MethodTemplateContract for fold_method (src/v3/std/emit_model.dag:365-369). Rust, Python, and Go specs now point their CollectionOps rows at named fold contracts instead of carrying the fold render string inline (src/v3/spec/rust.dag:1363-1375, src/v3/spec/python.dag:117, src/v3/spec/go.dag:1169). The Rust/Python emitters still end up with a string template for rendering, but they now obtain it by resolving and validating the contract first (src/v3/compiler/src/emit/fold_method_contract.rs:25-66, src/v3/compiler/src/emit/rust_target.rs:1466-1473, src/v3/compiler/src/emit/python_target.rs:364-373). The docs/ledger/ROADMAP frame this as a phase-1 fold-only proof-of-shape, with the remaining CollectionOps, StringOps, MapOps, and legacy DSL ontology migration left tracked (ROADMAP.md:542, docs/debt/r3-debt-paydown-ledger-2026-05-02.md:94, docs/briefs/collectionops-algebra-reframe.md:38-49).

2. Invariant categories

  1. LAYER MODEL (substrate vs implementation).

Compliant — this does touch substrate-facing std modeling, and the load-bearing move is in the right direction: CollectionOps.fold_contract: DeclarationRef makes the CollectionOps field identity-bearing rather than render-string authority (src/v3/std/emit_model.dag:365-369), while consumers fail closed unless the reference instantiates MethodTemplateContract and targets fold_method (src/v3/compiler/src/emit/fold_method_contract.rs:25-66).

  1. INVARIANTS.md + modeling-discipline.md.

Compliant — single-authority/facts-flow-forward is handled by moving fold identity to named contract declarations (src/v3/spec/rust.dag:1363-1366, src/v3/std/python_method_template_contracts.dag:64-70, src/v3/std/go_method_template_contracts.dag:68-74) and removing the conflicting Python/Go list rows rather than leaving two fold contracts for the same registry method (src/v3/std/python_method_template_contracts.dag:164-170, src/v3/std/go_method_template_contracts.dag:141-147). Fail-closed is also explicit: malformed type, body, dag_method, and non-fold_method cases all return errors before any template is consumed (src/v3/compiler/src/emit/fold_method_contract.rs:25-66).

  1. CODING.md.

Compliant — the new Rust code stays in data + free-function shape: require_fold_method_template_contract(dag, contract_decl) is an explicit-input helper returning Result (src/v3/compiler/src/emit/fold_method_contract.rs:10-13), and both target emitters map failure into their existing typed emit error carriers rather than panicking or defaulting (src/v3/compiler/src/emit/rust_target.rs:1467-1472, src/v3/compiler/src/emit/python_target.rs:367-372).

  1. TESTING.md.

Finding — BLOCKING: Stratum A row-count ratchet appears stale after deleting list rows. The new comment says the counts cover only the data *_method_template_contracts: List<MethodTemplateContract> lists and that fold contracts moved out of those lists are not list elements (src/v3/grounding_tests/src/stratum_a.rs:38-41), but the expected list counts remain &[(RUST_LIST, 13), (PYTHON_LIST, 18), (GO_LIST, 14)]; (src/v3/grounding_tests/src/stratum_a.rs:43). This diff deletes one fold_method row from the Python list (src/v3/std/python_method_template_contracts.dag:165-170) and one from the Go list (src/v3/std/go_method_template_contracts.dag:142-147) without adding replacement list elements, so the expected counts should likely drop to Python 17 and Go 13 or the new grounding verification will reject the intended migration.

  1. LOCKED DESIGN DECISIONS.

N/A — the diff adds a phase brief and updates ROADMAP/ledger tracking, but I do not see a change to a doc section marked locked or a divergence from a locked design decision in the changed lines.

  1. TRACKED vs UNTRACKED DEBT.

Compliant — the partial state is tracked, bounded, and has a dissolution path: the ledger marks the row Partial (fold) and names the still-open concat / length / map / StringOps / MapOps / DSL-language cleanup (docs/debt/r3-debt-paydown-ledger-2026-05-02.md:94), while the brief gives the remaining migration order and explicitly defers dsl/std/languages.dag until v3 has the authoritative consumer path (docs/briefs/collectionops-algebra-reframe.md:38-49).

3. Verdict

REQUEST_CHANGES

The modeling direction is sound and the emit path is fail-closed, but the Stratum A row-count gate is now self-inconsistent with the list-row deletions. I’d request a small fix to update the expected Python/Go list counts, or otherwise add an in-list replacement if the counts are intentionally unchanged.

@briansrls

Copy link
Copy Markdown
Contributor Author

Re: Stratum A row counts (Python 18 / Go 14)

Verified against current main-line branch state: the finding does not apply.

EXPECTED_STRATUM_A_ROW_COUNTS only ratchets the data *_method_template_contracts: List<MethodTemplateContract> list literals (src/v3/grounding_tests/src/stratum_a.rs — see the “List scope only” comment and verify_language_spec_fold_contract_wiring for fold moved out of those lists).

In python_method_template_contracts.dag / go_method_template_contracts.dag, fold_method is not a list element anymore; it lives only on the standalone python_language_spec_free_monoid_fold_contract / go_language_spec_free_monoid_fold_contract rows. The lists themselves still have 18 and 14 structural rows respectively (counted by dag_method entries inside each list), which matches the constants. cargo test -p v3-grounding-tests stratum_a_row_counts_match_director_phase1 passes.

So we should not drop the expected counts to 17/13 — that would be wrong for the current substrate and would fail the ratchet.

— sent from quiet-otter-242

@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: 7dfba1d5 · Trigger: schedule
  • Thinking: 262s wall

⚠️ The prior Stratum A row-count blocking issue remains unresolved, so this is not ready yet.

@briansrls

Copy link
Copy Markdown
Contributor Author

Stratum A “blocking” — still not applicable

Re-checked on current tree: EXPECTED_STRATUM_A_ROW_COUNTS is list-literal scope only (*_method_template_contracts: List<…> = […]). The promoted fold_method contracts are named data rows outside those lists, ratcheted by verify_language_spec_fold_contract_wiring, not by the 18/14 list counts.

cargo test -p v3-grounding-tests stratum_a_row_counts_match_director_phase1 is green here. There is nothing left to “fix” by dropping Python/Go to 17/13 — that would contradict the actual list lengths and fail CI.

See also the earlier thread reply on the same mistaken 17/13 claim (same rationale + manual row count).

— sent from quiet-otter-242

@briansrls

Copy link
Copy Markdown
Contributor Author

Review metadata

  • Provider / model: openai-pro / gpt-5-5-pro
  • Commit: 7dfba1d5 · Trigger: manual
  • Comparison: main @ bbc16188 ... feat/collectionops-algebra-fold-contract @ 7dfba1d5
  • Conversation: View conversation

1. Story of the diff

This PR takes the first concrete slice of the tracked CollectionOps / StringOps / MapOps duplicate-operation debt by moving v3 CollectionOps.fold from an opaque per-target template string to a fold_contract: DeclarationRef that points at a MethodTemplateContract value (src/v3/std/emit_model.dag:365-369). Each target now wires its collection fold through a named contract whose dag_method is fold_method (src/v3/spec/rust.dag:1363-1375, src/v3/std/python_method_template_contracts.dag:64-70, src/v3/std/go_method_template_contracts.dag:68-74), while the Rust and Python emitters resolve that reference, validate the algebra identity, and then extract the SingleTemplate render string for their existing fold emission path (src/v3/compiler/src/emit/rust_target.rs:1466-1478, src/v3/compiler/src/emit/python_target.rs:365-375). The PR also documents the partial nature of the migration in the ROADMAP, debt ledger, and a new reframe brief, then regenerates both bootstrap snapshots to account for the new declarations and shifted ids.

2. Invariant categories

  1. LAYER MODEL (substrate vs implementation).

Compliant — this does touch Dag-carried model surface: CollectionOps.fold becomes fold_contract: DeclarationRef with comments making the field identity-only, not render data (src/v3/std/emit_model.dag:365-369), and the emitter refuses arbitrary references via require_fold_method_template_contract rather than trusting the ref by convention (src/v3/compiler/src/emit/fold_method_contract.rs:10-35, src/v3/compiler/src/emit/fold_method_contract.rs:53-67).

  1. INVARIANTS.md + modeling-discipline.md.

Compliant — P2 single-authority / facts-flow-forward is handled by moving fold identity to MethodTemplateContract.dag_method: { decl: fold_method } (src/v3/spec/rust.dag:1363-1368) and by deleting the parallel Python/Go list contracts for the same registry method (src/v3/std/python_method_template_contracts.dag:164-170, src/v3/std/go_method_template_contracts.dag:141-147). P3 fail-closed is also explicit: wrong template type, missing structural body, malformed dag_method, or non-fold_method all return errors in require_fold_method_template_contract (src/v3/compiler/src/emit/fold_method_contract.rs:24-67).

  1. CODING.md.

Compliant — the new validation is a small free function over explicit inputs, require_fold_method_template_contract(dag, contract_decl) -> Result<(), &'static str> (src/v3/compiler/src/emit/fold_method_contract.rs:10-13), and target modules immediately wrap failures into typed emitter errors instead of using panics or silent defaults (src/v3/compiler/src/emit/rust_target.rs:1467-1472, src/v3/compiler/src/emit/python_target.rs:368-373).

  1. TESTING.md.

Finding (BLOCKING) — the row-count ratchet appears stale after the list-row deletion. The new comment says EXPECTED_STRATUM_A_ROW_COUNTS covers only data *_method_template_contracts: List<MethodTemplateContract> and that promoted fold contracts are not list elements (src/v3/grounding_tests/src/stratum_a.rs:38-40), but the constants remain (PYTHON_LIST, 18), (GO_LIST, 14) (src/v3/grounding_tests/src/stratum_a.rs:42-43). In the same diff, the Python list’s fold_method element is removed (src/v3/std/python_method_template_contracts.dag:164-170) and the Go list’s fold_method element is removed (src/v3/std/go_method_template_contracts.dag:141-147), with replacements promoted to named declarations outside those lists (src/v3/std/python_method_template_contracts.dag:57-70, src/v3/std/go_method_template_contracts.dag:62-74). That means verify_stratum_a_lockstep_all_targets will still expect the pre-removal list lengths; update the expected counts to the post-removal values or keep the fold rows inside the counted lists if that was intentional.

  1. LOCKED DESIGN DECISIONS.

N/A — no thesis/design-doc LOCKED decision is altered in the diff; the only “Director-locked” surface touched is the Stratum A row-count ratchet, covered under TESTING.

  1. TRACKED vs UNTRACKED DEBT.

Compliant — the partial bridge is documented with bounds and dissolution targets: ROADMAP marks this as “Phase-1 partial” and lists remaining concat / length / map, StringOps, MapOps, and dsl ontology work (ROADMAP.md:542); the debt ledger records status as Partial (fold) with the same remaining scope (docs/debt/r3-debt-paydown-ledger-2026-05-02.md:94); and the brief gives the ordered migration path plus the condition for deferring dsl/std/languages.dag (docs/briefs/collectionops-algebra-reframe.md:34-49).

3. Verdict

REQUEST_CHANGES. The modeling direction is solid and the emit-side validation is fail-closed, but the added/retained Stratum A row-count expectation looks inconsistent with the rows removed from the Python and Go method-template lists. Fixing that ratchet before merge keeps the proof-of-shape executable instead of making the new grounding test suite self-contradictory.

@briansrls

Copy link
Copy Markdown
Contributor Author

Stratum A row counts — finding does not hold (again)

The review’s inference (“removed fold_method from the Python/Go lists ⇒ constants must drop below 18/14”) skips how the ratchet is defined.

verify_stratum_a_lockstep_all_targets compares EXPECTED_STRATUM_A_ROW_COUNTS to list_rows(dag, \"python_method_template_contracts\") / go_method_template_contracts only — i.e. the lowered list value for those declarations (stratum_a.rs around verify_stratum_a_lockstep_all_targets / assert_expected_row_count). Promoted fold contracts are separate data … MethodTemplateContract declarations; their dag_method lines are not in that list length and are checked by verify_language_spec_fold_contract_wiring.

On the substrate as committed: each list still has 18 and 14 rows respectively (no fold_method row inside the list literals; fold is only on python_language_spec_free_monoid_fold_contract / go_language_spec_free_monoid_fold_contract). So 18/14 are already the post-removal list lengths, not “pre-removal” leftovers. Changing them to 17/13 would assert a length the Dag does not have and would fail CI.

(Also: the cited .dag line ranges for the removed fold_method list slice no longer match current files — easy sign the review snapshot is stale vs HEAD.)

— sent from quiet-otter-242

@briansrls
briansrls force-pushed the feat/collectionops-algebra-fold-contract branch from 7dfba1d to 245a39a Compare May 3, 2026 23:47
@briansrls

Copy link
Copy Markdown
Contributor Author

Review metadata

  • Provider / model: claude / claude-opus-4-7
  • Commit: 245a39a9 · Trigger: schedule
  • Comparison: origin/main @ 04fa1ed4 ... review/pr-1577-245a39a9 @ 245a39a9
  • Thinking: 34s wall

Findings: none blocking.

Verdict: APPROVE — Clean Phase 1 reframe. The diff actively dissolves parallel-representation debt: CollectionOps.fold (string) → fold_contract: DeclarationRef to a MethodTemplateContract, with a fail-closed identity gate (require_fold_method_template_contract) checking the referenced decl is an MTC instantiation whose dag_method == fold_method. The conflicting fold_method rows are removed from the Go/Python list-form contracts, ensuring single authority. A Stratum-A grounding witness (verify_language_spec_fold_contract_wiring) closes the receipt across all three targets. The remaining-work scope (concat/length/map/StringOps/MapOps/dsl languages.dag) is named, ordered, and tracked in the brief and ledger row — meets the documented/bounded/named-trigger bar for accepted scaffolding.

Exploratory observations (optional):

  • method_contract_single_emit_template_string is duplicated nearly verbatim across python_target.rs and rust_target.rs; only the Rust copy applies template.replace("%Q", "\""). The brief calls this Rust-local for now, but if a third target consumer appears, lifting %Q decoding (or this whole helper) into emit/fold_method_contract.rs or a shared emit/method_template.rs would prevent two divergence vectors. Non-blocking.
  • The fail-closed gate is fold-specific (require_fold_method_template_contract hard-codes fold_method). When concat/length/map migrate, this will likely want to become require_method_template_contract(dag, decl, expected_method_name) rather than 6 near-clones. Worth keeping in mind for the next slice.

@briansrls

Copy link
Copy Markdown
Contributor Author

Response to scheduled review (245a39a9)

Verdict: Taken as APPROVE — no blocking findings to act on in this PR.

Optional observations — verified, deferred by design

  1. method_contract_single_emit_template_string duplication: Confirmed: rust_target.rs ends with Ok(template.replace("%Q", "\"")) while python_target.rs ends with Ok(template.clone()), matching the brief’s Rust-local %Q decode story. Pulling a shared helper into emit/fold_method_contract.rs (or a small shared emit module) is a reasonable next-slice hygiene move once a third consumer exists or when we lift %Q substrate-side — not worth churning this PR after merge.

  2. require_fold_method_template_contract generalization: Agreed that concat / length / map (and any other MethodTemplateContract-backed fields) will likely want a single require_method_template_contract(dag, decl, expected_method_ref) (or equivalent) instead of parallel fold-only gates. That belongs in the migration slice that introduces the next contract, not as speculative abstraction ahead of those declarations.

— sent from quiet-otter-242

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