diff --git a/.github/workflows/audit-central-ruleset.yml b/.github/workflows/audit-central-ruleset.yml index 4fe44768..fa2bf95d 100644 --- a/.github/workflows/audit-central-ruleset.yml +++ b/.github/workflows/audit-central-ruleset.yml @@ -36,7 +36,7 @@ jobs: with: persist-credentials: false - - name: Read live inherited organization ruleset and public scope + - name: Read live inherited organization ruleset and repository scope env: ORG_LOGIN: ContextualWisdomLab RULESET_ID: "18156473" @@ -46,7 +46,7 @@ jobs: 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" - repositories_json="$RUNNER_TEMP/central-required-workflow-public-repositories.json" + repositories_json="$RUNNER_TEMP/central-required-workflow-organization-repositories.json" scope_json="$RUNNER_TEMP/central-required-workflow-scope.json" ruleset_endpoint="repos/${ORG_LOGIN}/${RULESET_SENTINEL_REPOSITORY}/rulesets/${RULESET_ID}?includes_parents=true" @@ -55,9 +55,9 @@ jobs: sed 's/^/ /' "$ruleset_error" exit 1 fi - if ! gh api --paginate "orgs/${ORG_LOGIN}/repos?type=public&per_page=100" \ + if ! gh api --paginate "orgs/${ORG_LOGIN}/repos?type=all&per_page=100" \ | jq -s 'add | map(.name) | unique | sort' >"$repositories_json"; then - echo "::error::Ruleset audit could not enumerate public repositories for ${ORG_LOGIN}." + echo "::error::Ruleset audit could not enumerate organization repositories for ${ORG_LOGIN}." exit 1 fi diff --git a/docs/org-required-workflow-rollout.md b/docs/org-required-workflow-rollout.md index c19f28b8..78fdbd8c 100644 --- a/docs/org-required-workflow-rollout.md +++ b/docs/org-required-workflow-rollout.md @@ -1,6 +1,6 @@ # ContextualWisdomLab central required workflow rollout -Updated: 2026-07-14 13:35 KST +Updated: 2026-07-23 06:35 KST ## Decision @@ -14,21 +14,22 @@ Use an organization repository ruleset instead of copying workflow files into ea - Required workflow source repository ID: `1274066402` - Active required workflow paths: - `.github/workflows/close-empty-pr.yml` + - `.github/workflows/noema-review.yml` - `.github/workflows/opencode-review.yml` - `.github/workflows/pr-review-merge-scheduler.yml` - `.github/workflows/security-scan.yml` - `.github/workflows/strix.yml` - `.github/workflows/sast-semgrep.yml` - Required workflow ref: `refs/heads/main` -- Last verified workflow implementation base commit: `ef9950e6b55bf943c0295e1df3e34c94210d21cc` (`#283`) +- Last verified workflow implementation base commit: `050e6d59b0de9e62c8413d5f8f26f4f2f9ebea09` (`#584`) - Required workflow trigger support: `pull_request`, `pull_request_target`, `push`, `workflow_run` -`.github` PRs through `#283` are now in `main`. The required-workflow -ruleset points at `.github@main`; if live organization ruleset inspection +The required-workflow implementation is current through merged `.github#584`. +The ruleset points at `.github@main`; if live organization ruleset inspection reports another ref, treat that as operations drift and restore ruleset `18156473` to the current `main` head. -This keeps Strix security evidence, OpenCode review evidence, and merge/update automation sourced from the central `.github` repository. Target repositories do not need local copies of these workflows for the organization required workflow rule, and new repositories inherit the rule without a repository-name list update. +This keeps Strix security evidence, OpenCode and independent Noema review evidence, and merge/update automation sourced from the central `.github` repository. Target repositories do not need local copies of these workflows for the organization required workflow rule, and new repositories inherit the rule without a repository-name list update. ## OpenCode required workflow posture @@ -115,18 +116,20 @@ Do not centralize the scheduler by running a `.github` scheduled job against oth The org's two-reviewer merge rule needs a second approving-review identity independent of OpenCode. That identity is `cwl-noema-review[bot]`, supplied by the organization-owned `cwl-noema-review` GitHub App. The central workflow -currently runs the centrally versioned `noema_review_gate.py` judgement path and +is an active organization required workflow. It runs the centrally versioned +`noema_review_gate.py` judgement path and mints a short-lived installation token restricted to the target repository; the App has read-only Actions/checks/contents/status/code-scanning/Dependabot access and write access only to pull-request reviews. The PydanticAI `ReviewAgent` product in `ContextualWisdomLab/noema` (`reviewer/noema_reviewer`, noema#9) is the target standalone judgement plane, -but this credential/fail-closed rollout does not yet install or invoke that -package. Do not raise the org approval count to two on the strength of this -document alone: first wire its full current-head logs/SARIF/dependency/comment/ -CodeGraph manifest into this required workflow and prove an App-authored live -review on a target-repository PR. +while the central Python gate remains the deployed fail-closed reviewer. The +standalone package is not imported into the privileged workflow. External proof +exists on `ContextualWisdomLab/clearfolio#161`: `cwl-noema-review[bot]` submitted +an `APPROVED` review whose review commit, explicit Head SHA, current-head checks, +SARIF/dependency evidence, test evidence, and review marker all bind to +`4512fb9e9b56ab95df3acd85ebec2e6b849335a7`. - Token posture: `noema-review.yml` prefers a `NOEMA_REVIEW_TOKEN` emergency fallback when present, otherwise mints the repository-scoped App token with @@ -148,7 +151,7 @@ review on a target-repository PR. The active ruleset no longer maintains a repository-name allowlist. Live ruleset inspection on 2026-07-02 18:15 KST reports `repository_name.include=["~ALL"]`, so all current and future organization -repositories inherit the three central required workflows on their default +repositories inherit the seven central required workflows on their default branch unless a later ruleset exclusion is added. The table below is the public non-fork inventory snapshot and rollout ledger, not the ruleset target list. @@ -175,12 +178,12 @@ non-fork inventory snapshot and rollout ledger, not the ruleset target list. ## Current policy 1. Security evidence, review evidence, and mechanical merge/update automation are centralized through the organization `workflows` ruleset rule. -2. The central required workflows come from `.github`; repositories should not receive copied Strix, OpenCode, or scheduler workflow files only to satisfy this rollout. +2. The central required workflows come from `.github`; repositories should not receive copied Strix, OpenCode, Noema, or scheduler workflow files only to satisfy this rollout. 3. GitHub Flow repositories are those whose default branch is `main` or `master`. 4. Git Flow repositories are those whose default branch is `develop`. 5. OpenCode remains responsible for review judgment and structured decisions. 6. GitHub Actions remains responsible for mechanical branch updates and merges. -7. A merge is acceptable only when the current head has required checks passing, current-head OpenCode approval, no unresolved review threads, and a clean or mergeable merge state. +7. A merge is acceptable only when the current head has required checks passing, distinct current-head OpenCode and Noema approvals, no unresolved review threads, and a clean or mergeable merge state. 8. Previous-head approvals or checks are not merge evidence. 9. Same-repository approved PRs should merge immediately when GitHub reports `CLEAN`; fork or external-head PRs are excluded from scheduler merge and auto-merge. @@ -192,8 +195,10 @@ non-fork inventory snapshot and rollout ledger, not the ruleset target list. - On 2026-07-02 07:25 KST, organization ruleset `18156473` still reported `enforcement=active`, `repository_name.include=["~ALL"]`, `ref_name.include=["~DEFAULT_BRANCH"]`, and the same three required workflow paths from `ContextualWisdomLab/.github@refs/heads/main`. - On 2026-07-11 11:30 KST, organization ruleset `18156473` was normalized to keep the five central required workflows, stale-review dismissal, last-pusher protection, and review-thread resolution while setting `required_approving_review_count=0` and `require_code_owner_review=false`. The merge gate remains current-head OpenCode approval plus required checks and scheduler evidence; the change removes self-authored/code-owner deadlocks that left approved PRs unable to merge. - On 2026-07-13 21:10 KST, live inspection found that `sast-semgrep.yml` described itself as the central replacement for removed repository-local Semgrep jobs but was absent from ruleset `18156473`. The active ruleset was updated to require that workflow from `.github@refs/heads/main`, while preserving one approval, stale-review dismissal, last-push approval, and review-thread resolution. `scripts/ci/audit_central_required_workflows.py` and the scheduled ruleset audit now report each missing workflow, wrong source ref, or weakened review protection explicitly. -- On 2026-07-13 22:21 KST, the first main-branch ruleset audit proved that a repository `GITHUB_TOKEN` cannot read the organization-administration endpoint (`HTTP 403 Resource not accessible by integration`). The audit now uses the least-privilege inherited-ruleset endpoint, enumerates every public organization repository, logs `RULESET_SCOPE` for each one, requires inheritance everywhere except `.github`, `argos`, and `noema`, and still validates the complete workflow and pull-request rule payload through `naruon`. +- On 2026-07-13 22:21 KST, the first main-branch ruleset audit proved that a repository `GITHUB_TOKEN` cannot read the organization-administration endpoint (`HTTP 403 Resource not accessible by integration`). The audit uses the least-privilege inherited-ruleset endpoint, logs `RULESET_SCOPE` for each enumerated repository, and validates the complete workflow and pull-request rule payload through `naruon`. The original public-only scope and its historical `.github`/`argos`/`noema` exclusions were superseded by the 2026-07-23 audit below. - On 2026-07-13 22:37 KST, xtrmLLMBatchPython current-head evidence proved that Semgrep 1.169.0 reports zero blocking findings while retaining 23 source-suppressed results in raw SARIF. The central gate now logs the suppressed count, removes only SARIF results carrying explicit in-source suppressions before upload, and fails from the remaining SARIF finding count even when Semgrep's SARIF-mode exit code is zero. +- On 2026-07-16 14:18 KST, `ContextualWisdomLab/clearfolio#161` proved the independent reviewer on exact current head `4512fb9e9b56ab95df3acd85ebec2e6b849335a7`: `cwl-noema-review[bot]` submitted an App-authored `APPROVED` review whose body records the same Head SHA and cites the clean SARIF, dependency, test, and diff evidence. +- On 2026-07-23 06:35 KST, ruleset `18156473` was updated to require `.github/workflows/noema-review.yml`, making seven central required workflows while preserving exactly two approvals, stale-review dismissal, last-push approval, review-thread resolution, and merge/squash-only policy. The all-repository scope excludes only `.github`, `noema`, and private `IRT-bibliography-set`; `argos` now inherits the ruleset. The scheduled audit now enumerates every organization repository visible to its credential (`type=all`), rather than only public repositories, so the private exclusion and all other visible private-repository inheritance are verified. Existing open PRs may need a new PR event or branch update before GitHub creates the newly required Noema run. - `.github` PR `#225` raised high reasoning effort for all reasoning-capable OpenCode review model definitions and merged at `50c6ef82f52af3eeb0e58c174902fc9855c36682`. - `.github` PR `#226` stopped the merge scheduler from treating old deterministic fallback approval bodies as current-head approval evidence and merged at `57a1fa580731a0f76b31dcf29a597c5715dba2fd`. - `.github` PR `#230` added changed-file candidates to merge-conflict guidance so `DIRTY` or `CONFLICTING` PRs name the first files to inspect instead of giving only generic conflict instructions. It merged at `0cab5c8d46e88c1a3f68ef3f71b5d44d971cd2ef`. diff --git a/requirements-strix-ci-hashes.txt b/requirements-strix-ci-hashes.txt index 4cf45203..655e090c 100644 --- a/requirements-strix-ci-hashes.txt +++ b/requirements-strix-ci-hashes.txt @@ -1501,9 +1501,9 @@ protobuf==6.33.6 \ # grpc-google-iam-v1 # grpcio-status # proto-plus -pyasn1==0.6.3 \ - --hash=sha256:697a8ecd6d98891189184ca1fa05d1bb00e2f84b5977c481452050549c8a72cf \ - --hash=sha256:a80184d120f0864a52a073acc6fc642847d0be408e7c7252f31390c0f4eadcde +pyasn1==0.6.4 \ + --hash=sha256:9c447d8431c947fe4c8febc4ed9e760bc29011a5b01e5c74b67025bd9fb8ce81 \ + --hash=sha256:deda9277cfd454080ec40b207fb6df82206a3a2688735233cdcd8d3d565f088b # via pyasn1-modules pyasn1-modules==0.4.2 \ --hash=sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a \ diff --git a/scripts/ci/audit_central_required_workflows.py b/scripts/ci/audit_central_required_workflows.py index 359105c1..2a963798 100644 --- a/scripts/ci/audit_central_required_workflows.py +++ b/scripts/ci/audit_central_required_workflows.py @@ -16,9 +16,10 @@ SOURCE_REF = "refs/heads/main" SOURCE_ORGANIZATION = "ContextualWisdomLab" INHERITED_SCOPE_FIELD = "_audit_repository_scope" -EXPECTED_EXCLUSIONS = {".github", "argos", "noema"} +EXPECTED_EXCLUSIONS = {".github", "IRT-bibliography-set", "noema"} REQUIRED_WORKFLOW_PATHS = ( ".github/workflows/close-empty-pr.yml", + ".github/workflows/noema-review.yml", ".github/workflows/opencode-review.yml", ".github/workflows/pr-review-merge-scheduler.yml", ".github/workflows/security-scan.yml", @@ -90,7 +91,7 @@ def audit_ruleset(payload: dict[str, Any]) -> list[str]: ) if missing_inheritance: errors.append( - "central ruleset is not inherited by public repository probes: " + "central ruleset is not inherited by organization repository probes: " f"{missing_inheritance}" ) else: @@ -148,8 +149,8 @@ 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 not isinstance(approving_reviews, int) or approving_reviews < 1: - errors.append("at least one approving review is not required") + if approving_reviews != 2: + errors.append("exactly two approving reviews are not required") 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: diff --git a/tests/test_central_required_workflow_ruleset_audit.py b/tests/test_central_required_workflow_ruleset_audit.py index a88b9c09..f3e2a7c1 100644 --- a/tests/test_central_required_workflow_ruleset_audit.py +++ b/tests/test_central_required_workflow_ruleset_audit.py @@ -11,6 +11,7 @@ def ruleset_payload() -> dict: """Return the expected live central required-workflow ruleset shape.""" workflow_paths = ( "close-empty-pr.yml", + "noema-review.yml", "opencode-review.yml", "pr-review-merge-scheduler.yml", "security-scan.yml", @@ -25,7 +26,7 @@ def ruleset_payload() -> dict: "conditions": { "repository_name": { "include": ["~ALL"], - "exclude": ["noema", "argos", ".github"], + "exclude": ["noema", "IRT-bibliography-set", ".github"], }, "ref_name": {"include": ["~DEFAULT_BRANCH"], "exclude": []}, }, @@ -47,7 +48,7 @@ def ruleset_payload() -> dict: { "type": "pull_request", "parameters": { - "required_approving_review_count": 1, + "required_approving_review_count": 2, "dismiss_stale_reviews_on_push": True, "require_code_owner_review": False, "require_last_push_approval": True, @@ -70,7 +71,8 @@ def inherited_ruleset_payload() -> dict: payload["source"] = "ContextualWisdomLab" payload[audit.INHERITED_SCOPE_FIELD] = { ".github": False, - "argos": False, + "IRT-bibliography-set": False, + "argos": True, "naruon": True, "noema": False, "xtrmLLMBatchPython": True, @@ -83,25 +85,26 @@ def test_expected_central_ruleset_passes(monkeypatch, capsys) -> None: assert audit.main([]) == 0 assert ( - "PASS: ruleset 18156473 enforces 6 central required workflows" + "PASS: ruleset 18156473 enforces 7 central required workflows" in capsys.readouterr().out ) -def test_inherited_ruleset_and_public_scope_probes_pass() -> None: +def test_inherited_ruleset_and_organization_scope_probes_pass() -> None: assert audit.audit_ruleset(inherited_ruleset_payload()) == [] def test_inherited_scope_reports_every_inclusion_and_exclusion_drift() -> None: payload = inherited_ruleset_payload() payload[audit.INHERITED_SCOPE_FIELD][".github"] = True + payload[audit.INHERITED_SCOPE_FIELD]["argos"] = False payload[audit.INHERITED_SCOPE_FIELD]["naruon"] = False payload[audit.INHERITED_SCOPE_FIELD].pop("noema") errors = audit.audit_ruleset(payload) assert "central ruleset unexpectedly applies to excluded repository .github" in errors - assert "central ruleset is not inherited by public repository probes: ['naruon']" in errors + assert "central ruleset is not inherited by organization repository probes: ['argos', 'naruon']" in errors assert "inherited repository scope probes omit expected exclusions: ['noema']" in errors @@ -112,7 +115,7 @@ def test_inherited_scope_rejects_non_boolean_probe_results() -> None: errors = audit.audit_ruleset(payload) assert "inherited repository scope probes are not boolean for: ['naruon']" in errors - assert "central ruleset is not inherited by public repository probes: ['naruon']" in errors + assert "central ruleset is not inherited by organization repository probes: ['naruon']" in errors def test_missing_semgrep_workflow_reports_exact_drift(capsys, tmp_path) -> None: @@ -134,6 +137,20 @@ def test_missing_semgrep_workflow_reports_exact_drift(capsys, tmp_path) -> None: ) +def test_missing_noema_workflow_reports_exact_drift() -> None: + payload = ruleset_payload() + workflow_rule = next(rule for rule in payload["rules"] if rule["type"] == "workflows") + workflow_rule["parameters"]["workflows"] = [ + workflow + for workflow in workflow_rule["parameters"]["workflows"] + if workflow["path"] != ".github/workflows/noema-review.yml" + ] + + errors = audit.audit_ruleset(payload) + + assert "missing central required workflow .github/workflows/noema-review.yml" in errors + + def test_wrong_workflow_ref_reports_exact_drift() -> None: payload = ruleset_payload() workflow_rule = next(rule for rule in payload["rules"] if rule["type"] == "workflows") @@ -150,11 +167,13 @@ def test_wrong_workflow_ref_reports_exact_drift() -> None: def test_review_policy_weakening_reports_exact_drift() -> None: payload = ruleset_payload() review_rule = next(rule for rule in payload["rules"] if rule["type"] == "pull_request") + review_rule["parameters"]["required_approving_review_count"] = 1 review_rule["parameters"]["require_last_push_approval"] = False review_rule["parameters"]["required_review_thread_resolution"] = False errors = audit.audit_ruleset(payload) + assert "exactly two approving reviews are not required" in errors assert "last-push approval protection is disabled" in errors assert "review-thread resolution protection is disabled" in errors @@ -177,10 +196,11 @@ def test_audit_reports_all_structural_and_protection_drift() -> None: "central ruleset target is not branch", "central ruleset enforcement is not active", "central ruleset does not include all repositories", - "central ruleset repository exclusions drifted: expected ['.github', 'argos', 'noema'], got []", + "central ruleset repository exclusions drifted: expected ['.github', 'IRT-bibliography-set', 'noema'], got []", "central ruleset does not target every default branch", "expected one workflows rule, found 0", "missing central required workflow .github/workflows/close-empty-pr.yml", + "missing central required workflow .github/workflows/noema-review.yml", "missing central required workflow .github/workflows/opencode-review.yml", "missing central required workflow .github/workflows/pr-review-merge-scheduler.yml", "missing central required workflow .github/workflows/security-scan.yml", @@ -211,7 +231,7 @@ def test_audit_reports_malformed_duplicate_workflows_and_weak_review_parameters( errors = audit.audit_ruleset(payload) assert "central required workflow .github/workflows/sast-semgrep.yml is configured 2 times" in errors - assert "at least one approving review is not required" in errors + assert "exactly two approving reviews are not required" in errors assert "stale-review dismissal on push is disabled" in errors assert "last-push approval protection is disabled" in errors assert "review-thread resolution protection is disabled" in errors @@ -228,7 +248,7 @@ def test_audit_handles_malformed_rule_parameter_shapes() -> None: errors = audit.audit_ruleset(payload) assert "missing central required workflow .github/workflows/sast-semgrep.yml" in errors - assert "at least one approving review is not required" in errors + assert "exactly two approving reviews are not required" in errors def test_load_payload_rejects_non_object_and_main_logs_load_reason(monkeypatch, capsys) -> None: @@ -251,11 +271,12 @@ def test_scheduled_audit_and_rollout_document_the_semgrep_requirement() -> None: assert 'cron: "11 2 * * *"' in workflow assert "repos/${ORG_LOGIN}/${RULESET_SENTINEL_REPOSITORY}/rulesets/${RULESET_ID}" in workflow - assert 'orgs/${ORG_LOGIN}/repos?type=public&per_page=100' in workflow + assert 'orgs/${ORG_LOGIN}/repos?type=all&per_page=100' in workflow assert "RULESET_SCOPE repository=${repository} inherited=${inherited}" in workflow assert "HTTP 404" in workflow assert "audit_central_required_workflows.py" in workflow assert "Ruleset audit could not read inherited organization ruleset" in workflow + assert "- `.github/workflows/noema-review.yml`" in rollout assert "- `.github/workflows/sast-semgrep.yml`" in rollout