Infrastructure

The Dash runs on DigitalOcean infrastructure deployed inside a private Virtual Private Cloud (VPC), which keeps all platform traffic on an isolated internal network rather than the public internet. Compute, storage, and database resources are provisioned within this VPC using infrastructure-as-code, so every environment is reproducible, version-controlled, and auditable. 

Introduction & Scope

Document purpose, platform context, referenced documents

Introduction & Scope

Chapter 1 — Introduction & Scope

1.1 Document Purpose

This document (LH-SBS-INF-001) is the authoritative infrastructure specification for The Dash — SBS's Multi-Gated AI Orchestrator Platform, designed by Ledger Hub Networks exclusively for SBS. It defines the complete DigitalOcean hosting environment on which the Platform operates: account and project structure, network topology, DNS and domain architecture, TLS and edge security, compute and storage layers, secrets management, identity integration, infrastructure-as-code practice, observability, audit infrastructure, and backup and disaster recovery.

The specification serves four functions. First, it is the build reference: every infrastructure resource provisioned for the Platform must conform to the configurations stated herein, and any deviation requires a documented change under the governance forms in effect for the engagement (LH-SBS-CO- series). Second, it is the verification baseline: the acceptance gates defined in LH-SBS-INST-001 (V1–V22) test the environment against this document. Third, it carries the Build Register (Appendix, Chapter 18): the tracked checklist of provisioning items, security controls, and open nonconformances between the design target and the as-built state, with per-item completion status. Fourth, it is the operating reference for the IT execution owner (Nelson Santos, IT Director) and any personnel subsequently authorized to administer the environment.

Where the as-built environment deviates from the design target, this document states both: the target configuration (normative) and the current state (informative, dated), with the gap tracked in the Build Register. A statement of current state is not acceptance of it.

This document is 100% technical in content. Commercial terms, fees, and engagement governance are out of scope and are addressed in their respective controlling documents.

1.2 Platform Context

The Dash is a zero-trust, identity-aware platform that runs SBS's routine corporate workflows through isolated, per-function Compartments. Inside each Compartment, three classes of AI agents operate under supervision — Workflow Agents perform the first-pass work, Guardian Agents enforce policy and Risk Tiers, and Supervisor Agents route Medium/High/Critical decisions to named SBS staff. The domain is publicly resolvable but serves a private audience behind SSO and phishing-resistant MFA; every agent decision is written to a tamper-evident, hash-chained audit log.

The Platform is hosted on DigitalOcean under a dedicated SBS account, organized into two DigitalOcean Projects that define the environment topology:

All internal service-to-service traffic is required to remain on private VPC networking. Public exposure is restricted to hardened entry points in two classes.

Tenant surfaces (three-subdomain tenant model):

Operational surfaces:

Operational surfaces carry no tenant data and sit behind the same TLS, reverse-proxy, and firewall controls as the tenant surfaces, with access scoped to administrative roles only under the least-privilege model of the IAM control family.

Each of the ten functional Compartments is isolated across eight layers — frontend, authentication, application, data, storage, secrets, network, and audit — and this specification defines how each isolation layer is realized in infrastructure: firewall rule sets and VPC segmentation (network); per-Compartment separation across the PostgreSQL, MongoDB, and Redis data services (data, storage); Vault namespace separation (secrets); and hash-chained log capture (audit). The infrastructure supporting the audit log — its storage, integrity chain, and retention — is specified in Chapter 13.

The environment follows a zero-trust posture per NIST SP 800-207 and application controls aligned to OWASP ASVS L2, with AI-specific controls addressing the OWASP LLM Top 10 (including LLM01 prompt injection) as carried in LH-SBS-SEC-001.

1.3 Referenced Documents
Document ID / Source Title Relationship to this Specification
LH-SBS-KO-001 V3.2 Technical Install and Discovery Master runbook. This specification elaborates the infrastructure control families imported into KO-001 from the Foundation Install Spec (Vault, IaC, supply-chain, observability, backup/DR, hash-chained audit, identity depth, WordPress isolation).
LH-SBS-SEC-001 Security & Access Control Specification Controlling security baseline. The control families (DNS, TLS, IAM, APP, AI, INF, FE, MON) constrain every configuration stated in this document. The Build Register in Chapter 18 tracks implementation status per control ID. Control-set reconciliation pending: the Build Register enumerates 41 control IDs against the 36 recorded in SEC-001 V1.0; SEC-001 revision to V1.1 or register correction to follow.
LH-SBS-PRE-001 Pre-Install Prerequisites Gate conditions. The nine prerequisite gates must be satisfied before any resource defined in this specification is provisioned (Day 0).
SERVER_CHANGELOG.md Server Changelog — SBS Infrastructure Operational as-built log maintained by platform engineering. Feeds the Build Register with dated change entries and verification evidence. Entries in the changelog do not modify this specification; divergences it records are tracked as nonconformances. The changelog must contain no credentials (INF-02); see Build Register item 15.

Where this specification and LH-SBS-SEC-001 address the same control, LH-SBS-SEC-001 states the requirement and this document states the implementation. In the event of conflict, LH-SBS-SEC-001 controls and this document shall be corrected.

Infrastructure Overview

DigitalOcean account structure, region selection, environment topology (production / staging)

Infrastructure Overview

Chapter 2 — Infrastructure Overview

2.1 DigitalOcean Account Structure

The Platform operates under a single dedicated DigitalOcean account owned by SBS (the "SBS Account"). A dedicated account — rather than a shared or Ledger Hub-owned account — keeps billing, resource ownership, and access control unambiguously with SBS and satisfies the tenant-separation intent of the IAM control family at the infrastructure root.

Account-level configuration (target):

Nonconformance — credential handling (CRITICAL). As of 2026-07-17 the operational changelog carried plaintext credentials: a shared root password stated to cover all project droplets, the GitHub account, the wiki admin account, and the Grafana admin account. All four credential sets are treated as compromised. Remediation (Build Register item 15): rotate all four immediately; migrate to Vault upon its deployment (Chapter 8) with interim storage in an approved password manager; strip the credentials section from the changelog and purge it from any version-control history; enforce distinct per-service credentials. No shared passwords are permitted for any host or service.

2.2 Project Topology

Resources are organized into two DigitalOcean Projects:

Project Project ID Purpose
SBS The Dash Dev (Default) ac656e28-2196-410e-847c-bf3b60363710 Development and staging. Build and integration work occurs here first; no tenant data.
SBS The Dash Prod c49c646f-f268-4b51-a00e-ad9fb5055569 Production. Live Platform resources: application services, observability stack, documentation portal, audit-log storage.

2.2.1 Droplet Inventory (As Built, 2026-07-18)

Droplet Project Region / VPC Size Public IP Private IP OS Role
Dev-SBS-server Dev NYC2 / default-nyc2 8 GB / 160 GB 162.243.252.138 10.100.0.2 Ubuntu 24.04 LTS Development server. Docker 29.1.3 / Compose 2.40.3.
sbsdash-server-prod Prod NYC2 / default-nyc2 8 GB / 160 GB 107.170.72.145 10.100.0.3 TBC Production application server; tenant surfaces. Docker 29.1.3 / Compose 2.40.3. Administered via jump host (§3.3).
monitor-servers Prod NYC2 / default-nyc2 4 GB / 120 GB 162.243.28.132 10.100.0.4 Ubuntu 26.04 LTS Observability — Grafana 13.1.0, Prometheus, nginx 1.28.3, TLS live at monitor.sbsdash.com. Docker 29.1.3 / Compose 2.40.3. De facto SSH bastion (§3.3).
sbs-wiki Prod NYC1 / default-nyc1 2 GB / 60 GB 167.172.135.88 10.116.0.2 TBC Documentation portal — BookStack; wiki.sbsdash.com.
Dev-Gitlab-server Unregistered VPC 10.120.x TBC 138.197.222.154 TBC TBC Undocumented droplet currently receiving the sbsdash.com apex A record. Disposition pending (Build Register item 22).

⚠ Two inventory nonconformances: (a) sbs-wiki region (§2.3); (b) Dev-Gitlab-server exists outside the two-project inventory yet holds production DNS — see §2.2.2.

2.2.2 Nonconformance — Production DNS Target (CRITICAL)

The apex record sbsdash.com → 138.197.222.154 currently resolves to Dev-Gitlab-server, not to sbsdash-server-prod (107.170.72.145). The primary tenant domain therefore points at an uninventoried development host. Required action (Build Register item 16), one of:

Until resolved, Dev-Gitlab-server is inside the security boundary of the engagement and subject to all INF-family controls.

2.2.3 Project Topology Rules

2.3 Region Selection

Primary region: NYC2 (New York).

Rationale:

Nonconformance — sbs-wiki (NYC1). DigitalOcean VPC networks are region-scoped: the NYC1 wiki droplet cannot join any NYC2 VPC. As built, the monitor scrapes the wiki's node_exporter over the public internet, unencrypted, admitted by a ufw rule scoped to the monitor's public IP. Metric content is low-sensitivity (CPU/RAM/disk), so this is an accepted interim state — but it is eliminated, not mitigated, by the wiki rebuild into NYC2 (Build Register item, carried from prior revision). Sequencing: rebuild before VPC enrollment and firewall standardization (Chapter 3); rebuilding after DNS/TLS/proxy work would force rework of all three.

Managed-service availability. NYC2 predates some newer DigitalOcean managed-service capacity. Availability of Managed Databases (PostgreSQL, MongoDB, Redis) in NYC2 is to be confirmed before Chapter 7 is finalized; the fallback design is self-hosted data services under Docker Compose on dedicated droplets within the Prod VPC.

2.4 Environment Topology
Attribute Dev (Staging) Prod
DigitalOcean Project SBS The Dash Dev SBS The Dash Prod
Compute Dev-SBS-server sbsdash-server-prod, monitor-servers, sbs-wiki (pending NYC2 rebuild)
VPC (target) Dedicated Dev VPC (NYC2) Dedicated Prod VPC (NYC2), no peering with Dev
VPC (as built) default-nyc2, shared ⚠ default-nyc2, shared ⚠ (wiki: default-nyc1)
OS Ubuntu 24.04 LTS Mixed: 26.04 (monitor), others TBC — standardization decision pending (Build Register item 21)
Public surfaces None (target); as built, sbsdash.com apex resolves to a Dev-side host ⚠ (§2.2.2) sbsdash.com, admin., clients. → sbsdash-server-prod (target); monitor.sbsdash.com → monitor-servers (live); wiki.sbsdash.com → sbs-wiki
Data services PostgreSQL, MongoDB, Redis — reduced sizing, synthetic data (not yet installed) PostgreSQL, MongoDB, Redis — production sizing, per-Compartment separation (Chapter 7) (not yet installed)
Observability node_exporter on VPC-private IP (deployed) Full stack on monitor-servers (deployed and verified 2026-07-17); node_exporter on prod/wiki (deployed)
Documentation BookStack on sbs-wiki (deployed)
Runtime toolchain Docker 29.1.3 / Compose 2.40.3 (deployed). Node.js (pnpm), Rust (Cargo), Python pending — versions pinned per Chapter 11 at install Same
Source control Single GitHub repository set (platform code + IaC), branch-protected per INF-03 — repository setup pending Same; Prod deploys from protected release branches only

Toolchain versions are pinned identically across both environments (Chapter 11) so that Dev verification is meaningful for Prod promotion — version drift between environments is treated as a build defect. The current OS mix (24.04 vs 26.04) is a tracked instance of exactly this class of drift.

2.5 Server Users and Host Access

Target (INF-04): named user accounts per individual; no shared accounts; no direct root login (PermitRootLogin no); SSH key authentication only, password authentication disabled at the daemon; privilege elevation via sudo, logged, without blanket NOPASSWD; authoritative user list maintained in the IaC repository (Chapter 10); administrative access paths and IdP enforcement per Chapter 9.

As built (2026-07-17/18) — nonconformant:

Remediation (Build Register item 17): create per-person named users with individually generated keys; disable root SSH; replace blanket NOPASSWD with scoped sudo (or passworded sudo); retire or restrict sbsRoot to break-glass with vaulted credentials; capture the authorized-user list in IaC. Until complete, individual attribution of host actions is impossible, which also degrades MON-01 (unified audit trail).

Network Architecture

VPC design, subnets, internal routing, egress control, firewall rules

Network Architecture

Chapter 3 — Network Architecture

3.1 VPC Design

Target: two dedicated VPC networks, one per environment, both in NYC2, no peering:

VPC Environment Members
prod-vpc-nyc2 Prod sbsdash-server-prod, monitor-servers, sbs-wiki (post NYC2 rebuild)
dev-vpc-nyc2 Dev Dev-SBS-server

As built: neither dedicated VPC exists. Current membership:

VPC (actual) CIDR Members
default-nyc2 10.100.0.0/20 Dev-SBS-server (.2), sbsdash-server-prod (.3), monitor-servers (.4) — Dev and Prod share one flat private network
default-nyc1 10.116.0.0/20 sbs-wiki (.2) — unreachable from NYC2 private networking
10.120.x Dev-Gitlab-server (§2.2.2)

Constraint driving the decision: a DigitalOcean droplet's VPC assignment is fixed at creation — moving a droplet to a new VPC requires snapshot-and-rebuild. Two paths (Build Register item 18):

