Skip to content

feat(AF-984): deployment DORA metrics service and read API - #1188

Merged
babltiga merged 2 commits into
mainfrom
feature/AF-984-deploygov-metrics-service
Oct 23, 2026
Merged

babltiga merged 2 commits into
mainfrom
feature/AF-984-deploygov-metrics-service

Conversation

@babltiga

Copy link
Copy Markdown
Contributor

Closes #984 (part of epic #983).

Summary

Read-time DORA and approval-SLA metrics for deployment governance. This PR adds the contract that #986 (compliance export) and #987 (page) build on.

  • GET /api/v1/deployment-pipelines/{id}/metrics and GET /api/v1/deployment-metrics
    • Query parameters: from / to (window [from, to), trailing 30 days by default, at most 366 days → 400 DEPLOYMENT_METRICS_PERIOD_INVALID) and environment (case-insensitive name). The org-wide endpoint also takes pipeline_id.
    • Visibility mirrors the deploygov: deployment governance — version inventory & drift API #742 version inventory. The per-pipeline endpoint answers 404, never 403. Callers with DEPLOYMENT_PIPELINE_MANAGE, DEPLOYMENT_REVIEW or QUERY_ADMIN, plus effective can_trigger holders, can read it. The org-wide endpoint accepts only the three functional permissions.
    • Returns deployment frequency, lead time (p50/p95 + source), change failure rate, time to restore, approval latency, timeout rate, break-glass count, and a zero-filled UTC daily series. Definitions are verbatim from epic decision 2.
  • deploygov.api.DeploymentMetricsService (JDK-only contract). DefaultDeploymentMetricsService and a native-SQL DeploymentMetricsRepositoryImpl do the work, with no metrics table and no job.
    • Enum values use explicit casts. Day buckets use AT TIME ZONE 'UTC'. Every value is a bound parameter; only constant scope fragments are concatenated.
  • V203
    • Adds deployment_requests.review_started_at, nullable and not backfilled. DeploymentRequestStateService stamps it on entry to PENDING_REVIEW.
    • Adds af_try_parse_iso_instant(text). It accepts only an ISO-8601 instant with an explicit offset, and works on PostgreSQL 15 and later (pg_input_is_valid would need 16).
  • Moved the per-pipeline visibility check into a shared package-private DeploymentPipelineVisibility, used by the inventory and the metrics.

Docs

  • docs/04-api-spec.md: new "Deployment metrics" section (parameters, response, definitions table). The trigger endpoint's metadata description now names the commit_timestamp key.
  • docs/03-data-model.md: review_started_at.
  • docs/18-deployment-governance.md: new §10a.
  • CLAUDE.md: deploygov module line.
  • No website/, README or help-corpus change. There is no UI or operator setting yet, and deploygov: metrics documentation sweep #988 is the epic's docs sweep. No frontend, so no e2e spec and no screenshots ("No UI change — no screenshots").

Deviations from the issue text

  • DeploymentMetricsControllerTest is a plain unit test. The repo has no @WebMvcTest precedent; its controller tests follow the DeploymentVersionInventoryControllerTest shape. The 403 (org-wide), 404 (per-pipeline) and 400 (period) responses are checked over real HTTP in DeploymentMetricsIntegrationTest, using MockMvc with API keys.
  • The integration test seeds most rows through repositories with explicit timestamps, so every percentile can be computed by hand. Two requests go through the real DeploymentRequestStateService:
    • one checks that the stamp persists under ddl-auto=validate;
    • one checks that a request decided through PENDING_AI → PENDING_REVIEW → APPROVED becomes an approval-latency sample.
  • "JVM zone set to a non-UTC offset": the test runs SET LOCAL TIME ZONE 'Pacific/Auckland' inside a transaction. pgjdbc derives the session zone from the JVM zone when a connection opens, so changing the JVM default can't reach connections already in the pool. The test first checks that a bare date_trunc does move the 23:30 UTC deployment to the next day in that session.
  • Approval latency counts only requests a human finished deciding (APPROVED / REJECTED / EXECUTED / FAILED with a decision row). This keeps a partially approved request (still in review, timed out or cancelled) out of the sample, per "only requests decided by a human".
  • Time-to-restore uses a correlated subquery (the earliest later SUCCEEDED execution on the same pipeline + environment) rather than LEAD(...), because the immediately next execution may itself have failed. A success that ran before the failure was reported counts as 0 seconds. Lead time is clamped at 0 the same way.
  • Empty percentiles, rates and source are omitted from the JSON (the global non_null inclusion), not sent as null. The docs say so.

Verification

  • mvn -o -f backend/pom.xml verify -Pcoverage passed: 11475 tests, 0 failures, 0 errors; Spotless and Checkstyle pass. ApplicationModulesTest, ApiPackageDependencyTest and MessagesParityTest pass.

  • JaCoCo coverage:

    Class Lines Branches
    DefaultDeploymentMetricsService 100% 100%
    DeploymentMetricsRepositoryImpl 100% 92.9%
    DeploymentPipelineVisibility 100% 100%
    Controller and response 100% —
    Backend overall 95.2% 84.0%

Review notes

Four reviewers ran before this PR was opened. Every blocker they raised is fixed in the second commit:

  • PG16-only pg_input_is_valid (af-java-reviewer, af-reviewer)
  • the commit_timestamp value could break the percentiles (infinity / now / no offset)
  • partial approvals were counted in approval latency (af-reviewer)
  • the wrong to default in the docs (af-content-reviewer)

Remaining concerns:

  • Approval latency still counts a request that got a partial human approval and was then approved through a change ticket (workflow: map ticket resolution onto a deployment decision #992). The ticket path writes no decision row, so the two cases can't be told apart (af-java-reviewer).
  • The approval query has no time bound on the inner aggregate, so it scans the org's full decision history on each call. A decided_at < :to bound was rejected: it would change which decision counts as a request's "last", and wrongly bring requests decided after the window into it. The tables are small (one row per release) (af-java-reviewer).
  • The "Delivery status" callout at the top of docs/18 doesn't mention §10a yet. Left for the deploygov: metrics documentation sweep #988 sweep (af-content-reviewer).

@github-actions

Copy link
Copy Markdown
Contributor

Backend Test Results

11 475 tests  +59   11 475 ✅ +59   10m 48s ⏱️ - 1m 42s
 1 223 suites + 4        0 💤 ± 0 
 1 223 files   + 4        0 ❌ ± 0 

Results for commit 590839b. ± Comparison against base commit ef62d38.

@github-actions

Copy link
Copy Markdown
Contributor

@babltiga
babltiga merged commit 5a2d2be into main Oct 23, 2026
34 checks passed
@babltiga
babltiga deleted the feature/AF-984-deploygov-metrics-service branch October 23, 2026 11:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

deploygov: DeploymentMetricsService and read API

1 participant