Skip to Content
ReferenceProposal lifecycle

Proposal lifecycle

A proposal is the DevOps Engineer’s answer to “how should this service be built and deployed?”, turned into files for your platform repo. This page follows one from the button to a running pod.

Analysis ──▶ Promote to proposal ──▶ DevOps Engineer emits files │ ▼ 1. Pre-emit validation ── fail ──▶ stop, no PR │ ▼ 2. Constitution gate (OPA) ── deny ──▶ stop, no PR │ ▼ 3. Advisory gap check (never blocks) │ ▼ 4. Pull request on your platform repo │ you review and merge ▼ 5. Merge detected │ you sync in ArgoCD ▼ 6. Deploy observed (optional) │ ▼ 7. Next commit: tag promotion proposal

Every step appears on the proposal’s timeline (#/proposals/<id>).

The diagram is for a Kubernetes target. For an ECS Fargate target (Beta), merging the pull request is the deploy and there is no ArgoCD sync or deploy observation yet; see ECS Fargate targets.

proposal detail timeline showing artifact validation, policy check, gap check, and pull request opened

Before: analysis

On a service, Run analysis starts the Code Reviewer. It clones the service repo in your runner and reports framework, language, runtime, dependencies, entrypoints, and whether the repo has tests, a Dockerfile, and CI. When it finishes, Promote to proposal starts the DevOps Engineer.

The service must belong to an Application with at least one deployment target (a target cluster in the app). The proposal is built for the Application’s first target: its name, its tier (dev, uat or prod) and its kind (kubernetes or ecs-fargate). The kind decides which files the DevOps Engineer can emit at all: a Fargate run is never offered the Helm, Kustomize or ArgoCD tools, and a Kubernetes run is never offered the task definition tool.

The DevOps Engineer emits

Grounded in the matching framework pack, your environment profile, and what is already in your platform repo, it emits the files described in Platform repo layout. The image reference and tag are supplied to it (your registry, your repo name, the commit it analyzed); it does not invent them.

1. Pre-emit validation

Does the output actually work? Each check, by name on the timeline:

CheckFails when
chart_rendershelm template fails on the chart
overlay_buildskustomize build fails on an overlay
workflow_parsesA workflow or ArgoCD Application is not valid YAML
action_sourcedA uses: action is not pinned to a full commit SHA, or does not come from a curated pack
k8s_identifierA name or label breaks Kubernetes naming rules (RFC 1123), or a Helm release name is too long
image_referenceThe image repository is not the one TruStacks supplied (in Helm values or an ECS task definition)
task_definition_validECS Fargate: the task definition is not a JSON object; a required field is missing; it is not FARGATE with awsvpc; container names repeat; cpu and memory are not a valid Fargate pair; a port is out of range or hostPort differs from containerPort; privileged is set; or a key is unknown or mis-cased
binding_validECS Fargate: deploy-target.json does not have exactly the expected keys, or a value that is set does not match its format

Mis-cased keys are refused for a reason. The AWS SDK inside the deploy action silently drops a task-definition key it does not recognise. So readOnlyRootFilesystem: true (capital O) registers a writable root filesystem and looks right in review. The check names the key and its correct spelling. Unit forms such as "1 vCPU" and "2 GB" are normalised, not refused, and the 32 vCPU sizes (61440, 122880 and 249856 MiB) are accepted.

What cannot be proven offline, and is not claimed: that the image exists, that your roles, cluster and service exist, that the IAM is right, that the CPU architecture matches the image, and that the service runs on Fargate. The deploy waits for the service to stabilise, so these fail visibly at deploy time rather than hanging.

A check that could not run counts as a failure. If a tool is missing or a pack did not load, the proposal stops as unverified rather than passing unchecked. On failure no pull request is opened, and the timeline shows the exact error.

2. Constitution gate

The runner evaluates the constitution (and any overlay it loaded) against the files with OPA, inside your cluster. Any deny stops the proposal. Overlay denies only add to the constitution’s; from runner 0.2.14 an overlay that reaches outside data.overlay is rejected and the proposal gets no verdict (see your own rules). The timeline lists each rule that fired and why. Nothing is written to your platform repo.

Denials count toward Policy in force on the Overview: how many proposals were checked and which rules refused work, over a window.

3. Advisory gap check

The runner runs the constitution’s gap query against your environment profile and lists tooling categories you have not declared. Advisory only: it never blocks the pull request.

4. Pull request

The runner pushes a branch to your platform repo and opens a pull request. The body explains what changed and why, cites the rules and profile entries behind each choice, and lists preservation notes for anything of yours it kept.

TruStacks never merges. You review and merge, under your branch protection.

5. Merge detection

Hosted TruStacks polls GitHub for the pull request’s state. No webhook to configure. On merge it records the merge commit, which is what ArgoCD will sync. The proposal shows open, merged, or closed.

6. Deploy observed (optional)

Applications are emitted with manual sync, so someone syncs in ArgoCD after the merge. If the runner’s observer is on (observer.enabled=true, see Runner Helm chart), the runner watches the ArgoCD Application read-only and reports when it is Synced and Healthy at the merged commit. The Application’s deploy-status chip shows the result.

7. Image tag promotion

Once your service builds images, each new commit on the service repo’s default branch needs to reach the platform repo as a new image.tag. Hosted TruStacks polls the service repo’s default branch; when it moves, the runner opens a new proposal that bumps the tag. It goes through steps 1 to 4 like any other proposal, so every tag change in production has a pull request, a verdict, and an approver.

⚠️

The tag can be proposed before the image exists. If you merge before CI finishes pushing the image, the pod waits in ImagePullBackOff until the image lands, then starts. If the image never lands (CI broken, registry credentials revoked), it waits indefinitely. The deploy-status chip shows it; nothing alerts you yet.

The tag is the service commit SHA. If the deployed tag is older than the newest commit, that is usually an open promotion pull request waiting for review.

ECS Fargate targets (Beta)

Steps 1 to 4 are the same. After that:

  • Merging deploys. The deploy workflow runs on the push to main that the merge makes, applies the committed task definition, and waits for the ECS service to stabilise. There is no Sync step, so the pull request review is the human approval. On a prod-tier target the deploy job must name a GitHub environment; required reviewers on that environment, where your GitHub plan offers them, add a second approval.
  • The first merge’s deploy fails until you fill in deploy-target.json. TruStacks emits it blank. The failed run names the missing key.
  • No deploy observation yet. The runner’s observer watches ArgoCD only. Check the deploy workflow’s run in GitHub Actions.
  • Tag promotion works the same way. A new service commit produces a proposal with the new commit-SHA tag in the task definition, and merging it deploys. If you merge before CI has pushed the image, the deploy job fails visibly instead of waiting.
  • Rollback is a revert pull request.

See ECS Fargate layout for the files and your AWS prerequisites.

Where to go next

Last updated on