This specification proceeds on path (a); §3.2 onward describes the target VPCs. If path (b) is elected, §3.1–3.2 are amended by Change Order.

DigitalOcean VPC model notes (unchanged): a VPC is a single flat private network per region — no user-defined subnets, route tables, or security groups; intra-VPC segmentation is enforced by Cloud Firewalls (tag-scoped) plus host firewalls (§3.5, §3.6). VPCs are region-scoped, which is the technical basis for the wiki rebuild requirement.

3.2 Address Plan
Network CIDR State
prod-vpc-nyc2 10.10.0.0/20 Target — created during rebuild wave
dev-vpc-nyc2 10.20.0.0/20 Target — non-overlapping with Prod and with the legacy 10.100/10.116/10.120 ranges, so logs and tooling remain unambiguous during migration
default-nyc2 (10.100.0.0/20), default-nyc1 (10.116.0.0/20), 10.120.x Legacy — vacated at end of rebuild wave

Private IPs are recorded in the Build Register per droplet at VPC enrollment; the authoritative address inventory lives in IaC state (Chapter 10), not in this document, to avoid drift.

3.3 Internal Routing and Service Communication

All service-to-service traffic between Prod droplets transits the VPC private interface (eth1), never public IPs.

Inter-Compartment note (unchanged): Compartment network isolation is realized within sbsdash-server-prod at the Docker Compose network level — one dedicated compose network per Compartment, no shared bridges, cross-Compartment traffic denied by non-membership. Docker 29.1.3 / Compose 2.40.3 are now present on prod, dev, and monitor (2026-07-18), so this layer is buildable. Container-level detail in Chapter 6.

3.4 Egress Control

DigitalOcean provides no managed NAT/egress gateway; egress control is enforced at the host firewall layer:

As built: no egress control exists on any droplet (all outbound open). Egress enforcement lands with the host-firewall baseline (§3.6, Build Register item 20).

3.5 Cloud Firewall Rules (DigitalOcean Cloud Firewalls, tag-scoped)

As built: no Cloud Firewalls exist. Host-level ufw is active on monitor and wiki only; prod and dev have no active firewall at any layer — their public interfaces are protected solely by service bind addresses. This is the most exposed element of the current state and is remediated first in the firewall workstream (Build Register item 19).

Target — four firewalls, attached by droplet tag:

FW prod-app → tag prod (sbsdash-server-prod)

Dir Proto/Port Source / Destination Purpose
In TCP 443 0.0.0.0/0, ::/0 Tenant surfaces via reverse proxy
In TCP 80 0.0.0.0/0, ::/0 ACME HTTP-01 + 301→443 only
In TCP 22 monitor-servers private IP only SSH via bastion (§3.3) — no direct public SSH
In TCP 9100 + app exporters monitor-servers private IP Prometheus scrape
Out Per §3.4 Restricted egress

FW prod-monitor → tag grafana (monitor-servers)

Dir Proto/Port Source Purpose
In TCP 443 0.0.0.0/0 monitor.sbsdash.com (Grafana behind IdP, Chapter 9)
In TCP 80 0.0.0.0/0 ACME + redirect
In TCP 22 Admin allowlist IPs (Build Register) Bastion SSH — sole public SSH entry to Prod
3000/9090/9100 bind 127.0.0.1 — no public rule exists (verified as-built)

FW prod-wiki → tag wiki (sbs-wiki, post-rebuild)

Dir Proto/Port Source Purpose
In TCP 443 0.0.0.0/0 wiki.sbsdash.com (BookStack behind SSO)
In TCP 80 0.0.0.0/0 ACME + redirect
In TCP 22 monitor-servers private IP SSH via bastion
In TCP 9100 monitor-servers private IP node_exporter scrape (rebinds private post-rebuild)

FW dev-app → tag develop — SSH from admin allowlist; web surfaces admit admin allowlist only (no production DNS points at Dev — enforcement of this depends on resolving §2.2.2).

Deviation from prior draft, deliberate: direct public SSH to prod-app is removed in favor of bastion-only SSH, codifying the as-built jump-host pattern — it is the stronger posture. Rule hygiene unchanged: firewalls are IaC-defined; console edits are drift and reverted; the admin SSH allowlist is a named IaC variable reviewed at each IAM-06 access review.

3.6 Host Firewall

Cloud Firewalls filter at the platform edge; a host firewall on each droplet provides defense-in-depth and the per-container egress allowlist (§3.4), which Cloud Firewalls cannot express.

As built: ufw on monitor (22/80/443 in) and wiki (22/80/443 + 9100-from-monitor in); ufw inactive on prod and dev; no egress rules anywhere; no nftables rulesets deployed.

Target: nftables on all droplets — required because per-Compartment egress chains (one chain per compose network) and logged default-deny outbound are beyond ufw's expressible policy. Migration order: (1) activate baseline host firewall on prod and dev immediately (ufw acceptable as a stopgap — inbound mirror of the Cloud Firewall admit set); (2) replace fleet-wide with IaC-rendered nftables rulesets at the rebuild wave; (3) add per-Compartment chains when Compartment compose networks are created (Chapter 6). Base policy: inbound default-deny mirroring the cloud firewall, outbound default-deny with the §3.4 allowlist, established/related admitted. Rulesets are rendered from IaC templates and versioned; the live ruleset hash is a monitored value (MON-01) so tampering surfaces as drift.

3.7 Verification

Network gates (LH-SBS-INST-001): public-listener scan — no data-service or Prometheus ports on public interfaces (passes today on monitor per ss -tlnp evidence; wiki exporter public listener is a tracked exception until rebuild); Dev↔Prod isolation probe — no private path between environments (fails today: shared default-nyc2; passes after rebuild wave); egress-allowlist test — denied-domain attempt logged (not testable today: no egress control); per-Compartment cross-network denial test (pending Chapter 6 build); bastion path test — prod SSH unreachable except via monitor. INF-01 is the controlling SEC-001 requirement throughout.

DNS & Domain Architecture

sbsdash.com zone, three-subdomain tenant model, record inventory, DNSSEC

DNS & Domain Architecture

Chapter 4 — DNS & Domain Architecture

4.1 Domain and Registrar

sbsdash.com is the sole Platform domain. Registrar-level state is a security control surface in its own right (DNS-01):

4.2 Authoritative DNS

As built (2026-07-18): the zone is hosted on GoDaddy nameservers. GoDaddy does not provide DNSSEC signing for externally manageable zones in this configuration, records are console-managed rather than IaC-managed, and no API-driven change control is in place. This state satisfies neither DNS-01 (DNSSEC) nor the IaC management rule (Chapter 10) and is a tracked nonconformance (Build Register item 9).

Target: the authoritative zone is hosted at a DNSSEC-capable, API-managed provider. Approved options, in preference order:

  1. Cloudflare DNS (free tier sufficient) — one-click DNSSEC signing, DS handoff to the registrar, full API for IaC management, native CAA/CT tooling.
  2. deSEC — DNSSEC-by-default, API-managed, nonprofit.
  3. Registrar-hosted DNS only if the registrar signs zones and exposes a management API.

Zone cutover sequence (Nelson Santos, execution owner): replicate the current record set at the new provider → verify resolution parity against §4.3 → lower TTLs to 300 → update NS delegation at the registrar → confirm propagation → enable DNSSEC and publish DS (§4.5) → raise TTLs. The zone is thereafter managed as code (Chapter 10): records are defined in the IaC repository and applied via provider API; console edits are drift and reverted.

4.3 Record Inventory

As built (2026-07-18):

Record Type Value (actual) State
sbsdash.com A 138.197.222.154 (Dev-Gitlab-server) ⚠ Nonconformant — apex resolves to an uninventoried dev host, not prod (§2.2.2, Build Register item 16)
monitor.sbsdash.com A 162.243.28.132 (monitor-servers) Live and verified — TLS valid, Grafana responding (changelog 2026-07-17)
wiki.sbsdash.com A 167.172.135.88 (sbs-wiki, NYC1) Live; IP changes at NYC2 rebuild
admin.sbsdash.com, clients.sbsdash.com, www Not yet published Pending prod proxy/TLS readiness (Chapter 5)

Target:

Record Type Value TTL Serves
sbsdash.com A 107.170.72.145 300 (build) → 3600 (steady) Primary tenant surface — repointed from 138.197.222.154 at cutover
www.sbsdash.com CNAME sbsdash.com 3600 Redirect to apex at proxy
admin.sbsdash.com A 107.170.72.145 300 → 3600 Administrative console
clients.sbsdash.com A 107.170.72.145 300 → 3600 Client-facing surface
monitor.sbsdash.com A 162.243.28.132 3600 Grafana/Prometheus (IdP-gated)
wiki.sbsdash.com A NYC2 rebuild IP 300 until rebuild, then 3600 BookStack portal

Notes:

4.4 Mail Posture (DNS-03)

sbsdash.com sends and receives no mail. Null-mail posture, published explicitly so the domain cannot be used as a spoofed sender:

Record Type Value
sbsdash.com MX 0 . (null MX, RFC 7505)
sbsdash.com TXT v=spf1 -all
_dmarc.sbsdash.com TXT v=DMARC1; p=reject; adkim=s; aspf=s; rua=mailto:<SBS security alias>

These records are publishable on GoDaddy today — they do not depend on the zone cutover and should not wait for it. DMARC aggregate reports route to an SBS security mailbox on an existing SBS mail domain (not sbsdash.com), making spoof attempts observable — a MON-02 anomaly input, not merely a static record.

One caution: the Grafana admin account and the changelog reference admin@sbsdash.com and a Yahoo mailbox as service identities. No mailbox exists or will exist on sbsdash.com under the null-mail posture; service accounts must use SBS-controlled aliases on SBS's mail domain. Folded into the credential remediation (Build Register item 15).

4.5 DNSSEC (DNS-01)

As built: unsigned — no signing path exists on the current GoDaddy hosting (§4.2). DNS-01 is unmet until zone cutover completes.

Target, unchanged in substance:

4.6 Certificate and Registration Surveillance
4.7 Change Control and Verification

Until zone cutover, GoDaddy console changes are restricted to: the three mail records (§4.4), the CAA record (§4.6), and the apex repoint (§4.3) — each logged in SERVER_CHANGELOG.md with date and operator. After cutover, zone changes follow the IaC path (Chapter 10) with the same drift rules as firewalls.

Verification gates: NS delegation at registrar points to the DNSSEC-capable provider; DNSSEC chain validates (AD flag, DNSViz clean); null-MX/SPF/DMARC present; CAA present; apex and all subdomain records resolve per the §4.3 target table (apex → 107.170.72.145, not 138.197.222.154); no wildcard resolves; no Dev host referenced anywhere in the zone. Controls covered: DNS-01 through DNS-05.


Build Register deltas from this chapter: item 9 restated as GoDaddy→provider cutover with sequence; item 16 apex repoint gated on Chapter 5 readiness; new item 23 — publish null-mail + CAA records on GoDaddy immediately (pre-cutover); item 15 extended to cover the admin@sbsdash.com / Yahoo service-identity cleanup. Chapter 5 (TLS & Edge Security) next — the monitor's live nginx/certbot stack gives it a verified as-built baseline to write against.

TLS & Edge Security

Certificate management, reverse proxy configuration, hardening headers

TLS & Edge Security

Chapter 5 — TLS & Edge Security

5.1 Edge Model

Every public surface is fronted by an nginx reverse proxy terminating TLS on the droplet that serves it. No CDN or external proxy layer is in scope at this fleet size; origin concealment (TLS-04) is addressed in §5.6 within that constraint.

Surface Droplet Proxy → Upstream State
monitor.sbsdash.com monitor-servers nginx 1.28.3 → Grafana 127.0.0.1:3000 (websockets on /api/live/) Live, verified 2026-07-17
wiki.sbsdash.com sbs-wiki nginx → BookStack (localhost) Live; re-verified at NYC2 rebuild
sbsdash.com, www sbsdash-server-prod nginx → Compartment frontend containers (Docker Compose, Chapter 6) Pending — gates the apex repoint (§4.3)
admin.sbsdash.com sbsdash-server-prod nginx → admin console container Pending
clients.sbsdash.com sbsdash-server-prod nginx → client surface container Pending

Host-header routing on sbsdash-server-prod separates the three tenant surfaces behind one IP. A default server block returns 444 (connection close, no response) for any request whose Host/SNI matches no configured surface — direct-to-IP scans and unrecognized hostnames get nothing, including no certificate hint (see §5.2 default cert note).

5.2 Certificate Management (TLS-02)
5.3 Protocol Floor (TLS-01)

Fleet-standard nginx TLS parameters, IaC-templated (Chapter 10), identical on every surface:

5.4 HSTS (TLS-03)
5.5 Security Headers (APP-02) and Frontend Exposure Controls (FE-01…FE-06)

Fleet-standard header set, applied at nginx (add_header ... always;), IaC-templated; per-surface CSP tightening happens at the application layer:

