Skip to content

Emitter param defaults: constant-fold a field projection into a data record, closing all six whole-tree emission diagnostics without duplicating repository identity - #8693

Merged
briansrls merged 3 commits into
mainfrom
session/snappy-hawk-608
Aug 20, 2026

Conversation

@briansrls

@briansrls briansrls commented Aug 20, 2026 •

Copy link
Copy Markdown
Contributor

The subject

Whole-tree emission (gunbc compile --source-root dag --source-root src/v2) refused with six hard diagnostics, all one class, and emitted no files at all:

workflow CLI default for parameter `X` must be a string, int, float, bool literal, or data reference

Four in gunbc.tools.review_codex (pr_owner, pr_repo, cycle_owner, cycle_repo), two in gunbc.tools.review (owner, repo). Measured on the pre-change binary, not assumed.

One root, two approach angles

v1.compiler.emit_rust resolve_param_default handled a default expression in exactly two shapes — an ExprLiteral, and an ExprVar naming a DataItem whose body is a literal — and refused everything else.

  • review.dag writes owner: String = gunbc_repository.owner. That is an ExprFieldAccess, so it hit the refusing wildcard at depth 0 and never reached the data-reference arm at all.
  • review_codex.dag writes pr_owner: String = owner, an ExprVar. It traversed the data-reference arm correctly, fetched data owner's body — which is gunbc_repository.owner, an ExprFieldAccess again — and extract_literal_string returned none.

Same wall, one hop apart. The compiler could not constant-fold a field projection into a data record.

The repair

fold_constant_default_expr is that fold: an ExprVar resolves to its DataItem body and recurses; an ExprFieldAccess folds its base, selects the named field out of the record literal (record_lit_named_field_value_optional, which already existed in v1.std.core) and recurses on the field's value; anything else is returned as-is. Depth-bounded by workflow_default_fold_depth_limit, refusing past it. resolve_param_default folds first and then extracts a literal, so a non-constant expression still reaches extract_literal_string and still refuses.

No evaluation, no runtime, no new grammar: every field of gunbc_repository is a literal.

This makes the diagnostic's own promise true rather than adding a capability it never claimed — the message already advertises "data reference" as admitted while the data-reference arm could not survive a non-literal body.

What was NOT done

The six were not closed by respelling the projected values as literals at the call sites. gunbc.repository exists because five spellings of this repository's identity were consolidated into one authority (gunbc_repository_authority_note); a sixth is the DESIGN §3 violation that note exists to prevent, and has already been rejected once on this carrier (review 46301). No repository literal appears in this diff.

Controls

New enrolled witness: dag/test/claim/workflow_default_field_projection_fold_witness_test.dag. Two arms over the same fixture module — same record type, same data declaration, same workflow func — differing in one token: which field is projected. host's authored value is a string literal; joined's is a concat call. Same ExprFieldAccess shape, same record, same String type.

arm pre-change binary post-change binary
1 — projection of a literal field is folded, emitted clap carries default_value = "wdp.invalid" false (discriminating RED) true
2 — projection of a non-constant field still refuses true true (regression control)

Measured by execution, both binaries, sha256 printed and distinct:

  • pre-change gunbc 769fc55a0317dd67…
  • post-change gunbc 42b0271a5e27b9e3…

Arm 2 is the one that matters: a repair that admitted any field access passes arm 1 and fails arm 2. The fold widens the admitted set by exactly the foldable projections and not one thing more.

Generation discipline

gen-0 build → --required-regen → install target/stage0-regen-candidate/src/v1_compiler_emit_rust.rs → rebuild → re-measure. The first regen drifted exactly one generated file (v1_compiler_emit_rust.rs) and nothing else. After merging current main, --required-regen re-run reports first_generation_equal=true planned=128 executed=128 — the committed mirror is a fixed point of the emitter that produced it.

Scope bound, stated against what was actually observed

The six diagnostics are closed. validate_workflow_param_defaults runs at the top of emit and returns EmitResult { files: [], diagnostics } immediately on any refusal: before this change whole-tree emit finished in 18 s and reported the six; after it, emit ran 88 minutes without that refusal and was still emitting when a 90-minute bound cut it off. Passing the gate is what is established.

