# Twin Coupling -- Data Convention ## Problem A digital-twin computation ingests readings from one or more sensor chains and produces a derived result. The result is appended to its own trusty chain (separate `entity_id`). How does an external verifier confirm which sensor rows fed the computation? ## Convention The twin row carries a `source_refs` array inside its `data` field: ```json { "computation": "rolling_avg_temp_c", "result": 21.37, "source_refs": [ {"chain_id": "chain_sensor-001_...", "sequence": 0, "row_hash": "ae1cfe445cda1f6d"}, {"chain_id": "chain_sensor-001_...", "sequence": 1, "row_hash": "dee9856d170787cf"}, {"chain_id": "chain_sensor-001_...", "sequence": 2, "row_hash": "26cd125f051243cf"} ] } ``` Each entry is: | Field | Source | Purpose | |------------|---------------------------------|--------------------------------------------| | `chain_id` | `AppendResult.chain_id` | Identifies which sensor chain | | `sequence` | `AppendResult.sequence` | Row position within that chain | | `row_hash` | `AppendResult.row_hash` | Tamper-evident fingerprint of the row | Because `data` is already part of the payload that `row_hash` commits to, the `source_refs` array is itself hash-protected. Altering any reference after the fact invalidates the twin row's `row_hash` and fails `verify_chain`. ## Verification flow (two ProofBundles) An external verifier needs no database access -- only two `ProofBundle` blobs exported via `Trusty::export_bundle`: 1. `sensor_bundle.verify(&sip_key)` -- confirms the sensor chain is internally consistent. 2. `twin_bundle.verify(&sip_key)` -- confirms the twin chain is internally consistent. 3. Walk twin rows; for each `source_refs` entry, look up the matching row in `sensor_bundle.rows` by `chain_id` + `sequence` and assert `row_hash` matches. If all three checks pass, the verifier knows: - The sensor readings are unaltered. - The twin computation row is unaltered. - The twin row commits to exactly those sensor row_hashes at write time. See `crates/trusty-sqlite/examples/twin_coupling.rs` for a runnable end-to-end demo. ## Design decisions **Why `data` field, not a new `break_reason` variant.** `break_reason` marks loss-of-continuity (tamper, gap, domain mismatch). A twin derivation is a planned event, not an anomaly. Mixing the two pollutes `breaks_since()` as an alerting surface and requires callers to filter by break kind. **Why not a separate `twin_reference` table.** A join table outside the hash surface cannot be verified from a ProofBundle alone. The verifier would need database access, breaking the self-contained export contract. `source_refs` in `data` is verifiable anywhere the bundle travels. **What this does NOT prove.** The convention proves the twin row committed to those row_hashes at write time. It does not prove the computation is correct (the twin operator could have computed a wrong average and still referenced the right sensor rows). Computation correctness is out of scope for trusty; it belongs to a separate audit or reproducibility layer. ## Multi-sensor / multi-chain refs `source_refs` can mix `chain_id` values from different sensor entities. The verifier simply exports a ProofBundle per sensor entity and checks each ref against the appropriate bundle. ```json "source_refs": [ {"chain_id": "chain_sensor-001_...", "sequence": 5, "row_hash": "..."}, {"chain_id": "chain_sensor-002_...", "sequence": 3, "row_hash": "..."} ] ```