# Due diligence: the 61 gates

Every gate a bank's procurement asks about, assessed against what is actually in the code and on the operator on 2026-09-29. **Done** names the file, route or test that proves it. Nothing here claims a certificate, an audit, an insurer, an opinion or a board that does not exist.

**47 done - 0 partial - 1 refused by design - 13 need an outside party or the maintainer's decision - 0 not started.**

## G1 - Security & crypto custody

*Owner: CISO, crypto governance*

| Gate | Status | Evidence |
|---|---|---|
| HSM / KMS-native corridor-secret custody *(deal-killer)* | Done | The corridor secret and every signing key can live in the bank's HSM or KMS: `mirror-custody` spawns the bank's own tool (no shell), with KCV checks at the ceremony and before every run. See [custody](/custody.md); tested in `kernel/mirror-custody.test.js` and `server/witness-supply.test.js`. |
| Key rotation ceremony for the corridor secret *(deal-killer)* | Done | Rotation by corridor version, never by clock: a signed plan (`rotate-plan`), a rehearsal against both HSM keys (`rotate-rehearse`), and a state machine with an honest rollback window and crypto-shredding of the old key. Rehearsed end to end in `server/rotation-rehearsal.test.js`. See [corridor key rotation](/corridor-key-rotation.md). |
| Key hierarchy and separation | Done | Member root keys delegate scoped, short-lived desk keys (`mirror-delegation`, `mirror-bander delegate`); origination, relay, agreement and witness keys are separate ceremonies. No chains: delegates cannot delegate. See [access control](/access-control.md). |
| FIPS 140-2/3 answer, documented | Done | [FIPS position](/fips-position.md): no validated module is shipped or claimed; the algorithms are approved; key operations can run inside the bank's own validated HSM. `mirror-bander fips` reports the running process's mode. |
| SBOM for every artefact | Done | `/sbom.cdx.json` (CycloneDX 1.5), freshness-tested by `server/sbom.test.js`. |
| Signed release provenance (SLSA / in-toto) | Done | `/provenance.intoto.json`: in-toto v1 + SLSA provenance v1 in a DSSE envelope, signed by `/release-key.jwk`, over every published artefact and every source file by digest. **SLSA Build Level 1** - not L2/L3, which need a hosted builder. See [provenance](/provenance.md). |
| Reproducible builds *(deal-killer)* | Done | Every subject of the signed provenance rebuilds byte for byte: the operator, bander and kit bundles, the vectors, the OpenAPI document, the SBOM and all four native binaries. One command, `node server/reproduce.js`, verifies the provenance, confirms the sources are the release's, rebuilds in a disposable copy at another path and compares each digest. Reproduced on 29 Sep 2026 on Windows 11 and on Ubuntu 24.04 (Node 24.16.0): 10 of 10 both times, the Linux run against provenance signed on Windows. `server/reproduce.test.js` rebuilds the JavaScript artefacts on every build. Not shown: a rebuild by anyone but the maintainer (both machines are one computer's), and the source tree is not public - a reviewer receives it in diligence; publishing it is the maintainer's decision. See [provenance](/provenance.md). |
| Independent penetration test + retest | Outside party | None commissioned and none claimed. Needs a testing firm. |
| Threat model with a public review history | Done | [Review history](/threat-model-reviews.md): every review that changed the threat model, what it found, what changed and the test that pins it. No named external reviewer yet - listed only with consent. |
| Secure development lifecycle statement | Done | [How Mirror is built](/secure-development.md) - the practice as it is, including where it falls short (one maintainer, no public repository, no second reviewer). |
| Disclosure programme with a published SLA | Outside party | Waiting on the maintainer's decision about a contact route. No intake channel is invented in the meantime. |

## G2 - Regulatory & contractual

*Owner: Legal, vendor management, DORA*