Header Value (baseline) Control
Strict-Transport-Security per §5.4 TLS-03
X-Frame-Options DENY APP-02 (clickjacking)
X-Content-Type-Options nosniff APP-02
Referrer-Policy no-referrer FE-04 — no sbsdash.com URL ever appears in an outbound Referer
Content-Security-Policy default-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self' — tightened per app APP-02 / FE-05 ('self'-only enforces first-party assets: no CDN fonts, no third-party scripts, no analytics beacons)
X-Robots-Tag noindex, nofollow, noarchive, nosnippet FE-01 — anti-indexing at the header layer, covers non-HTML responses too
Permissions-Policy camera=(), microphone=(), geolocation=(), interest-cohort=() APP-02
Cross-Origin-Opener-Policy same-origin APP-02
5.6 Origin Concealment (TLS-04)

Scoped honestly for a no-CDN architecture: the A records necessarily disclose droplet IPs, so concealment here means the origin exposes nothing but the intended service:

5.7 Admin Surface Isolation (APP-03, edge layer)

admin.sbsdash.com receives edge-layer restrictions beyond the tenant surfaces, ahead of the application-layer controls in Chapter 9:

5.8 Verification

Per-surface gate set (LH-SBS-INST-001): testssl.sh clean, protocol floor confirmed (no 1.0/1.1 handshake accepted); cert issued/renewing with expiry probe green; HTTP→HTTPS 301 with ACME path excluded; full header set present (curl -sI diff against template); robots.txt served; direct-IP request returns 444; no OG/Twitter tags in HTML; server_tokens absent; admin surface unreachable from a non-allowlisted IP; upstream ports absent from public ss -tlnp. The monitor surface — already live — is retro-fitted to the full template and re-verified as the reference implementation before the prod surfaces build against it.

Compute & Application Layer

Droplet/container inventory, sizing, per-Compartment isolation mapping to infrastructure resources

Compute & Application Layer

Chapter 6 — Compute & Application Layer

6.1 Compute Model

The Platform runs on a small fixed droplet fleet with containerized workloads under Docker Compose — no Kubernetes, no managed PaaS. Rationale: the fleet is ≤5 hosts with a single-application production profile; Compose delivers the required per-Compartment network isolation (§6.4) with a fraction of the operational surface of an orchestrator, and its state is fully expressible in versioned files (IaC, Chapter 10). Revisit only if the Compartment count or droplet fleet grows materially (Chapter 16 thresholds).

As-built baseline: Docker 29.1.3 + Compose v2.40.3 installed from Ubuntu official repos (no third-party apt sources — consistent with the supply-chain posture, Chapter 11) on sbsdash-server-prod, Dev-SBS-server, and monitor-servers (2026-07-18); docker service enabled at boot. The wiki droplet intentionally has no Docker — BookStack runs as a system service and stays that way until the NYC2 rebuild, where containerization is decided (§6.6).

6.2 Droplet Roles and Sizing
Droplet Size (as built) Workload Sizing assessment
sbsdash-server-prod 8 GB / 4 vCPU / 160 GB nginx edge (Ch. 5), Compartment application containers, data services (interim, §6.5) Adequate for build phase and first ~3 Compartments. Memory is the binding constraint once PostgreSQL + MongoDB + Redis colocate; Chapter 16 sets the resize/split trigger.
Dev-SBS-server 8 GB / 4 vCPU / 160 GB Mirror of prod stack, synthetic data Deliberate 1:1 with prod so Dev verification is load-meaningful.
monitor-servers 4 GB / 2 vCPU / 120 GB Prometheus + Grafana + nginx; bastion (§3.3) Adequate; Prometheus retention sizing in Ch. 12. No application workloads permitted — bastion role keeps this host minimal.
sbs-wiki 2 GB / 1 vCPU / 60 GB BookStack Adequate; carries at NYC2 rebuild.

Rule: the monitor and wiki are single-purpose hosts. Application or data workloads never deploy to them — the monitor because it is the bastion and alerting authority (compromise radius, §3.3), the wiki because it is an operational surface with no tenant data (§1.2).

6.3 Runtime Toolchain

Target toolchain on prod and dev (Build Register items; not yet installed as of 2026-07-18):

Component Purpose Install source Pinning
Node.js (LTS) + pnpm Platform application runtime and package manager NodeSource or nvm per Ch. 11 decision Exact version in .nvmrc / engines; pnpm via corepack, version pinned
Rust + Cargo Performance-critical platform components rustup, pinned toolchain file (rust-toolchain.toml) Exact toolchain per repo
Python 3 Tooling, ETL, operational scripts Ubuntu system python + venvs Per-project requirements.txt hash-pinned
Git Deploy pulls, IaC Ubuntu repo

Two rules carried from §2.4: versions are identical on prod and dev (drift = build defect), and runtimes exist on the host primarily for build/operational tooling — application code executes inside containers, whose images pin their own runtime versions (Ch. 11). Host-level Node/Rust/Python are not a second execution path for platform services.

6.4 Per-Compartment Isolation at the Compute Layer

This section realizes the application/network/data slices of the eight-layer isolation model (§1.2) in Compose terms. Each of the ten Compartments deploys as an isolated stack:

6.5 Data Services Placement (Interim)

Pending the NYC2 Managed Database availability check (§2.3), PostgreSQL, MongoDB, and Redis run as containers on sbsdash-server-prod:

6.6 Wiki and Monitor Stacks
6.7 Deployment Flow

Images are built in CI from the GitHub repository (Ch. 10/11), pushed to the registry enumerated in the supply-chain allowlist, and pulled by prod over the §3.4 egress path. Deploys are docker compose up -d against IaC-rendered files, executed by the deploy user (per-person or CI identity — INF-04 remediation applies, §2.5); manual docker run on prod is prohibited and surfaces as drift (running-container set vs. compose state is a monitored comparison, MON-01). Rollback = redeploy previous pinned image tag; images are immutable and tagged by digest.

6.8 Verification

Gates (LH-SBS-INST-001): Compose project per Compartment present with dedicated network; cross-Compartment connectivity probe fails between any two Compartment networks; frontend reachable only via nginx (direct container port probe fails); no privileged container, no docker.sock mount, no-new-privileges set (docker inspect sweep); data-service ports absent from public interface; resource limits present on every Compartment service; running containers match compose state (drift check); toolchain versions identical prod↔dev (node -v, rustc -V, python3 -V diff).

Storage & Data Layer

Managed databases, object storage, volume encryption, data isolation boundaries

Storage & Data Layer

Chapter 7 — Storage & Data Layer

7.1 Data Layer Overview

Three engines serve the Platform, each with a distinct role; nothing else is introduced without a Change Order:

Engine Role Persistence class
PostgreSQL System of record — Compartment relational data, platform configuration, decision metadata Durable, backed up, point-in-time recovery target
MongoDB Document workloads — agent artifacts, semi-structured Compartment content Durable, backed up
Redis Cache, queues, session and rate-limit state Ephemeral by default; only explicitly declared keyspaces persisted (AOF), never treated as a system of record

As of 2026-07-18 none of the three is installed — this chapter is fully normative, with the §6.5 interim placement (containers on sbsdash-server-prod) as the build path and managed services as the preferred end state.

7.2 Managed vs. Self-Hosted Decision
7.3 Object Storage

DigitalOcean Spaces (S3-compatible), region NYC3 (Spaces is not offered in NYC2; object storage over TLS to a neighboring region is acceptable — unlike database traffic, it is not latency- or VPC-bound):

Bucket Content Access
sbs-dash-artifacts Compartment file artifacts (agent outputs, uploads) Per-Compartment prefix + per-Compartment scoped keys (§7.4)
sbs-dash-audit Hash-chained audit log archives (Ch. 13) Write-once pattern: writer key has PUT-only; no key held by the platform can DELETE (deletion requires the account control plane)
sbs-dash-backups Database dumps + config archives (Ch. 14) Backup identity only; replicated to SFO3 per §2.3

Rules: all buckets private (no public-read ever — FE family applies to storage too); access exclusively via scoped Spaces keys stored in Vault, one keypair per purpose, never a shared account-wide key (INF-02); TLS enforced on every operation; bucket inventory and lifecycle policies defined in IaC (Ch. 10). CDN feature on Spaces stays off — these are private buckets, and enabling the CDN endpoint would create an unauthenticated public URL surface.

7.4 Data Isolation Boundaries (per-Compartment)

The data and storage slices of the eight-layer model (§1.2), stated per engine. The principle throughout: isolation is enforced by the engine's own privilege system, not by application discipline — a compromised Compartment 03 credential must be structurally unable to read Compartment 08 data.

7.5 Encryption
7.6 Operational Rules
7.7 Verification

Gates (LH-SBS-INST-001): cross-Compartment access probe per engine fails (c03 role → c08 database denied in PostgreSQL, MongoDB, Redis ACL, and Spaces prefix); no public listener on any engine port (ss -tlnp + external scan); TLS handshake required on every engine (plaintext connection refused); PUBLIC/default access revoked (engine ACL dump review); dumps encrypted before leaving host (backup pipeline inspection); Dev contains no production data (sampled content check); Spaces buckets private (unauthenticated GET fails); audit bucket writer key cannot DELETE (negative test).

Secrets Management

Vault deployment, secret lifecycle, rotation policy, application integration

Secrets Management

Chapter 8 — Secrets Management

8.1 Current State and Scope

As of 2026-07-18 no secrets manager is deployed. Credential state is the Chapter 2 CRITICAL nonconformance: plaintext passwords in the operational changelog, a shared fleet-wide root password, and service identities on non-SBS mailboxes (Build Register item 15). This chapter defines the target system that item 15 migrates into, covering: platform secrets (API tokens, DB credentials, Spaces keys, internal-CA material, webhook URLs), per-Compartment agent credentials (AI-03), and deploy-time injection into containers (§6.4). Out of scope: end-user passwords (IdP domain, Chapter 9) and TLS private keys for public surfaces (deliberately host-local, §5.2).

8.2 Vault Deployment

HashiCorp Vault (OSS) on a dedicated minimal droplet:

Attribute Value
Droplet prod-vault — 1 GB / 1 vCPU, NYC2, member of prod-vpc-nyc2 (created in the §3.1 rebuild wave)
Storage backend Integrated Raft, single node, on a dedicated DO Volume
Listener VPC-private interface only, TLS from the internal CA (§7.5) — no public interface listener at all; Vault has no DNS name in the public zone
Access paths API from prod/dev droplets over VPC; human access via bastion (§3.3) with individually attributed tokens
Firewall Cloud Firewall prod-vault: TCP 8200 from sbsdash-server-prod, Dev-SBS-server (for dev-namespace reads), and bastion private IPs only; SSH via bastion only

Placement rationale: not on sbsdash-server-prod (the secrets store must survive and be trustworthy independent of the host running the workloads that consume it — a prod-droplet compromise should not equal immediate Vault-storage compromise), and not on monitor-servers (bastion + alerting authority already concentrates enough; adding secrets makes it a single point of total compromise). A 1 GB droplet is sufficient — Vault's footprint at this scale is trivial.

Unseal model: DigitalOcean offers no KMS, so auto-unseal via cloud KMS is unavailable. Shamir key shares: 5 shares, threshold 3, held by named individuals (Ledger Hub platform engineering ×2, Nelson Santos, Alexandra Del Rey, one SBS-designated alternate) — no individual can unseal alone; any 3 can recover. Shares are stored in each holder's personal password manager, never together, never in the changelog or repo. Unseal is required at Vault restart only; restarts are rare, scheduled events. Root token is revoked after initial setup (vault operator generate-root recreates one under quorum if ever needed) — routine administration uses named admin tokens under policy.

Availability posture, stated honestly: single-node Vault means Vault downtime blocks deploys and credential rotation, not running workloads (containers hold their injected credentials until restart, §8.5). Accepted at this fleet size; mitigation is Raft snapshot every 6 h to sbs-dash-backups (encrypted client-side per §7.5), restore-tested quarterly (Ch. 14). HA (3-node Raft) is a Chapter 16 growth trigger, not a Day-1 requirement.

8.3 Namespace and Policy Layout

Vault OSS lacks Enterprise namespaces; the equivalent is mount-path + policy separation, which is sufficient because policies are default-deny:


secret/platform/        # DO API tokens, GitHub deploy keys, Spaces keys,
                        # registrar/DNS API tokens, alerting webhooks
secret/infra/ca/        # internal CA key material (§7.5)
secret/db/platform/     # migration-tool superuser credentials (§7.4)
secret/c01/ … secret/c10/   # per-Compartment: DB role, Redis ACL user,
                            # Spaces prefix keys, Anthropic API key,
                            # per-Compartment integration credentials
secret/dev/…            # dev-environment mirror, synthetic/dev-scoped values only

