Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,9 @@ Parked context (non-dispatchable): [compiler algorithm survey](docs/plans/compil

Every node declares its parent in the functional decomposition: gunbc-project-root, then four projects -- v2-compiler-live (the self-hosted compiler goes live), fabric-bringup (our own fleet as a fabric, with the end-of-year VM service and fabric storage under it), debt-payoff (known gaps and recurring failure modes retired against their triggers) and factory (the machine that lands and verifies work). Readiness is derived, never authored: a node is ready to dispatch only when it is rooted, centered, sized, bound to a closing contract, open, and every prerequisite is closed (gunbc.roadmap.roadmap_readiness). Node fields define boundary, first slice, RED control, exclusions, owner, and handback.

- [ ] **Work out what a name can see from where it is written, inside one file** — Make an ordinary compiled path produce, for each name written in a file, the declarations actually in view from that position, and record which one answered. Four cases, each shown on its own: an ordinary neighbour written earlier in the same file is in view; a declaration inside a different branch of a decision is not; a declaration written further down is not; and two declarations spelled the same at genuinely different positions stay distinct rather than merging. Why: The parser walk that can answer this already exists and is switched on, but nothing in an ordinary compile asks it. Until it is asked on a real compile, every claim about what a name can see rests on hand-built examples, and the four rules above are asserted rather than observed. [authority](docs/plans/namespace-unique-on-chain-operational-plan.md)
- [ ] **Derive each file's dependencies from what its names resolve to** — XL-4 of gunbc.namespace_cut_stage. For every reference, record which declaration answered it. Derive each file's dependency set from those answers alone, identical whichever unrelated files are loaded. This absorbs cross-file provenance (clause e) and pool independence (clause f). Why: Hand-written dependency statements can disagree with what files actually reference, and hundreds were patched by hand to keep them in step. [authority](docs/plans/namespace-unique-on-chain-operational-plan.md)
- [ ] **Settle the names that genuinely could mean two things** — Using the real answers from the step before — not a count taken by searching text — go through every name that genuinely has more than one candidate and either refuse it on purpose or say which one is meant, by renaming or by writing it out in full. Why: Counting these by searching for text gives a number that is wrong in both directions, so it cannot tell anyone how much work is left or whether it is finished. [authority](docs/plans/namespace-unique-on-chain-operational-plan.md)
- [ ] **Release the namespace rewrite waves under a wall that works** — ACT-0 collects the four activation receipts over base B. XL-5 lands the occurrence and import rewrites. Release needs act0_release_milestones CLEAR. Why: Without waves, XL-6 is a flag day. [authority](dag/gunbc/namespace/namespace_cut_stage.dag)
- [ ] **Derive each file's dependencies from what its names resolve to** — XL-4 of gunbc.namespace_cut_stage. For every reference, record which declaration answered it. Derive each file's dependency set from those answers alone, identical whichever unrelated files are loaded. This absorbs cross-file provenance (clause e) and pool independence (clause f). Why: Hand-written dependency statements can disagree with what files actually reference, and hundreds were patched by hand to keep them in step. [authority](docs/plans/namespace-unique-on-chain-operational-plan.md)
- [ ] **Settle the names that genuinely could mean two things** — Using the real answers from the step before — not a count taken by searching text — go through every name that genuinely has more than one candidate and either refuse it on purpose or say which one is meant, by renaming or by writing it out in full. Why: Counting these by searching for text gives a number that is wrong in both directions, so it cannot tell anyone how much work is left or whether it is finished. [authority](docs/plans/namespace-unique-on-chain-operational-plan.md)
- [ ] **Release the namespace rewrite waves under a wall that works** — ACT-0 collects the four activation receipts over base B. XL-5 lands the occurrence and import rewrites. Release needs act0_release_milestones CLEAR. Why: Without waves, XL-6 is a flag day. [authority](dag/gunbc/namespace/namespace_cut_stage.dag)
- [ ] **Remove the old raw progress printing** — Once the above work, inventory every place that prints progress directly and either move it over or mark it as real program output. Why: Two competing status channels means neither can be trusted, and formatting decisions stay scattered across the codebase. [authority](docs/plans/progress-observation-design.md)
- [ ] **Both parsers record every node's full source extent when they build it** — PRODUCER CONTRACT: every successfully published source occurrence has its complete source interval retained from the parse that establishes it -- including suffixes consumed after its identity was minted (v1 maybe_optional reuses a type's identity after consuming `?`) and excluding separators outside it (parse_import consumes trailing newlines its extent must not cover) -- and the parser publishes those intervals in its final source artifact, following the accepted branch through speculative parsing. The interval is recorded at SYNTAX COMPLETION, not at identity minting: minting and completion diverge (optional-type suffix, interpolation fragments minted together after the string ends, a property and its value minted together over different regions, binary expressions whose diagnostic span is the operator). The population is every source occurrence: terminals, production wrappers, infix applications, sequence/repeat composites that are not synthetic, nested productions and the root; an empty match records no interval and synthetic scaffolding records none. SOURCE SCOPES THAT SEMANTIC SIMPLIFICATION ERASES ARE RETAINED with their ordered containment (v1 parse_paren_expr returns the inner expression; parse_block returns a sole statement), in the parser's source side table, without adding semantic nodes. v2: SpanIndex extents beside DerivedBy lineage. v1: the same side-table shape published with the parse result, not a Node field. Diagnostic focus, source extent and causal lineage stay three facts (DESIGN 3). The existing v1 import-extent reader (the consumed-window rescan) reads the recorded interval instead. Why: Consumers that need containment (annotation attachment, import editing) cannot tell whether a position is inside a node or after its closing delimiter, so each reconstructs a range from descendants or tokens -- an approximate second answer to a fact the parser had and discarded (DESIGN 2, 6b). [authority](src/v2/compiler/02_parse.dag)
- [ ] **A comment attaches to the gap between its neighbouring nodes, anywhere in a file** — std.source_annotation replaces the single-subject anchor with AnnotationScope = FileScope | SourceScope and AnnotationGap { scope, before?, after? }. The scope is the innermost retained source scope containing the comment (the file when none does); before/after are its ORDERED DIRECT SOURCE-SYNTAX CHILDREN, with parser scaffolding (v2 production markers, sequence/repeat spines, projection edges) projected transparently. For a call `foo(a, b)` with a comment on its own line between the arguments, the scope is the argument-list scope with before = a and after = b, never a descendant of an argument. File scope covers leading and trailing trivia: the lexer's final source position is retained in its artifact rather than dropped, so a banner above the module header and a comment after the last item both land. One rule replaces the positional cases: BodyGrainNotModeled, TrailingNotModeled, UnattachedAtScopeEnd, module_header_gap_subject and v1 module_item_extent_ends are deleted. v1 v1.compiler.annotation_bind and the v2 authored parse both feed the one attach_annotations. Anchors reference existing occurrences and mint none (DESIGN 4c). Which node a comment documents is derived from the gap, never stored. Why: A comment inside a function body is a hard compile error today, and each further position (trailing, before a closing brace, file top) is its own special case or refusal. [authority](dag/std/source_annotation.dag)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
module gunbc.recurring_failure_mode.a_quoted_key_record_literal_refusal_left_body_lowering_unrostered

