Inspiration: The Post-Code Problem in Regulated Healthcare Software

My dad takes blood pressure and diabetes medication every month. When I checked his pharmacy bills against India's NPPA (National Pharmaceutical Pricing Authority) DPCO 2013 legal ceiling prices, 6 out of 13 medicines were overcharged or had an 85% cheaper government Jan Aushadhi generic equivalent.

I built RxAudit (github.com/SOLOxLEVELING/RxAudit) as a 24-hour prototype where a vision model extracts receipt line items once, and a deterministic TypeScript engine verifies prices against 153 regulated formulations. However, in that original prototype, the 153-drug database was frozen in git, with no CI test runner, no container pipeline, and no automated way to ship NPPA Gazette revisions.

Why? Because in regulated medical software, handing post-code maintenance over to unguarded AI coding agents introduces two dangerous failure modes:

  1. Ambiguity Guessing: If a regulatory issue says "Update Telmisartan 40 ceiling to ₹62" without specifying mg vs mcg or per-tablet vs per-strip, a standard LLM agent guesses the missing fields to close the issue.
  2. Reward-Hacking Failing Pipelines: When asked to fix a failing CI build or add a drug update, an unguarded agent can make the pipeline green by weakening a Zod schema (src/data/schemas.ts) or editing a failing test assertion (src/lib/audit-engine.test.ts).

For Life After Code (Path B: Bring Your Own), we built the 9-stage autonomous post-code lifecycle on the GitLab Duo Agent Platform and Google Cloud Run that makes agentic maintenance safe, tamper-evident, and mathematically verifiable.


Empirical Evaluation First: 24 Post-Code Scenarios & 2 Real Bugs Caught

Any CI/CD agent looks reliable on a single rehearsed happy path. We evaluated RxAudit's post-code governance gates against 24 real repository scenarios (src/lib/governance-benchmark.ts, executed on every commit in the verify stage) across three categories:

Category Scenarios Tested Result Governance Guarantee
A. Legitimate NPPA Updates 10 real chronic-care DPCO Gazette updates (Dapagliflozin 10mg, Empagliflozin 25mg, Vildagliptin 50mg, Rosuvastatin 20mg, etc.) 10 / 10 Approved 0% False Refusal Rate: Valid regulatory updates flow into MR creation without human bottlenecks.
B. Adversarial & Reward-Hacking 8 attacks: unit-vs-strip inversion (₹85/unit vs ₹80/pack), pack math drift (6.10 × 15 != 61.00), generic price > ceiling, clinical dosage advice injection, simulated AI agent tampering with protected test hashes, lowering test assertion count (75 to 50), canary 1-paisa drift (₹500.02 vs ₹500.00), and hallucinated ceiling on unlisted drug. 8 / 8 Blocked 100% Block Rate: Zero leaked invariants, zero test-weakening bypasses.
C. Ambiguous / Incomplete Inputs 6 edge cases: bare dosage ("40" without unit), missing pack size, missing DPCO S.O. Gazette citation, bare price without unit/strip distinction, unlisted strength on a known brand ("Okamet 999mg"), and unregulated supplement ("Ashwagandha Gold Forte"). 6 / 6 Silenced 0% Hallucination Rate: Enforces Silence Over Guessing — blocks MR creation and posts clarifying questions (blocked:ambiguous-regulatory-input).

