// The shell's boot decision — the code path that decides what EXECUTES on the device.
//
// ⛔ THIS WAS THE ONLY UNCOVERED FILE, and it is the one that can brick a handset: there is no adb on a
// user's phone and no UI left if the node cannot boot. It was uncovered because worklet.js imports
// react-native-bare-kit and react-native-fs at module scope and cannot load under bare. The decision is
// now in host/boot-plan.js with every filesystem touch injected, so the whole tree runs here.
import test from 'brittle'
import { planBoot } from '../lib/host/boot-plan.js'
const STORAGE = '/data/user/0/app/files'
const BDIR = STORAGE + '/bundles'
// A fake device: assets from the APK, a filesystem, and a log.
function device ({ assets = {}, files = {}, sizes = {} } = {}) {
const written = {}
const unlinked = []
const log = []
const io = {
async readAsset (n) { if (!(n in assets)) throw new Error('no asset ' + n); return assets[n] },
async size (p) { if (!(p in sizes)) throw new Error('ENOENT'); return sizes[p] },
async exists (p) { return p in files || p in written },
async readJson (p) { const v = p in written ? written[p] : files[p]; try { return JSON.parse(v) } catch { return null } },
async writeText (p, t) { written[p] = t },
async copyAsset (n, d) { if (!(n in assets)) throw new Error('no asset ' + n); written[d] = assets[n] },
async unlink (p) { unlinked.push(p) },
async dnsServers () { return ['1.1.1.1'] },
log: (m) => log.push(m)
}
return { io, written, unlinked, log }
}
const BASE = {
'worklet.bundle.size': '100',
'worklet.bundle.built': '5000',
'worklet.bundle': 'APK-BUNDLE-BYTES',
'loader.bundle': 'LOADER-BYTES',
'apk-addons.json': JSON.stringify(['libbare-fs.4.8.1.so'])
}
test('with nothing staged the APK bundle runs, through the loader', async (t) => {
const d = device({ assets: BASE, sizes: { [STORAGE + '/worklet.bundle']: 100 } })
const plan = await planBoot({ storage: STORAGE, io: d.io })
t.is(plan.entryPath, STORAGE + '/loader.bundle', 'the loader is the entry')
t.alike(plan.startArgs, ['--bundle=' + STORAGE + '/worklet.bundle', STORAGE], 'storage LAST, no --addons')
t.is(plan.bundleHash, null, 'nothing staged')
})
test('the watchdog arms BEFORE a staged bundle starts', async (t) => {
// If pending.json were written after start, a bundle that hangs the runtime would never be condemned.
const staged = { treeHash: 'a'.repeat(64), file: 'x.bundle', builtAt: 9000, addons: ['libnew.1.0.0.so'], demands: ['libnew.1.0.0.so'] }
const d = device({
assets: BASE,
sizes: { [STORAGE + '/worklet.bundle']: 100 },
files: { [BDIR + '/current.json']: JSON.stringify(staged), [BDIR + '/x.bundle']: 'STAGED' }
})
const plan = await planBoot({ storage: STORAGE, io: d.io })
t.is(plan.bundleHash, 'a'.repeat(64), 'the staged bundle was chosen')
t.alike(JSON.parse(d.written[BDIR + '/pending.json']), { treeHash: 'a'.repeat(64) }, 'pending written')
t.ok(plan.startArgs.includes('--addons=' + BDIR + '/x.bundle.addons'), 'and its addons passed')
})
test('a pending marker that survived a boot is condemned before anything starts', async (t) => {
const d = device({
assets: BASE,
sizes: { [STORAGE + '/worklet.bundle']: 100 },
files: {
[BDIR + '/pending.json']: JSON.stringify({ treeHash: 'b'.repeat(64) }),
[BDIR + '/current.json']: JSON.stringify({ treeHash: 'b'.repeat(64), file: 'y.bundle', builtAt: 9000 }),
[BDIR + '/y.bundle']: 'STAGED'
}
})
const plan = await planBoot({ storage: STORAGE, io: d.io })
t.alike(JSON.parse(d.written[BDIR + '/failed.json']), ['b'.repeat(64)], 'recorded as failed')
t.is(plan.bundleHash, null, 'and it is NOT run again')
t.ok(d.log.some((m) => m.includes('condemned')), 'and said so')
})
test('a bundle demanding a soname nobody has is refused, out loud', async (t) => {
const staged = {
treeHash: 'c'.repeat(64), file: 'z.bundle', builtAt: 9000,
addons: [], demands: ['libbare-fs.4.8.1.so', 'libmissing.9.9.9.so']
}
const d = device({
assets: BASE,
sizes: { [STORAGE + '/worklet.bundle']: 100 },
files: { [BDIR + '/current.json']: JSON.stringify(staged), [BDIR + '/z.bundle']: 'STAGED' }
})
const plan = await planBoot({ storage: STORAGE, io: d.io })
t.is(plan.bundleHash, null, 'the APK bundle runs instead')
t.absent(d.written[BDIR + '/pending.json'], 'and the watchdog is NOT armed for a boot we declined')
t.ok(d.log.some((m) => m.includes('REFUSED') && m.includes('libmissing.9.9.9.so')), 'named it: ' + d.log.join(' | '))
})
test('no loader asset falls back to starting the bundle directly', async (t) => {
// An APK built before pack:loader existed. The in-bundle bootstrap still registers staged addons, so
// this is a fallback in shape only.
const assets = { ...BASE }
delete assets['loader.bundle']
const d = device({ assets, sizes: { [STORAGE + '/worklet.bundle']: 100 } })
const plan = await planBoot({ storage: STORAGE, io: d.io })
t.is(plan.entryPath, STORAGE + '/worklet.bundle', 'the bundle itself is the entry')
t.alike(plan.startArgs, [STORAGE], 'and the old argv shape is used')
t.ok(d.log.some((m) => m.includes('no loader asset')))
})
test('a broken OTA directory never stops the app starting', async (t) => {
const d = device({ assets: BASE, sizes: { [STORAGE + '/worklet.bundle']: 100 } })
d.io.readJson = async () => { throw new Error('storage is corrupt') }
const plan = await planBoot({ storage: STORAGE, io: d.io })
t.is(plan.bundleHash, null, 'falls back to the APK bundle')
t.ok(d.log.some((m) => m.includes('bundle choice failed')))
})
test('the bundle asset is re-copied only when its size stamp changes', async (t) => {
const cached = device({ assets: BASE, sizes: { [STORAGE + '/worklet.bundle']: 100 } })
await planBoot({ storage: STORAGE, io: cached.io })
t.absent(cached.written[STORAGE + '/worklet.bundle'], 'sizes match — not re-copied')
const stale = device({ assets: BASE, sizes: { [STORAGE + '/worklet.bundle']: 99 } })
await planBoot({ storage: STORAGE, io: stale.io })
t.is(stale.written[STORAGE + '/worklet.bundle'], 'APK-BUNDLE-BYTES', 'size differs — re-copied')
t.ok(stale.unlinked.includes(STORAGE + '/worklet.bundle'), 'after unlinking the old one')
})
test('the shell handoff carries the APK addon list to the node', async (t) => {
// How the OTA store learns what it can skip downloading. Only this side can answer it.
const d = device({ assets: BASE, sizes: { [STORAGE + '/worklet.bundle']: 100 } })
await planBoot({ storage: STORAGE, io: d.io })
const shell = JSON.parse(d.written[STORAGE + '/shell.json'])
t.alike(shell.apkAddons, ['libbare-fs.4.8.1.so'])
t.is(shell.ota, true)
t.alike(shell.dns, ['1.1.1.1'])
})
test('an APK with no addon list still boots, and claims nothing', async (t) => {
const assets = { ...BASE }
delete assets['apk-addons.json']
const d = device({ assets, sizes: { [STORAGE + '/worklet.bundle']: 100 } })
await planBoot({ storage: STORAGE, io: d.io })
t.alike(JSON.parse(d.written[STORAGE + '/shell.json']).apkAddons, [], 'empty, which the store reads as skip-nothing')
})
// ── the shell reports its own verdict ─────────────────────────────────────────────────────────────
//
// ⛔ THE BUG THIS FIXES, OBSERVED ON A HANDSET: "an update is ready, restart to apply" shown for ever
// while restarting changed nothing. favela-surface's updateWaiting() re-implemented the decision —
// `if (u.running) return u.staged !== u.running` — with no build-time comparison and no knowledge of
// failed.json, so it said YES on exactly the bundles chooseBundle refuses. Two rules for one question,
// and the one the user could see was the wrong one.
//
// The shell already RUNS chooseBundle at boot. It knows the verdict and the reason, so it reports them
// rather than leaving the page to guess. Report state, never a derived verdict.
test('a staged bundle that WILL boot is reported as applicable', async (t) => {
const staged = { treeHash: 'a'.repeat(64), file: 'x.bundle', builtAt: 9000 }
const d = device({
assets: BASE, sizes: { [STORAGE + '/worklet.bundle']: 100 },
files: { [BDIR + '/current.json']: JSON.stringify(staged), [BDIR + '/x.bundle']: 'S' }
})
const plan = await planBoot({ storage: STORAGE, io: d.io })
const shell = JSON.parse(d.written[STORAGE + '/shell.json'])
t.is(shell.bundle, 'a'.repeat(64), 'it booted')
t.is(shell.refused, null, 'nothing refused')
})
test('a staged bundle the APK supersedes is SILENT, not reported', async (t) => {
// ⛔ CORRECTED FROM MY OWN EARLIER EXPECTATION. I first had this report the refusal with its reason.
// On the handset that produced "this update cannot be applied" for a bundle older than the one
// running — accurate and unactionable. An APK newer than the staged bundle means that bundle was
// never a pending update; there is nothing to tell anyone.
const staged = { treeHash: 'b'.repeat(64), file: 'y.bundle', builtAt: 1000 }
const d = device({
assets: { ...BASE, 'worklet.bundle.built': '9999' },
sizes: { [STORAGE + '/worklet.bundle']: 100 },
files: { [BDIR + '/current.json']: JSON.stringify(staged), [BDIR + '/y.bundle']: 'S' }
})
const plan = await planBoot({ storage: STORAGE, io: d.io })
const shell = JSON.parse(d.written[STORAGE + '/shell.json'])
t.is(shell.bundle, null, 'the APK bundle booted')
t.is(shell.refused, null, 'and nothing is said about a bundle that was never a candidate')
})
test('a CONDEMNED staged bundle is reported refused too', async (t) => {
// Nothing the page could ever have known: failed.json lives on the shell side.
const staged = { treeHash: 'c'.repeat(64), file: 'z.bundle', builtAt: 9000 }
const d = device({
assets: BASE, sizes: { [STORAGE + '/worklet.bundle']: 100 },
files: {
[BDIR + '/current.json']: JSON.stringify(staged),
[BDIR + '/failed.json']: JSON.stringify(['c'.repeat(64)]),
[BDIR + '/z.bundle']: 'S'
}
})
await planBoot({ storage: STORAGE, io: d.io })
const shell = JSON.parse(d.written[STORAGE + '/shell.json'])
t.is(shell.bundle, null)
t.is(shell.refused.hash, 'c'.repeat(64))
t.ok(shell.refused.reason.includes('failed to boot'), shell.refused.reason)
})
test('nothing staged means nothing refused', async (t) => {
const d = device({ assets: BASE, sizes: { [STORAGE + '/worklet.bundle']: 100 } })
await planBoot({ storage: STORAGE, io: d.io })
t.is(JSON.parse(d.written[STORAGE + '/shell.json']).refused, null)
})
test('a refused bundle OLDER than what booted is not reported at all', async (t) => {
// ⛔ SEEN ON THE HANDSET. After installing 0.0.22 the page said "this update cannot be applied —
// staged bundle previously failed at boot" while running 0.0.22. True, and useless: the condemned
// bundle was 0.0.21, older than what booted and permanently dead. A notice nobody can act on is
// noise, and noise in the update line is how a real one gets ignored.
//
// A refusal is only worth reporting when the staged bundle WOULD have been an update.
const staged = { treeHash: 'a'.repeat(64), file: 'x.bundle', builtAt: 1000 }
const d = device({
assets: { ...BASE, 'worklet.bundle.built': '9000' },
sizes: { [STORAGE + '/worklet.bundle']: 100 },
files: {
[BDIR + '/current.json']: JSON.stringify(staged),
[BDIR + '/failed.json']: JSON.stringify(['a'.repeat(64)]),
[BDIR + '/x.bundle']: 'S'
}
})
await planBoot({ storage: STORAGE, io: d.io })
t.is(JSON.parse(d.written[STORAGE + '/shell.json']).refused, null, 'silent — it is history, not a pending update')
})
test('a refused bundle NEWER than what booted IS reported', async (t) => {
// The case worth saying out loud: a genuinely new release that cannot run.
const staged = { treeHash: 'b'.repeat(64), file: 'y.bundle', builtAt: 9000 }
const d = device({
assets: { ...BASE, 'worklet.bundle.built': '1000' },
sizes: { [STORAGE + '/worklet.bundle']: 100 },
files: {
[BDIR + '/current.json']: JSON.stringify(staged),
[BDIR + '/failed.json']: JSON.stringify(['b'.repeat(64)]),
[BDIR + '/y.bundle']: 'S'
}
})
await planBoot({ storage: STORAGE, io: d.io })
const r = JSON.parse(d.written[STORAGE + '/shell.json']).refused
t.is(r.hash, 'b'.repeat(64))
t.ok(r.reason.includes('failed to boot'), r.reason)
})