Skip to content

fix: a deleted NodeType gates portal readiness forever (#1391) - #1396

Merged
rbuergi merged 5 commits into
mainfrom
fix/1391-nosources-default-queries
Aug 13, 2026
Merged

rbuergi merged 5 commits into
mainfrom
fix/1391-nosources-default-queries

Conversation

@rbuergi

@rbuergi rbuergi commented Aug 13, 2026 •

Copy link
Copy Markdown
Contributor

Part of #1391. Independent of #1393 (#1387) — different root, evidence in the issue.

The rule, stated

"No sources matched" is not the same as "sources matched and did not compile." The first is a CONTENT fact and must not gate readiness; the second is a verdict about this image and must.

PreWarmStatus.NoSources exists for exactly the first case and is deliberately non-gating. Almost nothing could reach it.

🚨 And "no sources matched" needs a second witness, which the first revision of this PR got wrong — see Round two below. The sources must have been LOST, not merely absent.

The defect

Both classifiers additionally required d.Sources is { Count: > 0 }:

DynamicTypePreWarmer.ClassifyCompileFailure activation-driven sweep
NodeTypeBatchBake.StampAndReport batch bake

An empty NodeTypeDefinition.Sources does not mean "configuration-only". It means "uses the DEFAULT queries" — namespace:{path}/Source scope:subtree, see CodeQueryResolver.DefaultSources — which is how very nearly every NodeType in a real mesh is authored.

So the conjunct made NoSources unreachable for essentially the whole population. Delete a type's Source/ subtree and it is compiled against nothing, the configuration lambda's resulting CS0246/CS1061 are recorded as an image regression, and a node that no longer exists holds portal readiness on every pod boot.

That is what Edu/Course was doing to memex — failing on 100% of 23 sampled boots, with a diagnostic naming code that no longer exists:

Executed source queries (2):
  - namespace:Edu/Course/Source scope:subtree nodeType:Code
  - namespace:Edu/Course/Test scope:subtree nodeType:Code
Matched Code nodes (0):
  (none) — the configuration lambda cannot reference types because no source
  files were included.
CS0246: The type or namespace name 'CourseContent' could not be found
CS1061: 'LayoutDefinition' does not contain a definition for 'AddCourseLayoutAreas'

Not a new policy — a restored one

SourceSnapshot.cs already documents the intended three-outcome contract, and it is unambiguous:

Established, EMPTY — every query answered and matched nothing. That is a CONTENT fact (the sources were deleted, or the type is configuration-only), so the compile proceeds and any failure classifies as PreWarmStatus.NoSources, which correctly does not gate a rollout.

The extra conjunct had silently drifted from that. A genuinely configuration-only type is folded in on purpose, exactly as that contract says: with no Code nodes at all there is nothing for an image to regress, so a failure in its configuration lambda is content drift for its owner to fix, not a reason to stall everyone else's rollout.

What is deliberately NOT changed

  • 🚨 The gate. This is a classification fix, never "compile errors stop gating". A type whose sources are still present and do not compile keeps a non-empty snapshot and keeps gating.
  • The two-witness rule (Batch bake's source discovery is truncated by the default query limit (50) — 169/237 types got zero sources in production #1216). The batch still requires BOTH its own resolved snapshot AND the persisted CurrentSourceVersions, so a starved discovery cannot reclassify itself as deleted content.
  • A NULL snapshot still gates — "the watcher never seeded" is not "the sources are gone".
  • NodeTypeBatchBake.Assemble's unestablished guard keeps its DeclaresSources exemption. That one is load-bearing and I left it alone on purpose: a legitimately source-less type must not abandon the whole batch, and the persisted snapshot is what tells the two apart at classification time. Fixing it too would make any configuration-only type abandon every batch.

A tested behaviour is being changed, deliberately

ClassifyCompileFailure_NoDeclaredSources_StaysCompileError asserted the old behaviour, on the premise that an empty Sources means the type "compiles from its Configuration alone". That premise is wrong about the domain — it describes a tiny minority as if it were the definition. The test is replaced rather than kept, with the reasoning recorded where the next reader will find it.

Round two: an empty snapshot alone was too coarse

The first revision keyed only on CurrentSourceVersions being empty. CI caught that with three reds in DynamicTypePreWarmerTest, and they were right — this was a defect in my classifier, not in the fixtures.

All three build their "broken" type as Configuration = "config => this is not valid C# at all ((" with no sources. That is not deleted content; it is a type that is broken as authored, and it must gate. Reclassifying it also downgraded its dependents' cascade from the gating UpstreamFailed to the non-gating UpstreamContentBroken — a real weakening of the gate, which is exactly what those tests exist to catch.

The second witness is LastCompileSucceededAt: a type that has NEVER built cannot have LOST anything. It survives a failure — ApplyCompileFailure clears CompiledSources but never the success stamp — so it is readable exactly where the verdict is formed.

The three failing tests now pass with their assertions and fixtures completely untouched (git diff origin/main...HEAD -- .../DynamicTypePreWarmerTest.cs is empty). The production rule got stricter; nothing was loosened to make a test green.

Verified against production, 11/11, on two independent durable signals

population lastCompileSucceededAt verdict
Edu/Course + 3 × Doc/* non-null reclassify — their sources genuinely were deleted
6 × FutuRe/{AmericasIns,AsiaRe,EuropeRe}/{LineOfBusiness,TransactionMapping} null gate — no sources key and no Source/ subdirectory in the repo, yet their lambdas reference LineOfBusiness / AddLineOfBusinessLayoutAreas. Broken as authored: the real-world twin of the test fixtures
Type/article null gate — never compiled. The importer derives the node path from the FILENAME while the Code node lands under the id (Type/Article/Source/Article, capital A)
2 × Northwind/{AnalyticsCatalog,Product} n/a gate, on the non-empty snapshot as before

mesh_node_history corroborates the same 3/7 split independently — those 7 never recorded a source across 44–47 versions each.

Blast radius: 4 of 16 failures reclassify. memex-cloud keeps 9 gating, memex keeps 1 (BinaryClickerV2/BinaryToggle, null snapshot, correctly gating). The ones left gating deserve to.

Two traps recorded in the code so they are not "simplified" back

  • THREE shapes of CurrentSourceVersions exist in production, not two: populated, explicitly {}, and ABSENT (SQL NULL, on public.mesh_nodes rows). Only the middle may reclassify. is { Count: 0 } is right by construction — a C# property pattern never matches null — so absent gates. Do not rewrite it as a bare .Count == 0, which throws.
  • CompiledSources would be the more precise evidence but is useless as history: written only on success and nulled on failure, so every currently-failing type in production lacks it — including the three that demonstrably compiled in June.

Also: the inverse hazard, documented

NodeTypeCompilation.md covered deleting a framework symbol in-mesh code calls. Adding one has the same invisibility: #1386 moved an Article extractor into compiled framework code as MarkdownBody.Of, and the in-mesh callers shipped before the image did — ACME/Article, Cornerstone/Article and Northwind/Article sat at CS0103: The name 'MarkdownBody' does not exist on memex-cloud until the portal self-updated to ci.3435, at which point all three returned to Ok on their own. No action needed; the rule ("the framework half must land first") is now written down.

Verification

Fail-before / pass-after:

before after
ClassifyCompileFailure_DefaultQueriesWithEmptySnapshot_IsNoSources FAIL PASS
DeletedDefaultQueryType_DoesNotGateReadiness (end-to-end through the gate) FAIL PASS
ClassifyCompileFailure_DefaultQueriesWithSourcesStillPresent_StaysCompileError PASS PASS
BrokenDefaultQueryTypeWithLiveSources_StillGatesReadiness (end-to-end) PASS PASS

Both regression guards pass in both states — which is what a guard should do; they exist so the fix cannot widen later.

Plus the round-two boundary, both directions:

before round two after
ClassifyCompileFailure_EmptySnapshotButNeverBuilt_StaysCompileError FAIL PASS
NeverBuiltSourcelessBrokenType_StillGatesReadiness (end-to-end) FAIL PASS
the three DynamicTypePreWarmerTest reds, fixtures UNTOUCHED FAIL PASS
  • 67/67 across DynamicTypePreWarmerTest, NodeTypeBakeGateStateTest, SourceDiscoveryUnavailableTest, SourceSnapshotEstablishmentTest, NodeTypeBatchBakeDiscoveryInvariantTest.
  • DocumentationLinkIntegrityTest + WhatsNewEntryIntegrityTest green.
  • Release -warnaserror clean: MeshWeaver.Hosting, MeshWeaver.Hosting.Monolith.Test.

SourceDiscoveryUnavailableTest.StarvedSourceDiscovery_IsNotACompileVerdict_WhileARealCompileErrorStillIs was raised as possibly sharing a root: it does not. Its broken control does create a {path}/Source/code node, so its snapshot is non-empty and this change cannot reach it — green locally throughout.

Confirmed against the durable row — the discriminator is 11 for 11

The one thing that could have made this fix a no-op for the real case is Edu/Course carrying a stale non-empty currentSourceVersions instead of an explicitly empty one. It does not. From edu.mesh_nodes (db memex, read-only SELECT):

SELECT jsonb_typeof(content->'currentSourceVersions'),
       content->'currentSourceVersions',
       (SELECT count(*) FROM jsonb_object_keys(content->'currentSourceVersions'))
FROM edu.mesh_nodes WHERE path='Edu/Course';
--  object | {} | 0

Type object, key count 0 — and sources is SQL NULL, i.e. default queries. So this fix does reclassify it.

The node itself is state=2 (ACTIVE); what is gone is its Source/ subtree — 0 rows in edu.code, and zero rows in edu.mesh_node_history for those paths, so a hard delete rather than a recycle. Edu/_GitSync dates it: the last version with a non-empty map is v282 at 2026-07-11 18:49:45.545+00, matching lastSyncedAt 2026-07-11T18:49:45.618+00 (sha 4ce635b). The GitSync import of the Plugins repo's Edu/ subdirectory removed the sources and left the NodeType node Active. v283→v301 are bake retries against nothing.

Across both portals the discriminator separates cleanly, with the DB LIKE '{path}/Source/%' count and the runtime Matched Code nodes (N) agreeing on every row:

population keys in currentSourceVersions verdict
Edu/Course (memex) 0 content-broken
10 memex-cloud types under MeshWeaver/samples/Graph/Data/ 0 content-broken
5 memex-cloud types (*/Article, Northwind/{AnalyticsCatalog,Product}) 2, 2, 2, 26, 2 still gates — genuine CS0103/CS0246 breakage

11 for 11, zero false positives, no overlap. The five REAL failures keep gating, which is the property the regression guards exist to protect.

The third shape, already handled

public.mesh_nodes holds rows whose currentSourceVersions is absent / SQL NULL — a third shape beyond "empty" and "populated". No change needed: d.CurrentSourceVersions is { Count: 0 } is a C# property pattern, which never matches null, so absent ⇒ CompileError ⇒ still gates. That is the intended "the watcher never seeded is not the sources are gone" rule, pinned by ClassifyCompileFailure_DefaultQueriesWithNullSnapshot_StaysCompileError.

Still open on #1391 (not addressed here)

The GitSync importer leaves a NodeType node behind when its sources go away or land at a different path. Edu/Course is one instance; MeshWeaver/samples/Graph/Data/Type/article is another and is a plain platform bug — the importer derives the node path from the FILENAME (article.json) while the Code node lands under the id (Type/Article/Source/Article), so the default query can never match.

This PR stops those from holding readiness hostage. It does not stop the importer creating the condition, and it does not clean up the existing rows. Both are separate, and any data repair is the owner's call to run — nothing was mutated during this investigation (SELECT-only). The repair options are written up on the issue.

🤖 Generated with Claude Code

"No sources matched" is not the same as "sources matched and did not compile."
The first is a CONTENT fact and must not gate readiness; the second is a verdict
about this image and must. PreWarmStatus.NoSources exists for exactly the first
case and is deliberately non-gating — but almost nothing could reach it.

Both classifiers additionally required `d.Sources is { Count: > 0 }`:

  DynamicTypePreWarmer.ClassifyCompileFailure   (activation-driven sweep)
  NodeTypeBatchBake.StampAndReport              (batch bake)

An empty NodeTypeDefinition.Sources does NOT mean "configuration-only". It means
"uses the DEFAULT queries" — `namespace:{path}/Source scope:subtree`, see
CodeQueryResolver.DefaultSources — which is how very nearly every NodeType in a real
mesh is authored. So the conjunct made NoSources unreachable for essentially the whole
population: delete a type's Source/ subtree and it is compiled against nothing, the
configuration lambda's resulting CS0246/CS1061 are recorded as an IMAGE regression, and
a node that no longer exists holds portal readiness on every pod boot.

That is what Edu/Course was doing to memex — deleted in the 2026-08-12 Edu rework,
failing on 100% of 23 sampled boots, with a diagnostic naming code that no longer exists:

  Executed source queries (2):
    - namespace:Edu/Course/Source scope:subtree nodeType:Code
    - namespace:Edu/Course/Test scope:subtree nodeType:Code
  Matched Code nodes (0):
    (none) — the configuration lambda cannot reference types because no source
    files were included.
  CS0246: The type or namespace name 'CourseContent' could not be found
  CS1061: 'LayoutDefinition' does not contain a definition for 'AddCourseLayoutAreas'

This restores the three-outcome contract SourceSnapshot.cs already documents — an
established-but-EMPTY snapshot is a content fact "(the sources were deleted, or the type
is configuration-only)" and classifies as NoSources — from which the extra conjunct had
silently drifted. A genuinely configuration-only type is folded in on purpose: with no
Code nodes there is nothing for an image to regress, so a failure in its configuration
lambda is content drift for its owner, not a reason to stall everyone else's rollout.

