Skip to content

fix(T13851): journal-stream remedy names only commands this build can run - #2348

Open
kryptobaseddev wants to merge 4 commits into
mainfrom
fix/T13851-behind-remedy-older-client
Open

kryptobaseddev wants to merge 4 commits into
mainfrom
fix/T13851-behind-remedy-older-client

Conversation

@kryptobaseddev

@kryptobaseddev kryptobaseddev commented Oct 17, 2026 •

Copy link
Copy Markdown
Owner

T13851 was found by the 10.19 staging gate (evidence: t13549r/evidence/4-a-push-global.json, 0-a-enable.json).

Root cause

On a stream that the change journal writes, the head snapshot is checkpoint/v3. Every message about such a stream used one fixed remedy, JOURNAL_STREAM_REMEDY, and it told the store to join with cleo sync enable push. On a build where sync.push is unreleased (UNRELEASED_FLAGS, which includes 10.19 and current main), that command refuses with E_NEXUS_SYNC_REFUSED. So once a newer device starts the journal on an account, an older device can't back up the global store, and the fix text it gets sends it to a command its own build refuses.

Fix

journalStreamRemedy(pushReleased = !UNRELEASED_FLAGS.has('sync.push')) replaces the constant at all 9 call sites: BEHIND, STREAM_UPGRADED (both the local check and the server's E_STREAM_VERSION), LOCAL_CHANGES, the kept-files warning, status, and the verify ahead/diverged remedies.

  • sync.push unreleased in this build (T13856, review of 08557cf): the message says sync.push is not released yet, that a pre-release or allowUnreleased CLEO build started this journal, and that no released CLEO can push to it yet. cleo cloud pull, cleo cloud restore and cleo cloud verify keep working. It never names cleo self-update: while sync.push is in UNRELEASED_FLAGS, no released CLEO ships it, so self-update would send the user in a loop.
  • cloud sync refused with E_SYNC_DISABLED: its fix (syncDisabledRemedy()) follows the same branch. It names cleo sync enable push only in a build that ships sync.push.
  • sync.push released: the message is unchanged, including the join with cleo sync enable push.

The vault does not record the writer's client version (the checkpoint carries no such field), so a v3 head is the only signal that a newer client wrote the stream. That signal is accurate here, because only a build with sync.push released writes v3.

This cannot fix devices already on 10.19 or older, because that text is already shipped in those builds. It fixes every later build while sync.push stays unreleased.

Proposed mixed-version policy (for owner and team-lead)

  1. Older clients may read a journal stream but not write to it. cloud pull, restore and verify keep working. A vault push onto a v3 head refuses, and its remedy only ever names a step the client can run, which in practice means an upgrade.
  2. The vault records a minimum reader version. Proposed for a follow-up: a checkpoint writes a minClientVersion, the version where sync.push was released. A client below it refuses with a message that names that version, rather than inferring it from manifest v3.
  3. No silent downgrade. A newer client never writes v2 over a v3 head. The server ratchet (E_STREAM_VERSION, reason stream-v3) already enforces this, and the client refuses before writing anything.
  4. Release order. sync.push should leave UNRELEASED_FLAGS only in a release whose notes tell every device on an account to upgrade first. Until then, any device that ran a staging or allowUnreleased build counts as "the newer client". A staging or allowUnreleased run against a real account starts a journal that released devices can't push to: their vault pushes are refused until a release ships sync.push.

Only point 1 (the remedy) is implemented here. Points 2–4 need owner decisions.

Tests

  • nexus-vault.test.ts: 168/168, run as a single file in a sandboxed HOME.
    • The existing v3-head BEHIND regression asserts that this build has sync.push unreleased, and that the fix names neither cleo self-update nor cleo sync enable push (T13851 AC3, T13856).
    • A unit test checks both branches of journalStreamRemedy, and a new one checks both branches of syncDisabledRemedy.
    • C-1 (cloud sync with the journal off) now expects syncDisabledRemedy(false). That expectation changed on purpose: the behaviour changed.
    • f7c355e (T13856): local runs pending axiom hold. The test file and gate 38 have not run locally on this head yet; CI covers them. Gates 28 and 32 (--base origin/main) and biome pass locally.
  • Mutation checks, both caught:
    • With the default forced to "released", the BEHIND regression fails.
    • With the unreleased branch removed, the unit test fails.

Gates

  • pnpm run typecheck (tsc -b) passed, then pnpm run build passed.
  • biome is clean, and lint-changesets passes (838 entries).
  • Gates 14 and 31 pass. Gates 32 and 38 pass against --base origin/main. Gate 39 passes.

Unverified

  • I did not run a live replay on staging with an older-build device. The behaviour is proven against the fake Nexus in nexus-vault.test.ts only.

🤖 Generated with Claude Code

kryptobaseddev and others added 4 commits October 17, 2026 04:54
… run

A build with sync.push unreleased was told to join the journal with
`cleo sync enable push`, which it refuses (E_NEXUS_SYNC_REFUSED), on
every journal-stream message (BEHIND, STREAM_UPGRADED, LOCAL_CHANGES,
verify, status). journalStreamRemedy() now tells such a build to
upgrade with `cleo self-update`; a build that ships sync.push keeps
the join remedy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
journalStreamRemedy(false) sent users to `cleo self-update`, but while
sync.push is in UNRELEASED_FLAGS no released CLEO ships it, so the user
would loop. It now says a pre-release or allowUnreleased build started
the journal, no released CLEO can push to it yet, and pull, restore and
verify keep working. cloudSyncImpl's E_SYNC_DISABLED fix gets the same
branch through syncDisabledRemedy(); C-1 asserts it, and a new unit test
covers both branches.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The unreleased branch of journalStreamRemedy dropped "which checkpoints
the stream (T12999)", which the mixed v2-then-v3 lineage regression
asserts (CI shard 2 on f7c355e). The clause is back, before the
reason this CLEO cannot run sync.push.

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