Skip to content

deploygov: gate and confirm-execution honour linked query requests #1032

Description

@babltiga

Part of #1029 (linked deployment changes epic — read it first for the big picture). Needs #1031.

Goal

Make the links mean something. The fail-closed gate additionally requires every linked query to be
APPROVED or EXECUTED; confirm-execution gains an optional execute_linked_queries=true that
runs the still-APPROVED links in sequence_order through the normal query execution path before
the deployment becomes EXECUTED; the first failed link marks the deployment FAILED and leaves
the rest untouched.

Today DefaultDeploymentGateService.releasable(status, frozen, scheduledFor, now) (:162-168)
is the one pure function, evaluate (:172-192) wraps the freeze lookup fail-closed, and
confirmExecution (:101-131) is @Transactional, moves APPROVED → EXECUTED through
DeploymentRequestStateService.apply and audits DEPLOYMENT_EXECUTED with trigger=pipeline.
DefaultDeploymentSimulationService replays the same function (docs/18:502).

Design constraints agreed on the epic:

  • deploygov must not import workflow — workflow/internal/DeploymentBreakGlassReviewListener.java:3
    already imports deploygov.events, so the reverse arrow is a cycle. Execution is reached
    through a new core.api interface that workflow implements (precedent:
    core.api.SessionRevocationService ← security/internal/DefaultSessionRevocationService.java).
  • Not a copy of requestgroups/internal/GroupExecutionService.runQuery (:148-179): that
    calls proxy.api.QueryExecutor.execute(new QueryExecutionRequest(...)) (:168) on a group
    item and audits REQUEST_GROUP_EXECUTED (:204-210). A linked item is a query_requests
    row, which must move to EXECUTED with QUERY_EXECUTED, results and QueryExecutedEvent —
    only DefaultQueryLifecycleService.doExecute (:273-275) does that.
  • No rollback. First failure stops; audited as a system row (null actor, trigger=linked_query),
    the deploygov: deployment governance — notifications & audit fan-out #695 convention at DeploymentRequestStateService.java:119-122. deploygov: DeploymentVerificationJob — window, sampling and breach → FAILED #1025 adds
    trigger=verification the same way.
  • deploygov: gate requires an approved ticket on change_ticket_required environments #993 extends releasable(...) for change tickets — sequence the PRs; both are additive booleans.

Steps

  1. docs/04-api-spec.md — document first (:7775): linked_queries_ready and
    linked_queries on the gate response, the optional confirm-execution body
    { "execute_linked_queries": true }, and the new 409 reason.
  2. core/api/LinkedQueryExecutionService.java (new) — QueryStatus executeLinked(UUID queryRequestId, String trigger): runs an APPROVED query as its submitter, returns the
    resulting EXECUTED / FAILED; returns the current status untouched when the row is already
    EXECUTED (idempotent) and throws core.api.QueryRequestNotFoundException when gone. JDK +
    project types only.
  3. workflow/internal/DefaultLinkedQueryExecutionService.java (new) — delegates to
    DefaultQueryLifecycleService.doExecute(snapshot, snapshot.submittedByUserId(), trigger, false, AuditAction.QUERY_EXECUTED), exactly as executeScheduled (:166-175) does with
    "scheduled"; doExecute is private today — widen it to package-private or expose a small
    package-private entry on the lifecycle service rather than duplicating the body.
  4. Gate — releasable(QueryStatus, boolean frozen, Instant scheduledFor, Instant now, boolean linkedQueriesReady); evaluate computes linkedQueriesReady inside its existing try (every
    link APPROVED or EXECUTED via QueryRequestLookupService.findById; a missing row counts as
    not ready), so any lookup error still answers not-releasable. GateEvaluation and
    deploygov/api/DeploymentGateView gain linkedQueriesReady and List<DeploymentLinkedQueryView> linkedQueries; DeploymentGateResponse renders them. Update the call in
    DefaultDeploymentSimulationService and add a LINKED_QUERIES DeploymentDecisionStepKind
    step to the trace so the simulator explains a held gate.
  5. confirm-execution — new optional body record ConfirmDeploymentExecutionRequest(Boolean executeLinkedQueries) in internal/web/ (@RequestBody(required = false); boxed, since CI
    payloads omit it). DeploymentGateService.confirmExecution gains the flag. Restructure so the
    linked run happens outside the transaction: a non-transactional orchestration method
    performs require + requireActor + the releasable check, runs the links, then calls the
    existing transactional APPROVED → EXECUTED write.
    • Serialize concurrent confirmations per request with scheduling.api.DistributedLockService
      (deploygov may import scheduling.api; ai already does) so two CI retries cannot run the
      same migration twice; a second caller that finds the row EXECUTED returns it (idempotent,
      as today at :107-110).
    • Without the flag, an APPROVED link is not executed and the gate contract is unchanged: the
      pipeline is expected to have run it, or to have chosen not to.
    • With the flag, iterate links in sequence_order: skip EXECUTED; call executeLinked(id, "linked_query"); on FAILED or any RuntimeException, stateService.apply(request, FAILED) (allowed, :59), audit DEPLOYMENT_EXECUTED-sibling action
      DEPLOYMENT_LINKED_QUERY_FAILED (new AuditAction value; audit_log.action is
      VARCHAR(100), no migration) with null actor and Map.of("trigger", "linked_query", "query_request_id", …, "sequence_order", …), stop, and throw
      DeploymentLinkedQueryFailedException → 409 DEPLOYMENT_LINKED_QUERY_FAILED. Remaining
      links stay as they are.
    • On success, the existing DEPLOYMENT_EXECUTED row gains linked_queries_executed (count).
  6. i18n — the new exception detail in all six messages*.properties.

Acceptance criteria

  • DefaultDeploymentGateServiceTest: releasable truth table with the new parameter; a
    PENDING_REVIEW link holds the gate; a lookup exception answers not-releasable; confirm without
    the flag ignores APPROVED links; confirm with the flag executes in order, skips EXECUTED,
    stops at the first FAILED, marks the deployment FAILED, writes the system audit row, and
    leaves later links APPROVED; a redelivered confirm after success is idempotent.
  • DefaultLinkedQueryExecutionServiceTest per .claude/patterns/backend-test-parity.md, plus
    DefaultQueryLifecycleServiceTest proving trigger=linked_query lands in the audit metadata.
  • DeploymentGateFlowIntegrationTest extended end to end: trigger with two approved DDL links →
    gate held while one is PENDING_REVIEW → approve it → releasable → confirm with the flag →
    both queries EXECUTED, deployment EXECUTED; and the failing variant.
  • DefaultDeploymentSimulationServiceTest covers the new trace step.
  • ApplicationModulesTest proves no deploygov → workflow edge; ApiPackageDependencyTest,
    MessagesParityTest pass; coverage ≥ 90 / 80.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions