ui-ota publish: the dl token follows packaged-config.sh's .next rule (IGNEUM_DL_TOKEN_FILE, else dl-token.next while a rotation runs, else dl-token), so a bundle staged during the rotation lands in the new folder and --verify reads the new URL; tokenFile and readToken exported, token.test.mjs on the gate, known-failed first on the direct read (7 October 2026, the dl token rotates with 0.3.22)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
igneum-labs 2026-10-07 19:20:28 +00:00
parent ecc8fb5443
commit bd29802e1e
3 changed files with 37 additions and 3 deletions

View file

@ -104,7 +104,7 @@ tree_checks() {
run "chain scene parity: one recorded feed through the home fold, /live and the app's Inspect view on build-2, three frames each pixel-equal apart from the app's own-key overlay (a changed token fails first; skipped with no box and no Playwright)" bash tools/scene/parity-remote.sh
run "explorer, emission and public stats unit tests" node --test site/lib/explorer.test.mjs site/lib/emission.test.mjs site/lib/money.test.mjs site/api/public-stats.test.mjs
run "ship tool self-test" node tools/ship-app.mjs --self-test
run "interface bundle publisher self-test (pack, and sign when the signer is built)" node tools/ui-ota/publish.mjs --self-test
run "interface bundle publisher self-test (pack, and sign when the signer is built)" node tools/ui-ota/publish.mjs --self-test && node --test tools/ui-ota/token.test.mjs
run "the interface and the app entries are one UI tree (version pair; self-test, then the staged manifest when one is given)" bash -c 'node tools/ui-ota/pair-check.mjs --self-test && { [ -z "${IGNEUM_MANIFEST:-}" ] || node tools/ui-ota/pair-check.mjs "$IGNEUM_MANIFEST"; }'
run "heat gate reader self-test (a known hold passes, a known drift fails)" node tools/heat-gate.mjs --self-test
run "relay unit tests" node --test relay/test/parse.test.mjs relay/test/auth.test.mjs relay/test/wake.test.mjs relay/test/ember.test.mjs

View file

@ -36,6 +36,13 @@ const flag = (name) => process.argv.includes(name);
const STAMP = new Date(Date.UTC(2026, 0, 1));
const threePart = (v) => /^\d+\.\d+\.\d+$/.test(v);
// the dl token (packaged-config.sh's igneum_secret_file rule, 7 October 2026): IGNEUM_DL_TOKEN_FILE when set, else
// dl-token.next while a rotation runs, else dl-token; a publish during the rotation lands in the NEW folder, and a cut that
// must target the old one says so with the variable
export function tokenFile(configDir, env) { env = env || process.env; if (env.IGNEUM_DL_TOKEN_FILE) return env.IGNEUM_DL_TOKEN_FILE; const next = path.join(configDir, 'dl-token.next'); return fs.existsSync(next) ? next : path.join(configDir, 'dl-token'); }
export function readToken(configDir, env) { return fs.readFileSync(tokenFile(configDir, env), 'utf8').trim(); }
const CONFIG_DIR = path.join(os.homedir(), '.config/igneum');
export function sha256(file) { return createHash('sha256').update(fs.readFileSync(file)).digest('hex'); }
// Copies the served files into a clean folder with the VERSION stamped, packs them in sorted order (one tar, the
@ -111,7 +118,7 @@ function selfTest() {
}
function verifyLive() {
const token = fs.readFileSync(path.join(os.homedir(), '.config/igneum/dl-token'), 'utf8').trim();
const token = readToken(CONFIG_DIR);
const url = arg('--url', `https://dl.igneum.network/dl/${token}/igneum-app-latest.json`);
const dlsite = process.env.IGNEUM_DLSITE || fs.readFileSync(path.join(os.homedir(), '.config/igneum/dlsite-dir'), 'utf8').trim();
const text = run('curl', ['-fsSL', '--max-time', '20', url]);
@ -143,7 +150,7 @@ function main() {
if (!minEngine || !threePart(minEngine)) { console.error('--min-engine major.minor.patch is required (the lowest app version that may serve this bundle)'); process.exit(2); }
const dry = flag('--dry-run');
const s = ensureSigner();
const token = fs.readFileSync(path.join(os.homedir(), '.config/igneum/dl-token'), 'utf8').trim();
const token = readToken(CONFIG_DIR);
const dlsite = process.env.IGNEUM_DLSITE || fs.readFileSync(path.join(os.homedir(), '.config/igneum/dlsite-dir'), 'utf8').trim();
const dest = path.join(dlsite, 'dl', token);
const scratch = fs.mkdtempSync(path.join(os.tmpdir(), 'ui-ota-'));

View file

@ -0,0 +1,27 @@
// node --test tools/ui-ota/token.test.mjs (the dl token rotation, 7 October 2026: tools/ui-ota/publish.mjs read
// ~/.config/igneum/dl-token directly, so after the rotation it would stage into the old folder and verify the old URL).
// The rule is packaged-config.sh's igneum_secret_file: IGNEUM_DL_TOKEN_FILE when set, else dl-token.next when present,
// else dl-token; the folder from IGNEUM_DLSITE, else dlsite-dir. Known-failed first on the direct read.
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { mkdtempSync, writeFileSync, rmSync, readFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
const here = dirname(fileURLToPath(import.meta.url));
const mod = await import(join(here, 'publish.mjs'));
test('the token file follows the .next rule: the env var, else dl-token.next, else dl-token; the folder from IGNEUM_DLSITE else dlsite-dir', () => {
assert.equal(typeof mod.tokenFile, 'function', 'publish.mjs exports tokenFile');
const dir = mkdtempSync(join(tmpdir(), 'igneum-token-'));
try {
writeFileSync(join(dir, 'dl-token'), 'oldtoken\n');
assert.equal(mod.tokenFile(dir, {}), join(dir, 'dl-token'), 'plain file when no .next');
writeFileSync(join(dir, 'dl-token.next'), 'newtoken\n');
assert.equal(mod.tokenFile(dir, {}), join(dir, 'dl-token.next'), 'the .next file wins while a rotation runs');
assert.equal(mod.tokenFile(dir, { IGNEUM_DL_TOKEN_FILE: join(dir, 'dl-token') }), join(dir, 'dl-token'), 'the variable pins the old folder knowingly');
assert.equal(mod.readToken(dir, {}), 'newtoken');
const src = readFileSync(join(here, 'publish.mjs'), 'utf8');
assert.equal((src.match(/'\.config\/igneum\/dl-token'\)/g) || []).length, 0, 'no direct read of dl-token left');
} finally { rmSync(dir, { recursive: true, force: true }); }
});