76 lines
2.1 KiB
TypeScript
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>
|