---
title: "Source control, CI-CD & SDLC — N1 IT Landscape"
---

N1 IT Landscape — The Blueprint · CF-Access internal

# Source control, CI-CD & SDLC

N1 IT Landscape · Owner: Arun Venkataraman · source of truth: markdown SSOT

# N1 IT Landscape — Source control, CI/CD & SDLC

> **CURRENT-STATE** as of **2026-06-09** · part of the **N1 IT Landscape** (start at **[Overview](/landscape/overview/)**). Owner: **Arun Venkataraman** (Principal Engineer).

_Where the code lives (GitHub) and how a change travels from a laptop to production — the build pipeline, governance, and the full software delivery lifecycle._

* * *

## 7\. SOURCE CONTROL & CI/CD

**GitHub is the only source-control system.** Org `n1healthcare`: **98 repos total — 48 active, 50 archived** as of this snapshot (2026-06-09). The active/archived split moves continuously as repos are archived during ongoing cleanup, so treat the count as a point-in-time figure. The dependency manifest in the companion file was captured earlier the same day when 66 were active, so it covers 65 cloned repos (18 of which are now archived). **2 public repos** (`OcrRouter`, `synthetic-medical-reports`), both archived; everything else is private. The repo count far exceeds the number of services in production — many are experiments, POCs, or archived workflows. 🔒 2FA is enforced org-wide; default repo permission is `read`.

> Note: the full repo catalogue (with language, default branch, and the exact `develop` HEAD commit for each) is in [§18](/landscape/dependencies/00-bom/); the full per-repo catalogue with HEAD SHAs is in the companion DEPENDENCIES file.

### 7.1 The build-and-ship pipeline (the canonical pattern)

Almost every service repo carries a thin `deploy_aws.yaml` that **delegates all real work to reusable workflows in the`bastion` repo** (`n1healthcare/bastion/.github/workflows/...@main`):
    
    
    push to develop  → deploy-staging → bastion/deploy.yml (env n1-staging) → ECR  n1-staging/<svc>
    push to main     → deploy-prod    → bastion/deploy.yml (env n1-prod)    → ECR  n1-prod/<svc>
    PR opened/sync   → precheck       → bastion/supply_chain_precheck.yml   (safe-chain dependency shims)
    PR merged        → release-notes  → bastion/release_notes.yml          → Slack (via release-scribe)
    

🔒 `bastion/deploy.yml` is **hardened** :

  1. Checkout via SHA-pinned wrapper actions.
  2. Resolve env → ECR prefix + IAM role + region (`us-east-2` for staging/prod; `us-east-1` for the retired twin-staging).
  3. **Assume an AWS role via GitHub OIDC** (`id-token: write`) — **no long-lived AWS keys** in CI.
  4. Login to ECR (`191421493115.dkr.ecr.us-east-2.amazonaws.com`).
  5. Build with Docker Buildx; derive the image tag from the repo's `VERSION` file (staging: `<VERSION>_<sha7>`, prod: clean `<VERSION>`).
  6. **Grype CVE scan** → SARIF upload (prod).
  7. Push `:latest` \+ `:<tag>` to ECR.

The canonical `bastion` pipeline builds **ARM64 only** (on `ubuntu-24.04-arm` runners), matching the all-Graviton/Bottlerocket-ARM fleet ([§8](/landscape/aws/)/[§9](/landscape/kubernetes/)). Multi-arch (amd64+arm64 via QEMU) is used only by `react-frontend` (see §7.4).

