import assert from 'node:assert/strict' import { createHash } from 'node:crypto' import { mkdtemp, readFile, writeFile } from 'node:fs/promises' import { tmpdir } from 'node:os' import { join } from 'node:path' import test from 'node:test' import { TrustLedger } from '../stage0/lib/ledger.mjs' import { document, officialDshPolicy, keyPair, keyring, registryCandidate, registryKeyPair, signature, target } from './helpers.mjs' async function fixture() { const recovery = keyPair() return { recovery, ledger: new TrustLedger(await mkdtemp(join(tmpdir(), 'dsh-trust-ledger-')), recovery.publicKey), } } test('persists only increasing keyring generations and identical repeats', async () => { const { ledger, recovery } = await fixture() const current = keyPair() const next = keyPair() const first = document(keyring(1, current, next)) await ledger.acceptKeyring(first, signature(first, recovery)) await ledger.acceptKeyring(first, signature(first, recovery)) const conflicting = document({ ...keyring(1, current, next), issuedAt: '2026-08-19T02:00:00.000Z' }) await assert.rejects(ledger.acceptKeyring(conflicting, signature(conflicting, recovery))) const older = document(keyring(1, current, next)) const third = keyPair() const rotated = document(keyring(2, next, third, [current.keyId])) await ledger.acceptKeyring(rotated, signature(rotated, recovery)) await assert.rejects(ledger.acceptKeyring(older, signature(older, recovery)), { code: 'TRUST_ROLLBACK' }) }) test('accepts targets only from current release key and prevents sequence rollback', async () => { const { ledger, recovery } = await fixture() const current = keyPair() const next = keyPair() const ring = document(keyring(1, current, next)) await ledger.acceptKeyring(ring, signature(ring, recovery)) const first = document(target(1, 1)) await assert.rejects(ledger.acceptTarget(first, signature(first, next)), { code: 'TRUST_UNKNOWN_KEY' }) await ledger.acceptTarget(first, signature(first, current)) await ledger.acceptTarget(first, signature(first, current)) const conflicting = document({ ...target(1, 1), target: { changed: true } }) await assert.rejects(ledger.acceptTarget(conflicting, signature(conflicting, current))) const second = document(target(1, 2)) await ledger.acceptTarget(second, signature(second, current)) await assert.rejects(ledger.acceptTarget(first, signature(first, current)), { code: 'TRUST_ROLLBACK' }) }) test('requires an accepted keyring before accepting a target', async () => { const { ledger } = await fixture() const release = keyPair() const bytes = document(target(1, 1)) await assert.rejects(ledger.acceptTarget(bytes, signature(bytes, release)), /keyring/) }) test('records official DSH identity monotonically without choosing an update channel', async () => { const { ledger, recovery } = await fixture() const current = keyPair() const next = keyPair() const registry = registryKeyPair() const ring = document(keyring(1, current, next)) await ledger.acceptKeyring(ring, signature(ring, recovery)) const stable = document(target(1, 4, officialDshPolicy(registry))) await ledger.acceptTarget(stable, signature(stable, current)) const content = Buffer.from('rc8') const first = registryCandidate(registry, '0.1.0-rc.8', content) const digest = createHash('sha256').update(content).digest('hex') await ledger.acceptOfficialDsh(first, digest, content.byteLength) await ledger.acceptOfficialDsh(first, digest, content.byteLength) assert.equal((await ledger.currentOfficialDsh()).value.version, first.version) const older = registryCandidate(registry, '0.1.0-rc.6', content) await assert.rejects(ledger.acceptOfficialDsh(older, digest, content.byteLength), { code: 'TRUST_ROLLBACK' }) const conflicting = registryCandidate(registry, first.version, Buffer.from('changed')) await assert.rejects( ledger.acceptOfficialDsh(conflicting, createHash('sha256').update('changed').digest('hex'), 7), /different content/, ) }) test('rejects new official DSH imports after the target Release Key is revoked', async () => { const { ledger, recovery } = await fixture() const current = keyPair() const next = keyPair() const registry = registryKeyPair() const ring = document(keyring(1, current, next)) await ledger.acceptKeyring(ring, signature(ring, recovery)) const stable = document(target(1, 4, officialDshPolicy(registry))) await ledger.acceptTarget(stable, signature(stable, current)) const content = Buffer.from('rc8') const candidate = registryCandidate(registry, '0.1.0-rc.8', content) const digest = createHash('sha256').update(content).digest('hex') await ledger.acceptOfficialDsh(candidate, digest, content.byteLength) const future = keyPair() const rotated = document(keyring(2, next, future, [current.keyId])) await ledger.acceptKeyring(rotated, signature(rotated, recovery)) await assert.rejects(ledger.acceptOfficialDsh(candidate, digest, content.byteLength), { code: 'TRUST_REVOKED' }) await assert.rejects(ledger.currentOfficialDsh(), { code: 'TRUST_REVOKED' }) }) test('retains historical keyrings so an accepted target remains verifiable after rotation', async () => { const { ledger, recovery } = await fixture() const first = keyPair() const second = keyPair() const third = keyPair() const initialRing = document(keyring(1, first, second)) await ledger.acceptKeyring(initialRing, signature(initialRing, recovery)) const initialTarget = document(target(1, 7)) await ledger.acceptTarget(initialTarget, signature(initialTarget, first)) const rotatedRing = document(keyring(2, second, third, [first.keyId])) await ledger.acceptKeyring(rotatedRing, signature(rotatedRing, recovery)) assert.equal((await ledger.currentTarget()).value.targetSequence, 7) const rotatedTarget = document(target(2, 8)) await ledger.acceptTarget(rotatedTarget, signature(rotatedTarget, second)) assert.equal((await ledger.currentTarget()).value.keyId, second.keyId) }) test('serializes concurrent target acceptance in call order', async () => { const { ledger, recovery } = await fixture() const current = keyPair() const next = keyPair() const ring = document(keyring(1, current, next)) await ledger.acceptKeyring(ring, signature(ring, recovery)) const first = document(target(1, 1)) const second = document(target(1, 2)) await Promise.all([ ledger.acceptTarget(first, signature(first, current)), ledger.acceptTarget(second, signature(second, current)), ]) assert.equal((await ledger.currentTarget()).value.targetSequence, 2) }) test('does not overwrite a conflicting historical generation after interrupted promotion', async () => { const { ledger, recovery } = await fixture() const first = keyPair() const second = keyPair() const third = keyPair() const initial = document(keyring(1, first, second)) await ledger.acceptKeyring(initial, signature(initial, recovery)) const rotated = document(keyring(2, second, third, [first.keyId])) await ledger.acceptKeyring(rotated, signature(rotated, recovery)) const rotatedRecord = await readFile(ledger.keyringPath('current.record.json')) const initialRecord = await readFile(ledger.keyringPath('generations/1.record.json')) await writeFile(ledger.keyringPath('current.record.json'), initialRecord) const replacement = keyPair() const conflicting = document(keyring(2, second, replacement, [first.keyId, third.keyId])) await assert.rejects( ledger.acceptKeyring(conflicting, signature(conflicting, recovery)), /history conflicts/, ) assert.deepEqual(await readFile(ledger.keyringPath('generations/2.record.json')), rotatedRecord) })