Offline evidence can no longer authorise anything. createDecisions used to prefer a live snapshot and fall back to the pooled historical one, so once the live lane woke up a live prediction could have drawn a BUY off backfill data. Backfill and replay are fine evidence that the pipeline works, they are not a live track record. No live snapshot now means ABSTAIN. The abstain says whether offline evidence existed for that cohort, so "we have 60 offline samples but no live ones" stays distinguishable from "we know nothing about this cohort". Also split the health counter. It counted qualifying cohorts across every source, which overstated how close we are to being able to trade now that only live cohorts can authorise. It reports qualifying_live_cohorts and qualifying_offline_cohorts separately, and applies the concentration cap it was previously ignoring. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WnNxwxfXSbeNtjvtz5gayb
275 lines
15 KiB
JavaScript
275 lines
15 KiB
JavaScript
const test = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const Database = require('better-sqlite3');
|
|
|
|
const { initAutonomySchema } = require('../src/autonomy/schema');
|
|
const { enqueueJob, leaseNextJob, completeJob } = require('../src/autonomy/jobs');
|
|
const { normalizeProposal, acceptProposal } = require('../src/autonomy/coordinator');
|
|
const { calibrateOutcomes, cohortKey } = require('../src/autonomy/calibration');
|
|
const { decide } = require('../src/autonomy/policy');
|
|
const { validatePaperIntent, createSimulator } = require('../src/autonomy/execution');
|
|
const { calculateOutcome } = require('../src/autonomy/outcomes');
|
|
const { createOrderIntent } = require('../src/autonomy/orderIntents');
|
|
const { enqueueCoordinatorEvent, reconcileArchiveBatch, reconcileLiveBatch } = require('../workers/autonomyWorker');
|
|
const { scheduleNext } = require('../workers/replayWorker');
|
|
const { refreshHistoricalCalibration, createDecisions, ensureCalibrationColumns } = require('../workers/calibrationWorker');
|
|
|
|
test('autonomy schema and leased jobs are restart-safe', () => {
|
|
const db = new Database(':memory:');
|
|
initAutonomySchema(db);
|
|
assert.equal(enqueueJob(db, {
|
|
jobType: 'enrich', lane: 'live', priority: 10, entityType: 'article', entityId: 42,
|
|
idempotencyKey: 'enrich:42',
|
|
}).inserted, true);
|
|
assert.equal(enqueueJob(db, {
|
|
jobType: 'enrich', lane: 'live', priority: 10, entityType: 'article', entityId: 42,
|
|
idempotencyKey: 'enrich:42',
|
|
}).inserted, false);
|
|
const job = leaseNextJob(db, 'test-worker');
|
|
assert.equal(job.lane, 'live');
|
|
assert.equal(completeJob(db, job.id, 'test-worker'), true);
|
|
assert.equal(db.prepare("SELECT status FROM autonomy_jobs WHERE id = ?").get(job.id).status, 'complete');
|
|
});
|
|
|
|
test('coordinator proposals require evidence and contain no arbitrary numeric confidence', () => {
|
|
const normalized = normalizeProposal({ predictions: [{
|
|
instrument: 'nvda', direction: 'positive', event_type: 'supply_constraint',
|
|
horizon_days: 10, evidence_article_ids: [7],
|
|
}] }, { informationCutoff: '2026-01-01T00:00:00Z', model: 'test-model' });
|
|
assert.equal(normalized.predictions[0].instrument, 'NVDA');
|
|
assert.equal('probability' in normalized.predictions[0], false);
|
|
assert.throws(() => normalizeProposal({ predictions: [{ instrument: 'NVDA', direction: 'positive', horizon_days: 10 }] }));
|
|
});
|
|
|
|
test('accepted proposal preserves evidence and creates immutable prediction', () => {
|
|
const archive = new Database(':memory:');
|
|
archive.exec('CREATE TABLE articles (id INTEGER PRIMARY KEY)');
|
|
archive.prepare('INSERT INTO articles (id) VALUES (?)').run(7);
|
|
const intelligence = new Database(':memory:');
|
|
initAutonomySchema(intelligence);
|
|
intelligence.prepare("INSERT INTO autonomy_instruments(symbol, broker, active, tradable) VALUES ('NVDA', 'test', 1, 1)").run();
|
|
const result = acceptProposal(intelligence, archive, {
|
|
predictions: [{ instrument: 'NVDA', direction: 'positive', event_type: 'earnings', horizon_days: 10, evidence_article_ids: [7] }],
|
|
}, { informationCutoff: '2026-01-01T00:00:00Z', strategyVersion: 'test' });
|
|
assert.equal(result.predictionCount, 1);
|
|
assert.deepEqual(JSON.parse(intelligence.prepare('SELECT evidence_article_ids FROM autonomy_predictions').get().evidence_article_ids), [7]);
|
|
});
|
|
|
|
test('replay evidence cannot look beyond its information cutoff and remains non-executable', () => {
|
|
const archive = new Database(':memory:');
|
|
archive.exec('CREATE TABLE articles (id INTEGER PRIMARY KEY, pub_date_effective TEXT, pub_date TEXT, ingested_at TEXT)');
|
|
archive.prepare("INSERT INTO articles VALUES (7, '2020-01-01T00:00:00Z', NULL, '2020-01-01T00:00:00Z')").run();
|
|
const intelligence = new Database(':memory:');
|
|
initAutonomySchema(intelligence);
|
|
intelligence.prepare("INSERT INTO autonomy_instruments(symbol, broker, active, tradable) VALUES ('NVDA', 'test', 1, 1)").run();
|
|
assert.throws(() => acceptProposal(intelligence, archive, {
|
|
predictions: [{ instrument: 'NVDA', direction: 'positive', event_type: 'earnings', horizon_days: 10, evidence_article_ids: [7] }],
|
|
}, { informationCutoff: '2019-12-31T00:00:00Z', origin: 'replay', replayRunId: 1 }), /missing evidence/);
|
|
const proposal = intelligence.prepare("INSERT INTO autonomy_proposals(payload, information_cutoff, status) VALUES ('{}', datetime('now'), 'accepted')").run();
|
|
const prediction = intelligence.prepare(`INSERT INTO autonomy_predictions
|
|
(proposal_id, instrument, direction, event_type, horizon_days, information_cutoff, evidence_article_ids, strategy_version, origin)
|
|
VALUES (?, 'NVDA', 'positive', 'test', 10, datetime('now'), '[7]', 'test', 'replay')`).run(proposal.lastInsertRowid);
|
|
intelligence.prepare("INSERT INTO autonomy_decisions(prediction_id, action, rationale, strategy_version) VALUES (?, 'BUY', 'test', 'test')").run(prediction.lastInsertRowid);
|
|
const executable = intelligence.prepare(`SELECT d.id FROM autonomy_decisions d JOIN autonomy_predictions p ON p.id=d.prediction_id
|
|
WHERE d.action IN ('BUY','SELL') AND p.origin='live'`).all();
|
|
assert.equal(executable.length, 0);
|
|
});
|
|
|
|
test('replay scheduler skips terminal replay jobs instead of pinning the cursor', () => {
|
|
const archive = new Database(':memory:');
|
|
archive.exec(`
|
|
CREATE TABLE articles (
|
|
id INTEGER PRIMARY KEY,
|
|
title TEXT,
|
|
description TEXT,
|
|
content TEXT,
|
|
pub_date_effective TEXT,
|
|
is_index_page INTEGER
|
|
)
|
|
`);
|
|
archive.prepare("INSERT INTO articles VALUES (1, 'bad', '', 'content', '2020-01-01T00:00:00Z', 0)").run();
|
|
archive.prepare("INSERT INTO articles VALUES (2, 'next', '', 'content', '2020-01-02T00:00:00Z', 0)").run();
|
|
|
|
const intelligence = new Database(':memory:');
|
|
initAutonomySchema(intelligence);
|
|
const runId = intelligence.prepare(`
|
|
INSERT INTO autonomy_replay_runs (watermark_at, strategy_version, prompt_version, coordinator_model)
|
|
VALUES ('2020-01-03T00:00:00Z', 'test', 'test', 'test')
|
|
`).run().lastInsertRowid;
|
|
enqueueJob(intelligence, {
|
|
jobType: 'replay_article', lane: 'historical', priority: 1, entityType: 'article', entityId: 1,
|
|
idempotencyKey: `replay:${runId}:article:1`,
|
|
});
|
|
intelligence.prepare("UPDATE autonomy_jobs SET status='dead_letter', attempts=5, last_error='fetch failed'").run();
|
|
|
|
const run = intelligence.prepare('SELECT * FROM autonomy_replay_runs WHERE id=?').get(runId);
|
|
const next = scheduleNext(intelligence, archive, run);
|
|
assert.equal(next.id, 2);
|
|
assert.equal(intelligence.prepare('SELECT cursor_article_id FROM autonomy_replay_runs WHERE id=?').get(runId).cursor_article_id, 1);
|
|
assert.equal(intelligence.prepare("SELECT COUNT(*) count FROM autonomy_jobs WHERE status='pending' AND entity_id='2'").get().count, 1);
|
|
});
|
|
|
|
test('calibration and policy abstain on insufficient evidence', () => {
|
|
const calibration = calibrateOutcomes([
|
|
{ excess_return: 0.02, direction_correct: 1 },
|
|
{ excess_return: -0.01, direction_correct: 0 },
|
|
]);
|
|
assert.equal(calibration.sampleSize, 2);
|
|
const result = decide({ ...calibration }, { minSampleSize: 30 });
|
|
assert.equal(result.action, 'ABSTAIN');
|
|
assert.equal(cohortKey({ sector: 'tech', eventType: 'earnings', horizonDays: 10, direction: 'positive' }), 'v2|tech|earnings|medium|positive');
|
|
assert.equal(decide({
|
|
direction: 'negative', probability: 0.8, expectedExcessReturn: -0.02, lowerReturn: -0.04,
|
|
sampleSize: 40, distinctInstruments: 9, topInstrumentShare: 0.25,
|
|
}).action, 'SELL');
|
|
});
|
|
|
|
test('historical replay outcomes create replay calibration snapshots', () => {
|
|
const db = new Database(':memory:');
|
|
initAutonomySchema(db);
|
|
const proposal = db.prepare("INSERT INTO autonomy_proposals(payload, information_cutoff, status) VALUES ('{}', '2020-01-01T00:00:00Z', 'accepted')").run();
|
|
const prediction = db.prepare(`
|
|
INSERT INTO autonomy_predictions
|
|
(proposal_id, instrument, direction, event_type, horizon_days, information_cutoff, evidence_article_ids,
|
|
learning_eligible, strategy_version, origin, replay_run_id, status)
|
|
VALUES (?, 'NVDA', 'positive', 'earnings', 10, '2020-01-01T00:00:00Z', '[1]', 0, 'test', 'replay', 7, 'resolved')
|
|
`).run(proposal.lastInsertRowid);
|
|
db.prepare(`
|
|
INSERT INTO autonomy_outcomes(prediction_id, excess_return, direction_correct)
|
|
VALUES (?, 0.04, 1)
|
|
`).run(prediction.lastInsertRowid);
|
|
|
|
// one per-run replay snapshot plus the pooled historical/replay snapshot
|
|
assert.equal(refreshHistoricalCalibration(db, 'test-cal'), 2);
|
|
const snapshot = db.prepare("SELECT source, replay_run_id, sample_size, directional_probability FROM autonomy_calibration_snapshots WHERE source='replay'").get();
|
|
assert.equal(snapshot.source, 'replay');
|
|
assert.equal(snapshot.replay_run_id, 7);
|
|
assert.equal(snapshot.sample_size, 1);
|
|
assert(snapshot.directional_probability > 0.5);
|
|
});
|
|
|
|
test('live decisions map calibration snapshot fields into policy inputs', () => {
|
|
const db = new Database(':memory:');
|
|
initAutonomySchema(db);
|
|
const proposal = db.prepare("INSERT INTO autonomy_proposals(payload, information_cutoff, status) VALUES ('{}', datetime('now'), 'accepted')").run();
|
|
const prediction = db.prepare(`
|
|
INSERT INTO autonomy_predictions
|
|
(proposal_id, instrument, direction, event_type, horizon_days, information_cutoff, evidence_article_ids,
|
|
learning_eligible, strategy_version, origin, status)
|
|
VALUES (?, 'NVDA', 'positive', 'earnings', 10, datetime('now'), '[1]', 1, 'test', 'live', 'open')
|
|
`).run(proposal.lastInsertRowid);
|
|
ensureCalibrationColumns(db);
|
|
db.prepare(`
|
|
INSERT INTO autonomy_calibration_snapshots
|
|
(cohort_key, sample_size, effective_sample_size, directional_probability, expected_excess_return,
|
|
lower_return, upper_return, parent_cohort_key, version, source)
|
|
VALUES ('v2|unknown|earnings|medium|positive', 40, 42, 0.7, 0.02, -0.01, 0.06, NULL, 'test-cal', 'live')
|
|
`).run();
|
|
db.prepare("UPDATE autonomy_calibration_snapshots SET distinct_instruments = 11, top_instrument_share = 0.2").run();
|
|
|
|
assert.equal(createDecisions(db), 1);
|
|
const decision = db.prepare('SELECT * FROM autonomy_decisions WHERE prediction_id=?').get(prediction.lastInsertRowid);
|
|
assert.equal(decision.action, 'BUY');
|
|
assert.equal(decision.calibrated_probability, 0.7);
|
|
assert.equal(decision.expected_excess_return, 0.02);
|
|
});
|
|
|
|
test('paper execution is allowlisted, bounded and idempotent', () => {
|
|
const intent = validatePaperIntent({ decisionId: 12, instrument: 'NVDA', action: 'BUY', notional: 100 }, { tradable: true, maxNotional: 500 });
|
|
const broker = createSimulator();
|
|
assert.deepEqual(broker.submit(intent), broker.submit(intent));
|
|
assert.throws(() => validatePaperIntent({ decisionId: 13, instrument: 'PRIVATE', action: 'BUY', notional: 100 }, { tradable: false }));
|
|
assert.throws(() => validatePaperIntent({ decisionId: 14, instrument: 'NVDA', action: 'BUY', notional: 501 }, { tradable: true, maxNotional: 500 }));
|
|
});
|
|
|
|
test('archive reconciliation prioritizes recent usable events and is bounded', () => {
|
|
const archive = new Database(':memory:');
|
|
archive.exec('CREATE TABLE articles (id INTEGER PRIMARY KEY, event_id INTEGER, ingested_at TEXT, content TEXT, has_embedding INTEGER)');
|
|
archive.prepare('INSERT INTO articles VALUES (1, 99, ?, ?, 1)').run(new Date().toISOString(), 'content');
|
|
const intelligence = new Database(':memory:');
|
|
initAutonomySchema(intelligence);
|
|
const result = reconcileArchiveBatch(archive, intelligence, 1);
|
|
assert.equal(result.scanned, 1);
|
|
const job = intelligence.prepare("SELECT lane, priority FROM autonomy_jobs WHERE job_type='coordinator_event'").get();
|
|
assert.equal(job.lane, 'live');
|
|
assert.equal(job.priority, 100);
|
|
const live = reconcileLiveBatch(archive, intelligence, 1);
|
|
assert.equal(live.scanned, 1);
|
|
});
|
|
|
|
test('archive reconciliation recovers transient dead-lettered coordinator jobs', () => {
|
|
const intelligence = new Database(':memory:');
|
|
initAutonomySchema(intelligence);
|
|
enqueueJob(intelligence, {
|
|
jobType: 'coordinator_event', lane: 'historical', priority: 10, entityType: 'event', entityId: 99,
|
|
idempotencyKey: 'coordinator_event:99',
|
|
});
|
|
intelligence.prepare(`
|
|
UPDATE autonomy_jobs
|
|
SET status='dead_letter', attempts=5, last_error='TypeError: fetch failed'
|
|
WHERE idempotency_key='coordinator_event:99'
|
|
`).run();
|
|
|
|
const result = enqueueCoordinatorEvent(intelligence, {
|
|
event_id: 99,
|
|
ingested_at: new Date().toISOString(),
|
|
content: 'content',
|
|
has_embedding: 1,
|
|
});
|
|
assert.equal(result.recovered, true);
|
|
const job = intelligence.prepare("SELECT status, lane, priority, attempts, last_error FROM autonomy_jobs WHERE idempotency_key='coordinator_event:99'").get();
|
|
assert.equal(job.status, 'pending');
|
|
assert.equal(job.lane, 'live');
|
|
assert.equal(job.priority, 100);
|
|
assert.equal(job.attempts, 0);
|
|
assert.match(job.last_error, /Recovered transient coordinator failure/);
|
|
});
|
|
|
|
test('archive reconciliation leaves non-transient coordinator dead letters alone', () => {
|
|
const intelligence = new Database(':memory:');
|
|
initAutonomySchema(intelligence);
|
|
enqueueJob(intelligence, {
|
|
jobType: 'coordinator_event', lane: 'historical', priority: 10, entityType: 'event', entityId: 100,
|
|
idempotencyKey: 'coordinator_event:100',
|
|
});
|
|
intelligence.prepare(`
|
|
UPDATE autonomy_jobs
|
|
SET status='dead_letter', attempts=5, last_error='no tradable instruments are allowlisted'
|
|
WHERE idempotency_key='coordinator_event:100'
|
|
`).run();
|
|
|
|
const result = enqueueCoordinatorEvent(intelligence, {
|
|
event_id: 100,
|
|
ingested_at: new Date().toISOString(),
|
|
content: 'content',
|
|
has_embedding: 1,
|
|
});
|
|
assert.equal(result.recovered, false);
|
|
const job = intelligence.prepare("SELECT status, attempts, last_error FROM autonomy_jobs WHERE idempotency_key='coordinator_event:100'").get();
|
|
assert.equal(job.status, 'dead_letter');
|
|
assert.equal(job.attempts, 5);
|
|
assert.equal(job.last_error, 'no tradable instruments are allowlisted');
|
|
});
|
|
|
|
test('outcome calculation uses benchmark-relative return', () => {
|
|
const outcome = calculateOutcome(
|
|
{ information_cutoff: '2026-01-02T00:00:00Z', horizon_days: 5, direction: 'positive' },
|
|
[{ date: '2026-01-02', close: 100 }, { date: '2026-01-09', close: 110 }],
|
|
[{ date: '2026-01-02', close: 100 }, { date: '2026-01-09', close: 105 }]
|
|
);
|
|
assert.equal(outcome.directionCorrect, 1);
|
|
assert.equal(outcome.excessReturn, 0.05);
|
|
});
|
|
|
|
test('order intents require the explicit instrument allowlist', () => {
|
|
const db = new Database(':memory:');
|
|
initAutonomySchema(db);
|
|
db.prepare("INSERT INTO autonomy_instruments(symbol, broker, active, tradable) VALUES ('NVDA', 'sim', 1, 1)").run();
|
|
const proposal = db.prepare(`INSERT INTO autonomy_proposals(payload, information_cutoff, status) VALUES ('{}', datetime('now'), 'accepted')`).run();
|
|
const prediction = db.prepare(`INSERT INTO autonomy_predictions(proposal_id, instrument, direction, event_type, horizon_days, information_cutoff, evidence_article_ids, learning_eligible, strategy_version) VALUES (?, 'NVDA', 'positive', 'test', 10, datetime('now'), '[1]', 1, 'test')`).run(proposal.lastInsertRowid);
|
|
const decision = db.prepare(`INSERT INTO autonomy_decisions(prediction_id, action, rationale, strategy_version) VALUES (?, 'BUY', 'test', 'test')`).run(prediction.lastInsertRowid);
|
|
const intent = createOrderIntent(db, decision.lastInsertRowid, 100, { maxNotional: 100 });
|
|
assert.equal(intent.side, 'buy');
|
|
assert.equal(db.prepare('SELECT status FROM autonomy_order_intents').get().status, 'shadow');
|
|
});
|