Policies, one per consumer class: policy-c01 grants read on secret/c01/* and nothing else — the AI-03 guarantee at the secrets layer, matching the network (§6.4) and engine-ACL (§7.4) walls: Compartment 03's runtime identity cannot read secret/c08/* even if every other control fails. policy-deploy reads what the deploy pipeline renders; policy-admin is held by named humans; no policy grants secret/* wildcard. Dev identities can read secret/dev/* only — prod paths are unreachable from Dev (the promotion path carries templates, not values, §2.2.3).

8.4 Secret Lifecycle and Rotation Policy
Class Examples Rotation Mechanism
Static, high-blast-radius DO API token, GitHub deploy key, registrar/DNS API token, Spaces keys 90 days, and immediately on personnel change or suspected exposure Manual rotation runbook (wiki), tracked as recurring Build Register/ops-calendar item; each rotation logged
Database credentials Per-Compartment PostgreSQL / MongoDB / Redis users 30 days target Vault database secrets engine (dynamic short-TTL credentials) once data services are up — Vault creates per-lease users, expiry is automatic, rotation ceases to be a human task. Until then: static creds at 90 days
Anthropic API keys Per-Compartment keys 90 days; immediately on anomalous-usage alert (MON-02 / §3.4 egress signals) Manual via Anthropic console, stored per-Compartment path
Internal CA leaf certs DB TLS (§7.5) 90-day leaf TTL, auto-renewed by IaC Vault PKI engine issues leaves; CA root offline in secret/infra/ca/
Unseal shares On any holder change Rekey ceremony (vault operator rekey, quorum)

Rules: every secret has a named owner and a recorded rotation date (KV metadata); KV v2 versioning on all mounts so rotation keeps the prior version recoverable for rollback; no secret is ever emailed, chat-pasted, or written to the changelog — the changelog records that a rotation happened, never the value. Audit device (file → shipped to Ch. 12 pipeline) logs every read/write with the requesting identity: secret access is itself a MON-01 event stream, and reads outside deploy windows or from unexpected identities are MON-02 anomaly inputs.

8.5 Application Integration

Injection model (carried from §6.4), chosen for minimal moving parts:

  1. AppRole per consumer — one AppRole per Compartment (role-c01 … bound to policy-c01) and one for the deploy pipeline. RoleID is IaC-managed config; SecretID is delivered response-wrapped at deploy time with short TTL and use-limit 1 — a leaked wrapped token is worthless after first use or expiry.
  2. Deploy-time render — the deploy step authenticates via AppRole, reads the Compartment's paths, renders the env-file consumed by docker compose (§6.7), sets mode 0600, and deletes the env-file after container start (compose has already captured the environment). Values never enter images, compose files, the repo, or CI logs (INF-02, INF-03).
  3. Refresh — containers hold credentials until redeploy. With dynamic DB credentials (§8.4), lease TTLs are set ≥ the deploy cadence so normal deploys renew credentials as a side effect; an emergency revocation is: revoke lease in Vault → redeploy Compartment (minutes, and only the affected Compartment restarts — per-Compartment stacks pay off here).
  4. Prohibited patterns — no Vault agent sidecars for now (added complexity before it's needed), no secrets in docker inspect-visible plain environment: keys in committed files, no shared "platform god token" consumed by all Compartments.

Bootstrap note (the classic first-secret problem): the deploy host must hold one credential to start — the RoleID (low sensitivity, useless without SecretID) on disk, and SecretID issuance gated by the CI/operator identity. This is the accepted trust anchor and is documented in the wiki runbook rather than hidden.

8.6 Migration of Existing Credentials (Item 15 Execution Order)
  1. Deploy prod-vault, initialize, distribute shares, revoke root token.
  2. Rotate the four exposed credential sets (GitHub, droplet root password → abolished entirely in favor of keys per §2.5, wiki admin, Grafana admin) — rotate then store; never vault a burned value.
  3. Write rotated values + all new secrets created since (DO tokens, Spaces keys as provisioned) into their §8.3 paths.
  4. Strip the changelog credentials section; purge from any git history (git filter-repo) if the file was ever committed; add pre-commit secret-scanning (gitleaks) to the repo (Ch. 10/11) so re-introduction is blocked mechanically, not procedurally.
  5. Re-point service identities to SBS-controlled aliases (§4.4 note) as each is touched.
8.7 Verification

Gates (LH-SBS-INST-001): Vault reachable from prod over VPC TLS, unreachable from any public interface (external scan); policy probe — role-c03 token reads secret/c03/*, denied on secret/c08/* and secret/platform/*; response-wrapped SecretID single-use confirmed (second unwrap fails); env-file absent post-deploy (filesystem check); no secret string present in repo, images, compose files, or CI logs (gitleaks sweep); audit device shipping to observability pipeline; Raft snapshot present in sbs-dash-backups, encrypted, restore-tested; changelog contains no credential values (item 15 closure evidence); rekey ceremony documented with current holder list.

Identity & Access

IdP integration, SSO, SCIM provisioning, admin access paths, least-privilege model

Identity & Access

Chapter 9 — Identity & Access

9.1 Current State and Scope

As of 2026-07-18 no IdP integration exists: Grafana and BookStack run on local admin accounts (both credentials exposed in the changelog — item 15 applies), the platform application is pre-deployment, and host access is the shared-key nonconformance of §2.5. This chapter defines the target identity architecture covering the IAM control family: IAM-01 (identity-aware proxy), IAM-02 (SSO federation), IAM-03 (phishing-resistant MFA), IAM-04 (tenant separation), IAM-05 (SCIM lifecycle), IAM-06 (access reviews). Human identity only — machine/agent credentials are Chapter 8; per-Compartment application RBAC internals (APP-05) are application-layer and specified in the platform application documentation, but their identity source is defined here.

9.2 Identity Provider

Single source of truth: SBS's corporate IdP. The Platform maintains no local user store — no local passwords on any surface (the two as-built local admins are migrated then reduced to break-glass, §9.6). Users exist on the Platform only as federated identities projected from the SBS directory.

Discovery dependency (blocking): SBS's incumbent IdP product (Entra ID, Okta, Google Workspace, or other) is unconfirmed — it is a named LH-SBS-DISC-002 IT-function question and a gate for this chapter's build items. The architecture below is written IdP-agnostic against capabilities every mainstream enterprise IdP provides: OIDC, SAML 2.0, SCIM 2.0, group claims, WebAuthn/FIDO2 MFA enforcement. Nelson Santos is execution owner for the IdP-side configuration (app registrations, group creation, SCIM token issuance).

Federation protocol: OIDC (Authorization Code + PKCE) everywhere it is supported — Grafana (native OIDC), the platform application (OIDC middleware), the identity-aware proxy (§9.3). SAML only where a component supports nothing better. Token hygiene: short-lived access tokens (≤1 h), refresh handled server-side, email_verified + group claims required in the ID token.

MFA (IAM-03): enforced at the IdP, not per-application — phishing-resistant methods only (WebAuthn/FIDO2 hardware or platform authenticators) for all Platform-mapped groups; TOTP acceptable only as a documented exception with expiry; SMS/voice prohibited. Conditional-access (where the IdP supports it): Platform app registrations require MFA on every authentication, no "remembered device" exceeding 12 h for admin roles.

9.3 Identity-Aware Proxy (IAM-01)

Enforcement point for surfaces that lack robust native OIDC or need pre-application gating. Implementation: oauth2-proxy deployed alongside nginx on the serving droplet, wired via auth_request:

Surface Enforcement
monitor.sbsdash.com Grafana native OIDC (preferred — it maps groups→roles internally, §9.5). oauth2-proxy not required
wiki.sbsdash.com BookStack SAML/OIDC if licensed features suffice; else oauth2-proxy in front — no unauthenticated byte beyond the IdP redirect is served
admin.sbsdash.com oauth2-proxy in addition to application auth — admin surface requires valid IdP session before the application sees the request, layered on the §5.7 edge IP allowlist
sbsdash.com, clients. Application-native OIDC; anonymous access limited to the sign-in redirect itself (FE posture: the public fingerprint is a sign-in page, §5.5)

oauth2-proxy session cookies: __Host- prefix, Secure, HttpOnly, SameSite=Lax, secret from Vault (secret/platform/), session lifetime ≤ 8 h with IdP re-auth. The proxy passes identity to upstreams via signed headers; upstreams reject unsigned/absent identity headers so the proxy cannot be bypassed by direct container access (pairs with the net-edge design, §6.4).

9.4 SCIM Provisioning (IAM-05)

Lifecycle automation — the control that makes offboarding real:

9.5 Least-Privilege Model and Group Mapping (IAM-04 / APP-05 boundary)

Access is group-derived, never user-granted. SBS directory groups map to Platform roles; the group list is the single audit surface for IAM-06:

Directory group (naming) Grants
dash-platform-admin Master Admin console; oauth2-proxy admission to admin.sbsdash.com. Named individuals only, target ≤ 4
dash-c01-userdash-c10-user That Compartment's surfaces and workflows — the human half of IAM-04 tenant separation, mirroring the network/data/secrets walls (§6.4, §7.4, §8.3)
dash-c01-supervisor Receives that Compartment's Medium/High/Critical routings from Supervisor Agents; resolution authority
dash-monitor-view / dash-monitor-admin Grafana Viewer / Admin via group-claim mapping
dash-wiki-edit / dash-wiki-view BookStack roles
dash-exec-dashboard COO/CFO read-only reporting views

Rules: no wildcard "all-Compartments" user group exists — cross-Compartment humans (e.g., the COO) hold the exec-dashboard role, which reads reporting outputs, not Compartment interiors; platform-admin grants administration, and its members' actions are fully logged (MON-01) — admin ≠ unobserved. Group membership changes are IdP-audited events; the quarterly IAM-06 access review walks every dash-* group against current staffing (Alexandra Del Rey coordinates sign-off; Nelson Santos executes directory changes), and the review artifact is filed in the wiki with date and reviewer — access certification (MON-04) consumes the same artifact.

9.6 Admin Access Paths

Consolidated statement of every privileged path and its chain, replacing ad-hoc practice:

Path Chain Controls
Web admin IdP (MFA) → oauth2-proxy → edge IP allowlist (§5.7) → admin.sbsdash.com IAM-01/02/03; all actions app-logged
Host SSH Operator (personal key) → bastion monitor-servers → target droplet Per-person users post item 17; bastion sessions logged; admin allowlist IaC-managed (§3.5)
Vault admin Bastion → named admin token under policy-admin §8.2; every operation in Vault audit device
DO console / registrar / DNS Provider MFA (phishing-resistant), SBS-owned accounts §2.1, §4.1; console use in Prod is break-glass + logged (§2.2.3)
Break-glass Sealed local credentials for Grafana/BookStack/app, stored in Vault + one printed copy in SBS-controlled physical custody Used only on IdP outage; use triggers immediate rotation + incident note (MON-05); tested annually

The break-glass row is the honest residual of the "no local accounts" rule: total IdP outage must not equal total platform lockout, so exactly one dormant local admin per critical surface survives — vaulted, alarmed on use, rotated after.

9.7 Verification

Gates (LH-SBS-INST-001): unauthenticated request to every gated surface yields IdP redirect, zero application bytes (curl sweep); direct-to-container bypass attempt rejected (unsigned identity header); MFA challenge presented on fresh session for admin group (manual gate); SCIM lifecycle test — provision/move/deactivate with deactivation lockout ≤ 15 min (timed); group-mapping probe — dash-c03-user reaches c03 surfaces, denied on c08 (IAM-04 human-layer test mirroring §7.7); local password login disabled on Grafana/BookStack (negative login test); break-glass procedure executed in drill, rotation confirmed; IAM-06 review artifact exists with current date.

Infrastructure as Code

IaC tooling, repository structure, change workflow, drift detection

Infrastructure as Code

Chapter 10 — Infrastructure as Code

10.1 Current State and Scope

As of 2026-07-18 no IaC exists: all resources to date (droplets, DNS records, ufw rules, Grafana/Prometheus configs) were created by console or shell, documented after the fact in SERVER_CHANGELOG.md. That changelog discipline is the right instinct executed at the wrong layer — this chapter moves the source of truth from prose describing what was done to code that does it, with the changelog demoted to narrative context. Scope: DigitalOcean resources, DNS zone (post-cutover, §4.2), host configuration, firewall rulesets, Vault policy/mount layout, compose templates, and monitoring config. Out of scope: application source code (its CI/CD is platform-repo concern; supply-chain controls shared with Chapter 11).

10.2 Tooling

Two tools, sharply divided by responsibility — resisting the single-tool temptation because each is poor at the other's job:

Layer Tool Manages
Provisioning (things that exist) OpenTofu (Terraform-compatible, MPL-licensed — no BSL exposure) with the digitalocean provider Droplets, VPCs, Cloud Firewalls, Volumes, Spaces buckets + keys, DO project membership, DNS records via the post-cutover provider API (§4.2), reserved IPs
Configuration (state of things that exist) Ansible OS baseline (users/keys post-item-17, sshd config, unattended-upgrades), nftables rulesets rendered from templates (§3.6), nginx server blocks + TLS params (§5.3), node_exporter binds, Vault server config, compose file rendering + deploy (§6.7), internal-CA leaf distribution (§7.5)

Supporting: gitleaks (pre-commit + CI, §8.6), promtool/nginx -t/vault policy fmt as validation steps in CI, Infracost not adopted (fleet too small to justify).

State backend: OpenTofu state in the sbs-dash-backups Spaces bucket (S3-compatible backend) with state locking via DO Spaces conditional writes; state contains secrets-adjacent values (IPs, IDs — not credentials, which stay in Vault per §8.5) and the bucket is private + encrypted (§7.3). Ansible is stateless by design; its "state" is the repo.

10.3 Repository Structure

One infrastructure repository (sbs-dash-infra) in the GitHub organization — separate from application code so infra reviewers and app reviewers are distinct CODEOWNERS populations:


sbs-dash-infra/
├── tofu/
│   ├── environments/
│   │   ├── prod/          # prod VPC, droplets, firewalls, volumes
│   │   └── dev/           # dev VPC, droplet
│   ├── modules/
│   │   ├── droplet/       # standard droplet: VPC member, tags, monitoring
│   │   ├── firewall/      # tag-scoped Cloud Firewall pattern (§3.5)
│   │   └── spaces/        # bucket + scoped key pattern (§7.3)
│   ├── dns/               # zone records post-cutover (§4.3 target table)
│   └── globals/           # admin SSH allowlist, region, naming — the named
│                          #   variables referenced throughout this spec
├── ansible/
│   ├── inventory/         # generated FROM tofu output — single source for IPs
│   ├── roles/
│   │   ├── base/          # users, sshd, patching (§2.5 target, INF-05)
│   │   ├── nftables/      # §3.6 templates incl. per-Compartment chains
│   │   ├── nginx-edge/    # §5.3 TLS params + §5.5 header template
│   │   ├── monitoring/    # prometheus jobs, exporter binds, dashboards
│   │   ├── vault/         # §8.2 server config
│   │   └── compose/       # §6.4 Compartment template renderer
│   └── playbooks/
├── vault/policies/        # §8.3 policy HCL, applied via CI
├── .gitleaks.toml
└── CODEOWNERS

Repository controls (INF-03, shared baseline with Ch. 11): default branch protected — PR required, ≥1 review from CODEOWNERS, no force-push, no direct commit, signed commits required, CI green as merge condition; admin bypass disabled; the GitHub org and repo live under SBS ownership with Ledger Hub engineers as members (same revocability principle as §2.1).

10.4 Change Workflow

Every infrastructure change follows one path — the §2.2.3 promotion rule made concrete:

  1. Branch + PR against sbs-dash-infra. PR description states intent and links the Build Register item or Change Order where applicable (LH-SBS-CO- series for scope-level changes; routine parameter changes need only the PR).
  2. CI validation on PR: tofu validate + tofu plan (both environments, plan output posted to the PR), ansible-lint, template syntax checks (nginx -t in container, promtool check config), gitleaks sweep. A plan showing destruction of stateful resources (Volumes, databases, Spaces buckets) requires an explicit destructive-change label + second reviewer.
  3. Review by CODEOWNERS — for firewall, Vault-policy, or IAM-adjacent paths, the reviewer set includes the security owner (Alexandra Del Rey routing).
  4. Apply to Dev on merge: CI runs tofu apply (dev workspace) + Ansible against dev inventory automatically.
  5. Apply to Prod as a manually approved CI job (GitHub environment protection, named approvers) — never from an operator laptop. The CI identity authenticates to Vault via its own AppRole (§8.5) for any secret-touching render; the DO token it uses is the pipeline-scoped token of §2.1.
  6. Emergency path: break-glass console/shell changes are permitted to restore service (MON-05 incident context), and must be back-ported to the repo within 24 h — the next drift run (§10.5) enforces this mechanically by flagging anything that wasn't.
10.5 Drift Detection

Drift — divergence between repo-declared state and live state — is the failure mode this whole chapter exists to prevent, and every preceding chapter's "console edits are drift and reverted" clauses land here as mechanism:

10.6 Bootstrap and Migration of As-Built Resources

The existing fleet was built by hand; it is imported, not rebuilt, except where the §3.1 rebuild wave already mandates recreation:

  1. Stand up repo, branch protection, CI skeleton, gitleaks (already Build Register item 47).
  2. tofu import current resources: 4–5 droplets, existing ufw-era firewall facts recorded as-is, Spaces buckets when created. Import produces an honest baseline whose first plan output is the formal nonconformance list — the delta between as-built and this specification, machine-generated.
  3. Author target state per this spec (dedicated VPCs, Cloud Firewalls, prod-vault droplet); the §3.1 rebuild wave then executes as reviewed PRs — the rebuild becomes the first real exercise of the §10.4 workflow rather than another manual event.
  4. Ansible baseline role applied fleet-wide, which is the execution vehicle for item 17 (per-person users, sshd hardening) and item 20 (host firewalls).
  5. SERVER_CHANGELOG.md thereafter records narrative/context only; "what exists" questions are answered by the repo and its state, and the changelog's §1.3 role is updated accordingly.
10.7 Verification

Gates (LH-SBS-INST-001): repo exists under SBS org with branch protection verified (settings API dump); direct push to default branch rejected (negative test); PR without CI green cannot merge; tofu plan clean on both environments (zero drift at gate time); nightly drift jobs scheduled and alerting path tested (induced drift: manual test-tag change → alert received → reverted); Prod apply requires named approval (job history evidence); emergency-change backport rule exercised in drill; gitleaks blocks a seeded dummy secret in pre-commit and CI; inventory generated from tofu output matches live fleet.

Supply-Chain Controls

Image provenance, dependency pinning, artifact signing, registry policy

Supply-Chain Controls

Chapter 11 — Supply-Chain Controls

11.1 Current State and Scope

As-built supply-chain posture is minimal but deliberately clean so far: everything installed to date (Docker, Compose, Prometheus, nginx, certbot) came from Ubuntu official repos or the official Grafana apt repo — no curl-pipe-bash installs, no third-party PPAs, no unpinned container images yet, because no application containers exist yet. This chapter sets the controls before the application build starts pulling npm/crates/PyPI dependencies and publishing images — retrofitting provenance onto an already-polluted dependency tree is an order of magnitude harder than starting gated. Scope: container images, OS packages, language dependencies (Node/pnpm, Rust/Cargo, Python), the artifact registry, and the build pipeline that connects them. Controlling SEC-001 requirement: INF-03 (repository controls) plus the supply-chain family imported into KO-001; AI-02 intersects where MCP tool containers are concerned (§6.4).

11.2 Registry Policy

GitHub Container Registry (GHCR) under the SBS GitHub organization is the sole artifact registry:

11.3 Image Provenance
11.4 Dependency Pinning

Per-ecosystem, all enforced in CI (a lockfile that isn't verified is documentation, not control):

Ecosystem Pinning mechanism CI enforcement
Node / pnpm pnpm-lock.yaml committed; packageManager field + corepack pins pnpm itself pnpm install --frozen-lockfile — any drift fails the build; pnpm audit report attached
Rust / Cargo Cargo.lock committed (binaries); rust-toolchain.toml pins the compiler cargo build --locked; cargo audit (RustSec)
Python requirements.txt with --hash=sha256:… per package (pip-compile generated) pip install --require-hashes; pip-audit
OS packages Versions asserted in the Ansible base role for security-relevant packages Drift surfaces in the nightly --check run (§10.5)

Dependency updates are PRs like any other change (Renovate bot, grouped weekly, auto-PR but never auto-merge) — the update path goes through the same review + CI gauntlet, so a poisoned upstream release sits in a diff a human looks at, not in a silent nightly pull. New top-level dependencies require a one-line justification in the PR body; transitive bloat is reviewed via the lockfile diff.

11.5 Artifact Signing and Verification
11.6 Pipeline Integrity and Scanning

The build pipeline is itself supply chain:

11.7 Verification

Gates (LH-SBS-INST-001): base-image allowlist file exists, all Dockerfiles resolve FROM digests within it (lint); :latest absent from every compose/Dockerfile (grep gate); frozen-lockfile builds pass and a deliberately drifted lockfile fails (negative test); cosign verify passes on a pipeline image and fails on a manually-pushed unsigned test image (negative test); deploy refuses the unsigned image end-to-end; SBOM attestation present for every deployed digest; Trivy CI gate demonstrated on a seeded-CVE test image; language registries unreachable from prod runtime (egress probe, §3.4); Actions digests pinned (workflow lint); prod running-set digests ⊆ registry signed-set (drift cross-check with §10.5).

Observability

Monitoring stack, logging pipeline, alert routing, dashboards, health checks

Observability

Chapter 12 — Observability

12.1 Current State and Scope

Prometheus + Grafana 13.1.0 + nginx/TLS on monitor-servers, serving monitor.sbsdash.com; node_exporter on all four droplets (prod/dev private-bind at 10.100.0.3/.2:9100, wiki public-bind with ufw scope — the §3.3 interim, monitor local at 127.0.0.1); scrape job droplets with per-server labels; dashboards "Node Exporter Full" (ID 1860) and custom "SBS Servers Overview" (uid sbs-overview, provisioned as home dashboard); 30-second kiosk playlist. Verified 2026-07-17: 5 targets up, promtool clean, Grafana healthy.

What exists is metrics + visualization. This chapter completes the design: logging pipeline (absent), alerting (absent — dashboards without alerts is observability that only works when someone is looking), blackbox/health probes (absent), and the MON-family feeds every prior chapter has been promising (MON-01 unified audit trail inputs, MON-02 anomaly detection). Grafana local-admin → OIDC migration is already item 52 (Ch. 9); Prometheus job additions below fold into the Ansible monitoring role (§10.3) rather than remaining hand-edits.

12.2 Metrics (Prometheus)

Retained as deployed — apt-installed system service on monitor-servers, 127.0.0.1-bound behind nginx (§6.6 rationale: don't containerize the watcher). Extensions, all IaC-managed:

Job Targets Purpose
droplets (live) node_exporter ×4 (+prod-vault, +rebuilt wiki private bind at rebuild wave) Host CPU/RAM/disk/net
blackbox-external (new) Probes of all five public URLs (§4.3) from the monitor via blackbox_exporter Surface up/down, TLS validity, cert expiry (§5.2 feed), HTTP status, redirect correctness
nginx (new) stub_status per edge droplet, private bind Request rates, connections — MON-02 baseline input
containers (new, at Compartment build) cAdvisor on prod/dev, private bind Per-Compartment container CPU/RAM — §6.4 resource-limit pressure visibility
integrity (new) textfile-collector metrics: nftables ruleset hash, compose-vs-running match, backup age, drift-job status §10.5 fast-tamper indicators; §14 backup freshness
vault (new, at Ch. 8 build) Vault telemetry endpoint, VPC-private Seal status, token/lease counts, audit-device health
postgres/mongo/redis exporters (new, at Ch. 7 build) Private binds per engine Engine health, connections, replication/persistence state

Retention: 30 days local TSDB (sized fine on 120 GB); longer-horizon capacity trending is served by the 30-day window plus quarterly-review screenshots — remote-write long-term storage is a Chapter 16 growth item, not Day-1. Scrape interval stays 15 s. Every new exporter binds VPC-private or localhost — the §3.7 public-listener gate applies to observability itself.

12.3 Logging Pipeline

Grafana Loki + promtail/alloy agents — chosen over an ELK stack deliberately: one vendor surface with Grafana (logs and metrics correlate in the same UI), index-light architecture that fits a 4 GB monitor droplet, and LogQL's label model matches the Prometheus labels already in place.

Stream Source Notes
System journald sshd (bastion session evidence, §3.3/§9.6), sudo, unattended-upgrades
Edge nginx access/error, structured format (§5.7) All five surfaces; access logs are the MON-02 baseline for request anomalies
Auth oauth2-proxy, Grafana, BookStack auth events IAM-01/02 evidence; failed-auth series feeds §12.4 alerts
Vault audit device (§8.4) Secret-access event stream — MON-01's highest-value feed
Containers Docker json-file via agent, per-Compartment labels Compartment-scoped log isolation: Grafana folder permissions (§9.5 group mapping) keep c03 logs invisible to c08 personnel — the log layer of IAM-04
Egress denials nftables log target (§3.4) AI-02/LLM01 exfiltration-attempt indicator
Drift/CI §10.5 job outputs Codified-change audit trail
12.4 Alert Routing

Alertmanager on monitor-servers (the changelog's own "pendiente" item, now normative), 127.0.0.1-bound:

12.5 Dashboards

Provisioned-by-file discipline (as already practiced — sbs-overview and 1860 are file-provisioned) becomes the rule: no console-authored production dashboards; Grafana UI is for exploration, dashboards graduate to the repo. Dashboard set:

Dashboard Audience (via §9.5 groups) Content
SBS Servers Overview (live) dash-monitor-view Fleet UP/DOWN, CPU/RAM/disk bars, history, uptime — home dashboard, kiosk playlist retained
Node Exporter Full (live) dash-monitor-view Per-host deep dive
Edge & Surfaces (new) monitor-view Blackbox probe status, cert days-remaining, request rates, 4xx/5xx, auth failures
Compartments (new, at build) monitor-view; per-Compartment panels visible per group cAdvisor per-Compartment resources, container restarts, egress denials by Compartment
Data Services (new, at Ch. 7 build) monitor-admin Engine health, connections, volume capacity, backup age
Security & Integrity (new) monitor-admin + security routing Vault seal/audit status, drift jobs, ruleset hashes, gitleaks/Trivy findings, bastion session count
12.6 Monitoring the Monitor

The stack's structural blind spot, addressed three ways: the dead-man's switch (§12.4); an external uptime check on https://monitor.sbsdash.com from outside DO (UptimeRobot free tier or equivalent — the one approved external service, alerting to the same email path); and the monitor droplet's own metrics riding the same droplets job with its alerts routed identically. Loki/Alertmanager/blackbox process health via systemd unit-state metrics on the integrity job. The bastion role (§3.3) already makes this host priority-patched; observability adds priority-watched.

12.7 Verification

Gates (LH-SBS-INST-001): all §12.2 jobs green with private binds confirmed (ss -tlnp sweep per droplet); blackbox probes cover all five URLs incl. cert-expiry metric present; induced-failure drill — stop a service, alert received on both channels inside SLA, inhibition correct; dead-man silence drill — stop Alertmanager, heartbeat receiver fires; Loki streams present per table incl. Vault audit and egress denials (LogQL sample per stream); Compartment log visibility probe — c03 group cannot read c08 stream (mirrors §7.7/§9.7); dashboard set file-provisioned, console-save on prod folders denied; alert rules pass promtool in CI, console-edit reverted by drift run; external uptime check firing path tested; retention conf 30 d TSDB / 90 d Loki confirmed; wiki agent path migrates to VPC at rebuild (carried gate).

Audit Infrastructure

Hash-chained audit log architecture, retention, integrity verification

Audit Infrastructure

Chapter 13 — Audit Infrastructure

13.1 Purpose and Distinction from Operational Logging

This chapter specifies the evidentiary tier: the tamper-evident, hash-chained audit record backing the Platform's compliance claims — every AI Agent decision tagged by Risk Tier, admissible for FCC and SOX review, retained seven years minimum (per the engagement's audit-log commitments). It is deliberately separate from Loki (§12.3), and the separation is the design:

Property Loki (operational) Audit tier (this chapter)
Purpose Search, debugging, alerting Evidence, attestation, regulatory review
Retention 90 days 7 years minimum
Integrity Best-effort (filesystem) Cryptographic hash chain, externally anchored
Mutability Deletable by admins Append-only; no platform-held credential can delete (§7.3)
Query LogQL, fast Sequential verification + indexed retrieval, slow is acceptable

As of 2026-07-18 nothing is built; nothing blocks it either — the storage substrate (sbs-dash-audit bucket, §7.3) and the event sources (§13.3) are already specified. This is the audit layer of the eight-layer Compartment isolation model (§1.2) and the infrastructure realization of MON-01 (unified audit trail), consumed by Guardian Agents as writers and by SBS/regulators as readers.

13.2 Hash-Chain Architecture

Design: per-Compartment append-only event chains, batched into signed, anchored segments.

13.3 Write Path and Event Sources
13.4 Retention and Lifecycle
13.5 Integrity Verification

Verification is continuous and layered — an integrity system that is only checked when someone subpoenas it is not an integrity system:

13.6 Verification (Build Gates)

Gates (LH-SBS-INST-001): writer reachable only on net-audit/VPC (public + cross-Compartment probe fails); Guardian test event → chained entry with correct prev_hash linkage (recompute check); segment close → bucket object + cosign signature + Rekor inclusion proof all present; PUT-only posture proven (writer credential DELETE attempt fails, §7.7 carried gate); induced-tamper drill — modify one byte in a staged segment copy, continuous verifier alerts within its cycle; sequence-gap drill — drop an event in staging, gap detected and alerted; independent verifier CLI produces PASS on clean export and FAIL on the tampered copy, run from a machine with zero platform access; queue-age alert fires in writer-outage drill; anchor email arriving at SBS alias confirmed; 7-year lifecycle rules + hold flag present in IaC with no automated deletion path (code review gate).

Backup & Disaster Recovery

Backup schedule, RPO/RTO targets, restore procedures, DR test cadence

Backup & Disaster Recovery

Chapter 14 — Backup & Disaster Recovery

14.1 Current State and Scope

As of 2026-07-18, tier 1 (DigitalOcean Backups) is enabled on the droplet fleet — the first backup layer in production. Tiers 2 and 3 remain unbuilt: no database dumps (no databases yet), no repo mirrors, no Vault snapshots (no Vault yet), no offline custody artifacts. Single-droplet loss is now recoverable; application-consistent recovery, region loss, and account compromise are not yet covered. Scope of this chapter: everything required to reconstruct the Platform (data, configuration, state, secrets, evidence); the recovery procedures per failure class; and the test cadence that keeps "we have backups" meaning "we can restore." Chapter 13's audit anchors are the integrity counterpart; this chapter is survivability. Controlling family: the backup/DR control set imported into KO-001.

14.2 Backup Architecture — Three Tiers

The design principle: IaC-first recovery — most of the platform is rebuilt from code, not restored from images. Three complementary tiers, each covering a failure class the others cannot:

Tier Mechanism State Covers Cannot cover
1 — Disk images DigitalOcean Backups (native) Live Fast single-droplet restore Application consistency (crash-consistent only); account compromise (backups are not downloadable and live inside the DO control plane)
2 — Application artifacts Encrypted dumps + state archives to Spaces, replicated NYC3→SFO3 Unbuilt Application-consistent data recovery; region loss Hostile DO account — see tier 3
3 — Off-account survivability Repo mirrors, offline age identity (SBS custody), Vault Shamir shares, audit anchors (Rekor + SBS email) Unbuilt Total account/custody compromise (§14.6) — (this is the floor)
14.2.1 Tier 1 — DigitalOcean Backups (Live)

Enabled on the fleet as of 2026-07-18. Target configuration:

Droplet Frequency (target) Retention (target)
sbsdash-server-prod Daily 7 days
prod-vault (at creation) Daily 7 days
monitor-servers Daily 7 days
sbs-wiki (carries through NYC2 rebuild) Daily 7 days
Dev-SBS-server Weekly 4 weeks

Open verification items against the live enablement (Build Register item 96 residual):

Ongoing tier-1 mechanics: monthly, one Prod backup per droplet is converted to a snapshot and retained on a 12-month rolling basis (backups convert to snapshots for indefinite storage — the medium-term image tier). Restore paths are in-place restore or new-droplet-from-backup; creation runs roughly 2 min/GB used, keeping backup windows in minutes at our footprint.

Position in the design, stated plainly: tier 1 is the speed layer for single-droplet loss only. Disk images are crash-consistent — a mid-write PostgreSQL image can restore dirty, so databases recover from tier 2; and an image restores whatever existed at image time, drift and compromise included, so the IaC rebuild (§10) remains the clean-room path.

14.2.2 Tier 2 — Application Artifacts (What Code Cannot Regenerate)
Asset Reconstruction source Backup artifact
Droplets (OS + services) Rebuilt — tofu + Ansible (§10); tier-1 image as fast path
Infra + app repos, IaC state GitHub (off-platform) + tofu state in Spaces (§10.2) Nightly git bundle mirror of both repos to sbs-dash-backups — GitHub org loss is inside the threat model (§2.1 revocability cuts both ways)
PostgreSQL pg_dump per-database (per-Compartment, §7.4) nightly + WAL archiving continuous once the PITR trigger fires (§14.4)
MongoDB mongodump per-database nightly
Redis Declared AOF keyspaces only (§7.1) AOF-covered; nightly RDB copy off-host. Cache loss accepted by design
Vault Raft snapshot every 6 h (§8.2) — the single most critical artifact; losing Vault + its snapshots = re-keying the platform
Audit segments Already in sbs-dash-audit Cross-region replication NYC3→SFO3 (§13.4); anchors survive dual-bucket loss
Grafana dashboards, alert rules, nginx, compose, nftables Repo (file-provisioned, §12.5/§10.3) Covered by repo mirror — this is why §12.5 bans console-authored dashboards
TLS keys Not backed up — reissued via ACME (§5.2) Deliberate: reissuance ≤ minutes; escrow risk > loss cost
BookStack (wiki) Nightly DB dump + uploads archive (runbooks are operational capital)

All dumps are encrypted client-side before upload (age; key in Vault, with the age identity also held offline in SBS custody per the §9.6 break-glass pattern — an encrypted backup whose only key lives in the Vault it must survive is a circular dependency, broken here) and land in sbs-dash-backups (NYC3) with lifecycle replication to SFO3.

14.3 Schedule and Retention
Artifact Frequency Retention
DO Backups (disk images) — live Daily Prod / weekly Dev (target; audit pending), DO-scheduled 7 d / 4 w native; 12 monthly via snapshot conversion
DB dumps (PG, Mongo, BookStack) Nightly 03:00 ET 14 nightly, 8 weekly, 12 monthly (GFS)
WAL archive (when enabled) Continuous 14 days
Vault Raft snapshot 6-hourly 28 snapshots (7 d) + 12 monthly
Repo mirrors Nightly 30 days
Audit replication On segment close (§13.2) 7 y (§13.4)
Redis RDB Nightly 7 days

Every tier-2 job emits completion + size + age metrics to the integrity job (§12.2); tier-1 backup age is polled from the DO API per droplet and exported to the same job — now actionable immediately and prioritized, since it is the only automated proof the live backups are actually completing. Any backup age > 26 h (tier-1 daily set or tier-2 nightly set) is critical (§12.4) — a silently failing backup is the canonical DR failure and is alerted, not discovered.

14.4 RPO / RTO Targets

Per failure class — one blended number would be fiction:

Failure class RPO RTO Recovery path
Single service/container failure 0 ≤ 15 min Compose restart / redeploy pinned digest (§6.7)
Single droplet loss (non-data) 0 (stateless) ≤ 30 min (tier-1 image restore) / ≤ 2 h (IaC clean-room rebuild) Image restore when the failure is mechanical; IaC rebuild when state is suspect
Prod app droplet loss (interim data services, §6.5) ≤ 24 h (nightly dumps); ≤ 5 min once WAL PITR enabled ≤ 4 h Image-restore or rebuild droplet, restore tier-2 dumps, re-assert ACLs (§7.4), redeploy
Vault droplet loss ≤ 6 h ≤ 2 h New droplet (IaC or image) + Raft snapshot restore + quorum unseal (§8.2) — RTO includes assembling 3 shareholders; that human dependency is the honest bottleneck
Monitor/bastion loss 0 config / 30 d metrics history accepted lost ≤ 4 h Image restore or IaC rebuild; interim SSH path: temporary direct-SSH firewall rule to prod via emergency PR (§10.4), removed at bastion restoration
Wiki loss ≤ 24 h ≤ 4 h Image restore or IaC rebuild + dump restore
Region loss (NYC2) ≤ 24 h (SFO3 replicas; tier-1 images are region-bound and do not participate) ≤ 3 business days Full IaC re-deploy to alternate region (region is a tofu variable, §10.3 globals); DNS repoint (§4.3); restore from SFO3. Not hot-standby — stated cost decision at this scale, revisited at Ch. 16 triggers
Account compromise (DO or GitHub org) Bounded by tier-3 artifacts Days, incident-governed §14.6 — tier 1 and in-account tier 2 presumed lost

Until tier 2 exists, the honest fleet-wide posture is: RPO for any future data = undefined; recoverability = single-droplet mechanical failure only. This line is deleted from the specification when items 86–92 close — it is written here so the gap is a stated fact, not an implication. Database RPO ≤ 24 h is the accepted Day-1 posture once dumps run; the WAL-archiving upgrade to ≤ 5 min is triggered when the first Compartment carries production tenant data — not before (cost honesty), mandatorily not after.

14.5 Restore Procedures

Each is a wiki runbook (§1.4 publication target) with exact commands and a verification tail; summarized:

  1. Single-droplet fast path (tier 1 — available today): restore image or create-from-backup → verify drift (tofu plan + Ansible --check clean against repo state once §10 exists; until then, manual diff against SERVER_CHANGELOG.md — the interim reality, stated) → rejoin monitoring → smoke test.
  2. Database restore (tier 2): provision clean engine (IaC) → decrypt dump (age key from Vault, or offline custody copy if Vault is the casualty) → restore per-database → re-assert per-Compartment roles/ACLs from IaC before any application start (a restored database with default grants is an isolation breach, §7.4) → row-count comparison vs. manifest → smoke test.
  3. Vault restore: IaC/image droplet → raft snapshot restore → quorum unseal → audit-device continuity check → rotate any credential issued after snapshot timestamp (derivable from the Vault audit stream).
  4. Full-region DR: run-order: VPCs → Vault (secrets first — everything authenticates through it) → data services + restores → app droplets + deploys → monitor → DNS cutover (TTLs drop to 300 at DR declaration) → §12.7-style verification sweep before declaring recovery.
  5. Restore-time isolation rule: Compartment agent workloads stopped during recovery — agents never run against partially restored data (Guardian policy state could lag data state); restart is gated on the verification tail and is itself an audit-chain event (§13.3).
14.6 Worst-Case: Account/Custody Compromise

The survivability floor. If the DO account and GitHub org are both lost or hostile: tier 1 is gone by definition (backups are not downloadable and live inside the account); in-account tier 2 is presumed gone. Reconstruction requires exactly the tier-3 set — repo mirrors (SFO3 replicas), encrypted dumps + offline age identity (SBS custody), Vault snapshot + Shamir shares (5 named holders, §8.2), and audit anchors (Rekor + SBS email, §13.2). Each lives outside the compromised trust domain by design. The runbook is the incident-response DR annex (MON-05 linkage); the §14.7 annual tabletop rehearses this scenario specifically.

14.7 DR Test Cadence

Untested backups are hypotheses. Each test produces a dated wiki report (MON-04/quarterly-review evidence):

Test Cadence Pass criterion
Tier-1 restore drill — first execution scheduled now (the layer is live; prove it): restore latest backup of a non-critical droplet (wiki or dev) to a new droplet, boot, inspect, destroy First: immediate. Then quarterly within the rotation below Boots, services start, data present as of backup time
Automated restore-verify: latest PG/Mongo dump into throwaway Dev container, row counts + ACL assertions Weekly, automated (CI), from tier-2 build Green integrity metric; failure = critical
Single-droplet recovery drill, alternating quarterly between tier-1 image restore and IaC clean-room rebuild Quarterly RTO ≤ target; post-restore drift check clean
Vault snapshot restore into isolated instance (test shares, no prod unseal) Quarterly Unseal + read probe passes
Full-region tabletop + partial technical (Vault+DB restore into SFO3) Annually Runbook gaps filed as Build Register items
Account-compromise tabletop (§14.6) Annually Custody chain confirmed current (share holders, offline key, alias access)
14.8 Verification (Build Gates)

Gates (LH-SBS-INST-001): DO Backups confirmed active on all five droplets with plan/frequency audit closed and settings codified in tofu (item 96 residual); tier-1 backup-age visible via DO API and exported to integrity per droplet; first tier-1 restore drill passed; every §14.2.2 artifact present in sbs-dash-backups with age metric green; SFO3 replication object-count parity; dump decrypts with the offline-custody key copy (circular-dependency break proven, not just the Vault copy); weekly restore-verify green ≥ 2 consecutive cycles pre-Cutover; Vault snapshot restore drill passed; backup-age alert fires in induced-failure drill; monthly snapshot-conversion automation present in IaC; restore runbooks on wiki with last-tested dates; WAL-PITR trigger condition documented; region variable proven by clean tofu plan targeting SFO3; Dev-Gitlab-server pre-repoint backup taken.

Capacity & Scaling

Baseline sizing, growth thresholds, scaling procedures

Capacity & Scaling

Chapter 15 — Capacity & Scaling

15.1 Philosophy and Scope

Capacity planning at this fleet size is not autoscaling — it is knowing the numbers, naming the thresholds, and pre-writing the procedures so scaling is a scheduled maintenance action rather than an incident response. The Platform's load profile helps: Compartment agent workloads are queue-driven and tolerate minutes of latency (a traffic-log reconciliation that waits 5 minutes is not an incident), so the design scales for throughput over time, not burst absorption. The only latency-sensitive paths are the human web surfaces and the audit write path. Scope: droplet sizing, per-Compartment resource budgets, storage growth, the thresholds that trigger action, and the procedures per scaling event. Growth triggers named in earlier chapters (Vault HA §8.2, remote-write metrics §12.2, hot-standby DR §14.4, private DNS §3.3) consolidate here.

15.2 Baseline Sizing and Budgets

As-built fleet totals: 22 GB RAM / 11 vCPU / 500 GB disk across five droplets (§2.2.1, incl. prod-vault at creation). Per-droplet budgets:

sbsdash-server-prod (8 GB / 4 vCPU / 160 GB) — the contended host. Budget under the §6.5 interim (data services colocated):

Allocation RAM Notes
OS + Docker + nginx + agents 1.0 GB  
PostgreSQL 1.5 GB shared_buffers 512 MB, capped connections via per-Compartment pool
MongoDB 1.0 GB WiredTiger cache capped 512 MB
Redis 0.5 GB maxmemory 384 MB, allkeys-lru on cache keyspaces
Audit writer + buffer 0.5 GB  
Compartment containers 3.5 GB ≈ 1.0–1.2 GB per Compartment stack (app + MCP sidecar) at §6.4 limits
Headroom ~0.5 GB Below 1 GB free is itself a warning signal

Consequence, stated plainly: the 8 GB prod droplet supports ~3 concurrent Compartment stacks in the interim topology. That matches the engagement's minimum-Compartment starting footprint, but full ten-Compartment operation on this host requires either the data-service offload (Managed DBs, §7.2) or the resize/split ladder (§15.4) — this is a designed checkpoint, not a surprise.

Other hosts: monitor-servers (4 GB) — Prometheus 30 d TSDB ≈ 2–6 GB disk at current cardinality, Loki 90 d on its Volume; RAM adequate until Loki ingest grows with Compartment count (threshold below). prod-vault (1 GB) — trivial load, no growth coupling. sbs-wiki (2 GB) — static. Dev-SBS-server — mirrors prod budgets by design (§2.4).

15.3 Growth Thresholds (Named Triggers → Named Actions)

Thresholds are Prometheus alert rules (§12.4 warning tier unless noted), each mapped to a §15.4 procedure — a threshold without a pre-decided action is just anxiety:

Signal Threshold Action
Prod RAM sustained (15 min) ≥ 85 % P1: resize droplet
Prod free RAM < 1 GB at Compartment deploy time Deploy blocked; P1 or P2 first
Compartment count 4th Compartment Workorder received P2: data-service offload decision forced (Managed DB check, item 36, must be closed by now)
Per-Compartment container at its §6.4 limit throttling/OOM-kill events > 0 Raise that Compartment's budget via PR (visible, reviewed) — never silently unlimit
Any disk ≥ 80 % / ≥ 90 % warning: P3 volume grow / critical: immediate P3
PG connections ≥ 80 % of max Pool tuning, then P2
Loki ingest > 5 GB/day sustained P4: monitor resize or Loki retention drop to 60 d (decision, not drift)
Prometheus TSDB > 40 GB or scrape latency alerts P4 + revisit remote-write (§12.2 deferred item)
Audit writer queue age > 5 min sustained under normal ops (distinct from §13.3 outage alert) P5: writer I/O tuning / dedicated Volume IOPS
Fleet size > 10 droplets Private DNS zone (§3.3 trigger); Vault HA evaluation (§8.2)
Any single droplet > 16 GB resize on the table P6: split evaluation instead — vertical scaling stops being the answer
15.4 Scaling Procedures

All procedures are IaC changes (§10.4 workflow) executed in maintenance windows; none is novel at execution time because each is pre-written as a wiki runbook:

15.5 Capacity Review Cadence

Quarterly (aligned with the §12.5/§13.5 quarterly review artifacts): dashboard-sourced trend review of RAM/CPU/disk/ingest slopes per host, Compartment-count forecast against the P2 checkpoint, cost line (droplets + Volumes + Spaces + DO Backups usage-based charges + Anthropic consumption trend from egress/billing data), and a written one-page capacity position filed to the wiki. The review's only mandatory output: confirmation that the next scaling event is known, named, and scheduled — or explicitly "none within horizon."

15.6 Verification (Build Gates)

Gates (LH-SBS-INST-001): §15.3 thresholds present as alert rules in the repo (promtool-validated, mapped action in annotation); §6.4 resource limits present on every Compartment service and OOM/throttle events exported as metrics; P1 resize drill executed once on Dev (timed, rollback path proven); Compartment-deploy RAM precondition check wired into the deploy pipeline (blocked-deploy negative test); P2 decision documented and item 36 closed before 4th-Compartment Workorder acceptance; capacity runbooks (P1–P6) published to wiki; first quarterly capacity position filed.

Maintenance & Patching

Patch windows, upgrade procedure, rollback

Maintenance & Patching

Chapter 17 — Maintenance & Patching

17.1 Current State and Scope

As-built patching is unmanaged: no unattended-upgrades confirmation, no defined windows, no reboot policy, and one already-tracked drift instance (OS mix 24.04/26.04, item 21). This chapter defines the patch regime for every layer — OS packages, kernel/reboots, container images, system services (nginx, Prometheus, Grafana, Vault, BookStack), data engines, and the DO platform events outside our control — plus the rollback path per layer. Controlling requirement: INF-05 (patching); MON-06 (vulnerability scanning, §11.6) is the detection feed this chapter remediates. The governing principle: patching is a pipeline event with a rollback, never an SSH session with apt upgrade and hope.

17.2 Patch Windows and Cadence

Window When (ET) Scope
Standing weekly Tuesday 05:00–07:00 Dev: everything. Prod: security-only OS patches (unattended-upgrades applies these continuously; the window is for anything needing coordination)
Standing monthly Second Tuesday 05:00–07:00 Prod: full OS package upgrade, pending reboots, service minor versions, base-image rebuild rollout (§11.3 weekly rebuilds accumulate; deploys land here unless CVE-driven earlier)
Emergency Any time, MON-05 incident context Actively exploited / CRITICAL-with-fix (Trivy nightly or vendor advisory). Target: patch in Dev + deploy to Prod ≤ 48 h from advisory; §10.4 emergency path applies with 24 h backport rule
Freeze Cutover ± 5 business days; declared SBS blackout dates Security-only; everything else queues

Ordering rule, always: Dev first, soak ≥ 24 h (weekly cycle) with monitoring green, then Prod. The 1:1 Dev sizing (§2.4) exists precisely so this soak is meaningful. Within Prod, order is: wiki → monitor → vault → prod-app — least-critical first, and the bastion/alerting host (monitor) is patched before the app host so a monitor regression is discovered while the app host is still stable, never both at once.

17.3 Patching by Layer
17.4 Upgrade Procedure (Standard Template)

Every window execution follows one runbook shape (wiki-published, per-layer specifics as annexes):

  1. Pre: confirm monitoring green fleet-wide; confirm last backup age green (tier-1 and applicable tier-2, §14.3 metrics); announce in ops channel (start/scope/expected end); freeze deploys for the window.
  2. Execute: Dev-proven changes only (except emergency path); one host at a time in §17.2 order; between hosts, verify: services active, blackbox probes green (§12.2), no new alerts for 10 min before proceeding.
  3. Verify: per-layer smoke (nginx -t + probe sweep; Grafana health + one dashboard render; Vault unsealed + read probe; app surfaces via blackbox; audit writer queue age nominal).
  4. Close: announce complete; changelog entry (narrative layer, §10.6) with versions before/after; any deviation → Build Register item within 24 h.
17.5 Rollback

Rollback authority: the window executor rolls back without seeking approval when any post-step verification fails — the approval was for the change; reverting to known-good needs none. Paths per layer, worst-case first:

Layer Rollback Bound
Container/app Redeploy previous digest (§6.7) Minutes
OS package set apt downgrade where pinnable; else tier-1 image restore pre-window backup ≤ 30 min (§14.4)
Kernel/reboot regression Boot previous kernel (GRUB retains) or image restore ≤ 30 min
Grafana/Prometheus/Loki apt downgrade or image restore; dashboards/rules are repo-provisioned so config rollback = git revert Minutes–30 min
Vault Binary revert + pre-upgrade Raft snapshot; quorum re-unseal ≤ 2 h (§14.4 — shareholder-bound)
Data engine minor Previous image digest + AOF/WAL intact Minutes
Data engine major Restore rehearsed dump (the rehearsal is the Change-Order gate) Per §14.4 data-loss class
DNS/zone changes Not a window item (§4.7 change control); rollback = revert record, 300-TTL bound ≤ 5 min propagation

Rollback is itself verified (same §17.4.3 smoke set) and logged; a rolled-back change re-enters through Dev with a written cause, never retried directly on Prod — the failure taught something or it didn't; the PR discussion says which.

17.6 Verification (Build Gates)

Gates (LH-SBS-INST-001): unattended-upgrades active fleet-wide with security-pocket-only config (Ansible-asserted, drift-checked); reboot-required metric exported and alerting at 14 d; window calendar published to wiki with §17.2 order and freeze rules; standard runbook + per-layer annexes published; one full monthly-window execution completed end-to-end on the fleet with changelog evidence; one deliberate rollback drill on Dev (failed-verification simulation → digest revert → smoke green); Vault pre-upgrade snapshot step present in its annex and rehearsed with test shares (§14.7 linkage); OS-mix remediation (item 21) closed or scheduled through these windows — the mechanism that fixes the existing drift is the mechanism that prevents the next.

Appendices

Resource inventory, firewall rule table, acronym table, references (NIST SP 800-207, OWASP ASVS L2)

Appendices

Chapter 18 — Appendices

Appendix A — Resource Inventory

A.1 DigitalOcean Account & Projects

Resource Identifier Notes
DO Account SBS-owned (alias ownership pending, §2.1) MFA/team-audit pending item 15/17 chain
Project: SBS The Dash Dev ac656e28-2196-410e-847c-bf3b60363710 Default project
Project: SBS The Dash Prod c49c646f-f268-4b51-a00e-ad9fb5055569  

A.2 Droplets (As Built)

Droplet Project Region / VPC Size Public IP Private IP OS Role DO Backups
Dev-SBS-server Dev NYC2 / default-nyc2 8 GB / 4 vCPU / 160 GB 162.243.252.138 10.100.0.2 Ubuntu 24.04 Dev server; Docker 29.1.3 ✅ (target: weekly)
sbsdash-server-prod Prod NYC2 / default-nyc2 8 GB / 4 vCPU / 160 GB 107.170.72.145 10.100.0.3 TBC Prod app; tenant surfaces; Docker 29.1.3; bastion-only SSH ✅ (target: daily)
monitor-servers Prod NYC2 / default-nyc2 4 GB / 2 vCPU / 120 GB 162.243.28.132 10.100.0.4 Ubuntu 26.04 Grafana 13.1.0 / Prometheus / nginx 1.28.3 / Loki (planned); bastion ✅ (target: daily)
sbs-wiki Prod NYC1 / default-nyc1 2 GB / 1 vCPU / 60 GB 167.172.135.88 10.116.0.2 TBC BookStack; NYC2 rebuild pending ✅ (target: daily)
Dev-Gitlab-server Unregistered VPC 10.120.x TBC 138.197.222.154 TBC TBC Holds apex DNS; disposition item 22 Pre-repoint backup: item 101
prod-vault Prod NYC2 / prod-vpc-nyc2 1 GB / 1 vCPU Planned — Vault (§8.2), rebuild wave Daily at creation

A.3 Network (Target vs. As Built)

Network CIDR State
prod-vpc-nyc2 10.10.0.0/20 Target — rebuild wave (item 18/59)
dev-vpc-nyc2 10.20.0.0/20 Target — rebuild wave
default-nyc2 10.100.0.0/20 As built — Dev+Prod shared ⚠, vacated at rebuild
default-nyc1 10.116.0.0/20 As built — wiki only, vacated at rebuild
A.4 DNS Zone (sbsdash.com — GoDaddy NS, cutover pending item 9)
Record Target (as built) Target (spec)
apex A 138.197.222.154 ⚠ 107.170.72.145 (item 16, gated on Ch. 5)
www CNAME sbsdash.com
admin A / clients A 107.170.72.145
monitor A 162.243.28.132 ✅ live+TLS unchanged
wiki A 167.172.135.88 NYC2 rebuild IP
MX / SPF / DMARC / CAA absent null-mail + LE-only CAA (item 23 — publishable now)
A.5 Storage & Registry (Planned)
Resource Location Chapter
Spaces: sbs-dash-artifacts, sbs-dash-audit, sbs-dash-backups NYC3 (+SFO3 replication) §7.3, §13, §14
DO Volumes: data engines, Loki, audit writer, Vault Raft NYC2 §7.6, §12.3, §13.3, §8.2
GHCR (SBS org) §11.2
Repos: sbs-dash-infra, platform app repo GitHub SBS org §10.3

A.6 Software Baseline (As Built)

Docker 29.1.3 / Compose 2.40.3 (prod, dev, monitor) · Grafana 13.1.0 (grafana apt repo) · Prometheus + node_exporter ×4 (Ubuntu repo) · nginx 1.28.3 · certbot + LE cert live on monitor · BookStack (wiki). Pending install: Node LTS/pnpm, Rust/Cargo, Python venvs, PostgreSQL, MongoDB, Redis, Vault, Loki, Alertmanager, blackbox/cAdvisor/engine exporters, oauth2-proxy, AnythingMCP.


Appendix B — Firewall Rule Tables (Target, §3.5–3.6)

B.1 Cloud Firewalls (tag-scoped) — as built: none exist ⚠ (item 19)

FW / tag Inbound Outbound
prod-app / prod 443, 80 ← any · 22 ← monitor private IP only (bastion) · 9100+exporters ← monitor private IP §3.4 allowlist
prod-monitor / grafana 443, 80 ← any · 22 ← admin allowlist (sole public SSH into Prod) · [3000/9090/9100 localhost-bound — no rule] §3.4
prod-wiki / wiki 443, 80 ← any · 22 ← monitor private IP · 9100 ← monitor private IP §3.4
prod-vault / vault 8200 ← prod-app, dev, bastion private IPs · 22 ← monitor private IP §3.4 (narrow)
dev-app / develop 22 + web ← admin allowlist only §3.4 + language registries (build context)
B.2 Host Firewall (nftables target; as built: ufw on monitor/wiki only, none on prod/dev ⚠ item 20)

Baseline: inbound default-deny mirroring Cloud FW; outbound default-deny + allowlist — 443→Anthropic API, GitHub/GHCR, apt mirrors, LE ACME; 123→NTP; 53→DO resolvers. Per-Compartment chains: egress = Anthropic + that Compartment's declared integrations only (§3.4). Ruleset hash exported to integrity job (§12.2).

B.3 Egress Allowlist Summary
Destination Ports Scope
Anthropic API 443 All prod + per-Compartment chains
GitHub / GHCR 443 Deploy/CI paths
Ubuntu archive/security, grafana apt 443/80 Hosts
registry.npmjs.org, crates.io, pypi.org 443 Build contexts only — unreachable from prod runtime (§11.2)
Let's Encrypt ACME 443 Edge hosts
DO resolvers / NTP pool 53 / 123 All

Appendix C — Build Register (Consolidated, items 1–115)

Status: ✅ done · ◐ partial · ○ open · ⛔ blocked/decision · numbering per chapter of origin.

# Item Ch. Status
1–3 DO account; Dev + Prod projects 2
4 Dedicated VPCs ×2 (10.10/10.20) 3 ○ (rebuild wave)
5 Cloud Firewalls ×4(+vault) 3
6 Admin SSH allowlist captured in IaC 3
7 nftables baseline + Compartment chains 3
8 Domain transfer to SBS + lock + registrar MFA 4 ◐ (registered; transfer pending)
9 Zone cutover GoDaddy → DNSSEC provider + NS 4
10 DS publication + chain verification (AD/DNSViz) 4 ○ (after 9)
11–12 Null-mail records; CAA 4 ○ — publishable now (item 23)
13–14 CT alert subscription; dnstwist weekly 4
15 Credential rotation + Vault migration + changelog purge + SBS-alias identities 2/8 CRITICAL — execute §8.6 order
16 Apex repoint 138.197.222.154 → prod 2/4 ⛔ gated on Ch. 5 prod edge
17 Per-person users, root-SSH disable, sudo scoping 2 ○ (via item 60)
18 VPC rebuild decision (a)/(b) 3 ⛔ decision — spec assumes (a)
19 Cloud FW creation 3 ○ (=5)
20 Host firewall activation prod/dev (stopgap ufw → nftables) 3 ○ urgent
21 OS standardization (24.04 vs 26.04) 2 ⛔ decision; executes via item 115
22 Dev-Gitlab-server disposition 2 ⛔ decision
23 Publish null-mail + CAA on GoDaddy pre-cutover 4 ○ immediate
24–29 Monitor nginx to fleet TLS/header template; social-card stripping; HSTS staged+preload sign-off; expiry probes; prod nginx+certs; admin edge allowlist 5
30–35 Toolchain install+pinning; Compartment compose template+net-edge; container hardening; interim data stacks; MCP sidecar pattern; compose-vs-running drift metric 6
36 NYC2 Managed DB availability check 7 ⛔ blocks §7.2 path + item 106
37–41 Data Volumes; per-Compartment DB/role/ACL scripts; internal CA; Spaces buckets+keys; sanitized-dump script 7
42–48 Vault droplet+Volume+FW; Shamir ceremony; mounts/policies/AppRoles; DB+PKI engines; deploy-render integration; gitleaks; rotation runbook 8
49 IdP product confirmation (DISC-002) 9 ⛔ blocks 50–54
50–56 IdP registrations+groups; oauth2-proxy; Grafana OIDC; BookStack SSO; SCIM+timed test; break-glass ceremony; first IAM-06 review 9
57–62 Infra repo+protection+CI; tofu import (nonconformance baseline); rebuild-wave authoring; Ansible base role fleet-wide; drift jobs+induced test; Prod approval gate 10 ○ — 58 executes 96-residual; 59 executes 4/18/42; 60 executes 17/20/109
63–70 GHCR+pull token; base-image allowlist+digest lint; frozen-lockfile gates; cosign sign+verify; SBOM; Trivy gate+nightly; Renovate; Actions digest pinning 11
71–78 Alertmanager+routes; blackbox ×5; Loki+agents; cAdvisor+engine exporters; integrity metrics; dead-man+external check; dashboard repo graduation; monitoring-role absorption 12 ○ (core stack ✅ live)
79–85 Audit writer+net-audit+Volume; segment format+signing; Rekor+email anchors; continuous verifier; independent CLI; deletion ceremony+legal hold; drill set 13
86–95 Tier-2 dump pipeline; offline age custody; repo mirrors; Vault snapshots; SFO3 replication; age metrics+alert; weekly restore-verify; runbooks; WAL-PITR trigger; drill calendar 14
96 DO Backups enablement 14 ◐ ✅ enabled; residual: plan/frequency audit + tofu codification
97 DO API backup-age → integrity metric 14 prioritized
98 Monthly backup→snapshot conversion 14
99 Plan-type/pricing verification 14
100 First tier-1 restore drill 14 ○ immediate
101 Gitlab-server pre-repoint backup 14 ○ before item 16
102–108 Threshold rules+annotations; OOM metrics; P1 drill; deploy RAM gate; P2 decision before Compartment #4; P1–P6 runbooks; quarterly capacity filing 15 ○ (106 ⛔ on 36)
109–115 unattended-upgrades; reboot metric; window calendar; window runbook+annexes; first monthly window; rollback drill; OS-mix via windows 17

Critical path: 15 → 20 → 57/58 → 18(a) rebuild wave (4, 19, 42, wiki NYC2) → 60 (17) → Ch. 5 prod edge (28) → 16 apex repoint → 9/10 DNSSEC cutover. Blocking decisions: 18, 21, 22, 36, 49.


Appendix D — Acronym Table
Acronym Expansion
ACL Access Control List
ACME Automatic Certificate Management Environment
AD (flag) Authenticated Data (DNSSEC)
AOF Append-Only File (Redis)
API Application Programming Interface
CA / CAA Certificate Authority / CA Authorization (DNS record)
CD / CI Continuous Delivery / Continuous Integration
CIDR Classless Inter-Domain Routing
CLI Command-Line Interface
CSP Content Security Policy
CT Certificate Transparency
CVE Common Vulnerabilities and Exposures
DMARC / SPF Domain-based Message Authentication, Reporting & Conformance / Sender Policy Framework
DNS / DNSSEC Domain Name System / DNS Security Extensions
DO DigitalOcean
DR Disaster Recovery
DS Delegation Signer (DNSSEC record)
ECDSA Elliptic Curve Digital Signature Algorithm
FCC Federal Communications Commission
FW Firewall
GFS Grandfather-Father-Son (retention)
GHCR GitHub Container Registry
HSTS HTTP Strict Transport Security
IaC Infrastructure as Code
IAM Identity and Access Management
IdP Identity Provider
IOPS Input/Output Operations Per Second
JCS JSON Canonicalization Scheme (RFC 8785)
KMS Key Management Service
KV Key-Value (Vault store)
LE Let's Encrypt
LTS Long-Term Support
MCP Model Context Protocol
MFA Multi-Factor Authentication
MX Mail Exchanger (DNS record)
NS Nameserver
NTP Network Time Protocol
OCSP Online Certificate Status Protocol
OIDC OpenID Connect
OOM Out Of Memory
OS Operating System
OWASP ASVS Open Worldwide Application Security Project — Application Security Verification Standard
PII Personally Identifiable Information
PITR Point-In-Time Recovery
PKCE Proof Key for Code Exchange
PKI Public Key Infrastructure
PR Pull Request
RBAC Role-Based Access Control
RDB Redis Database (snapshot format)
RPO / RTO Recovery Point Objective / Recovery Time Objective
SAML Security Assertion Markup Language
SAN Subject Alternative Name
SBOM Software Bill of Materials
SCIM System for Cross-domain Identity Management
SLA Service Level Agreement
SNI Server Name Indication
SOX Sarbanes-Oxley Act
SPDX Software Package Data Exchange
SSH Secure Shell
SSO Single Sign-On
TDE Transparent Data Encryption
TLS Transport Layer Security
TSDB Time-Series Database
TTL Time To Live
UID User Identifier
VPC Virtual Private Cloud
WAL Write-Ahead Log (PostgreSQL)

Appendix E — References
Ref Document Applied in
[1] NIST SP 800-207, Zero Trust Architecture §1.2, §7.5 (TLS on internal links — private network ≠ trust boundary), §9 (identity-centric access)
[2] OWASP ASVS v4.x, Level 2 §1.2, APP-01 baseline, §5.5 headers, §9 session/auth
[3] OWASP LLM Top 10 (incl. LLM01 Prompt Injection) §3.4 egress-as-exfiltration control, §6.4 MCP scoping (AI-02), §8.3 credential isolation (AI-03)
[4] LH-SBS-SEC-001 — Security & Access Control Specification Controlling baseline, all chapters; reconciliation 41-vs-36 pending
[5] LH-SBS-KO-001 V3.2 — Technical Install and Discovery Master runbook; control-family import
[6] LH-SBS-PRE-001 — Pre-Install Prerequisites Day-0 gates
[7] LH-SBS-INST-001 — Install Runbook, gates V1–V22 Verification sections §§2–17
[8] RFC 7505 (Null MX) · RFC 8785 (JCS) · RFC 8659 (CAA) §4.4, §13.2, §4.6
[9] DigitalOcean product documentation — Backups, VPC, Spaces, Managed Databases §14.2.1, §3.1, §7.2–7.3
[10] Sigstore (cosign, Rekor) documentation §11.5, §13.2
[11] SERVER_CHANGELOG.md As-built evidence layer (§1.3)