Skip to content

bug: receiver retains graph-scoped SWM copy after VM finalization #1995

Description

@Bojan131

Summary

When a receiver already holds a graph-scoped/rootless knowledge asset assertion in Shared Working Memory (SWM), and that same assertion later becomes finalized and materialized in Verifiable Memory (VM), the receiver keeps the old SWM graph instead of removing it.

The promotion itself succeeds, but the receiver ends up storing the same assertion in both tiers. Aggregate queries can therefore count the same data twice.

Impact

A successful SWM-to-VM promotion can leave receivers in an inconsistent state:

  • curator/publisher: 0 SWM + 1 VM
  • receiver: 1 SWM + 1 VM
  • aggregate receiver query: duplicated entities/triples

This is persistent, not a short synchronization overlap.

Exact trigger

The problem occurs when all of the following are true:

  1. the KA is graph-scoped/rootless;
  2. the receiver already has the exact assertion in SWM;
  3. that same assertion is later confirmed and materialized in VM on the receiver.

It is easy to miss when testing a VM-only late joiner, using separate assets for SWM and VM, or observing only the publisher, because the publisher still cleans its own SWM copy.

Reproduction evidence

A clean same-asset SWM-to-VM run used one real KA containing 10 entities and 40 triples.

Before promotion:

  • receiver: 1 SWM / 0 VM / 40 triples
  • receiver content digest exactly matched the curator

After promotion:

  • curator: 0 SWM / 1 VM / 40 triples
  • receiver after about 13 seconds: 1 SWM / 1 VM / 80 triples

The receiver remained at 1 SWM / 1 VM / 80 triples on checks every 15 seconds for five minutes, confirming this was not a transient overlap.

Expected behavior

After the exact assertion is finalized successfully:

  • matching stale SWM state is removed;
  • VM contains one finalized copy;
  • aggregate queries return the original 40 triples once.

If a newer SWM assertion arrived during finalization, it must be preserved.

Actual behavior

Receiver finalization writes the VM graph and metadata, but does not clean the matching SWM graph. The already-confirmed repair path also returns without cleaning an exact stale SWM copy.

Affected paths on current main:

  • packages/agent/src/finalization-handler.ts
    • exact VM/metadata state returns already-confirmed without SWM cleanup;
    • the normal materialization update writes VM graph and metadata only;
    • a comment defers SWM cleanup to the publisher-side per-KA writer lock.
  • packages/agent/src/dkg-agent-swm-host.ts
    • the already-confirmed result updates caches/telemetry and returns without receiver cleanup.
  • packages/publisher/src/dkg-publisher.ts
    • publisher-side cleanup still exists, which explains why the curator is correct while receivers remain duplicated.

Confirmed history / root cause

This was not introduced by PR #1991; that PR changed scoped query/store scheduling and did not modify the receiver finalization or SWM cleanup paths.

The original graph-scoped finalization implementation in commit 5431468 cleaned receiver SWM state with dropGraph(swmGraph) in both the normal apply and already-confirmed paths.

Commit 2cfbaf1 (fix(agent): harden rootless recovery invariants) removed both receiver cleanup calls and changed the focused tests to expect SWM to remain. It entered testnet-canary through PR #1736 and was promoted to main through PR #1746.

The likely reason was to avoid deleting a newer SWM assertion that races with finalization. That protection is correct, but leaving the exact finalized assertion in SWM permanently is not.

Focused unit evidence

The current finalization test explicitly expects both tiers to retain data after the exact VM graph is written. A focused run of the relevant tests passes under current behavior:

pnpm --filter @origintrail-official/dkg-agent exec vitest run \
  test/ka-graph-finalization-handler.test.ts \
  --config vitest.unit.config.ts \
  --reporter=verbose \
  -t 'atomically replaces the exact VM graph|does not delete a newer SWM assertion'

Result: one file passed, two tests passed, 44 skipped. This confirms the duplicate is currently encoded as expected behavior, while the separate newer-assertion test correctly requires newer SWM state to survive.

Why the existing blackbox scenario missed it

The convergence scenario uses one asset for SWM and a different asset for VM, so it never exercises promotion of the same assertion. Its convergence check also does not fail on extra content unless the stricter exact condition is used.

The SWM harness work in dkg-blackbox-harness PR #6 verifies read-only SWM parity independently from VM differences, so it also does not cover same-asset SWM-to-VM promotion.

Proposed fix direction

Do not restore an unconditional dropGraph, because that can delete a newer SWM assertion.

Instead, receiver cleanup should:

  1. use the same per-KA SWM writer lock used by local and synchronized SWM writes;
  2. re-read the durable SWM head while holding that lock;
  3. verify it still represents the assertion that was just finalized (UAL/assertion version, operation/receipt identity, content digest or Merkle root as appropriate);
  4. atomically delete only the matching SWM graph and metadata;
  5. preserve the SWM state if it is newer or otherwise does not match.

Apply this cleanup after successful receiver promotion and in the already-confirmed repair path.

Acceptance criteria

Add regressions for all three cases:

  • Same-asset v1 SWM -> VM promotion ends at 0 SWM + 1 VM on the receiver.
  • A newer v2 SWM assertion staged during v1 finalization is preserved.
  • An already-confirmed exact VM state with a stale matching v1 SWM copy repairs to 0 SWM + 1 VM.
  • Add a blackbox same-asset promotion scenario that asserts exact tier counts (SWM=0, VM=N) and fails on extra content.

Related issue

#1778 is related to rootless SWM behavior but is not a duplicate: it covers a missing assertion seal that blocks curator VM publication, while this issue occurs after publication/finalization succeeds and concerns stale receiver SWM retention.

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