vpp-ai-platform/packages/domain/src/proposal.ts
stewart hu f681e134cc 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

76 lines
2.1 KiB
TypeScript

import { z } from 'zod'
import { Curve96, DecimalString, Id, IsoUtc, MarketDate, SnapshotRef, Timescale } from './common.js'
export const ProposalType = z.enum(['BID', 'INVITATION', 'CONTROL_PLAN', 'DISPATCH_PLAN'])
export type ProposalType = z.infer<typeof ProposalType>
export const TriggerKind = z.enum(['MANUAL', 'EVENT', 'SCHEDULED'])
/** Full lifecycle per docs/03 §2 (incl. FRESH_CHECK/STALE/AUTHORIZED from the staleness model). */
export const ProposalStatus = z.enum([
'DRAFT',
'RULE_CHECK',
'SIMULATION',
'ENVELOPE_CHECK',
'PENDING_HUMAN',
'AUTO_APPROVED',
'APPROVED',
'FRESH_CHECK',
'STALE',
'AUTHORIZED',
'RELEASED',
'EXECUTING',
'COMPLETED',
'ROLLED_BACK',
'REJECTED',
])
export const ToolCallRef = z.object({
tool_call_id: Id,
tool: Id,
version: z.string().min(1),
inputs_ref: SnapshotRef,
outputs_ref: SnapshotRef,
})
/**
* Provenance attached to every proposal (docs/03 §1). Rule check enforces that
* every numeric payload field traces to one of these tool calls (invariant/P2).
*/
export const Lineage = z.object({
tool_calls: z.array(ToolCallRef),
data_refs: z.array(SnapshotRef),
ledger_version: z.int().nonnegative(),
policy_pack_version: z.string().min(1),
})
/** Day-ahead bid: per-interval price/quantity pairs. */
export const BidPayload = z.object({
kind: z.literal('BID'),
market_date: MarketDate,
prices_yuan_per_mwh: Curve96,
quantities_mwh: Curve96,
expected_revenue_yuan: DecimalString,
})
const ProposalBase = z.object({
id: Id,
timescale: Timescale,
originator: z.object({ agent: Id, trigger: TriggerKind, task_id: Id }),
lineage: Lineage,
envelope_ref: Id.nullable(),
/** sha256 over canonical payload + lineage. Approvals and permits bind to this (I3/I4). */
digest: SnapshotRef,
status: ProposalStatus,
created_at: IsoUtc,
})
/**
* M1 carries the BID variant only; INVITATION / CONTROL_PLAN / DISPATCH_PLAN
* payloads arrive with their milestones (ROADMAP M4 / phase 2).
*/
export const Proposal = z.discriminatedUnion('type', [
ProposalBase.extend({ type: z.literal('BID'), payload: BidPayload }),
])
export type Proposal = z.infer<typeof Proposal>