Repository navigation
Mechanical: TargetModel carries ProducedDeclSupport, unwired everywhere - #8155
Conversation
Pure field addition, no behavior change. The identical edit at all 106 TargetModel record literals across 55 files, plus the type and its import. ProducedDeclSupport is a coproduct with an explicit unwired arm, not an optional with a default. Every existing target gets ProducedDeclUnwired, so nothing renders and nothing changes; a later change wires exactly the targets that register a renderer. There is deliberately no default that RENDERS -- such a default would be a fabricated plausible output at every unwired site at once, and it would make an unwired-target refusal unreachable by turning every unwired target into a quietly-working one. The bad state has no constructor rather than being avoided by discipline. Why the renderer is a function on the carrier rather than data in the target's Node bundle: rendering depends on the declaration, and a function cannot survive Node encode/decode. token_class_emit_transforms -- already a function-valued field here -- is the precedent for both expressibility and placement. Why this is not a compiler-side slot template: a template puts signature ORDER in the compiler, so the terminal architecture deletes it. A renderer puts order in the TARGET, so when the GrammarRelationRow forward reading lands, each renderer's body changes to derive from rows while the seam -- compiler asks, target renders -- survives untouched. Named limit, so a third target does not discover it: the compiler no longer knows how a declaration CLOSES, but still knows it is prefix then body then suffix, because that is TargetBodiedArrowStatementScaffold's shape -- a pre-existing type shared by every bodied arrow and consumed by 06_translate. A target whose signature follows its body needs the body-scaffold lane. Not verified by execution -- CI is the consumer. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review 51155 (claude/claude-opus-4-7) is correct: the type body carried
produced_decl_support: ProducedDeclUnwired, which names a VARIANT CONSTRUCTOR
where a type belongs, alongside the real declaration. It would not typecheck.
Cause, worth naming because it also corrupted my verification: the mechanical
pass matched "TargetModel {" excluding a "-> " prefix, which also matches
"type TargetModel {". So the script inserted an initializer into the type body,
and the same flawed pattern then COUNTED that site as a construction -- the
count agreed with the edit because both were wrong in the same way, which is
why 106/106 looked complete.
Recounted with the type definition excluded: 105 true record literals, 105
unwired fields, none missing. The earlier 106 included the type definition. The
separately-found rust_produced_decl_target site was a genuine literal and its
fix stands.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Verified and fixed in Cause, worth naming because it also corrupted my verification. The mechanical pass matched Recounted with the type definition excluded: 105 true record literals, 105 unwired fields, zero missing. The earlier 106 included the type definition itself. One thing this did not invalidate: the separately-found The PR remains what it claims to be — a pure field addition with no behavior change, now at 105 sites. |
|
The observation in review 51163 is correct and I want to answer it rather than let the PR title carry the whole justification.
So the commitment, stated so it is checkable rather than assumed: if the follow-up does not land the wired arm with an executing consumer — Rust rendering its own order, C rendering its own, both driven through one compiler API with no compiler edit between them — then What this PR does earn on its own is narrower and I would not claim more: the No change pushed for this; flagging only. — sent from wise-swift-269 |
Blocking defect relayed via program review on 8155, verified against the code before acting. Augmenters rebuild a TargetModel field by field; the mechanically inserted produced_decl_support was RESET to ProducedDeclUnwired instead of copied from the source, so an ordinary augmentation drops the capability. Invisible today, fatal tomorrow: every target is currently unwired, so the reset is a no-op and nothing reds. The moment a target is wired, augmenting it silently erases its rendering -- and a criterion-5 proof could pass while a derived target lost the capability. The relayed report named two production augmenters. An audit for the SHAPE rather than the two names found TEN copy-constructions with the same defect: target_model_augment_use_site_ownership_catalog and target_model_augment_value_semantics_carriers, two in extdeps/languages/dag.dag copying from spec, and six in rust_test_fixtures.dag copying from base. All ten now preserve source.produced_decl_support. The 105 constructions classify into two kinds, not three: FRESH constructors keep an explicit ProducedDeclUnwired (target_model_make_with_emit_transforms is one -- every field is a parameter, no source target), and DERIVED constructions preserve. There is no INTENTIONAL-removal site; nothing in the corpus means to drop the capability, so an incidental reset can no longer hide among deliberate ones. Control: produced_decl_support_preserved_test proves preservation by EXECUTING the surviving renderer, since equality over function values is unavailable -- a comparison could not have caught this class. Both augmenters are driven with NONEMPTY input deliberately, because each returns its argument unchanged on empty input and an empty-input control would pass without reaching the reconstruction. The unwired negative controls keep the positive arm from being satisfied by a constant. This also gives ProducedDeclWired its first constructor and consumer, partially answering the specification-without-execution observation in review 51163. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review 51220 (cursor/composer-2.5) is correct. dag_bodied_fn_target_model read spec.produced_decl_support, but DagBodiedFnFamilySpec has no such field -- it carries inhabitant_field, emitted_fn, grammar_row, source_literal, production_binding, authority_source_text, relation_rule and scaffold. That would fail typecheck on an undefined field access. Root cause, mine: the preservation pass inferred "this construction derives from a source target" from a field read like <var>.authority_source_text. spec IS a DagBodiedFnFamilySpec -- a DESCRIPTOR that happens to share a field name, not a TargetModel being reconstructed. Copying a field name is not deriving from a target. Taking the reviewer's second option: these sites build a FRESH target from a descriptor (bundle derived from the spec, lex from dag_lex()), so ProducedDeclUnwired is the correct value and no spec field is needed. Re-audited both directions rather than only the two reported lines. Preserving, verified to have a genuine TargetModel source: the two target_model_augment_* reconstructions (target: TargetModel) and six rust_test_fixtures staging targets (let base = <...>_target_model_staging()). Correctly NOT preserving, and left alone deliberately: the two Python/Go targets in test/fixture/rung_5_6_common.dag borrow only the Rust target's BUNDLE while supplying VoidLexRules, empty binding spellings, and their own source text and runtime row. They are different-language targets, not reconstructions. Preserving there would hand a Python target a Rust renderer -- exactly the mismatch the coproduct exists to make unwritable. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Verified and fixed in Root cause, mine. The preservation pass inferred "this construction derives from a source target" from a field read like Took your second option: these sites build a fresh target from a descriptor (bundle derived from the spec, Re-audited in both directions rather than only the two reported lines, since a misclassification can go either way: Preserving, each verified to have a genuine Correctly not preserving, and deliberately left alone — the two Python/Go targets in Net: 8 preserving sites, 2 reverted to unwired, 2 unwired by design. |
|
No fix needed for review 51227, but one correction to the record, because it credits me with something I did not do. The review says the commit series "caught its own bug" regarding the augmenters that reset Worth stating precisely because the provenance is the interesting part: three defects in this PR were caught by review, not by me. The type-body insertion (review 51155), the augmenter reset (relayed program review), and the That is also why the regression control is built the way review 51227 credits: it executes the surviving renderer rather than comparing, and it drives both augmenters with nonempty input, because each returns its argument unchanged on empty input and an empty-input control would pass without ever reaching the reconstruction that carries the defect. A control that cannot fail is the thing that let all three through. Status: — sent from wise-swift-269 |
The compiler asks the TARGET to render a produced declaration and receives a
bodied-arrow scaffold. emit_produced.dag imports no target language: it matches
on target.produced_decl_support, refuses ProducedDeclUnwired with a typed,
located produced_decl_target_unwired, and otherwise calls the target's renderer.
Rust and C each own their own order, authored in their own modules:
Rust fn add(x: i32, y: i32) -> i32 { x + y }
C int add(int x, int y) { return x + y; }
Those differ in every axis the compiler previously owned -- keyword-first versus
return-type-first, NAME colon TYPE versus TYPE NAME, return arrow versus none,
value-producing versus statement-sequenced with a return token and terminator.
C required no compiler edit: it registers a renderer and supplies its own
binding spellings. That is criterion 5, and the cross-golden arms are its
discriminating RED -- neither target can produce the other's text, so a seam
that kept order in the compiler would fail the witness rather than pass quietly.
target_model_with_produced_scaffold installs the rendered scaffold, deciding
only WHERE it attaches. It reuses target_model_bundle_replace_edge_target rather
than adding node surgery, and takes the BARE base row from ProducedDeclWired --
the target's ordinary rule already carries a fixed scaffold, and appending a
second bodied-scaffold edge would let the fixed one win every lookup, emitting
one declaration's signature for every declaration.
Deleted as obsoleted by the climb: rust_produced_decl_target and, cascading,
rust_bodied_relation_rules_node_with_scaffold. The rust_signature_* chain is
KEPT -- it is now Rust's renderer body, which is where order belongs.
Re-cut onto merged main after #8155 squash-merged; the field carrier and its
preservation fixes come from that commit rather than being duplicated here.
Not verified by execution -- CI is the consumer.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… 5) (#8168) * Wire produced-declaration rendering into Rust and C; close criterion 5 The compiler asks the TARGET to render a produced declaration and receives a bodied-arrow scaffold. emit_produced.dag imports no target language: it matches on target.produced_decl_support, refuses ProducedDeclUnwired with a typed, located produced_decl_target_unwired, and otherwise calls the target's renderer. Rust and C each own their own order, authored in their own modules: Rust fn add(x: i32, y: i32) -> i32 { x + y } C int add(int x, int y) { return x + y; } Those differ in every axis the compiler previously owned -- keyword-first versus return-type-first, NAME colon TYPE versus TYPE NAME, return arrow versus none, value-producing versus statement-sequenced with a return token and terminator. C required no compiler edit: it registers a renderer and supplies its own binding spellings. That is criterion 5, and the cross-golden arms are its discriminating RED -- neither target can produce the other's text, so a seam that kept order in the compiler would fail the witness rather than pass quietly. target_model_with_produced_scaffold installs the rendered scaffold, deciding only WHERE it attaches. It reuses target_model_bundle_replace_edge_target rather than adding node surgery, and takes the BARE base row from ProducedDeclWired -- the target's ordinary rule already carries a fixed scaffold, and appending a second bodied-scaffold edge would let the fixed one win every lookup, emitting one declaration's signature for every declaration. Deleted as obsoleted by the climb: rust_produced_decl_target and, cascading, rust_bodied_relation_rules_node_with_scaffold. The rust_signature_* chain is KEPT -- it is now Rust's renderer body, which is where order belongs. Re-cut onto merged main after #8155 squash-merged; the field carrier and its preservation fixes come from that commit rather than being duplicated here. Not verified by execution -- CI is the consumer. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Wire staging support and C value-expression projection Both findings in review 51267 verified against the code and fixed. FINDING 1 - a regression I introduced. On main, emit_produced_module_rust ignored the passed target and built a per-decl Rust target internally. Making the passed TargetModel authoritative is the right seam cut, but it means rust_target_model_staging -- still ProducedDeclUnwired after the mechanical pass -- now refuses. Its two consumers, v2.workflow.realization_attempt and emit_host_produced_module_equals_eval_test, would have hit produced_decl_target_unwired instead of emitting. Staging now carries rust_produced_decl_support(): it is a Rust target and renders Rust order, differing from production only in lex, which is the dissolution-by-identity sequencing already ruled for it. FINDING 2 - the criterion-5 witness could not have passed. C's bundle carried selection policy, declared inhabitants, translation rules, serialize source and semantic-decl emission, but neither value_expression_projection nor operator_realizations, and produced-decl emission serializes the arrow BODY through those. The signature renderer covers prefix and scaffold order only; it does not substitute for body projection. Took the wire-it option rather than narrowing the golden to signature-only, because narrowing would have made the receipt claim less than criterion 5 asks while still reading like a two-target proof. C now carries a minimal value-expression surface -- binding-ref plus an op_add catalog on c_token_plus, mirroring Rust -- so the shared fixture body projects for C. Forms C has not modeled carry token classes with no lex rule, so reaching one REFUSES at serialization rather than emitting fabricated text. That is the existing fail-closed lex-miss arm, not a new mechanism, and it keeps C's unmodeled surface honest instead of quietly wrong. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Add executing witness for the module fold this PR changed This PR changed emit_produced_module's signature and the target threading through it, and nothing executed the result. Its two consumers are both dark in CI: emit_host_produced_module_equals_eval is a legacy frozen row in gunbc.witness_deferral_freeze, and realization_attempt did not appear in the run log either. Verified by grepping job 93997368123 rather than inferring from the green ci conclusion -- a passing job means the job succeeded, not that a given claim ran. The decisive fact is that emit_produced_module is not glue behind two dormant tests: v2.workflow.realization_attempt is a PRODUCTION consumer, calling it at the canonical realization attempt. So a signature and threading change to live code was landing compiled and unrun. The witness goes in produced_decl_two_target_test, which already executes. Creating a new file would have risked solving this by committing the same problem one layer over -- a correct witness with no consumer. Scope is exactly what changed: accumulation, separator, final medium and target threading. The swapped-order arm is the discriminating RED -- same two declarations, same target, so only accumulation ORDER distinguishes them, and a fold that dropped, duplicated or misordered a declaration cannot satisfy both arms. The golden is a literal rather than composed from emit_module_decl_separator, so the expected text shares no ancestor with the implementation producing it. NOT addressed here, and inherited rather than introduced: the two dark consumers themselves. Enrolling the frozen row, scheduling realization_attempt, or widening the freeze are pre-existing questions this PR does not open. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Fix parse error: data expression must sit on the = line The v1 parser refuses a data declaration whose value starts on the next line: "expected expression, found Newline" at src/v2/test/claim/emit/produced_decl_two_target_test.dag. I had wrapped the module golden after the = for line length. Caught by the build job's v1-compiler-tests, whose for_each_parsed_module_binding parses every .dag in the tree, so a parse error in a witness file fails four unrelated materialization tests. The ci job's 7-second failure was the downstream upstream-already-red refusal, not a second defect. Scanned the other seven files this PR touches for the same shape and found none. A trailing = is legal for a coproduct TYPE declaration (dag/std/content_hash.dag has several) and only illegal for a data expression, so the scan is for the failing class rather than for the character. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Brian Searls <briansearls1@gmail.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Operator-authorized (2026-08-12). These are the two lines the PR body named, and they are true rather than mechanical filler: .dag is ML-style, all three of if/then/else are really lexed, and the else arm ends the expression, so there is no closing token. ConditionalSelfDelimiting and LoopSelfDelimiting carry no field in which a fictional token could be written. Not a pre-existing defect: `delimiting` does not exist on main at all. These sites need updating only because this PR adds the required field -- the same class of mechanical addition already made to this file in #8155 when TargetModel gained ProducedDeclSupport. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Model conditional and loop delimiting discipline on the target The shapes did not carry whether a construct needs a token AFTER its final arm, so target_value_expression_conditional_to_concrete_tokens ended at the else arm for every target and a brace-closing language had no way to emit its own close. The brace was supplied instead by hand-authored declaration scaffolds ending in two rbrace, which is why the fixtures looked correct: the golden was validated against a scaffold authored to match it. ConditionalDelimiting and LoopDelimiting carry the closing token as payload on the CLOSING arm only, so a self-delimiting target has no field in which to write a fictional token. Optional<Symbol> would not have had that property: Absent would mean both "self-delimiting" and "not modeled yet". Decode refuses an unrecognised discriminant rather than defaulting, because defaulting to self-delimiting makes a closing target emit one token short. C and TypeScript are corrected, not converted. Neither GAINED a ternary -- both always were one, and the shape was describing a construct they do not have in a value position. C's if is a statement; ts_token_kw_then named a keyword TypeScript does not have. None of those tokens had a lex rule, so nothing executed them and nothing caught the fiction. INCOMPLETE: extdeps/languages/dag.dag 5042 and 5052 still need delimiting: ConditionalSelfDelimiting and delimiting: LoopSelfDelimiting. That file is no-touch for this lane, so the field is deliberately unwritten and the build is red until its owner adds it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Guard the intentionally-invalid golden in the witness itself The self-delimiting golden is the closed golden one token short, and it is the only thing proving the discipline is consumed rather than ignored. A contributor meeting a golden that asserts broken Rust will fix it, and the fix collapses both arms of the discriminating pair into one expectation. The PR body is not readable from the corpus; the next person meets the file. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Fix parse error: record field value may not start on the next line delimiting: with its if/else beginning on the following line is the same parse class that broke #8168 -- expected expression, found Newline. My pre-push scan only covered data/fn/let declarations and not record fields, which is why it reported clean. Extracted to cld_delimiting_for. Verified by remote execution: the module now reaches typecheck, where the only remaining errors are the two known dag.dag missing-field refusals. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Complete the slice: .dag declares itself self-delimiting Operator-authorized (2026-08-12). These are the two lines the PR body named, and they are true rather than mechanical filler: .dag is ML-style, all three of if/then/else are really lexed, and the else arm ends the expression, so there is no closing token. ConditionalSelfDelimiting and LoopSelfDelimiting carry no field in which a fictional token could be written. Not a pre-existing defect: `delimiting` does not exist on main at all. These sites need updating only because this PR adds the required field -- the same class of mechanical addition already made to this file in #8155 when TargetModel gained ProducedDeclSupport. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Hoist the delimiting rationale out of declaration bodies DESIGN section 4c models annotations at module-item grain only, and I put explanatory blocks inside the projection record literals -- exactly the violation whose 73 instances I measured in promotion_admission earlier today, reintroduced by me in the same session while writing about it. The content is load-bearing rationale rather than restatement, so it is hoisted to each projection's declaration header unabridged rather than deleted. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * wip: narrow to conditional; shrink pick scaffold * Narrow to conditional; withdraw the loop half and shrink the pick scaffold Review 51456 was right and I confirmed it by execution before acting: with the shared projection declaring closure, rust_conditional_emit_pick_fn_accepts and rust_loop_emit_one_accepts both returned false. My pre-push check had run only my own three witnesses, not the existing consumers of the authority I changed -- the staging fixtures import rust_value_expression_projection. CONDITIONAL, fixed as designed: rust_pick_block_close shrinks from two rbrace to one. The second used to close the brace the conditional left open, and the conditional now emits its own from its declared discipline. That shrinking is the dissolution the change was supposed to buy, so the fixture getting smaller is the receipt rather than a patch. LOOP, withdrawn, and the reason is a finding: Rust's value-producing loop is `loop { break v; }` -- it needs a break token and a terminator, which an open/close pair cannot express. The staging spelling `loop { break ` smuggles exactly those, the same way the conditional smuggled its close. Declaring an open/close discipline for it would have been the same under-modeling one level over, and unwitnessable besides, since production loop_token has no lex rule. TargetLoopShape is left exactly as it was. The witness is renamed conditional_delimiting_test: it never covered loop, and a name claiming coverage it does not have is the defect this PR is about. Verified by execution on this head: both enrolled goldens true again, and all three delimiting witnesses true. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Brian Searls <briansearls1@gmail.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Pure field addition. No behavior change anywhere. Split out deliberately so the architecture it enables is reviewable on its own rather than buried in churn — this PR is the churn, and it should read as nothing but churn.
What
TargetModelgainsproduced_decl_support: ProducedDeclSupport, at 105TargetModelrecord literals across 54 files, plus the type definition and its imports.Why the shape is a coproduct, not an optional with a default
ProducedDeclSupport = ProducedDeclUnwired | ProducedDeclWired { render, scaffold_relation_rule_name, scaffold_base_row }.There is deliberately no default that renders — such a default would be a fabricated plausible output at every unwired site simultaneously, and would make an unwired-target refusal unreachable by turning every unwired target into a quietly-working one. The bad state has no constructor rather than being avoided by discipline (DESIGN §5).
Why a function on the carrier rather than data in the Node bundle
Rendering depends on the declaration, and a function cannot survive Node encode/decode — which is what the target's
bundleis.token_class_emit_transformsis already a function-valued field onTargetModel, and is the precedent for both expressibility and placement.Why this is not a compiler-side slot template
A template puts signature order in the compiler, so the terminal architecture deletes it. A renderer puts order in the target — so when the
GrammarRelationRowforward reading lands, each renderer's body changes to derive from rows while the seam (compiler asks, target renders) survives untouched.Construction sites classify into two kinds, not three
FRESH constructors take an explicit
ProducedDeclUnwired(target_model_make_with_emit_transformsis one — every field is a parameter, no source target). DERIVED constructions preservesource.produced_decl_support. There is no intentional capability removal site; nothing in the corpus means to drop the capability, so an incidental reset cannot hide among deliberate ones.A blocking defect was found and fixed here: augmenters rebuild a
TargetModelfield-by-field and the inserted field was reset rather than copied, so an ordinary augmentation dropped the capability. Invisible today (all targets unwired ⇒ the reset is a no-op) and fatal once anything is wired. Auditing for the shape rather than the two reported names found ten such sites: both production augmenters, two inextdeps/languages/dag.dag, six inrust_test_fixtures.dag.produced_decl_support_preserved_testproves preservation by executing the surviving renderer, since equality over function values is unavailable — a comparison could not have caught this class. Both augmenters are driven with nonempty input deliberately, because each returns its argument unchanged on empty input and an empty-input control would pass without reaching the reconstruction. Unwired negative controls keep the positive arm from being satisfied by a constant. This also givesProducedDeclWiredits first constructor and consumer.Named limit
Stated in the carrier: the compiler no longer knows how a declaration closes, but still knows it is prefix → body → suffix, because that is
TargetBodiedArrowStatementScaffold's shape — pre-existing, shared by every bodied arrow, consumed by06_translate. A target whose signature follows its body needs the body-scaffold lane.Review notes
import ... { ... }statements reflowed to multi-line; no symbol is removed.rust_test_fixtures.dagis pre-existing atorigin/main(a brace inside a string literal).target_model_with_bundle(target, replacement_bundle)helper so the next field addition need not touch every constructor. It is a genuine cost-shape point rather than style, but it is a refactor of its own and this PR is meant to stay pure churn.Follow-on PR moves signature order into the Rust handler, registers C, and proves another target can register without a compiler edit by executing both targets through one API.