| Gate | Status | Evidence |
|---|---|---|
| DORA Article 30(2) clause pack, all provisions *(deal-killer)* | Outside party | Counsel's to draft. Product-side support is written: [contract schedules](/contract-schedules.md) (audit, exit, location, incidents, cooperation), each promise naming its mechanism. |
| Critical-function scoping opinion, in writing *(deal-killer)* | Outside party | Counsel's to write. The facts it needs are test-pinned in `/cannot`. |
| TLPT cooperation clause | Outside party | Contract text for counsel. |
| Incident reporting support pack (4h / 72h / 1 month) | Done | `mirror-incident`: the three clocks from stated times, and what the rail did during the window counted only from signed records the bank verified. See [incident support](/incident-support.md). Not legal advice. |
| GDPR pack with a pre-filled DPIA | Done | [DPIA pre-fill](/dpia-prefill.md): every fact marked as code-enforced (with its test) or a design statement; the conservative reading of pseudonymised commitments. |
| Records retention schedule, erasure conflict resolved | Done | [Retention schedule](/retention-schedule.md): per record class; erasure resolved by crypto-shredding the secrets that make commitments linkable, after the retention period and any hold. Automated pruning of two classes is stated as not yet built. |
| Audit rights clause with notice periods | Done | Drafted for counsel: [Schedule A](/contract-schedules.md) - continuous offline verification without notice, the supervisor seat, 30 days' notice for on-site audit. |
| Exit and portability clause | Done | Drafted for counsel: [Schedule B](/contract-schedules.md) - standing portability, a 30-day export, succession, continued verifiability. |
| Liability cap + published E&O / PI cover | Outside party | No cover is held and none is described. Needs an insurer. |
| Data residency commitment | Done | Processing and storage in the EU. The operator runs in a Cloudflare Durable Object restricted to the EU jurisdiction, read from the object's own id at runtime; if it is unreachable, the request is refused and processed nowhere else. The store is in AWS eu-central-1. The operator signs this at `GET /residency`; a fail-closed `meets(record, "eu")` ships in the kit and every SDK. Tested in `server/residency.test.js` and `kernel/mirror-residency.test.js`. The record lists what it does not cover: the first edge hop (forwarded unread), object ids in Cloudflare's logs, and storage that is declared rather than observed. Clause drafted for counsel in [Schedule C](/contract-schedules.md). See [data residency](/data-residency.md). |
| Published sub-processor register | Done | [Sub-processors](/subprocessors.md): Cloudflare and Supabase, what each processes and where, the shared database project disclosed, 30 days' notice of change proposed. |
| AMLR / 6AMLD position paper + R.16 alignment | Done | [Position paper for counsel](/amlr-position.md): AMLR information-sharing partnerships, both directives called 6AMLD, and why R.16 compliance stays with the payment chain. |

## G3 - Assurance & certifications

*Owner: Third-party risk, vendor assurance*

| Gate | Status | Evidence |
|---|---|---|
| SOC 2 Type II *(deal-killer)* | Outside party | Not held, and never shown. |
| ISO 27001:2022 | Outside party | Not held. |
| ISO 42001 | Outside party | Not held. Mirror runs no model (deterministic kernels). |
| Published pen-test summary + retest | Outside party | Depends on the pen test. |
| ISO 22301 or equivalent, tested | Outside party | Not held. |
| Financial-crime programme alignment statement | Done | [Programme map](/fincrime-alignment.md): what Mirror contributes to each part of an AML/CFT programme, and what it deliberately does not. The per-bank mapping is the bank's. |
| Regulator engagement pack | Done | [Supervisor pack](/supervisor-pack.md): one hand-over document - what it is, what it cannot do, how to check it offline, the seat, the known limits, who operates it. |

## G4 - The model

*Owner: Protocol*

| Gate | Status | Evidence |
|---|---|---|
| Versioned schema with a published stability policy *(deal-killer)* | Done | `/stability-policy.md`; the spec is pinned by hash on every record. |
| Extensible leg-role registry, without breaking v1 *(deal-killer)* | Done | An open lowercase namespace with defined unknown-role behaviour; independence comes from the signing key's class, never the role name. |
| Formal canonicalization / normalization spec | Done | SPEC §8.2, normative: canonical JSON, length-prefixing, exact decimal, every commitment derivation, key ids, low-S, webhook ids, and the wall's key normalisation - each pinned by named vectors. |
| Versioned signal / error taxonomy | Done | `/signals.json`, drift-tested against every code the operator can return. |
| /cannot: the refusal list as a contract *(deal-killer)* | Done | Signed at `GET /cannot`; version 1.1.0, 39 refusals, change-controlled by test. |
| Stable identifier scheme | Done | `mirror-locator`: domain-separated locator commitments with normalisation rules and vectors. |
| Independent-implementation interoperability test | Done | `conformance/reference-impl.js`, written from the spec text and importing nothing from the kernels, reproduces every vector byte for byte - including fine, basis, origin, both witness axes, webhook ids and canonical JSON. |

## G5 - Architecture & operations

*Owner: Infrastructure, bank platform team*

