Skip to content

Design for review: typed-module materialization across runs (v1 → v2 relief) - #11779

Merged
briansrls merged 2 commits into
mainfrom
design/typed-module-cross-run-materialization
Sep 20, 2026
Merged

briansrls merged 2 commits into
mainfrom
design/typed-module-cross-run-materialization

Conversation

@briansrls

Copy link
Copy Markdown
Contributor

Design only — one markdown file, no code, no generated artifact touched. For review before anything is built.

The measurement it answers

Required CI per push, after the consolidation: fold 21 min, drift gate 6.3 min. Inside the fold:

Phase Wall
parse ~2 min
namespace-wave admission ~1.7 min
strict preparation — resolve 1,733 modules 8.3 min
discovery + site projection 3.2 min
reach probes + 400 claims ~2 min (claims themselves: 53 s)

CPU equals wall in every phase, and the floor's teardown drops process_resolve_index, process_resolve_store and the emit memo at exit. The corpus is re-derived once per process, and the drift gate is a second process over the same corpus.

The shape

One ladder rung, not a cache:

  • a runner-host frame (SharedStateFrame), broader than the executor process and narrower than the fleet;
  • a second CacheProvider over the same identity the in-process share covers — union-typecheck-store(dag,src/v2) — with CasTier { keying: ContentKeyed } and persistent_retention.

Modelling it is what forces the properties a disk cache would have skipped: retention_admission refuses ReleasedNever with CapacityUnbounded or CapacityUnobserved, so bounded capacity and observed occupancy are obligations, not options.

Soundness comes from PureComputation: same text, same dependency closure, same compiler ⇒ same typed result. The key is spelled out component by component with what breaks if each is omitted — the compiler binary digest included, which is why a src/v1 Rust change gets no relief and a .dag corpus change gets nearly all of it.

It reuses what's already there

shared_typecheck_store.rs ("S2a increment C") already serialises typed modules by authored name so a different process can decode them against its own intern table. Cross-run is that same problem with a longer gap; only the backing changes. Its counters become the ladder's required observation.

