Skip to content

Doc: the storage-side Durable-But-Unreadable mechanism, reproduced, plus the sweep for the rows already lost (#4513) - #4562

Merged
meshweaver-cloud[bot] merged 1 commit into
mainfrom
fix/4513-durable-unreadable
Sep 17, 2026
Merged

meshweaver-cloud[bot] merged 1 commit into
mainfrom
fix/4513-durable-unreadable

Conversation

@rbuergi

@rbuergi rbuergi commented Sep 17, 2026 •

Copy link
Copy Markdown
Contributor

Doc-only. Records the storage-side mechanism of the class this page defines — found in code,
reproduced deterministically, and fixed in
MeshWeaver.Plugins#1998 —
plus two things the code settles about the third instance for free, and the sweep for the instances
already in production.

From #4513.

The mechanism, and it reproduces on demand

PostgreSqlStorageAdapter resolved a WRITE's table from (path, nodeType) and every READ's table
from the path alone — a reader only ever has a path. So a node whose NodeType maps to a
satellite table but whose PATH carries no satellite segment (a Comment, Thread,
Notification, Activity, … filed at an ordinary path) went into the satellite table while Read,
ReadMany, Exists, ListChildPaths, ListDescendantPaths, FindBestPrefixMatch and DeleteMany
all looked in mesh_nodes. The version store is matched on the stored path column and is not
table-routed, so the history row lands regardless — index absent, point read absent, version store
present, and the write reports success twice over.

DurableButUnreadableTest (MeshWeaver.Plugins) is the first repro of this class: measured on
origin/main 4 of 4 satellite-typed cases fail with the acknowledgement in hand and the version
row written; after the fix 8 of 8 pass. Every pre-existing satellite test writes to a path that
already contains the segment, where the two resolutions agree by construction — the asymmetry lived
in the argument, so no test that never varied it could see it.

Two things the code settles for free

  • changed_by names the writer. mesh_node_copy_to_history is installed on mesh_nodes ONLY
    and copies last_modified_by; the app-side PostgreSqlVersionQuery.WriteVersion binds no
    changed_by at all. A stamped history row therefore PROVES a committed mesh_nodes row of that
    version. All 53 of Deployments/client-b-provision-20260914's rows carry system-security, so its
    row was in deployments.mesh_nodes with main_node == path and state: Active at v62
    (get_version … 62, measured 2026-09-17 on memex.systemorph.com). The write path is exonerated by
    its own evidence; Durable But Unreadable: a third confirmed instance — Deployments/client-b-provision-20260914 has 53 version rows and no readable current row (Plugins#1922 half 2) #4513's open question moves to what REMOVED or replaced the row.
  • The newest version row is not the current row. The upsert applies at an EQUAL version by
    design (WHERE target.version <= EXCLUDED.version) while the history trigger is
    ON CONFLICT (namespace, id, version) DO NOTHING, so a same-version write replaces content,
    state and main_node and records nothing.

Finding the ones already out there

DurableButUnreadableDetector compares a partition's node tables against its version store, reads
them directly as the system (so its denominator is the partition, not a caller's grants) and
classifies every row it cannot reconcile — MisplacedRow, AcknowledgedButAbsent,
RemovedAfterCommit (what a DELETE looks like, held apart on evidence) and RowBehindItsHistory —
with the denominators travelling alongside. Read-only; the repair of an existing row is a decision
about content, not a mechanical one.

Verification

  • dotnet build test/MeshWeaver.Documentation.Test/MeshWeaver.Documentation.Test.csproj -c Release -warnaserror
    → 0 Warning(s), 0 Error(s).
  • MeshWeaver.Documentation.Test (incl. DocumentationLinkIntegrityTest) → 505 total, 0 failed.

No public surface change, no i18n key, no package version — Pairs-with, Implementers,
Satellite-pins and Mirror-sync do not apply to a doc-only diff.

🤖 Generated with Claude Code

…nd the sweep for the ones already out there (#4513)

A second producer of the three-seam signature lived in the Postgres backend and is
now fixed in MeshWeaver.Plugins: the write resolved its table from (path, nodeType)
while every read resolves it from the path alone, so a node whose NodeType maps to a
satellite table but whose PATH carries no satellite segment was written where no
reader looks - acknowledged, versioned, invisible. It is the first instance of this
class that reproduces on demand, in a test.

Also records two things the code settles for free:

- changed_by on a mesh_node_history row says WHICH writer produced it. The trigger
  is on mesh_nodes only and copies last_modified_by; the app-side WriteVersion binds
  none. So a stamped row proves a committed mesh_nodes row of that version - which
  exonerates the write path for the third instance and moves its open question to
  what removed the row.
- an EQUAL-version write applies and the history trigger's ON CONFLICT DO NOTHING
  records nothing, so get_version <max> is the last DISTINCT version, not
  necessarily the current row.

And documents the sweep that finds the instances already in production rather than
one at a time: DurableButUnreadableDetector compares a partition's node tables
against its version store, as the system, and classifies each row it cannot
reconcile - holding deleted nodes apart from lost ones.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings September 17, 2026 05:50

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.

🟢 Approval recommended

Doc-only change with passing documentation tests; remaining feedback is a small documentation formatting fix (table header clarity).

Pull request overview

Updates the “Durable But Unreadable” architecture doc to record the now-identified PostgreSQL storage-side mechanism (and its fix in MeshWeaver.Plugins), plus how to detect already-affected rows and what the evidence implies for the third confirmed instance (#4513).

Changes:

  • Document the storage-table routing asymmetry (write: (path,nodeType) vs read: path) and the two-part fix strategy (placement + unconfirmed-write handling).
  • Clarify what the history changed_by stamp proves for the third instance, and what remains unexplained.
  • Add the recommended sweep approach (DurableButUnreadableDetector) and how to run it operationally.
File summaries
File Description
src/MeshWeaver.Documentation/Data/Architecture/DurableButUnreadable.md Adds storage-side mechanism + detection sweep guidance; updates “Open” section based on new evidence for #4513.
Review details
  • Files reviewed: 1/1 changed files
  • Comments generated: 1
  • Review effort level: Lite

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


Each seam then answers exactly as the three-seam test describes:

| Seam | Why it misses | |
@github-actions

Copy link
Copy Markdown
Contributor

Test Results (shard 0)

264 tests  ±0   264 ✅ ±0   2m 34s ⏱️ +46s
  1 suites ±0     0 💤 ±0 
  1 files   ±0     0 ❌ ±0 

Results for commit 5a092ac. ± Comparison against base commit b5d3734.

@github-actions

Copy link
Copy Markdown
Contributor

Test Results (shard 1)

1 538 tests  ±0   1 538 ✅ ±0   3m 11s ⏱️ +2s
    2 suites ±0       0 💤 ±0 
    2 files   ±0       0 ❌ ±0 

Results for commit 5a092ac. ± Comparison against base commit b5d3734.

@github-actions

Copy link
Copy Markdown
Contributor

Test Results (shard 4)

    4 files  ±0      4 suites  ±0   5m 22s ⏱️ +33s
2 017 tests ±0  1 825 ✅ ±0  192 💤 ±0  0 ❌ ±0 
2 018 runs  ±0  1 826 ✅ ±0  192 💤 ±0  0 ❌ ±0 

Results for commit 5a092ac. ± Comparison against base commit b5d3734.

@github-actions

Copy link
Copy Markdown
Contributor

Test Results (shard 5)

    6 files  ±0      6 suites  ±0   5m 34s ⏱️ -2s
2 259 tests ±0  2 259 ✅ ±0  0 💤 ±0  0 ❌ ±0 
2 263 runs  ±0  2 263 ✅ ±0  0 💤 ±0  0 ❌ ±0 

Results for commit 5a092ac. ± Comparison against base commit b5d3734.

@github-actions

Copy link
Copy Markdown
Contributor

Test Results (shard 2)

969 tests  ±0   969 ✅ ±0   5m 55s ⏱️ +6s
  2 suites ±0     0 💤 ±0 
  2 files   ±0     0 ❌ ±0 

Results for commit 5a092ac. ± Comparison against base commit b5d3734.

@github-actions

Copy link
Copy Markdown
Contributor

Test Results (shard 3)

1 199 tests  ±0   1 197 ✅ ±0   6m 3s ⏱️ -21s
    3 suites ±0       2 💤 ±0 
    3 files   ±0       0 ❌ ±0 

Results for commit 5a092ac. ± Comparison against base commit b5d3734.

@github-actions

Copy link
Copy Markdown
Contributor

Test Results

   18 files  ±0     18 suites  ±0   28m 41s ⏱️ + 1m 4s
8 246 tests ±0  8 052 ✅ ±0  194 💤 ±0  0 ❌ ±0 
8 251 runs  ±0  8 057 ✅ ±0  194 💤 ±0  0 ❌ ±0 

Results for commit 5a092ac. ± Comparison against base commit b5d3734.

@meshweaver-cloud
meshweaver-cloud Bot added this pull request to the merge queue Sep 17, 2026
Merged via the queue into main with commit d2897b6 Sep 17, 2026
37 checks passed
@rbuergi
rbuergi deleted the fix/4513-durable-unreadable branch October 10, 2026 13:10
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