M01 — Foundations

One-line goal: the platform’s infrastructure, guardrails, and contracts are in place such that any subsequent milestone can build on them without re-litigating decisions.

This is the milestone with the highest leverage. Done well, every other milestone benefits. Done sloppily, every other milestone pays for it. Treat it accordingly.


Tracks involved

  • Cloud Infra — primary owner. GCP project, GKE, networking, KMS, Terraform, CI/CD.
  • Backend — defines the canonical contracts (events, API DTOs) and stands up the hello-world NestJS shell.
  • AI Worker — hello-world inference container + GPU-node verification.
  • Edge — hello-world supervisor binary + hello-world Python application container.
  • Frontend — hello-world Vite SPA + canary auth integration shell.

The infra track produces the foundation everyone else builds on; the other tracks each ship a smoke-test artifact to confirm the foundation works for their workload type.

Dependencies

  • CNTXT / GCP Dammam account active with billing — or the documented temporary-project workaround active for dev (see 05-contingencies.md §3).
  • Decisions already locked: tech stack (01-architecture-summary.md), cost guardrails (02-cost-guardrails.md), data protection (03-data-protection.md), contracts (04-abstractions-and-contracts.md). Do not re-litigate during this milestone.

Deliverables

1. Cloud project setup

  • GCP project wiqaia-pilot-prod (and wiqaia-pilot-dev, wiqaia-pilot-staging as separate projects for cleaner blast-radius isolation; each has its own quota + budget).
  • Billing account linked through CNTXT.
  • Organization-level policies enforcing: no public storage buckets without exception; CMEK required for storage; no default service account for new resources; private GKE clusters only.

2. Cost guardrails (per §3 of 02-cost-guardrails.md)

  • Project budget: $4,000 USD/month (pilot-prod), $200/month (dev, staging), with alerts at 25 / 50 / 75 / 90 / 100 / 110 / 120%. The pilot-prod cap is sized for one always-warm L4 (~$1,000/mo) plus the rest of the data plane plus headroom — see docs/plan/02-cost-guardrails.md §7.
  • Per-service budgets: GPU $1,500 (one always-warm L4 baseline ~$1,000 + surge headroom), egress $200, BigQuery $100, storage $200.
  • Quotas set to the pilot profile in §4.1.
  • billing-alerts Pub/Sub topic exists (audit trail destination; no programmatic consumer — L3 auto-kill deferred per decisions).
  • Daily anomaly-detection alerts enabled.
  • Cost-overview dashboard live in Cloud Monitoring.

3. Networking

  • One VPC per environment, with subnets per region/zone.
  • Private GKE cluster (no public node IPs).
  • Cloud NAT for outbound; egress firewall rule blocks everything except a documented allow-list (Pub/Sub, Cloud SQL, Cloud Storage, Secret Manager, KMS, Artifact Registry, logging, monitoring, package registries for image pulls).
  • Cloud Armor on the external load balancer (geographic + rate-based rules).
  • VPC Service Controls perimeter around the sensitive APIs (KMS, Secret Manager, Cloud Storage) — limits exfil even if a service account leaks.

4. KMS and encryption

  • KMS keyring wiqaia-cmek in me-central2 with keys per data class (db, storage-frames, storage-clips, storage-models, logging, bigquery). 90-day rotation enabled.
  • KMS keyring wiqaia-app-keys (separate IAM, stricter access) for application-layer KEKs (per 03-data-protection.md §5). 30-day rotation enabled.
  • Verified CMEK enforced on every storage primitive created by Terraform — Terraform policy fails on resources without CMEK.

5. Data plane primitives (empty but ready)

  • Cloud SQL Postgres 15 instance, db-custom-2-7680, HA with regional replica, CMEK, private IP only. Empty database wiqaia with PostGIS extension installed.
  • Memorystore Redis 7 M1 standard 4 GB, CMEK, private IP only. Empty.
  • Cloud Storage buckets created per (env, tenant_id, data_class) — for the pilot, wiqaia-pilot-{saudi_airlines_tenant_uuid}-{frames|clips|models|floorplans}. CMEK on each. Lifecycle policies set.
  • Pub/Sub topics created: frame-events, analysis-events, tier2-events, alarm-events, alert-events, audit-events, edge-telemetry-events. Per-topic subscriptions with IAM bindings to the consumer service accounts.
  • BigQuery dataset wiqaia_analytics, CMEK, with empty tables for analysis events and alerts (matching protobuf shapes from the contracts package).
  • Secret Manager seeded with placeholder secrets (database connection, JWT signing key, edge bootstrap token, etc.) — actual values populated as needed.
  • Artifact Registry repository wiqaia in me-central2 with the standard naming convention me-central2-docker.pkg.dev/wiqaia-pilot-prod/wiqaia/<app-name>:<tag>.

