Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/2716-team-file-incomplete.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
A team file that is empty or half written is now described as what it is instead of telling the person to update FlyBot. `state.json` holding `{}`, `null`, `[]` or an empty string parses but carries no `version`, and the read-only sentence used to read `saved by a newer FlyBot; update FlyBot` — a repair that cannot help an install already on the current version. It now reads `the team file has no version — it is incomplete (an empty or half-written file)`, so the first thing a broken install shows points at the file to move aside. The newer-version sentence is kept for a file whose `version` is above this build's, the file is still never changed, and a zero-byte or truncated file keeps its existing backup-and-load-fresh path. See [#2716](https://github.com/ThinkFlowLab/FlyBot/issues/2716).
6 changes: 6 additions & 0 deletions core/src/host/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,12 @@ export function install(rt) {
stuck = undefined
return parsed
}
// A file that parses but carries no version is not a newer one: it is empty or was
// cut short, and "update FlyBot" would send the person to the wrong repair (#2716).
if (parsed?.version === undefined) {
stuck = 'the team file has no version — it is incomplete (an empty or half-written file)'
return undefined
}
stuck = 'saved by a newer FlyBot; update FlyBot'
return undefined
}
Expand Down
28 changes: 28 additions & 0 deletions core/test/store.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -673,3 +673,31 @@ test('a version-1 home whose roster is null loads instead of throwing (#2715)',
await rt.save()
assert.deepEqual(JSON.parse(fs.readFileSync(statePath, 'utf8')).bots, {})
})

test('a team file with no version is reported as incomplete, not as a newer FlyBot', async (t) => {
const shapes = [
['an empty object', '{}'],
['null', 'null'],
['an empty array', '[]'],
['an empty string', '""'],
]
for (const [label, original] of shapes) {
const { rt, home, statePath } = runtime(t, original)
await rt.load()
assert.match(rt.readOnly(), /the team file has no version — it is incomplete \(an empty or half-written file\)/, label)
assert.doesNotMatch(rt.readOnly(), /newer FlyBot/, label)
rt.state.prefs.marker = 'not saved'
await rt.save()
assert.equal(fs.readFileSync(statePath, 'utf8'), original, label)
assert.deepEqual(fs.readdirSync(home).filter(name => name.startsWith('state.json.broken-')), [], label)
}
})

test('a zero-byte or truncated team file is backed up and loads fresh, never refused as newer', async (t) => {
for (const [label, original] of [['zero bytes', ''], ['truncated json', '{"version":2,"bots":{']]) {
const { rt, home } = runtime(t, original)
await rt.load()
assert.equal(rt.readOnly(), undefined, label)
assert.equal(fs.readdirSync(home).filter(name => name.startsWith('state.json.broken-')).length, 1, label)
}
})
2 changes: 1 addition & 1 deletion docs/subsystems/core-host.md

Large diffs are not rendered by default.

8 changes: 7 additions & 1 deletion patches/ds-bot/0002-flybot-api-host.patch
Original file line number Diff line number Diff line change
Expand Up @@ -1011,7 +1011,7 @@ diff --git a/src/host/store.js b/src/host/store.js
const readFileState = async () => {
let contents
try {
@@ -87,10 +87,32 @@ export function install(rt) {
@@ -87,10 +87,38 @@ export function install(rt) {
return undefined
}
if (parsed?.version === 1) {
Expand Down Expand Up @@ -1041,6 +1041,12 @@ diff --git a/src/host/store.js b/src/host/store.js
+ stuck = undefined
+ return parsed
+ }
+ // A file that parses but carries no version is not a newer one: it is empty or was
+ // cut short, and "update FlyBot" would send the person to the wrong repair (#2716).
+ if (parsed?.version === undefined) {
+ stuck = 'the team file has no version — it is incomplete (an empty or half-written file)'
+ return undefined
+ }
+ stuck = 'saved by a newer FlyBot; update FlyBot'
return undefined
}
Expand Down
30 changes: 29 additions & 1 deletion patches/ds-bot/0014-flybot-branding.patch
Original file line number Diff line number Diff line change
Expand Up @@ -2448,7 +2448,7 @@ diff --git a/test/store.test.js b/test/store.test.js
test('dispose waits for a pending save and prevents later writes', async (t) => {
const { rt, home, statePath, dispose } = runtime(t)
await rt.load()
@@ -285,3 +285,235 @@ test('dispose waits for a pending save and prevents later writes', async (t) =>
@@ -285,3 +285,263 @@ test('dispose waits for a pending save and prevents later writes', async (t) =>
restore()
}
})
Expand Down Expand Up @@ -2684,6 +2684,34 @@ diff --git a/test/store.test.js b/test/store.test.js
+ await rt.save()
+ assert.deepEqual(JSON.parse(fs.readFileSync(statePath, 'utf8')).bots, {})
+})
+
+test('a team file with no version is reported as incomplete, not as a newer FlyBot', async (t) => {
+ const shapes = [
+ ['an empty object', '{}'],
+ ['null', 'null'],
+ ['an empty array', '[]'],
+ ['an empty string', '""'],
+ ]
+ for (const [label, original] of shapes) {
+ const { rt, home, statePath } = runtime(t, original)
+ await rt.load()
+ assert.match(rt.readOnly(), /the team file has no version — it is incomplete \(an empty or half-written file\)/, label)
+ assert.doesNotMatch(rt.readOnly(), /newer FlyBot/, label)
+ rt.state.prefs.marker = 'not saved'
+ await rt.save()
+ assert.equal(fs.readFileSync(statePath, 'utf8'), original, label)
+ assert.deepEqual(fs.readdirSync(home).filter(name => name.startsWith('state.json.broken-')), [], label)
+ }
+})
+
+test('a zero-byte or truncated team file is backed up and loads fresh, never refused as newer', async (t) => {
+ for (const [label, original] of [['zero bytes', ''], ['truncated json', '{"version":2,"bots":{']]) {
+ const { rt, home } = runtime(t, original)
+ await rt.load()
+ assert.equal(rt.readOnly(), undefined, label)
+ assert.equal(fs.readdirSync(home).filter(name => name.startsWith('state.json.broken-')).length, 1, label)
+ }
+})
diff --git a/test/update.test.js b/test/update.test.js
--- a/test/update.test.js
+++ b/test/update.test.js
Expand Down
Loading