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 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