6. GKE cluster

  • Cluster wiqaia-pilot in me-central2, GKE Standard, multi-zone within the region for HA.
  • Node pools:
    • general-cpu — 3× e2-medium, autoscale 3–10.
    • gpu-tier2g2-standard-8 (L4 GPU) always-warm (minReplicas: 1), autoscale 1–2 (max 2 to allow a brief surge during model swap or canary). Note: T4 / A100 / H100 are not available in me-central2; L4 is the only GPU option in-region, only in zones me-central2-a and me-central2-c. The always-warm policy is required because fires happen year-round and 4-7 minute GPU cold-starts are unacceptable on a fire-detection pipeline.

The earlier 3-tier plan included a separate gpu-tier1 pool. On 2026-05-13 that workload (Tier 0 motion filter + anomaly autoencoder) moved to the edge device as CPU-only ONNX inference — see docs/plan/01-architecture-summary.md §0. There is now exactly one GPU node pool in the cloud.

  • Workload Identity Federation enabled (no static service-account keys).
  • Network policies enforced (default deny intra-namespace; explicit allow per service).
  • Pod Security Standards: restricted profile enforced for application namespaces.

7. Service mesh — Linkerd (self-managed)

Decision recorded in decisions 2026-05-14. Linkerd’s smaller per-pod overhead (~10-30 MB vs Istio’s ~100-200 MB) and operational simplicity beat Istio’s feature richness at our scale; we don’t need traffic-shifting / VirtualService machinery.

  • Linkerd deployed in cluster-wide mode. Automatic mTLS for every meshed pod — no per-service policy configuration needed to meet the security baseline.
  • Installs via the Linkerd CLI / Helm chart post-cluster-bootstrap (not via Terraform — the kubernetes provider is brittle for Linkerd’s CRDs + identity-issuer lifecycle).
  • Service-to-service authorization policies via Linkerd’s Server + ServerAuthorization resources: deny-by-default, explicit allows per app in their deployment manifests.

8. Identity skeleton

  • Keycloak deployed on the cluster (private IP, accessed through the mesh gateway). Realm wiqaia created. Roles seeded: super_admin, national_hq_operator, regional_commander, building_admin, building_manager, operator, fd_dispatcher, auditor.
  • Hello-world test user created in the dev realm only; production users come in M02.

9. The packages/contracts/ package (v0.1.0)

  • Protobuf files for the canonical events (FrameEvent, AnalysisEvent, Tier2AnalysisEvent, AlarmEvent, Alert, EdgeTelemetryEvent, AuditLogEntry).
  • JSON Schemas for the initial REST DTOs (UserDto, BuildingDto, CameraDto, AlertDto).
  • Generated bindings for TS, Python, Go produced by a single pnpm build (or make) at the package root.
  • Driver interfaces (per 04-abstractions-and-contracts.md §5) — Python Protocol classes and TS interface definitions. Stubs only at M01.
  • Published as a workspace package; importable from every other workspace.
  • CHANGELOG.md initialized.

10. CI/CD

Source of truth is our self-hosted Gitea (rizoma/wiqaia at git.rizoma.sa). Gitea Actions is not used — the ops VM is 2 GiB RAM and cannot safely host a runner without OOM risk. Two layers instead:

  • Local pre-push gate (now): scripts/git-hooks/pre-push runs the same checks CI would — static validation, Terraform fmt + validate, pnpm -r test, pnpm -r typecheck. Install with make hooks-install (idempotent symlink to the version-controlled hook). Skips heavy checks for docs-only pushes.
  • Cloud Build CI (planned, separate issue): per-app cloudbuild.yaml workflows triggered by Gitea push webhooks. Steps: lint (ESLint / Ruff / golangci-lint), typecheck (tsc / mypy / go vet), test, Docker build per app, push to Artifact Registry on main merge, deploy-canary. Turborepo caching backed by GCS in Dammam. Set up when the cloud engineer hire has bandwidth — see the “Set up Cloud Build CI infrastructure” issue in Gitea.
  • Branch-protection rules on master (once Cloud Build is live): require passing CI, no force-push. Two-person review when team grows beyond solo.

11. Hello-world deployments