What is deliberately NOT changed:

- The GATE. This is a classification fix, never "compile errors stop gating". A type
  whose sources are still present and do not compile keeps a non-empty snapshot and
  keeps gating.
- The two-witness rule (#1216). The batch still requires BOTH its own resolved snapshot
  AND the persisted CurrentSourceVersions, so a starved discovery cannot reclassify
  itself as deleted content.
- A NULL snapshot still gates — "the watcher never seeded" is not "the sources are gone".
- NodeTypeBatchBake.Assemble's `unestablished` guard keeps its DeclaresSources exemption.
  That one is load-bearing: a legitimately source-less type must not abandon the whole
  batch, and the persisted snapshot is what tells the two apart at classification time.

The existing ClassifyCompileFailure_NoDeclaredSources_StaysCompileError asserted the old
behaviour on the premise that an empty Sources means the type "compiles from its
Configuration alone". That premise is wrong about the domain, so the test is replaced
rather than kept — with the reasoning recorded where the next reader will find it.

Verification (fail-before / pass-after):
  before — ClassifyCompileFailure_DefaultQueriesWithEmptySnapshot_IsNoSources FAIL
           DeletedDefaultQueryType_DoesNotGateReadiness                       FAIL
  after  — 46/46 NodeTypeBakeGateStateTest; 55/55 including
           SourceDiscoveryUnavailableTest, SourceSnapshotEstablishmentTest,
           NodeTypeBatchBakeDiscoveryInvariantTest
Both regression guards pass in BOTH states, which is what a guard should do.
Release -warnaserror clean: MeshWeaver.Hosting, Hosting.Monolith.Test.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings August 13, 2026 07:38

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes how NodeType compile failures are classified during pre-warm/batch bake so that “no sources matched” (deleted or genuinely source-less content) is treated as a non-gating content condition (PreWarmStatus.NoSources), rather than a gating image regression (CompileError). This prevents deleted/default-query NodeTypes from holding portal readiness indefinitely.

Changes:

  • Adjust DynamicTypePreWarmer.ClassifyCompileFailure to key solely off CurrentSourceVersions being explicitly empty, removing the (incorrect) dependency on NodeTypeDefinition.Sources being non-empty.
  • Align batch bake classification (NodeTypeBatchBake.StampAndReport) with the activation path so default-query NodeTypes can reach NoSources when their resolved source set is empty and the persisted snapshot corroborates it.
  • Update/replace regression tests and add an end-to-end gate assertion for deleted default-query NodeTypes; add a WhatsNew entry describing the user-visible fix.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
test/MeshWeaver.Hosting.Monolith.Test/NodeTypeBakeGateStateTest.cs Replaces the old assumption about empty Sources and adds end-to-end gate coverage for deleted vs. broken default-query NodeTypes.
src/MeshWeaver.Hosting/NodeTypeBatchBake.cs Updates batch bake’s compile-failure classification to treat empty resolved sources as NoSources without requiring declared queries, while keeping the two-witness rule.
src/MeshWeaver.Hosting/DynamicTypePreWarmer.cs Fixes the activation-path classifier so explicitly empty snapshots classify as NoSources regardless of whether queries were declared.
src/MeshWeaver.Documentation/Data/WhatsNew/2026-08-13-a-deleted-page-type-no-longer-holds-up-the-portal-starting.md Documents the behavioral fix as a user-facing “Fix” entry.
Suppressed comments (1)

src/MeshWeaver.Hosting/DynamicTypePreWarmer.cs:832

  • The XML doc cref Graph.Configuration.SourceSnapshot doesn’t resolve (no such namespace). Also, SourceSnapshot is internal to MeshWeaver.Graph.Configuration, so a cross-assembly <see cref> may not bind even with the correct namespace; using <c>SourceSnapshot</c> avoids broken cref warnings.
    ///
    /// <para>This restores the three-outcome contract <see cref="Graph.Configuration.SourceSnapshot"/>
    /// already documents — an established-but-EMPTY snapshot is a content fact "(the sources were
    /// deleted, or the type is configuration-only)" and classifies as <c>NoSources</c> — from which

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +820 to +823
/// does not mean "configuration-only" — it means <b>"uses the DEFAULT queries"</b>
/// (<c>namespace:{path}/Source scope:subtree</c>, see
/// <see cref="Graph.Configuration.CodeQueryResolver.DefaultSources"/>), which is how very
/// nearly every NodeType in a real mesh is authored. Requiring declared queries therefore made
…lt (#1391)

The member doc still said 'the type's DECLARED source queries', which is the
premise the classifier fix removes. Restates it as the effective queries and
records why the declared-only condition must not come back.

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

github-actions Bot commented Aug 13, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 0)

723 tests  ±0   723 ✅ ±0   10m 43s ⏱️ - 1m 3s
 10 suites ±0     0 💤 ±0 
 10 files   ±0     0 ❌ ±0 

Results for commit 6346405. ± Comparison against base commit b073c6b.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Aug 13, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 3)

   11 files  ±0     11 suites  ±0   5m 15s ⏱️ -34s
