# Offline Kit Import Verification Runbook This runbook supports AIRGAP-MANIFEST-510-010, AIRGAP-REPLAY-510-013, and AIRGAP-VERIFY-510-014. It validates bundles fully offline and enforces replay depth. ## Replay depth levels (manifest `replayPolicy`) - `hash-only`: verify manifest/bundle digests, staleness window, optional signature. - `full-recompute`: hash-only + every chunk hash + AV report hash. - `policy-freeze`: full-recompute + manifest policies must include the sealed policy hash (prevents imports with drifting policy/graph material). ## Quick steps ```bash src/AirGap/scripts/verify-kit.sh \ --manifest offline-kit/manifest.json \ --bundle offline-kit/bundle.tar.gz \ --signature offline-kit/manifest.sig --pubkey offline-kit/manifest.pub.pem \ --av-report offline-kit/reports/av-report.json \ --receipt offline-kit/receipts/ingress.json \ --sealed-policy-hash "aa55..." \ --depth policy-freeze ``` ## What the script enforces 1) Manifest & bundle digests match (`hashes.*`). 2) Optional manifest signature is valid (OpenSSL). 3) Staleness: `createdAt` must be within `stalenessWindowHours` of `--now` (defaults to UTC now). 4) AV: `avScan.status` must not be `findings`; if `reportSha256` is present, the provided report hash must match. 5) Chunks (full-recompute/policy-freeze): every `chunks[].path` exists relative to the manifest and matches its recorded SHA-256. 6) Policy-freeze: `--sealed-policy-hash` must appear in `policies[].sha256`. 7) Optional: `--expected-graph-sha` checks the graph chunk hash; `--receipt` reuses `verify-receipt.sh` to bind the receipt to the manifest/bundle hashes. Exit codes: hash mismatch (3/4), staleness (5), AV issues (6–8), chunk drift (9–10), graph mismatch (11), policy drift (12–13), bad depth (14). ## Controller verify endpoint (server-side guard) `POST /system/airgap/verify` (scope `airgap:verify`) expects `VerifyRequest`: ```jsonc { "depth": "PolicyFreeze", "manifestSha256": "...", "bundleSha256": "...", "computedManifestSha256": "...", // from offline verifier "computedBundleSha256": "...", "manifestCreatedAt": "2025-12-02T00:00:00Z", "stalenessWindowHours": 168, "bundlePolicyHash": "aa55...", "sealedPolicyHash": "aa55..." // optional, controller fills from state if omitted } ``` The controller applies the same replay rules and returns `{ "valid": true|false, "reason": "..." }`. ## References - Schema: `docs/airgap/manifest.schema.json` - Samples: `docs/airgap/samples/offline-kit-manifest.sample.json`, `docs/airgap/samples/av-report.sample.json`, `docs/airgap/samples/receipt.sample.json` - Scripts: `src/AirGap/scripts/verify-kit.sh`, `src/AirGap/scripts/verify-manifest.sh`, `src/AirGap/scripts/verify-receipt.sh`