Named non-goals: threading the resolver (13,313 Rc< vs 40 Arc< in src/v1; even parse_source returns Rc<ParseResult>), re-sharding the fold (forbidden by the fold's own authority), and hosted runners for the fold (20.2 GiB peak vs 16 GB).

Expected relief, on the measured basis

Now Warm store
Strict preparation 8.3 min ~1 min
Drift gate 6.3 min ~2 min
Fold 21 min ~12 min
Push ~32 min ~20 min

Four decisions for you (§8)

  1. Store location, and whether ctrl's reclaimer prunes it or it evicts for itself.
  2. The capacity limit and at-capacity policy — the ladder refuses the provider without them.
  3. Warm-seeding from main, or PRs warming it themselves.
  4. Whether verify mode is a scheduled job or an operator instrument.

Rollout in §9 is staged so steps 1–2 land dark (GUNBC_TYPED_STORE_PERSIST, default off), step 3 is the measurement, step 4 arms it, and step 5 widens required_gate_prefixes back as preparation gets cheaper.

🤖 Generated with Claude Code

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 20, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-20T02:53:54.702505Z 9cedb0d PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9cedb0d43a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +133 to +136
At the seam the cross-worker store already uses — `index_insert_typed` writes through, a miss
decodes from the store — so there is no second cache layer and no new representation. The existing
transport is reused as-is; only its backing changes from an in-process map to a host directory of
content-addressed entries.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Route the single-thread fold through the persistent store

In the target scenario the nominal fold runs with one thread, but the existing store this design proposes to reuse is armed only when the adaptive worker width exceeds one; dag/gunbc/floor/floor_materialization.dag explicitly says width 1 retains the private per-index Rc cache, and index_get_typed uses that private cache whenever cross_worker_store is absent. Merely changing the shared map's backing therefore leaves the measured 21-minute fold unable to read or populate the persistent store, so the claimed warm-run relief cannot occur unless the design explicitly adds a width-1 persistent-store route and accounts for its serialization costs.

Useful? React with 👍 / 👎.

Comment on lines +159 to +161
A second, cheaper check belongs in CI itself: the fold already prints a subject digest
(`digest=96e8b36e00b61c7a`). A warm run and a cold run of the same commit must print the same
digest.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Compare typed outputs instead of the input subject digest

This warm/cold check cannot detect a stale typed-module hit: PreparedSubject.subject_digest is computed before strict resolution by subject_digest_for_closure, which hashes the closure source content and compiler transform content, not the resulting TypecheckModuleResult. Warm and cold runs of the same commit will therefore print the same subject digest even if the cache returns a wrong typed result; the CI falsifier needs a digest of the typed outputs or an equivalent verdict/result comparison.

Useful? React with 👍 / 👎.

```
data floor_runner_host_frame: Frame = Frame {
name: "runner-host",
kind: SharedStateFrame

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Model recurring runs with an emergent frame

SharedStateFrame models rewireable plurality within one shared-state workspace/process, whereas std.materialization_ladder explicitly assigns recurring CI invocations to UnboundedSiblingsFrame. With the proposed kind, successive processes are classified as authored duplication rather than expected-emergent cross-run demand, so the ladder does not derive the persistent-cache obligation this row is intended to discharge; use an UnboundedSiblingsFrame in the host/process frame path.

Useful? React with 👍 / 👎.

id: "host_persisted_typecheck_store",
scope: [floor_runner_host_frame],
coverage: CoversIdentities { identities: [floor_typecheck_store_identity] },
tier: CasTier { keying: ContentKeyed },

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use the filesystem provider tier

The proposed implementation is a per-host directory, but std.materialization_ladder.tier_axes defines CasTier as RemoteNetwork and ArtifactTier as LocalFilesystem. Declaring this row as CasTier therefore gives the modeled provider a placement it will not implement and contradicts the stated no-cross-host boundary; this host-local content-addressed directory should be an ArtifactTier provider.

Useful? React with 👍 / 👎.

gunbc-ci-auto-heal and others added 2 commits September 20, 2026 10:39
Hand-authored design for review, not a projection: no generated_artifact row produces this file,
and on acceptance its declarations move into gunbc.floor_materialization and this file gives way to
that module's projection.

The subject is the measurement. Required CI spends 21 minutes in the fold and 6.3 in the drift
gate; inside the fold, 8.3 minutes is strict preparation resolving a 1,733-module closure while the
400 claims it then executes take 53 seconds. CPU equals wall in every phase, and the floor's
teardown names the three caches it drops at exit, so the corpus is re-derived once per process.

The design adds one ladder rung rather than a cache: a runner-host frame, and a second provider
over the SAME identity the in-process share already covers, with CasTier{ContentKeyed} and
persistent_retention. The ladder's admission rules then force what a disk cache would have skipped,
since ReleasedNever refuses CapacityUnbounded and CapacityUnobserved: bounded capacity and observed
occupancy. Typed-module resolution is PureComputation, which is what makes content keying sound;
the key is spelled out with what breaks if each component is omitted, compiler binary digest
included.

It reuses what exists: shared_typecheck_store.rs already serializes typed modules by authored name
so another process can decode them, which is cross-run with a longer gap, and its counters become
the observation the ladder demands. Threading the resolver (13,313 Rc against 40 Arc) and
re-sharding the fold are named non-goals.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… corrections

Four findings, all four fair, two of them fatal to the design as written.

THE SEAM DOES NOT REACH THE FOLD. The shared typed store is armed only when the adaptive worker width
exceeds one, and floor_materialization says width 1 keeps the private per-index Rc cache.
index_get_typed takes that fallback -- with a counter for it -- so a persistent backing behind the
shared store would never have been read or populated by the nominal fold, which runs at width 1. No
warm relief could have occurred. The design now arms the persistent provider independently of width,
and does it as TWO LEVELS rather than one: the store's own rule is that when armed it becomes the sole
typed-cache authority and reads decode bytes, which at width 1 would replace a cheap Rc clone with a
decode on thousands of reads and could make a warm run slower than a cold one. L1 stays the in-process
map; L2 answers the first read of a module a previous run resolved. The added encode cost is named as a
first-class unknown with a measurement that can fail the design.

THE FALSIFIER COULD NOT FAIL. PreparedSubject.subject_digest is computed before strict resolution over
closure source and compiler transform content -- it hashes the inputs, so warm and cold runs of one
commit agree even if the store returns a wrong typed result. Comparing it was spec-without-execution.
The check now compares outputs: a digest over the typed results, plus the floor's existing disposition,
claim-cost and cross-claim-demand receipts, which must be byte-identical warm against cold.

THE FRAME KIND DECIDED WHETHER THE LADDER DERIVES THE CACHE OR FORBIDS IT. SharedStateFrame puts
recurring runs in plurality state (1), authored duplication, whose answer is 'rewire/Share, never a
cache'. Recurring CI invocations are state (3) under UnboundedSiblingsFrame, which is what obligates a
cross-run cache up front.

THE TIER NAMED A PLACEMENT NOTHING IMPLEMENTS. tier_axes grounds ArtifactTier at LocalFilesystem and
CasTier at RemoteNetwork; a per-host directory is the former. CasTier also contradicted this row's own
no-cross-host boundary. A fleet-wide store would be a different provider with a coherence question
this one does not have.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@briansrls
briansrls force-pushed the design/typed-module-cross-run-materialization branch from 2500238 to df88404 Compare September 20, 2026 10:39
@briansrls
briansrls added this pull request to the merge queue Sep 20, 2026
Merged via the queue into main with commit 4dd944e Sep 20, 2026
4 checks passed
@briansrls
briansrls deleted the design/typed-module-cross-run-materialization branch September 20, 2026 11:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant