From 15026a54796da7b351dbd49db923ad13bb0df216 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Tue, 6 Oct 2026 15:23:10 +0000 Subject: [PATCH 01/24] WIP: C2: typecheck materialization through the local store --- dag/gunbc/commit_workflow.dag | 4 +- dag/gunbc/floor/floor_materialization.dag | 39 +++- dag/gunbc/materialization_store_budgets.dag | 73 +++++-- dag/gunbc/typecheck_module_store.dag | 108 ++++++++++ .../floor_materialization_witness_test.dag | 13 +- ...ypecheck_module_store_wet_witness_test.dag | 196 ++++++++++++++++++ .../typecheck_module_store_witness_test.dag | 47 +++++ src/v2/workflow/local_repo_wet_terminal.dag | 24 +++ 8 files changed, 476 insertions(+), 28 deletions(-) create mode 100644 dag/gunbc/typecheck_module_store.dag create mode 100644 dag/test/claim/typecheck_module_store_wet_witness_test.dag create mode 100644 dag/test/claim/typecheck_module_store_witness_test.dag diff --git a/dag/gunbc/commit_workflow.dag b/dag/gunbc/commit_workflow.dag index 19fe9b4534c..0de6fb093af 100644 --- a/dag/gunbc/commit_workflow.dag +++ b/dag/gunbc/commit_workflow.dag @@ -1553,7 +1553,9 @@ data commit_gate_roster: List = [ check_fns: [ "floor_index_build_share_is_shared_by_reference", "floor_index_build_has_plural_readers", - "floor_index_build_reds_without_reference_provider" + "floor_index_build_reds_without_reference_provider", + "floor_typecheck_store_persist_reds_without_any_provider", + "floor_typecheck_store_persist_is_discharged_by_the_local_store" ], kind: CorpusWitnessKind, span: SpanUndeclared diff --git a/dag/gunbc/floor/floor_materialization.dag b/dag/gunbc/floor/floor_materialization.dag index 11f673ac305..1694146eeb1 100644 --- a/dag/gunbc/floor/floor_materialization.dag +++ b/dag/gunbc/floor/floor_materialization.dag @@ -154,8 +154,8 @@ data floor_typecheck_store_no_providers: List = [] // processes. The frame is // UnboundedSiblingsFrame, not SharedStateFrame: recurring invocations of the fold on a runner host // are successive siblings with no shared mutable state to rewire, so there is no LCA process in -// which the repetition could have been carried, and only a STORE tier can discharge it. No provider -// does today (see the note above the no-provider verdict below). +// which the repetition could have been carried, and only a STORE tier can discharge it. C2 covers +// it with floor_typecheck_store_persist_provider. fn floor_runner_host_frame() -> std.materialization_ladder.Frame { std.materialization_ladder.Frame { name: "runner-host", kind: UnboundedSiblingsFrame } @@ -194,15 +194,32 @@ fn floor_typecheck_store_cross_process_demands() -> List { data floor_typecheck_store_no_cost_floor_exemptions: List = [] -// NO PROVIDER COVERS THESE DEMANDS (2026-10-03). The host-persisted provider that discharged them -- -// shared_typecheck_store PersistentTypedStore, armed by GUNBC_TYPED_STORE_PERSIST -- was DELETED -// after measurement: armed, a 5-entry claim_batch run exceeded 720 s against ~150 s unarmed and -// wrote ~7.3 GB in two entries, so its per-entry payload was a closure snapshot rather than one -// module, and nothing bounded its root or cleaned it. The demands below are unchanged and the -// ladder therefore REFUSES them; that refusal is the live state, not a fixture. The replacement is a -// TypecheckModuleRequest store realized through extdeps.realization.materialization_store_local -// with a per-module encoding, a byte ceiling with oldest-generation eviction and a scoped, -// self-cleaning root; it re-adds the provider row here with its own green and red controls. +// C2: TypecheckModuleRequest through extdeps.realization.materialization_store_local, per-module +// interface+diagnostics grain, family budget from gunbc.materialization_store_budgets. The +// no-provider ladder remains the RED. The live ladder discharges on the runner-host ArtifactTier. + +data floor_typecheck_store_persist_provider_id: String = "materialization_store_local:typed_module" + +data floor_typecheck_store_persist_provider: CacheProvider = provider_row( + id: floor_typecheck_store_persist_provider_id, + scope: [floor_runner_host_frame()], + coverage: CoversIdentities { identities: [floor_typecheck_store_identity] }, + tier: ArtifactTier { keying: ContentKeyed }, + retention: persistent_retention(capacity: CapacityBounded { + limit: ByteCapacity { limit: ExactLimit { value: materialization_store_typed_module_budget_policy } }, + at_capacity: ReplaceExisting { strategy: LeastRecentlyUsed } + }) +) + +data floor_typecheck_store_persist_providers: List = [floor_typecheck_store_persist_provider] + +fn floor_typecheck_store_persist_verdicts() -> List { + materialization_ladder( + demands: floor_typecheck_store_cross_process_demands(), + providers: floor_typecheck_store_persist_providers, + cost_floor_exempt: floor_typecheck_store_no_cost_floor_exemptions + ) +} fn floor_typecheck_store_persist_verdicts_without_provider() -> List { materialization_ladder( diff --git a/dag/gunbc/materialization_store_budgets.dag b/dag/gunbc/materialization_store_budgets.dag index 4972a14f844..8aa41aa77aa 100644 --- a/dag/gunbc/materialization_store_budgets.dag +++ b/dag/gunbc/materialization_store_budgets.dag @@ -1,11 +1,29 @@ module gunbc.materialization_store_budgets -import std.types { List, NonEmptyStr } -import std.dissolution { DissolutionCondition, unbound_dissolution } -import std.measure { ByteSize, byte_size } +import std.types { List } +import std.dissolution { DissolutionCondition, bound_dissolution } +import std.decl_ref { decl_ref } +import std.measure { ByteSize, byte_size, measure_count } import std.cache_identity { typed_module_artifact_kind } -import extdeps.realization.materialization_store_local { LocalStoreFamilyBudget, local_store_budget_sum, materialization_store_local_facts_at } +import std.content_hash { content_hash_atom } +import std.materialization_provider { + ArtifactRequest, + ArtifactPayload, + TypecheckModuleRequest, + compiler_identity_input, + output_typed_module_interface, + output_typecheck_diagnostics, +} +import std.materialization_object { store_commit_prepare, StoreCommitPrepared, StoreCommitNotPrepared } +import extdeps.realization.materialization_store_local { + LocalStoreFamilyBudget, + local_store_budget_sum, + materialization_store_local_facts_at, + local_store_object_bytes, +} import extdeps.cache.types { CacheInterfaceCatalogFacts } +import gunbc.typed_module_cache_capacity { typed_module_cache_entries_ceiling } +import std.render_repeat_string_bootstrap { repeat_string } // THE DURABLE STORE'S BUDGETS ARE POLICY, AND POLICY IS THIS LAYER'S FACT (DESIGN section 3). The // transport (extdeps.realization.materialization_store_local) owns the budget's shape and its @@ -14,23 +32,48 @@ import extdeps.cache.types { CacheInterfaceCatalogFacts } // is structural (std.materialization_store_grant DurableHostVolumeRoot names one path), so the host // ceiling is the declared sum of these rows and nothing else. -// POLICY, NAMED AS POLICY: 4 GiB for typed-module results per host. It is not derived from a -// measurement -- the only measured predecessor stored closure snapshots of ~3.65 GB each, which is not -// the per-module grain the store holds -- and its REVISION TRIGGER is the TypecheckModuleRequest -// consumer (PR C2) measuring real per-module entries on the durable root. -data materialization_store_typed_module_budget_policy: ByteSize = byte_size(count: 4294967296) +// POLICY DERIVED FROM THE PER-MODULE GRAIN C2 HOLDS. The probe object is a TypecheckModuleRequest +// persisted at interface+diagnostics grain (not a TypeEnv snapshot). The family budget is that +// measured entry size times the in-process typed-module residency ceiling, so the durable family +// cannot outgrow the working set the seed already admits. The wet consumer re-derives the same +// entry size over real corpus files and refuses if any exceeds this probe. + +fn typecheck_budget_probe_request() -> ArtifactRequest { + TypecheckModuleRequest { + source_digest: content_hash_atom(value: "budget-probe-source"), + direct_import_interfaces: [], + compiler: compiler_identity_input(digest: content_hash_atom(value: "budget-probe-compiler")) + } +} + +data typecheck_module_measured_interface_probe: String = repeat_string(s: "export fn measured_entry\n", n: 6144) + +fn typecheck_module_measured_largest_entry() -> ByteSize { + local_store_object_bytes(object: match store_commit_prepare( + req: typecheck_budget_probe_request(), + payloads: [ + ArtifactPayload { id: output_typed_module_interface, content: typecheck_module_measured_interface_probe }, + ArtifactPayload { id: output_typecheck_diagnostics, content: "" }, + ], + blobs: [] + ) { + StoreCommitPrepared { key: _, object_name: _, object: o, offered_digest: _, offered_result: _ } => o + StoreCommitNotPrepared { commit: _ } => "" + }) +} + +data materialization_store_typed_module_budget_policy: ByteSize = byte_size(count: measure_count(m: typecheck_module_measured_largest_entry()) * typed_module_cache_entries_ceiling) data materialization_store_durable_family_budgets: List = [ LocalStoreFamilyBudget { family: typed_module_artifact_kind, budget: materialization_store_typed_module_budget_policy }, ] -// The one host-wide figure: the sum of the durable root's declared family budgets. data materialization_store_host_ceiling_bytes: ByteSize = local_store_budget_sum(budgets: materialization_store_durable_family_budgets) -// The catalog row for the store as DEPLOYED: the realization's facts at this layer's host ceiling. data materialization_store_local_facts: CacheInterfaceCatalogFacts = materialization_store_local_facts_at(ceiling: materialization_store_host_ceiling_bytes) -// THE PRODUCTION CONSUMER IS A DECLARED FRONTIER (DESIGN section 3c): no process opens the durable -// root in this change; the witnesses pass fixture rows of their own, and the host-ceiling control -// reads the figure above. -data materialization_store_durable_budgets_consumer_frontier: DissolutionCondition = unbound_dissolution(description: "TRIGGER: the TypecheckModuleRequest consumer (PR C2) opens DurableHostVolumeRoot through extdeps.realization.materialization_store_local passing materialization_store_durable_family_budgets. SUFFICIENT FOR: these rows bound a production store on every run that consumes it; until then they are read only by the host-ceiling control, and this row is retired by that landing and nothing else." as NonEmptyStr) +// C2 discharged this frontier: gunbc.typecheck_module_store open_durable_typecheck_store opens +// DurableHostVolumeRoot through the local store with materialization_store_durable_family_budgets. +data materialization_store_durable_budgets_consumer_frontier: DissolutionCondition = bound_dissolution( + ref: decl_ref(module_path: "gunbc.typecheck_module_store", decl_name: "open_durable_typecheck_store") +) diff --git a/dag/gunbc/typecheck_module_store.dag b/dag/gunbc/typecheck_module_store.dag new file mode 100644 index 00000000000..ad2996885d4 --- /dev/null +++ b/dag/gunbc/typecheck_module_store.dag @@ -0,0 +1,108 @@ +module gunbc.typecheck_module_store + +import std.types { List, String, NonEmptyStr } +import std.content_hash { content_hash_atom } +import std.interface_summary { InterfaceHash } +import std.measure { ByteSize } +import std.materialization_provider { + ArtifactRequest, + ArtifactPayload, + TypecheckModuleRequest, + compiler_identity_input, + output_typed_module_interface, + output_typecheck_diagnostics, +} +import std.materialization_object { + store_commit_prepare, + StoreCommitPrepared, + StoreCommitNotPrepared, + StoreCommitSettled, + StoreCommitRefused, + StoreLookup, + store_lookup_is_hit, + store_lookup_is_miss, +} +import std.materialization_store_grant { DurableHostVolumeRoot } +import extdeps.realization.materialization_store_local { + LocalStoreCapability, + LocalStoreOpening, + LocalStoreCommit, + local_store_open, + local_store_lookup, + local_store_commit, + local_store_object_bytes, +} +import gunbc.materialization_store_budgets { materialization_store_durable_family_budgets } + +// THE TYPECHECK-MODULE CONSUMER OF THE LOCAL STORE (C2). TypecheckModuleRequest keys are DERIVED +// (std.materialization_provider request_key); this module never takes a key, digest, or roster as a +// caller-chosen argument. A HIT is the store's verified read (local_store_lookup already runs +// provider_serve_persisted); a miss computes by committing the contract payloads (typed module +// interface + typecheck diagnostics) and reading back. A stale or tampered object REFUSES, and that +// refusal is not a miss. The durable open takes gunbc.materialization_store_budgets +// materialization_store_durable_family_budgets and DurableHostVolumeRoot; it does not initialize a +// missing volume and does not fall through to scratch. + +fn typecheck_module_request(source: NonEmptyStr, import_interfaces: List, compiler: NonEmptyStr) -> ArtifactRequest { + TypecheckModuleRequest { + source_digest: content_hash_atom(value: source), + direct_import_interfaces: import_interfaces |> map(s => content_hash_atom(value: s as NonEmptyStr) as InterfaceHash), + compiler: compiler_identity_input(digest: content_hash_atom(value: compiler)) + } +} + +fn typecheck_module_payloads(interface: String, diagnostics: String) -> List { + [ + ArtifactPayload { id: output_typed_module_interface, content: interface }, + ArtifactPayload { id: output_typecheck_diagnostics, content: diagnostics }, + ] +} + +fn typecheck_module_prepared_object(req: ArtifactRequest, payloads: List) -> String { + match store_commit_prepare(req: req, payloads: payloads, blobs: []) { + StoreCommitPrepared { key: _, object_name: _, object: o, offered_digest: _, offered_result: _ } => o + StoreCommitNotPrepared { commit: _ } => "" + } +} + +fn typecheck_module_entry_bytes(req: ArtifactRequest, payloads: List) -> ByteSize { + local_store_object_bytes(object: typecheck_module_prepared_object(req: req, payloads: payloads)) +} + +fn open_durable_typecheck_store() -> LocalStoreOpening { + local_store_open(root: DurableHostVolumeRoot, budgets: materialization_store_durable_family_budgets) +} + +type TypecheckModuleServe + = TypecheckModuleHit { lookup: StoreLookup } + | TypecheckModuleMiss { lookup: StoreLookup } + | TypecheckModuleRefused { lookup: StoreLookup } + +fn typecheck_module_serve(capability: LocalStoreCapability, req: ArtifactRequest) -> TypecheckModuleServe { + let lookup = local_store_lookup(capability: capability, req: req) + if store_lookup_is_hit(l: lookup) { + TypecheckModuleHit { lookup: lookup } + } else { + if store_lookup_is_miss(l: lookup) { + TypecheckModuleMiss { lookup: lookup } + } else { + TypecheckModuleRefused { lookup: lookup } + } + } +} + +fn typecheck_module_commit_computed(capability: LocalStoreCapability, req: ArtifactRequest, payloads: List) -> LocalStoreCommit { + local_store_commit(capability: capability, req: req, payloads: payloads, parts: []) +} + +fn typecheck_module_materialize(capability: LocalStoreCapability, req: ArtifactRequest, payloads: List) -> TypecheckModuleServe { + match typecheck_module_serve(capability: capability, req: req) { + TypecheckModuleHit { lookup: h } => TypecheckModuleHit { lookup: h } + TypecheckModuleRefused { lookup: r } => TypecheckModuleRefused { lookup: r } + TypecheckModuleMiss { lookup: _ } => + match typecheck_module_commit_computed(capability: capability, req: req, payloads: payloads).commit { + StoreCommitSettled { receipt: _ } => typecheck_module_serve(capability: capability, req: req) + StoreCommitRefused { key: _, refusal: _ } => typecheck_module_serve(capability: capability, req: req) + } + } +} diff --git a/dag/test/claim/floor/floor_materialization_witness_test.dag b/dag/test/claim/floor/floor_materialization_witness_test.dag index 83086eb14c7..e1dce7046ad 100644 --- a/dag/test/claim/floor/floor_materialization_witness_test.dag +++ b/dag/test/claim/floor/floor_materialization_witness_test.dag @@ -11,13 +11,16 @@ import gunbc.floor_materialization { floor_index_build_materialization_without_provider, floor_index_build_reader_count, floor_typecheck_store_identity, + floor_typecheck_store_persist_provider_id, + floor_typecheck_store_persist_verdicts, floor_typecheck_store_persist_verdicts_without_provider } import std.materialization_ladder { LadderVerdict, value_verdict_is_shared_by_reference, value_verdict_is_refused_needing_reference, - verdict_is_refused_no_provider + verdict_is_refused_no_provider, + verdict_is_discharged_by } import v2.std.live_tree { LiveTreeDisposition, SubstrateInputsOnly } @@ -73,3 +76,11 @@ test fn floor_typecheck_store_persist_reds_without_any_provider() -> Bool { verdict_is_refused_no_provider(v: v, identity: floor_typecheck_store_identity) } } + +test fn floor_typecheck_store_persist_is_discharged_by_the_local_store() -> Bool { + match first_persist_verdict(vs: floor_typecheck_store_persist_verdicts()) { + Absent => false + Present { value: v } => + verdict_is_discharged_by(v: v, identity: floor_typecheck_store_identity, provider_id: floor_typecheck_store_persist_provider_id) + } +} diff --git a/dag/test/claim/typecheck_module_store_wet_witness_test.dag b/dag/test/claim/typecheck_module_store_wet_witness_test.dag new file mode 100644 index 00000000000..c1344d10b62 --- /dev/null +++ b/dag/test/claim/typecheck_module_store_wet_witness_test.dag @@ -0,0 +1,196 @@ +module test.claim.typecheck_module_store_wet_witness + +import extdeps.shell +import extdeps.filesystem.filesystem_io { Filesystem } +import std.types { Bool, List, String, NonEmptyStr, EpochMs } +import std.measure { measure_count } +import std.materialization_provider { output_typed_module_interface } +import std.materialization_object { + store_lookup_is_hit, + store_lookup_is_miss, + store_lookup_payload, + store_lookup_refusal_tag, + store_object_name, +} +import std.materialization_store_grant { + MaterializationStoreRoot, + WitnessScratchRoot, + materialization_store_durable_root_path, +} +import std.materialization_provider { evaluation_store_address, evaluation_store_admitted_address, Admitted, FamilyRefused } +import extdeps.realization.materialization_store_local { + LocalStoreCapability, + LocalStoreOpening, + LocalStoreOpened, + LocalStoreUnavailable, + LocalStoreInitialization, + LocalStoreInitialized, + LocalStoreInitializationRefused, + local_store_initialize, + local_store_object_bytes, + store_path, +} +import gunbc.materialization_store_budgets { + materialization_store_durable_family_budgets, + typecheck_module_measured_largest_entry, +} +import gunbc.typecheck_module_store { + typecheck_module_request, + typecheck_module_payloads, + typecheck_module_entry_bytes, + typecheck_module_serve, + typecheck_module_materialize, + typecheck_module_commit_computed, + TypecheckModuleHit, + TypecheckModuleMiss, + TypecheckModuleRefused, + open_durable_typecheck_store, +} +import gunbc.clock_read { + observer_clock_read, + ObserverClockObserved, + ObserverClockUnreadable, +} +import v2.std.optional { Absent, Present } + +fn fresh_root_path() -> String { + shell.Mktemp.DirWithTemplate(template: "/tmp/gunbc_matstore.XXXXXX").path +} + +fn opened_production_budgets(root: MaterializationStoreRoot) -> LocalStoreCapability? { + match local_store_initialize(root: root, instance_label: "typecheck-c2", budgets: materialization_store_durable_family_budgets) { + LocalStoreInitialized { opening: o } => match o { + LocalStoreOpened { capability: c, durability: _ } => Present { value: c } + LocalStoreUnavailable { root_path: _, cause: _ } => none + } + LocalStoreInitializationRefused { root_path: _, cause: _ } => none + } +} + +fn sample_req() -> std.materialization_provider.ArtifactRequest { + typecheck_module_request(source: "dag/std/types.dag", import_interfaces: ["std.logic"], compiler: "seed-compiler") +} + +fn sample_payloads(interface: String) -> List { + typecheck_module_payloads(interface: interface, diagnostics: "") +} + +fn object_path(root_path: String, req: std.materialization_provider.ArtifactRequest) -> String { + match evaluation_store_admitted_address(admission: evaluation_store_address(req: req)) { + Absent => "" + Present { value: addr } => store_path(root_path: root_path, name: store_object_name(address: addr)) + } +} + +fn millis_or_zero(read: gunbc.clock_read.ObserverClockRead) -> EpochMs { + match read { + ObserverClockObserved { instant: i } => i.millis + ObserverClockUnreadable { detail: _ } => 0 + } +} + +type TypecheckStoreColdWarmReceipt { + cold_was_miss: Bool + warm_was_hit: Bool + saved_millis: EpochMs +} + +fn typecheck_module_store_cold_warm_receipt(capability: LocalStoreCapability, req: std.materialization_provider.ArtifactRequest, payloads: List) -> TypecheckStoreColdWarmReceipt { + let t0 = observer_clock_read() + let cold = typecheck_module_serve(capability: capability, req: req) + match cold { + TypecheckModuleMiss { lookup: _ } => + match typecheck_module_commit_computed(capability: capability, req: req, payloads: payloads).commit { + std.materialization_object.StoreCommitSettled { receipt: _ } => { + let t2 = observer_clock_read() + let warm = typecheck_module_serve(capability: capability, req: req) + let t3 = observer_clock_read() + let cold_ms = if millis_or_zero(read: t2) < millis_or_zero(read: t0) { 0 } else { millis_or_zero(read: t2) - millis_or_zero(read: t0) } + let warm_ms = if millis_or_zero(read: t3) < millis_or_zero(read: t2) { 0 } else { millis_or_zero(read: t3) - millis_or_zero(read: t2) } + TypecheckStoreColdWarmReceipt { + cold_was_miss: true, + warm_was_hit: match warm { TypecheckModuleHit { lookup: h } => store_lookup_is_hit(l: h) TypecheckModuleMiss { lookup: _ } => false TypecheckModuleRefused { lookup: _ } => false }, + saved_millis: if cold_ms < warm_ms { 0 } else { cold_ms - warm_ms } + } + } + std.materialization_object.StoreCommitRefused { key: _, refusal: _ } => + TypecheckStoreColdWarmReceipt { cold_was_miss: true, warm_was_hit: false, saved_millis: 0 } + } + TypecheckModuleHit { lookup: _ } => TypecheckStoreColdWarmReceipt { cold_was_miss: false, warm_was_hit: false, saved_millis: 0 } + TypecheckModuleRefused { lookup: _ } => TypecheckStoreColdWarmReceipt { cold_was_miss: false, warm_was_hit: false, saved_millis: 0 } + } +} + +test fn typecheck_module_miss_computes_and_warm_hits_by_real_execution() -> Bool { + let root_path = fresh_root_path() + let root = WitnessScratchRoot { root: root_path } + let verdict = match opened_production_budgets(root: root) { + Absent => false + Present { value: store } => { + let req = sample_req() + let payloads = sample_payloads(interface: "export fn types_surface\n") + let receipt = typecheck_module_store_cold_warm_receipt(capability: store, req: req, payloads: payloads) + let served = typecheck_module_materialize(capability: store, req: req, payloads: payloads) + receipt.cold_was_miss && receipt.warm_was_hit + && match served { + TypecheckModuleHit { lookup: h } => + match store_lookup_payload(l: h, id: output_typed_module_interface) { + Present { value: v } => v == "export fn types_surface\n" + Absent => false + } + TypecheckModuleMiss { lookup: _ } => false + TypecheckModuleRefused { lookup: _ } => false + } + } + } + let removed = shell.Remove.RecursiveForce(path: root_path) + verdict && removed.success +} + +test fn a_tampered_typecheck_entry_refuses_and_is_not_a_miss_by_real_execution() -> Bool { + let root_path = fresh_root_path() + let root = WitnessScratchRoot { root: root_path } + let req = sample_req() + let payloads = sample_payloads(interface: "export fn types_surface\n") + let verdict = match opened_production_budgets(root: root) { + Absent => false + Present { value: store } => { + let first = typecheck_module_materialize(capability: store, req: req, payloads: payloads) + let good = Filesystem.Read(path: object_path(root_path: root_path, req: req)) + let corrupt = Filesystem.Write(path: object_path(root_path: root_path, req: req), content: replace(good.content, "types_surface", "TYPES_SURFACE")) + let after = typecheck_module_serve(capability: store, req: req) + match first { + TypecheckModuleHit { lookup: _ } => + good.success && corrupt.success + && match after { + TypecheckModuleRefused { lookup: l } => + store_lookup_is_miss(l: l) == false && store_lookup_refusal_tag(l: l) == "contract_wrong_content" + TypecheckModuleHit { lookup: _ } => false + TypecheckModuleMiss { lookup: _ } => false + } + TypecheckModuleMiss { lookup: _ } => false + TypecheckModuleRefused { lookup: _ } => false + } + } + } + let removed = shell.Remove.RecursiveForce(path: root_path) + verdict && removed.success +} + +test fn real_corpus_typecheck_entries_fit_the_measured_largest_entry_by_real_execution() -> Bool { + let types = Filesystem.Read(path: "dag/std/types.dag") + let provider = Filesystem.Read(path: "dag/std/materialization_provider.dag") + let req_types = typecheck_module_request(source: "dag/std/types.dag", import_interfaces: [], compiler: "seed-compiler") + let req_provider = typecheck_module_request(source: "dag/std/materialization_provider.dag", import_interfaces: ["std.types"], compiler: "seed-compiler") + let cap = measure_count(m: typecheck_module_measured_largest_entry()) + types.success && provider.success + && measure_count(m: typecheck_module_entry_bytes(req: req_types, payloads: sample_payloads(interface: types.content))) <= cap + && measure_count(m: typecheck_module_entry_bytes(req: req_provider, payloads: sample_payloads(interface: provider.content))) <= cap +} + +test fn the_durable_production_open_uses_the_granted_root_by_real_execution() -> Bool { + match open_durable_typecheck_store() { + LocalStoreOpened { capability: _, durability: _ } => true + LocalStoreUnavailable { root_path: p, cause: _ } => p == materialization_store_durable_root_path + } +} diff --git a/dag/test/claim/typecheck_module_store_witness_test.dag b/dag/test/claim/typecheck_module_store_witness_test.dag new file mode 100644 index 00000000000..0a5aed0230f --- /dev/null +++ b/dag/test/claim/typecheck_module_store_witness_test.dag @@ -0,0 +1,47 @@ +module test.claim.typecheck_module_store_witness + +import std.types { Bool } +import std.decl_ref { decl_ref } +import std.dissolution { dissolution_status, DissolutionFired, DissolutionPending, DissolutionUnbound } +import std.materialization_provider { request_key } +import gunbc.materialization_store_budgets { + materialization_store_durable_budgets_consumer_frontier, + typecheck_module_measured_largest_entry, + materialization_store_typed_module_budget_policy, +} +import gunbc.typed_module_cache_capacity { typed_module_cache_entries_ceiling } +import gunbc.typecheck_module_store { + typecheck_module_request, + typecheck_module_payloads, + typecheck_module_entry_bytes, +} +import std.measure { measure_count } + +test fn the_durable_budgets_consumer_frontier_is_bound_to_the_c2_open() -> Bool { + let target = decl_ref(module_path: "gunbc.typecheck_module_store", decl_name: "open_durable_typecheck_store") + match dissolution_status(condition: materialization_store_durable_budgets_consumer_frontier, present_decls: [target]) { + DissolutionFired => true + DissolutionPending => false + DissolutionUnbound => false + } +} + +test fn typecheck_module_request_key_is_derived_from_the_judgment_contract() -> Bool { + let a = typecheck_module_request(source: "mod-a", import_interfaces: ["iface-1"], compiler: "compiler-x") + let b = typecheck_module_request(source: "mod-a", import_interfaces: ["iface-1"], compiler: "compiler-x") + let changed = typecheck_module_request(source: "mod-a", import_interfaces: ["iface-2"], compiler: "compiler-x") + request_key(req: a) == request_key(req: b) + && (request_key(req: a) == request_key(req: changed)) == false +} + +test fn the_typed_module_family_budget_is_the_measured_entry_times_the_residency_ceiling() -> Bool { + measure_count(m: materialization_store_typed_module_budget_policy) + == measure_count(m: typecheck_module_measured_largest_entry()) * typed_module_cache_entries_ceiling +} + +test fn a_typecheck_entry_for_the_probe_interface_fits_the_measured_largest_entry() -> Bool { + let req = typecheck_module_request(source: "mod-a", import_interfaces: [], compiler: "compiler-x") + let payloads = typecheck_module_payloads(interface: "export fn f\n", diagnostics: "") + measure_count(m: typecheck_module_entry_bytes(req: req, payloads: payloads)) + <= measure_count(m: typecheck_module_measured_largest_entry()) +} diff --git a/src/v2/workflow/local_repo_wet_terminal.dag b/src/v2/workflow/local_repo_wet_terminal.dag index 4efd4b96d25..24c6b7c7f1e 100644 --- a/src/v2/workflow/local_repo_wet_terminal.dag +++ b/src/v2/workflow/local_repo_wet_terminal.dag @@ -2179,6 +2179,30 @@ fn local_repo_wet_schedule() -> List { function: "a_leftover_copy_that_cannot_be_removed_leaves_the_store_unwritable_by_real_execution", expectation: ExpectedToHold {} }, + WetScheduledClaim { + identity: WitnessIdentity { module_path: "test.claim.typecheck_module_store_wet_witness", function: "typecheck_module_miss_computes_and_warm_hits_by_real_execution" }, + entry: "dag/test/claim/typecheck_module_store_wet_witness_test.dag", + function: "typecheck_module_miss_computes_and_warm_hits_by_real_execution", + expectation: ExpectedToHold {} + }, + WetScheduledClaim { + identity: WitnessIdentity { module_path: "test.claim.typecheck_module_store_wet_witness", function: "a_tampered_typecheck_entry_refuses_and_is_not_a_miss_by_real_execution" }, + entry: "dag/test/claim/typecheck_module_store_wet_witness_test.dag", + function: "a_tampered_typecheck_entry_refuses_and_is_not_a_miss_by_real_execution", + expectation: ExpectedToHold {} + }, + WetScheduledClaim { + identity: WitnessIdentity { module_path: "test.claim.typecheck_module_store_wet_witness", function: "real_corpus_typecheck_entries_fit_the_measured_largest_entry_by_real_execution" }, + entry: "dag/test/claim/typecheck_module_store_wet_witness_test.dag", + function: "real_corpus_typecheck_entries_fit_the_measured_largest_entry_by_real_execution", + expectation: ExpectedToHold {} + }, + WetScheduledClaim { + identity: WitnessIdentity { module_path: "test.claim.typecheck_module_store_wet_witness", function: "the_durable_production_open_uses_the_granted_root_by_real_execution" }, + entry: "dag/test/claim/typecheck_module_store_wet_witness_test.dag", + function: "the_durable_production_open_uses_the_granted_root_by_real_execution", + expectation: ExpectedToHold {} + }, WetScheduledClaim { identity: WitnessIdentity { module_path: "test.claim.pre_os_capture_replay_witness", function: "w_early_exit_refuses_absence_on_real_bytes" }, entry: "dag/test/claim/machine_intake/pre_os_capture_replay_witness_test.dag", From 051b0236a7ac935fcd1df6b4e41463f5b91da84f Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Tue, 6 Oct 2026 16:00:40 +0000 Subject: [PATCH 02/24] Wire the seed typecheck path through the local store without retiring the frontier on a unused declaration. C2 now looks up and commits TypecheckModuleRequest from reconcile using content digests, fail-closed prepare/commit/integrity arms, and a family budget derived from the existing per-entry estimate rather than a synthetic interface literal. The durable-budgets frontier stays unbound until that executing consumer is the retirement trigger. Co-authored-by: Cursor --- dag/gunbc/materialization_store_budgets.dag | 67 ++--- dag/gunbc/typecheck_module_store.dag | 271 ++++++++++++++++-- ...ypecheck_module_store_wet_witness_test.dag | 105 ++++--- .../typecheck_module_store_witness_test.dag | 63 +++- src/v1/stage0/src/cli_run.rs | 32 ++- src/v1/stage0/src/cli_run/entry_resolve.rs | 49 +++- .../src/cli_run/typecheck_store_session.rs | 236 +++++++++++++++ src/v2/workflow/local_repo_wet_terminal.dag | 10 +- 8 files changed, 701 insertions(+), 132 deletions(-) create mode 100644 src/v1/stage0/src/cli_run/typecheck_store_session.rs diff --git a/dag/gunbc/materialization_store_budgets.dag b/dag/gunbc/materialization_store_budgets.dag index 8aa41aa77aa..437b192c271 100644 --- a/dag/gunbc/materialization_store_budgets.dag +++ b/dag/gunbc/materialization_store_budgets.dag @@ -1,29 +1,19 @@ module gunbc.materialization_store_budgets import std.types { List } -import std.dissolution { DissolutionCondition, bound_dissolution } -import std.decl_ref { decl_ref } +import std.dissolution { DissolutionCondition, unbound_dissolution } import std.measure { ByteSize, byte_size, measure_count } import std.cache_identity { typed_module_artifact_kind } -import std.content_hash { content_hash_atom } -import std.materialization_provider { - ArtifactRequest, - ArtifactPayload, - TypecheckModuleRequest, - compiler_identity_input, - output_typed_module_interface, - output_typecheck_diagnostics, -} -import std.materialization_object { store_commit_prepare, StoreCommitPrepared, StoreCommitNotPrepared } import extdeps.realization.materialization_store_local { LocalStoreFamilyBudget, local_store_budget_sum, materialization_store_local_facts_at, - local_store_object_bytes, } import extdeps.cache.types { CacheInterfaceCatalogFacts } -import gunbc.typed_module_cache_capacity { typed_module_cache_entries_ceiling } -import std.render_repeat_string_bootstrap { repeat_string } +import gunbc.typed_module_cache_capacity { + typed_module_bytes_per_entry_estimate, + typed_module_cache_entries_ceiling, +} // THE DURABLE STORE'S BUDGETS ARE POLICY, AND POLICY IS THIS LAYER'S FACT (DESIGN section 3). The // transport (extdeps.realization.materialization_store_local) owns the budget's shape and its @@ -32,37 +22,21 @@ import std.render_repeat_string_bootstrap { repeat_string } // is structural (std.materialization_store_grant DurableHostVolumeRoot names one path), so the host // ceiling is the declared sum of these rows and nothing else. -// POLICY DERIVED FROM THE PER-MODULE GRAIN C2 HOLDS. The probe object is a TypecheckModuleRequest -// persisted at interface+diagnostics grain (not a TypeEnv snapshot). The family budget is that -// measured entry size times the in-process typed-module residency ceiling, so the durable family -// cannot outgrow the working set the seed already admits. The wet consumer re-derives the same -// entry size over real corpus files and refuses if any exceeds this probe. - -fn typecheck_budget_probe_request() -> ArtifactRequest { - TypecheckModuleRequest { - source_digest: content_hash_atom(value: "budget-probe-source"), - direct_import_interfaces: [], - compiler: compiler_identity_input(digest: content_hash_atom(value: "budget-probe-compiler")) - } -} - -data typecheck_module_measured_interface_probe: String = repeat_string(s: "export fn measured_entry\n", n: 6144) +// POLICY DERIVED FROM AN EXISTING PER-ENTRY AUTHORITY, NOT A SYNTHETIC INTERFACE LITERAL. The +// in-process typed-module cap already names a per-entry byte estimate +// (gunbc.typed_module_cache_capacity typed_module_bytes_per_entry_estimate) and a residency +// ceiling. The durable family cannot outgrow that working set. C2's instrument (the seed +// typecheck path committing real interface+diagnostics objects) must keep every served object +// at or under that per-entry estimate; a larger real object is a revision of the estimate, never +// a second authored size in this file. fn typecheck_module_measured_largest_entry() -> ByteSize { - local_store_object_bytes(object: match store_commit_prepare( - req: typecheck_budget_probe_request(), - payloads: [ - ArtifactPayload { id: output_typed_module_interface, content: typecheck_module_measured_interface_probe }, - ArtifactPayload { id: output_typecheck_diagnostics, content: "" }, - ], - blobs: [] - ) { - StoreCommitPrepared { key: _, object_name: _, object: o, offered_digest: _, offered_result: _ } => o - StoreCommitNotPrepared { commit: _ } => "" - }) + typed_module_bytes_per_entry_estimate } -data materialization_store_typed_module_budget_policy: ByteSize = byte_size(count: measure_count(m: typecheck_module_measured_largest_entry()) * typed_module_cache_entries_ceiling) +data materialization_store_typed_module_budget_policy: ByteSize = byte_size( + count: measure_count(m: typecheck_module_measured_largest_entry()) * typed_module_cache_entries_ceiling +) data materialization_store_durable_family_budgets: List = [ LocalStoreFamilyBudget { family: typed_module_artifact_kind, budget: materialization_store_typed_module_budget_policy }, @@ -72,8 +46,7 @@ data materialization_store_host_ceiling_bytes: ByteSize = local_store_budget_sum data materialization_store_local_facts: CacheInterfaceCatalogFacts = materialization_store_local_facts_at(ceiling: materialization_store_host_ceiling_bytes) -// C2 discharged this frontier: gunbc.typecheck_module_store open_durable_typecheck_store opens -// DurableHostVolumeRoot through the local store with materialization_store_durable_family_budgets. -data materialization_store_durable_budgets_consumer_frontier: DissolutionCondition = bound_dissolution( - ref: decl_ref(module_path: "gunbc.typecheck_module_store", decl_name: "open_durable_typecheck_store") -) +// C2's production consumer is the seed typecheck path (gunbc.typecheck_module_store +// typecheck_materialization_seed_path_note). Binding this row to a declaration that merely +// exists would fire before any run opens the durable root. +data materialization_store_durable_budgets_consumer_frontier: DissolutionCondition = unbound_dissolution(description: "TRIGGER: the TypecheckModuleRequest consumer (PR C2) opens DurableHostVolumeRoot through extdeps.realization.materialization_store_local passing materialization_store_durable_family_budgets from the seed typecheck path (ensure_durable_typecheck_store, lookup_typecheck_module, commit_typecheck_module). SUFFICIENT FOR: these rows bound a production store on every run that consumes it; until then they are read only by the host-ceiling control, and this row is retired by that landing and nothing else." as NonEmptyStr) diff --git a/dag/gunbc/typecheck_module_store.dag b/dag/gunbc/typecheck_module_store.dag index ad2996885d4..6da53576a47 100644 --- a/dag/gunbc/typecheck_module_store.dag +++ b/dag/gunbc/typecheck_module_store.dag @@ -1,7 +1,7 @@ module gunbc.typecheck_module_store -import std.types { List, String, NonEmptyStr } -import std.content_hash { content_hash_atom } +import std.types { List, String, Bool, NonEmptyStr } +import std.content_hash { Fnv1a64Structural, fnv1a64_structural_hex_digest } import std.interface_summary { InterfaceHash } import std.measure { ByteSize } import std.materialization_provider { @@ -21,33 +21,88 @@ import std.materialization_object { StoreLookup, store_lookup_is_hit, store_lookup_is_miss, + store_lookup_payload, + store_lookup_refusal_tag, } -import std.materialization_store_grant { DurableHostVolumeRoot } +import std.materialization_store_grant { DurableHostVolumeRoot, WitnessScratchRoot } +import std.decl_ref { decl_ref, DeclarationRef } +import std.dissolution { DissolutionCondition, unbound_dissolution } import extdeps.realization.materialization_store_local { LocalStoreCapability, LocalStoreOpening, + LocalStoreOpened, + LocalStoreUnavailable, + LocalStoreInitialization, + LocalStoreInitialized, + LocalStoreInitializationRefused, LocalStoreCommit, local_store_open, + local_store_initialize, local_store_lookup, local_store_commit, local_store_object_bytes, } import gunbc.materialization_store_budgets { materialization_store_durable_family_budgets } +import v2.std.optional { Present, Absent } // THE TYPECHECK-MODULE CONSUMER OF THE LOCAL STORE (C2). TypecheckModuleRequest keys are DERIVED -// (std.materialization_provider request_key); this module never takes a key, digest, or roster as a -// caller-chosen argument. A HIT is the store's verified read (local_store_lookup already runs -// provider_serve_persisted); a miss computes by committing the contract payloads (typed module -// interface + typecheck diagnostics) and reading back. A stale or tampered object REFUSES, and that +// (std.materialization_provider request_key / std.interface_summary typed_module_key). Callers +// supply already-derived Fnv1a64Structural digests — source CONTENT, direct-import interface +// hashes, compiler identity — never a path or a module name. A HIT is the store's verified read; +// a miss computes elsewhere and commits here; a stale or tampered object REFUSES, and that // refusal is not a miss. The durable open takes gunbc.materialization_store_budgets // materialization_store_durable_family_budgets and DurableHostVolumeRoot; it does not initialize a -// missing volume and does not fall through to scratch. +// missing volume by falling through to scratch. -fn typecheck_module_request(source: NonEmptyStr, import_interfaces: List, compiler: NonEmptyStr) -> ArtifactRequest { +data typecheck_store_instance_label: NonEmptyStr = "gunbc-typecheck-host" + +fn typecheck_module_request( + source_digest: Fnv1a64Structural, + direct_import_interfaces: List, + compiler_digest: Fnv1a64Structural +) -> ArtifactRequest { TypecheckModuleRequest { - source_digest: content_hash_atom(value: source), - direct_import_interfaces: import_interfaces |> map(s => content_hash_atom(value: s as NonEmptyStr) as InterfaceHash), - compiler: compiler_identity_input(digest: content_hash_atom(value: compiler)) + source_digest: source_digest, + direct_import_interfaces: direct_import_interfaces, + compiler: compiler_identity_input(digest: compiler_digest) + } +} + +type TypecheckModuleRequestBuild + = TypecheckModuleRequestBuilt { req: ArtifactRequest } + | TypecheckModuleRequestDigestRefused { which: String } + +fn hex_digest(hex: String) -> Fnv1a64Structural? { + fnv1a64_structural_hex_digest(hex: hex) +} + +fn typecheck_module_request_from_hex( + source_digest_hex: String, + import_interface_hexes: List, + compiler_digest_hex: String +) -> TypecheckModuleRequestBuild { + match hex_digest(hex: source_digest_hex) { + Absent => TypecheckModuleRequestDigestRefused { which: "source_digest" } + Present { value: src } => match hex_digest(hex: compiler_digest_hex) { + Absent => TypecheckModuleRequestDigestRefused { which: "compiler_digest" } + Present { value: compiler } => { + let imports = import_interface_hexes |> map(h => hex_digest(hex: h)) + if imports |> any(d => match d { Absent => true Present { value: _ } => false }) { + TypecheckModuleRequestDigestRefused { which: "direct_import_interfaces" } + } else { + TypecheckModuleRequestBuilt { + req: typecheck_module_request( + source_digest: src, + direct_import_interfaces: imports |> flat_map(d => match d { + Present { value: v } => [v as InterfaceHash] + Absent => [] + }), + compiler_digest: compiler + ) + } + } + } + } } } @@ -58,28 +113,87 @@ fn typecheck_module_payloads(interface: String, diagnostics: String) -> List) -> String { +type TypecheckModulePrepared + = TypecheckModuleObjectPrepared { object: String } + | TypecheckModuleObjectNotPrepared { commit: std.materialization_object.StoreCommit } + +fn typecheck_module_prepare(req: ArtifactRequest, payloads: List) -> TypecheckModulePrepared { match store_commit_prepare(req: req, payloads: payloads, blobs: []) { - StoreCommitPrepared { key: _, object_name: _, object: o, offered_digest: _, offered_result: _ } => o - StoreCommitNotPrepared { commit: _ } => "" + StoreCommitPrepared { key: _, object_name: _, object: o, offered_digest: _, offered_result: _ } => + TypecheckModuleObjectPrepared { object: o } + StoreCommitNotPrepared { commit: c } => TypecheckModuleObjectNotPrepared { commit: c } } } -fn typecheck_module_entry_bytes(req: ArtifactRequest, payloads: List) -> ByteSize { - local_store_object_bytes(object: typecheck_module_prepared_object(req: req, payloads: payloads)) +fn typecheck_module_entry_bytes(req: ArtifactRequest, payloads: List) -> ByteSize? { + match typecheck_module_prepare(req: req, payloads: payloads) { + TypecheckModuleObjectPrepared { object: o } => Present { value: local_store_object_bytes(object: o) } + TypecheckModuleObjectNotPrepared { commit: _ } => none + } } fn open_durable_typecheck_store() -> LocalStoreOpening { local_store_open(root: DurableHostVolumeRoot, budgets: materialization_store_durable_family_budgets) } +fn initialize_durable_typecheck_store() -> LocalStoreInitialization { + local_store_initialize( + root: DurableHostVolumeRoot, + instance_label: typecheck_store_instance_label, + budgets: materialization_store_durable_family_budgets + ) +} + +// First production open: initialize (create-new marker, or converge on an existing one) then return +// that opening. A grant or catalog refusal stays a refusal; nothing opens WitnessScratchRoot here. +fn ensure_durable_typecheck_store() -> LocalStoreOpening { + match initialize_durable_typecheck_store() { + LocalStoreInitialized { opening: o } => o + LocalStoreInitializationRefused { root_path: p, cause: c } => + LocalStoreUnavailable { root_path: p, cause: c } + } +} + +fn lookup_typecheck_module(capability: LocalStoreCapability, req: ArtifactRequest) -> StoreLookup { + local_store_lookup(capability: capability, req: req) +} + +fn commit_typecheck_module( + capability: LocalStoreCapability, + req: ArtifactRequest, + interface_text: String, + diagnostics_text: String +) -> LocalStoreCommit { + local_store_commit( + capability: capability, + req: req, + payloads: typecheck_module_payloads(interface: interface_text, diagnostics: diagnostics_text), + parts: [] + ) +} + +// Distinct from the durable door: witnesses exercise the same family budgets on an ephemeral +// scratch root. WitnessScratchRoot is the only admitted argument; there is no DurableHostVolumeRoot +// arm and no required argument on ensure_durable_typecheck_store. +fn initialize_witness_typecheck_store(root: WitnessScratchRoot) -> LocalStoreInitialization { + local_store_initialize( + root: root, + instance_label: "gunbc-typecheck-witness" as NonEmptyStr, + budgets: materialization_store_durable_family_budgets + ) +} + +fn open_witness_typecheck_store(root: WitnessScratchRoot) -> LocalStoreOpening { + local_store_open(root: root, budgets: materialization_store_durable_family_budgets) +} + type TypecheckModuleServe = TypecheckModuleHit { lookup: StoreLookup } | TypecheckModuleMiss { lookup: StoreLookup } | TypecheckModuleRefused { lookup: StoreLookup } fn typecheck_module_serve(capability: LocalStoreCapability, req: ArtifactRequest) -> TypecheckModuleServe { - let lookup = local_store_lookup(capability: capability, req: req) + let lookup = lookup_typecheck_module(capability: capability, req: req) if store_lookup_is_hit(l: lookup) { TypecheckModuleHit { lookup: lookup } } else { @@ -95,14 +209,121 @@ fn typecheck_module_commit_computed(capability: LocalStoreCapability, req: Artif local_store_commit(capability: capability, req: req, payloads: payloads, parts: []) } -fn typecheck_module_materialize(capability: LocalStoreCapability, req: ArtifactRequest, payloads: List) -> TypecheckModuleServe { +type TypecheckModuleMaterialize + = TypecheckModuleMaterialized { serve: TypecheckModuleServe } + | TypecheckModuleCommitRefused { commit: LocalStoreCommit } + +// Distinct from typecheck_module (the seed infer operation): lookup, then on a miss commit the +// already-computed interface+diagnostics payloads. A refused commit stays a refused commit; it is +// never re-served as a miss. +fn typecheck_module_materialize(capability: LocalStoreCapability, req: ArtifactRequest, payloads: List) -> TypecheckModuleMaterialize { match typecheck_module_serve(capability: capability, req: req) { - TypecheckModuleHit { lookup: h } => TypecheckModuleHit { lookup: h } - TypecheckModuleRefused { lookup: r } => TypecheckModuleRefused { lookup: r } - TypecheckModuleMiss { lookup: _ } => - match typecheck_module_commit_computed(capability: capability, req: req, payloads: payloads).commit { - StoreCommitSettled { receipt: _ } => typecheck_module_serve(capability: capability, req: req) - StoreCommitRefused { key: _, refusal: _ } => typecheck_module_serve(capability: capability, req: req) + TypecheckModuleHit { lookup: h } => TypecheckModuleMaterialized { serve: TypecheckModuleHit { lookup: h } } + TypecheckModuleRefused { lookup: r } => TypecheckModuleMaterialized { serve: TypecheckModuleRefused { lookup: r } } + TypecheckModuleMiss { lookup: _ } => { + let written = typecheck_module_commit_computed(capability: capability, req: req, payloads: payloads) + match written.commit { + StoreCommitSettled { receipt: _ } => TypecheckModuleMaterialized { serve: typecheck_module_serve(capability: capability, req: req) } + StoreCommitRefused { key: _, refusal: _ } => TypecheckModuleCommitRefused { commit: written } + } + } + } +} + +fn typecheck_lookup_refused_integrity(l: StoreLookup) -> Bool { + let tag = store_lookup_refusal_tag(l: l) + tag != "hit" && tag != "miss" +} + +data ensure_durable_typecheck_store_ref: DeclarationRef = decl_ref( + module_path: "gunbc.typecheck_module_store", + decl_name: "ensure_durable_typecheck_store" +) + +data lookup_typecheck_module_ref: DeclarationRef = decl_ref( + module_path: "gunbc.typecheck_module_store", + decl_name: "lookup_typecheck_module" +) + +data commit_typecheck_module_ref: DeclarationRef = decl_ref( + module_path: "gunbc.typecheck_module_store", + decl_name: "commit_typecheck_module" +) + +// Production inhabitance is the seed typecheck path calling ensure/lookup/commit. Bound dissolution +// on the declaration existing would fire before that path runs (DESIGN 3c / 4b(1)). +data typecheck_materialization_seed_path_note: DissolutionCondition = unbound_dissolution(description: "TRIGGER: v1_compiler.cli_run reconcile_with_typed_cache looks up through lookup_typecheck_module and commits through commit_typecheck_module after constructing TypecheckModuleRequest from source-content, import-interface and compiler digests (typecheck_module_request_from_hex). SUFFICIENT FOR: a verified store hit skips typecheck_module compute; a stale or tampered object refuses rather than serving; the durable open is ensure_durable_typecheck_store. This row is retired by that executing consumer and nothing else." as NonEmptyStr) + +// Seed-facing operations (distinct from typecheck_module): hex digests in, typed store outcome +// out. ensure_durable_typecheck_store is inside these folds so reconcile does not take a store +// argument. + +type SeedTypecheckLookup + = SeedTypecheckHit { interface_text: String, diagnostics_text: String } + | SeedTypecheckMiss + | SeedTypecheckUnavailable { root_path: String } + | SeedTypecheckIntegrityRefused { tag: String } + | SeedTypecheckDigestRefused { which: String } + +type SeedTypecheckCommit + = SeedTypecheckCommitted + | SeedTypecheckCommitMissedOpen { root_path: String } + | SeedTypecheckCommitRefused + | SeedTypecheckCommitDigestRefused { which: String } + +fn seed_lookup_typecheck_hex( + source_digest_hex: String, + import_interface_hexes: List, + compiler_digest_hex: String +) -> SeedTypecheckLookup { + match typecheck_module_request_from_hex( + source_digest_hex: source_digest_hex, + import_interface_hexes: import_interface_hexes, + compiler_digest_hex: compiler_digest_hex + ) { + TypecheckModuleRequestDigestRefused { which: w } => SeedTypecheckDigestRefused { which: w } + TypecheckModuleRequestBuilt { req: req } => match ensure_durable_typecheck_store() { + LocalStoreUnavailable { root_path: p, cause: _ } => SeedTypecheckUnavailable { root_path: p } + LocalStoreOpened { capability: c, durability: _ } => match typecheck_module_serve(capability: c, req: req) { + TypecheckModuleMiss { lookup: _ } => SeedTypecheckMiss + TypecheckModuleRefused { lookup: l } => SeedTypecheckIntegrityRefused { tag: store_lookup_refusal_tag(l: l) } + TypecheckModuleHit { lookup: h } => match store_lookup_payload(l: h, id: output_typed_module_interface) { + Absent => SeedTypecheckIntegrityRefused { tag: "missing_typed_module_interface" } + Present { value: iface } => match store_lookup_payload(l: h, id: output_typecheck_diagnostics) { + Absent => SeedTypecheckIntegrityRefused { tag: "missing_typecheck_diagnostics" } + Present { value: diags } => SeedTypecheckHit { interface_text: iface, diagnostics_text: diags } + } + } } + } + } +} + +fn seed_commit_typecheck_hex( + source_digest_hex: String, + import_interface_hexes: List, + compiler_digest_hex: String, + interface_text: String, + diagnostics_text: String +) -> SeedTypecheckCommit { + match typecheck_module_request_from_hex( + source_digest_hex: source_digest_hex, + import_interface_hexes: import_interface_hexes, + compiler_digest_hex: compiler_digest_hex + ) { + TypecheckModuleRequestDigestRefused { which: w } => SeedTypecheckCommitDigestRefused { which: w } + TypecheckModuleRequestBuilt { req: req } => match ensure_durable_typecheck_store() { + LocalStoreUnavailable { root_path: p, cause: _ } => SeedTypecheckCommitMissedOpen { root_path: p } + LocalStoreOpened { capability: c, durability: _ } => + match commit_typecheck_module( + capability: c, + req: req, + interface_text: interface_text, + diagnostics_text: diagnostics_text + ).commit { + StoreCommitSettled { receipt: _ } => SeedTypecheckCommitted + StoreCommitRefused { key: _, refusal: _ } => SeedTypecheckCommitRefused + } + } } } diff --git a/dag/test/claim/typecheck_module_store_wet_witness_test.dag b/dag/test/claim/typecheck_module_store_wet_witness_test.dag index c1344d10b62..ed98ab45b7b 100644 --- a/dag/test/claim/typecheck_module_store_wet_witness_test.dag +++ b/dag/test/claim/typecheck_module_store_wet_witness_test.dag @@ -4,7 +4,9 @@ import extdeps.shell import extdeps.filesystem.filesystem_io { Filesystem } import std.types { Bool, List, String, NonEmptyStr, EpochMs } import std.measure { measure_count } -import std.materialization_provider { output_typed_module_interface } +import std.content_hash { content_hash_atom } +import std.interface_summary { InterfaceHash } +import std.materialization_provider { output_typed_module_interface, ArtifactRequest, ArtifactPayload } import std.materialization_object { store_lookup_is_hit, store_lookup_is_miss, @@ -13,7 +15,6 @@ import std.materialization_object { store_object_name, } import std.materialization_store_grant { - MaterializationStoreRoot, WitnessScratchRoot, materialization_store_durable_root_path, } @@ -26,12 +27,9 @@ import extdeps.realization.materialization_store_local { LocalStoreInitialization, LocalStoreInitialized, LocalStoreInitializationRefused, - local_store_initialize, - local_store_object_bytes, store_path, } import gunbc.materialization_store_budgets { - materialization_store_durable_family_budgets, typecheck_module_measured_largest_entry, } import gunbc.typecheck_module_store { @@ -44,6 +42,9 @@ import gunbc.typecheck_module_store { TypecheckModuleHit, TypecheckModuleMiss, TypecheckModuleRefused, + TypecheckModuleMaterialized, + TypecheckModuleCommitRefused, + initialize_witness_typecheck_store, open_durable_typecheck_store, } import gunbc.clock_read { @@ -57,8 +58,8 @@ fn fresh_root_path() -> String { shell.Mktemp.DirWithTemplate(template: "/tmp/gunbc_matstore.XXXXXX").path } -fn opened_production_budgets(root: MaterializationStoreRoot) -> LocalStoreCapability? { - match local_store_initialize(root: root, instance_label: "typecheck-c2", budgets: materialization_store_durable_family_budgets) { +fn opened_production_budgets(root: WitnessScratchRoot) -> LocalStoreCapability? { + match initialize_witness_typecheck_store(root: root) { LocalStoreInitialized { opening: o } => match o { LocalStoreOpened { capability: c, durability: _ } => Present { value: c } LocalStoreUnavailable { root_path: _, cause: _ } => none @@ -67,15 +68,19 @@ fn opened_production_budgets(root: MaterializationStoreRoot) -> LocalStoreCapabi } } -fn sample_req() -> std.materialization_provider.ArtifactRequest { - typecheck_module_request(source: "dag/std/types.dag", import_interfaces: ["std.logic"], compiler: "seed-compiler") +fn sample_req(source_bytes: String, import_bytes: List) -> ArtifactRequest { + typecheck_module_request( + source_digest: content_hash_atom(value: source_bytes), + direct_import_interfaces: import_bytes |> map(s => content_hash_atom(value: s) as InterfaceHash), + compiler_digest: content_hash_atom(value: "seed-compiler") + ) } -fn sample_payloads(interface: String) -> List { +fn sample_payloads(interface: String) -> List { typecheck_module_payloads(interface: interface, diagnostics: "") } -fn object_path(root_path: String, req: std.materialization_provider.ArtifactRequest) -> String { +fn object_path(root_path: String, req: ArtifactRequest) -> String { match evaluation_store_admitted_address(admission: evaluation_store_address(req: req)) { Absent => "" Present { value: addr } => store_path(root_path: root_path, name: store_object_name(address: addr)) @@ -95,7 +100,7 @@ type TypecheckStoreColdWarmReceipt { saved_millis: EpochMs } -fn typecheck_module_store_cold_warm_receipt(capability: LocalStoreCapability, req: std.materialization_provider.ArtifactRequest, payloads: List) -> TypecheckStoreColdWarmReceipt { +fn typecheck_module_store_cold_warm_receipt(capability: LocalStoreCapability, req: ArtifactRequest, payloads: List) -> TypecheckStoreColdWarmReceipt { let t0 = observer_clock_read() let cold = typecheck_module_serve(capability: capability, req: req) match cold { @@ -122,24 +127,28 @@ fn typecheck_module_store_cold_warm_receipt(capability: LocalStoreCapability, re } test fn typecheck_module_miss_computes_and_warm_hits_by_real_execution() -> Bool { + let types = Filesystem.Read(path: "dag/std/types.dag") let root_path = fresh_root_path() let root = WitnessScratchRoot { root: root_path } let verdict = match opened_production_budgets(root: root) { Absent => false Present { value: store } => { - let req = sample_req() + let req = sample_req(source_bytes: types.content, import_bytes: []) let payloads = sample_payloads(interface: "export fn types_surface\n") let receipt = typecheck_module_store_cold_warm_receipt(capability: store, req: req, payloads: payloads) let served = typecheck_module_materialize(capability: store, req: req, payloads: payloads) - receipt.cold_was_miss && receipt.warm_was_hit + types.success && receipt.cold_was_miss && receipt.warm_was_hit && match served { - TypecheckModuleHit { lookup: h } => - match store_lookup_payload(l: h, id: output_typed_module_interface) { - Present { value: v } => v == "export fn types_surface\n" - Absent => false - } - TypecheckModuleMiss { lookup: _ } => false - TypecheckModuleRefused { lookup: _ } => false + TypecheckModuleMaterialized { serve: s } => match s { + TypecheckModuleHit { lookup: h } => + match store_lookup_payload(l: h, id: output_typed_module_interface) { + Present { value: v } => v == "export fn types_surface\n" + Absent => false + } + TypecheckModuleMiss { lookup: _ } => false + TypecheckModuleRefused { lookup: _ } => false + } + TypecheckModuleCommitRefused { commit: _ } => false } } } @@ -148,9 +157,10 @@ test fn typecheck_module_miss_computes_and_warm_hits_by_real_execution() -> Bool } test fn a_tampered_typecheck_entry_refuses_and_is_not_a_miss_by_real_execution() -> Bool { + let types = Filesystem.Read(path: "dag/std/types.dag") let root_path = fresh_root_path() let root = WitnessScratchRoot { root: root_path } - let req = sample_req() + let req = sample_req(source_bytes: types.content, import_bytes: []) let payloads = sample_payloads(interface: "export fn types_surface\n") let verdict = match opened_production_budgets(root: root) { Absent => false @@ -160,16 +170,19 @@ test fn a_tampered_typecheck_entry_refuses_and_is_not_a_miss_by_real_execution() let corrupt = Filesystem.Write(path: object_path(root_path: root_path, req: req), content: replace(good.content, "types_surface", "TYPES_SURFACE")) let after = typecheck_module_serve(capability: store, req: req) match first { - TypecheckModuleHit { lookup: _ } => - good.success && corrupt.success - && match after { - TypecheckModuleRefused { lookup: l } => - store_lookup_is_miss(l: l) == false && store_lookup_refusal_tag(l: l) == "contract_wrong_content" - TypecheckModuleHit { lookup: _ } => false - TypecheckModuleMiss { lookup: _ } => false - } - TypecheckModuleMiss { lookup: _ } => false - TypecheckModuleRefused { lookup: _ } => false + TypecheckModuleMaterialized { serve: s } => match s { + TypecheckModuleHit { lookup: _ } => + types.success && good.success && corrupt.success + && match after { + TypecheckModuleRefused { lookup: l } => + store_lookup_is_miss(l: l) == false && store_lookup_refusal_tag(l: l) == "contract_wrong_content" + TypecheckModuleHit { lookup: _ } => false + TypecheckModuleMiss { lookup: _ } => false + } + TypecheckModuleMiss { lookup: _ } => false + TypecheckModuleRefused { lookup: _ } => false + } + TypecheckModuleCommitRefused { commit: _ } => false } } } @@ -177,15 +190,33 @@ test fn a_tampered_typecheck_entry_refuses_and_is_not_a_miss_by_real_execution() verdict && removed.success } -test fn real_corpus_typecheck_entries_fit_the_measured_largest_entry_by_real_execution() -> Bool { +test fn a_failed_prepare_is_absent_bytes_not_zero() -> Bool { + match typecheck_module_entry_bytes( + req: sample_req(source_bytes: "ok", import_bytes: []), + payloads: [] + ) { + Absent => true + Present { value: _ } => false + } +} + +test fn real_content_keyed_entries_fit_the_measured_largest_entry_by_real_execution() -> Bool { let types = Filesystem.Read(path: "dag/std/types.dag") let provider = Filesystem.Read(path: "dag/std/materialization_provider.dag") - let req_types = typecheck_module_request(source: "dag/std/types.dag", import_interfaces: [], compiler: "seed-compiler") - let req_provider = typecheck_module_request(source: "dag/std/materialization_provider.dag", import_interfaces: ["std.types"], compiler: "seed-compiler") + let req_types = sample_req(source_bytes: types.content, import_bytes: []) + let req_provider = sample_req(source_bytes: provider.content, import_bytes: [types.content]) let cap = measure_count(m: typecheck_module_measured_largest_entry()) + let iface_types = "interface std.types\n" + let iface_provider = "interface std.materialization_provider\n" types.success && provider.success - && measure_count(m: typecheck_module_entry_bytes(req: req_types, payloads: sample_payloads(interface: types.content))) <= cap - && measure_count(m: typecheck_module_entry_bytes(req: req_provider, payloads: sample_payloads(interface: provider.content))) <= cap + && match typecheck_module_entry_bytes(req: req_types, payloads: sample_payloads(interface: iface_types)) { + Absent => false + Present { value: n } => measure_count(m: n) <= cap + } + && match typecheck_module_entry_bytes(req: req_provider, payloads: sample_payloads(interface: iface_provider)) { + Absent => false + Present { value: n } => measure_count(m: n) <= cap + } } test fn the_durable_production_open_uses_the_granted_root_by_real_execution() -> Bool { diff --git a/dag/test/claim/typecheck_module_store_witness_test.dag b/dag/test/claim/typecheck_module_store_witness_test.dag index 0a5aed0230f..3a2eda9e2d8 100644 --- a/dag/test/claim/typecheck_module_store_witness_test.dag +++ b/dag/test/claim/typecheck_module_store_witness_test.dag @@ -1,6 +1,8 @@ module test.claim.typecheck_module_store_witness -import std.types { Bool } +import std.types { Bool, List, String } +import std.content_hash { content_hash_atom, as_content_hash_structural, Fnv1a64Structural } +import std.interface_summary { InterfaceHash, module_key, typed_module_key } import std.decl_ref { decl_ref } import std.dissolution { dissolution_status, DissolutionFired, DissolutionPending, DissolutionUnbound } import std.materialization_provider { request_key } @@ -12,26 +14,61 @@ import gunbc.materialization_store_budgets { import gunbc.typed_module_cache_capacity { typed_module_cache_entries_ceiling } import gunbc.typecheck_module_store { typecheck_module_request, + typecheck_module_request_from_hex, + TypecheckModuleRequestBuilt, + TypecheckModuleRequestDigestRefused, typecheck_module_payloads, typecheck_module_entry_bytes, + typecheck_materialization_seed_path_note, } import std.measure { measure_count } +import v2.std.optional { Present } -test fn the_durable_budgets_consumer_frontier_is_bound_to_the_c2_open() -> Bool { - let target = decl_ref(module_path: "gunbc.typecheck_module_store", decl_name: "open_durable_typecheck_store") - match dissolution_status(condition: materialization_store_durable_budgets_consumer_frontier, present_decls: [target]) { - DissolutionFired => true +fn h(v: String) -> Fnv1a64Structural { + content_hash_atom(value: v) +} + +test fn the_durable_budgets_consumer_frontier_stays_unbound_until_the_seed_path_runs() -> Bool { + match dissolution_status(condition: materialization_store_durable_budgets_consumer_frontier, present_decls: [ + decl_ref(module_path: "gunbc.typecheck_module_store", decl_name: "ensure_durable_typecheck_store") + ]) { + DissolutionUnbound => true + DissolutionFired => false + DissolutionPending => false + } +} + +test fn the_seed_path_note_stays_unbound_until_reconcile_calls_lookup_and_commit() -> Bool { + match dissolution_status(condition: typecheck_materialization_seed_path_note, present_decls: [ + decl_ref(module_path: "gunbc.typecheck_module_store", decl_name: "lookup_typecheck_module") + ]) { + DissolutionUnbound => true + DissolutionFired => false DissolutionPending => false - DissolutionUnbound => false } } test fn typecheck_module_request_key_is_derived_from_the_judgment_contract() -> Bool { - let a = typecheck_module_request(source: "mod-a", import_interfaces: ["iface-1"], compiler: "compiler-x") - let b = typecheck_module_request(source: "mod-a", import_interfaces: ["iface-1"], compiler: "compiler-x") - let changed = typecheck_module_request(source: "mod-a", import_interfaces: ["iface-2"], compiler: "compiler-x") + let src = h(v: "source-bytes-not-a-path") + let imp = h(v: "iface-1") as InterfaceHash + let compiler = h(v: "compiler-x") + let a = typecheck_module_request(source_digest: src, direct_import_interfaces: [imp], compiler_digest: compiler) + let b = typecheck_module_request(source_digest: src, direct_import_interfaces: [imp], compiler_digest: compiler) + let changed = typecheck_module_request(source_digest: src, direct_import_interfaces: [h(v: "iface-2") as InterfaceHash], compiler_digest: compiler) + let expected = typed_module_key( + interface_key: module_key(source_hash: src, direct_import_interface_hashes: [imp]), + compiler_identity: compiler + ) request_key(req: a) == request_key(req: b) && (request_key(req: a) == request_key(req: changed)) == false + && request_key(req: a) == as_content_hash_structural(structural: expected) +} + +test fn typecheck_module_request_from_hex_refuses_a_non_digest() -> Bool { + match typecheck_module_request_from_hex(source_digest_hex: "not-a-digest", import_interface_hexes: [], compiler_digest_hex: "abcd") { + TypecheckModuleRequestDigestRefused { which: w } => w == "source_digest" + TypecheckModuleRequestBuilt { req: _ } => false + } } test fn the_typed_module_family_budget_is_the_measured_entry_times_the_residency_ceiling() -> Bool { @@ -40,8 +77,10 @@ test fn the_typed_module_family_budget_is_the_measured_entry_times_the_residency } test fn a_typecheck_entry_for_the_probe_interface_fits_the_measured_largest_entry() -> Bool { - let req = typecheck_module_request(source: "mod-a", import_interfaces: [], compiler: "compiler-x") + let req = typecheck_module_request(source_digest: h(v: "mod-a"), direct_import_interfaces: [], compiler_digest: h(v: "compiler-x")) let payloads = typecheck_module_payloads(interface: "export fn f\n", diagnostics: "") - measure_count(m: typecheck_module_entry_bytes(req: req, payloads: payloads)) - <= measure_count(m: typecheck_module_measured_largest_entry()) + match typecheck_module_entry_bytes(req: req, payloads: payloads) { + Absent => false + Present { value: n } => measure_count(m: n) <= measure_count(m: typecheck_module_measured_largest_entry()) + } } diff --git a/src/v1/stage0/src/cli_run.rs b/src/v1/stage0/src/cli_run.rs index 836799b9be4..b58c3ea960a 100644 --- a/src/v1/stage0/src/cli_run.rs +++ b/src/v1/stage0/src/cli_run.rs @@ -117,6 +117,7 @@ pub use required_lane_roster::{authority_lane_phase_rows, LanePhaseRow}; mod entry_resolve; pub mod pre_entry_phase; mod required_lane_resolution_census; +mod typecheck_store_session; pub(crate) use active_workset::*; pub(crate) use entry_resolve::*; pub use required_lane_resolution_census::{ @@ -16430,7 +16431,7 @@ fn reconcile_with_typed_cache( { check_index_module_source_identity(index, &mod_name, &decl_file)?; } - let typed_key = match typed_module_content_key( + let store_parts = match typed_module_store_hex_parts( index, &resolved, &mod_name, @@ -16439,14 +16440,23 @@ fn reconcile_with_typed_cache( &closure_path_to_authored_name, !cycle_residue_slots.contains(&slot), ) { - Ok(key) => key, + Ok(parts) => parts, Err(ref e) if typed_key_dependency_hash_not_ready(e) => { next_pending.push(slot); continue; } Err(e) => return Err(e), }; + let typed_key = store_parts.key.clone(); let cached = index_get_typed(index, &typed_key)?; + if let Some(hit) = cached.as_ref() { + typecheck_store_session::durable_typecheck_commit( + &store_parts.source_digest_hex, + &store_parts.import_interface_hexes, + &store_parts.compiler_digest_hex, + hit.as_ref(), + )?; + } let was_cache_hit = cached.is_some(); // Record this module's cache keys with the armed schedule retention // (idempotent; hit or miss) so its state can be dropped exactly when no @@ -16482,6 +16492,18 @@ fn reconcile_with_typed_cache( if let Some(hit) = index_get_typed(index, &typed_key)? { return Ok(hit); } + match typecheck_store_session::durable_typecheck_lookup( + &store_parts.source_digest_hex, + &store_parts.import_interface_hexes, + &store_parts.compiler_digest_hex, + )? { + typecheck_store_session::DurableTypecheckGet::Hit(hit) => { + return index_insert_typed(index, typed_key.clone(), hit); + } + typecheck_store_session::DurableTypecheckGet::Miss + | typecheck_store_session::DurableTypecheckGet::Unavailable => { + } + } // A miss on a key this index already evicted is a READMISSION — // the governor re-buying paid-for work — counted for the stall // refusal's observation (`gunbc.memory_stall_refusal`). @@ -16587,6 +16609,12 @@ fn reconcile_with_typed_cache( } let computed = index_insert_typed(index, typed_key.clone(), computed)?; + typecheck_store_session::durable_typecheck_commit( + &store_parts.source_digest_hex, + &store_parts.import_interface_hexes, + &store_parts.compiler_digest_hex, + computed.as_ref(), + )?; Ok(computed) }; diff --git a/src/v1/stage0/src/cli_run/entry_resolve.rs b/src/v1/stage0/src/cli_run/entry_resolve.rs index fac80c436ac..b0da6f1bb26 100644 --- a/src/v1/stage0/src/cli_run/entry_resolve.rs +++ b/src/v1/stage0/src/cli_run/entry_resolve.rs @@ -1187,7 +1187,14 @@ pub(crate) fn new_multi_entry_index_shell( } } -pub(crate) fn typed_module_content_key( +pub(crate) struct TypedModuleStoreHexParts { + pub key: String, + pub source_digest_hex: String, + pub import_interface_hexes: Vec, + pub compiler_digest_hex: String, +} + +pub(crate) fn typed_module_store_hex_parts( index: &MultiEntryIndex, resolved: &Rc, mod_name: &str, @@ -1195,7 +1202,7 @@ pub(crate) fn typed_module_content_key( closure_names: &std::collections::HashSet<&str>, closure_path_to_authored_name: &HashMap, include_reference_derived_term: bool, -) -> Result { +) -> Result { let file = &resolved.module.span.file; let source_hash = index .source_hash_by_file @@ -1271,21 +1278,49 @@ pub(crate) fn typed_module_content_key( panic!("typed-module content key refused: reconcile digest is not a valid fnv1a64 structural wire form") }) } - Ok(typed_module_key( + let compiler_digest_hex = transform_content_digest(); + let key = typed_module_key( module_key( - structural_from_wire(source_hash), + structural_from_wire(source_hash.clone()), Rc::new( import_hashes .iter() .cloned() - .map(|h| structural_from_wire(h)) + .map(structural_from_wire) .collect(), ), ), - structural_from_wire(transform_content_digest()), + structural_from_wire(compiler_digest_hex.clone()), ) .digest - .clone()) + .clone(); + Ok(TypedModuleStoreHexParts { + key, + source_digest_hex: source_hash, + import_interface_hexes: import_hashes.iter().cloned().collect(), + compiler_digest_hex, + }) +} + +pub(crate) fn typed_module_content_key( + index: &MultiEntryIndex, + resolved: &Rc, + mod_name: &str, + interface_hash_by_name: &std::collections::HashMap, + closure_names: &std::collections::HashSet<&str>, + closure_path_to_authored_name: &HashMap, + include_reference_derived_term: bool, +) -> Result { + Ok(typed_module_store_hex_parts( + index, + resolved, + mod_name, + interface_hash_by_name, + closure_names, + closure_path_to_authored_name, + include_reference_derived_term, + )? + .key) } /// One derivation of the typed-cache entry cap: env override, else the host diff --git a/src/v1/stage0/src/cli_run/typecheck_store_session.rs b/src/v1/stage0/src/cli_run/typecheck_store_session.rs new file mode 100644 index 00000000000..bdb9bbb290c --- /dev/null +++ b/src/v1/stage0/src/cli_run/typecheck_store_session.rs @@ -0,0 +1,236 @@ +//! Distinct seed operations that look up and commit TypecheckModuleRequest through +//! `gunbc.typecheck_module_store` (C2). Not arguments on `typecheck_module`. + +use std::cell::{Cell, RefCell}; +use std::rc::Rc; + +use crate::v1_compiler_infer::TypecheckModuleResult; +use crate::v1_interpreter::{self, list_value, str_value, ExecutionMode, Value}; +use im::Vector; + +use std::path::Path; + +use super::{make_eval_context, resolve_entry_graph_shared, witness_layer_roots}; + +const STORE_ENTRY: &str = "dag/gunbc/typecheck_module_store.dag"; +const DURABLE_ROOT: &str = "/var/lib/gunbc/materialization-store"; + +thread_local! { + static PREPARING: Cell = const { Cell::new(false) }; + static SESSION: RefCell> = const { RefCell::new(None) }; + static OBSERVED_LARGEST_ENTRY: Cell = const { Cell::new(0) }; +} + +pub(crate) enum DurableTypecheckGet { + Hit(Rc), + Miss, + Unavailable, +} + +fn durable_volume_present() -> bool { + Path::new(DURABLE_ROOT).is_dir() +} + +fn with_session( + f: impl FnOnce(&v1_interpreter::InterpContext) -> Result, +) -> Result { + if PREPARING.with(|c| c.get()) { + return Err("typecheck-store session prepare re-entered".to_string()); + } + SESSION.with(|slot| { + if slot.borrow().is_none() { + PREPARING.with(|c| c.set(true)); + let prepared = (|| { + let roots = witness_layer_roots(); + let (graph, indices) = resolve_entry_graph_shared(&roots, STORE_ENTRY)?; + Ok(make_eval_context(&graph, indices, ExecutionMode::Wet)) + })(); + PREPARING.with(|c| c.set(false)); + match prepared { + Ok(ctx) => *slot.borrow_mut() = Some(ctx), + Err(e) => return Err(e), + } + } + let borrow = slot.borrow(); + let ctx = borrow.as_ref().expect("session installed"); + f(ctx) + }) +} + +fn field_str( + ctx: &v1_interpreter::InterpContext, + fields: &[(v1_interpreter::Symbol, Value)], + name: &str, +) -> Result { + match ctx.field(fields, name) { + Some(Value::Str(s)) => Ok(s.to_string()), + other => Err(format!("{name} not a String: {other:?}")), + } +} + +/// Distinct from `index_get_typed`: durable TypecheckModuleRequest lookup. +pub(crate) fn durable_typecheck_lookup( + source_digest_hex: &str, + import_interface_hexes: &[String], + compiler_digest_hex: &str, +) -> Result { + if PREPARING.with(|c| c.get()) { + return Ok(DurableTypecheckGet::Miss); + } + if !durable_volume_present() { + return Ok(DurableTypecheckGet::Unavailable); + } + let imports = list_value( + import_interface_hexes + .iter() + .cloned() + .map(str_value) + .collect::>(), + ); + let args = [ + ( + Some("source_digest_hex".to_string()), + str_value(source_digest_hex), + ), + (Some("import_interface_hexes".to_string()), imports), + ( + Some("compiler_digest_hex".to_string()), + str_value(compiler_digest_hex), + ), + ]; + with_session(|ctx| { + let result = v1_interpreter::run_in_context_with_args( + ctx, + "seed_lookup_typecheck_hex", + &args, + false, + ) + .map_err(|e| format!("seed_lookup_typecheck_hex: {e}"))?; + let Value::Variant { + variant_name, + fields, + .. + } = &result + else { + return Err(format!( + "seed_lookup_typecheck_hex not a variant: {result:?}" + )); + }; + if ctx.sym_eq(*variant_name, "SeedTypecheckMiss") { + return Ok(DurableTypecheckGet::Miss); + } + if ctx.sym_eq(*variant_name, "SeedTypecheckUnavailable") { + return Ok(DurableTypecheckGet::Unavailable); + } + if ctx.sym_eq(*variant_name, "SeedTypecheckDigestRefused") { + let which = field_str(ctx, fields, "which")?; + return Err(format!("typecheck-store request digest refused ({which})")); + } + if ctx.sym_eq(*variant_name, "SeedTypecheckIntegrityRefused") { + let tag = field_str(ctx, fields, "tag")?; + return Err(format!( + "typecheck-store integrity refusal ({tag}): stale or tampered entry" + )); + } + if ctx.sym_eq(*variant_name, "SeedTypecheckHit") { + let interface_text = field_str(ctx, fields, "interface_text")?; + let computed: TypecheckModuleResult = serde_json::from_str(&interface_text) + .map_err(|e| format!("typecheck-store hit is not a TypecheckModuleResult: {e}"))?; + return Ok(DurableTypecheckGet::Hit(Rc::new(computed))); + } + Err(format!( + "seed_lookup_typecheck_hex unknown variant {}", + ctx.resolve(*variant_name) + )) + }) +} + +/// Distinct from `index_insert_typed`: durable TypecheckModuleRequest commit. +pub(crate) fn durable_typecheck_commit( + source_digest_hex: &str, + import_interface_hexes: &[String], + compiler_digest_hex: &str, + result: &TypecheckModuleResult, +) -> Result<(), String> { + if PREPARING.with(|c| c.get()) { + return Ok(()); + } + if !durable_volume_present() { + return Ok(()); + } + let interface_text = serde_json::to_string(result) + .map_err(|e| format!("typecheck-store serialize TypecheckModuleResult: {e}"))?; + let diagnostics_text = serde_json::to_string(&result.diagnostics) + .map_err(|e| format!("typecheck-store serialize diagnostics: {e}"))?; + let bytes = (interface_text.len() + diagnostics_text.len()) as u64; + OBSERVED_LARGEST_ENTRY.with(|c| { + if bytes > c.get() { + c.set(bytes); + } + }); + let imports = list_value( + import_interface_hexes + .iter() + .cloned() + .map(str_value) + .collect::>(), + ); + let args = [ + ( + Some("source_digest_hex".to_string()), + str_value(source_digest_hex), + ), + (Some("import_interface_hexes".to_string()), imports), + ( + Some("compiler_digest_hex".to_string()), + str_value(compiler_digest_hex), + ), + ( + Some("interface_text".to_string()), + str_value(interface_text), + ), + ( + Some("diagnostics_text".to_string()), + str_value(diagnostics_text), + ), + ]; + with_session(|ctx| { + let result = v1_interpreter::run_in_context_with_args( + ctx, + "seed_commit_typecheck_hex", + &args, + false, + ) + .map_err(|e| format!("seed_commit_typecheck_hex: {e}"))?; + let Value::Variant { + variant_name, + fields, + .. + } = &result + else { + return Err(format!( + "seed_commit_typecheck_hex not a variant: {result:?}" + )); + }; + if ctx.sym_eq(*variant_name, "SeedTypecheckCommitted") + || ctx.sym_eq(*variant_name, "SeedTypecheckCommitMissedOpen") + { + return Ok(()); + } + if ctx.sym_eq(*variant_name, "SeedTypecheckCommitRefused") { + return Err("typecheck-store commit refused".to_string()); + } + if ctx.sym_eq(*variant_name, "SeedTypecheckCommitDigestRefused") { + let which = field_str(ctx, fields, "which")?; + return Err(format!("typecheck-store commit digest refused ({which})")); + } + Err(format!( + "seed_commit_typecheck_hex unknown variant {}", + ctx.resolve(*variant_name) + )) + }) +} + +pub(crate) fn observed_largest_entry_bytes() -> u64 { + OBSERVED_LARGEST_ENTRY.with(|c| c.get()) +} diff --git a/src/v2/workflow/local_repo_wet_terminal.dag b/src/v2/workflow/local_repo_wet_terminal.dag index 24c6b7c7f1e..ecff2166991 100644 --- a/src/v2/workflow/local_repo_wet_terminal.dag +++ b/src/v2/workflow/local_repo_wet_terminal.dag @@ -2192,9 +2192,15 @@ fn local_repo_wet_schedule() -> List { expectation: ExpectedToHold {} }, WetScheduledClaim { - identity: WitnessIdentity { module_path: "test.claim.typecheck_module_store_wet_witness", function: "real_corpus_typecheck_entries_fit_the_measured_largest_entry_by_real_execution" }, + identity: WitnessIdentity { module_path: "test.claim.typecheck_module_store_wet_witness", function: "real_content_keyed_entries_fit_the_measured_largest_entry_by_real_execution" }, entry: "dag/test/claim/typecheck_module_store_wet_witness_test.dag", - function: "real_corpus_typecheck_entries_fit_the_measured_largest_entry_by_real_execution", + function: "real_content_keyed_entries_fit_the_measured_largest_entry_by_real_execution", + expectation: ExpectedToHold {} + }, + WetScheduledClaim { + identity: WitnessIdentity { module_path: "test.claim.typecheck_module_store_wet_witness", function: "a_failed_prepare_is_absent_bytes_not_zero" }, + entry: "dag/test/claim/typecheck_module_store_wet_witness_test.dag", + function: "a_failed_prepare_is_absent_bytes_not_zero", expectation: ExpectedToHold {} }, WetScheduledClaim { From 2634f571cddb36109517240739480e8241f22a99 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Tue, 6 Oct 2026 16:03:06 +0000 Subject: [PATCH 03/24] Stop calling the typecheck-store family budget a measurement. Review 77087 was right that a function named measured_largest_entry was still an estimate. The family unit is now typed_module_bytes_per_entry_estimate under its own name, and the wet size check no longer claims to be a real typecheck-entry reading. Co-authored-by: Cursor --- dag/gunbc/materialization_store_budgets.dag | 4 ++-- .../claim/typecheck_module_store_wet_witness_test.dag | 6 +++--- dag/test/claim/typecheck_module_store_witness_test.dag | 10 +++++----- src/v2/workflow/local_repo_wet_terminal.dag | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/dag/gunbc/materialization_store_budgets.dag b/dag/gunbc/materialization_store_budgets.dag index 437b192c271..73b6dcd1ce6 100644 --- a/dag/gunbc/materialization_store_budgets.dag +++ b/dag/gunbc/materialization_store_budgets.dag @@ -30,12 +30,12 @@ import gunbc.typed_module_cache_capacity { // at or under that per-entry estimate; a larger real object is a revision of the estimate, never // a second authored size in this file. -fn typecheck_module_measured_largest_entry() -> ByteSize { +fn typecheck_module_family_entry_budget_unit() -> ByteSize { typed_module_bytes_per_entry_estimate } data materialization_store_typed_module_budget_policy: ByteSize = byte_size( - count: measure_count(m: typecheck_module_measured_largest_entry()) * typed_module_cache_entries_ceiling + count: measure_count(m: typecheck_module_family_entry_budget_unit()) * typed_module_cache_entries_ceiling ) data materialization_store_durable_family_budgets: List = [ diff --git a/dag/test/claim/typecheck_module_store_wet_witness_test.dag b/dag/test/claim/typecheck_module_store_wet_witness_test.dag index ed98ab45b7b..069945fd070 100644 --- a/dag/test/claim/typecheck_module_store_wet_witness_test.dag +++ b/dag/test/claim/typecheck_module_store_wet_witness_test.dag @@ -30,7 +30,7 @@ import extdeps.realization.materialization_store_local { store_path, } import gunbc.materialization_store_budgets { - typecheck_module_measured_largest_entry, + typecheck_module_family_entry_budget_unit, } import gunbc.typecheck_module_store { typecheck_module_request, @@ -200,12 +200,12 @@ test fn a_failed_prepare_is_absent_bytes_not_zero() -> Bool { } } -test fn real_content_keyed_entries_fit_the_measured_largest_entry_by_real_execution() -> Bool { +test fn content_keyed_probe_objects_fit_the_family_entry_budget_by_real_execution() -> Bool { let types = Filesystem.Read(path: "dag/std/types.dag") let provider = Filesystem.Read(path: "dag/std/materialization_provider.dag") let req_types = sample_req(source_bytes: types.content, import_bytes: []) let req_provider = sample_req(source_bytes: provider.content, import_bytes: [types.content]) - let cap = measure_count(m: typecheck_module_measured_largest_entry()) + let cap = measure_count(m: typecheck_module_family_entry_budget_unit()) let iface_types = "interface std.types\n" let iface_provider = "interface std.materialization_provider\n" types.success && provider.success diff --git a/dag/test/claim/typecheck_module_store_witness_test.dag b/dag/test/claim/typecheck_module_store_witness_test.dag index 3a2eda9e2d8..fa0d2e6770a 100644 --- a/dag/test/claim/typecheck_module_store_witness_test.dag +++ b/dag/test/claim/typecheck_module_store_witness_test.dag @@ -8,7 +8,7 @@ import std.dissolution { dissolution_status, DissolutionFired, DissolutionPendin import std.materialization_provider { request_key } import gunbc.materialization_store_budgets { materialization_store_durable_budgets_consumer_frontier, - typecheck_module_measured_largest_entry, + typecheck_module_family_entry_budget_unit, materialization_store_typed_module_budget_policy, } import gunbc.typed_module_cache_capacity { typed_module_cache_entries_ceiling } @@ -71,16 +71,16 @@ test fn typecheck_module_request_from_hex_refuses_a_non_digest() -> Bool { } } -test fn the_typed_module_family_budget_is_the_measured_entry_times_the_residency_ceiling() -> Bool { +test fn the_typed_module_family_budget_is_the_entry_unit_times_the_residency_ceiling() -> Bool { measure_count(m: materialization_store_typed_module_budget_policy) - == measure_count(m: typecheck_module_measured_largest_entry()) * typed_module_cache_entries_ceiling + == measure_count(m: typecheck_module_family_entry_budget_unit()) * typed_module_cache_entries_ceiling } -test fn a_typecheck_entry_for_the_probe_interface_fits_the_measured_largest_entry() -> Bool { +test fn a_typecheck_entry_for_the_probe_interface_fits_the_family_entry_budget_unit() -> Bool { let req = typecheck_module_request(source_digest: h(v: "mod-a"), direct_import_interfaces: [], compiler_digest: h(v: "compiler-x")) let payloads = typecheck_module_payloads(interface: "export fn f\n", diagnostics: "") match typecheck_module_entry_bytes(req: req, payloads: payloads) { Absent => false - Present { value: n } => measure_count(m: n) <= measure_count(m: typecheck_module_measured_largest_entry()) + Present { value: n } => measure_count(m: n) <= measure_count(m: typecheck_module_family_entry_budget_unit()) } } diff --git a/src/v2/workflow/local_repo_wet_terminal.dag b/src/v2/workflow/local_repo_wet_terminal.dag index ecff2166991..a8d6e688550 100644 --- a/src/v2/workflow/local_repo_wet_terminal.dag +++ b/src/v2/workflow/local_repo_wet_terminal.dag @@ -2192,9 +2192,9 @@ fn local_repo_wet_schedule() -> List { expectation: ExpectedToHold {} }, WetScheduledClaim { - identity: WitnessIdentity { module_path: "test.claim.typecheck_module_store_wet_witness", function: "real_content_keyed_entries_fit_the_measured_largest_entry_by_real_execution" }, + identity: WitnessIdentity { module_path: "test.claim.typecheck_module_store_wet_witness", function: "content_keyed_probe_objects_fit_the_family_entry_budget_by_real_execution" }, entry: "dag/test/claim/typecheck_module_store_wet_witness_test.dag", - function: "real_content_keyed_entries_fit_the_measured_largest_entry_by_real_execution", + function: "content_keyed_probe_objects_fit_the_family_entry_budget_by_real_execution", expectation: ExpectedToHold {} }, WetScheduledClaim { From 4da3c8e84b5b6afb5ca574f084058159a3a7af63 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Tue, 6 Oct 2026 16:06:34 +0000 Subject: [PATCH 04/24] Type the typecheck-store per-entry budget as a TranscribedUncited bet. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The family byte ceiling is still estimate × residency, but the per-entry figure is no longer an untyped authored constant: it carries CitedFigureStanding with a read_obligation naming observed_largest_entry_bytes on the real typecheck commit path, and the budget frontier stays unbound. Drop the live-ladder discharge claim while those frontiers stay open. Co-authored-by: Cursor --- dag/gunbc/commit_workflow.dag | 3 +- dag/gunbc/floor/floor_materialization.dag | 15 ++++++--- dag/gunbc/materialization_store_budgets.dag | 32 +++++++++++++------ .../floor_materialization_witness_test.dag | 20 +++--------- .../typecheck_module_store_witness_test.dag | 21 ++++++++++++ 5 files changed, 59 insertions(+), 32 deletions(-) diff --git a/dag/gunbc/commit_workflow.dag b/dag/gunbc/commit_workflow.dag index 0de6fb093af..dfa0ee098d1 100644 --- a/dag/gunbc/commit_workflow.dag +++ b/dag/gunbc/commit_workflow.dag @@ -1554,8 +1554,7 @@ data commit_gate_roster: List = [ "floor_index_build_share_is_shared_by_reference", "floor_index_build_has_plural_readers", "floor_index_build_reds_without_reference_provider", - "floor_typecheck_store_persist_reds_without_any_provider", - "floor_typecheck_store_persist_is_discharged_by_the_local_store" + "floor_typecheck_store_persist_reds_without_any_provider" ], kind: CorpusWitnessKind, span: SpanUndeclared diff --git a/dag/gunbc/floor/floor_materialization.dag b/dag/gunbc/floor/floor_materialization.dag index 1694146eeb1..2514b490dd5 100644 --- a/dag/gunbc/floor/floor_materialization.dag +++ b/dag/gunbc/floor/floor_materialization.dag @@ -154,8 +154,11 @@ data floor_typecheck_store_no_providers: List = [] // processes. The frame is // UnboundedSiblingsFrame, not SharedStateFrame: recurring invocations of the fold on a runner host // are successive siblings with no shared mutable state to rewire, so there is no LCA process in -// which the repetition could have been carried, and only a STORE tier can discharge it. C2 covers -// it with floor_typecheck_store_persist_provider. +// which the repetition could have been carried, and only a STORE tier can discharge it. +// floor_typecheck_store_persist_provider is the modeled STORE row; it does not discharge the +// live ladder while typecheck_materialization_seed_path_note and +// typecheck_store_family_budget_frontier remain unbound. Live verdicts are +// floor_typecheck_store_persist_verdicts_without_provider. fn floor_runner_host_frame() -> std.materialization_ladder.Frame { std.materialization_ladder.Frame { name: "runner-host", kind: UnboundedSiblingsFrame } @@ -194,9 +197,11 @@ fn floor_typecheck_store_cross_process_demands() -> List { data floor_typecheck_store_no_cost_floor_exemptions: List = [] -// C2: TypecheckModuleRequest through extdeps.realization.materialization_store_local, per-module -// interface+diagnostics grain, family budget from gunbc.materialization_store_budgets. The -// no-provider ladder remains the RED. The live ladder discharges on the runner-host ArtifactTier. +// Modeled STORE row for TypecheckModuleRequest through +// extdeps.realization.materialization_store_local. The no-provider ladder is the LIVE RED. +// persist_verdicts with this provider is the intended discharge fold; it is not claimed as live +// while typecheck_materialization_seed_path_note and typecheck_store_family_budget_frontier stay +// unbound. data floor_typecheck_store_persist_provider_id: String = "materialization_store_local:typed_module" diff --git a/dag/gunbc/materialization_store_budgets.dag b/dag/gunbc/materialization_store_budgets.dag index 73b6dcd1ce6..463aff4b0c8 100644 --- a/dag/gunbc/materialization_store_budgets.dag +++ b/dag/gunbc/materialization_store_budgets.dag @@ -1,9 +1,10 @@ module gunbc.materialization_store_budgets -import std.types { List } +import std.types { List, NonEmptyStr } import std.dissolution { DissolutionCondition, unbound_dissolution } import std.measure { ByteSize, byte_size, measure_count } import std.cache_identity { typed_module_artifact_kind } +import extdeps.external_authority { CitedFigureStanding, TranscribedUncited } import extdeps.realization.materialization_store_local { LocalStoreFamilyBudget, local_store_budget_sum, @@ -22,16 +23,29 @@ import gunbc.typed_module_cache_capacity { // is structural (std.materialization_store_grant DurableHostVolumeRoot names one path), so the host // ceiling is the declared sum of these rows and nothing else. -// POLICY DERIVED FROM AN EXISTING PER-ENTRY AUTHORITY, NOT A SYNTHETIC INTERFACE LITERAL. The -// in-process typed-module cap already names a per-entry byte estimate -// (gunbc.typed_module_cache_capacity typed_module_bytes_per_entry_estimate) and a residency -// ceiling. The durable family cannot outgrow that working set. C2's instrument (the seed -// typecheck path committing real interface+diagnostics objects) must keep every served object -// at or under that per-entry estimate; a larger real object is a revision of the estimate, never -// a second authored size in this file. +// The product of per-entry unit × residency ceiling remains the family budget shape. The per-entry +// figure is typed as a TranscribedUncited BET (DESIGN 4d): it reuses typed_module_bytes_per_entry_estimate +// (TypeEnv-grain in-process denominator) and does not claim to be a measured +// interface-plus-diagnostics store object. The read_obligation names the instrument that would +// ground it. typecheck_store_family_budget_frontier stays unbound until that standing is +// CitedToAuthority. + +type TypecheckStoreFamilyEntryBet { + bytes: ByteSize + standing: CitedFigureStanding +} + +data typecheck_store_family_entry_size_read_obligation: NonEmptyStr = "v1_compiler.cli_run typecheck_store_session observed_largest_entry_bytes after seed_commit_typecheck_hex of TypecheckModuleRequest objects constructed on reconcile_with_typed_cache (interface_text plus diagnostics_text of the real typecheck_module result). Cite that producer; do not transcribe a count into this module." + +data typecheck_store_family_entry_bet: TypecheckStoreFamilyEntryBet = TypecheckStoreFamilyEntryBet { + bytes: typed_module_bytes_per_entry_estimate, + standing: TranscribedUncited { read_obligation: typecheck_store_family_entry_size_read_obligation } +} + +data typecheck_store_family_budget_frontier: DissolutionCondition = unbound_dissolution(description: "TRIGGER: typecheck_store_family_entry_bet.standing is CitedToAuthority whose authority is the executing observed_largest_entry_bytes instrument on the real typecheck commit path (v1_compiler.cli_run typecheck_store_session). SUFFICIENT FOR: typecheck_module_family_entry_budget_unit is derived from that measurement rather than from this bet. Retired by that standing change and nothing else." as NonEmptyStr) fn typecheck_module_family_entry_budget_unit() -> ByteSize { - typed_module_bytes_per_entry_estimate + typecheck_store_family_entry_bet.bytes } data materialization_store_typed_module_budget_policy: ByteSize = byte_size( diff --git a/dag/test/claim/floor/floor_materialization_witness_test.dag b/dag/test/claim/floor/floor_materialization_witness_test.dag index e1dce7046ad..beea6c4b38b 100644 --- a/dag/test/claim/floor/floor_materialization_witness_test.dag +++ b/dag/test/claim/floor/floor_materialization_witness_test.dag @@ -11,16 +11,13 @@ import gunbc.floor_materialization { floor_index_build_materialization_without_provider, floor_index_build_reader_count, floor_typecheck_store_identity, - floor_typecheck_store_persist_provider_id, - floor_typecheck_store_persist_verdicts, floor_typecheck_store_persist_verdicts_without_provider } import std.materialization_ladder { LadderVerdict, value_verdict_is_shared_by_reference, value_verdict_is_refused_needing_reference, - verdict_is_refused_no_provider, - verdict_is_discharged_by + verdict_is_refused_no_provider } import v2.std.live_tree { LiveTreeDisposition, SubstrateInputsOnly } @@ -60,10 +57,9 @@ test fn floor_index_build_reds_without_reference_provider() -> Bool { // C3 — the cross-process demand set: two sibling PROCESSES over one corpus under one built binary // (the witness fold and the generated-artifact drift gate), whose obligation LCA is the runner -// host. Only a store tier can discharge it, and since 2026-10-03 none does: the host-persisted -// provider was deleted after measurement (see gunbc.floor_materialization). The live verdict is -// therefore the no-provider refusal below. The provider's discharge claim and its three per- -// obligation reds were deleted with it; the replacement store re-adds all four. +// host. Only a store tier can discharge it. The live verdict remains the no-provider refusal +// below: typecheck_materialization_seed_path_note and typecheck_store_family_budget_frontier +// stay unbound, so this module does not claim the cross-run demand is discharged. fn first_persist_verdict(vs: List) -> LadderVerdict? { vs.first() @@ -76,11 +72,3 @@ test fn floor_typecheck_store_persist_reds_without_any_provider() -> Bool { verdict_is_refused_no_provider(v: v, identity: floor_typecheck_store_identity) } } - -test fn floor_typecheck_store_persist_is_discharged_by_the_local_store() -> Bool { - match first_persist_verdict(vs: floor_typecheck_store_persist_verdicts()) { - Absent => false - Present { value: v } => - verdict_is_discharged_by(v: v, identity: floor_typecheck_store_identity, provider_id: floor_typecheck_store_persist_provider_id) - } -} diff --git a/dag/test/claim/typecheck_module_store_witness_test.dag b/dag/test/claim/typecheck_module_store_witness_test.dag index fa0d2e6770a..e5782e1475a 100644 --- a/dag/test/claim/typecheck_module_store_witness_test.dag +++ b/dag/test/claim/typecheck_module_store_witness_test.dag @@ -10,6 +10,9 @@ import gunbc.materialization_store_budgets { materialization_store_durable_budgets_consumer_frontier, typecheck_module_family_entry_budget_unit, materialization_store_typed_module_budget_policy, + typecheck_store_family_budget_frontier, + typecheck_store_family_entry_bet, + typecheck_store_family_entry_size_read_obligation, } import gunbc.typed_module_cache_capacity { typed_module_cache_entries_ceiling } import gunbc.typecheck_module_store { @@ -23,6 +26,7 @@ import gunbc.typecheck_module_store { } import std.measure { measure_count } import v2.std.optional { Present } +import extdeps.external_authority { TranscribedUncited, CitedToAuthority } fn h(v: String) -> Fnv1a64Structural { content_hash_atom(value: v) @@ -71,6 +75,23 @@ test fn typecheck_module_request_from_hex_refuses_a_non_digest() -> Bool { } } +test fn the_family_entry_size_is_a_transcribed_uncited_bet_naming_the_real_route_instrument() -> Bool { + match typecheck_store_family_entry_bet.standing { + TranscribedUncited { read_obligation: o } => (o as String) == (typecheck_store_family_entry_size_read_obligation as String) + CitedToAuthority { authority: _ } => false + } +} + +test fn the_family_budget_frontier_stays_unbound_while_the_entry_size_is_a_bet() -> Bool { + match dissolution_status(condition: typecheck_store_family_budget_frontier, present_decls: [ + decl_ref(module_path: "gunbc.materialization_store_budgets", decl_name: "typecheck_store_family_entry_bet") + ]) { + DissolutionUnbound => true + DissolutionFired => false + DissolutionPending => false + } +} + test fn the_typed_module_family_budget_is_the_entry_unit_times_the_residency_ceiling() -> Bool { measure_count(m: materialization_store_typed_module_budget_policy) == measure_count(m: typecheck_module_family_entry_budget_unit()) * typed_module_cache_entries_ceiling From 8654240ae6e670966793ceb365f975e7a5fa11d1 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Tue, 6 Oct 2026 17:30:44 +0000 Subject: [PATCH 05/24] Fix typecheck-store compile: ArtifactPayload lives on the object layer. Import the payload type from std.materialization_object, and keep the wet cold/warm millis if-arms as EpochMs so declarations resolve. Co-authored-by: Cursor --- dag/gunbc/typecheck_module_store.dag | 2 +- .../typecheck_module_store_wet_witness_test.dag | 17 +++++++++-------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/dag/gunbc/typecheck_module_store.dag b/dag/gunbc/typecheck_module_store.dag index 6da53576a47..116120fa918 100644 --- a/dag/gunbc/typecheck_module_store.dag +++ b/dag/gunbc/typecheck_module_store.dag @@ -6,13 +6,13 @@ import std.interface_summary { InterfaceHash } import std.measure { ByteSize } import std.materialization_provider { ArtifactRequest, - ArtifactPayload, TypecheckModuleRequest, compiler_identity_input, output_typed_module_interface, output_typecheck_diagnostics, } import std.materialization_object { + ArtifactPayload, store_commit_prepare, StoreCommitPrepared, StoreCommitNotPrepared, diff --git a/dag/test/claim/typecheck_module_store_wet_witness_test.dag b/dag/test/claim/typecheck_module_store_wet_witness_test.dag index 069945fd070..acc234d8578 100644 --- a/dag/test/claim/typecheck_module_store_wet_witness_test.dag +++ b/dag/test/claim/typecheck_module_store_wet_witness_test.dag @@ -6,8 +6,9 @@ import std.types { Bool, List, String, NonEmptyStr, EpochMs } import std.measure { measure_count } import std.content_hash { content_hash_atom } import std.interface_summary { InterfaceHash } -import std.materialization_provider { output_typed_module_interface, ArtifactRequest, ArtifactPayload } +import std.materialization_provider { output_typed_module_interface, ArtifactRequest } import std.materialization_object { + ArtifactPayload, store_lookup_is_hit, store_lookup_is_miss, store_lookup_payload, @@ -90,7 +91,7 @@ fn object_path(root_path: String, req: ArtifactRequest) -> String { fn millis_or_zero(read: gunbc.clock_read.ObserverClockRead) -> EpochMs { match read { ObserverClockObserved { instant: i } => i.millis - ObserverClockUnreadable { detail: _ } => 0 + ObserverClockUnreadable { detail: _ } => 0 as EpochMs } } @@ -110,19 +111,19 @@ fn typecheck_module_store_cold_warm_receipt(capability: LocalStoreCapability, re let t2 = observer_clock_read() let warm = typecheck_module_serve(capability: capability, req: req) let t3 = observer_clock_read() - let cold_ms = if millis_or_zero(read: t2) < millis_or_zero(read: t0) { 0 } else { millis_or_zero(read: t2) - millis_or_zero(read: t0) } - let warm_ms = if millis_or_zero(read: t3) < millis_or_zero(read: t2) { 0 } else { millis_or_zero(read: t3) - millis_or_zero(read: t2) } + let cold_ms = if millis_or_zero(read: t2) < millis_or_zero(read: t0) { 0 as EpochMs } else { millis_or_zero(read: t2) - millis_or_zero(read: t0) } + let warm_ms = if millis_or_zero(read: t3) < millis_or_zero(read: t2) { 0 as EpochMs } else { millis_or_zero(read: t3) - millis_or_zero(read: t2) } TypecheckStoreColdWarmReceipt { cold_was_miss: true, warm_was_hit: match warm { TypecheckModuleHit { lookup: h } => store_lookup_is_hit(l: h) TypecheckModuleMiss { lookup: _ } => false TypecheckModuleRefused { lookup: _ } => false }, - saved_millis: if cold_ms < warm_ms { 0 } else { cold_ms - warm_ms } + saved_millis: if cold_ms < warm_ms { 0 as EpochMs } else { cold_ms - warm_ms } } } std.materialization_object.StoreCommitRefused { key: _, refusal: _ } => - TypecheckStoreColdWarmReceipt { cold_was_miss: true, warm_was_hit: false, saved_millis: 0 } + TypecheckStoreColdWarmReceipt { cold_was_miss: true, warm_was_hit: false, saved_millis: 0 as EpochMs } } - TypecheckModuleHit { lookup: _ } => TypecheckStoreColdWarmReceipt { cold_was_miss: false, warm_was_hit: false, saved_millis: 0 } - TypecheckModuleRefused { lookup: _ } => TypecheckStoreColdWarmReceipt { cold_was_miss: false, warm_was_hit: false, saved_millis: 0 } + TypecheckModuleHit { lookup: _ } => TypecheckStoreColdWarmReceipt { cold_was_miss: false, warm_was_hit: false, saved_millis: 0 as EpochMs } + TypecheckModuleRefused { lookup: _ } => TypecheckStoreColdWarmReceipt { cold_was_miss: false, warm_was_hit: false, saved_millis: 0 as EpochMs } } } From 47785a97a3eb815a74b40cd805f536079395ee42 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Tue, 6 Oct 2026 18:55:33 +0000 Subject: [PATCH 06/24] Brand InterfaceHash at a typed return, not with a runtime cast. The wet family-budget probe refused because map(... as InterfaceHash) cannot coerce Fnv1a64Structural at eval. Seed hex imports use the same pattern. Co-authored-by: Cursor --- dag/gunbc/typecheck_module_store.dag | 8 ++++++-- .../claim/typecheck_module_store_wet_witness_test.dag | 6 +++++- dag/test/claim/typecheck_module_store_witness_test.dag | 4 ++++ 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/dag/gunbc/typecheck_module_store.dag b/dag/gunbc/typecheck_module_store.dag index 116120fa918..1429bc4b904 100644 --- a/dag/gunbc/typecheck_module_store.dag +++ b/dag/gunbc/typecheck_module_store.dag @@ -86,7 +86,7 @@ fn typecheck_module_request_from_hex( Present { value: src } => match hex_digest(hex: compiler_digest_hex) { Absent => TypecheckModuleRequestDigestRefused { which: "compiler_digest" } Present { value: compiler } => { - let imports = import_interface_hexes |> map(h => hex_digest(hex: h)) + let imports = import_interface_hexes |> map(h => interface_hash_from_hex(hex: h)) if imports |> any(d => match d { Absent => true Present { value: _ } => false }) { TypecheckModuleRequestDigestRefused { which: "direct_import_interfaces" } } else { @@ -94,7 +94,7 @@ fn typecheck_module_request_from_hex( req: typecheck_module_request( source_digest: src, direct_import_interfaces: imports |> flat_map(d => match d { - Present { value: v } => [v as InterfaceHash] + Present { value: v } => [v] Absent => [] }), compiler_digest: compiler @@ -106,6 +106,10 @@ fn typecheck_module_request_from_hex( } } +fn interface_hash_from_hex(hex: String) -> InterfaceHash? { + fnv1a64_structural_hex_digest(hex: hex) +} + fn typecheck_module_payloads(interface: String, diagnostics: String) -> List { [ ArtifactPayload { id: output_typed_module_interface, content: interface }, diff --git a/dag/test/claim/typecheck_module_store_wet_witness_test.dag b/dag/test/claim/typecheck_module_store_wet_witness_test.dag index acc234d8578..f5bfb9ccc2f 100644 --- a/dag/test/claim/typecheck_module_store_wet_witness_test.dag +++ b/dag/test/claim/typecheck_module_store_wet_witness_test.dag @@ -69,10 +69,14 @@ fn opened_production_budgets(root: WitnessScratchRoot) -> LocalStoreCapability? } } +fn interface_hash_of_bytes(s: String) -> InterfaceHash { + content_hash_atom(value: s) +} + fn sample_req(source_bytes: String, import_bytes: List) -> ArtifactRequest { typecheck_module_request( source_digest: content_hash_atom(value: source_bytes), - direct_import_interfaces: import_bytes |> map(s => content_hash_atom(value: s) as InterfaceHash), + direct_import_interfaces: import_bytes |> map(s => interface_hash_of_bytes(s: s)), compiler_digest: content_hash_atom(value: "seed-compiler") ) } diff --git a/dag/test/claim/typecheck_module_store_witness_test.dag b/dag/test/claim/typecheck_module_store_witness_test.dag index e5782e1475a..f31cc88c011 100644 --- a/dag/test/claim/typecheck_module_store_witness_test.dag +++ b/dag/test/claim/typecheck_module_store_witness_test.dag @@ -32,6 +32,10 @@ fn h(v: String) -> Fnv1a64Structural { content_hash_atom(value: v) } +fn iface(v: String) -> InterfaceHash { + content_hash_atom(value: v) +} + test fn the_durable_budgets_consumer_frontier_stays_unbound_until_the_seed_path_runs() -> Bool { match dissolution_status(condition: materialization_store_durable_budgets_consumer_frontier, present_decls: [ decl_ref(module_path: "gunbc.typecheck_module_store", decl_name: "ensure_durable_typecheck_store") From 8fa3964f3b7cd823114a409541bf812995832cf0 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Tue, 6 Oct 2026 21:52:58 +0000 Subject: [PATCH 07/24] Enroll typecheck-store wet claims as LocalRepoWetLane route gaps. The hermetic floor has no DirWithTemplate/Read mock for those identities; their terminal stays the wet lane. Drop the InterfaceHash runtime cast from the hermetic key claim. Co-authored-by: Cursor --- dag/gunbc/ci/ci_layer_roots.dag | 5 ++++ .../typecheck_module_store_witness_test.dag | 23 ++++++------------- src/v2/workflow/floor_route_gap.dag | 6 +++-- 3 files changed, 16 insertions(+), 18 deletions(-) diff --git a/dag/gunbc/ci/ci_layer_roots.dag b/dag/gunbc/ci/ci_layer_roots.dag index 40ca0b403d4..6d592277f04 100644 --- a/dag/gunbc/ci/ci_layer_roots.dag +++ b/dag/gunbc/ci/ci_layer_roots.dag @@ -1111,6 +1111,11 @@ data witness_exclusion_frontier: List = [ classification: LocalRepoWetLane, reason: excl_local_repo_wet_tempdir_write_reason, dissolution: excl_local_repo_wet_dissolve}, + WitnessExclusionRow { + pattern: "typecheck_module_store_wet_witness_test.dag", + classification: LocalRepoWetLane, + reason: excl_local_repo_wet_tempdir_write_reason, + dissolution: excl_local_repo_wet_dissolve}, WitnessExclusionRow { pattern: "host_capacity_wet_witness_test.dag", classification: LocalRepoWetLane, diff --git a/dag/test/claim/typecheck_module_store_witness_test.dag b/dag/test/claim/typecheck_module_store_witness_test.dag index f31cc88c011..fd7972e9e84 100644 --- a/dag/test/claim/typecheck_module_store_witness_test.dag +++ b/dag/test/claim/typecheck_module_store_witness_test.dag @@ -1,8 +1,7 @@ module test.claim.typecheck_module_store_witness import std.types { Bool, List, String } -import std.content_hash { content_hash_atom, as_content_hash_structural, Fnv1a64Structural } -import std.interface_summary { InterfaceHash, module_key, typed_module_key } +import std.content_hash { content_hash_atom, Fnv1a64Structural } import std.decl_ref { decl_ref } import std.dissolution { dissolution_status, DissolutionFired, DissolutionPending, DissolutionUnbound } import std.materialization_provider { request_key } @@ -32,10 +31,6 @@ fn h(v: String) -> Fnv1a64Structural { content_hash_atom(value: v) } -fn iface(v: String) -> InterfaceHash { - content_hash_atom(value: v) -} - test fn the_durable_budgets_consumer_frontier_stays_unbound_until_the_seed_path_runs() -> Bool { match dissolution_status(condition: materialization_store_durable_budgets_consumer_frontier, present_decls: [ decl_ref(module_path: "gunbc.typecheck_module_store", decl_name: "ensure_durable_typecheck_store") @@ -58,18 +53,14 @@ test fn the_seed_path_note_stays_unbound_until_reconcile_calls_lookup_and_commit test fn typecheck_module_request_key_is_derived_from_the_judgment_contract() -> Bool { let src = h(v: "source-bytes-not-a-path") - let imp = h(v: "iface-1") as InterfaceHash let compiler = h(v: "compiler-x") - let a = typecheck_module_request(source_digest: src, direct_import_interfaces: [imp], compiler_digest: compiler) - let b = typecheck_module_request(source_digest: src, direct_import_interfaces: [imp], compiler_digest: compiler) - let changed = typecheck_module_request(source_digest: src, direct_import_interfaces: [h(v: "iface-2") as InterfaceHash], compiler_digest: compiler) - let expected = typed_module_key( - interface_key: module_key(source_hash: src, direct_import_interface_hashes: [imp]), - compiler_identity: compiler - ) + let a = typecheck_module_request(source_digest: src, direct_import_interfaces: [], compiler_digest: compiler) + let b = typecheck_module_request(source_digest: src, direct_import_interfaces: [], compiler_digest: compiler) + let changed_source = typecheck_module_request(source_digest: h(v: "other-source"), direct_import_interfaces: [], compiler_digest: compiler) + let changed_compiler = typecheck_module_request(source_digest: src, direct_import_interfaces: [], compiler_digest: h(v: "compiler-y")) request_key(req: a) == request_key(req: b) - && (request_key(req: a) == request_key(req: changed)) == false - && request_key(req: a) == as_content_hash_structural(structural: expected) + && (request_key(req: a) == request_key(req: changed_source)) == false + && (request_key(req: a) == request_key(req: changed_compiler)) == false } test fn typecheck_module_request_from_hex_refuses_a_non_digest() -> Bool { diff --git a/src/v2/workflow/floor_route_gap.dag b/src/v2/workflow/floor_route_gap.dag index 66dede8b866..97e4c214426 100644 --- a/src/v2/workflow/floor_route_gap.dag +++ b/src/v2/workflow/floor_route_gap.dag @@ -1174,7 +1174,7 @@ fn floor_route_gap_expectation_chunk_09() -> List { // The catalog-contents Read of the same checkout path is hermetic input, not a route gap. // Memory capture verification consumes DigestStdin over the bytes read once. fn floor_route_gap_expectation_chunk_10() -> List { - Cons { head: FloorRouteGapExpectation { identity: "test.claim.memory_capture_real_execution_witness.w_capture_digest_binds_consumed_content", operation: "DigestStdin", ground: NoMockResponse {} }, tail: Cons { head: FloorRouteGapExpectation { identity: "test.claim.pre_os_capture_replay_witness.w_early_exit_refuses_absence_on_real_bytes", operation: "DigestStdin", ground: NoMockResponse {} }, tail: Cons { head: FloorRouteGapExpectation { identity: "test.claim.pre_os_capture_replay_witness.w_four_real_captures_retain_statement_inventory_and_missing_receipts", operation: "DigestStdin", ground: NoMockResponse {} }, tail: Cons { head: FloorRouteGapExpectation { identity: "test.claim.pre_os_capture_replay_witness.w_truncated_configuration_cannot_support_absence", operation: "DigestStdin", ground: NoMockResponse {} }, tail: Cons { head: FloorRouteGapExpectation { identity: "test.claim.pre_os_capture_replay_witness.w_real_host_userspace_positive_and_truncated_negative", operation: "DigestStdin", ground: NoMockResponse {} }, tail: Cons { + Cons { head: FloorRouteGapExpectation { identity: "test.claim.typecheck_module_store_wet_witness.typecheck_module_miss_computes_and_warm_hits_by_real_execution", operation: "DirWithTemplate", ground: NoMockResponse {} }, tail: Cons { head: FloorRouteGapExpectation { identity: "test.claim.typecheck_module_store_wet_witness.a_tampered_typecheck_entry_refuses_and_is_not_a_miss_by_real_execution", operation: "DirWithTemplate", ground: NoMockResponse {} }, tail: Cons { head: FloorRouteGapExpectation { identity: "test.claim.typecheck_module_store_wet_witness.the_durable_production_open_uses_the_granted_root_by_real_execution", operation: "Read", ground: NoMockResponse {} }, tail: Cons { head: FloorRouteGapExpectation { identity: "test.claim.memory_capture_real_execution_witness.w_capture_digest_binds_consumed_content", operation: "DigestStdin", ground: NoMockResponse {} }, tail: Cons { head: FloorRouteGapExpectation { identity: "test.claim.pre_os_capture_replay_witness.w_early_exit_refuses_absence_on_real_bytes", operation: "DigestStdin", ground: NoMockResponse {} }, tail: Cons { head: FloorRouteGapExpectation { identity: "test.claim.pre_os_capture_replay_witness.w_four_real_captures_retain_statement_inventory_and_missing_receipts", operation: "DigestStdin", ground: NoMockResponse {} }, tail: Cons { head: FloorRouteGapExpectation { identity: "test.claim.pre_os_capture_replay_witness.w_truncated_configuration_cannot_support_absence", operation: "DigestStdin", ground: NoMockResponse {} }, tail: Cons { head: FloorRouteGapExpectation { identity: "test.claim.pre_os_capture_replay_witness.w_real_host_userspace_positive_and_truncated_negative", operation: "DigestStdin", ground: NoMockResponse {} }, tail: Cons { head: FloorRouteGapExpectation { identity: "test.claim.megarac_spx_ui_surface_artifact_integrity_witness.committed_spx_ui_surface_hashes_to_the_catalog_identity", operation: "Dir", ground: NoMockResponse {} }, tail: Cons { head: FloorRouteGapExpectation { identity: "test.claim.megarac_spx_ui_surface_artifact_integrity_witness.committed_sol_host_payload_hashes_to_the_positive_control_identity", operation: "Dir", ground: NoMockResponse {} }, @@ -1184,7 +1184,9 @@ fn floor_route_gap_expectation_chunk_10() -> List { } } } - + } + } + } } } } From 0d6cb0a5470f370687c80e4696a1fb03942fa1e8 Mon Sep 17 00:00:00 2001 From: x Date: Tue, 6 Oct 2026 22:09:42 +0000 Subject: [PATCH 08/24] WIP: typecheck-store own-interface split (pre-regen) --- dag/gunbc/ci/ci_layer_roots.dag | 5 - dag/gunbc/materialization_store_budgets.dag | 2 +- dag/gunbc/typecheck_module_store.dag | 108 +++++++--- .../typecheck_module_store_witness_test.dag | 23 ++- src/v1/04_infer.dag | 97 +++++++-- src/v1/stage0/src/cli_run.rs | 154 +++++++------- .../src/cli_run/typecheck_store_session.rs | 189 ++++++++++++++---- src/v2/workflow/floor_route_gap.dag | 6 +- 8 files changed, 410 insertions(+), 174 deletions(-) diff --git a/dag/gunbc/ci/ci_layer_roots.dag b/dag/gunbc/ci/ci_layer_roots.dag index 6d592277f04..40ca0b403d4 100644 --- a/dag/gunbc/ci/ci_layer_roots.dag +++ b/dag/gunbc/ci/ci_layer_roots.dag @@ -1111,11 +1111,6 @@ data witness_exclusion_frontier: List = [ classification: LocalRepoWetLane, reason: excl_local_repo_wet_tempdir_write_reason, dissolution: excl_local_repo_wet_dissolve}, - WitnessExclusionRow { - pattern: "typecheck_module_store_wet_witness_test.dag", - classification: LocalRepoWetLane, - reason: excl_local_repo_wet_tempdir_write_reason, - dissolution: excl_local_repo_wet_dissolve}, WitnessExclusionRow { pattern: "host_capacity_wet_witness_test.dag", classification: LocalRepoWetLane, diff --git a/dag/gunbc/materialization_store_budgets.dag b/dag/gunbc/materialization_store_budgets.dag index 463aff4b0c8..a97de51e60d 100644 --- a/dag/gunbc/materialization_store_budgets.dag +++ b/dag/gunbc/materialization_store_budgets.dag @@ -35,7 +35,7 @@ type TypecheckStoreFamilyEntryBet { standing: CitedFigureStanding } -data typecheck_store_family_entry_size_read_obligation: NonEmptyStr = "v1_compiler.cli_run typecheck_store_session observed_largest_entry_bytes after seed_commit_typecheck_hex of TypecheckModuleRequest objects constructed on reconcile_with_typed_cache (interface_text plus diagnostics_text of the real typecheck_module result). Cite that producer; do not transcribe a count into this module." +data typecheck_store_family_entry_size_read_obligation: NonEmptyStr = "v1_compiler.cli_run typecheck_store_session observed_largest_entry_bytes after seed_commit_typecheck_hex of TypecheckModuleRequest objects constructed on reconcile_with_typed_cache (interface_text plus diagnostics_text of the module's OWN typecheck_module_items tail, never the import-derived closure), reported on the run receipt line by typecheck_store_session store_receipt_line as largest_entry_bytes. Cite that producer; do not transcribe a count into this module." data typecheck_store_family_entry_bet: TypecheckStoreFamilyEntryBet = TypecheckStoreFamilyEntryBet { bytes: typed_module_bytes_per_entry_estimate, diff --git a/dag/gunbc/typecheck_module_store.dag b/dag/gunbc/typecheck_module_store.dag index 1429bc4b904..7de319190ea 100644 --- a/dag/gunbc/typecheck_module_store.dag +++ b/dag/gunbc/typecheck_module_store.dag @@ -275,6 +275,44 @@ type SeedTypecheckCommit | SeedTypecheckCommitRefused | SeedTypecheckCommitDigestRefused { which: String } +// One body per operation, parameterized by the opening: the production door and the witness door +// differ ONLY in which root they open, never in what they do after it. +fn seed_lookup_typecheck_opened( + opening: LocalStoreOpening, + req: ArtifactRequest +) -> SeedTypecheckLookup { + match opening { + LocalStoreUnavailable { root_path: p, cause: _ } => SeedTypecheckUnavailable { root_path: p } + LocalStoreOpened { capability: c, durability: _ } => match typecheck_module_serve(capability: c, req: req) { + TypecheckModuleMiss { lookup: _ } => SeedTypecheckMiss + TypecheckModuleRefused { lookup: l } => SeedTypecheckIntegrityRefused { tag: store_lookup_refusal_tag(l: l) } + TypecheckModuleHit { lookup: h } => match store_lookup_payload(l: h, id: output_typed_module_interface) { + Absent => SeedTypecheckIntegrityRefused { tag: "missing_typed_module_interface" } + Present { value: iface } => match store_lookup_payload(l: h, id: output_typecheck_diagnostics) { + Absent => SeedTypecheckIntegrityRefused { tag: "missing_typecheck_diagnostics" } + Present { value: diags } => SeedTypecheckHit { interface_text: iface, diagnostics_text: diags } + } + } + } + } +} + +fn seed_commit_typecheck_opened( + opening: LocalStoreOpening, + req: ArtifactRequest, + interface_text: String, + diagnostics_text: String +) -> SeedTypecheckCommit { + match opening { + LocalStoreUnavailable { root_path: p, cause: _ } => SeedTypecheckCommitMissedOpen { root_path: p } + LocalStoreOpened { capability: c, durability: _ } => + match commit_typecheck_module(capability: c, req: req, interface_text: interface_text, diagnostics_text: diagnostics_text).commit { + StoreCommitSettled { receipt: _ } => SeedTypecheckCommitted + StoreCommitRefused { key: _, refusal: _ } => SeedTypecheckCommitRefused + } + } +} + fn seed_lookup_typecheck_hex( source_digest_hex: String, import_interface_hexes: List, @@ -286,20 +324,7 @@ fn seed_lookup_typecheck_hex( compiler_digest_hex: compiler_digest_hex ) { TypecheckModuleRequestDigestRefused { which: w } => SeedTypecheckDigestRefused { which: w } - TypecheckModuleRequestBuilt { req: req } => match ensure_durable_typecheck_store() { - LocalStoreUnavailable { root_path: p, cause: _ } => SeedTypecheckUnavailable { root_path: p } - LocalStoreOpened { capability: c, durability: _ } => match typecheck_module_serve(capability: c, req: req) { - TypecheckModuleMiss { lookup: _ } => SeedTypecheckMiss - TypecheckModuleRefused { lookup: l } => SeedTypecheckIntegrityRefused { tag: store_lookup_refusal_tag(l: l) } - TypecheckModuleHit { lookup: h } => match store_lookup_payload(l: h, id: output_typed_module_interface) { - Absent => SeedTypecheckIntegrityRefused { tag: "missing_typed_module_interface" } - Present { value: iface } => match store_lookup_payload(l: h, id: output_typecheck_diagnostics) { - Absent => SeedTypecheckIntegrityRefused { tag: "missing_typecheck_diagnostics" } - Present { value: diags } => SeedTypecheckHit { interface_text: iface, diagnostics_text: diags } - } - } - } - } + TypecheckModuleRequestBuilt { req: req } => seed_lookup_typecheck_opened(opening: ensure_durable_typecheck_store(), req: req) } } @@ -316,18 +341,49 @@ fn seed_commit_typecheck_hex( compiler_digest_hex: compiler_digest_hex ) { TypecheckModuleRequestDigestRefused { which: w } => SeedTypecheckCommitDigestRefused { which: w } - TypecheckModuleRequestBuilt { req: req } => match ensure_durable_typecheck_store() { - LocalStoreUnavailable { root_path: p, cause: _ } => SeedTypecheckCommitMissedOpen { root_path: p } - LocalStoreOpened { capability: c, durability: _ } => - match commit_typecheck_module( - capability: c, - req: req, - interface_text: interface_text, - diagnostics_text: diagnostics_text - ).commit { - StoreCommitSettled { receipt: _ } => SeedTypecheckCommitted - StoreCommitRefused { key: _, refusal: _ } => SeedTypecheckCommitRefused - } + TypecheckModuleRequestBuilt { req: req } => + seed_commit_typecheck_opened(opening: ensure_durable_typecheck_store(), req: req, interface_text: interface_text, diagnostics_text: diagnostics_text) + } +} + +// Witness doors: the SAME bodies over a WitnessScratchRoot, so an executing control round-trips a +// real typecheck entry without touching the granted durable volume. +fn seed_lookup_typecheck_witness_hex( + root_path: String, + source_digest_hex: String, + import_interface_hexes: List, + compiler_digest_hex: String +) -> SeedTypecheckLookup { + match typecheck_module_request_from_hex( + source_digest_hex: source_digest_hex, + import_interface_hexes: import_interface_hexes, + compiler_digest_hex: compiler_digest_hex + ) { + TypecheckModuleRequestDigestRefused { which: w } => SeedTypecheckDigestRefused { which: w } + TypecheckModuleRequestBuilt { req: req } => match initialize_witness_typecheck_store(root: WitnessScratchRoot { root: root_path }) { + LocalStoreInitialized { opening: o } => seed_lookup_typecheck_opened(opening: o, req: req) + LocalStoreInitializationRefused { root_path: p, cause: c } => SeedTypecheckUnavailable { root_path: p } + } + } +} + +fn seed_commit_typecheck_witness_hex( + root_path: String, + source_digest_hex: String, + import_interface_hexes: List, + compiler_digest_hex: String, + interface_text: String, + diagnostics_text: String +) -> SeedTypecheckCommit { + match typecheck_module_request_from_hex( + source_digest_hex: source_digest_hex, + import_interface_hexes: import_interface_hexes, + compiler_digest_hex: compiler_digest_hex + ) { + TypecheckModuleRequestDigestRefused { which: w } => SeedTypecheckCommitDigestRefused { which: w } + TypecheckModuleRequestBuilt { req: req } => match initialize_witness_typecheck_store(root: WitnessScratchRoot { root: root_path }) { + LocalStoreInitialized { opening: o } => seed_commit_typecheck_opened(opening: o, req: req, interface_text: interface_text, diagnostics_text: diagnostics_text) + LocalStoreInitializationRefused { root_path: p, cause: c } => SeedTypecheckCommitMissedOpen { root_path: p } } } } diff --git a/dag/test/claim/typecheck_module_store_witness_test.dag b/dag/test/claim/typecheck_module_store_witness_test.dag index fd7972e9e84..1a56c2522a3 100644 --- a/dag/test/claim/typecheck_module_store_witness_test.dag +++ b/dag/test/claim/typecheck_module_store_witness_test.dag @@ -1,7 +1,8 @@ module test.claim.typecheck_module_store_witness import std.types { Bool, List, String } -import std.content_hash { content_hash_atom, Fnv1a64Structural } +import std.content_hash { content_hash_atom, as_content_hash_structural, Fnv1a64Structural } +import std.interface_summary { InterfaceHash, module_key, typed_module_key } import std.decl_ref { decl_ref } import std.dissolution { dissolution_status, DissolutionFired, DissolutionPending, DissolutionUnbound } import std.materialization_provider { request_key } @@ -31,6 +32,10 @@ fn h(v: String) -> Fnv1a64Structural { content_hash_atom(value: v) } +fn iface(v: String) -> InterfaceHash { + content_hash_atom(value: v) +} + test fn the_durable_budgets_consumer_frontier_stays_unbound_until_the_seed_path_runs() -> Bool { match dissolution_status(condition: materialization_store_durable_budgets_consumer_frontier, present_decls: [ decl_ref(module_path: "gunbc.typecheck_module_store", decl_name: "ensure_durable_typecheck_store") @@ -53,14 +58,18 @@ test fn the_seed_path_note_stays_unbound_until_reconcile_calls_lookup_and_commit test fn typecheck_module_request_key_is_derived_from_the_judgment_contract() -> Bool { let src = h(v: "source-bytes-not-a-path") + let imp = iface(v: "iface-1") let compiler = h(v: "compiler-x") - let a = typecheck_module_request(source_digest: src, direct_import_interfaces: [], compiler_digest: compiler) - let b = typecheck_module_request(source_digest: src, direct_import_interfaces: [], compiler_digest: compiler) - let changed_source = typecheck_module_request(source_digest: h(v: "other-source"), direct_import_interfaces: [], compiler_digest: compiler) - let changed_compiler = typecheck_module_request(source_digest: src, direct_import_interfaces: [], compiler_digest: h(v: "compiler-y")) + let a = typecheck_module_request(source_digest: src, direct_import_interfaces: [imp], compiler_digest: compiler) + let b = typecheck_module_request(source_digest: src, direct_import_interfaces: [imp], compiler_digest: compiler) + let changed = typecheck_module_request(source_digest: src, direct_import_interfaces: [iface(v: "iface-2")], compiler_digest: compiler) + let expected = typed_module_key( + interface_key: module_key(source_hash: src, direct_import_interface_hashes: [imp]), + compiler_identity: compiler + ) request_key(req: a) == request_key(req: b) - && (request_key(req: a) == request_key(req: changed_source)) == false - && (request_key(req: a) == request_key(req: changed_compiler)) == false + && (request_key(req: a) == request_key(req: changed)) == false + && request_key(req: a) == as_content_hash_structural(structural: expected) } test fn typecheck_module_request_from_hex_refuses_a_non_digest() -> Bool { diff --git a/src/v1/04_infer.dag b/src/v1/04_infer.dag index 476618d6dc5..57ef0597218 100644 --- a/src/v1/04_infer.dag +++ b/src/v1/04_infer.dag @@ -16844,7 +16844,27 @@ fn ground_kernel_views(items: List, env: TypeEnv, module_name: String) -> } } -fn typecheck_module(resolved: ResolvedModule, parent_index: Map, variant_surfaces: Map, source_indices: Map, intern_table: InternTable, symbol_index: SymbolIndex, global_variant_base: Map) -> TypecheckModuleResult { +// THE ENTRY/CLOSURE BOUNDARY (C2, gunbc#13482). typecheck_module is two facts, not one: the HEAD is +// everything DERIVED FROM THE IMPORTS -- the type env (ancestry, parents, the global bare census), the +// module context (func-env views of the imports, variant locals, the item registry) -- and the TAIL is +// what THIS MODULE'S ITEMS produce: typed items, resolved signatures, the refreshed registry and the +// diagnostics. A materialized entry stores only the tail (TypecheckModuleOwn); a hit re-runs the head +// from the imports' already-reconstructed modules (typecheck_module_restore) and grafts the stored tail. +// Storing the head would store the closure: that snapshot is what made the deleted store's entries +// multi-GB. typecheck_module itself is head + tail with no second derivation, so a miss is the same +// TypedModule and diagnostics as before the split. +type TypecheckModuleHead + = TypecheckHeadAbandoned { result: TypecheckModuleResult } + | TypecheckHeadReady { env_result: BuildTypeEnvResult, module_name: String, ctx: ModuleContext } + +type TypecheckModuleOwn { + items: List + func_local: Map + item_registry: Map + diagnostics: List +} + +fn typecheck_module_head(resolved: ResolvedModule, parent_index: Map, variant_surfaces: Map, source_indices: Map, intern_table: InternTable, symbol_index: SymbolIndex, global_variant_base: Map) -> TypecheckModuleHead { let env_result = build_type_env(module: resolved, parent_index: parent_index, source_indices: source_indices, intern_table: intern_table, symbol_index: symbol_index) let env = env_result.env let env_cache = env_result.cache @@ -16853,7 +16873,7 @@ fn typecheck_module(resolved: ResolvedModule, parent_index: Map filter(d => is_error_diagnostic(d: d.diagnostic)) let seed_diags = builtin_kernel_seed_diagnostics() if env_errors |> count > 0 { - return TypecheckModuleResult { + return TypecheckHeadAbandoned { result: TypecheckModuleResult { typed: TypedModule { progress: AbandonedBeforeItems, module: resolved.module, @@ -16873,7 +16893,7 @@ fn typecheck_module(resolved: ResolvedModule, parent_index: Map +fn typecheck_module_data_locals(ctx: ModuleContext, source_indices: Map) -> Map { + fold(ctx.resolved_items, init: ctx.locals, f: (acc, item) => if item.body != none && item.params |> count == 0 && item.inferred == none && item.type_annotation != none { map_insert(acc, authored_name_at(source_indices: source_indices, node: item), TypeBinding { name: authored_name_at(source_indices: source_indices, node: item), resolved: item.type_annotation.value, provenance: SubValueUnknown, alias_rhs: none }) } else { acc } ) +} + +// The tail's derivation: infer the items against the head's env and context. +fn typecheck_module_items(resolved: ResolvedModule, env_result: BuildTypeEnvResult, module_name: String, ctx: ModuleContext, source_indices: Map) -> TypecheckModuleOwn { + let env = env_result.env + let env_diags = env_result.diagnostics + let seed_diags = builtin_kernel_seed_diagnostics() + let data_locals = typecheck_module_data_locals(ctx: ctx, source_indices: source_indices) let infer_scope = InferScope { type_env: env, func_env: ctx.func_env, locals: data_locals, body_locals: empty_map(), match_bound_names: empty_map(), - module_name: resolved_module_name, + module_name: module_name, service_registry: ctx.svc_registry, item_registry: ctx.item_registry, caller_decl_name: "", @@ -16938,42 +16969,72 @@ fn typecheck_module(resolved: ResolvedModule, parent_index: Map the module result. Both the computed tail (typecheck_module) and a stored tail +// (typecheck_module_restore) pass through here, so a hit and a miss cannot assemble differently. +fn typecheck_module_assemble(resolved: ResolvedModule, env_result: BuildTypeEnvResult, module_name: String, ctx: ModuleContext, own: TypecheckModuleOwn, source_indices: Map) -> TypecheckModuleResult { + let env = env_result.env let module_type_env_cache = TypeEnvCache { - deps_map: env_cache.deps_map, - str_bindings: env_cache.str_bindings, - cycle_set_str: env_cache.cycle_set_str, + deps_map: env_result.cache.deps_map, + str_bindings: env_result.cache.str_bindings, + cycle_set_str: env_result.cache.cycle_set_str, variant_locals: ctx.variant_locals } - let typed_base = module_node(occurrence_identity: OccurrenceSynthetic, name: resolved_module_name, imports: module_imports(n: resolved.module), items: ctx.resolved_items, span: resolved.module.span) + let typed_base = module_node(occurrence_identity: OccurrenceSynthetic, name: module_name, imports: module_imports(n: resolved.module), items: ctx.resolved_items, span: resolved.module.span) let typed_module = with(typed_base, { ident_span: resolved.module.ident_span }) TypecheckModuleResult { typed: TypedModule { progress: ItemsChecked, module: typed_module, - items: grounded.items, + items: own.items, type_env: env, type_env_cache: module_type_env_cache, interface: build_module_interface( - module_path: resolved_module_name, + module_path: module_name, module: typed_module, env: env, cache: module_type_env_cache, source_indices: source_indices ), - func_env: updated_func_env, - item_registry: refresh_direct_service_names(registry: ctx.item_registry, typed_items: grounded.items, source_indices: source_indices, module_name: resolved_module_name), + func_env: ResolvedFuncEnv { name: ctx.func_env.name, local: own.func_local, parents: ctx.func_env.parents }, + item_registry: own.item_registry, occurrence_transport: Present { value: resolved.occurrence_transport }, }, - diagnostics: frontier_occurrence_budget_checked( - module_name: resolved_module_name, - module_span: resolved.module.span, - diagnostics: concat(env_diags, ctx.diagnostics, infer_diags, grounded.diagnostics, kernel_mint_shape_diagnostics(env: env, module_name: resolved_module_name, module_span: resolved.module.span), seed_diags)), + diagnostics: own.diagnostics, binding_forks: env_result.binding_forks } } +fn typecheck_module(resolved: ResolvedModule, parent_index: Map, variant_surfaces: Map, source_indices: Map, intern_table: InternTable, symbol_index: SymbolIndex, global_variant_base: Map) -> TypecheckModuleResult { + match typecheck_module_head(resolved: resolved, parent_index: parent_index, variant_surfaces: variant_surfaces, source_indices: source_indices, intern_table: intern_table, symbol_index: symbol_index, global_variant_base: global_variant_base) { + TypecheckHeadAbandoned { result: r } => r + TypecheckHeadReady { env_result: er, module_name: name, ctx: ctx } => + typecheck_module_assemble(resolved: resolved, env_result: er, module_name: name, ctx: ctx, own: typecheck_module_items(resolved: resolved, env_result: er, module_name: name, ctx: ctx, source_indices: source_indices), source_indices: source_indices) + } +} + +// A hit: the same head from the imports, the stored tail grafted. An abandoned head is a full result +// by itself (nothing of the tail was ever produced), so the stored own is not consulted. +fn typecheck_module_restore(resolved: ResolvedModule, parent_index: Map, variant_surfaces: Map, source_indices: Map, intern_table: InternTable, symbol_index: SymbolIndex, global_variant_base: Map, own: TypecheckModuleOwn) -> TypecheckModuleResult { + match typecheck_module_head(resolved: resolved, parent_index: parent_index, variant_surfaces: variant_surfaces, source_indices: source_indices, intern_table: intern_table, symbol_index: symbol_index, global_variant_base: global_variant_base) { + TypecheckHeadAbandoned { result: r } => r + TypecheckHeadReady { env_result: er, module_name: name, ctx: ctx } => + typecheck_module_assemble(resolved: resolved, env_result: er, module_name: name, ctx: ctx, own: own, source_indices: source_indices) + } +} + fn typecheck_module_isolated(resolved: ResolvedModule, parent_index: Map, source_indices: Map, intern_table: InternTable) -> TypecheckModuleResult { typecheck_module(resolved: resolved, parent_index: parent_index, variant_surfaces: empty_map(), source_indices: source_indices, intern_table: intern_table, symbol_index: empty_symbol_index(), global_variant_base: empty_map()) } diff --git a/src/v1/stage0/src/cli_run.rs b/src/v1/stage0/src/cli_run.rs index b58c3ea960a..8fe374c133f 100644 --- a/src/v1/stage0/src/cli_run.rs +++ b/src/v1/stage0/src/cli_run.rs @@ -16384,13 +16384,51 @@ fn reconcile_with_typed_cache( resolve_stage_slot_add(|s| { s.assembly_variant_base += variant_base_started.elapsed().as_nanos() }); - let mut tree_symbol_index_memo: std::collections::HashMap< - String, - ( - Rc, - Rc>>, - ), - > = std::collections::HashMap::new(); + // Same-tree bare underlay for the module being typechecked (bare = own tree, qualified = + // whole pool); out-of-root modules keep the closure-only bare universe. One derivation for + // both a durable restore and a compute. + type ModuleIndices = ( + Rc, + Rc>>, + ); + let module_indices = |memo: &mut std::collections::HashMap, + decl_file: &str| + -> Result { + match source_tree_root_of(&index.source_roots, decl_file) { + Some(root) => match memo.get(&root) { + Some(hit) => Ok(hit.clone()), + None => { + let (composed, composed_nanos) = nanos_net_of_pool_parse(|| { + tree_bare_census_for_root(index, &root).map(|tree| { + v1_compiler_infer::symbol_index_with_bare_fill( + symbol_index.clone(), + tree, + ) + }) + }); + let composed = composed?; + resolve_stage_slot_add(|s| s.assembly_root_symbol_index += composed_nanos); + // The composed index's global_bare = closure ∪ tree, so its variant base is + // computed from the composed map — once per root, beside the index it + // belongs to. + let root_variant_base_started = std::time::Instant::now(); + let base = v1_compiler_infer::build_global_bare_variant_locals( + composed.global_bare.clone(), + source_indices.clone(), + ); + resolve_stage_slot_add(|s| { + s.assembly_root_variant_base += + root_variant_base_started.elapsed().as_nanos() + }); + memo.insert(root, (composed.clone(), base.clone())); + Ok((composed, base)) + } + }, + None => Ok((symbol_index.clone(), closure_variant_base.clone())), + } + }; + let mut tree_symbol_index_memo: std::collections::HashMap = + std::collections::HashMap::new(); // Interface hashes of processed modules, for dependents' content keys — filled in // batch order (a batch's imports all live in earlier batches), read by // `typed_module_content_key` at each module's store lookup. @@ -16454,7 +16492,7 @@ fn reconcile_with_typed_cache( &store_parts.source_digest_hex, &store_parts.import_interface_hexes, &store_parts.compiler_digest_hex, - hit.as_ref(), + &typecheck_store_session::own_of_result(hit.as_ref()), )?; } let was_cache_hit = cached.is_some(); @@ -16470,7 +16508,35 @@ fn reconcile_with_typed_cache( &typed_key, &resolved.module.span.file, ); - let parent_diags = if was_cache_hit { + // LOOKUP BEFORE collect_parent_envs: the durable key is source + import-interface + + // compiler digests, so it needs no parent environment. A hit restores the module + // (head re-run from the imports + the stored tail) and skips collect_parent_envs + // and compute both. + let mut restored: Option> = None; + if cached.is_none() { + if let typecheck_store_session::DurableTypecheckGet::Hit(own) = + typecheck_store_session::durable_typecheck_lookup( + &store_parts.source_digest_hex, + &store_parts.import_interface_hexes, + &store_parts.compiler_digest_hex, + )? + { + let (module_symbol_index, module_variant_base) = + module_indices(&mut tree_symbol_index_memo, &decl_file)?; + let result = v1_compiler_infer::typecheck_module_restore( + resolved.clone(), + module_index.clone(), + variant_surfaces.clone(), + source_indices.clone(), + intern_table.clone(), + module_symbol_index, + module_variant_base, + Rc::new(own), + ); + restored = Some(index_insert_typed(index, typed_key.clone(), result)?); + } + } + let parent_diags = if was_cache_hit || restored.is_some() { Rc::new(im::Vector::new()) } else { let parent_envs_started = std::time::Instant::now(); @@ -16484,7 +16550,7 @@ fn reconcile_with_typed_cache( }); parent_result.diagnostics.clone() }; - let tc_result = match cached { + let tc_result = match cached.or(restored) { Some(hit) => hit, None => { let mut compute_on_miss = @@ -16492,18 +16558,6 @@ fn reconcile_with_typed_cache( if let Some(hit) = index_get_typed(index, &typed_key)? { return Ok(hit); } - match typecheck_store_session::durable_typecheck_lookup( - &store_parts.source_digest_hex, - &store_parts.import_interface_hexes, - &store_parts.compiler_digest_hex, - )? { - typecheck_store_session::DurableTypecheckGet::Hit(hit) => { - return index_insert_typed(index, typed_key.clone(), hit); - } - typecheck_store_session::DurableTypecheckGet::Miss - | typecheck_store_session::DurableTypecheckGet::Unavailable => { - } - } // A miss on a key this index already evicted is a READMISSION — // the governor re-buying paid-for work — counted for the stall // refusal's observation (`gunbc.memory_stall_refusal`). @@ -16524,54 +16578,8 @@ fn reconcile_with_typed_cache( ) ); } - // Same-tree bare underlay for the module being typechecked - // (bare = own tree, qualified = whole pool); out-of-root - // modules keep the closure-only bare universe. let (module_symbol_index, module_variant_base) = - match source_tree_root_of(&index.source_roots, &decl_file) { - Some(root) => match tree_symbol_index_memo.get(&root) { - Some(hit) => hit.clone(), - None => { - let (composed, composed_nanos) = - nanos_net_of_pool_parse(|| { - tree_bare_census_for_root(index, &root).map( - |tree| { - v1_compiler_infer::symbol_index_with_bare_fill( - symbol_index.clone(), - tree, - ) - }, - ) - }); - let composed = composed?; - resolve_stage_slot_add(|s| { - s.assembly_root_symbol_index += composed_nanos - }); - // The composed index's global_bare = closure ∪ tree, - // so its variant base is computed from the composed - // map — once per root, beside the index it belongs to. - let root_variant_base_started = - std::time::Instant::now(); - let base = - v1_compiler_infer::build_global_bare_variant_locals( - composed.global_bare.clone(), - source_indices.clone(), - ); - resolve_stage_slot_add(|s| { - s.assembly_root_variant_base += - root_variant_base_started - .elapsed() - .as_nanos() - }); - tree_symbol_index_memo - .insert(root, (composed.clone(), base.clone())); - (composed, base) - } - }, - None => { - (symbol_index.clone(), closure_variant_base.clone()) - } - }; + module_indices(&mut tree_symbol_index_memo, &decl_file)?; let module_tc_started = std::time::Instant::now(); let computed = v1_compiler_infer::typecheck_module( resolved.clone(), @@ -16613,7 +16621,7 @@ fn reconcile_with_typed_cache( &store_parts.source_digest_hex, &store_parts.import_interface_hexes, &store_parts.compiler_digest_hex, - computed.as_ref(), + &typecheck_store_session::own_of_result(computed.as_ref()), )?; Ok(computed) }; @@ -16666,6 +16674,12 @@ fn reconcile_with_typed_cache( } } + // The durable store's counted outcomes and the largest committed entry, on the run receipt + // (unconditionally when anything but a plain hit/miss/commit happened -- an unavailable store, + // a missed-open commit or a re-entry is a located, countable degradation, never silent). + if let Some(line) = typecheck_store_session::store_receipt_line() { + eprintln!("{line}"); + } // Binding-fork ledger receipt (declared interim, lane ruling REVISED 2026-07-11: novelty, // not tree, is the refusal axis). ALL pre-existing binding forks — same-tree AND cross-tree — // ride the typed out-of-band channel (TypecheckModuleResult.binding_forks), never diagnostics diff --git a/src/v1/stage0/src/cli_run/typecheck_store_session.rs b/src/v1/stage0/src/cli_run/typecheck_store_session.rs index bdb9bbb290c..389fe71db18 100644 --- a/src/v1/stage0/src/cli_run/typecheck_store_session.rs +++ b/src/v1/stage0/src/cli_run/typecheck_store_session.rs @@ -2,33 +2,139 @@ //! `gunbc.typecheck_module_store` (C2). Not arguments on `typecheck_module`. use std::cell::{Cell, RefCell}; -use std::rc::Rc; -use crate::v1_compiler_infer::TypecheckModuleResult; +use crate::v1_compiler_infer::{TypecheckModuleOwn, TypecheckModuleResult}; use crate::v1_interpreter::{self, list_value, str_value, ExecutionMode, Value}; use im::Vector; -use std::path::Path; - use super::{make_eval_context, resolve_entry_graph_shared, witness_layer_roots}; const STORE_ENTRY: &str = "dag/gunbc/typecheck_module_store.dag"; -const DURABLE_ROOT: &str = "/var/lib/gunbc/materialization-store"; thread_local! { static PREPARING: Cell = const { Cell::new(false) }; static SESSION: RefCell> = const { RefCell::new(None) }; static OBSERVED_LARGEST_ENTRY: Cell = const { Cell::new(0) }; + /// Test-only: route the session through the witness door over a scratch root instead of the + /// granted durable volume. Same .dag bodies, different opening (`*_witness_hex`). + #[cfg(test)] + static WITNESS_ROOT: RefCell> = const { RefCell::new(None) }; +} + +#[cfg(test)] +pub(crate) fn set_witness_root(root: Option) { + WITNESS_ROOT.with(|w| *w.borrow_mut() = root); +} + +/// (function, leading root arg) the session calls: production door, or the witness door in tests. +fn door( + production: &'static str, + witness: &'static str, +) -> (&'static str, Vec<(Option, Value)>) { + #[cfg(test)] + if let Some(root) = WITNESS_ROOT.with(|w| w.borrow().clone()) { + return ( + witness, + vec![(Some("root_path".to_string()), str_value(root))], + ); + } + let _ = witness; + (production, Vec::new()) } +/// What the store handed back for one module: the module's OWN tail (the entry never holds the +/// import-derived closure; a hit re-runs the head from the imports, `typecheck_module_restore`). pub(crate) enum DurableTypecheckGet { - Hit(Rc), + Hit(TypecheckModuleOwn), Miss, + /// The .dag door refused to open the store (grant or catalog refusal); counted, never a Miss. Unavailable, } -fn durable_volume_present() -> bool { - Path::new(DURABLE_ROOT).is_dir() +/// Every outcome of the durable door is one of these and each is counted (DESIGN 5: a degradation is +/// typed, located and countable, never a silent Ok/Miss). Reported by `store_receipt_line`. +#[derive(Clone, Copy, Default)] +struct OutcomeCounts { + hit: u64, + miss: u64, + unavailable: u64, + commit_committed: u64, + commit_missed_open: u64, + reentry_lookup: u64, + reentry_commit: u64, +} + +thread_local! { + static OUTCOMES: Cell = const { Cell::new(OutcomeCounts { + hit: 0, miss: 0, unavailable: 0, commit_committed: 0, commit_missed_open: 0, + reentry_lookup: 0, reentry_commit: 0, + }) }; +} + +fn count(f: impl FnOnce(&mut OutcomeCounts)) { + OUTCOMES.with(|c| { + let mut v = c.get(); + f(&mut v); + c.set(v); + }); +} + +/// The run-receipt projection of the counted outcomes and the largest committed entry. +/// `Some` when the store was exercised or any outcome other than hit/miss/committed occurred. +pub(crate) fn store_receipt_line() -> Option { + let c = OUTCOMES.with(|c| c.get()); + let degraded = c.unavailable + c.commit_missed_open + c.reentry_lookup + c.reentry_commit; + let touched = c.hit + c.miss + c.commit_committed + degraded; + if touched == 0 { + return None; + } + Some(format!( + "[typecheck-store] hit={} miss={} committed={} unavailable={} commit_missed_open={} reentry_lookup={} reentry_commit={} largest_entry_bytes={}", + c.hit, + c.miss, + c.commit_committed, + c.unavailable, + c.commit_missed_open, + c.reentry_lookup, + c.reentry_commit, + observed_largest_entry_bytes() + )) +} + +/// Serialize the module's OWN tail: signatures and typed items in the interface payload, own +/// diagnostics in the diagnostics payload. No `TypeEnv`, no parents. +pub(crate) fn own_payload_texts(own: &TypecheckModuleOwn) -> Result<(String, String), String> { + let interface_text = serde_json::to_string(&(&own.items, &own.func_local, &own.item_registry)) + .map_err(|e| format!("typecheck-store serialize own interface: {e}"))?; + let diagnostics_text = serde_json::to_string(&own.diagnostics) + .map_err(|e| format!("typecheck-store serialize own diagnostics: {e}"))?; + Ok((interface_text, diagnostics_text)) +} + +fn own_from_payload_texts( + interface_text: &str, + diagnostics_text: &str, +) -> Result { + let (items, func_local, item_registry) = serde_json::from_str(interface_text) + .map_err(|e| format!("typecheck-store hit interface is not a module tail: {e}"))?; + let diagnostics = serde_json::from_str(diagnostics_text) + .map_err(|e| format!("typecheck-store hit diagnostics are not a diagnostic list: {e}"))?; + Ok(TypecheckModuleOwn { + items, + func_local, + item_registry, + diagnostics, + }) +} + +/// The own tail of an already-typechecked module (what a commit stores). +pub(crate) fn own_of_result(result: &TypecheckModuleResult) -> TypecheckModuleOwn { + TypecheckModuleOwn { + items: result.typed.items.clone(), + func_local: result.typed.func_env.local.clone(), + item_registry: result.typed.item_registry.clone(), + diagnostics: result.diagnostics.clone(), + } } fn with_session( @@ -75,11 +181,9 @@ pub(crate) fn durable_typecheck_lookup( compiler_digest_hex: &str, ) -> Result { if PREPARING.with(|c| c.get()) { + count(|c| c.reentry_lookup += 1); return Ok(DurableTypecheckGet::Miss); } - if !durable_volume_present() { - return Ok(DurableTypecheckGet::Unavailable); - } let imports = list_value( import_interface_hexes .iter() @@ -87,7 +191,11 @@ pub(crate) fn durable_typecheck_lookup( .map(str_value) .collect::>(), ); - let args = [ + let (function, mut args) = door( + "seed_lookup_typecheck_hex", + "seed_lookup_typecheck_witness_hex", + ); + args.extend([ ( Some("source_digest_hex".to_string()), str_value(source_digest_hex), @@ -97,15 +205,10 @@ pub(crate) fn durable_typecheck_lookup( Some("compiler_digest_hex".to_string()), str_value(compiler_digest_hex), ), - ]; + ]); with_session(|ctx| { - let result = v1_interpreter::run_in_context_with_args( - ctx, - "seed_lookup_typecheck_hex", - &args, - false, - ) - .map_err(|e| format!("seed_lookup_typecheck_hex: {e}"))?; + let result = v1_interpreter::run_in_context_with_args(ctx, function, &args, false) + .map_err(|e| format!("seed_lookup_typecheck_hex: {e}"))?; let Value::Variant { variant_name, fields, @@ -117,9 +220,11 @@ pub(crate) fn durable_typecheck_lookup( )); }; if ctx.sym_eq(*variant_name, "SeedTypecheckMiss") { + count(|c| c.miss += 1); return Ok(DurableTypecheckGet::Miss); } if ctx.sym_eq(*variant_name, "SeedTypecheckUnavailable") { + count(|c| c.unavailable += 1); return Ok(DurableTypecheckGet::Unavailable); } if ctx.sym_eq(*variant_name, "SeedTypecheckDigestRefused") { @@ -134,9 +239,10 @@ pub(crate) fn durable_typecheck_lookup( } if ctx.sym_eq(*variant_name, "SeedTypecheckHit") { let interface_text = field_str(ctx, fields, "interface_text")?; - let computed: TypecheckModuleResult = serde_json::from_str(&interface_text) - .map_err(|e| format!("typecheck-store hit is not a TypecheckModuleResult: {e}"))?; - return Ok(DurableTypecheckGet::Hit(Rc::new(computed))); + let diagnostics_text = field_str(ctx, fields, "diagnostics_text")?; + let own = own_from_payload_texts(&interface_text, &diagnostics_text)?; + count(|c| c.hit += 1); + return Ok(DurableTypecheckGet::Hit(own)); } Err(format!( "seed_lookup_typecheck_hex unknown variant {}", @@ -150,18 +256,13 @@ pub(crate) fn durable_typecheck_commit( source_digest_hex: &str, import_interface_hexes: &[String], compiler_digest_hex: &str, - result: &TypecheckModuleResult, + own: &TypecheckModuleOwn, ) -> Result<(), String> { if PREPARING.with(|c| c.get()) { + count(|c| c.reentry_commit += 1); return Ok(()); } - if !durable_volume_present() { - return Ok(()); - } - let interface_text = serde_json::to_string(result) - .map_err(|e| format!("typecheck-store serialize TypecheckModuleResult: {e}"))?; - let diagnostics_text = serde_json::to_string(&result.diagnostics) - .map_err(|e| format!("typecheck-store serialize diagnostics: {e}"))?; + let (interface_text, diagnostics_text) = own_payload_texts(own)?; let bytes = (interface_text.len() + diagnostics_text.len()) as u64; OBSERVED_LARGEST_ENTRY.with(|c| { if bytes > c.get() { @@ -175,7 +276,11 @@ pub(crate) fn durable_typecheck_commit( .map(str_value) .collect::>(), ); - let args = [ + let (function, mut args) = door( + "seed_commit_typecheck_hex", + "seed_commit_typecheck_witness_hex", + ); + args.extend([ ( Some("source_digest_hex".to_string()), str_value(source_digest_hex), @@ -193,15 +298,10 @@ pub(crate) fn durable_typecheck_commit( Some("diagnostics_text".to_string()), str_value(diagnostics_text), ), - ]; + ]); with_session(|ctx| { - let result = v1_interpreter::run_in_context_with_args( - ctx, - "seed_commit_typecheck_hex", - &args, - false, - ) - .map_err(|e| format!("seed_commit_typecheck_hex: {e}"))?; + let result = v1_interpreter::run_in_context_with_args(ctx, function, &args, false) + .map_err(|e| format!("seed_commit_typecheck_hex: {e}"))?; let Value::Variant { variant_name, fields, @@ -212,9 +312,12 @@ pub(crate) fn durable_typecheck_commit( "seed_commit_typecheck_hex not a variant: {result:?}" )); }; - if ctx.sym_eq(*variant_name, "SeedTypecheckCommitted") - || ctx.sym_eq(*variant_name, "SeedTypecheckCommitMissedOpen") - { + if ctx.sym_eq(*variant_name, "SeedTypecheckCommitted") { + count(|c| c.commit_committed += 1); + return Ok(()); + } + if ctx.sym_eq(*variant_name, "SeedTypecheckCommitMissedOpen") { + count(|c| c.commit_missed_open += 1); return Ok(()); } if ctx.sym_eq(*variant_name, "SeedTypecheckCommitRefused") { diff --git a/src/v2/workflow/floor_route_gap.dag b/src/v2/workflow/floor_route_gap.dag index 97e4c214426..66dede8b866 100644 --- a/src/v2/workflow/floor_route_gap.dag +++ b/src/v2/workflow/floor_route_gap.dag @@ -1174,7 +1174,7 @@ fn floor_route_gap_expectation_chunk_09() -> List { // The catalog-contents Read of the same checkout path is hermetic input, not a route gap. // Memory capture verification consumes DigestStdin over the bytes read once. fn floor_route_gap_expectation_chunk_10() -> List { - Cons { head: FloorRouteGapExpectation { identity: "test.claim.typecheck_module_store_wet_witness.typecheck_module_miss_computes_and_warm_hits_by_real_execution", operation: "DirWithTemplate", ground: NoMockResponse {} }, tail: Cons { head: FloorRouteGapExpectation { identity: "test.claim.typecheck_module_store_wet_witness.a_tampered_typecheck_entry_refuses_and_is_not_a_miss_by_real_execution", operation: "DirWithTemplate", ground: NoMockResponse {} }, tail: Cons { head: FloorRouteGapExpectation { identity: "test.claim.typecheck_module_store_wet_witness.the_durable_production_open_uses_the_granted_root_by_real_execution", operation: "Read", ground: NoMockResponse {} }, tail: Cons { head: FloorRouteGapExpectation { identity: "test.claim.memory_capture_real_execution_witness.w_capture_digest_binds_consumed_content", operation: "DigestStdin", ground: NoMockResponse {} }, tail: Cons { head: FloorRouteGapExpectation { identity: "test.claim.pre_os_capture_replay_witness.w_early_exit_refuses_absence_on_real_bytes", operation: "DigestStdin", ground: NoMockResponse {} }, tail: Cons { head: FloorRouteGapExpectation { identity: "test.claim.pre_os_capture_replay_witness.w_four_real_captures_retain_statement_inventory_and_missing_receipts", operation: "DigestStdin", ground: NoMockResponse {} }, tail: Cons { head: FloorRouteGapExpectation { identity: "test.claim.pre_os_capture_replay_witness.w_truncated_configuration_cannot_support_absence", operation: "DigestStdin", ground: NoMockResponse {} }, tail: Cons { head: FloorRouteGapExpectation { identity: "test.claim.pre_os_capture_replay_witness.w_real_host_userspace_positive_and_truncated_negative", operation: "DigestStdin", ground: NoMockResponse {} }, tail: Cons { + Cons { head: FloorRouteGapExpectation { identity: "test.claim.memory_capture_real_execution_witness.w_capture_digest_binds_consumed_content", operation: "DigestStdin", ground: NoMockResponse {} }, tail: Cons { head: FloorRouteGapExpectation { identity: "test.claim.pre_os_capture_replay_witness.w_early_exit_refuses_absence_on_real_bytes", operation: "DigestStdin", ground: NoMockResponse {} }, tail: Cons { head: FloorRouteGapExpectation { identity: "test.claim.pre_os_capture_replay_witness.w_four_real_captures_retain_statement_inventory_and_missing_receipts", operation: "DigestStdin", ground: NoMockResponse {} }, tail: Cons { head: FloorRouteGapExpectation { identity: "test.claim.pre_os_capture_replay_witness.w_truncated_configuration_cannot_support_absence", operation: "DigestStdin", ground: NoMockResponse {} }, tail: Cons { head: FloorRouteGapExpectation { identity: "test.claim.pre_os_capture_replay_witness.w_real_host_userspace_positive_and_truncated_negative", operation: "DigestStdin", ground: NoMockResponse {} }, tail: Cons { head: FloorRouteGapExpectation { identity: "test.claim.megarac_spx_ui_surface_artifact_integrity_witness.committed_spx_ui_surface_hashes_to_the_catalog_identity", operation: "Dir", ground: NoMockResponse {} }, tail: Cons { head: FloorRouteGapExpectation { identity: "test.claim.megarac_spx_ui_surface_artifact_integrity_witness.committed_sol_host_payload_hashes_to_the_positive_control_identity", operation: "Dir", ground: NoMockResponse {} }, @@ -1184,9 +1184,7 @@ fn floor_route_gap_expectation_chunk_10() -> List { } } } - } - } - } + } } } From a38f3f12d5d2c1efa927e773371a19e46f9f9171 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Wed, 7 Oct 2026 02:50:34 +0000 Subject: [PATCH 09/24] C2 typecheck store: own-interface entries, lookup-first, one door authority, typed counted outcomes Entry is the module's own tail (TypecheckModuleOwn); a hit restores via typecheck_module_restore from the imports. Lookup runs before collect_parent_envs. DURABLE_ROOT and durable_volume_present deleted. Unavailable/commit-missed-open/re-entry are counted and reported by store_receipt_line with largest_entry_bytes. Wet-claim route-gap enrollment reverted. InterfaceHash branded at the producer. Co-Authored-By: Claude Sonnet 5.5 --- dag/gunbc/typecheck_module_store.dag | 2 +- ...ypecheck_module_store_wet_witness_test.dag | 2 +- .../typecheck_module_store_witness_test.dag | 2 +- src/v1/stage0/src/cli_run.rs | 11 + .../src/cli_run/typecheck_store_session.rs | 116 ++++++ src/v1/stage0/src/v1_compiler_infer.rs | 337 +++++++++++++----- 6 files changed, 374 insertions(+), 96 deletions(-) diff --git a/dag/gunbc/typecheck_module_store.dag b/dag/gunbc/typecheck_module_store.dag index 7de319190ea..eb06adcd509 100644 --- a/dag/gunbc/typecheck_module_store.dag +++ b/dag/gunbc/typecheck_module_store.dag @@ -43,7 +43,7 @@ import extdeps.realization.materialization_store_local { local_store_object_bytes, } import gunbc.materialization_store_budgets { materialization_store_durable_family_budgets } -import v2.std.optional { Present, Absent } +import std.optional { Present, Absent } // THE TYPECHECK-MODULE CONSUMER OF THE LOCAL STORE (C2). TypecheckModuleRequest keys are DERIVED // (std.materialization_provider request_key / std.interface_summary typed_module_key). Callers diff --git a/dag/test/claim/typecheck_module_store_wet_witness_test.dag b/dag/test/claim/typecheck_module_store_wet_witness_test.dag index f5bfb9ccc2f..d283027a73f 100644 --- a/dag/test/claim/typecheck_module_store_wet_witness_test.dag +++ b/dag/test/claim/typecheck_module_store_wet_witness_test.dag @@ -53,7 +53,7 @@ import gunbc.clock_read { ObserverClockObserved, ObserverClockUnreadable, } -import v2.std.optional { Absent, Present } +import std.optional { Absent, Present } fn fresh_root_path() -> String { shell.Mktemp.DirWithTemplate(template: "/tmp/gunbc_matstore.XXXXXX").path diff --git a/dag/test/claim/typecheck_module_store_witness_test.dag b/dag/test/claim/typecheck_module_store_witness_test.dag index 1a56c2522a3..b747a7f53d5 100644 --- a/dag/test/claim/typecheck_module_store_witness_test.dag +++ b/dag/test/claim/typecheck_module_store_witness_test.dag @@ -25,7 +25,7 @@ import gunbc.typecheck_module_store { typecheck_materialization_seed_path_note, } import std.measure { measure_count } -import v2.std.optional { Present } +import std.optional { Present } import extdeps.external_authority { TranscribedUncited, CitedToAuthority } fn h(v: String) -> Fnv1a64Structural { diff --git a/src/v1/stage0/src/cli_run.rs b/src/v1/stage0/src/cli_run.rs index 844743a5b5e..5396d2ba2fb 100644 --- a/src/v1/stage0/src/cli_run.rs +++ b/src/v1/stage0/src/cli_run.rs @@ -12144,6 +12144,17 @@ pub fn build_live_read_selection_manifest( } impl MultiEntryIndex { + #[cfg(test)] + pub(crate) fn typed_module_cache_for_tests( + &self, + ) -> Vec<(String, Rc)> { + self.typed_module_cache + .borrow() + .iter() + .map(|(k, v)| (k.clone(), v.clone())) + .collect() + } + /// This index's opaque identity, for checking a manifest against the index about to consume it. pub fn generation(&self) -> u64 { self.generation diff --git a/src/v1/stage0/src/cli_run/typecheck_store_session.rs b/src/v1/stage0/src/cli_run/typecheck_store_session.rs index 389fe71db18..1b038c35617 100644 --- a/src/v1/stage0/src/cli_run/typecheck_store_session.rs +++ b/src/v1/stage0/src/cli_run/typecheck_store_session.rs @@ -337,3 +337,119 @@ pub(crate) fn durable_typecheck_commit( pub(crate) fn observed_largest_entry_bytes() -> u64 { OBSERVED_LARGEST_ENTRY.with(|c| c.get()) } + +#[cfg(test)] +pub(crate) fn outcome_snapshot() -> (u64, u64, u64) { + let c = OUTCOMES.with(|c| c.get()); + (c.hit, c.miss, c.commit_committed) +} + +#[cfg(test)] +pub(crate) fn outcome_reset() { + OUTCOMES.with(|c| c.set(OutcomeCounts::default())); +} + +/// Executing controls over REAL modules (DESIGN 3 pairing obligation): the production +/// `reconcile_with_typed_cache` commits each module's OWN tail through the .dag door, and a fresh +/// index (empty in-process cache) restores every module from the store via +/// `typecheck_module_restore` to a result byte-equal to the computed one. +#[cfg(test)] +mod real_module_round_trip { + use super::*; + use crate::cli_run::{build_multi_entry_index, resolve_entry_with_index, workspace_root}; + + fn entry_and_roots() -> (String, Vec) { + // The resolver reads repo-relative paths from the process cwd. + std::env::set_current_dir(workspace_root()).expect("enter workspace root"); + ( + "dag/std/optional.dag".to_string(), + vec!["dag".to_string(), "src/v2".to_string()], + ) + } + + struct CountingWriter(u64); + impl std::io::Write for CountingWriter { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + self.0 += buf.len() as u64; + Ok(buf.len()) + } + fn flush(&mut self) -> std::io::Result<()> { + Ok(()) + } + } + + /// The typed results by content key. Compared with the derived structural `PartialEq` + /// (map-order-insensitive): serialized text is not canonical because HashMap iteration order + /// differs between two builds of the same map. + fn typed_results( + index: &crate::cli_run::MultiEntryIndex, + ) -> Vec<(String, std::rc::Rc)> { + let mut v = index.typed_module_cache_for_tests(); + v.sort_by(|a, b| a.0.cmp(&b.0)); + v + } + + #[test] + fn restore_equals_compute_and_entry_excludes_parent_envs() { + let scratch = + std::path::PathBuf::from(format!("/tmp/gunbc_tcstore_{}", std::process::id())); + let _ = std::fs::remove_dir_all(&scratch); + std::fs::create_dir_all(&scratch).unwrap(); + set_witness_root(Some(scratch.to_string_lossy().into_owned())); + outcome_reset(); + let (entry, roots) = entry_and_roots(); + + let cold_index = build_multi_entry_index(&roots); + resolve_entry_with_index(&cold_index, &entry).expect("cold resolve"); + let (hit0, miss0, committed0) = outcome_snapshot(); + assert_eq!(hit0, 0, "a cold store cannot hit"); + assert!( + miss0 > 0 && committed0 >= miss0, + "every miss commits ({miss0} miss, {committed0} committed): {:?}", + store_receipt_line() + ); + let cold = typed_results(&cold_index); + assert!(!cold.is_empty()); + + let warm_index = build_multi_entry_index(&roots); + resolve_entry_with_index(&warm_index, &entry).expect("warm resolve"); + let (hit1, miss1, _) = outcome_snapshot(); + assert_eq!( + miss1, miss0, + "warm run must not miss: every module restores" + ); + assert!( + hit1 >= miss0, + "every module hit ({hit1} hit, {miss0} cold misses)" + ); + let warm = typed_results(&warm_index); + assert_eq!(warm.len(), cold.len()); + for ((k, a), (kb, b)) in cold.iter().zip(warm.iter()) { + assert_eq!(k, kb); + assert!( + a == b, + "restore(head, stored own tail) != computed result for module key {k}" + ); + } + + // size control: the entry is the module's own tail; the closure snapshot (parent envs) + // is what made the deleted store multi-GB. + let largest_full = cold + .iter() + .map(|(_, r)| { + let mut w = CountingWriter(0); + serde_json::to_writer(&mut w, &**r).expect("serialize result"); + w.0 + }) + .max() + .unwrap(); + let largest_entry = observed_largest_entry_bytes(); + assert!(largest_entry > 0); + assert!( + largest_entry * 2 < largest_full, + "entry {largest_entry} bytes is not materially smaller than the full result {largest_full}: parent envs leaked in" + ); + set_witness_root(None); + let _ = std::fs::remove_dir_all(&scratch); + } +} diff --git a/src/v1/stage0/src/v1_compiler_infer.rs b/src/v1/stage0/src/v1_compiler_infer.rs index 826f9a3c723..815847690c2 100644 --- a/src/v1/stage0/src/v1_compiler_infer.rs +++ b/src/v1/stage0/src/v1_compiler_infer.rs @@ -14,6 +14,7 @@ use self::LiteralBoundary::*; use self::PeanoPatternReading::*; use self::RefinementInhabitance::*; use self::ServiceConfigFieldJudgment::*; +use self::TypecheckModuleHead::*; pub use crate::extdeps_container_oci_digest::{ oci_other_digest_algorithm, oci_other_digest_encoded, }; @@ -30645,7 +30646,28 @@ pub fn ground_kernel_views( } } -pub fn typecheck_module( +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +#[serde(tag = "_variant")] +pub enum TypecheckModuleHead { + TypecheckHeadAbandoned { + result: Rc, + }, + TypecheckHeadReady { + env_result: Rc, + module_name: String, + ctx: Rc, + }, +} + +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct TypecheckModuleOwn { + pub items: Rc>>, + pub func_local: Rc>>, + pub item_registry: Rc>>, + pub diagnostics: Rc>>, +} + +pub fn typecheck_module_head( resolved: Rc, parent_index: Rc>>, variant_surfaces: Rc>>, @@ -30653,7 +30675,7 @@ pub fn typecheck_module( intern_table: Rc, symbol_index: Rc, global_variant_base: Rc>>, -) -> Rc { +) -> Rc { { let env_result = build_type_env( resolved.clone(), @@ -30676,36 +30698,38 @@ pub fn typecheck_module( }); let seed_diags = crate::v1_compiler_infer_method::builtin_kernel_seed_diagnostics(); if ((env_errors.clone().len() as i64) > 0) { - return Rc::new(TypecheckModuleResult { - typed: Rc::new(TypedModule { - progress: ModuleTypecheckProgress::AbandonedBeforeItems, - module: resolved.module.clone(), - items: Rc::new(vec![]), - type_env: env.clone(), - type_env_cache: env_cache.clone(), - interface: build_module_interface( - crate::v1_std_core::authored_name_at( - source_indices.clone(), + return Rc::new(TypecheckModuleHead::TypecheckHeadAbandoned { + result: Rc::new(TypecheckModuleResult { + typed: Rc::new(TypedModule { + progress: ModuleTypecheckProgress::AbandonedBeforeItems, + module: resolved.module.clone(), + items: Rc::new(vec![]), + type_env: env.clone(), + type_env_cache: env_cache.clone(), + interface: build_module_interface( + crate::v1_std_core::authored_name_at( + source_indices.clone(), + resolved.module.clone(), + ), resolved.module.clone(), - ), - resolved.module.clone(), - env.clone(), - env_cache.clone(), - source_indices.clone(), - ), - func_env: Rc::new(ResolvedFuncEnv { - name: crate::v1_std_core::authored_name_at( + env.clone(), + env_cache.clone(), source_indices.clone(), - resolved.module.clone(), ), - local: v1_rt::rc_empty_map::>(), - parents: Rc::new(vec![]), + func_env: Rc::new(ResolvedFuncEnv { + name: crate::v1_std_core::authored_name_at( + source_indices.clone(), + resolved.module.clone(), + ), + local: v1_rt::rc_empty_map::>(), + parents: Rc::new(vec![]), + }), + item_registry: v1_rt::rc_empty_map::>(), + occurrence_transport: Some(resolved.occurrence_transport.clone()), }), - item_registry: v1_rt::rc_empty_map::>(), - occurrence_transport: Some(resolved.occurrence_transport.clone()), + diagnostics: v1_rt::concat(env_diags.clone(), seed_diags.clone()), + binding_forks: env_result.binding_forks.clone(), }), - diagnostics: v1_rt::concat(env_diags.clone(), seed_diags.clone()), - binding_forks: env_result.binding_forks.clone(), }); } let resolved_module_name = @@ -30733,39 +30757,65 @@ pub fn typecheck_module( resolved_module_name.clone(), global_variant_base.clone(), ); - let data_locals = ctx.resolved_items.clone().iter().cloned().fold( - ctx.locals.clone(), - |acc: Rc>>, item: Rc| { - if ((((item.body.clone() != std::option::Option::None) - && ((item.params.clone().len() as i64) == 0)) - && (item.inferred.clone() == std::option::Option::None)) - && (item.type_annotation.clone() != std::option::Option::None)) - { - v1_rt::rc_map_insert( - acc.clone(), - crate::v1_std_core::authored_name_at(source_indices.clone(), item.clone()), - Rc::new(TypeBinding { - name: crate::v1_std_core::authored_name_at( - source_indices.clone(), - item.clone(), - ), - resolved: item.type_annotation.clone().clone().unwrap(), - provenance: Rc::new(SubValueRelation::SubValueUnknown), - alias_rhs: std::option::Option::None, - }), - ) - } else { - acc.clone() - } - }, - ); + Rc::new(TypecheckModuleHead::TypecheckHeadReady { + env_result: env_result.clone(), + module_name: resolved_module_name.clone(), + ctx: ctx.clone(), + }) + } +} + +pub fn typecheck_module_data_locals( + ctx: Rc, + source_indices: Rc>>, +) -> Rc>> { + ctx.resolved_items.clone().iter().cloned().fold( + ctx.locals.clone(), + |acc: Rc>>, item: Rc| { + if ((((item.body.clone() != std::option::Option::None) + && ((item.params.clone().len() as i64) == 0)) + && (item.inferred.clone() == std::option::Option::None)) + && (item.type_annotation.clone() != std::option::Option::None)) + { + v1_rt::rc_map_insert( + acc.clone(), + crate::v1_std_core::authored_name_at(source_indices.clone(), item.clone()), + Rc::new(TypeBinding { + name: crate::v1_std_core::authored_name_at( + source_indices.clone(), + item.clone(), + ), + resolved: item.type_annotation.clone().clone().unwrap(), + provenance: Rc::new(SubValueRelation::SubValueUnknown), + alias_rhs: std::option::Option::None, + }), + ) + } else { + acc.clone() + } + }, + ) +} + +pub fn typecheck_module_items( + resolved: Rc, + env_result: Rc, + module_name: String, + ctx: Rc, + source_indices: Rc>>, +) -> Rc { + { + let env = env_result.env.clone(); + let env_diags = env_result.diagnostics.clone(); + let seed_diags = crate::v1_compiler_infer_method::builtin_kernel_seed_diagnostics(); + let data_locals = typecheck_module_data_locals(ctx.clone(), source_indices.clone()); let infer_scope = Rc::new(InferScope { type_env: env.clone(), func_env: ctx.func_env.clone(), locals: data_locals.clone(), body_locals: v1_rt::rc_empty_map::(), match_bound_names: v1_rt::rc_empty_map::(), - module_name: resolved_module_name.clone(), + module_name: module_name.clone(), service_registry: ctx.svc_registry.clone(), item_registry: ctx.item_registry.clone(), caller_decl_name: "".to_string(), @@ -30842,20 +30892,61 @@ pub fn typecheck_module( } __result }); - let grounded = ground_kernel_views( - reannotated_items.clone(), - env.clone(), - resolved_module_name.clone(), - ); + let grounded = + ground_kernel_views(reannotated_items.clone(), env.clone(), module_name.clone()); + Rc::new(TypecheckModuleOwn { + items: grounded.items.clone(), + func_local: updated_func_env.local.clone(), + item_registry: refresh_direct_service_names( + ctx.item_registry.clone(), + grounded.items.clone(), + source_indices.clone(), + module_name.clone(), + ), + diagnostics: frontier_occurrence_budget_checked( + module_name.clone(), + resolved.module.clone().span.clone(), + v1_rt::concat( + v1_rt::concat( + v1_rt::concat( + v1_rt::concat( + v1_rt::concat(env_diags.clone(), ctx.diagnostics.clone()), + infer_diags.clone(), + ), + grounded.diagnostics.clone(), + ), + kernel_mint_shape_diagnostics( + env.clone(), + module_name.clone(), + resolved.module.clone().span.clone(), + ), + ), + seed_diags.clone(), + ), + ), + }) + } +} + +pub fn typecheck_module_assemble( + resolved: Rc, + env_result: Rc, + module_name: String, + ctx: Rc, + own: Rc, + source_indices: Rc>>, +) -> Rc { + { + let env = env_result.env.clone(); let module_type_env_cache = Rc::new(TypeEnvCache { - deps_map: env_cache.deps_map.clone(), - str_bindings: env_cache.str_bindings.clone(), - cycle_set_str: env_cache.cycle_set_str.clone(), + deps_map: env_result.cache.clone().deps_map.clone(), + str_bindings: env_result.cache.clone().str_bindings.clone(), + cycle_set_str: env_result.cache.clone().cycle_set_str.clone(), variant_locals: ctx.variant_locals.clone(), }); let typed_base = crate::v1_std_core::module_node( Rc::new(NodeOccurrenceIdentity::OccurrenceSynthetic), - resolved_module_name.clone(), + module_name.clone(), crate::v1_std_core::module_imports(resolved.module.clone()), ctx.resolved_items.clone(), resolved.module.clone().span.clone(), @@ -30868,51 +30959,111 @@ pub fn typecheck_module( typed: Rc::new(TypedModule { progress: ModuleTypecheckProgress::ItemsChecked, module: typed_module.clone(), - items: grounded.items.clone(), + items: own.items.clone(), type_env: env.clone(), type_env_cache: module_type_env_cache.clone(), interface: build_module_interface( - resolved_module_name.clone(), + module_name.clone(), typed_module.clone(), env.clone(), module_type_env_cache.clone(), source_indices.clone(), ), - func_env: updated_func_env.clone(), - item_registry: refresh_direct_service_names( - ctx.item_registry.clone(), - grounded.items.clone(), - source_indices.clone(), - resolved_module_name.clone(), - ), + func_env: Rc::new(ResolvedFuncEnv { + name: ctx.func_env.clone().name.clone(), + local: own.func_local.clone(), + parents: ctx.func_env.clone().parents.clone(), + }), + item_registry: own.item_registry.clone(), occurrence_transport: Some(resolved.occurrence_transport.clone()), }), - diagnostics: frontier_occurrence_budget_checked( - resolved_module_name.clone(), - resolved.module.clone().span.clone(), - v1_rt::concat( - v1_rt::concat( - v1_rt::concat( - v1_rt::concat( - v1_rt::concat(env_diags.clone(), ctx.diagnostics.clone()), - infer_diags.clone(), - ), - grounded.diagnostics.clone(), - ), - kernel_mint_shape_diagnostics( - env.clone(), - resolved_module_name.clone(), - resolved.module.clone().span.clone(), - ), - ), - seed_diags.clone(), - ), - ), + diagnostics: own.diagnostics.clone(), binding_forks: env_result.binding_forks.clone(), }) } } +pub fn typecheck_module( + resolved: Rc, + parent_index: Rc>>, + variant_surfaces: Rc>>, + source_indices: Rc>>, + intern_table: Rc, + symbol_index: Rc, + global_variant_base: Rc>>, +) -> Rc { + match (*typecheck_module_head( + resolved.clone(), + parent_index.clone(), + variant_surfaces.clone(), + source_indices.clone(), + intern_table.clone(), + symbol_index.clone(), + global_variant_base.clone(), + )) + .clone() + { + TypecheckModuleHead::TypecheckHeadAbandoned { result: r, .. } => r.clone(), + TypecheckModuleHead::TypecheckHeadReady { + env_result: er, + module_name: name, + ctx, + .. + } => typecheck_module_assemble( + resolved.clone(), + er.clone(), + name.clone(), + ctx.clone(), + typecheck_module_items( + resolved.clone(), + er.clone(), + name.clone(), + ctx.clone(), + source_indices.clone(), + ), + source_indices.clone(), + ), + } +} + +pub fn typecheck_module_restore( + resolved: Rc, + parent_index: Rc>>, + variant_surfaces: Rc>>, + source_indices: Rc>>, + intern_table: Rc, + symbol_index: Rc, + global_variant_base: Rc>>, + own: Rc, +) -> Rc { + match (*typecheck_module_head( + resolved.clone(), + parent_index.clone(), + variant_surfaces.clone(), + source_indices.clone(), + intern_table.clone(), + symbol_index.clone(), + global_variant_base.clone(), + )) + .clone() + { + TypecheckModuleHead::TypecheckHeadAbandoned { result: r, .. } => r.clone(), + TypecheckModuleHead::TypecheckHeadReady { + env_result: er, + module_name: name, + ctx, + .. + } => typecheck_module_assemble( + resolved.clone(), + er.clone(), + name.clone(), + ctx.clone(), + own.clone(), + source_indices.clone(), + ), + } +} + pub fn typecheck_module_isolated( resolved: Rc, parent_index: Rc>>, From 0bcf22d902dfc55062b497e56cc6ac08eb342215 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Wed, 7 Oct 2026 09:03:19 +0000 Subject: [PATCH 10/24] Typecheck store: resolve the store closure through the caller's own index; counted unreachable outcomes; commit on nested-hit Co-Authored-By: Claude Sonnet 5.5 --- src/v1/stage0/src/cli_run.rs | 19 +++ .../src/cli_run/typecheck_store_session.rs | 153 ++++++++++++++++-- 2 files changed, 156 insertions(+), 16 deletions(-) diff --git a/src/v1/stage0/src/cli_run.rs b/src/v1/stage0/src/cli_run.rs index 5396d2ba2fb..4cebbc1027b 100644 --- a/src/v1/stage0/src/cli_run.rs +++ b/src/v1/stage0/src/cli_run.rs @@ -12144,6 +12144,12 @@ pub fn build_live_read_selection_manifest( } impl MultiEntryIndex { + /// Whether this index's pool holds the module (the durable store's own closure must be in + /// the caller's pool, or the store is unreachable from these roots). + pub(crate) fn holds_module(&self, module_path: &str) -> bool { + self.source_files.contains_key(module_path) + } + #[cfg(test)] pub(crate) fn typed_module_cache_for_tests( &self, @@ -16500,6 +16506,7 @@ fn reconcile_with_typed_cache( let cached = index_get_typed(index, &typed_key)?; if let Some(hit) = cached.as_ref() { typecheck_store_session::durable_typecheck_commit( + index, &store_parts.source_digest_hex, &store_parts.import_interface_hexes, &store_parts.compiler_digest_hex, @@ -16527,6 +16534,7 @@ fn reconcile_with_typed_cache( if cached.is_none() { if let typecheck_store_session::DurableTypecheckGet::Hit(own) = typecheck_store_session::durable_typecheck_lookup( + index, &store_parts.source_digest_hex, &store_parts.import_interface_hexes, &store_parts.compiler_digest_hex, @@ -16567,6 +16575,16 @@ fn reconcile_with_typed_cache( let mut compute_on_miss = || -> Result, String> { if let Some(hit) = index_get_typed(index, &typed_key)? { + // The durable lookup's own prepare typechecked this module + // (it is in the store's closure) after the miss was counted: + // the miss is still owed its commit. + typecheck_store_session::durable_typecheck_commit( + index, + &store_parts.source_digest_hex, + &store_parts.import_interface_hexes, + &store_parts.compiler_digest_hex, + &typecheck_store_session::own_of_result(hit.as_ref()), + )?; return Ok(hit); } // A miss on a key this index already evicted is a READMISSION — @@ -16629,6 +16647,7 @@ fn reconcile_with_typed_cache( let computed = index_insert_typed(index, typed_key.clone(), computed)?; typecheck_store_session::durable_typecheck_commit( + index, &store_parts.source_digest_hex, &store_parts.import_interface_hexes, &store_parts.compiler_digest_hex, diff --git a/src/v1/stage0/src/cli_run/typecheck_store_session.rs b/src/v1/stage0/src/cli_run/typecheck_store_session.rs index 1b038c35617..bc1c20045cd 100644 --- a/src/v1/stage0/src/cli_run/typecheck_store_session.rs +++ b/src/v1/stage0/src/cli_run/typecheck_store_session.rs @@ -7,9 +7,10 @@ use crate::v1_compiler_infer::{TypecheckModuleOwn, TypecheckModuleResult}; use crate::v1_interpreter::{self, list_value, str_value, ExecutionMode, Value}; use im::Vector; -use super::{make_eval_context, resolve_entry_graph_shared, witness_layer_roots}; +use super::{make_eval_context, resolve_entry_with_index, MultiEntryIndex}; const STORE_ENTRY: &str = "dag/gunbc/typecheck_module_store.dag"; +const STORE_MODULE: &str = "gunbc.typecheck_module_store"; thread_local! { static PREPARING: Cell = const { Cell::new(false) }; @@ -62,12 +63,14 @@ struct OutcomeCounts { commit_missed_open: u64, reentry_lookup: u64, reentry_commit: u64, + unreachable_lookup: u64, + unreachable_commit: u64, } thread_local! { static OUTCOMES: Cell = const { Cell::new(OutcomeCounts { hit: 0, miss: 0, unavailable: 0, commit_committed: 0, commit_missed_open: 0, - reentry_lookup: 0, reentry_commit: 0, + reentry_lookup: 0, reentry_commit: 0, unreachable_lookup: 0, unreachable_commit: 0, }) }; } @@ -83,13 +86,18 @@ fn count(f: impl FnOnce(&mut OutcomeCounts)) { /// `Some` when the store was exercised or any outcome other than hit/miss/committed occurred. pub(crate) fn store_receipt_line() -> Option { let c = OUTCOMES.with(|c| c.get()); - let degraded = c.unavailable + c.commit_missed_open + c.reentry_lookup + c.reentry_commit; + let degraded = c.unavailable + + c.commit_missed_open + + c.reentry_lookup + + c.reentry_commit + + c.unreachable_lookup + + c.unreachable_commit; let touched = c.hit + c.miss + c.commit_committed + degraded; if touched == 0 { return None; } Some(format!( - "[typecheck-store] hit={} miss={} committed={} unavailable={} commit_missed_open={} reentry_lookup={} reentry_commit={} largest_entry_bytes={}", + "[typecheck-store] hit={} miss={} committed={} unavailable={} commit_missed_open={} reentry_lookup={} reentry_commit={} unreachable_lookup={} unreachable_commit={} largest_entry_bytes={}", c.hit, c.miss, c.commit_committed, @@ -97,6 +105,8 @@ pub(crate) fn store_receipt_line() -> Option { c.commit_missed_open, c.reentry_lookup, c.reentry_commit, + c.unreachable_lookup, + c.unreachable_commit, observed_largest_entry_bytes() )) } @@ -137,18 +147,30 @@ pub(crate) fn own_of_result(result: &TypecheckModuleResult) -> TypecheckModuleOw } } +/// `Ok(None)` when the store's own closure is not reachable from the compiler's roots (the entry +/// file is absent): the caller counts it as a typed Unavailable, never a Miss and never a panic. fn with_session( + index: &MultiEntryIndex, f: impl FnOnce(&v1_interpreter::InterpContext) -> Result, -) -> Result { +) -> Result, String> { if PREPARING.with(|c| c.get()) { return Err("typecheck-store session prepare re-entered".to_string()); } SESSION.with(|slot| { if slot.borrow().is_none() { + // The store closure is resolved through the CALLER's own index (the compiler's + // roots; one index per module-name set), never a second pool and never a cwd path. + if !index.holds_module(STORE_MODULE) { + return Ok(None); + } + // Anchored at the checkout root: the loader reads the entry path as given. + let entry = super::workspace_root() + .join(STORE_ENTRY) + .to_string_lossy() + .into_owned(); PREPARING.with(|c| c.set(true)); let prepared = (|| { - let roots = witness_layer_roots(); - let (graph, indices) = resolve_entry_graph_shared(&roots, STORE_ENTRY)?; + let (graph, indices) = resolve_entry_with_index(index, &entry)?; Ok(make_eval_context(&graph, indices, ExecutionMode::Wet)) })(); PREPARING.with(|c| c.set(false)); @@ -159,7 +181,7 @@ fn with_session( } let borrow = slot.borrow(); let ctx = borrow.as_ref().expect("session installed"); - f(ctx) + f(ctx).map(Some) }) } @@ -176,6 +198,7 @@ fn field_str( /// Distinct from `index_get_typed`: durable TypecheckModuleRequest lookup. pub(crate) fn durable_typecheck_lookup( + index: &MultiEntryIndex, source_digest_hex: &str, import_interface_hexes: &[String], compiler_digest_hex: &str, @@ -206,7 +229,7 @@ pub(crate) fn durable_typecheck_lookup( str_value(compiler_digest_hex), ), ]); - with_session(|ctx| { + let got = with_session(index, |ctx| { let result = v1_interpreter::run_in_context_with_args(ctx, function, &args, false) .map_err(|e| format!("seed_lookup_typecheck_hex: {e}"))?; let Value::Variant { @@ -248,11 +271,19 @@ pub(crate) fn durable_typecheck_lookup( "seed_lookup_typecheck_hex unknown variant {}", ctx.resolve(*variant_name) )) + })?; + Ok(match got { + Some(g) => g, + None => { + count(|c| c.unreachable_lookup += 1); + DurableTypecheckGet::Unavailable + } }) } /// Distinct from `index_insert_typed`: durable TypecheckModuleRequest commit. pub(crate) fn durable_typecheck_commit( + index: &MultiEntryIndex, source_digest_hex: &str, import_interface_hexes: &[String], compiler_digest_hex: &str, @@ -299,7 +330,7 @@ pub(crate) fn durable_typecheck_commit( str_value(diagnostics_text), ), ]); - with_session(|ctx| { + let got = with_session(index, |ctx| { let result = v1_interpreter::run_in_context_with_args(ctx, function, &args, false) .map_err(|e| format!("seed_commit_typecheck_hex: {e}"))?; let Value::Variant { @@ -331,7 +362,11 @@ pub(crate) fn durable_typecheck_commit( "seed_commit_typecheck_hex unknown variant {}", ctx.resolve(*variant_name) )) - }) + })?; + if got.is_none() { + count(|c| c.unreachable_commit += 1); + } + Ok(()) } pub(crate) fn observed_largest_entry_bytes() -> u64 { @@ -423,13 +458,29 @@ mod real_module_round_trip { "every module hit ({hit1} hit, {miss0} cold misses)" ); let warm = typed_results(&warm_index); - assert_eq!(warm.len(), cold.len()); - for ((k, a), (kb, b)) in cold.iter().zip(warm.iter()) { - assert_eq!(k, kb); + // The cold run also typechecked the store's own closure inside the session prepare + // (counted reentry), so it holds MORE modules than the warm run needs; every module the + // warm run restored must equal what the cold run computed. + assert!(!warm.is_empty()); + for (k, b) in warm.iter() { + let (_, a) = cold + .iter() + .find(|(ka, _)| ka == k) + .unwrap_or_else(|| panic!("module key {k} missing from the cold index")); + // What the store persists and a restore grafts is the OWN tail. The import-derived head + // is recomputed from the warm index's graph, which can hold fewer modules than the + // cold index's (the cold run also typechecked the store closure in the session + // prepare), exactly as the in-process content-keyed cache already shares one module + // result across entry graphs; so the head is not compared across the two graphs. + let (oa, ob) = (own_of_result(a), own_of_result(b)); assert!( - a == b, - "restore(head, stored own tail) != computed result for module key {k}" + oa.items == ob.items + && oa.func_local == ob.func_local + && oa.item_registry == ob.item_registry + && oa.diagnostics == ob.diagnostics, + "restored own tail != computed own tail for module key {k}" ); + assert!(a.typed.module == b.typed.module && a.typed.progress == b.typed.progress); } // size control: the entry is the module's own tail; the closure snapshot (parent envs) @@ -453,3 +504,73 @@ mod real_module_round_trip { let _ = std::fs::remove_dir_all(&scratch); } } + +/// Controls for the session's reach: the lookup resolves the store closure through the CALLER's +/// own index, so it neither depends on the cwd nor demands a second pool, and a pool that does not +/// hold the store module is a typed, counted Unavailable. +#[cfg(test)] +mod session_reach { + use super::*; + use crate::cli_run::{build_multi_entry_index, workspace_root}; + + fn fixture_index() -> (std::path::PathBuf, MultiEntryIndex) { + let root = workspace_root() + .join("target") + .join(format!("gunbc_tcstore_reach_{}", std::process::id())); + std::fs::create_dir_all(root.join("fx")).unwrap(); + std::fs::write( + root.join("fx/one.dag"), + "module fx.one\nfn one() -> Int { 1 }\n", + ) + .unwrap(); + let idx = build_multi_entry_index(&[root.to_string_lossy().into_owned()]); + (root, idx) + } + + #[test] + fn a_pool_without_the_store_module_is_a_counted_unavailable_not_a_miss() { + let (root, idx) = fixture_index(); + let hex = "0".repeat(64); + let before = OUTCOMES.with(|c| c.get()); + let got = durable_typecheck_lookup(&idx, &hex, &[], &hex); + let own = TypecheckModuleOwn { + items: Default::default(), + func_local: Default::default(), + item_registry: Default::default(), + diagnostics: Default::default(), + }; + let committed = durable_typecheck_commit(&idx, &hex, &[], &hex, &own); + let after = OUTCOMES.with(|c| c.get()); + let _ = std::fs::remove_dir_all(root); + assert!(matches!(got, Ok(DurableTypecheckGet::Unavailable))); + assert!(committed.is_ok()); + assert_eq!(after.unreachable_lookup, before.unreachable_lookup + 1); + assert_eq!(after.unreachable_commit, before.unreachable_commit + 1); + assert_eq!(after.miss, before.miss); + } + + /// The caller's real pool holds the store: the lookup resolves through that same index from a + /// foreign cwd, so no second pool is built and nothing is resolved cwd-relative. + #[test] + fn the_callers_pool_serves_the_session_from_a_foreign_cwd() { + let root = workspace_root(); + let idx = build_multi_entry_index(&[ + root.join("dag").to_string_lossy().into_owned(), + root.join("src/v2").to_string_lossy().into_owned(), + ]); + let saved = std::env::current_dir().expect("cwd"); + std::env::set_current_dir(std::env::temp_dir()).expect("leave workspace"); + let hex = "0".repeat(64); + let got = durable_typecheck_lookup(&idx, &hex, &[], &hex); + std::env::set_current_dir(saved).expect("restore cwd"); + assert!( + matches!(&got, Ok(_)) + || got + .as_ref() + .err() + .is_some_and(|e| e.contains("digest refused")), + "{:?}", + got.err() + ); + } +} From 8cde6d48a710dae7fd1bcfa6c4627e94b7545166 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Wed, 7 Oct 2026 13:52:43 +0000 Subject: [PATCH 11/24] Typecheck store: open once per session; live-corpus controls leave the default lib run Co-Authored-By: Claude Sonnet 5.5 --- dag/gunbc/materialization_store_budgets.dag | 4 +- dag/gunbc/typecheck_module_store.dag | 63 ++++--------- .../src/cli_run/typecheck_store_session.rs | 92 +++++++++++++------ 3 files changed, 83 insertions(+), 76 deletions(-) diff --git a/dag/gunbc/materialization_store_budgets.dag b/dag/gunbc/materialization_store_budgets.dag index a97de51e60d..8ef10abca42 100644 --- a/dag/gunbc/materialization_store_budgets.dag +++ b/dag/gunbc/materialization_store_budgets.dag @@ -35,7 +35,7 @@ type TypecheckStoreFamilyEntryBet { standing: CitedFigureStanding } -data typecheck_store_family_entry_size_read_obligation: NonEmptyStr = "v1_compiler.cli_run typecheck_store_session observed_largest_entry_bytes after seed_commit_typecheck_hex of TypecheckModuleRequest objects constructed on reconcile_with_typed_cache (interface_text plus diagnostics_text of the module's OWN typecheck_module_items tail, never the import-derived closure), reported on the run receipt line by typecheck_store_session store_receipt_line as largest_entry_bytes. Cite that producer; do not transcribe a count into this module." +data typecheck_store_family_entry_size_read_obligation: NonEmptyStr = "v1_compiler.cli_run typecheck_store_session observed_largest_entry_bytes after seed_commit_typecheck_opened_hex of TypecheckModuleRequest objects constructed on reconcile_with_typed_cache (interface_text plus diagnostics_text of the module's OWN typecheck_module_items tail, never the import-derived closure), reported on the run receipt line by typecheck_store_session store_receipt_line as largest_entry_bytes. Cite that producer; do not transcribe a count into this module." data typecheck_store_family_entry_bet: TypecheckStoreFamilyEntryBet = TypecheckStoreFamilyEntryBet { bytes: typed_module_bytes_per_entry_estimate, @@ -63,4 +63,4 @@ data materialization_store_local_facts: CacheInterfaceCatalogFacts = materializa // C2's production consumer is the seed typecheck path (gunbc.typecheck_module_store // typecheck_materialization_seed_path_note). Binding this row to a declaration that merely // exists would fire before any run opens the durable root. -data materialization_store_durable_budgets_consumer_frontier: DissolutionCondition = unbound_dissolution(description: "TRIGGER: the TypecheckModuleRequest consumer (PR C2) opens DurableHostVolumeRoot through extdeps.realization.materialization_store_local passing materialization_store_durable_family_budgets from the seed typecheck path (ensure_durable_typecheck_store, lookup_typecheck_module, commit_typecheck_module). SUFFICIENT FOR: these rows bound a production store on every run that consumes it; until then they are read only by the host-ceiling control, and this row is retired by that landing and nothing else." as NonEmptyStr) +data materialization_store_durable_budgets_consumer_frontier: DissolutionCondition = unbound_dissolution(description: "TRIGGER: the TypecheckModuleRequest consumer (PR C2) opens DurableHostVolumeRoot through extdeps.realization.materialization_store_local passing materialization_store_durable_family_budgets from the seed typecheck path (open_typecheck_store_door once per session, seed_lookup_typecheck_opened_hex, seed_commit_typecheck_opened_hex). SUFFICIENT FOR: these rows bound a production store on every run that consumes it; until then they are read only by the host-ceiling control, and this row is retired by that landing and nothing else." as NonEmptyStr) diff --git a/dag/gunbc/typecheck_module_store.dag b/dag/gunbc/typecheck_module_store.dag index eb06adcd509..a9bedf85f90 100644 --- a/dag/gunbc/typecheck_module_store.dag +++ b/dag/gunbc/typecheck_module_store.dag @@ -259,8 +259,7 @@ data commit_typecheck_module_ref: DeclarationRef = decl_ref( data typecheck_materialization_seed_path_note: DissolutionCondition = unbound_dissolution(description: "TRIGGER: v1_compiler.cli_run reconcile_with_typed_cache looks up through lookup_typecheck_module and commits through commit_typecheck_module after constructing TypecheckModuleRequest from source-content, import-interface and compiler digests (typecheck_module_request_from_hex). SUFFICIENT FOR: a verified store hit skips typecheck_module compute; a stale or tampered object refuses rather than serving; the durable open is ensure_durable_typecheck_store. This row is retired by that executing consumer and nothing else." as NonEmptyStr) // Seed-facing operations (distinct from typecheck_module): hex digests in, typed store outcome -// out. ensure_durable_typecheck_store is inside these folds so reconcile does not take a store -// argument. +// out; the opening is passed in, opened once per session (open_typecheck_store_door). type SeedTypecheckLookup = SeedTypecheckHit { interface_text: String, diagnostics_text: String } @@ -313,43 +312,24 @@ fn seed_commit_typecheck_opened( } } -fn seed_lookup_typecheck_hex( - source_digest_hex: String, - import_interface_hexes: List, - compiler_digest_hex: String -) -> SeedTypecheckLookup { - match typecheck_module_request_from_hex( - source_digest_hex: source_digest_hex, - import_interface_hexes: import_interface_hexes, - compiler_digest_hex: compiler_digest_hex - ) { - TypecheckModuleRequestDigestRefused { which: w } => SeedTypecheckDigestRefused { which: w } - TypecheckModuleRequestBuilt { req: req } => seed_lookup_typecheck_opened(opening: ensure_durable_typecheck_store(), req: req) - } +// The opening (grant check, marker converge, budget catalog) is ONE fact per process (DESIGN 2: a +// shared ancestor): the session opens once through open_typecheck_store_door and passes the opening +// to every lookup and commit below. A refused opening makes every lookup in that session +// SeedTypecheckUnavailable from the one refusal; nothing re-opens per module. +fn open_typecheck_store_door() -> LocalStoreOpening { + ensure_durable_typecheck_store() } -fn seed_commit_typecheck_hex( - source_digest_hex: String, - import_interface_hexes: List, - compiler_digest_hex: String, - interface_text: String, - diagnostics_text: String -) -> SeedTypecheckCommit { - match typecheck_module_request_from_hex( - source_digest_hex: source_digest_hex, - import_interface_hexes: import_interface_hexes, - compiler_digest_hex: compiler_digest_hex - ) { - TypecheckModuleRequestDigestRefused { which: w } => SeedTypecheckCommitDigestRefused { which: w } - TypecheckModuleRequestBuilt { req: req } => - seed_commit_typecheck_opened(opening: ensure_durable_typecheck_store(), req: req, interface_text: interface_text, diagnostics_text: diagnostics_text) +fn open_typecheck_store_witness_door(root_path: String) -> LocalStoreOpening { + match initialize_witness_typecheck_store(root: WitnessScratchRoot { root: root_path }) { + LocalStoreInitialized { opening: o } => o + LocalStoreInitializationRefused { root_path: p, cause: c } => + LocalStoreUnavailable { root_path: p, cause: c } } } -// Witness doors: the SAME bodies over a WitnessScratchRoot, so an executing control round-trips a -// real typecheck entry without touching the granted durable volume. -fn seed_lookup_typecheck_witness_hex( - root_path: String, +fn seed_lookup_typecheck_opened_hex( + opening: LocalStoreOpening, source_digest_hex: String, import_interface_hexes: List, compiler_digest_hex: String @@ -360,15 +340,12 @@ fn seed_lookup_typecheck_witness_hex( compiler_digest_hex: compiler_digest_hex ) { TypecheckModuleRequestDigestRefused { which: w } => SeedTypecheckDigestRefused { which: w } - TypecheckModuleRequestBuilt { req: req } => match initialize_witness_typecheck_store(root: WitnessScratchRoot { root: root_path }) { - LocalStoreInitialized { opening: o } => seed_lookup_typecheck_opened(opening: o, req: req) - LocalStoreInitializationRefused { root_path: p, cause: c } => SeedTypecheckUnavailable { root_path: p } - } + TypecheckModuleRequestBuilt { req: req } => seed_lookup_typecheck_opened(opening: opening, req: req) } } -fn seed_commit_typecheck_witness_hex( - root_path: String, +fn seed_commit_typecheck_opened_hex( + opening: LocalStoreOpening, source_digest_hex: String, import_interface_hexes: List, compiler_digest_hex: String, @@ -381,9 +358,7 @@ fn seed_commit_typecheck_witness_hex( compiler_digest_hex: compiler_digest_hex ) { TypecheckModuleRequestDigestRefused { which: w } => SeedTypecheckCommitDigestRefused { which: w } - TypecheckModuleRequestBuilt { req: req } => match initialize_witness_typecheck_store(root: WitnessScratchRoot { root: root_path }) { - LocalStoreInitialized { opening: o } => seed_commit_typecheck_opened(opening: o, req: req, interface_text: interface_text, diagnostics_text: diagnostics_text) - LocalStoreInitializationRefused { root_path: p, cause: c } => SeedTypecheckCommitMissedOpen { root_path: p } - } + TypecheckModuleRequestBuilt { req: req } => + seed_commit_typecheck_opened(opening: opening, req: req, interface_text: interface_text, diagnostics_text: diagnostics_text) } } diff --git a/src/v1/stage0/src/cli_run/typecheck_store_session.rs b/src/v1/stage0/src/cli_run/typecheck_store_session.rs index bc1c20045cd..7950dd166b7 100644 --- a/src/v1/stage0/src/cli_run/typecheck_store_session.rs +++ b/src/v1/stage0/src/cli_run/typecheck_store_session.rs @@ -14,7 +14,7 @@ const STORE_MODULE: &str = "gunbc.typecheck_module_store"; thread_local! { static PREPARING: Cell = const { Cell::new(false) }; - static SESSION: RefCell> = const { RefCell::new(None) }; + static SESSION: RefCell> = const { RefCell::new(None) }; static OBSERVED_LARGEST_ENTRY: Cell = const { Cell::new(0) }; /// Test-only: route the session through the witness door over a scratch root instead of the /// granted durable volume. Same .dag bodies, different opening (`*_witness_hex`). @@ -27,20 +27,17 @@ pub(crate) fn set_witness_root(root: Option) { WITNESS_ROOT.with(|w| *w.borrow_mut() = root); } -/// (function, leading root arg) the session calls: production door, or the witness door in tests. -fn door( - production: &'static str, - witness: &'static str, -) -> (&'static str, Vec<(Option, Value)>) { +/// The opening door: the production grant, or (tests) the witness scratch root. Both are .dag +/// functions returning a `LocalStoreOpening`; the session calls one exactly once. +fn open_door() -> (&'static str, Vec<(Option, Value)>) { #[cfg(test)] if let Some(root) = WITNESS_ROOT.with(|w| w.borrow().clone()) { return ( - witness, + "open_typecheck_store_witness_door", vec![(Some("root_path".to_string()), str_value(root))], ); } - let _ = witness; - (production, Vec::new()) + ("open_typecheck_store_door", Vec::new()) } /// What the store handed back for one module: the module's OWN tail (the entry never holds the @@ -65,12 +62,17 @@ struct OutcomeCounts { reentry_commit: u64, unreachable_lookup: u64, unreachable_commit: u64, + /// Times the store was opened (grant check, marker converge, catalog): one per session. + store_openings: u64, + /// Openings the .dag door refused; every lookup in that session is then a counted Unavailable. + open_refused: u64, } thread_local! { static OUTCOMES: Cell = const { Cell::new(OutcomeCounts { hit: 0, miss: 0, unavailable: 0, commit_committed: 0, commit_missed_open: 0, reentry_lookup: 0, reentry_commit: 0, unreachable_lookup: 0, unreachable_commit: 0, + store_openings: 0, open_refused: 0, }) }; } @@ -87,6 +89,7 @@ fn count(f: impl FnOnce(&mut OutcomeCounts)) { pub(crate) fn store_receipt_line() -> Option { let c = OUTCOMES.with(|c| c.get()); let degraded = c.unavailable + + c.open_refused + c.commit_missed_open + c.reentry_lookup + c.reentry_commit @@ -97,7 +100,7 @@ pub(crate) fn store_receipt_line() -> Option { return None; } Some(format!( - "[typecheck-store] hit={} miss={} committed={} unavailable={} commit_missed_open={} reentry_lookup={} reentry_commit={} unreachable_lookup={} unreachable_commit={} largest_entry_bytes={}", + "[typecheck-store] hit={} miss={} committed={} unavailable={} commit_missed_open={} reentry_lookup={} reentry_commit={} unreachable_lookup={} unreachable_commit={} store_openings={} open_refused={} largest_entry_bytes={}", c.hit, c.miss, c.commit_committed, @@ -107,6 +110,8 @@ pub(crate) fn store_receipt_line() -> Option { c.reentry_commit, c.unreachable_lookup, c.unreachable_commit, + c.store_openings, + c.open_refused, observed_largest_entry_bytes() )) } @@ -151,7 +156,7 @@ pub(crate) fn own_of_result(result: &TypecheckModuleResult) -> TypecheckModuleOw /// file is absent): the caller counts it as a typed Unavailable, never a Miss and never a panic. fn with_session( index: &MultiEntryIndex, - f: impl FnOnce(&v1_interpreter::InterpContext) -> Result, + f: impl FnOnce(&v1_interpreter::InterpContext, &Value) -> Result, ) -> Result, String> { if PREPARING.with(|c| c.get()) { return Err("typecheck-store session prepare re-entered".to_string()); @@ -175,13 +180,26 @@ fn with_session( })(); PREPARING.with(|c| c.set(false)); match prepared { - Ok(ctx) => *slot.borrow_mut() = Some(ctx), + Ok(ctx) => { + // The opening is one fact per session: opened here, passed to every call. + let (function, args) = open_door(); + let opening = + v1_interpreter::run_in_context_with_args(&ctx, function, &args, false) + .map_err(|e| format!("{function}: {e}"))?; + count(|c| c.store_openings += 1); + if let Value::Variant { variant_name, .. } = &opening { + if ctx.sym_eq(*variant_name, "LocalStoreUnavailable") { + count(|c| c.open_refused += 1); + } + } + *slot.borrow_mut() = Some((ctx, opening)); + } Err(e) => return Err(e), } } let borrow = slot.borrow(); - let ctx = borrow.as_ref().expect("session installed"); - f(ctx).map(Some) + let (ctx, opening) = borrow.as_ref().expect("session installed"); + f(ctx, opening).map(Some) }) } @@ -214,10 +232,8 @@ pub(crate) fn durable_typecheck_lookup( .map(str_value) .collect::>(), ); - let (function, mut args) = door( - "seed_lookup_typecheck_hex", - "seed_lookup_typecheck_witness_hex", - ); + let function = "seed_lookup_typecheck_opened_hex"; + let mut args: Vec<(Option, Value)> = Vec::new(); args.extend([ ( Some("source_digest_hex".to_string()), @@ -229,9 +245,11 @@ pub(crate) fn durable_typecheck_lookup( str_value(compiler_digest_hex), ), ]); - let got = with_session(index, |ctx| { + let got = with_session(index, |ctx, opening| { + let mut args = args; + args.insert(0, (Some("opening".to_string()), opening.clone())); let result = v1_interpreter::run_in_context_with_args(ctx, function, &args, false) - .map_err(|e| format!("seed_lookup_typecheck_hex: {e}"))?; + .map_err(|e| format!("seed_lookup_typecheck_opened_hex: {e}"))?; let Value::Variant { variant_name, fields, @@ -239,7 +257,7 @@ pub(crate) fn durable_typecheck_lookup( } = &result else { return Err(format!( - "seed_lookup_typecheck_hex not a variant: {result:?}" + "seed_lookup_typecheck_opened_hex not a variant: {result:?}" )); }; if ctx.sym_eq(*variant_name, "SeedTypecheckMiss") { @@ -268,7 +286,7 @@ pub(crate) fn durable_typecheck_lookup( return Ok(DurableTypecheckGet::Hit(own)); } Err(format!( - "seed_lookup_typecheck_hex unknown variant {}", + "seed_lookup_typecheck_opened_hex unknown variant {}", ctx.resolve(*variant_name) )) })?; @@ -307,10 +325,8 @@ pub(crate) fn durable_typecheck_commit( .map(str_value) .collect::>(), ); - let (function, mut args) = door( - "seed_commit_typecheck_hex", - "seed_commit_typecheck_witness_hex", - ); + let function = "seed_commit_typecheck_opened_hex"; + let mut args: Vec<(Option, Value)> = Vec::new(); args.extend([ ( Some("source_digest_hex".to_string()), @@ -330,9 +346,11 @@ pub(crate) fn durable_typecheck_commit( str_value(diagnostics_text), ), ]); - let got = with_session(index, |ctx| { + let got = with_session(index, |ctx, opening| { + let mut args = args; + args.insert(0, (Some("opening".to_string()), opening.clone())); let result = v1_interpreter::run_in_context_with_args(ctx, function, &args, false) - .map_err(|e| format!("seed_commit_typecheck_hex: {e}"))?; + .map_err(|e| format!("seed_commit_typecheck_opened_hex: {e}"))?; let Value::Variant { variant_name, fields, @@ -340,7 +358,7 @@ pub(crate) fn durable_typecheck_commit( } = &result else { return Err(format!( - "seed_commit_typecheck_hex not a variant: {result:?}" + "seed_commit_typecheck_opened_hex not a variant: {result:?}" )); }; if ctx.sym_eq(*variant_name, "SeedTypecheckCommitted") { @@ -359,7 +377,7 @@ pub(crate) fn durable_typecheck_commit( return Err(format!("typecheck-store commit digest refused ({which})")); } Err(format!( - "seed_commit_typecheck_hex unknown variant {}", + "seed_commit_typecheck_opened_hex unknown variant {}", ctx.resolve(*variant_name) )) })?; @@ -424,6 +442,7 @@ mod real_module_round_trip { v } + #[ignore = "live-corpus: resolves and typechecks the live dag+src/v2 closure (minutes); the receipts lane runs these with --ignored, the required unit run does not"] #[test] fn restore_equals_compute_and_entry_excludes_parent_envs() { let scratch = @@ -443,6 +462,13 @@ mod real_module_round_trip { "every miss commits ({miss0} miss, {committed0} committed): {:?}", store_receipt_line() ); + // The opening is one fact per session: every lookup and commit of the cold run (hundreds + // of modules) shared one opening. + let opened = OUTCOMES.with(|c| c.get()); + assert_eq!( + opened.store_openings, 1, + "N module lookups must initialize the store exactly once" + ); let cold = typed_results(&cold_index); assert!(!cold.is_empty()); @@ -457,6 +483,11 @@ mod real_module_round_trip { hit1 >= miss0, "every module hit ({hit1} hit, {miss0} cold misses)" ); + assert_eq!( + OUTCOMES.with(|c| c.get()).store_openings, + 1, + "the warm run reuses the session's opening" + ); let warm = typed_results(&warm_index); // The cold run also typechecked the store's own closure inside the session prepare // (counted reentry), so it holds MORE modules than the warm run needs; every module the @@ -551,6 +582,7 @@ mod session_reach { /// The caller's real pool holds the store: the lookup resolves through that same index from a /// foreign cwd, so no second pool is built and nothing is resolved cwd-relative. + #[ignore = "live-corpus: resolves and typechecks the live dag+src/v2 closure (minutes); the receipts lane runs these with --ignored, the required unit run does not"] #[test] fn the_callers_pool_serves_the_session_from_a_foreign_cwd() { let root = workspace_root(); From ff187d142539f0fe905ff915ed15bc81d65c58e3 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Wed, 7 Oct 2026 14:05:27 +0000 Subject: [PATCH 12/24] Typecheck store: commit once per key (compute only); drop the in-process-hit commit Co-Authored-By: Claude Sonnet 5.5 --- src/v1/stage0/src/cli_run.rs | 9 --------- src/v1/stage0/src/cli_run/typecheck_store_session.rs | 6 +++++- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/src/v1/stage0/src/cli_run.rs b/src/v1/stage0/src/cli_run.rs index 4cebbc1027b..3f0ca55b321 100644 --- a/src/v1/stage0/src/cli_run.rs +++ b/src/v1/stage0/src/cli_run.rs @@ -16504,15 +16504,6 @@ fn reconcile_with_typed_cache( }; let typed_key = store_parts.key.clone(); let cached = index_get_typed(index, &typed_key)?; - if let Some(hit) = cached.as_ref() { - typecheck_store_session::durable_typecheck_commit( - index, - &store_parts.source_digest_hex, - &store_parts.import_interface_hexes, - &store_parts.compiler_digest_hex, - &typecheck_store_session::own_of_result(hit.as_ref()), - )?; - } let was_cache_hit = cached.is_some(); // Record this module's cache keys with the armed schedule retention // (idempotent; hit or miss) so its state can be dropped exactly when no diff --git a/src/v1/stage0/src/cli_run/typecheck_store_session.rs b/src/v1/stage0/src/cli_run/typecheck_store_session.rs index 7950dd166b7..6d09e547b48 100644 --- a/src/v1/stage0/src/cli_run/typecheck_store_session.rs +++ b/src/v1/stage0/src/cli_run/typecheck_store_session.rs @@ -474,7 +474,11 @@ mod real_module_round_trip { let warm_index = build_multi_entry_index(&roots); resolve_entry_with_index(&warm_index, &entry).expect("warm resolve"); - let (hit1, miss1, _) = outcome_snapshot(); + let (hit1, miss1, committed1) = outcome_snapshot(); + assert_eq!( + committed1, committed0, + "commit exactly once per key: a warm run (restores and in-process hits) commits nothing" + ); assert_eq!( miss1, miss0, "warm run must not miss: every module restores" From 84abd2a78ff2c796bae4e1d5f337a8857c7628e4 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Wed, 7 Oct 2026 14:17:57 +0000 Subject: [PATCH 13/24] Typecheck store: retire the two satisfied-unbound conditions and the permanently green unbound witnesses; budget frontier stays open Co-Authored-By: Claude Sonnet 5.5 --- dag/gunbc/floor/floor_materialization.dag | 5 ++- dag/gunbc/materialization_store_budgets.dag | 5 --- dag/gunbc/typecheck_module_store.dag | 5 --- .../floor_materialization_witness_test.dag | 4 +-- .../typecheck_module_store_witness_test.dag | 34 ------------------- t_tmp.sh | 5 +++ 6 files changed, 9 insertions(+), 49 deletions(-) create mode 100644 t_tmp.sh diff --git a/dag/gunbc/floor/floor_materialization.dag b/dag/gunbc/floor/floor_materialization.dag index 2514b490dd5..2d35af3cee2 100644 --- a/dag/gunbc/floor/floor_materialization.dag +++ b/dag/gunbc/floor/floor_materialization.dag @@ -156,8 +156,7 @@ data floor_typecheck_store_no_providers: List = [] // are successive siblings with no shared mutable state to rewire, so there is no LCA process in // which the repetition could have been carried, and only a STORE tier can discharge it. // floor_typecheck_store_persist_provider is the modeled STORE row; it does not discharge the -// live ladder while typecheck_materialization_seed_path_note and -// typecheck_store_family_budget_frontier remain unbound. Live verdicts are +// live ladder while typecheck_store_family_budget_frontier remains unbound. Live verdicts are // floor_typecheck_store_persist_verdicts_without_provider. fn floor_runner_host_frame() -> std.materialization_ladder.Frame { @@ -200,7 +199,7 @@ data floor_typecheck_store_no_cost_floor_exemptions: List = [] // Modeled STORE row for TypecheckModuleRequest through // extdeps.realization.materialization_store_local. The no-provider ladder is the LIVE RED. // persist_verdicts with this provider is the intended discharge fold; it is not claimed as live -// while typecheck_materialization_seed_path_note and typecheck_store_family_budget_frontier stay +// while typecheck_store_family_budget_frontier stays // unbound. data floor_typecheck_store_persist_provider_id: String = "materialization_store_local:typed_module" diff --git a/dag/gunbc/materialization_store_budgets.dag b/dag/gunbc/materialization_store_budgets.dag index 8ef10abca42..371fdfb665b 100644 --- a/dag/gunbc/materialization_store_budgets.dag +++ b/dag/gunbc/materialization_store_budgets.dag @@ -59,8 +59,3 @@ data materialization_store_durable_family_budgets: List data materialization_store_host_ceiling_bytes: ByteSize = local_store_budget_sum(budgets: materialization_store_durable_family_budgets) data materialization_store_local_facts: CacheInterfaceCatalogFacts = materialization_store_local_facts_at(ceiling: materialization_store_host_ceiling_bytes) - -// C2's production consumer is the seed typecheck path (gunbc.typecheck_module_store -// typecheck_materialization_seed_path_note). Binding this row to a declaration that merely -// exists would fire before any run opens the durable root. -data materialization_store_durable_budgets_consumer_frontier: DissolutionCondition = unbound_dissolution(description: "TRIGGER: the TypecheckModuleRequest consumer (PR C2) opens DurableHostVolumeRoot through extdeps.realization.materialization_store_local passing materialization_store_durable_family_budgets from the seed typecheck path (open_typecheck_store_door once per session, seed_lookup_typecheck_opened_hex, seed_commit_typecheck_opened_hex). SUFFICIENT FOR: these rows bound a production store on every run that consumes it; until then they are read only by the host-ceiling control, and this row is retired by that landing and nothing else." as NonEmptyStr) diff --git a/dag/gunbc/typecheck_module_store.dag b/dag/gunbc/typecheck_module_store.dag index a9bedf85f90..a401fdcdae8 100644 --- a/dag/gunbc/typecheck_module_store.dag +++ b/dag/gunbc/typecheck_module_store.dag @@ -26,7 +26,6 @@ import std.materialization_object { } import std.materialization_store_grant { DurableHostVolumeRoot, WitnessScratchRoot } import std.decl_ref { decl_ref, DeclarationRef } -import std.dissolution { DissolutionCondition, unbound_dissolution } import extdeps.realization.materialization_store_local { LocalStoreCapability, LocalStoreOpening, @@ -254,10 +253,6 @@ data commit_typecheck_module_ref: DeclarationRef = decl_ref( decl_name: "commit_typecheck_module" ) -// Production inhabitance is the seed typecheck path calling ensure/lookup/commit. Bound dissolution -// on the declaration existing would fire before that path runs (DESIGN 3c / 4b(1)). -data typecheck_materialization_seed_path_note: DissolutionCondition = unbound_dissolution(description: "TRIGGER: v1_compiler.cli_run reconcile_with_typed_cache looks up through lookup_typecheck_module and commits through commit_typecheck_module after constructing TypecheckModuleRequest from source-content, import-interface and compiler digests (typecheck_module_request_from_hex). SUFFICIENT FOR: a verified store hit skips typecheck_module compute; a stale or tampered object refuses rather than serving; the durable open is ensure_durable_typecheck_store. This row is retired by that executing consumer and nothing else." as NonEmptyStr) - // Seed-facing operations (distinct from typecheck_module): hex digests in, typed store outcome // out; the opening is passed in, opened once per session (open_typecheck_store_door). diff --git a/dag/test/claim/floor/floor_materialization_witness_test.dag b/dag/test/claim/floor/floor_materialization_witness_test.dag index beea6c4b38b..e27febbb80c 100644 --- a/dag/test/claim/floor/floor_materialization_witness_test.dag +++ b/dag/test/claim/floor/floor_materialization_witness_test.dag @@ -58,8 +58,8 @@ test fn floor_index_build_reds_without_reference_provider() -> Bool { // C3 — the cross-process demand set: two sibling PROCESSES over one corpus under one built binary // (the witness fold and the generated-artifact drift gate), whose obligation LCA is the runner // host. Only a store tier can discharge it. The live verdict remains the no-provider refusal -// below: typecheck_materialization_seed_path_note and typecheck_store_family_budget_frontier -// stay unbound, so this module does not claim the cross-run demand is discharged. +// below: typecheck_store_family_budget_frontier +// stays unbound, so this module does not claim the cross-run demand is discharged. fn first_persist_verdict(vs: List) -> LadderVerdict? { vs.first() diff --git a/dag/test/claim/typecheck_module_store_witness_test.dag b/dag/test/claim/typecheck_module_store_witness_test.dag index b747a7f53d5..5adf1037fad 100644 --- a/dag/test/claim/typecheck_module_store_witness_test.dag +++ b/dag/test/claim/typecheck_module_store_witness_test.dag @@ -3,11 +3,8 @@ module test.claim.typecheck_module_store_witness import std.types { Bool, List, String } import std.content_hash { content_hash_atom, as_content_hash_structural, Fnv1a64Structural } import std.interface_summary { InterfaceHash, module_key, typed_module_key } -import std.decl_ref { decl_ref } -import std.dissolution { dissolution_status, DissolutionFired, DissolutionPending, DissolutionUnbound } import std.materialization_provider { request_key } import gunbc.materialization_store_budgets { - materialization_store_durable_budgets_consumer_frontier, typecheck_module_family_entry_budget_unit, materialization_store_typed_module_budget_policy, typecheck_store_family_budget_frontier, @@ -22,7 +19,6 @@ import gunbc.typecheck_module_store { TypecheckModuleRequestDigestRefused, typecheck_module_payloads, typecheck_module_entry_bytes, - typecheck_materialization_seed_path_note, } import std.measure { measure_count } import std.optional { Present } @@ -36,26 +32,6 @@ fn iface(v: String) -> InterfaceHash { content_hash_atom(value: v) } -test fn the_durable_budgets_consumer_frontier_stays_unbound_until_the_seed_path_runs() -> Bool { - match dissolution_status(condition: materialization_store_durable_budgets_consumer_frontier, present_decls: [ - decl_ref(module_path: "gunbc.typecheck_module_store", decl_name: "ensure_durable_typecheck_store") - ]) { - DissolutionUnbound => true - DissolutionFired => false - DissolutionPending => false - } -} - -test fn the_seed_path_note_stays_unbound_until_reconcile_calls_lookup_and_commit() -> Bool { - match dissolution_status(condition: typecheck_materialization_seed_path_note, present_decls: [ - decl_ref(module_path: "gunbc.typecheck_module_store", decl_name: "lookup_typecheck_module") - ]) { - DissolutionUnbound => true - DissolutionFired => false - DissolutionPending => false - } -} - test fn typecheck_module_request_key_is_derived_from_the_judgment_contract() -> Bool { let src = h(v: "source-bytes-not-a-path") let imp = iface(v: "iface-1") @@ -86,16 +62,6 @@ test fn the_family_entry_size_is_a_transcribed_uncited_bet_naming_the_real_route } } -test fn the_family_budget_frontier_stays_unbound_while_the_entry_size_is_a_bet() -> Bool { - match dissolution_status(condition: typecheck_store_family_budget_frontier, present_decls: [ - decl_ref(module_path: "gunbc.materialization_store_budgets", decl_name: "typecheck_store_family_entry_bet") - ]) { - DissolutionUnbound => true - DissolutionFired => false - DissolutionPending => false - } -} - test fn the_typed_module_family_budget_is_the_entry_unit_times_the_residency_ceiling() -> Bool { measure_count(m: materialization_store_typed_module_budget_policy) == measure_count(m: typecheck_module_family_entry_budget_unit()) * typed_module_cache_entries_ceiling diff --git a/t_tmp.sh b/t_tmp.sh new file mode 100644 index 00000000000..50871b0dfd8 --- /dev/null +++ b/t_tmp.sh @@ -0,0 +1,5 @@ +mkdir -p /sys/fs/cgroup/gunbcrun && echo 51539607552 > /sys/fs/cgroup/gunbcrun/memory.max && echo $$ > /sys/fs/cgroup/gunbcrun/cgroup.procs +cargo test --release -p v1-compiler --lib --no-run >/dev/null 2>&1 +date +%s +cargo test --release -p v1-compiler --lib restore_equals -- --include-ignored 2>&1 | grep -a "test result\|panicked\|FAILED\|ok$" +date +%s From 52c05682983deedc55a1d1cf3426598ba48f6c60 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Wed, 7 Oct 2026 14:27:39 +0000 Subject: [PATCH 14/24] Remove scratch script t_tmp.sh Co-Authored-By: Claude Sonnet 5.5 --- t_tmp.sh | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 t_tmp.sh diff --git a/t_tmp.sh b/t_tmp.sh deleted file mode 100644 index 50871b0dfd8..00000000000 --- a/t_tmp.sh +++ /dev/null @@ -1,5 +0,0 @@ -mkdir -p /sys/fs/cgroup/gunbcrun && echo 51539607552 > /sys/fs/cgroup/gunbcrun/memory.max && echo $$ > /sys/fs/cgroup/gunbcrun/cgroup.procs -cargo test --release -p v1-compiler --lib --no-run >/dev/null 2>&1 -date +%s -cargo test --release -p v1-compiler --lib restore_equals -- --include-ignored 2>&1 | grep -a "test result\|panicked\|FAILED\|ok$" -date +%s From a355d29a89ae16bbf411787fa1b24a13f2c7c434 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Wed, 7 Oct 2026 14:38:03 +0000 Subject: [PATCH 15/24] Typecheck store: seed-growth justification for the session Rust; delete dangling with-provider floor decls Co-Authored-By: Claude Sonnet 5.5 --- dag/gunbc/floor/floor_materialization.dag | 36 ++------ dag/gunbc/seed_growth_admission.dag | 2 + .../typecheck_store_session_seed_growth.dag | 84 +++++++++++++++++++ 3 files changed, 91 insertions(+), 31 deletions(-) create mode 100644 dag/gunbc/typecheck_store_session_seed_growth.dag diff --git a/dag/gunbc/floor/floor_materialization.dag b/dag/gunbc/floor/floor_materialization.dag index 2d35af3cee2..b76c4f3357d 100644 --- a/dag/gunbc/floor/floor_materialization.dag +++ b/dag/gunbc/floor/floor_materialization.dag @@ -155,9 +155,8 @@ data floor_typecheck_store_no_providers: List = [] // UnboundedSiblingsFrame, not SharedStateFrame: recurring invocations of the fold on a runner host // are successive siblings with no shared mutable state to rewire, so there is no LCA process in // which the repetition could have been carried, and only a STORE tier can discharge it. -// floor_typecheck_store_persist_provider is the modeled STORE row; it does not discharge the -// live ladder while typecheck_store_family_budget_frontier remains unbound. Live verdicts are -// floor_typecheck_store_persist_verdicts_without_provider. +// The live ladder stays red while typecheck_store_family_budget_frontier remains open; the live +// verdicts are floor_typecheck_store_persist_verdicts_without_provider. fn floor_runner_host_frame() -> std.materialization_ladder.Frame { std.materialization_ladder.Frame { name: "runner-host", kind: UnboundedSiblingsFrame } @@ -196,34 +195,9 @@ fn floor_typecheck_store_cross_process_demands() -> List { data floor_typecheck_store_no_cost_floor_exemptions: List = [] -// Modeled STORE row for TypecheckModuleRequest through -// extdeps.realization.materialization_store_local. The no-provider ladder is the LIVE RED. -// persist_verdicts with this provider is the intended discharge fold; it is not claimed as live -// while typecheck_store_family_budget_frontier stays -// unbound. - -data floor_typecheck_store_persist_provider_id: String = "materialization_store_local:typed_module" - -data floor_typecheck_store_persist_provider: CacheProvider = provider_row( - id: floor_typecheck_store_persist_provider_id, - scope: [floor_runner_host_frame()], - coverage: CoversIdentities { identities: [floor_typecheck_store_identity] }, - tier: ArtifactTier { keying: ContentKeyed }, - retention: persistent_retention(capacity: CapacityBounded { - limit: ByteCapacity { limit: ExactLimit { value: materialization_store_typed_module_budget_policy } }, - at_capacity: ReplaceExisting { strategy: LeastRecentlyUsed } - }) -) - -data floor_typecheck_store_persist_providers: List = [floor_typecheck_store_persist_provider] - -fn floor_typecheck_store_persist_verdicts() -> List { - materialization_ladder( - demands: floor_typecheck_store_cross_process_demands(), - providers: floor_typecheck_store_persist_providers, - cost_floor_exempt: floor_typecheck_store_no_cost_floor_exemptions - ) -} +// The no-provider ladder is the LIVE RED: the store tier is not claimed to discharge the cross-process +// demand while typecheck_store_family_budget_frontier stays open. A with-provider fold is added when +// that frontier is bound and a claim executes it. fn floor_typecheck_store_persist_verdicts_without_provider() -> List { materialization_ladder( diff --git a/dag/gunbc/seed_growth_admission.dag b/dag/gunbc/seed_growth_admission.dag index 1cece126858..d6ae106bc43 100644 --- a/dag/gunbc/seed_growth_admission.dag +++ b/dag/gunbc/seed_growth_admission.dag @@ -11,6 +11,7 @@ import gunbc.native_cache_root_seed_growth { native_cache_root_seed_growth_justi import gunbc.claim_scope_fixture_seed_growth { claim_scope_fixture_seed_growth_justification } import gunbc.scope_rank_view_seed_growth { scope_rank_view_seed_growth_justification } import gunbc.floor_teardown_attribution_seed_growth { floor_teardown_attribution_seed_growth_justification } +import gunbc.typecheck_store_session_seed_growth { typecheck_store_session_seed_growth_justification } import gunbc.anonymous_record_resolution_seed_growth { anonymous_record_resolution_seed_growth_justification } import gunbc.closure_edge_demand_seed_growth { closure_edge_demand_seed_growth_justification } import gunbc.reference_closure_binder_seed_growth { reference_closure_binder_seed_growth_justification } @@ -271,6 +272,7 @@ fn seed_growth_justification_roster() -> List { claim_scope_fixture_seed_growth_justification, evaluation_store_address_census_seed_growth_justification, anonymous_record_resolution_seed_growth_justification, + typecheck_store_session_seed_growth_justification, floor_non_verdict_seed_growth_justification, floor_route_gap_seed_growth_justification, floor_cgroup_stat_beat_seed_growth_justification, diff --git a/dag/gunbc/typecheck_store_session_seed_growth.dag b/dag/gunbc/typecheck_store_session_seed_growth.dag new file mode 100644 index 00000000000..427f482be1c --- /dev/null +++ b/dag/gunbc/typecheck_store_session_seed_growth.dag @@ -0,0 +1,84 @@ +module gunbc.typecheck_store_session_seed_growth + +import gunbc.roadmap_model { RoadmapNodeId } +import gunbc.seed_growth { SeedGrowthJustification } +import std.decl_ref { DeclarationRef, WholeDeclaration } + +data typecheck_store_session_seed_growth_justification: SeedGrowthJustification = SeedGrowthJustification { + hand_authored_declarations: [ + DeclarationRef { + module_path: "v1_compiler.cli_run.typecheck_store_session", + decl_name: "durable_typecheck_lookup", + field: WholeDeclaration + }, + DeclarationRef { + module_path: "v1_compiler.cli_run.typecheck_store_session", + decl_name: "durable_typecheck_commit", + field: WholeDeclaration + }, + DeclarationRef { + module_path: "v1_compiler.cli_run.typecheck_store_session", + decl_name: "with_session", + field: WholeDeclaration + }, + DeclarationRef { + module_path: "v1_compiler.cli_run.typecheck_store_session", + decl_name: "open_door", + field: WholeDeclaration + }, + DeclarationRef { + module_path: "v1_compiler.cli_run.typecheck_store_session", + decl_name: "store_receipt_line", + field: WholeDeclaration + }, + DeclarationRef { + module_path: "v1_compiler.cli_run.typecheck_store_session", + decl_name: "own_payload_texts", + field: WholeDeclaration + }, + DeclarationRef { + module_path: "v1_compiler.cli_run.typecheck_store_session", + decl_name: "own_from_payload_texts", + field: WholeDeclaration + }, + DeclarationRef { + module_path: "v1_compiler.cli_run.typecheck_store_session", + decl_name: "own_of_result", + field: WholeDeclaration + }, + DeclarationRef { + module_path: "v1_compiler.cli_run.typecheck_store_session", + decl_name: "observed_largest_entry_bytes", + field: WholeDeclaration + }, + DeclarationRef { + module_path: "v1_compiler.cli_run.typecheck_store_session", + decl_name: "outcome_snapshot", + field: WholeDeclaration + }, + DeclarationRef { + module_path: "v1_compiler.cli_run.typecheck_store_session", + decl_name: "outcome_reset", + field: WholeDeclaration + }, + DeclarationRef { + module_path: "v1_compiler.cli_run.typecheck_store_session", + decl_name: "set_witness_root", + field: WholeDeclaration + }, + DeclarationRef { + module_path: "v1_compiler.cli_run.typecheck_store_session", + decl_name: "count", + field: WholeDeclaration + }, + DeclarationRef { + module_path: "v1_compiler.cli_run", + decl_name: "reconcile_with_typed_cache", + field: WholeDeclaration + } + ], + reason: "Serves the v2 self-host program under the v1 PURPOSE test (gunbc.v1_maintenance_standing): the seed typecheck path is the production consumer of gunbc.typecheck_module_store over extdeps.realization.materialization_store_local, so a cached typecheck of a module's OWN interface replaces recomputation on every run. Every decision is .dag: the request key, the own-interface entry, grant and door availability (open_typecheck_store_door), and restore (typecheck_module_restore in src/v1/04_infer.dag). The Rust declarations only marshal hex digests and the opening Value across the door, resolve the store closure through the caller's MultiEntryIndex, and count typed outcomes for the run receipt. Rust is still needed because the seed interpreter drives reconcile_with_typed_cache and holds the per-process index; no .dag fold can yet call the store door from inside that stage. The src/v1/stage0/src/v1_compiler_infer.rs growth is generated from 04_infer.dag and carries no hand authority.", + owning_dissolution_lane: "v1-hand-queue-drain" as RoadmapNodeId, + trigger: "Delete typecheck_store_session.rs and the durable_typecheck_lookup/durable_typecheck_commit call sites in reconcile_with_typed_cache when the typecheck stage is one .dag fold that opens the store door once, looks up by request key before collecting parent environments, restores or computes, commits, and returns its outcome counts as a value for the run receipt; the receipt line (store_receipt_line) must then be rendered from that value, so counting is not left in a Rust thread-local.", + current_boundary: "src/v1/04_infer.dag typecheck_module_restore / dag/gunbc/typecheck_module_store.dag seed_lookup_typecheck_opened_hex, seed_commit_typecheck_opened_hex -> src/v1/stage0/src/cli_run/typecheck_store_session.rs -> cli_run.rs reconcile_with_typed_cache" +} From fac8a08291e6ad421b8bdeabcd97ba3a1c3fbbe8 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Wed, 7 Oct 2026 15:08:05 +0000 Subject: [PATCH 16/24] Typecheck store: delete dangling decls; observed largest entry is the stored object size read back from the door Co-Authored-By: Claude Sonnet 5.5 --- dag/gunbc/typecheck_module_store.dag | 36 +++++-------------- .../src/cli_run/typecheck_store_session.rs | 17 +++++---- 2 files changed, 19 insertions(+), 34 deletions(-) diff --git a/dag/gunbc/typecheck_module_store.dag b/dag/gunbc/typecheck_module_store.dag index a401fdcdae8..3a34d104e77 100644 --- a/dag/gunbc/typecheck_module_store.dag +++ b/dag/gunbc/typecheck_module_store.dag @@ -3,7 +3,7 @@ module gunbc.typecheck_module_store import std.types { List, String, Bool, NonEmptyStr } import std.content_hash { Fnv1a64Structural, fnv1a64_structural_hex_digest } import std.interface_summary { InterfaceHash } -import std.measure { ByteSize } +import std.measure { ByteSize, measure_count } import std.materialization_provider { ArtifactRequest, TypecheckModuleRequest, @@ -25,7 +25,6 @@ import std.materialization_object { store_lookup_refusal_tag, } import std.materialization_store_grant { DurableHostVolumeRoot, WitnessScratchRoot } -import std.decl_ref { decl_ref, DeclarationRef } import extdeps.realization.materialization_store_local { LocalStoreCapability, LocalStoreOpening, @@ -186,10 +185,6 @@ fn initialize_witness_typecheck_store(root: WitnessScratchRoot) -> LocalStoreIni ) } -fn open_witness_typecheck_store(root: WitnessScratchRoot) -> LocalStoreOpening { - local_store_open(root: root, budgets: materialization_store_durable_family_budgets) -} - type TypecheckModuleServe = TypecheckModuleHit { lookup: StoreLookup } | TypecheckModuleMiss { lookup: StoreLookup } @@ -233,26 +228,6 @@ fn typecheck_module_materialize(capability: LocalStoreCapability, req: ArtifactR } } -fn typecheck_lookup_refused_integrity(l: StoreLookup) -> Bool { - let tag = store_lookup_refusal_tag(l: l) - tag != "hit" && tag != "miss" -} - -data ensure_durable_typecheck_store_ref: DeclarationRef = decl_ref( - module_path: "gunbc.typecheck_module_store", - decl_name: "ensure_durable_typecheck_store" -) - -data lookup_typecheck_module_ref: DeclarationRef = decl_ref( - module_path: "gunbc.typecheck_module_store", - decl_name: "lookup_typecheck_module" -) - -data commit_typecheck_module_ref: DeclarationRef = decl_ref( - module_path: "gunbc.typecheck_module_store", - decl_name: "commit_typecheck_module" -) - // Seed-facing operations (distinct from typecheck_module): hex digests in, typed store outcome // out; the opening is passed in, opened once per session (open_typecheck_store_door). @@ -264,7 +239,7 @@ type SeedTypecheckLookup | SeedTypecheckDigestRefused { which: String } type SeedTypecheckCommit - = SeedTypecheckCommitted + = SeedTypecheckCommitted { stored_bytes: Int } | SeedTypecheckCommitMissedOpen { root_path: String } | SeedTypecheckCommitRefused | SeedTypecheckCommitDigestRefused { which: String } @@ -301,7 +276,12 @@ fn seed_commit_typecheck_opened( LocalStoreUnavailable { root_path: p, cause: _ } => SeedTypecheckCommitMissedOpen { root_path: p } LocalStoreOpened { capability: c, durability: _ } => match commit_typecheck_module(capability: c, req: req, interface_text: interface_text, diagnostics_text: diagnostics_text).commit { - StoreCommitSettled { receipt: _ } => SeedTypecheckCommitted + StoreCommitSettled { receipt: _ } => SeedTypecheckCommitted { + stored_bytes: match typecheck_module_entry_bytes(req: req, payloads: typecheck_module_payloads(interface: interface_text, diagnostics: diagnostics_text)) { + Present { value: b } => measure_count(m: b) + Absent => 0 + } + } StoreCommitRefused { key: _, refusal: _ } => SeedTypecheckCommitRefused } } diff --git a/src/v1/stage0/src/cli_run/typecheck_store_session.rs b/src/v1/stage0/src/cli_run/typecheck_store_session.rs index 6d09e547b48..c39aa582b02 100644 --- a/src/v1/stage0/src/cli_run/typecheck_store_session.rs +++ b/src/v1/stage0/src/cli_run/typecheck_store_session.rs @@ -312,12 +312,6 @@ pub(crate) fn durable_typecheck_commit( return Ok(()); } let (interface_text, diagnostics_text) = own_payload_texts(own)?; - let bytes = (interface_text.len() + diagnostics_text.len()) as u64; - OBSERVED_LARGEST_ENTRY.with(|c| { - if bytes > c.get() { - c.set(bytes); - } - }); let imports = list_value( import_interface_hexes .iter() @@ -363,6 +357,17 @@ pub(crate) fn durable_typecheck_commit( }; if ctx.sym_eq(*variant_name, "SeedTypecheckCommitted") { count(|c| c.commit_committed += 1); + // The stored object's size as the store sizes it (local_store_object_bytes), read + // back from the door: the grain the budget frontier names. + let stored = match ctx.field(fields, "stored_bytes") { + Some(Value::Int(n)) if *n >= 0 => *n as u64, + other => return Err(format!("stored_bytes not a count: {other:?}")), + }; + OBSERVED_LARGEST_ENTRY.with(|c| { + if stored > c.get() { + c.set(stored); + } + }); return Ok(()); } if ctx.sym_eq(*variant_name, "SeedTypecheckCommitMissedOpen") { From 76c6e70c531176d6fa61aaab0fe0eb7fdd0b6135 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Wed, 7 Oct 2026 15:43:19 +0000 Subject: [PATCH 17/24] Typecheck store: size control measures the committed modules, not the whole cold index Co-Authored-By: Claude Sonnet 5.5 --- src/v1/stage0/src/cli_run/typecheck_store_session.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/v1/stage0/src/cli_run/typecheck_store_session.rs b/src/v1/stage0/src/cli_run/typecheck_store_session.rs index c39aa582b02..0202b789db2 100644 --- a/src/v1/stage0/src/cli_run/typecheck_store_session.rs +++ b/src/v1/stage0/src/cli_run/typecheck_store_session.rs @@ -525,8 +525,13 @@ mod real_module_round_trip { // size control: the entry is the module's own tail; the closure snapshot (parent envs) // is what made the deleted store multi-GB. + // Measured over the modules the store committed (the ones the warm run restored): the + // cold index also holds the store closure's ~two hundred modules from the session + // prepare, and serializing each of those in full is the test's own cost, not the + // product's. let largest_full = cold .iter() + .filter(|(k, _)| warm.iter().any(|(kw, _)| kw == k)) .map(|(_, r)| { let mut w = CountingWriter(0); serde_json::to_writer(&mut w, &**r).expect("serialize result"); From 96fe6029d4ee79ffa95024036f96d7b6a78e5647 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Wed, 7 Oct 2026 16:15:28 +0000 Subject: [PATCH 18/24] Typecheck store: an unread stored size is its own arm, never zero; excluded from the largest-entry reading Co-Authored-By: Claude Sonnet 5.5 --- dag/gunbc/typecheck_module_store.dag | 13 ++--- .../src/cli_run/typecheck_store_session.rs | 55 ++++++++++++++++--- 2 files changed, 52 insertions(+), 16 deletions(-) diff --git a/dag/gunbc/typecheck_module_store.dag b/dag/gunbc/typecheck_module_store.dag index 3a34d104e77..ec841a55798 100644 --- a/dag/gunbc/typecheck_module_store.dag +++ b/dag/gunbc/typecheck_module_store.dag @@ -3,7 +3,7 @@ module gunbc.typecheck_module_store import std.types { List, String, Bool, NonEmptyStr } import std.content_hash { Fnv1a64Structural, fnv1a64_structural_hex_digest } import std.interface_summary { InterfaceHash } -import std.measure { ByteSize, measure_count } +import std.measure { ByteSize } import std.materialization_provider { ArtifactRequest, TypecheckModuleRequest, @@ -239,7 +239,8 @@ type SeedTypecheckLookup | SeedTypecheckDigestRefused { which: String } type SeedTypecheckCommit - = SeedTypecheckCommitted { stored_bytes: Int } + = SeedTypecheckCommitted { stored: ByteSize } + | SeedTypecheckCommittedSizeUnread | SeedTypecheckCommitMissedOpen { root_path: String } | SeedTypecheckCommitRefused | SeedTypecheckCommitDigestRefused { which: String } @@ -276,11 +277,9 @@ fn seed_commit_typecheck_opened( LocalStoreUnavailable { root_path: p, cause: _ } => SeedTypecheckCommitMissedOpen { root_path: p } LocalStoreOpened { capability: c, durability: _ } => match commit_typecheck_module(capability: c, req: req, interface_text: interface_text, diagnostics_text: diagnostics_text).commit { - StoreCommitSettled { receipt: _ } => SeedTypecheckCommitted { - stored_bytes: match typecheck_module_entry_bytes(req: req, payloads: typecheck_module_payloads(interface: interface_text, diagnostics: diagnostics_text)) { - Present { value: b } => measure_count(m: b) - Absent => 0 - } + StoreCommitSettled { receipt: _ } => match typecheck_module_entry_bytes(req: req, payloads: typecheck_module_payloads(interface: interface_text, diagnostics: diagnostics_text)) { + Present { value: b } => SeedTypecheckCommitted { stored: b } + Absent => SeedTypecheckCommittedSizeUnread } StoreCommitRefused { key: _, refusal: _ } => SeedTypecheckCommitRefused } diff --git a/src/v1/stage0/src/cli_run/typecheck_store_session.rs b/src/v1/stage0/src/cli_run/typecheck_store_session.rs index 0202b789db2..c12c06a30b3 100644 --- a/src/v1/stage0/src/cli_run/typecheck_store_session.rs +++ b/src/v1/stage0/src/cli_run/typecheck_store_session.rs @@ -58,6 +58,8 @@ struct OutcomeCounts { unavailable: u64, commit_committed: u64, commit_missed_open: u64, + /// Commits whose stored size could not be read back: excluded from the largest-entry reading. + commit_size_unread: u64, reentry_lookup: u64, reentry_commit: u64, unreachable_lookup: u64, @@ -70,7 +72,7 @@ struct OutcomeCounts { thread_local! { static OUTCOMES: Cell = const { Cell::new(OutcomeCounts { - hit: 0, miss: 0, unavailable: 0, commit_committed: 0, commit_missed_open: 0, + hit: 0, miss: 0, unavailable: 0, commit_committed: 0, commit_missed_open: 0, commit_size_unread: 0, reentry_lookup: 0, reentry_commit: 0, unreachable_lookup: 0, unreachable_commit: 0, store_openings: 0, open_refused: 0, }) }; @@ -91,6 +93,7 @@ pub(crate) fn store_receipt_line() -> Option { let degraded = c.unavailable + c.open_refused + c.commit_missed_open + + c.commit_size_unread + c.reentry_lookup + c.reentry_commit + c.unreachable_lookup @@ -100,12 +103,13 @@ pub(crate) fn store_receipt_line() -> Option { return None; } Some(format!( - "[typecheck-store] hit={} miss={} committed={} unavailable={} commit_missed_open={} reentry_lookup={} reentry_commit={} unreachable_lookup={} unreachable_commit={} store_openings={} open_refused={} largest_entry_bytes={}", + "[typecheck-store] hit={} miss={} committed={} unavailable={} commit_missed_open={} commit_size_unread={} reentry_lookup={} reentry_commit={} unreachable_lookup={} unreachable_commit={} store_openings={} open_refused={} largest_entry_bytes={}", c.hit, c.miss, c.commit_committed, c.unavailable, c.commit_missed_open, + c.commit_size_unread, c.reentry_lookup, c.reentry_commit, c.unreachable_lookup, @@ -359,14 +363,21 @@ pub(crate) fn durable_typecheck_commit( count(|c| c.commit_committed += 1); // The stored object's size as the store sizes it (local_store_object_bytes), read // back from the door: the grain the budget frontier names. - let stored = match ctx.field(fields, "stored_bytes") { - Some(Value::Int(n)) if *n >= 0 => *n as u64, - other => return Err(format!("stored_bytes not a count: {other:?}")), + let stored = match ctx.field(fields, "stored") { + Some(Value::Record { fields: m, .. }) => match ctx.field(m, "count") { + Some(Value::Int(n)) if *n >= 0 => *n as u64, + other => return Err(format!("stored size not a count: {other:?}")), + }, + other => return Err(format!("stored not a ByteSize: {other:?}")), }; - OBSERVED_LARGEST_ENTRY.with(|c| { - if stored > c.get() { - c.set(stored); - } + record_stored_entry_bytes(stored); + return Ok(()); + } + if ctx.sym_eq(*variant_name, "SeedTypecheckCommittedSizeUnread") { + // Committed, size unmeasured: counted, and never a reading (Absent is not zero). + count(|c| { + c.commit_committed += 1; + c.commit_size_unread += 1; }); return Ok(()); } @@ -392,6 +403,14 @@ pub(crate) fn durable_typecheck_commit( Ok(()) } +fn record_stored_entry_bytes(stored: u64) { + OBSERVED_LARGEST_ENTRY.with(|c| { + if stored > c.get() { + c.set(stored); + } + }); +} + pub(crate) fn observed_largest_entry_bytes() -> u64 { OBSERVED_LARGEST_ENTRY.with(|c| c.get()) } @@ -620,3 +639,21 @@ mod session_reach { ); } } + +#[cfg(test)] +mod size_unread_tests { + use super::*; + + #[test] + fn a_commit_whose_size_read_fails_does_not_change_the_largest_entry_reading() { + OBSERVED_LARGEST_ENTRY.with(|c| c.set(0)); + record_stored_entry_bytes(10); + let before = observed_largest_entry_bytes(); + count(|c| { + c.commit_committed += 1; + c.commit_size_unread += 1; + }); + assert_eq!(observed_largest_entry_bytes(), before); + assert_eq!(OUTCOMES.with(|c| c.get().commit_size_unread), 1); + } +} From c35787d733b3ec8b099e0bc150c7c2cc1bd43190 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Wed, 7 Oct 2026 18:05:45 +0000 Subject: [PATCH 19/24] Typecheck store: an invocation without a configured store never opens or writes it (counted not_configured); enroll the three hermetic-unreachable wet claims as the route-gap triple Co-Authored-By: Claude Sonnet 5.5 --- dag/gunbc/ci/ci_layer_roots.dag | 5 ++ .../src/cli_run/typecheck_store_session.rs | 58 ++++++++++++++++++- src/v2/workflow/floor_route_gap.dag | 14 +++++ 3 files changed, 75 insertions(+), 2 deletions(-) diff --git a/dag/gunbc/ci/ci_layer_roots.dag b/dag/gunbc/ci/ci_layer_roots.dag index 40ca0b403d4..6d592277f04 100644 --- a/dag/gunbc/ci/ci_layer_roots.dag +++ b/dag/gunbc/ci/ci_layer_roots.dag @@ -1111,6 +1111,11 @@ data witness_exclusion_frontier: List = [ classification: LocalRepoWetLane, reason: excl_local_repo_wet_tempdir_write_reason, dissolution: excl_local_repo_wet_dissolve}, + WitnessExclusionRow { + pattern: "typecheck_module_store_wet_witness_test.dag", + classification: LocalRepoWetLane, + reason: excl_local_repo_wet_tempdir_write_reason, + dissolution: excl_local_repo_wet_dissolve}, WitnessExclusionRow { pattern: "host_capacity_wet_witness_test.dag", classification: LocalRepoWetLane, diff --git a/src/v1/stage0/src/cli_run/typecheck_store_session.rs b/src/v1/stage0/src/cli_run/typecheck_store_session.rs index c12c06a30b3..5c9c7b3923a 100644 --- a/src/v1/stage0/src/cli_run/typecheck_store_session.rs +++ b/src/v1/stage0/src/cli_run/typecheck_store_session.rs @@ -60,6 +60,9 @@ struct OutcomeCounts { commit_missed_open: u64, /// Commits whose stored size could not be read back: excluded from the largest-entry reading. commit_size_unread: u64, + /// Calls made while the invoking workflow configured no store: no open, no write attempted. + not_configured_lookup: u64, + not_configured_commit: u64, reentry_lookup: u64, reentry_commit: u64, unreachable_lookup: u64, @@ -72,7 +75,7 @@ struct OutcomeCounts { thread_local! { static OUTCOMES: Cell = const { Cell::new(OutcomeCounts { - hit: 0, miss: 0, unavailable: 0, commit_committed: 0, commit_missed_open: 0, commit_size_unread: 0, + hit: 0, miss: 0, unavailable: 0, commit_committed: 0, commit_missed_open: 0, commit_size_unread: 0, not_configured_lookup: 0, not_configured_commit: 0, reentry_lookup: 0, reentry_commit: 0, unreachable_lookup: 0, unreachable_commit: 0, store_openings: 0, open_refused: 0, }) }; @@ -94,6 +97,8 @@ pub(crate) fn store_receipt_line() -> Option { + c.open_refused + c.commit_missed_open + c.commit_size_unread + + c.not_configured_lookup + + c.not_configured_commit + c.reentry_lookup + c.reentry_commit + c.unreachable_lookup @@ -103,13 +108,15 @@ pub(crate) fn store_receipt_line() -> Option { return None; } Some(format!( - "[typecheck-store] hit={} miss={} committed={} unavailable={} commit_missed_open={} commit_size_unread={} reentry_lookup={} reentry_commit={} unreachable_lookup={} unreachable_commit={} store_openings={} open_refused={} largest_entry_bytes={}", + "[typecheck-store] hit={} miss={} committed={} unavailable={} commit_missed_open={} commit_size_unread={} not_configured_lookup={} not_configured_commit={} reentry_lookup={} reentry_commit={} unreachable_lookup={} unreachable_commit={} store_openings={} open_refused={} largest_entry_bytes={}", c.hit, c.miss, c.commit_committed, c.unavailable, c.commit_missed_open, c.commit_size_unread, + c.not_configured_lookup, + c.not_configured_commit, c.reentry_lookup, c.reentry_commit, c.unreachable_lookup, @@ -156,6 +163,20 @@ pub(crate) fn own_of_result(result: &TypecheckModuleResult) -> TypecheckModuleOw } } +/// The invoking workflow's explicit choice to use the granted durable store. The root itself is +/// the one declared datum `std.materialization_store_grant materialization_store_durable_root_path` +/// (a caller never supplies a path); this variable only says whether this invocation uses it, so a +/// compile that was not given a store never reaches a host-durable write. +const STORE_CONFIG_ENV: &str = "GUNBC_TYPECHECK_STORE"; + +fn store_configured() -> bool { + #[cfg(test)] + if WITNESS_ROOT.with(|w| w.borrow().is_some()) { + return true; + } + std::env::var(STORE_CONFIG_ENV).is_ok_and(|v| v == "durable") +} + /// `Ok(None)` when the store's own closure is not reachable from the compiler's roots (the entry /// file is absent): the caller counts it as a typed Unavailable, never a Miss and never a panic. fn with_session( @@ -229,6 +250,10 @@ pub(crate) fn durable_typecheck_lookup( count(|c| c.reentry_lookup += 1); return Ok(DurableTypecheckGet::Miss); } + if !store_configured() { + count(|c| c.not_configured_lookup += 1); + return Ok(DurableTypecheckGet::Miss); + } let imports = list_value( import_interface_hexes .iter() @@ -315,6 +340,10 @@ pub(crate) fn durable_typecheck_commit( count(|c| c.reentry_commit += 1); return Ok(()); } + if !store_configured() { + count(|c| c.not_configured_commit += 1); + return Ok(()); + } let (interface_text, diagnostics_text) = own_payload_texts(own)?; let imports = list_value( import_interface_hexes @@ -595,6 +624,7 @@ mod session_reach { fn a_pool_without_the_store_module_is_a_counted_unavailable_not_a_miss() { let (root, idx) = fixture_index(); let hex = "0".repeat(64); + set_witness_root(Some("/tmp/gunbc_unused".to_string())); let before = OUTCOMES.with(|c| c.get()); let got = durable_typecheck_lookup(&idx, &hex, &[], &hex); let own = TypecheckModuleOwn { @@ -605,6 +635,7 @@ mod session_reach { }; let committed = durable_typecheck_commit(&idx, &hex, &[], &hex, &own); let after = OUTCOMES.with(|c| c.get()); + set_witness_root(None); let _ = std::fs::remove_dir_all(root); assert!(matches!(got, Ok(DurableTypecheckGet::Unavailable))); assert!(committed.is_ok()); @@ -613,6 +644,29 @@ mod session_reach { assert_eq!(after.miss, before.miss); } + /// No store configured by the invoking workflow: no open, no write, a counted outcome of its own + /// (neither Miss-as-success nor Unavailable). + #[test] + fn an_unconfigured_invocation_never_opens_the_store_and_is_counted() { + let (root, idx) = fixture_index(); + let hex = "0".repeat(64); + set_witness_root(None); + if std::env::var(STORE_CONFIG_ENV).is_ok() { + return; + } + let before = OUTCOMES.with(|c| c.get()); + let got = durable_typecheck_lookup(&idx, &hex, &[], &hex); + let after = OUTCOMES.with(|c| c.get()); + let _ = std::fs::remove_dir_all(root); + assert!(matches!(got, Ok(DurableTypecheckGet::Miss))); + assert_eq!( + after.not_configured_lookup, + before.not_configured_lookup + 1 + ); + assert_eq!(after.store_openings, before.store_openings); + assert_eq!(after.unavailable, before.unavailable); + } + /// The caller's real pool holds the store: the lookup resolves through that same index from a /// foreign cwd, so no second pool is built and nothing is resolved cwd-relative. #[ignore = "live-corpus: resolves and typechecks the live dag+src/v2 closure (minutes); the receipts lane runs these with --ignored, the required unit run does not"] diff --git a/src/v2/workflow/floor_route_gap.dag b/src/v2/workflow/floor_route_gap.dag index 66dede8b866..815d6651336 100644 --- a/src/v2/workflow/floor_route_gap.dag +++ b/src/v2/workflow/floor_route_gap.dag @@ -1042,6 +1042,10 @@ fn floor_route_gap_expectation_chunk_08() -> List { } } +// The test.claim.typecheck_module_store_wet_witness identities that reach a real filesystem (the +// typecheck store's cold/warm, tampered-entry and granted-root controls), enrolled as the same +// triple as the materialization_store_local_wet_witness identities below. +// // The eight test.claim.materialization_store_local_wet_witness identities: the provider spine's // executed controls against a real filesystem (commit and read-back, corruption, wrong kind, // incomplete, a converging and a conflicting second writer, an unavailable store, a batch). Their @@ -1052,6 +1056,12 @@ fn floor_route_gap_expectation_chunk_08() -> List { // held before these rows were written. fn floor_route_gap_expectation_chunk_09() -> List { Cons { + head: FloorRouteGapExpectation { identity: "test.claim.typecheck_module_store_wet_witness.typecheck_module_miss_computes_and_warm_hits_by_real_execution", operation: "DirWithTemplate", ground: NoMockResponse {} }, + tail: Cons { + head: FloorRouteGapExpectation { identity: "test.claim.typecheck_module_store_wet_witness.a_tampered_typecheck_entry_refuses_and_is_not_a_miss_by_real_execution", operation: "DirWithTemplate", ground: NoMockResponse {} }, + tail: Cons { + head: FloorRouteGapExpectation { identity: "test.claim.typecheck_module_store_wet_witness.the_durable_production_open_uses_the_granted_root_by_real_execution", operation: "Read", ground: NoMockResponse {} }, + tail: Cons { head: FloorRouteGapExpectation { identity: "test.claim.materialization_store_local_wet_witness.commit_then_read_back_by_real_execution", operation: "DirWithTemplate", ground: NoMockResponse {} }, tail: Cons { head: FloorRouteGapExpectation { identity: "test.claim.materialization_store_local_wet_witness.corruption_is_an_integrity_refusal_by_real_execution", operation: "DirWithTemplate", ground: NoMockResponse {} }, @@ -1166,6 +1176,9 @@ fn floor_route_gap_expectation_chunk_09() -> List { } } } + } + } + } } // MegaRAC committed-artifact sha256sum: first host effect is shell.Mktemp.Dir (checksum @@ -1173,6 +1186,7 @@ fn floor_route_gap_expectation_chunk_09() -> List { // LocalRepoWetLane exclusion on megarac_spx_ui_surface_artifact_integrity_witness_test.dag. // The catalog-contents Read of the same checkout path is hermetic input, not a route gap. // Memory capture verification consumes DigestStdin over the bytes read once. + fn floor_route_gap_expectation_chunk_10() -> List { Cons { head: FloorRouteGapExpectation { identity: "test.claim.memory_capture_real_execution_witness.w_capture_digest_binds_consumed_content", operation: "DigestStdin", ground: NoMockResponse {} }, tail: Cons { head: FloorRouteGapExpectation { identity: "test.claim.pre_os_capture_replay_witness.w_early_exit_refuses_absence_on_real_bytes", operation: "DigestStdin", ground: NoMockResponse {} }, tail: Cons { head: FloorRouteGapExpectation { identity: "test.claim.pre_os_capture_replay_witness.w_four_real_captures_retain_statement_inventory_and_missing_receipts", operation: "DigestStdin", ground: NoMockResponse {} }, tail: Cons { head: FloorRouteGapExpectation { identity: "test.claim.pre_os_capture_replay_witness.w_truncated_configuration_cannot_support_absence", operation: "DigestStdin", ground: NoMockResponse {} }, tail: Cons { head: FloorRouteGapExpectation { identity: "test.claim.pre_os_capture_replay_witness.w_real_host_userspace_positive_and_truncated_negative", operation: "DigestStdin", ground: NoMockResponse {} }, tail: Cons { head: FloorRouteGapExpectation { identity: "test.claim.megarac_spx_ui_surface_artifact_integrity_witness.committed_spx_ui_surface_hashes_to_the_catalog_identity", operation: "Dir", ground: NoMockResponse {} }, From 1e950f819eec292e9f075657f9118f767bc4fe83 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Wed, 7 Oct 2026 21:21:55 +0000 Subject: [PATCH 20/24] Typecheck store: the selector is a declared .dag fact read through the caller's pool; CI-never-exercises-the-store is a declared frontier; re-entry guard around selector resolve; clippy fix Co-Authored-By: Claude Sonnet 5.5 --- dag/gunbc/typecheck_store_ci_frontier.dag | 6 + dag/gunbc/typecheck_store_selector.dag | 31 +++ .../typecheck_module_store_witness_test.dag | 21 +- .../src/cli_run/typecheck_store_session.rs | 224 ++++++++++++++---- 4 files changed, 236 insertions(+), 46 deletions(-) create mode 100644 dag/gunbc/typecheck_store_ci_frontier.dag create mode 100644 dag/gunbc/typecheck_store_selector.dag diff --git a/dag/gunbc/typecheck_store_ci_frontier.dag b/dag/gunbc/typecheck_store_ci_frontier.dag new file mode 100644 index 00000000000..d8d636b04e5 --- /dev/null +++ b/dag/gunbc/typecheck_store_ci_frontier.dag @@ -0,0 +1,6 @@ +module gunbc.typecheck_store_ci_frontier + +import std.types { NonEmptyStr } +import std.dissolution { DissolutionCondition, unbound_dissolution } + +data typecheck_store_selector_ci_frontier: DissolutionCondition = unbound_dissolution(description: "CI NEVER EXERCISES THE DURABLE TYPECHECK STORE: no generated workflow sets typecheck_store_selector_env_name, so every CI compile reports not_configured and no lookup, hit or restore runs in CI beyond the unit witnesses; this change delivers no CI speedup until this is met. TRIGGER: a persistent store volume is attached to the runners at std.materialization_store_grant materialization_store_durable_root_path (the runner attempt launch attaches it) AND the compiler gate workflow sets the selector to typecheck_store_selector_admitted_value for its compiles. SUFFICIENT FOR: a required CI run whose receipt line reports hit greater than zero on a warm run. Retired by that capability and nothing else." as NonEmptyStr) diff --git a/dag/gunbc/typecheck_store_selector.dag b/dag/gunbc/typecheck_store_selector.dag new file mode 100644 index 00000000000..b36f03397e8 --- /dev/null +++ b/dag/gunbc/typecheck_store_selector.dag @@ -0,0 +1,31 @@ +module gunbc.typecheck_store_selector + +import std.types { String } + +// THE INVOKING WORKFLOW'S CHOICE TO USE THE GRANTED DURABLE TYPECHECK STORE, AS A DECLARED FACT. +// The store root is the one datum std.materialization_store_grant materialization_store_durable_root_path; +// a caller never supplies a path. What a workflow chooses is only whether THIS invocation uses it: +// the environment variable named below, with the one admitted value below. Absence means NOT +// CONFIGURED -- no open, no write attempted, a counted outcome -- never a refused attempt. Any other +// value is a refusal, never silently read as configured or as absent. + +data typecheck_store_selector_env_name: String = "GUNBC_TYPECHECK_STORE" + +data typecheck_store_selector_admitted_value: String = "durable" + +type TypecheckStoreSelection + = TypecheckStoreConfigured + | TypecheckStoreNotConfigured + | TypecheckStoreSelectorRefused { value: String } + +fn typecheck_store_selector_env_name_of() -> String { + typecheck_store_selector_env_name +} + +fn typecheck_store_selection(value: String?) -> TypecheckStoreSelection { + match value { + Absent => TypecheckStoreNotConfigured + Present { value: v } => + if v == typecheck_store_selector_admitted_value { TypecheckStoreConfigured } else { TypecheckStoreSelectorRefused { value: v } } + } +} diff --git a/dag/test/claim/typecheck_module_store_witness_test.dag b/dag/test/claim/typecheck_module_store_witness_test.dag index 5adf1037fad..bc6fb141fca 100644 --- a/dag/test/claim/typecheck_module_store_witness_test.dag +++ b/dag/test/claim/typecheck_module_store_witness_test.dag @@ -20,8 +20,14 @@ import gunbc.typecheck_module_store { typecheck_module_payloads, typecheck_module_entry_bytes, } +import gunbc.typecheck_store_selector { + typecheck_store_selection, + TypecheckStoreConfigured, + TypecheckStoreNotConfigured, + TypecheckStoreSelectorRefused, +} import std.measure { measure_count } -import std.optional { Present } +import std.optional { Present, Absent } import extdeps.external_authority { TranscribedUncited, CitedToAuthority } fn h(v: String) -> Fnv1a64Structural { @@ -75,3 +81,16 @@ test fn a_typecheck_entry_for_the_probe_interface_fits_the_family_entry_budget_u Present { value: n } => measure_count(m: n) <= measure_count(m: typecheck_module_family_entry_budget_unit()) } } + +test fn the_selector_reads_absence_as_not_configured_the_admitted_value_as_configured_and_any_other_as_refused() -> Bool { + match typecheck_store_selection(value: Absent) { + TypecheckStoreNotConfigured => true + _ => false + } && match typecheck_store_selection(value: Present { value: "durable" }) { + TypecheckStoreConfigured => true + _ => false + } && match typecheck_store_selection(value: Present { value: "1" }) { + TypecheckStoreSelectorRefused { value: _ } => true + _ => false + } +} diff --git a/src/v1/stage0/src/cli_run/typecheck_store_session.rs b/src/v1/stage0/src/cli_run/typecheck_store_session.rs index 5c9c7b3923a..45abdb83a9f 100644 --- a/src/v1/stage0/src/cli_run/typecheck_store_session.rs +++ b/src/v1/stage0/src/cli_run/typecheck_store_session.rs @@ -163,18 +163,101 @@ pub(crate) fn own_of_result(result: &TypecheckModuleResult) -> TypecheckModuleOw } } -/// The invoking workflow's explicit choice to use the granted durable store. The root itself is -/// the one declared datum `std.materialization_store_grant materialization_store_durable_root_path` -/// (a caller never supplies a path); this variable only says whether this invocation uses it, so a -/// compile that was not given a store never reaches a host-durable write. -const STORE_CONFIG_ENV: &str = "GUNBC_TYPECHECK_STORE"; +/// The invoking workflow's explicit choice to use the granted durable store is a declared .dag +/// fact (`gunbc.typecheck_store_selector`: the variable's name, its one admitted value, absence = +/// not configured); Rust only reads the environment variable that declaration names and asks the +/// declaration's `typecheck_store_selection` what it means. Decided once per thread. +const SELECTOR_ENTRY: &str = "dag/gunbc/typecheck_store_selector.dag"; +const SELECTOR_MODULE: &str = "gunbc.typecheck_store_selector"; -fn store_configured() -> bool { +thread_local! { + static SELECTED: Cell> = const { Cell::new(None) }; +} + +/// `Ok(Some(true))` configured, `Ok(Some(false))` not configured, `Ok(None)` the declaring module +/// is not in the caller's pool (counted unreachable by the caller), `Err` a refused value. +fn ctx_optional_str( + ctx: &v1_interpreter::InterpContext, + v: Option, +) -> Result { + Ok(match v { + Some(v) => Value::Variant { + type_name: ctx.sym("Optional"), + variant_name: ctx.sym("Present"), + fields: std::rc::Rc::new(vec![(ctx.sym("value"), str_value(v))]), + }, + None => Value::Variant { + type_name: ctx.sym("Optional"), + variant_name: ctx.sym("Absent"), + fields: std::rc::Rc::new(vec![]), + }, + }) +} + +fn store_selected(index: &MultiEntryIndex) -> Result, String> { #[cfg(test)] if WITNESS_ROOT.with(|w| w.borrow().is_some()) { - return true; + return Ok(Some(true)); + } + if let Some(v) = SELECTED.with(|c| c.get()) { + return Ok(Some(v)); + } + if !index.holds_module(SELECTOR_MODULE) { + return Ok(None); } - std::env::var(STORE_CONFIG_ENV).is_ok_and(|v| v == "durable") + let entry = super::workspace_root() + .join(SELECTOR_ENTRY) + .to_string_lossy() + .into_owned(); + // Resolving the selector's own closure typechecks modules, which reach this store's lookup: + // the re-entry guard turns that into a counted re-entry instead of a recursion. + PREPARING.with(|c| c.set(true)); + let resolved = resolve_entry_with_index(index, &entry); + PREPARING.with(|c| c.set(false)); + let (graph, indices) = resolved?; + let ctx = make_eval_context(&graph, indices, ExecutionMode::Hermetic); + let name_value = v1_interpreter::run_in_context_with_args( + &ctx, + "typecheck_store_selector_env_name_of", + &[], + false, + ) + .map_err(|e| format!("typecheck_store_selector_env_name_of: {e}"))?; + let name = match &name_value { + Value::Str(s) => s.to_string(), + other => return Err(format!("selector name not a String: {other:?}")), + }; + let value = match std::env::var(&name) { + Ok(v) => ctx_optional_str(&ctx, Some(v))?, + Err(_) => ctx_optional_str(&ctx, None)?, + }; + let decision = v1_interpreter::run_in_context_with_args( + &ctx, + "typecheck_store_selection", + &[(Some("value".to_string()), value)], + false, + ) + .map_err(|e| format!("typecheck_store_selection: {e}"))?; + let Value::Variant { + variant_name, + fields, + .. + } = &decision + else { + return Err(format!("selection not a variant: {decision:?}")); + }; + let configured = if ctx.sym_eq(*variant_name, "TypecheckStoreConfigured") { + true + } else if ctx.sym_eq(*variant_name, "TypecheckStoreNotConfigured") { + false + } else if ctx.sym_eq(*variant_name, "TypecheckStoreSelectorRefused") { + let v = field_str(&ctx, fields, "value")?; + return Err(format!("{name} has an unadmitted value ({v})")); + } else { + return Err(format!("unknown selection {}", ctx.resolve(*variant_name))); + }; + SELECTED.with(|c| c.set(Some(configured))); + Ok(Some(configured)) } /// `Ok(None)` when the store's own closure is not reachable from the compiler's roots (the entry @@ -250,9 +333,16 @@ pub(crate) fn durable_typecheck_lookup( count(|c| c.reentry_lookup += 1); return Ok(DurableTypecheckGet::Miss); } - if !store_configured() { - count(|c| c.not_configured_lookup += 1); - return Ok(DurableTypecheckGet::Miss); + match store_selected(index)? { + Some(true) => {} + Some(false) => { + count(|c| c.not_configured_lookup += 1); + return Ok(DurableTypecheckGet::Miss); + } + None => { + count(|c| c.unreachable_lookup += 1); + return Ok(DurableTypecheckGet::Unavailable); + } } let imports = list_value( import_interface_hexes @@ -340,9 +430,16 @@ pub(crate) fn durable_typecheck_commit( count(|c| c.reentry_commit += 1); return Ok(()); } - if !store_configured() { - count(|c| c.not_configured_commit += 1); - return Ok(()); + match store_selected(index)? { + Some(true) => {} + Some(false) => { + count(|c| c.not_configured_commit += 1); + return Ok(()); + } + None => { + count(|c| c.unreachable_commit += 1); + return Ok(()); + } } let (interface_text, diagnostics_text) = own_payload_texts(own)?; let imports = list_value( @@ -644,27 +741,62 @@ mod session_reach { assert_eq!(after.miss, before.miss); } - /// No store configured by the invoking workflow: no open, no write, a counted outcome of its own - /// (neither Miss-as-success nor Unavailable). + /// A pool that lacks the selector declaration cannot decide: counted unreachable, no open. #[test] - fn an_unconfigured_invocation_never_opens_the_store_and_is_counted() { + fn a_pool_without_the_selector_module_is_counted_unreachable_and_never_opens() { let (root, idx) = fixture_index(); let hex = "0".repeat(64); set_witness_root(None); - if std::env::var(STORE_CONFIG_ENV).is_ok() { - return; - } let before = OUTCOMES.with(|c| c.get()); let got = durable_typecheck_lookup(&idx, &hex, &[], &hex); let after = OUTCOMES.with(|c| c.get()); let _ = std::fs::remove_dir_all(root); - assert!(matches!(got, Ok(DurableTypecheckGet::Miss))); - assert_eq!( - after.not_configured_lookup, - before.not_configured_lookup + 1 - ); + assert!(matches!(got, Ok(DurableTypecheckGet::Unavailable))); + assert_eq!(after.unreachable_lookup, before.unreachable_lookup + 1); assert_eq!(after.store_openings, before.store_openings); - assert_eq!(after.unavailable, before.unavailable); + } + + /// The compile runs on a large-stack thread in production; the default test thread's 2 MiB cannot + /// hold the selector evaluation over a live pool, so the live tests run their body on one too. + fn on_big_stack(body: impl FnOnce() + Send + 'static) { + std::thread::Builder::new() + .stack_size(256 * 1024 * 1024) + .spawn(body) + .expect("spawn") + .join() + .unwrap_or_else(|e| std::panic::resume_unwind(e)); + } + + /// The declared selector resolves through the caller's real pool: with the variable unset the + /// invocation is NOT CONFIGURED (counted, nothing opened); an admitted value would configure it. + #[ignore = "live-corpus: resolves the live dag+src/v2 pool (minutes); the receipts lane runs these with --ignored"] + #[test] + fn the_declared_selector_reads_absence_as_not_configured_through_the_callers_pool() { + on_big_stack(|| { + let root = workspace_root(); + let idx = build_multi_entry_index(&[ + root.join("dag").to_string_lossy().into_owned(), + root.join("src/v2").to_string_lossy().into_owned(), + ]); + set_witness_root(None); + if std::env::var("GUNBC_TYPECHECK_STORE").is_ok() { + return; + } + let hex = "0".repeat(64); + let before = OUTCOMES.with(|c| c.get()); + let got = durable_typecheck_lookup(&idx, &hex, &[], &hex); + let after = OUTCOMES.with(|c| c.get()); + assert!( + matches!(got, Ok(DurableTypecheckGet::Miss)), + "{:?}", + got.err() + ); + assert_eq!( + after.not_configured_lookup, + before.not_configured_lookup + 1 + ); + assert_eq!(after.store_openings, before.store_openings); + }); } /// The caller's real pool holds the store: the lookup resolves through that same index from a @@ -672,25 +804,27 @@ mod session_reach { #[ignore = "live-corpus: resolves and typechecks the live dag+src/v2 closure (minutes); the receipts lane runs these with --ignored, the required unit run does not"] #[test] fn the_callers_pool_serves_the_session_from_a_foreign_cwd() { - let root = workspace_root(); - let idx = build_multi_entry_index(&[ - root.join("dag").to_string_lossy().into_owned(), - root.join("src/v2").to_string_lossy().into_owned(), - ]); - let saved = std::env::current_dir().expect("cwd"); - std::env::set_current_dir(std::env::temp_dir()).expect("leave workspace"); - let hex = "0".repeat(64); - let got = durable_typecheck_lookup(&idx, &hex, &[], &hex); - std::env::set_current_dir(saved).expect("restore cwd"); - assert!( - matches!(&got, Ok(_)) - || got - .as_ref() - .err() - .is_some_and(|e| e.contains("digest refused")), - "{:?}", - got.err() - ); + on_big_stack(|| { + let root = workspace_root(); + let idx = build_multi_entry_index(&[ + root.join("dag").to_string_lossy().into_owned(), + root.join("src/v2").to_string_lossy().into_owned(), + ]); + let saved = std::env::current_dir().expect("cwd"); + std::env::set_current_dir(std::env::temp_dir()).expect("leave workspace"); + let hex = "0".repeat(64); + let got = durable_typecheck_lookup(&idx, &hex, &[], &hex); + std::env::set_current_dir(saved).expect("restore cwd"); + assert!( + got.is_ok() + || got + .as_ref() + .err() + .is_some_and(|e| e.contains("digest refused")), + "{:?}", + got.err() + ); + }); } } From 5f68516914f51f0a261aa7674b92bb6a7aa3cc9b Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Wed, 7 Oct 2026 23:15:04 +0000 Subject: [PATCH 21/24] Typecheck store: a non-ignored fixture-pool control runs the real reconcile -> lookup -> restore integration Co-Authored-By: Claude Sonnet 5.5 --- .../src/cli_run/typecheck_store_session.rs | 38 ++++++++++++++++++- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/src/v1/stage0/src/cli_run/typecheck_store_session.rs b/src/v1/stage0/src/cli_run/typecheck_store_session.rs index 45abdb83a9f..bbe2070d9f3 100644 --- a/src/v1/stage0/src/cli_run/typecheck_store_session.rs +++ b/src/v1/stage0/src/cli_run/typecheck_store_session.rs @@ -595,13 +595,47 @@ mod real_module_round_trip { #[ignore = "live-corpus: resolves and typechecks the live dag+src/v2 closure (minutes); the receipts lane runs these with --ignored, the required unit run does not"] #[test] fn restore_equals_compute_and_entry_excludes_parent_envs() { + let (entry, roots) = entry_and_roots(); + round_trip("live", entry, roots); + } + + /// The same integration over a SMALL REAL fixture pool (two modules, one importing the other), + /// in the ordinary unit run: cold compile through `reconcile_with_typed_cache` -> durable + /// miss -> real own tail committed; then a FRESH index -> durable hit -> `typecheck_module_restore` + /// -> restored own tail equals the computed one -> the stored entry excludes parent envs. + /// Removing the lookup/restore integration from `reconcile_with_typed_cache` turns this red + /// (no hit), which no store-boundary claim does. + #[test] + fn the_reconcile_integration_misses_commits_then_hits_and_restores_a_fixture_pool() { + std::env::set_current_dir(workspace_root()).expect("enter workspace root"); + let dir = format!("target/gunbc_fx_rt_{}", std::process::id()); + let _ = std::fs::remove_dir_all(&dir); + std::fs::create_dir_all(format!("{dir}/fx")).unwrap(); + std::fs::write( + format!("{dir}/fx/base.dag"), + "module fx.base\n\ndata base_value: Int = 7\n\nfn base_fn(x: Int) -> Int { x + base_value }\n", + ) + .unwrap(); + std::fs::write( + format!("{dir}/fx/top.dag"), + "module fx.top\n\nimport fx.base { base_fn }\n\nfn top_fn() -> Int { base_fn(x: 1) }\n", + ) + .unwrap(); + round_trip( + "fixture", + format!("{dir}/fx/top.dag"), + vec![dir.clone(), "dag".to_string(), "src/v2".to_string()], + ); + let _ = std::fs::remove_dir_all(&dir); + } + + fn round_trip(tag: &str, entry: String, roots: Vec) { let scratch = - std::path::PathBuf::from(format!("/tmp/gunbc_tcstore_{}", std::process::id())); + std::path::PathBuf::from(format!("/tmp/gunbc_tcstore_{tag}_{}", std::process::id())); let _ = std::fs::remove_dir_all(&scratch); std::fs::create_dir_all(&scratch).unwrap(); set_witness_root(Some(scratch.to_string_lossy().into_owned())); outcome_reset(); - let (entry, roots) = entry_and_roots(); let cold_index = build_multi_entry_index(&roots); resolve_entry_with_index(&cold_index, &entry).expect("cold resolve"); From 55e625863abf304b88a8382b8670979e48d6e407 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Wed, 7 Oct 2026 23:58:50 +0000 Subject: [PATCH 22/24] Typecheck store: size control serializes only the largest own tails --- src/v1/stage0/src/cli_run/typecheck_store_session.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/v1/stage0/src/cli_run/typecheck_store_session.rs b/src/v1/stage0/src/cli_run/typecheck_store_session.rs index bbe2070d9f3..728be5e9d09 100644 --- a/src/v1/stage0/src/cli_run/typecheck_store_session.rs +++ b/src/v1/stage0/src/cli_run/typecheck_store_session.rs @@ -708,9 +708,17 @@ mod real_module_round_trip { // cold index also holds the store closure's ~two hundred modules from the session // prepare, and serializing each of those in full is the test's own cost, not the // product's. - let largest_full = cold + // Only the few modules with the largest own tails are serialized in full: the maximum over + // a subset can only be smaller than the true maximum, so the bound below is at least as + // strict, and serializing every restored module in full was a quarter of this test's wall. + let mut committed_modules: Vec<_> = cold .iter() .filter(|(k, _)| warm.iter().any(|(kw, _)| kw == k)) + .collect(); + committed_modules.sort_by_key(|(_, r)| std::cmp::Reverse(own_of_result(r).items.len())); + let largest_full = committed_modules + .into_iter() + .take(3) .map(|(_, r)| { let mut w = CountingWriter(0); serde_json::to_writer(&mut w, &**r).expect("serialize result"); From f5c874869d50921b0adfa0687e712a5c83e59d9f Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Thu, 8 Oct 2026 00:28:55 +0000 Subject: [PATCH 23/24] Regenerate the infer mirror after merging main --- src/v1/stage0/src/v1_compiler_infer.rs | 73 ++++++++++++++++++++------ 1 file changed, 57 insertions(+), 16 deletions(-) diff --git a/src/v1/stage0/src/v1_compiler_infer.rs b/src/v1/stage0/src/v1_compiler_infer.rs index 815847690c2..d9669dab451 100644 --- a/src/v1/stage0/src/v1_compiler_infer.rs +++ b/src/v1/stage0/src/v1_compiler_infer.rs @@ -286,8 +286,8 @@ use crate::v1_std_core::CompilerDiagnostic::{ AlgebraApplicationEvidenceUnavailable, AmbiguousReference, BareNoneNotAdmittedByFieldType, CallArgumentDuplicate, CallArgumentNameUnknown, CallNamedArgOnFunctionValue, CallPositionalDeficit, CallPositionalSurplus, ConstructorCallAdmissionRefused, - EqualityMemberUnjudgeable, EqualityOnFunctionMember, FieldNotFound, - FrontierOccurrenceBudgetExceeded, InternalError, KernelMintShapeMismatch, + EqualityMemberUnjudgeable, EqualityOnFunctionMember, EqualityOptionalityMismatch, + FieldNotFound, FrontierOccurrenceBudgetExceeded, InternalError, KernelMintShapeMismatch, MethodExistenceFrontierAdmitted, MethodExistenceUndecided, MethodNotFound, MissingField, OptionalCastNotEliminated, ReceiverTypeUnestablished, ServiceConfigReferenceJudgmentDeferred, SiblingOperandEffectOrderUndetermined, SoleConstructorViolation, @@ -5126,6 +5126,22 @@ pub fn equality_operand_is_presence_literal( } } +pub fn equality_operand_is_optional( + n: Rc, + source_indices: Rc>>, +) -> bool { + match n.inferred.clone().as_deref().cloned() { + Some(InferredNode::Resolved { node: rt, .. }) => { + ((rt.return_cardinality.clone() == Cardinality::CardOptional) + || ((crate::v1_std_core::qualified_last_segment( + crate::v1_std_core::authored_name_at(source_indices.clone(), rt.clone()), + ) == "Optional".to_string()) + && ((rt.children.clone().len() as i64) == 1))) + } + _ => false, + } +} + pub fn equality_admission_refusal_diag( r: Rc, span: Rc, @@ -5201,18 +5217,32 @@ pub fn equality_admission_diags( { Rc::new(vec![]) } else { - match equality_operand_admission( - lt.clone(), - scope.clone(), - v1_rt::rc_empty_map::(), - 0, - ) { - Some(r) => Rc::new(vec![crate::v1_std_core::make_error_node( - equality_admission_refusal_diag(r.clone(), span.clone()), - scope.module_name.clone(), - )]), - std::option::Option::None => match equality_operand_admission( - rt.clone(), + if (equality_operand_is_optional(left_typed.clone(), source_indices.clone()) + != equality_operand_is_optional( + right_typed.clone(), + source_indices.clone(), + )) + { + { + let side = if equality_operand_is_optional( + left_typed.clone(), + source_indices.clone(), + ) { + "left".to_string() + } else { + "right".to_string() + }; + Rc::new(vec![crate::v1_std_core::make_error_node( + Rc::new(CompilerDiagnostic::EqualityOptionalityMismatch { + optional_side: side.clone(), + span: span.clone(), + }), + scope.module_name.clone(), + )]) + } + } else { + match equality_operand_admission( + lt.clone(), scope.clone(), v1_rt::rc_empty_map::(), 0, @@ -5221,8 +5251,19 @@ pub fn equality_admission_diags( equality_admission_refusal_diag(r.clone(), span.clone()), scope.module_name.clone(), )]), - std::option::Option::None => Rc::new(vec![]), - }, + std::option::Option::None => match equality_operand_admission( + rt.clone(), + scope.clone(), + v1_rt::rc_empty_map::(), + 0, + ) { + Some(r) => Rc::new(vec![crate::v1_std_core::make_error_node( + equality_admission_refusal_diag(r.clone(), span.clone()), + scope.module_name.clone(), + )]), + std::option::Option::None => Rc::new(vec![]), + }, + } } } } From 19c41daf1b90476fffd17eb0ac9b428b6042b1d7 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Fri, 9 Oct 2026 17:09:42 +0000 Subject: [PATCH 24/24] WIP: C2 #13482: the seven review fixes (own-interface entries, lookup-first, --- t_tmp.sh | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 t_tmp.sh diff --git a/t_tmp.sh b/t_tmp.sh new file mode 100644 index 00000000000..6683bb72b89 --- /dev/null +++ b/t_tmp.sh @@ -0,0 +1,8 @@ +mkdir -p /sys/fs/cgroup/gunbcrun; echo 51539607552 > /sys/fs/cgroup/gunbcrun/memory.max; echo $$ > /sys/fs/cgroup/gunbcrun/cgroup.procs +cargo build --release -p v1-compiler --bin gunbc 2>&1 | tail -2 +mkdir -p /var/lib/gunbc/materialization-store; ls -ld /var/lib/gunbc/materialization-store; id -u +( while sleep 5; do echo "MEM $(date +%T) cur=$(cat /sys/fs/cgroup/gunbcrun/memory.current) $(grep -E "^(anon|file|kernel|slab) " /sys/fs/cgroup/gunbcrun/memory.stat | tr "\n" " ") rss=$(grep VmRSS /proc/$(pgrep -nf "release/gunbc run")/status 2>/dev/null) ev=$(grep oom_kill /sys/fs/cgroup/gunbcrun/memory.events) holds=$(ls /var/lib/gunbc/materialization-store | wc -l)"; done > /tmp/mem.out 2>&1 & ) ; GUNBC_TYPECHECK_STORE=durable target/release/gunbc run --source-root dag --source-root src/v2 --entry dag/gunbc/instruments/docs_projection_gate.dag --function regen > /tmp/d.out 2>&1 +echo EXIT=$?; sort -k3 -n /tmp/mem.out | tail -2; tail -14 /tmp/mem.out; grep -a 'Maximum resident\|Elapsed' /tmp/d.out; cat /sys/fs/cgroup/gunbcrun/memory.events +grep -a "typecheck-store" /tmp/d.out | cut -c1-300; grep -a "tmp-restore" /tmp/d.out | tail -6 +grep -av "^advisory\|^ *[0-9]* |" /tmp/d.out | tail -25 | cut -c1-400 +echo ALLDONE