| Gate | Status | Evidence |
|---|---|---|
| Streaming / event-driven, not batch *(deal-killer)* | Done | Signed webhooks to proven parties only, first attempt immediately (under 2 s on loopback, test-enforced), a stated retry schedule. See [webhooks](/webhooks.md). |
| Idempotency and replay semantics *(deal-killer)* | Done | A retry and a failover resubmission are one verdict: counts key on the judgement, not signature bytes (`mirror-idempotency`). |
| HA bander, active-active with a degraded mode | Done | Two hosts band to byte-identical commitments; the operator counts one verdict; an unreachable HSM stops the run with no fallback. Tested in `server/bander-ha.test.js`. See [HA bander](/bander-ha.md). |
| Clock discipline, documented and monitored | Done | Signed, nonce-bound `POST /time`; `mirror-clock` proves an offset interval from the round trip; `client.clock()` monitors it. See [clock discipline](/clock-discipline.md). |
| Backfill and re-processing | Done | Party-side re-banding under a new table version makes new verdicts on the append-only ledger; `/replay` bounds a period. See [HA bander](/bander-ha.md). |
| Documented RTO / RPO, tested | Done | [RTO / RPO](/rto-rpo.md): a recovery drill on every build (restore and rebuild from members' signed copies to the same log root) and measured restore times. The store's backup setting is stated as unconfirmed. |
| Business continuity plan, tested annually | Outside party | Needs an operating organisation to test it. |
| Multi-operator resilience | Done | `mirror-cosign`, federation, succession, witness-cosigned heads with gossip fork detection. |
| Documented graceful degradation | Done | [Degradation runbook](/degradation-runbook.md): every failure degrades toward not clearing and toward telling the member; each row is tested. |

## G6 - Integration & enterprise plumbing

*Owner: Bank engineering, procurement*

| Gate | Status | Evidence |
|---|---|---|
| Terraform provider *(deal-killer)* | Done | A compiled provider (`terraform/provider`, Go, terraform-plugin-framework). It manages webhook subscriptions with drift detection and import, and sends signed governance events; a data source reads the registry. The key never enters Terraform: the bander signs. Tested with a real `terraform` 1.16.4 against an operator (`server/terraform-provider.test.js`): apply, drift recreated, import, no key in the state. The module is validated as well (`server/terraform-e2e.test.js`). Not on the Terraform Registry: publishing is the maintainer's decision. See [Terraform](/terraform.md). |
| OpenAPI 3.1 + SDKs (Python, Java, .NET, Go) *(deal-killer)* | Done | `/openapi.json` (generated, drift-tested, raw-data wall in the schema), and verifying SDKs in JavaScript, Python, Go, Java and .NET. Each is held to the 100 shared cases in `/sdk-vectors.json` and run against an operator (`server/sdk-polyglot.test.js`). Shipped as source; not on PyPI, Maven Central, NuGet or the Go module proxy. See [SDK](/sdk.md). |
| SSO (SAML 2.0 + OIDC) with SCIM | Refused by design | By design, not at the operator: Mirror authenticates keys, not people, and will not hold a staff directory (`/cannot` `staff_directory`). SSO sits at the member's gateway, which holds scoped desk keys. See [access control](/access-control.md). |
| Fine-grained RBAC | Done | Delegated desk keys with scopes (reconcile, governance, webhooks, read), enforced on every request, revocable live. |
| Native audit-log export to the bank's SIEM | Done | `POST /audit/export`: the member's own signed records as JSON lines, signed batch, cursor paging. The operator stores no SIEM credential. |
| Reference connectors for the top trade platforms | Done | Reference connectors for the messages trade platforms exchange: SWIFT MT700 / MT710 / MT720 documentary credits, and DCSA eBL 3.0 bills of lading (`connectors/mirror-connect.js`; tested in `kernel/mirror-mt700.test.js` and `kernel/mirror-ebl.test.js`). They sit beside the origination sidecar, the ISO 20022 ingest, the e-invoice readers and import mapping. Tested against messages built from the published standards. No named platform is connected; that is per-platform work with its owner. See [connectors](/connectors.md). |
| Webhooks with documented retry and delivery guarantees | Done | At least once, dedupe id stable across retries and failover, 7-step schedule, 410 pauses, redelivery. See [webhooks](/webhooks.md). |
| Migration and import tooling | Done | `import-check` (a dry run reporting counts and column names only), `band --mapping`, `table-import`. Tested. |
| Sandbox with production-parity auth, RBAC and audit | Done | `server/sandbox.js`: the same code with every production default on; its verdicts can never verify as production ones. See [sandbox](/sandbox.md). |

## G7 - Governance & protocol stability

*Owner: Board, ecosystem*

| Gate | Status | Evidence |
|---|---|---|
| Published change control over the refusal list *(deal-killer)* | Done | `server/cannot.test.js`: no removal or weakening within a major version; additions announced as minor versions. |
| Protocol stability policy *(deal-killer)* | Done | `/stability-policy.md`. |
| Public RFC process | Done | [RFC process](/rfc-process.md): what needs an RFC, a 30-day last call, published objections and answers. Today the maintainer decides - stated. |
| Independent security advisory board, named | Outside party | No names are published until people have agreed. |
| Transparency-log operations policy | Done | [Log policy](/transparency-log-policy.md): one history, mirroring, compelled withholding, what happens on a detected fork. |
| Roadmap governance, published and signed | Done | `GET /roadmap` signs the roadmap's Refused list (binding, change-controlled) and pins the page by hash. |

*Generated by `server/deal-gates.js`; the same data drives the private tracker.*