Two Unscripted Bugs Caught During Our Build

  1. The "Okamet 999mg" Single-Candidate Strength Leak: During Phase 1 adversarial testing, we tested what happens when a receipt contains a known Indian brand with a fabricated dosage strength ("Okamet 999mg" or "Okamet 250mg"). Because Okamet SR 1000mg was the only Okamet entry in drug-database.json, Pass 2 of src/lib/drug-matcher.ts filtered out the candidate during strength checks, fell through to a candidatesByBrand.length === 1 branch without checking !extractedStrength, and falsely matched 1000mg. We patched Pass 2 and Pass 3 so any dosage mismatch on a known brand immediately returns matched: false (UNVERIFIED), and locked the regression test in scripts/ratchet-lock.json.

  2. Pipeline #2958744286 and GitLab Duo Merge Request !1: When we pushed our first 9-stage .gitlab-ci.yml, Pipeline #2958744286 failed with sast job: chosen stage test does not exist. Although we had remapped semgrep-sast and secret_detection to stage: secure, GitLab's Security/SAST.gitlab-ci.yml template defines a parent sast job defaulting to stage: test. We triggered GitLab Duo Fix Pipeline. Duo diagnosed the template inheritance issue, ran glab ci lint to verify that overriding sast: stage: secure fixed the parser error, created branch duo/fix/sast-job-stage-config-error-9647204, and opened Merge Request !1. In its session log, Duo noted: "AGENTS.md: It has no rules about this comment, so I didn't adapt anything. Its rules cover audit logic, tests and deployment." That insight led us to add Section 2 of AGENTS.md (GitLab Duo MR & Issue Comment Formatting Rules), requiring every agent comment to declare its touched files, ratchet hash status, and silence-over-guessing verification, alongside a permanent test in src/lib/pipeline-stages.test.ts ensuring stage: test never re-enters .gitlab-ci.yml.


Path B: Before vs. After GitLab Transcend

Operational Dimension Before (github.com/SOLOxLEVELING/RxAudit) After (GitLab Duo Agent Platform + Google Cloud Run)
CI/CD & Test Suite Standalone test script; no test runner in package.json; zero CI pipeline. 108 Vitest tests across 7 suites outputting native GitLab JUnit XML (artifacts/junit.xml) across a 9-stage .gitlab-ci.yml pipeline.
Anti-Reward-Hacking Guard None — an AI agent could edit tests or weaken schemas.ts to pass CI. Cryptographic SHA-256 Ratchet (scripts/verify-ratchet.ts) locking 11 governance files, a monotonic catalog floor (≥ 153 drugs), and a monotonic test assertion floor (≥ 75, currently 93 assertions / 108 tests).
Regulatory Issue Triage (Plan) Manual JSON edits. Deterministic NPPA Triage Guard (src/lib/nppa-triage.ts) + GitLab Issue Seeder (scripts/seed-gitlab-issues.ts) enforcing APPROVED_FOR_MR, AMBIGUOUS_OR_INCOMPLETE (exit 2), and REJECTED_INVARIANT_VIOLATION (exit 1) on live Issues #1–#3.
External Verification (Govern) Users had to trust the web UI. Standalone MCP Server (dist/rxaudit-mcp-server.cjs) + /api/provenance endpoint signing every audit and triage verdict with a 64-char SHA-256 compositeRulebookHash (a7cc4de2644e...).
Supply Chain & Security (Package / Secure) Local dev server only. Multi-stage non-root (USER nextjs) Docker build, CycloneDX v1.5 SBOM (20 components including the SHA-256 hash of drug-database.json), deterministic secret/env scanner, and GitLab SAST + Secret Detection.
Deployment & Canary (Configure / Monitor) Static Vercel config without canary checks. Google Cloud Run (us-central1) with Knative IaC (deploy/cloudrun-service.yaml), 2-phase --no-traffic --tag canary rollout (scripts/deploy-cloudrun.sh), and a 4-probe live canary verifier (scripts/verify-canary.ts) that triggers automatic traffic rollback on a 1-paisa drift.