### 7.2 Shared CI machinery

  * **`bastion`** — the reusable-workflow hub: `deploy.yml`, `supply_chain_precheck.yml` (enforces `safe-chain v1.5.0`, SHA-pinned, for `uv`/`npm` installs), `release_notes.yml`, `mirror_dhi_images.yaml` (mirrors third-party images into ECR). All third-party actions are wrapped in composite actions that **SHA-pin** every external dependency.
  * **`release-scribe`** (`@v1`) — on PR merge, sends the PR diff + commits to the internal **LiteLLM** proxy (Gemini 2.5 Flash), gets structured JSON back (summary / change type / breaking changes / test recommendations), posts a **Slack Block Kit** message, and optionally creates a **ClickUp** ticket. This is the "what changed / what to test / will it break" note posted on every develop and main merge.
  * **`github-action-deployment-approval`** — a Slack Approve/Reject gate that resolves approvers from `CODEOWNERS`. 🔒 The approval step in the shared `bastion/deploy.yml` is **commented out** , so most service prod deploys run **without a human gate** in CI. The exception is **admin-dashboard** , which wires the `deployment-approval@main` action directly into its own pipeline.

Pinned third-party action versions (from `bastion`): `aws-actions/configure-aws-credentials` v6.1.0, `actions/checkout` v6.0.2, `docker/setup-buildx-action` v4.0.0, `docker/build-push-action` v7.1.0, `docker/setup-qemu-action` v4.0.0, `actions/upload-artifact` v7.0.1 (all SHA-pinned).

### 7.3 Deployment (GitOps) and who can deploy

  * **ArgoCD runs in-cluster** in both clusters and continuously syncs from `git@github.com:n1healthcare/n1-helm-charts.git` (branch `develop`). **ArgoCD Image Updater** (`v1.0.2`) watches ECR and advances image tags — which is why several apps show `OutOfSync` but `Healthy` (the live image is ahead of the git manifest).
  * **Staging deploys: any developer** — via `argocd-staging.n1-research.com` (Access-gated, Google sign-in required).
  * **Production deploys: Arun, Darpan, Jasper only.**

### 7.4 Branch protection & governance (GitHub rulesets)

Branch protection is implemented with **GitHub rulesets** (the classic branch-protection API is unused and returns 404). Three **organization-level rulesets** apply to all repos:

Ruleset | Target | Enforces  
---|---|---  
Organization Branch Protection Policy | `main` \+ `develop`, all repos | ≥1 PR approval, dismiss-stale-reviews, require-thread-resolution, require-last-push-approval, block force-push, block deletion, no direct commits (PR required), signed commits  
Protect all default branches | default branch, all repos | block force-push, block deletion  
Require signed commits (org-wide) | all branches, all repos | every commit cryptographically signed  
  
Bypass actors: org admins, plus one team (via PR) on the main policy. Some repos add their own — e.g. **forge-runner** requires linear history + Copilot code review on `main`.

### 7.5 GitHub security & Actions configuration

  * The enforced "**N1 Default** " code-security config has secret-scanning, push-protection, and code-scanning **disabled** — these are GitHub Advanced Security features that are **not available on the Team plan** N1 is on. Dependabot alerts and security updates are **enabled**. An unenforced "GitHub recommended" config (which would enable the scanning features on a plan that offers them) also exists.
  * The org **Actions policy is`all`** (any public or private action may run); **members can create repos**.
  * `react-frontend` builds multi-arch (amd64+arm64 via QEMU), has no Grype scan, uses date-sequential `V<YYYYMMDD>_<n>` tags, and its EKS rollout steps are commented out (image reaches ECR, not auto-rolled). `admin-dashboard` uses the same date-tag scheme with rollout steps commented out, and **does** run Grype + the deployment-approval gate.
  * `api-backend` has a webhook to `search.clickup-eu.com` (ClickUp EU).

* * *

## 7A. SOFTWARE DELIVERY LIFECYCLE (SDLC) — what runs where, and what ships to staging vs prod

This ties §7, [§8](/landscape/aws/), [§9](/landscape/kubernetes/) and [§10](/landscape/app-architecture/) into one description of how a change travels from a laptop to production.

### 7A.1 The three places work happens

