1use serde::{Deserialize, Serialize};
20
21use crate::schema::{IsoDateTime, NonEmptyString, RunxSchema};
22use crate::{
23 ActForm, AuthorityAttenuation, AuthorityTerm, Closure, ClosureDisposition, CriterionBinding,
24 Decision, HashAlgorithm, Intent, JsonObject, Reference, RevisionDetails, VerificationDetails,
25};
26
27pub const RECEIPT_SCHEMA: &str = "runx.receipt.v1";
29
30pub const EFFECT_FINALITY_RECEIPT_SCHEMA: &str = "runx.effect_finality_receipt.v1";
34
35pub const RECEIPT_CANONICALIZATION: &str = "runx.receipt.c14n.v1";
37
38#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
39pub enum ReceiptSchema {
40 #[serde(rename = "runx.receipt.v1")]
41 V1,
42}
43
44#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
45pub enum EffectFinalityReceiptSchema {
46 #[serde(rename = "runx.effect_finality_receipt.v1")]
47 V1,
48}
49
50#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
51#[serde(rename_all = "snake_case")]
52pub enum EffectFinalityPhase {
53 Provisional,
54 InFlight,
55 Sealed,
56 Failed,
57 Reversed,
58}
59
60#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, RunxSchema)]
61#[serde(deny_unknown_fields)]
62#[runx_schema(id = "runx.effect_finality_receipt.v1")]
63pub struct EffectFinalityReceipt {
64 pub schema: EffectFinalityReceiptSchema,
65 pub id: NonEmptyString,
66 pub created_at: IsoDateTime,
67 pub family: NonEmptyString,
68 pub phase: EffectFinalityPhase,
69 pub original_receipt_ref: Reference,
70 pub criterion_id: NonEmptyString,
71 #[serde(skip_serializing_if = "Option::is_none")]
72 pub proof_ref: Option<Reference>,
73 #[serde(default, skip_serializing_if = "Vec::is_empty")]
74 pub evidence_refs: Vec<Reference>,
75 #[serde(default, skip_serializing_if = "Vec::is_empty")]
76 pub norm_refs: Vec<NonEmptyString>,
77 #[serde(skip_serializing_if = "Option::is_none")]
78 pub confirmation_depth: Option<u64>,
79 #[serde(default, skip_serializing_if = "JsonObject::is_empty")]
80 pub payload: JsonObject,
81}
82
83#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
84#[serde(rename_all = "snake_case")]
85pub enum FanoutReceiptStrategy {
86 All,
87 Any,
88 Quorum,
89}
90
91#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
92#[serde(rename_all = "snake_case")]
93pub enum FanoutReceiptDecision {
94 Proceed,
95 Halt,
96 Pause,
97 Escalate,
98}
99
100#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, RunxSchema)]
101#[serde(deny_unknown_fields)]
102pub struct FanoutReceiptSyncPoint {
103 pub group_id: NonEmptyString,
104 pub strategy: FanoutReceiptStrategy,
105 pub decision: FanoutReceiptDecision,
106 pub rule_fired: NonEmptyString,
107 pub reason: NonEmptyString,
108 pub branch_count: usize,
109 pub success_count: usize,
110 pub failure_count: usize,
111 pub required_successes: usize,
112 #[serde(default)]
113 pub branch_receipts: Vec<NonEmptyString>,
114 #[serde(skip_serializing_if = "Option::is_none")]
115 pub gate: Option<JsonObject>,
116}
117
118#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
120#[serde(rename_all = "snake_case")]
121pub enum ReceiptCommitmentScope {
122 Input,
123 Output,
124 Stdout,
125 Stderr,
126 Error,
127}
128
129#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
130#[serde(deny_unknown_fields)]
131pub struct ReceiptCommitment {
132 pub scope: ReceiptCommitmentScope,
133 pub algorithm: HashAlgorithm,
134 pub value: NonEmptyString,
135 pub canonicalization: NonEmptyString,
136}
137
138pub mod receipt_subject_kind {
143 pub const SKILL: &str = "skill";
145 pub const GRAPH: &str = "graph";
147}
148
149#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
152#[serde(deny_unknown_fields)]
153pub struct ReceiptInputContext {
154 pub source: NonEmptyString,
155 pub preview: String,
156 pub value_hash: NonEmptyString,
157}
158
159#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
160#[serde(deny_unknown_fields)]
161pub struct Subject {
162 pub kind: NonEmptyString,
166 #[serde(rename = "ref")]
167 pub reference: Reference,
168 #[serde(skip_serializing_if = "Option::is_none")]
169 pub input_context: Option<ReceiptInputContext>,
170 #[serde(default)]
171 pub commitments: Vec<ReceiptCommitment>,
172}
173
174#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
176#[serde(deny_unknown_fields)]
177pub struct ReceiptEnforcement {
178 pub profile_hash: NonEmptyString,
179 #[serde(default)]
180 pub redaction_refs: Vec<Reference>,
181 #[serde(default)]
182 pub setup_refs: Vec<Reference>,
183 #[serde(default)]
184 pub teardown_refs: Vec<Reference>,
185}
186
187#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, RunxSchema)]
188#[serde(deny_unknown_fields)]
189pub struct ReceiptAuthority {
190 pub actor_ref: Reference,
191 #[serde(default)]
192 pub grant_refs: Vec<Reference>,
193 #[serde(default)]
194 pub scope_refs: Vec<Reference>,
195 #[serde(default)]
196 pub authority_proof_refs: Vec<Reference>,
197 pub attenuation: AuthorityAttenuation,
198 #[serde(skip_serializing_if = "Option::is_none")]
199 pub mandate_ref: Option<Reference>,
200 #[serde(default)]
201 pub terms: Vec<AuthorityTerm>,
202 pub enforcement: ReceiptEnforcement,
203}
204
205#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
206#[serde(deny_unknown_fields)]
207pub struct ReceiptIdempotency {
208 pub intent_key: NonEmptyString,
209 pub trigger_fingerprint: NonEmptyString,
210 pub content_hash: NonEmptyString,
211}
212
213#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
215#[serde(deny_unknown_fields)]
216pub struct RunnerProvenance {
217 pub provider: Option<String>,
218 pub model: Option<String>,
219 pub prompt_version: Option<String>,
220}
221
222#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
227#[serde(deny_unknown_fields)]
228pub struct ReceiptAct {
229 pub id: NonEmptyString,
230 pub form: ActForm,
231 pub intent: Intent,
232 pub summary: NonEmptyString,
233 #[serde(default)]
234 pub criterion_bindings: Vec<CriterionBinding>,
235 #[serde(skip_serializing_if = "Option::is_none")]
236 pub by: Option<RunnerProvenance>,
237 #[serde(default)]
238 pub source_refs: Vec<Reference>,
239 #[serde(default)]
240 pub target_refs: Vec<Reference>,
241 #[serde(default)]
242 pub artifact_refs: Vec<Reference>,
243 #[serde(skip_serializing_if = "Option::is_none")]
246 pub context_ref: Option<Reference>,
247 pub closure: Closure,
248 #[serde(skip_serializing_if = "Option::is_none")]
249 pub revision: Option<RevisionDetails>,
250 #[serde(skip_serializing_if = "Option::is_none")]
251 pub verification: Option<VerificationDetails>,
252}
253
254#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
256#[serde(deny_unknown_fields)]
257pub struct Seal {
258 pub disposition: ClosureDisposition,
259 pub reason_code: NonEmptyString,
260 pub summary: NonEmptyString,
261 pub closed_at: IsoDateTime,
262 pub last_observed_at: IsoDateTime,
265 #[serde(default)]
266 pub criteria: Vec<CriterionBinding>,
267}
268
269#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, RunxSchema)]
270#[serde(deny_unknown_fields)]
271pub struct Lineage {
272 #[serde(skip_serializing_if = "Option::is_none")]
273 pub parent: Option<Reference>,
274 #[serde(skip_serializing_if = "Option::is_none")]
275 pub previous: Option<Reference>,
276 #[serde(default)]
277 pub children: Vec<Reference>,
278 #[serde(default)]
279 pub sync: Vec<FanoutReceiptSyncPoint>,
280 #[serde(skip_serializing_if = "Option::is_none")]
282 pub resume_ref: Option<Reference>,
283}
284
285#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
286#[serde(rename_all = "snake_case")]
287pub enum ReceiptIssuerType {
288 Local,
289 Hosted,
290 Ci,
291 Verifier,
292}
293
294#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
295#[serde(deny_unknown_fields)]
296pub struct ReceiptIssuer {
297 #[serde(rename = "type")]
298 pub issuer_type: ReceiptIssuerType,
299 pub kid: NonEmptyString,
300 pub public_key_sha256: NonEmptyString,
301}
302
303#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
304#[serde(rename_all = "PascalCase")]
305pub enum SignatureAlgorithm {
306 Ed25519,
307}
308
309#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
310#[serde(deny_unknown_fields)]
311pub struct ReceiptSignature {
312 pub alg: SignatureAlgorithm,
313 pub value: NonEmptyString,
314}
315
316#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, RunxSchema)]
328#[serde(deny_unknown_fields)]
329#[runx_schema(id = "runx.receipt.v1")]
330pub struct Receipt {
331 pub schema: ReceiptSchema,
332 pub id: NonEmptyString,
333 pub created_at: IsoDateTime,
334 pub canonicalization: NonEmptyString,
335 pub issuer: ReceiptIssuer,
336 pub signature: ReceiptSignature,
337 pub digest: NonEmptyString,
338 pub idempotency: ReceiptIdempotency,
339 pub subject: Subject,
340 pub authority: ReceiptAuthority,
341 #[serde(default)]
344 pub signals: Vec<Reference>,
345 #[serde(default)]
346 pub decisions: Vec<Decision>,
347 #[serde(default)]
348 pub acts: Vec<ReceiptAct>,
349 pub seal: Seal,
350 #[serde(skip_serializing_if = "Option::is_none")]
351 pub lineage: Option<Lineage>,
352 #[serde(skip_serializing_if = "Option::is_none")]
353 pub metadata: Option<JsonObject>,
354}