1 767 tests ±0  1 662 ✅ ±0  105 💤 ±0  0 ❌ ±0 
2 074 runs  +5  1 969 ✅ +5  105 💤 ±0  0 ❌ ±0 

Results for commit 6346405. ± Comparison against base commit b073c6b.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Aug 13, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 5)

1 908 tests  +2   1 716 ✅ +2   5m 10s ⏱️ -38s
   11 suites ±0     192 💤 ±0 
   11 files   ±0       0 ❌ ±0 

Results for commit 6346405. ± Comparison against base commit b073c6b.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Aug 13, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 4)

1 975 tests  +16   1 967 ✅ +16   8m 33s ⏱️ +6s
   11 suites ± 0       8 💤 ± 0 
   11 files   ± 0       0 ❌ ± 0 

Results for commit 6346405. ± Comparison against base commit b073c6b.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Aug 13, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 2)

2 411 tests  +2   2 410 ✅ +2   6m 25s ⏱️ -17s
   12 suites ±0       1 💤 ±0 
   12 files   ±0       0 ❌ ±0 

Results for commit 6346405. ± Comparison against base commit b073c6b.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Aug 13, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 1)

1 474 tests  +21   1 474 ✅ +21   7m 34s ⏱️ +26s
   11 suites ± 0       0 💤 ± 0 
   11 files   ± 0       0 ❌ ± 0 