Environment | Where | What runs there | Who deploys  
---|---|---|---  
**Local / dev** | Engineer's laptop | Code is written mostly **with Claude Code** (only Eyad/Arun hand-write occasionally). Local stacks via `local-development-env`; CHR work via the `chr-dev-kit` with synthetic patients. | —  
**Staging** | `n1-staging-cluster`, namespace `staging` \+ `staging.n1.care` | All services **plus** the evals suite (`parser/router/grouper-evals`), `automated-testing`, `helix-parser`, `rosetta-genetics-grouper`, `n1-api-slack-bot`, and `data-validation-service`. Developers get read-only staging DB/Redis over WARP. | Any developer  
**Production** | `n1-production-cluster`, namespace `prod` \+ `app.n1.care` | Core services + the **3 production CHR types** , plus the prod-only `api-proxy`/`auth-proxy`/`billing-proxy` and `pdf-converter`. | Arun, Darpan, Jasper  
  
### 7A.2 Branch model = environment model (build once per branch)
    
    
    feature branch ──PR──▶ develop ──(CI builds ARM64 image)──▶ ECR  n1-staging/<svc>:V<date>_<sha7>
                              │                                         └─▶ ArgoCD Image Updater ▶ STAGING
                              └──PR (develop→main)──▶ main ──(CI)──▶ ECR n1-prod/<svc>:V<date>
                                                                           └─▶ ArgoCD ▶ PRODUCTION
    

`develop` maps to **staging** , `main` maps to **production**. A merge is the deploy trigger; promotion to production is the `develop → main` pull request. The staging image tag carries the git sha; the production tag is the clean `VERSION`. Both branches are gated by rulesets (≥1 approval, signed commits, no force-push — §7.4).

### 7A.3 What happens on each event

Trigger | Pipeline (`bastion`) | Result  
---|---|---  
**PR opened/updated** | `supply_chain_precheck.yml` | `safe-chain`-shimmed `uv`/`npm` install  
**Merge →`develop`** | `deploy.yml` (env `n1-staging`) | OIDC role → build ARM64 → (Grype on prod path) → push `n1-staging/<svc>` → ArgoCD Image Updater rolls it into `staging`  
**Merge →`main`** | `deploy.yml` (env `n1-prod`) | same, pushes `n1-prod/<svc>` → ArgoCD rolls into `prod`  
**PR merged (either)** | `release_notes.yml` → `release-scribe` | AI-generated release note posted to Slack  
**Pod starts** | migration **sidecar** | DB schema migrated on startup (Alembic / raw SQL / Ory automigration / LiteLLM)  
  
### 7A.4 Delivery paths outside the standard ECR→ArgoCD flow

  * **Web app & marketing** — `react-frontend` and `n1care-website` are built and deployed **on Cloudflare** (Workers for `n1.care` / `app.n1.care` / `staging.n1.care`).
  * **N1 libraries** — `n1r-cortex`, `n1r-rosetta`, `n1r-phoenix`, `n1r-chr`, `n1-api-client`, `medical-unit-conversion`, `n1r-document-manager` publish wheels to the public R2 bucket (`artifacts.n1-research.com`) via their own publish workflows.
  * **`forge-runner`** ships as an image but runs as on-demand Kubernetes **Jobs** (one per CHR), launched by `forge-sentinel`.

### 7A.5 Testing

PR-time: `supply_chain_precheck` plus each repo's `test`/`quick-check`/lint workflows. In **staging** : `automated-testing`, `e2e-tests`, the `parser/router/grouper-evals` services, and `data-validation-service` (evaluates parser + CHR output). Production builds add a **Grype** CVE scan.

### 7A.6 Deploy state & rollback

ArgoCD Image Updater advances image tags as new images land in ECR, which is why several ArgoCD apps read `OutOfSync` but `Healthy` (live image ahead of the git manifest). Rollback is re-pointing the image tag or reverting the PR and letting CI rebuild. Staging and production run different tags at any given time (visible in [§9.4](/landscape/kubernetes/)).

* * *
