IR-WP-0006-T01/T02: billing reconciliation contract and settlement mechanism

Spec and implement external-evidence settlement for pending trial
reservations, so the USD 0.023712 HTTP-401 reservation (and future ones)
can be resolved without fabricating a completed trial or treating a failed
request as zero-charge proof. intelligence_radar.trials.reconcile (+ CLI
reconcile) is write-once under the existing trials lock, rejects unknown
reservations, duplicate/conflicting settlement, cross-campaign references,
invalid amounts and budget overrun, and records reviewer attribution plus
an evidence pointer rather than any secret value. 9 new tests; full suite
(48 tests) and repository check pass. T03 (applying this to the real
reservation) stays blocked on authoritative provider billing evidence.

ADR-0003 closes the IR-WP-0007-T03 uptime question: retain the local
systemd timer, no hosted worker, since nothing here needs sub-day
freshness and the scheduler already tolerates downtime.

Mark IR-WP-0005/0006/0007 workplan status blocked: each still has at
least one task genuinely blocked on an external party (secrets-engine
human approval, authoritative billing evidence, and paid-execution
outcomes respectively) rather than anything actionable in this repo.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: sonnet
Assistant-Process: 8472@bnt-lap001
Assistant-Session: 1cac6b6d-c1a5-4a04-a950-c4febb8d3f67
This commit is contained in:
tegwick 2026-09-27 17:12:13 +02:00
parent 7d03727945
commit 710b2ebbb5
8 changed files with 312 additions and 6 deletions

View file

@ -6,7 +6,7 @@ import tempfile
import unittest
from unittest.mock import patch
from intelligence_radar.trials import budget, execute, plan, verify
from intelligence_radar.trials import budget, execute, plan, reconcile, verify
class TrialTests(unittest.TestCase):
@ -98,3 +98,73 @@ class TrialTests(unittest.TestCase):
plan(self.root, config, {'cases': [self.case, self.case]})
result = plan(self.root, config, {'cases': [self.case]})
self.assertIn('planned reservations exceed campaign budget', result['budget_blockers'])
def _pending_run_id(self):
def fail(*args, **kwargs):
raise TimeoutError('timeout')
with self.assertRaises(TimeoutError):
execute(self.root, self.config, self.case, self.model, opener=fail)
return budget(self.root, 'test')
def test_reconcile_unknown_reservation_refused(self):
with self.assertRaisesRegex(ValueError, 'unknown reservation'):
reconcile(self.root, 'test', 'nope', '0.001', 'evidence-sha', 'codex')
def test_reconcile_settles_pending_reservation_without_fabricating_trial(self):
self._pending_run_id()
from intelligence_radar.store import all_records
run_id = [e for i, e in all_records(self.root, 'trial-events') if e['kind'] == 'reserved'][0]['run_id']
reconcile(self.root, 'test', run_id, '0.001', 'evidence-sha256:abc', 'codex')
state = budget(self.root, 'test')
self.assertEqual(state['pending_usd'], '0')
self.assertEqual(state['spent_usd'], '0.001')
trials_dir = self.root / 'data/trials'
self.assertEqual(list(trials_dir.glob('*.json')) if trials_dir.exists() else [], [])
def test_reconcile_refuses_duplicate_settlement(self):
self._pending_run_id()
from intelligence_radar.store import all_records
run_id = [e for i, e in all_records(self.root, 'trial-events') if e['kind'] == 'reserved'][0]['run_id']
reconcile(self.root, 'test', run_id, '0.001', 'evidence-sha256:abc', 'codex')
with self.assertRaisesRegex(ValueError, 'already settled'):
reconcile(self.root, 'test', run_id, '0.001', 'evidence-sha256:abc', 'codex')
def test_reconcile_refuses_cross_campaign_reference(self):
self._pending_run_id()
from intelligence_radar.store import all_records
run_id = [e for i, e in all_records(self.root, 'trial-events') if e['kind'] == 'reserved'][0]['run_id']
with self.assertRaisesRegex(ValueError, 'cross-campaign'):
reconcile(self.root, 'other-campaign', run_id, '0.001', 'evidence-sha256:abc', 'codex')
def test_reconcile_refuses_invalid_amount(self):
self._pending_run_id()
from intelligence_radar.store import all_records
run_id = [e for i, e in all_records(self.root, 'trial-events') if e['kind'] == 'reserved'][0]['run_id']
with self.assertRaises(ValueError):
reconcile(self.root, 'test', run_id, '-1', 'evidence-sha256:abc', 'codex')
def test_reconcile_refuses_budget_overrun(self):
self.config['budget_usd'] = '0.02'
self._pending_run_id()
from intelligence_radar.store import all_records
run_id = [e for i, e in all_records(self.root, 'trial-events') if e['kind'] == 'reserved'][0]['run_id']
with self.assertRaisesRegex(ValueError, 'exceeds campaign budget'):
reconcile(self.root, 'test', run_id, '5', 'evidence-sha256:abc', 'codex')
def test_reconcile_requires_evidence_and_reviewer(self):
self._pending_run_id()
from intelligence_radar.store import all_records
run_id = [e for i, e in all_records(self.root, 'trial-events') if e['kind'] == 'reserved'][0]['run_id']
with self.assertRaisesRegex(ValueError, 'evidence_ref'):
reconcile(self.root, 'test', run_id, '0.001', '', 'codex')
with self.assertRaisesRegex(ValueError, 'reviewer'):
reconcile(self.root, 'test', run_id, '0.001', 'evidence-sha256:abc', '')
def test_reconcile_is_safe_to_retry_after_success(self):
self._pending_run_id()
from intelligence_radar.store import all_records
run_id = [e for i, e in all_records(self.root, 'trial-events') if e['kind'] == 'reserved'][0]['run_id']
first = reconcile(self.root, 'test', run_id, '0.001', 'evidence-sha256:abc', 'codex')
with self.assertRaisesRegex(ValueError, 'already settled'):
reconcile(self.root, 'test', run_id, '0.001', 'evidence-sha256:abc', 'codex')
self.assertEqual(budget(self.root, 'test')['spent_usd'], '0.001')