From 82072bd32fc26efc922cd527238de0fc67a43964 Mon Sep 17 00:00:00 2001 From: Karthik Chandrasekaran Date: Sat, 19 Sep 2026 20:43:17 -0500 Subject: [PATCH 1/2] docs: diagram AWS and local private storage topologies Document proposed operator-supplied AWS peers, ordered backup publication and explicit restore, plus default local and isolated IPFS fixture paths. Keep Azure topology undecided. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: fc4430e2-35f5-48b8-bd0f-5dc6fad2a0ff --- docs/AWS_HYBRID_STORAGE.md | 3 + docs/DISTRIBUTED_STORAGE.md | 3 + docs/STORAGE_DESIGN.md | 4 + docs/STORAGE_QUICKSTART.md | 4 + docs/STORAGE_TOPOLOGY_DIAGRAMS.md | 255 ++++++++++++++++++++++++++++++ 5 files changed, 269 insertions(+) create mode 100644 docs/STORAGE_TOPOLOGY_DIAGRAMS.md diff --git a/docs/AWS_HYBRID_STORAGE.md b/docs/AWS_HYBRID_STORAGE.md index 987b08e..6a7b07e 100644 --- a/docs/AWS_HYBRID_STORAGE.md +++ b/docs/AWS_HYBRID_STORAGE.md @@ -6,6 +6,9 @@ certification. No AWS account, credentials, live deployment, or cloud validation was used to implement this sample. Review the draft before changing settings. Nothing here creates a GitHub deployment workflow or stores secrets in GitHub. +See the [AWS topology and write/restore diagrams](STORAGE_TOPOLOGY_DIAGRAMS.md) +for a visual guide separating the foundation from operator-supplied peers. + ## What is implemented `infra/aws/storage-foundation.json` is a manually deployable CloudFormation diff --git a/docs/DISTRIBUTED_STORAGE.md b/docs/DISTRIBUTED_STORAGE.md index ab39bea..f13094d 100644 --- a/docs/DISTRIBUTED_STORAGE.md +++ b/docs/DISTRIBUTED_STORAGE.md @@ -9,6 +9,9 @@ verify bytes against the pointer CID before decoding. The storage adapter does not encrypt: callers must supply authenticated ciphertext, never plaintext FHIR, biometrics, tokens, or derived keys. +See the [AWS and local topology diagrams](STORAGE_TOPOLOGY_DIAGRAMS.md) for +deployment boundaries and the ordered write and explicit-restore flows. + ## Atomicity and failure model The injected `BlobStore` index supplies atomic create and numeric-version CAS. diff --git a/docs/STORAGE_DESIGN.md b/docs/STORAGE_DESIGN.md index f54fbe5..36f24a9 100644 --- a/docs/STORAGE_DESIGN.md +++ b/docs/STORAGE_DESIGN.md @@ -7,6 +7,10 @@ and private-IPFS experiments without changing dual unlock, FHIR validation, signatures, MACs, or provenance verification. It does not claim clinical, regulatory, or production readiness. +The [storage topology diagrams](STORAGE_TOPOLOGY_DIAGRAMS.md) distinguish +implemented composition from proposed AWS infrastructure and local test setups. +Azure deployment topology is **To be decided**. + ```text PatientRecordService -> BlobStore |-- InMemoryBlobStore (test) diff --git a/docs/STORAGE_QUICKSTART.md b/docs/STORAGE_QUICKSTART.md index 15a9431..ecaab1e 100644 --- a/docs/STORAGE_QUICKSTART.md +++ b/docs/STORAGE_QUICKSTART.md @@ -10,6 +10,10 @@ Provider implementations and setup are segregated under for the folder layout and registering a custom adapter without editing the record service or built-in factory. +See the [storage topology diagrams](STORAGE_TOPOLOGY_DIAGRAMS.md) for the +proposed AWS deployment, default local app and optional private-IPFS fixture. +Azure deployment topology is **To be decided**. + ## Without any cloud accounts ```sh diff --git a/docs/STORAGE_TOPOLOGY_DIAGRAMS.md b/docs/STORAGE_TOPOLOGY_DIAGRAMS.md new file mode 100644 index 0000000..ae450bb --- /dev/null +++ b/docs/STORAGE_TOPOLOGY_DIAGRAMS.md @@ -0,0 +1,255 @@ +# Storage topology diagrams + +**Global Patient Record Project — synthetic-only research POC.** These diagrams +describe the implemented storage composition and a **proposed** AWS deployment, +not a deployed system or a clinical, regulatory, or production-readiness claim. +Azure deployment: **To be decided**; an adapter exists, but no Azure topology is +selected here. + +**Legend:** solid arrows show calls/data movement; dotted arrows show control, +credentials, or infrastructure encryption. Subgraph labels distinguish +CloudFormation resources from operator-supplied deployment prerequisites. +CID means content identifier; CAS means compare-and-swap. All persisted record +content is application-encrypted; storage adapters do not perform dual unlock. + +## AWS: proposed private three-peer deployment + +```mermaid +flowchart TB + subgraph APP["Application host - operator supplied"] + UI["Local browser UI"] + SERVICE["Loopback API and record service
Patient factor AND clinician private key
Encryption and verification in application"] + STORE["DistributedBlobStore
BackedUpContentStore and IpfsClusterClient"] + UI --> SERVICE + SERVICE -->|"Already-encrypted values"| STORE + end + + subgraph OPS["Operator supplied - private networking and runtime"] + AUTH["Secrets Manager or restricted external mounts
Infrastructure and API authentication only"] + PROXY["Authenticated private HTTPS endpoints
Separate Kubo and Cluster authorization"] + subgraph A["Proposed AZ A - optional foundation EC2 or operator host"] + C1["Cluster 1"] + K1["Kubo 1"] + E1["Separately encrypted EBS data volume
Kubo blocks and Cluster state"] + C1 -.->|"Pin management"| K1 + K1 --> E1 + C1 --> E1 + end + subgraph B["Proposed AZ B - additional operator-supplied EC2"] + C2["Cluster 2"] + K2["Kubo 2"] + E2["Separately encrypted EBS data volume"] + C2 -.->|"Pin management"| K2 + K2 --> E2 + C2 --> E2 + end + subgraph C["Proposed AZ C - additional operator-supplied EC2"] + C3["Cluster 3"] + K3["Kubo 3"] + E3["Separately encrypted EBS data volume"] + C3 -.->|"Pin management"| K3 + K3 --> E3 + C3 --> E3 + end + C1 -.->|"Cluster control membership - proposed Raft"| C2 + C2 -.->|"Not application metadata CAS"| C3 + C3 -.-> C1 + K1 <-->|"Private swarm - encrypted blocks"| K2 + K2 <-->|"Private swarm - encrypted blocks"| K3 + K3 <--> K1 + end + + subgraph FOUNDATION["CloudFormation foundation - if manually deployed"] + INDEX["DynamoDB single-Region key-to-CID index
Strong reads and atomic conditional writes
PITR and infrastructure encryption"] + BACKUP["Private versioned S3
Immutable CID-addressed encrypted backups
SSE-KMS"] + KMS["Separate infrastructure KMS keys
Not patient or clinician keys"] + OPTIONAL["CreateNode=false by default
At most ONE optional EC2 and data EBS
Approved prebuilt AMI required"] + end + + STORE -->|"Private route and authenticated HTTPS"| PROXY + PROXY -->|"Block put/get"| K1 + PROXY -->|"Pin request and replica observation"| C1 + STORE -->|"Synchronous backup before pointer publication"| BACKUP + STORE -->|"Read pointer; publish only after pins and backup"| INDEX + AUTH -.->|"API headers"| STORE + AUTH -.->|"Operator-injected swarm and Cluster secrets"| C1 + AUTH -.->|"Operator-injected private swarm key"| K1 + KMS -.-> INDEX + KMS -.-> BACKUP + KMS -.-> E1 + KMS -.->|"Operator-configured EBS encryption"| E2 + KMS -.->|"Operator-configured EBS encryption"| E3 + OPTIONAL -.->|"Compute only - no cluster bootstrap"| K1 +``` + +- The three nodes/AZs are a **proposal**, not stack-created replicas. The + [foundation template](../infra/aws/storage-foundation.json) provisions storage + and at most one optional node. The operator supplies additional peers, private + routes/SG rules, service endpoints, DNS/TLS/authenticated proxies, approved AMI, + secret injection, swarm configuration and monitoring. Repeat node-secret + provisioning for every peer; peer identities must be distinct. +- The app may run on the approved EC2 host or on a local host with reviewed + private connectivity, API credentials and scoped AWS identity. The current web + server remains loopback-only; this is **not** an internet-facing app deployment. + The foundation role trusts EC2; a local/other host needs its own reviewed + identity. No GitHub credentials belong in runtime secret mounts or configuration. +- Cluster places pins among distinct Kubo peers. Success requires observing at + least **two** live, distinct Kubo identities reporting pinned content (sample + maximum: three). This observation is not a continuing durability guarantee. + Raft, if selected by the operator, concerns Cluster control state/membership, + **not** the application's CAS; DynamoDB supplies that authority. +- A single EC2/EBS node remains a single point of failure and cannot satisfy the + remote two-copy policy alone. Even the proposed topology retains ingress, + metadata, AWS account/IAM and KMS dependencies; automatic API failover is not + implemented. A private swarm alone does not establish independently governed + decentralized durability. Operators still see CIDs, sizes, placement and timing. +- KMS adds infrastructure encryption; it never replaces patient/clinician + factors or receives the reconstructed record key. External secrets contain + infrastructure/API material, not patient factors or plaintext records. + +Implementation: [IPFS provider](../src/storage/adapters/ipfs/provider.ts), +[client](../src/storage/adapters/ipfs/ipfs-cluster-client.ts), +[DynamoDB adapter](../src/storage/adapters/dynamodb/dynamodb-blob-store.ts), +[S3 adapter](../src/storage/adapters/s3/s3-blob-store.ts). +Deployment details: [AWS runbook](AWS_HYBRID_STORAGE.md), +[runtime samples](../infra/aws/runtime), and +[nonsecret configuration](../examples/config/aws-hybrid.env.example). + +## Write ordering: replicated content, backup, then index + +```mermaid +sequenceDiagram + participant App as Application crypto and record service + participant Store as DistributedBlobStore + participant Wrapper as BackedUpContentStore + participant Primary as IpfsClusterClient via private HTTPS + participant S3 as S3 backup + participant Index as DynamoDB index + App->>Store: Create or CAS with already-encrypted value + Note over Store,Index: CAS first checks the current metadata version + Store->>Wrapper: Put serialized envelope bytes + Wrapper->>Primary: Put bytes + Note over Primary: Compute and verify raw CID; upload to Kubo; request Cluster pins + Note over Primary: Poll pin state and live peers; require at least two distinct Kubo copies + Primary-->>Wrapper: Verified CID after replication threshold + Wrapper->>S3: Conditional immutable create with SSE-KMS + Note over Wrapper,S3: Existing backup is accepted only after byte and CID verification + S3-->>Wrapper: Backup complete + Wrapper-->>Store: CID + Store->>Index: Atomic create or expected-version CAS of key-to-CID pointer + Index-->>Store: Published version + Store-->>App: Success +``` + +No pointer publication occurs if replication or backup fails. A lost CAS can +leave unreachable blocks/backups; no automatic unpin/delete occurs. A timeout +can have an ambiguous outcome, so reconcile before retrying. This is ordered +publication, not a transaction spanning IPFS, S3 and DynamoDB. +Sources: [distributed store](../src/storage/adapters/ipfs/distributed-blob-store.ts) +and [backup wrapper](../src/storage/adapters/ipfs/backed-up-content-store.ts). + +## Explicit restore: never an invisible read fallback + +```mermaid +sequenceDiagram + participant Operator as Operator + participant Store as DistributedBlobStore + participant Index as DynamoDB index + participant Wrapper as BackedUpContentStore + participant S3 as S3 backup + participant Primary as Private Kubo and Cluster via client + Operator->>Store: Restore opaque object key + Store->>Index: Strong read of current pointer + Index-->>Store: CID and version + Store->>Wrapper: Restore CID + Wrapper->>S3: Read immutable backup + S3-->>Wrapper: Encrypted bytes and CID envelope + Note over Wrapper: Validate format, version, bytes and matching CID + Wrapper->>Primary: Republish bytes and repin + Primary-->>Wrapper: Same CID after replica threshold + Wrapper-->>Store: Verified restored CID + Store-->>Operator: Complete; metadata unchanged +``` + +Ordinary reads use the index and primary Kubo bytes with CID verification; +primary failure is surfaced, not silently replaced with S3. Restore the metadata +index separately first if it is lost. Concurrent pointer changes can require +another recovery pass; content restore cannot recover lost factors or establish +snapshot freshness. See [recovery guidance](AWS_HYBRID_STORAGE.md). + +## Local default: no IPFS, Docker or cloud required + +With dependencies installed and no backend override, `npm start` runs this path: + +```mermaid +flowchart LR + UI["Browser UI
http://127.0.0.1:3000"] + API["Localhost-only API
Patient factor AND clinician private key"] + SERVICE["Record service
Application encryption and verification"] + FILE["FileBlobStore
.data/web
Encrypted records; single process"] + UI --> API + API --> SERVICE + SERVICE --> FILE +``` + +This is a single-host demonstration, not a distributed durability test. Existing +`.data/web` data is not automatically migrated when another backend is selected. +Sources: [web server](../src/web/server.ts) and +[file provider](../src/storage/adapters/file/provider.ts). + +## Optional local private-IPFS integration fixture + +`npm run test:integration:ipfs` is a **separate native Linux Docker** test path, +not part of `npm start`. Docker Desktop and remote Docker daemons are unsupported. + +```mermaid +flowchart TB + subgraph HOST["One native Linux host - synthetic integration only; no AWS"] + RUN["Integration runner and record-service tests
DistributedBlobStore and backup wrapper"] + FILES["Temporary local FileBlobStore
Metadata pointers and encrypted backups"] + RELAYS["Runner-owned TCP API relays
127.0.0.1 dynamic ports only"] + subgraph NET["Docker internal bridge - six containers"] + C1["Cluster 1"] -.->|"Pin control"| K1["Kubo 1"] + C2["Cluster 2"] -.->|"Pin control"| K2["Kubo 2"] + C3["Cluster 3"] -.->|"Pin control"| K3["Kubo 3"] + C1 -.->|"Raft control membership"| C2 + C2 -.-> C3 + C3 -.-> C1 + K1 <-->|"Private swarm"| K2 + K2 <--> K3 + K3 <--> K1 + VOLUMES["Per-node Docker data volumes
Separate restricted swarm and Cluster secret volume"] + K1 --> VOLUMES + K2 --> VOLUMES + K3 --> VOLUMES + end + RUN --> FILES + RUN -->|"Explicit loopback HTTP emulator mode"| RELAYS + RELAYS -->|"Suite ingress"| K1 + RELAYS -->|"Suite pin API"| C1 + RELAYS -->|"Probe and readiness APIs on other peers"| K2 + RELAYS --> K3 + RELAYS --> C2 + RELAYS --> C3 + end +``` + +The runner owns six API relays; no Docker host ports, public gateways or swarm +ports are published. APIs are unauthenticated for an isolated development host, +not safe for a shared untrusted machine. Only image pulls need internet access; +the running fixture stays on its internal bridge. Metadata/backups use temporary +local files, never AWS. Cluster state also persists in per-node Docker volumes. + +The suite exercises synthetic records and CID-verified restore. A separate +node-loss probe first confirms a synthetic block on **all three** Kubo peers, +then stops Kubo 1/Cluster 1 and checks the same bytes through Kubo 2. This is not +automatic ingress failover or proof of host/region failure tolerance: all peers, +metadata and backups still share one host. See the +[fixture contract](../test/fixtures/ipfs/README.md) and +[runner](../scripts/run-ipfs-integration.ts). + +## Azure: To be decided + +The Azure adapter does not select an Azure deployment architecture. Hosting, +networking, identity, metadata placement and recovery topology are intentionally +undefined here; no cross-cloud mirroring is implied. From af1fe06b8e8de9eb45f7529229db8693acb0dd06 Mon Sep 17 00:00:00 2001 From: Karthik Chandrasekaran Date: Sat, 19 Sep 2026 20:56:06 -0500 Subject: [PATCH 2/2] Fix Mermaid sequence diagram rendering Replace unescaped semicolons in sequence messages with commas so they are not parsed as statement separators. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: fc4430e2-35f5-48b8-bd0f-5dc6fad2a0ff --- docs/STORAGE_TOPOLOGY_DIAGRAMS.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/STORAGE_TOPOLOGY_DIAGRAMS.md b/docs/STORAGE_TOPOLOGY_DIAGRAMS.md index ae450bb..c98b877 100644 --- a/docs/STORAGE_TOPOLOGY_DIAGRAMS.md +++ b/docs/STORAGE_TOPOLOGY_DIAGRAMS.md @@ -129,8 +129,8 @@ sequenceDiagram Note over Store,Index: CAS first checks the current metadata version Store->>Wrapper: Put serialized envelope bytes Wrapper->>Primary: Put bytes - Note over Primary: Compute and verify raw CID; upload to Kubo; request Cluster pins - Note over Primary: Poll pin state and live peers; require at least two distinct Kubo copies + Note over Primary: Compute and verify raw CID, upload to Kubo, request Cluster pins + Note over Primary: Poll pin state and live peers, require at least two distinct Kubo copies Primary-->>Wrapper: Verified CID after replication threshold Wrapper->>S3: Conditional immutable create with SSE-KMS Note over Wrapper,S3: Existing backup is accepted only after byte and CID verification @@ -168,7 +168,7 @@ sequenceDiagram Wrapper->>Primary: Republish bytes and repin Primary-->>Wrapper: Same CID after replica threshold Wrapper-->>Store: Verified restored CID - Store-->>Operator: Complete; metadata unchanged + Store-->>Operator: Complete, metadata unchanged ``` Ordinary reads use the index and primary Kubo bytes with CID verification;