Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
130 commits
Select commit Hold shift + click to select a range
c11fb65
fix(governance): require central reviews for stacked prs
seonghobae Aug 20, 2026
ab65fcc
docs(governance): record restored approval contract
seonghobae Aug 20, 2026
bc2c93a
docs(governance): refresh stacked review rollout ledger
seonghobae Aug 20, 2026
501fe54
fix(router): permit exact-head dispatch enqueue
seonghobae Aug 20, 2026
aa63517
fix(router): preserve every trusted mention with least privilege
seonghobae Aug 20, 2026
a7aeb56
fix(workflows): remove unsupported concurrency queue
seonghobae Aug 20, 2026
c18d8c0
Merge remote-tracking branch 'refs/remotes/origin/main' into fix/stac…
seonghobae Aug 20, 2026
158f090
fix(governance): enforce exact central ref scope
seonghobae Aug 20, 2026
b873e71
fix(router): use reviewer token for sibling acknowledgements
seonghobae Aug 20, 2026
a56bf7f
fix(strix): classify caido sandbox startup failure
seonghobae Aug 20, 2026
33b85a8
fix(strix): require trusted caido traceback marker
seonghobae Aug 20, 2026
08f0f04
Merge main into stacked PR governance fix
seonghobae Aug 21, 2026
b628e88
fix(governance): preserve proposal branch create transition
seonghobae Aug 21, 2026
cf94d18
chore(governance): synchronize protected main
seonghobae Aug 21, 2026
f0bef61
Merge branch 'main' into fix/stacked-pr-central-required-workflows
opencode-agent[bot] Aug 21, 2026
8923bad
fix(ci): refresh audit lock and scheduler assertion
seonghobae Aug 21, 2026
d6be648
Merge branch 'main' into fix/stacked-pr-central-required-workflows
opencode-agent[bot] Aug 21, 2026
c6e1ba3
chore(governance): restore canonical pip lock ownership
seonghobae Aug 21, 2026
6bf0447
fix(governance): avoid misleading multi-rule drift
seonghobae Aug 21, 2026
5d64102
ci: refresh dependency and scheduler contracts
seonghobae Aug 21, 2026
4eedfa4
Merge remote-tracking branch 'origin/main' into codex/pr1176-restack
seonghobae Aug 21, 2026
5b2a216
Merge branch 'main' into fix/stacked-pr-central-required-workflows
opencode-agent[bot] Aug 21, 2026
49f6988
merge(main): retain only proposal-branch governance repair
seonghobae Aug 23, 2026
2f16ea9
merge(main): refresh proposal-branch governance repair
seonghobae Aug 24, 2026
cc941b2
merge(main): refresh create-transition audit after Strix hotfix
seonghobae Aug 24, 2026
55a6a79
Merge branch 'main' into fix/stacked-pr-central-required-workflows
opencode-agent[bot] Aug 24, 2026
27a686b
Merge protected main into fix/stacked-pr-central-required-workflows
seonghobae Aug 25, 2026
366fe2f
merge: converge governance create-transition owner with protected main
seonghobae Aug 25, 2026
437ea84
Merge branch 'main' into fix/stacked-pr-central-required-workflows
opencode-agent[bot] Aug 26, 2026
d6bb951
Merge branch 'main' into fix/stacked-pr-central-required-workflows
opencode-agent[bot] Aug 26, 2026
5486790
Merge branch 'main' into fix/stacked-pr-central-required-workflows
opencode-agent[bot] Aug 26, 2026
2701cf9
Merge branch 'main' into fix/stacked-pr-central-required-workflows
opencode-agent[bot] Aug 26, 2026
8664a7c
Merge branch 'main' into fix/stacked-pr-central-required-workflows
opencode-agent[bot] Aug 26, 2026
36d4fec
Merge branch 'main' into fix/stacked-pr-central-required-workflows
seonghobae Aug 26, 2026
6b09d65
Merge branch 'main' into fix/stacked-pr-central-required-workflows
seonghobae Aug 27, 2026
31e00c1
Merge branch 'main' into fix/stacked-pr-central-required-workflows
seonghobae Aug 28, 2026
940511d
Merge branch 'main' into fix/stacked-pr-central-required-workflows
opencode-agent[bot] Aug 28, 2026
f94292a
Merge branch 'main' into fix/stacked-pr-central-required-workflows
opencode-agent[bot] Aug 28, 2026
482d4c0
Merge protected main into proposal-branch governance repair
seonghobae Aug 28, 2026
2a9d115
fix(governance): audit owner repository review ruleset
seonghobae Aug 30, 2026
4ae3c61
fix(governance): reject hidden ruleset drift
seonghobae Aug 30, 2026
d222401
fix(governance): audit organization bypass actors
seonghobae Aug 30, 2026
0b0a45b
fix(governance): fail closed on missing bypass evidence
seonghobae Aug 30, 2026
63ca5e7
Merge branch 'main' into fix/stacked-pr-central-required-workflows
opencode-agent[bot] Aug 30, 2026
8ea2ec5
Retrigger required checks against refreshed main (no new main commits…
claude Aug 30, 2026
8dea465
Merge branch 'main' into fix/stacked-pr-central-required-workflows
opencode-agent[bot] Aug 30, 2026
ce108e7
Merge branch 'main' into fix/stacked-pr-central-required-workflows
opencode-agent[bot] Aug 30, 2026
8a7c5b1
Merge branch 'main' into fix/stacked-pr-central-required-workflows
opencode-agent[bot] Aug 30, 2026
faf1dd6
Merge branch 'main' into fix/stacked-pr-central-required-workflows
opencode-agent[bot] Aug 30, 2026
36ade87
Merge branch 'main' into fix/stacked-pr-central-required-workflows
opencode-agent[bot] Aug 30, 2026
53f99d7
Merge branch 'main' into fix/stacked-pr-central-required-workflows
seonghobae Aug 30, 2026
dc8d7d9
Merge branch 'main' into fix/stacked-pr-central-required-workflows
seonghobae Aug 30, 2026
718ae19
Merge protected main into fix/stacked-pr-central-required-workflows
seonghobae Aug 30, 2026
c1b31b2
Merge branch 'main' into fix/stacked-pr-central-required-workflows
opencode-agent[bot] Aug 31, 2026
2bc22cc
Merge branch 'main' into fix/stacked-pr-central-required-workflows
opencode-agent[bot] Aug 31, 2026
73b5b28
Merge branch 'main' into fix/stacked-pr-central-required-workflows
opencode-agent[bot] Aug 31, 2026
135f16f
Merge branch 'main' into fix/stacked-pr-central-required-workflows
opencode-agent[bot] Aug 31, 2026
5acc547
Merge branch 'main' into fix/stacked-pr-central-required-workflows
opencode-agent[bot] Aug 31, 2026
a14822c
Merge branch 'main' into fix/stacked-pr-central-required-workflows
opencode-agent[bot] Aug 31, 2026
3407ca6
Merge branch 'main' into fix/stacked-pr-central-required-workflows
opencode-agent[bot] Aug 31, 2026
df92a2e
Merge branch 'main' into fix/stacked-pr-central-required-workflows
opencode-agent[bot] Aug 31, 2026
d33dd13
Merge branch 'main' into fix/stacked-pr-central-required-workflows
opencode-agent[bot] Aug 31, 2026
d435f88
Merge protected main into fix/stacked-pr-central-required-workflows
seonghobae Aug 31, 2026
4d88d45
Merge branch 'main' into fix/stacked-pr-central-required-workflows
opencode-agent[bot] Sep 1, 2026
1141b31
Merge branch 'main' into fix/stacked-pr-central-required-workflows
opencode-agent[bot] Sep 1, 2026
ed314f6
test(governance): define solo-maintainer ruleset RED
seonghobae Sep 1, 2026
a815b54
ci(governance): run focused solo-maintainer contract
seonghobae Sep 1, 2026
3830a7d
fix(governance): align ruleset audit to solo maintainer
seonghobae Sep 1, 2026
f01d2cb
test(governance): rebaseline solo-maintainer ruleset policy
seonghobae Sep 1, 2026
260705b
test(governance): reject synthetic required reviewers
seonghobae Sep 1, 2026
1f0d0e8
ci(governance): exercise synthetic-reviewer RED
seonghobae Sep 1, 2026
033d6ec
fix(governance): reject synthetic required reviewers
seonghobae Sep 1, 2026
1c9c831
test(governance): require executable ruleset regressions
seonghobae Sep 1, 2026
06e1ba2
fix(governance): execute full focused ruleset suite
seonghobae Sep 1, 2026
c3a0571
test(governance): detach temporary proof from permanent suite
seonghobae Sep 1, 2026
2b381e1
fix(governance): pin focused contract Python
seonghobae Sep 1, 2026
3ec4abf
ci(governance): move focused ruleset contract off saturated latest queue
seonghobae Sep 1, 2026
81a7afb
ci(governance): retire proven focused ruleset contract lane
seonghobae Sep 1, 2026
29f004d
Merge branch 'main' into fix/stacked-pr-central-required-workflows
opencode-agent[bot] Sep 1, 2026
66757ec
ci(governance): restore unproven focused contract lane
seonghobae Sep 1, 2026
c52470b
test(governance): cover complete ruleset drift evidence
seonghobae Sep 1, 2026
a9505c3
fix(governance): reject undeclared ruleset controls
seonghobae Sep 1, 2026
c3ec79a
fix(governance): report all fetched ruleset drift
seonghobae Sep 1, 2026
d1c0b7c
ci(governance): execute completeness regressions
seonghobae Sep 1, 2026
c2ab699
docs(governance): align rollout with solo-maintainer policy
seonghobae Sep 1, 2026
49ebfae
test(governance): pin focused proof interpreter
seonghobae Sep 1, 2026
1bd407d
fix(governance): restore Python 3.14 proof runtime
seonghobae Sep 1, 2026
76516f4
fix(governance): run focused contract on explicit runner
seonghobae Sep 1, 2026
af04bac
docs(governance): preserve regression fixture history
seonghobae Sep 1, 2026
63609f2
Merge branch 'main' into fix/stacked-pr-central-required-workflows
opencode-agent[bot] Sep 1, 2026
3156622
ci(governance): allow exact-head focused redispatch
seonghobae Sep 1, 2026
74c0148
test(governance): reproduce malformed merge-method audit crash
seonghobae Sep 1, 2026
9b5d822
test(governance): reject malformed merge methods
seonghobae Sep 1, 2026
b3c2f6d
ci(governance): repair malformed merge-method finding on exact head
seonghobae Sep 1, 2026
ce752a4
fix(governance): reject malformed merge-method payloads
seonghobae Sep 1, 2026
7e82462
chore(governance): retire source-fix helper
seonghobae Sep 1, 2026
2fb3a48
chore(governance): retire focused proof workflow
seonghobae Sep 1, 2026
a4b817b
chore(governance): integrate current main into ruleset writer
seonghobae Sep 1, 2026
5d1e416
fix(governance): retire stale temporary-workflow regression
seonghobae Sep 1, 2026
ed3b562
chore(governance): integrate current main after queue repair
seonghobae Sep 1, 2026
5c46858
chore(governance): integrate hourly queue-pressure repair
seonghobae Sep 1, 2026
15ce91c
chore(governance): integrate required-review runner pin
seonghobae Sep 1, 2026
214b0bd
fix(governance): preserve protected-main review runner repair
seonghobae Sep 1, 2026
a53b008
Merge branch 'main' into fix/stacked-pr-central-required-workflows
opencode-agent[bot] Sep 1, 2026
8977092
Merge branch 'main' into fix/stacked-pr-central-required-workflows
opencode-agent[bot] Sep 1, 2026
2bbdcaa
Merge branch 'main' into fix/stacked-pr-central-required-workflows
opencode-agent[bot] Sep 1, 2026
6455ecd
Merge branch 'main' into fix/stacked-pr-central-required-workflows
opencode-agent[bot] Sep 1, 2026
f77890e
Merge branch 'main' into fix/stacked-pr-central-required-workflows
opencode-agent[bot] Sep 1, 2026
0852bc5
Merge current main into solo-maintainer governance writer
seonghobae Sep 1, 2026
a6d169d
Merge current main into solo-maintainer governance writer
seonghobae Sep 1, 2026
9a33ecc
chore: preserve current label taxonomy
seonghobae Sep 1, 2026
37f7f77
chore: preserve current repository metadata
seonghobae Sep 1, 2026
8f66c9a
docs: preserve current public-surface reconciliation
seonghobae Sep 1, 2026
c69b254
test: preserve current label taxonomy contract
seonghobae Sep 1, 2026
a001ec2
test: preserve current repository metadata contract
seonghobae Sep 1, 2026
48d9772
chore: restore exact protected-main metadata blobs
seonghobae Sep 1, 2026
8339b9b
test: reject code-owner review in solo-maintainer rulesets
seonghobae Sep 1, 2026
2887bb6
fix: reject code-owner approval deadlocks
seonghobae Sep 1, 2026
fbc90b3
Merge dedicated metadata credential into governance writer
seonghobae Sep 1, 2026
0097f93
Merge current protected main into governance writer
seonghobae Sep 1, 2026
9457e66
fix(governance): preserve current-main Pingora evidence
seonghobae Sep 1, 2026
5c684d8
Merge current protected main into governance writer
seonghobae Sep 1, 2026
65be10b
fix(governance): preserve current-main NIM retirement
seonghobae Sep 1, 2026
12076f9
Merge protected main into solo-maintainer ruleset writer
seonghobae Sep 1, 2026
41b0c97
test(governance): make code-owner policy explicit in passing fixture
seonghobae Sep 1, 2026
437a782
merge(governance): integrate current protected main without losing ru…
seonghobae Sep 1, 2026
44b4ea4
merge(governance): integrate current protected main without losing ru…
seonghobae Sep 2, 2026
51c469c
merge(governance): integrate current protected main
seonghobae Sep 2, 2026
a3f1b0f
merge(main): preserve current OpenCode dispatch repair
seonghobae Sep 2, 2026
dcb8580
merge(main): preserve current scheduler cadence on ruleset audit writer
seonghobae Sep 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 25 additions & 2 deletions .github/workflows/audit-central-ruleset.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,11 @@ jobs:
ORG_LOGIN: ContextualWisdomLab
RULESET_ID: "18156473"
STACKED_RULESET_ID: "21732164"
REPOSITORY_RULESET_ID: "17921150"
RULESET_SENTINEL_REPOSITORY: naruon
run: |
set -euo pipefail
audit_status=0
ruleset_json="$RUNNER_TEMP/central-required-workflow-ruleset.json"
ruleset_with_scope_json="$RUNNER_TEMP/central-required-workflow-ruleset-with-scope.json"
ruleset_error="$RUNNER_TEMP/central-required-workflow-ruleset.error"
Expand Down Expand Up @@ -89,7 +91,21 @@ jobs:
jq --slurpfile scope "$scope_json" \
'. + {"_audit_repository_scope": $scope[0]}' \
"$ruleset_json" >"$ruleset_with_scope_json"
python3 scripts/ci/audit_central_required_workflows.py "$ruleset_with_scope_json"
if ! python3 scripts/ci/audit_central_required_workflows.py "$ruleset_with_scope_json"; then
audit_status=1
fi

repository_ruleset_json="$RUNNER_TEMP/owner-repository-ruleset.json"
repository_ruleset_error="$RUNNER_TEMP/owner-repository-ruleset.error"
repository_ruleset_endpoint="repos/${ORG_LOGIN}/.github/rulesets/${REPOSITORY_RULESET_ID}?includes_parents=true"
if ! gh api "$repository_ruleset_endpoint" >"$repository_ruleset_json" 2>"$repository_ruleset_error"; then
echo "::error::Ruleset audit could not read owner repository ruleset ${REPOSITORY_RULESET_ID}."
sed 's/^/ /' "$repository_ruleset_error"
exit 1
fi
if ! python3 scripts/ci/audit_central_required_workflows.py --repository "$repository_ruleset_json"; then
audit_status=1
Comment thread
seonghobae marked this conversation as resolved.
fi

stacked_ruleset_json="$RUNNER_TEMP/stacked-opencode-ruleset.json"
stacked_ruleset_error="$RUNNER_TEMP/stacked-opencode-ruleset.error"
Expand All @@ -99,4 +115,11 @@ jobs:
sed 's/^/ /' "$stacked_ruleset_error"
exit 1
fi
python3 scripts/ci/audit_central_required_workflows.py --stacked "$stacked_ruleset_json"
if ! python3 scripts/ci/audit_central_required_workflows.py --stacked "$stacked_ruleset_json"; then
audit_status=1
fi

if [[ "$audit_status" -ne 0 ]]; then
echo "::error::One or more fetched rulesets drift from the declared governance contract."
exit "$audit_status"
fi
436 changes: 111 additions & 325 deletions docs/org-required-workflow-rollout.md

Large diffs are not rendered by default.

219 changes: 199 additions & 20 deletions scripts/ci/audit_central_required_workflows.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,17 @@

import argparse
import json
from pathlib import Path
import sys
from pathlib import Path
from typing import Any, TextIO


RULESET_ID = 18156473
RULESET_NAME = "CWL Central required workflows"
STACKED_RULESET_ID = 21732164
STACKED_RULESET_NAME = "CWL Stacked OpenCode required workflow"
REPOSITORY_RULESET_ID = 17921150
REPOSITORY_RULESET_NAME = "Lock default branch"
REPOSITORY_RULESET_SOURCE = "ContextualWisdomLab/.github"
SOURCE_REPOSITORY_ID = 1274066402
SOURCE_REF = "refs/heads/main"
SOURCE_ORGANIZATION = "ContextualWisdomLab"
Expand All @@ -32,6 +34,17 @@
".github/workflows/strix.yml",
".github/workflows/sast-semgrep.yml",
)
CENTRAL_ALLOWED_RULE_TYPES = {
"workflows",
"pull_request",
"deletion",
"non_fast_forward",
}
REPOSITORY_ALLOWED_RULE_TYPES = {
"pull_request",
"deletion",
"non_fast_forward",
}
STACKED_WORKFLOW_PATH = ".github/workflows/opencode-review.yml"


