Repository navigation
Conversation
added 7 commits
October 8, 2026 23:13
AutoSync off only stops this device from starting a sync. A paired device can still ask for the manifest and move files, and an in-memory pause dies on restart. Record the hold in the same transaction as the game, in a table older builds ignore, so a crash cannot leave a game the next launch will sync. Existing games have no row and stay as they are.
Creating or placing with provisioningHold, or with autoSync false, records the game and refuses manifest, block, delete, snapshot, watch, retry, reconcile and direct sync until that one game is released. Releasing does not sync the rest of the library. An explicit id is accepted only while held, so the other device can be registered without an offer and without sending save contents.
Cover paired creation without an offer, unmanaged daemon.New restart, inbound refusal, offer placement, symlink and duplicate paths, named roots, and a 100-game library that still syncs an unrelated title.
A failed SQLite read used to look like "not held", so a protected game could be watched, served, or synced. Hold lookups now return the error, and every transfer path refuses rather than continuing. Release no longer calls SyncGame, which would contact every online peer before the caller has said which pair should converge.
Default release still enables AutoSync, and reconcile then syncs that
game with every online peer that is not held. That is now tested.
{"autoSync": false} clears the hold and leaves AutoSync off, so
reconcile, reconnect and the watcher do not sync. A PATCH cannot clear
the hold. A failed hold read is not reported as an ordinary false.
The unrelated live game was tracked on both sides with identical bytes and no recorded convergence. A later edit then correctly conflicted instead of syncing. Sync once first, require both agreed hashes, and only then create the held games.
|
Someone is attempting to deploy a commit to the sivadaboi's projects Team on Vercel. A member of the Team first needs to authorize it. |
6 of 8 tasks
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Refs #40.
A headless peer, a home server, or another controller sometimes needs to record a game and its save locations before any bytes move. Today, tracking forces AutoSync on and can sync before a later update turns it off. Turning ordinary AutoSync off also does not stop a peer that already knows the id from requesting the manifest.
This is opt-in. Ordinary tracking and offer placement are unchanged when the new fields are omitted.
The release action is deliberate, and it is not the same as the settings update suggested in #40. A normal
PATCHofautoSynccannot enforce an inbound hold, so it does not clear one. Maintainer feedback on that split is welcome.Contract
POST /api/gamesorPOST /api/offered-games/{id}/placewith no new fields"provisioningHold": trueor"autoSync": falseidon held createPOST /api/games/{id}/release-provisioningwith an empty bodySyncGame. A later reconcile or reconnect can{"autoSync": false}PATCHof ordinary game settingsA failed hold read is not treated as "not held". The payload sets
provisioningHoldUnknownrather than reporting a false ready state.Held creation and a held path update reject a symlink as the save root. Extra named locations can be configured while the hold is in place.
Examples
Place an offered game without syncing:
Create the same id on another machine, still held:
Clear the hold without turning local automatic sync on:
CLI:
opensave add --hold [--id slug],opensave offers place --hold,opensave game <id> release [--no-autosync]. An idempotent offline release reports the stored AutoSync value.Why the hold is checked in several places
AutoSync only stops this process from starting a sync. Pause does not survive
daemon.New. The hold is therefore checked at the store, at watch/cloud publish, at reconcile and direct sync, and on LAN and WAN manifest, block, delete, and snapshot serve. That is existing entry points, not a second sync engine.Review map
0038_game_provisioning_holds.sql, hold read/release in one transactionleafA,hub,leafB)Warning
A stock or older daemon on this database does not understand the hold table. In a mixed-version probe it served a held manifest with HTTP 200. Do not downgrade a data directory that has provisioning holds. A stock client talking to a patched held server was refused for the tested manifest and delete requests. That is not general mixed-version safety.
Staged release is not a peer lock. After the hold is cleared, an unheld peer can still request the save even if this device's AutoSync is off. Other peers must stay held if they must not exchange bytes.
Tests
The 100-game case originally tracked the same live bytes on both sides with no agreed hash. A later edit correctly returned
conflictand did not transfer. That was the fixture, not a capacity failure. The fixture now syncs once, requires both persisted agreed hashes, and only then creates the 100 held games.TestProvisioningHold_HundredGamesStayOneDaemonpassed three times on Go 1.26.4 (0.98s, 0.99s, 0.85s).go test -count=1 -timeout 25m ./e2e/...on the fixture working tree that became2fee76fgo test -count=1 -timeout 20mp2p, store, api, deltago test -race -run TestProvisioningon e2e, store, p2pgo vet,gofmt,git diff --check./internal/daemonexcluding onlyTestSessionNamesTheSnapshotAlreadyTakenTestSessionNamesTheSnapshotAlreadyTakenis not part of this change. It failed on an untouched archive of346d9bda(eight runs, extraAuto backup,want the watcher) and still fails intermittently on Go 1.26.4. It was excluded only from that supplemental daemon gate. This is not a fully greengo test ./....cmd/opensave-appembedsfrontend/dist, which is not in the clone. That target was not stubbed and was not tested.A separate self-hosted CI run exercised the combined production code of the previous provisioning tip plus the timestamp branch, on Go 1.26.4. That is supporting evidence for the combination, not upstream OpenSave CI, and it is not a substitute for the tests on
2fee76f.Non-goals
No new sync engine, no peer-targeted sync, no version vectors, no conversion of an already live game into a hold, and no claim that default release means nothing moves until an explicit sync. Empty-body release turns AutoSync on, so reconcile can transfer.
Draft #30 uses migration numbers
0038and0039for a different design. This branch adds0038_game_provisioning_holds.sqlagainst currentmain, where0037is the latest. If both are merged, one migration will need renumbering. They are not the same feature.