diff --git a/ROADMAP.md b/ROADMAP.md index fa4a67cc0a3..dfd9e3643d2 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -28,7 +28,7 @@ Ground truth (2026-07-01): `src/v1` = ~174k hand-written `.rs` LOC (the ~154k fi ## 2. Compute fabric — own the infra (CI · deploy · control plane) -State (2026-07-01): required CI is back on the fleet after the Ubicloud detour (#6107, reverted #6111). The detour was the **diagnostic and emergency valve, not a failure**: it proved raw VM isolation alone does not solve a serial floor (same ~50min wall on both runner types), and it stays the **break-glass/burst option**, just not the active required path. So the active work is (1) reduce floor recompute, (2) make fleet admission/caps safe — the dominant wall-clock is inside the floor, not host placement alone. The fleet — srv1/srv2 (128c/125GiB Ampere) + srv3 (greenfield, OpenBMC) — carries agent sessions, the dashboard, and all runners, with live cgroup caps, runner registration, sudoers, and tailscale-ACL actuation still **hand-managed**; the fleet-era OOM/sccache exposure returned with the traffic (operator-accepted interim). #6096 (fleet converge control plane) was **closed unmerged**; its review gap-list is the re-land spec, and the findings apply to the committed `.github/fleet-converge.sh` too (mutate-then-read, `|| true` swallowing, no hostname dispatch) — the re-land is an architecture change, not a rebase. → [CI humming](docs/plans/ci-humming.md) · [host-effect orchestration](docs/plans/host-effect-orchestration.md) · [compute envelope](docs/plans/compute-envelope-model.md) · [charter](docs/plans/ci-process-end-to-end.md) +State (2026-07-01): required CI is back on the fleet after the Ubicloud detour (#6107, reverted #6111). The detour was the **diagnostic and emergency valve, not a failure**: it proved raw VM isolation alone does not solve a serial floor (same ~50min wall on both runner types), and it stays the **break-glass/burst option**, just not the active required path. So the active work is (1) reduce floor recompute, (2) make fleet admission/caps safe — the dominant wall-clock is inside the floor, not host placement alone. The fleet — srv1/srv2 (128c/125GiB Ampere) + srv3 (greenfield, OpenBMC) — carries agent sessions, the dashboard, and all runners, with live cgroup caps, runner registration, sudoers, and tailscale-ACL actuation still **hand-managed**; the fleet-era OOM/sccache exposure returned with the traffic (operator-accepted interim). #6096 (fleet converge control plane) was **closed unmerged**; its review gap-list is the re-land spec, and the findings apply to the committed `.github/fleet-converge.sh` too (mutate-then-read, `|| true` swallowing, no hostname dispatch) — the re-land is an architecture change, not a rebase. → [CI humming](docs/plans/ci-humming.md) · [host-effect orchestration](docs/plans/host-effect-orchestration.md) · [compute envelope](docs/plans/compute-envelope-model.md) · [charter](docs/plans/ci-process-end-to-end.md) · [host-converge inventory](docs/plans/host-converge-inventory.md) **The core design rule (operator-signed, 2026-07-01):** provider-specific power is earned behind a `ProviderOffer` adapter; the core fabric only knows `RunShape → Allocation → Receipt`; default allocation is **strict/ephemeral**; oversubscription is an optimization proven by receipts, never assumed by placement. Not "Ubicloud vs fleet" — GitHub Actions asks for a RunShape, the fabric chooses a ProviderOffer (srv1/srv2 fixed-host slice, Ubicloud ephemeral VM, or later others). srv1/srv2 are modeled as little Ubiclouds first (N strict slots, hard caps, read-back receipts); their richer allocation opportunity (stranded cores, burst, co-residency) is Phase 2, earned by receipts. @@ -65,7 +65,7 @@ State (2026-07-01): required CI is back on the fleet after the Ubicloud detour ( - [ ] **resolve-cache default-on** — purity proven (616/616) but opt-in: whole-file JSON IO buffers ~11× the packed graph and OOMed the concurrent floor (#5789 reverted); gated on a streaming IO realization. The warm ~18% lever, downstream of incrementality. - [ ] **G4 dispatch dup** — `workflow_dispatch`+PR fire two same-SHA runs; the concurrency fallback won't collapse them → wasted runners and OOM risk (live instance: #6110's draft-attach + ready-flip fired two runs, the cancelled one reporting as a failing check). Superseded runs also linger `in_progress` for 12–37min — audit cancel-in-progress coverage; leaked runner minutes. [decision record](docs/plans/ci-merge-freshness.md) -Receipts + adjacent levers: [fractal Gantt](docs/plans/ci-floor-fractal-gantt.md) · [memory chronicle](docs/plans/interpreter-memory-chronicle.md) · [resolver pathology profile](docs/plans/resolver-pathology-profile-receipt.md) · [representation minimization](docs/plans/representation-minimization.md) · [shared-compute memoization](docs/plans/floor-shared-compute-memoization.md) *(M1 ✓ #6008; M2 gated on determinism #5941)* · [OOM reclassification](docs/plans/ci-oom-reclassification.md). +Receipts + adjacent levers: [fractal Gantt](docs/plans/ci-floor-fractal-gantt.md) · [memory chronicle](docs/plans/interpreter-memory-chronicle.md) · [resolver pathology profile](docs/plans/resolver-pathology-profile-receipt.md) · [representation minimization](docs/plans/representation-minimization.md) · [shared-compute memoization](docs/plans/floor-shared-compute-memoization.md) *(M1 ✓ #6008; M2 gated on determinism #5941)* · [OOM reclassification](docs/plans/ci-oom-reclassification.md) · [post-engine deferred ledger + algorithm audit](docs/plans/post-engine-pr-roadmap.md). **Control plane — the #6096 re-land, done right (G2/G3):** @@ -157,4 +157,3 @@ Every lane below was live roadmap before the reset. Shelved = plan docs and carr - **privacy / isolation model for the compute fabric** — trust boundaries, tenant isolation, what the fabric may observe about a workload; was compute-fabric downstream work pre-reset, parked here so it is not lost (`trust_class` on `RunShape` is its active §2 seam). Includes committed-secret references (operator 2026-07-02, not urgent): a typed `SecretRef` — GCP Secret Manager resource name + version/content-hash — so sensitive facts (router serial, WAN MAC, public prefixes, credentials) commit as model-legible references while values stay in Secret Manager; redaction by construction (public shapes carry no raw-value field); first consumer: the network-identity observation fixtures. - **structural correctness walls (standing, not active)** — generated-output gate · coproduct exhaustiveness · cross-representation equality · oracle-method map; they keep gating, no expansion work dispatches from here - **design sketches without an active lane** [orchestration-as-intent](docs/plans/orchestration-as-intent-design.md) [model-grounding extract](docs/plans/model-grounding-lens-extract.md) [node-minimal representation](docs/plans/node-minimal-representation-sketch.md) [func-env sigs](docs/plans/func-env-sigs-single-authority.md) [resolved-graph minimization](docs/plans/resolved-graph-representation-minimization.md) [emit-host batch isolation](docs/plans/emit-host-batch-isolation.md) [accelerator roundtrip](docs/plans/accelerator-demo-roundtrip.md) [commit workflow](docs/plans/commit-workflow.md) [input envelope](docs/plans/input-envelope-roadmap.md) [seed-debt bundle 2](docs/plans/seed-debt-bundle-item-2.md) [resolver collision wall](docs/plans/resolver-type-name-collision-wall.md) [regime-2 shared emission](docs/plans/regime2-shared-emission-fold.md) - diff --git a/dag/ctrl/review_verdict.dag b/dag/ctrl/review_verdict.dag index 138e88cd7ed..7bc671e7fc5 100644 --- a/dag/ctrl/review_verdict.dag +++ b/dag/ctrl/review_verdict.dag @@ -15,7 +15,7 @@ type ReviewProvider | ProviderOther { name: NonEmptyStr } type ReviewSource - = Llm + = LlmSource | Human | Policy | Unattributed @@ -33,7 +33,7 @@ type Reviewer fn reviewer_source(reviewer: Reviewer) -> ReviewSource { match reviewer { - AgentReviewer { provider: _ } => Llm + AgentReviewer { provider: _ } => LlmSource HumanSeatReviewer => Human PolicyReviewer { policy: _ } => Policy UnattributedReviewer { provider_name: _ } => Unattributed diff --git a/dag/extdeps/cache/buildbuddy.dag b/dag/extdeps/cache/buildbuddy.dag index 3ad5a67b2ce..a2ac75e2c2f 100644 --- a/dag/extdeps/cache/buildbuddy.dag +++ b/dag/extdeps/cache/buildbuddy.dag @@ -1,9 +1,8 @@ module extdeps.cache.buildbuddy -import extdeps.cache.types { CacheInterfaceCatalogFacts, ContentAddressed } +import extdeps.cache.types { CacheInterfaceCatalogFacts, ContentAddressed, CatalogCasContentEncoding } import std.cache_identity { CacheInterfaceId } import std.cache_interface { - CasContentEncoding, ContentAddressLookup, WriteOnce, MissThenCreate, @@ -34,7 +33,7 @@ data buildbuddy_cas_id: CacheInterfaceId = "buildbuddy_cas" data buildbuddy_cas_facts: CacheInterfaceCatalogFacts = { identity: buildbuddy_cas_id backing_surface: "buildbuddy-remote-cas" - transport_encoding: CasContentEncoding + transport_encoding: CatalogCasContentEncoding key_derivation: { key_inputs: ContentAddressed diff --git a/dag/extdeps/cache/cache.dag b/dag/extdeps/cache/cache.dag index 7596f1800b8..ca7a26db551 100644 --- a/dag/extdeps/cache/cache.dag +++ b/dag/extdeps/cache/cache.dag @@ -20,6 +20,10 @@ import extdeps.cache.types { CargoFingerprint, SccacheInternalEncoding, CargoFingerprintEncoding, + CatalogOpaqueStringEncoding, + CatalogCasContentEncoding, + CatalogOpaqueStringKey, + CatalogCasContentDigest, } import extdeps.realization.parse_table_memo { parse_table_memo_facts, parse_table_memo_id } import extdeps.realization.reconcile_in_process { @@ -38,8 +42,6 @@ import std.cache_interface { OpaqueStringEncoding, CasContentEncoding, NativeInternalEncoding, - OpaqueStringKey, - CasContentDigest, } import std.types { ContentHash } import std.list { List } @@ -68,8 +70,8 @@ fn cache_interface_facts_from_catalog(row: CacheInterfaceCatalogFacts) -> CacheI identity: row.identity backing_surface: row.backing_surface transport_encoding: match row.transport_encoding { - OpaqueStringEncoding => OpaqueStringEncoding - CasContentEncoding => CasContentEncoding + CatalogOpaqueStringEncoding => OpaqueStringEncoding + CatalogCasContentEncoding => CasContentEncoding SccacheInternalEncoding => NativeInternalEncoding CargoFingerprintEncoding => CasContentEncoding } @@ -102,9 +104,9 @@ fn cache_key_projection_provider_key( backend: CacheInterfaceCatalogFacts, ) -> CatalogProviderKey { match backend.transport_encoding { - OpaqueStringEncoding => OpaqueStringKey { key: artifact.subject_digest } + CatalogOpaqueStringEncoding => CatalogOpaqueStringKey { key: artifact.subject_digest } SccacheInternalEncoding => SccacheInternalHash { digest: artifact.subject_digest } - CasContentEncoding => CasContentDigest { digest: artifact.subject_digest } + CatalogCasContentEncoding => CatalogCasContentDigest { digest: artifact.subject_digest } CargoFingerprintEncoding => CargoFingerprint { digest: artifact.subject_digest } } } diff --git a/dag/extdeps/cache/github_actions.dag b/dag/extdeps/cache/github_actions.dag index 908d08e86bb..6b2a7f9cd76 100644 --- a/dag/extdeps/cache/github_actions.dag +++ b/dag/extdeps/cache/github_actions.dag @@ -5,10 +5,10 @@ import extdeps.cache.types { HandAuthored, UpsertSubject, FilePaths, + CatalogOpaqueStringEncoding, } import std.cache_identity { CacheInterfaceId } import std.cache_interface { - OpaqueStringEncoding, PrefixFallback, OverwriteAllowed, WriteThenCommit, @@ -41,7 +41,7 @@ data gha_actions_cache_id: CacheInterfaceId = "gha_actions_cache" data gha_actions_cache_facts: CacheInterfaceCatalogFacts = { identity: gha_actions_cache_id backing_surface: "github-actions-cache-api" - transport_encoding: OpaqueStringEncoding + transport_encoding: CatalogOpaqueStringEncoding key_derivation: { key_inputs: HandAuthored { cited_inputs: [UpsertSubject, FilePaths] } diff --git a/dag/extdeps/cache/rustup.dag b/dag/extdeps/cache/rustup.dag index 87cf2a87d0b..ffd5457399b 100644 --- a/dag/extdeps/cache/rustup.dag +++ b/dag/extdeps/cache/rustup.dag @@ -1,9 +1,13 @@ module extdeps.cache.rustup -import extdeps.cache.types { CacheInterfaceCatalogFacts, NativeInternal, ToolchainSpec } +import extdeps.cache.types { + CacheInterfaceCatalogFacts, + NativeInternal, + ToolchainSpec, + CatalogOpaqueStringEncoding, +} import std.cache_identity { CacheInterfaceId } import std.cache_interface { - OpaqueStringEncoding, NativeInternalLookup, ProviderInternal, MissThenCreate, @@ -35,7 +39,7 @@ data rustup_toolchain_store_id: CacheInterfaceId = "rustup_toolchain_store" data rustup_toolchain_store_facts: CacheInterfaceCatalogFacts = { identity: rustup_toolchain_store_id backing_surface: "rustup-toolchains-dir" - transport_encoding: OpaqueStringEncoding + transport_encoding: CatalogOpaqueStringEncoding key_derivation: { key_inputs: NativeInternal { cited_inputs: [ToolchainSpec] } diff --git a/dag/extdeps/cache/types.dag b/dag/extdeps/cache/types.dag index 04437d76ac2..4f90252f2e2 100644 --- a/dag/extdeps/cache/types.dag +++ b/dag/extdeps/cache/types.dag @@ -19,10 +19,6 @@ import std.cache_interface { ManualPurge, DependencyChange, CacheEvidence, - OpaqueStringEncoding, - CasContentEncoding, - OpaqueStringKey, - CasContentDigest, ArtifactIdentity, ProducerReceipt, } @@ -43,15 +39,15 @@ type CatalogInputSurface | ToolchainSpec type CatalogProviderKeyEncoding - = OpaqueStringEncoding - | CasContentEncoding + = CatalogOpaqueStringEncoding + | CatalogCasContentEncoding | SccacheInternalEncoding | CargoFingerprintEncoding type CatalogProviderKey - = OpaqueStringKey { key: NonEmptyStr } + = CatalogOpaqueStringKey { key: NonEmptyStr } | SccacheInternalHash { digest: ContentHash } - | CasContentDigest { digest: ContentHash } + | CatalogCasContentDigest { digest: ContentHash } | CargoFingerprint { digest: ContentHash } type CatalogBackendCacheKey { @@ -134,8 +130,8 @@ fn catalog_has_vendor_only_key_inputs(inputs: List) -> Bool fn catalog_transport_encoding_collapsed_in_std_projection(encoding: CatalogProviderKeyEncoding) -> Bool { match encoding { - OpaqueStringEncoding => false - CasContentEncoding => false + CatalogOpaqueStringEncoding => false + CatalogCasContentEncoding => false SccacheInternalEncoding => false CargoFingerprintEncoding => true } diff --git a/dag/extdeps/github/pulls.dag b/dag/extdeps/github/pulls.dag index bd42a8fb0c4..b837d11a7d1 100644 --- a/dag/extdeps/github/pulls.dag +++ b/dag/extdeps/github/pulls.dag @@ -25,7 +25,7 @@ type PullRequestState = Open | Closed type ReviewState = Pending | Commented | Approved | ChangesRequested | Dismissed -type ReviewEvent = Approve | RequestChanges | Comment | Pending +type ReviewEvent = Approve | RequestChanges | Comment | PendingEvent type PullRequestRef { ref: GitRef diff --git a/dag/extdeps/languages/markdown.dag b/dag/extdeps/languages/markdown.dag index b8a8e914f82..29c6d02231d 100644 --- a/dag/extdeps/languages/markdown.dag +++ b/dag/extdeps/languages/markdown.dag @@ -16,7 +16,7 @@ import std.decl_ref { DeclarationRef, WholeDeclaration } import std.markup { Fragment, Element, - TextNode, + FragmentText, RawNode, MarkupMedium, TagDelimiter, @@ -155,13 +155,13 @@ fn markdown_markup_medium(sp: MarkdownSpellings) -> MarkupMedium { fn md_inline_to_fragment(inline: MarkdownInline, sp: MarkdownSpellings) -> Fragment { match inline { TextInline { text } => - TextNode { content: text } + FragmentText { content: text } EmphasisInline { inlines } => Element { tag: "em", attributes: [], children: md_inlines_to_fragments(inlines: inlines, sp: sp) } StrongInline { inlines } => Element { tag: "strong", attributes: [], children: md_inlines_to_fragments(inlines: inlines, sp: sp) } CodeInline { text } => - Element { tag: "code", attributes: [], children: [ TextNode { content: text } ] } + Element { tag: "code", attributes: [], children: [ FragmentText { content: text } ] } LinkInline { inlines, url } => RawNode { content: md_link_form(text: md_render_inlines(inlines: inlines, sp: sp), url: url, sp: sp) } ImageInline { alt, url } => diff --git a/dag/extdeps/llm/openai.dag b/dag/extdeps/llm/openai.dag index f192eadda65..e38b00e3c49 100644 --- a/dag/extdeps/llm/openai.dag +++ b/dag/extdeps/llm/openai.dag @@ -127,7 +127,7 @@ type OpenAiChatCompletion200UrlCitation { } type OpenAiChatCompletion200Annotation - = UrlCitation { url_citation: OpenAiChatCompletion200UrlCitation } + = ChatUrlCitation { url_citation: OpenAiChatCompletion200UrlCitation } type OpenAiChatCompletion200Message { role: String @@ -335,7 +335,7 @@ data structural_coverage_gap_openai_responses_200_residual: List = [ }, CommitCheckEnrollment { check: CommitWitnessClaim { - entry: "dag/test/claim/variant_owner_expected_type_test.dag", - check_fns: ["expected_type_picks_variant_owner_not_alpha_order"] + entry: "dag/test/claim/github_merge_state_witness_test.dag", + check_fns: ["witness_clean_and_blocked_are_distinct_variants"] }, surfaces: [GithubActionsCiJob] }, diff --git a/dag/gunbc/fleet_posix_accounts.dag b/dag/gunbc/fleet_posix_accounts.dag index 407c12793ee..4357db0243c 100644 --- a/dag/gunbc/fleet_posix_accounts.dag +++ b/dag/gunbc/fleet_posix_accounts.dag @@ -2,7 +2,7 @@ module gunbc.fleet_posix_accounts import extdeps.access.posix { PosixUser } import std.types { NonEmptyStr, List, String, Bool } -import std.disposition { Disposition, Terminal, Scaffold } +import std.disposition { Disposition, Terminal, Scaffold, ConstructionMechanism } import std.decl_ref { DeclarationRef, WholeDeclaration } data fleet_posix_accounts_scope_disposition: Disposition = Terminal { diff --git a/dag/gunbc/fleet_show_effective_read.dag b/dag/gunbc/fleet_show_effective_read.dag index a6af06cf602..e01cb08bc50 100644 --- a/dag/gunbc/fleet_show_effective_read.dag +++ b/dag/gunbc/fleet_show_effective_read.dag @@ -26,7 +26,7 @@ import gunbc.fleet_intent { srv1_host, srv2_host } import gunbc.fleet_intent_network { operator_host_srv1, operator_host_srv2 } import gunbc.host_converge { ConvergeVerdict, - Converged, + VerdictConverged, Drifted, Absent, } @@ -330,7 +330,7 @@ fn slice_property_memory_max_verdict( fn oom_pressure_limit_converge_verdict(read: OomPressureLimitLiveRead, desired: Percent) -> ConvergeVerdict { if percent_count(p: read.pressure_limit) == percent_count(p: desired) { - Converged + VerdictConverged } else { Drifted } @@ -364,7 +364,7 @@ fn runner_width_live_read_typed(row: RunnerWidthLiveReadRow) -> RunnerWidthLiveR } fn runner_width_converge_verdict(observed: Int, desired: Int) -> ConvergeVerdict { - if observed == desired { Converged } else { Drifted } + if observed == desired { VerdictConverged } else { Drifted } } fn show_effective_read_cannot_converge(read: ShowEffectiveRead) -> Bool { diff --git a/dag/gunbc/gunbhub_serve.dag b/dag/gunbc/gunbhub_serve.dag index 2346fbce444..ce4d62fd454 100644 --- a/dag/gunbc/gunbhub_serve.dag +++ b/dag/gunbc/gunbhub_serve.dag @@ -1,7 +1,7 @@ module gunbc.gunbhub_serve import std.types { HttpMethod, HttpStatus, GET } -import std.http_path { PathTemplate } +import std.http_path { PathTemplate, UrlPathToken } import extdeps.uri_path { PathTemplateParseResult, ParsedPathTemplate, diff --git a/dag/gunbc/host_converge.dag b/dag/gunbc/host_converge.dag index 0b350c81c9f..00c4bfcba03 100644 --- a/dag/gunbc/host_converge.dag +++ b/dag/gunbc/host_converge.dag @@ -113,19 +113,19 @@ type GunbcPinnedTreeDriftReceipt { } type ConvergeVerdict - = Converged + = VerdictConverged | Drifted | Absent fn converge_verdict(effective: String, expected: String) -> ConvergeVerdict { if effective == "" { Absent } - else { if effective == expected { Converged } else { Drifted } } + else { if effective == expected { VerdictConverged } else { Drifted } } } fn converge_verdict_label(v: ConvergeVerdict) -> String { match v { - Converged => "converged" + VerdictConverged => "converged" Drifted => "drifted" Absent => "absent" } diff --git a/dag/gunbc/os_install_deduction.dag b/dag/gunbc/os_install_deduction.dag index 1162efabc60..0882ef45c7f 100644 --- a/dag/gunbc/os_install_deduction.dag +++ b/dag/gunbc/os_install_deduction.dag @@ -35,14 +35,14 @@ type OsInstallPreflightPlan | RefuseNotUnattendedByPolicy type OsInstallRuntimeDiagnosticVerdict - = ReadyToBoot + = RuntimeReadyToBoot | KvmFirmwareIdleObserved - | BootOverrideConsumedNoInstallEvidence + | RuntimeBootOverrideConsumedNoInstallEvidence | InstallerAwaitingStorage | InstallerRunning - | InstallerHung + | RuntimeInstallerHung | KvmSuggestsOsBooted - | OsInstalled + | RuntimeOsInstalled type KvmObservedScreen = FirmwareSetup @@ -112,6 +112,6 @@ fn runtime_verdict_from_kvm_attestation( FirmwareSetup => KvmFirmwareIdleObserved UefiBootMenu => KvmFirmwareIdleObserved GrubMenu => InstallerRunning - ErrorScreen { summary: _ } => InstallerHung + ErrorScreen { summary: _ } => RuntimeInstallerHung } } diff --git a/dag/gunbc/plans/bounded_input_cost_envelope_scheduling.dag b/dag/gunbc/plans/bounded_input_cost_envelope_scheduling.dag index a4314f57a0b..8b8f5750ced 100644 --- a/dag/gunbc/plans/bounded_input_cost_envelope_scheduling.dag +++ b/dag/gunbc/plans/bounded_input_cost_envelope_scheduling.dag @@ -102,6 +102,14 @@ fn bounded_input_cost_envelope_scheduling_body() -> List { li(text: "Drop Predicted cost from runnable → width fold fails closed / refuses over-wide spawn"), li(text: "Affected witness skipped on touched dep → selection soundness RED (independent of this design, but scheduling must not bypass it)"), ]), + h2(text: "11. Folded deltas (from the retired `static-resource-envelope.md` draft, 2026-07-03)"), + p(text: "That draft (dispatched from `vivid-carp-243` → `merry-koi-140` → revived under `floor-width-budget-source-a`) forked this doc's `InputEnvelope`/width vocabulary and was retired as scope moot under the current convergence (CI de-prioritized). Its census (§4/§8 there) was a point-in-time re-verification of load-bearing sites, not unique design and is dropped; these four points *are* unique thinking not otherwise captured here, folded in as additions per §3/§9's existing convention (not new types):"), + ol(items: [ + li(text: "**N-axis width, beyond memory+cpu.** §5's formula reconciles memory and cpu_bound. When storage and energy caps are eventually instrumented, the same `min(...)` reconciliation extends per-axis (`width_storage`, `width_energy` alongside `width_memory`/`width_compute`) over a per-Runnable demand vector — do not fuse storage bytes onto `ByteSize`/`Memory` (a distinct `Quantity` is needed, e.g. `Storage`/`DiskSize`); this is a P2/P3 extension, not a new carrier family."), + li(text: "**Provenance-as-metric.** A `DemandProvenance = StaticDerived | Measured | ConvergedState | Fixture` field on declared bounds/demand would let admission distinguish a real ceiling from `Fixture` scaffolding (mirrors `EnvelopeUnknown => RefusedUndeclared` — a `Fixture`-provenance bound is *declared but not yet grounded*, a different failure mode from *undeclared*) and would make `static_derived_fraction(bounds) -> Percent` a quantitative version of the \"input-starved\" thesis. Candidate home: `InputBound` and/or the per-Runnable demand vector (P1-consumer follow-up, since `InputEnvelope` has live consumers already)."), + li(text: "**`InputEnvelope` streaming projection.** `BoundedInput` today assumes a finite, known-at-admission corpus. A streaming/incremental consumer has a *rate*, not a count — rather than a sibling envelope type, `BoundedInput` could carry a second bound-kind projection (`FiniteKnown` vs `RateBounded { rate, burst, window, backlog }`) answering the same `input_admitted` question against a rate ceiling. `RefusedOverEnvelope`/`RefusedUndeclared` stay projection-agnostic. P1-consumer follow-up, not P0/P1 here."), + li(text: "**Cost-model single authority beyond scheduling.** The per-axis magnitude record this design's `CostEnvelope`/width-fold reads (§3b, §6) should not become scheduler-private: the complexity lens's asymptotic-class analysis asks \"what order does this axis grow in,\" the scheduler asks \"does this fit the cap\" — both over the same declared/derived magnitude. A second scheduler-local taxonomy would re-fork what this doc already keeps singular. Direction only; confirm before any P2/P3 lens assumes it. Relatedly: space-like axes (memory/storage) should schedule on the upper bound (asymmetric OOM failure), time-like axes (compute-as-thread-seconds) may schedule on average/p95 (degraded, not crashed) — worth stating explicitly whenever P2/P3 extends beyond a hard per-axis cap."), + ]), ] } diff --git a/dag/gunbc/plans/regime2_shared_emission_fold.dag b/dag/gunbc/plans/regime2_shared_emission_fold.dag index acffc7f9fe7..c3095b3f838 100644 --- a/dag/gunbc/plans/regime2_shared_emission_fold.dag +++ b/dag/gunbc/plans/regime2_shared_emission_fold.dag @@ -28,7 +28,7 @@ fn regime2_shared_emission_fold_body() -> List { p(text: "One fold replaces three:"), CodeBlock { code: "expected_X = render( project_X_to_doc(authority) , X_protocol )\n" }, ul(items: [ - li(text: "**The emission model** = a generic **layout / document IR** in `std` (the standard pretty-printer `Doc`: `text` / `line` / `nest` / `concat` / `sep`, whatever the faithful minimal set turns out to be). This is the \"one concept, every format\" carrier. **DFS `std` first** (§6): confirm no existing layout primitive before minting. Note `std.markup.Fragment` (Element/TextNode/RawNode) is a **markup** tree (HTML tags) — a *different* concern from line/indent layout, so it is **not** the shared IR here; confirm and move on. v2's `serialize_target` has layout entangled with grammar rows, so it is not seed-usable here either. A new `std.layout` (or similarly-named) module is therefore net-new concept, not re-invention — but **prove that** by the DFS, don't assume it."), + li(text: "**The emission model** = a generic **layout / document IR** in `std` (the standard pretty-printer `Doc`: `text` / `line` / `nest` / `concat` / `sep`, whatever the faithful minimal set turns out to be). This is the \"one concept, every format\" carrier. **DFS `std` first** (§6): confirm no existing layout primitive before minting. Note `std.markup.Fragment` (Element/FragmentText/RawNode) is a **markup** tree (HTML tags) — a *different* concern from line/indent layout, so it is **not** the shared IR here; confirm and move on. v2's `serialize_target` has layout entangled with grammar rows, so it is not seed-usable here either. A new `std.layout` (or similarly-named) module is therefore net-new concept, not re-invention — but **prove that** by the DFS, don't assume it."), li(text: "**The protocol** = a per-format record of the genuinely format-global spelling knobs (indent unit, comment prefix, newline style — whatever survives after structure moves into the `Doc`). Prefer encoding structure in the `Doc`; keep the protocol record as thin as the formats actually require. If it ends up near-empty because the `Doc` determines everything, that is a fine outcome — say so."), li(text: "**`project_X_to_doc`** = the per-format projection that builds the `Doc` from the authority, baking any format-specific **text** decisions (e.g. yaml scalar quoting / escaping) into `Doc` text nodes at projection time — **never** into the render fold."), ]), diff --git a/dag/gunbc/roadmap_authority.dag b/dag/gunbc/roadmap_authority.dag index 9fa2163b31b..b363cbe67ae 100644 --- a/dag/gunbc/roadmap_authority.dag +++ b/dag/gunbc/roadmap_authority.dag @@ -137,7 +137,7 @@ fn section_2() -> RoadmapSection { RoadmapSection { title: "2. Compute fabric — own the infra (CI · deploy · control plane)", elements: [ - section_prose(content: "State (2026-07-01): required CI is back on the fleet after the Ubicloud detour (#6107, reverted #6111). The detour was the **diagnostic and emergency valve, not a failure**: it proved raw VM isolation alone does not solve a serial floor (same ~50min wall on both runner types), and it stays the **break-glass/burst option**, just not the active required path. So the active work is (1) reduce floor recompute, (2) make fleet admission/caps safe — the dominant wall-clock is inside the floor, not host placement alone. The fleet — srv1/srv2 (128c/125GiB Ampere) + srv3 (greenfield, OpenBMC) — carries agent sessions, the dashboard, and all runners, with live cgroup caps, runner registration, sudoers, and tailscale-ACL actuation still **hand-managed**; the fleet-era OOM/sccache exposure returned with the traffic (operator-accepted interim). #6096 (fleet converge control plane) was **closed unmerged**; its review gap-list is the re-land spec, and the findings apply to the committed `.github/fleet-converge.sh` too (mutate-then-read, `|| true` swallowing, no hostname dispatch) — the re-land is an architecture change, not a rebase. → [CI humming](docs/plans/ci-humming.md) · [host-effect orchestration](docs/plans/host-effect-orchestration.md) · [compute envelope](docs/plans/compute-envelope-model.md) · [charter](docs/plans/ci-process-end-to-end.md)"), + section_prose(content: "State (2026-07-01): required CI is back on the fleet after the Ubicloud detour (#6107, reverted #6111). The detour was the **diagnostic and emergency valve, not a failure**: it proved raw VM isolation alone does not solve a serial floor (same ~50min wall on both runner types), and it stays the **break-glass/burst option**, just not the active required path. So the active work is (1) reduce floor recompute, (2) make fleet admission/caps safe — the dominant wall-clock is inside the floor, not host placement alone. The fleet — srv1/srv2 (128c/125GiB Ampere) + srv3 (greenfield, OpenBMC) — carries agent sessions, the dashboard, and all runners, with live cgroup caps, runner registration, sudoers, and tailscale-ACL actuation still **hand-managed**; the fleet-era OOM/sccache exposure returned with the traffic (operator-accepted interim). #6096 (fleet converge control plane) was **closed unmerged**; its review gap-list is the re-land spec, and the findings apply to the committed `.github/fleet-converge.sh` too (mutate-then-read, `|| true` swallowing, no hostname dispatch) — the re-land is an architecture change, not a rebase. → [CI humming](docs/plans/ci-humming.md) · [host-effect orchestration](docs/plans/host-effect-orchestration.md) · [compute envelope](docs/plans/compute-envelope-model.md) · [charter](docs/plans/ci-process-end-to-end.md) · [host-converge inventory](docs/plans/host-converge-inventory.md)"), section_prose(content: "**The core design rule (operator-signed, 2026-07-01):** provider-specific power is earned behind a `ProviderOffer` adapter; the core fabric only knows `RunShape → Allocation → Receipt`; default allocation is **strict/ephemeral**; oversubscription is an optimization proven by receipts, never assumed by placement. Not \"Ubicloud vs fleet\" — GitHub Actions asks for a RunShape, the fabric chooses a ProviderOffer (srv1/srv2 fixed-host slice, Ubicloud ephemeral VM, or later others). srv1/srv2 are modeled as little Ubiclouds first (N strict slots, hard caps, read-back receipts); their richer allocation opportunity (stranded cores, burst, co-residency) is Phase 2, earned by receipts."), section_prose(content: "**◆ Milestones:** OOM root-caused (hand-set caps vs measured peaks + budget double-count) ✓ · Ubicloud stabilization trialed ✓ (#6107) → reverted, operator call (#6111 — no wall-clock win; width=9 was live by construction and never touched the serial wall) · converge shell emitted ✓ (#5725/#5827) · `apply()` Phase A ✓ (#5756) · CD to srv1 ✓ (#6030/#6060/#6093) · witness-level affected-set pruning LIVE ✓ (#6061) → **▸ NOW: kill the serial compile-clean wall · floor <1min · CD wrong-host fix · stateless-frontend MVP (first non-CI fabric consumer) · converge re-land to the signed design** → srv1/srv2 runner-allocation v0 (hand-config retired) → StrictLease + shape labels → dormant-Ubicloud provider row (design-break probe) → periodic actuation with receipts → srv3 OsInstalled · `apply()` Phases B–D → **TERMINAL: required CI runs on the compute fabric — the model can choose any admitted ProviderOffer, the receipt proves the shape was honored, and install/manage/decom all ride `apply()` with fail-closed receipts**"), section_prose(content: "**Acceptance rule (operator, 2026-07-02): an item is not complete because its algebra exists — it is complete only when a NAMED consumer uses it on the LIVE path and produces a receipt** (DESIGN §5: a typecheck or a grep is not a consumer). Completion tiers: **T0** modeled · **T1** fixture/synthetic witness · **T2** wired to the intended consumer (proof by consumption, never grep) · **T3** live dry-run receipt · **T4** live apply + independent read-back receipt · **T5** a periodic/CI consumer keeps it alive. Every dispatchable node states **Accept:** — its required tier plus an operator-checkable end state on the real fleet (what is different on srv1/srv2 or in CI afterward) plus a discriminating RED; control-plane/fabric items close at **T4/T5**, and anything below the named tier is a subtask, never completion. A sized node without an explicit Accept is not ready to dispatch; algebra, carriers, and lenses are mechanisms and never their own acceptance. The bar for real progress: *can srv1/srv2 runner allocation be changed through the model, read back, re-run safely, and retracted?* Until yes, the work is scaffolding. Full per-node checklists: [fleet acceptance criteria](docs/plans/fleet-acceptance-criteria.md) — the node's Accept line is the summary, the doc is the checklist, and edits to one land with the other."), @@ -184,7 +184,7 @@ fn section_2() -> RoadmapSection { RoadmapEdge { child: nid(s: "1-sched-resource-aware"), parent: nid(s: "1-bics-design") }, ], ), - section_prose(content: "Receipts + adjacent levers: [fractal Gantt](docs/plans/ci-floor-fractal-gantt.md) · [memory chronicle](docs/plans/interpreter-memory-chronicle.md) · [resolver pathology profile](docs/plans/resolver-pathology-profile-receipt.md) · [representation minimization](docs/plans/representation-minimization.md) · [shared-compute memoization](docs/plans/floor-shared-compute-memoization.md) *(M1 ✓ #6008; M2 gated on determinism #5941)* · [OOM reclassification](docs/plans/ci-oom-reclassification.md)."), + section_prose(content: "Receipts + adjacent levers: [fractal Gantt](docs/plans/ci-floor-fractal-gantt.md) · [memory chronicle](docs/plans/interpreter-memory-chronicle.md) · [resolver pathology profile](docs/plans/resolver-pathology-profile-receipt.md) · [representation minimization](docs/plans/representation-minimization.md) · [shared-compute memoization](docs/plans/floor-shared-compute-memoization.md) *(M1 ✓ #6008; M2 gated on determinism #5941)* · [OOM reclassification](docs/plans/ci-oom-reclassification.md) · [post-engine deferred ledger + algorithm audit](docs/plans/post-engine-pr-roadmap.md)."), section_group( label: "Control plane — the #6096 re-land, done right (G2/G3)", nodes: [ diff --git a/dag/gunbc/runner_unit_live_read.dag b/dag/gunbc/runner_unit_live_read.dag index 9f07febf3d8..3d0b096f189 100644 --- a/dag/gunbc/runner_unit_live_read.dag +++ b/dag/gunbc/runner_unit_live_read.dag @@ -16,7 +16,7 @@ import extdeps.os.systemd { import gunbc.host_converge { ConvergeTarget, ConvergeVerdict, - Converged, + VerdictConverged, Drifted, Absent, converge_verdict, @@ -138,7 +138,7 @@ fn runner_unit_memory_max_drifts_against_cap( fn runner_unit_swap_max_converged(read: RunnerUnitMemoryLiveRead, target: ConvergeTarget) -> Bool { match converge_target_live_verdict(read: read, target: target) { - Present { value: Converged } => true + Present { value: VerdictConverged } => true _ => false } } diff --git a/dag/gunbc/srv3_os_install_diagnostic.dag b/dag/gunbc/srv3_os_install_diagnostic.dag index 9695e323f88..a4db782c80b 100644 --- a/dag/gunbc/srv3_os_install_diagnostic.dag +++ b/dag/gunbc/srv3_os_install_diagnostic.dag @@ -118,10 +118,10 @@ type IsoServeReadinessObservation type NbdServeReadinessVerdict = ServeAbsent - | ServePortAbsent - | ServePortConflict - | TokenMissing - | IsoMissing + | ServePortAbsentVerdict + | ServePortConflictVerdict + | TokenMissingVerdict + | IsoMissingVerdict | IsoHashMismatch | ServeReady @@ -199,7 +199,7 @@ type KvmObservation { } type KvmDiagnosticVerdict - = KvmUntrusted + = KvmWeakUntrusted | KvmWeakInconclusive type OsInstallActuationObservations { @@ -216,7 +216,7 @@ type OsInstallActuationObservations { } type InstallDiagnosticVerdict - = ReadyToBoot + = ReadyToBootVerdict | VirtualMediaServeDown | VirtualMediaNotMounted | BootOverrideNotSet @@ -225,10 +225,10 @@ type InstallDiagnosticVerdict | FirmwareIdle | InstallerBooted | AutoinstallRunning - | InstallerHung - | OsInstalled + | InstallerHungVerdict + | OsInstalledVerdict | KvmUntrustedVerdict - | Inconclusive + | InconclusiveVerdict type BmcWebsocketObservation = WebsocketUpgradeAccepted @@ -292,20 +292,20 @@ fn classify_nbd_serve_readiness( WebsocatAbsent => ServeAbsent ServeProcessesPresent => match session { - TokenMissing => TokenMissing + TokenMissing => TokenMissingVerdict TokenPresent => match iso { - IsoMissing => IsoMissing + IsoMissing => IsoMissingVerdict IsoPresent { observed_content_hash: observed } => if (observed as String) != (intent.iso_content_hash as String) { IsoHashMismatch } else { match port { - ServePortAbsent => ServePortAbsent - ServePortConflict => ServePortConflict + ServePortAbsent => ServePortAbsentVerdict + ServePortConflict => ServePortConflictVerdict ServePortListening { port: p, owner_matches_expected: owner_ok } => if p != intent.serve_port || !owner_ok { - ServePortConflict + ServePortConflictVerdict } else { ServeReady } @@ -320,10 +320,10 @@ fn nbd_serve_readiness_is_ready(verdict: NbdServeReadinessVerdict) -> Bool { match verdict { ServeReady => true ServeAbsent => false - ServePortAbsent => false - ServePortConflict => false - TokenMissing => false - IsoMissing => false + ServePortAbsentVerdict => false + ServePortConflictVerdict => false + TokenMissingVerdict => false + IsoMissingVerdict => false IsoHashMismatch => false } } @@ -477,7 +477,7 @@ fn classify_router_lease_table( fn classify_kvm(obs: KvmObservation) -> KvmDiagnosticVerdict { if !obs.keyboard_accepted { - KvmUntrusted + KvmWeakUntrusted } else { KvmWeakInconclusive } @@ -546,7 +546,7 @@ fn diagnose_when_serve_ready( match virtual_media { VirtualMediaMountedExpectedIso => if observations.elapsed_since_boot_once_ms == 0 { - ReadyToBoot + ReadyToBootVerdict } else { diagnose_when_not_pre_boot_ready( observations: observations, @@ -616,7 +616,7 @@ fn diagnose_install_hung_or_idle( if sol_has_installer_evidence(verdict: sol) { if observations.elapsed_since_boot_once_ms >= srv3_install_hang_no_installer_progress_ms { if router_lacks_os_installed_lease(router: router) { - InstallerHung + InstallerHungVerdict } else { InstallerBooted } @@ -646,10 +646,10 @@ fn diagnose_boot_override_consumed_or_weak( SolFirmwareMenu => BootOverrideConsumedNoInstall SolSilent => if observations.elapsed_since_boot_once_ms >= srv3_install_hang_no_router_lease_ms { - InstallerHung + InstallerHungVerdict } else { if observations.sol_console.duration_ms >= srv3_install_hang_no_installer_progress_ms { - Inconclusive + InconclusiveVerdict } else { BootOverrideConsumedNoInstall } @@ -678,9 +678,9 @@ fn diagnose_weak_kvm_or_inconclusive( if !observations.kvm.keyboard_accepted { KvmUntrustedVerdict } else { - Inconclusive + InconclusiveVerdict } - _ => Inconclusive + _ => InconclusiveVerdict } } } @@ -706,7 +706,7 @@ fn diagnose_os_install_actuation( let _kvm = classify_kvm(obs: observations.kvm) match router { - Srv3OsLeasePresent => OsInstalled + Srv3OsLeasePresent => OsInstalledVerdict _ => diagnose_when_router_not_installed( intent: intent, @@ -722,14 +722,14 @@ fn diagnose_os_install_actuation( fn install_diagnostic_verdict_is_ready_to_boot(verdict: InstallDiagnosticVerdict) -> Bool { match verdict { - ReadyToBoot => true + ReadyToBootVerdict => true _ => false } } fn install_diagnostic_verdict_is_os_installed(verdict: InstallDiagnosticVerdict) -> Bool { match verdict { - OsInstalled => true + OsInstalledVerdict => true _ => false } } diff --git a/dag/gunbc/srv3_os_install_reconcile_receipt.dag b/dag/gunbc/srv3_os_install_reconcile_receipt.dag index 82aaba4402b..342577dd76e 100644 --- a/dag/gunbc/srv3_os_install_reconcile_receipt.dag +++ b/dag/gunbc/srv3_os_install_reconcile_receipt.dag @@ -150,14 +150,14 @@ fn os_install_preflight_verdict_wire(verdict: OsInstallPreflightVerdict) -> Stri fn os_install_runtime_verdict_wire(verdict: OsInstallRuntimeDiagnosticVerdict) -> String { match verdict { - ReadyToBoot => "ReadyToBoot" + RuntimeReadyToBoot => "ReadyToBoot" KvmFirmwareIdleObserved => "KvmFirmwareIdleObserved" - BootOverrideConsumedNoInstallEvidence => "BootOverrideConsumedNoInstallEvidence" + RuntimeBootOverrideConsumedNoInstallEvidence => "BootOverrideConsumedNoInstallEvidence" InstallerAwaitingStorage => "InstallerAwaitingStorage" InstallerRunning => "InstallerRunning" - InstallerHung => "InstallerHung" + RuntimeInstallerHung => "InstallerHung" KvmSuggestsOsBooted => "KvmSuggestsOsBooted" - OsInstalled => "OsInstalled" + RuntimeOsInstalled => "OsInstalled" } } diff --git a/dag/gunbc/workflow/types.dag b/dag/gunbc/workflow/types.dag index a0024b1e97e..d0975de869a 100644 --- a/dag/gunbc/workflow/types.dag +++ b/dag/gunbc/workflow/types.dag @@ -12,7 +12,7 @@ import extdeps.cloud.gcp.gcp { GcpProjectId, ServiceAccountEmail } import extdeps.uri { Uri } import ctrl.review_verdict { FindingSeverity } -type ArtifactKind = Design | Review | Plan | CiReport +type ArtifactKind = DesignDoc | Review | Plan | CiReport type AuthorSource = Llm | Human type ReviewDimension = Coherence | Quality | Requirements | Aspirational @@ -165,7 +165,7 @@ type OutcomeStatus | Failed | PendingApproval | Skipped - | TerminalFailed + | OutcomeTerminalFailed type StageOutcome { run_key: NonEmptyStr diff --git a/dag/std/computation.dag b/dag/std/computation.dag index e2cb70c6f78..9eafa2e1a1c 100644 --- a/dag/std/computation.dag +++ b/dag/std/computation.dag @@ -15,18 +15,20 @@ import std.algebra { kernel_algebra_profile } +data subtree_size_rename_marker: String = "Constructor-owner ruling (§1c) collision fix: SizeBound's tree bound arm was named TreeSize, colliding with RankingDimension.TreeSize (std.termination) in this module's own scope once one arm name may have only one owner. Renamed to SubtreeSize — the more precise fact: a tree-descent bound measures the shrinking SUBTREE consumed per step. RankingDimension.TreeSize (the ranking-dimension taxonomy, ~12 sites in v1.complexity/induction) keeps the shared name." + type SizeBound = CollectionSize { param: String } | ParserStreamSize { witness: String } | WorklistDrainSize { element: String } - | TreeSize { param: String } + | SubtreeSize { param: String } | ArithmeticParam { param: String } | ExplicitCountZero | ExplicitCountPositive { steps: PositiveDescentAmount } | Forever fn tree_size_bound(param: String) -> SizeBound { - TreeSize { param: param } + SubtreeSize { param: param } } type CallPattern @@ -60,7 +62,7 @@ fn lower_call_pattern(pattern: CallPattern) -> LoweringTarget { match pattern { ChildAccessorCall { accessor: a } => LoweringTarget { primitive: Descend, - bound: TreeSize { param: a }, + bound: SubtreeSize { param: a }, evidence: Strict, factor: none } CollectionShrinkCall { amount: p, collection: c } => @@ -105,7 +107,7 @@ fn lower_call_pattern(pattern: CallPattern) -> LoweringTarget { fn size_bound_param(bound: SizeBound) -> String? { match bound { - TreeSize { param: p } => Present { value: p } + SubtreeSize { param: p } => Present { value: p } CollectionSize { param: p } => Present { value: p } ParserStreamSize { witness: w } => Present { value: w } WorklistDrainSize { element: e } => Present { value: e } diff --git a/dag/std/markdown.dag b/dag/std/markdown.dag index bb12e34f254..23eaa8a06b9 100644 --- a/dag/std/markdown.dag +++ b/dag/std/markdown.dag @@ -3,7 +3,7 @@ module std.markdown import std.markup { Fragment, Element, - TextNode, + FragmentText, Attribute, AttributeContext, UrlContext, @@ -62,12 +62,12 @@ fn heading_level_to_tag(level: HeadingLevel) -> String { fn markdown_inline_to_fragments(inline: MarkdownInline) -> List { match inline { TextInline { text } => - [ TextNode { content: text } ] + [ FragmentText { content: text } ] CodeInline { text } => [ Element { tag: "code", attributes: [], - children: [ TextNode { content: text } ], + children: [ FragmentText { content: text } ], } ] EmphasisInline { inlines } => [ Element { @@ -128,7 +128,7 @@ fn markdown_task_item_to_fragment(item: MarkdownTaskItem) -> Fragment { tag: "li", attributes: [], children: concat( - [ TextNode { content: checkbox_label(state: item.state) } ], + [ FragmentText { content: checkbox_label(state: item.state) } ], markdown_blocks_to_fragments(blocks: item.blocks) ), } @@ -172,7 +172,7 @@ fn markdown_block_to_fragment(block: MarkdownBlock) -> Fragment { Element { tag: "code", attributes: [], - children: [ TextNode { content: code } ], + children: [ FragmentText { content: code } ], }, ], } diff --git a/dag/std/markup.dag b/dag/std/markup.dag index 2574a76ada8..ae0e89b2c5e 100644 --- a/dag/std/markup.dag +++ b/dag/std/markup.dag @@ -10,9 +10,11 @@ type Attribute { value: String } +data fragment_text_rename_marker: String = "Constructor-owner ruling (§1c) collision fix: Fragment's text arm was named TextNode, colliding with MarkupNode.TextNode in this module's own scope (markup_node_to_fragment even converted one to the other cross-owner via the deleted expected-type pick). Renamed to FragmentText — Fragment is the escaped render tree; MarkupNode (the larger consumer surface, ~15 files) keeps TextNode." + type Fragment = Element { tag: String, attributes: List, children: List } - | TextNode { content: String } + | FragmentText { content: String } | RawNode { content: String } fn escape_text(s: String) -> String { @@ -116,7 +118,7 @@ fn serialize_attributes_with(attrs: List, bracket: MarkupBracket) -> fn serialize_fragment_with(frag: Fragment, medium: MarkupMedium) -> String { match frag { - TextNode { content: c } => escape_text_for(mode: medium.text_escape, s: c) + FragmentText { content: c } => escape_text_for(mode: medium.text_escape, s: c) RawNode { content: c } => c Element { tag: t, attributes: attrs, children: kids } => { let attrs_str = serialize_attributes_with(attrs: attrs, bracket: medium.bracket) @@ -181,7 +183,7 @@ fn markup_attr_to_fragment(attr: MarkupAttr) -> Attribute { fn markup_node_to_fragment(node: MarkupNode) -> Fragment { match node { TextNode { text } => - TextNode { content: text } + FragmentText { content: text } ElementNode { tag, attrs, children } => Element { tag: tag, diff --git a/dag/test/claim/bmc_redfish_grounding_witness_test.dag b/dag/test/claim/bmc_redfish_grounding_witness_test.dag index 59e59d7bb3b..0f706cd315c 100644 --- a/dag/test/claim/bmc_redfish_grounding_witness_test.dag +++ b/dag/test/claim/bmc_redfish_grounding_witness_test.dag @@ -11,6 +11,7 @@ import std.measure { gibibyte_to_byte_size, memory_scale_factor_bytes, Mega, + Gibi, gibibyte_scale_factor_bytes, } import extdeps.bmc.types { diff --git a/dag/test/claim/diagnostics_test.dag b/dag/test/claim/diagnostics_test.dag index c782b485978..95f931e0720 100644 --- a/dag/test/claim/diagnostics_test.dag +++ b/dag/test/claim/diagnostics_test.dag @@ -5,6 +5,7 @@ import tools.diagnostics_witness_transport { run_diagnostics_reexport_surface_witness, run_diagnostics_type_and_arity_witness, run_diagnostics_empty_list_context_witness, + run_diagnostics_constructor_owner_witness, } import std.logic { Bool } @@ -25,3 +26,7 @@ test fn diagnostics_type_and_arity_holds() -> Bool { test fn diagnostics_empty_list_context_holds() -> Bool { run_diagnostics_empty_list_context_witness() } + +test fn diagnostics_constructor_owner_walls_hold() -> Bool { + run_diagnostics_constructor_owner_witness() +} diff --git a/dag/test/claim/fleet_converge_emit_test.dag b/dag/test/claim/fleet_converge_emit_test.dag index 7f68e5b759c..61dec297574 100644 --- a/dag/test/claim/fleet_converge_emit_test.dag +++ b/dag/test/claim/fleet_converge_emit_test.dag @@ -20,7 +20,7 @@ import gunbc.host_converge { ConvergePolicyDerived, ConvergePolicyUnsound, ConvergeVerdict, - Converged, + VerdictConverged, Drifted, Absent, ConvergeKnob, diff --git a/dag/test/claim/fleet_show_effective_read_witness_test.dag b/dag/test/claim/fleet_show_effective_read_witness_test.dag index 37d52e944f3..2402eceb36f 100644 --- a/dag/test/claim/fleet_show_effective_read_witness_test.dag +++ b/dag/test/claim/fleet_show_effective_read_witness_test.dag @@ -15,7 +15,7 @@ import extdeps.os.oomd { } import gunbc.host_converge { PerSlotMemoryCap, - Converged, + VerdictConverged, Drifted, gunbc_runner_per_slot_dropin_path, runner_instance_glob, @@ -127,7 +127,7 @@ fn witness_oom_fixture_converges_to_policy_percent() -> Bool { oom_pressure_limit_converge_verdict( read: read, desired: gunbc_oomd_sessions_policy.managed_oom_memory_pressure_limit - ) == Converged + ) == VerdictConverged Absent => false } } @@ -151,7 +151,7 @@ fn witness_srv2_slice_cap_converges_to_pool_budget() -> Bool { property: systemd_memory_max_property, expected: gunbc_runner_pool_budget, ) { - Present { value: Converged } => true + Present { value: VerdictConverged } => true _ => false } } @@ -176,7 +176,7 @@ fn witness_srv1_width_converges_to_declared_count() -> Bool { runner_width_converge_verdict( observed: read.active_count, desired: declared_runner_count() - ) == Converged + ) == VerdictConverged Absent => false } } diff --git a/dag/test/claim/hardware_selection_witness_test.dag b/dag/test/claim/hardware_selection_witness_test.dag index e312be53eec..daae8253b0e 100644 --- a/dag/test/claim/hardware_selection_witness_test.dag +++ b/dag/test/claim/hardware_selection_witness_test.dag @@ -4,8 +4,9 @@ import std.logic { Bool } import std.types { Int, List } import std.measure { byte_size, byte_size_count, bit_width, watt, watt_count, hardware_thread_count, hertz, money_amount_micro } import product.hardware_selection { milliwatt } -import extdeps.cpu.types { CpuModelCatalogRow, CpuSocket } -import extdeps.memory.types { DramModuleCatalogRow, dram_module_is_realizable, dram_module_required_die_density, dram_generation_ddr4 } +import extdeps.cpu.types { CpuModelCatalogRow, CpuSocket, SocketPackage } +import extdeps.toolchain.types { Architecture } +import extdeps.memory.types { DimmBuffering, DramModuleCatalogRow, dram_module_is_realizable, dram_module_required_die_density, dram_generation_ddr4 } import extdeps.vendor.sk_hynix { sk_hynix } import extdeps.vendor.ampere { ampere } import extdeps.memory.jedec { ddr4_generation_catalog, jedec_ddr_generation_catalog_row } @@ -16,6 +17,7 @@ import product.hardware_selection { CpuCandidate, Motherboard, PsuModel, + EfficiencyRating, SystemConfig, HardwareAxes, effective_data_rate_mts, diff --git a/dag/test/claim/jedec_ddr_generation_witness_test.dag b/dag/test/claim/jedec_ddr_generation_witness_test.dag index cd1b9a3abd8..8cf4fb9eaac 100644 --- a/dag/test/claim/jedec_ddr_generation_witness_test.dag +++ b/dag/test/claim/jedec_ddr_generation_witness_test.dag @@ -3,6 +3,7 @@ module test.claim.jedec_ddr_generation import std.logic { Bool } import std.measure { byte_size, byte_size_count, bit_width } import extdeps.memory.types { + DimmBuffering, DoubleDataRate, DramModuleCatalogRow, dram_module_is_realizable, diff --git a/dag/test/claim/markup_serializer_witness_test.dag b/dag/test/claim/markup_serializer_witness_test.dag index dca53b64779..8d19edae4dd 100644 --- a/dag/test/claim/markup_serializer_witness_test.dag +++ b/dag/test/claim/markup_serializer_witness_test.dag @@ -1,7 +1,7 @@ module test.claim.markup_serializer_witness import std.markup { - Fragment, Element, TextNode, + Fragment, Element, FragmentText, Attribute, AttributeContext, UrlContext, escape_text, escape_attribute, escape_url, unescape_text, unescape_attribute, unescape_url @@ -25,8 +25,8 @@ fn sample_fragment() -> Fragment { tag: "a", attributes: [ Attribute { name: "href", ctx: UrlContext, value: "/x?a=1&b=2" } ], children: [ - TextNode { content: "hi & \"q\"" }, - Element { tag: "span", attributes: [], children: [ TextNode { content: ">end" } ] } + FragmentText { content: "hi & \"q\"" }, + Element { tag: "span", attributes: [], children: [ FragmentText { content: ">end" } ] } ] } } @@ -40,7 +40,7 @@ fn html_consumer_button() -> Fragment { Element { tag: "button", attributes: [ Attribute { name: "class", ctx: AttributeContext, value: "primary" } ], - children: [ TextNode { content: "Go" } ] + children: [ FragmentText { content: "Go" } ] } } @@ -48,7 +48,7 @@ fn react_consumer_button() -> Fragment { Element { tag: "button", attributes: [ Attribute { name: "class", ctx: AttributeContext, value: "primary" } ], - children: [ TextNode { content: "Go" } ] + children: [ FragmentText { content: "Go" } ] } } diff --git a/dag/test/claim/os_install_deduction_witness_test.dag b/dag/test/claim/os_install_deduction_witness_test.dag index 9653ecdc7ef..c97fae41ef1 100644 --- a/dag/test/claim/os_install_deduction_witness_test.dag +++ b/dag/test/claim/os_install_deduction_witness_test.dag @@ -39,7 +39,7 @@ import gunbc.os_install_deduction { InstallerAwaitingStorage, KvmFirmwareIdleObserved, KvmSuggestsOsBooted, - OsInstalled, + RuntimeOsInstalled, runtime_verdict_from_kvm_attestation, } import gunbc.fleet_intent_network { operator_host_srv3 } @@ -239,7 +239,7 @@ test fn kvm_login_prompt_is_weak_not_os_installed() -> Bool { preflight: CompleteForUnattended, ) { KvmSuggestsOsBooted => true - OsInstalled => false + RuntimeOsInstalled => false _ => false } } diff --git a/dag/test/claim/python_pep440_version_witness_test.dag b/dag/test/claim/python_pep440_version_witness_test.dag index 9b7a502f1ef..9912c71ec0e 100644 --- a/dag/test/claim/python_pep440_version_witness_test.dag +++ b/dag/test/claim/python_pep440_version_witness_test.dag @@ -1,5 +1,6 @@ module test.claim.python_pep440_version_witness +import std.algebra { Ordering } import extdeps.version.pep440 { Pep440Alpha, Pep440PreRelease, diff --git a/dag/test/claim/reviewer_source_witness_test.dag b/dag/test/claim/reviewer_source_witness_test.dag index e42911ffcfe..ce6a018b358 100644 --- a/dag/test/claim/reviewer_source_witness_test.dag +++ b/dag/test/claim/reviewer_source_witness_test.dag @@ -1,6 +1,6 @@ module test.claim.reviewer_source_witness_test -import ctrl.review_verdict { reviewer_source } +import ctrl.review_verdict { LlmReviewProvider, Reviewer, reviewer_source } import std.process { ProcessExit, ExitSuccess, exit_failure } import std.types { NonEmptyStr } @@ -11,7 +11,7 @@ data witness_policy_string: NonEmptyStr = "policy" fn witness_agent_claude_is_llm() -> Bool { match reviewer_source(reviewer: AgentReviewer { provider: Claude }) { - Llm => true + LlmSource => true _ => false } } diff --git a/dag/test/claim/runner_unit_live_read_witness_test.dag b/dag/test/claim/runner_unit_live_read_witness_test.dag index 554ba81d65d..c99129e6ad6 100644 --- a/dag/test/claim/runner_unit_live_read_witness_test.dag +++ b/dag/test/claim/runner_unit_live_read_witness_test.dag @@ -16,7 +16,7 @@ import gunbc.host_converge { ConvergeTarget, PerSlotMemoryCap, ConvergeVerdict, - Converged, + VerdictConverged, Drifted, Absent, gunbc_runner_per_slot_dropin_path, @@ -140,7 +140,7 @@ fn witness_fixture_swap_max_converges_to_zero() -> Bool { fn witness_matching_read_converges_on_memory_max_target() -> Bool { let read = runner_unit_live_read_typed(row: matching_memory_max_row()) match converge_target_live_verdict(read: read, target: fixture_memory_max_target()) { - Present { value: Converged } => true + Present { value: VerdictConverged } => true _ => false } } @@ -162,7 +162,7 @@ fn witness_target_bytes_value_is_single_authority() -> Bool { bytes_value: "30000000000", } match converge_target_live_verdict(read: read, target: target) { - Present { value: Converged } => true + Present { value: VerdictConverged } => true _ => false } } diff --git a/dag/test/claim/srv3_os_install_diagnostic_witness_test.dag b/dag/test/claim/srv3_os_install_diagnostic_witness_test.dag index cdf064c95a5..ffc5cd43f83 100644 --- a/dag/test/claim/srv3_os_install_diagnostic_witness_test.dag +++ b/dag/test/claim/srv3_os_install_diagnostic_witness_test.dag @@ -34,13 +34,13 @@ import gunbc.srv3_os_install_diagnostic { IsoHashMismatch, BootProgressNone, BootProgressOsRunning, - ReadyToBoot, + ReadyToBootVerdict, VirtualMediaServeDown, BootOverrideConsumedNoInstall, BootAttemptInProgress, AutoinstallRunning, - InstallerHung, - OsInstalled, + InstallerHungVerdict, + OsInstalledVerdict, OnlyBmcLeasePresent, } import gunbc.srv3_seeded_install_media { srv3_seeded_install_media_content_hash } @@ -238,7 +238,7 @@ test fn router_srv3_dynamic_lease_is_os_installed() -> Bool { }, ) install_diagnostic_verdict_is_os_installed(verdict: verdict) - && verdict == OsInstalled + && verdict == OsInstalledVerdict } test fn kvm_frozen_alone_does_not_dominate_router_success() -> Bool { @@ -279,7 +279,7 @@ test fn kvm_frozen_alone_does_not_dominate_router_success() -> Bool { elapsed_since_boot_once_ms: 0, }, ) - verdict == OsInstalled + verdict == OsInstalledVerdict } test fn sol_silent_past_timeout_is_hung_or_inconclusive_not_success() -> Bool { @@ -304,7 +304,7 @@ test fn sol_silent_past_timeout_is_hung_or_inconclusive_not_success() -> Bool { router_table: srv3_pre_install_lease_table, ), ) - hung == InstallerHung + hung == InstallerHungVerdict && !install_diagnostic_verdict_is_os_installed(verdict: hung) } @@ -314,7 +314,7 @@ test fn serve_ready_pre_boot_is_ready_to_boot() -> Bool { observations: serve_ready_sol_silent_pre_boot_observations(), ) install_diagnostic_verdict_is_ready_to_boot(verdict: verdict) - && verdict == ReadyToBoot + && verdict == ReadyToBootVerdict } test fn boot_once_armed_is_boot_attempt_in_progress() -> Bool { diff --git a/dag/test/claim/storage_nvme_grounding_witness_test.dag b/dag/test/claim/storage_nvme_grounding_witness_test.dag index 2533cc4d090..036abd1dd07 100644 --- a/dag/test/claim/storage_nvme_grounding_witness_test.dag +++ b/dag/test/claim/storage_nvme_grounding_witness_test.dag @@ -4,6 +4,7 @@ import std.logic { Bool } import std.measure { byte_size_count, bandwidth_count } import extdeps.storage.types { NvmeDeviceCatalogRow, + PcieGeneration, pcie_link_bandwidth, pcie_generation_per_lane_bandwidth, nvme_storage_device, diff --git a/dag/test/claim/transport_fidelity_witness_test.dag b/dag/test/claim/transport_fidelity_witness_test.dag index 33e19e51ef1..e48b7167ebe 100644 --- a/dag/test/claim/transport_fidelity_witness_test.dag +++ b/dag/test/claim/transport_fidelity_witness_test.dag @@ -1,6 +1,7 @@ module test.claim.transport_fidelity import std.fidelity { TransportClass, TestClass, DerivedClassification } +import std.types { FermiDepth } import std.fermi { fermi_eq } import std.logic { Bool } import extdeps.transports.fidelity { diff --git a/dag/test/claim/uri_path_parse_witness_test.dag b/dag/test/claim/uri_path_parse_witness_test.dag index cdc5bba9f96..dbeab939872 100644 --- a/dag/test/claim/uri_path_parse_witness_test.dag +++ b/dag/test/claim/uri_path_parse_witness_test.dag @@ -7,6 +7,7 @@ import extdeps.uri_path { } import std.http_path { PathTemplate, + UrlPathToken, has_path_params, last_path_param, PathParamBinding, diff --git a/dag/test/claim/variant_owner_expected_type_test.dag b/dag/test/claim/variant_owner_expected_type_test.dag deleted file mode 100644 index c64dba8f530..00000000000 --- a/dag/test/claim/variant_owner_expected_type_test.dag +++ /dev/null @@ -1,28 +0,0 @@ -module test.claim.variant_owner_expected_type - -import std.logic { Bool } - -type AEarlyOwner = SharedVariant | AOnlyVariant -type ZLaterOwner = SharedVariant | ZOnlyVariant - -fn make_z_variant() -> ZLaterOwner { SharedVariant } - -fn make_a_variant() -> AEarlyOwner { SharedVariant } - -fn is_shared_in_z(v: ZLaterOwner) -> Bool { - match v { - SharedVariant => true - ZOnlyVariant => false - } -} - -fn is_shared_in_a(v: AEarlyOwner) -> Bool { - match v { - SharedVariant => true - AOnlyVariant => false - } -} - -test fn expected_type_picks_variant_owner_not_alpha_order() -> Bool { - is_shared_in_z(v: make_z_variant()) && is_shared_in_a(v: make_a_variant()) -} diff --git a/dag/tools/diagnostics_witness_transport.dag b/dag/tools/diagnostics_witness_transport.dag index 752018d6ac5..91d7efe772c 100644 --- a/dag/tools/diagnostics_witness_transport.dag +++ b/dag/tools/diagnostics_witness_transport.dag @@ -14,6 +14,7 @@ data diagnostics_import_resolution_suite: String = "import_resolution" data diagnostics_reexport_surface_suite: String = "reexport_surface" data diagnostics_type_and_arity_suite: String = "type_and_arity" data diagnostics_empty_list_context_suite: String = "empty_list_context" +data diagnostics_constructor_owner_suite: String = "constructor_owner" fn diagnostics_witness_program(suite: String) -> ShellProgram { let root = var_ref(name: "ROOT") @@ -54,3 +55,8 @@ fn run_diagnostics_empty_list_context_witness() -> Bool { let result = shell.Exec.Run(script: serialize_bash(p: diagnostics_witness_program(suite: diagnostics_empty_list_context_suite))) result.success } + +fn run_diagnostics_constructor_owner_witness() -> Bool { + let result = shell.Exec.Run(script: serialize_bash(p: diagnostics_witness_program(suite: diagnostics_constructor_owner_suite))) + result.success +} diff --git a/docs/plans/compiler-algorithm-survey-2026-07-04.md b/docs/plans/compiler-algorithm-survey-2026-07-04.md new file mode 100644 index 00000000000..05ec9a774a3 --- /dev/null +++ b/docs/plans/compiler-algorithm-survey-2026-07-04.md @@ -0,0 +1,359 @@ +# Compiler algorithm survey — raw stage catalogs (2026-07-04) + +**Provenance:** eight parallel read-only survey agents, one per stage group, each instructed with the resolver reference lesson (request-major redundancy + per-unit whole-env scan) and asked to catalog every pass with unit, asymptotic class in named scaling variables, memoization story, and a verdict (OPTIMAL / ACCEPTABLE / SUSPICIOUS / PATHOLOGICAL), citing file:line anchors. Run 2026-07-04 on branch `claude/ci-witness-opt-in-10h2he` at the post-engine-PR state (constructor-owner ruling landed, suite 656/0/85 green). + +**Status of claims:** these are READ-verdicts — asymptotic classes argued from code with anchors, not yet measured. The four highest-leverage claims were spot-verified against the live tree by the synthesizer (see the roadmap's Survey results section). Every PATHOLOGICAL row still owes a corpus-scale measurement before its fix lands (roadmap §B method, step 3). + +**Synthesis:** `docs/plans/post-engine-pr-roadmap.md` § Survey results. This file is the raw receipt. + +--- + +## Parse / lex (src/v1/02_parse.dag + parse-table memo) + +I now have the complete picture. Here is the audit catalog. + +--- + +# gunbc parser audit — `src/v1/02_parse.dag` + parse-table memo machinery + +**Scaling variables:** `N` = files/modules per run · `M` = tokens per module (∝ file size) · `A` = operators/keywords in the grammar spec (fixed constant) · `k` = lookahead span of a disambiguation scan. + +**Headline:** the v1 parser is a clean recursive-descent + Pratt parser with *no* exponential backtracking — but it accesses its token stream through `skip(list, pos) |> first`, and the interpreter's `skip` **materializes the whole tail of the token list on every access**. That turns every peek into O(M) and the whole per-file parse into **O(M²)**. This is the classic "cursor over a list, re-scanned from the head each step" pathology, and it is almost certainly the "legacy CYK floor ≈ 63 s" cost the memo tests benchmark against. The cross-file *redundancy* axis, by contrast, is already correct: each file parses exactly once per run. + +## Catalog + +| Pass / algorithm | Anchor | Computes / over what unit | Asymptotic | Memo / sharing | Verdict | Fix direction | +|---|---|---|---|---|---|---| +| **TokenStream random access** (`token_stream_first` / `peek` / `advance`) | `02_parse.dag:79,82,86` → `skip` builtin `v1_interpreter.rs:3187`, `first` `:3172` | Reads token at current `pos`; `skip(all,pos) |> first` copies the entire `M−pos` tail into a fresh RRB vector then discards all but the front. Per token access. | **O(M−pos) per access → O(M²) per module** (time + allocation) | None. Recomputed from the head on *every* peek; `pos` is just an int carried in `TokenStream{all,pos}`. | **PATHOLOGICAL** | Index directly: `all.get(pos)` (RRB get is O(log M)), or carry a real cursor. Drops parse to O(M log M). | +| **Parse driver** (recursive-descent + Pratt): `parse_module`/`parse_items_acc`/`parse_expr_bp`/`parse_expr_loop` | `02_parse.dag:856, 916, 3254, 3261` | Walks tokens once, O(M) parse *steps*, no re-parse of alternatives (Pratt binding-power, commits as it goes). | O(M) steps — but each step ≥1 token access → **inherits O(M²)** | Structure is fine; it's the *victim* of the access cost above. | ACCEPTABLE structurally / PATHOLOGICAL in practice via access | Fix the accessor; driver needs no change. | +| **`scan_for_fat_arrow_after_braces`** (lambda-vs-record lookahead) | `02_parse.dag:3983`, called `:3957` | Scans forward token-by-token tracking brace depth to disambiguate. Per ambiguous `{`-construct. | O(k) steps × O(M−pos)/step → **up to O(M²) per site**, ×(sites/module) | None | **SUSPICIOUS→PATHOLOGICAL** (same root cause, compounds) | Same accessor fix makes each scan O(k). | +| **`infix_bp` / `find_operator_bp` / `find_operator_binop`** | `02_parse.dag:3326, 3316, 3333` | `filter`+`count`+`first` over `dag_syntax_spec.operators` per operator token to get binding power / BinOp. Per operator token. | O(A) per token, rebuilds a filtered list each call → O(A·M)/module, A constant | Recomputed per use-site; operator table is derivable-once & constant | SUSPICIOUS (minor; A small) | Precompute a `symbol → {bp, binop}` map once at grammar load; O(1) lookup. | +| **Item / import accumulation** (`parse_items_acc`, `parse_imports_acc`) | `02_parse.dag:916, 901`; `list_push` `v1_interpreter.rs:3067` over `List = Rc` `:331` | `list_push(acc, item)` per item. | O(log items) per push (RRB clone O(1) + push_back O(log n)) → O(items·log items) | — | **ACCEPTABLE** (not the quadratic it would be on a `Vec`; RRB saves it) | none | +| **Per-file parse-cache** (`MultiEntryIndex.parse_cache`) | `cli_run.rs:954` field, `:1091` lookup, `:1105` parse, `:1108` insert; shared index `process_shared_index:902` | Parse result keyed by `source.path`; index is process-shared per `(thread, source_roots)`. | Each file parsed **once per run** regardless of how many entries include it | Memoized per path, shared across all entries of the union closure | **OPTIMAL** (redundancy axis handled — graph-major already) | none | +| **Cold front-end** (`front_end_sources`) | `v1_compiler_compile.rs:2217`, parse `:2246` | Pre-interns all files then folds parse over them; each file parsed once. | O(Σ M) — once per file, no re-parse | one-shot (no cross-call cache, but no redundancy either) | ACCEPTABLE | none | +| **ParseTable packrat memo intrinsic** (`parse_table_lookup`/`insert`) — the "parse-table memo machinery" | `v1_interpreter.rs:2492` dispatch, `:2460` key, `:650` struct | Memoizes parse result keyed `(grammar_digest, token_stream_digest, position, production)`. Used by the **v2** parser, *not* `02_parse.dag`. | Turns re-parse of identical (stream, position, production) into O(1) → linearizes v2 parse; amortizes across repeat ingest | Correct packrat scoping (per grammar × token stream) | **OPTIMAL** design | Minor: `keepalive` Vec (`:652`) pushes 3 clones per insert, never cleared — memory grows with memo size, not a time bug. | + +## Three worst offenders (evidence) + +1. **TokenStream access is O(M) per peek → O(M²) per module.** `src/v1/02_parse.dag:79` `token_stream_first = skip(xs: stream.all, n: stream.pos) |> first` and `:87` `token_stream_peek = skip(..., pos+offset) |> first`. The `skip` builtin at `src/v1/stage0/src/v1_interpreter.rs:3187` is `items.iter().skip(n).cloned().collect::>()` — it clones and reallocates the entire `M−pos`-element tail on every call, then `first` (`:3172`) throws all but the head away. There are **263** `token_stream_*` call sites in the parser (grep count) and the driver takes O(M) steps, so total ≈ Σ_pos O(M−pos) = **O(M²)** time *and* allocation per file. This is the dominant per-unit pathology and the direct analogue of the reference lesson's flatten+scan-per-literal. Fix: index by position (`all.get(pos)`, RRB O(log M)) → O(M log M). + +2. **`scan_for_fat_arrow_after_braces` compounds the same cost.** `src/v1/02_parse.dag:3983` disambiguates lambda-vs-record by walking the stream one `token_stream_advance`+`first` at a time — each hop is O(M−pos) because of offender #1, so a single k-token lookahead is O(k·M), and it fires once per ambiguous `{`. Same fix eliminates it (each hop → O(log M)). + +3. **`find_operator_bp` / `find_operator_binop` / `infix_bp` re-filter the operator spec per token.** `src/v1/02_parse.dag:3316/3333/3326` run `ops |> filter(...) |> count/first` over `dag_syntax_spec.operators` for every operator token — a derivable-once, constant-size fact re-derived at each use site (O(A) per token, allocating a filtered list each time). Minor because A is a small grammar constant, but it's the textbook "per-use-site recomputation of a precompute-once index." Fix: build a `symbol → {bp, binop}` map once when the grammar spec loads. + +**Redundancy axis is clean:** `parse_cache` (`cli_run.rs:954`, keyed by `source.path`) plus the process-shared `MultiEntryIndex` (`process_shared_index`, `cli_run.rs:902`) guarantee each file is parsed once per run across the whole union closure — no request-major re-parse. The v2 `ParseTable` packrat memo (`v1_interpreter.rs:2492`, keyed by grammar+stream+position+production) is a correct linearizing design. So the only real problem here is the **per-module O(M²) token accessor** (offenders #1/#2); everything else is optimal or a small constant-factor cleanup. + +--- + +## Resolve (src/v1/03_resolve.dag + 04_resolve.dag) + +I have everything needed. Both files are fully analyzed, the map primitive costs are confirmed (`map_merge` = `base.extend(overlay)` → copies overlay; `map_values`/`map_keys` = full clone-to-Vec), the `TypeEnv` shape is confirmed (local `str_bindings` + `parents: List` + `ancestry_str_bindings`), and the resolve pass is confirmed graph-major (once per item via `analyze_item`). + +--- + +# Efficiency Audit — `src/v1/03_resolve.dag` & `src/v1/04_resolve.dag` + +**Scaling variables:** N = modules · M = nodes/item-size per module · E = visible-env binding count (local + ancestry + all parents) · P = parent-env count (≈ import depth D) · A = arms/variants/fields per type · Eg = import edges · U = use-sites of a given generic/alias · K = kernel-type-set size (constant). + +**Confirmed primitive costs** (`stage0/src/v1_rt.rs`): `map_get` O(1)+val-clone · `map_insert` O(1) (owned) · `map_merge(base,overlay)` = clone-base + extend → **O(|base|+|overlay|)** · `map_values`/`map_keys` → **O(|map|)** clone-to-Vec. `authored_name_at` = map_get + substring (cheap, non-scaling but very hot). + +## Catalog + +| Pass (anchor) | Computes / unit | Asymptotic | Memoization | Verdict | +|---|---|---|---|---| +| `resolve_modules` orchestrator `03:41` | Prebuilds `module_index` + `export_sets` once, resolves each module vs. index; single fold over N | O(ΣM) = O(corpus) | index & export_sets built **once**, passed in | **OPTIMAL** — correct graph-major shape (the *fixed* form of the lesson's request-major resolver) | +| `export_sets` build `03:49` / `get_exported_names` `03:177` | Export name-set per module | O(ΣM + N·K) | per module once; but `map_keys(kernel_type_set)` re-materialized every module (`03:183`) | **ACCEPTABLE** (hoist the constant kernel keys) | +| `resolve_import` `03:130` | Import target + missing-export check / per import | O(imports·names), each lookup O(1) | vs. prebuilt index | **OPTIMAL** | +| `check_duplicate_modules` `03:201` | Dup detection / per module | O(N) map-dedup | — | **OPTIMAL** | +| `topological_sort` + `kahn_drain` `03:241/302` | Kahn topo-order / per module+edge | O((N+Eg)·log N); rebuilds in-degree by re-filtering imports, re-scans queue neighbors 2× per layer | recomputed each call | **ACCEPTABLE** (2× edge-scan + unused `fuel` guard are constant slop) | +| `resolve_item_types` `04:948` | Full item resolve (params/ret/uses/body/anno/children) / per item | O(item nodes)×per-node cost | **once per item** (`analyze_item` `04_infer:5894`) | **ACCEPTABLE**, but resolves `item.inferred` structure *and* re-resolves authored children `04:1023-1050` → same coproduct/product walked twice (SUSPICIOUS sub-part) | +| `resolve_expr_types` `04:778` | Expr-tree type-attach / per expr node | O(expr size), 1 visit/node | structural, no shared substructure | **OPTIMAL** | +| `resolve_node_bounded` `04:369` | Type-expr resolution, depth-capped at 100 (`04:370`) | per-node O(1) **except** generic/alias branch | none at use-site | **SUSPICIOUS→PATHOLOGICAL** (see #1,#2,#3) | +| `collect_unit_variant_phantom_matches` `04:81` (via `lookup_unit_variant_phantom_type` `04:69`) | Nullary-variant-as-type fallback / **per unresolved leaf** | **O(E·(P+A))** per call | **none** — flattens whole visible env every call | **PATHOLOGICAL** | +| `resolve_generic_use_decl` `04:233` | Which decl a generic name binds / per use-site | O(D) ancestry fold (shadow-retry), else O(1) | none; recomputed 2-3×/node | **SUSPICIOUS** | +| `substitute_type_slots` `04:279` | Slot→arg substitution over decl body / per generic use-site | O(decl-body size) full tree rebuild | **none** across use-sites | **SUSPICIOUS** (part of #2) | +| `resolve_nominal_alias_rhs` `04:348` | Parametric-alias RHS resolve / per alias decl | **O(2^nesting)** — recurses children *and* calls `resolve_node(n)` which re-descends same children (`04:350`+`04:353`) | none | **SUSPICIOUS** (exponential in type-nesting depth; small in practice, bounded by alias count) | +| `missing_generic_args_diagnostics` `04:586` | Arity diag / per field & param | re-invokes `is_user_generic_use_site`→`resolve_generic_use_decl` | recomputed | **SUSPICIOUS** (folds into #3) | +| `peel_nominal_alias_identity` `04:142` | Nominal-brand preservation / per alias identity | up to 3× `resolve_node_bounded` on resolved/target (`04:150-163`) | none | **ACCEPTABLE** (bounded fan-out, minor) | + +## The 3 worst offenders + +**1. `collect_unit_variant_phantom_matches` — `04_resolve.dag:81` (PATHOLOGICAL).** +This is the reference lesson's per-unit pathology, verbatim. Every unresolved leaf that reaches the fallback at `04:569` runs: +``` +let direct_bindings = fold(env.parents, init: env.str_bindings, f: (acc, parent) => + map_merge(parent.str_bindings, acc)) // O(P·E): clones every parent's str_bindings +fold(direct_bindings |> map_values, init: [], ... unit_variant_in_coproduct(...)) // O(E) scan × O(A) variant-fold each +``` +`map_merge` clones the base map each fold step (`v1_rt.rs:213` `base.extend`), so the flatten is O(P·E); then `map_values` materializes all E bindings and each is folded over its A variants → **O(E·(P+A)) per lookup**. Fires per nullary-variant-as-type use-site (and per genuinely-unresolved name before it errors) → literals × env-size × variant-expansion, matching the measured 60-min blowup shape. **Fix:** build a `Map>` index once per env (invert the visible set), or memoize the flattened visible-binding set on the env; the lookup becomes O(1)/O(#matches). + +**2. Use-site generic & parameterized-alias re-expansion — `04_resolve.dag:461-502` + `substitute_type_slots:279` (REDUNDANCY).** +Every occurrence of a generic type or parameterized alias re-runs the full `substitute_type_slots` tree-rebuild over the decl body (`04:483`/`04:492-494`) and then re-resolves the expanded target via `resolve_node_bounded(depth+1)` (`04:486`). Nothing is keyed on the instantiation, so `Optional` appearing at U field/param sites does U full expansions + U re-resolutions of an identical result. This is the "unit of caching is wrong" axis at the type level. **Fix:** memoize resolved expansion on `(decl identity, resolved arg-type identities)` — a per-env instantiation cache — so each distinct instantiation expands once. + +**3. Redundant `resolve_generic_use_decl` / `is_user_generic_use_site` recomputation — `04_resolve.dag:461→463`, `270`, `586` (REDUNDANCY / derivable-once fact).** +For a generic use-site with args, `resolve_node_bounded` calls `is_user_generic_use_site` (`04:461`, which internally calls `resolve_generic_use_decl`) and then immediately calls `resolve_generic_use_decl` **again** at `04:463`; the same classification is recomputed a third time per field/param inside `missing_generic_args_diagnostics` (`04:587`→`resolve_field:616`/`resolve_param:650`/item-child loops `04:1027,1038`). Each call is an O(D=P) ancestry `fold(env.parents …)` in the kernel-shadow retry case (`04:239-246`). The decl a name binds to is a derivable-once fact per node. **Fix:** compute the decl/classification once per node and thread it through the arity-diag + expansion paths (hoist out of `missing_generic_args_diagnostics`). + +## Note on the assigned specifics +- **`resolve_type_node` recursion depth:** bounded — `resolve_node_bounded` hard-caps at `depth > 100` (`04:370`) emitting an `InternalError`; every structural descent passes `depth + 1`. Safe against runaway recursion; the cost risk is *fan-out per node* (offenders #1-#3), not depth. +- **Per-use-site re-resolution of the same type reference:** confirmed present — offenders #2 and #3 both re-derive the same instantiation/decl per use-site with no use-site cache. The env's stored `resolved` bindings are reused, but use-site *expansion* is not. +- **`03_resolve.dag` is clean:** it is already graph-major (build-index-once, resolve-each-once) — the corrected form of the lesson's quadratic request-major resolver. Only constant-factor slop (kernel-keys re-materialized per module; 2× edge-scan in Kahn; unused `fuel` guard). + +--- + +## Infer — env construction (04_env.dag + env/rewire passes of 04_infer.dag) + +Confirmed the picture. `flatten_visible_bindings`/`merge_envs` are cold in the `.dag` corpus (only reference each other; `merge_envs` is imported but not invoked on the hot path), matching the `flatten_visible_parent_recurses == 0` witness. The per-module resolver `resolve_env_bindings` (6290) does not call flatten. Here is the catalog. + +--- + +# Efficiency Audit — `04_env.dag` + env/rewire passes in `04_infer.dag` + +Scaling vars: **N** = modules (~89) · **M** = local items/nodes per module · **B** = local type bindings per module · **C** = transitive *visible-name closure* size per module (grows with import depth **D**) · **I** = direct imports per module · **E** = local type-dep edges. + +## Outer schedule (context) +`typecheck_modules` (04_infer.dag:6510) is already **graph-major**: topological single pass, `module_index` accumulates typed modules, `parent_index` is passed by reference. Each module is typechecked once and parent `TypeEnv`s are shared by reference. So the resolver's *request-major* redundancy is **not** re-introduced at the outer loop — the cost lives in per-module cache materialization and the corpus-wide rewire pass. + +## Catalog + +| Pass / algo | Anchor | Unit / what it computes | Asymptotic | Memo / sharing | Verdict | Fix direction | +|---|---|---|---|---|---|---| +| **rewire import str-binding identity** | 04_infer.dag:6613 (`rewire_type_env_import_str_binding_identity`) → :6593 `rewire_inherited_str_binding` | per **inherited name** per **module**: rewrites each visible binding to its canonical exporter | **Θ(N²·C·B)** — per name it calls `global_type_exporter_count` (6556, scans all N modules × B bindings) **and** `local_authority_for_name` (6562, same whole-corpus scan) | index built once per pass (6614) but exporter/authority lookups recomputed from scratch **per (module,name)** | **PATHOLOGICAL** | precompute one `name → {exporter modules, canonical binding}` index in a single O(corpus) pass; each rewire becomes O(1) | +| **per-module cache = full closure** | 04_infer.dag:5689 `union_parent_type_env_caches` (:5491) + cache built :5743; `all_deps_map` :5703 | per **module**: materializes the *entire transitive import closure* deps_map + str_bindings + cycle_set_str as a fresh map | union fold over I imports × `merge_type_env_cache` (each 4× `map_merge` of C-sized maps) → **Θ(I·C)/module**; corpus **Θ(N·C) … Θ(N²·M)** by import shape | **nothing shared by reference** — `merge_type_env_cache` (04_env.dag:53) clones via `map_merge`; every module holds its own O(C) copy of the shared core | **SUSPICIOUS→PATHOLOGICAL** | store only the **local delta** per module + resolve through parent-cache chain (references), not a re-materialized closure; or intern one shared closure map | +| **flatten_visible_bindings** | 04_env.dag:217 | whole **parent DAG**: flatten all visible bindings | un-memoized recursion: re-flattens each shared parent **once per path** → exponential on diamond imports; each step whole-map `map_merge` | **no memo** on env identity | **SUSPICIOUS (latent/cold)** | currently avoided on hot path by precomputed `ancestry_str_bindings` (witness `flatten_visible_parent_recurses==0`); if re-enabled, memoize per-env | +| **merge_envs** | 04_env.dag:225 | one-shot merge of a list of envs | Σ flatten (calls the exponential fn) + `map_merge` + re-intern every merged binding (:237) → O(total bindings) atop flatten's blowup | none; rebuilds `bindings_by_ident` from scratch | **SUSPICIOUS (latent/cold)** — imported (04_infer:86) but not invoked on hot path | if used, drop the flatten dependency / memoize | +| **merge_type_env_cache** | 04_env.dag:53 | primitive: 4× `map_merge` | O(\|base\|+\|overlay\|)=O(C) per call | fresh map each call (correct for a primitive) | **ACCEPTABLE** (primitive) — the *caller* (union loop) is the problem | — | +| **detect_type_cycles_kahn** | 04_cycle.dag:99 (via :5704) | per **module**, over **local** names only (`bindings: local_str_bindings`, C-sized `deps_map` only `map_get`-probed) | Kahn O((V+E)·log) with V,E = local; corpus Σ O(M log M) | recomputed per module (correct — local fact) | **ACCEPTABLE** — correctly per-unit-bounded, not closure-wide | — | +| **rewire parent links** | 04_infer.dag:6682 / 6732 | per module: rebuild `parents` / func parents from import index | O(N) index + Σ O(I); `shared_kernel` found once & shared by ref | parent envs shared by reference (good); `type_env_for_import` only re-filters for `std.types` importers | **ACCEPTABLE** | (minor: cache the `std.types` filtered env once) | +| **build_type_env local scaffolding** | 04_infer.dag:5588–5714 | per module: kernel seed + local bindings + inductive fields | kernel re-seeded **every module** (5595–5619) ≈ O(kernel) const; local folds O(M) | kernel bindings rebuilt per module, not shared | **ACCEPTABLE** (bounded const) — but redundant; hoist kernel env once | build kernel `TypeEnv` once (cf. `compiler_kernel_type_env` at :6655) and share | + +## Three worst offenders (evidence) + +**1. `rewire_type_env_import_str_binding_identity` — PATHOLOGICAL, Θ(N²·C·B).** 04_infer.dag:6617 maps over all N modules; 6622 folds over `inherited_keys` (the module's whole visible closure, ~C names); each name enters `rewire_inherited_str_binding` (6593) which calls **`global_type_exporter_count(modules, name)`** (6556: `filter(modules, …)` over all N modules, each `module_exports_type_name` filtering B bindings) **and** **`local_authority_for_name(modules, name)`** (6562: another all-N-modules × B-bindings scan). This is the reference "whole-corpus scan per literal" pathology, one level up: whole-corpus scan **per visible name per module**. A single global `name→exporters` index (one O(corpus) pass) collapses it to O(N·C + corpus). + +**2. Per-module full-closure `TypeEnvCache` — SUSPICIOUS→PATHOLOGICAL (the KEY QUESTION).** Answer: **yes, whole-map merges are recomputed per module and nothing is shared by reference.** `union_parent_type_env_caches` (5491) folds `merge_type_env_cache(acc, parent.type_env_cache)` over imports; each parent cache *already holds its full transitive closure* (built identically at 5743 with `deps_map: all_deps_map`, `str_bindings: visible_str_bindings`), and `merge_type_env_cache` (04_env.dag:53) `map_merge`-clones rather than referencing. So every module re-materializes an O(C) copy of the shared stdlib core: Θ(I·C) per module, and N redundant copies of the common closure across the corpus (Θ(N·C), degrading toward Θ(N²·M) on deep chains). The `TypeEnv.parents` list *is* shared by reference (5628/5736) — good — but the parallel `TypeEnvCache` (deps_map/str_bindings/cycle_set_str) is fully re-cloned. Fix: keep only the local delta and resolve through the parent chain. + +**3. `flatten_visible_bindings` — SUSPICIOUS (latent).** 04_env.dag:217–223: recursion over `env.parents` with `map_merge(flatten_visible_bindings(parent), acc)` and no memo — on a diamond import DAG the same parent subtree is re-flattened once per path (exponential), each step merging whole maps. Currently **cold**: build_type_env precomputes `ancestry_str_bindings` and `lookup_binding_by_name` is fail-closed with no flatten fallback (witness `flatten_visible_parent_recurses == 0`, 04_env.dag:36); only `merge_envs` (itself uninvoked on the hot path) routes through it. It is a loaded gun, not a firing one — memoize per-env identity before any hot-path use. + +Note: the emit-stage `--target dag` slowdown is not in these files — this scope is the typecheck/`reconcile` path (04_infer.dag:6755). Offender #1 fires during `reconcile` on every full-corpus compile. + +--- + +## Infer — core (04_infer.dag inference passes) + +I have enough evidence across all assigned passes. Here is the catalog. + +--- + +# Efficiency audit — `src/v1/04_infer.dag` (inference core) + +**Scaling variables:** N = #modules · M = module size (items/nodes) · E = visible-env bindings · A = arms/fields per match/record · K = imported names per import · D = import/alias-chain depth · T = type-node subtree size. + +**Baseline established (kills the reference-lesson's first pathology):** name lookups here are O(1) HashMap gets, not env scans. `lookup_type_by_name`→`lookup_binding_by_name` (`src/v1/04_env.dag:79`) is `str_bindings` → `ancestry_str_bindings` → `intern+bindings`, all `map_get`; the `flatten_visible_bindings` fallback was reverted (note at `04_env.dag:36`). Constructor-owner lookup is now the O(1) binding-edge (`variant_owner_node`, authority note `04_infer.dag:490`). So the *per-literal whole-env flatten+scan* the lesson describes is already gone from this file. The remaining offenders are (a) a graph-major redundancy in import-variant binding and (b) accidental-quadratic / latent-exponential inner loops. + +## Catalog + +| Pass / anchor | Computes over unit | Asymptotic | Memo/sharing | Verdict | Fix direction | +|---|---|---|---|---|---| +| `infer_expr` dispatch `04_infer.dag:1338` | match on `expr_data`, one visit per expr node; post-order recursion | O(1)/node dispatch → O(nodes) per body | none needed (tree walk) | **OPTIMAL** as dispatch | — (but see match branch) | +| `ExprMatch` two-pass arm re-inference `04_infer.dag:1983` (pass1) + `2023-2063` (pass2) | per match, per arm | pass2 does `arm_infer_results \|> enumerate \|> filter(ap==idx)` per arm ⇒ **O(A²)** positional index (`:2027`); re-inference recurses into nested matches ⇒ worst-case **2^(match-nesting depth)** | none — arm bodies re-`infer_expr`'d, `annotate_pattern_parent_enums`+`extend_scope_with_pattern_node` re-run per arm (`:2036-2037`) | **SUSPICIOUS** (accidental-quadratic + latent exponential, gated by "type resolves only via outer expected") | zip the two parallel lists instead of enumerate+filter (A²→A); unify once / memoize node inference instead of full re-infer | +| `infer_record_lit` ladder `04_infer.dag:2863` | per record-literal use-site | field template re-derived every use-site via `record_lit_instantiated_fields`/`_expected_fields`/`_alias_struct_fields`; per field-init `struct_fields \|> filter \|> first` ⇒ **O(A²)** (`:2912`); `field_in_any_variant_named` walks all arms × `expand_type_for_field_access` per field-init in coproduct-construction case (`:2841-2851`, `:2905`) | none — per-decl field list recomputed per literal | **SUSPICIOUS** (A² + per-use-site rederivation of a per-decl fact) | index `struct_fields` by name into a map; memoize field template per type decl | +| `record_lit_instantiated_fields` `04_infer.dag:607` | per generic record use-site | fold over `decl.params` + map over template fields, each `substitute_generics` O(T) | none (args differ per use-site — inherent) | **ACCEPTABLE** | — | +| `expand_type_for_field_access` / `expand_alias_chain_for_field_access` `04_infer.dag:2807` / `2752` | per field-access base type | alias-chain walk bounded by `seen` set ⇒ O(D_alias); per hop O(1) lookup + `substitute_generics` O(T) | `seen` map prevents cycles; recomputed per field-access use-site | **ACCEPTABLE** (chain depth small; parallel to `04_resolve` — flagged for dissolution at `:2796`) | — | +| `substitute_generics(_apply)` `04_infer.dag:5405` | per type subtree | O(T), with `rc_ptr_eq` short-circuit to share unchanged subtrees | structural sharing via ptr-eq | **OPTIMAL** | — | +| `annotate_pattern_parent_enums` `04_infer.dag:1030` | per arm pattern, recursive over field bindings | per binding: `resolve_pattern_subject`+`lookup_variant_in_type`+`lookup_field_in_variant` (`find_child_named`=O(arms/fields)); bounded by pattern size | none; **run twice per arm** by the two-pass above | **ACCEPTABLE** (doubled by match finding) | fold once (fix the match two-pass) | +| `extend_scope_with_pattern_node` `04_infer.dag:1169` | per arm, fold over field bindings + nested patterns | per binding: lookups + `derive_field_provenance`→`inductive_fields_for` which **recurses `env.parents`** O(D) (`04_env.dag:157`); `concat(acc.diagnostics,…)` in fold ⇒ O(bindings²) diag accumulation | none | **ACCEPTABLE** (D-factor per binding; bindings small) | build provenance index once; accumulate diags without repeated concat | +| `check_match_exhaustiveness` `04_patterns.dag:279` | per match | `covered_set` fold O(A) + `variant_names` filter O(V) | none needed | **OPTIMAL** | — | +| `build_local_variants` / `bind_coproduct_item_arms` `04_infer.dag:6022` / `6010` | per module | fold items × arms = O(M) once/module | once per module | **OPTIMAL** | — | +| `build_imported_variants` + `owner_of_exported_arm` + `exported_coproduct_item` `04_infer.dag:6079` / `6033` / `6056` | per (importing module × imported name) | for **every** specific name (incl. non-variant names) calls BOTH followers, each `tm.items \|> filter(Disj) \|> filter(child==name)` = O(M_target·A), then follows re-export chain O(D). Over corpus ⇒ **O(N·K·D·M_target)** | **none** — per-exporter arm→owner / enum→item index rebuilt on every importer's every name | **PATHOLOGICAL / SUSPICIOUS** (request-major; the reference-lesson redundancy shape) | precompute per-exporting-module `Map` + `Map` once (with re-export resolution baked in); binder does O(1) `map_get` | +| `build_module_context` `04_infer.dag:6106` orchestrator; `typecheck_modules` fold `:6510` | per module, N-fold | drives all the above once/module; passes accumulating `parent_index` | no cross-module variant-index cache | **SUSPICIOUS** (carrier for the redundancy above) | attach per-module export index to `TypedModule` at typecheck, reuse from `parent_index` | + +## 3 worst offenders (evidence) + +**1. `build_imported_variants` + re-export followers — whole-corpus redundancy (graph-major inversion).** `04_infer.dag:6079-6104`. For each resolved import, for **each** specific name it calls *both* `exported_coproduct_item` and `owner_of_exported_arm`, and each (`:6037-6052`, `:6060-6075`) does `tm.items |> filter(Disj) |> filter(any child == name) |> first` — a full scan of the exporting module's item list — and on miss walks the specific-name re-export chain, re-scanning at every hop (depth D). Crucially this fires for *every imported name*, and most imports are ordinary functions/types that are not variant arms, so the common case is a full Disj-scan + chain-walk that returns nothing. `build_imported_variants` runs once per module inside the `typecheck_modules` fold (`:6510-6537`) with no per-exporter index cached in `TypedModule` or `parent_index`. Net **O(N·K·D·M_target)**: if a large module (e.g. core) is imported by all N modules pulling K names each, its M items are re-scanned N·K times. This is exactly the reference lesson's request-major redundancy. Fix: compute `Map`+`Map` once per exporting module (re-export resolved), store on `TypedModule`, binder does O(1) `map_get` per name. + +**2. `ExprMatch` two-pass arm inference — accidental O(A²) + latent 2^depth.** `04_infer.dag:2023-2063`. When `unified_arm_type` resolves, pass 2 rebuilds each arm; to fetch the pass-1 result for arm `idx` it does `arm_infer_results |> enumerate |> filter(ap => ap.first == idx) |> map(...)` (`:2027`) — an O(A) positional lookup inside a per-arm map ⇒ **O(A²)** for a list it already holds in parallel order with `arm_nodes`. Worse, when re-inference triggers it calls `infer_expr` on the arm body again (`:2043`); if that body is itself a match whose types only resolve given the now-known expected type, its own pass 2 fires ⇒ worst-case **2^(nesting depth)** re-inference, unmemoized. Gated by "arm body type unresolved on pass 1 but unified type resolved," so not always hot, but unbounded in principle. Fix: `zip(arm_nodes, arm_infer_results)` (A²→A); replace re-inference with a unify/constraint step or memoize per-node inference. + +**3. `infer_record_lit` — per-use-site field-template rederivation + O(A²) field match.** `04_infer.dag:2863-2955`. The struct's field list is a per-decl fact, but every `T { … }` literal recomputes it through the `record_lit_instantiated_fields → record_lit_fields_from_expected → record_lit_expected_fields → record_lit_alias_struct_fields` ladder (`:2864-2891`), including alias expansion and generic substitution. Then for **each** field-init it does `struct_fields |> filter(sf => name==fi_name) |> first` (`:2912`) ⇒ **O(A²)** in field count, and `field_in_any_variant_named` (`:2905`→`:2841`) walks every arm of the variant owner calling `expand_type_for_field_access` per arm in the coproduct-construction path. Fix: index `struct_fields` by name into a map (A²→A) and memoize the non-generic field template per type declaration so repeated literals of the same type reuse it. + +**Adjacent (outside assigned set, worth flagging):** `04_infer.dag:6563` / `:6570` in the str-bindings rewire post-pass do `filter(modules, m => filter(m.type_env.bindings |> map_values, b => b.name==name)…)` — an **O(N·E)** whole-corpus binding scan per name lookup; same graph-major-index fix applies. + +--- + +## Emit (--target dag path + rust emit) + +I have everything needed. Here's my analysis. + +--- + +# Emit-stage efficiency audit — `--target dag` (prime suspect) + rust emit + +**Key structural finding:** `--target dag` does **not** go through `emit_rust`/`05_emit_rust.dag` at all. `emit_artifact` (compile.dag:147) dispatches `Dag => emit_dag_artifact` (compile.dag:151), a wholly separate path in `compile.dag` + `dag_collect.dag` + `dag_collect_support.dag`. The rust path is comparatively healthy; the 20 minutes lives in the dag **node-collection** phase, and it is a genuine **O(M²)** blowup that I confirmed in the generated Rust. + +## Catalog + +| # | Pass / anchor | Computes over units | Asymptotics | Memo/sharing | Verdict | +|---|---|---|---|---|---| +| **D1** | `collect_dag_nodes` → `dag_collect_insert` (dag_collect.dag:157, :117) | dedup+order **every node** in the whole closure | **O(M²)** in total nodes M | accumulator `Rc` threaded through fold; **whole `seen` map + `order` list deep-cloned per node** | **PATHOLOGICAL** | +| **D2** | `dag_node_surface_fingerprint_rec` (dag_collect_support.dag:98), called via `dag_node_fingerprint` at dag_collect.dag:120 | full **subtree hash per insert-call** | **O(M·D)**, worse w/ sharing (D=depth) | none; recomputed per **edge**, even for already-`seen` nodes; children re-hashed at every ancestor | **PATHOLOGICAL** | +| D3 | `build_dag_key_to_id` (compile.dag:223) | id per node | O(M) | bare `Rc` moved into `rc_map_insert` → unique → in-place | OPTIMAL | +| D4 | `dag_graph_source_indices` (compile.dag:229) | merge per-module `source_indices` | O(N·C) cheap-Rc copies (C=closure) | move-threaded merge, but each module carries its whole import-closure's indices | ACCEPTABLE (mild closure redundancy) | +| D5 | `serialize_dag_nodes_table`/`serialize_node_record`/`serialize_node_ref` (compile.dag:777,741,233) | JSON string per node/ref | O(output) | native `Vec::push`+`join`; `dag_node_key` on real spans skips the fingerprint | ACCEPTABLE | +| D6 | `dag_emit_ref_errors` (compile.dag:219) | ref-target check per edge | O(edges) map_get | key_to_id read-only | ACCEPTABLE | +| R1 | `emit_rust` orchestration (05_emit_rust.dag:1607) | corpus → per-module files | O(corpus) | **graph-major**: `build_emit_graph_info`, `build_ownership_results`, `build_shared_types`, `build_module_index`, `build_module_export_sets`, `build_data_item_index` all built **once**, then `modules |> map(emit_module_full)` | OPTIMAL | +| R2 | `build_shared_types` (05_emit_rust.dag:1554) | shared-type set from summaries | O(#types) | once per corpus, carried in `emit_info` | ACCEPTABLE | +| R3 | `render_rust_type` family / `render_rust_applied_type_shared` / `render_rust_decl_type` (148,513,519) | recursion per **type node** | O(type structure) | not memoized, but type exprs are small/bounded | ACCEPTABLE | +| R4 | per-use-site `type_name_is_rust_importable_in_module` etc. (05_emit_rust.dag:1944,1915,1931) | O(1) `typed_module_by_name` (module_index) + scan that module | O(module size) per use-site; **rebuilds `module_emit_scope(tm)` per call** | index built once (good); scope re-derived per call (not) | SUSPICIOUS (minor) | +| R5 | `needs_box_wrapping` (05_emit_rust.dag:3192) | per type node | O(1)-bounded recursion | set lookups O(1) | OPTIMAL | +| R6 | unified expr emitters `emit_unified_typed_expr` + `05_emit.dag` shared layer (2390, whole file) | per **expr node**, single walk | O(total output); `concat`=`push_str`, `join` native | registry/scope O(1) lookups | ACCEPTABLE | + +`emit_go`/`emit_python` (05_emit_go.dag:125, 05_emit_python.dag:117) share the `05_emit.dag` unified emitters; they call `emit_*_module(tm)` per module rebuilding single-module `build_emit_graph_info([tm])` — per-module (not corpus-shared like rust) but still **O(corpus)**, not quadratic. Same acceptable shape. + +## The 3 worst offenders (with evidence) + +**1. `dag_collect_insert` — O(M²) accumulator deep-clone. This is the 20 minutes.** +The `DagCollectAcc` struct is `Rc`-wrapped and its `seen: Map` / `order: List` fields are themselves `Rc`-wrapped. You cannot move a field out of an `Rc`, so codegen emits `.clone()` on each field while the old handle is still live. Generated proof — `v1_compiler_dag_collect.rs:226-231`: +```rust +seen: v1_rt::rc_map_insert(inner.seen.clone(), pending.key.clone(), pending.fp.clone()), +order: v1_rt::rc_list_push (inner.order.clone(), pending.anchor.clone()), +``` +`inner.seen.clone()` bumps the refcount to ≥2 (inner still needed for `.order`/`.collision_errors`), so `rc_map_insert`'s `Rc::make_mut` (v1_rt.rs:350-358) sees a shared Rc and **clones the entire growing HashMap**; same for `rc_list_push` on `order`. One full copy of the size-k collection per node → Σk = **O(M²)**. Contrast the healthy sibling `build_dag_key_to_id` (`v1_compiler_compile.rs:533`): `rc_map_insert(acc, …)` — `acc` passed **by move**, unique refcount, in-place, O(M). Fix direction: **thread the accumulator as three bare (unwrapped, move-only) locals instead of an `Rc`-wrapped struct** (or build `seen`/`order` with a single owned mutable pass), so `make_mut` stays unique. + +**2. `dag_node_surface_fingerprint_rec` — unmemoized whole-subtree rehash, per edge.** +`dag_collect_insert` computes `let fp = dag_node_fingerprint(anchor)` (dag_collect.dag:120) **unconditionally, before** the `seen` check — so it runs once per *incoming edge*, including for nodes already collected. Each call (dag_collect_support.dag:98-103) recursively hashes the node's entire children+params subtree with no memo, so every node's fingerprint is recomputed once per ancestor and once per referencing edge → **O(M·D)** and up. Fix direction: **memoize fingerprint on the node key** (compute once, store in `seen`'s value or a side map) and only compute it in the `Absent`/new branch. + +**3. `dag_graph_source_indices` closure-redundant merge (compile.dag:229) / R4 per-use-site `module_emit_scope` rebuild (05_emit_rust.dag:1944).** Secondary. The merge re-copies each module's full import-closure source-index set (O(N·C), cheap Rc values — measurable but small at N=89); the rust-emit importability checks re-derive `module_emit_scope(tm)` on every use-site instead of caching per module. Fix direction: **precompute one corpus-wide source-index map / cache `module_emit_scope` per module in the module_index.** + +## One command to confirm + +Profile the dag emit and read the top frames: +``` +perf record -g -- --target dag <89-module corpus> && perf report --stdio | head -40 +``` +Expected hot symbols: `hashbrown`/`::clone` and `Rc::make_mut` under `v1_compiler_dag_collect::dag_collect_insert`, plus `dag_node_surface_fingerprint_rec` — with `serialize_*` far below. (Cheaper corroboration without perf: time `--target dag` on the first 20/40/60/89 modules; O(M²) shows ~4× time per module-count doubling, which typecheck's 21s linear baseline will not.) + +--- + +## Interpreter (src/v1/stage0/src/v1_interpreter.rs) + +## Catalog — `src/v1/stage0/src/v1_interpreter.rs` (tree-walking evaluator for the self-hosted compiler) + +Scaling variables: **S** = eval-steps executed (dynamic program work), **d** = dynamic call/recursion depth, **A** = fields/args per node, **B** = builtin+method arm count (~89 + ~34), **L** = string/collection length. This is an AST interpreter, so most costs are *per-eval-step constant factors*; the axis that matters is (a) whether any per-step factor hides a whole-structure walk, and (b) whether any structure grows with **d**. + +| Pass / algo | Anchor | Units | Asymptotic | Memo/sharing | Verdict | +|---|---|---|---|---|---| +| eval_expr / eval_expr_inner dispatch | 1336 / 1363 | per node/step | O(1)/step but `(*node.expr_data).clone()` + `ctx.si()` Rc-clone **every** step | none (re-done each step) | SUSPICIOUS const — match on `&*expr_data`; lift `si()` into the one arm that needs it | +| Env repr + `extend` + `lookup` | 538–576; used 1328, 5988 | per call / per var read | `extend`=O(new bindings), no parent copy (good); **but chain depth = dynamic call depth**, so `lookup` = O(**d**) probes; ≈O(**d²**) over a depth-**d** recursion | frame shared by Rc; chain *rebuilt taller* each nested call | **PATHOLOGICAL/SUSPICIOUS** — `call_function` extends the **caller** env, not a fixed base | +| eval_var | 1460 | per var node | Symbol cached per node-ptr (good); fast path `env.lookup` inherits O(**d**); slow path registry+`data_cache` memoized | var_sym_cache + data_cache keyed on Rc-ptr | ACCEPTABLE (modulo Env) | +| call_function | 1259 | per user call | O(params) bind build; param-name list cached per fn-ptr; re-calls `ctx.sym(pname)` a few times | param_name_cache per fn Rc-ptr | ACCEPTABLE | +| **match_pattern** | 1899 | **per arm-test, per field** | re-slices each field name from **source** (`authored_name_at`: hashmap lookup + String alloc) every eval; `fields_get` linear O(A) | **none** — unlike eval_var/eval_call, names here are NOT cached | **SUSPICIOUS** — memoize field name/Symbol on node-ptr | +| eval_call dispatch | 2275 | per call | func_name cached (good); **arg names re-sliced per arg per call** (`arg_name_at`, uncached); 7 cheap bridge `contains`; then `eval_builtin` runs on **every** user call | partial (func_name only) | SUSPICIOUS const — cache arg names; precompute builtin-id | +| **eval_builtin** | 4985 | per builtin **and** per user call | `match name:&str` over ~89 arms, default `_=>Ok(None)`; O(**B**) str-compares before real fn lookup | none | SUSPICIOUS const-factor dispatch | +| eval_method_call + eval_algebra_method | 2595 / 2957 | per method call | method name re-sliced per call (uncached); ~34-arm str match; `fields_get` O(A) | none | SUSPICIOUS const | +| List HOFs: map/filter/fold/flat_map/sort_by/join/concat | 2972–3248, 5093 | per collection op | `im_rc::Vector` = O(1) clone, O(log L) index/split; ops single-pass O(L); `free_monoid_to_vec` materializes an O(L) Vec copy | structural sharing | ACCEPTABLE/OPTIMAL (no accidental quadratic from list clone) | +| Value clone traffic | 325 (enum), clones throughout | per bind/read/return | all heap payloads Rc-shared **except `Value::Str(String)`** → deep O(L) copy on every clone/bind/read | Rc for List/Map/Record/Variant/Closure; **not** for Str | SUSPICIOUS const-factor — use `Rc` | +| CanonKey / value_hash (map keys) | 192 / 228 | per map insert/lookup | deep structural hash O(key size), no cached hash | none | ACCEPTABLE (inherent) | + +Note: registry/module iterations at 1051, 1216, 1231 are one-time setup (O(N·M) once), not per-step — fine. No per-eval-step whole-corpus scan exists; function *calls* hit `ctx.lookup_fn` (hashmap) before env, so they don't walk the chain. + +## Three worst offenders (evidence) + +**1. Env chain grows with dynamic recursion depth — the only superlinear defect.** `call_function` (1328) and `apply_closure` (5988) do `Env::extend(env, bindings)` where `env` is the **caller's** env (threaded from `eval_call` 2401 / `run_in_context` 1190), not a fixed global/definition base. So at recursion depth **d** the parent-linked chain is **d** frames deep (verified: `run_in_context`→`call_function(...&env)`→body `eval_call`→`call_function(...&call_env)`→`Env::extend(call_env,…)`). `Env::lookup` (567) walks the chain, and `eval_var` (1503) calls `env.lookup` **before** the global-registry fallback (1509) — so every bare reference to a data-item/global inside a deep recursion walks all **d** frames (finds it at the root under `eager_data_env`, or misses to root then hits registry) → O(**d**) per reference, ≈O(**d²**) per recursion. This is the classic "inner walk over something that should be bounded" and is the prime in-file suspect for the `--target dag` emit blowup (emit = deep recursive AST descent). *Fix: invoke top-level `Value::Fn` bodies against a fixed global/definition base env (true lexical scoping), bounding chain depth to lexical nesting, not dynamic depth.* + +**2. Un-memoized per-eval source-slicing of node names on the hottest paths.** `match_pattern` re-derives every field name via `field_binding_name_at`→`authored_name_at` (v1_std_core 1192) on **every** arm evaluation (2000, 2016, 2046, 2079, 2110, 2127) — each is a `map_get` hashmap lookup + `source_text_at` char-slice + `String` allocation (plus spurious `text.clone()==""` allocs). Same uncached slicing for method names (`expr_method_name_at`, 2596), field access (2661), and call arg names (`arg_name_at`, 2294). The name of an immutable AST node is derivable-once, and the codebase already proves the pattern (var_sym_cache 1470, call_func_name_cache 2278, param_name_cache 1276 keyed on `Rc::as_ptr`) — these three sites are just the un-memoized siblings. Cost is bounded (per identifier length) but multiplied by S·A on the pattern-match/method paths that dominate functional-compiler workloads. *Fix: cache name/Symbol on the node Rc-ptr exactly like the existing caches (or resolve at parse time).* + +**3. Per-call mega string-dispatch + Str deep-clone traffic.** Every user function call falls through `eval_builtin`'s ~89-arm `match name:&str` (4985, default `_=>Ok(None)` at 5967) and, for methods, `eval_algebra_method`'s ~34-arm match (2957) before reaching the real body — O(**B**) string comparisons per call where a builtin-id/enum resolved once onto the call node would be O(1). Compounding it: `Value::Str(String)` (330) is the only Value payload not Rc-shared, so string values are deep-copied on every `env.lookup(...).clone()` (1504), bind, and return — in string-building emit this is O(total bytes) traffic, and it amplifies any interpreted `acc = concat(acc, piece)` fold (5093 allocates a fresh String each call) into program-level O(N²). *Fix: precompute a builtin/method discriminant on the call/method node at resolve time (O(1) dispatch); switch `Value::Str` to `Rc` so string values share like every other heap value.* + +--- + +## Floor / claim host (cli_run.rs + claim_executor.rs) + +# gunbc floor/claim algorithm efficiency catalog + +Scope: `src/v1/stage0/src/cli_run.rs` (floor/claim paths) + `src/v1/stage0/src/bin/claim_executor.rs` (plan eval). Scaling vars: **N** modules in corpus, **F** dag files, **E** import edges (whole corpus), **C** entry-closure size, **D** import depth, **W** shard/spawn width, **R** rows/claims. + +## Catalog + +| # | Pass (anchor) | Computes / unit | Asymptotic | Memo / reuse boundary | Verdict | Fix direction | +|---|---|---|---|---|---|---| +| 1 | `import_closure_from_facts` (cli_run.rs:571) — the engine under `resolve_transitively`→`load_sources_for_entry_with_index` | Entry's transitive import closure, per **entry** | **~O(D·R·E)** string-normalizations + O(D·C·E)/join per entry; invoked ≥2× per distinct entry | **NONE** — recomputed every call; no closure memo | **PATHOLOGICAL** | Prebuild adjacency (`path→imports`, `module→path`, pre-normalized) once per `ModuleGraphFactsLive`; worklist BFS → O(C+closure-edges) | +| 2 | Per-shard index build (cli_run.rs:5008) + per-claim cold resolve (`run_shared_entry_claims`→`resolve_entry_graph`, claim_executor.rs:455 → cli_run.rs:857) | Whole-tree module index + facts + typed cache, per **shard** / per **claim-entry-group** | O(W·(F+E)) rebuilds; shared std/spec prefix re-typechecked W× (shards) / per-unit (claims) | Cold cache **per shard thread / per claim unit**; NOT via `process_shared_index` | **SUSPICIOUS** (request/unit-major, the reference lesson only half-fixed) | Freeze typed cache into a `Send`/Arc snapshot shared across shard+claim threads (S2b/Arc frontier) | +| 3 | `precompute_whole_tree_published_mock_keys` (cli_run.rs:1603) | ~13 `PublishedMockCase` keys; builds own whole-tree index+facts, Strict-resolves declarer closures, per **invocation** | O(F+E) index + Σ declarer O(D·C·E) [inherits #1] + Strict typecheck | Own index+facts (not shared); result cloned to every shard+row; gated by `skip_precompute` | **SUSPICIOUS** (was RSS-dominant ~1.46 GiB) | Reuse `process_shared_index`; select declarers via facts, not a fresh index | +| 4 | `build_floor_lens_hygiene_graph` / `discover_floor_corpus_rows` (cli_run.rs:3464/3631) + naming hygiene | Read every file, extract module/imports, SIDECAR rule scans, build path maps, per **file (whole corpus)** | O(F·L_file + Rules·F) | Once per invocation (and once pre-plan in claim_executor.rs:1763) | **ACCEPTABLE** (linear read) — but a *separate* whole-tree read from `build_module_index` (redundant I/O axis) | Single corpus read feeding both hygiene + index | +| 5 | `check_floor_filename_hygiene` (cli_run.rs:3409) | Basename `__` check, per **filename** | O(F) filenames, no reads | Once per invocation | **OPTIMAL** | — | +| 6 | `inert_lens_modules` (cli_run.rs:3812) | Lens reachability, per **module/edge** | O(V+E) worklist BFS | Once per discovery | **OPTIMAL** (correct worklist — the counter-example to #1) | — | +| 7 | `reconcile_with_typed_cache` (cli_run.rs:1230) | Typecheck+accumulate, per **module in closure** | O(distinct modules) typecheck (memoized); accumulation linear | `typed_module_cache`/`parse_cache` keyed by module/path, once per index; `rc_map_merge`/`rc_map_insert`/`rc_list_push` on **unaliased** accumulators = O(1) amortized (verified: not the quadratic-merge trap) | **ACCEPTABLE** | (minor) `rewire_*` + `build_emit_graph_info` re-walk full closure each resolve, unmemoized | +| 8 | `shard_row_indices_by_entry` (cli_run.rs:5048) | Group rows by entry, round-robin to W | O(R) | per invocation | **OPTIMAL** (note: round-robins by group count, not closure weight → load-skew, not asymptotic) | — | +| 9 | Plan eval: `batches_from_plan` / `group_batch_units` / `run_walk` (claim_executor.rs:234/296/1315) | Materialize batches, coalesce same-entry claims, schedule | O(total runnables); grouping O(batch) hashmap | `entry_to_unit` coalesces to one resolve/entry; `walk_memo` across batches | **OPTIMAL** scheduling — but the resolve *substrate* it dispatches to is #2 | route units through per-thread shared index | + +## 3 worst offenders (evidence) + +**1. `import_closure_from_facts` — cli_run.rs:571 — PATHOLOGICAL, per-entry whole-corpus fixpoint.** +Non-worklist fixpoint bounded by `let fuel = nodes.len()` (= N passes). Each pass does `let mut next = reached.clone()`, then for every already-reached importer scans **all** edges — calling `workspace_relative_repo_path(&edge.path)` (allocates a `String` via `.replace`+`strip_prefix`) *on every edge, every importer, every pass* — and for each matching edge scans **all** nodes to resolve the target, with an O(R) `next.iter().any` dedup. This is the exact reference pathology ("inner loop scanning something whole-corpus" + "re-computation of derivable-once facts": the adjacency and the normalized paths are rebuilt every pass). It is invoked **≥2× per distinct entry** in discovery (`load_sources_for_entry_with_index` at cli_run.rs:5135 for the subject digest, again inside `resolve_entry_with_parse_cache` at cli_run.rs:1071) and once per SharedClaims/precompute-declarer — none memoized. Notably `resolve_transitively_bfs_legacy` (cli_run.rs:687) right above it *is* a proper O(V+E) worklist BFS; the live facts-based path regressed the closure walk to a naive join. + +**2. Per-shard / per-claim cold resolve substrate — cli_run.rs:5008 and claim_executor.rs:455→cli_run.rs:857 — SUSPICIOUS, request/unit-major redundancy.** +The main-thread discovery was fixed to share one index (`process_shared_index`, S1), but each shard thread calls `build_multi_entry_index(&roots)` fresh (cli_run.rs:5008) and every `SharedClaims` unit resolves through **cold** `resolve_entry_graph` (`build_module_index` + `build_module_graph_facts_live` per call). Consequence: the whole-tree file walk/read/parse runs ~W× (plus once per claim entry-group), and the shared std/spec prefix re-typechecks with a cold `typed_module_cache` each time — the "resolve the same closure N times because the caching unit is per-request" shape, only partially retired. `run_walk`'s comment (claim_executor.rs:1321) confirms the cross-thread share is deliberately absent (Rc `!Send`). + +**3. `precompute_whole_tree_published_mock_keys` — cli_run.rs:1603 — SUSPICIOUS, redundant whole-tree substrate + inherited #1.** +Builds a private `build_module_index(&dag_roots)` + `build_module_graph_facts_live` (whole-tree walk, separate from the already-built shared index), then `resolve_transitively(declarers, …)` loops ~13 declarers each paying the full `import_closure_from_facts` fixpoint (#1), then a Strict whole-closure typecheck. Runs once per `run_discovery_corpus_with_options` (gated by `skip_precompute`), historically the RSS peak (~1.46 GiB per the in-code comment). Frequency is bounded, but the substrate build is pure-duplicate of `process_shared_index` and the declarer loop multiplies offender #1. + +**Direct answer — what still runs whole-tree per invocation / per shard / per claim:** +- Per **invocation**: naming-hygiene whole-tree read (#4), `precompute_whole_tree_published_mock_keys` index+facts+resolve (#3), plus the first `process_shared_index` build. +- Per **shard**: full `build_multi_entry_index` (whole-tree read ×~4: `build_module_index` + `build_module_path_index`×2 + import scan) and a cold typed cache (#2). +- Per **claim entry-group**: cold `resolve_entry_graph` = whole-tree index+facts rebuild + per-entry `import_closure_from_facts` fixpoint (#1, #2). +- Per **entry, ≥2×**: the `import_closure_from_facts` whole-corpus edge+node fixpoint (#1) — the single highest-leverage fix. + +--- + +## v2 interpreted stages (src/v2/compiler/0*.dag) + +I have enough to produce the catalog. Let me confirm one more critical helper — the `find_named_child` full body and whether infer's `solve_constraints` walks the node subtree — but I already have decisive evidence. Key confirmations: + +- `Map` (collection.dag:49-84) is a **closure chain**: `lookup` is O(K) in #inserts; absent lookups traverse the whole chain. +- `list_snoc_item` = `list_append(left, [item])` = **O(len(left))** → fold-accumulation is O(n²). +- `fold_node` (node.dag:178) runs `algebra.init(n)` at **every** node. +- infer's `InferredTree.facts` (04_infer.dag:365) is backed by `lookup_inferred_facts_in_entries` = **linear O(entries) scan per lookup**. +- `termination_proof_witness_for_node` (cardinality.dag:260) is itself a **full `fold_node` over the subtree**, called per-node inside infer's catamorphism. + +--- + +# Efficiency Catalog — v2 interpreted self-compiler stages (`src/v2/compiler/0*.dag`) + +Scaling vars: **N**=modules in corpus, **M**=nodes in one module tree, **A**=atom/use-site occurrences (~O(M)), **E**=binding/namespace size (~O(M)), **C**=node arity (children), **D**=tree depth, **P**=grammar productions (corpus-constant), **T**=tokens in module, **L**=serialized output length. All stages run **interpreted (~100× constant)**, so every super-linear term below is amplified. + +Foundational primitive costs (drive everything): `Map` = closure chain → **lookup O(E)**; `list_snoc_item`/`list_append` → **O(len)** ⇒ fold-with-snoc = **O(n²)**; `fold_node` init runs per node. + +| # | Pass / algorithm | file:line | Computes / unit | Asymptotic | Memo/sharing | Verdict | Fix direction | +|---|---|---|---|---|---|---|---| +| 1 | **parse packrat memo table** | 02_parse.dag:728,774 | memoize prod×position | **dead** — `let _memo = parse_table_insert(...)` result **discarded**, table never threaded through `ParseExprResult`; lookup (744) always hits empty table | never — every prod re-parsed at every position | **PATHOLOGICAL** | thread updated table out of `parse_expr`/thunks (state-monad), or key a real memo by (pos,prod) → true packrat O(T·P) | +| 2 | parse position key | 02_parse.dag:317,736 | `len(all)-len(toks)` per nonterminal | **O(T)** per prod-attempt → O(T²+) overall | recomputed each call | **PATHOLOGICAL** | carry an integer index in the parse cursor; don't recompute via `length` | +| 3 | grammar validation (nullable/left-corner/undefined/dup) | 02_parse.dag:823,908,942,979 | pure fn of fixed grammar; nullable **O(P²)**, left-corner closure ~**O(P³)** (`grammar_lookup_production` is O(P) filter inside 3 nested folds) | recomputed **once per module** ⇒ **N×O(P³)** | not memoized across N | **SUSPICIOUS** (cross-module redundancy) | validate once per grammar digest (they already compute `grammar_digest` — cache on it) | +| 4 | `grammar_lookup_production` | 02_parse.dag:214 | `filter` productions by name, take[0] | **O(P)** linear per call; called per nonterminal parse | none | **SUSPICIOUS** | precompute name→production index Map once | +| 5 | **normalize** bottom-up rebuild | 03_normalize.dag:133,53 | rebuild tree; per node `list_snoc_item(shell.children,…)` | single pass O(M), but per-node child rebuild **O(C²)** → O(Σ C²) | one walk (good) | **SUSPICIOUS** | build children with prepend+reverse (O(C)) not snoc | +| 6 | **resolve namespace lookup** | 03_resolve.dag:200,261,790 | resolve each atom via `lookup_chain`→`map_get` on closure-chain `bindings` | **O(E)** per atom × **A** atoms = **O(M²)** per module (+O(D) scope frames) | namespace built once (179), but stored as O(E)-lookup closure chain | **PATHOLOGICAL** | back `Namespace.bindings` with an indexed/hashed map (O(1)/O(log) lookup) — this is the reference-lesson shape, interpreted | +| 7 | resolve child folds | 03_resolve.dag:333,529,561,654 | accumulate resolved edges via `list_snoc_item` (+`length(done)` at 341) | **O(C²)** per node | — | **SUSPICIOUS** | prepend+reverse; track index/len in acc | +| 8 | **infer termination proof** | 04_infer.dag:298,302 → cardinality.dag:260 | per node, `termination_proof_witness_for_node(n)` = **full `fold_node` over subtree(n)** | nested catamorphism: **O(Σ subtree(n)) = O(M²)** (O(M·D)) | none — re-walked per node | **PATHOLOGICAL** | compute descent proofs compositionally in the *same* bottom-up fold (combine child proofs in `step`) | +| 9 | **infer facts map** | 04_infer.dag:346,365,323 | `facts.lookup` = `lookup_inferred_facts_in_entries` linear scan | **O(M)** per lookup; used on every branch/match node (518,676,715) | map is a linear list wrapped in a closure | **PATHOLOGICAL** | key entries by `occurrence_id` in an indexed map | +| 10 | infer entry accumulation | 04_infer.dag:272,1018,352 | `concat_inferred_facts_entries`/`list_snoc_item` merge child entry-lists up tree | **O(M²)** (repeated append up the tree) | — | **SUSPICIOUS** | accumulate with a balanced/diff structure or emit into indexed map directly | +| 11 | infer `solve_constraints` per node | 04_infer.dag:307 → constraints.dag:111 | per-node candidate search over graph rooted at n | ≥O(subtree(n)) per node → compounds #8 | none | **SUSPICIOUS** | fold constraints once; share candidate sets | +| 12 | **translate emit driver** | 06_translate.dag:3555,3522,1270,398,375 | per node `coerce_grounded_node`→`canonical_grounding_for_node`→`facts.lookup` (linear, #9) | **M nodes × O(M) lookup = O(M²)** (≥2× via reject re-lookup 3536) | facts map is linear-scan (from infer) | **PATHOLOGICAL** (the measured >20-min emit) | index `facts` by occurrence_id; or thread facts positionally through the same fold | +| 13 | translate double walk | 06_translate.dag:3833–3860,3724 | `fold_node` raw pre-pass, then full `translate_node_mvp1_fold` | 2× full O(M) walks (+#12 per node) | raw result thrown away if relation-row miss | **SUSPICIOUS** | fuse the relation-row check into one pass | +| 14 | translate child rebuild | 06_translate.dag:3895,3908,3702 | `list_snoc_item(parent.children,…)` per step | **O(C²)** per node | — | **SUSPICIOUS** | prepend+reverse | +| 15 | **serialize to source string** | 06_translate.dag:814,931,1076 | `list_append(left: partial, right: source)` accumulate output chars | **O(L²)** in output length | — | **PATHOLOGICAL** (`--target dag`) | build with a rope/right-fold or accumulate reversed chunks, join once | +| 16 | serialize measure budgets | 06_translate.dag:227,238,256,206 | `node_subtree_count` (`fold_node`) per serialize entry (+rules budget each) | O(subtree) per entry; bounded variants thread `remaining` (OK) | rules-budget recomputed per measure call | **ACCEPTABLE** (watch) | compute rules budget once per target | + +--- + +## 3 worst offenders (evidence) + +**1. `06_translate` emit — O(M²) per-node facts lookup + O(L²) serialization → the measured >20 min.** +Every node's fold-init (`translate_mvp1_fold_init`, 3555) calls `coerce_grounded_node` (1270) → `canonical_grounding_for_node` (397) → `tree.facts.lookup(node)`. That `.lookup` is `lookup_inferred_facts_in_entries` (04_infer.dag:323), a **linear fold over all entries**. `fold_node` runs init at all **M** nodes ⇒ **M × O(M) = O(M²)**, doubled on the reject branch (3536 re-looks-up). Then `serialize_concrete_syntax_tokens_to_source_string` (814) and `target_serialize_relation_row_from_model_bounded` (967/989) grow the output with `list_append(left: partial, …)` ⇒ **O(L²)** in serialized length. Two independent quadratics on the emit path; ×100 interpretation ⇒ minutes for an 89-module corpus. *Fix:* index `InferredTree.facts` by `occurrence_id` (O(1)); accumulate serialized text reversed and join once. + +**2. `04_infer` — O(M²) nested catamorphism (termination proof re-walked per node), plus quadratic entry plumbing.** +The stage is one `fold_node` (1066) — good shape — but its per-node init `infer_node_facts` (298) calls `infer_descent_witness_for_node` → `termination_proof_witness_for_node` (cardinality.dag:260) which is **itself a full `fold_node` over the node's subtree**. Summed over all nodes = **O(Σ subtree(n)) = O(M²)**. Compounded by `concat_inferred_facts_entries`/`list_snoc_item` merging child entry-lists up the tree (272/1018 ⇒ O(M²)) and linear `lookup_inferred_facts_in_entries` on every branch/match node (518,676,715). This directly answers the "one fold, 7 stages" claim: the catamorphism *shape* is reused, but infer **re-walks each subtree per node** and re-scans accumulated entries — it does not share substructure. *Fix:* fold descent proofs compositionally in the existing bottom-up `step`; emit facts into an indexed map instead of a snoc'd list. + +**3. `03_resolve` closure-chain namespace — O(M²) per module (the reference-lesson shape, interpreted) — tied with `02_parse`'s dead packrat memo.** +`build_program_namespace` (179) harvests every atom into `bindings`, a `Map` that is a **chain of O(M) `lookup` closures** (collection.dag:75). Then `resolve_atom` (261) → `lookup_chain` (200) → `map_get` walks that chain **O(E)≈O(M)** for each of **A≈O(M)** atom occurrences ⇒ **O(M²)** per module. Same defect as the reference resolver, just via the closure-Map primitive rather than an env flatten. *Co-worst — `02_parse`:* the packrat table is **inert**: `parse_nonterminal_memoized_core` (774) does `let _memo = parse_table_insert(...)` and **discards it** — the table is never threaded out of `parse_expr`/thunks, so lookup (744) always sees the initial empty table and every production re-parses at every position (backtracking recursive descent, exponential worst case), with `parse_current_position` (317) adding an O(T) `length` subtraction per attempt. *Fixes:* back `Namespace.bindings` with an indexed map; thread the parse memo table through results (state-monad) so writes survive. + +Cross-cutting: (a) `list_snoc_item`/`list_append` accumulation is quadratic in **every** stage's child-rebuild fold (#5,7,10,14) — replace snoc-fold with prepend+reverse; (b) grammar validation (#3) and canonical-symbol/set construction are **corpus-constant work recomputed per module** (N× redundant) — hoist/memoize on the already-computed `grammar_digest`. + +--- diff --git a/docs/plans/post-engine-pr-roadmap.md b/docs/plans/post-engine-pr-roadmap.md new file mode 100644 index 00000000000..6afabda1ec2 --- /dev/null +++ b/docs/plans/post-engine-pr-roadmap.md @@ -0,0 +1,88 @@ +# Post-engine-PR roadmap — deferred ledger + compiler algorithm audit + +**Status:** authored 2026-07-04 at the tail of PR #6235 (constructor-owner ruling). Two parts: (A) everything the engine PR deliberately deferred, so nothing silently drops; (B) the plan for the operator's next directive — assess every compiler algorithm for rock-bottom (optimal) efficiency, generalizing the resolver lesson. Dissolves when: every §A item is either landed or has its own carrier doc, and §B's audit table lands with measured receipts and each SUSPICIOUS/PATHOLOGICAL row has a fix commit or a priced deferral. + +DESIGN refs: §2 (the resolver lesson IS §2's redundancy axes), §5 (fail-open seams found on the way), §6 (price fixes by displaced cost, not elegance). + +--- + +## A. Deferred ledger (engine PR #6235) + +Ordered by weight, heaviest first. + +1. **Emitter restoration → regen convergence (C8 tail).** Regen was semantically broken on main before this PR; three deficiencies fixed (statement-separator instances dissolved with the env counters; `needs_box_wrapping` shared-dominates reorder; `phase_profile.rs` registry), three open with receipts in `resolver-graph-major-design.md` §7b: deref-side boxing asymmetry (~800 errors — field-ACCESS emission has a second boxing-decision site that must consult the one `needs_box_wrapping` authority), alias-brand rendering (`Nat` emitted as its grounding `Rc>`, ~250), misc residue (~80). Acceptance: regen output builds + full suite green + `bootstrap_fixed_point` stage1==stage2 + RegenVerifyGate green; `--emit-fresh` probes are ~2 min each post-pathology. Also fold in: the emitter's statement-then-expression mis-render (instances gone, deficiency remains); the seed hand-patches this PR accumulated (constructor ladder, chain followers, `resolve_generic_use_decl` parents-retry, cascade-locator message) all get replaced by true emission here. +2. **Emit-stage cost (the next resolver-class pathology).** `--target dag` emit of the 89-module wave-1 closure exceeds 20 minutes single-core (typecheck of the same closure: 21s); whole-tree emit unbounded in observation. Blocks the CI compile-clean gate's 10-minute budget. Profile first (extend the phase profiler into emit with per-module attribution), then fix; §B's survey carries the candidate mechanisms. +3. **Kernel-prelude shadowing — the principled root.** This PR's fix is surgical (a type-argument-bearing use site that resolves paramless retries direct import parents). The principled rule — explicit imports shadow the implicit kernel prelude everywhere, types AND functions, the same precedence the constructor kernel-merge already uses — was tried wholesale and broke kernel *function* dispatch (`join`/`split`/`length` "not found") through an unmapped path; that path must be mapped before the flip lands. Includes unifying the single-import vs multi-import ancestry asymmetry (`build_type_env` merges kernel as overlay only when imports ≠ 1). +4. **`Value::Null` split (DESIGN open thread, operator-fenced runway).** The kernel-optional raw-value-or-Null representation caused the shadowed-unwrap interpreter bug this PR fixed narrowly (guards hoisted). The split — Optional/Witness/miss into own carriers (~131 `present == None` sites) — remains the root fix; the hoisted guards and the `CrossRepresentationEquality` backstop dissolve with it. +5. **Flat visibility (rule ii) decision.** Operator: include if a re-census with the fixed compiler shows < ~300 sites needing explicit imports; the re-census never ran (the engine PR's tail went to the cascade root-cause instead). Cheap now: instrument `lookup_binding_by_name`'s ancestry rung with a hit counter + site log over a whole-tree compile, count distinct (file, name) pairs. +6. **C9 receipts (this PR's own tail, if not landed before merge):** full witness-suite run via claim_batch; `main_wet` generated-doc regen + drift gate; timing receipts into `resolver-graph-major-design.md` §0 placeholders (single-module before/after, whole-tree compile, floor phase marks vs the 10-min budget — note the pre-fix corpus baseline receipt: 4 hours, zero verdicts); CI-green confirmation on #6235. (Local suite receipt landed 2026-07-04: 656 passed / 0 failed / 85 ignored — the last two reds were the retraversal-guard brace fix and the interp_dry_run /tmp fixture, not a stale binary.) +7. **Determinism roster re-signing (#5941).** `v2.std.determinism` rows `^unique_imported_variant_owner` and `^alpha_sorted_variant_fold` name fns this PR deleted; quoted symbols still compile but the roster is stale — operator re-signs successor rows (`insert_variant_owner_checked` / the declaration-ordered folds). +8. **Binder find-first hardening.** `owner_of_exported_arm` / `exported_coproduct_item` take the first matching coproduct within a module's items — ambiguity is unreachable in a swept corpus (the defining module collides first) but the walls should not rely on that indirection; make them collision-aware at regen time. +9. **Diagnostics polish.** Collision diagnostics print 2–3× per occurrence (dedup at the reporting seam); `is_user_generic_use_site`'s remaining kernel-name special cases (`is_container_type` gate) audited alongside §A.3. +10. **Witness re-enrollment (standing dissolve-on).** CI witness roster is minimal by design (opt-in inversion); enrollment returns to discovery shrunk by the affected set when `v2.lens.affected_set` selection + floor memoization land. +11. **Coverage-by-illusion census.** The shadowed-unwrap bug survived in an idiom used corpus-wide (`match xs |> first { Present … }` over records) because no green test ever executed that shape — a §5 concern: measure which corpus fns/branches the witness suite actually executes; the delta is the illusion surface. +12. **Resolver roadmap (design doc §7):** S2a runner + node-keyed store (CI floor first with unit results, byte-identical 11-gate receipt; then the module lane — per-module typecheck as antichains), S2b Merkle persistence (gated #5941), S3 shared store; `Rc`→`Arc` retires the per-shard W× residual. + +## B. Compiler algorithm audit — "rock bottom" efficiency + +**The resolver lesson, generalized.** Every stage cost factors as `units × work-per-unit`. The resolver failed on both axes at once: request-major scheduling multiplied *units* (every entry re-resolved the shared prefix) and the constructor scan blew up *work-per-unit* (per-literal whole-env flatten). Neither showed up as a diagnosis until instrumented — CI just timed out. The audit closes both axes for every stage, with measurement before and after. + +**Method (each stage):** +1. **Inventory** — name every pass, its unit, its asymptotic class in named scaling variables (N modules, M nodes/module, E visible names, C closure, A arms/fields, D import depth), and its sharing story (computed once per what?). +2. **Verdict per pass** — OPTIMAL (information-theoretic floor: touches each input once) / ACCEPTABLE (log-linear) / SUSPICIOUS (polynomial where linear is plausible) / PATHOLOGICAL (measured blowup). +3. **Measure before fixing** — extend the phase profiler + per-stage attribution counters (the typecheck-attribution pattern) so every SUSPICIOUS verdict gets a corpus-scale number; the operator's standing "not very scientific" bar applies. +4. **Fix by displaced cost** (§6) — the 5ms pass doesn't get a pass for not being the 80s one, but fixes land in measured-share order; every fix carries a before/after receipt on the same fixture. +5. **Wall the floor** — each stage gains a budget witness (the floor phase marks pattern) so a future regression is a red gate, not a slow rot discovered at the 90-minute timeout. + +**Survey inventory:** populated from the eight-stage parallel survey (parse/lex, resolve, infer-env, infer-core, emit, interpreter, floor-host, v2-interpreted stages) — table appended below when the fleet reports. + +**Known entries ahead of the survey** (from this session's receipts): emit `--target dag` (PATHOLOGICAL, measured >20 min / 89 modules); per-shard union-index duplication (ACCEPTABLE-interim, W× bounded, S2b retires); interpreted v2 stages amplify constants ~100× so anything super-linear there is a priority multiplier. + +## Survey results (2026-07-04) + +Eight parallel stage surveys, all reported; raw catalogs with full per-pass tables in [compiler-algorithm-survey-2026-07-04.md](compiler-algorithm-survey-2026-07-04.md). These are read-verdicts with file:line anchors; the four highest-leverage claims were spot-verified against the live tree (`dag_collect_insert` clone pattern in the generated Rust; the `skip |> first` token accessor + its tail-materializing builtin; the discarded v2 packrat insert; the phantom-match flatten — confirmed present but in its post-ruling FLAT one-level form, fallback-only). Every PATHOLOGICAL row still owes a corpus-scale measurement before its fix lands (§B step 3). + +### Ranked findings — PATHOLOGICAL tier + +| # | Stage | Site | Shape | Cost | Fix direction | +|---|---|---|---|---|---| +| P1 | emit `--target dag` | `dag_collect_insert` (dag_collect.dag:117,157) + `dag_node_surface_fingerprint_rec` (dag_collect_support.dag:98) | `Rc`-wrapped accumulator: `inner.seen.clone()` while `inner` is live → `Rc::make_mut` deep-clones the whole growing map **per node**; fingerprint recomputed per **edge**, before the `seen` check, unmemoized | **O(M²)** in total corpus nodes — the prime suspect for the measured >20-min emit (typecheck of the same closure: 21s). Key structural fact: `--target dag` never touches `05_emit_rust.dag`; it is `compile.dag → dag_collect` | Thread the accumulator by move (the healthy sibling `build_dag_key_to_id` proves the idiom); memoize fingerprint on node key, compute only in the Absent branch | +| P2 | parse (v1) | `token_stream_first/peek` (02_parse.dag:79,87) → `skip` builtin (v1_interpreter.rs:3187) | Every peek = `skip(all, pos) \|> first`; `skip` clones the **entire tail** into a fresh Vec, `first` discards all but the head; 263 call sites × O(M) driver steps | **O(M²)** time+allocation per module; likely the "legacy CYK floor ≈63s" | Index by position (RRB `get` is O(log M)) → parse drops to O(M log M); also fixes `scan_for_fat_arrow_after_braces` lookahead for free | +| P3 | infer-env | `rewire_type_env_import_str_binding_identity` (04_infer.dag:6613) → `global_type_exporter_count`/`local_authority_for_name` (:6556,:6562) | Whole-corpus scan (all N modules × all B bindings) **per visible name per module** during reconcile | **Θ(N²·C·B)**; fires on every full-corpus compile | One O(corpus) pass building `name → {exporters, canonical binding}`; each rewire becomes O(1) | +| P4 | floor-host | `import_closure_from_facts` (cli_run.rs:571) | Non-worklist fixpoint: N passes × all edges × path-normalization allocs × O(R) dedup; invoked ≥2× per distinct entry, unmemoized. A regression — `resolve_transitively_bfs_legacy` directly above it IS a proper O(V+E) worklist | ~O(D·R·E) per entry, every discovery/claim resolve | Prebuild normalized adjacency once per `ModuleGraphFactsLive`; worklist BFS | +| P5 | infer-core | `build_imported_variants` + `owner_of_exported_arm`/`exported_coproduct_item` (04_infer.dag:6079,6033,6056) | Full Disj-scan + re-export chain walk of the exporting module **per imported name per importer** — fires for every name incl. non-variants (common case scans and finds nothing). Part of the machinery this PR built; already ledgered as §A.8's sibling | **O(N·K·D·M_target)** — core imported by all N modules is re-scanned N·K times | Per-exporting-module `Map` + `Map` computed once (re-exports baked in), stored on `TypedModule`; binder does O(1) `map_get` | +| P6 | interpreter | `Env::extend` chain (v1_interpreter.rs:538-576, 1328, 5988) | `call_function` extends the **caller's** env, not a fixed base → chain depth = dynamic call depth d; every var/global read walks O(d) | **O(d²)** per depth-d recursion; multiplies every interpreted workload incl. all v2 stages (~100× constant on top) | Invoke top-level fn bodies against a fixed global base env (true lexical scoping) — chain depth becomes lexical nesting | +| P7 | v2 translate/serialize | `06_translate.dag:3555→1270→397` facts lookup; `:814,931,1076` serialization | `InferredTree.facts.lookup` = linear scan over all entries, called per node (O(M²)); output built with `list_append(partial, …)` (O(L²)) | Two independent quadratics on the v2 emit path, ×100 interpreted | Index facts by `occurrence_id`; accumulate output reversed / join once | +| P8 | v2 parse | packrat memo table (src/v2/compiler/02_parse.dag:744,774) | **Dead memo**: `let _memo = parse_table_insert(...)` — result discarded, table never threaded out, lookup always sees the empty table; position key recomputed via `length` O(T) per attempt | Exponential worst-case backtracking; every production re-parses at every position. The performance twin of the "inert lens" (coverage by illusion — the machinery exists, nothing flows through it) | Thread the updated table through `ParseExprResult` (state-monad); carry an integer cursor | +| P9 | v2 resolve + infer | `03_resolve.dag:200,261` namespace closure-chain; `04_infer.dag:298→cardinality.dag:260` nested catamorphism | `Map` primitive is a closure chain → O(E) per atom lookup × A atoms = O(M²)/module (the resolver lesson verbatim, interpreted); termination-proof witness re-runs a full `fold_node` over each node's subtree = O(M²) | O(M²) per module, ×100 interpreted | Indexed map for `Namespace.bindings`; compute descent proofs compositionally inside the existing bottom-up fold | + +### SUSPICIOUS tier (fix-worthy, lower share) + +- **infer-env:** per-module full-closure `TypeEnvCache` re-materialization — every module holds its own O(C) clone of the shared std core (Θ(N·C), toward Θ(N²·M) on deep chains); `TypeEnv.parents` shares by reference but the parallel cache clones. Store the local delta, resolve through the parent chain. Also: `flatten_visible_bindings` is exponential-on-diamonds but **cold** (witness `flatten_visible_parent_recurses == 0` holds) — a loaded gun, memoize before any re-enable. +- **resolve (v1):** `collect_unit_variant_phantom_matches` (04_resolve.dag:81) still flattens+scans the flat visible set per fallback call — the surviving sibling of the deleted constructor scan, tempered post-ruling (one-level, fallback-only). Invert to a `variant_name → owners` index per env. Generic/alias use-site re-expansion: `substitute_type_slots` re-runs per use-site with no instantiation cache; `resolve_generic_use_decl` recomputed 2-3× per node. +- **infer-core:** `ExprMatch` two-pass arm re-inference — O(A²) enumerate+filter positional lookup (zip instead) + latent 2^nesting re-inference; `infer_record_lit` O(A²) field match + per-use-site field-template rederivation. +- **floor-host:** per-shard cold `build_multi_entry_index` (W× whole-tree walk) and per-claim-unit cold `resolve_entry_graph` — the request-major shape half-retired by S1; the `Rc`→`Arc` frontier (§A.12) retires it. `precompute_whole_tree_published_mock_keys` builds a private duplicate substrate. +- **interpreter (constants that multiply everything):** `match_pattern` re-slices field names from source per arm-eval (the un-memoized sibling of the existing node-ptr caches); ~89-arm string dispatch in `eval_builtin` on **every** user call; `Value::Str(String)` is the only non-Rc payload → deep copy on every bind/read (→ `Rc`). +- **v2 stages, cross-cutting:** `list_snoc_item`/`list_append` fold-accumulation is O(n²) in every stage's child rebuild (prepend+reverse); grammar validation ~O(P³) recomputed per module instead of once per `grammar_digest`. + +### Root-cause clusters (§6: fix the language layer, not the site) + +1. **Wrong caching unit / request-major** — P3, P4, P5, floor per-shard/per-claim: the resolver lesson's first axis, still live in four places. One fix shape: build the index once at the owning scope, O(1) at the use-site. +2. **Per-unit whole-structure scan** — P2, P7, P9, phantom-matches: the second axis. Fix shape: index or cursor. +3. **`Rc`-shared accumulator defeating `make_mut`** — P1: a *codegen/idiom* class, not a site bug. The emitter (or the accumulator convention) must thread moves; audit for other `Rc`-struct-in-fold accumulators at regen time. +4. **Dead/inert memo** — P8: machinery present, nothing flows through it. The §5 "inert lens" failure mode in performance clothing; a memo that exists must have a witness that it *hits*. +5. **Quadratic list accumulation** — snoc/append folds corpus-wide in v2. +6. **Interpreter amplifier** — P6 + Str clones + name re-slicing: constants that multiply every interpreted v2 stage; fixing these is a priority multiplier on everything in cluster 5/P7/P9. + +### Proposed fix order (priced by displaced cost, §6) + +1. **P1** — unblocks the CI compile-clean gate's 10-min budget (the only measured PATHOLOGICAL: >20 min). Verify first by the scaling curve: time `--target dag` at 20/40/89 modules — O(M²) shows ~4× per doubling against typecheck's linear 21s baseline; then fix, then re-curve. +2. **P4** — every discovery/claim resolve pays it ≥2× per entry; the worklist fix is small and the legacy BFS is the in-file template. +3. **P3** — every full-corpus compile pays it during reconcile. +4. **P5** — request-major re-scan in the machinery this PR just built (fold into §A.8's collision-aware binder rework). +5. **P2** — per-module parse floor; the accessor fix is one function + covers the lookahead scan. +6. **P6 + interpreter constants** — multiplies all interpreted work; do before investing in v2-stage fixes. +7. **P7/P8/P9 + snoc sweep** — the v2 lane, in emit → parse → resolve/infer order (P7 is on the live `serialize_target ∘ translate` path). +8. Phantom-match index + SUSPICIOUS residue, in measured-share order once the profiler extension (§B step 3) lands. + +Every fix carries: before/after receipt on the same fixture + a budget witness (§B step 5) so regression is a red gate. diff --git a/docs/plans/regime2-shared-emission-fold.md b/docs/plans/regime2-shared-emission-fold.md index 9abe5b2dec7..60aadd76962 100644 --- a/docs/plans/regime2-shared-emission-fold.md +++ b/docs/plans/regime2-shared-emission-fold.md @@ -27,7 +27,7 @@ One fold replaces three: expected_X = render( project_X_to_doc(authority) , X_protocol ) ``` -- **The emission model** = a generic **layout / document IR** in `std` (the standard pretty-printer `Doc`: `text` / `line` / `nest` / `concat` / `sep`, whatever the faithful minimal set turns out to be). This is the "one concept, every format" carrier. **DFS `std` first** (§6): confirm no existing layout primitive before minting. Note `std.markup.Fragment` (Element/TextNode/RawNode) is a **markup** tree (HTML tags) — a *different* concern from line/indent layout, so it is **not** the shared IR here; confirm and move on. v2's `serialize_target` has layout entangled with grammar rows, so it is not seed-usable here either. A new `std.layout` (or similarly-named) module is therefore net-new concept, not re-invention — but **prove that** by the DFS, don't assume it. +- **The emission model** = a generic **layout / document IR** in `std` (the standard pretty-printer `Doc`: `text` / `line` / `nest` / `concat` / `sep`, whatever the faithful minimal set turns out to be). This is the "one concept, every format" carrier. **DFS `std` first** (§6): confirm no existing layout primitive before minting. Note `std.markup.Fragment` (Element/FragmentText/RawNode) is a **markup** tree (HTML tags) — a *different* concern from line/indent layout, so it is **not** the shared IR here; confirm and move on. v2's `serialize_target` has layout entangled with grammar rows, so it is not seed-usable here either. A new `std.layout` (or similarly-named) module is therefore net-new concept, not re-invention — but **prove that** by the DFS, don't assume it. - **The protocol** = a per-format record of the genuinely format-global spelling knobs (indent unit, comment prefix, newline style — whatever survives after structure moves into the `Doc`). Prefer encoding structure in the `Doc`; keep the protocol record as thin as the formats actually require. If it ends up near-empty because the `Doc` determines everything, that is a fine outcome — say so. - **`project_X_to_doc`** = the per-format projection that builds the `Doc` from the authority, baking any format-specific **text** decisions (e.g. yaml scalar quoting / escaping) into `Doc` text nodes at projection time — **never** into the render fold. diff --git a/docs/plans/resolver-graph-major-design.md b/docs/plans/resolver-graph-major-design.md index 8f499b76099..8e4b3872e14 100644 --- a/docs/plans/resolver-graph-major-design.md +++ b/docs/plans/resolver-graph-major-design.md @@ -13,6 +13,7 @@ DESIGN refs: §2 (minimize redundancy — one concept, every scale), §5 (correc - Cost is wildly non-uniform per module: 2-module closure = **7ms**; 11-module closure (generic-heavy `std.realization_schedule` prefix) = **killed at 5 minutes** (loaded box, ≤2.5× inflation). The expensive modules are exactly the widely-shared ones. (Non-uniformity itself is a separate lane: see resolver-pathology-profile-receipt.md — graph-major removes the *redundancy* factor, not a pathological per-module cost.) - **Phase verdict (isolated, instrumented, 2026-07-04):** with `GUNBC_FLOOR_PHASE_PROFILE` wired into `claim_batch`, the 11-module closure enters `phase=typecheck` at **t≈1.1s** — parse + name-resolution + normalize together are ~1 second — and then sits in typecheck for **13+ minutes and counting**. Inference is ~99.9% of the cold cost; the per-entry uncached global passes are a ~1-second item. Consequences: (a) the pathology lane (which module, why) is the top prize — a `[typecheck-attribution] module=… ms=…` line now names any module over 2s; (b) S1's union-of-global-passes is demoted to a minor cut; (c) the in-process `typed_module_cache` already amortizes the dominant phase across entries *within one index* — the surviving redundancy is per-shard, per-subprocess, per-run cold starts of that cache. Open oddity to settle: a 188-module *superset* closure resolved in under ~10 minutes (contended) while this 11-module subset exceeds 20 — plausibly the discovery path's *advisory* typecheck gate vs the single-entry *strict* gate checking different amounts; one controlled A/B settles it. - Fleet numbers: floor prelude (plan-closure resolve ×2 + interpreted scheduler evals) ran **35+ minutes silent** before the first output line; whole-tree compile-clean ≈ **48 min**. CI job timeout is now **10 minutes** (operator inversion, forcing function) — the floor cannot fit it until this design lands. +- **Post-fix receipts (namespacing ruling §1c implemented): filled at the receipts commit of the engine PR** — ex-pathological module (before → after), whole-tree compile-clean (before → after), floor prelude phase marks vs the 10-minute budget. Placeholder by design; numbers land with the measurement, not the prose. - Prior mechanisms and why they underdelivered — all the same lesson, *wrong unit / wrong key / wrong medium*: - **M1 `walk_memo`** (landed #6008): keyed by **entry**; deduplicates the same gate entry across batches (~105s/run). Zero cross-entry sharing — 543 distinct witness entries get 543 private universes regardless. - **`resolved_graph_cache.rs`** (#4867, dormant): keyed by **entry + compiler binary** (cold on every commit), serialized as **monolithic multi-GiB JSON** (unusable when warm). @@ -37,8 +38,8 @@ Vocabulary mapping (§3 discipline — the concepts already have names here; do | external formulation | existing authority | |---|---| -| Obligation (node × input × context, `demanded_by` witnesses) | `Runnable` + a demand set; the artifact-bearing variant is M2's staged `RunnableCompile` | -| ExecutionKey (node, input digest, artifact digest, config, effect shape) | the Realization content-hash key + `EffectShape` — "one kernel, N handlers" applied to compile steps | +| Obligation (node × input × context, `demanded_by` witnesses) | `Runnable` + a demand set; the module-node payload is M2's staged `RunnableCompile`, dispatched by the layer-2 runner (below) | +| ExecutionKey (node, input digest, artifact digest, config, effect shape) | the Realization content-hash key + `EffectShape` — "one kernel, N handlers" applied to compile steps; a **store** concern (layer 2/3), never a scheduler concern | | antichain frontier / cursor | the executor's batches (`parallel_executor_plan_from_dependencies`) | | typed Receipt instead of log-line truth | receipts + the merge-admission stamp; the `[t+Xs]` phase marks are the interim form | | "witnesses are assertions over graph cuts; run the graph, not the witnesses" | the end-state: witnesses check materialized node values at declared cuts; today's corpus witnesses are leaf assertions whose heavy shared "path" is module resolution itself — so the first obligation corpus the unified cursor runs is **modules** | @@ -46,7 +47,13 @@ Vocabulary mapping (§3 discipline — the concepts already have names here; do Adopted invariant (four terminal states, no fifth): every enrolled witness ends **Checked** (receipt) | **Skipped** (selector proof) | **Refused** (machinery/input absent — loud) | **Not enrolled**. "Silently fell back to something else" is the state that destroyed the gate and is now unrepresentable on the paths converted so far. -The one structural primitive the executor lacks for unification (named by the M2 doc already): the executor computes a *schedule* — `ExecutorJob { node }` is a bare node, `DependencyView { source, dependent, kind, usage_site }` is pure structure — so a dependent runs *after* its dependency but never *consumes an artifact* it produced. Module obligations need exactly that (a module's `ModuleInterface` flowing to its importers' typecheck). **This is an interface generalization, and it belongs to the executor, not to the module lane (operator, 2026-07-04):** add the artifact-bearing job **once, generically** — `ExecutorJob` gains produce/consume *keys* into a content-hash artifact store (the §3 Merkle store), i.e. it becomes the staged `RunnableCompile` shape — and **both** riders adopt it. The CI floor rides it with a **degenerate (unit) artifact** (its gates are ordering-only; nothing downstream reads a gate's output), module resolution with a **`ModuleInterface`** artifact. One executor, N payloads (§2 horizontal, §4 N-models-not-N×M). So the pre-req is not a module-specific blocker to clear before S2a — it *is* the consolidation, and the two lanes are its first two instances. The scheduling logic (`scheduler_plan_from_dependencies` → antichain batches) is genuinely reused unchanged; what's added is the artifact threading, shared by both. +The unification is **three separable layers, not a fattened executor** (operator refinement, 2026-07-04: "artifacting seems like it would be a separate concern, or totally opaque to this scheduling" — the earlier "artifact-bearing executor" framing fused layers 1 and 2 and is superseded): + +1. **Scheduler — payload-agnostic, unchanged.** `scheduler_plan_from_dependencies` folds `DependencyView { source, dependent, kind, usage_site }` rows into antichain batches. It never knows what a node produces or consumes; what flows between nodes is opaque to scheduling. This layer needs zero edits for the module lane — that is the point of calling it general. +2. **Runner + store — where dataflow lives.** A runner walks the schedule, dispatches each node's payload to its realization handler, and records the result in a **store keyed by node identity**. Dependents' handlers read their dependencies' results from the store; the scheduler only guaranteed the order. This store already exists in embryo as the host's `typed_module_cache` (module-name-keyed, in-process) — S2a names it as the one store concept, **key-abstracted**: node-id key in S2a (in-process), Merkle content key in S2b (persistent). A module node's handler produces its `ModuleInterface`; a CI gate's handler produces unit (ordering-only). One runner, N handler/result types (§2 horizontal, §4 N-models-not-N×M). +3. **Content-key cache — S2b only.** The same store re-keyed by content hash (§3 Merkle shape) so results survive across runs; gated on determinism (#5941). Neither the scheduler nor the runner interface changes when this lands — only the store's key function. + +So the pre-req named by the M2 doc ("edges carry no payload") is dissolved in layer 2, not by editing the executor: the CI floor and module resolution are the first two riders of the same runner+store, and the floor migration is behavior-preserving by construction (its results are unit; the receipt is a byte-identical 11-gate plan before/after). ## 1c. Operator ruling (2026-07-04): constructor ambiguity is an ERROR; resolution follows the binding edge @@ -96,13 +103,21 @@ Key shape (Merkle): `key(m) = H(source_bytes(m), key(dep_1), …, key(dep_n), re | Site | Role | |---|---| | `std.realization_schedule` | inhabit the staged `RunnableCompile` node (M2's shape) as the module-node workload: `{ module, source_key, dep_keys, profile }`; the resolver plan is a `RealizationPlan` over these — modeled in v2, realized by the v1 host, same seam as the CI floor | -| `v2.workflow.executor` | **generalized to an artifact-bearing (dataflow) executor** — `ExecutorJob { node }` → carries produce/consume *keys* into the content-hash store; the scheduler (`scheduler_plan_from_dependencies` → antichain batches) is reused verbatim, the artifact threading is the new shared capability. The CI floor rides it with a degenerate unit payload (behavior-identical; a byte-identical 11-gate plan is the migration receipt), module resolution with `ModuleInterface`. One executor, N payloads — the pre-req (§1b) is dissolved here, not module-side | +| `v2.workflow.executor` | **unchanged as scheduler** (§1b layer 1) — `scheduler_plan_from_dependencies` → antichain batches is reused verbatim for module nodes. The new capability is a peer, not an edit: a **runner + node-keyed store** (§1b layer 2) that dispatches scheduled payloads to realization handlers and threads results (unit for CI gates, `ModuleInterface` for modules) from producers to consumers. Byte-identical 11-gate plan is the floor-migration receipt | | `v2.std.dependency` (`DependencyView`) | module import edges as rows — same vocabulary as floor edges | | `module_graph` / `decl_index` (v2) | the model-side edge-graph authority; a witness asserts host `build_multi_entry_index` edges == model edges (kills the dual representation) | | `std.content_hash` | key authority (fnv1a64 convergence thread lands here) | | `gunbc.ci_floor_measurement` / `ci_budget_tree` | width/memory rows for the module lane: peak = frontier × interface size | | `v2.std.determinism` (#5941) | gates ONLY the persistent tier (S2b): serving stored artifacts across runs requires proven determinism; in-process S1 does not | +## 5b. The realization tower (operator, 2026-07-04: "a CPU can't execute Rust either") + +Where Rust sits, stated once so no architecture decision quietly treats it as foundation: + +- A `.dag` program's meaning is fixed by the substrate (§4 DESIGN); *running* it requires a realization — interpretation by a host, or emission through `extdeps/languages/` rows to a downstream toolchain. Rust is not the floor: it is one more surface with its own tower below it (rustc → LLVM → machine code). "The compiler is written in Rust" mis-describes the system — the compiler is the `.dag` graph; the Rust seed is the current **bootstrap row**, a priced choice of first target, regenerated from the graph (RegenVerifyGate) and shrinking to zero (DESIGN §7). +- **The intrinsic kernel is declared, not accidental:** `HAND_MAINTAINED_STAGE0_FILES` (6 files: `cli_run.rs`, `v1_interpreter.rs`, …) + the registered builtins (fnv1a64 primitives in `04_method.dag`) are the current irreducible host surface; the other 92 stage0 files are generated. A capability living in hand-maintained Rust that the model could carry is un-migrated modeling (§6 DESIGN: one fold, a named kernel, or un-migrated — no third), not "infrastructure." +- **Realization-scoped decisions carry dissolve-ons.** `Rc` being `!Send` → per-shard workers (§7 S1) is a fact about this realization, not the model; its dissolve-on is the `Arc`/store migration. The same discipline as every 🟡 scaffold: the model never inherits a constraint that only a realization owns. + ## 6. Oracles & red controls (all execute) 1. **Purity oracle — LANDED (S1).** Union-view result == private (request-major) result, byte-identical claim outcomes, for a sampled witness set, in every resolve order: `union_view_result_equals_private_resolve_in_every_order` (v1-compiler-tests), on top of the pre-existing `typed_module_cache_matches_cold_oracle_in_every_order` (cached == cold) and `memo_warm_cold_results_are_identical` (warm == cold). Red control is built in: a divergent shared resolution flips a sampled PASS/FAIL verdict. Interior-mutable leak (order-dependent memo) → the every-order sweep goes red. @@ -119,19 +134,32 @@ Key shape (Merkle): `key(m) = H(source_bytes(m), key(dep_1), …, key(dep_n), re *Realization (host, `src/v1/stage0`).* The mechanism is ONE shared `MultiEntryIndex` per (thread, source_roots), so the union of every consumer's closure shares one `parse_cache` + `typed_module_cache` — the shared std/spec prefix typechecks once per node, not once per consumer. Concretely: (a) `resolve_entry_graph_shared` now resolves through a thread-local shared index (`process_shared_index`) instead of a fresh per-call module index, so the executor prelude (plan entry + `install_output_policy` + `install_group_syntax`) unions; (b) `run_discovery_corpus_with_options` builds one index threaded through frontier attribution, the floor-runner context, and every roster row (the floor runner is now `resolve_entry_with_index(&index, FLOOR_RUNNER_ENTRY)`, not a private resolve); (c) each parallel shard resolves its floor runner + its rows against its OWN single index — the within-shard private re-resolve is gone. The switch to the index path is proven behaviorally identical to the cold `resolve_entry_graph` by `resolve_typed_cache_equivalence_test` (cached == cold in every resolve order); the union receipts (§6) extend that to the once-per-node and collision-honesty properties. **`Rc` thread boundary — SETTLED for S1 (evaluate-per-shard, not evaluate-prefix-then-fanout):** `Rc` is `!Send`, so the shared index cannot cross the shard boundary without the `Rc`→`Arc` migration (out of host-only scope). So the once-per-node contract holds **per shard** (each shard's cost ≤ 1× its shard-union, the true minimum upper bound under the `Rc` constraint), and the residual cross-shard duplication of the shared prefix — bounded by shard width W, not entry count N — is the S2b/`Arc` frontier. This is the deliberate interim: S1 collapses the per-entry N× to per-shard 1×; S2b/S3 collapse the per-shard W× to whole-run once. -**S2a — general executor + module nodes, still in-process (operator framing, 2026-07-04: "one general executor, both lanes on it").** Two moves, in order, landing after S1 merges: +**S2a — runner + store beside the untouched scheduler, still in-process (§1b three-layer split; operator: "one general executor, both lanes on it" / "artifacting is opaque to scheduling").** Two moves, in order, landing after S1 merges: - 1. **Generalize the executor (the consolidation).** Turn `ExecutorJob { node }` into the artifact-bearing job (the staged `RunnableCompile` shape: `{ node, produces: ArtifactKey, consumes: List }`) and pair the executor with a content-hash-keyed artifact store (§3). Scheduling is untouched — `scheduler_plan_from_dependencies` still folds `DependencyView` edges into antichain batches; the new capability is *threading each node's produced artifact to its consumers by key*. **Migrate the CI floor onto it first**, with a **degenerate unit artifact** — its gates are ordering-only, so this is behavior-preserving and its receipt is a **byte-identical 11-gate plan** before/after. This lands the pre-req (§1b) generically, exercised by the lane that already exists. - 2. **Put module resolution on it (the deep change).** Split `compile_to_resolved` at module boundaries (`ModuleInterface`/`ModuleBody`); a module node *produces* its `ModuleInterface`, *consumes* its imports' interfaces (import edges are `DependencyView` rows sourced from the `module_graph` lens, keyed via `std.content_hash`). Now the SAME executor schedules module typecheck as topo antichains × Altra cores — the 48-minute single-threaded whole-tree resolve becomes parallel — and the S1 union monolith is the degenerate one-batch plan it dissolves into. The Rust `resolve_entry_with_index` / `reconcile_with_typed_cache` become the realization *handler* (read file, run `typecheck_module`) behind the modeled schedule, not the policy owner. + 1. **Land the runner + node-keyed store (the consolidation).** The scheduler is not edited — `scheduler_plan_from_dependencies` still folds `DependencyView` edges into antichain batches over bare nodes. The new layer-2 capability: a runner that walks those batches, dispatches each node's payload to its realization handler, and records the result in the node-keyed store; a dependent's handler reads its dependencies' results from the store. **Migrate the CI floor onto it first** — gate handlers return unit (ordering-only), so this is behavior-preserving and its receipt is a **byte-identical 11-gate plan** before/after. This lands the §1b pre-req generically, exercised by the lane that already exists. + 2. **Put module resolution on it (the deep change).** Split `compile_to_resolved` at module boundaries (`ModuleInterface`/`ModuleBody`); a module node's handler consumes its imports' interfaces from the store and produces the module's `ModuleInterface` (import edges are `DependencyView` rows sourced from the `module_graph` lens). Now the SAME scheduler schedules module typecheck as topo antichains × Altra cores — the 48-minute single-threaded whole-tree resolve becomes parallel — and the S1 union monolith is the degenerate one-batch plan it dissolves into. The Rust `resolve_entry_with_index` / `reconcile_with_typed_cache` become the realization *handler* (read file, run `typecheck_module`) behind the modeled schedule, not the policy owner; `typed_module_cache` is the store's S2a embryo. - Not gated on determinism (#5941) — that gates only S2b's cross-run persistence; the in-process general executor + module lane land without it. + Not gated on determinism (#5941) — that gates only S2b's re-keying of the store by content hash; the in-process runner + module lane land without it. - **Open decisions for the consolidation (to settle before authoring):** (a) artifact lives in a content-keyed store with the job carrying *keys* (rustc `rmeta` model, aligns with §3), not inline on the edge — confirm; (b) CI gates stay ordering-only (unit artifact) for the migration, with outcome-consuming (skip-if-upstream-green) left as a later opt-in — confirm; (c) who computes the 5.5k-node module schedule — the interpreted `.dag` executor as the authority vs. the host computing batches and the `.dag` witnessing them (the §4/§7 authority-vs-realization seam); schedule computation is cheap next to typecheck (99.9%) and runs once, so `.dag`-authority is the default unless measured otherwise. + **Consolidation decisions (settled with the three-layer split, 2026-07-04):** (a) results live in the store and jobs reference them by node identity — keys are a store concern, invisible to the scheduler; the Merkle content key is S2b's key function swap, not a job-shape change; (b) CI gates stay ordering-only (unit result) for the migration, outcome-consuming (skip-if-upstream-green) is a later opt-in; (c) schedule authority: the interpreted `.dag` executor computes the 5.5k-node module schedule (cheap next to typecheck, runs once), the host is the runner realization — `.dag`-authority default stands unless measured otherwise. **S2b — persistence across runs (gated on #5941).** Store node artifacts by Merkle key; a PR's run evaluates changed modules + dependents + uncached leaves. This — not S1 — is what makes the 10-minute budget comfortable rather than merely reachable; S1 makes a run cost ~1× union-resolve instead of ~N×, S2b makes warm runs cost ~Δ. **S3 — shared store across runner slots.** A Realization handler change (local disk → shared), no new semantics. +## 7b. Emitter restoration ledger (2026-07-04, discovered by the constructor-ruling PR's regen attempt) + +RegenVerifyGate's red-by-timeout had been masking that **regen was semantically broken**: the seed on main could not be reproduced by the current emitter, and B1/B2 co-landed hand-edited generated files because of it. The constructor-ruling PR fixed what it could and ledgers the rest; every item below has an execution receipt from the regen probes: + +1. **FIXED — statement-then-expression blocks** emitted without a separator (invalid Rust). Only corpus instances were the B1 env-counter statements; dissolved with the counters (their pathology is dead). +2. **FIXED — `needs_box_wrapping` ordering**: recursive→Box short-circuited before the shared(Rc)→no-Box check; benign while emit-time recursive_types stayed module-local, a full ABI break (`Box>` ×150 fields) once B1's build_type_env propagated imported recursive types. shared now dominates (dag + seed two-step). +3. **FIXED — `phase_profile.rs`** unregistered in HAND_MAINTAINED_STAGE0_FILES (fresh-crate assembly failed). +4. **OPEN — deref-side boxing asymmetry**: field-ACCESS emission still derefs per the old boxing policy while declarations honor the new one (~800 `Option> cannot be dereferenced` errors); the deref decision lives in a second site that must consult the same `needs_box_wrapping` authority (§3: one boxing authority, N consumers). +5. **OPEN — alias-brand rendering**: signatures render the DEALIASED grounding (`Nat` → `Rc>`, ~250 errors) instead of the authored alias the committed seed carries — drift from the Nat-grounding era, never propagated through a green regen. +6. **OPEN — misc residue** (~80 errors: move-out-of-shared-ref, `?`-on-non-Try, annotations) — classify after 4–5 land. + +Until 4–6 land, the seed remains the functionally-verified hand-patched realization (all suites + witnesses green by execution); `bootstrap_fixed_point` and byte-identity are the restoration PR's acceptance gates, with `--emit-fresh` probes (~2 min each, post-pathology) as the loop. + ## 8. Dissolution triggers - **S1 interim contract (the operator's ship-now expectations, held by the §6 receipts):** (a) minimum upper bound — a process's resolve cost is ≤ 1× its union closure (per-shard under the `Rc` constraint; §7), enforced by the once-per-node counter, *now enforced not aspired*; (b) no feature growth — S1 must dissolve into S2a's parallel module-obligation architecture, not accrete knobs that delay it; (c) frontier-window memory is S2a's, not S1's (S1 keeps the monolith's retention). S1 landing does NOT close this doc — it is the interim rung. diff --git a/src/v1/02_parse.dag b/src/v1/02_parse.dag index 06b0e3cc9c4..27f0494e5fc 100644 --- a/src/v1/02_parse.dag +++ b/src/v1/02_parse.dag @@ -40,7 +40,7 @@ import v1.std.core { ShStrBegin, ShStrMid, ShStrEnd, ShNewline, ShEof, ShUnknown, SourceSpan, make_span, - ErrorNode, make_error_node, ParseError, + ErrorNode, make_error_node, ParseError, InternalError, with_required_cardinality, error_type, is_compiler_error, node_name_span, no_span, diff --git a/src/v1/04_env.dag b/src/v1/04_env.dag index 51fb68600da..7d0a3b7a807 100644 --- a/src/v1/04_env.dag +++ b/src/v1/04_env.dag @@ -48,13 +48,9 @@ fn build_type_env_calls() -> Int { 0 } fn merge_type_env_cache_calls() -> Int { 0 } fn rewire_type_env_parent_links_calls() -> Int { 0 } fn rewire_type_env_import_str_binding_calls() -> Int { 0 } -fn record_build_type_env_call() -> Unit { } -fn record_merge_type_env_cache_call() -> Unit { } -fn record_rewire_type_env_parent_links_call() -> Unit { } -fn record_rewire_type_env_import_str_binding_call() -> Unit { } +data type_env_counter_dissolution: String = "The 2026-07-04 use-site inference pathology instrument (env-amplification counters) dissolves with the pathology it measured: the record_* statement calls and their stub fns are deleted (they were also the only corpus instances of a statement-then-expression block the Rust emitter mis-renders — that emitter deficiency is ledgered with the emit-stage work). The Int getter stubs below stay for API compatibility with the hand-maintained phase_profile heartbeat until that consumer drops them." fn merge_type_env_cache(base: TypeEnvCache, overlay: TypeEnvCache) -> TypeEnvCache { - record_merge_type_env_cache_call() TypeEnvCache { deps_map: map_merge(base.deps_map, overlay.deps_map), str_bindings: map_merge(base.str_bindings, overlay.str_bindings), @@ -217,7 +213,6 @@ fn flatten_visible_parent_recurses() -> Int { 0 } fn reset_flatten_visible_profile() -> Unit { } fn reset_type_env_lookup_profile() -> Unit { reset_flatten_visible_profile() } fn maybe_print_type_env_lookup_profile() -> Unit { } -fn record_flatten_visible_parent_recurse() -> Unit { } fn flatten_visible_bindings(env: TypeEnv) -> Map { let from_parents = fold(env.parents, init: empty_map(), f: (acc, parent) => diff --git a/src/v1/04_infer.dag b/src/v1/04_infer.dag index 38488601aa7..890ce75458e 100644 --- a/src/v1/04_infer.dag +++ b/src/v1/04_infer.dag @@ -6,7 +6,7 @@ import v1.std.core { Node, InternTable, intern, intern_str, authored_name_at, generic_param_name_at, NewlineIndex, find_child_named, has_child_named, build_newline_index, - module_node, module_imports, module_items, import_is_all, + module_node, module_imports, module_items, import_is_all, import_specific_names_at, make_param_node, param_node_name_at, param_node_type_expr, field_node_type_expr, field_node_name_at, Connective, Conj, Disj, NoConnective, Arrow, @@ -22,7 +22,7 @@ import v1.std.core { has_inferred, is_compiler_error, InferredNode, Resolved, CompilerError, TypeVariable, Cardinality, Required, CardOptional, ErrorNode, make_error_node, is_error_diagnostic, - InternalError, TypeMismatch, VariantCollision, SoleConstructorViolation, + InternalError, TypeMismatch, VariantCollision, SoleConstructorViolation, UnresolvedType, FieldNotFound, resource_use_name_at, resource_use_resource, kernel_type_set, is_kernel_type, is_child_accessor_in_model, is_tree_size_reducing, is_property_contraction, @@ -83,11 +83,9 @@ import v1.compiler.infer_env { str_bindings_from_bindings, build_type_env_calls, merge_type_env_cache_calls, rewire_type_env_parent_links_calls, rewire_type_env_import_str_binding_calls, - record_build_type_env_call, record_merge_type_env_cache_call, - record_rewire_type_env_parent_links_call, record_rewire_type_env_import_str_binding_call, is_recursive_type, is_recursive_type_by_name, lookup_type, lookup_type_by_name, lookup_type_for, merge_envs, put_inductive_field, put_inductive_field_cross, merge_inductive_fields, inductive_fields_for, - inductive_fields_list_to_map, flatten_visible_bindings + inductive_fields_list_to_map } import std.node { compiler_inductive_fields, compiler_recursive_types } import std.induction { @@ -123,7 +121,7 @@ import v1.compiler.infer_emit_info { import v1.compiler.infer_items { ItemKind, FnItem, FuncItem, TypeItem, DataItem, ServiceItem, OtherItem, ItemInfo, TypedModule, TypedGraph, ResolvedGraph, - inferred_to_outputs, item_kind, variant_locals_from_items + inferred_to_outputs, item_kind } import v1.compiler.infer_service { UniqueAccum, OpEntry, ServiceMethodResult, @@ -489,6 +487,25 @@ fn lookup_variant_parent_enum(scope: InferScope, name: String) -> String? { } } +data variant_owner_node_authority: String = "Constructor-owner ruling (DESIGN resolver-graph-major §1c, 2026-07-04): a constructor literal's owner is the BINDING EDGE — scope.locals binds the arm name to its owning coproduct node at env construction (local declaration or direct import), so owner lookup is O(1) map_get + dealias. No whole-environment scan, no expected-type owner-picking, no name round-trip through lookup_type_by_name (the owner node rides the binding even when the enum's name is not itself visible). Unbound constructor literal = UnresolvedType error; two visible owners for one arm name = VariantCollision at env construction." + +fn variant_owner_node(scope: InferScope, name: String) -> Node? { + match map_get(scope.locals, name) { + Present { value: binding } => + match lookup_type_for(env: scope.type_env, node: binding.resolved) { + Present { value: owner } => + let owner_is_coproduct = owner.connective == Disj + if owner_is_coproduct { + if has_child_named(n: owner, name: name, source_indices: scope.type_env.source_indices) { + Present { value: owner } + } else { none } + } else { none } + Absent => none + } + Absent => none + } +} + fn infer_var_binding_kind(scope: InferScope, name: String) -> VarBindingKind { match lookup_variant_parent_enum(scope: scope, name: name) { Present { value: parent_enum } => VariantValueBinding { parent_enum: parent_enum } @@ -502,23 +519,6 @@ fn infer_var_binding_kind(scope: InferScope, name: String) -> VarBindingKind { } } -fn expected_type_override_enum(scope: InferScope, name: String, scope_enum: String, expected: Node?) -> String? { - match expected { - Present { value: exp_node } => - let exp_name = authored_name_at(source_indices: scope.type_env.source_indices, node: exp_node) - if exp_name != "" && exp_name != scope_enum && exp_name != name { - match lookup_type_by_name(env: scope.type_env, name: exp_name) { - Present { value: exp_type } => - if exp_type.connective == Disj && has_child_named(n: exp_type, name: name, source_indices: scope.type_env.source_indices) { - Present { value: exp_name } - } else { none } - Absent => none - } - } else { none } - Absent => none - } -} - fn inference_error(message: String, span: SourceSpan, module_name: String) -> ErrorNode { make_error_node(diagnostic: InternalError { message: message, span: span }, module_name: module_name) } @@ -547,50 +547,19 @@ fn field_type_is_optional_coproduct(ft: Node, env: TypeEnv) -> Bool { ft.return_cardinality == CardOptional && inner.connective == Disj } -fn record_lit_variant_fields_from_visible_env(env: TypeEnv, variant_name: String, module_name: String) -> List { - flatten_visible_bindings(env: env) |> map_values |> fold(init: [], f: (acc, binding) => - if acc |> count > 0 { - acc - } else { - let node = expand_type_for_field_access(n: binding.resolved, env: env, module_name: module_name) - let is_disj = node.connective == Disj - if is_disj { - match node.children |> filter(arm => authored_name_at(source_indices: env.source_indices, node: arm) == variant_name) |> first { - Present { value: arm } => { - let expanded = expand_type_for_field_access(n: arm, env: env, module_name: module_name) - let expanded_is_conj = expanded.connective == Conj - if expanded_is_conj { - expanded.children - } else { - arm.children - } - } - Absent => acc - } - } else { - acc - } - } - ) -} - fn record_lit_expected_fields(type_name: String?, scope: InferScope) -> List { match type_name { Present { value: tn } => match lookup_type_by_name(env: scope.type_env, name: tn) { Present { value: direct } => direct.children Absent => - match lookup_variant_parent_enum(scope: scope, name: tn) { - Present { value: parent_name } => - match lookup_type_by_name(env: scope.type_env, name: parent_name) { - Present { value: parent } => - match parent.children |> filter(v => authored_name_at(source_indices: scope.type_env.source_indices, node: v) == tn) |> first { - Present { value: variant } => variant.children - Absent => [] - } + match variant_owner_node(scope: scope, name: tn) { + Present { value: parent } => + match parent.children |> filter(v => authored_name_at(source_indices: scope.type_env.source_indices, node: v) == tn) |> first { + Present { value: variant } => variant.children Absent => [] } - Absent => record_lit_variant_fields_from_visible_env(env: scope.type_env, variant_name: tn, module_name: scope.module_name) + Absent => [] } } Absent => [] @@ -1387,24 +1356,11 @@ fn infer_expr(texpr: Node, scope: InferScope, expected: Node?) -> InferResult { let scope_parent = lookup_variant_parent_enum(scope: scope, name: name) match scope_parent { Present { value: scope_enum } => - let override_enum = expected_type_override_enum(scope: scope, name: name, scope_enum: scope_enum, expected: expected) - let effective_enum = match override_enum { - Present { value: ov } => ov - Absent => scope_enum - } - let effective_resolved = match override_enum { - Present { value: ov } => - match lookup_type_by_name(env: scope.type_env, name: ov) { - Present { value: t } => t - Absent => binding.resolved - } - Absent => binding.resolved - } ok_infer(texpr: make_named_expr_node( name: name, - expr_data: ExprVar { binding_kind: Present { value: VariantValueBinding { parent_enum: effective_enum } } }, + expr_data: ExprVar { binding_kind: Present { value: VariantValueBinding { parent_enum: scope_enum } } }, children: [], - inferred: Present { value: Resolved { node: effective_resolved } }, + inferred: Present { value: Resolved { node: binding.resolved } }, span: span, name_span: span )) @@ -2884,18 +2840,17 @@ fn record_lit_construction_field_names(type_name: String, scope: InferScope) -> fn field_in_any_variant_named(type_name: String, field: String, scope: InferScope) -> Bool { let si = scope.type_env.source_indices - flatten_visible_bindings(env: scope.type_env) |> map_values |> any(binding => - let node = binding.resolved - if (node.connective == Disj) { - node.children |> any(arm => + match variant_owner_node(scope: scope, name: type_name) { + Present { value: owner } => + owner.children |> any(arm => if authored_name_at(source_indices: si, node: arm) == type_name { let ea = expand_type_for_field_access(n: arm, env: scope.type_env, module_name: scope.module_name) let fields = if (ea.connective == Conj) { ea.children } else { arm.children } fields |> any(f => authored_name_at(source_indices: si, node: f) == field) } else { false } ) - } else { false } - ) + Absent => false + } } fn type_has_sole_constructor(type_name: String, scope: InferScope) -> Bool { @@ -2908,26 +2863,21 @@ fn type_has_sole_constructor(type_name: String, scope: InferScope) -> Bool { fn infer_record_lit(type_name: String?, field_inits: List, span: SourceSpan, name_span: SourceSpan, scope: InferScope, expected: Node?) -> InferResult { let struct_fields = match type_name { Present { value: tn } => - let visible_fields = record_lit_variant_fields_from_visible_env(env: scope.type_env, variant_name: tn, module_name: scope.module_name) - if visible_fields |> count > 0 { - visible_fields - } else { - match record_lit_instantiated_fields(type_name: type_name, expected: expected, scope: scope) { - Present { value: fields } => fields - Absent => - match record_lit_fields_from_expected(type_name: type_name, expected: expected, scope: scope) { - Present { value: fields } => fields - Absent => - match expected { - Present { value: _ } => record_lit_expected_fields(type_name: type_name, scope: scope) - Absent => - match record_lit_alias_struct_fields(type_name: tn, scope: scope) { - Present { value: fields } => fields - Absent => record_lit_expected_fields(type_name: type_name, scope: scope) - } - } - } - } + match record_lit_instantiated_fields(type_name: type_name, expected: expected, scope: scope) { + Present { value: fields } => fields + Absent => + match record_lit_fields_from_expected(type_name: type_name, expected: expected, scope: scope) { + Present { value: fields } => fields + Absent => + match expected { + Present { value: _ } => record_lit_expected_fields(type_name: type_name, scope: scope) + Absent => + match record_lit_alias_struct_fields(type_name: tn, scope: scope) { + Present { value: fields } => fields + Absent => record_lit_expected_fields(type_name: type_name, scope: scope) + } + } + } } Absent => match record_lit_instantiated_fields(type_name: type_name, expected: expected, scope: scope) { @@ -3054,9 +3004,8 @@ fn infer_record_lit(type_name: String?, field_inits: List, span: SourceSpa } else { raw_resolved } let type_diags = match effective_lookup { Present { value: _ } => [] - Absent => [inference_error( - message: concat("type '", type_name.value, "' not found in scope"), - span: span, + Absent => [make_error_node( + diagnostic: UnresolvedType { name: type_name.value, span: span }, module_name: scope.module_name )] } @@ -5551,62 +5500,24 @@ fn union_parent_type_env_caches( ) } -fn union_parent_variant_locals( - resolved_imports: List, - parent_index: Map -) -> Map { - fold(resolved_imports, init: empty_map(), f: (acc, imp) => - match map_get(parent_index, imp.module_path) { - Present { value: parent } => map_merge(acc, parent.type_env_cache.variant_locals) - Absent => acc - } - ) -} - -fn fold_local_coproduct_variant_locals( - bindings: Map, - imported_variants: Map, - source_indices: Map, - init_locals: Map -) -> VariantFoldState { - fold( - bindings |> map_values |> sort_by(b => b.name), - init: VariantFoldState { locals: init_locals, collision_errors: [] }, - f: (acc, binding) => - let is_coproduct = binding.resolved.connective == Disj - if is_coproduct { - let binding_enum_name = authored_name_at(source_indices: source_indices, node: binding.resolved) - fold(binding.resolved.children, init: acc, f: (vacc, child) => - let child_name = authored_name_at(source_indices: source_indices, node: child) - let curr_child_is_imported = is_imported_variant_owned_by(imported_variants: imported_variants, variant_name: child_name, enum_name: binding_enum_name) - match map_get(vacc.locals, child_name) { - Present { value: _prev } => - if curr_child_is_imported { - VariantFoldState { - locals: map_insert(vacc.locals, child_name, TypeBinding { name: child_name, resolved: binding.resolved, provenance: SubValueUnknown }), - collision_errors: vacc.collision_errors - } - } else { vacc } - Absent => - VariantFoldState { - locals: map_insert(vacc.locals, child_name, TypeBinding { name: child_name, resolved: binding.resolved, provenance: SubValueUnknown }), - collision_errors: vacc.collision_errors - } - } - ) - } else { acc } - ) -} - fn kernel_coproduct_variant_locals(env: TypeEnv) -> Map { match env.parents |> last { Present { value: kernel } => - fold_local_coproduct_variant_locals( - bindings: kernel.bindings, - imported_variants: empty_map(), - source_indices: env.source_indices, - init_locals: empty_map() - ).locals + fold( + kernel.bindings |> map_values |> sort_by(b => b.name), + init: empty_map(), + f: (acc, binding) => + let is_coproduct = binding.resolved.connective == Disj + if is_coproduct { + fold(binding.resolved.children, init: acc, f: (vacc, child) => + let child_name = authored_name_at(source_indices: env.source_indices, node: child) + match map_get(vacc, child_name) { + Present { value: _prev } => vacc + Absent => map_insert(vacc, child_name, TypeBinding { name: child_name, resolved: binding.resolved, provenance: SubValueUnknown }) + } + ) + } else { acc } + ) Absent => empty_map() } } @@ -5672,8 +5583,8 @@ fn type_env_for_import(module_path: String, parent_env: TypeEnv) -> TypeEnv { } else { parent_env } } + fn build_type_env(module: ResolvedModule, parent_index: Map, source_indices: Map, intern_table: InternTable) -> BuildTypeEnvResult { - record_build_type_env_call() let source_indices = fold(map_keys(kernel_type_set), init: source_indices, f: (acc, name) => map_insert(acc, concat(""), build_newline_index(file: concat(""), source: name)) ) @@ -5843,7 +5754,6 @@ fn build_type_env(module: ResolvedModule, parent_index: Map } fn build_type_env_unresolved(module: ResolvedModule, parent_index: Map, source_indices: Map, intern_table: InternTable) -> BuildTypeEnvResult { - record_build_type_env_call() let intern_table = fold(map_keys(kernel_type_set), init: intern_table, f: (t, name) => intern(table: t, s: name).table) let intern_table = fold(["Optional"], init: intern_table, f: (t, name) => intern(table: t, s: name).table) let zero_span = make_span(start: 0, end: 0) @@ -6068,41 +5978,126 @@ fn fold_module_contributions(remaining: List, } } -fn enum_parents_for_variant_in_items(variant_name: String, items: List, source_indices: Map) -> List { - items - |> filter(item => item.connective == Disj) - |> filter(item => item.children |> any(child => authored_name_at(source_indices: source_indices, node: child) == variant_name)) - |> map(item => authored_name_at(source_indices: source_indices, node: item)) +data constructor_binding_authority: String = "Constructor-owner ruling (§1c): a module's constructor namespace is FLAT — its own coproduct declarations plus its DIRECT imports' contributions, never transitive ancestry. Each arm name binds to its owning coproduct NODE (the binding edge carries the owner; no name re-lookup, so the owner need not be name-visible). One arm name bound to two DIFFERENT owners in one file = VariantCollision at env construction (the per-file wall); the same declaration reached via two import paths is one owner, not a collision. Kernel coproduct arms (Optional/Witness/...) merge low-priority afterwards, prelude semantics." + +fn insert_variant_owner_checked(state: VariantFoldState, arm_name: String, owner: Node, source_indices: Map, module_name: String) -> VariantFoldState { + match map_get(state.locals, arm_name) { + Present { value: prev } => + let prev_name = authored_name_at(source_indices: source_indices, node: prev.resolved) + let owner_name = authored_name_at(source_indices: source_indices, node: owner) + let same_owner = prev_name == owner_name + && prev.resolved.span.file == owner.span.file + && prev.resolved.span.start == owner.span.start + if same_owner { + state + } else { + VariantFoldState { + locals: state.locals, + collision_errors: concat(state.collision_errors, [make_error_node( + diagnostic: VariantCollision { variant: arm_name, enum1: prev_name, enum2: owner_name, span: owner.span }, + module_name: module_name + )]) + } + } + Absent => + VariantFoldState { + locals: map_insert(state.locals, arm_name, TypeBinding { name: arm_name, resolved: owner, provenance: SubValueUnknown }), + collision_errors: state.collision_errors + } + } } -fn unique_imported_variant_owner(variant_name: String, items: List, source_indices: Map) -> String? { - let parents = enum_parents_for_variant_in_items(variant_name: variant_name, items: items, source_indices: source_indices) - if parents |> count == 1 { - parents |> first - } else { none } +fn bind_coproduct_item_arms(state: VariantFoldState, item: Node, source_indices: Map, module_name: String) -> VariantFoldState { + fold(item.children, init: state, f: (vacc, child) => + insert_variant_owner_checked( + state: vacc, + arm_name: authored_name_at(source_indices: source_indices, node: child), + owner: item, + source_indices: source_indices, + module_name: module_name + ) + ) } -fn is_imported_variant_owned_by(imported_variants: Map, variant_name: String, enum_name: String) -> Bool { - match map_get(imported_variants, variant_name) { - Present { value: unique_parent } => unique_parent == enum_name - Absent => false +fn build_local_variants(items: List, source_indices: Map, module_name: String, init: VariantFoldState) -> VariantFoldState { + fold(items, init: init, f: (acc, item) => + let item_is_coproduct = item.connective == Disj + if item_is_coproduct { + bind_coproduct_item_arms(state: acc, item: item, source_indices: source_indices, module_name: module_name) + } else { acc } + ) +} + +data reexport_chain_authority: String = "A module re-exports its specific-name imports (get_exported_names in v1.compiler.resolve), so `import proxy { B }` where proxy itself imports B is an EXPLICIT import under the §1c ruling — the binder follows the specific-name chain to the defining module's coproduct (the import DAG is acyclic, so the recursion terminates; a chain hop that dead-ends binds nothing and the use site fails closed as UnresolvedType). Glob (is_all) imports bind the target's OWN coproduct arms only — glob re-export pass-through is not modeled here, matching get_exported_names which excludes is_all imports from the export surface." + +fn owner_of_exported_arm(module_path: String, arm_name: String, parent_index: Map, source_indices: Map) -> Node? { + match map_get(parent_index, module_path) { + Absent => none + Present { value: tm } => + let own = tm.items + |> filter(item => item.connective == Disj) + |> filter(item => item.children |> any(c => authored_name_at(source_indices: source_indices, node: c) == arm_name)) + |> first + match own { + Present { value: item } => Present { value: item } + Absent => + let reexport = module_imports(n: tm.module) + |> filter(imp => !import_is_all(n: imp)) + |> filter(imp => import_specific_names_at(n: imp, source_indices: source_indices) |> any(n => n == arm_name)) + |> first + match reexport { + Present { value: imp } => owner_of_exported_arm(module_path: authored_name_at(source_indices: source_indices, node: imp), arm_name: arm_name, parent_index: parent_index, source_indices: source_indices) + Absent => none + } + } } } -fn build_imported_variants(resolved_imports: List, parent_index: Map, source_indices: Map) -> Map { - fold(resolved_imports, init: empty_map(), f: (acc, imp) => - fold(imp.specific_names, init: acc, f: (inner, variant_name) => - if map_contains_key(inner, variant_name) { - inner - } else { - let source_items = match map_get(parent_index, imp.module_path) { - Present { value: parent_tm } => parent_tm.items - Absent => [] - } - match unique_imported_variant_owner(variant_name: variant_name, items: source_items, source_indices: source_indices) { - Present { value: parent } => map_insert(inner, variant_name, parent) - Absent => inner - } +fn exported_coproduct_item(module_path: String, enum_name: String, parent_index: Map, source_indices: Map) -> Node? { + match map_get(parent_index, module_path) { + Absent => none + Present { value: tm } => + let own = tm.items + |> filter(item => item.connective == Disj) + |> filter(item => authored_name_at(source_indices: source_indices, node: item) == enum_name) + |> first + match own { + Present { value: item } => Present { value: item } + Absent => + let reexport = module_imports(n: tm.module) + |> filter(imp => !import_is_all(n: imp)) + |> filter(imp => import_specific_names_at(n: imp, source_indices: source_indices) |> any(n => n == enum_name)) + |> first + match reexport { + Present { value: imp } => exported_coproduct_item(module_path: authored_name_at(source_indices: source_indices, node: imp), enum_name: enum_name, parent_index: parent_index, source_indices: source_indices) + Absent => none + } + } + } +} + +fn build_imported_variants(resolved_imports: List, parent_index: Map, source_indices: Map, module_name: String, init: VariantFoldState) -> VariantFoldState { + fold(resolved_imports, init: init, f: (acc, imp) => + let with_glob = if imp.is_all { + let source_items = match map_get(parent_index, imp.module_path) { + Present { value: parent_tm } => parent_tm.items + Absent => [] + } + fold(source_items, init: acc, f: (iacc, item) => + let item_is_coproduct = item.connective == Disj + if item_is_coproduct { + bind_coproduct_item_arms(state: iacc, item: item, source_indices: source_indices, module_name: module_name) + } else { iacc } + ) + } else { acc } + fold(imp.specific_names, init: with_glob, f: (nacc, name) => + let after_enum = match exported_coproduct_item(module_path: imp.module_path, enum_name: name, parent_index: parent_index, source_indices: source_indices) { + Present { value: item } => bind_coproduct_item_arms(state: nacc, item: item, source_indices: source_indices, module_name: module_name) + Absent => nacc + } + match owner_of_exported_arm(module_path: imp.module_path, arm_name: name, parent_index: parent_index, source_indices: source_indices) { + Present { value: owner } => insert_variant_owner_checked(state: after_enum, arm_name: name, owner: owner, source_indices: source_indices, module_name: module_name) + Absent => after_enum } ) ) @@ -6123,63 +6118,21 @@ fn build_module_context(contributions: List, diag_chunks: [], source_indices: env.source_indices ) - let imported_variants = build_imported_variants(resolved_imports: resolved_imports, parent_index: parent_index, source_indices: env.source_indices) - let ancestry_cache = union_parent_type_env_caches(resolved_imports: resolved_imports, parent_index: parent_index) - let local_str_bindings = fold(env.bindings |> map_values, init: empty_map(), f: (acc, b) => - map_insert(acc, b.name, b) - ) - let bindings_for_variants = map_merge(ancestry_cache.str_bindings, local_str_bindings) - let variant_fold = fold( - bindings_for_variants |> map_values |> sort_by(b => b.name), - init: VariantFoldState { locals: empty_map(), collision_errors: [] }, - f: (acc, binding) => - let is_coproduct = binding.resolved.connective == Disj - if is_coproduct { - let binding_enum_name = authored_name_at(source_indices: env.source_indices, node: binding.resolved) - fold(binding.resolved.children, init: acc, f: (vacc, child) => - let child_name = authored_name_at(source_indices: env.source_indices, node: child) - let curr_child_is_imported = is_imported_variant_owned_by(imported_variants: imported_variants, variant_name: child_name, enum_name: binding_enum_name) - match map_get(vacc.locals, child_name) { - Present { value: _prev } => - if curr_child_is_imported { - VariantFoldState { - locals: map_insert(vacc.locals, child_name, TypeBinding { name: child_name, resolved: binding.resolved, provenance: SubValueUnknown }), - collision_errors: vacc.collision_errors - } - } else { vacc } - Absent => - VariantFoldState { - locals: map_insert(vacc.locals, child_name, TypeBinding { name: child_name, resolved: binding.resolved, provenance: SubValueUnknown }), - collision_errors: vacc.collision_errors - } - } - ) - } else { acc } + let local_variant_fold = build_local_variants( + items: local.resolved_items, + source_indices: env.source_indices, + module_name: module_name, + init: VariantFoldState { locals: empty_map(), collision_errors: [] } ) - let imported_variant_locals = merge_kernel_variant_locals_low_priority(env: env, locals: variant_fold.locals) - let imported_variant_locals = fold(resolved_imports, init: imported_variant_locals, f: (acc, imp) => - fold(imp.specific_names, init: acc, f: (inner, variant_name) => - if map_contains_key(inner, variant_name) { - inner - } else { - match map_get(imported_variants, variant_name) { - Present { value: enum_name } => - match lookup_type_by_name(env: env, name: enum_name) { - Present { value: enum_node } => - map_insert(inner, variant_name, TypeBinding { - name: variant_name, - resolved: enum_node, - provenance: SubValueUnknown - }) - Absent => inner - } - Absent => inner - } - } - ) + let variant_fold = build_imported_variants( + resolved_imports: resolved_imports, + parent_index: parent_index, + source_indices: env.source_indices, + module_name: module_name, + init: local_variant_fold ) let variant_collision_errors = variant_fold.collision_errors - let env_variant_locals = variant_locals_from_items(items: local.resolved_items, init: imported_variant_locals, source_indices: env.source_indices) + let env_variant_locals = merge_kernel_variant_locals_low_priority(env: env, locals: variant_fold.locals) let merged_scope = merge_scope_from_imports( remaining: resolved_imports, parent_index: parent_index, @@ -6658,7 +6611,6 @@ fn rewire_inherited_str_binding( } fn rewire_type_env_import_str_binding_identity(modules: List, source_indices: Map) -> List { - record_rewire_type_env_import_str_binding_call() let index = fold(modules, init: empty_map(), f: (acc, m) => map_insert(acc, authored_name_at(source_indices: source_indices, node: m.module), m) ) @@ -6728,7 +6680,6 @@ fn compiler_kernel_type_env(source_indices: Map, intern_ta } fn rewire_type_env_parent_links(modules: List, source_indices: Map) -> List { - record_rewire_type_env_parent_links_call() let index = fold(modules, init: empty_map(), f: (acc, m) => map_insert(acc, authored_name_at(source_indices: source_indices, node: m.module), m) ) diff --git a/src/v1/04_items.dag b/src/v1/04_items.dag index 87688cb64ce..9053858322f 100644 --- a/src/v1/04_items.dag +++ b/src/v1/04_items.dag @@ -15,9 +15,8 @@ import v1.std.core { import v1.compiler.infer_types { child_type_node } -import std.induction { SubValueUnknown } import v1.compiler.infer_env { - TypeEnv, TypeBinding, TypeEnvCache, empty_type_env_cache + TypeEnv, TypeEnvCache, empty_type_env_cache } import v1.compiler.infer_sigs { ResolvedFuncEnv @@ -110,18 +109,3 @@ fn item_kind(item: Node) -> ItemKind { kind } -fn variant_locals_from_items(items: List, init: Map, source_indices: Map) -> Map { - fold(items, init: init, f: (acc, item) => - let is_coproduct = item.connective == Disj - if is_coproduct { - fold(item.children, init: acc, f: (vacc, child) => - let child_name = authored_name_at(source_indices: source_indices, node: child) - map_insert(vacc, child_name, TypeBinding { - name: child_name, - resolved: item, - provenance: SubValueUnknown - }) - ) - } else { acc } - ) -} diff --git a/src/v1/04_resolve.dag b/src/v1/04_resolve.dag index 8c71a5077d9..39486314863 100644 --- a/src/v1/04_resolve.dag +++ b/src/v1/04_resolve.dag @@ -37,7 +37,7 @@ import v1.compiler.infer_types { } import std.induction { SubValueUnknown } import v1.compiler.infer_env { - TypeEnv, TypeBinding, lookup_type, lookup_type_by_name, lookup_type_for, is_recursive_type, is_recursive_type_by_name, is_recursive_type_for, authored_name, flatten_visible_bindings + TypeEnv, TypeBinding, lookup_type, lookup_type_by_name, lookup_type_for, is_recursive_type, is_recursive_type_by_name, is_recursive_type_for, authored_name } fn is_unit_variant_node(variant: Node) -> Bool { @@ -76,8 +76,13 @@ fn lookup_unit_variant_phantom_type(env: TypeEnv, variant_name: String) -> Node? } else { none } } +data phantom_match_flat_scope: String = "Constructor-owner ruling (§1c): the nullary-variant-as-type fallback searches the FLAT visible set — the module's own str_bindings plus its direct parents' str_bindings, one level, never the recursive ancestry flatten. Unique match resolves; anything else falls through to the UnresolvedType error at the caller (fail-closed)." + fn collect_unit_variant_phantom_matches(env: TypeEnv, variant_name: String) -> List { - fold(flatten_visible_bindings(env: env) |> map_values, init: [], f: (acc, binding) => + let direct_bindings = fold(env.parents, init: env.str_bindings, f: (acc, parent) => + map_merge(parent.str_bindings, acc) + ) + fold(direct_bindings |> map_values, init: [], f: (acc, binding) => match unit_variant_in_coproduct( env: env, ty: structural_type_for_variant_lookup(env: env, ty: binding.resolved), @@ -223,15 +228,33 @@ fn resolve_node(n: Node, env: TypeEnv, module_name: String) -> NodeResolveResult resolve_node_bounded(n: n, env: env, module_name: module_name, depth: 0) } +data parameterized_use_site_prefers_parameterized_decl: String = "A use site WITH type arguments (children > 0) whose primary name lookup lands on a PARAMLESS decl is definitionally mis-resolved - type args imply a generic declaration. The concrete seam: the kernel prelude binding for a name (e.g. the kernel Optional) shadows an explicitly imported user generic of the same name in the ancestry rung, so v2's Optional consumers resolved the paramless kernel decl, is_user_generic_use_site went false, the sig return was never expanded, and pattern bindings went error-typed with zero diagnostics (the interpreted-parse cascade; pre-existing on main). Retry the DIRECT import parents (str_bindings) for a parameterized decl before accepting the paramless one - the params filter naturally excludes the kernel parent, and the ancestry rung itself carries the kernel-shadowed entry so it cannot serve as the retry source." + fn resolve_generic_use_decl(env: TypeEnv, n: Node) -> Node { let brand = authored_name(env: env, node: n) match lookup_type_for(env: env, node: n) { Present { value: decl } => if decl.params |> count > 0 { decl } - else if n.children |> count > 0 && n.name != "" && n.name != brand { - match lookup_type_by_name(env: env, name: n.name) { - Present { value: structural } => structural - Absent => decl + else if n.children |> count > 0 { + let ancestry_generic = fold(env.parents, init: none, f: (acc, parent) => + if acc != none { acc } + else { + match map_get(parent.str_bindings, brand) { + Present { value: b } => + if b.resolved.params |> count > 0 { Present { value: b.resolved } } else { acc } + Absent => acc + } + } + ) + match ancestry_generic { + Present { value: user_decl } => user_decl + Absent => + if n.name != "" && n.name != brand { + match lookup_type_by_name(env: env, name: n.name) { + Present { value: structural } => structural + Absent => decl + } + } else { decl } } } else { decl } Absent => diff --git a/src/v1/05_emit_rust.dag b/src/v1/05_emit_rust.dag index 57f77aa3538..b6ebc308177 100644 --- a/src/v1/05_emit_rust.dag +++ b/src/v1/05_emit_rust.dag @@ -3187,12 +3187,14 @@ fn emit_typed_item(item: Node, module_name: String, registry: Map, shared_types: Set, source_indices: Map) -> Bool { let name = authored_name_at(source_indices: source_indices, node: n) - if set_contains(recursive_types, name) { true } + if set_contains(shared_types, name) { false } + else if set_contains(recursive_types, name) { true } else if n.children |> count == 0 { - if set_contains(shared_types, name) { false } - else if coerce_primitive_type(target: Rust, dag_name: name) != name { false } + if coerce_primitive_type(target: Rust, dag_name: name) != name { false } else { set_contains(recursive_types, name) } } else { let is_optional = n.return_cardinality == CardOptional diff --git a/src/v1/stage0/src/bin/diagnostics_witness.rs b/src/v1/stage0/src/bin/diagnostics_witness.rs index 4782d422ab9..8cb438c8dd1 100644 --- a/src/v1/stage0/src/bin/diagnostics_witness.rs +++ b/src/v1/stage0/src/bin/diagnostics_witness.rs @@ -25,6 +25,7 @@ const SUITE_IMPORT_RESOLUTION: &str = "import_resolution"; const SUITE_REEXPORT_SURFACE: &str = "reexport_surface"; const SUITE_TYPE_AND_ARITY: &str = "type_and_arity"; const SUITE_EMPTY_LIST_CONTEXT: &str = "empty_list_context"; +const SUITE_CONSTRUCTOR_OWNER: &str = "constructor_owner"; fn fail(msg: impl std::fmt::Display) -> ExitCode { eprintln!("diagnostics_witness: {msg}"); @@ -357,6 +358,53 @@ fn empty_list_literal_context(module_index: &ModuleIndex) { ); } +/// Constructor-owner ruling (§1c) walls. RED control: the retired CI canary +/// fixture (two local coproducts sharing SharedVariant, disambiguated by the +/// deleted expected-type pick) must now raise VariantCollision. RED control: +/// an unbound constructor literal must raise UnresolvedType. GREEN control: +/// a sole-owner arm constructs clean. +fn constructor_owner_ruling_walls(module_index: &ModuleIndex) { + let collided = "module test.claim.variant_owner_expected_type\n\ + import std.logic { Bool }\n\ + type AEarlyOwner = SharedVariant | AOnlyVariant\n\ + type ZLaterOwner = SharedVariant | ZOnlyVariant\n\ + fn make_z_variant() -> ZLaterOwner { SharedVariant }\n"; + let collided_result = compile_multi(module_index, &[("variant_owner.dag", collided)]); + assert!( + collided_result.diagnostics.iter().any(|d| matches!( + &*d.diagnostic, + CompilerDiagnostic::VariantCollision { variant, .. } if variant == "SharedVariant" + )), + "retired canary fixture must raise VariantCollision, got: {:?}", + diagnostic_messages(&collided_result) + ); + + let unbound = "module test.claim.unbound_ctor\n\ + fn mk() -> Int { let g = GhostArm { x: 1 } 2 }\n"; + let unbound_result = compile_multi(module_index, &[("unbound_ctor.dag", unbound)]); + assert!( + unbound_result.diagnostics.iter().any(|d| matches!( + &*d.diagnostic, + CompilerDiagnostic::UnresolvedType { name, .. } if name == "GhostArm" + )), + "unbound constructor literal must raise UnresolvedType, got: {:?}", + diagnostic_messages(&unbound_result) + ); + + let sole = "module test.claim.sole_owner\n\ + type OnlyOwner = SoleArm | OtherArm\n\ + fn mk() -> OnlyOwner { SoleArm }\n"; + let sole_result = compile_multi(module_index, &[("sole_owner.dag", sole)]); + let sole_hard: Vec = diagnostic_messages(&sole_result) + .into_iter() + .filter(|m| !m.starts_with("complexity: ")) + .collect(); + assert!( + sole_hard.is_empty(), + "sole-owner construction must stay clean, got: {sole_hard:?}" + ); +} + fn suite_cases(suite: &str) -> Result, String> { match suite { SUITE_IMPORT_RESOLUTION => Ok(vec![( @@ -375,9 +423,14 @@ fn suite_cases(suite: &str) -> Result, String> { "empty_list_literal_context", empty_list_literal_context, )]), + SUITE_CONSTRUCTOR_OWNER => Ok(vec![( + "constructor_owner_ruling_walls", + constructor_owner_ruling_walls, + )]), _ => Err(format!( "unknown suite '{suite}'; expected one of: {SUITE_IMPORT_RESOLUTION}, \ - {SUITE_REEXPORT_SURFACE}, {SUITE_TYPE_AND_ARITY}, {SUITE_EMPTY_LIST_CONTEXT}" + {SUITE_REEXPORT_SURFACE}, {SUITE_TYPE_AND_ARITY}, {SUITE_EMPTY_LIST_CONTEXT}, \ + {SUITE_CONSTRUCTOR_OWNER}" )), } } diff --git a/src/v1/stage0/src/bin/regen_stage0.rs b/src/v1/stage0/src/bin/regen_stage0.rs index 2986effd91b..d584dd5169f 100644 --- a/src/v1/stage0/src/bin/regen_stage0.rs +++ b/src/v1/stage0/src/bin/regen_stage0.rs @@ -126,6 +126,9 @@ const HAND_MAINTAINED_STAGE0_FILES: &[&str] = &[ // Dissolution: run regen_stage0 --verify; on green, move to GENERATED_STAGE0_FILES. "main.rs", "v1_interpreter.rs", + // Phase-local heartbeat for long floor walks (2026-07-04); its own header + // carries the dissolution trigger (realization_measurement_loop Phase 0). + "phase_profile.rs", ]; /// Hand-maintained stage0 support living in subdirectories (not flat `.rs` files). diff --git a/src/v1/stage0/src/cli_run.rs b/src/v1/stage0/src/cli_run.rs index 508f56e1214..d4b08570797 100644 --- a/src/v1/stage0/src/cli_run.rs +++ b/src/v1/stage0/src/cli_run.rs @@ -4868,7 +4868,10 @@ pub fn run_discovery_corpus_with_options( // shared std/spec prefix above all — typechecks once per node instead of once per // consumer. Previously the frontier build, the floor runner (via a separate thread-local // store), and the rows each carried a private cold cache of that same prefix. - let index = build_multi_entry_index(source_roots); + // The index is the THREAD's shared one (process_shared_index), not a private build: + // when the executor prelude already resolved its entries on this thread, discovery + // reuses that index's parse/typed caches instead of paying the union cold a second time. + let index = process_shared_index(source_roots); let (skip_enabled, diff_edits) = if options.skip_unaffected_node_frontier && !line_ranges_by_file.is_empty() { match floor_diff_edits_from_line_ranges( diff --git a/src/v1/stage0/src/v1_compiler_emit_rust.rs b/src/v1/stage0/src/v1_compiler_emit_rust.rs index 353a0700ef7..b45fa7ffa52 100644 --- a/src/v1/stage0/src/v1_compiler_emit_rust.rs +++ b/src/v1/stage0/src/v1_compiler_emit_rust.rs @@ -7600,14 +7600,18 @@ pub fn needs_box_wrapping( mut source_indices: Rc>>, ) -> bool { loop { + // HAND-PATCH (seed two-step): shared (Rc-rendered) types never need Box + // — shared_types dominates the recursive check. Replaced by regen's + // true emission of the reordered dag source in the convergence commit. let name = authored_name_at(source_indices.clone(), n.clone()); + if v1_rt::set_contains(&shared_types, name.clone()) { + break false; + } if v1_rt::set_contains(&recursive_types, name.clone()) { break true; } if ((n.children.clone().len() as i64) == 0) { - if v1_rt::set_contains(&shared_types, name.clone()) { - break false; - } else { + { if (coerce_primitive_type(RenderTarget::Rust, name.clone()) != name.clone()) { break false; } else { diff --git a/src/v1/stage0/src/v1_compiler_infer.rs b/src/v1/stage0/src/v1_compiler_infer.rs index 94f4dd380a5..0525069bf0f 100644 --- a/src/v1/stage0/src/v1_compiler_infer.rs +++ b/src/v1/stage0/src/v1_compiler_infer.rs @@ -110,6 +110,8 @@ use crate::v1_rt; use crate::v1_rt::Witness; use crate::v1_rt::Witness::{Holds, Violates}; use crate::v1_std_core::empty_intern_table; +// HAND-PATCH (§1c re-export chains): accessor for the chain followers below. +use crate::v1_std_core::import_specific_names_at; use crate::v1_std_core::CallSemantics::{LookupCallSemantics, PlainCallSemantics}; use crate::v1_std_core::Cardinality::{CardOptional, Required}; use crate::v1_std_core::CompilerDiagnostic::{ @@ -797,6 +799,31 @@ pub fn lookup_variant_parent_enum(scope: Rc, name: String) -> Option } } +// HAND-PATCH (constructor-owner ruling §1c; seed two-step): O(1) owner lookup +// via the binding edge — replaced by regen's true emission of variant_owner_node +// in the convergence commit. +pub fn variant_owner_node(scope: Rc, name: String) -> Option> { + match v1_rt::map_get(&scope.locals.clone(), name.clone()) { + Some(binding) => match lookup_type_for(scope.type_env.clone(), binding.resolved.clone()) { + Some(owner) => { + if owner.connective.clone() == Connective::Disj + && has_child_named( + owner.clone(), + name.clone(), + scope.type_env.clone().source_indices.clone(), + ) + { + Some(owner) + } else { + None + } + } + None => None, + }, + None => None, + } +} + pub fn infer_var_binding_kind(scope: Rc, name: String) -> Rc { match lookup_variant_parent_enum(scope.clone(), name.clone()) { Some(parent_enum) => Rc::new(VarBindingKind::VariantValueBinding { @@ -927,36 +954,30 @@ pub fn record_lit_expected_fields( match type_name { Some(tn) => match lookup_type_by_name(scope.type_env.clone(), tn.clone()) { Some(direct) => direct.children.clone(), - None => match lookup_variant_parent_enum(scope.clone(), tn.clone()) { - Some(parent_name) => { - match lookup_type_by_name(scope.type_env.clone(), parent_name.clone()) { - Some(parent) => match Rc::new({ - let mut __result = Vec::new(); - for v in parent.children.clone().iter().cloned() { - if (authored_name_at( - scope.type_env.clone().source_indices.clone(), - v.clone(), - ) == tn.clone()) - { - __result.push(v); - } - } - __result - }) - .first() - .cloned() + // HAND-PATCH (§1c): owner via the binding edge (variant_owner_node), + // no name round-trip, no whole-env scan fallback. Regen replaces + // this with the dag source's true emission. + None => match variant_owner_node(scope.clone(), tn.clone()) { + Some(parent) => match Rc::new({ + let mut __result = Vec::new(); + for v in parent.children.clone().iter().cloned() { + if (authored_name_at( + scope.type_env.clone().source_indices.clone(), + v.clone(), + ) == tn.clone()) { - Some(variant) => variant.children.clone(), - None => Rc::new(vec![]), - }, - None => Rc::new(vec![]), + __result.push(v); + } } - } - None => record_lit_variant_fields_from_visible_env( - scope.type_env.clone(), - tn.clone(), - scope.module_name.clone(), - ), + __result + }) + .first() + .cloned() + { + Some(variant) => variant.children.clone(), + None => Rc::new(vec![]), + }, + None => Rc::new(vec![]), }, }, None => Rc::new(vec![]), @@ -2697,44 +2718,25 @@ pub fn infer_expr( Some(binding) => { let scope_parent = lookup_variant_parent_enum(scope.clone(), name.clone()); match scope_parent { - Some(scope_enum) => { - let override_enum = expected_type_override_enum( - scope.clone(), - name.clone(), - scope_enum.clone(), - expected.clone(), - ); - let effective_enum = match override_enum.clone() { - Some(ov) => ov.clone(), - None => scope_enum.clone(), - }; - let effective_resolved = match override_enum.clone() { - Some(ov) => match lookup_type_by_name( - scope.type_env.clone(), - ov.clone(), - ) { - Some(t) => t.clone(), - None => binding.resolved.clone(), - }, - None => binding.resolved.clone(), - }; - ok_infer(make_named_expr_node( - name.clone(), - Rc::new(ExprData::ExprVar { - binding_kind: Some(Rc::new( - VarBindingKind::VariantValueBinding { - parent_enum: effective_enum, - }, - )), - }), - Rc::new(vec![]), - Some(Rc::new(InferredNode::Resolved { - node: effective_resolved, - })), - span.clone(), - span.clone(), - )) - } + // HAND-PATCH (§1c): the expected-type-as-owner-picker + // is deleted; the binding edge alone names the owner. + // Regen replaces this with the dag source's true emission. + Some(scope_enum) => ok_infer(make_named_expr_node( + name.clone(), + Rc::new(ExprData::ExprVar { + binding_kind: Some(Rc::new( + VarBindingKind::VariantValueBinding { + parent_enum: scope_enum.clone(), + }, + )), + }), + Rc::new(vec![]), + Some(Rc::new(InferredNode::Resolved { + node: binding.resolved.clone(), + })), + span.clone(), + span.clone(), + )), None => { let binding_kind = infer_var_binding_kind(scope.clone(), name.clone()); @@ -2843,10 +2845,20 @@ pub fn infer_expr( false }; if resolved_base_is_error { + // HAND-PATCH (debug instrumentation, removed with regen): + // name the error-typed base so the fail-open seam is located. Rc::new(InferResult { typed: make_expr_error_node( ExprErrorKind::SemanticExprError, - "error type cascade".to_string(), + format!( + "error type cascade (field '{}' on error-typed base '{}' in {})", + field_name, + authored_name_at( + scope.type_env.clone().source_indices.clone(), + base_typed.clone() + ), + scope.module_name + ), span.clone(), ), diagnostics: base_diags, @@ -5821,45 +5833,36 @@ pub fn infer_record_lit( expected: Option>, ) -> Rc { { + // HAND-PATCH (§1c): the unconditional whole-env scan-first rung is + // deleted; the ladder is instantiate -> expected -> declaration/ + // binding-edge only. Regen replaces this with the dag source's true + // emission. let struct_fields = match type_name.clone() { Some(tn) => { - let visible_fields = record_lit_variant_fields_from_visible_env( - scope.type_env.clone(), - tn.clone(), - scope.module_name.clone(), - ); - if ((visible_fields.clone().len() as i64) > 0) { - visible_fields.clone() - } else { - match record_lit_instantiated_fields( + match record_lit_instantiated_fields( + type_name.clone(), + expected.clone(), + scope.clone(), + ) { + Some(fields) => fields.clone(), + None => match record_lit_fields_from_expected( type_name.clone(), expected.clone(), scope.clone(), ) { Some(fields) => fields.clone(), - None => match record_lit_fields_from_expected( - type_name.clone(), - expected.clone(), - scope.clone(), - ) { - Some(fields) => fields.clone(), - None => match expected.clone() { - Some(_) => { - record_lit_expected_fields(type_name.clone(), scope.clone()) - } - None => { - match record_lit_alias_struct_fields(tn.clone(), scope.clone()) - { - Some(fields) => fields.clone(), - None => record_lit_expected_fields( - type_name.clone(), - scope.clone(), - ), + None => match expected.clone() { + Some(_) => record_lit_expected_fields(type_name.clone(), scope.clone()), + None => { + match record_lit_alias_struct_fields(tn.clone(), scope.clone()) { + Some(fields) => fields.clone(), + None => { + record_lit_expected_fields(type_name.clone(), scope.clone()) } } - }, + } }, - } + }, } } None => match record_lit_instantiated_fields( @@ -6148,14 +6151,16 @@ pub fn infer_record_lit( } else { raw_resolved }; + // HAND-PATCH (§1c): unbound constructor literal is the typed + // UnresolvedType diagnostic, not a generic InternalError message. + // Regen replaces this with the dag source's true emission. let type_diags = match effective_lookup.clone() { Some(_) => Rc::new(vec![]), - None => Rc::new(vec![inference_error( - v1_rt::concat( - v1_rt::concat("type '".to_string(), type_name.clone().unwrap()), - "' not found in scope".to_string(), - ), - span.clone(), + None => Rc::new(vec![make_error_node( + Rc::new(CompilerDiagnostic::UnresolvedType { + name: type_name.clone().unwrap(), + span: span.clone(), + }), scope.module_name.clone(), )]), }; @@ -13566,6 +13571,94 @@ pub fn build_imported_variants( ) } +// HAND-PATCH (constructor-owner ruling §1c; seed two-step): re-export chain +// followers — a module re-exports its specific-name imports, so the binder +// walks the acyclic import chain to the defining module's coproduct. Replaced +// by regen's true emission in the convergence commit. +pub fn owner_of_exported_arm( + module_path: String, + arm_name: String, + parent_index: Rc>>, + source_indices: Rc>>, +) -> Option> { + match v1_rt::map_get(&parent_index, module_path) { + None => None, + Some(tm) => { + let own = tm.items.iter().cloned().find(|item| { + item.connective.clone() == Connective::Disj + && item + .children + .iter() + .cloned() + .any(|c| authored_name_at(source_indices.clone(), c) == arm_name) + }); + match own { + Some(item) => Some(item), + None => { + let reexport = module_imports(tm.module.clone()) + .iter() + .cloned() + .find(|imp| { + !import_is_all(imp.clone()) + && import_specific_names_at(imp.clone(), source_indices.clone()) + .iter() + .any(|n| *n == arm_name) + }); + match reexport { + Some(imp) => owner_of_exported_arm( + authored_name_at(source_indices.clone(), imp), + arm_name, + parent_index, + source_indices, + ), + None => None, + } + } + } + } + } +} + +pub fn exported_coproduct_item( + module_path: String, + enum_name: String, + parent_index: Rc>>, + source_indices: Rc>>, +) -> Option> { + match v1_rt::map_get(&parent_index, module_path) { + None => None, + Some(tm) => { + let own = tm.items.iter().cloned().find(|item| { + item.connective.clone() == Connective::Disj + && authored_name_at(source_indices.clone(), item.clone()) == enum_name + }); + match own { + Some(item) => Some(item), + None => { + let reexport = module_imports(tm.module.clone()) + .iter() + .cloned() + .find(|imp| { + !import_is_all(imp.clone()) + && import_specific_names_at(imp.clone(), source_indices.clone()) + .iter() + .any(|n| *n == enum_name) + }); + match reexport { + Some(imp) => exported_coproduct_item( + authored_name_at(source_indices.clone(), imp), + enum_name, + parent_index, + source_indices, + ), + None => None, + } + } + } + } + } +} + pub fn build_module_context( contributions: Rc>>, parent_index: Rc>>, @@ -13584,152 +13677,138 @@ pub fn build_module_context( Rc::new(vec![]), env.source_indices.clone(), ); - let imported_variants = build_imported_variants( - resolved_imports.clone(), - parent_index.clone(), - env.source_indices.clone(), - ); - let ancestry_cache = - union_parent_type_env_caches(resolved_imports.clone(), parent_index.clone()); - let local_str_bindings = Rc::new(v1_rt::map_values(&env.bindings.clone())) - .iter() - .cloned() - .fold( - v1_rt::rc_empty_map::>(), - |acc: Rc>>, b: Rc| { - v1_rt::rc_map_insert(acc, b.name.clone(), b.clone()) - }, - ); - let bindings_for_variants = - v1_rt::rc_map_merge(ancestry_cache.str_bindings.clone(), local_str_bindings); - let variant_fold = Rc::new({ - let mut __sorted: Vec<_> = Rc::new(v1_rt::map_values(&bindings_for_variants)) - .iter() - .cloned() - .collect(); - __sorted.sort_by(|a: &Rc, b: &Rc| { - let __ka = (|b: Rc| b.name.clone())(a.clone()); - let __kb = (|b: Rc| b.name.clone())(b.clone()); - __ka.partial_cmp(&__kb).unwrap_or(std::cmp::Ordering::Equal) - }); - __sorted - }) - .iter() - .cloned() - .fold( + // HAND-PATCH (constructor-owner ruling §1c; seed two-step): functional + // realization of the new v1.compiler.infer dag source — replaced + // wholesale by regen output in the convergence commit. Constructor + // namespace is FLAT: local coproduct arms + direct imports' + // contributions; the binding carries the owner NODE; one arm name + // bound to two different owners = VariantCollision (per-file wall). + let insert_variant_owner_checked = |state: Rc, + arm_name: String, + owner: Rc| + -> Rc { + match v1_rt::map_get(&state.locals.clone(), arm_name.clone()) { + Some(prev) => { + let prev_name = + authored_name_at(env.source_indices.clone(), prev.resolved.clone()); + let owner_name = authored_name_at(env.source_indices.clone(), owner.clone()); + let same_owner = prev_name == owner_name + && prev.resolved.span.file == owner.span.file + && prev.resolved.span.start == owner.span.start; + if same_owner { + state + } else { + Rc::new(VariantFoldState { + locals: state.locals.clone(), + collision_errors: v1_rt::concat( + state.collision_errors.clone(), + Rc::new(vec![make_error_node( + Rc::new(CompilerDiagnostic::VariantCollision { + variant: arm_name, + enum1: prev_name, + enum2: owner_name, + span: owner.span.clone(), + }), + module_name.clone(), + )]), + ), + }) + } + } + None => Rc::new(VariantFoldState { + locals: v1_rt::rc_map_insert( + state.locals.clone(), + arm_name.clone(), + Rc::new(TypeBinding { + name: arm_name, + resolved: owner, + provenance: Rc::new(SubValueRelation::SubValueUnknown), + }), + ), + collision_errors: state.collision_errors.clone(), + }), + } + }; + let bind_coproduct_item_arms = + |state: Rc, item: Rc| -> Rc { + item.children + .clone() + .iter() + .cloned() + .fold(state, |vacc, child| { + insert_variant_owner_checked( + vacc, + authored_name_at(env.source_indices.clone(), child.clone()), + item.clone(), + ) + }) + }; + let local_variant_fold = local.resolved_items.clone().iter().cloned().fold( Rc::new(VariantFoldState { locals: v1_rt::rc_empty_map::>(), collision_errors: Rc::new(vec![]), }), - |acc: Rc, binding: Rc| { - let is_coproduct = - (binding.resolved.clone().connective.clone() == Connective::Disj); - if is_coproduct.clone() { - { - let binding_enum_name = - authored_name_at(env.source_indices.clone(), binding.resolved.clone()); - binding - .resolved - .clone() - .children - .clone() - .iter() - .cloned() - .fold( - acc.clone(), - |vacc: Rc, child: Rc| { - let child_name = - authored_name_at(env.source_indices.clone(), child.clone()); - let curr_child_is_imported = is_imported_variant_owned_by( - imported_variants.clone(), - child_name.clone(), - binding_enum_name.clone(), - ); - match v1_rt::map_get(&vacc.locals.clone(), child_name.clone()) { - Some(_prev) => { - if curr_child_is_imported.clone() { - Rc::new(VariantFoldState { - locals: v1_rt::rc_map_insert( - vacc.locals.clone(), - child_name.clone(), - Rc::new(TypeBinding { - name: child_name.clone(), - resolved: binding.resolved.clone(), - provenance: Rc::new( - SubValueRelation::SubValueUnknown, - ), - }), - ), - collision_errors: vacc.collision_errors.clone(), - }) - } else { - vacc.clone() - } - } - None => Rc::new(VariantFoldState { - locals: v1_rt::rc_map_insert( - vacc.locals.clone(), - child_name.clone(), - Rc::new(TypeBinding { - name: child_name.clone(), - resolved: binding.resolved.clone(), - provenance: Rc::new( - SubValueRelation::SubValueUnknown, - ), - }), - ), - collision_errors: vacc.collision_errors.clone(), - }), - } - }, - ) - } + |acc: Rc, item: Rc| { + if item.connective.clone() == Connective::Disj { + bind_coproduct_item_arms(acc, item) } else { - acc.clone() + acc } }, ); - let imported_variant_locals = - merge_kernel_variant_locals_low_priority(env.clone(), variant_fold.locals.clone()); - let imported_variant_locals = resolved_imports.clone().iter().cloned().fold( - imported_variant_locals.clone(), - |acc: Rc>>, imp: Rc| { - imp.specific_names.clone().iter().cloned().fold( - acc, - |inner: Rc>>, variant_name: String| { - if v1_rt::map_contains_key(&inner, variant_name.clone()) { - inner.clone() - } else { - match v1_rt::map_get(&imported_variants, variant_name.clone()) { - Some(enum_name) => { - match lookup_type_by_name(env.clone(), enum_name.clone()) { - Some(enum_node) => v1_rt::rc_map_insert( - inner.clone(), - variant_name.clone(), - Rc::new(TypeBinding { - name: variant_name.clone(), - resolved: enum_node.clone(), - provenance: Rc::new( - SubValueRelation::SubValueUnknown, - ), - }), - ), - None => inner.clone(), - } - } - None => inner.clone(), + let variant_fold = resolved_imports.clone().iter().cloned().fold( + local_variant_fold, + |acc: Rc, imp: Rc| { + // Glob (is_all) imports bind the target's OWN coproduct arms only. + let with_glob = if imp.is_all { + let source_items = match v1_rt::map_get(&parent_index, imp.module_path.clone()) + { + Some(parent_tm) => parent_tm.items.clone(), + None => Rc::new(vec![]), + }; + source_items.iter().cloned().fold( + acc, + |iacc: Rc, item: Rc| { + if item.connective.clone() == Connective::Disj { + bind_coproduct_item_arms(iacc, item) + } else { + iacc } + }, + ) + } else { + acc + }; + // Specific names resolve through the re-export chain to the + // defining module (owner_of_exported_arm / exported_coproduct_item). + imp.specific_names.iter().cloned().fold( + with_glob, + |nacc: Rc, name: String| { + let after_enum = match exported_coproduct_item( + imp.module_path.clone(), + name.clone(), + parent_index.clone(), + env.source_indices.clone(), + ) { + Some(item) => bind_coproduct_item_arms(nacc, item), + None => nacc, + }; + match owner_of_exported_arm( + imp.module_path.clone(), + name.clone(), + parent_index.clone(), + env.source_indices.clone(), + ) { + Some(owner) => insert_variant_owner_checked(after_enum, name, owner), + None => after_enum, } }, ) }, ); let variant_collision_errors = variant_fold.collision_errors.clone(); - let env_variant_locals = variant_locals_from_items( - local.resolved_items.clone(), - imported_variant_locals.clone(), - env.source_indices.clone(), - ); + let env_variant_locals = + merge_kernel_variant_locals_low_priority(env.clone(), variant_fold.locals.clone()); let merged_scope = merge_scope_from_imports( resolved_imports.clone(), parent_index.clone(), diff --git a/src/v1/stage0/src/v1_compiler_infer_resolve.rs b/src/v1/stage0/src/v1_compiler_infer_resolve.rs index 80f150d5996..84f1b36551f 100644 --- a/src/v1/stage0/src/v1_compiler_infer_resolve.rs +++ b/src/v1/stage0/src/v1_compiler_infer_resolve.rs @@ -354,18 +354,45 @@ pub fn resolve_generic_use_decl(env: Rc, n: Rc) -> Rc { Some(decl) => { if ((decl.params.clone().len() as i64) > 0) { decl.clone() - } else { - if ((((n.children.clone().len() as i64) > 0) - && (n.name.clone() != "".to_string())) - && (n.name.clone() != brand)) - { - match lookup_type_by_name(env.clone(), n.name.clone()) { - Some(structural) => structural.clone(), - None => decl.clone(), + } else if ((n.children.clone().len() as i64) > 0) { + // HAND-PATCH (parameterized use site prefers parameterized + // decl; replaced at regen): a use site WITH type args whose + // primary lookup lands paramless (kernel prelude shadowing + // an imported user generic, e.g. Optional) retries the + // ancestry binding for a parameterized decl. + let ancestry_generic = env.parents.iter().fold( + None, + |acc: Option>, parent: &Rc| { + if acc.is_some() { + return acc; + } + match v1_rt::map_get(&parent.str_bindings, brand.clone()) { + Some(b) => { + if ((b.resolved.params.clone().len() as i64) > 0) { + Some(b.resolved.clone()) + } else { + acc + } + } + None => acc, + } + }, + ); + match ancestry_generic { + Some(user_decl) => user_decl, + None => { + if ((n.name.clone() != "".to_string()) && (n.name.clone() != brand)) { + match lookup_type_by_name(env.clone(), n.name.clone()) { + Some(structural) => structural.clone(), + None => decl.clone(), + } + } else { + decl.clone() + } } - } else { - decl.clone() } + } else { + decl.clone() } } None => match lookup_type_by_name(env.clone(), n.name.clone()) { diff --git a/src/v1/stage0/src/v1_interpreter.rs b/src/v1/stage0/src/v1_interpreter.rs index d735e4e645b..2c3e43f38d3 100644 --- a/src/v1/stage0/src/v1_interpreter.rs +++ b/src/v1/stage0/src/v1_interpreter.rs @@ -1427,7 +1427,15 @@ fn eval_expr_inner(node: &Rc, env: &Rc, ctx: &InterpContext) -> Inter Err(InterpError::EarlyReturn { value: val }) } - ExprData::ExprError { message, .. } => Err(InterpError::TypeError { msg: message }), + ExprData::ExprError { message, .. } => Err(InterpError::TypeError { + // Located: an inference-side error node reaching evaluation is a + // fail-open seam (typed error without a blocking diagnostic); the + // span is the only thread back to the source. + msg: format!( + "{message} at {}:{}-{}", + node.span.file, node.span.start, node.span.end + ), + }), ExprData::NoExprData => Ok(Value::Unit), } @@ -1915,119 +1923,125 @@ fn match_pattern( name, parent_enum, field_bindings, - } => match value { - Value::Variant { - variant_name, - fields, - .. - } => { - if name == "Holds" - && parent_enum.as_deref() == Some("Witness") - && *variant_name != ctx.sym("Holds") - && *variant_name != ctx.sym("Violates") - { - let mut bindings = HashMap::new(); - for fb in field_bindings.iter() { - let fb_pat = field_binding_pattern(fb.clone()); - let sub_bindings = match_pattern(&fb_pat, value, ctx)?; - bindings.extend(sub_bindings); - } - return Some(bindings); - } - if name == "Present" - && parent_enum.as_deref() == Some("Optional") - && *variant_name != ctx.sym("Present") - && *variant_name != ctx.sym("Absent") - { - let mut bindings = HashMap::new(); - for fb in field_bindings.iter() { - let fb_pat = field_binding_pattern(fb.clone()); - let sub_bindings = match_pattern(&fb_pat, value, ctx)?; - bindings.extend(sub_bindings); - } - return Some(bindings); - } - if *variant_name != ctx.sym(name) { - return None; - } + } => { + // Kernel-optional / witness raw representation (value-or-Null): + // the `_ if Present+Optional` / `_ if Holds+Witness` unwrap arms + // below the kind-specific arms were UNREACHABLE for Record/List/ + // Str/Int payloads — Value::Record etc. match their kind arm + // first and return None from inside it, so + // `match xs |> first { Present { value: t } => ... }` failed + // non-exhaustive on any record element (pre-existing on main; + // located via the interpreted-parse suite reds). Hoisted here + // verbatim; Variant payloads are excluded so the Variant arm's + // inline raw-value handling stays authoritative. + if name == "Present" + && parent_enum.as_deref() == Some("Optional") + && !matches!(value, Value::Null) + && !matches!(value, Value::Variant { .. }) + { let mut bindings = HashMap::new(); for fb in field_bindings.iter() { - let field_name = field_binding_name_at(fb.clone(), ctx.source_indices.clone()); let fb_pat = field_binding_pattern(fb.clone()); - let field_val = fields_get(fields, ctx.sym(&field_name)) - .cloned() - .unwrap_or(Value::Null); - let sub_bindings = match_pattern(&fb_pat, &field_val, ctx)?; + let sub_bindings = match_pattern(&fb_pat, value, ctx)?; bindings.extend(sub_bindings); } - Some(bindings) + return Some(bindings); } - Value::Record { type_name, fields } => { - if *type_name != ctx.sym(name) { - return None; - } + if name == "Holds" + && parent_enum.as_deref() == Some("Witness") + && !matches!(value, Value::Null) + && !matches!(value, Value::Variant { .. }) + { let mut bindings = HashMap::new(); for fb in field_bindings.iter() { - let field_name = field_binding_name_at(fb.clone(), ctx.source_indices.clone()); let fb_pat = field_binding_pattern(fb.clone()); - let field_val = fields_get(fields, ctx.sym(&field_name)) - .cloned() - .unwrap_or(Value::Null); - let sub_bindings = match_pattern(&fb_pat, &field_val, ctx)?; + let sub_bindings = match_pattern(&fb_pat, value, ctx)?; bindings.extend(sub_bindings); } - Some(bindings) + return Some(bindings); } - Value::List(items) => match name.as_str() { - "Empty" => { - if items.is_empty() { - Some(HashMap::new()) - } else { - None + match value { + Value::Variant { + variant_name, + fields, + .. + } => { + if name == "Holds" + && parent_enum.as_deref() == Some("Witness") + && *variant_name != ctx.sym("Holds") + && *variant_name != ctx.sym("Violates") + { + let mut bindings = HashMap::new(); + for fb in field_bindings.iter() { + let fb_pat = field_binding_pattern(fb.clone()); + let sub_bindings = match_pattern(&fb_pat, value, ctx)?; + bindings.extend(sub_bindings); + } + return Some(bindings); } - } - "Cons" => { - if items.is_empty() { - None - } else { - let head = items[0].clone(); - let tail = { - let mut rest = (**items).clone(); - list_value(rest.split_off(1)) - }; + if name == "Present" + && parent_enum.as_deref() == Some("Optional") + && *variant_name != ctx.sym("Present") + && *variant_name != ctx.sym("Absent") + { let mut bindings = HashMap::new(); for fb in field_bindings.iter() { - let field_name = - field_binding_name_at(fb.clone(), ctx.source_indices.clone()); let fb_pat = field_binding_pattern(fb.clone()); - let field_val = match field_name.as_str() { - "head" => head.clone(), - "tail" => tail.clone(), - _ => return None, - }; - let sub_bindings = match_pattern(&fb_pat, &field_val, ctx)?; + let sub_bindings = match_pattern(&fb_pat, value, ctx)?; bindings.extend(sub_bindings); } - Some(bindings) + return Some(bindings); + } + if *variant_name != ctx.sym(name) { + return None; } + let mut bindings = HashMap::new(); + for fb in field_bindings.iter() { + let field_name = + field_binding_name_at(fb.clone(), ctx.source_indices.clone()); + let fb_pat = field_binding_pattern(fb.clone()); + let field_val = fields_get(fields, ctx.sym(&field_name)) + .cloned() + .unwrap_or(Value::Null); + let sub_bindings = match_pattern(&fb_pat, &field_val, ctx)?; + bindings.extend(sub_bindings); + } + Some(bindings) } - _ => None, - }, - Value::Str(s) if name == "Empty" || name == "Cons" => match name.as_str() { - "Empty" => { - if s.is_empty() { - Some(HashMap::new()) - } else { - None + Value::Record { type_name, fields } => { + if *type_name != ctx.sym(name) { + return None; } + let mut bindings = HashMap::new(); + for fb in field_bindings.iter() { + let field_name = + field_binding_name_at(fb.clone(), ctx.source_indices.clone()); + let fb_pat = field_binding_pattern(fb.clone()); + let field_val = fields_get(fields, ctx.sym(&field_name)) + .cloned() + .unwrap_or(Value::Null); + let sub_bindings = match_pattern(&fb_pat, &field_val, ctx)?; + bindings.extend(sub_bindings); + } + Some(bindings) } - "Cons" => { - let mut chars = s.chars(); - match chars.next() { - None => None, - Some(c) => { - let head = char_value(c); - let tail = Value::Str(chars.as_str().to_string()); + Value::List(items) => match name.as_str() { + "Empty" => { + if items.is_empty() { + Some(HashMap::new()) + } else { + None + } + } + "Cons" => { + if items.is_empty() { + None + } else { + let head = items[0].clone(); + let tail = { + let mut rest = (**items).clone(); + list_value(rest.split_off(1)) + }; let mut bindings = HashMap::new(); for fb in field_bindings.iter() { let field_name = @@ -2044,84 +2058,121 @@ fn match_pattern( Some(bindings) } } - } - _ => None, - }, - Value::Int(n) if name == "Zero" || name == "Succ" => match name.as_str() { - "Zero" => { - if *n == 0 { - Some(HashMap::new()) - } else { - None + _ => None, + }, + Value::Str(s) if name == "Empty" || name == "Cons" => match name.as_str() { + "Empty" => { + if s.is_empty() { + Some(HashMap::new()) + } else { + None + } } - } - "Succ" => { - if *n <= 0 { - None - } else { - let mut bindings = HashMap::new(); - for fb in field_bindings.iter() { - let field_name = - field_binding_name_at(fb.clone(), ctx.source_indices.clone()); - let fb_pat = field_binding_pattern(fb.clone()); - let field_val = match field_name.as_str() { - "prev" => Value::Int(n - 1), - _ => return None, - }; - let sub_bindings = match_pattern(&fb_pat, &field_val, ctx)?; - bindings.extend(sub_bindings); + "Cons" => { + let mut chars = s.chars(); + match chars.next() { + None => None, + Some(c) => { + let head = char_value(c); + let tail = Value::Str(chars.as_str().to_string()); + let mut bindings = HashMap::new(); + for fb in field_bindings.iter() { + let field_name = field_binding_name_at( + fb.clone(), + ctx.source_indices.clone(), + ); + let fb_pat = field_binding_pattern(fb.clone()); + let field_val = match field_name.as_str() { + "head" => head.clone(), + "tail" => tail.clone(), + _ => return None, + }; + let sub_bindings = match_pattern(&fb_pat, &field_val, ctx)?; + bindings.extend(sub_bindings); + } + Some(bindings) + } } - Some(bindings) } + _ => None, + }, + Value::Int(n) if name == "Zero" || name == "Succ" => match name.as_str() { + "Zero" => { + if *n == 0 { + Some(HashMap::new()) + } else { + None + } + } + "Succ" => { + if *n <= 0 { + None + } else { + let mut bindings = HashMap::new(); + for fb in field_bindings.iter() { + let field_name = + field_binding_name_at(fb.clone(), ctx.source_indices.clone()); + let fb_pat = field_binding_pattern(fb.clone()); + let field_val = match field_name.as_str() { + "prev" => Value::Int(n - 1), + _ => return None, + }; + let sub_bindings = match_pattern(&fb_pat, &field_val, ctx)?; + bindings.extend(sub_bindings); + } + Some(bindings) + } + } + _ => None, + }, + Value::Null if name == "Violates" && parent_enum.as_deref() == Some("Witness") => { + let mut bindings = HashMap::new(); + for fb in field_bindings.iter() { + let field_name = + field_binding_name_at(fb.clone(), ctx.source_indices.clone()); + let fb_pat = field_binding_pattern(fb.clone()); + let field_val = match field_name.as_str() { + "diagnostic" => native_map_absent_diagnostic_value(ctx), + _ => return None, + }; + let sub_bindings = match_pattern(&fb_pat, &field_val, ctx)?; + bindings.extend(sub_bindings); + } + Some(bindings) } - _ => None, - }, - Value::Null if name == "Violates" && parent_enum.as_deref() == Some("Witness") => { - let mut bindings = HashMap::new(); - for fb in field_bindings.iter() { - let field_name = field_binding_name_at(fb.clone(), ctx.source_indices.clone()); - let fb_pat = field_binding_pattern(fb.clone()); - let field_val = match field_name.as_str() { - "diagnostic" => native_map_absent_diagnostic_value(ctx), - _ => return None, - }; - let sub_bindings = match_pattern(&fb_pat, &field_val, ctx)?; - bindings.extend(sub_bindings); - } - Some(bindings) - } - Value::Null if name == "None" && parent_enum.as_deref() == Some("Diagnostics") => { - Some(HashMap::new()) - } - Value::Null if name == "Absent" && parent_enum.as_deref() == Some("Optional") => { - Some(HashMap::new()) - } - _ if name == "Present" && parent_enum.as_deref() == Some("Optional") => { - if matches!(value, Value::Null) { - return None; + Value::Null if name == "None" && parent_enum.as_deref() == Some("Diagnostics") => { + Some(HashMap::new()) } - let mut bindings = HashMap::new(); - for fb in field_bindings.iter() { - let fb_pat = field_binding_pattern(fb.clone()); - let sub_bindings = match_pattern(&fb_pat, value, ctx)?; - bindings.extend(sub_bindings); + Value::Null if name == "Absent" && parent_enum.as_deref() == Some("Optional") => { + Some(HashMap::new()) } - Some(bindings) - } - _ if name == "Holds" && parent_enum.as_deref() == Some("Witness") => { - if matches!(value, Value::Null) { - return None; + _ if name == "Present" && parent_enum.as_deref() == Some("Optional") => { + if matches!(value, Value::Null) { + return None; + } + let mut bindings = HashMap::new(); + for fb in field_bindings.iter() { + let fb_pat = field_binding_pattern(fb.clone()); + let sub_bindings = match_pattern(&fb_pat, value, ctx)?; + bindings.extend(sub_bindings); + } + Some(bindings) } - let mut bindings = HashMap::new(); - for fb in field_bindings.iter() { - let fb_pat = field_binding_pattern(fb.clone()); - let sub_bindings = match_pattern(&fb_pat, value, ctx)?; - bindings.extend(sub_bindings); + _ if name == "Holds" && parent_enum.as_deref() == Some("Witness") => { + if matches!(value, Value::Null) { + return None; + } + let mut bindings = HashMap::new(); + for fb in field_bindings.iter() { + let fb_pat = field_binding_pattern(fb.clone()); + let sub_bindings = match_pattern(&fb_pat, value, ctx)?; + bindings.extend(sub_bindings); + } + Some(bindings) } - Some(bindings) + _ => None, } - _ => None, - }, + } } } diff --git a/src/v1/tests/src/cache_purity_oracle_test.rs b/src/v1/tests/src/cache_purity_oracle_test.rs index 4d971a9e13e..6c6ac714ce8 100644 --- a/src/v1/tests/src/cache_purity_oracle_test.rs +++ b/src/v1/tests/src/cache_purity_oracle_test.rs @@ -24,11 +24,13 @@ fn temp_dir(label: &str) -> std::path::PathBuf { .duration_since(UNIX_EPOCH) .expect("clock") .as_nanos(); - let dir = std::env::temp_dir().join(format!( - "gunbc-cache-purity-{label}-{}-{}", - std::process::id(), - nanos - )); + let dir = crate::helpers::workspace_root() + .join("target") + .join(format!( + "gunbc-cache-purity-{label}-{}-{}", + std::process::id(), + nanos + )); fs::create_dir_all(&dir).expect("temp dir"); dir } diff --git a/src/v1/tests/src/constructor_owner_ruling_test.rs b/src/v1/tests/src/constructor_owner_ruling_test.rs new file mode 100644 index 00000000000..72f14ec0cde --- /dev/null +++ b/src/v1/tests/src/constructor_owner_ruling_test.rs @@ -0,0 +1,149 @@ +//! Constructor-owner ruling (§1c, operator 2026-07-04): resolution follows the +//! binding edge; the constructor namespace is flat (locals + direct imports); +//! ambiguity and unbound constructors are typed errors, never a scan. +//! +//! Red controls: the collision wall (`VariantCollision`) and the unbound +//! constructor literal (`UnresolvedType`) must go red on the bad inputs and +//! stay silent on the legal ones — including the re-export case, where one +//! declaration reached via two import paths is one owner, not a collision. + +use crate::helpers::{compile_dag, compile_multi, diagnostic_messages}; +use v1_compiler::v1_std_core::CompilerDiagnostic; + +#[test] +fn intra_file_arm_collision_is_a_typed_error() { + let source = "module test.collide\n\ + type Alpha\n\ + \x20 = SharedVariant { x: Int }\n\ + \x20 | AlphaOnly { y: Int }\n\ + type Beta\n\ + \x20 = SharedVariant { x: Int }\n\ + \x20 | BetaOnly { z: Int }\n\ + fn mk() -> Alpha { SharedVariant { x: 1 } }\n"; + let result = compile_dag(source); + let has_collision = result.diagnostics.iter().any(|diag| { + matches!( + &*diag.diagnostic, + CompilerDiagnostic::VariantCollision { variant, enum1, enum2, .. } + if variant == "SharedVariant" + && ((enum1 == "Alpha" && enum2 == "Beta") + || (enum1 == "Beta" && enum2 == "Alpha")) + ) + }); + assert!( + has_collision, + "two local coproducts sharing an arm name must raise VariantCollision, got:\n{}", + diagnostic_messages(&result).join("\n") + ); +} + +#[test] +fn cross_import_arm_collision_is_a_typed_error() { + let lib_a = "module test.liba\ntype Left = Shared { x: Int } | LeftOnly { y: Int }\n"; + let lib_b = "module test.libb\ntype Right = Shared { x: Int } | RightOnly { y: Int }\n"; + let entry = "module test.entry\n\ + import test.liba { Left }\n\ + import test.libb { Right }\n\ + fn mk() -> Left { Shared { x: 1 } }\n"; + let result = compile_multi(&[ + ("liba.dag", lib_a), + ("libb.dag", lib_b), + ("entry.dag", entry), + ]); + let has_collision = result.diagnostics.iter().any(|diag| { + matches!( + &*diag.diagnostic, + CompilerDiagnostic::VariantCollision { variant, .. } if variant == "Shared" + ) + }); + assert!( + has_collision, + "one arm name from two imported owners must raise VariantCollision, got:\n{}", + diagnostic_messages(&result).join("\n") + ); +} + +#[test] +fn unbound_constructor_literal_is_unresolved_type() { + let source = "module test.unbound\n\ + fn mk() -> Int { let g = Ghost { x: 1 } 2 }\n"; + let result = compile_dag(source); + let has_unresolved = result.diagnostics.iter().any(|diag| { + matches!( + &*diag.diagnostic, + CompilerDiagnostic::UnresolvedType { name, .. } if name == "Ghost" + ) + }); + assert!( + has_unresolved, + "a constructor literal bound nowhere in scope must raise UnresolvedType, got:\n{}", + diagnostic_messages(&result).join("\n") + ); +} + +#[test] +fn imported_enum_arms_construct_via_binding_edge() { + let lib = "module test.colorlib\ntype Color = Red { shade: Int } | Blue { shade: Int }\n"; + let entry = "module test.usecolor\n\ + import test.colorlib { Color }\n\ + fn mk() -> Color { Red { shade: 3 } }\n"; + let result = compile_multi(&[("colorlib.dag", lib), ("usecolor.dag", entry)]); + assert!( + result.diagnostics.is_empty(), + "importing the enum by name must bind its arms (owner on the binding edge), got:\n{}", + diagnostic_messages(&result).join("\n") + ); +} + +#[test] +fn specifically_imported_arm_constructs_without_enum_name_visible() { + let lib = "module test.shapelib\ntype Shape = Circle { r: Int } | Square { s: Int }\n"; + let entry = "module test.useshape\n\ + import test.shapelib { Circle, Shape }\n\ + fn mk() -> Shape { Circle { r: 2 } }\n"; + let result = compile_multi(&[("shapelib.dag", lib), ("useshape.dag", entry)]); + assert!( + result.diagnostics.is_empty(), + "importing an arm name directly must bind it to its owner node, got:\n{}", + diagnostic_messages(&result).join("\n") + ); +} + +#[test] +fn glob_import_binds_all_arms() { + let lib = "module test.toollib\ntype Tool = Hammer { w: Int } | Saw { t: Int }\n"; + // The brace-less import form is the glob (is_all) import. + let entry = "module test.usetool\n\ + import test.toollib\n\ + fn mk() -> Tool { Hammer { w: 5 } }\n"; + let result = compile_multi(&[("toollib.dag", lib), ("usetool.dag", entry)]); + assert!( + result.diagnostics.is_empty(), + "a glob import must bind every coproduct arm of the module, got:\n{}", + diagnostic_messages(&result).join("\n") + ); +} + +#[test] +fn same_declaration_via_two_import_paths_is_not_a_collision() { + // test.reexport re-exposes test.baselib's enum; the entry sees the SAME + // declaration node through both paths — one owner, not a collision. + let base = "module test.baselib\ntype Fruit = Apple { n: Int } | Pear { n: Int }\n"; + let reexport = "module test.reexport\n\ + import test.baselib { Fruit }\n\ + fn pick() -> Fruit { Apple { n: 1 } }\n"; + let entry = "module test.usefruit\n\ + import test.baselib { Fruit }\n\ + import test.reexport { pick }\n\ + fn mk() -> Fruit { Pear { n: 2 } }\n"; + let result = compile_multi(&[ + ("baselib.dag", base), + ("reexport.dag", reexport), + ("usefruit.dag", entry), + ]); + assert!( + result.diagnostics.is_empty(), + "the same declaration via two import paths is one owner, got:\n{}", + diagnostic_messages(&result).join("\n") + ); +} diff --git a/src/v1/tests/src/dependency_pool_index_compile_test.rs b/src/v1/tests/src/dependency_pool_index_compile_test.rs index abc4d8816a2..9acad3285f1 100644 --- a/src/v1/tests/src/dependency_pool_index_compile_test.rs +++ b/src/v1/tests/src/dependency_pool_index_compile_test.rs @@ -147,6 +147,7 @@ fn strict_dependency_pool_index_panics_on_cross_root_collision() { } #[test] +#[ignore = "CI witness opt-in inversion (2026-07-04): whole-tree compile over dag+src/v2 via the release gunbc binary — the rust-lane twin of the corpus witnesses inverted out of the per-PR floor. Missed by the original inversion because it self-skips on CI (no release binary); locally it is the same unbounded corpus walk. Run explicitly: cargo test -p v1-compiler-tests -- --ignored primary_precedence_keeps_dag_extdeps_shell_for_dag_only_services. Re-enroll when affected-set selection + floor memoization land (see ci_witness_optin_inversion in gunbc.commit_workflow)."] fn primary_precedence_keeps_dag_extdeps_shell_for_dag_only_services() { let Some(gunbc) = gunbc_bin() else { eprintln!("skipping: release gunbc binary not found"); @@ -184,6 +185,7 @@ fn primary_precedence_keeps_dag_extdeps_shell_for_dag_only_services() { } #[test] +#[ignore = "CI witness opt-in inversion (2026-07-04): whole-tree compile over dag+src/v2 via the release gunbc binary — the rust-lane twin of the corpus witnesses inverted out of the per-PR floor. Missed by the original inversion because it self-skips on CI (no release binary); locally it is the same unbounded corpus walk. Run explicitly: cargo test -p v1-compiler-tests -- --ignored primary_precedence_two_root_compile_with_shadow_masked_fixture_succeeds. Re-enroll when affected-set selection + floor memoization land (see ci_witness_optin_inversion in gunbc.commit_workflow)."] fn primary_precedence_two_root_compile_with_shadow_masked_fixture_succeeds() { let Some(gunbc) = gunbc_bin() else { eprintln!("skipping: release gunbc binary not found"); diff --git a/src/v1/tests/src/fn_as_value_test.rs b/src/v1/tests/src/fn_as_value_test.rs index de2b56fd20e..e77a5ea65f2 100644 --- a/src/v1/tests/src/fn_as_value_test.rs +++ b/src/v1/tests/src/fn_as_value_test.rs @@ -40,11 +40,13 @@ fn scoped_entry_resolves_import_closure_not_entire_v4_tree() { .duration_since(UNIX_EPOCH) .expect("clock") .as_nanos(); - let dir = std::env::temp_dir().join(format!( - "gunbc-scoped-entry-{}-{}", - std::process::id(), - nanos - )); + let dir = crate::helpers::workspace_root() + .join("target") + .join(format!( + "gunbc-scoped-entry-{}-{}", + std::process::id(), + nanos + )); fs::create_dir_all(&dir).expect("temp dir"); let dep = "module test.scoped.dep\nfn dep_fn() -> Int { 1 }\n"; diff --git a/src/v1/tests/src/func_env_scope_chain_test.rs b/src/v1/tests/src/func_env_scope_chain_test.rs index 149dce4f1ad..4bfc302d6e8 100644 --- a/src/v1/tests/src/func_env_scope_chain_test.rs +++ b/src/v1/tests/src/func_env_scope_chain_test.rs @@ -48,11 +48,13 @@ fn temp_dir(label: &str) -> std::path::PathBuf { .duration_since(UNIX_EPOCH) .expect("clock") .as_nanos(); - let dir = std::env::temp_dir().join(format!( - "gunbc-func-env-{label}-{}-{}", - std::process::id(), - nanos - )); + let dir = crate::helpers::workspace_root() + .join("target") + .join(format!( + "gunbc-func-env-{label}-{}-{}", + std::process::id(), + nanos + )); fs::create_dir_all(&dir).expect("temp dir"); dir } diff --git a/src/v1/tests/src/interp_dry_run_test.rs b/src/v1/tests/src/interp_dry_run_test.rs index 94cbae365c8..3b0e30d03e7 100644 --- a/src/v1/tests/src/interp_dry_run_test.rs +++ b/src/v1/tests/src/interp_dry_run_test.rs @@ -17,7 +17,7 @@ fn claim_batch_exe() -> std::path::PathBuf { } fn hermetic_witness_temp_root() -> std::path::PathBuf { - std::env::temp_dir().join(format!( + workspace_root().join("target").join(format!( "gunbc-claim-batch-hermetic-{}-{}", std::process::id(), std::time::SystemTime::now() diff --git a/src/v1/tests/src/kernel_shadow_seams_test.rs b/src/v1/tests/src/kernel_shadow_seams_test.rs new file mode 100644 index 00000000000..8267ff8bb31 --- /dev/null +++ b/src/v1/tests/src/kernel_shadow_seams_test.rs @@ -0,0 +1,107 @@ +//! Walls for two pre-existing silent seams located 2026-07-04 (both confirmed +//! on pristine main via a worktree A/B; both fixed dag-authority-first): +//! +//! 1. Interpreter kernel-optional unwrap: `match xs |> first { Present {..} }` +//! over a RECORD element failed non-exhaustive — the raw-payload unwrap +//! guards were shadowed by the kind-specific match arms (Variant payloads +//! had an inlined fix; Record/List/Str/Int fell through). +//! +//! 2. Kernel-prelude generic shadowing: a user generic coproduct NAMED +//! `Optional`, explicitly imported into a multi-import consumer module, +//! resolved to the paramless KERNEL Optional (ancestry merged kernel as +//! overlay), so `Optional` signatures were never expanded and pattern +//! bindings went error-typed with ZERO diagnostics ("error type cascade" +//! at eval). resolve_generic_use_decl now retries the direct import +//! parents for a parameterized decl when a type-argument-bearing use site +//! lands on a paramless one. + +use std::fs; + +use crate::helpers::workspace_root; +use v1_compiler::cli_run::{make_eval_context, resolve_entry_graph, run_claim, ClaimOutcome}; +use v1_compiler::v1_interpreter::ExecutionMode; + +fn run_fixture(files: &[(&str, &str)], entry: &str, function: &str) -> ClaimOutcome { + use std::sync::atomic::{AtomicU64, Ordering}; + static SEQ: AtomicU64 = AtomicU64::new(0); + let seq = SEQ.fetch_add(1, Ordering::SeqCst); + let dir = workspace_root() + .join("target") + .join(format!("kernel-shadow-seams-{}-{seq}", std::process::id())); + let _ = fs::remove_dir_all(&dir); + fs::create_dir_all(&dir).expect("fixture dir"); + for (name, src) in files { + fs::write(dir.join(name), src).unwrap_or_else(|e| panic!("write {name}: {e}")); + } + let roots = vec![ + dir.to_string_lossy().into_owned(), + workspace_root().join("dag").to_string_lossy().into_owned(), + ]; + let entry_path = dir.join(entry).to_string_lossy().into_owned(); + let (graph, si) = resolve_entry_graph(&roots, &entry_path).expect("fixture resolve"); + let ctx = make_eval_context(&graph, si, ExecutionMode::Wet); + let outcome = run_claim(&ctx, function); + let _ = fs::remove_dir_all(&dir); + outcome +} + +#[test] +fn kernel_optional_record_payload_unwraps_in_match() { + let src = "module t.seam1\n\ + import std.logic { Bool }\n\ + type Tok { class: Int }\n\ + fn pick(xs: List) -> Int {\n\ + match xs |> first {\n\ + Present { value: t } => t.class\n\ + Absent => 0\n\ + }\n\ + }\n\ + test fn seam_one() -> Bool {\n\ + pick(xs: [Tok { class: 3 }]) == 3 && pick(xs: []) == 0\n\ + }\n"; + let outcome = run_fixture(&[("seam1.dag", src)], "seam1.dag", "seam_one"); + assert!( + matches!(outcome, ClaimOutcome::Pass), + "kernel-optional record-payload match must unwrap, got: {outcome:?}" + ); +} + +#[test] +fn imported_user_generic_named_optional_shadows_kernel_prelude() { + let lib = "module t.seamlib\n\ + type Optional\n\ + \x20 = Present { value: T }\n\ + \x20 | Absent\n"; + let entry = "module t.seam2\n\ + import std.logic { Bool }\n\ + import t.seamlib { Optional, Present, Absent }\n\ + type Tok { class: Int }\n\ + fn first_or(xs: List) -> Optional {\n\ + match xs |> count > 0 {\n\ + true => Present { value: xs |> fold(init: Tok { class: 0 }, f: (acc, t) => t) }\n\ + false => Absent\n\ + }\n\ + }\n\ + type Thunk { apply: fn(List) -> Int }\n\ + fn mk() -> Thunk {\n\ + Thunk { apply: fn(toks) {\n\ + match first_or(xs: toks) {\n\ + Present { value: tok } => tok.class\n\ + Absent => 0\n\ + }\n\ + } }\n\ + }\n\ + test fn seam_two() -> Bool {\n\ + let t = mk()\n\ + t.apply([Tok { class: 8 }]) == 8\n\ + }\n"; + let outcome = run_fixture( + &[("seamlib.dag", lib), ("seam2.dag", entry)], + "seam2.dag", + "seam_two", + ); + assert!( + matches!(outcome, ClaimOutcome::Pass), + "imported user generic named Optional must shadow the kernel prelude, got: {outcome:?}" + ); +} diff --git a/src/v1/tests/src/lib.rs b/src/v1/tests/src/lib.rs index 1c0359f85b1..d2ae9eb4722 100644 --- a/src/v1/tests/src/lib.rs +++ b/src/v1/tests/src/lib.rs @@ -17,6 +17,8 @@ mod bug_sentinel_ratchet; #[cfg(test)] mod cache_purity_oracle_test; #[cfg(test)] +mod constructor_owner_ruling_test; +#[cfg(test)] mod consumed_input_closure_drift_test; #[cfg(test)] mod coproduct_reflection_conformance_test; @@ -71,6 +73,8 @@ mod interpreted_parse_termination_test; #[cfg(test)] mod ir_fixture_seam_soundness_test; #[cfg(test)] +mod kernel_shadow_seams_test; +#[cfg(test)] mod list_free_monoid_chokepoint_test; #[cfg(test)] mod map_literal_string_key_test; diff --git a/src/v1/tests/src/pipeline.rs b/src/v1/tests/src/pipeline.rs index 91757a1e1b8..ea9101a8cd8 100644 --- a/src/v1/tests/src/pipeline.rs +++ b/src/v1/tests/src/pipeline.rs @@ -391,18 +391,6 @@ fn generic_type_declaration_smoke() { assert_no_diagnostics(&result); } -#[test] -fn ambiguous_variant_name_resolves_correctly() { - let source = "module ambig_test\n\ntype Color = Red | Blue | Green\ntype Signal = Red | Yellow | Green\n\nfn pick_color() -> Color { Red }\nfn pick_signal() -> Signal { Yellow }\n"; - let result = compile_dag(source); - assert_no_diagnostics(&result); - let content = find_file(&result, "src/ambig_test.rs"); - assert!( - content.contains("Color::Red") || content.contains("Signal::Red"), - "ambiguous variant Red should be qualified to a parent enum" - ); -} - #[test] fn fold_returns_accumulator_type() { let source = "module fold_acc_test\n\ntype Entry { label: String }\n\nfn pick(items: List) -> String {\n let found = fold(items, init: { label: \"default\" }, f: (acc, e) => e)\n found.label\n}\n"; @@ -1366,11 +1354,13 @@ fn resolve_entry_parse_cache_fail_closed_on_closure_parse_errors() { .duration_since(UNIX_EPOCH) .expect("clock") .as_nanos(); - let dir = std::env::temp_dir().join(format!( - "gunbc_parse_fail_closed_{}_{}", - std::process::id(), - stamp - )); + let dir = crate::helpers::workspace_root() + .join("target") + .join(format!( + "gunbc_parse_fail_closed_{}_{}", + std::process::id(), + stamp + )); std::fs::create_dir_all(&dir).expect("create temp dir"); let root = dir.to_string_lossy().into_owned(); let cleanup = || { @@ -5793,35 +5783,37 @@ fn total_cost(items: List) -> Int { assert_no_diagnostics(&result); } +// Constructor-owner ruling (§1c) rule 4: patterns resolve via the scrutinee's +// type — arm names that are NOT bound in constructor scope stay legal in +// pattern position. The consumer imports ONLY the producing fn (never the +// enum or its arms), so the match arms have no constructor binding at all; +// the scrutinee's type is the sole authority, and the emitted match still +// carries the owner qualifier. #[test] -fn duplicate_variant_names_across_enums_dont_collide() { - let source = "\ -module test_dup_variants -type Color = Red | Blue | Green -type Signal = Red | Yellow | Green - -fn pick_color() -> Color { Blue } -fn pick_signal() -> Signal { Yellow } -fn use_both(c: Color, s: Signal) -> String { - let c_str = match c { - Red => \"red\" - Blue => \"blue\" - Green => \"green\" - } - let s_str = match s { - Red => \"stop\" - Yellow => \"caution\" - Green => \"go\" - } - concat(c_str, s_str) -} -"; - let result = compile_dag(source); +fn pattern_position_arms_resolve_via_scrutinee_without_imports() { + let producer = ( + "pattern_arms_producer.dag", + "module test_pattern_arms_producer\n\ + type Level = Low | High\n\ + fn make() -> Level { Low }\n", + ); + let consumer = ( + "pattern_arms_consumer.dag", + "module test_pattern_arms_consumer\n\ + import test_pattern_arms_producer { make }\n\ + fn describe() -> String {\n\ + match make() {\n\ + Low => \"low\"\n\ + High => \"high\"\n\ + }\n\ + }\n", + ); + let result = compile_multi(&[producer, consumer]); assert_no_diagnostics(&result); - let content = find_file(&result, "src/test_dup_variants.rs"); + let content = find_file(&result, "src/test_pattern_arms_consumer.rs"); assert!( - content.contains("Color::") && content.contains("Signal::"), - "both Color:: and Signal:: qualifiers should appear in match arms, got:\n{}", + content.contains("Level::Low") && content.contains("Level::High"), + "pattern arms must qualify to the scrutinee's owner enum, got:\n{}", content ); } @@ -8092,7 +8084,7 @@ fn github_review_enums_wire_matches_screaming_snake_contract() { review_event_attrs ); let review_event_body = enum_block(&content, "pub enum ReviewEvent"); - for needle in ["Approve,", "RequestChanges,", "Comment,", "Pending,"] { + for needle in ["Approve,", "RequestChanges,", "Comment,", "PendingEvent,"] { assert!( review_event_body.contains(needle), "expected variant {needle} in ReviewEvent; got:\n{review_event_body}" @@ -8230,7 +8222,7 @@ fn anthropic_request_coproduct_wire_contracts_emit_targeted_serde() { #[test] fn coproduct_wire_contract_target_must_name_local_coproduct() { let source = r#"module stale_coproduct_wire_contract -import std.serialization { CoproductWireContract, VariantEncoding } +import std.serialization { CoproductWireContract, VariantEncoding, VariantNaming } import std.decl_ref { DeclarationRef, WholeDeclaration } data stale_contract: CoproductWireContract = { @@ -8260,7 +8252,7 @@ type RealEnum #[test] fn structural_coproduct_wire_contract_shape_is_not_authority() { let source = r#"module structural_coproduct_wire_contract -import std.serialization { VariantEncoding } +import std.serialization { VariantEncoding, VariantNaming } type FakeContract { coproduct: String @@ -8297,7 +8289,7 @@ type RealEnum #[test] fn local_same_name_coproduct_wire_contract_is_not_authority() { let source = r#"module local_spoof_coproduct_wire_contract -import std.serialization { CoproductWireContract, VariantEncoding } +import std.serialization { CoproductWireContract, VariantEncoding, VariantNaming } type CoproductWireContract { coproduct: String @@ -8330,7 +8322,7 @@ type RealEnum #[test] fn local_alias_coproduct_wire_contract_is_not_authority() { let source = r#"module local_alias_spoof_coproduct_wire_contract -import std.serialization { CoproductWireContract, VariantEncoding } +import std.serialization { CoproductWireContract, VariantEncoding, VariantNaming } type LocalContractShape { coproduct: String @@ -8369,7 +8361,7 @@ type RealEnum #[test] fn local_decl_coproduct_wire_contract_suppresses_std_import() { let source = r#"module local_decl_spoof_coproduct_wire_contract -import std.serialization { CoproductWireContract, VariantEncoding } +import std.serialization { CoproductWireContract, VariantEncoding, VariantNaming } type CoproductWireContract @@ -8398,7 +8390,7 @@ type RealEnum #[test] fn coproduct_wire_contract_affix_policy_must_match_variant_names() { let source = r#"module bad_affix_coproduct_wire_contract -import std.serialization { CoproductWireContract, VariantEncoding } +import std.serialization { CoproductWireContract, VariantEncoding, VariantNaming } import std.decl_ref { DeclarationRef, WholeDeclaration } data bad_affix_contract: CoproductWireContract = { @@ -8427,7 +8419,7 @@ type RealEnum #[test] fn coproduct_wire_contract_string_variant_requires_unit_variants() { let source = r#"module fielded_string_variant_coproduct_wire_contract -import std.serialization { CoproductWireContract, VariantEncoding } +import std.serialization { CoproductWireContract, VariantEncoding, VariantNaming } import std.decl_ref { DeclarationRef, WholeDeclaration } data string_contract: CoproductWireContract = { @@ -8460,7 +8452,7 @@ type RealEnum #[test] fn internally_tagged_coproduct_wire_contract_requires_literal_tag_field() { let source = r#"module malformed_internal_coproduct_wire_contract -import std.serialization { CoproductWireContract, VariantEncoding } +import std.serialization { CoproductWireContract, VariantEncoding, VariantNaming } import std.decl_ref { DeclarationRef, WholeDeclaration } data bad_internal_contract: CoproductWireContract = { @@ -8488,7 +8480,7 @@ type RealEnum #[test] fn coproduct_wire_contract_requires_declared_naming_fields() { let source = r#"module malformed_naming_coproduct_wire_contract -import std.serialization { CoproductWireContract, VariantEncoding } +import std.serialization { CoproductWireContract, VariantEncoding, VariantNaming } import std.decl_ref { DeclarationRef, WholeDeclaration } data missing_naming_contract: CoproductWireContract = { diff --git a/src/v1/tests/src/resolve_cross_process_cache_test.rs b/src/v1/tests/src/resolve_cross_process_cache_test.rs index aca4ff0caf2..a5f93cdb943 100644 --- a/src/v1/tests/src/resolve_cross_process_cache_test.rs +++ b/src/v1/tests/src/resolve_cross_process_cache_test.rs @@ -49,11 +49,13 @@ fn temp_dir(label: &str) -> std::path::PathBuf { .duration_since(UNIX_EPOCH) .expect("clock") .as_nanos(); - let dir = std::env::temp_dir().join(format!( - "gunbc-rg-cache-{label}-{}-{}", - std::process::id(), - nanos - )); + let dir = crate::helpers::workspace_root() + .join("target") + .join(format!( + "gunbc-rg-cache-{label}-{}-{}", + std::process::id(), + nanos + )); fs::create_dir_all(&dir).expect("temp dir"); dir } diff --git a/src/v1/tests/src/resolve_typed_cache_equivalence_test.rs b/src/v1/tests/src/resolve_typed_cache_equivalence_test.rs index 6619d4ed8dc..475c9d93b4c 100644 --- a/src/v1/tests/src/resolve_typed_cache_equivalence_test.rs +++ b/src/v1/tests/src/resolve_typed_cache_equivalence_test.rs @@ -1,5 +1,5 @@ +use crate::helpers::workspace_root; use std::fs; -use std::time::{SystemTime, UNIX_EPOCH}; use v1_compiler::cli_run::{ self, build_multi_entry_index, make_eval_context, resolve_entry_graph, @@ -30,15 +30,14 @@ fn cached(index: &cli_run::MultiEntryIndex, entry: &str, function: &str) -> Stri #[test] fn typed_module_cache_matches_cold_oracle_in_every_order() { - let nanos = SystemTime::now() - .duration_since(UNIX_EPOCH) - .expect("clock") - .as_nanos(); - let dir = std::env::temp_dir().join(format!( - "gunbc-typed-cache-eq-{}-{}", - std::process::id(), - nanos - )); + // Workspace-relative fixture root (target/ is gitignored + ephemeral): + // build_module_path_index fails closed on paths outside the workspace, so + // std::env::temp_dir() fixtures cannot resolve (same precedent as + // union_resolve_receipts_test). + let dir = workspace_root() + .join("target") + .join(format!("gunbc-typed-cache-eq-{}", std::process::id())); + let _ = fs::remove_dir_all(&dir); fs::create_dir_all(&dir).expect("temp dir"); let common = "module test.common\n\ diff --git a/src/v1/tests/src/resolved_graph_cache_size_bound_test.rs b/src/v1/tests/src/resolved_graph_cache_size_bound_test.rs index fbbff7f4eb9..b45b96727f3 100644 --- a/src/v1/tests/src/resolved_graph_cache_size_bound_test.rs +++ b/src/v1/tests/src/resolved_graph_cache_size_bound_test.rs @@ -13,11 +13,13 @@ fn temp_dir(label: &str) -> std::path::PathBuf { .duration_since(UNIX_EPOCH) .expect("clock") .as_nanos(); - let dir = std::env::temp_dir().join(format!( - "gunbc-rg-cap-{label}-{}-{}", - std::process::id(), - nanos - )); + let dir = crate::helpers::workspace_root() + .join("target") + .join(format!( + "gunbc-rg-cap-{label}-{}-{}", + std::process::id(), + nanos + )); fs::create_dir_all(&dir).expect("temp dir"); dir } diff --git a/src/v1/tests/src/type_env_scope_chain_test.rs b/src/v1/tests/src/type_env_scope_chain_test.rs index 3683b39f7f0..237f4c78bf2 100644 --- a/src/v1/tests/src/type_env_scope_chain_test.rs +++ b/src/v1/tests/src/type_env_scope_chain_test.rs @@ -36,15 +36,21 @@ impl Drop for CacheEnvGuard { } fn temp_dir(label: &str) -> std::path::PathBuf { + // Workspace-relative fixture root (target/ is gitignored + ephemeral): + // build_module_path_index fails closed on paths outside the workspace, so + // std::env::temp_dir() fixtures cannot resolve (union_resolve_receipts_test + // precedent). let nanos = SystemTime::now() .duration_since(UNIX_EPOCH) .expect("clock") .as_nanos(); - let dir = std::env::temp_dir().join(format!( - "gunbc-type-env-{label}-{}-{}", - std::process::id(), - nanos - )); + let dir = crate::helpers::workspace_root() + .join("target") + .join(format!( + "gunbc-type-env-{label}-{}-{}", + std::process::id(), + nanos + )); fs::create_dir_all(&dir).expect("temp dir"); dir } @@ -578,8 +584,13 @@ fn type_env_std_types_type_variable_filtered_from_import() { ); } +// Constructor-owner ruling (§1c, 2026-07-04): local-shadows-imported is deleted +// semantics. A glob import binding Alpha to E while a local coproduct binds +// Alpha to F is two owners in one scope — a VariantCollision, never a silent +// shadow. The fixture survives as that wall's red control; the parent module +// (sole owner) stays clean. #[test] -fn type_env_local_variant_shadows_imported_variant_local() { +fn local_variant_over_glob_imported_variant_is_a_collision() { let sources = vec![ Rc::new(SourceFile { path: "parent.dag".to_string(), @@ -590,51 +601,49 @@ fn type_env_local_variant_shadows_imported_variant_local() { content: "module test.variant_shadow_consumer\nimport test.variant_shadow_parent\ntype F = Alpha { x: String } | Gamma { z: Int }\n".to_string(), }), ]; - let resolved = compile_modules(sources); - let graph = resolved.graph.as_ref().expect("graph"); - let parent = typed_module_by_name( - &graph.modules, - &resolved.source_indices, - "test.variant_shadow_parent", - ); - let consumer = typed_module_by_name( - &graph.modules, - &resolved.source_indices, - "test.variant_shadow_consumer", - ); - let parent_alpha = parent - .type_env_cache - .variant_locals - .get("Alpha") - .expect("parent Alpha variant local"); - let consumer_alpha = consumer - .type_env_cache - .variant_locals - .get("Alpha") - .expect("consumer Alpha variant local"); - assert_eq!( - authored_name_at( - parent.type_env.source_indices.clone(), - parent_alpha.resolved.clone() - ), - "E", - "parent Alpha must point at imported enum E" - ); - assert_eq!( - authored_name_at( - consumer.type_env.source_indices.clone(), - consumer_alpha.resolved.clone() - ), - "F", - "consumer Alpha must shadow parent variant with local enum F" - ); - assert!( - parent_alpha.resolved.connective == Connective::Disj - && consumer_alpha.resolved.connective == Connective::Disj, - "variant locals must reference coproduct carriers" - ); + // Raw compile (not compile_modules): the collision IS the expected outcome, + // so the no-hard-errors helper assertion does not apply here. + let resolved = compile_to_resolved(Rc::new(sources)); + let has_collision = resolved.diagnostics.iter().any(|d| { + matches!( + &*d.diagnostic, + v1_compiler::v1_std_core::CompilerDiagnostic::VariantCollision { variant, enum1, enum2, .. } + if variant == "Alpha" + && ((enum1 == "E" && enum2 == "F") || (enum1 == "F" && enum2 == "E")) + ) + }); assert!( - !Rc::ptr_eq(&parent_alpha.resolved, &consumer_alpha.resolved), - "parent variant locals must not replace consumer-local variant bindings" - ); + has_collision, + "a local arm sharing a glob-imported arm's name must collide (shadowing is deleted), got: {:?}", + resolved + .diagnostics + .iter() + .map(|d| v1_compiler::v1_std_core::diagnostic_to_message(d.diagnostic.clone())) + .collect::>() + ); + // GREEN control: the sole-owner parent module still binds Alpha -> E. + if let Some(graph) = resolved.graph.as_ref() { + let parent = typed_module_by_name( + &graph.modules, + &resolved.source_indices, + "test.variant_shadow_parent", + ); + let parent_alpha = parent + .type_env_cache + .variant_locals + .get("Alpha") + .expect("parent Alpha variant local"); + assert_eq!( + authored_name_at( + parent.type_env.source_indices.clone(), + parent_alpha.resolved.clone() + ), + "E", + "parent Alpha must point at its sole owner E" + ); + assert!( + parent_alpha.resolved.connective == Connective::Disj, + "variant locals must reference coproduct carriers" + ); + } } diff --git a/src/v1/tests/src/variant_owner_disambiguation_test.rs b/src/v1/tests/src/variant_owner_disambiguation_test.rs index 81bf3ccb9e5..94f87ad8b34 100644 --- a/src/v1/tests/src/variant_owner_disambiguation_test.rs +++ b/src/v1/tests/src/variant_owner_disambiguation_test.rs @@ -1,95 +1,56 @@ -/// Discriminating witness for variant owner-selection correctness. -/// -/// Two grounding axes, one test each: -/// -/// §1 — local-vs-transitive: when one owner is locally defined and another is transitively -/// imported, the locally-defined owner wins (PR #5879). -/// -/// §2 — expected-type-at-site (this PR): when BOTH owners are locally defined (or both -/// imported) in the same scope, the EXPECTED/FIELD type at each individual call site -/// is the authoritative discriminant — not scope-order or alpha-sort. This is the -/// deeper §3 grounding: the single authority for which enum owns the variant at a -/// given site is the declared type of that site. +//! Discriminating witnesses for variant-owner resolution under the +//! constructor-owner ruling (§1c, operator 2026-07-04). +//! +//! These tests previously witnessed the *disambiguation* semantics +//! (expected-type-at-site picks, local-shadows-transitive, per-site +//! independence). The operator ruled that ambiguity support itself is the +//! bug: one arm name bound to two different owners in one scope is a +//! VariantCollision, never a pick. The fixtures survive as the red controls +//! for that wall, plus a positive control that the binding edge alone names +//! the owner all the way through emission. use v1_compiler::v1_compiler_artifact::RenderTarget; +use v1_compiler::v1_std_core::CompilerDiagnostic; use crate::helpers::compile_multi_target; -/// Two coproducts share the variant name `SharedV`. -/// `ATransitiveOwner` is defined in the "transitive" module and brought into "source" -/// via a specific-name import — so it lands in source's type_env.bindings transitively. -/// `ZLocalOwner` is LOCALLY DEFINED in "source". -/// -/// "consumer" imports `SharedV` from "source". The unique locally-defined parent for -/// `SharedV` in "source" is `ZLocalOwner`, so the emitter must emit `ZLocalOwner::SharedV`. +/// RED control (import-vs-local): `SharedV` arrives via a specific-name +/// import AND a local coproduct declares it — two owners in one scope is a +/// collision, not a local-wins shadow. #[test] -fn shared_variant_resolves_to_locally_defined_owner_not_transitive() { - // Module that provides ATransitiveOwner (wrong candidate, sorts first alphabetically). +fn shared_arm_via_import_and_local_decl_is_a_collision() { let transitive = ( "dag/test/disc_transitive.dag", "module test.disc_transitive\ntype ATransitiveOwner = SharedV | TransitiveOnly", ); - // Source module: imports ATransitiveOwner from transitive (pollutes its type_env), - // then locally defines ZLocalOwner (the correct owner of SharedV). let source = ( "dag/test/disc_source.dag", "module test.disc_source\n\ import test.disc_transitive { SharedV, ATransitiveOwner }\n\ type ZLocalOwner = SharedV | LocalOnly", ); - // Consumer: imports SharedV from source; fn return type is ZLocalOwner. - // Wrong owner → ATransitiveOwner::SharedV → type mismatch in inference → diagnostic. - // Correct owner → ZLocalOwner::SharedV → valid, emitted code contains ZLocalOwner::SharedV. - let consumer = ( - "dag/test/disc_consumer.dag", - "module test.disc_consumer\n\ - import test.disc_source { SharedV, ZLocalOwner }\n\ - fn make_shared() -> ZLocalOwner { SharedV }", - ); - - let result = compile_multi_target(&[transitive, source, consumer], RenderTarget::Rust); - let diags: Vec = result - .diagnostics - .iter() - .map(|d| v1_compiler::v1_std_core::diagnostic_to_message(d.diagnostic.clone())) - .filter(|m| !m.starts_with("complexity: ")) - .collect(); + let result = compile_multi_target(&[transitive, source], RenderTarget::Rust); + let has_collision = result.diagnostics.iter().any(|d| { + matches!( + &*d.diagnostic, + CompilerDiagnostic::VariantCollision { variant, .. } if variant == "SharedV" + ) + }); assert!( - diags.is_empty(), - "expected no diagnostics (wrong owner causes type mismatch), got: {diags:?}" - ); - - let emitted: String = result - .files - .iter() - .filter(|f| f.path.contains("disc_consumer")) - .map(|f| f.content.clone()) - .collect::>() - .join("\n"); - assert!( - emitted.contains("ZLocalOwner::SharedV"), - "emitted code must use the locally-defined owner ZLocalOwner, not the transitive ATransitiveOwner:\n{emitted}" - ); - assert!( - !emitted.contains("ATransitiveOwner::SharedV"), - "emitted code must NOT use the transitive owner ATransitiveOwner:\n{emitted}" + has_collision, + "an arm bound by both an import and a local declaration must collide, got: {:?}", + result + .diagnostics + .iter() + .map(|d| v1_compiler::v1_std_core::diagnostic_to_message(d.diagnostic.clone())) + .collect::>() ); } -/// §2 — expected-type-at-site grounding. -/// -/// `SharedVarAmbig` belongs to TWO locally-defined enums in a single module: -/// `ZLaterEnum` (defined first — scope will be overwritten) -/// `AEarlyEnum` (defined second — wins scope by last-write in variant_locals_from_items) -/// -/// The function return type is `ZLaterEnum`, so the expected type overrides the scope -/// winner `AEarlyEnum`. The correct emit is `ZLaterEnum::SharedVarAmbig`. -/// Without the fix the scope winner `AEarlyEnum::SharedVarAmbig` causes a type mismatch. -/// -/// Single-module (no imports): imported_enum_names is empty → no VariantCollision. +/// RED control (the deleted expected-type picker): two locally-defined +/// owners share an arm; the site's declared return type may NOT pick a +/// winner — the scope is malformed and fails closed. #[test] -fn shared_variant_resolves_by_expected_type_not_alpha_order() { - // ZLaterEnum defined first, AEarlyEnum second → AEarlyEnum wins scope (last-write). - // fn make_z return type is ZLaterEnum → expected-type override must fire. +fn two_local_owners_for_one_arm_collide_instead_of_expected_type_pick() { let source = ( "dag/test/disc_ambig.dag", "module test.disc_ambig\n\ @@ -97,60 +58,38 @@ fn shared_variant_resolves_by_expected_type_not_alpha_order() { type AEarlyEnum = SharedVarAmbig | AEarlyOnly\n\ fn make_z() -> ZLaterEnum { SharedVarAmbig }", ); - let result = compile_multi_target(&[source], RenderTarget::Rust); - let diags: Vec = result - .diagnostics - .iter() - .map(|d| v1_compiler::v1_std_core::diagnostic_to_message(d.diagnostic.clone())) - .filter(|m| !m.starts_with("complexity: ")) - .collect(); - assert!( - diags.is_empty(), - "expected no diagnostics — wrong owner causes type mismatch, got: {diags:?}" - ); - - let emitted: String = result - .files - .iter() - .filter(|f| f.path.contains("disc_ambig")) - .map(|f| f.content.clone()) - .collect::>() - .join("\n"); - assert!( - emitted.contains("ZLaterEnum::SharedVarAmbig"), - "emitted code must use the expected-type owner ZLaterEnum:\n{emitted}" - ); + let has_collision = result.diagnostics.iter().any(|d| { + matches!( + &*d.diagnostic, + CompilerDiagnostic::VariantCollision { variant, enum1, enum2, .. } + if variant == "SharedVarAmbig" + && ((enum1 == "ZLaterEnum" && enum2 == "AEarlyEnum") + || (enum1 == "AEarlyEnum" && enum2 == "ZLaterEnum")) + ) + }); assert!( - !emitted.contains("AEarlyEnum::SharedVarAmbig"), - "emitted code must NOT use the scope-order winner AEarlyEnum:\n{emitted}" + has_collision, + "two local owners for one arm must collide (expected-type picking is deleted), got: {:?}", + result + .diagnostics + .iter() + .map(|d| v1_compiler::v1_std_core::diagnostic_to_message(d.diagnostic.clone())) + .collect::>() ); } -/// §2b — per-site independence: two functions with DIFFERENT declared return types in the -/// same module, both returning the same shared variant. -/// -/// `make_early() -> AEarlyEnum` → scope already picks AEarlyEnum (last-write wins) → no -/// override needed → must emit `AEarlyEnum::SharedVarField` -/// `make_late() -> ZLaterEnum` → scope picks AEarlyEnum (wrong) → expected-type override -/// fires → must emit `ZLaterEnum::SharedVarField` -/// -/// Both functions in the same scope prove the fix is per-site not per-module. -/// Single-module (no imports): no VariantCollision possible. +/// GREEN control (emission axis): with exactly one owner in scope, the +/// binding edge names the owner and the emitted Rust carries it — no +/// expected type needed at either site. #[test] -fn shared_variant_resolves_per_site_independently() { - // ZLaterEnum defined first, AEarlyEnum second → AEarlyEnum wins scope (last-write). - // make_early return type matches scope winner → no override, correct trivially. - // make_late return type is ZLaterEnum → override fires for that site only. +fn unambiguous_arm_emits_binding_edge_owner() { let source = ( - "dag/test/disc_per_site.dag", - "module test.disc_per_site\n\ - type ZLaterEnum = SharedVarField | ZLaterOnlyF\n\ - type AEarlyEnum = SharedVarField | AEarlyOnlyF\n\ - fn make_early() -> AEarlyEnum { SharedVarField }\n\ - fn make_late() -> ZLaterEnum { SharedVarField }", + "dag/test/disc_unambig.dag", + "module test.disc_unambig\n\ + type OnlyOwner = SoleArm | OtherArm\n\ + fn make_it() -> OnlyOwner { SoleArm }", ); - let result = compile_multi_target(&[source], RenderTarget::Rust); let diags: Vec = result .diagnostics @@ -159,20 +98,15 @@ fn shared_variant_resolves_per_site_independently() { .filter(|m| !m.starts_with("complexity: ")) .collect(); assert!(diags.is_empty(), "expected no diagnostics, got: {diags:?}"); - let emitted: String = result .files .iter() - .filter(|f| f.path.contains("disc_per_site")) + .filter(|f| f.path.contains("disc_unambig")) .map(|f| f.content.clone()) .collect::>() .join("\n"); assert!( - emitted.contains("AEarlyEnum::SharedVarField"), - "make_early must emit AEarlyEnum::SharedVarField:\n{emitted}" - ); - assert!( - emitted.contains("ZLaterEnum::SharedVarField"), - "make_late must emit ZLaterEnum::SharedVarField:\n{emitted}" + emitted.contains("OnlyOwner::SoleArm"), + "emitted code must carry the binding-edge owner:\n{emitted}" ); } diff --git a/src/v2/compiler/03_normalize.dag b/src/v2/compiler/03_normalize.dag index 180cd5ddcee..9ec8b0cfa1c 100644 --- a/src/v2/compiler/03_normalize.dag +++ b/src/v2/compiler/03_normalize.dag @@ -5,6 +5,7 @@ import v2.std.collection { Absent, Present } import v2.std.grammar { ParseTree } import v2.std.diagnostic { + Correction, Diagnostic, Diagnostics, node_locus, diff --git a/src/v2/compiler/03_resolve.dag b/src/v2/compiler/03_resolve.dag index 6c22ca05239..8ce397d1fd4 100644 --- a/src/v2/compiler/03_resolve.dag +++ b/src/v2/compiler/03_resolve.dag @@ -24,6 +24,7 @@ import v2.std.collection { } import v2.std.compilers.target_model { target_model_canonical_operation_wire_node } import v2.std.diagnostic { + Correction, Diagnostic, node_locus, Outcome, diff --git a/src/v2/compiler/04_infer.dag b/src/v2/compiler/04_infer.dag index 0cfc990268d..2c41d671ab1 100644 --- a/src/v2/compiler/04_infer.dag +++ b/src/v2/compiler/04_infer.dag @@ -25,6 +25,7 @@ import v2.std.constraints { } import v2.std.diagnostic { Diagnostic, + Diagnostics, ExternalContractUnknown, Locus, node_locus, diff --git a/src/v2/compiler/07_target_carriers.dag b/src/v2/compiler/07_target_carriers.dag index f5551c31d0b..4cbfbaa611e 100644 --- a/src/v2/compiler/07_target_carriers.dag +++ b/src/v2/compiler/07_target_carriers.dag @@ -7,6 +7,7 @@ import v2.std.collection { Absent, Optional, Present } import v2.std.diagnostic { Accepted, Diagnostic, + Diagnostics, ExternalContractUnknown, Outcome, Rejected, diff --git a/src/v2/extdeps/cpp_abi.dag b/src/v2/extdeps/cpp_abi.dag index 9eb6ce93eaa..fd82f7e4319 100644 --- a/src/v2/extdeps/cpp_abi.dag +++ b/src/v2/extdeps/cpp_abi.dag @@ -56,12 +56,12 @@ type CppAbiModelPlatformCxxAbi type CppTargetDataModel type CppTargetProfile -type CppIntegerWidth8 = CppWidth8 { width: CppMachineWidth8 } -type CppIntegerWidth16 = CppWidth16 { width: CppMachineWidth16 } -type CppIntegerWidth32 = CppWidth32 { width: CppMachineWidth32 } -type CppIntegerWidth64 = CppWidth64 { width: CppMachineWidth64 } +type CppIntegerWidth8 = CppWidth8Fixed { width: CppMachineWidth8 } +type CppIntegerWidth16 = CppWidth16Fixed { width: CppMachineWidth16 } +type CppIntegerWidth32 = CppWidth32Fixed { width: CppMachineWidth32 } +type CppIntegerWidth64 = CppWidth64Fixed { width: CppMachineWidth64 } -type CppILP32IntegerWidths = CppCoreIntegerWidthModel { +type CppILP32IntegerWidths = CppILP32CoreIntegerWidthModel { char_width: CppIntegerWidth8, short_width: CppIntegerWidth16, int_width: CppIntegerWidth32, @@ -72,7 +72,7 @@ type CppILP32IntegerWidths = CppCoreIntegerWidthModel { } type CppILP32DataModel = CppTargetDataModel -type CppLP64IntegerWidths = CppCoreIntegerWidthModel { +type CppLP64IntegerWidths = CppLP64CoreIntegerWidthModel { char_width: CppIntegerWidth8, short_width: CppIntegerWidth16, int_width: CppIntegerWidth32, @@ -83,7 +83,7 @@ type CppLP64IntegerWidths = CppCoreIntegerWidthModel { } type CppLP64DataModel = CppTargetDataModel -type CppLLP64IntegerWidths = CppCoreIntegerWidthModel { +type CppLLP64IntegerWidths = CppLLP64CoreIntegerWidthModel { char_width: CppIntegerWidth8, short_width: CppIntegerWidth16, int_width: CppIntegerWidth32, @@ -94,7 +94,7 @@ type CppLLP64IntegerWidths = CppCoreIntegerWidthModel { } type CppLLP64DataModel = CppTargetDataModel -type CppILP64IntegerWidths = CppCoreIntegerWidthModel { +type CppILP64IntegerWidths = CppILP64CoreIntegerWidthModel { char_width: CppIntegerWidth8, short_width: CppIntegerWidth16, int_width: CppIntegerWidth64, diff --git a/src/v2/extdeps/formats/sql.dag b/src/v2/extdeps/formats/sql.dag index be6b9b62c3c..1e4f6409865 100644 --- a/src/v2/extdeps/formats/sql.dag +++ b/src/v2/extdeps/formats/sql.dag @@ -1,7 +1,7 @@ module v2.extdeps.formats.sql import v2.std.grammar { GrammarSchema } -import v2.std.algebra { Empty, fold_list, list_append, list_snoc_item } +import v2.std.algebra { Cons, Empty, fold_list, list_append, list_snoc_item } import v2.std.collection { Absent, List, Map, Optional, Present } import v2.std.diagnostic { Diagnostic, diff --git a/src/v2/extdeps/languages/go.dag b/src/v2/extdeps/languages/go.dag index 52275039999..a36be389282 100644 --- a/src/v2/extdeps/languages/go.dag +++ b/src/v2/extdeps/languages/go.dag @@ -123,12 +123,12 @@ type UnsignedIntWidth | UnsignedPointerSized type GoFloatWidth - = Bits32 - | Bits64 + = FloatBits32 + | FloatBits64 type GoComplexWidth - = Bits64 - | Bits128 + = ComplexBits64 + | ComplexBits128 type GoScalarKind = Int @@ -335,28 +335,28 @@ data go_facts_uintptr: GoIntegerPrimitiveFacts = GoUnsignedIntegerPrimitiveFacts data go_facts_float32: GoFloatPrimitiveFacts = GoFloatPrimitiveFacts { surface_spelling: ^go_surface_spelling_float32, - width: Bits32, + width: FloatBits32, ieee754_binary: ^go_ieee754_binary32, std_projection: ^go_std_projection_float32, } data go_facts_float64: GoFloatPrimitiveFacts = GoFloatPrimitiveFacts { surface_spelling: ^go_surface_spelling_float64, - width: Bits64, + width: FloatBits64, ieee754_binary: ^go_ieee754_binary64, std_projection: ^go_std_projection_float64, } data go_facts_complex64: GoComplexPrimitiveFacts = GoComplexPrimitiveFacts { surface_spelling: ^go_surface_spelling_complex64, - width: Bits64, + width: ComplexBits64, component_ieee754_binary: ^go_repr_complex_pair_binary32, std_projection: ^go_std_projection_complex64, } data go_facts_complex128: GoComplexPrimitiveFacts = GoComplexPrimitiveFacts { surface_spelling: ^go_surface_spelling_complex128, - width: Bits128, + width: ComplexBits128, component_ieee754_binary: ^go_repr_complex_pair_binary64, std_projection: ^go_std_projection_complex128, } @@ -449,15 +449,15 @@ fn go_unsigned_int_width_node(width: UnsignedIntWidth) -> Node { fn go_float_width_node(width: GoFloatWidth) -> Node { match width { - Bits32 => go_inhabitant_atom(id: ^go_tag_float_bits32) - Bits64 => go_inhabitant_atom(id: ^go_tag_float_bits64) + FloatBits32 => go_inhabitant_atom(id: ^go_tag_float_bits32) + FloatBits64 => go_inhabitant_atom(id: ^go_tag_float_bits64) } } fn go_complex_width_node(width: GoComplexWidth) -> Node { match width { - Bits64 => go_inhabitant_atom(id: ^go_tag_complex_bits64) - Bits128 => go_inhabitant_atom(id: ^go_tag_complex_bits128) + ComplexBits64 => go_inhabitant_atom(id: ^go_tag_complex_bits64) + ComplexBits128 => go_inhabitant_atom(id: ^go_tag_complex_bits128) } } diff --git a/src/v2/extdeps/languages/java.dag b/src/v2/extdeps/languages/java.dag index 4c7d677afe8..491b98dce05 100644 --- a/src/v2/extdeps/languages/java.dag +++ b/src/v2/extdeps/languages/java.dag @@ -85,6 +85,8 @@ import v2.std.algebra { boolean_algebra_type_node, commutative_semiring_node, commutative_semiring_type_node, + Cons, + Empty, fold_list, fold_list_right, list_append, @@ -103,8 +105,8 @@ type JavaIntWidth | Bits64 type JavaFloatWidth - = Bits32 - | Bits64 + = FloatBits32 + | FloatBits64 type JavaScalar = IntScalar { kind: JavaIntKind, width: JavaIntWidth } @@ -128,7 +130,7 @@ type JavaFloatPrimitiveFacts { } type JavaNonIntegerScalar - = BoolScalar + = NonIntegerBoolScalar | JavaNonIntegerScalarReserved type JavaNonIntegerPrimitiveFacts { @@ -215,21 +217,21 @@ data java_facts_long: JavaIntegerPrimitiveFacts = JavaIntegerPrimitiveFacts { data java_facts_float: JavaFloatPrimitiveFacts = JavaFloatPrimitiveFacts { surface_spelling: ^java_surface_spelling_float, - width: Bits32, + width: FloatBits32, ieee754_binary: ^java_ieee754_binary32, std_projection: ^java_std_projection_float, } data java_facts_double: JavaFloatPrimitiveFacts = JavaFloatPrimitiveFacts { surface_spelling: ^java_surface_spelling_double, - width: Bits64, + width: FloatBits64, ieee754_binary: ^java_ieee754_binary64, std_projection: ^java_std_projection_double, } data java_facts_boolean: JavaNonIntegerPrimitiveFacts = JavaNonIntegerPrimitiveFacts { surface_spelling: ^java_surface_spelling_boolean, - scalar: BoolScalar, + scalar: NonIntegerBoolScalar, std_projection: ^java_std_projection_boolean, representation: ^java_repr_boolean, } @@ -292,8 +294,8 @@ fn java_int_width_node(width: JavaIntWidth) -> Node { fn java_float_width_node(width: JavaFloatWidth) -> Node { match width { - Bits32 => java_inhabitant_atom(id: ^java_tag_bits32) - Bits64 => java_inhabitant_atom(id: ^java_tag_bits64) + FloatBits32 => java_inhabitant_atom(id: ^java_tag_bits32) + FloatBits64 => java_inhabitant_atom(id: ^java_tag_bits64) } } @@ -336,7 +338,7 @@ fn java_scalar_node(scalar: JavaScalar) -> Node { fn java_noninteger_scalar_as_java_scalar(scalar: JavaNonIntegerScalar) -> JavaScalar { match scalar { - BoolScalar => BoolScalar + NonIntegerBoolScalar => BoolScalar JavaNonIntegerScalarReserved => BoolScalar } } diff --git a/src/v2/extdeps/languages/kotlin.dag b/src/v2/extdeps/languages/kotlin.dag index 14ed49ab88d..51c133bebbf 100644 --- a/src/v2/extdeps/languages/kotlin.dag +++ b/src/v2/extdeps/languages/kotlin.dag @@ -102,8 +102,8 @@ type KotlinIntWidth | Bits64 type KotlinFloatWidth - = Bits32 - | Bits64 + = FloatBits32 + | FloatBits64 type KotlinRuntimeRepresentation {} @@ -138,8 +138,8 @@ type KotlinIntegerPrimitiveFacts { } type KotlinOrderedRingIntWidth - = Bits32 - | Bits64 + = OrderedRingBits32 + | OrderedRingBits64 type KotlinOrderedRingIntegerFacts = KotlinOrderedRingIntFacts { @@ -224,14 +224,14 @@ data kotlin_facts_long: KotlinIntegerPrimitiveFacts = KotlinIntegerPrimitiveFact data kotlin_facts_float: KotlinFloatPrimitiveFacts = KotlinFloatPrimitiveFacts { surface_spelling: ^kotlin_surface_spelling_float, - width: Bits32, + width: FloatBits32, value_format: Ieee754Binary32Precision, representation: kotlin_platform_specific_runtime_representation, } data kotlin_facts_double: KotlinFloatPrimitiveFacts = KotlinFloatPrimitiveFacts { surface_spelling: ^kotlin_surface_spelling_double, - width: Bits64, + width: FloatBits64, value_format: Ieee754Binary64Precision, representation: kotlin_platform_specific_runtime_representation, } @@ -354,8 +354,8 @@ fn kotlin_int_width_node(width: KotlinIntWidth) -> Node { fn kotlin_float_width_node(width: KotlinFloatWidth) -> Node { match width { - Bits32 => kotlin_atom(id: ^kotlin_tag_bits32) - Bits64 => kotlin_atom(id: ^kotlin_tag_bits64) + FloatBits32 => kotlin_atom(id: ^kotlin_tag_bits32) + FloatBits64 => kotlin_atom(id: ^kotlin_tag_bits64) } } diff --git a/src/v2/extdeps/languages/llvm_ir.dag b/src/v2/extdeps/languages/llvm_ir.dag index 4d447dd5509..ef5191870e3 100644 --- a/src/v2/extdeps/languages/llvm_ir.dag +++ b/src/v2/extdeps/languages/llvm_ir.dag @@ -36,7 +36,7 @@ import v2.std.compilers.lexing { TriviaRule, WhitespaceChar } -import v2.std.algebra { fold_list, list_append, list_snoc_item } +import v2.std.algebra { Empty, fold_list, list_append, list_snoc_item } import v2.std.cardinality { NonZeroNat } import v2.std.collection { List, Map } import v2.std.model_core { diff --git a/src/v2/extdeps/languages/machine_code.dag b/src/v2/extdeps/languages/machine_code.dag index be3282c4c23..958645280b6 100644 --- a/src/v2/extdeps/languages/machine_code.dag +++ b/src/v2/extdeps/languages/machine_code.dag @@ -16,7 +16,7 @@ import v2.std.compilers.lexing { TriviaRule, WhitespaceChar } -import v2.std.algebra { fold_list, list_snoc_item } +import v2.std.algebra { Empty, fold_list, list_snoc_item } import v2.std.collection { List, Map } import v2.std.machine { Byte } import v2.std.text { String } diff --git a/src/v2/extdeps/languages/ptx.dag b/src/v2/extdeps/languages/ptx.dag index 38f30572874..de27887f651 100644 --- a/src/v2/extdeps/languages/ptx.dag +++ b/src/v2/extdeps/languages/ptx.dag @@ -68,15 +68,15 @@ type BitsWidth | Bits128 type IntegerWidth - = Bits8 - | Bits16 - | Bits32 - | Bits64 + = IntBits8 + | IntBits16 + | IntBits32 + | IntBits64 type FloatWidth - = Bits16 - | Bits32 - | Bits64 + = FloatBits16 + | FloatBits32 + | FloatBits64 type RegisterScalarKind = Bits diff --git a/src/v2/extdeps/languages/rust.dag b/src/v2/extdeps/languages/rust.dag index 1576d3603d3..899b053e86e 100644 --- a/src/v2/extdeps/languages/rust.dag +++ b/src/v2/extdeps/languages/rust.dag @@ -229,8 +229,8 @@ type RustIntWidth | Pointer type RustFloatWidth - = Bits32 - | Bits64 + = FloatBits32 + | FloatBits64 type RustScalar = IntScalar { kind: RustIntKind, width: RustIntWidth } @@ -260,11 +260,11 @@ type RustFloatPrimitiveFacts { } type RustNonIntegerScalar - = BoolScalar - | CharScalar - | StrScalar - | UnitScalar - | NeverScalar + = NonIntegerBoolScalar + | NonIntegerCharScalar + | NonIntegerStrScalar + | NonIntegerUnitScalar + | NonIntegerNeverScalar type RustNonIntegerPrimitiveFacts { surface_spelling: Symbol @@ -438,49 +438,49 @@ data rust_facts_usize: RustIntegerPrimitiveFacts = RustIntegerPrimitiveFacts { data rust_facts_f32: RustFloatPrimitiveFacts = RustFloatPrimitiveFacts { surface_spelling: ^rust_surface_spelling_f32, - width: Bits32, + width: FloatBits32, ieee754_binary: ^rust_ieee754_binary32, std_projection: ^rust_std_projection_float32, } data rust_facts_f64: RustFloatPrimitiveFacts = RustFloatPrimitiveFacts { surface_spelling: ^rust_surface_spelling_f64, - width: Bits64, + width: FloatBits64, ieee754_binary: ^rust_ieee754_binary64, std_projection: ^rust_std_projection_float64, } data rust_facts_bool: RustNonIntegerPrimitiveFacts = RustNonIntegerPrimitiveFacts { surface_spelling: ^rust_surface_spelling_bool, - scalar: BoolScalar, + scalar: NonIntegerBoolScalar, std_projection: ^rust_std_projection_bool, representation: ^rust_repr_bool } data rust_facts_char: RustNonIntegerPrimitiveFacts = RustNonIntegerPrimitiveFacts { surface_spelling: ^rust_surface_spelling_char, - scalar: CharScalar, + scalar: NonIntegerCharScalar, std_projection: ^rust_std_projection_char, representation: ^rust_char_unicode_scalar } data rust_facts_str: RustNonIntegerPrimitiveFacts = RustNonIntegerPrimitiveFacts { surface_spelling: ^rust_surface_spelling_str, - scalar: StrScalar, + scalar: NonIntegerStrScalar, std_projection: ^rust_std_projection_str, representation: ^rust_str_unsized } data rust_facts_unit: RustNonIntegerPrimitiveFacts = RustNonIntegerPrimitiveFacts { surface_spelling: ^rust_surface_spelling_unit, - scalar: UnitScalar, + scalar: NonIntegerUnitScalar, std_projection: ^rust_std_projection_unit, representation: ^rust_repr_unit } data rust_facts_never: RustNonIntegerPrimitiveFacts = RustNonIntegerPrimitiveFacts { surface_spelling: ^rust_surface_spelling_never, - scalar: NeverScalar, + scalar: NonIntegerNeverScalar, std_projection: ^rust_std_projection_never, representation: ^rust_repr_never } @@ -561,8 +561,8 @@ fn rust_int_width_node(width: RustIntWidth) -> Node { fn rust_float_width_node(width: RustFloatWidth) -> Node { match width { - Bits32 => rust_inhabitant_atom(id: ^rust_tag_bits32) - Bits64 => rust_inhabitant_atom(id: ^rust_tag_bits64) + FloatBits32 => rust_inhabitant_atom(id: ^rust_tag_bits32) + FloatBits64 => rust_inhabitant_atom(id: ^rust_tag_bits64) } } @@ -664,11 +664,11 @@ fn rust_scalar_node(scalar: RustScalar) -> Node { fn rust_noninteger_scalar_as_rust_scalar(scalar: RustNonIntegerScalar) -> RustScalar { match scalar { - BoolScalar => BoolScalar - CharScalar => CharScalar - StrScalar => StrScalar - UnitScalar => UnitScalar - NeverScalar => NeverScalar + NonIntegerBoolScalar => BoolScalar + NonIntegerCharScalar => CharScalar + NonIntegerStrScalar => StrScalar + NonIntegerUnitScalar => UnitScalar + NonIntegerNeverScalar => NeverScalar } } diff --git a/src/v2/extdeps/languages/rust_wire_serde.dag b/src/v2/extdeps/languages/rust_wire_serde.dag index 02c7d2efe6a..c55134caf0b 100644 --- a/src/v2/extdeps/languages/rust_wire_serde.dag +++ b/src/v2/extdeps/languages/rust_wire_serde.dag @@ -46,7 +46,7 @@ import v2.std.grammar { grammar_relation_tokens_node } import v2.std.host_transport { target_emit_host_runtime_row_unconfigured } -import v2.std.node { Atom, Conj, Edge, Node, Positional, Symbol, SyntheticOccurrence, TypeNode } +import v2.std.node { Arrow, Atom, Conj, Edge, Node, Positional, Symbol, SyntheticOccurrence, TypeNode } fn rust_wire_serde_string_variant_rename_all_attr(naming: VariantNaming) -> String { match naming { diff --git a/src/v2/extdeps/languages/swift.dag b/src/v2/extdeps/languages/swift.dag index 29cd2ab049d..0901a2bb798 100644 --- a/src/v2/extdeps/languages/swift.dag +++ b/src/v2/extdeps/languages/swift.dag @@ -116,8 +116,8 @@ type SwiftIntSignedness | Unsigned type SwiftFloatWidth - = Bits32 - | Bits64 + = FloatBits32 + | FloatBits64 type SwiftStringRepresentation { tag: Symbol @@ -296,13 +296,13 @@ data swift_facts_uint64: SwiftIntegerPrimitiveFacts = SwiftIntegerPrimitiveFacts data swift_facts_float: SwiftFloatPrimitiveFacts = SwiftFloatPrimitiveFacts { surface_spelling: ^swift_surface_spelling_float, - width: Bits32, + width: FloatBits32, ieee754_binary: ^swift_tag_ieee754_binary32, } data swift_facts_double: SwiftFloatPrimitiveFacts = SwiftFloatPrimitiveFacts { surface_spelling: ^swift_surface_spelling_double, - width: Bits64, + width: FloatBits64, ieee754_binary: ^swift_tag_ieee754_binary64, } @@ -384,8 +384,8 @@ fn swift_width_node(width: SwiftIntWidth) -> Node { fn swift_float_width_node(width: SwiftFloatWidth) -> Node { match width { - Bits32 => swift_atom(id: ^swift_tag_bits32) - Bits64 => swift_atom(id: ^swift_tag_bits64) + FloatBits32 => swift_atom(id: ^swift_tag_bits32) + FloatBits64 => swift_atom(id: ^swift_tag_bits64) } } diff --git a/src/v2/lens/leaf_model_verification.dag b/src/v2/lens/leaf_model_verification.dag index 54fd88ee31f..9f432776fe6 100644 --- a/src/v2/lens/leaf_model_verification.dag +++ b/src/v2/lens/leaf_model_verification.dag @@ -95,7 +95,7 @@ import v2.std.leaf_model_verification { TargetTypeScriptCompileAccepted, TargetTypeScriptCompileRejected, } -import v2.std.node { Node, Symbol, SyntheticOccurrence } +import v2.std.node { Atom, Node, Symbol, SyntheticOccurrence, TypeNode } import v2.std.text { String } data rust_r1_happy_fixture_source: String = "pub fn r1_test() -> i32 { 0i32 }\n" diff --git a/src/v2/program.dag b/src/v2/program.dag index 0c99bf0331f..d0f753bd2c2 100644 --- a/src/v2/program.dag +++ b/src/v2/program.dag @@ -29,6 +29,7 @@ import v2.std.collection { } import v2.std.diagnostic { Accepted, + Diagnostics, Outcome, Rejected, diagnostics_singleton diff --git a/src/v2/std/bounded_lattice_completeness.dag b/src/v2/std/bounded_lattice_completeness.dag index f1571a28f61..611a728bef0 100644 --- a/src/v2/std/bounded_lattice_completeness.dag +++ b/src/v2/std/bounded_lattice_completeness.dag @@ -17,6 +17,7 @@ import v2.std.collection { List } import v2.std.diagnostic { Accepted, Diagnostic, + Diagnostics, ExternalContractUnknown, Locus, Outcome, diff --git a/src/v2/std/compilers/body_lowering.dag b/src/v2/std/compilers/body_lowering.dag index 5e460d6f5bf..bf01cffb0a6 100644 --- a/src/v2/std/compilers/body_lowering.dag +++ b/src/v2/std/compilers/body_lowering.dag @@ -1,6 +1,6 @@ module v2.std.compilers.body_lowering -import v2.std.algebra { list_append, list_snoc_item } +import v2.std.algebra { FreeMonoid, list_append, list_snoc_item } import v2.std.collection { Absent, List, Present, list_at_optional } import v2.std.diagnostic { Diagnostic, diff --git a/src/v2/std/compilers/sugar.dag b/src/v2/std/compilers/sugar.dag index 72a32285f32..ba82ed048c9 100644 --- a/src/v2/std/compilers/sugar.dag +++ b/src/v2/std/compilers/sugar.dag @@ -12,6 +12,7 @@ import v2.std.algebra { } import v2.std.collection { Absent, Present, optional_absent, optional_present } import v2.std.diagnostic { + Correction, Diagnostic, node_locus, Outcome, diff --git a/src/v2/std/compilers/target_model.dag b/src/v2/std/compilers/target_model.dag index dcad80737be..4b404b1747c 100644 --- a/src/v2/std/compilers/target_model.dag +++ b/src/v2/std/compilers/target_model.dag @@ -17,6 +17,7 @@ import v2.std.collection { import v2.std.diagnostic { Accepted, Diagnostic, + Diagnostics, ExternalContractUnknown, Outcome, Rejected, @@ -9832,8 +9833,8 @@ fn target_reference_layer_apply_value_expression( } type TargetReferenceLayerWrapped - = ReferenceLayerRc - | ReferenceLayerBox + = WrappedReferenceLayerRc + | WrappedReferenceLayerBox type TargetReferenceLayerProbe = ReferenceLayerProbe { value: TargetReferenceLayerWrapped } @@ -9857,10 +9858,10 @@ fn target_reference_layer_probe_from_emitted_type( match target_model_node_atom_identity(node: head, source_carrier: emitted) { Accepted { value: head_id, diagnostics: _ } => if head_id == tokens.rc_type_head { - ReferenceLayerProbe { value: ReferenceLayerRc } + ReferenceLayerProbe { value: WrappedReferenceLayerRc } } else { if head_id == tokens.box_type_head { - ReferenceLayerProbe { value: ReferenceLayerBox } + ReferenceLayerProbe { value: WrappedReferenceLayerBox } } else { ReferenceLayerProbeNotWrapped } diff --git a/src/v2/std/effects.dag b/src/v2/std/effects.dag index c17a9a8b2a7..d75d605a898 100644 --- a/src/v2/std/effects.dag +++ b/src/v2/std/effects.dag @@ -17,8 +17,8 @@ type BreakingCreateCause | KeylessFallback { method: Symbol } type CreateCause - = PostAlways - | KeylessFallback { method: Symbol } + = CreatePostAlways + | CreateKeylessFallback { method: Symbol } | CreateIfAbsent { key_source: KeySource } type KeySource @@ -107,9 +107,9 @@ fn effect_shape_for_create_cause(cause: CreateCause) -> EffectShape { match cause { CreateIfAbsent { key_source: key_source } => IsIdempotent(CreateIfAbsentEffect { key_source: key_source }) - PostAlways => + CreatePostAlways => IsBreaking(CreateEffect { cause: PostAlways }) - KeylessFallback { method: method } => + CreateKeylessFallback { method: method } => IsBreaking(CreateEffect { cause: KeylessFallback { method: method } }) } } @@ -168,7 +168,7 @@ fn create_if_absent_shape(key_source: KeySource) -> EffectShape { } fn create_always_shape() -> EffectShape { - effect_shape_for_create_cause(cause: PostAlways) + effect_shape_for_create_cause(cause: CreatePostAlways) } fn atom_node(s: Symbol) -> Node { @@ -400,7 +400,7 @@ data witness_create_if_absent_cause_is_idempotent: Bool = match effect_shape_for IsIdempotent(DeleteEffect { key_source: key_source }) => false } -data witness_post_always_cause_is_breaking: Bool = match effect_shape_for_create_cause(cause: PostAlways) { +data witness_post_always_cause_is_breaking: Bool = match effect_shape_for_create_cause(cause: CreatePostAlways) { IsBreaking(CreateEffect { cause: PostAlways }) => true IsIdempotent(CreateIfAbsentEffect { key_source: key_source }) => false IsBreaking(CreateEffect { cause: KeylessFallback { method: method } }) => false diff --git a/src/v2/std/inhabitant_neutralization.dag b/src/v2/std/inhabitant_neutralization.dag index 711b483e9bf..a8be303744c 100644 --- a/src/v2/std/inhabitant_neutralization.dag +++ b/src/v2/std/inhabitant_neutralization.dag @@ -9,6 +9,7 @@ import v2.std.compilers.target_model { import v2.std.diagnostic { Accepted, Diagnostic, + Diagnostics, ExternalContractUnknown, Outcome, Rejected, diff --git a/src/v2/std/integer.dag b/src/v2/std/integer.dag index 4b556a89296..84079a91707 100644 --- a/src/v2/std/integer.dag +++ b/src/v2/std/integer.dag @@ -78,15 +78,15 @@ type DecimalDigit | D9 type NonZeroDecimalDigit - = D1 - | D2 - | D3 - | D4 - | D5 - | D6 - | D7 - | D8 - | D9 + = NonZeroD1 + | NonZeroD2 + | NonZeroD3 + | NonZeroD4 + | NonZeroD5 + | NonZeroD6 + | NonZeroD7 + | NonZeroD8 + | NonZeroD9 type DecimalNonZeroMagnitude { lead: NonZeroDecimalDigit @@ -467,71 +467,71 @@ fn integer_pointer_width_bound( fn int8_interval_spec() -> IntegerIntervalSpec { integer_interval( - inclusive_min: integer_concrete_bound(sign: IntegerBoundNegative, magnitude: DecimalNonZeroMagnitude { lead: D1, tail: [D2, D8] }), - inclusive_max: integer_concrete_bound(sign: IntegerBoundNonNegative, magnitude: DecimalNonZeroMagnitude { lead: D1, tail: [D2, D7] }) + inclusive_min: integer_concrete_bound(sign: IntegerBoundNegative, magnitude: DecimalNonZeroMagnitude { lead: NonZeroD1, tail: [D2, D8] }), + inclusive_max: integer_concrete_bound(sign: IntegerBoundNonNegative, magnitude: DecimalNonZeroMagnitude { lead: NonZeroD1, tail: [D2, D7] }) ) } fn int16_interval_spec() -> IntegerIntervalSpec { integer_interval( - inclusive_min: integer_concrete_bound(sign: IntegerBoundNegative, magnitude: DecimalNonZeroMagnitude { lead: D3, tail: [D2, D7, D6, D8] }), - inclusive_max: integer_concrete_bound(sign: IntegerBoundNonNegative, magnitude: DecimalNonZeroMagnitude { lead: D3, tail: [D2, D7, D6, D7] }) + inclusive_min: integer_concrete_bound(sign: IntegerBoundNegative, magnitude: DecimalNonZeroMagnitude { lead: NonZeroD3, tail: [D2, D7, D6, D8] }), + inclusive_max: integer_concrete_bound(sign: IntegerBoundNonNegative, magnitude: DecimalNonZeroMagnitude { lead: NonZeroD3, tail: [D2, D7, D6, D7] }) ) } fn int32_interval_spec() -> IntegerIntervalSpec { integer_interval( - inclusive_min: integer_concrete_bound(sign: IntegerBoundNegative, magnitude: DecimalNonZeroMagnitude { lead: D2, tail: [D1, D4, D7, D4, D8, D3, D6, D4, D8] }), - inclusive_max: integer_concrete_bound(sign: IntegerBoundNonNegative, magnitude: DecimalNonZeroMagnitude { lead: D2, tail: [D1, D4, D7, D4, D8, D3, D6, D4, D7] }) + inclusive_min: integer_concrete_bound(sign: IntegerBoundNegative, magnitude: DecimalNonZeroMagnitude { lead: NonZeroD2, tail: [D1, D4, D7, D4, D8, D3, D6, D4, D8] }), + inclusive_max: integer_concrete_bound(sign: IntegerBoundNonNegative, magnitude: DecimalNonZeroMagnitude { lead: NonZeroD2, tail: [D1, D4, D7, D4, D8, D3, D6, D4, D7] }) ) } fn int64_interval_spec() -> IntegerIntervalSpec { integer_interval( - inclusive_min: integer_concrete_bound(sign: IntegerBoundNegative, magnitude: DecimalNonZeroMagnitude { lead: D9, tail: [D2, D2, D3, D3, D7, D2, D0, D3, D6, D8, D5, D4, D7, D7, D5, D8, D0, D8] }), - inclusive_max: integer_concrete_bound(sign: IntegerBoundNonNegative, magnitude: DecimalNonZeroMagnitude { lead: D9, tail: [D2, D2, D3, D3, D7, D2, D0, D3, D6, D8, D5, D4, D7, D7, D5, D8, D0, D7] }) + inclusive_min: integer_concrete_bound(sign: IntegerBoundNegative, magnitude: DecimalNonZeroMagnitude { lead: NonZeroD9, tail: [D2, D2, D3, D3, D7, D2, D0, D3, D6, D8, D5, D4, D7, D7, D5, D8, D0, D8] }), + inclusive_max: integer_concrete_bound(sign: IntegerBoundNonNegative, magnitude: DecimalNonZeroMagnitude { lead: NonZeroD9, tail: [D2, D2, D3, D3, D7, D2, D0, D3, D6, D8, D5, D4, D7, D7, D5, D8, D0, D7] }) ) } fn int128_interval_spec() -> IntegerIntervalSpec { integer_interval( - inclusive_min: integer_concrete_bound(sign: IntegerBoundNegative, magnitude: DecimalNonZeroMagnitude { lead: D1, tail: [D7, D0, D1, D4, D1, D1, D8, D3, D4, D6, D0, D4, D6, D9, D2, D3, D1, D7, D3, D1, D6, D8, D7, D3, D0, D3, D7, D1, D5, D8, D8, D4, D1, D0, D5, D7, D2, D8] }), - inclusive_max: integer_concrete_bound(sign: IntegerBoundNonNegative, magnitude: DecimalNonZeroMagnitude { lead: D1, tail: [D7, D0, D1, D4, D1, D1, D8, D3, D4, D6, D0, D4, D6, D9, D2, D3, D1, D7, D3, D1, D6, D8, D7, D3, D0, D3, D7, D1, D5, D8, D8, D4, D1, D0, D5, D7, D2, D7] }) + inclusive_min: integer_concrete_bound(sign: IntegerBoundNegative, magnitude: DecimalNonZeroMagnitude { lead: NonZeroD1, tail: [D7, D0, D1, D4, D1, D1, D8, D3, D4, D6, D0, D4, D6, D9, D2, D3, D1, D7, D3, D1, D6, D8, D7, D3, D0, D3, D7, D1, D5, D8, D8, D4, D1, D0, D5, D7, D2, D8] }), + inclusive_max: integer_concrete_bound(sign: IntegerBoundNonNegative, magnitude: DecimalNonZeroMagnitude { lead: NonZeroD1, tail: [D7, D0, D1, D4, D1, D1, D8, D3, D4, D6, D0, D4, D6, D9, D2, D3, D1, D7, D3, D1, D6, D8, D7, D3, D0, D3, D7, D1, D5, D8, D8, D4, D1, D0, D5, D7, D2, D7] }) ) } fn uint8_interval_spec() -> IntegerIntervalSpec { integer_interval( inclusive_min: integer_concrete_zero(), - inclusive_max: integer_concrete_bound(sign: IntegerBoundNonNegative, magnitude: DecimalNonZeroMagnitude { lead: D2, tail: [D5, D5] }) + inclusive_max: integer_concrete_bound(sign: IntegerBoundNonNegative, magnitude: DecimalNonZeroMagnitude { lead: NonZeroD2, tail: [D5, D5] }) ) } fn uint16_interval_spec() -> IntegerIntervalSpec { integer_interval( inclusive_min: integer_concrete_zero(), - inclusive_max: integer_concrete_bound(sign: IntegerBoundNonNegative, magnitude: DecimalNonZeroMagnitude { lead: D6, tail: [D5, D5, D3, D5] }) + inclusive_max: integer_concrete_bound(sign: IntegerBoundNonNegative, magnitude: DecimalNonZeroMagnitude { lead: NonZeroD6, tail: [D5, D5, D3, D5] }) ) } fn uint32_interval_spec() -> IntegerIntervalSpec { integer_interval( inclusive_min: integer_concrete_zero(), - inclusive_max: integer_concrete_bound(sign: IntegerBoundNonNegative, magnitude: DecimalNonZeroMagnitude { lead: D4, tail: [D2, D9, D4, D9, D6, D7, D2, D9, D5] }) + inclusive_max: integer_concrete_bound(sign: IntegerBoundNonNegative, magnitude: DecimalNonZeroMagnitude { lead: NonZeroD4, tail: [D2, D9, D4, D9, D6, D7, D2, D9, D5] }) ) } fn uint64_interval_spec() -> IntegerIntervalSpec { integer_interval( inclusive_min: integer_concrete_zero(), - inclusive_max: integer_concrete_bound(sign: IntegerBoundNonNegative, magnitude: DecimalNonZeroMagnitude { lead: D1, tail: [D8, D4, D4, D6, D7, D4, D4, D0, D7, D3, D7, D0, D9, D5, D5, D1, D6, D1, D5] }) + inclusive_max: integer_concrete_bound(sign: IntegerBoundNonNegative, magnitude: DecimalNonZeroMagnitude { lead: NonZeroD1, tail: [D8, D4, D4, D6, D7, D4, D4, D0, D7, D3, D7, D0, D9, D5, D5, D1, D6, D1, D5] }) ) } fn uint128_interval_spec() -> IntegerIntervalSpec { integer_interval( inclusive_min: integer_concrete_zero(), - inclusive_max: integer_concrete_bound(sign: IntegerBoundNonNegative, magnitude: DecimalNonZeroMagnitude { lead: D3, tail: [D4, D0, D2, D8, D2, D3, D6, D6, D9, D2, D0, D9, D3, D8, D4, D6, D3, D4, D6, D3, D3, D7, D4, D6, D0, D7, D4, D3, D1, D7, D6, D8, D2, D1, D1, D4, D5, D5] }) + inclusive_max: integer_concrete_bound(sign: IntegerBoundNonNegative, magnitude: DecimalNonZeroMagnitude { lead: NonZeroD3, tail: [D4, D0, D2, D8, D2, D3, D6, D6, D9, D2, D0, D9, D3, D8, D4, D6, D3, D4, D6, D3, D3, D7, D4, D6, D0, D7, D4, D3, D1, D7, D6, D8, D2, D1, D1, D4, D5, D5] }) ) } @@ -599,15 +599,15 @@ fn integer_decimal_digit_node(digit: DecimalDigit) -> Node { fn integer_nonzero_decimal_digit_widen(digit: NonZeroDecimalDigit) -> DecimalDigit { match digit { - D1 => D1 - D2 => D2 - D3 => D3 - D4 => D4 - D5 => D5 - D6 => D6 - D7 => D7 - D8 => D8 - D9 => D9 + NonZeroD1 => D1 + NonZeroD2 => D2 + NonZeroD3 => D3 + NonZeroD4 => D4 + NonZeroD5 => D5 + NonZeroD6 => D6 + NonZeroD7 => D7 + NonZeroD8 => D8 + NonZeroD9 => D9 } } diff --git a/src/v2/std/verification.dag b/src/v2/std/verification.dag index 972d2bad154..a3cd18627fe 100644 --- a/src/v2/std/verification.dag +++ b/src/v2/std/verification.dag @@ -9,6 +9,7 @@ import v2.std.diagnostic { Accepted, Correction, Diagnostic, + Diagnostics, Extent, Locus, NodeLocus, diff --git a/src/v2/test/claim/execution/bind_eval_by_execution_test.dag b/src/v2/test/claim/execution/bind_eval_by_execution_test.dag index dd5c5572114..7fb3a98f5cd 100644 --- a/src/v2/test/claim/execution/bind_eval_by_execution_test.dag +++ b/src/v2/test/claim/execution/bind_eval_by_execution_test.dag @@ -33,6 +33,7 @@ import v2.std.diagnostic { Accepted, Diagnostic, ExternalContractUnknown, + None, Outcome, Rejected, Unavailable, diff --git a/src/v2/test/claim/execution/branch_eval_by_execution_test.dag b/src/v2/test/claim/execution/branch_eval_by_execution_test.dag index e4c3635a03d..0921f9b2d71 100644 --- a/src/v2/test/claim/execution/branch_eval_by_execution_test.dag +++ b/src/v2/test/claim/execution/branch_eval_by_execution_test.dag @@ -20,6 +20,7 @@ import v2.compiler.eval { test_claim_typed_input_node } import v2.compiler.infer { InferredFacts, InferredTree } +import v2.std.algebra { Empty } import v2.std.collection { Absent, List, @@ -34,6 +35,7 @@ import v2.std.diagnostic { Accepted, Diagnostic, ExternalContractUnknown, + None, Outcome, Rejected, Unavailable, diff --git a/src/v2/test/claim/execution/effect_io_eval_by_execution_test.dag b/src/v2/test/claim/execution/effect_io_eval_by_execution_test.dag index 9702049e5af..dd408113f64 100644 --- a/src/v2/test/claim/execution/effect_io_eval_by_execution_test.dag +++ b/src/v2/test/claim/execution/effect_io_eval_by_execution_test.dag @@ -27,6 +27,7 @@ import v2.std.cardinality { RankingDimension, TerminationProof } import v2.std.collection { List, Map } import v2.std.diagnostic { Accepted, + None, Outcome, Rejected, diagnostics_singleton diff --git a/src/v2/test/claim/execution/emit_ingest_type_decl_round_trip_test.dag b/src/v2/test/claim/execution/emit_ingest_type_decl_round_trip_test.dag index 475b33d0e7c..8028d80119e 100644 --- a/src/v2/test/claim/execution/emit_ingest_type_decl_round_trip_test.dag +++ b/src/v2/test/claim/execution/emit_ingest_type_decl_round_trip_test.dag @@ -29,6 +29,7 @@ import v2.std.compilers.target_model { } import v2.std.diagnostic { Accepted, + None, Outcome, Rejected, bind_outcome, @@ -37,6 +38,7 @@ import v2.std.diagnostic { import v2.std.grammar { formal_productions_catalog_to_node } import v2.std.logic { Bool } import v2.std.node { + Conj, Edge, Named, Node, diff --git a/src/v2/test/claim/execution/loop_eval_by_execution_test.dag b/src/v2/test/claim/execution/loop_eval_by_execution_test.dag index 38e6f1ab7d8..31fbddf8cf5 100644 --- a/src/v2/test/claim/execution/loop_eval_by_execution_test.dag +++ b/src/v2/test/claim/execution/loop_eval_by_execution_test.dag @@ -38,6 +38,7 @@ import v2.std.diagnostic { Accepted, Diagnostic, ExternalContractUnknown, + None, Outcome, Rejected, Unavailable, diff --git a/src/v2/test/claim/execution/self_host_candidate_generation_test.dag b/src/v2/test/claim/execution/self_host_candidate_generation_test.dag index f0d1641ecc6..ea56341401a 100644 --- a/src/v2/test/claim/execution/self_host_candidate_generation_test.dag +++ b/src/v2/test/claim/execution/self_host_candidate_generation_test.dag @@ -36,7 +36,7 @@ import v2.test.manual.sg2_type_expression_projection { sg2_type_expr_target } import v2.std.algebra { bag_eq, length, list_snoc_item } -import v2.std.diagnostic { Accepted, Rejected } +import v2.std.diagnostic { Accepted, None, Rejected } import v2.std.logic { Bool } import v2.std.node { Symbol } diff --git a/src/v2/test/claim/extdeps_shape_transport_policy/lens_unit/clean_git_diff_test.dag b/src/v2/test/claim/extdeps_shape_transport_policy/lens_unit/clean_git_diff_test.dag index 59160b75560..7480929eda7 100644 --- a/src/v2/test/claim/extdeps_shape_transport_policy/lens_unit/clean_git_diff_test.dag +++ b/src/v2/test/claim/extdeps_shape_transport_policy/lens_unit/clean_git_diff_test.dag @@ -4,6 +4,7 @@ import v2.lens.extdeps_shape_transport_policy { ExtdepsOperationInputFact, ExtdepsTransportArgvFact, ExtdepsTransportFusionFact, + ExtdepsTransportKind, extdeps_shape_transport_policy_dead_param_count_for_operation, extdeps_shape_transport_policy_violation_count_for_operation } diff --git a/src/v2/test/claim/extdeps_shape_transport_policy/lens_unit/dead_param_cargo_build_test.dag b/src/v2/test/claim/extdeps_shape_transport_policy/lens_unit/dead_param_cargo_build_test.dag index c63bb7496e2..160b54088ba 100644 --- a/src/v2/test/claim/extdeps_shape_transport_policy/lens_unit/dead_param_cargo_build_test.dag +++ b/src/v2/test/claim/extdeps_shape_transport_policy/lens_unit/dead_param_cargo_build_test.dag @@ -3,6 +3,7 @@ module v2.test.extdeps_shape_transport_policy.lens_unit.dead_param_cargo_build import v2.lens.extdeps_shape_transport_policy { ExtdepsOperationInputFact, ExtdepsTransportArgvFact, + ExtdepsTransportKind, extdeps_shape_transport_policy_dead_param_count_for_operation } import v2.lens.extdeps_shape_transport_policy.module_refs { extdeps_cargo_build_module } diff --git a/src/v2/test/claim/extdeps_shape_transport_policy/lens_unit/dead_param_cargo_clippy_test.dag b/src/v2/test/claim/extdeps_shape_transport_policy/lens_unit/dead_param_cargo_clippy_test.dag index ce1eec8a8ff..3dfe652f2d6 100644 --- a/src/v2/test/claim/extdeps_shape_transport_policy/lens_unit/dead_param_cargo_clippy_test.dag +++ b/src/v2/test/claim/extdeps_shape_transport_policy/lens_unit/dead_param_cargo_clippy_test.dag @@ -3,6 +3,7 @@ module v2.test.extdeps_shape_transport_policy.lens_unit.dead_param_cargo_clippy import v2.lens.extdeps_shape_transport_policy { ExtdepsOperationInputFact, ExtdepsTransportArgvFact, + ExtdepsTransportKind, extdeps_shape_transport_policy_dead_param_count_for_operation } import v2.lens.extdeps_shape_transport_policy.module_refs { extdeps_cargo_build_module } diff --git a/src/v2/test/claim/extdeps_shape_transport_policy/lens_unit/dead_param_gcp_login_test.dag b/src/v2/test/claim/extdeps_shape_transport_policy/lens_unit/dead_param_gcp_login_test.dag index 4d377bfc354..0008dd71b57 100644 --- a/src/v2/test/claim/extdeps_shape_transport_policy/lens_unit/dead_param_gcp_login_test.dag +++ b/src/v2/test/claim/extdeps_shape_transport_policy/lens_unit/dead_param_gcp_login_test.dag @@ -3,6 +3,7 @@ module v2.test.extdeps_shape_transport_policy.lens_unit.dead_param_gcp_login import v2.lens.extdeps_shape_transport_policy { ExtdepsOperationInputFact, ExtdepsTransportArgvFact, + ExtdepsTransportKind, extdeps_shape_transport_policy_dead_param_count_for_operation } import v2.lens.extdeps_shape_transport_policy.module_refs { extdeps_gcp_module } diff --git a/src/v2/test/claim/extdeps_shape_transport_policy/lens_unit/policy_leak_cargo_build_test.dag b/src/v2/test/claim/extdeps_shape_transport_policy/lens_unit/policy_leak_cargo_build_test.dag index f0ba28354ff..2b0093fce59 100644 --- a/src/v2/test/claim/extdeps_shape_transport_policy/lens_unit/policy_leak_cargo_build_test.dag +++ b/src/v2/test/claim/extdeps_shape_transport_policy/lens_unit/policy_leak_cargo_build_test.dag @@ -3,6 +3,7 @@ module v2.test.extdeps_shape_transport_policy.lens_unit.policy_leak_cargo_build import v2.lens.extdeps_shape_transport_policy { ExtdepsTransportArgvFact, ExtdepsTransportFusionFact, + ExtdepsTransportKind, extdeps_shape_transport_policy_violation_count } import v2.lens.extdeps_shape_transport_policy.module_refs { extdeps_cargo_build_module } diff --git a/src/v2/test/claim/extdeps_shape_transport_policy/lens_unit/policy_leak_cargo_doc_test.dag b/src/v2/test/claim/extdeps_shape_transport_policy/lens_unit/policy_leak_cargo_doc_test.dag index 8c679f3ef17..7fff6fa702d 100644 --- a/src/v2/test/claim/extdeps_shape_transport_policy/lens_unit/policy_leak_cargo_doc_test.dag +++ b/src/v2/test/claim/extdeps_shape_transport_policy/lens_unit/policy_leak_cargo_doc_test.dag @@ -3,6 +3,7 @@ module v2.test.extdeps_shape_transport_policy.lens_unit.policy_leak_cargo_doc import v2.lens.extdeps_shape_transport_policy { ExtdepsTransportArgvFact, ExtdepsTransportFusionFact, + ExtdepsTransportKind, extdeps_shape_transport_policy_violation_count } import v2.lens.extdeps_shape_transport_policy.module_refs { extdeps_cargo_build_module } diff --git a/src/v2/test/claim/extdeps_shape_transport_policy/lens_unit/policy_leak_cargo_fmt_test.dag b/src/v2/test/claim/extdeps_shape_transport_policy/lens_unit/policy_leak_cargo_fmt_test.dag index e053699d467..1a8f96c3a29 100644 --- a/src/v2/test/claim/extdeps_shape_transport_policy/lens_unit/policy_leak_cargo_fmt_test.dag +++ b/src/v2/test/claim/extdeps_shape_transport_policy/lens_unit/policy_leak_cargo_fmt_test.dag @@ -3,6 +3,7 @@ module v2.test.extdeps_shape_transport_policy.lens_unit.policy_leak_cargo_fmt import v2.lens.extdeps_shape_transport_policy { ExtdepsTransportArgvFact, ExtdepsTransportFusionFact, + ExtdepsTransportKind, extdeps_shape_transport_policy_violation_count } import v2.lens.extdeps_shape_transport_policy.module_refs { extdeps_cargo_build_module } diff --git a/src/v2/test/claim/fact_cardinality/lens_unit/synthetic_fork_red_test.dag b/src/v2/test/claim/fact_cardinality/lens_unit/synthetic_fork_red_test.dag index ac7e4372dd1..86a7679748e 100644 --- a/src/v2/test/claim/fact_cardinality/lens_unit/synthetic_fork_red_test.dag +++ b/src/v2/test/claim/fact_cardinality/lens_unit/synthetic_fork_red_test.dag @@ -2,6 +2,7 @@ module v2.test.fact_cardinality.lens_unit.synthetic_fork_red import v2.lens.fact_cardinality { FactCardinalityDeclFact, + FactCardinalityTree, fact_cardinality_cross_tree_diverged_fork } import v2.std.logic { Bool } diff --git a/src/v2/test/claim/fact_cardinality/lens_unit/synthetic_unified_green_test.dag b/src/v2/test/claim/fact_cardinality/lens_unit/synthetic_unified_green_test.dag index 9cb3465b7af..e533a4a4489 100644 --- a/src/v2/test/claim/fact_cardinality/lens_unit/synthetic_unified_green_test.dag +++ b/src/v2/test/claim/fact_cardinality/lens_unit/synthetic_unified_green_test.dag @@ -2,6 +2,7 @@ module v2.test.fact_cardinality.lens_unit.synthetic_unified_green import v2.lens.fact_cardinality { FactCardinalityDeclFact, + FactCardinalityTree, fact_cardinality_cross_tree_coexistence, fact_cardinality_cross_tree_variant_mismatch } diff --git a/src/v2/test/claim/manual/bind_demand_driven_eval_test.dag b/src/v2/test/claim/manual/bind_demand_driven_eval_test.dag index 78f09d3792a..8d47ad5f0de 100644 --- a/src/v2/test/claim/manual/bind_demand_driven_eval_test.dag +++ b/src/v2/test/claim/manual/bind_demand_driven_eval_test.dag @@ -24,6 +24,7 @@ import v2.std.witness { Holds, Witness } import v2.std.diagnostic { Accepted, Diagnostic, + Diagnostics, ExternalContractUnknown, Outcome, Rejected, diff --git a/src/v2/test/claim/manual/branch_lazy_arm_eval_acceptance_test.dag b/src/v2/test/claim/manual/branch_lazy_arm_eval_acceptance_test.dag index 73044728da6..0c2160ff091 100644 --- a/src/v2/test/claim/manual/branch_lazy_arm_eval_acceptance_test.dag +++ b/src/v2/test/claim/manual/branch_lazy_arm_eval_acceptance_test.dag @@ -27,6 +27,7 @@ import v2.std.witness { Holds, Witness } import v2.std.diagnostic { Accepted, Diagnostic, + Diagnostics, ExternalContractUnknown, Outcome, Rejected, @@ -82,6 +83,7 @@ import v2.std.verification { Unit, manual_claim_anchor } +import v2.std.algebra { FreeMonoid } fn lazy_arm_atom(s: Symbol) -> Node { Node { diff --git a/src/v2/test/claim/manual/connective_anchors.dag b/src/v2/test/claim/manual/connective_anchors.dag index 4733bb6344d..62e32d97c76 100644 --- a/src/v2/test/claim/manual/connective_anchors.dag +++ b/src/v2/test/claim/manual/connective_anchors.dag @@ -39,6 +39,7 @@ import v2.std.node { Conj, Disj, Edge, + EdgeLabel, Instantiation, Loop, Node, diff --git a/src/v2/test/claim/manual/cpp_scalar_grounding_anchor.dag b/src/v2/test/claim/manual/cpp_scalar_grounding_anchor.dag index 2b1ac8d52b6..275148e6050 100644 --- a/src/v2/test/claim/manual/cpp_scalar_grounding_anchor.dag +++ b/src/v2/test/claim/manual/cpp_scalar_grounding_anchor.dag @@ -23,4 +23,4 @@ type cpp_anchor_data_model = CppTargetDataModel type cpp_anchor_signed_width_selection = CppSignedIntegerWidthSelection { spelling: CppSignedIntegerSurfaceSpelling, target_profile: cpp_anchor_target_profile } type cpp_anchor_wchar_t_width_selection = CppWcharTWidthSelection { spelling: CppWcharTSurfaceSpelling, target_profile: cpp_anchor_target_profile } -type cpp_anchor_signed_scalar = SignedIntegerScalar { width_selection: cpp_anchor_signed_width_selection, signed_representation: CppTwosComplement, overflow_disposition: CppRepresentableResultRequired } +type cpp_anchor_signed_scalar = SignedIntegerScalarAnchor { width_selection: cpp_anchor_signed_width_selection, signed_representation: CppTwosComplement, overflow_disposition: CppRepresentableResultRequired } diff --git a/src/v2/test/claim/manual/find_witness_project_to_core_controls.dag b/src/v2/test/claim/manual/find_witness_project_to_core_controls.dag index 37a63d9c11b..7b3ca08347f 100644 --- a/src/v2/test/claim/manual/find_witness_project_to_core_controls.dag +++ b/src/v2/test/claim/manual/find_witness_project_to_core_controls.dag @@ -51,6 +51,7 @@ import v2.std.verification { BoolWitnessClaim, UnifiedTestClaim } +import v2.std.algebra { Ordering } fn control_candidate_set(candidates: List, closedness_evidence: Node) -> CandidateSet { CandidateSet { diff --git a/src/v2/test/claim/manual/loop_demand_driven_eval_test.dag b/src/v2/test/claim/manual/loop_demand_driven_eval_test.dag index fbdae3bfe85..5a0ab9c8c47 100644 --- a/src/v2/test/claim/manual/loop_demand_driven_eval_test.dag +++ b/src/v2/test/claim/manual/loop_demand_driven_eval_test.dag @@ -23,6 +23,7 @@ import v2.std.witness { Holds, Witness } import v2.std.diagnostic { Accepted, Diagnostic, + Diagnostics, ExternalContractUnknown, Outcome, Rejected, diff --git a/src/v2/test/claim/manual/posix_output_capture.dag b/src/v2/test/claim/manual/posix_output_capture.dag index 56d354ec950..7caa157c3e4 100644 --- a/src/v2/test/claim/manual/posix_output_capture.dag +++ b/src/v2/test/claim/manual/posix_output_capture.dag @@ -1,7 +1,7 @@ module v2.test.manual.posix_output_capture import v2.extdeps.posix { CapturedOutputPipes } -import v2.std.algebra { is_empty } +import v2.std.algebra { FreeMonoid, is_empty } import v2.std.node { Node } import v2.std.verification { EqualsClaim, diff --git a/src/v2/test/claim/manual/runtime_run_test.dag b/src/v2/test/claim/manual/runtime_run_test.dag index eec8c1b65f0..46f1eab7cf3 100644 --- a/src/v2/test/claim/manual/runtime_run_test.dag +++ b/src/v2/test/claim/manual/runtime_run_test.dag @@ -12,6 +12,7 @@ import v2.std.collection { List, Map } import v2.std.dependency { DependencyView } import v2.std.diagnostic { Accepted, + Diagnostics, Outcome, Rejected, diagnostics_singleton diff --git a/src/v2/test/claim/manual/sg_rc_layering_test.dag b/src/v2/test/claim/manual/sg_rc_layering_test.dag index 563c685266f..0d438f6d14e 100644 --- a/src/v2/test/claim/manual/sg_rc_layering_test.dag +++ b/src/v2/test/claim/manual/sg_rc_layering_test.dag @@ -28,6 +28,7 @@ import v2.std.compilers.target_model { ReferenceLayerProbe, ReferenceLayerProbeNotWrapped, ReferenceLayerRc, + WrappedReferenceLayerRc, target_carrier_diagnostics_node, target_carrier_node_node, target_carrier_probe_heap_node, @@ -380,7 +381,7 @@ fn sg_rc_f6_round_trip_rc() -> Bool { projection: rust_type_expression_projection(), tokens: rust_reference_layer_tokens() ) { - ReferenceLayerProbe { value: ReferenceLayerRc } => true + ReferenceLayerProbe { value: WrappedReferenceLayerRc } => true _ => false } } diff --git a/src/v2/test/claim/manual/typescript_eager_materialize_forward_dedup_test.dag b/src/v2/test/claim/manual/typescript_eager_materialize_forward_dedup_test.dag index fa4e5b11c59..642243689f2 100644 --- a/src/v2/test/claim/manual/typescript_eager_materialize_forward_dedup_test.dag +++ b/src/v2/test/claim/manual/typescript_eager_materialize_forward_dedup_test.dag @@ -14,6 +14,7 @@ import v2.std.compilers.target_model { } import v2.std.diagnostic { Accepted, + Diagnostics, Outcome, Rejected, bind_outcome, diff --git a/src/v2/test/claim/manual/validate_ingest_staging_stage_bisect_test.dag b/src/v2/test/claim/manual/validate_ingest_staging_stage_bisect_test.dag index 77faf8bbaf4..7e8540b2003 100644 --- a/src/v2/test/claim/manual/validate_ingest_staging_stage_bisect_test.dag +++ b/src/v2/test/claim/manual/validate_ingest_staging_stage_bisect_test.dag @@ -26,6 +26,7 @@ import v2.extdeps.languages.dag { dag_language_model_wave1 } import v2.std.collection { List } import v2.std.diagnostic { Accepted, + Diagnostics, Outcome, Rejected, bind_outcome, diff --git a/src/v2/test/claim/parse/parse_table_memo_amortization_test.dag b/src/v2/test/claim/parse/parse_table_memo_amortization_test.dag index 92727aaf66e..81c40e7895f 100644 --- a/src/v2/test/claim/parse/parse_table_memo_amortization_test.dag +++ b/src/v2/test/claim/parse/parse_table_memo_amortization_test.dag @@ -6,7 +6,7 @@ import v2.compiler.source_authority { DagSourceReadWitness, SourceRootIngest } import v2.extdeps.languages.dag { dag_language_model_wave1 } import v2.std.algebra { Cons, Empty, fold_list, length } import v2.std.artifact { Artifact, SourceFile } -import v2.std.diagnostic { Accepted, Outcome, Rejected, outcome_accepted } +import v2.std.diagnostic { Accepted, None, Outcome, Rejected, outcome_accepted } import v2.std.logic { Bool } import v2.std.cross_tree.import_model { V2Tree } import v2.std.node { Hash, Node, content_hash } diff --git a/src/v2/test/fixture/extdeps_external_authority/bogus_scheme/plant.dag b/src/v2/test/fixture/extdeps_external_authority/bogus_scheme/plant.dag index 6a8ebe53a41..6a919b72433 100644 --- a/src/v2/test/fixture/extdeps_external_authority/bogus_scheme/plant.dag +++ b/src/v2/test/fixture/extdeps_external_authority/bogus_scheme/plant.dag @@ -3,6 +3,10 @@ module extdeps.fixture.external_authority_bogus_scheme import extdeps.external_authority { ExternalAuthority } import extdeps.uri { Uri } +type BogusUriScheme + = Gopher + | Wais + data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { uri: Uri { scheme: Gopher diff --git a/src/v2/test/fixture/medium_structure_scan/leak/plant.dag b/src/v2/test/fixture/medium_structure_scan/leak/plant.dag index 6f5ddba8896..799689ce632 100644 --- a/src/v2/test/fixture/medium_structure_scan/leak/plant.dag +++ b/src/v2/test/fixture/medium_structure_scan/leak/plant.dag @@ -1,7 +1,7 @@ module v2.test.fixture.medium_structure_scan.leak_plant import v2.std.logic { Bool } import v2.std.text { String } -import std.markup { Fragment, TextNode } +import std.markup { Fragment, FragmentText } import extdeps.languages.html_fragment { serialize_fragment } fn planted_emit_side_leak() -> String { @@ -30,7 +30,7 @@ fn planted_shell_emit_side_leak() -> String { } fn planted_html_check_side_leak() -> Bool { - let html = serialize_fragment(frag: TextNode { content: "x" }) + let html = serialize_fragment(frag: FragmentText { content: "x" }) starts_with(s: html, prefix: "<") } diff --git a/src/v2/workflow/affected_set_floor_runner.dag b/src/v2/workflow/affected_set_floor_runner.dag index 796fa4bc95e..a31cfe5fb8c 100644 --- a/src/v2/workflow/affected_set_floor_runner.dag +++ b/src/v2/workflow/affected_set_floor_runner.dag @@ -162,6 +162,8 @@ fn floor_kernel_would_skip( } } +data floor_host_scaffold_would_skip_note: String = "Host-scan walls read the live tree via host intrinsics (`layer_import_facts`, `*_facts_live`, ...). Node-frontier skip is unsound for them: an unrelated diff can leave a corpus-wide violation red while the witness is assumed green." + fn floor_host_scaffold_would_skip( changed_paths: List, frontier_nodes: List, @@ -169,9 +171,6 @@ fn floor_host_scaffold_would_skip( function_edited: Bool, entry_file_touched: Bool ) -> Bool { - // Host-scan walls read the live tree via host intrinsics (`layer_import_facts`, - // `*_facts_live`, …). Node-frontier skip is unsound for them: an unrelated diff - // can leave a corpus-wide violation red while the witness is assumed green. false } @@ -216,14 +215,14 @@ fn floor_kernel_precompute_would_skip( } } +data floor_host_scaffold_precompute_would_skip_note: String = "Conservative: host-scaffold witnesses in the corpus always run, so the whole-tree published-mock precompute they may depend on must not be skipped." + fn floor_host_scaffold_precompute_would_skip( changed_paths: List, frontier_node_count: Int, edited_test_fn_count: Int, touched_entry_file_count: Int ) -> Bool { - // Conservative: host-scaffold witnesses in the corpus always run, so the - // whole-tree published-mock precompute they may depend on must not be skipped. false }