MDLC build dossier · v0.3.3
Every gate failed it first.Then every gate passed.
ThreatWatch is a multi-tenant security operations platform: signed event ingest, detection and correlation on Temporal, alert triage, a live dashboard and threat map. MDLC built it end to end from a one-page blueprint. The reviewer, the security auditor, the design scorer and the web baseline each sent it back at least once. This page is the record of what they caught and how each failure became a pass.
Gate ledger
Twenty-three runs. Fifteen sent it back.
Each square is one run of one gate, in the order it happened. A red square sent the build back. The bar a gate measures against never moved: the build changed until it cleared. Select any square to see what that run found.
What the gates caught
Recent alerts, from the build itself.
Every one of these was written, committed, and passing its own tests when a gate flagged it. None of them would have shown up by running the app and clicking around.
- High Runtime images were built on stale bases with fixable critical and high OS CVEs.Trivy found 9 critical and 108 high in the API and worker images. Rebuilt on patched bases with the npm CLI stripped out; the unfixable upstream residue is reasoned in ADR-028. Security S1-3Resolved
- Medium Outbound webhooks checked the destination IP, then connected to whatever DNS said next.A DNS-rebinding window let a customer webhook reach an internal address after passing the SSRF guard. Fixed by pinning the connection to the validated IP, then a reviewer insisted that pinned path get its own test. Security S1-1 · R2-4Resolved
- Medium
Sign-in, registration and account forms had no
method="post".Before JavaScript loads, a submit would put the password in the URL, where it lands in history and proxy logs. A live probe confirmed the server accepted such URLs. Security S1-2Resolved - Fail The fix for duplicate requests broke duplicate requests.Round 1 caught concurrent requests with the same Idempotency-Key both running. The fix dropped the stored result when a client disconnected, so a retry ran the side effect again. Round 2 caught the regression; it was reverted and re-fixed. Reviewer R1-8 → R2-1Resolved
- Fail A crafted pagination cursor returned a 500 on nine list endpoints.Now a 400 with a field error. The next round found five more lists that still ignored a bad cursor silently. Reviewer R1-2 · R2-3Resolved
- Fail Scheduled-job tests checked what the job returned, not what it did.Five of seven Temporal job tests asserted returned counts. The invariant requires asserting the effect in the database. They were rewritten to drive each job per organization and check the rows. Reviewer R1-1 · INV-11Resolved
- Fail The alert list promised live updates and polled every 20 seconds instead.The WebSocket hook existed, but only the dashboard used it. Reviewer R1-3Resolved
- ✗ Design The integration-health tile rendered every integration: a page 14,708 px tall on a phone.Now the eight least healthy, with a link to the rest. The same pass caught IP addresses split mid-octet and table headers that had drifted from the rest of the app. Design score 1Resolved
- Fail Twenty-six loading skeletons used an ARIA label on an element that cannot have one.Surfaced only when a baseline run happened to snapshot the dashboard mid-load. Fixed across all 26, and reviewed again in round 4. Web baseline 15:36 · RB-7Resolved
Held, not waived
The floor was 80. It stayed 80.
The dashboard's Lighthouse performance score has a floor of 80. Six measurements came in under it, several on a host loaded by unrelated work. None was accepted or explained away. Each was re-measured on unchanged code.
The reruns kept surfacing real defects in between: a hydration mismatch, a pulse animation that ignored reduced motion, the skeleton ARIA bug. Those were fixed at the root. The gate passed at 81, with accessibility 100 on both the landing page and the dashboard.
--full runs, 2026-10-05 UTC. Only runs that recorded a dashboard score are shown.Governance
Anything touching auth waited for a human.
Every commit was classified by what it changed and written to an append-only ledger: 40 decision records over 23 changes. Eleven changes cleared automatically. Twelve were held for a person because they touched authentication, authorization, the security boundary or infrastructure. Nothing was blocked.
At the end, the build tried to commit the ledger itself. The guard refused it as audit tampering, so the last commit of that file is a human's to make.
{"id":"GD-0001","change":"F-1", "category":["api","application","authentication", … "security-boundary","tests","unknown"], "outcome":"HUMAN_APPROVAL", "reason":"minimum for project authentication+ dependencies+infrastructure+security-boundary+unknown"} {"id":"GD-0030","change":"F-1","supersedes":"GD-0001", "outcome":"AUTO_APPROVE","approver":"human", "reason":"approved by human"}
The product
Twenty-nine screens, measured at three widths in two themes.
Every screen below was captured from the running stack. Each one was checked at 375, 768 and 1280 px in light and dark: 145 cells for layout overflow, contrast, keyboard focus, axe, console errors and Core Web Vitals.
ThreatWatch
- Server tests
- 403 / 403
- Web component tests
- 15 / 15
- End-to-end
- 42 / 42
- Smoke, live stack
- 24 / 24
- Coverage (floor 70%)
- 93.29%
- Invariants
- 43
- machine-proven
- 42
- manual, verdict
- 1 PASS
- Compliance
- 50✓ 1⚠ 0✗
- Baseline cells
- 145 / 145
- Lighthouse a11y
- 100 / 100
- Screens
- 29
- Public endpoints
- 78
- ADRs
- 32
- Commits
- 30
- Active time
- 17.3 h
- Assistant turns
- 6,289
- Output tokens
- 4.1 M
- Cache-read tokens
- 652.6 M
Honest posture
What this build does not claim.
It runs locally
The whole stack ships as Docker Compose behind Caddy TLS on localhost:9443. There is no public deployment of this build.
Payments are deferred
The blueprint asked for them; no workflow charges money, so ADR-006 defers them and keeps the money invariant in place for later.
Scale is not measured
Built for the small tier: one API, one worker, in-memory rate limits. Availability and horizontal-scale metrics are recorded as not measured.
Four low findings remain
Round 4 passed with four LOW advisories, none correctness-class, such as a search count label that screen readers skip. One compliance item is ⚠: volume encryption is left to the deploy target.
One drift line waits on a person
The drift gate reads 41 of 42 because the governance ledger is uncommitted, and committing it is a human action by design.
Evidence pack
Read it in the order it was written.
| File | What it is |
|---|---|
| RESEARCH.md | The blueprint: product vision, 12 key workflows, threat model, market research, GO decision. |
| ARCHITECTURE.md · diagram | System design and the 43 invariants, each tied to the proof that enforces it. |
| SPEC.md | 29 screens, 78 endpoints, workflows W1 to W12 and the error contract. |
| DECISIONS.md | 32 ADRs, including every accepted residual and why. |
| Reviewer rounds | Five independent review reports, verbatim, from round 1 FAIL to round 4 PASS. |
| Security passes | Three audit passes: scanners, live probes and manual review of auth, ingest, SSRF and outboxes. |
| Design scores | Three design-rubric scorings of all 29 screens, from 7 ✗ to 0 ✗. |
| COMPLIANCE.md | Control mapping with evidence: 50 ✓, 1 ⚠, 0 ✗. |
| GOVERNANCE-DECISIONS.jsonl | The append-only decision ledger: 40 records, 12 human approvals. |
| REPORT.md | The full build report: every gate, every run, smoke log and usage receipt. |