Results for commit 6346405. ± Comparison against base commit b073c6b.

This pull request removes 1 and adds 22 tests. Note that renamed tests count towards both.
MeshWeaver.Hosting.Monolith.Test.NodeTypeBakeGateStateTest ‑ ClassifyCompileFailure_NoDeclaredSources_StaysCompileError
MeshWeaver.Data.Test.PatchStringSpliceTest ‑ AFullBaseStringCarriedWithASplice_StillGetsTheThreeWayRebase
MeshWeaver.Data.Test.PatchStringSpliceTest ‑ ALiveValueThatIsNoLongerAString_RefusesRatherThanWritingTheMarker
MeshWeaver.Data.Test.PatchStringSpliceTest ‑ ANearTotalRewrite_StaysOnTheWholeValuePath
MeshWeaver.Data.Test.PatchStringSpliceTest ‑ ASmallString_StaysOnTheWholeValuePath
MeshWeaver.Data.Test.PatchStringSpliceTest ‑ ASpliceWithNoBaseSignal_IsRefused_NeverAppliedAtAnUnverifiedOffset
MeshWeaver.Data.Test.PatchStringSpliceTest ‑ AgainstTheBaseItWasComputedFrom_TheSpliceReproducesTheFullValueExactly
MeshWeaver.Data.Test.PatchStringSpliceTest ‑ AnAppendToABigString_EncodesAsASplice_NotTheWholeValue
MeshWeaver.Data.Test.PatchStringSpliceTest ‑ ExtractBaseValues_ShipsAFingerprintForASplicedLeaf_NotTheWholeOldString
MeshWeaver.Data.Test.PatchStringSpliceTest ‑ ExtractBaseValues_StillShipsTheWholeOldValueForANonSplicedLeaf
MeshWeaver.Data.Test.PatchStringSpliceTest ‑ ManySequentialSplices_ReproduceTheWholeStreamedText
…

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Aug 13, 2026 •

