M1: domain schemas, contracts pipeline, ledger and snapshot services
- packages/domain: zod schemas for the 07-scenario object set with a
schemaRegistry driving schema export, fixture generation, and Python
module naming; decimal-string/sha256/curve primitives enforce the
docs/11 §3.3 representation rules at the type layer
- contracts/: 12 JSON Schemas (draft 2020-12), 12 golden fixtures, 3
invalid fixtures crafted to fail on both sides (float money, bad
digest, missing concurrency version)
- skills-py: generated pydantic models (committed, never hand-edited),
regeneration script, mirror pytest using JSON-mode validation —
dual-side contract tests agree on all 15 fixtures
- packages/services: content-addressed immutable snapshot store
(memory + fs, rejects floats), LedgerService v1 with optimistic
concurrency and the P7 constraint cascade (monthly position bounds
day-ahead bids; no-anchor bids rejected), quality-gate stub
- cascade band shape is an OPEN-QUESTION A5 placeholder (pro-rata
daily share ±band) — see ledger.ts checkCascade
All green: tsc typecheck, 41 TS tests, 15 Python tests.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019u5SLNweVio6ozJX7yfxQr
🔮 View transcript: https://logs.lojong.info/s/e8u90k3t33w590r7b5y7yzqh
2026-09-01 21:50:22 -04:00
|
|
|
|
/**
|
|
|
|
|
|
* Generates golden fixtures into contracts/fixtures/<name>/*.json and invalid
|
|
|
|
|
|
* fixtures into contracts/fixtures-invalid/<name>/*.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<string, Record<string, unknown>> = {
|
|
|
|
|
|
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,
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
M2: skill contracts, Python skill service, L2 eval harness with baseline
- packages/domain: ForecastRequest, BidOptimizationRequest/Result,
ReportRequest, SkillReport (+ golden and invalid fixtures, exported to
contracts/ and regenerated as pydantic models).
- skills-py/vpp_skills: FastAPI service with versioned registry; load/PV/
price forecasts (same-day-type EWM point forecast, conformal residual
quantiles — coverage test as acceptance gate); bid-optimization MILP on
HiGHS (binary block participation, hard ledger energy bounds, exact
Decimal fit of the rounded curve inside the bounds, revenue distribution
over quantile paths); report generator whose every figure is a
{tool_call_id, path} reference, with a verifier. 48 tests incl. hypothesis
property test that bids respect ledger constraints.
- packages/services: LedgerService.dayAheadBounds (the P7 cascade band
handed to the optimizer); Decimal resolved once for CJS/ESM interop.
- packages/evals: L2 metrics (MAPE, nRMSE, coverage, direction accuracy,
naive/hindsight revenue baselines), HTTP skill client, rolling-origin
harness that pushes each bid through the real ledger, CLI with
--check/--write-baseline; committed baseline on the SYNTHETIC dataset
(no historical Hubei data yet — baselines measure the harness, not KPI).
- CI: evals job boots the skill service and fails on baseline digest drift.
- docs/open-questions: A6 (flexibility marginal cost = offer floor); A4/B6
wired as placeholders. README/CLAUDE.md status → M2 done, M3 next.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UoYoGYzHkFyv3ALenkRPhA
2026-09-02 06:29:08 -04:00
|
|
|
|
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,
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
M3: Mastra runtime, safety chain, two agents, Case Desk v1
- packages/domain: safety-chain objects (ValidationResult, SimulationResult,
EnvelopeMatch, StaleDenial, ExecutionReceipt, LineageRef/BidProposalDraft,
RouterDecision, BidExportFile) + fixtures on both sides.
- packages/services: proposal digest; PolicyEngine + hubei-spot-bidding pack
(digest-valid, bid-format, price-limits, quantity-non-negative,
ledger-consistency, lineage-integrity, originator-permission — each with
pass/fail tests); EnvelopeService; AuthorityService (fresh check, permits,
revoke, gateway validate); FileExportGateway (idempotent receipts);
Memory/File EventBus; LineageRecorder + P2 assembler; RevenueScenario
simulator; CaseDeskService; FsRepository; skill HTTP client moved here.
- packages/runtime: createRuntime (LibSQL storage, per-runtime workflow
factories), proposal-lifecycle (rule check → simulation → envelope gate
with suspend/resume → fresh check + permit → release), day-ahead-situation,
day-ahead-bid, TriggerService (scheduled/event/manual), LlmPort
(Mastra/Scripted/Null), Case Desk HTTP API, dev entry point.
- Tests: all eight docs/01 invariants, docs/07 06:00→08:30 end to end with
LLM down, restart survival of a suspended approval, permit expiry and
revocation, replay of a released proposal, trigger scheduling. 141 TS +
60 Python tests.
- Known gaps: ledger not yet persisted (replayed on restart); STALE ends the
run instead of looping to rule check; synthetic data stands in for
historical replay.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UoYoGYzHkFyv3ALenkRPhA
2026-09-02 06:55:55 -04:00
|
|
|
|
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,
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
M1: domain schemas, contracts pipeline, ledger and snapshot services
- packages/domain: zod schemas for the 07-scenario object set with a
schemaRegistry driving schema export, fixture generation, and Python
module naming; decimal-string/sha256/curve primitives enforce the
docs/11 §3.3 representation rules at the type layer
- contracts/: 12 JSON Schemas (draft 2020-12), 12 golden fixtures, 3
invalid fixtures crafted to fail on both sides (float money, bad
digest, missing concurrency version)
- skills-py: generated pydantic models (committed, never hand-edited),
regeneration script, mirror pytest using JSON-mode validation —
dual-side contract tests agree on all 15 fixtures
- packages/services: content-addressed immutable snapshot store
(memory + fs, rejects floats), LedgerService v1 with optimistic
concurrency and the P7 constraint cascade (monthly position bounds
day-ahead bids; no-anchor bids rejected), quality-gate stub
- cascade band shape is an OPEN-QUESTION A5 placeholder (pro-rata
daily share ±band) — see ledger.ts checkCascade
All green: tsc typecheck, 41 TS tests, 15 Python tests.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019u5SLNweVio6ozJX7yfxQr
🔮 View transcript: https://logs.lojong.info/s/e8u90k3t33w590r7b5y7yzqh
2026-09-01 21:50:22 -04:00
|
|
|
|
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<string, Record<string, unknown>> = {
|
|
|
|
|
|
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' },
|
|
|
|
|
|
},
|
M2: skill contracts, Python skill service, L2 eval harness with baseline
- packages/domain: ForecastRequest, BidOptimizationRequest/Result,
ReportRequest, SkillReport (+ golden and invalid fixtures, exported to
contracts/ and regenerated as pydantic models).
- skills-py/vpp_skills: FastAPI service with versioned registry; load/PV/
price forecasts (same-day-type EWM point forecast, conformal residual
quantiles — coverage test as acceptance gate); bid-optimization MILP on
HiGHS (binary block participation, hard ledger energy bounds, exact
Decimal fit of the rounded curve inside the bounds, revenue distribution
over quantile paths); report generator whose every figure is a
{tool_call_id, path} reference, with a verifier. 48 tests incl. hypothesis
property test that bids respect ledger constraints.
- packages/services: LedgerService.dayAheadBounds (the P7 cascade band
handed to the optimizer); Decimal resolved once for CJS/ESM interop.
- packages/evals: L2 metrics (MAPE, nRMSE, coverage, direction accuracy,
naive/hindsight revenue baselines), HTTP skill client, rolling-origin
harness that pushes each bid through the real ledger, CLI with
--check/--write-baseline; committed baseline on the SYNTHETIC dataset
(no historical Hubei data yet — baselines measure the harness, not KPI).
- CI: evals job boots the skill service and fails on baseline digest drift.
- docs/open-questions: A6 (flexibility marginal cost = offer floor); A4/B6
wired as placeholders. README/CLAUDE.md status → M2 done, M3 next.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UoYoGYzHkFyv3ALenkRPhA
2026-09-02 06:29:08 -04:00
|
|
|
|
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
|
|
|
|
|
|
})(),
|
|
|
|
|
|
},
|
M3: Mastra runtime, safety chain, two agents, Case Desk v1
- packages/domain: safety-chain objects (ValidationResult, SimulationResult,
EnvelopeMatch, StaleDenial, ExecutionReceipt, LineageRef/BidProposalDraft,
RouterDecision, BidExportFile) + fixtures on both sides.
- packages/services: proposal digest; PolicyEngine + hubei-spot-bidding pack
(digest-valid, bid-format, price-limits, quantity-non-negative,
ledger-consistency, lineage-integrity, originator-permission — each with
pass/fail tests); EnvelopeService; AuthorityService (fresh check, permits,
revoke, gateway validate); FileExportGateway (idempotent receipts);
Memory/File EventBus; LineageRecorder + P2 assembler; RevenueScenario
simulator; CaseDeskService; FsRepository; skill HTTP client moved here.
- packages/runtime: createRuntime (LibSQL storage, per-runtime workflow
factories), proposal-lifecycle (rule check → simulation → envelope gate
with suspend/resume → fresh check + permit → release), day-ahead-situation,
day-ahead-bid, TriggerService (scheduled/event/manual), LlmPort
(Mastra/Scripted/Null), Case Desk HTTP API, dev entry point.
- Tests: all eight docs/01 invariants, docs/07 06:00→08:30 end to end with
LLM down, restart survival of a suspended approval, permit expiry and
revocation, replay of a released proposal, trigger scheduling. 141 TS +
60 Python tests.
- Known gaps: ledger not yet persisted (replayed on restart); STALE ends the
run instead of looping to rule check; synthetic data stands in for
historical replay.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UoYoGYzHkFyv3ALenkRPhA
2026-09-02 06:55:55 -04:00
|
|
|
|
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' },
|
|
|
|
|
|
},
|
M1: domain schemas, contracts pipeline, ledger and snapshot services
- packages/domain: zod schemas for the 07-scenario object set with a
schemaRegistry driving schema export, fixture generation, and Python
module naming; decimal-string/sha256/curve primitives enforce the
docs/11 §3.3 representation rules at the type layer
- contracts/: 12 JSON Schemas (draft 2020-12), 12 golden fixtures, 3
invalid fixtures crafted to fail on both sides (float money, bad
digest, missing concurrency version)
- skills-py: generated pydantic models (committed, never hand-edited),
regeneration script, mirror pytest using JSON-mode validation —
dual-side contract tests agree on all 15 fixtures
- packages/services: content-addressed immutable snapshot store
(memory + fs, rejects floats), LedgerService v1 with optimistic
concurrency and the P7 constraint cascade (monthly position bounds
day-ahead bids; no-anchor bids rejected), quality-gate stub
- cascade band shape is an OPEN-QUESTION A5 placeholder (pro-rata
daily share ±band) — see ledger.ts checkCascade
All green: tsc typecheck, 41 TS tests, 15 Python tests.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019u5SLNweVio6ozJX7yfxQr
🔮 View transcript: https://logs.lojong.info/s/e8u90k3t33w590r7b5y7yzqh
2026-09-01 21:50:22 -04:00
|
|
|
|
position_update: {
|
|
|
|
|
|
// missing optimistic-concurrency field
|
|
|
|
|
|
'missing-version': (() => {
|
|
|
|
|
|
const o = structuredClone(valid['position_update']!['award-da']) as Record<string, unknown>
|
|
|
|
|
|
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<string, Record<string, unknown>>) => {
|
|
|
|
|
|
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')
|