Skip to content

Promote a Recipe

When you finish editing a MasterRecipe in your dev namespace, the Promote button creates a ChangeRequest that will apply the same edit to your prod namespace once a supervisor approves it. The request carries a semantic-diff preview, a free-text reason, and your electronic signature, and the configured backend (direct-apply, git-pr, or the experimental in-cluster-queue seam) realises the change after approval.

This task is the user-facing half of the GitOps-enforcement workflow described in GitOps for automation engineers. The protocol contract lives in the GitOps-enforcement RFC.

Prerequisites

  • A MasterRecipe you have permission to edit in a dev-tier site namespace
  • create permission on changerequests.changecontrol.dcs.io (Engineer or Supervisor role, and the Promote button is hidden when missing)
  • A recent OIDC sign-in — the gateway returns reauthentication_required if your token was issued more than five minutes ago (21 CFR Part 11 §11.200)

Submit a Promote request

  1. In the system app, open Master Recipes for your dev site and click Edit on the recipe you want to promote.
  2. Adjust whatever fields you need (hold time, version, formula, parameters, ...). The form is identical to the one you use to create a recipe. See the recipe authoring reference if you need help with a specific field.
  3. Optionally expand View YAML to peek at the resulting CR YAML so you know exactly what will be committed.
  4. Click Promote... at the bottom of the form.
  5. In the review panel that expands under the form:
    1. Read the Semantic diff. Each entry is rendered in engineer-friendly language ("Mash Hold: hold time 30 min → 45 min"), with no raw YAML lines.
    2. Optionally expand View Git diff for the line-level YAML comparison.
    3. Fill in Reason for change (mandatory, and it ends up in the audit record).
    4. Pick a Backend — leave at (deployment default) unless you have a specific reason to override. The option names the deployment's default where the gateway knows it, so (deployment default — git-pr) tells you what you are accepting.
    5. If the backend is git-pr, read the note under the semantic diff. The diff above it is computed against the live cluster object, so it is exactly the edit you are signing. The git-pr backend diffs the file that declares the recipe on the base branch. The pull request therefore also carries anything the cluster already has and Git does not (an earlier direct-apply change, most often). The two are correct answers to different questions, and the pull request is the wider one.
    6. Confirm the Target namespace (defaults to the same site namespace. Change this to your prod namespace, e.g. site-acme-prod).
    7. Click Submit ChangeRequest & Sign.
  6. The page hops to the Approval Queue so you can see your proposal alongside any other pending requests.

Promote review panel with semantic diff and reason

The headline path runs below: the same edit aimed at the prod namespace, with both panel expands opened on the way, a second request withdrawn by its author, the two-persona handover, and the recipe verified in the prod site afterwards:

M9 on camera: the Target namespace changed to prod, both expands read, the author's Withdraw, the real two-persona handover, and the recipe found in the prod site.

Scripted promotion submits a ChangeRequest manifest through dcs apply -f. The gateway stamps the "proposed" e-signature from your authenticated session, exactly as the UI path does (Engineer or Admin role required):

# changerequest-mash-v2.yaml
apiVersion: changecontrol.dcs.io/v1alpha1
kind: ChangeRequest
metadata:
  name: masterrecipe-mash-v2-ccr-1234
  namespace: site-acme-dev   # authored in dev; targets prod below
spec:
  target:
    apiVersion: recipe.dcs.io/v1alpha1
    kind: MasterRecipe
    namespace: site-acme-prod
    name: mash-v2
  operation: Update
  reason: "Bump hold time per CCR-1234"
  backend: direct-apply
  desiredObject:
    # Full proposed MasterRecipe (apiVersion, kind, metadata, spec).
    apiVersion: recipe.dcs.io/v1alpha1
    kind: MasterRecipe
    metadata:
      name: mash-v2
      namespace: site-acme-prod
    spec: { }
dcs apply -f changerequest-mash-v2.yaml

