130 lines
11 KiB
JavaScript
130 lines
11 KiB
JavaScript
import { test } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { mkdtempSync, mkdirSync, writeFileSync, rmSync, readFileSync } from 'node:fs';
|
|
import { tmpdir } from 'node:os';
|
|
import { join } from 'node:path';
|
|
const secret = 'sk-test-NOT-A-SECRET-1234';
|
|
function scratch(t) { const root = mkdtempSync(join(tmpdir(), 'r4-')); t.after(() => rmSync(root, { recursive: true, force: true })); return root; }
|
|
function safe(label, Type, props = {}, omitted = secret) { return error => {
|
|
assert.ok(error instanceof Type, error.stack);
|
|
for (const [key, value] of Object.entries(props)) assert.equal(error[key], value);
|
|
assert.ok(error.message.includes(label), error.message);
|
|
assert.doesNotMatch(error.message + error.stack, /sk-test|NOT-A-SECRET|Unexpected token/);
|
|
assert.ok(!error.message.includes(omitted), error.message);
|
|
assert.equal(error.cause, undefined);
|
|
return true;
|
|
}; }
|
|
import { checkCallerOpId, checkName, checkRepoPath, parseManifest, validateRow, loadDoc, serialize } from '../src/queue.mjs';
|
|
import { QueueError } from '../src/errors.mjs';
|
|
for (const [field, fn] of [
|
|
['op', () => checkCallerOpId(secret + '/', 'op')],
|
|
['op', () => checkCallerOpId(secret.toLowerCase() + '.outcome', 'op')],
|
|
['owner', () => checkName(secret, 'owner')],
|
|
['path', () => checkRepoPath(secret + '/', 'path')],
|
|
['manifest', () => parseManifest(secret + '\n')],
|
|
['row', () => validateRow({ id: secret })],
|
|
['version', () => loadDoc(Buffer.from(serialize({ version: secret, canonicalRoot: '/x', revision: 0, rows: [], log: [] })))],
|
|
]) test(`queue ${field} semantic refusal omits the value`, () => assert.throws(fn, safe(field, QueueError, { code: 2 }, secret.toLowerCase())));
|
|
import { checkTransport, parseBriefSpec, genesisRows, parseMigrationMap, render, sha256, genesisReceipt } from '../src/queue.mjs';
|
|
import { MAP_ROWS, mapText } from './helpers.mjs';
|
|
const at = '2026-11-10T00:00:00.000Z';
|
|
function doc() {
|
|
const map = parseMigrationMap(mapText()), rows = genesisRows(map, new Map(map.rows.filter(r => r.brief).map(r => [r.id, 'a'.repeat(40)])), 'genesis-op-0001', at, 'sage');
|
|
const entry = { rev: 0, op: 'genesis-op-0001', verb: 'genesis', args: { root: '/x', branch: 'refactor', map: 'docs/map.md' }, by: 'sage', at, semantics: 1,
|
|
result: { mapBlob: 'b'.repeat(40), highWater: map.highWater, retired: map.retired, rows, legacyView: 'legacy\n', receipt: genesisReceipt('genesis-op-0001', rows.length) }, viewSha: sha256(render(rows, 0)) };
|
|
return { version: 1, canonicalRoot: '/x', revision: 0, rows, log: [entry] };
|
|
}
|
|
for (const [field, edit, value = secret] of [
|
|
['id', r => { r.id = secret; }], ['state', r => { r.state = secret; }],
|
|
['createdAt', r => { r.createdAt = secret; }],
|
|
['createdAt', r => { r.createdAt = '2026-02-30'; }, '2026-02-30'],
|
|
['claim seat', r => { r.claim = { seat: secret, op: 'claimed-op-0001' }; }],
|
|
]) test(`row ${field} omits semantic value ${value === secret ? 'sentinel' : 'calendar control'}`, () => {
|
|
const r = doc().rows[0]; edit(r); assert.throws(() => validateRow(r), safe(field, QueueError, { code: 2 }, value));
|
|
});
|
|
for (const [field, edit] of [
|
|
['rev', d => { d.log[0].rev = secret; }], ['verb', d => { d.log[0].verb = secret; }],
|
|
['semantics', d => { d.log[0].semantics = secret; }], ['revision', d => { d.revision = secret; }],
|
|
]) test(`queue ${field} omits semantic value from the log/document`, () => {
|
|
const d = doc(); edit(d); assert.throws(() => loadDoc(Buffer.from(serialize(d))), safe(field, QueueError, { code: 2 }));
|
|
});
|
|
test('transport comment omits its value', () => assert.throws(() => checkTransport({ outcome: 'posted', status: 201, comment: secret, detail: 'fixture' }), safe('comment', QueueError, { code: 2 })));
|
|
test('brief omits its unparseable spec', () => assert.throws(() => parseBriefSpec(secret), safe('brief', QueueError, { code: 2 })));
|
|
test('manifest duplicate path omits the value', () => assert.throws(() => parseManifest(`${'a'.repeat(64)} ${secret}\n`.repeat(2)), safe('manifest', QueueError, { code: 2 })));
|
|
|
|
import { canonArgs, applyEntry, replay } from '../src/queue.mjs';
|
|
const seatValue = secret.toLowerCase(), digestValue = 'a'.repeat(40);
|
|
function reviewRow(row, state = 'requesting') {
|
|
row.state = 'in-review';
|
|
const posted = state === 'posted';
|
|
row.review = { rounds: [{ n: 1, op: seatValue, by: 'darkwing', at, issue: 1511,
|
|
candidate: { kind: 'commit', digest: digestValue, text: null }, request: 'comment',
|
|
attempts: [{ op: seatValue, by: 'darkwing', at, state, comment: posted ? 1 : null,
|
|
transport: posted ? { outcome: 'posted', status: 201, comment: 1, detail: 'fixture' } : null, resolutions: [] }],
|
|
duplicateRisk: false, receipts: [] }] };
|
|
}
|
|
function operation(verb, args, edit = () => {}, options = {}) {
|
|
const state = replay(doc().log), row = state.rows.get(6);
|
|
edit(row);
|
|
if (!options.unchecked) validateRow(row);
|
|
const by = options.by ?? 'sage', op = options.op ?? 'operation-op-0001';
|
|
const entry = { verb, args: options.raw ? args : canonArgs(verb, { id: 6, ...args }), by, op, at, semantics: 2 };
|
|
return applyEntry(state, entry, options.resolved ?? {});
|
|
}
|
|
const ownerValue = row => { row.owner = seatValue; if (row.claim) row.claim.seat = seatValue; };
|
|
for (const [name, field, fn, omitted = seatValue] of [
|
|
['canonical duplicate reviewer', 'repeated value', () => canonArgs('set', { id: 6, field: 'reviewers', value: [seatValue, seatValue] })],
|
|
['canonical state', 'state', () => canonArgs('move', { id: 6, to: secret }), secret],
|
|
['canonical verb', 'verb', () => canonArgs(secret, {}), secret],
|
|
['claimed owner authority', 'owner', () => operation('move', { to: 'blocked', reason: 'pause' }, ownerValue, { by: 'outsider' })],
|
|
['start owner', 'owner', () => operation('move', { to: 'in-progress' }, r => { ownerValue(r); r.state = 'briefed'; r.claim = null; }, { by: 'outsider' })],
|
|
['review claimant', 'claimant', () => operation('move', { to: 'in-review', candidate: 'HEAD' }, ownerValue, { by: 'outsider' })],
|
|
['close gate owner', 'gate owner', () => operation('move', { to: 'done' }, r => { reviewRow(r, 'posted'); r.gateOwner = seatValue; }, { by: 'outsider' })],
|
|
['set owner reviewer', 'owner', () => operation('set', { field: 'reviewers', value: [seatValue] }, ownerValue)],
|
|
['set stored piece', 'piece', () => operation('set', { field: 'piece', value: secret }, r => { r.piece = secret; }), secret],
|
|
['set pinned brief', 'brief', () => { const r = doc().rows.find(r => r.id === 6), brief = { ...r.brief, blob: digestValue };
|
|
return operation('set', { field: 'brief', value: `${brief.path}#${brief.anchor}` }, r => { r.brief = brief; }, { resolved: { brief } }); }, digestValue],
|
|
['release claimant', 'claimant', () => operation('release', {}, ownerValue, { by: 'outsider' })],
|
|
['assign existing owner', 'owned', () => operation('assign', { seat: seatValue }, ownerValue)],
|
|
['assign reviewer', 'reviewers', () => operation('assign', { seat: seatValue }, r => { r.reviewers = [seatValue]; })],
|
|
['note owner', 'owner', () => operation('note', { text: 'note' }, ownerValue, { by: 'outsider' })],
|
|
['add owner reviewer', 'owner', () => operation('add', { piece: 'piece', gate: 'gate', brief: 'docs/brief.md#Brief', owner: seatValue, reviewers: [seatValue] }, () => {},
|
|
{ resolved: { brief: { path: 'docs/brief.md', anchor: 'Brief', blob: digestValue } } })],
|
|
['unresolved request', 'unresolved review request', () => operation('review-request', {}, r => reviewRow(r))],
|
|
['missing approval', 'approval', () => operation('move', { to: 'done' }, r => { reviewRow(r, 'posted'); r.gateOwner = 'sage'; r.reviewers = [seatValue]; })],
|
|
['missing attempt without round', 'review request', () => operation('review-resolve', { attempt: seatValue, comment: 1 })],
|
|
['missing attempt in round', 'review request', () => operation('review-resolve', { attempt: seatValue + '-missing', comment: 1 }, r => reviewRow(r))],
|
|
['posted request', 'posted request', () => operation('review-request', {}, r => reviewRow(r, 'posted'))],
|
|
['outcome op', 'outcome', () => operation('review-outcome', { attempt: seatValue, outcome: 'posted', status: 201, comment: 1, detail: 'fixture' }, r => reviewRow(r))],
|
|
['outcome actor', 'outcome', () => operation('review-outcome', { attempt: seatValue, outcome: 'posted', status: 201, comment: 1, detail: 'fixture' }, r => { reviewRow(r); r.review.rounds[0].attempts[0].by = seatValue; },
|
|
{ by: 'outsider', op: seatValue + '.outcome' })],
|
|
['duplicate outcome', 'outcome', () => operation('review-outcome', { attempt: seatValue, outcome: 'posted', status: 201, comment: 1, detail: 'fixture' }, r => reviewRow(r, 'posted'),
|
|
{ by: 'darkwing', op: seatValue + '.outcome' })],
|
|
['settled resolution', 'request', () => operation('review-resolve', { attempt: seatValue, comment: 1 }, r => reviewRow(r, 'posted'))],
|
|
['reviewer list', 'reviewer', () => operation('review-record', { verdict: 'approve', comment: 1, candidate: digestValue }, r => { reviewRow(r, 'posted'); r.reviewers = [seatValue]; }, { by: 'outsider' })],
|
|
['candidate mismatch', 'candidate', () => operation('review-record', { verdict: 'approve', comment: 1, candidate: 'b'.repeat(40) }, r => reviewRow(r, 'posted'), { by: 'filbert' }), digestValue],
|
|
['duplicate verdict', 'verdict', () => operation('review-record', { verdict: 'approve', comment: 1, candidate: digestValue }, r => { reviewRow(r, 'posted'); r.reviewers = [seatValue];
|
|
r.review.rounds[0].receipts = [{ reviewer: seatValue, op: 'receipt-op-0001', at, verdict: 'approve', comment: 1, candidate: digestValue }]; }, { by: seatValue })],
|
|
['legacy candidate evidence', 'candidate', () => operation('move', { to: 'done', evidence: `comment=1,round=1,candidate=${'b'.repeat(40)}` }, r => {
|
|
reviewRow(r); r.gateOwner = 'sage'; const round = r.review.rounds[0]; round.request = 'none'; delete round.attempts; delete round.duplicateRisk; delete round.receipts;
|
|
}), digestValue],
|
|
['direct unknown operation', 'verb', () => operation(secret, {}, () => {}, { raw: true }), secret],
|
|
// outcomeState also defends direct callers with an inconsistent transport record.
|
|
['inconsistent outcome record', 'outcome', () => operation('review-outcome', { attempt: seatValue, outcome: 'posted', status: 201, comment: 1, detail: 'fixture' }, r => reviewRow(r, 'failed'),
|
|
{ by: 'darkwing', op: seatValue + '.outcome', unchecked: true })],
|
|
]) test(`queue replay refusal ${name} omits stored values`, () => assert.throws(fn, safe(field, QueueError, { code: 2 }, omitted)));
|
|
|
|
test('migration map shape omits an unchecked row id', () => assert.throws(() => parseMigrationMap(mapText([{ id: secret }])), safe('migration map row', QueueError, { code: 2 })));
|
|
test('migration map brief shape omits an unchecked row id', () => {
|
|
const row = structuredClone(MAP_ROWS[1]); row.id = secret; row.brief = {};
|
|
assert.throws(() => parseMigrationMap(mapText([row])), safe('brief', QueueError, { code: 2 }));
|
|
});
|
|
test('migration map duplicate owner omits unvalidated values', () => {
|
|
const row = structuredClone(MAP_ROWS[1]); row.owner = secret; row.reviewers = [secret];
|
|
assert.throws(() => parseMigrationMap(mapText([row])), safe('owner', QueueError, { code: 2 }));
|
|
});
|
|
test('BASE control: genesis expected claim already omits the stored op', () => {
|
|
const d = doc(); d.log[0].op = seatValue; d.log[0].result.receipt = genesisReceipt(seatValue, d.rows.length);
|
|
assert.throws(() => loadDoc(Buffer.from(serialize(d))), safe('claim', QueueError, { code: 2 }, seatValue));
|
|
});
|