Skip to main content

enfinitos_auditor/
types.rs

1//! Wire + domain types. Mirrors the TypeScript and Python ports
2//! field-for-field. Serde-rename attributes keep the on-disk JSON keys
3//! identical to the platform's canonical shape.
4
5use serde::{Deserialize, Serialize};
6use std::collections::BTreeMap;
7
8/// Bumped on any semantic break in the SignedProofPack shape.
9pub const SUPPORTED_ENVELOPE_VERSIONS: &[&str] = &["envelope.v1"];
10
11/// Algorithm identifiers the SDK understands.
12pub const SUPPORTED_SIGNATURE_ALGORITHMS: &[&str] = &["ed25519"];
13
14/// SDK version stamped onto every audit report.
15pub const SDK_VERSION: &str = "0.0.1";
16
17pub type EnvelopeVersion = String;
18pub type SignatureAlgorithm = String;
19
20// ---------------------------------------------------------------------
21// Verification keys
22// ---------------------------------------------------------------------
23
24/// One of N public keys the platform may have used to sign records.
25#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
26pub struct VerificationKey {
27    #[serde(rename = "keyId")]
28    pub key_id: String,
29    pub algorithm: SignatureAlgorithm,
30    #[serde(rename = "publicKey")]
31    pub public_key: String,
32    #[serde(rename = "notBefore")]
33    pub not_before: String,
34    #[serde(rename = "notAfter")]
35    pub not_after: Option<String>,
36    #[serde(rename = "revokedAt")]
37    pub revoked_at: Option<String>,
38    #[serde(default, skip_serializing_if = "Option::is_none")]
39    pub purpose: Option<String>,
40}
41
42/// Wire envelope of `/v1/runtime-keys`.
43#[derive(Debug, Clone, Serialize, Deserialize)]
44pub struct RuntimeKeysResponse {
45    pub ok: bool,
46    #[serde(rename = "contractVersion")]
47    pub contract_version: String,
48    pub data: RuntimeKeysData,
49}
50
51#[derive(Debug, Clone, Serialize, Deserialize)]
52pub struct RuntimeKeysData {
53    pub keys: Vec<VerificationKey>,
54    #[serde(rename = "issuedAt")]
55    pub issued_at: String,
56    #[serde(default, rename = "snapshotId", skip_serializing_if = "Option::is_none")]
57    pub snapshot_id: Option<String>,
58}
59
60// ---------------------------------------------------------------------
61// Proof pack
62// ---------------------------------------------------------------------
63
64/// Exactly the shape platform proof receipts emit, version "1".
65#[derive(Debug, Clone, Serialize, Deserialize)]
66pub struct ProofReceiptPayload {
67    pub version: String,
68    #[serde(rename = "receiptId")]
69    pub receipt_id: String,
70    #[serde(rename = "correlationId")]
71    pub correlation_id: Option<String>,
72    #[serde(rename = "spatialAnchorId")]
73    pub spatial_anchor_id: String,
74    #[serde(rename = "spatialPlacementId")]
75    pub spatial_placement_id: Option<String>,
76    #[serde(rename = "issuedAt")]
77    pub issued_at: String,
78    #[serde(rename = "renderedAt")]
79    pub rendered_at: String,
80    #[serde(rename = "dwellMs")]
81    pub dwell_ms: i64,
82    pub nonce: String,
83    pub witness: Option<String>,
84}
85
86/// A single signed receipt + provenance chain fields.
87#[derive(Debug, Clone, Serialize, Deserialize)]
88pub struct ProofRecord {
89    pub payload: ProofReceiptPayload,
90    #[serde(rename = "keyId")]
91    pub key_id: String,
92    pub algorithm: SignatureAlgorithm,
93    pub signature: String,
94    #[serde(rename = "payloadCanonical")]
95    pub payload_canonical: String,
96    #[serde(rename = "beforeHash")]
97    pub before_hash: Option<String>,
98    #[serde(rename = "afterHash")]
99    pub after_hash: String,
100}
101
102#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
103pub enum MeterUnitType {
104    #[serde(rename = "DWELL_SECONDS")]
105    DwellSeconds,
106    #[serde(rename = "IMPRESSION_IN_PLACE")]
107    ImpressionInPlace,
108    #[serde(rename = "ATTENTION_SECONDS")]
109    AttentionSeconds,
110    #[serde(rename = "OCCUPANCY_WEIGHTED_EXPOSURE")]
111    OccupancyWeightedExposure,
112    #[serde(rename = "COMPLIANT_DELIVERY_MINUTE")]
113    CompliantDeliveryMinute,
114    #[serde(rename = "CUSTOM")]
115    Custom,
116}
117
118impl MeterUnitType {
119    pub fn as_wire_str(&self) -> &'static str {
120        match self {
121            MeterUnitType::DwellSeconds => "DWELL_SECONDS",
122            MeterUnitType::ImpressionInPlace => "IMPRESSION_IN_PLACE",
123            MeterUnitType::AttentionSeconds => "ATTENTION_SECONDS",
124            MeterUnitType::OccupancyWeightedExposure => "OCCUPANCY_WEIGHTED_EXPOSURE",
125            MeterUnitType::CompliantDeliveryMinute => "COMPLIANT_DELIVERY_MINUTE",
126            MeterUnitType::Custom => "CUSTOM",
127        }
128    }
129}
130
131#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
132pub enum MeterStatus {
133    #[serde(rename = "PROJECTED")]
134    Projected,
135    #[serde(rename = "ACCEPTED")]
136    Accepted,
137    #[serde(rename = "SETTLED")]
138    Settled,
139    #[serde(rename = "VOID")]
140    Void,
141}
142
143#[derive(Debug, Clone, Serialize, Deserialize)]
144pub struct MeterRecord {
145    #[serde(rename = "idemKey")]
146    pub idem_key: String,
147    #[serde(rename = "proofReceiptId")]
148    pub proof_receipt_id: String,
149    #[serde(rename = "unitType")]
150    pub unit_type: MeterUnitType,
151    #[serde(rename = "unitCount")]
152    pub unit_count: String,
153    pub weight: String,
154    #[serde(rename = "spatialAnchorId")]
155    pub spatial_anchor_id: String,
156    #[serde(rename = "spatialPlacementId")]
157    pub spatial_placement_id: Option<String>,
158    #[serde(rename = "observedAt")]
159    pub observed_at: String,
160    pub status: MeterStatus,
161}
162
163#[derive(Debug, Clone, Serialize, Deserialize)]
164pub struct MeteringSummary {
165    #[serde(rename = "schemaVersion")]
166    pub schema_version: String,
167    #[serde(rename = "orgId")]
168    pub org_id: String,
169    #[serde(rename = "periodStart")]
170    pub period_start: String,
171    #[serde(rename = "periodEnd")]
172    pub period_end: String,
173    pub records: Vec<MeterRecord>,
174    #[serde(default, skip_serializing_if = "Option::is_none")]
175    pub totals: Option<BTreeMap<String, String>>,
176}
177
178#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
179pub enum SettlementPartyRole {
180    #[serde(rename = "TENANT")]
181    Tenant,
182    #[serde(rename = "VENUE")]
183    Venue,
184    #[serde(rename = "CUSTOMER")]
185    Customer,
186    #[serde(rename = "PLATFORM")]
187    Platform,
188}
189
190impl SettlementPartyRole {
191    pub fn as_wire_str(&self) -> &'static str {
192        match self {
193            SettlementPartyRole::Tenant => "TENANT",
194            SettlementPartyRole::Venue => "VENUE",
195            SettlementPartyRole::Customer => "CUSTOMER",
196            SettlementPartyRole::Platform => "PLATFORM",
197        }
198    }
199}
200
201#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
202pub enum SettlementStatus {
203    #[serde(rename = "PROJECTED")]
204    Projected,
205    #[serde(rename = "ACCEPTED")]
206    Accepted,
207    #[serde(rename = "POSTED")]
208    Posted,
209    #[serde(rename = "VOID")]
210    Void,
211}
212
213#[derive(Debug, Clone, Serialize, Deserialize)]
214pub struct SettlementLine {
215    #[serde(rename = "idemKey")]
216    pub idem_key: String,
217    #[serde(rename = "meterRecordIdemKey")]
218    pub meter_record_idem_key: String,
219    #[serde(rename = "partyRole")]
220    pub party_role: SettlementPartyRole,
221    pub share: String,
222    #[serde(rename = "ledgerAccountCode")]
223    pub ledger_account_code: String,
224    #[serde(rename = "amountCents")]
225    pub amount_cents: i64,
226    pub currency: String,
227    pub status: SettlementStatus,
228}
229
230#[derive(Debug, Clone, Serialize, Deserialize)]
231pub struct SettlementTotals {
232    #[serde(rename = "grossCents")]
233    pub gross_cents: i64,
234    #[serde(rename = "netToTenantCents")]
235    pub net_to_tenant_cents: i64,
236    #[serde(rename = "platformFeeCents")]
237    pub platform_fee_cents: i64,
238}
239
240#[derive(Debug, Clone, Serialize, Deserialize)]
241pub struct SettlementSummary {
242    #[serde(rename = "schemaVersion")]
243    pub schema_version: String,
244    #[serde(rename = "orgId")]
245    pub org_id: String,
246    #[serde(rename = "periodStart")]
247    pub period_start: String,
248    #[serde(rename = "periodEnd")]
249    pub period_end: String,
250    pub currency: String,
251    #[serde(rename = "meterGross")]
252    pub meter_gross: BTreeMap<String, i64>,
253    pub lines: Vec<SettlementLine>,
254    #[serde(default, skip_serializing_if = "Option::is_none")]
255    pub totals: Option<SettlementTotals>,
256}
257
258#[derive(Debug, Clone, Serialize, Deserialize)]
259pub struct SignedProofPack {
260    #[serde(rename = "envelopeVersion")]
261    pub envelope_version: EnvelopeVersion,
262    #[serde(rename = "issuedAt")]
263    pub issued_at: String,
264    #[serde(rename = "orgId")]
265    pub org_id: String,
266    #[serde(rename = "packId")]
267    pub pack_id: String,
268    #[serde(default, skip_serializing_if = "Option::is_none")]
269    pub label: Option<String>,
270    pub records: Vec<ProofRecord>,
271    #[serde(default, skip_serializing_if = "Option::is_none")]
272    pub metering: Option<MeteringSummary>,
273    #[serde(default, skip_serializing_if = "Option::is_none")]
274    pub settlement: Option<SettlementSummary>,
275}
276
277#[derive(Debug, Clone)]
278pub struct ProofPack {
279    pub envelope_version: EnvelopeVersion,
280    pub issued_at: String,
281    pub org_id: String,
282    pub pack_id: String,
283    pub records: Vec<ProofRecord>,
284}
285
286#[derive(Debug, Clone)]
287pub struct AuditBundle {
288    pub pack: SignedProofPack,
289    pub metering: Option<MeteringSummary>,
290    pub settlement: Option<SettlementSummary>,
291}
292
293// ---------------------------------------------------------------------
294// Reports
295// ---------------------------------------------------------------------
296
297#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
298pub enum AuditStepStatus {
299    #[serde(rename = "VALID")]
300    Valid,
301    #[serde(rename = "INVALID")]
302    Invalid,
303    #[serde(rename = "SKIPPED")]
304    Skipped,
305}
306
307#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
308pub enum AuditStepKind {
309    #[serde(rename = "envelope")]
310    Envelope,
311    #[serde(rename = "signature")]
312    Signature,
313    #[serde(rename = "canonicalisation")]
314    Canonicalisation,
315    #[serde(rename = "chain_link")]
316    ChainLink,
317    #[serde(rename = "meter_projection")]
318    MeterProjection,
319    #[serde(rename = "meter_total")]
320    MeterTotal,
321    #[serde(rename = "settlement_line")]
322    SettlementLine,
323    #[serde(rename = "settlement_total")]
324    SettlementTotal,
325    #[serde(rename = "key_lookup")]
326    KeyLookup,
327}
328
329/// Reason codes are deliberately enumerable + stable across SDK releases.
330/// Regulators and auditors cite them in formal reports. Adding codes is
331/// forward-compatible; renaming them is a breaking change.
332#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
333pub enum AuditReasonCode {
334    // Envelope / pack-level
335    #[serde(rename = "UNSUPPORTED_ENVELOPE_VERSION")]
336    UnsupportedEnvelopeVersion,
337    #[serde(rename = "MALFORMED_PACK")]
338    MalformedPack,
339    #[serde(rename = "EMPTY_PACK")]
340    EmptyPack,
341    #[serde(rename = "PACK_ORG_MISMATCH")]
342    PackOrgMismatch,
343    #[serde(rename = "UNSUPPORTED_ALGORITHM")]
344    UnsupportedAlgorithm,
345    // Signature
346    #[serde(rename = "SIGNATURE_INVALID")]
347    SignatureInvalid,
348    #[serde(rename = "SIGNATURE_MALFORMED")]
349    SignatureMalformed,
350    #[serde(rename = "UNKNOWN_KEY_ID")]
351    UnknownKeyId,
352    #[serde(rename = "KEY_OUTSIDE_VALIDITY_WINDOW")]
353    KeyOutsideValidityWindow,
354    #[serde(rename = "KEY_REVOKED_BEFORE_ISSUANCE")]
355    KeyRevokedBeforeIssuance,
356    // Canonicalisation
357    #[serde(rename = "PAYLOAD_CANONICAL_MISMATCH")]
358    PayloadCanonicalMismatch,
359    #[serde(rename = "AFTER_HASH_MISMATCH")]
360    AfterHashMismatch,
361    // Chain
362    #[serde(rename = "GENESIS_BEFORE_HASH_NOT_NULL")]
363    GenesisBeforeHashNotNull,
364    #[serde(rename = "CHAIN_LINK_MISMATCH")]
365    ChainLinkMismatch,
366    #[serde(rename = "CHAIN_OUT_OF_ORDER")]
367    ChainOutOfOrder,
368    // Metering
369    #[serde(rename = "METER_RECORD_FOR_UNKNOWN_PROOF")]
370    MeterRecordForUnknownProof,
371    #[serde(rename = "METER_UNIT_COUNT_MISMATCH")]
372    MeterUnitCountMismatch,
373    #[serde(rename = "METER_IDEM_KEY_MISMATCH")]
374    MeterIdemKeyMismatch,
375    #[serde(rename = "METER_TOTAL_MISMATCH")]
376    MeterTotalMismatch,
377    #[serde(rename = "METER_ORG_MISMATCH")]
378    MeterOrgMismatch,
379    // Settlement
380    #[serde(rename = "SETTLEMENT_LINE_FOR_UNKNOWN_METER")]
381    SettlementLineForUnknownMeter,
382    #[serde(rename = "SETTLEMENT_SHARE_SUM_NOT_ONE")]
383    SettlementShareSumNotOne,
384    #[serde(rename = "SETTLEMENT_AMOUNT_MISMATCH")]
385    SettlementAmountMismatch,
386    #[serde(rename = "SETTLEMENT_IDEM_KEY_MISMATCH")]
387    SettlementIdemKeyMismatch,
388    #[serde(rename = "SETTLEMENT_TOTAL_MISMATCH")]
389    SettlementTotalMismatch,
390    #[serde(rename = "SETTLEMENT_ORG_MISMATCH")]
391    SettlementOrgMismatch,
392    // Keys
393    #[serde(rename = "KEYS_FETCH_FAILED")]
394    KeysFetchFailed,
395    #[serde(rename = "KEYS_RESPONSE_MALFORMED")]
396    KeysResponseMalformed,
397}
398
399#[derive(Debug, Clone, Serialize, Deserialize)]
400pub struct AuditStep {
401    pub target: String,
402    pub kind: AuditStepKind,
403    pub status: AuditStepStatus,
404    #[serde(default, skip_serializing_if = "Option::is_none")]
405    pub reason: Option<AuditReasonCode>,
406    pub message: String,
407    #[serde(default, skip_serializing_if = "Option::is_none")]
408    pub detail: Option<serde_json::Value>,
409}
410
411#[derive(Debug, Clone, Serialize, Deserialize)]
412pub struct KeysSnapshot {
413    pub source: String,
414    #[serde(rename = "snapshotId")]
415    pub snapshot_id: Option<String>,
416    #[serde(rename = "keyCount")]
417    pub key_count: usize,
418    #[serde(rename = "keyIds")]
419    pub key_ids: Vec<String>,
420}
421
422#[derive(Debug, Clone, Serialize, Deserialize)]
423pub struct AuditReport {
424    pub status: AuditStepStatus,
425    #[serde(rename = "packId")]
426    pub pack_id: String,
427    #[serde(rename = "orgId")]
428    pub org_id: String,
429    #[serde(rename = "verifiedAt")]
430    pub verified_at: String,
431    #[serde(rename = "sdkVersion")]
432    pub sdk_version: String,
433    #[serde(rename = "envelopeVersion")]
434    pub envelope_version: String,
435    #[serde(rename = "keysSnapshot")]
436    pub keys_snapshot: KeysSnapshot,
437    pub steps: Vec<AuditStep>,
438}
439
440#[derive(Debug, Clone, Serialize, Deserialize)]
441pub struct ChainAuditReport {
442    pub status: AuditStepStatus,
443    #[serde(rename = "verifiedAt")]
444    pub verified_at: String,
445    #[serde(rename = "sdkVersion")]
446    pub sdk_version: String,
447    #[serde(rename = "recordCount")]
448    pub record_count: usize,
449    pub steps: Vec<AuditStep>,
450}
451
452#[derive(Debug, Clone, Serialize, Deserialize)]
453pub struct ProjectionAuditReport {
454    pub status: AuditStepStatus,
455    #[serde(rename = "verifiedAt")]
456    pub verified_at: String,
457    #[serde(rename = "sdkVersion")]
458    pub sdk_version: String,
459    #[serde(rename = "proofRecordCount")]
460    pub proof_record_count: usize,
461    #[serde(rename = "meterRecordCount")]
462    pub meter_record_count: usize,
463    pub steps: Vec<AuditStep>,
464}
465
466#[derive(Debug, Clone, Serialize, Deserialize)]
467pub struct SettlementAuditReport {
468    pub status: AuditStepStatus,
469    #[serde(rename = "verifiedAt")]
470    pub verified_at: String,
471    #[serde(rename = "sdkVersion")]
472    pub sdk_version: String,
473    #[serde(rename = "meterRecordCount")]
474    pub meter_record_count: usize,
475    #[serde(rename = "settlementLineCount")]
476    pub settlement_line_count: usize,
477    pub steps: Vec<AuditStep>,
478}
479
480#[derive(Debug, Clone, Serialize, Deserialize)]
481pub struct FullAuditReport {
482    pub status: AuditStepStatus,
483    #[serde(rename = "packId")]
484    pub pack_id: String,
485    #[serde(rename = "orgId")]
486    pub org_id: String,
487    #[serde(rename = "verifiedAt")]
488    pub verified_at: String,
489    #[serde(rename = "sdkVersion")]
490    pub sdk_version: String,
491    #[serde(rename = "keysSnapshot")]
492    pub keys_snapshot: KeysSnapshot,
493    pub pack: AuditReport,
494    pub chain: ChainAuditReport,
495    pub metering: ProjectionAuditReport,
496    pub settlement: SettlementAuditReport,
497}