Expand All @@ -47,6 +60,26 @@ def _typed_rules(payload: dict[str, Any], rule_type: str) -> list[dict[str, Any]
]


def _forbidden_rule_types(
payload: dict[str, Any], allowed_rule_types: set[str]
) -> list[str]:
"""Return undeclared or malformed rule types from a ruleset payload."""
rules = payload.get("rules")
if not isinstance(rules, list):
return []
Comment thread
seonghobae marked this conversation as resolved.
forbidden: set[str] = set()
for rule in rules:
if not isinstance(rule, dict):
forbidden.add("<malformed>")
continue
rule_type = rule.get("type")
if not isinstance(rule_type, str) or not rule_type:
forbidden.add("<missing>")
elif rule_type not in allowed_rule_types:
forbidden.add(rule_type)
return sorted(forbidden)


def audit_ruleset(payload: dict[str, Any]) -> list[str]:
"""Return explicit drift reasons for a live organization ruleset payload."""
errors: list[str] = []
Expand All @@ -59,6 +92,8 @@ def audit_ruleset(payload: dict[str, Any]) -> list[str]:
errors.append("central ruleset target is not branch")
if payload.get("enforcement") != "active":
errors.append("central ruleset enforcement is not active")
if payload.get("bypass_actors") != []:
Comment thread
seonghobae marked this conversation as resolved.
errors.append("central ruleset must not configure bypass actors")