Copy link
Copy Markdown
Contributor

Test Results

    66 files  ± 0      66 suites  ±0   43m 41s ⏱️ - 2m 1s
10 258 tests +41   9 952 ✅ +41  306 💤 ±0  0 ❌ ±0 
10 565 runs  +46  10 259 ✅ +46  306 💤 ±0  0 ❌ ±0 

Results for commit 6346405. ± Comparison against base commit b073c6b.

This pull request removes 1 and adds 42 tests. Note that renamed tests count towards both.
MeshWeaver.Hosting.Monolith.Test.NodeTypeBakeGateStateTest ‑ ClassifyCompileFailure_NoDeclaredSources_StaysCompileError
MeshWeaver.Data.Test.PatchStringSpliceTest ‑ AFullBaseStringCarriedWithASplice_StillGetsTheThreeWayRebase
MeshWeaver.Data.Test.PatchStringSpliceTest ‑ ALiveValueThatIsNoLongerAString_RefusesRatherThanWritingTheMarker
MeshWeaver.Data.Test.PatchStringSpliceTest ‑ ANearTotalRewrite_StaysOnTheWholeValuePath
MeshWeaver.Data.Test.PatchStringSpliceTest ‑ ASmallString_StaysOnTheWholeValuePath
MeshWeaver.Data.Test.PatchStringSpliceTest ‑ ASpliceWithNoBaseSignal_IsRefused_NeverAppliedAtAnUnverifiedOffset
MeshWeaver.Data.Test.PatchStringSpliceTest ‑ AgainstTheBaseItWasComputedFrom_TheSpliceReproducesTheFullValueExactly
MeshWeaver.Data.Test.PatchStringSpliceTest ‑ AnAppendToABigString_EncodesAsASplice_NotTheWholeValue
MeshWeaver.Data.Test.PatchStringSpliceTest ‑ ExtractBaseValues_ShipsAFingerprintForASplicedLeaf_NotTheWholeOldString
MeshWeaver.Data.Test.PatchStringSpliceTest ‑ ExtractBaseValues_StillShipsTheWholeOldValueForANonSplicedLeaf
MeshWeaver.Data.Test.PatchStringSpliceTest ‑ ManySequentialSplices_ReproduceTheWholeStreamedText
…

