Space / dev /constraints /integration.test.js
Harp404's picture
Round 2 — Constraint Awareness: Completion Signal
87bb7d6
Raw History Blame Contribute Delete
20.9 kB
/**
* Adversarial integration tests.
* node dev/constraints/integration.test.js
*
* The engine test suite proves the state machine. This one attacks everything
* around it: malformed rulebooks, hostile contexts, corrupt artefacts, absurd
* numbers, and every path where a module could quietly produce a confident
* answer it has not earned.
*
* The bar throughout: a module may return a correct answer or refuse. It may
* never guess, default, impute, or crash.
*
* Zero dependencies, zero network.
*/
'use strict';
const CE = require('./engine');
const { maneuverRulebook, classify, rightOfWay, screeningInterval, LIMITS } = require('./rulebooks/orbital');
const { reentryRulebook } = require('./rulebooks/reentry');
const { releaseRulebook, SCENARIOS } = require('./rulebooks/release-gate');
const CV = require('./voting');
const { PropellantLedger } = require('./ledger');
const RE = require('./reentry');
const CF = require('./conformal');
const A = require('./authoring');
const FL = require('./flares');
const SW = require('./spaceweather');
let pass = 0, fail = 0;
const G = '\x1b[32m', R = '\x1b[31m', B = '\x1b[1m', X = '\x1b[0m';
const ok = (n, c, x) => { if (c) { pass++; console.log(` ${G}✓${X} ${n}`); } else { fail++; console.log(` ${R}✗${X} ${n}${x ? ' → ' + x : ''}`); } };
const eq = (n, a, b) => ok(n, a === b, `got ${JSON.stringify(a)}, want ${JSON.stringify(b)}`);
const sec = (t) => console.log(`\n${B}${t}${X}`);
/** Assert a call does not throw, whatever it is fed. */
const noThrow = (n, fn) => { try { fn(); ok(n, true); } catch (e) { ok(n, false, 'threw: ' + e.message); } };
const NOW = Date.parse('2026-08-22T12:00:00Z');
const H = 3600000;
// ---------------------------------------------------------------------------
sec('ENGINE — hostile rulebooks and contexts');
noThrow('rule with no evaluate() does not crash the engine', () => {
const r = CE.evaluate({ rulebook: { id: 'x', rules: [{ id: 'A', title: 'no evaluator', class: 'HARD' }] }, context: {}, now: NOW });
if (r.rules[0].state !== CE.STATE.UNEVALUATED) throw new Error('became ' + r.rules[0].state);
});
noThrow('evaluate() returning garbage becomes UNEVALUATED', () => {
const r = CE.evaluate({ rulebook: { id: 'x', rules: [{ id: 'A', class: 'HARD', evaluate: () => 42 }] }, context: {}, now: NOW });
if (r.rules[0].state !== CE.STATE.UNEVALUATED) throw new Error('became ' + r.rules[0].state);
});
noThrow('evaluate() returning an unknown state string is tolerated', () => {
CE.evaluate({ rulebook: { id: 'x', rules: [{ id: 'A', class: 'HARD', evaluate: () => ({ state: 'BANANA' }) }] }, context: {}, now: NOW });
});
noThrow('null context does not crash', () => CE.evaluate({ rulebook: maneuverRulebook, context: null, now: NOW }));
noThrow('undefined context does not crash', () => CE.evaluate({ rulebook: maneuverRulebook, context: undefined, now: NOW }));
noThrow('context with circular reference does not crash', () => {
const c = { conj: {} }; c.self = c;
CE.evaluate({ rulebook: maneuverRulebook, context: c, now: NOW });
});
noThrow('waivers as a non-array is tolerated', () => CE.evaluate({ rulebook: maneuverRulebook, context: {}, waivers: 'nope', now: NOW }));
noThrow('empty rulebook is tolerated', () => CE.evaluate({ rulebook: { id: 'e', rules: [] }, context: {}, now: NOW }));
{
const r = CE.evaluate({ rulebook: { id: 'e', rules: [] }, context: {}, now: NOW });
eq('empty rulebook signals COMPLETE', r.signal, CE.SIGNAL.COMPLETE);
eq('...with progress 0 (nothing applicable)', r.progress, 0);
}
{
let threw = false;
try { CE.evaluate({ rulebook: { id: 'x' }, context: {} }); } catch { threw = true; }
ok('rulebook without a rules array throws rather than passing', threw);
}
{
// A rule that mutates the context must not corrupt other rules' view of it.
const ctx = { conj: { min_range_km: 5 } };
CE.evaluate({
rulebook: { id: 'm', rules: [
{ id: 'A', class: 'SOFT', evaluate: (c) => { c.conj.min_range_km = 999; return CE.R.pass('ok'); } },
{ id: 'B', class: 'SOFT', evaluate: (c) => CE.R.pass('saw ' + c.conj.min_range_km) },
] }, context: ctx, now: NOW,
});
ok('a mutating rule is visible but does not crash later rules', true);
}
sec('ENGINE — absurd numbers');
for (const v of [NaN, Infinity, -Infinity, null, undefined, '', 'abc', {}, [], -0]) {
noThrow(`miss_range = ${JSON.stringify(v)} handled`, () => {
CE.evaluate({ rulebook: maneuverRulebook, context: { conj: { min_range_km: v, probability: v, tca_ms: v, tle_age_days: v } }, now: NOW });
});
}
{
const r = CE.evaluate({ rulebook: maneuverRulebook, context: { conj: { min_range_km: NaN, probability: NaN } }, now: NOW });
ok('NaN inputs never produce SATISFIED on the rules that read them',
!['FR-01', 'FR-12'].some((id) => r.rules.find((x) => x.id === id).state === CE.STATE.SATISFIED));
}
sec('ENGINE — rollup edge cases');
noThrow('rollup(null)', () => CE.rollup(null));
noThrow('rollup([null, undefined])', () => CE.rollup([null, undefined]));
eq('rollup of nothing has 0 items', CE.rollup([]).items, 0);
{
const one = CE.evaluate({ rulebook: { id: 'x', rules: [{ id: 'A', class: 'HARD', evaluate: () => CE.R.fail('no') }] }, context: {}, now: NOW });
eq('rollup takes the worst state', CE.rollup([one, one]).signal, CE.SIGNAL.BLOCKED);
}
// ---------------------------------------------------------------------------
sec('CLASSIFY / RIGHT OF WAY — hostile names');
for (const n of [null, undefined, '', ' ', 123, {}, [], '', 'A'.repeat(5000)]) {
noThrow(`classify(${JSON.stringify(n)?.slice(0, 20)}) does not crash`, () => classify(n));
}
eq('empty name is unknown, not a guess', classify(''), null);
eq('whitespace name is unknown', classify(' '), null);
noThrow('rightOfWay with nulls', () => rightOfWay(null, null, null, null));
{
const rw = rightOfWay('A', 'B', 'NONMANEUVERABLE', 'NONMANEUVERABLE');
ok('debris x debris names nobody as the mover', rw.mover === null && rw.resolved === true);
const rw2 = rightOfWay('A', 'B', 'MANEUVERABLE', 'MANEUVERABLE');
ok('same class is explicitly UNRESOLVED, not arbitrated', rw2.resolved === false);
}
// ---------------------------------------------------------------------------
sec('VOTING — degenerate clusters');
{
const blocked = CE.evaluate({ rulebook: { id: 'x', rules: [{ id: 'FR-03', title: 't', class: 'HARD', waivable: false, evaluate: () => CE.R.fail('no') }] }, context: {}, now: NOW });
noThrow('poll with no nodes', () => CV.poll([], blocked, 3));
noThrow('poll with null nodes', () => CV.poll(null, blocked, 3));
noThrow('poll with unknown node ids', () => CV.poll([{ id: 999, name: 'X', online: true }], blocked, 3));
eq('empty cluster cannot approve', CV.poll([], blocked, 3).status, 'DENIED');
const p = CV.poll([{ id: 1, online: true }], blocked, 3);
eq('single node cannot reach quorum 3', p.status, 'DENIED');
ok('every vote carries a reason even for unknown nodes',
CV.poll([{ id: 999, name: 'X', online: true }], blocked, 3).votes.every((v) => v.reason && v.reason.length));
eq('offline nodes are excluded from the poll', CV.poll([{ id: 1, online: false }, { id: 2, online: true }], blocked, 3).votes.length, 1);
}
{
// Emergency must refuse a non-negotiable violation and refuse unknowns.
const nn = CE.evaluate({ rulebook: { id: 'x', rules: [{ id: 'FR-03', title: 't', class: 'HARD', waivable: false, evaluate: () => CE.R.fail('no') }] }, context: {}, now: NOW });
ok('emergency refuses a non-negotiable violation', CV.emergencyWaivers(nn).allowed === false);
const un = CE.evaluate({ rulebook: { id: 'x', rules: [{ id: 'Z', title: 't', class: 'HARD', waivable: true, evaluate: () => CE.R.unknown('no data') }] }, context: {}, now: NOW });
ok('emergency refuses while a rule is unevaluated', CV.emergencyWaivers(un).allowed === false);
const cl = CE.evaluate({ rulebook: { id: 'x', rules: [{ id: 'Z', title: 't', class: 'HARD', waivable: true, evaluate: () => CE.R.pass('fine') }] }, context: {}, now: NOW });
ok('emergency on a clean report allows, with no waivers', CV.emergencyWaivers(cl).allowed === true && CV.emergencyWaivers(cl).waivers.length === 0);
}
// ---------------------------------------------------------------------------
sec('LEDGER — arithmetic cannot be cheated');
{
const L = new PropellantLedger();
noThrow('get("") is safe', () => L.get(''));
eq('get("") is null, not a phantom asset', L.get(''), null);
noThrow('debit with NaN', () => L.debit('SAT', NaN));
ok('NaN debit refused', L.debit('SAT', NaN).ok === false);
ok('negative debit does not create propellant', (() => {
const s = new PropellantLedger(); s.seed('S', { used_ms: 5 });
const before = s.get('S').ca_remaining_ms;
s.debit('S', -100);
return s.get('S').ca_remaining_ms <= before;
})());
const s2 = new PropellantLedger(); s2.seed('S', { used_ms: 14.9 });
eq('a burn beyond the allocation is refused', s2.debit('S', 5).ok, false);
ok('...and the balance is untouched after a refusal', s2.get('S').ca_used_ms === 14.9);
ok('the disposal reserve is never reachable', (() => {
const s = new PropellantLedger(); s.seed('S', { used_ms: 0 });
for (let i = 0; i < 100; i++) s.debit('S', 1);
return s.get('S').ca_used_ms <= s.get('S').ca_allocation_ms;
})());
ok('seeded history is stable across instances',
new PropellantLedger().get('STARLINK-1008').ca_used_ms === new PropellantLedger().get('STARLINK-1008').ca_used_ms);
}
// ---------------------------------------------------------------------------
sec('RE-ENTRY — determinism and refusal');
{
const o = { entry_lat: -20, entry_lon: -140, inclination_deg: 53, ballistic_coefficient: 80, kp: 2, seed: 'fixed' };
const a = RE.footprint(o), b = RE.footprint(o);
eq('identical inputs give an identical footprint', a.span_km, b.span_km);
eq('...and identical population exposure', a.mean_population_density, b.mean_population_density);
const c = RE.footprint({ ...o, seed: 'other' });
ok('a different seed gives a different sample path', c.span_km !== a.span_km || c.mean_population_density !== a.mean_population_density);
ok('missing Kp refuses rather than assuming quiet', RE.footprint({ ...o, kp: undefined }).unresolved === true);
ok('null Kp refuses', RE.footprint({ ...o, kp: null }).unresolved === true);
noThrow('NaN Kp', () => RE.footprint({ ...o, kp: NaN }));
eq('casualty of an unresolved footprint is null', RE.casualtyExpectancy({ unresolved: true }, 3.5), null);
eq('casualty with an unknown casualty area is null', RE.casualtyExpectancy(a, null), null);
eq('casualty with NaN area is null', RE.casualtyExpectancy(a, NaN), null);
eq('consequence of an unresolved footprint is null', RE.consequenceProfile({ unresolved: true }), null);
for (const [lat, lon] of [[91, 0], [-91, 0], [0, 181], [0, -181], [NaN, NaN]]) {
noThrow(`cellAt(${lat}, ${lon}) clamps rather than throwing`, () => RE.cellAt(lat, lon));
}
ok('an extreme ballistic coefficient still produces a finite span',
Number.isFinite(RE.footprint({ ...o, ballistic_coefficient: 100000 }).span_km));
}
// ---------------------------------------------------------------------------
sec('CONFORMAL — refuses to fabricate a guarantee');
{
ok('too few pairs is refused', CF.calibrate([{ predicted: 1, actual: 1 }]).ok === false);
ok('empty is refused', CF.calibrate([]).ok === false);
noThrow('null input', () => CF.calibrate(null));
const pairs = Array.from({ length: 200 }, (_, i) => ({ predicted: 1 + (i % 7) * 0.1, actual: 1 + (i % 5) * 0.12 }));
const cal = CF.calibrate(pairs);
ok('200 pairs calibrate', cal.ok === true);
ok('coverage is reported', cal.coverage === 0.95);
eq('interval on a bad calibrator is null', CF.interval({ ok: false }, 1), null);
eq('interval on a non-positive value in log space is null', CF.interval(cal, 0), null);
const iv = CF.interval(cal, 1);
ok('the interval brackets the point estimate', iv.lower <= iv.point && iv.point <= iv.upper);
eq('a straddling interval is UNEVALUATED', CF.decide({ point: 1, lower: 0.5, upper: 2, coverage: 0.95 }, 1, 'below').verdict, 'UNEVALUATED');
eq('an interval entirely below the limit is SATISFIED', CF.decide({ point: 0.1, lower: 0.05, upper: 0.2, coverage: 0.95 }, 1, 'below').verdict, 'SATISFIED');
eq('an interval entirely above is VIOLATED', CF.decide({ point: 9, lower: 8, upper: 10, coverage: 0.95 }, 1, 'below').verdict, 'VIOLATED');
eq('decide(null) is UNEVALUATED', CF.decide(null, 1).verdict, 'UNEVALUATED');
// Wired lookups
ok('a calibrated age returns an interval', !!screeningInterval(0.8));
eq('an uncalibrated age returns null, not a neighbour', screeningInterval(30), null);
eq('NaN age returns null', screeningInterval(NaN), null);
}
// ---------------------------------------------------------------------------
sec('AUTHORING — the model does not get to decide');
{
const base = { id: 'ZZ-1', key: 'K', title: 'A title', field: 'conj.min_range_km', op: 'lte', value: 5, class: 'SOFT', waivable: true, authority: 'operator-declared', rationale: 'x'.repeat(40) };
ok('a well-formed rule validates', A.validate(base, []).ok === true);
const bad = {
'field not in the allow-list': { ...base, field: 'process.env.SECRET' },
'nested traversal field': { ...base, field: '__proto__.polluted' },
'unsupported operator': { ...base, op: 'exec' },
'operator as code': { ...base, op: '(()=>{})' },
'missing authority': { ...base, authority: '' },
'rationale too short': { ...base, rationale: 'no' },
'class not HARD/SOFT': { ...base, class: 'MEDIUM' },
'waivable not boolean': { ...base, waivable: 'yes' },
'value not numeric': { ...base, value: 'five' },
'bad id shape': { ...base, id: 'DROP TABLE' },
'boolean field with numeric op': { ...base, field: 'plan.clear_vs_catalogue', op: 'lte', value: 1 },
};
for (const [name, d] of Object.entries(bad)) ok(`refused: ${name}`, A.validate(d, []).ok === false);
ok('a model declining honestly is surfaced as such', A.validate({ error: 'cannot express that' }, []).model_declined === true);
noThrow('validate(null)', () => A.validate(null, []));
noThrow('validate on an array', () => A.validate([], []));
// Prototype pollution must be impossible.
const store = new A.AuthoredRules();
store.add({ ...base, id: 'ZZ-2', field: 'conj.min_range_km' }, []);
const compiled = store.rules[0];
noThrow('compiled rule runs on an empty context', () => compiled.evaluate({ now: NOW }));
eq('...and reports UNEVALUATED, not a pass', compiled.evaluate({ now: NOW }).state, CE.STATE.UNEVALUATED);
ok('Object.prototype was not polluted', ({}).polluted === undefined);
// Probe must expose an inverted rule.
const inv = A.probe(A.compile({ ...base, id: 'ZZ-3', field: 'cluster.online_nodes', op: 'lt', value: 3 }));
const cor = A.probe(A.compile({ ...base, id: 'ZZ-4', field: 'cluster.online_nodes', op: 'gte', value: 3 }));
ok('probing distinguishes an inverted rule from a correct one',
JSON.stringify(inv.map((p) => p.result)) !== JSON.stringify(cor.map((p) => p.result)));
ok('TLA+ is emitted for an authored rule', A.toTla(compiled).includes('status = "APPROVED"'));
ok('a non-negotiable authored rule also emits a never-waived invariant',
A.toTla(A.compile({ ...base, id: 'ZZ-5', waivable: false })).includes('NeverWaived'));
}
// ---------------------------------------------------------------------------
sec('FLARES — parser robustness');
{
eq('classify(0) is null', FL.classify(0), null);
eq('classify(-1) is null', FL.classify(-1), null);
eq('classify(NaN) is null', FL.classify(NaN), null);
ok('an X-class flux classifies as X', FL.classify(2e-4).startsWith('X'));
ok('an M-class flux classifies as M', FL.classify(5e-5).startsWith('M'));
eq('R-scale of an X1 flare is R3', FL.rScale(1.2e-4).scale, 'R3');
eq('R-scale below M1 is R0', FL.rScale(1e-6).scale, 'R0');
eq('rScale(NaN) is null', FL.rScale(NaN), null);
eq('align([]) is empty', FL.align([]).length, 0);
noThrow('align on malformed records', () => FL.align([{ time_tag: 'nonsense', flux: 'x', energy: 'z' }, null, {}]));
ok('records missing one band are dropped, not half-used',
FL.align([{ time_tag: '2026-01-01T00:00:00Z', flux: 1e-6, energy: '0.1-0.8nm' }]).length === 0);
ok('forecast on a short series refuses', FL.forecast([{ t: 1, soft: 1e-6, hard: 1e-8 }]).available === false);
const flat = Array.from({ length: 120 }, (_, i) => ({ t: NOW + i * 60000, soft: 1e-6, hard: 1e-8 }));
const f = FL.forecast(flat);
ok('a flat quiet series forecasts no blackout', f.available && f.blackout_expected === false);
eq('...and predicts no impact', FL.predictedImpact(f).expected, false);
eq('predictedImpact(null) is null', FL.predictedImpact(null), null);
eq('nowcast on a flat series finds no flare', FL.nowcast(flat).length, 0);
noThrow('nowcast on an empty series', () => FL.nowcast([]));
}
// ---------------------------------------------------------------------------
sec('SPACE WEATHER — derived zones');
{
eq('gScale below storm', SW.gScale(2), 'G0');
eq('gScale Kp 5 is G1', SW.gScale(5), 'G1');
eq('gScale Kp 9 is G5', SW.gScale(9), 'G5');
eq('gScale is clamped above 9', SW.gScale(99), 'G5');
eq('gScale(NaN) is G0', SW.gScale(NaN), 'G0');
eq('zones(null) is null', SW.zones(null), null);
eq('groundSegment(null) is null', SW.groundSegment(null), null);
const quiet = SW.groundSegment({ kp: 1, scale_s: 'S0' });
eq('all stations reachable when quiet', quiet.stations_available, quiet.stations_assigned);
const storm = SW.groundSegment({ kp: 9, scale_s: 'S3' });
ok('a radiation storm takes polar stations offline', storm.stations_available < storm.stations_assigned);
ok('...and never takes them all, at these latitudes', storm.stations_available > 0);
ok('every offline station states a reason', storm.stations.filter((s) => !s.available).every((s) => s.reason));
eq('an unknown replay is null', SW.replay('nope'), null);
ok('the gannon replay is complete', !!SW.replay('gannon').groundSegment);
}
// ---------------------------------------------------------------------------
sec('CROSS-RULEBOOK — invariants that must hold everywhere');
{
const books = [maneuverRulebook, reentryRulebook, releaseRulebook];
const all = books.flatMap((b) => b.rules);
ok('every rule has a unique id within its book',
books.every((b) => new Set(b.rules.map((r) => r.id)).size === b.rules.length));
ok('every rule cites an authority', all.every((r) => r.authority && r.authority.length > 4));
ok('every rule explains itself', all.every((r) => r.rationale && r.rationale.length > 20));
ok('every rule states its requirement', all.every((r) => r.requirement && r.requirement.length > 10));
ok('no SOFT rule is marked non-negotiable', all.every((r) => !(r.class === 'SOFT' && r.waivable === false)));
ok('every rule has an evaluate()', all.every((r) => typeof r.evaluate === 'function'));
// Every rule, fed an empty context, must report UNEVALUATED or NOT_APPLICABLE.
// Never SATISFIED — that would be a pass earned from no evidence at all.
for (const b of books) {
const rep = CE.evaluate({ rulebook: b, context: {}, now: NOW });
const wrong = rep.rules.filter((r) => r.state === CE.STATE.SATISFIED);
ok(`${b.id}: no rule is SATISFIED on an empty context`, wrong.length === 0, wrong.map((r) => r.id).join(','));
ok(`${b.id}: an empty context is never authorised`, rep.authorised === false);
}
// A waiver on a non-negotiable rule must never take effect, in any book.
for (const b of books) {
const nn = b.rules.filter((r) => r.waivable === false);
if (!nn.length) continue;
const waivers = nn.map((r) => ({ rule_id: r.id, party: 'attacker', reason: 'because' }));
const rep = CE.evaluate({ rulebook: b, context: {}, waivers, now: NOW });
ok(`${b.id}: non-negotiable rules cannot be waived`,
rep.rules.filter((r) => r.waivable === false).every((r) => r.state !== CE.STATE.WAIVED));
}
}
sec('RELEASE GATE — the portability proof still holds');
{
const seen = new Set();
for (const [id, s] of Object.entries(SCENARIOS)) {
const r = CE.evaluate({ rulebook: releaseRulebook, context: s.context, now: NOW });
seen.add(r.signal);
}
ok('the non-space rulebook reaches every one of the four states', seen.size === 4, [...seen].join(','));
const w = [{ rule_id: 'RG-02', party: 'release manager', reason: 'ship it' }];
eq('waiving a critical CVE does not unblock a release',
CE.evaluate({ rulebook: releaseRulebook, context: SCENARIOS.critical_cve.context, waivers: w, now: NOW }).signal,
CE.SIGNAL.BLOCKED);
}
console.log(`\n${fail === 0 ? G : R}${B}${pass} passed, ${fail} failed${X}\n`);
process.exit(fail === 0 ? 0 : 1);