/** * Generates golden fixtures into contracts/fixtures//*.json and invalid * fixtures into contracts/fixtures-invalid//*.json. Run once and commit; * fixtures are reviewed via diff. Invalid cases are crafted to fail on BOTH the * zod and pydantic sides (dual-side contract test, docs/11 §3.2). */ import { mkdirSync, writeFileSync } from 'node:fs' import { fileURLToPath } from 'node:url' import { schemaRegistry } from '../src/index.js' const root = fileURLToPath(new URL('../../../contracts/', import.meta.url)) const REF_A = 'a'.repeat(64) const REF_B = 'b'.repeat(64) const REF_C = 'c'.repeat(64) const DATE = '2026-03-15' const T0 = '2026-03-14T06:00:00Z' const T1 = '2026-03-14T08:30:00Z' const T2 = '2026-03-15T23:59:59Z' const curve = (v: string, date = DATE) => ({ interval_minutes: 15, date, values: Array.from({ length: 96 }, () => v), }) const valid: Record> = { proposal: { 'bid-basic': { id: 'prop-001', type: 'BID', timescale: 'DAY_AHEAD', originator: { agent: 'trading-agent', trigger: 'SCHEDULED', task_id: 'task-001' }, payload: { kind: 'BID', market_date: DATE, prices_yuan_per_mwh: curve('425.50'), quantities_mwh: curve('12.5'), expected_revenue_yuan: '510600.00', }, lineage: { tool_calls: [ { tool_call_id: 'tc-001', tool: 'bid-optimization-milp', version: '1.0.0', inputs_ref: REF_A, outputs_ref: REF_B, }, ], data_refs: [REF_C], ledger_version: 42, policy_pack_version: '2026.03', }, envelope_ref: 'env-bid-001', digest: REF_B, status: 'DRAFT', created_at: T1, }, }, approval: { approve: { id: 'appr-001', proposal_digest: REF_B, decision: 'APPROVE', approver: { id: 'user-trader-01', role: 'senior-trader' }, scope: { effect_type: 'BID', limits: { max_energy_mwh: '1500.0' } }, validity: { from: T1, to: T2 }, evidence_versions: { policy_pack_version: '2026.03', ledger_version: 42, data_snapshot_refs: [REF_C], }, comment: 'within monthly position band', decided_at: T1, }, }, execution_permit: { active: { id: 'permit-001', proposal_digest: REF_B, issued_at: T1, expires_at: T2, effect_limits: { max_energy_mwh: '1500.0' }, revoked_at: null, issuer: 'authority-service', }, }, envelope: { 'bid-envelope': { id: 'env-bid-001', scope: { proposal_type: 'BID', timescales: ['DAY_AHEAD'], resource_set: 'pool-hubei-01' }, bounds: { price_deviation_pct: '5.0', max_energy_mwh: '1500.0' }, validity: { from: T0, to: T2 }, approval: { level: 'L2', approved_by: ['user-ops-lead', 'user-risk-officer'] }, escalation: { max_consecutive_deviations: 3, deviation_threshold_pct: '10.0' }, status: 'ACTIVE', }, }, flexibility_envelope: { 'da-aggregation-unit': { id: 'flex-001', provider: { unit_id: 'agg-unit-wuhan-01', level: 'AGGREGATION_UNIT' }, market_date: DATE, up_mw: curve('8.5'), down_mw: curve('6.0'), valid_until: T2, schema_version: '1.0.0', signature: null, }, }, position_update: { 'award-da': { id: 'pos-001', timescale: 'DAY_AHEAD', period: DATE, kind: 'AWARD', energy_mwh: '1180.0', curve: curve('12.3'), source_ref: REF_B, expected_version: 42, }, }, ledger_view: { basic: { version: 43, entries: [ { id: 'pos-000', timescale: 'MONTHLY', period: '2026-03', kind: 'CONTRACT', energy_mwh: '36000.0', curve: null, source_ref: 'contract-2026-03-001', recorded_at: T0, }, ], }, }, forecast_bundle: { 'load-da': { id: 'fc-001', kind: 'LOAD', market_date: DATE, unit: 'mw', quantiles: { p10: curve('40.1'), p50: curve('45.7'), p90: curve('52.3') }, model: { name: 'load-forecast', version: '1.2.0' }, features_snapshot_ref: REF_A, generated_at: T0, }, }, forecast_request: { 'price-da': { kind: 'PRICE', market_date: DATE, unit: 'yuan_per_mwh', history: [curve('398.20', '2026-03-13'), curve('410.75', '2026-03-14')], exogenous: {}, features_snapshot_ref: REF_A, }, }, bid_optimization_request: { 'da-basic': { market_date: DATE, price_forecast: { id: 'fc-price-001', kind: 'PRICE', market_date: DATE, unit: 'yuan_per_mwh', quantiles: { p10: curve('360.00'), p50: curve('425.50'), p90: curve('490.00') }, model: { name: 'price-forecast', version: '1.0.0' }, features_snapshot_ref: REF_A, generated_at: T0, }, adjustable_capacity_mw: curve('60.0'), position_bounds: { ledger_version: 42, daily_energy_min_mwh: '1140.0', daily_energy_max_mwh: '1260.0', }, risk: { risk_aversion: '0.3', commitment_buffer_k: '0.9', min_block_mwh: '1.0', marginal_cost_yuan_per_mwh: '0', }, }, }, bid_optimization_result: { 'da-basic': { market_date: DATE, prices_yuan_per_mwh: curve('0.00'), quantities_mwh: curve('12.5'), daily_energy_mwh: '1200.0', expected_revenue_yuan: '510600.00', revenue_distribution_yuan: { p10: '432000.00', p50: '510600.00', p90: '588000.00' }, position_bounds: { ledger_version: 42, daily_energy_min_mwh: '1140.0', daily_energy_max_mwh: '1260.0', }, solver: { name: 'highs', version: '1.7.0', status: 'OPTIMAL', objective_value: '487020.00', wall_time_ms: 12, }, binding_constraints: ['daily_energy_max'], skill_version: '1.0.0', }, }, report_request: { 'da-bid-summary': { kind: 'DAY_AHEAD_BID_SUMMARY', market_date: DATE, sources: [ { tool_call_id: 'tc-001', tool: 'bid-optimization-milp', version: '1.0.0', output: { expected_revenue_yuan: '510600.00', daily_energy_mwh: '1200.0' }, }, ], }, }, skill_report: { 'da-bid-summary': { id: 'rep-001', kind: 'DAY_AHEAD_BID_SUMMARY', market_date: DATE, sections: [ { title: 'Bid summary', metrics: [ { name: 'expected_revenue', value: '510600.00', unit: 'yuan', ref: { tool_call_id: 'tc-001', path: 'expected_revenue_yuan' }, }, ], notes: ['all figures reference solver output tc-001'], }, ], skill_version: '1.0.0', generated_at: T1, }, }, validation_result: { pass: { proposal_digest: REF_B, policy_pack: { id: 'hubei-spot-bidding', version: '2026.03' }, ok: true, violations: [], rules_evaluated: ['bid-format', 'price-limits', 'ledger-consistency', 'lineage-integrity'], checked_at: T1, }, 'reject-price': { proposal_digest: REF_B, policy_pack: { id: 'hubei-spot-bidding', version: '2026.03' }, ok: false, violations: [{ rule_id: 'price-limits', severity: 'REJECT', message: 'price 1500.00 above cap' }], rules_evaluated: ['bid-format', 'price-limits'], checked_at: T1, }, }, simulation_result: { 'revenue-ok': { proposal_digest: REF_B, kind: 'REVENUE_SCENARIOS', alert: false, alerts: [], metrics: { revenue_p05_yuan: '410200.00', revenue_p50_yuan: '510600.00', worst_loss_yuan: '0.00' }, scenario_count: 1000, simulator: { name: 'revenue-scenarios', version: '1.0.0', seed: 20260315 }, simulated_at: T1, }, }, envelope_match: { within: { proposal_digest: REF_B, within: true, envelope_id: 'env-bid-001', required_level: 'L1', reasons: [], checked_at: T1, }, outside: { proposal_digest: REF_B, within: false, envelope_id: null, required_level: 'L2', reasons: ['no ACTIVE envelope covers BID/DAY_AHEAD for pool-hubei-01'], checked_at: T1, }, }, stale_denial: { 'ledger-moved': { proposal_digest: REF_B, reasons: ['ledger version 42 at approval, now 43'], checked_at: T2, }, }, execution_receipt: { 'file-export': { receipt_id: 'rcpt-001', proposal_digest: REF_B, permit_id: 'permit-001', channel: 'FILE_EXPORT', idempotency_key: `${REF_B}:permit-001`, artifact_ref: 'exports/bid-2026-03-15-prop-001.json', accepted_at: T1, }, }, bid_proposal_draft: { basic: { market_date: DATE, prices_ref: { tool_call_id: 'tc-001', path: 'prices_yuan_per_mwh' }, quantities_ref: { tool_call_id: 'tc-001', path: 'quantities_mwh' }, expected_revenue_ref: { tool_call_id: 'tc-001', path: 'expected_revenue_yuan' }, rationale: 'Evening peak priced above P50; allocate bounded energy to 18:00–20:00.', }, }, router_decision: { 'day-ahead-bid': { workflow_id: 'day-ahead-bid', params: { market_date: DATE }, confidence: 'HIGH', }, }, bid_export_file: { basic: { format_version: '1.0.0', proposal_id: 'prop-001', proposal_digest: REF_B, permit_id: 'permit-001', proposal_type: 'BID', market_date: DATE, prices_yuan_per_mwh: curve('0.00'), quantities_mwh: curve('12.5'), exported_at: T1, }, }, situation_report: { 'normal-day': { id: 'sit-001', market_date: DATE, risk_level: 'LOW', findings: [ { kind: 'TREND', summary: 'mild temperatures, load near seasonal norm', refs: [REF_A] }, ], forecast_refs: ['fc-001'], generated_at: T0, }, }, resource_profile: { storage: { resource_id: 'res-storage-01', name: 'Wuhan industrial park storage #1', type: 'STORAGE', rated_power_mw: '10.0', certified_adjustable_mw: '8.0', confidence: '0.92', reliability_score: '0.88', constraints: { min_duration_min: 60, recovery_rate_mw_per_min: '0.5' }, evidence_refs: [REF_C], updated_at: T0, }, }, decision_case: { 'da-bid': { id: 'case-001', kind: 'DAY_AHEAD_BID', objective: 'Complete day-ahead bid for 2026-03-15', owner: 'user-trader-01', deadline: '2026-03-14T10:00:00Z', status: 'AWAITING_APPROVAL', refs: { evidence: ['sit-001', 'fc-001'], proposals: ['prop-001'], approvals: [], permits: [], scenarios: [], outcome: null, }, opened_at: T0, closed_at: null, }, }, event_envelope: { 'situation-published': { event_id: 'evt-001', event_type: 'SituationReportPublished', schema_version: '1.0.0', occurred_at: T0, causation_id: 'task-001', correlation_id: 'case-001', payload: { situation_report_id: 'sit-001' }, }, }, } // Each invalid case breaks exactly one rule and must fail on both sides. const invalid: Record> = { proposal: { // money as JSON float — the forbidden representation (docs/11 §3.3) 'bid-float-money': structuredClone(valid['proposal']!['bid-basic']!), }, approval: { // digest not sha256 hex 'bad-digest': { ...(valid['approval']!['approve'] as object), proposal_digest: 'not-a-digest' }, }, skill_report: { // a metric without a lineage reference — the one thing a report may never contain (I1) 'metric-without-ref': (() => { const o = structuredClone(valid['skill_report']!['da-bid-summary']) as any delete o.sections[0].metrics[0].ref return o })(), }, bid_optimization_result: { // solver status outside the enum 'bad-solver-status': (() => { const o = structuredClone(valid['bid_optimization_result']!['da-basic']) as any o.solver.status = 'SOLVED' return o })(), }, bid_proposal_draft: { // a literal number where only a lineage reference is allowed (I1/P2) 'literal-number': { market_date: DATE, prices_ref: { tool_call_id: 'tc-001', path: 'prices_yuan_per_mwh' }, quantities_ref: { tool_call_id: 'tc-001', path: 'quantities_mwh' }, expected_revenue_ref: '510600.00', rationale: 'x', }, }, router_decision: { // a workflow id that is not a registered template 'unknown-template': { workflow_id: 'transfer-funds', params: { market_date: DATE }, confidence: 'HIGH' }, }, position_update: { // missing optimistic-concurrency field 'missing-version': (() => { const o = structuredClone(valid['position_update']!['award-da']) as Record delete o['expected_version'] return o })(), }, } ;(invalid['proposal']!['bid-float-money'] as any).payload.expected_revenue_yuan = 510600.0 const write = (dir: string, sets: Record>) => { for (const [name, cases] of Object.entries(sets)) { mkdirSync(`${root}${dir}/${name}`, { recursive: true }) for (const [caseName, obj] of Object.entries(cases)) { writeFileSync(`${root}${dir}/${name}/${caseName}.json`, JSON.stringify(obj, null, 2) + '\n') } } } write('fixtures', valid) write('fixtures-invalid', invalid) console.log('fixtures written')