Skip to content

feat(sync): a restored store joins a journaled stream; two-device sync end to end (T13312) - #1967

Open
kryptobaseddev wants to merge 8 commits into
feat/T12996-cloud-syncfrom
feat/T13312-journal-join
Open

kryptobaseddev wants to merge 8 commits into
feat/T12996-cloud-syncfrom
feat/T13312-journal-join

Conversation

@kryptobaseddev

Copy link
Copy Markdown
Owner

Task: T13312 (T12999 JOIN; closes T12996's last AC, the two-device headline test)

Stacked on #1962 (feat/T12996-cloud-sync).

Problem

A store restored from another device's journal checkpoint carried no _sync_row_meta, no _sync_meta join keys and no cursor (a new machine's restore clears local-only tables). cleo sync enable push refused it, so a second device could never enter a journaled stream.

Change

  • Restore (vault-manifest.ts, nexus-vault.ts): on a journal checkpoint, carryMachineState keeps the snapshot's _sync_row_meta and _sync_field_leave (snapshotCarried).
  • joinStream (store/sync/genesis.ts):
    • It refuses, changing nothing, unless every sync table's rows match their restored meta (planRepair is clean), no capture is waiting and the triggers are present. Only "table does not exist", "not in the sync set" and "no uid column" skips are benign.
    • Then, in one transaction, it:
      • clears the restored suspect marks;
      • baselines the ledgers;
      • cuts at the capture position;
      • turns on undo, push and pull;
      • raises the writer version;
      • writes the pull cursor seeded from the checkpoint (writeStreamCursor, now shared with the pull page transaction).
    • sync.seal is turned on only after the check passes.
  • enableSyncPush:
    • When the head journal checkpoint is the one this store synced, it compares the local manifest with the checkpoint's (row data), then calls joinStream (row meta) and returns status 'joined'. No second genesis.
    • A store that changed since the restore gets E_NEXUS_SYNC_REFUSED with the remedy cleo cloud restore --force, then join. It is never folded.
    • A store that never restored the checkpoint gets E_NEXUS_SYNC_STREAM_JOURNALED with the same restore-then-join path. The T13306 pull refusal already names that path.
  • Contracts: CloudSyncPushEnableResult.status adds 'joined'.
  • Gate 28: the genesis.ts _sync_meta exemption goes from 3 to 4, with a reason (the join's suspect-mark delete).

Tests (nexus-vault.test.ts, fake server)

  • Two-device headline:
    • A creates the genesis; B restores it and joins.
    • A edits T1 and B inserts T9. After cloud sync on both, each device has the other's write.
    • A concurrent done (A) / cancel (B) of T2 converges identically on both: cancelled with stage cancelled, stamps as one group. The typed rules decide it (absorbing LWW plus the coupled stage).
    • B's cursor equals cursorFromCheckpoint(genesis).
  • Pull-only joiner (feat(cloud): cleo cloud sync - seal, push, pull and apply each attached stream (T12996) #1962 LOW-3): status: 'synced', skipped: ['sync.push is off'], and A's write applied.
  • Refusals:
    • changed rows: refused with the --force restore remedy; no cut, push off, no segments;
    • row meta mismatch: refused; no cut, seal off;
    • a waiting capture: refused.
  • The forgotten-cut store now joins. A different store that never restored gets E_NEXUS_SYNC_STREAM_JOURNALED.
  • T13306: the test now follows the remedy (restore, join, folded change present, pull clean).

Verification

  • Tests: the targeted files (nexus-vault, vault-manifest, genesis, push, pull) pass, 183 of 183.
  • Mutations: 7 of 7 killed. The checks: no manifest check, no row check, every skip benign, no carried meta, no cursor, seal before the check, no pull flag.
  • Typecheck: core src tsc is clean. The test file adds no new error class: the one added error is the existing restoreNexusVault(vopts(..., {mode:'pull'})) idiom, already present 15 times.
  • Biome: clean on the changed set.
  • Gates: 28, 36, 37, 38 (--base feat/T12996-cloud-sync), 40 and 32 (--base) pass; lint-changesets passes.

sync.push and sync.pull stay unreleased.

🤖 Generated with Claude Code

…c end to end (T13312, T12999, T12996)

A store restored from another device's journal checkpoint had no row meta
and no join state, so enable push refused it and a second device could not
enter the stream.

- Restore keeps _sync_row_meta and _sync_field_leave on a journal checkpoint
  (carryMachineState snapshotCarried).
- joinStream: refuses unless every row matches its restored meta, there are no
  waiting captures and the triggers are present, then cuts at the capture
  position, baselines the ledgers, turns on push/pull/undo and seeds the pull
  cursor from the checkpoint. Sealing is enabled only after the check.
- enableSyncPush joins (status 'joined') when the head journal checkpoint is the
  one this store synced and its manifest matches; a changed store is refused
  with the restore --force remedy, never folded; any other store gets
  E_NEXUS_SYNC_STREAM_JOURNALED.
- Tests: the two-device headline (A and B each write, sync, see the other's
  write; a concurrent done/cancel converges by the typed rules), pull-only
  joiner reports synced with the push leg skipped, the three refusals, the
  T13306 remedy followed through to a join. All 7 mutations killed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kryptobaseddev and others added 5 commits October 6, 2026 20:48
…he join; a refused join restores capture (T13312 review)

MED (#1967): planRepair runs outside the cut's transaction, so a write in
that gap was captured live at seq <= cut and baselined as checkpoint
state. Capture is on before the check, so any such write is a live
capture: inside the cut's BEGIN IMMEDIATE the join now refuses on one
(the store changed since its restore). Sealing turns on inside that
transaction too.

LOW: a refused join puts capture back as it found it, so a store that
had it off does not pile up captures that refuse every retry.

Tests (genesis.test.ts): a join of a meta-complete store; a write in the
gap (via the now() hook) refuses, no cut, push off, the row unbaselined;
a refused join leaves capture off and no capture triggers. Both
mutations killed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… test

Main's T13109 binds a new replica when a vault restore places a store;
the CLI's relink attaches it, which this helper machine does by hand.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
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