Skip to content

A StaleAdopted live-source compile is dispatched DURING its package's install: source discovery under-matches, CS0246 on committed siblings, and CompileWatcher PARKS the transient failure #4280

Closed

Description

@rbuergi

The defect

When bake-seed finds a NodeType's prebuilt bundle stale, CompileWatcher keeps the adopted build
serving as StaleAdopted and dispatches a compile of the live source (#3583). That compile is
dispatched while the package is still installing, so its source discovery sees only the subset of
the package's shared Source/ siblings that have been written so far. It fails CS0246 on
committed siblings — and CompileWatcher then PARKS the NodeType, so a transient,
install-order-dependent failure becomes the run's terminal verdict.

A later compile of the same type, after the install completes, succeeds in the same run. The
gate never re-reads it.

Measured

Systemorph/MeshWeaver.Reinsurance main run
34804118498,
job test-repos / Gate shard 1/1, platform set 3.0.0-ci.8547 (core
1c1d62adffc1d7d0545f1600898b89ca475e6ea0).

04:11:44.095  ── Ifrs17: installing 403 file(s)…
04:11:45.778  bake-seed  Prebuilt assembly ADOPTED for Ifrs17/RawVariable … module version 1.0.7
04:11:45.783  CompileWatcher  [AdoptedSourceStamp] Ifrs17/RawVariable: the source moved past the
              adopted build (bundle fingerprint aa82137e45651a6c, live bebbfc488bd3c8bd) and the two
              are COMPATIBLE by module version (1.0.7 over (unknown)) — the build keeps serving as
              StaleAdopted (#3583); a compile of the live source is dispatched
04:11:45.868  bake-seed  Prebuilt assembly for Ifrs17/RawVariable DECLINED before writing (#2813):
              the bundle records source fingerprint aa82137e45651a6c but the live sources are
              bebbfc488bd3c8bd … Rebake this pack[age]
04:11:46.109  Compiling assembly for Ifrs17_RawVariable (disk, 0 NuGet refs)
04:11:47.772  fail: Failed to compile assembly for node 'Ifrs17/RawVariable' … CS0246 'PeriodType' …
04:11:48.008  fail: NodeType 'Ifrs17/RawVariable' PARKED after compile failure — further activations
              serve the cached error without recompiling (failure contained, no retry storm)
04:11:50.038  RED Ifrs17/RawVariable [compile:Failed render:Skipped tests:Skipped]
04:11:50.777  [PackageInstaller] warmed installed root Ifrs17        ← the install ends HERE
…
04:12:03.338  Compiling assembly for Ifrs17_RawVariable (disk, 0 NuGet refs)
04:12:03.974  info: Successfully compiled assembly for Ifrs17/RawVariable to
              …/compilation-cache/Ifrs17_RawVariable_…/Ifrs17_RawVariable.dll     ← it COMPILES
…
04:12:46.459  RED Ifrs17/RawVariable: compile=FAILED(Error) render=skipped tests=skipped

The compile is dispatched 1.7 s into a 6.7 s install and fails 3.0 s before the install ends.
The other two affected types are tighter: Planning install starts 04:11:57.861 and its compile
fails 04:11:58.847 (0.99 s in); Underwriting starts 04:12:37.324, fails 04:12:38.224
(0.90 s in).

The under-match

The premature compile prints its own source discovery:

Executed source queries (4):
  - namespace:Ifrs17/RawVariable/Source scope:subtree nodeType:Code
  - path:Ifrs17/Source nodeType:Code
  - namespace:Ifrs17/Source scope:subtree nodeType:Code
  - namespace:Ifrs17/RawVariable/Test scope:subtree nodeType:Code
Matched Code nodes (8):
  - Ifrs17/RawVariable/Source/RawVariable
  - Ifrs17/RawVariable/Test/RawVariableTests
  - Ifrs17/Source/Arithmetics
  - Ifrs17/Source/Consts
  - Ifrs17/Source/DataCube
  - Ifrs17/Source/IScopeRegistryFactory
  - Ifrs17/Source/QueryReach
  - Ifrs17/Source/ScopeConfiguration

Ifrs17/Source/ holds 12 committed .cs files; 6 matched. Of the 6 that did not, Enums.cs
declares PeriodType, CashFlowPeriodicity and InterpolationMethod and IScope.cs declares
IScope — exactly the CS0246 names in the failure. Ifrs17/RawVariable/Test/RawVariableTestsArea
is missing from query 4 while its own-folder sibling RawVariableTests matched, producing
CS0103: The name 'RawVariableTestsArea' does not exist in the current context.

Note that the live source fingerprint bebbfc488bd3c8bd in step 2 is itself computed over this
partial set, which is why the bundle is declined in the first place: the decline and the compile
failure have the same cause, one instant apart.

Why it is a race and not a property of any type

The affected set is drawn fresh every run, and in each run it is exactly the set bake-seed
declined, CompileWatcher parked, and the compiler failed — three independently printed lists:

run declined = parked = compile-failed
34804118498 (09-14 04:11Z) Ifrs17/RawVariable, Planning/PlanVersion, Underwriting/Decision
34770922648 (09-13 17:13Z) Ifrs17/RiskDriver, Reinsurance/Cedent, Reinsurance/Country, Reinsurance/RiskCategory, Signature/DeepSignCredential

The two sets are disjoint, and Signature/DeepSignCredential is not a type MeshWeaver.Reinsurance
owns — so this is not a satellite's content.

Four candidate discriminators were checked and falsified: the // <meshweaver> heading block
(Enums.cs has one and is missing; IScopeRegistryFactory.cs has none and matched), file size,
node-Id collisions across packages (QueryReach collides 9× and matched, Enums is unique and is
missing), and a stale manifest.lock (gen-manifests.py --check → "✓ all manifest.lock files up
to date"
).

Two separable fixes

  1. Do not judge a NodeType's sources mid-install. The StaleAdopted fingerprint comparison and
    the live-source compile it dispatches must not run against a package whose install has not
    completed — either gate them on the package's completion, or derive the live fingerprint from the
    manifest rather than from a live nodeType:Code query.
  2. A compile that failed on an incomplete source set must not be PARKED. The park is what turns
    a transient failure terminal: Ifrs17/RawVariable compiled cleanly 16 s later in the same
    process and the verdict never moved. Park on a compile whose inputs were complete; retry (or at
    minimum re-evaluate) one whose inputs were not.

Related

Impact

MeshWeaver.Reinsurance main cannot go green while this bites, and the repo cannot fix it: the
affected set is different every run and includes types it does not own. Its
Tests-area ratchet was additionally converting the parked types' tests=skipped into an
instruction to write test suites those types already ship — corrected in
Systemorph/MeshWeaver.Reinsurance#209.

🤖 Generated with Claude Code

Activity

  1. rbuergi commented on Sep 14, 2026

    @rbuergi
    ContributorAuthor

    Cross-reference from the other half — #4295 (the in-memory children index handing a subtree query a SHORT listing that the synced query then caches for good). Not touching this issue's three files; stating the composition so the two are not merged into one:

    #4280 (this) #4295
    where ApplyAdoptedSourceStamp / CompileWatcher / park registry InMemoryStorageAdapter children index (#4169)
    what the fingerprint is judged 0.5–1.7 s into an install; the park keeps the first verdict the walk under the source query is handed fewer children than the store holds, for a directory written minutes ago
    tell Matched Code nodes GROWS between compiles; a later compile succeeds identical after the install ended; a later compile fails identically

    Your Reinsurance runs show the first tell — on PR#209 job 103902719770, Ifrs17/ReportingNode matched 13 on compiles 1 and 2 (0.5 s apart) and succeeded on compile 3 after the idempotence re-install; same for AggregateExposureProfile and AuthorityRung. The Plugins run (#1823) shows the second: 39 of 49 on both compiles, the second one 15 s after the install ended, with 8 of the missing 10 in Store/Core/Source — written three minutes earlier and read back as "unchanged" by Store's re-install. Note that the Reinsurance log cannot separate the two on its own: the CreateOrUpdate reply precedes the debounced per-node persist, so "N seconds after Installed" says nothing about the store.

    Neither fix alone clears both runs: yours leaves Plugins red (its second compile still saw the frozen 39), mine leaves Reinsurance red on the first park. Doc page for the index half: Doc/Architecture/InMemoryChildIndexConsistency (in #4295).

    🤖 Generated with Claude Code

  2. rbuergi commented on Sep 14, 2026

    @rbuergi
    ContributorAuthor

    Reopening — #4293 merged past a review finding that says half of it cannot fire, and I have verified the finding on main

    #4293 merged at 08:44:48Z (812d82b684) with two Copilot findings unanswered; the second is correct and is load-bearing.

    The finding (NodeTypeCompilationHelpers.cs:4463): the park-lift half — "at the terminal failure stamp, if Result.CompiledSources differs from CurrentSourceVersions, lift the park and flip Pending with the live set" — compares SourcesMovedSince(Result.CompiledSources, …). But the failure NodeCompilationResult never carries CompiledSources.

    Verified on origin/main after the merge, src/MeshWeaver.Compiler.Pipeline/MeshNodeCompilationService.cs ~1240–1255:

    var (assemblyLocation, _, log, sources) = attempt;
    if (string.IsNullOrEmpty(assemblyLocation))
        …
        return BuildFailureDiagnostics(node, sources)
            .Select(diags => (NodeCompilationResult?)new NodeCompilationResult(
                null, [], log, Diagnostics: diags));

    sources — the exact set the failed compile consumed — is destructured on the first line and discarded on the return. So on the normal Roslyn-diagnostic failure (the CS0246 that Reinsurance#209 showed on three types and Plugins#1823 on Essentials/OperationRequest), the comparison is SourcesMovedSince(null, def.CurrentSourceVersions), and a verdict formed under a moved set still rests at Error. Only the deferral half (CanJudgeAdoption waiting on AdoptedSourcePaths) is live.

    The fix is small and the value is already in hand: populate CompiledSources from sources on the failure result at that return. The success path's own comment three lines below says why this is the right source — "one snapshot per compile, so CompiledSources records what was compiled instead of a second, independently raced observation" — and that reasoning applies equally to the failure.

    The first finding (:1286) also stands: the completeness witness covers manifest sourceVersions only, and both bake paths populate that from query-resolved roots and omit ResolvedIncludePaths (TreeBake.cs:114–119, 452–479). An @@-included Code node can still be landing while every recorded path is present, so CanJudgeAdoption returns true and the premature compile dispatches anyway. That is a hole in the deferral half too, narrower than the first.

    Why it merged anyway: the findings arrived while the PR was in the queue and it merged on green — the fourth instance today of core #4299 (review advisory on main because checks outrun the reviewer). Not a fault of #4293's author; the lane did what it does.

    Reopening so this does not read as fixed. The delivery freeze is now: #4295 (the index race, in CI) + this (populate CompiledSources on failure; cover ResolvedIncludePaths in the witness) + the Roll.


    🤖 Generated with Claude Code

  3. rbuergi commented on Sep 14, 2026

    @rbuergi
    ContributorAuthor

    Both review findings on #4293 are now in follow-up PRs, each opened as a draft so the Copilot review lands before the lane can merge (#4299):

    🚨 The obvious wrong fix for finding 1, stated so the next reader does not reach for it: adding include paths to sourceVersions alone would defer every include-bearing type forever, because the live snapshot (CurrentSourceVersions) never contains includes by design.

    With #4295 (the index race) this is the delivery-freeze set; the Roll of memex/memex-cloud (Plugins#1823) is the remaining half and is the maintainer's.

    🤖 Generated with Claude Code

  4. rbuergi commented on Sep 14, 2026

    @rbuergi
    ContributorAuthor

    Roll target shared with MeshWeaver.Plugins#1823: the first sealed core CD set whose commit range includes #4295 (9f3598b7017c9bd5f048ff2e9fa9b6e8b90d2662, merged 09:02Z — CD run ≥ 8576), #4293 (merged) AND #4302/#4304 once they land. Both issues now point at the same target; the portals stay on 8411 until that Roll.

  5. added 2 commits that reference this issue on Sep 14, 2026
  6. rbuergi commented on Sep 14, 2026

    @rbuergi
    ContributorAuthor

    Closing — both halves are on main, and the review finding that reopened this is fixed

    half PR merge sha what
    the failure result carries the set it consumed #4302 63c3c49150 SourcesMovedSince can now fire on the CS0246 path — the comparison was against null before, so the park-lift was inert exactly where it was needed. Pinned by a real CS0246 compile through the mesh compiler: red on the old code, green with the fix.
    the completeness witness covers @@ includes #4304 f639c7130b27d22650e6bd9b6ce2b8650ac8f26b bundle carries sourceIncludes per assembly; CanJudgeAdoption waits on both lists; an include's arrival now wakes the watcher (Copilot found the deferral was correct but unbounded — 72 s timeout; converges in 12 s). The rejected shortcut — adding include paths to sourceVersions alone — would have deferred every include-bearing type forever, since the live snapshot never has them.

    The original #4293 (812d82b684) shipped the deferral itself; these two are its review findings, both verified real, both landed with the shape the repo asks for (draft-first, findings answered before arming).

    What this composes with, stated once more so the Roll is not made short: the seal freeze was two defects. This issue is the mid-install judgement. The other is the in-memory child-index race — core #4295 (9f3598b701) — which made the bundle's fingerprint decline false by handing the query a partial listing. Neither alone clears both measured runs (Plugins#1823 shows the index race's tell: never recovered; Reinsurance#209 shows this issue's tell: recovered on compile 3).

    The Roll target, measured: CD run #8580 (ffae2e1b7, sealed 09:37Z) carries 9f3598b701 and 63c3c49150 but not f639c713 — #4304 merged after it. So the target is the first sealed main-cd run on a head at or after f639c7130 — the next one. A roll onto #8580 would carry two of three and leave the include case open. That decision is the maintainer's; this is only the sentence.


    🤖 Generated with Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions