# BUILD NOTES — Critical-Infrastructure Air-Gap

## Design decisions

- **Cross-directory imports.** `data_diode.py` and `air_gap_gateway.py` add
  `../sovereign_fabric` and `../vault` to `sys.path` at import time so they can
  reuse `identity_fabric` and the existing `vault` module without packaging or
  copying code. No code was duplicated.
- **One-way is structural, not a flag.** `transfer_reverse()` has no transfer
  logic at all — it only logs and returns a refusal. This makes it impossible to
  "accidentally" send HIGH→LOW; there is literally no such path. Real diodes get
  this from hardware; the note in the README is explicit about that.
- **Isolation posture is a first-class object.** `isolation.py` keeps a per-zone
  posture with a `_bindings` list that normal operation never populates. Only the
  explicit `simulate_open_socket()` test hook adds to it, so `assert_isolated()`
  passing is meaningful and its failure path is exercised in verify_run (#9).
- **Vault freshness.** `create_zone(with_vault=True)` re-initialises the vault
  unless an existing one opens with the supplied password. This keeps a zone
  stood up in a fresh run from being bound to a stale key from a previous run
  (the earlier `InvalidTag` failure mode), while preserving a real vault that
  matches.
- **Breach log hash-chain.** Independent from the fabric ledger:
  `row_hash = sha256(prev_hash + canonical_json(row))` over
  `{ts, zone, entity, event, reason}`. `verify_breach_chain()` recomputes it
  (verify_run #12).
- **Breach response ties the two modules together.** `_trip()` performs all four
  required actions atomically: seal diode, set `LOCKED_DOWN`, call
  `identity_fabric.rotate(cause="breach")` (so the stolen token dies fabric-wide,
  verify_run #4), and append the hash-chained breach row.
- **Wrong-zone detection.** A token can be valid for entity X yet presented at
  the wrong zone; `cross(..., expected_zone=)` treats a mismatch as a breach
  (verify_run #7). This models per-zone scoping without minting zone into the
  token itself.

## Verification
`verify_run.py` runs **13** checks, all PASS, covering: forward crossing,
refused reverse, forged→lockdown, fabric breach-rotation, locked-out crossings,
`reset()`, wrong-zone lockdown, `assert_isolated()` pass/fail, vault
pw-gating, vault-holds-item-after-crossing, breach-chain integrity, and
multi-zone management. It resets the fabric DB, breach log, and isolation
registry first for determinism.

## Constraints honored
- No `socket` / `requests` / `urllib` outbound. High sides are local directories
  under `./zones/<name>/high_side/`. Only local SQLite + on-disk AES-GCM (vault).
- The gateway itself is a library (no Flask server); the air-gap surfaces through
  the Sovereign Fabric dashboard and these harnesses.

## Known caveats
- The isolation guarantee is a simulation posture, not a kernel-level netns or a
  hardware diode. It asserts *our* code opens no bindings and gives operators a
  testable posture; it is not a substitute for physical air-gapping.
- Secure deletion in the vault is best-effort on the prototype (see
  `vault.delete_item`), as with the upstream module.