import std.types { NonEmptyStr }
import std.decl_ref { DeclarationRef, WholeDeclaration }
import gunbc.recurring_failure_mode { RecurringFailureMode }

data a_quoted_key_record_literal_refusal_left_body_lowering_unrostered: RecurringFailureMode = RecurringFailureMode {
identity: "a_quoted_key_record_literal_refusal_left_body_lowering_unrostered" as NonEmptyStr,

receipts: [
"**A headless brace with a quoted key, under a non-map annotation, is no longer refused at body lowering** (`data ar_k: Bool = { \"n\": true }`). `v2.test.claim.body_lowering.anonymous_record` `ar_a_quoted_key_is_not_a_record_field_holds` expects `normalize` to refuse at the item with `body_lowering_reason_field_init_unlowered`, so that the quoted key is never read as field `n`. On origin/main, 2026-10-05, `normalize` ACCEPTS the subject (claim_batch on BuildBuddy, clever-raven-680; a probe claim over the same `ar_key_quoted_subject().normalized` returned Accepted). The sibling `ar_a_quoted_key_takes_the_string_key_production_holds` passes, so the parse still takes the string-key production. The claim was red on main. It is listed in `v2.workflow.floor_grandfathered_roster`, but that roster only selects an eval budget, so why the required lane did not expose the red is NOT established.",
"NOT ESTABLISHED: whether a later stage refuses the subject. The map-literal arm of resolve (`v2.compiler.resolve` `resolve_elided_construct_walk`) refuses a headless brace whose expected head is not a Map, so the refusal may have moved from body lowering to resolve. In that case the claim keys a stage the compiler no longer refuses at, which is a stale expectation and not a fail-open. If no later stage refuses, the source is accepted with a quoted key standing where a record field was expected: silent wrongness, below the floor.",
"RUNG: unknown until the above is measured. NEXT TRIGGER: one run of the subject through resolve and infer that records which stage refuses and with what reason. Then the claim either asserts that refusal at that stage, or the body-lowering refusal is restored. Not repaired here.",
"**A headless brace with a quoted key, under a non-map annotation, is no longer refused at body lowering** (`data ar_k: Bool = { \"n\": true }`). MEASURED 2026-10-09 (zesty-heron-486, claim_batch on BuildBuddy at d0f2067f8b): `normalize` ACCEPTS; `ar_a_quoted_key_takes_the_string_key_production_holds` PASSES; resolve REFUSES with `resolve_anonymous_map_expected_type_not_map`. The map-literal arm (gunbc#12758, `v2.compiler.resolve` `resolve_elided_construct_walk`) is the wall: a string-keyed brace whose expected head is not Map. The former claim that keyed `body_lowering_reason_field_init_unlowered` was a stale stage, not a fail-open. The claim now asserts the resolve reason and that lowering still accepts, so a reader that treats `\"n\"` as field `n` still reds (that path refuses as a record, not as a map).",
"RUNG: mechanically preventable on the resolve path (enrolled claim). NEXT TRIGGER: none for this class's original silent-wrongness question; the quoted-key-as-field reading is refused. A later climb would make a quoted key under a non-Map annotation unwritable at lowering without restoring a second authority beside the map arm.",
],

evidence: [],
evidence: [
DeclarationRef { module_path: "v2.test.claim.body_lowering.anonymous_record", decl_name: "ar_a_quoted_key_is_not_a_record_field_holds", field: WholeDeclaration },
DeclarationRef { module_path: "v2.test.claim.body_lowering.anonymous_record", decl_name: "ar_a_quoted_key_takes_the_string_key_production_holds", field: WholeDeclaration },
DeclarationRef { module_path: "v2.compiler.resolve", decl_name: "resolve_elided_construct_walk", field: WholeDeclaration },
],
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
module gunbc.recurring_failure_mode.rust_ingest_lexer_missing_a_bare_operator_spelling

import std.types { NonEmptyStr }
import gunbc.recurring_failure_mode { RecurringFailureMode }

data rust_ingest_lexer_missing_a_bare_operator_spelling: RecurringFailureMode = RecurringFailureMode {
identity: "rust_ingest_lexer_missing_a_bare_operator_spelling" as NonEmptyStr,

receipts: [
"INVALID STATE: v2.extdeps.languages.rust rust_extended_lex_rules, the INGEST rule set that carries whitespace trivia and re-spells emission tokens as bare lexemes (kw_let, =, >, ;), has no bare spelling for an operator whose only spelling is the emission one with baked spacing (rust_fn_add_lex_rules spells plus as space-plus-space). Ordinary Rust source with an unspaced operator (`z=x+1`) is unlexable.",

"SPECIMEN: v2.test.parse rust's claim_rust_layout_tight_parses refused in tokenize with tokenize_lex_e1_unrecognized_char at the unspaced `+` (gunbc run, BuildBuddy invocation b9e2870f); the same source with spaced operators was accepted. The claim was a data TestClaim row that no runner executes, so the refusal stood unseen.",

"HARM: a grammar claim asserting ingestion of tight layout was false and silent. No production consumer ingests Rust today, so the harm is to the claim's truth, not to a compile.",

"REPAIR: the bare `+` rule in rust_extended_lex_rules. RED-FIRST CONTROL: test.claim.parse_test_rust_grammar claim_rust_layout_tight_parses_holds, false before the repair and true after.",

"RUNG FOUND AT: below the floor (a false claim, unexecuted). CEILING: 2 -- the claim is the wall; a structural guarantee would need the ingest set derived from the grammar's token classes rather than authored row by row, which is the separation named in rust_lex_resolves_two_tokenizations_by_rule_order. NEXT TRIGGER: that separation. OWNER: grammar program (stern-bear-500).",
],

evidence: [],
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
module gunbc.recurring_failure_mode.rust_lex_resolves_two_tokenizations_by_rule_order

import std.types { NonEmptyStr }
import gunbc.recurring_failure_mode { RecurringFailureMode }

data rust_lex_resolves_two_tokenizations_by_rule_order: RecurringFailureMode = RecurringFailureMode {
identity: "rust_lex_resolves_two_tokenizations_by_rule_order" as NonEmptyStr,

receipts: [
"INVALID STATE: v2.extdeps.languages.rust rust_lex is rust_fn_add_lex_rules (emission spellings with baked spacing, plus as space-plus-space) followed by rust_extended_lex_rules (an ingest set with whitespace trivia and bare spellings). A text such as ` + ` has two tokenizations, one emission token or trivia then a bare plus then trivia, and first-match rule order picks one. The zero-ambiguity ruling forbids order deciding between readings, lexer included.",

"WHY NOT FIXED WITH ITS NEIGHBOUR: rust_ingest_lexer_missing_a_bare_operator_spelling adds the bare plus and so ADDS one more such pair. The ambiguity predates it (=, > and ; were already doubly spelled) and its repair is a separation, not a row.",

"HARM: the reading of an ingested text depends on rule order, a fact no reader of the grammar can see; a reordering silently changes token boundaries.",

"RUNG FOUND AT: 1 (both readings yield the same token class today, so no wrong program results, but nothing prevents one that would). CEILING: 4 -- one reading per text is a property of a disjoint rule set. NEXT TRIGGER: ingest and emission token sets are separate (emission consumes the grammar backward per gunbc#12878), so ingest lexing has one reading per text. OWNER: grammar program (stern-bear-500).",
],

evidence: [],
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
module gunbc.recurring_failure_mode.ts_lexer_has_no_ingest_rule_set

import std.types { NonEmptyStr }
import gunbc.recurring_failure_mode { RecurringFailureMode }

data ts_lexer_has_no_ingest_rule_set: RecurringFailureMode = RecurringFailureMode {
identity: "ts_lexer_has_no_ingest_rule_set" as NonEmptyStr,

receipts: [
"STATED FRONTIER, not a repaired defect. v2.extdeps.languages.typescript ts_lex has no whitespace trivia rule: every rule is an emission spelling with baked spacing (eq as space-equals, colon as colon-space), so TypeScript text not laid out exactly as the emitter writes it is unlexable. `const bit: string = bit;` refuses at the space after `=` (gunbc run, BuildBuddy invocation b9e2870f).",

"MEMBERS: v2.test.parse.typescript claim_ts_union_task_status_parses and claim_ts_union_minimal_parses. They assume an ingest lexer, stay ungated as data rows, and are red until this frontier closes; their string literals lex since ts_single_quoted_string_body_reuses_the_double_quoted_char_class was repaired.",

"WHY A FRONTIER: nothing in v2 ingests TypeScript today, and the operator's phase-4 ruling (via sharp-raven-357) keeps the grammar valid but unconsumed. An ingest set is new capability, not a repair of this one.",

"RUNG: outside the ladder for ingestion (no ingest lexer exists). NEXT TRIGGER: a TypeScript ingest rule set with trivia exists, the rust_extended_lex_rules pattern done disjointly (see rust_lex_resolves_two_tokenizations_by_rule_order). OWNER: grammar program (stern-bear-500).",
],

evidence: [],
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
module gunbc.recurring_failure_mode.ts_single_quoted_string_body_reuses_the_double_quoted_char_class

import std.types { NonEmptyStr }
import gunbc.recurring_failure_mode { RecurringFailureMode }

data ts_single_quoted_string_body_reuses_the_double_quoted_char_class: RecurringFailureMode = RecurringFailureMode {
identity: "ts_single_quoted_string_body_reuses_the_double_quoted_char_class" as NonEmptyStr,

receipts: [
"INVALID STATE: v2.extdeps.languages.typescript ts_string_lit_lex_rule delimited a single-quoted string with a body of RepeatPattern over StringTextChar, the DOUBLE-quoted body class (v2.compiler.tokenize: excludes the double quote and newline, not the single quote). The greedy body consumed the closing quote, the close never matched, and every single-quoted TypeScript string refused in tokenize.",

"SPECIMENS: `const bit: 'On' | 'Off' = bit;` refused tokenize_lex_e1_unrecognized_char at the first quote (gunbc run, BuildBuddy invocation b9e2870f), as did the three-variant status union. Both were data TestClaim rows no runner executes.",

"HARM: a modeled token class that can never be produced; any TypeScript claim over a string literal was false and silent.",

"REPAIR: ONE single-quoted body authority, v2.std.compilers.lexing SingleQuotedStringTextChar (excludes only the single quote and newline; matched in v2.compiler.tokenize beside StringTextChar), consumed by ts_string_lit_lex_rule. The delimiter is excluded by the class, so body and close are disjoint by construction and a double quote is ordinary text; no NotFollowedByPattern guard remains. A first repair guarded each body char with NotFollowedByPattern but still reused StringTextChar, so 'a\"b' still refused (side-chat review of gunbc#13760). Escapes (a backslash-escaped quote) are a stated frontier: the TS subset models none. CONTROLS: a_single_quoted_ts_string_lexes_as_one_string_token_holds ('On') and a_double_quote_inside_a_single_quoted_ts_string_stays_in_one_token_holds ('a\"b'), both in test.claim.parse_test_typescript_grammar.",

"RUNG FOUND AT: below the floor (unexecuted). CEILING: 2 -- the control is the wall; a delimited pattern whose body can match its own close is decidable from the rule and could be refused when the lexer is prepared (3), which is the next trigger. OWNER: grammar program (stern-bear-500).",
],

evidence: [],
}
Loading