Canonical Model
Provider-neutral domain entities, typed values, and SQLite storage.
Collectors normalize provider payloads into a canonical model before analysis. Domain structs hold typed fields and source IDs, not raw SDK objects.
Typed values
| Type | Description |
|---|---|
Money |
Minor units + ISO currency code |
Percentage |
Basis points |
Bytes, Duration |
Standard units |
Timestamp |
RFC3339 UTC |
| IDs | Provider, account, resource, snapshot identifiers |
Core entities
| Entity | Contents |
|---|---|
| Accounts, regions, resources | Compute, volumes, databases, NAT, network |
| Tags, relationships | Includes TargetMissing for broken refs |
| Costs | Attribution method, confidence, billing interval |
| Metrics | Raw series and derived utilization signals |
| Snapshots | Complete or partial; per-service coverage |
| Analysis runs | Findings, evidence, recommendations, rule outcomes |
Data quality
Each record tags DataQuality:
| Value | Meaning |
|---|---|
observed |
From a provider API |
derived |
Computed from observed data |
estimated |
Heuristic; labeled as such |
unavailable |
Signal absent |
stale |
Outside freshness window |
Storage
SQLite via modernc.org/sqlite (pure Go, CGO_ENABLED=0):
- Embedded forward-only migrations
- Schema version in
schema_meta - Atomic transactions for snapshots and analysis runs
- Idempotent save when
external_keyis set
Path: {DataDir}/cloudopt.db
Fixtures
YAML under testdata/fixtures/ loads the same schema as live collectors. Use import-fixture or --offline on collect.
Format spec: testdata/fixtures/README.md.
Partial snapshots
When a region or service fails independently, collectors mark the snapshot partial and record coverage metadata. analyze rejects partial snapshots unless you pass --allow-partial-snapshot.