CI/CD Pipeline — Deploy Path Validated (2026-07-07)

CI/CD Pipeline — Deploy Path Validated (2026-07-07)

MC: #104863
Status: ✅ COMPLETE — First fully green end-to-end demo deploy run
Evidence: https://dev.azure.com/alai-holding/QODY/_build/results?buildId=272


Summary

QODY's Azure DevOps CI/CD pipeline is now fully validated end-to-end. Build #272 (2026-07-07 00:22 UTC) was the first run to successfully complete all 4 stages:

  1. CI Gates — Backend tests (Java 21 + Gradle) + frontend builds (npm ci + TypeScript/Vite) + Gitleaks secret scan
  2. Build images → ACR — 4 Docker images (qody-api, qody-guest, qody-admin, qody-staff-kitchen) built and pushed to qodydemoacr.azurecr.io
  3. Security: Trivy image scan — All 4 images scanned (HIGH + CRITICAL severity, exit-code 1 on findings) — CLEAN (jackson-databind 2.18.8, netty-bom 4.1.135.Final, Alpine patched)
  4. Deploy → QODY demo ACA — Canary deploy (qody-api: 0%→smoke curl→100%) + single-revision updates (guest/admin/kitchen) + flagship pay-to-kitchen UAT (Playwright hard gate) — PASS

Live verification (John, 00:24 UTC):


Root Cause (Why Pipeline Was Dead)

The Real Problem (tool-verified):
The azure-qody Azure RM service connection (and qody-demo ADO environment) had never been explicitly authorized for use by pipeline definition #3 ("QODY CI-CD"). Azure DevOps resource protection requires this one-time authorization the first time a pipeline run reaches a job that references the resource.

Every previous run either:

Fixed via:

PATCH .../pipelines/pipelinePermissions/endpoint/62c293a8-2241-465a-a9ac-93af127472de
PATCH .../pipelines/pipelinePermissions/environment/4
body: {"pipelines":[{"id":3,"authorized":true}]}

Both returned authorized: true immediately. The stuck Checkpoint.Authorization record transitioned inProgresscompleted/succeeded within the same second — direct, unambiguous proof.

Initial hypothesis (tag/branch trigger mismatch) was a red herring: The tag run did fire (build #236 exists, its CI_Gates stage succeeded), it just hung afterward on the authorization gap. The tag/branch filter theory is likely correct as a matter of ADO semantics, but it was NOT the blocker.


Path to Green (5 Runs, 5 Real Bugs Fixed)

Each failed run exposed a genuine latent issue:

Run Failure Root Cause Fix
#253 Build_Images failed Missing apps/guest/nginx.default.conf + apps/staff-kitchen/nginx.default.conf (referenced by Dockerfile COPY lines since MC #104749, but never committed to git) Committed the 2 missing .conf files (HSTS + security headers, identical pattern to admin)
#265 Security_Image_Scan failed 16 HIGH CVEs in jackson-databind + 2 HIGH in p11-kit Bumped jackson-databind → 2.18.8, netty-bom → 4.1.135.Final, apk upgrade in Dockerfiles
#267 Security_Image_Scan failed 14 HIGH CVEs in netty (after jackson fix) Explicit netty-bom override in build.gradle.kts
#269 Deploy_Demo flagship UAT failed deploy_demo job used checkout: none$(Build.SourcesDirectory)/e2e didn't exist for Playwright test script Added checkout: self before UAT step
#272 SUCCESS All stages green N/A

Each failure was a real bug (not flakiness), and each fix made the system more correct.


CVE Fixes (Image Security)

Before: 16 HIGH jackson-databind CVEs + 14 HIGH netty CVEs + 2 HIGH p11-kit Alpine CVEs
After (run #272): CLEAN — Trivy scan returned exit-code 0 for all 4 images

Dependency bumps:


Production Deploy Path (Prepared, Not Yet Live)

Service Principal: qody-azdo-pipeline-prod (separate identity from demo's qody-azdo-pipeline)
Authentication: Workload Identity Federation (no static secret)
Scope: Least-privilege — Azure Container Apps Contributor on rg-qody-prod (not full Contributor), AcrPush on qodyprodacr only
Securion Verdict:PASS (v2 proposal, 2 blocking findings fixed, 4 required verifications folded in)

Deploy_Prod stage: Drafted at infrastructure/pipeline-proposals/deploy-prod-stage.yml.proposal (deferred from live azure-pipelines.yml) — includes:

Status: Proposal only, NOT executed. Activation = separate task requiring CEO approval.

Full proposal (with Securion review history): /Users/makinja/system/evidence/104863/d2-prod-sp-proposal.md


Deploy Path Truth (As of 2026-07-07)

Path Status Evidence
Manual run with deployDemo=true PROVEN end-to-end Build #272 (all stages green, flagship UAT PASS)
Semver tag v* from main Analytically sound Tag/branch ancestry checked + trigger semantics verified, but NOT empirically confirmed (no executed tag run post-fix). Recommend: watch the first real v* tag deploy as final validation.

Caveat (Proveo): Run #272 was triggered reason: manual with templateParameters: {deployDemo: "true"} — NOT a v* tag push. The tag-trigger mechanism itself was deliberately not re-tested with a second live deploy (to honor the task's "ONE live validation deploy" constraint). Tag trigger proof rests on static evidence, not an executed tag run.


Evidence Location

Directory: /Users/makinja/system/evidence/104863/

Key files:


Guardrails Added

  1. RUNBOOK.md D3 guardrail (committed to main): Every QODY deploy task (demo or prod) MUST cite the Azure DevOps pipeline run URL in MC evidence. mc.js done/ready on a QODY deploy task without a run URL is incomplete evidence (ZAKON PI2).

  2. Revision-suffix collision fix (committed to main): Deploy_Demo's qody-api canary step used REV_SUFFIX="$(SHORT_SHA)" — a redeploy of the exact same commit would collide with the existing ACA revision name and fail. Fixed to REV_SUFFIX="$(SHORT_SHA)-$(Build.BuildId)" — unique per pipeline run regardless of SourceVersion. Mirrored into the deferred Deploy_Prod proposal.


Next Steps

  1. First real v* tag from main: Watch as empirical confirmation of the tag trigger path (analytically sound, but not yet executed post-authorization-fix).
  2. Prod deploy activation: Separate task, requires CEO approval. Execute d2-prod-sp-proposal.md commands, enable Deploy_Prod stage in azure-pipelines.yml, verify manual-only + approval gate.
  3. CI contention (optional, out-of-scope for #104863): The alai-holding ADO org shares Microsoft-hosted parallel-job capacity across QODY/Bilko/LumisCare. Consider requesting additional parallelism or self-hosted agents.

Last Updated: 2026-07-07
Author: Skillforge (documentation) + FlowForge (pipeline execution) + John (orchestration)
Proveo Verdict: ✅ PASS (all 4 claims verified against primary sources)


Revision #1
Created 2026-07-07 00:40:57 UTC by John
Updated 2026-07-07 00:40:57 UTC by John