Constitution
The constitution is the Rego policy TruStacks writes and every proposal is
judged against. It is Apache 2.0, lives in the public
trustacks-policy
repository, and is published as a signed OCI artifact. Free at all tiers.
The runner evaluates it with OPA inside your cluster, after the DevOps Engineer emits a proposal and before anything is written to your platform repo. A proposal with any deny never becomes a pull request.
The rules
Twenty-four rules in three kinds. The kind decides what a rule can do. Eight of them apply only to ECS Fargate targets (Beta), and four only to Kubernetes targets; see Which targets a rule applies to.
| Kind | Can refuse a proposal | Feeds the maturity score |
|---|---|---|
| Gate | Yes | No |
| Behavior | Yes | Yes, as a practice row |
| Posture | No, never | Yes, as a tooling row |
Gate rules
Is the artifact the right shape, in the right place?
| Rule ID | What it checks |
|---|---|
proposal.allowed_paths | Every file lives under gitops/, argo-apps/, or .github/workflows/. |
proposal.no_path_traversal | No path is absolute or contains a .. segment. |
proposal.has_workflow | The proposal includes at least one .github/workflows/*.yaml file. |
proposal.has_helm_chart | Kubernetes, Helm renderer: at least one Chart.yaml under gitops/. |
proposal.has_kustomization | Kubernetes, Kustomize renderer: at least one kustomization.yaml under gitops/. |
proposal.has_argocd_application | Kubernetes: at least one argo-apps/argo-apps-<cluster>/<service>-application.yaml. |
argocd.repoURL_is_canonical | Every ArgoCD Application’s spec.source.repoURL equals your platform repo URL. |
proposal.has_ecs_task_definition | ECS Fargate: a gitops/<application>/<target>/<service>/task-definition.json. |
proposal.has_ecs_deploy_workflow | ECS Fargate: a workflow that deploys the task definition with aws-actions/amazon-ecs-deploy-task-definition. |
Only the renderer check that matches your Application’s renderer applies, and only on a Kubernetes target. A Fargate target has no renderer.
Behavior rules
Is the artifact doing the right thing? Each one maps to a practice key in your environment profile.
| Rule ID | What it checks | Practice |
|---|---|---|
practice.workflow_has_test_step | Every CI workflow has a run: step that runs tests: pytest, npm test, yarn test, pnpm test, dotnet test, go test, or a Maven test/verify or Gradle test/check/build. | tests_in_ci |
practice.workflow_has_lint_step | Every CI workflow has a lint or static-analysis step: ruff, eslint, biome, golangci-lint, go vet, staticcheck, dotnet format, checkstyle, spotless, spotbugs, pmd, or -Dlint. | linting_in_ci |
practice.argocd_prod_requires_manual_sync | Kubernetes: when the proposal’s target cluster has tier prod, its ArgoCD Application has no spec.syncPolicy.automated, whatever the cluster is named. Runners older than 0.2.14-rc25 send no tier, so the rule falls back to argo-apps/argo-apps-prod/ (a cluster named prod). | manual_promotion_to_prod |
practice.dockerfile_runs_as_nonroot | Every Dockerfile in the proposal has a USER that is not root or 0. | non_root_containers |
practice.workflow_pins_action_versions | Every third-party uses: pins a full 40-character commit SHA. actions/* and local ./ actions are exempt. | supply_chain_pinning |
practice.ecs_task_runs_as_nonroot | ECS Fargate: every container sets user, and it is not root, 0, root:* or 0:*. (AWS Security Hub ECS.20) | non_root_containers |
practice.ecs_task_readonly_root_filesystem | ECS Fargate: every container sets readonlyRootFilesystem: true, spelled exactly that way. (ECS.5) | none yet |
practice.ecs_task_secrets_not_in_environment | ECS Fargate: no environment entry whose name or value looks like a credential, and every secrets[].valueFrom is a full Secrets Manager or SSM ARN. (Stricter than ECS.8) | none yet |
practice.ecs_task_image_is_pinned | ECS Fargate: every image has a digest or a non-floating tag (not latest, main, prod and the like, and not untagged). A no-floating-tags check, not a pin: see below. | none yet |
practice.ecs_task_configures_logging | ECS Fargate: every container sets logConfiguration.logDriver. (ECS.9) | none yet |
practice.workflow_prod_deploy_requires_environment | ECS Fargate, prod-tier target: every job that runs amazon-ecs-deploy-task-definition declares a non-empty environment:. | none, deliberately |
The test-step and lint-step rules judge CI workflows. A Fargate deploy workflow applies an artifact and has no suite to run, so it is skipped; the CI workflow beside it is still judged.
Why most Fargate rules score nothing yet. A practice key is a row on
every customer’s score. Adding one that only Fargate can check would add a
row Kubernetes customers could only self-attest, and lower their coverage
for a check that does not apply to them. So these rules refuse bad
Fargate proposals today and score nothing. ecs_task_runs_as_nonroot is
the exception: its intent is the same as the Dockerfile rule’s and it is
enforced in the artifact, so it feeds non_root_containers.
Why the prod-environment rule never scores. It proves the deploy job names a GitHub environment. Whether that environment has required reviewers is a repository setting TruStacks does not read, and on private repositories it depends on your GitHub plan. If the rule’s silence counted as “a human approves promotion to prod: verified”, the score would claim something nobody checked. For a Fargate Application, that practice can only be claimed, by your attestation.
Tags are not image identity. A commit-SHA tag can be overwritten by
anyone who can push to your registry, and ECS resolves the tag when it
deploys. ecs_task_image_is_pinned guarantees there is no floating tag. It
does not guarantee the image that runs is the one that was reviewed. Until
deploys pin a digest, that guarantee does not exist.
Which targets a rule applies to
Every rule declares the target kinds it applies to in its metadata
(target_kinds), beside the tiers it applies to (tier_scope). A rule
without target_kinds applies to every kind.
target_kinds | Rules |
|---|---|
kubernetes | proposal.has_helm_chart, proposal.has_kustomization, proposal.has_argocd_application, practice.argocd_prod_requires_manual_sync |
ecs-fargate | proposal.has_ecs_task_definition, proposal.has_ecs_deploy_workflow, the five practice.ecs_task_* rules, practice.workflow_prod_deploy_requires_environment |
| any | everything else |
argocd.repoURL_is_canonical declares no kind, but it only inspects ArgoCD
Applications, so it never fires on a Fargate proposal. The runner tells the
policy which kind and tier a proposal targets (input.context.target). A
target with no kind is Kubernetes, so policy judges older runners exactly as
before. A policy bundle older than 1.3.0 knows nothing about Fargate and
refuses a Fargate proposal for having no ArgoCD Application: it fails
closed.
Posture rules
Do you run the tools? These never refuse a proposal. A missing scanner is a gap in your posture, not a broken artifact, so it shows up in your gap report and your score instead of blocking delivery.
| Rule ID | Category it scores |
|---|---|
posture.image_scanning_declared | image_scanning |
posture.sast_sca_declared | sast_sca |
posture.secret_scanning_declared | secret_scanning |
posture.sbom_signing_declared | sbom_signing |
The naming rule
constitution.overlay_rule_naming checks the IDs of your own overlay rules
against the rule-naming standard. It
judges overlay rule metadata, not proposals.
The Rules page (#/rules) lists every rule your runner reports, with its
description, tooling categories, and Rego source. Filter by Structural
(gate) or Behaviors.
How the score uses the rules
Two rule fields drive the maturity score:
required_tooling_categoriesputs a tooling row on the scorecard. Today that is six categories:ci_cd_platform(fromproposal.has_workflow),gitops_controller(fromproposal.has_argocd_application), and the four posture categories.practice_dimensionsputs a practice row on the scorecard: the five practice keys above. Two rules feednon_root_containers(the Dockerfile rule and the ECS task rule); the other Fargate rules feed none yet.
Gate rules with neither field are enforcement only. They never move your score.
How the policy reaches your runner
The constitution is published from the trustacks-policy repository.
| Artifact | ghcr.io/trustacks/policy/constitution |
| Current version | 1.3.0, also tagged 1 and latest |
| Signed by | https://github.com/TruStacks/trustacks-policy/.github/workflows/publish.yml@refs/tags/v<version> |
| Signing | Sigstore keyless, Rekor transparency log entry per release |
Hosted runners follow the :1 tag. Fixes in the 1.x line reach your runner
on its next start, without a runner upgrade. A 2.0 that needs a newer runner
never arrives on a tag your runner follows.
At start, the runner’s load-policy-bundle init container resolves :1 to a
digest, pulls that digest, and verifies its signature. If anything fails it
falls back to the copy of the constitution baked into the runner image and
reports why. See Policy in force.
Runner versions. Signed-policy pull for hosted runners ships in runner
0.2.14-rc24 and later, which are pre-releases; no stable release includes
it yet. Earlier runners, including 0.2.13, enforce the
copy baked into the runner image and do not verify a signature at start.
Policy versions independently of the product. The constitution is on the 1.x line; runner and control-plane releases are on 0.2.x. See Versioning.
Verify the policy yourself:
cosign verify \
--certificate-identity-regexp '^https://github\.com/TruStacks/trustacks-policy/\.github/workflows/publish\.yml@refs/tags/v.*$' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
ghcr.io/trustacks/policy/constitution:1Your own rules
Overlay rules let you add your own reasons to refuse a proposal. Their denies
are unioned into the constitution’s (data.proposal.deny), so an overlay can
only add refusals. It has no way to cancel a constitution deny. That holds by
how the policy is assembled, not because a tool proves it.
What works today:
- Drafting. Type
/rule new <id>in the Workspace chat and the Coordinator drafts a rule grounded in your environment profile. Drafts are not saved; copy or download them. - Authoring and testing.
trustacks rule new,test, andlintwork on a local checkout. See the Rule CLI.
No delivery path to hosted runners yet. trustacks rule sign publishes
to a local development registry with a local key, and the hosted runner chart
does not load overlay bundles. Per-customer signing keys are not built. Until
they are, overlay rules run only on local and self-hosted setups.
What is not in the constitution
- Rules about approvals, review counts, or who may merge. Merge control belongs to your git provider’s branch protection.
- Rules about credentials, data residency, or citations.
- Waivers. There is no waiver mechanism.
- SOC 2, HIPAA, PCI, or FedRAMP rules. See Specialist Packs.
Where to go next
- Maturity scoring · how rules become a score
- Policy in force · what your runner is actually enforcing
- Proposal lifecycle · where the gate sits
- Supply-chain verification · every artifact and its signing identity