♻️ This comment has been updated with latest results.

rbuergi and others added 3 commits August 13, 2026 13:15
…ST (#1391)

Three DynamicTypePreWarmerTest failures on CI were caused by my diff, and they were
RIGHT. This is the classifier's fault, not the fixtures'.

An empty CurrentSourceVersions has two very different causes, and the previous commit
conflated them:

  Sources were DELETED (Edu/Course)  → content verdict, must NOT gate
  NEVER had sources, own Configuration is broken → authored defect, MUST gate

All three fixtures are the second kind — `Configuration = "config => this is not valid
C# at all (("` with no sources — so reclassifying them to NoSources also downgraded
their dependents' cascade from the gating UpstreamFailed to the non-gating
UpstreamContentBroken. That is a real weakening of the gate, which is precisely what
those tests exist to catch. Their assertions are untouched.

The second witness is LastCompileSucceededAt: a type that has NEVER built cannot have
LOST anything. It survives a failure — ApplyCompileFailure clears CompiledSources but
never the success stamp — so it is readable exactly where the verdict is formed.

Verified against production, 11/11, on two independent durable signals:

  Edu/Course + 3 × Doc/*        non-null → reclassify (their sources WERE deleted)
  6 × FutuRe/{region}/*         null     → gate. No `sources` key and no Source/
                                           subdirectory in the repo, yet their lambdas
                                           reference LineOfBusiness /
                                           AddLineOfBusinessLayoutAreas. Broken as
                                           authored — the real-world twin of the test
                                           fixtures.
  Type/article                  null     → gate. Never compiled: the importer derives
                                           the node path from the FILENAME while the
                                           Code node lands under the id (capital A).
  2 × Northwind (live sources)  n/a      → gate, on the non-empty snapshot as before.

mesh_node_history corroborates the same 3/7 split — those 7 never recorded a source
across 44-47 versions each. Blast radius: 4 of 16 failures reclassify; memex-cloud keeps
9 gating, memex keeps 1 (BinaryClickerV2/BinaryToggle, null, correctly gating).

Two traps recorded in the code so they are not "simplified" back:

- THREE shapes of CurrentSourceVersions exist in production, not two: populated,
  explicitly {}, and ABSENT (SQL NULL, on public.mesh_nodes rows). Only the middle may
  reclassify. `is { Count: 0 }` is right BY CONSTRUCTION — a C# property pattern never
  matches null — so absent gates. Do not rewrite it as a bare `.Count == 0`, which
  throws.
- CompiledSources would be the more precise evidence but is useless as history: written
  only on success and nulled on failure, so every currently-failing type in production
  lacks it, INCLUDING the three that demonstrably compiled in June.

Also documents the inverse of the deletion hazard in NodeTypeCompilation.md: ADDING a
framework symbol that in-mesh source calls has the same invisibility. #1386 moved an
Article extractor to MarkdownBody.Of and the in-mesh callers shipped before the image,
leaving three types at CS0103 in prod until the portal self-updated. The framework half
must land first.

Verification: 67/67 across DynamicTypePreWarmerTest (all three previously-red, fixtures
UNCHANGED), NodeTypeBakeGateStateTest, SourceDiscoveryUnavailableTest,
SourceSnapshotEstablishmentTest, NodeTypeBatchBakeDiscoveryInvariantTest. Doc link +
What's New integrity green. Release -warnaserror clean: Hosting, Hosting.Monolith.Test.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@rbuergi
rbuergi merged commit cd8b09a into main Aug 13, 2026
19 checks passed
@rbuergi
rbuergi deleted the fix/1391-nosources-default-queries branch August 19, 2026 10:33
@rbuergi rbuergi mentioned this pull request Sep 6, 2026
6 of 26 tasks
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.

2 participants