How All 9 GitLab DevSecOps Lifecycle Stages Work

  1. plan (plan:nppa-triage): Executes scripts/triage-nppa-update.ts on NPPA Gazette proposals. If dosage units (mg/mcg/IU), pack size, or S.O. Gazette citations are missing, it exits with code 2, blocks MR creation, and posts clarifying questions on the GitLab Issue (live in Issues #1, #2, and #3).
  2. create (create:lint-and-typecheck): Enforces strict Next.js 15 / TypeScript 5 AST compilation (npx tsc --noEmit) and ESLint rules governed by AGENTS.md and .gitlab/duo/chat-rules.md.
  3. verify (verify:unit-and-ratchet): Validates all 153 formulations across 5 mathematical invariants (npm run validate-db), verifies the SHA-256 hashes of 11 protected files (npm run verify:ratchet), runs all 108 Vitest tests (npm test), and executes the 24-scenario benchmark (npm run verify:benchmark).
  4. package (package:docker-and-sbom): Compiles the Next.js standalone server, bundles the zero-dependency production MCP server (dist/rxaudit-mcp-server.cjs), and generates a CycloneDX v1.5 SBOM (artifacts/sbom.cyclonedx.json).
  5. secure (secure:deterministic-audit + sast + semgrep-sast + secret_detection): Runs GitLab native SAST and Secret Detection alongside scripts/verify-security.ts (verifying zero leaked tokens, .env exclusion, and non-root USER nextjs execution).
  6. release (release:manifest-and-changelog): Generates artifacts/release-manifest.json and artifacts/CHANGELOG-RELEASE.md stamped with the cryptographic RulebookProvenanceStamp.
  7. configure (configure:cloudrun-iac-and-deploy): Validates the Google Cloud Run Knative manifest (deploy/cloudrun-service.yaml), Dockerfile, and two-phase canary deployment script (scripts/deploy-cloudrun.sh).
  8. monitor (monitor:canary-verification): Executes 4 synthetic canary probes (scripts/verify-canary.ts) verifying health/ratchet lock, exact ₹500.00 overcharge math to the paisa, "Okamet 999mg" silence on unknown strength, and < 2000ms latency.
  9. govern (govern:dossier-and-mcp-smoke): Compiles the tamper-evident compliance dossier (artifacts/governance-dossier.md & .json) and runs a live JSON-RPC 2.0 stdio smoke test against the bundled production MCP server.

Environmental Impact & Software Carbon Intensity (SCI) Quantification

We quantified RxAudit's sustainability architecture using the Green Software Foundation Software Carbon Intensity (SCI) specification (artifacts/governance-dossier.json):

$$\text{SCI} = \frac{(E \times I) + M}{R}$$

where functional unit $R$ = 1 audited pharmacy bill (at runtime) or 1 post-code verification run (in CI/CD), deployed in Google Cloud Run region us-central1 (Iowa grid carbon intensity $I \approx 386\text{ gCO}_2\text{eq/kWh}$).

  • 99.99% Operational Energy ($E$) Reduction via Zero-LLM Core Audit & Canary Path: Standard agentic compliance tools call a frontier LLM on every price comparison and post-deploy check (~$1.20\text{ Wh}$ per bill audit and ~$0.80\text{ Wh}$ per synthetic canary check). RxAudit restricts vision extraction to a single pass and executes all 153-drug matching, GST normalization, and 4-probe canary verifications in pure TypeScript (< 5ms CPU time, ~$0.00012\text{ Wh}$ per audit), reducing per-audit compute energy by 99.99%.
  • Pre-MR Deterministic Ambiguity Triage (plan Stage Compute Avoidance): Executing scripts/triage-nppa-update.ts (< 10ms CPU time) in Stage 1 before allowing coding agents to run multi-turn LLM reasoning loops and full container builds on incomplete issues avoids ~$18.5\text{ Wh}$ (~$8.1\text{ gCO}_2\text{eq}$) of wasted compute per blocked/ambiguous issue.
  • Slim Multi-Stage Container & Scale-to-Zero Knative ($M$ & Idle $E$): Next.js 15 output: "standalone" on node:22-alpine + a 223 KB bundled CJS MCP server paired with Cloud Run minScale: 0 eliminates idle server emissions between bill audits.

Built With

  • amazon-bedrock
  • cyclonedx
  • docker
  • gitlab
  • gitlab-ci-cd
  • gitlab-duo
  • google-cloud-run
  • mcp
  • model-context-protocol
  • nextjs
  • node.js
  • react
  • tailwindcss
  • typescript
  • vitest
  • zod
Share this project:

Updates

Submission history