What is NOT established, and is not claimed: that whole-tree emission completes. It did not finish within 90 minutes on this container, so "the first complete emitted-tree subject" is a thing this change unblocks rather than a thing it demonstrates. Whole-tree emit wall time is a separate, unmeasured question and this PR does not answer it. A second run under a larger bound is in flight; if it lands before merge the number goes here, and if it does not, this paragraph stands as written.

Beyond that: this does not show the emitted Rust compiles, does not show behavioural equivalence, does not retire the seed, and does not move the rustc board.

Witness cost, because the first revision failed the floor on it

The first push ran the floor green on the assertion — planned=9840 executed=9840 passed=9531 failed=0 — and then failed the run on cost: COMPLETED-OVER-COST-REQUIREMENT … Cpu, cost exactly 62567ms against 5000ms, and it was the run's worst single claim by memory at +1.12GB RSS.

That cost was the fixture's import closure, not the claim. import std.resources { Network } resolves transitively against the real corpus, so each arm compiled std in order to check a param default. Network was never load-bearing — is_workflow_item only asks that a uses clause exist, not what type it names — so the fixture now declares its own WdpNet and imports nothing at all. Same record, same two fields, same one-token difference between the arms; ~27 ms standalone instead of the whole corpus. Both arms re-run true on the post-change binary.

Brian Searls and others added 2 commits August 20, 2026 20:56
…record

Whole-tree emission (source roots dag + src/v2) refused with six hard
diagnostics, all one class:

  workflow CLI default for parameter `X` must be a string, int, float,
  bool literal, or data reference

Four in gunbc.tools.review_codex, two in gunbc.tools.review, and no files
emitted at all.

ONE ROOT, TWO APPROACH ANGLES. v1.compiler.emit_rust resolve_param_default
handled a default in exactly two shapes: a literal, and a bare reference to
a data declaration whose body is a literal. review projects the field
directly (`gunbc_repository.owner`), so it hit the refusing wildcard at
depth 0. review_codex names a data declaration whose BODY is that
projection, so it traversed the data-reference arm correctly and died one
hop down on the same missing fold. The compiler could not constant-fold a
field projection into a data record.

fold_constant_default_expr is that fold: resolve a variable to its DataItem
body, resolve a field access by folding its base and selecting the named
field from the record literal, bounded by a depth limit and refusing past
it. resolve_param_default now folds first and extracts a literal from the
result, so a non-constant expression still reaches extract_literal_string
and still refuses.

This makes the diagnostic's own promise true rather than adding a
capability it never claimed: the message already advertises "data
reference" as admitted while the data-reference arm could not survive a
non-literal body.

NOT closed by respelling the projected values as literals at the six sites.
gunbc.repository exists because five spellings of this repository's
identity were consolidated into one authority; a sixth would be the DESIGN
§3 violation the authority note exists to prevent. No repository literal
appears in this diff.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@gunbai-bot
gunbai-bot Bot marked this pull request as ready for review August 20, 2026 21:13
…was the import closure, not the claim

The floor ran the new witness green — planned=9840 executed=9840 passed=9531
failed=0 — and then failed the run on cost:

  COMPLETED-OVER-COST-REQUIREMENT
  test.claim.workflow_default_field_projection_fold_witness_test
    .field_projection_to_a_literal_field_is_folded_into_the_cli_default
  reached its verdict and then exceeded its budget: Cpu, cost exactly
  62567ms against 5000ms

and it was also the run's worst single claim by memory, growing RSS 1.12GB.

The cost was the fixture's import closure, not the assertion. `import
std.resources { Network }` and `import std.types { String }` resolve
transitively against the real corpus, so each arm compiled std to check a
param default. `Network` was never load-bearing: is_workflow_item only asks
that a `uses` clause exist, not what type it names, so a locally declared
`WdpNet` satisfies it and the fixture imports nothing at all. Both arms
still assert exactly what they asserted before — same record, same two
fields, same one-token difference — against a two-declaration closure that
compiles in ~27ms standalone rather than the whole corpus.

Re-run on the post-change binary: both arms true.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@briansrls
briansrls merged commit 1ebac31 into main Aug 20, 2026
1 check passed
@briansrls
briansrls deleted the session/snappy-hawk-608 branch August 20, 2026 23:03
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