Skip to content

Maturity Score Card

A single 0–100 number for how well an application is built, shipped, and run.


What it does

Engineering maturity is usually invisible. Test coverage lives in one tool, vulnerabilities in another, availability in a dashboard, incident response in a spreadsheet. Nobody can answer "is this service in good shape?" without opening six tabs, and nobody can answer "is the organisation improving?" at all.

Maturity Score Card collects those signals from wherever they already exist, scores each one on the same 0–100 scale, and rolls them up per app, per team, and per area.

It is not a CI/CD scorecard. Pipelines are one source of signal among several — and the heaviest scorecard is the one measuring what happens after deploy:

flowchart LR
    B["<b>Build</b><br/>how the app is made"] --> S["<b>Ship</b><br/>what we catch before prod"] --> R["<b>Run</b><br/>how it behaves in prod"]
    B -.->|application · 25%| SC["<b>Maturity Score</b>"]
    S -.->|security · 35%| SC
    R -.->|reliability · 40%| SC
Dimension Scorecard Weight Answers
Build application 25% Is it tested? Instrumented? Does it hold up under load?
Ship security 35% What did we catch before it reached production?
Run reliability 40% Does it stay up? How fast do we notice and recover?

Anything that can be expressed as a number at the end of a job, a query, or a report can feed it — a pipeline step, a nightly cron against Prometheus, a scheduled scan, a manual audit. The service does not care where a value came from.

What it measures today

Fifteen metrics ship in the box. Each has a scoring function that turns a raw payload into 0–100, so a coverage percentage and a vulnerability count end up comparable.

Metric Measures Raw input
sla Availability against your SLO availability_pct
change_failure_rate Share of deploys causing a failure rate_pct
mttd How fast you detect an incident minutes
mttr How fast you recover from one minutes

This is where SLIs and SLOs land. sla takes whatever availability figure your SLI already produces — error-budget-based, uptime-based, or synthetic-probe-based — and downtime is expressed through it rather than as a separate metric. mttd and mttr cover detection and recovery separately, because a team that recovers fast but detects slowly has a very different problem from the reverse.

Metric Measures Raw input
image_scan Vulnerabilities in the container image critical, high, medium
sast Vulnerabilities in the source critical, high, medium
dast Vulnerabilities found against a running instance high, medium
secret_scan Hardcoded credentials — pass/fail found

Counts are weighted by severity, so one critical costs far more than one medium. secret_scan is deliberately binary: a leaked credential is not a matter of degree.

Metric Measures Raw input
unit_coverage Unit test coverage percentage
integration_coverage Integration test coverage percentage
stress_test Error rate, p95 latency and check pass rate under load error_rate, p95_ms, checks_pct
libs_observability Standard telemetry library in use enabled
libs_secrets Standard secrets library in use enabled
health_check Health endpoint exposed enabled
unique_db_user Dedicated database user enabled

The two coverage metrics are scored on different curves — 80% unit coverage and 60% integration coverage both earn full marks, because they are not equally cheap to reach.

See Scorecards for the exact thresholds behind every metric, and the roadmap for what is specified, what is deliberately a dashboard panel instead, and what is not planned.

Tracking problems, not just scoring them

A score tells you where you stand. It does not tell you what to fix, and it does not survive being fixed — once a scan is clean, the finding is gone from the score.

POST /problem/scan-result is the other half. It records concrete findings (a secret in main.tf at line 42) with severity and file-level detail, and keeps them until a later scan reports zero. That gives you a live worklist and a history of how long things stayed broken, and it fires a Slack alert the moment something new appears.

Key properties

  • Partial evaluation. Weights redistribute automatically among the metrics that actually reported. A service with no integration tests is not silently scored as if it had 0% — the metric is simply excluded. Send what you have.
  • Stateless service, persistent state. All state lives in PostgreSQL; the API can be restarted, scaled, or replaced freely.
  • Last value wins. Each (area, team, app, env, scorecard, metric) holds exactly one score, upserted on every submission. No double counting from pipeline retries.
  • Aggregation is unweighted by size. Team scores average their apps, area scores average their teams — so a team with 40 services does not drown out one with 3.

Where the numbers go

Scores are exposed at /metrics in Prometheus format, scraped every 15s, and pre-aggregated into recording rules that Grafana reads.

CI/CD step · scheduled query · scanner · manual audit
    │
    ▼
POST /score              POST /problem/scan-result
    │                           │
    ▼                           ▼
calculate_score()        save problem state
    │                           │
    └──────────┬────────────────┘
               ▼
          PostgreSQL  ◄──── upsert (state persists until next scan)
               │
               ▼
          GET /metrics  ◄──── Prometheus scrapes every 15s
               │
               ▼
          Prometheus  ──── evaluates PromQL recording rules
               │
               ▼
            Grafana
Service Role
FastAPI REST API — scoring + problem intake
PostgreSQL State store — latest score and problem count per app
Prometheus Scrapes /metrics, evaluates the recording rules
Grafana Dashboards — per app, per team, per area

Get started → See the scorecards →