Rendered from specs/S14-delivery-performance.md — edit it there, not here.
S14 — Delivery Performance (DORA Metrics)
Overview
The maturity scorecard (S12) measures practices — is the right machinery installed and enforced. This spec adds the first outcome measure: the four DORA delivery-performance metrics — deployment frequency, lead time for changes, change failure rate, and time to restore service — computed entirely from git tags and commit history, so they are as observable and backfillable as any other fact in this repository, with no external service.
DORA (DevOps Research and Assessment) is the industry-standard research program behind the Accelerate book and the annual State of DevOps Report; its key finding is that throughput (frequency, lead time) and stability (failure rate, restore time) improve together in healthy teams. This project has no production servers, so a published release is the unit of deployment: the release-please tag → Marketplace publish pipeline is the "deploy", and every metric below is defined against release tags.
Repository-context mapping
| DORA metric | This repository's observable proxy |
|---|---|
| Deployment frequency | cadence of vX.Y.Z release tags over time |
| Lead time for changes | time from a commit's authoring to the release tag that first ships it |
| Change failure rate | share of releases that required a subsequent patch release (a fix:-driven hotfix under release-please) |
| Time to restore service | elapsed time from a failure-causing release to the patch release that fixed it |
These are deliberately proxies, honestly labelled: a semver patch release under Conventional Commits + release-please is fix:-driven, which makes it a clean signal that the preceding release shipped a defect — but a patch can also carry minor non-defect fixes, so the failure rate is an upper-ish bound, not ground truth. The site states this next to the number.
Functional Requirements
S14-SR-01 Deployment frequency MUST be computed from the sequence of release tags: the median number of days between consecutive releases, and a derived human phrasing (e.g. releases per week). It MUST also expose the per-release intervals so a trend is visible.
S14-SR-02 Lead time for changes MUST be computed per release as the median of (release-tag date − commit author date) over the commits first shipped in that release (the commits in
previousTag..thisTag), and reported as the median across all such commits, in days. Per-release medians MUST be exposed for the trend.S14-SR-03 Change failure rate MUST be the fraction of releases classified as failures, where a release is a failure when the next release is a semver patch bump (a hotfix). The classification MUST be derivable from the tag sequence alone (semver comparison), and each failing release MUST be identifiable.
S14-SR-04 Time to restore service MUST be computed for each failure as the elapsed time from the failing release to its fixing patch release, and reported as the median across failures. When there are no failures it is reported as not-applicable, never as zero.
S14-SR-05 Each metric MUST be classified into a DORA performance band (Elite / High / Medium / Low) using published reference thresholds declared once in the generator, and shown with the caveat that those thresholds target continuously-deployed services — a release-based extension is measured against them for reference, not as a target.
S14-SR-06 All metrics MUST be computed deterministically and offline from
gittag and commit metadata plus Conventional Commit types; no network call and no external service. The result MUST be written to a committeddora.json, regenerable withnpm run dora. Because the source is git history itself, regeneration reconstructs the full timeline — there is no separate backfill step and no hand-maintained history file.S14-SR-08
dora.jsonMUST be refreshed when a release happens, not only on a periodic schedule. Every metric here is a pure function of the release-tag set —perWeekdivides by the span between the first and last tag, never by wall-clock now — so the file goes stale at exactly one moment: a new tag. Refreshing it on an unrelated weekly clock while releases land at a median interval of ~1.3 days leaves the published Delivery view stale for most of its life; it sat three releases behind (ending at v0.13.0 while the extension shipped v0.16.0) when this requirement was written.S14-SR-09
npm run dora:checkMUST distinguish "no release tags are visible" from "dora.jsonis stale", and MUST say which. The two are indistinguishable by value — a checkout with no tags computes zero releases, which compares unequal to any committed timeline and so reports staleness — but they demand opposite responses: one is a broken checkout (the shallow-clone hazard S10-SR-15 records, sinceactions/checkoutfetches no tags by default), the other is a missing refresh. A check that cannot see the data it validates MUST NOT claim the data is wrong.S14-SR-07 The docs site MUST present a Delivery view rendering
dora.json: the four metrics as labelled tiles with their bands and the repository-context caveat, plus a per-release trend (lead time and release interval) and a per-release table. It MUST link todora.devfor the metric definitions.
Non-Functional Requirements
- S14-NFR-01 The DORA metrics MUST NOT be folded into the maturity score in this spec:
scripts/maturity-score.mjsMUST NOT readdora.json. Unlike the effort estimates (S13, a judgement), these metrics are observable facts and would be eligible to become a maturity dimension later — but adding an eighth dimension re-weights the overall score, which is a deliberate rubric decision to make on its own, not a side effect of adding the measurement. Until then this is a standalone delivery dashboard. - S14-NFR-02 The generator MUST be plain Node with no third-party dependencies (like the repository's other
scripts/*.mjs), anddora.jsonMUST remain tool-neutral JSON.
Out of Scope
- MTTR for incidents not tied to a release (there is no incident tracker; S05's zero-telemetry stance means the issue tracker is the only ops signal, covered separately if issue-health is ever added).
- Promoting DORA to a maturity dimension (a future decision, per S14-NFR-01).
- The 2024 fifth metric (rework/reliability) — needs an operational reliability signal this project does not yet collect.
Acceptance Criteria
npm run dorawritesdora.jsoncontaining the four metrics, their bands, and a per-release breakdown, computed purely from git — running it twice with no new tags produces identical output.- Cutting a new release tag changes the metrics on the next
npm run dorawith no other edit. - The Delivery page renders the four tiles, the trend, and the per-release table, each labelled with the release-as-deploy caveat and linking to
dora.dev. scripts/maturity-score.mjscontains no reference todora.json, and the maturity score is unaffected by this spec.
Requirement traceability
Status of each requirement in specs/traceability.json. Test coverage is auto-discovered from [ID] tags in test titles and is not listed here.
| Requirement | Status | Implementation | Note |
|---|---|---|---|
S14-NFR-01 | implemented | scripts/maturity-score.mjs, scripts/dora-metrics.mjs | scorer has no reference to dora.json; standalone dashboard |
S14-NFR-02 | implemented | scripts/dora-metrics.mjs | plain Node, no dependencies; tool-neutral JSON |
S14-SR-01 | implemented | scripts/dora-metrics.mjs | median inter-release interval + per-week; per-release intervals exposed |
S14-SR-02 | implemented | scripts/dora-metrics.mjs | per-release median lead time (author→tag) over previousTag..thisTag; seed release excluded; aggregate median |
S14-SR-03 | implemented | scripts/dora-metrics.mjs | failure = next release is a semver patch bump; derivable from tag sequence; failing releases flagged |
S14-SR-04 | implemented | scripts/dora-metrics.mjs | restore = failing release → fixing patch elapsed; median; n/a when no failures |
S14-SR-05 | implemented | scripts/dora-metrics.mjs, docs/delivery/index.md | Elite/High/Medium/Low bands declared once; docs shows the continuously-deployed caveat |
S14-SR-06 | implemented | scripts/dora-metrics.mjs, package.json | offline git-only compute; npm run dora writes dora.json; --check detects staleness |
S14-SR-07 | manual | docs/delivery/index.md, docs/.vitepress/dora.data.ts, docs/.vitepress/theme/DoraMetrics.vue, docs/.vitepress/config.ts | Delivery view: four tiles + bands, lead-time trend, per-release table, dora.dev link |
S14-SR-08 | implemented | .github/workflows/maturity-refresh.yml, .github/workflows/ci.yml, dora.json | dora.json is refreshed by maturity-refresh.yml, triggered bi-weekly (cron 0 9 1,15 * *) and after Release Please, gated on that run having actually tagged a release (a `v*` tag pointing at its head_sha) — success alone fires on every push to main, where release-please usually only opens/updates its release PR, which turned the periodic job into a near-every-merge one. Post-tag rather than inside the release PR: the metrics stay tag-only (S14-SR-06), since dating a staged release from package.json + CHANGELOG would make interval/lead-time/restore figures estimates rather than measurements. Every artefact has a per-artefact due-check so a still-fresh file is not regenerated for its date stamp alone; dora's is tag-based (`dora:check`) rather than elapsed-time, so a release makes it due immediately. All results collect onto one standing branch (chore/refresh-repo-metrics, force-pushed, reusing the open PR) so runs update a single PR. Scheduled runs stand down when a release landed inside the last fortnight. Freshness test compares against the latest visible git tag rather than package.json's version, since a release-please PR bumps the version ahead of tagging — ci.yml's coverage job fetches full tag history (fetch-depth: 0) so that comparison isn't skipped there |
S14-SR-09 | implemented | scripts/dora-metrics.mjs | --check reports a tagless checkout as a checkout problem before comparing for staleness |
Documented in
Documentation sections tagged spec:S14 (or one of its requirement ids) — 157 of ~643 expected words (24%). The expectation comes from this spec's evaluated complexity of 16.1 (11 documentable requirements — 9 SR, 2 NFR — weighted by kind and by each definition's length).
Changes over time
027722c2026-08-04fix(ci): collect metric refreshes into one bi-weekly standing PR851c6af2026-08-04fix(ci): refresh repo metrics inside the release PR, bi-weekly otherwisebb1fb762026-07-26fix(dora): refresh delivery metrics on release, not on an unrelated weekly clock6de34152026-07-20feat(S14): delivery-performance (DORA) metrics + actionlint in CI