conditions = payload.get("conditions")
conditions = conditions if isinstance(conditions, dict) else {}
Expand All @@ -73,7 +108,9 @@ def audit_ruleset(payload: dict[str, Any]) -> list[str]:
)
Comment thread
seonghobae marked this conversation as resolved.
if is_inherited_org_payload:
malformed_scope = sorted(
name for name, inherited in inherited_scope.items() if not isinstance(inherited, bool)
name
for name, inherited in inherited_scope.items()
if not isinstance(inherited, bool)
)
if malformed_scope:
errors.append(
Expand Down Expand Up @@ -115,32 +152,59 @@ def audit_ruleset(payload: dict[str, Any]) -> list[str]:

ref_names = conditions.get("ref_name")
ref_names = ref_names if isinstance(ref_names, dict) else {}
if "~DEFAULT_BRANCH" not in (ref_names.get("include") or []):
errors.append("central ruleset does not target every default branch")
if (
ref_names.get("include") != ["~DEFAULT_BRANCH"]
or ref_names.get("exclude") != []
):
errors.append("central ruleset ref scope must be exactly the default branch")
Comment thread
seonghobae marked this conversation as resolved.

workflow_rules = _typed_rules(payload, "workflows")
workflow_parameters: dict[str, Any] = {}
if len(workflow_rules) != 1:
errors.append(f"expected one workflows rule, found {len(workflow_rules)}")
workflows: list[Any] = []
else:
parameters = workflow_rules[0].get("parameters")
parameters = parameters if isinstance(parameters, dict) else {}
workflows = parameters.get("workflows")
workflow_parameters = parameters if isinstance(parameters, dict) else {}
workflows = workflow_parameters.get("workflows")
workflows = workflows if isinstance(workflows, list) else []
Comment thread
seonghobae marked this conversation as resolved.

if (
len(workflow_rules) == 1
and workflow_parameters.get("do_not_enforce_on_create") is not True
):
errors.append("central required workflows block the branch create transition")

malformed_workflows = sum(
1
for workflow in workflows
if not isinstance(workflow, dict) or not isinstance(workflow.get("path"), str)
)
if malformed_workflows:
suffix = "entry" if malformed_workflows == 1 else "entries"
errors.append(
f"central required workflows contain {malformed_workflows} malformed {suffix}"
)

workflows_by_path: dict[str, list[dict[str, Any]]] = {}
for workflow in workflows:
if not isinstance(workflow, dict) or not isinstance(workflow.get("path"), str):
continue
workflows_by_path.setdefault(workflow["path"], []).append(workflow)

unexpected_workflows = sorted(set(workflows_by_path) - set(REQUIRED_WORKFLOW_PATHS))
if unexpected_workflows:
errors.append(f"unexpected central required workflows: {unexpected_workflows}")

for path in REQUIRED_WORKFLOW_PATHS:
matches = workflows_by_path.get(path, [])
if not matches:
errors.append(f"missing central required workflow {path}")
continue
if len(matches) != 1:
errors.append(f"central required workflow {path} is configured {len(matches)} times")
errors.append(
f"central required workflow {path} is configured {len(matches)} times"
)
if not any(
workflow.get("repository_id") == SOURCE_REPOSITORY_ID
and workflow.get("ref") == SOURCE_REF
Expand All @@ -158,23 +222,45 @@ def audit_ruleset(payload: dict[str, Any]) -> list[str]:
parameters = review_rules[0].get("parameters")
parameters = parameters if isinstance(parameters, dict) else {}
approving_reviews = parameters.get("required_approving_review_count")
if approving_reviews != 2:
errors.append("exactly two approving reviews are not required")
if approving_reviews != 0:
errors.append(
"central solo-maintainer ruleset must not require approving reviews"
)
if parameters.get("required_reviewers") not in (None, []):
errors.append(
"central solo-maintainer ruleset must not configure required reviewers"
)
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.
if parameters.get("require_code_owner_review") is not False:
errors.append(
"central solo-maintainer ruleset must not require code-owner review"
)
if parameters.get("dismiss_stale_reviews_on_push") is not True:
errors.append("stale-review dismissal on push is disabled")
if parameters.get("require_last_push_approval") is not True:
errors.append("last-push approval protection is disabled")
if parameters.get("require_last_push_approval") is not False:
errors.append(
"central solo-maintainer ruleset must not require last-push approval"
)
if parameters.get("required_review_thread_resolution") is not True:
errors.append("review-thread resolution protection is disabled")
allowed_methods = set(parameters.get("allowed_merge_methods") or [])
if not {"merge", "squash"}.issubset(allowed_methods):
errors.append("merge and squash are not both allowed merge methods")
raw_allowed_methods = parameters.get("allowed_merge_methods")
allowed_methods = (
set(raw_allowed_methods)
if isinstance(raw_allowed_methods, list)
and all(isinstance(method, str) for method in raw_allowed_methods)
else set()
)
if allowed_methods != {"merge", "squash"}:
errors.append("only merge and squash may be allowed merge methods")
Comment thread
seonghobae marked this conversation as resolved.

if not _typed_rules(payload, "deletion"):
errors.append("default-branch deletion protection is missing")
if not _typed_rules(payload, "non_fast_forward"):
errors.append("default-branch non-fast-forward protection is missing")

forbidden_rule_types = _forbidden_rule_types(payload, CENTRAL_ALLOWED_RULE_TYPES)
if forbidden_rule_types:
errors.append(f"central ruleset has forbidden rule types: {forbidden_rule_types}")

return errors


Expand Down Expand Up @@ -237,6 +323,86 @@ def audit_stacked_ruleset(payload: dict[str, Any]) -> list[str]:
return errors


def audit_repository_ruleset(payload: dict[str, Any]) -> list[str]:
"""Return drift reasons for the owner repository's default-branch policy."""

errors: list[str] = []
if payload.get("id") != REPOSITORY_RULESET_ID:
errors.append(f"expected repository ruleset id {REPOSITORY_RULESET_ID}")
if payload.get("name") != REPOSITORY_RULESET_NAME:
errors.append(f"expected repository ruleset name {REPOSITORY_RULESET_NAME}")
if (
payload.get("source_type") != "Repository"
or payload.get("source") != REPOSITORY_RULESET_SOURCE
):
errors.append("repository ruleset source is not ContextualWisdomLab/.github")
Comment thread
seonghobae marked this conversation as resolved.
if payload.get("target") != "branch":
errors.append("repository ruleset target is not branch")
if payload.get("enforcement") != "active":
errors.append("repository ruleset enforcement is not active")
if payload.get("bypass_actors") != []:
errors.append("repository ruleset must not configure bypass actors")

conditions = payload.get("conditions")
conditions = conditions if isinstance(conditions, dict) else {}
ref_names = conditions.get("ref_name")
ref_names = ref_names if isinstance(ref_names, dict) else {}
if ref_names != {"include": ["~DEFAULT_BRANCH"], "exclude": []}:
errors.append("repository ruleset ref scope must be exactly the default branch")

review_rules = _typed_rules(payload, "pull_request")
if len(review_rules) != 1:
errors.append(f"expected one repository pull_request rule, found {len(review_rules)}")
else:
raw_parameters = review_rules[0].get("parameters")
parameters = raw_parameters if isinstance(raw_parameters, dict) else {}
if parameters.get("required_approving_review_count") != 0:
errors.append(
"repository solo-maintainer ruleset must not require approving reviews"
)
if parameters.get("required_reviewers") not in (None, []):
errors.append(
"repository solo-maintainer ruleset must not configure required reviewers"
)
if parameters.get("require_code_owner_review") is not False:
errors.append(
"repository solo-maintainer ruleset must not require code-owner review"
)
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.
if parameters.get("dismiss_stale_reviews_on_push") is not True:
errors.append("repository ruleset stale-review dismissal on push is disabled")
if parameters.get("require_last_push_approval") is not False:
errors.append(
"repository solo-maintainer ruleset must not require last-push approval"
)
if parameters.get("required_review_thread_resolution") is not True:
errors.append(
"repository ruleset review-thread resolution protection is disabled"
)
raw_allowed_methods = parameters.get("allowed_merge_methods")
allowed_methods = (
set(raw_allowed_methods)
if isinstance(raw_allowed_methods, list)
and all(isinstance(method, str) for method in raw_allowed_methods)
else set()
)
if allowed_methods != {"merge", "squash"}:
errors.append("repository ruleset must allow only merge and squash")

if not _typed_rules(payload, "deletion"):
errors.append("repository default-branch deletion protection is missing")
if not _typed_rules(payload, "non_fast_forward"):
errors.append("repository default-branch non-fast-forward protection is missing")
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.

forbidden_rule_types = _forbidden_rule_types(
payload, REPOSITORY_ALLOWED_RULE_TYPES
)
if forbidden_rule_types:
errors.append(
f"repository ruleset has forbidden rule types: {forbidden_rule_types}"
)
return errors
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.


def load_payload(path: Path | None, stdin: TextIO) -> dict[str, Any]:
"""Load a ruleset object from ``path`` or standard input."""
if path is None:
Expand All @@ -252,7 +418,9 @@ def load_payload(path: Path | None, stdin: TextIO) -> dict[str, Any]:
def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
"""Parse the optional ruleset JSON path."""
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--stacked", action="store_true")
mode = parser.add_mutually_exclusive_group()
mode.add_argument("--stacked", action="store_true")
mode.add_argument("--repository", action="store_true")
parser.add_argument("ruleset_json", nargs="?", type=Path)
return parser.parse_args(argv)

Expand All @@ -266,9 +434,18 @@ def main(argv: list[str] | None = None) -> int:
print(f"ERROR: unable to load ruleset JSON: {exc}", file=sys.stderr)
return 2

auditor = audit_stacked_ruleset if args.stacked else audit_ruleset
ruleset_id = STACKED_RULESET_ID if args.stacked else RULESET_ID
workflow_count = 1 if args.stacked else len(REQUIRED_WORKFLOW_PATHS)
if args.repository:
auditor = audit_repository_ruleset
ruleset_id = REPOSITORY_RULESET_ID
workflow_count = 0
elif args.stacked:
auditor = audit_stacked_ruleset
ruleset_id = STACKED_RULESET_ID
workflow_count = 1
else:
auditor = audit_ruleset
ruleset_id = RULESET_ID
workflow_count = len(REQUIRED_WORKFLOW_PATHS)
errors = auditor(payload)
if errors:
for error in errors:
Expand All @@ -279,7 +456,9 @@ def main(argv: list[str] | None = None) -> int:
)
return 1

if args.stacked:
if args.repository:
print(f"PASS: repository ruleset {ruleset_id} protects the default branch")
elif args.stacked:
print(
f"PASS: ruleset {ruleset_id} audits {workflow_count} "
"central required workflows in evaluate mode"
Expand Down
Loading
Loading