diff --git a/deploy/organization-rulesets/README.md b/deploy/organization-rulesets/README.md index f328391..d4711f7 100644 --- a/deploy/organization-rulesets/README.md +++ b/deploy/organization-rulesets/README.md @@ -41,6 +41,7 @@ verb — e.g. `require-pull-request.yaml`). Repo-scoped rulesets live next door | `require-signed-commits.yaml` | **Require signed commits** (existing, retired) | Observe + Update — retain the disabled record; never create or delete | | `protect-release-tags.yaml` | **Protect release tags** (net-new) | Managed (Create) — block tag delete + force-move + require `v` | | `require-world-at-ruin-trusted-regressions.yaml` | **Require workflow - World at Ruin trusted regressions** (net-new) | Managed (Create) — target only World at Ruin and require the canonical catalogue's trusted regression workflow | +| `require-world-at-ruin-product-regressions.yaml` | **Require workflow - World at Ruin product regressions** (net-new) | Managed (Create) — target only World at Ruin and require its product-owned trusted regression workflow from reviewed `main` | | `require-monorepo-ci-aggregate-contract.yaml` | **Require workflow - Monorepo CI aggregate contract** (net-new) | Managed (Create) — target only monorepo and require the aggregate-execution control from its reviewed `main` | | `require-dotgithub-deploy-guards.yaml` | **Require workflow - .github deploy guards** (net-new) | Managed (Create) — target only this repository and run the `deploy/` release-contract and deletion validators from its reviewed `main` | | `require-go-template-validation.yaml` | **Require workflow - Go template validation** (net-new) | Managed (Create) — target only go-template and require the canonical Go validation workflow from this repository's reviewed `main`, restoring the gate the property-conditioned UI ruleset stopped applying there | @@ -113,10 +114,11 @@ gates; a team audit cannot clear the latter two. that file's header for the team-vs-enterprise tier caveat on the name-pattern rule and its fallback. - **Required-workflow source pins** — v0.20.0 exposes the source repository, path and a - branch/tag `ref`, but not GitHub's immutable workflow `sha` selector. The World at Ruin - rule therefore binds the external trusted source to `devantler-tech/.github` on - `refs/heads/main`; Actions review and merge gates own source changes until the provider - exposes `sha`. + branch/tag `ref`, but not GitHub's immutable workflow `sha` selector. The two World at + Ruin rules bind the established external source in `devantler-tech/.github` and the + product-owned replacement in `devantler-tech/world-at-ruin` independently to + `refs/heads/main`. Their separate rulesets preserve replacement enforcement while the + established rule is later disabled and retired. - **Actions policies** — the 2026-06-18 [workflow execution protections](https://github.blog/changelog/2026-06-18-control-who-and-what-triggers-github-actions-workflows/) (actor + event allow-lists controlling who/what triggers workflows, delivered as org diff --git a/deploy/organization-rulesets/kustomization.yaml b/deploy/organization-rulesets/kustomization.yaml index 4df22b2..e98cb2c 100644 --- a/deploy/organization-rulesets/kustomization.yaml +++ b/deploy/organization-rulesets/kustomization.yaml @@ -21,6 +21,7 @@ resources: # Net-new, managed (Create). - protect-release-tags.yaml - require-world-at-ruin-trusted-regressions.yaml + - require-world-at-ruin-product-regressions.yaml - require-monorepo-ci-aggregate-contract.yaml - require-dotgithub-deploy-guards.yaml - require-go-template-validation.yaml diff --git a/deploy/organization-rulesets/require-world-at-ruin-product-regressions.yaml b/deploy/organization-rulesets/require-world-at-ruin-product-regressions.yaml new file mode 100644 index 0000000..2a73fac --- /dev/null +++ b/deploy/organization-rulesets/require-world-at-ruin-product-regressions.yaml @@ -0,0 +1,39 @@ +# Require World at Ruin's product-owned regression workflow on the product's +# default branch. This rule is separate from the established external rule so +# the replacement remains active while that rule is disabled and retired. +# +# GitHub selects the workflow from World at Ruin's reviewed main branch rather +# than from candidate bytes. provider-upjet-github v0.20.0 exposes a branch/tag +# ref but not GitHub's workflow SHA selector, so refs/heads/main is the strongest +# declarative source binding the deployed provider can express. +# +# This is net-new and managed Observe + Create + Update + LateInitialize, never +# Delete. No bypassActors are declared. +apiVersion: enterprise.github.m.upbound.io/v1alpha1 +kind: OrganizationRuleset +metadata: + name: require-world-at-ruin-product-regressions +spec: + managementPolicies: + - Observe + - Create + - Update + - LateInitialize + forProvider: + name: Require workflow - World at Ruin product regressions + target: branch + enforcement: active + conditions: + - refName: + - include: ["~DEFAULT_BRANCH"] + exclude: [] + repositoryId: [1303188705] + rules: + - requiredWorkflows: + - requiredWorkflow: + - repositoryId: 1303188705 + path: .github/workflows/trusted-regressions.yaml + ref: refs/heads/main + providerConfigRef: + kind: ProviderConfig + name: default diff --git a/tests/world-at-ruin-regression-ruleset.sh b/tests/world-at-ruin-regression-ruleset.sh index 757eea8..a077d60 100755 --- a/tests/world-at-ruin-regression-ruleset.sh +++ b/tests/world-at-ruin-regression-ruleset.sh @@ -57,17 +57,36 @@ assert_value "source repository" "933213756" '.spec.forProvider.rules[0].require assert_value "source path" ".github/workflows/world-at-ruin-required-regressions.yaml" '.spec.forProvider.rules[0].requiredWorkflows[0].requiredWorkflow[0].path' assert_value "source ref" "refs/heads/main" '.spec.forProvider.rules[0].requiredWorkflows[0].requiredWorkflow[0].ref' +selector='select(.kind == "OrganizationRuleset" and .metadata.name == "require-world-at-ruin-product-regressions")' +count="$(yq -N "${selector} | .metadata.name" "${render}" | grep -c . || true)" +[[ "${count}" == "1" ]] || fail "expected exactly one rendered product-regression ruleset, got ${count}" + +assert_value "product ruleset name" "Require workflow - World at Ruin product regressions" '.spec.forProvider.name' +assert_value "product ruleset target" "branch" '.spec.forProvider.target' +assert_value "product ruleset enforcement" "active" '.spec.forProvider.enforcement' +assert_json "product management policy" '["Observe","Create","Update","LateInitialize"]' '.spec.managementPolicies' +assert_json "product target repository" '[1303188705]' '.spec.forProvider.conditions[0].repositoryId' +assert_json "product target branch" '["~DEFAULT_BRANCH"]' '.spec.forProvider.conditions[0].refName[0].include' +assert_json "product target exclusions" '[]' '.spec.forProvider.conditions[0].refName[0].exclude' +assert_value "product bypass actor count" "0" '(.spec.forProvider.bypassActors // []) | length' +assert_value "product rule count" "1" '.spec.forProvider.rules | length' +assert_value "product required workflow block count" "1" '.spec.forProvider.rules[0].requiredWorkflows | length' +assert_value "product required workflow count" "1" '.spec.forProvider.rules[0].requiredWorkflows[0].requiredWorkflow | length' +assert_value "product source repository" "1303188705" '.spec.forProvider.rules[0].requiredWorkflows[0].requiredWorkflow[0].repositoryId' +assert_value "product source path" ".github/workflows/trusted-regressions.yaml" '.spec.forProvider.rules[0].requiredWorkflows[0].requiredWorkflow[0].path' +assert_value "product source ref" "refs/heads/main" '.spec.forProvider.rules[0].requiredWorkflows[0].requiredWorkflow[0].ref' + inventory="${repo_root}/deploy/organization-rulesets/README.md" grep -Fq 'The 10 imported org rulesets' "${inventory}" || fail "organization ruleset inventory must account for 10 imported rulesets" # The backticks are literal Markdown table cell delimiters, not command substitution. # shellcheck disable=SC2016 managed_rows="$(grep -c '^| `[a-z-]*\.yaml` | .*(net-new) | Managed (Create)' "${inventory}" || true)" -[[ "${managed_rows}" == "5" ]] || - fail "organization ruleset inventory must list 5 managed rulesets, got ${managed_rows}" +[[ "${managed_rows}" == "6" ]] || + fail "organization ruleset inventory must list 6 managed rulesets, got ${managed_rows}" managed_rendered="$(yq -N 'select(.kind == "OrganizationRuleset" and (.spec.managementPolicies | contains(["Create"]))) | .metadata.name' "${render}" | grep -c . || true)" -[[ "${managed_rendered}" == "5" ]] || - fail "expected 5 rendered managed (Create) organization rulesets, got ${managed_rendered}" +[[ "${managed_rendered}" == "6" ]] || + fail "expected 6 rendered managed (Create) organization rulesets, got ${managed_rendered}" # Schema inspection is not a live census. Keep rendered ownership checks above, # and require the capability inventory to preserve that evidence boundary. if ! grep -Fq 'Schema support determines what can be declared; it does not prove adoption,' "${inventory}" ||