Leave spec.signatures empty. The gateway stamps the proposed signature server-side so the reconciler can HMAC-verify it, and it rejects manifests that supply their own. Applying a ChangeRequest is create-only: once submitted, act on it via the Approval Queue (or the approve/reject/withdraw endpoints). Do not re-apply it.

The ChangeRequest then sits in Proposed until a supervisor submits an approval signature via the Approval Queue.

Approve a promotion

A different user must approve

Segregation of duties: the user who proposed a ChangeRequest cannot approve it, even with supervisor or admin rights. The gateway rejects a self-approval with 403. Have a second supervisor (or an admin) review and sign.

The workflow above and below, end to end, with the segregation-of-duties rule on camera: the engineer who proposes logs out, and a second person signs the approval.
  1. Click the approval badge in the system app top bar (it appears whenever there are pending ChangeRequests and is hidden when the queue is empty). After submitting from the previous step the page is already on the Approval Queue.
  2. The pending requests are listed with their target, author, reason, age, and current signatures. Click Open on the row you want to review.

Approval Queue list with pending ChangeRequests

  1. The detail page shows the same semantic-diff preview the author saw, the conditions, and the resolution (once it lands).
  2. Click Approve & Sign to stamp the canonical "approved" e-signature. The reconciler picks it up on the next pass and transitions the ChangeRequest from Proposed to Approved. The configured backend then realises the mutation.

ChangeRequest detail with Approve and Sign action

Approving via kubectl is intentionally not supported, because the HMAC-bound signature payload is computed server-side. Use the UI or the gateway HTTP API:

curl -X POST "$GATEWAY/api/v1/changerequests/site-acme-dev/<cr-name>/approve" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"reason":"Reviewed and signed"}'

Withdraw a promotion

If you submitted a promotion in error, click Withdraw on the detail page. Only the original author (or an admin) can withdraw a Proposed ChangeRequest. Once it has reached Applied it is too late to withdraw, and the remedy is a follow-up ChangeRequest that reverts it.

Backend resolution and audit anchors

Every approved ChangeRequest carries a status.resolution block linking back to the source-of-truth artefact:

  • direct-apply populates auditRecordRef. The mutation also writes the standard dcs.io/esig-approved annotation onto the target.
  • git-pr populates gitCommitSHA and gitPullRequestURL once the PR merges. The detail page renders both as clickable links.
  • webhook populates externalRecordID, externalRecordURL and externalRecordState for the record it opened in the external quality system, alongside whatever its delegate backend resolves once that record is approved.
  • in-cluster-queue (experimental — no reference integration) populates auditRecordRef once the deployer-supplied controller advances the phase.
The git-pr backend end to end: approval opens a real PR, the merge realises the change, and the ChangeRequest links back to the commit and PR. Those are the audit anchors above, live.

See ChangeRequest backends for operator-side configuration.

Troubleshooting

  • Promote button is missing. You don't have create permission on changerequests. Ask your platform admin to add you to the Engineer or Supervisor role.
  • "reauthentication_required" error. Your session token is more than five minutes old. The panel closes and offers Re-authenticate, which takes you to the login page and returns you to the page you were on. Repeat the promotion from there.
  • Semantic diff is empty ("No semantic differences detected"). The renderer compared the proposed object against the current cluster state and found no meaningful changes. Your edit is effectively a no-op. This only applies while the ChangeRequest is under review: from Applied onward the detail view renders the diff against the target state recorded at proposal (captioned below the diff), so the approved change stays reconstructable from the record. A ChangeRequest that predates base recording falls back to the live target and says so in the caption.
  • Diff shows "No semantic renderer for kind …; see raw diff." The target kind has no registered semantic-diff renderer, so the panel falls back to a raw structural diff. This is rare, because every engineering kind ships a renderer fed with current state. If you hit it for a first-class kind, it's a bug worth filing.
  • No backend resolves. Either the deployment default backend (gitopsEnforcement.changeRequest.defaultBackend) is misconfigured or your spec.backend override does not match a registered backend. See the operator docs for the registered names.