Each app ships a minimum-viable container that proves the toolchain end-to-end:

  • apps/api — NestJS app with a single GET /healthz route that returns { ok: true, version, env }. Deployed; reachable through the mesh gateway via HTTPS at https://api.dev.wiqaia.sa/healthz.
  • apps/dashboard — Vite SPA showing “WiqAIa+ — coming online” and a styled “log in” button (no auth flow yet). Deployed; reachable at https://dev.wiqaia.sa/.
  • apps/fd-view — same minimal Vite SPA, separate origin (https://fd.dev.wiqaia.sa/).
  • apps/edge-dashboard — same minimal Vite SPA but configured to be served by the edge supervisor on http://<edge-ip>:8443/ over a self-signed cert on the LAN.
  • apps/ai-worker — Python service that accepts a POST /healthz and a POST /echo-inference that runs a no-op against an installed model artifact (verifies GPU availability when scaled up).
  • apps/edge-supervisor — Go binary that, when started, registers with the cloud fleet-manager stub and emits a EdgeTelemetryEvent to its assigned Pub/Sub topic. systemd unit file shipped.
  • apps/edge-app — Python service that the supervisor starts as a container; logs a “hello from edge” message at startup.

12. Bootstrap install script

  • infra/edge-bootstrap/install.sh — a single Bash script the customer runs on their Ubuntu box. Does: install Docker, drop the supervisor binary into /usr/local/bin/, install the systemd unit, fetch a one-time onboarding token from a passed argument, register with the cloud, pull and run the hello-world edge-app container.
  • Hosted at https://install.wiqaia.sa/edge (Cloud CDN, with version pinning support).
  • Documented in infra/edge-bootstrap/README.md.

13. Observability

  • Cloud Logging ingesting from all GKE pods, with CMEK on the logs bucket.
  • Cloud Monitoring with alerts on: GKE pod crash loops, Cloud SQL CPU > 80%, Memorystore memory > 80%, audit-log chain inconsistency.
  • Sentry SaaS project for dev/staging error tracking — synthetic data only.
  • Cost-overview dashboard live with the panels listed in 02-cost-guardrails.md §9.

Verification

Concrete observable behaviors. A milestone is not “done” until these pass.

  1. The hello-world API responds. curl https://api.dev.wiqaia.sa/healthz returns 200 with the expected JSON. The request hits Cloud LB → mesh gateway → API pod and is logged with full request context in Cloud Logging.
  2. The hello-world dashboard loads in a browser with proper TLS (HSTS, modern cipher) and Arabic + English language switching working on the static UI shell.
  3. The hello-world AI worker can run on a GPU. The always-warm gpu-tier2 node is up; running kubectl exec against the worker pod confirms CUDA is visible and a no-op inference completes. (No on-demand GPU scaling needed — the pool is minReplicas: 1.)
  4. The edge supervisor registers and reports telemetry. Starting the supervisor on a developer’s Ubuntu VM with a valid token results in the fleet-manager stub recording the registration and the cloud receiving at least one EdgeTelemetryEvent per minute.
  5. A synthetic budget alert lands on billing-alerts. Publishing a test payload to the topic with "alert_threshold_percent": 100 is observable via gcloud pubsub topics ... pull. There’s no programmatic consumer (L3 deferred); the test confirms the audit-trail path works for postmortems. See decisions for why L3 was deferred.
  6. CI passes a smoke PR. Open a PR that modifies one comment in apps/api/. CI runs lint, typecheck, test, build, push, deploy-canary, and all complete in under 10 minutes.
  7. The contracts package is importable everywhere. apps/api imports a generated TS type, apps/ai-worker imports a generated Python pydantic class, apps/edge-supervisor imports a generated Go struct. All compile.
  8. The audit log hash chain is verifiable. Insert 10 synthetic AuditLogEntry rows; run the chain-verify script; it returns OK. Tamper with one row’s changes field directly in Postgres; re-run; the script flags the inconsistency.
  9. A non-CMEK storage bucket fails to apply. Attempting terraform apply on a config that creates a bucket without a kms_key_name results in a policy-engine failure during plan, not at apply time.
  10. No service account has broader-than-necessary IAM. A manual audit of all service-account bindings confirms least-privilege. (Documented in infra/iam/audit.md.)

Risks

RiskLikelihoodMitigation
CNTXT account not active in timeMediumTemporary GCP project workaround (05-contingencies.md §3); only synthetic data; production deploy still gated on Dammam account
GPU quota denied or delayedMediumOn-prem GPU box pre-ordered as insurance (05-contingencies.md §1)
Istio vs Linkerd indecision dragsLowTime-box to 2 days; default to Istio if no clear winner
Keycloak deployment complexityLow-MedWe accept Keycloak’s operational weight; alternative (SuperTokens) evaluated quickly only if Keycloak blocks
Workload Identity Federation frictionLowStandard pattern; documentation is solid
Bootstrap install script breaks on a non-mainstream Ubuntu variantLowTarget Ubuntu Server 22.04 LTS officially; document support; surface clear errors on unsupported OSes

Open questions

  • Istio vs Linkerd for the service mesh. Resolved 2026-05-14: Linkerd, self-managed. See decisions.
  • Helm vs Kustomize for K8s manifests. Helm leans more toolable; Kustomize is leaner. Default Helm; decide quickly.
  • Cloud Build vs GitHub Actions. Resolved 2026-05-14: Cloud Build, when we set it up. Source hosting is self-hosted Gitea in-Kingdom; no GitHub involvement. See the “Set up Cloud Build CI infrastructure” issue in Gitea.
  • Sentry self-hosted vs GlitchTip for production. Defer to M10 (production-ready hardening); Sentry SaaS for dev/staging is fine now.

Exit criteria

M01 is complete when all 10 verification items above pass. Sign off explicitly — append a one-paragraph “M01 complete” entry to the completion log with date and verifier.

WiqAIa+ Docs · Rizoma