From c11fb657e1a71fabd8cdbf06d1e6a821163bd836 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 03:21:10 +0900 Subject: [PATCH 01/62] fix(governance): require central reviews for stacked prs --- README.md | 10 +++--- ...01-central-review-stacked-pull-requests.md | 36 +++++++++++++++++++ docs/org-required-workflow-rollout.md | 13 ++++++- .../ci/audit_central_required_workflows.py | 4 +-- ...central_required_workflow_ruleset_audit.py | 13 +++++-- 5 files changed, 67 insertions(+), 9 deletions(-) create mode 100644 docs/adr/0001-central-review-stacked-pull-requests.md diff --git a/README.md b/README.md index 94e91fe4db..4e740b4838 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ that means: | Mode | What happens | | --- | --- | | **따로 (this repo alone)** | Clone, test, and operate `.github` as the org profile and workflow source. Local quality gates, Cloudflare dry-run, and this repository's own PRs do not depend on naruon or any sibling product checkout. | -| **또 같이 (siblings call it)** | A sibling enables the org required-workflow ruleset (already `repository_name.include=["~ALL"]` on default branches). GitHub runs the trusted workflows from `ContextualWisdomLab/.github@main` in that sibling's repository context. Optional reusable callers (`deploy-pages.yml`, `pr-review-fix-scheduler.yml`) are `workflow_call` entry points, not files to copy. | +| **또 같이 (siblings call it)** | A sibling enables the org required-workflow ruleset (already `repository_name.include=["~ALL"]`, `ref_name.include=["~ALL"]`). GitHub runs the trusted workflows from `ContextualWisdomLab/.github@main` in that sibling's repository context, including stacked PR base branches. Optional reusable callers (`deploy-pages.yml`, `pr-review-fix-scheduler.yml`) are `workflow_call` entry points, not files to copy. | Do not copy Strix, OpenCode, Noema, or scheduler workflow files into a sibling to "satisfy CI." Thick downstream sync PRs are an anti-pattern @@ -65,8 +65,9 @@ The last checked-in ruleset ledger is Checked-in operator facts: -- Ruleset `18156473` is **active**. It targets every repository default - branch (`~ALL` / `~DEFAULT_BRANCH`) and sources workflows from this +- Ruleset `18156473` is **active**. It targets every repository branch + (`repository_name.include=["~ALL"]`, `ref_name.include=["~ALL"]`), including + stacked pull-request base branches, and sources workflows from this repository at `refs/heads/main`. - Active required workflow paths: `close-empty-pr.yml`, `noema-review.yml`, `opencode-review.yml`, `pr-review-merge-scheduler.yml`, @@ -89,7 +90,8 @@ workflows into siblings. public repositories inherit ruleset `18156473` without a name-list update. 2. Keep product, build, release, and repo-specific security workflows local. Do not add local copies of OpenCode, Strix, Noema, or the merge scheduler. -3. On each default-branch pull request, GitHub creates the required checks in +3. On each pull request, including stacked pull requests targeting a feature + branch, GitHub creates the required checks in the sibling context. Review judgment stays with OpenCode (and the independent Noema reviewer). Mechanical branch update and merge stay with GitHub Actions in that sibling context, using the configured central diff --git a/docs/adr/0001-central-review-stacked-pull-requests.md b/docs/adr/0001-central-review-stacked-pull-requests.md new file mode 100644 index 0000000000..47476b0f92 --- /dev/null +++ b/docs/adr/0001-central-review-stacked-pull-requests.md @@ -0,0 +1,36 @@ +# ADR-0001: Central review for stacked pull requests + +- Status: Accepted +- Date: 2026-08-20 +- Owners: ContextualWisdomLab platform maintainers +- Figma File ID: N/A — this is a workflow and governance contract with no UI + +## Decision + +The organization ruleset `CWL Central required workflows` (`18156473`) applies +to every branch reference (`ref_name.include=["~ALL"]`) in inherited +repositories. Central OpenCode, Noema, security, and scheduler workflows stay +owned by `ContextualWisdomLab/.github` at `refs/heads/main`. + +## Context + +Stacked PRs target another feature branch, so a default-branch-only ruleset did +not materialize the central required workflow entrypoints. This left buyer- +visible changes with local checks but without the same independent review and +security evidence used for main-targeting PRs. + +## Consequences + +- Every stacked PR receives the same current-head governance entrypoints. +- The scheduler may dispatch review-only work for non-default base branches; + merge automation remains guarded by the PR's actual policy and checks. +- Branch-scope drift is detected by + `scripts/ci/audit_central_required_workflows.py` and its regression tests. +- No workflow is copied into a product repository, preserving the MSA control + boundary. + +## Verification + +The exact live ruleset was read before and after the change. TEPP PRs #158 and +#159 were re-read at their current heads before review-only dispatch. Hosted +Checks remain authoritative for merge decisions. diff --git a/docs/org-required-workflow-rollout.md b/docs/org-required-workflow-rollout.md index 9c42ab0637..26bdb1cdae 100644 --- a/docs/org-required-workflow-rollout.md +++ b/docs/org-required-workflow-rollout.md @@ -9,7 +9,7 @@ Use an organization repository ruleset instead of copying workflow files into ea - Ruleset: `CWL Central required workflows` - Ruleset ID: `18156473` - Enforcement: `active` -- Target: branch rules on every repository's default branch (`repository_name.include=["~ALL"]`, `ref_name.include=["~DEFAULT_BRANCH"]`) +- Target: branch rules on every repository branch (`repository_name.include=["~ALL"]`, `ref_name.include=["~ALL"]`), including stacked pull-request base branches - Required workflow source repository: `ContextualWisdomLab/.github` - Required workflow source repository ID: `1274066402` - Active required workflow paths: @@ -31,6 +31,17 @@ reports another ref, treat that as operations drift and restore ruleset 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. +### Stacked pull-request coverage + +On 2026-08-20, live PRs #158 and #159 in `ContextualWisdomLab/TEPP` targeted +`feat/lineageweave-live-consumer-contract` rather than `main`. They had product +checks but no centrally materialized OpenCode or Noema workflow runs because +the ruleset was scoped to `~DEFAULT_BRANCH`. Ruleset `18156473` now uses +`ref_name.include=["~ALL"]`; the existing scheduler already enumerates open +PRs across base branches and dispatches exact-head review-only work for +stacked PRs. The audit script and regression test enforce this scope so a +future ruleset rollback fails closed. + ## OpenCode required workflow posture The central `.github/workflows/opencode-review.yml` is now part of the active organization required workflow ruleset. diff --git a/scripts/ci/audit_central_required_workflows.py b/scripts/ci/audit_central_required_workflows.py index 34dd161333..8ad0e3b2f5 100644 --- a/scripts/ci/audit_central_required_workflows.py +++ b/scripts/ci/audit_central_required_workflows.py @@ -112,8 +112,8 @@ 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 "~ALL" not in (ref_names.get("include") or []): + errors.append("central ruleset does not target stacked and default-branch PRs") workflow_rules = _typed_rules(payload, "workflows") if len(workflow_rules) != 1: diff --git a/tests/test_central_required_workflow_ruleset_audit.py b/tests/test_central_required_workflow_ruleset_audit.py index ca7dd4234c..fd82491165 100644 --- a/tests/test_central_required_workflow_ruleset_audit.py +++ b/tests/test_central_required_workflow_ruleset_audit.py @@ -28,7 +28,7 @@ def ruleset_payload() -> dict: "include": ["~ALL"], "exclude": ["noema", "IRT-bibliography-set", ".github"], }, - "ref_name": {"include": ["~DEFAULT_BRANCH"], "exclude": []}, + "ref_name": {"include": ["~ALL"], "exclude": []}, }, "rules": [ { @@ -94,6 +94,15 @@ def test_inherited_ruleset_and_organization_scope_probes_pass() -> None: assert audit.audit_ruleset(inherited_ruleset_payload()) == [] +def test_default_branch_only_scope_rejects_stacked_pull_requests() -> None: + payload = ruleset_payload() + payload["conditions"]["ref_name"]["include"] = ["~DEFAULT_BRANCH"] + + assert audit.audit_ruleset(payload) == [ + "central ruleset does not target stacked and default-branch PRs" + ] + + def test_inherited_scope_allows_private_exclusion_outside_token_visibility() -> None: payload = inherited_ruleset_payload() payload[audit.INHERITED_SCOPE_FIELD].pop("IRT-bibliography-set") @@ -204,7 +213,7 @@ def test_audit_reports_all_structural_and_protection_drift() -> None: "central ruleset enforcement is not active", "central ruleset does not include all repositories", "central ruleset repository exclusions drifted: expected ['.github', 'IRT-bibliography-set', 'noema'], got []", - "central ruleset does not target every default branch", + "central ruleset does not target stacked and default-branch PRs", "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", From ab65fcc7dd6547e84cca8a699430d06077dad022 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 03:28:13 +0900 Subject: [PATCH 02/62] docs(governance): record restored approval contract --- docs/org-required-workflow-rollout.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/org-required-workflow-rollout.md b/docs/org-required-workflow-rollout.md index 26bdb1cdae..2eb856122e 100644 --- a/docs/org-required-workflow-rollout.md +++ b/docs/org-required-workflow-rollout.md @@ -40,7 +40,9 @@ the ruleset was scoped to `~DEFAULT_BRANCH`. Ruleset `18156473` now uses `ref_name.include=["~ALL"]`; the existing scheduler already enumerates open PRs across base branches and dispatches exact-head review-only work for stacked PRs. The audit script and regression test enforce this scope so a -future ruleset rollback fails closed. +future ruleset rollback fails closed. The live repair also restored the +checked-in two-approval contract; workflow source, repository exclusions, and +stale-review/thread-resolution/last-push protections were preserved. ## OpenCode required workflow posture From bc2c93ac88599837555cb1ff3292bb49d245dc5a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 03:29:16 +0900 Subject: [PATCH 03/62] docs(governance): refresh stacked review rollout ledger --- README.md | 2 +- docs/org-required-workflow-rollout.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4e740b4838..3e42071baa 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ Live work and roadmap live on The narrative brief is [docs/CWL-MASTER-CONTEXT.md](docs/CWL-MASTER-CONTEXT.md). The last checked-in ruleset ledger is [docs/org-required-workflow-rollout.md](docs/org-required-workflow-rollout.md) -(updated 2026-07-23 KST). +(updated 2026-08-21 KST). Checked-in operator facts: diff --git a/docs/org-required-workflow-rollout.md b/docs/org-required-workflow-rollout.md index 2eb856122e..d217b00176 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-23 06:35 KST +Updated: 2026-08-21 03:30 KST ## Decision From 501fe541b725f611145d42672c66b3411287d36d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 05:04:16 +0900 Subject: [PATCH 04/62] fix(router): permit exact-head dispatch enqueue --- .github/workflows/agent-mention-router.yml | 3 +-- CHANGELOG.md | 1 + tests/test_agent_mention_downstream_idempotency.py | 6 +++--- tests/test_agent_mention_queue_isolation.py | 5 ++--- tests/test_agent_mention_workflow_contract.py | 2 +- 5 files changed, 8 insertions(+), 9 deletions(-) diff --git a/.github/workflows/agent-mention-router.yml b/.github/workflows/agent-mention-router.yml index b922ba5abd..57600cdfee 100644 --- a/.github/workflows/agent-mention-router.yml +++ b/.github/workflows/agent-mention-router.yml @@ -26,11 +26,10 @@ jobs: ) concurrency: group: review-agent-mention-router-local-${{ github.repository }} - queue: max runs-on: ubuntu-24.04 timeout-minutes: 5 permissions: - actions: read + actions: write contents: write issues: write pull-requests: read diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d2f9f24dc..7a25abb1ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ Semantic Versioning where the repository publishes a release. ### Fixed +- Grant the local agent-mention router the `actions: write` permission required for its repository-dispatch ledger, and remove the unsupported `concurrency.queue` key so the workflow passes actionlint and can enqueue exact-head review work. - Parsed `opencode.jsonc` as JSONC (stripping `//` and `/* */` comments outside string literals) in the reasoning-effort guard and its contract tests, instead of raw `json.loads`, which rejected the file the moment it carried its first explanatory comment (added for the `contextual-orchestrator` provider block) with `Expecting property name enclosed in double quotes`. Comment markers inside string values, such as the `$schema` URL, are left untouched. - Download the pinned `uv` 0.12.1 exporter from the official GitHub Releases URL instead of `releases.astral.sh`, which now returns HTTP 403 and blocks org-wide OpenCode `coverage-evidence`. The SHA-256 pin is unchanged. The opener may follow one hop onto `release-assets.githubusercontent.com` or `objects.githubusercontent.com` and still rejects every other host, userinfo, non-HTTPS scheme, and nondefault port (ContextualWisdomLab/.github#1109). - Compared the trusted `uv` executable's post-install `--version` output against the real GitHub Releases build's full string, `uv 0.12.1 (x86_64-unknown-linux-gnu)`, instead of the bare `uv 0.12.1` the prior check required; the genuine release binary always prints the target triple, so every installation was failing the pin check immediately after the archive download itself was fixed (ContextualWisdomLab/.github#1109). diff --git a/tests/test_agent_mention_downstream_idempotency.py b/tests/test_agent_mention_downstream_idempotency.py index 23634f293d..eaa17f953f 100644 --- a/tests/test_agent_mention_downstream_idempotency.py +++ b/tests/test_agent_mention_downstream_idempotency.py @@ -11,12 +11,12 @@ UPLOAD_ARTIFACT_SHA = "043fb46d1a93c77aae656e7c1c64a875d1fc6a0a" -def test_router_can_read_durable_central_artifacts() -> None: - """Both local routing and sibling sweeping receive actions read access.""" +def test_router_can_dispatch_and_read_durable_central_artifacts() -> None: + """Local routing dispatches and sibling sweeping reads durable artifacts.""" text = ROUTER_WORKFLOW.read_text(encoding="utf-8") local, sweep = text.split("\n sweep-organization-agent-mentions:\n", 1) - assert "permissions:\n actions: read" in local + assert "permissions:\n actions: write" in local assert "permissions:\n actions: read" in sweep assert "AGENT_DISPATCH_TOKEN: ${{ github.token }}" in local assert "AGENT_DISPATCH_TOKEN: ${{ github.token }}" in sweep diff --git a/tests/test_agent_mention_queue_isolation.py b/tests/test_agent_mention_queue_isolation.py index 8af11e04a8..c92e0e3456 100644 --- a/tests/test_agent_mention_queue_isolation.py +++ b/tests/test_agent_mention_queue_isolation.py @@ -46,8 +46,7 @@ def test_interactive_mentions_and_sweeps_use_independent_queues() -> None: assert not any(line.startswith("concurrency:") for line in header.splitlines()) assert _concurrency_block(local_job) == ( " concurrency:\n" - " group: review-agent-mention-router-local-${{ github.repository }}\n" - " queue: max" + " group: review-agent-mention-router-local-${{ github.repository }}" ) assert _concurrency_block(sweep_job) == ( " concurrency:\n" @@ -67,5 +66,5 @@ def test_interactive_queue_retains_pending_requests_without_cancellation() -> No ) concurrency = _concurrency_block(local_job) - assert "queue: max" in concurrency + assert "queue: max" not in concurrency assert "cancel-in-progress: true" not in concurrency diff --git a/tests/test_agent_mention_workflow_contract.py b/tests/test_agent_mention_workflow_contract.py index c5fc4cae54..396472bcff 100644 --- a/tests/test_agent_mention_workflow_contract.py +++ b/tests/test_agent_mention_workflow_contract.py @@ -29,7 +29,7 @@ def test_workflow_uses_local_event_and_central_sweep_with_job_scoped_writes() -> assert "route-local-agent-mention:" in local assert "github.repository == 'ContextualWisdomLab/.github'" in local for permission in ( - "actions: read", + "actions: write", "contents: write", "issues: write", "pull-requests: read", From aa63517844476ad5d5cfac37f6126d640d5d5115 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 13:15:06 -0700 Subject: [PATCH 05/62] fix(router): preserve every trusted mention with least privilege --- .github/workflows/agent-mention-router.yml | 4 +--- CHANGELOG.md | 2 +- .../agent-mention-concurrency-isolation.md | 24 ++++++++++++------- ...st_agent_mention_downstream_idempotency.py | 6 ++--- tests/test_agent_mention_queue_isolation.py | 22 ++++++++--------- tests/test_agent_mention_workflow_contract.py | 2 +- 6 files changed, 31 insertions(+), 29 deletions(-) diff --git a/.github/workflows/agent-mention-router.yml b/.github/workflows/agent-mention-router.yml index 57600cdfee..1640940f1d 100644 --- a/.github/workflows/agent-mention-router.yml +++ b/.github/workflows/agent-mention-router.yml @@ -24,12 +24,10 @@ jobs: contains(github.event.comment.body, '@cwl-noema-review') || contains(github.event.comment.body, '@opencode-agent') ) - concurrency: - group: review-agent-mention-router-local-${{ github.repository }} runs-on: ubuntu-24.04 timeout-minutes: 5 permissions: - actions: write + actions: read contents: write issues: write pull-requests: read diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a25abb1ef..c2270e7142 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,7 +35,7 @@ Semantic Versioning where the repository publishes a release. ### Fixed -- Grant the local agent-mention router the `actions: write` permission required for its repository-dispatch ledger, and remove the unsupported `concurrency.queue` key so the workflow passes actionlint and can enqueue exact-head review work. +- Keep local agent-mention Actions access read-only and remove its replacing concurrency group together with the unsupported `concurrency.queue` key, so every eligible exact-head mention can enqueue while scheduled sweeps remain independently single-flight. - Parsed `opencode.jsonc` as JSONC (stripping `//` and `/* */` comments outside string literals) in the reasoning-effort guard and its contract tests, instead of raw `json.loads`, which rejected the file the moment it carried its first explanatory comment (added for the `contextual-orchestrator` provider block) with `Expecting property name enclosed in double quotes`. Comment markers inside string values, such as the `$schema` URL, are left untouched. - Download the pinned `uv` 0.12.1 exporter from the official GitHub Releases URL instead of `releases.astral.sh`, which now returns HTTP 403 and blocks org-wide OpenCode `coverage-evidence`. The SHA-256 pin is unchanged. The opener may follow one hop onto `release-assets.githubusercontent.com` or `objects.githubusercontent.com` and still rejects every other host, userinfo, non-HTTPS scheme, and nondefault port (ContextualWisdomLab/.github#1109). - Compared the trusted `uv` executable's post-install `--version` output against the real GitHub Releases build's full string, `uv 0.12.1 (x86_64-unknown-linux-gnu)`, instead of the bare `uv 0.12.1` the prior check required; the genuine release binary always prints the target triple, so every installation was failing the pin check immediately after the archive download itself was fixed (ContextualWisdomLab/.github#1109). diff --git a/docs/doctoring/agent-mention-concurrency-isolation.md b/docs/doctoring/agent-mention-concurrency-isolation.md index 163a5bc850..266af2c4ca 100644 --- a/docs/doctoring/agent-mention-concurrency-isolation.md +++ b/docs/doctoring/agent-mention-concurrency-isolation.md @@ -16,7 +16,7 @@ Neither defect is evidence that the requesting maintainer, model, repository all The permanent regression contracts were committed before their corresponding production changes. - `tests/test_agent_mention_dispatch_payload_limit.py` requires both dispatch hops to stay at or below ten top-level payload properties and requires the router to reject an oversized payload before GitHub does. -- `tests/test_agent_mention_queue_isolation.py` requires the interactive route and scheduled sweep to use different job-level concurrency groups, with `queue: max` on the interactive route and no cancellation of in-progress interactive work. +- `tests/test_agent_mention_queue_isolation.py` requires the interactive route to have no replacing concurrency group while the scheduled sweep remains independently single-flight. ## Decision @@ -34,15 +34,15 @@ merge_mode=disabled The wrapper-to-scheduler payload carries exactly ten fields, including the three values that override unsafe scheduler defaults. The wrapper therefore remains review-only and cannot merge or update a branch. -### Isolated concurrency queues +### Non-replacing interactive routing -Concurrency is scoped to each job rather than the whole workflow: +The event-driven local route has no concurrency group, so each eligible +`issue_comment` event receives its own run. The scheduled sweep remains +single-flight and cannot cancel an interactive run: ```yaml route-local-agent-mention: - concurrency: - group: review-agent-mention-router-local-${{ github.repository }} - queue: max + runs-on: ubuntu-24.04 sweep-organization-agent-mentions: concurrency: @@ -50,7 +50,13 @@ sweep-organization-agent-mentions: cancel-in-progress: false ``` -GitHub documents that `queue: max` permits up to 100 pending jobs or workflow runs in one concurrency group and cannot be combined with `cancel-in-progress: true`. The interactive queue therefore retains bounded pending requests instead of replacing the previous pending request. Scheduled sweeps retain coalescing behavior in a separate group and cannot displace interactive work. +GitHub's supported concurrency contract permits at most one running and one +pending member per group, and a newly queued member replaces an older pending +member. The unsupported `queue: max` key cannot provide durability. Omitting a +local concurrency group is therefore the smallest supported boundary that does +not discard a trusted mention before its durable invocation claim exists. +Scheduled sweeps still coalesce in their separate group and cannot displace +interactive work. Concurrency is not the idempotency authority. Duplicate forwarding remains governed by the complete canonical invocation key, exact-key downstream concurrency, and the immutable exact-name Actions artifact ledger. @@ -58,7 +64,7 @@ Concurrency is not the idempotency authority. Duplicate forwarding remains gover - No model provider, reviewer identity, repository allowlist, token name, credential scope, or branch-protection rule changes. - `COPILOT_GITHUB_TOKEN` remains unused. -- Workflow-default permissions remain read-only; existing bounded jobs keep only their required writes. +- Workflow-default permissions remain read-only; the local router has read-only Actions access and keeps only the content/comment writes required for dispatch and acknowledgement. - Only trusted non-bot `OWNER`, `MEMBER`, or `COLLABORATOR` comments on open pull requests are eligible. - Pull request number, exact head and base SHAs, base branch, source comment, requested agent, and requesting actor remain bound to the invocation key. - Mention routing remains unable to approve, merge, update branches, publish, or release. @@ -81,7 +87,7 @@ Do not restore either defective boundary: - do not increase the first- or second-hop payload beyond GitHub's limit; - do not move local and scheduled work back into one workflow-level concurrency group; -- do not replace `queue: max` with the default single-pending interactive queue unless another independently reviewed durable queue preserves every eligible request. +- do not add a local concurrency group whose default single-pending contract can replace an eligible interactive request before the durable claim exists. A safe emergency degradation may suspend the scheduled sweep while retaining the isolated interactive route. diff --git a/tests/test_agent_mention_downstream_idempotency.py b/tests/test_agent_mention_downstream_idempotency.py index eaa17f953f..cc65570c8c 100644 --- a/tests/test_agent_mention_downstream_idempotency.py +++ b/tests/test_agent_mention_downstream_idempotency.py @@ -11,12 +11,12 @@ UPLOAD_ARTIFACT_SHA = "043fb46d1a93c77aae656e7c1c64a875d1fc6a0a" -def test_router_can_dispatch_and_read_durable_central_artifacts() -> None: - """Local routing dispatches and sibling sweeping reads durable artifacts.""" +def test_router_uses_read_only_actions_access_for_durable_artifacts() -> None: + """Both local routing and sibling sweeping only read Actions artifacts.""" text = ROUTER_WORKFLOW.read_text(encoding="utf-8") local, sweep = text.split("\n sweep-organization-agent-mentions:\n", 1) - assert "permissions:\n actions: write" in local + assert "permissions:\n actions: read" in local assert "permissions:\n actions: read" in sweep assert "AGENT_DISPATCH_TOKEN: ${{ github.token }}" in local assert "AGENT_DISPATCH_TOKEN: ${{ github.token }}" in sweep diff --git a/tests/test_agent_mention_queue_isolation.py b/tests/test_agent_mention_queue_isolation.py index c92e0e3456..15f1ae4ea2 100644 --- a/tests/test_agent_mention_queue_isolation.py +++ b/tests/test_agent_mention_queue_isolation.py @@ -22,13 +22,15 @@ def _job_block(workflow: str, job_name: str, next_job_name: str | None) -> str: def _concurrency_block(job: str) -> str: """Return the job-scoped concurrency mapping before ``runs-on``.""" + if " concurrency:\n" not in job: + return "" start = job.index(" concurrency:\n") end = job.index("\n runs-on:", start) return job[start:end] -def test_interactive_mentions_and_sweeps_use_independent_queues() -> None: - """A scheduled sweep cannot replace a pending trusted mention request.""" +def test_interactive_mentions_run_without_a_replacing_concurrency_queue() -> None: + """Every trusted mention receives a run while sweeps stay single-flight.""" workflow = WORKFLOW.read_text(encoding="utf-8") header = workflow.split("\njobs:\n", 1)[0] @@ -44,10 +46,7 @@ def test_interactive_mentions_and_sweeps_use_independent_queues() -> None: ) assert not any(line.startswith("concurrency:") for line in header.splitlines()) - assert _concurrency_block(local_job) == ( - " concurrency:\n" - " group: review-agent-mention-router-local-${{ github.repository }}" - ) + assert _concurrency_block(local_job) == "" assert _concurrency_block(sweep_job) == ( " concurrency:\n" " group: review-agent-mention-router-sweep-${{ github.repository }}\n" @@ -55,8 +54,8 @@ def test_interactive_mentions_and_sweeps_use_independent_queues() -> None: ) -def test_interactive_queue_retains_pending_requests_without_cancellation() -> None: - """The bounded interactive queue retains work and never cancels in progress.""" +def test_interactive_route_has_no_unsupported_or_replacing_queue_controls() -> None: + """Interactive work is neither invalid YAML nor a replaceable pending run.""" workflow = WORKFLOW.read_text(encoding="utf-8") local_job = _job_block( @@ -64,7 +63,6 @@ def test_interactive_queue_retains_pending_requests_without_cancellation() -> No "route-local-agent-mention", "sweep-organization-agent-mentions", ) - concurrency = _concurrency_block(local_job) - - assert "queue: max" not in concurrency - assert "cancel-in-progress: true" not in concurrency + assert _concurrency_block(local_job) == "" + assert "queue: max" not in local_job + assert "cancel-in-progress" not in local_job diff --git a/tests/test_agent_mention_workflow_contract.py b/tests/test_agent_mention_workflow_contract.py index 396472bcff..c5fc4cae54 100644 --- a/tests/test_agent_mention_workflow_contract.py +++ b/tests/test_agent_mention_workflow_contract.py @@ -29,7 +29,7 @@ def test_workflow_uses_local_event_and_central_sweep_with_job_scoped_writes() -> assert "route-local-agent-mention:" in local assert "github.repository == 'ContextualWisdomLab/.github'" in local for permission in ( - "actions: write", + "actions: read", "contents: write", "issues: write", "pull-requests: read", From a7aeb56804c043b97f837a82d9066dfc03eec938 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 06:02:02 +0900 Subject: [PATCH 06/62] fix(workflows): remove unsupported concurrency queue --- .github/workflows/agent-mention-noema-dispatch.yml | 1 - .github/workflows/agent-mention-opencode-dispatch.yml | 1 - tests/test_agent_mention_downstream_idempotency.py | 2 +- 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/agent-mention-noema-dispatch.yml b/.github/workflows/agent-mention-noema-dispatch.yml index 4912e5addc..8b09f9b473 100644 --- a/.github/workflows/agent-mention-noema-dispatch.yml +++ b/.github/workflows/agent-mention-noema-dispatch.yml @@ -11,7 +11,6 @@ on: concurrency: group: agent-mention-noema-${{ github.event.client_payload.agent_invocation_key || github.run_id }} cancel-in-progress: false - queue: max permissions: contents: read diff --git a/.github/workflows/agent-mention-opencode-dispatch.yml b/.github/workflows/agent-mention-opencode-dispatch.yml index 02a3f6f082..7a8cfadd69 100644 --- a/.github/workflows/agent-mention-opencode-dispatch.yml +++ b/.github/workflows/agent-mention-opencode-dispatch.yml @@ -11,7 +11,6 @@ on: concurrency: group: agent-mention-opencode-${{ github.event.client_payload.agent_invocation_key || github.run_id }} cancel-in-progress: false - queue: max permissions: contents: read diff --git a/tests/test_agent_mention_downstream_idempotency.py b/tests/test_agent_mention_downstream_idempotency.py index cc65570c8c..31095eba45 100644 --- a/tests/test_agent_mention_downstream_idempotency.py +++ b/tests/test_agent_mention_downstream_idempotency.py @@ -33,7 +33,7 @@ def test_downstream_workflows_claim_artifacts_and_bind_exact_key() -> None: assert "source_comment_id" in text assert "requested_agent" in text assert "cancel-in-progress: false" in text - assert "queue: max" in text + assert "queue: max" not in text assert "cancel-in-progress: true" not in text assert "^[0-9a-f]{64}$" in text assert "^[1-9][0-9]*$" in text From 158f090fbe2c659b3217cc8ec74714b5416903c0 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 06:41:24 +0900 Subject: [PATCH 07/62] fix(governance): enforce exact central ref scope --- CHANGELOG.md | 3 +++ README.md | 11 ++++++----- ...001-central-review-stacked-pull-requests.md | 6 +++--- docs/org-required-workflow-rollout.md | 2 +- scripts/ci/audit_central_required_workflows.py | 2 +- ..._central_required_workflow_ruleset_audit.py | 18 ++++++++++++++++++ 6 files changed, 32 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aa46462882..f72ce57211 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,9 @@ Semantic Versioning where the repository publishes a release. ### Fixed +- Tightened the central ruleset auditor to require the exact `ref_name` shape + (`include=["~ALL"]`, `exclude=[]`) and aligned operator documentation with + the three repository exclusions. - Keep local agent-mention Actions access read-only and remove its replacing concurrency group together with the unsupported `concurrency.queue` key, so every eligible exact-head mention can enqueue while scheduled sweeps remain independently single-flight. - Parsed `opencode.jsonc` as JSONC (stripping `//` and `/* */` comments outside string literals) in the reasoning-effort guard and its contract tests, instead of raw `json.loads`, which rejected the file the moment it carried its first explanatory comment (added for the `contextual-orchestrator` provider block) with `Expecting property name enclosed in double quotes`. Comment markers inside string values, such as the `$schema` URL, are left untouched. - Download the pinned `uv` 0.12.1 exporter from the official GitHub Releases URL instead of `releases.astral.sh`, which now returns HTTP 403 and blocks org-wide OpenCode `coverage-evidence`. The SHA-256 pin is unchanged. The opener may follow one hop onto `release-assets.githubusercontent.com` or `objects.githubusercontent.com` and still rejects every other host, userinfo, non-HTTPS scheme, and nondefault port (ContextualWisdomLab/.github#1109). diff --git a/README.md b/README.md index 3e42071baa..1f5ffdc5cb 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ that means: | Mode | What happens | | --- | --- | | **따로 (this repo alone)** | Clone, test, and operate `.github` as the org profile and workflow source. Local quality gates, Cloudflare dry-run, and this repository's own PRs do not depend on naruon or any sibling product checkout. | -| **또 같이 (siblings call it)** | A sibling enables the org required-workflow ruleset (already `repository_name.include=["~ALL"]`, `ref_name.include=["~ALL"]`). GitHub runs the trusted workflows from `ContextualWisdomLab/.github@main` in that sibling's repository context, including stacked PR base branches. Optional reusable callers (`deploy-pages.yml`, `pr-review-fix-scheduler.yml`) are `workflow_call` entry points, not files to copy. | +| **또 같이 (siblings call it)** | A sibling enables the org required-workflow ruleset (already `repository_name.include=["~ALL"]` with exclusions `.github`, `IRT-bibliography-set`, and `noema`, plus `ref_name.include=["~ALL"]`). GitHub runs the trusted workflows from `ContextualWisdomLab/.github@main` in that sibling's repository context, including stacked PR base branches. Optional reusable callers (`deploy-pages.yml`, `pr-review-fix-scheduler.yml`) are `workflow_call` entry points, not files to copy. | Do not copy Strix, OpenCode, Noema, or scheduler workflow files into a sibling to "satisfy CI." Thick downstream sync PRs are an anti-pattern @@ -65,10 +65,11 @@ The last checked-in ruleset ledger is Checked-in operator facts: -- Ruleset `18156473` is **active**. It targets every repository branch - (`repository_name.include=["~ALL"]`, `ref_name.include=["~ALL"]`), including - stacked pull-request base branches, and sources workflows from this - repository at `refs/heads/main`. +- Ruleset `18156473` is **active**. It targets every non-excluded repository + branch (`repository_name.include=["~ALL"]`, exclusions `.github`, + `IRT-bibliography-set`, and `noema`, and `ref_name.include=["~ALL"]`), + including stacked pull-request base branches, and sources workflows from + this repository at `refs/heads/main`. - Active required workflow paths: `close-empty-pr.yml`, `noema-review.yml`, `opencode-review.yml`, `pr-review-merge-scheduler.yml`, `security-scan.yml`, `strix.yml`, and `sast-semgrep.yml`. diff --git a/docs/adr/0001-central-review-stacked-pull-requests.md b/docs/adr/0001-central-review-stacked-pull-requests.md index 47476b0f92..cb59cc384c 100644 --- a/docs/adr/0001-central-review-stacked-pull-requests.md +++ b/docs/adr/0001-central-review-stacked-pull-requests.md @@ -31,6 +31,6 @@ security evidence used for main-targeting PRs. ## Verification -The exact live ruleset was read before and after the change. TEPP PRs #158 and -#159 were re-read at their current heads before review-only dispatch. Hosted -Checks remain authoritative for merge decisions. +The exact live ruleset was read before and after the change. TEPP pull requests +`#158` and `#159` were re-read at their current heads before review-only +dispatch. Hosted Checks remain authoritative for merge decisions. diff --git a/docs/org-required-workflow-rollout.md b/docs/org-required-workflow-rollout.md index d217b00176..95be025043 100644 --- a/docs/org-required-workflow-rollout.md +++ b/docs/org-required-workflow-rollout.md @@ -9,7 +9,7 @@ Use an organization repository ruleset instead of copying workflow files into ea - Ruleset: `CWL Central required workflows` - Ruleset ID: `18156473` - Enforcement: `active` -- Target: branch rules on every repository branch (`repository_name.include=["~ALL"]`, `ref_name.include=["~ALL"]`), including stacked pull-request base branches +- Target: branch rules on every non-excluded repository branch (`repository_name.include=["~ALL"]`, `repository_name.exclude=[".github", "IRT-bibliography-set", "noema"]`, `ref_name.include=["~ALL"]`, `ref_name.exclude=[]`), including stacked pull-request base branches - Required workflow source repository: `ContextualWisdomLab/.github` - Required workflow source repository ID: `1274066402` - Active required workflow paths: diff --git a/scripts/ci/audit_central_required_workflows.py b/scripts/ci/audit_central_required_workflows.py index 8ad0e3b2f5..43979f3b9e 100644 --- a/scripts/ci/audit_central_required_workflows.py +++ b/scripts/ci/audit_central_required_workflows.py @@ -112,7 +112,7 @@ 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 "~ALL" not in (ref_names.get("include") or []): + if ref_names.get("include") != ["~ALL"] or ref_names.get("exclude") != []: errors.append("central ruleset does not target stacked and default-branch PRs") workflow_rules = _typed_rules(payload, "workflows") diff --git a/tests/test_central_required_workflow_ruleset_audit.py b/tests/test_central_required_workflow_ruleset_audit.py index fd82491165..842fd9d73e 100644 --- a/tests/test_central_required_workflow_ruleset_audit.py +++ b/tests/test_central_required_workflow_ruleset_audit.py @@ -103,6 +103,24 @@ def test_default_branch_only_scope_rejects_stacked_pull_requests() -> None: ] +def test_ref_scope_rejects_branch_exclusions() -> None: + payload = ruleset_payload() + payload["conditions"]["ref_name"]["exclude"] = ["refs/heads/release/*"] + + assert audit.audit_ruleset(payload) == [ + "central ruleset does not target stacked and default-branch PRs" + ] + + +def test_ref_scope_rejects_string_include() -> None: + payload = ruleset_payload() + payload["conditions"]["ref_name"]["include"] = "~ALL" + + assert audit.audit_ruleset(payload) == [ + "central ruleset does not target stacked and default-branch PRs" + ] + + def test_inherited_scope_allows_private_exclusion_outside_token_visibility() -> None: payload = inherited_ruleset_payload() payload[audit.INHERITED_SCOPE_FIELD].pop("IRT-bibliography-set") From b873e7157c7f3fb345c1d9de347cc4295b8024d2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 06:51:21 +0900 Subject: [PATCH 08/62] fix(router): use reviewer token for sibling acknowledgements --- .github/workflows/agent-mention-router.yml | 15 ++++++++++++--- CHANGELOG.md | 2 +- README.md | 2 +- .../agent-mention-concurrency-isolation.md | 4 +++- docs/org-required-workflow-rollout.md | 3 ++- organization_commercial_readiness_fixtures.py | 2 ++ .../ci/organization_commercial_readiness_loop.py | 4 +++- .../test_agent_mention_downstream_idempotency.py | 13 +++++++++++++ ...est_central_required_workflow_ruleset_audit.py | 3 +++ 9 files changed, 40 insertions(+), 8 deletions(-) diff --git a/.github/workflows/agent-mention-router.yml b/.github/workflows/agent-mention-router.yml index 1640940f1d..8e74c7325f 100644 --- a/.github/workflows/agent-mention-router.yml +++ b/.github/workflows/agent-mention-router.yml @@ -58,9 +58,18 @@ jobs: "$SOURCE_EVENT_PATH" >"${RUNNER_TEMP}/agent-mention-event.json" - name: Route trusted local agent mention - run: >- - python3 scripts/ci/agent_mention_router.py - --event-path "${RUNNER_TEMP}/agent-mention-event.json" + env: + PR_REVIEW_MERGE_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN }} + OPENCODE_APPROVE_TOKEN: ${{ secrets.OPENCODE_APPROVE_TOKEN }} + run: | + set -euo pipefail + if [ -n "$PR_REVIEW_MERGE_TOKEN" ]; then + export TARGET_REPOSITORY_TOKEN="$PR_REVIEW_MERGE_TOKEN" + elif [ -n "$OPENCODE_APPROVE_TOKEN" ]; then + export TARGET_REPOSITORY_TOKEN="$OPENCODE_APPROVE_TOKEN" + fi + python3 scripts/ci/agent_mention_router.py \ + --event-path "${RUNNER_TEMP}/agent-mention-event.json" sweep-organization-agent-mentions: if: >- diff --git a/CHANGELOG.md b/CHANGELOG.md index f72ce57211..29609cb596 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,7 +38,7 @@ Semantic Versioning where the repository publishes a release. - Tightened the central ruleset auditor to require the exact `ref_name` shape (`include=["~ALL"]`, `exclude=[]`) and aligned operator documentation with the three repository exclusions. -- Keep local agent-mention Actions access read-only and remove its replacing concurrency group together with the unsupported `concurrency.queue` key, so every eligible exact-head mention can enqueue while scheduled sweeps remain independently single-flight. +- Keep local agent-mention Actions access read-only, use the established reviewer tokens for sibling-repository acknowledgements, and remove the replacing concurrency group together with the unsupported `concurrency.queue` key, so every eligible exact-head mention can enqueue while scheduled sweeps remain independently single-flight. - Parsed `opencode.jsonc` as JSONC (stripping `//` and `/* */` comments outside string literals) in the reasoning-effort guard and its contract tests, instead of raw `json.loads`, which rejected the file the moment it carried its first explanatory comment (added for the `contextual-orchestrator` provider block) with `Expecting property name enclosed in double quotes`. Comment markers inside string values, such as the `$schema` URL, are left untouched. - Download the pinned `uv` 0.12.1 exporter from the official GitHub Releases URL instead of `releases.astral.sh`, which now returns HTTP 403 and blocks org-wide OpenCode `coverage-evidence`. The SHA-256 pin is unchanged. The opener may follow one hop onto `release-assets.githubusercontent.com` or `objects.githubusercontent.com` and still rejects every other host, userinfo, non-HTTPS scheme, and nondefault port (ContextualWisdomLab/.github#1109). - Compared the trusted `uv` executable's post-install `--version` output against the real GitHub Releases build's full string, `uv 0.12.1 (x86_64-unknown-linux-gnu)`, instead of the bare `uv 0.12.1` the prior check required; the genuine release binary always prints the target triple, so every installation was failing the pin check immediately after the archive download itself was fixed (ContextualWisdomLab/.github#1109). diff --git a/README.md b/README.md index 1f5ffdc5cb..4eb3ebedbd 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ that means: | Mode | What happens | | --- | --- | | **따로 (this repo alone)** | Clone, test, and operate `.github` as the org profile and workflow source. Local quality gates, Cloudflare dry-run, and this repository's own PRs do not depend on naruon or any sibling product checkout. | -| **또 같이 (siblings call it)** | A sibling enables the org required-workflow ruleset (already `repository_name.include=["~ALL"]` with exclusions `.github`, `IRT-bibliography-set`, and `noema`, plus `ref_name.include=["~ALL"]`). GitHub runs the trusted workflows from `ContextualWisdomLab/.github@main` in that sibling's repository context, including stacked PR base branches. Optional reusable callers (`deploy-pages.yml`, `pr-review-fix-scheduler.yml`) are `workflow_call` entry points, not files to copy. | +| **또 같이 (siblings call it)** | A sibling enables the org required-workflow ruleset (already `repository_name.include=["~ALL"]` with `.github`, `IRT-bibliography-set`, and `noema` excluded, plus `ref_name.include=["~ALL"]`). GitHub runs the trusted workflows from `ContextualWisdomLab/.github@main` in every non-excluded sibling repository branch, including stacked PR base branches. Optional reusable callers (`deploy-pages.yml`, `pr-review-fix-scheduler.yml`) are `workflow_call` entry points, not files to copy. | Do not copy Strix, OpenCode, Noema, or scheduler workflow files into a sibling to "satisfy CI." Thick downstream sync PRs are an anti-pattern diff --git a/docs/doctoring/agent-mention-concurrency-isolation.md b/docs/doctoring/agent-mention-concurrency-isolation.md index 266af2c4ca..402bf685b0 100644 --- a/docs/doctoring/agent-mention-concurrency-isolation.md +++ b/docs/doctoring/agent-mention-concurrency-isolation.md @@ -65,6 +65,7 @@ Concurrency is not the idempotency authority. Duplicate forwarding remains gover - No model provider, reviewer identity, repository allowlist, token name, credential scope, or branch-protection rule changes. - `COPILOT_GITHUB_TOKEN` remains unused. - Workflow-default permissions remain read-only; the local router has read-only Actions access and keeps only the content/comment writes required for dispatch and acknowledgement. +- Local sibling-repository acknowledgement writes prefer the established `PR_REVIEW_MERGE_TOKEN` and `OPENCODE_APPROVE_TOKEN` reviewer credentials; `github.token` remains the central repository-dispatch fallback and no new credential name is introduced. - Only trusted non-bot `OWNER`, `MEMBER`, or `COLLABORATOR` comments on open pull requests are eligible. - Pull request number, exact head and base SHAs, base branch, source comment, requested agent, and requesting actor remain bound to the invocation key. - Mention routing remains unable to approve, merge, update branches, publish, or release. @@ -77,7 +78,8 @@ After protected integration: 2. require the hidden receipt marker, acknowledgement comment, or durable exact-name artifact for the source comment; 3. require the trusted OpenCode wrapper and review-only scheduler dispatch to start for the same repository, pull request, and exact head; 4. verify that a scheduled sweep cannot cancel or replace the interactive route; -5. distinguish downstream provider or review failure from routing failure rather than treating every missing verdict as the same incident. +5. verify the local route uses an established cross-repository reviewer token for target acknowledgement when configured; +6. distinguish downstream provider or review failure from routing failure rather than treating every missing verdict as the same incident. A receipt proves routing and durable claim processing. It is not an approval and never substitutes for exact-head checks or branch protection. diff --git a/docs/org-required-workflow-rollout.md b/docs/org-required-workflow-rollout.md index 95be025043..ca1c5275a0 100644 --- a/docs/org-required-workflow-rollout.md +++ b/docs/org-required-workflow-rollout.md @@ -191,7 +191,8 @@ SARIF/dependency evidence, test evidence, and review marker all bind to 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 +`repository_name.include=["~ALL"]` with `.github`, `IRT-bibliography-set`, and +`noema` excluded, so all current and future non-excluded organization 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. diff --git a/organization_commercial_readiness_fixtures.py b/organization_commercial_readiness_fixtures.py index d865961962..0ba0e0a01d 100644 --- a/organization_commercial_readiness_fixtures.py +++ b/organization_commercial_readiness_fixtures.py @@ -90,6 +90,8 @@ def __init__( repositories: list[dict[str, Any]], snapshots: dict[str, list[RepositorySnapshot | Exception]], ) -> None: + """Initialize a deterministic client with queued snapshot outcomes.""" + self.repositories = repositories self.snapshots = snapshots self.dispatched_repairs: list[tuple[str, str]] = [] diff --git a/scripts/ci/organization_commercial_readiness_loop.py b/scripts/ci/organization_commercial_readiness_loop.py index c00cfa1e0a..8d11096c34 100644 --- a/scripts/ci/organization_commercial_readiness_loop.py +++ b/scripts/ci/organization_commercial_readiness_loop.py @@ -239,6 +239,8 @@ class GitHubClient: """Use the GitHub CLI as an authenticated, bounded REST transport.""" def __init__(self, token: str, *, timeout_seconds: int = 60) -> None: + """Initialize the transport with one required scheduler credential.""" + if not token: raise GitHubError("GH_TOKEN is required for organization coordination") self._token = token @@ -853,4 +855,4 @@ def main( if __name__ == "__main__": # pragma: no cover - exercised through main() - raise SystemExit(main()) \ No newline at end of file + raise SystemExit(main()) diff --git a/tests/test_agent_mention_downstream_idempotency.py b/tests/test_agent_mention_downstream_idempotency.py index 31095eba45..f417b3ff36 100644 --- a/tests/test_agent_mention_downstream_idempotency.py +++ b/tests/test_agent_mention_downstream_idempotency.py @@ -22,6 +22,19 @@ def test_router_uses_read_only_actions_access_for_durable_artifacts() -> None: assert "AGENT_DISPATCH_TOKEN: ${{ github.token }}" in sweep +def test_local_router_prefers_existing_cross_repository_review_tokens() -> None: + """Sibling acknowledgements use the existing reviewer token scheme.""" + + text = ROUTER_WORKFLOW.read_text(encoding="utf-8") + local = text.split("\n sweep-organization-agent-mentions:\n", 1)[0] + assert "PR_REVIEW_MERGE_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN }}" in local + assert "OPENCODE_APPROVE_TOKEN: ${{ secrets.OPENCODE_APPROVE_TOKEN }}" in local + assert 'if [ -n "$PR_REVIEW_MERGE_TOKEN" ]; then' in local + assert 'elif [ -n "$OPENCODE_APPROVE_TOKEN" ]; then' in local + assert 'export TARGET_REPOSITORY_TOKEN="$PR_REVIEW_MERGE_TOKEN"' in local + assert 'export TARGET_REPOSITORY_TOKEN="$OPENCODE_APPROVE_TOKEN"' in local + + def test_downstream_workflows_claim_artifacts_and_bind_exact_key() -> None: """Exact-key concurrency serializes claims before authoritative forwarding.""" diff --git a/tests/test_central_required_workflow_ruleset_audit.py b/tests/test_central_required_workflow_ruleset_audit.py index 842fd9d73e..6ae8b8a46b 100644 --- a/tests/test_central_required_workflow_ruleset_audit.py +++ b/tests/test_central_required_workflow_ruleset_audit.py @@ -104,6 +104,8 @@ def test_default_branch_only_scope_rejects_stacked_pull_requests() -> None: def test_ref_scope_rejects_branch_exclusions() -> None: + """All branch refs must be included without exclusions.""" + payload = ruleset_payload() payload["conditions"]["ref_name"]["exclude"] = ["refs/heads/release/*"] @@ -113,6 +115,7 @@ def test_ref_scope_rejects_branch_exclusions() -> None: def test_ref_scope_rejects_string_include() -> None: + """The ruleset API contract requires an exact include list.""" payload = ruleset_payload() payload["conditions"]["ref_name"]["include"] = "~ALL" From a56bf7fb5078497d47fe65113be3ed070f65669c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 07:33:07 +0900 Subject: [PATCH 09/62] fix(strix): classify caido sandbox startup failure --- .github/workflows/strix.yml | 5 +-- CHANGELOG.md | 1 + .../strix-nvidia-nim-not-found-fallback.md | 5 +++ ...est_strix_nvidia_nim_not_found_fallback.py | 32 +++++++++++++++++++ 4 files changed, 41 insertions(+), 2 deletions(-) diff --git a/.github/workflows/strix.yml b/.github/workflows/strix.yml index 4155c73461..b362dc94f6 100644 --- a/.github/workflows/strix.yml +++ b/.github/workflows/strix.yml @@ -844,7 +844,8 @@ jobs: # exit 1 both for genuine blocking vulnerabilities AND for # LLM-backend-unavailable outcomes (GitHub Models "Too many requests" # rate limits, OpenAI quota starvation, 413 tokens_limit_reached - # token-cap, connection/warm-up failures) that could not complete a scan. A backend outage is CI + # token-cap, connection/warm-up failures, and the local Caido guest-login + # sandbox startup failure) that could not complete a scan. A backend outage is CI # infrastructure noise, not a security finding, so it must not fail # the required check and block merges. strix_run_log="$RUNNER_TEMP/strix_gate_console.log" @@ -866,7 +867,7 @@ jobs: fi # Recognized signals that the LLM backend was unavailable / starved. - backend_unavailable_signal='RateLimitError|Too many requests\. For more on scraping GitHub|exceeded your current quota|insufficient_quota|billing details|"status"[[:space:]]*:[[:space:]]*"RESOURCE_EXHAUSTED"|tokens_limit_reached|Request body too large|Max size:[[:space:]]*[0-9]+[[:space:]]+tokens|Error code:[[:space:]]*413|LLM CONNECTION FAILED|Could not establish connection to the language model|LLM warm-up failed|Configured model and fallback models were unavailable|Configured Vertex model and fallback models were unavailable|emitted provider infrastructure or failure-signal output|before provider infrastructure failure|litellm(\.exceptions)?\.NotFoundError[^[:cntrl:]]*Nvidia_nimException[^[:cntrl:]]*Error code:[[:space:]]*404' + backend_unavailable_signal='RateLimitError|Too many requests\. For more on scraping GitHub|exceeded your current quota|insufficient_quota|billing details|"status"[[:space:]]*:[[:space:]]*"RESOURCE_EXHAUSTED"|tokens_limit_reached|Request body too large|Max size:[[:space:]]*[0-9]+[[:space:]]+tokens|Error code:[[:space:]]*413|LLM CONNECTION FAILED|Could not establish connection to the language model|LLM warm-up failed|Configured model and fallback models were unavailable|Configured Vertex model and fallback models were unavailable|emitted provider infrastructure or failure-signal output|before provider infrastructure failure|litellm(\.exceptions)?\.NotFoundError[^[:cntrl:]]*Nvidia_nimException[^[:cntrl:]]*Error code:[[:space:]]*404|loginAsGuest failed after [0-9]+ attempts:[^[:cntrl:]]*curl exit 7:[^[:cntrl:]]*Failed to connect to 127\.0\.0\.1 port 48080' # Any evidence that a vulnerability was actually reported. Its presence # forces a hard failure so real findings are NEVER downgraded. Keep the # severity branch anchored away from identifiers so environment lines diff --git a/CHANGELOG.md b/CHANGELOG.md index 29609cb596..c4a94b04c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ Semantic Versioning where the repository publishes a release. ### Changed +- Classified the known Caido guest-login sandbox startup failure on `127.0.0.1:48080` as bounded Strix infrastructure evidence, while retaining fail-closed behavior for arbitrary connection failures and all vulnerability signals. - Require the hourly repair worker to establish an exact-head root cause, enumerate the smallest remediation candidates, and prove writer authority, sealed-path scope, credentials, dependency order, verifiability, and causal effect before editing; infeasible or external blockers leave the tree unchanged while the broader loop continues with another eligible PR or buyer-visible product gap. - Run the bounded Quarantine Sandbox Runtime heartbeat at minute 14 without granting the caller model secrets, repository mutation permissions, approval, merge, release, artifact-execution, or final security-verdict authority. - Run the bounded Clearfolio PR review-feedback repair caller at minute 23 of every hour while keeping the shared scheduler free of product-specific timers and repository names for modular reuse by naruon, contextual-orchestrator, Inkspan, and other CWL services. diff --git a/docs/doctoring/strix-nvidia-nim-not-found-fallback.md b/docs/doctoring/strix-nvidia-nim-not-found-fallback.md index 70299ebdf7..8842a017f9 100644 --- a/docs/doctoring/strix-nvidia-nim-not-found-fallback.md +++ b/docs/doctoring/strix-nvidia-nim-not-found-fallback.md @@ -35,6 +35,11 @@ when the run log contains no vulnerability signal. Any reported severity or non-zero vulnerability count remains blocking. Scanner reports and attempt logs remain available as artifacts. +The same bounded infrastructure classification covers the known Caido guest-login +sandbox startup failure (`loginAsGuest` followed by `curl exit 7` to the local +`127.0.0.1:48080` service). It does not cover arbitrary loopback or curl failures, +and a vulnerability signal still fails the check. + ## Verification contract Regression evidence proves that: diff --git a/tests/test_strix_nvidia_nim_not_found_fallback.py b/tests/test_strix_nvidia_nim_not_found_fallback.py index a48f3092d2..0c38dcdef6 100644 --- a/tests/test_strix_nvidia_nim_not_found_fallback.py +++ b/tests/test_strix_nvidia_nim_not_found_fallback.py @@ -233,6 +233,38 @@ def test_outer_workflow_rejects_nvidia_404_without_litellm_context(self) -> None ) ) + def test_outer_workflow_neutralizes_caido_guest_login_sandbox_failure(self) -> None: + """Neutralize only the known local Caido guest-login startup failure.""" + + self.assertTrue( + _workflow_neutralizes( + "loginAsGuest failed after 10 attempts: curl exit 7: " + "Failed to connect to 127.0.0.1 port 48080\n" + "Vulnerabilities 0\n" + ) + ) + + def test_outer_workflow_does_not_neutralize_caido_failure_with_findings(self) -> None: + """Keep vulnerability evidence blocking despite a Caido startup failure.""" + + self.assertFalse( + _workflow_neutralizes( + "loginAsGuest failed after 10 attempts: curl exit 7: " + "Failed to connect to 127.0.0.1 port 48080\n" + "Severity: HIGH\nVulnerabilities 1\n" + ) + ) + + def test_outer_workflow_does_not_neutralize_arbitrary_local_connection_failure(self) -> None: + """Do not broaden the exception to arbitrary curl or loopback failures.""" + + self.assertFalse( + _workflow_neutralizes( + "curl exit 7: Failed to connect to 127.0.0.1 port 48081\n" + "Vulnerabilities 0\n" + ) + ) + def test_outer_workflow_never_neutralizes_reported_vulnerabilities(self) -> None: """Keep a real vulnerability signal blocking despite provider failure.""" From 33b85a8cf48d5b6e0880d5071b360ffa46f83457 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 07:45:52 +0900 Subject: [PATCH 10/62] fix(strix): require trusted caido traceback marker --- .github/workflows/strix.yml | 2 +- CHANGELOG.md | 2 +- .../strix-nvidia-nim-not-found-fallback.md | 7 ++++--- tests/test_strix_nvidia_nim_not_found_fallback.py | 15 +++++++++++++-- 4 files changed, 19 insertions(+), 7 deletions(-) diff --git a/.github/workflows/strix.yml b/.github/workflows/strix.yml index b362dc94f6..24f0f98b5f 100644 --- a/.github/workflows/strix.yml +++ b/.github/workflows/strix.yml @@ -867,7 +867,7 @@ jobs: fi # Recognized signals that the LLM backend was unavailable / starved. - backend_unavailable_signal='RateLimitError|Too many requests\. For more on scraping GitHub|exceeded your current quota|insufficient_quota|billing details|"status"[[:space:]]*:[[:space:]]*"RESOURCE_EXHAUSTED"|tokens_limit_reached|Request body too large|Max size:[[:space:]]*[0-9]+[[:space:]]+tokens|Error code:[[:space:]]*413|LLM CONNECTION FAILED|Could not establish connection to the language model|LLM warm-up failed|Configured model and fallback models were unavailable|Configured Vertex model and fallback models were unavailable|emitted provider infrastructure or failure-signal output|before provider infrastructure failure|litellm(\.exceptions)?\.NotFoundError[^[:cntrl:]]*Nvidia_nimException[^[:cntrl:]]*Error code:[[:space:]]*404|loginAsGuest failed after [0-9]+ attempts:[^[:cntrl:]]*curl exit 7:[^[:cntrl:]]*Failed to connect to 127\.0\.0\.1 port 48080' + backend_unavailable_signal='RateLimitError|Too many requests\. For more on scraping GitHub|exceeded your current quota|insufficient_quota|billing details|"status"[[:space:]]*:[[:space:]]*"RESOURCE_EXHAUSTED"|tokens_limit_reached|Request body too large|Max size:[[:space:]]*[0-9]+[[:space:]]+tokens|Error code:[[:space:]]*413|LLM CONNECTION FAILED|Could not establish connection to the language model|LLM warm-up failed|Configured model and fallback models were unavailable|Configured Vertex model and fallback models were unavailable|emitted provider infrastructure or failure-signal output|before provider infrastructure failure|litellm(\.exceptions)?\.NotFoundError[^[:cntrl:]]*Nvidia_nimException[^[:cntrl:]]*Error code:[[:space:]]*404|RuntimeError:[[:space:]]*loginAsGuest failed after [0-9]+ attempts:[^[:cntrl:]]*curl exit 7:[^[:cntrl:]]*Failed to connect to 127\.0\.0\.1 port 48080' # Any evidence that a vulnerability was actually reported. Its presence # forces a hard failure so real findings are NEVER downgraded. Keep the # severity branch anchored away from identifiers so environment lines diff --git a/CHANGELOG.md b/CHANGELOG.md index c4a94b04c1..de4d4c5060 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,7 +21,7 @@ Semantic Versioning where the repository publishes a release. ### Changed -- Classified the known Caido guest-login sandbox startup failure on `127.0.0.1:48080` as bounded Strix infrastructure evidence, while retaining fail-closed behavior for arbitrary connection failures and all vulnerability signals. +- Classified only the known Caido `RuntimeError: loginAsGuest` sandbox startup failure on `127.0.0.1:48080` as bounded Strix infrastructure evidence, while retaining fail-closed behavior for arbitrary connection failures, repository-output text, and all vulnerability signals. - Require the hourly repair worker to establish an exact-head root cause, enumerate the smallest remediation candidates, and prove writer authority, sealed-path scope, credentials, dependency order, verifiability, and causal effect before editing; infeasible or external blockers leave the tree unchanged while the broader loop continues with another eligible PR or buyer-visible product gap. - Run the bounded Quarantine Sandbox Runtime heartbeat at minute 14 without granting the caller model secrets, repository mutation permissions, approval, merge, release, artifact-execution, or final security-verdict authority. - Run the bounded Clearfolio PR review-feedback repair caller at minute 23 of every hour while keeping the shared scheduler free of product-specific timers and repository names for modular reuse by naruon, contextual-orchestrator, Inkspan, and other CWL services. diff --git a/docs/doctoring/strix-nvidia-nim-not-found-fallback.md b/docs/doctoring/strix-nvidia-nim-not-found-fallback.md index 8842a017f9..761883b0de 100644 --- a/docs/doctoring/strix-nvidia-nim-not-found-fallback.md +++ b/docs/doctoring/strix-nvidia-nim-not-found-fallback.md @@ -36,9 +36,10 @@ non-zero vulnerability count remains blocking. Scanner reports and attempt logs remain available as artifacts. The same bounded infrastructure classification covers the known Caido guest-login -sandbox startup failure (`loginAsGuest` followed by `curl exit 7` to the local -`127.0.0.1:48080` service). It does not cover arbitrary loopback or curl failures, -and a vulnerability signal still fails the check. +sandbox startup failure when the runtime traceback contains `RuntimeError: +loginAsGuest`, `curl exit 7`, and the local `127.0.0.1:48080` service. It does not +cover arbitrary loopback, curl, or repository-output text, and a vulnerability +signal still fails the check. ## Verification contract diff --git a/tests/test_strix_nvidia_nim_not_found_fallback.py b/tests/test_strix_nvidia_nim_not_found_fallback.py index 0c38dcdef6..8ceae5d190 100644 --- a/tests/test_strix_nvidia_nim_not_found_fallback.py +++ b/tests/test_strix_nvidia_nim_not_found_fallback.py @@ -238,7 +238,7 @@ def test_outer_workflow_neutralizes_caido_guest_login_sandbox_failure(self) -> N self.assertTrue( _workflow_neutralizes( - "loginAsGuest failed after 10 attempts: curl exit 7: " + "RuntimeError: loginAsGuest failed after 10 attempts: curl exit 7: " "Failed to connect to 127.0.0.1 port 48080\n" "Vulnerabilities 0\n" ) @@ -249,12 +249,23 @@ def test_outer_workflow_does_not_neutralize_caido_failure_with_findings(self) -> self.assertFalse( _workflow_neutralizes( - "loginAsGuest failed after 10 attempts: curl exit 7: " + "RuntimeError: loginAsGuest failed after 10 attempts: curl exit 7: " "Failed to connect to 127.0.0.1 port 48080\n" "Severity: HIGH\nVulnerabilities 1\n" ) ) + def test_outer_workflow_rejects_untrusted_caido_like_text_without_traceback(self) -> None: + """Require the trusted runtime-error marker before classifying Caido output.""" + + self.assertFalse( + _workflow_neutralizes( + "loginAsGuest failed after 10 attempts: curl exit 7: " + "Failed to connect to 127.0.0.1 port 48080\n" + "Vulnerabilities 0\n" + ) + ) + def test_outer_workflow_does_not_neutralize_arbitrary_local_connection_failure(self) -> None: """Do not broaden the exception to arbitrary curl or loopback failures.""" From b628e88871ecc845a46989bde48b15292a41690e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 06:18:59 -0700 Subject: [PATCH 11/62] fix(governance): preserve proposal branch create transition --- CHANGELOG.md | 8 ++-- README.md | 12 +++--- ...01-central-review-stacked-pull-requests.md | 26 ++++++----- docs/org-required-workflow-rollout.md | 25 ++++++----- .../ci/audit_central_required_workflows.py | 18 +++++--- ...central_required_workflow_ruleset_audit.py | 43 +++++++++++++------ 6 files changed, 82 insertions(+), 50 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eed7f6ace4..201343d210 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,9 +37,11 @@ Semantic Versioning where the repository publishes a release. ### Fixed -- Tightened the central ruleset auditor to require the exact `ref_name` shape - (`include=["~ALL"]`, `exclude=[]`) and aligned operator documentation with - the three repository exclusions. +- Tightened the central ruleset auditor to require exact default-branch scope + (`include=["~DEFAULT_BRANCH"]`, `exclude=[]`) and non-blocking create + transitions, rejecting `~ALL` and every extra proposal-ref target after live + 409/422 canaries proved the broader combined ruleset deadlocked normal branch + creation and updates. - Keep local agent-mention Actions access read-only, use the established reviewer tokens for sibling-repository acknowledgements, and remove the replacing concurrency group together with the unsupported `concurrency.queue` key, so every eligible exact-head mention can enqueue while scheduled sweeps remain independently single-flight. - Refused PR Review Merge Scheduler head mutations, `update-branch` and the last-push approval head restamp, whenever the resolved mutation credential is the workflow `GITHUB_TOKEN`. GitHub starts no workflow run for events created with that credential, so the moved head collected no current-head required checks and the PR stayed permanently `BLOCKED` with a `github-actions[bot]` merge commit that no later scheduler run could repair, because the branch was no longer behind. The scheduler now waits with `head_mutation_credential_upgrade` guidance naming `PR_REVIEW_MERGE_TOKEN`, `OPENCODE_APPROVE_TOKEN`, and the OpenCode app token exchange. - Parsed `opencode.jsonc` as JSONC (stripping `//` and `/* */` comments outside string literals) in the reasoning-effort guard and its contract tests, instead of raw `json.loads`, which rejected the file the moment it carried its first explanatory comment (added for the `contextual-orchestrator` provider block) with `Expecting property name enclosed in double quotes`. Comment markers inside string values, such as the `$schema` URL, are left untouched. diff --git a/README.md b/README.md index 4eb3ebedbd..602f582b7f 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ that means: | Mode | What happens | | --- | --- | | **따로 (this repo alone)** | Clone, test, and operate `.github` as the org profile and workflow source. Local quality gates, Cloudflare dry-run, and this repository's own PRs do not depend on naruon or any sibling product checkout. | -| **또 같이 (siblings call it)** | A sibling enables the org required-workflow ruleset (already `repository_name.include=["~ALL"]` with `.github`, `IRT-bibliography-set`, and `noema` excluded, plus `ref_name.include=["~ALL"]`). GitHub runs the trusted workflows from `ContextualWisdomLab/.github@main` in every non-excluded sibling repository branch, including stacked PR base branches. Optional reusable callers (`deploy-pages.yml`, `pr-review-fix-scheduler.yml`) are `workflow_call` entry points, not files to copy. | +| **또 같이 (siblings call it)** | A sibling inherits the org required-workflow ruleset (`repository_name.include=["~ALL"]` with `.github`, `IRT-bibliography-set`, and `noema` excluded; `ref_name.include=["~DEFAULT_BRANCH"]`). GitHub runs the trusted workflows from `ContextualWisdomLab/.github@main` at the default-branch integration boundary. The scheduler provides review-only evidence for stacked PRs without governing proposal refs into a create/update deadlock. Optional reusable callers (`deploy-pages.yml`, `pr-review-fix-scheduler.yml`) are `workflow_call` entry points, not files to copy. | Do not copy Strix, OpenCode, Noema, or scheduler workflow files into a sibling to "satisfy CI." Thick downstream sync PRs are an anti-pattern @@ -65,11 +65,11 @@ The last checked-in ruleset ledger is Checked-in operator facts: -- Ruleset `18156473` is **active**. It targets every non-excluded repository - branch (`repository_name.include=["~ALL"]`, exclusions `.github`, - `IRT-bibliography-set`, and `noema`, and `ref_name.include=["~ALL"]`), - including stacked pull-request base branches, and sources workflows from - this repository at `refs/heads/main`. +- Ruleset `18156473` is **active**. It targets the default branch of every + non-excluded repository (`repository_name.include=["~ALL"]`, exclusions + `.github`, `IRT-bibliography-set`, and `noema`, and + `ref_name.include=["~DEFAULT_BRANCH"]`), permits the create transition, and + sources workflows from this repository at `refs/heads/main`. - Active required workflow paths: `close-empty-pr.yml`, `noema-review.yml`, `opencode-review.yml`, `pr-review-merge-scheduler.yml`, `security-scan.yml`, `strix.yml`, and `sast-semgrep.yml`. diff --git a/docs/adr/0001-central-review-stacked-pull-requests.md b/docs/adr/0001-central-review-stacked-pull-requests.md index cb59cc384c..5123d7810d 100644 --- a/docs/adr/0001-central-review-stacked-pull-requests.md +++ b/docs/adr/0001-central-review-stacked-pull-requests.md @@ -8,22 +8,28 @@ ## Decision The organization ruleset `CWL Central required workflows` (`18156473`) applies -to every branch reference (`ref_name.include=["~ALL"]`) in inherited -repositories. Central OpenCode, Noema, security, and scheduler workflows stay -owned by `ContextualWisdomLab/.github` at `refs/heads/main`. +exactly to each inherited repository's default branch +(`ref_name.include=["~DEFAULT_BRANCH"]`, `ref_name.exclude=[]`). Its workflows +use `do_not_enforce_on_create=true`. Central OpenCode, Noema, security, and +scheduler workflows stay owned by `ContextualWisdomLab/.github` at +`refs/heads/main`. ## Context -Stacked PRs target another feature branch, so a default-branch-only ruleset did -not materialize the central required workflow entrypoints. This left buyer- -visible changes with local checks but without the same independent review and -security evidence used for main-targeting PRs. +Stacked PRs target another feature branch, so the default-branch ruleset does +not materialize required-workflow entrypoints for that intermediate PR. The +central scheduler supplies exact-head review-only evidence for the stacked +phase. Applying the combined workflow, pull-request, deletion, and +non-fast-forward rules to every ref was rejected after live 409/422 canaries +proved that it made normal proposal-branch creation and updates impossible. ## Consequences -- Every stacked PR receives the same current-head governance entrypoints. -- The scheduler may dispatch review-only work for non-default base branches; - merge automation remains guarded by the PR's actual policy and checks. +- Every stacked PR receives current-head central review-only dispatch. +- The scheduler does not update or merge non-default-base PRs; the final + default-branch integration PR receives the full required-workflow gate. +- Proposal refs can be created and updated without a circular requirement for + checks that cannot exist before the ref exists. - Branch-scope drift is detected by `scripts/ci/audit_central_required_workflows.py` and its regression tests. - No workflow is copied into a product repository, preserving the MSA control diff --git a/docs/org-required-workflow-rollout.md b/docs/org-required-workflow-rollout.md index ca1c5275a0..b66a5cc292 100644 --- a/docs/org-required-workflow-rollout.md +++ b/docs/org-required-workflow-rollout.md @@ -9,7 +9,8 @@ Use an organization repository ruleset instead of copying workflow files into ea - Ruleset: `CWL Central required workflows` - Ruleset ID: `18156473` - Enforcement: `active` -- Target: branch rules on every non-excluded repository branch (`repository_name.include=["~ALL"]`, `repository_name.exclude=[".github", "IRT-bibliography-set", "noema"]`, `ref_name.include=["~ALL"]`, `ref_name.exclude=[]`), including stacked pull-request base branches +- Target: the default branch of every non-excluded repository (`repository_name.include=["~ALL"]`, `repository_name.exclude=[".github", "IRT-bibliography-set", "noema"]`, `ref_name.include=["~DEFAULT_BRANCH"]`, `ref_name.exclude=[]`) +- Create transition: required workflows use `do_not_enforce_on_create=true`; proposal branches do not match this strict ruleset, and a new repository can establish its first default-branch commit before subsequent changes are governed - Required workflow source repository: `ContextualWisdomLab/.github` - Required workflow source repository ID: `1274066402` - Active required workflow paths: @@ -33,16 +34,18 @@ This keeps Strix security evidence, OpenCode and independent Noema review eviden ### Stacked pull-request coverage -On 2026-08-20, live PRs #158 and #159 in `ContextualWisdomLab/TEPP` targeted -`feat/lineageweave-live-consumer-contract` rather than `main`. They had product -checks but no centrally materialized OpenCode or Noema workflow runs because -the ruleset was scoped to `~DEFAULT_BRANCH`. Ruleset `18156473` now uses -`ref_name.include=["~ALL"]`; the existing scheduler already enumerates open -PRs across base branches and dispatches exact-head review-only work for -stacked PRs. The audit script and regression test enforce this scope so a -future ruleset rollback fails closed. The live repair also restored the -checked-in two-approval contract; workflow source, repository exclusions, and -stale-review/thread-resolution/last-push protections were preserved. +On 2026-08-20, live PRs #158 and #159 in `ContextualWisdomLab/TEPP` showed that +stacked PRs need centrally materialized OpenCode and Noema review evidence. +Expanding the combined required-workflow and pull-request ruleset to `~ALL` +was later falsified by independent 409/422 canaries: proposal branches could +not be created or updated because GitHub required a PR and required-workflow +evidence before the proposal ref could exist. Ruleset `18156473` therefore +stays exact-default-branch. The central scheduler enumerates non-default-base +PRs and dispatches exact-head review-only work; merge and branch mutation stay +disabled for that stacked phase. The final integration PR to the default +branch remains subject to the full required-workflow, two-approval, +stale-review, thread-resolution, last-push, deletion, and non-fast-forward +contract. ## OpenCode required workflow posture diff --git a/scripts/ci/audit_central_required_workflows.py b/scripts/ci/audit_central_required_workflows.py index 43979f3b9e..af5a33b0d4 100644 --- a/scripts/ci/audit_central_required_workflows.py +++ b/scripts/ci/audit_central_required_workflows.py @@ -5,11 +5,10 @@ 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" SOURCE_REPOSITORY_ID = 1274066402 @@ -112,19 +111,26 @@ 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 ref_names.get("include") != ["~ALL"] or ref_names.get("exclude") != []: - errors.append("central ruleset does not target stacked and default-branch PRs") + if ( + ref_names.get("include") != ["~DEFAULT_BRANCH"] + or ref_names.get("exclude") != [] + ): + errors.append("central ruleset ref scope must be exactly the default branch") 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 [] + if workflow_parameters.get("do_not_enforce_on_create") is not True: + errors.append("central required workflows block the branch create transition") + 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): diff --git a/tests/test_central_required_workflow_ruleset_audit.py b/tests/test_central_required_workflow_ruleset_audit.py index 6ae8b8a46b..b4cd997c76 100644 --- a/tests/test_central_required_workflow_ruleset_audit.py +++ b/tests/test_central_required_workflow_ruleset_audit.py @@ -1,11 +1,10 @@ +import json from copy import deepcopy from io import StringIO -import json from pathlib import Path from scripts.ci import audit_central_required_workflows as audit - REPO_ROOT = Path(__file__).resolve().parents[1] def ruleset_payload() -> dict: """Return the expected live central required-workflow ruleset shape.""" @@ -28,13 +27,13 @@ def ruleset_payload() -> dict: "include": ["~ALL"], "exclude": ["noema", "IRT-bibliography-set", ".github"], }, - "ref_name": {"include": ["~ALL"], "exclude": []}, + "ref_name": {"include": ["~DEFAULT_BRANCH"], "exclude": []}, }, "rules": [ { "type": "workflows", "parameters": { - "do_not_enforce_on_create": False, + "do_not_enforce_on_create": True, "workflows": [ { "repository_id": 1274066402, @@ -94,23 +93,28 @@ def test_inherited_ruleset_and_organization_scope_probes_pass() -> None: assert audit.audit_ruleset(inherited_ruleset_payload()) == [] -def test_default_branch_only_scope_rejects_stacked_pull_requests() -> None: - payload = ruleset_payload() - payload["conditions"]["ref_name"]["include"] = ["~DEFAULT_BRANCH"] +def test_ref_scope_rejects_all_branch_and_extra_proposal_branch_targets() -> None: + for include in ( + ["~ALL"], + ["~DEFAULT_BRANCH", "~ALL"], + ["~DEFAULT_BRANCH", "refs/heads/feature/*"], + ): + payload = ruleset_payload() + payload["conditions"]["ref_name"]["include"] = include - assert audit.audit_ruleset(payload) == [ - "central ruleset does not target stacked and default-branch PRs" - ] + assert audit.audit_ruleset(payload) == [ + "central ruleset ref scope must be exactly the default branch" + ] def test_ref_scope_rejects_branch_exclusions() -> None: - """All branch refs must be included without exclusions.""" + """The strict default-branch ruleset must not hide excluded refs.""" payload = ruleset_payload() payload["conditions"]["ref_name"]["exclude"] = ["refs/heads/release/*"] assert audit.audit_ruleset(payload) == [ - "central ruleset does not target stacked and default-branch PRs" + "central ruleset ref scope must be exactly the default branch" ] @@ -120,7 +124,17 @@ def test_ref_scope_rejects_string_include() -> None: payload["conditions"]["ref_name"]["include"] = "~ALL" assert audit.audit_ruleset(payload) == [ - "central ruleset does not target stacked and default-branch PRs" + "central ruleset ref scope must be exactly the default branch" + ] + + +def test_workflows_must_not_block_branch_create_transition() -> None: + payload = ruleset_payload() + workflow_rule = next(rule for rule in payload["rules"] if rule["type"] == "workflows") + workflow_rule["parameters"]["do_not_enforce_on_create"] = False + + assert audit.audit_ruleset(payload) == [ + "central required workflows block the branch create transition" ] @@ -234,8 +248,9 @@ def test_audit_reports_all_structural_and_protection_drift() -> None: "central ruleset enforcement is not active", "central ruleset does not include all repositories", "central ruleset repository exclusions drifted: expected ['.github', 'IRT-bibliography-set', 'noema'], got []", - "central ruleset does not target stacked and default-branch PRs", + "central ruleset ref scope must be exactly the default branch", "expected one workflows rule, found 0", + "central required workflows block the branch create transition", "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", From 8923badcc65094b3119b40cfca44f751a4751612 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 03:32:52 +0900 Subject: [PATCH 12/62] fix(ci): refresh audit lock and scheduler assertion --- requirements-pip-audit-ci-hashes.txt | 6 +++--- scripts/ci/test_strix_quick_gate.sh | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/requirements-pip-audit-ci-hashes.txt b/requirements-pip-audit-ci-hashes.txt index ade197a49a..0ae099d8fe 100644 --- a/requirements-pip-audit-ci-hashes.txt +++ b/requirements-pip-audit-ci-hashes.txt @@ -213,9 +213,9 @@ packaging==26.2 \ # via # pip-audit # pip-requirements-parser -pip==26.1.2 \ - --hash=sha256:382ff9f685ee3bc25864f820aa50505825f10f5458ffff07e30a6d96e5715cab \ - --hash=sha256:f49cd134c61cf2fd75e0ce2676db03e4054504a5a4986d00f8299ae632dc4605 +pip==26.2.1 \ + --hash=sha256:71138adf1f4ca900cdb7d289c21b7494329f2332b6d85f0e1c42108c0384ed3e \ + --hash=sha256:f6ad667e89a1fe78046c8f13232b247200f5258d7828f3f7883d660878e0813f # via pip-api pip-api==0.0.34 \ --hash=sha256:8b2d7d7c37f2447373aa2cf8b1f60a2f2b27a84e1e9e0294a3f6ef10eb3ba6bb \ diff --git a/scripts/ci/test_strix_quick_gate.sh b/scripts/ci/test_strix_quick_gate.sh index ac9ce1d8bd..bbe9aa8b21 100755 --- a/scripts/ci/test_strix_quick_gate.sh +++ b/scripts/ci/test_strix_quick_gate.sh @@ -1506,8 +1506,8 @@ assert_pr_review_merge_scheduler_uses_github_actions_bot_token() { assert_file_contains "$workflow_file" "github.event_name == 'pull_request_target' && format('pr-{0}', github.event.pull_request.number)" "scheduler scopes pull_request_target concurrency to the active PR" assert_file_contains "$workflow_file" "github.event_name == 'workflow_run' && github.event.workflow_run.pull_requests[0].number && format('pr-{0}', github.event.workflow_run.pull_requests[0].number)" "scheduler scopes workflow_run concurrency to the completed review PR" assert_file_contains "$workflow_file" "github.event_name == 'schedule' && format('schedule-{0}', github.event.schedule)" "scheduler isolates the 15-minute organization sweep from the separate 30-minute scheduled scan" - assert_file_contains "$workflow_file" "github.event_name == 'repository_dispatch' && github.run_id" "scheduler keeps manual queue scans isolated per run" - assert_file_contains "$workflow_file" "cancel-in-progress: \${{ github.event_name == 'pull_request_target' || github.event_name == 'pull_request_review' || github.event_name == 'repository_dispatch' }}" "scheduler cancels stale PR/review/manual queue scans instead of accumulating merge/update attempts" + assert_file_contains "$workflow_file" "github.event_name == 'repository_dispatch' && format('repo-dispatch-{0}', github.repository)" "scheduler keeps manual queue scans isolated per repository" + assert_file_contains "$workflow_file" "cancel-in-progress: \${{ github.event_name == 'pull_request_target' || github.event_name == 'pull_request_review' || github.event_name == 'repository_dispatch' || (github.event_name == 'workflow_run' && !github.event.workflow_run.pull_requests[0].number) }}" "scheduler cancels stale PR/review/manual queue scans and orphaned workflow runs without cancelling active workflow-run evidence" assert_file_contains "$workflow_file" "timeout-minutes: 60" "organization sweep has enough headroom to finish the complete repository walk" assert_file_contains "$workflow_file" "ORG_SWEEP_TRIGGER_REVIEWS: \${{ github.event_name == 'schedule' ||" "scheduled organization sweeps retry missing current-head OpenCode reviews" assert_file_contains "$workflow_file" "ORG_SWEEP_ENABLE_AUTO_MERGE: \${{ github.event_name == 'schedule' ||" "scheduled organization sweeps merge approved current heads" From c6e1ba3cee48edd233e0c63b05d1f3785e307f98 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 11:55:10 -0700 Subject: [PATCH 13/62] chore(governance): restore canonical pip lock ownership --- requirements-pip-audit-ci-hashes.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements-pip-audit-ci-hashes.txt b/requirements-pip-audit-ci-hashes.txt index 0ae099d8fe..ade197a49a 100644 --- a/requirements-pip-audit-ci-hashes.txt +++ b/requirements-pip-audit-ci-hashes.txt @@ -213,9 +213,9 @@ packaging==26.2 \ # via # pip-audit # pip-requirements-parser -pip==26.2.1 \ - --hash=sha256:71138adf1f4ca900cdb7d289c21b7494329f2332b6d85f0e1c42108c0384ed3e \ - --hash=sha256:f6ad667e89a1fe78046c8f13232b247200f5258d7828f3f7883d660878e0813f +pip==26.1.2 \ + --hash=sha256:382ff9f685ee3bc25864f820aa50505825f10f5458ffff07e30a6d96e5715cab \ + --hash=sha256:f49cd134c61cf2fd75e0ce2676db03e4054504a5a4986d00f8299ae632dc4605 # via pip-api pip-api==0.0.34 \ --hash=sha256:8b2d7d7c37f2447373aa2cf8b1f60a2f2b27a84e1e9e0294a3f6ef10eb3ba6bb \ From 6bf0447388c08f699f9550e4ed4f71c84fb4c723 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 04:04:02 +0900 Subject: [PATCH 14/62] fix(governance): avoid misleading multi-rule drift --- scripts/ci/audit_central_required_workflows.py | 2 +- .../test_central_required_workflow_ruleset_audit.py | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/scripts/ci/audit_central_required_workflows.py b/scripts/ci/audit_central_required_workflows.py index af5a33b0d4..02bc7a4553 100644 --- a/scripts/ci/audit_central_required_workflows.py +++ b/scripts/ci/audit_central_required_workflows.py @@ -128,7 +128,7 @@ def audit_ruleset(payload: dict[str, Any]) -> list[str]: workflows = workflow_parameters.get("workflows") workflows = workflows if isinstance(workflows, list) else [] - if workflow_parameters.get("do_not_enforce_on_create") is not True: + 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") workflows_by_path: dict[str, list[dict[str, Any]]] = {} diff --git a/tests/test_central_required_workflow_ruleset_audit.py b/tests/test_central_required_workflow_ruleset_audit.py index b4cd997c76..74eedf9fa5 100644 --- a/tests/test_central_required_workflow_ruleset_audit.py +++ b/tests/test_central_required_workflow_ruleset_audit.py @@ -138,6 +138,17 @@ def test_workflows_must_not_block_branch_create_transition() -> None: ] +def test_multiple_workflow_rules_do_not_invent_create_transition_drift() -> None: + """Report structural multiplicity without attributing a missing flag to it.""" + payload = ruleset_payload() + payload["rules"].append(payload["rules"][0].copy()) + + errors = audit.audit_ruleset(payload) + + assert "expected one workflows rule, found 2" in errors + assert "central required workflows block the branch create transition" not in errors + + def test_inherited_scope_allows_private_exclusion_outside_token_visibility() -> None: payload = inherited_ruleset_payload() payload[audit.INHERITED_SCOPE_FIELD].pop("IRT-bibliography-set") @@ -250,7 +261,6 @@ def test_audit_reports_all_structural_and_protection_drift() -> None: "central ruleset repository exclusions drifted: expected ['.github', 'IRT-bibliography-set', 'noema'], got []", "central ruleset ref scope must be exactly the default branch", "expected one workflows rule, found 0", - "central required workflows block the branch create transition", "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", From 5d641026076713af1d200b67a7a571e758348b6d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 04:57:44 +0900 Subject: [PATCH 15/62] ci: refresh dependency and scheduler contracts --- requirements-pip-audit-ci-hashes.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements-pip-audit-ci-hashes.txt b/requirements-pip-audit-ci-hashes.txt index ade197a49a..0ae099d8fe 100644 --- a/requirements-pip-audit-ci-hashes.txt +++ b/requirements-pip-audit-ci-hashes.txt @@ -213,9 +213,9 @@ packaging==26.2 \ # via # pip-audit # pip-requirements-parser -pip==26.1.2 \ - --hash=sha256:382ff9f685ee3bc25864f820aa50505825f10f5458ffff07e30a6d96e5715cab \ - --hash=sha256:f49cd134c61cf2fd75e0ce2676db03e4054504a5a4986d00f8299ae632dc4605 +pip==26.2.1 \ + --hash=sha256:71138adf1f4ca900cdb7d289c21b7494329f2332b6d85f0e1c42108c0384ed3e \ + --hash=sha256:f6ad667e89a1fe78046c8f13232b247200f5258d7828f3f7883d660878e0813f # via pip-api pip-api==0.0.34 \ --hash=sha256:8b2d7d7c37f2447373aa2cf8b1f60a2f2b27a84e1e9e0294a3f6ef10eb3ba6bb \ From 4ae3c6184ae96b588e98ac1e66820a63bdc9ca24 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 20:23:42 -0700 Subject: [PATCH 16/62] fix(governance): reject hidden ruleset drift --- .../ci/audit_central_required_workflows.py | 25 ++++++-- ...central_required_workflow_ruleset_audit.py | 57 ++++++++++++++++++- 2 files changed, 76 insertions(+), 6 deletions(-) diff --git a/scripts/ci/audit_central_required_workflows.py b/scripts/ci/audit_central_required_workflows.py index f2ad2cd10c..a14c543cc4 100644 --- a/scripts/ci/audit_central_required_workflows.py +++ b/scripts/ci/audit_central_required_workflows.py @@ -137,12 +137,27 @@ def audit_ruleset(payload: dict[str, Any]) -> list[str]: 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: @@ -176,8 +191,8 @@ def audit_ruleset(payload: dict[str, Any]) -> list[str]: 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") + if allowed_methods != {"merge", "squash"}: + errors.append("only merge and squash may be allowed merge methods") if not _typed_rules(payload, "deletion"): errors.append("default-branch deletion protection is missing") @@ -260,6 +275,8 @@ def audit_repository_ruleset(payload: dict[str, Any]) -> list[str]: 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 {} @@ -283,8 +300,8 @@ def audit_repository_ruleset(payload: dict[str, Any]) -> list[str]: if parameters.get("required_review_thread_resolution") is not True: errors.append("repository ruleset review-thread resolution protection is disabled") allowed_methods = set(parameters.get("allowed_merge_methods") or []) - if not {"merge", "squash"}.issubset(allowed_methods): - errors.append("repository ruleset does not allow both merge and squash") + 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") diff --git a/tests/test_central_required_workflow_ruleset_audit.py b/tests/test_central_required_workflow_ruleset_audit.py index fcb0d29a19..d599f4e9d9 100644 --- a/tests/test_central_required_workflow_ruleset_audit.py +++ b/tests/test_central_required_workflow_ruleset_audit.py @@ -149,6 +149,34 @@ def test_expected_central_ruleset_passes(monkeypatch, capsys) -> None: ) +def test_central_ruleset_rejects_unexpected_and_malformed_workflows() -> None: + payload = ruleset_payload() + workflow_rule = next(rule for rule in payload["rules"] if rule["type"] == "workflows") + workflow_rule["parameters"]["workflows"].extend( + [ + { + "repository_id": 1274066402, + "path": ".github/workflows/unexpected.yml", + "ref": "refs/heads/main", + }, + {"repository_id": 1274066402, "path": 42, "ref": "refs/heads/main"}, + ] + ) + + errors = audit.audit_ruleset(payload) + + assert "unexpected central required workflows: ['.github/workflows/unexpected.yml']" in errors + assert "central required workflows contain 1 malformed entry" in errors + + +def test_central_ruleset_rejects_rebase_merge_method() -> None: + payload = ruleset_payload() + review_rule = next(rule for rule in payload["rules"] if rule["type"] == "pull_request") + review_rule["parameters"]["allowed_merge_methods"].append("rebase") + + assert "only merge and squash may be allowed merge methods" in audit.audit_ruleset(payload) + + def test_inherited_ruleset_and_organization_scope_probes_pass() -> None: assert audit.audit_ruleset(inherited_ruleset_payload()) == [] @@ -175,6 +203,31 @@ def test_repository_ruleset_rejects_live_weakened_review_controls() -> None: ] +def test_repository_ruleset_rejects_rebase_merge_method() -> None: + payload = repository_ruleset_payload() + review_rule = next(rule for rule in payload["rules"] if rule["type"] == "pull_request") + review_rule["parameters"]["allowed_merge_methods"].append("rebase") + + assert audit.audit_repository_ruleset(payload) == [ + "repository ruleset must allow only merge and squash", + ] + + +def test_repository_ruleset_rejects_bypass_actors() -> None: + payload = repository_ruleset_payload() + payload["bypass_actors"] = [ + { + "actor_id": None, + "actor_type": "OrganizationAdmin", + "bypass_mode": "always", + } + ] + + assert audit.audit_repository_ruleset(payload) == [ + "repository ruleset must not configure bypass actors", + ] + + def test_repository_ruleset_reports_structural_and_protection_drift() -> None: payload = { "id": 0, @@ -210,7 +263,7 @@ def test_repository_ruleset_rejects_malformed_review_parameters() -> None: "repository ruleset stale-review dismissal on push is disabled", "repository ruleset last-push approval protection is disabled", "repository ruleset review-thread resolution protection is disabled", - "repository ruleset does not allow both merge and squash", + "repository ruleset must allow only merge and squash", ] @@ -496,7 +549,7 @@ def test_audit_reports_malformed_duplicate_workflows_and_weak_review_parameters( 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 - assert "merge and squash are not both allowed merge methods" in errors + assert "only merge and squash may be allowed merge methods" in errors def test_audit_handles_malformed_rule_parameter_shapes() -> None: From d2224010a7eafa5ff23682521e10cbf09f94b5b1 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 20:28:50 -0700 Subject: [PATCH 17/62] fix(governance): audit organization bypass actors --- scripts/ci/audit_central_required_workflows.py | 2 ++ ...est_central_required_workflow_ruleset_audit.py | 15 +++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/scripts/ci/audit_central_required_workflows.py b/scripts/ci/audit_central_required_workflows.py index a14c543cc4..fc7f655732 100644 --- a/scripts/ci/audit_central_required_workflows.py +++ b/scripts/ci/audit_central_required_workflows.py @@ -61,6 +61,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", []) != []: + errors.append("central ruleset must not configure bypass actors") conditions = payload.get("conditions") conditions = conditions if isinstance(conditions, dict) else {} diff --git a/tests/test_central_required_workflow_ruleset_audit.py b/tests/test_central_required_workflow_ruleset_audit.py index d599f4e9d9..b3d4d4afef 100644 --- a/tests/test_central_required_workflow_ruleset_audit.py +++ b/tests/test_central_required_workflow_ruleset_audit.py @@ -177,6 +177,21 @@ def test_central_ruleset_rejects_rebase_merge_method() -> None: assert "only merge and squash may be allowed merge methods" in audit.audit_ruleset(payload) +def test_central_ruleset_rejects_bypass_actors() -> None: + payload = ruleset_payload() + payload["bypass_actors"] = [ + { + "actor_id": None, + "actor_type": "OrganizationAdmin", + "bypass_mode": "always", + } + ] + + assert audit.audit_ruleset(payload) == [ + "central ruleset must not configure bypass actors", + ] + + def test_inherited_ruleset_and_organization_scope_probes_pass() -> None: assert audit.audit_ruleset(inherited_ruleset_payload()) == [] From 0b0a45b34aa135f10db82d2a81d685f2d5f64980 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 20:41:33 -0700 Subject: [PATCH 18/62] fix(governance): fail closed on missing bypass evidence --- .../ci/audit_central_required_workflows.py | 4 ++-- ...central_required_workflow_ruleset_audit.py | 22 +++++++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/scripts/ci/audit_central_required_workflows.py b/scripts/ci/audit_central_required_workflows.py index fc7f655732..0eeaf87c6a 100644 --- a/scripts/ci/audit_central_required_workflows.py +++ b/scripts/ci/audit_central_required_workflows.py @@ -61,7 +61,7 @@ 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", []) != []: + if payload.get("bypass_actors") != []: errors.append("central ruleset must not configure bypass actors") conditions = payload.get("conditions") @@ -277,7 +277,7 @@ def audit_repository_ruleset(payload: dict[str, Any]) -> list[str]: 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", []) != []: + if payload.get("bypass_actors") != []: errors.append("repository ruleset must not configure bypass actors") conditions = payload.get("conditions") diff --git a/tests/test_central_required_workflow_ruleset_audit.py b/tests/test_central_required_workflow_ruleset_audit.py index b3d4d4afef..35b050eb88 100644 --- a/tests/test_central_required_workflow_ruleset_audit.py +++ b/tests/test_central_required_workflow_ruleset_audit.py @@ -24,6 +24,7 @@ def ruleset_payload() -> dict: "name": "CWL Central required workflows", "target": "branch", "enforcement": "active", + "bypass_actors": [], "conditions": { "repository_name": { "include": ["~ALL"], @@ -119,6 +120,7 @@ def repository_ruleset_payload() -> dict: "source_type": "Repository", "source": "ContextualWisdomLab/.github", "enforcement": "active", + "bypass_actors": [], "conditions": { "ref_name": {"include": ["~DEFAULT_BRANCH"], "exclude": []}, }, @@ -192,6 +194,15 @@ def test_central_ruleset_rejects_bypass_actors() -> None: ] +def test_central_ruleset_rejects_missing_bypass_evidence() -> None: + payload = ruleset_payload() + del payload["bypass_actors"] + + assert audit.audit_ruleset(payload) == [ + "central ruleset must not configure bypass actors", + ] + + def test_inherited_ruleset_and_organization_scope_probes_pass() -> None: assert audit.audit_ruleset(inherited_ruleset_payload()) == [] @@ -243,6 +254,15 @@ def test_repository_ruleset_rejects_bypass_actors() -> None: ] +def test_repository_ruleset_rejects_missing_bypass_evidence() -> None: + payload = repository_ruleset_payload() + del payload["bypass_actors"] + + assert audit.audit_repository_ruleset(payload) == [ + "repository ruleset must not configure bypass actors", + ] + + def test_repository_ruleset_reports_structural_and_protection_drift() -> None: payload = { "id": 0, @@ -261,6 +281,7 @@ def test_repository_ruleset_reports_structural_and_protection_drift() -> None: "repository ruleset source is not ContextualWisdomLab/.github", "repository ruleset target is not branch", "repository ruleset enforcement is not active", + "repository ruleset must not configure bypass actors", "repository ruleset ref scope must be exactly the default branch", "expected one repository pull_request rule, found 0", "repository default-branch deletion protection is missing", @@ -524,6 +545,7 @@ def test_audit_reports_all_structural_and_protection_drift() -> None: "expected ruleset name CWL Central required workflows", "central ruleset target is not branch", "central ruleset enforcement is not active", + "central ruleset must not configure bypass actors", "central ruleset does not include all repositories", "central ruleset repository exclusions drifted: expected ['.github', 'IRT-bibliography-set', 'noema'], got []", "central ruleset ref scope must be exactly the default branch", From 8ea2ec58bcd9f204a807a2925e779999480e087f Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 30 Aug 2026 06:42:59 +0000 Subject: [PATCH 19/62] Retrigger required checks against refreshed main (no new main commits to merge; branch already current) From ed314f6140a791b46724b84daff747fa5fb63604 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 1 Sep 2026 21:31:48 +0900 Subject: [PATCH 20/62] test(governance): define solo-maintainer ruleset RED --- tests/test_solo_maintainer_ruleset_policy.py | 93 ++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 tests/test_solo_maintainer_ruleset_policy.py diff --git a/tests/test_solo_maintainer_ruleset_policy.py b/tests/test_solo_maintainer_ruleset_policy.py new file mode 100644 index 0000000000..125f3ce1ca --- /dev/null +++ b/tests/test_solo_maintainer_ruleset_policy.py @@ -0,0 +1,93 @@ +"""Regression contract for solo-maintainer protected-branch governance.""" + +from scripts.ci import audit_central_required_workflows as audit + + +def _central_ruleset_payload() -> dict: + """Return the desired organization ruleset for a one-human-maintainer fleet.""" + return { + "id": audit.RULESET_ID, + "name": audit.RULESET_NAME, + "target": "branch", + "enforcement": "active", + "bypass_actors": [], + "conditions": { + "repository_name": { + "include": ["~ALL"], + "exclude": [".github", "IRT-bibliography-set", "noema"], + }, + "ref_name": {"include": ["~DEFAULT_BRANCH"], "exclude": []}, + }, + "rules": [ + { + "type": "workflows", + "parameters": { + "do_not_enforce_on_create": True, + "workflows": [ + { + "repository_id": audit.SOURCE_REPOSITORY_ID, + "path": path, + "ref": audit.SOURCE_REF, + } + for path in audit.REQUIRED_WORKFLOW_PATHS + ], + }, + }, + { + "type": "pull_request", + "parameters": { + "required_approving_review_count": 0, + "dismiss_stale_reviews_on_push": True, + "require_code_owner_review": False, + "require_last_push_approval": False, + "required_review_thread_resolution": True, + "required_reviewers": [], + "require_extra_approval_for_unattributed_changes": True, + "allowed_merge_methods": ["merge", "squash"], + }, + }, + {"type": "deletion"}, + {"type": "non_fast_forward"}, + ], + } + + +def _repository_ruleset_payload() -> dict: + """Return the desired .github repository ruleset under the same model.""" + return { + "id": audit.REPOSITORY_RULESET_ID, + "name": audit.REPOSITORY_RULESET_NAME, + "target": "branch", + "source_type": "Repository", + "source": audit.REPOSITORY_RULESET_SOURCE, + "enforcement": "active", + "bypass_actors": [], + "conditions": { + "ref_name": {"include": ["~DEFAULT_BRANCH"], "exclude": []}, + }, + "rules": [ + { + "type": "pull_request", + "parameters": { + "required_approving_review_count": 0, + "dismiss_stale_reviews_on_push": True, + "require_last_push_approval": False, + "required_review_thread_resolution": True, + "require_extra_approval_for_unattributed_changes": True, + "allowed_merge_methods": ["merge", "squash"], + }, + }, + {"type": "deletion"}, + {"type": "non_fast_forward"}, + ], + } + + +def test_central_ruleset_accepts_zero_approvals_without_last_push_approval() -> None: + """A one-human fleet must not require an approval its sole author cannot give.""" + assert audit.audit_ruleset(_central_ruleset_payload()) == [] + + +def test_repository_ruleset_accepts_zero_approvals_without_last_push_approval() -> None: + """The control-plane repository must use the same satisfiable admission model.""" + assert audit.audit_repository_ruleset(_repository_ruleset_payload()) == [] From a815b54be2ec0a7eed009afeb1a4363cff9ec9a2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 1 Sep 2026 21:33:43 +0900 Subject: [PATCH 21/62] ci(governance): run focused solo-maintainer contract --- .../solo-maintainer-ruleset-contract.yml | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 .github/workflows/solo-maintainer-ruleset-contract.yml diff --git a/.github/workflows/solo-maintainer-ruleset-contract.yml b/.github/workflows/solo-maintainer-ruleset-contract.yml new file mode 100644 index 0000000000..41637e8d6e --- /dev/null +++ b/.github/workflows/solo-maintainer-ruleset-contract.yml @@ -0,0 +1,38 @@ +name: Solo Maintainer Ruleset Contract + +on: + push: + branches: [fix/stacked-pr-central-required-workflows] + paths: + - ".github/workflows/solo-maintainer-ruleset-contract.yml" + - "scripts/ci/audit_central_required_workflows.py" + - "tests/test_solo_maintainer_ruleset_policy.py" + +permissions: + contents: read + +jobs: + focused-contract: + name: Focused solo-maintainer ruleset contract + runs-on: ubuntu-latest + steps: + - name: Checkout exact pushed head + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + + - name: Execute focused contract without third-party test dependencies + run: | + python3 - <<'PY' + import importlib.util + from pathlib import Path + + test_path = Path("tests/test_solo_maintainer_ruleset_policy.py") + spec = importlib.util.spec_from_file_location("solo_ruleset_contract", test_path) + if spec is None or spec.loader is None: + raise RuntimeError(f"unable to load {test_path}") + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + module.test_central_ruleset_accepts_zero_approvals_without_last_push_approval() + module.test_repository_ruleset_accepts_zero_approvals_without_last_push_approval() + PY From 3830a7dc837a0a5e18dee2539859abd49c161c26 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 1 Sep 2026 21:43:29 +0900 Subject: [PATCH 22/62] fix(governance): align ruleset audit to solo maintainer --- scripts/ci/audit_central_required_workflows.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/scripts/ci/audit_central_required_workflows.py b/scripts/ci/audit_central_required_workflows.py index 0eeaf87c6a..aed797271f 100644 --- a/scripts/ci/audit_central_required_workflows.py +++ b/scripts/ci/audit_central_required_workflows.py @@ -184,12 +184,12 @@ 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("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 []) @@ -293,12 +293,12 @@ def audit_repository_ruleset(payload: dict[str, Any]) -> list[str]: else: raw_parameters = review_rules[0].get("parameters") parameters = raw_parameters if isinstance(raw_parameters, dict) else {} - if parameters.get("required_approving_review_count") != 2: - errors.append("repository ruleset does not require exactly two approving reviews") + if parameters.get("required_approving_review_count") != 0: + errors.append("repository solo-maintainer ruleset must not require approving reviews") 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 True: - errors.append("repository ruleset last-push approval protection 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") allowed_methods = set(parameters.get("allowed_merge_methods") or []) From f01d2cbf76f28b813d00d382492054b730037e4c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 1 Sep 2026 21:47:20 +0900 Subject: [PATCH 23/62] test(governance): rebaseline solo-maintainer ruleset policy --- ...central_required_workflow_ruleset_audit.py | 45 ++++++++++--------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/tests/test_central_required_workflow_ruleset_audit.py b/tests/test_central_required_workflow_ruleset_audit.py index 35b050eb88..908c465a24 100644 --- a/tests/test_central_required_workflow_ruleset_audit.py +++ b/tests/test_central_required_workflow_ruleset_audit.py @@ -50,10 +50,10 @@ def ruleset_payload() -> dict: { "type": "pull_request", "parameters": { - "required_approving_review_count": 2, + "required_approving_review_count": 0, "dismiss_stale_reviews_on_push": True, "require_code_owner_review": False, - "require_last_push_approval": True, + "require_last_push_approval": False, "required_review_thread_resolution": True, "required_reviewers": [], "allowed_merge_methods": ["merge", "squash"], @@ -128,9 +128,9 @@ def repository_ruleset_payload() -> dict: { "type": "pull_request", "parameters": { - "required_approving_review_count": 2, + "required_approving_review_count": 0, "dismiss_stale_reviews_on_push": True, - "require_last_push_approval": True, + "require_last_push_approval": False, "required_review_thread_resolution": True, "allowed_merge_methods": ["merge", "squash"], }, @@ -214,18 +214,18 @@ def test_expected_repository_ruleset_passes() -> None: assert audit.audit_repository_ruleset(repository_ruleset_payload()) == [] -def test_repository_ruleset_rejects_live_weakened_review_controls() -> None: +def test_repository_ruleset_rejects_unsatisfiable_review_controls() -> None: assert hasattr(audit, "audit_repository_ruleset"), ( "the central audit must inspect the repository ruleset that protects .github" ) payload = repository_ruleset_payload() review_rule = next(rule for rule in payload["rules"] if rule["type"] == "pull_request") - review_rule["parameters"]["required_approving_review_count"] = 0 - review_rule["parameters"]["require_last_push_approval"] = False + review_rule["parameters"]["required_approving_review_count"] = 1 + review_rule["parameters"]["require_last_push_approval"] = True assert audit.audit_repository_ruleset(payload) == [ - "repository ruleset does not require exactly two approving reviews", - "repository ruleset last-push approval protection is disabled", + "repository solo-maintainer ruleset must not require approving reviews", + "repository solo-maintainer ruleset must not require last-push approval", ] @@ -295,9 +295,9 @@ def test_repository_ruleset_rejects_malformed_review_parameters() -> None: review_rule["parameters"] = None assert audit.audit_repository_ruleset(payload) == [ - "repository ruleset does not require exactly two approving reviews", + "repository solo-maintainer ruleset must not require approving reviews", "repository ruleset stale-review dismissal on push is disabled", - "repository ruleset last-push approval protection is disabled", + "repository solo-maintainer ruleset must not require last-push approval", "repository ruleset review-thread resolution protection is disabled", "repository ruleset must allow only merge and squash", ] @@ -371,6 +371,8 @@ def test_multiple_workflow_rules_do_not_invent_create_transition_drift() -> None assert "expected one workflows rule, found 2" in errors assert "central required workflows block the branch create transition" not in errors + + def test_expected_stacked_ruleset_passes(monkeypatch, capsys) -> None: payload = stacked_ruleset_payload() payload["rules"][0]["parameters"]["workflows"][0]["sha"] = "a" * 40 @@ -514,17 +516,17 @@ def test_wrong_workflow_ref_reports_exact_drift() -> None: ) -def test_review_policy_weakening_reports_exact_drift() -> None: +def test_unsatisfiable_review_policy_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"]["require_last_push_approval"] = True 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 "central solo-maintainer ruleset must not require approving reviews" in errors + assert "central solo-maintainer ruleset must not require last-push approval" in errors assert "review-thread resolution protection is disabled" in errors @@ -563,7 +565,7 @@ def test_audit_reports_all_structural_and_protection_drift() -> None: ] -def test_audit_reports_malformed_duplicate_workflows_and_weak_review_parameters() -> None: +def test_audit_handles_duplicate_workflows_and_unsatisfiable_review_parameters() -> None: payload = ruleset_payload() workflow_rule = next(rule for rule in payload["rules"] if rule["type"] == "workflows") workflows = workflow_rule["parameters"]["workflows"] @@ -572,9 +574,9 @@ def test_audit_reports_malformed_duplicate_workflows_and_weak_review_parameters( workflows.append(deepcopy(workflows[-1])) review_rule = next(rule for rule in payload["rules"] if rule["type"] == "pull_request") review_rule["parameters"] = { - "required_approving_review_count": 0, + "required_approving_review_count": 1, "dismiss_stale_reviews_on_push": False, - "require_last_push_approval": False, + "require_last_push_approval": True, "required_review_thread_resolution": False, "allowed_merge_methods": ["squash"], } @@ -582,9 +584,9 @@ 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 "exactly two approving reviews are not required" in errors + assert "central solo-maintainer ruleset must not require approving reviews" in errors assert "stale-review dismissal on push is disabled" in errors - assert "last-push approval protection is disabled" in errors + assert "central solo-maintainer ruleset must not require last-push approval" in errors assert "review-thread resolution protection is disabled" in errors assert "only merge and squash may be allowed merge methods" in errors @@ -599,7 +601,8 @@ 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 "exactly two approving reviews are not required" in errors + assert "central solo-maintainer ruleset must not require approving reviews" in errors + assert "central solo-maintainer ruleset must not require last-push approval" in errors def test_load_payload_rejects_non_object_and_main_logs_load_reason(monkeypatch, capsys) -> None: From 260705b92c55de2135816afa7ff373ed95ddcc39 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 1 Sep 2026 21:48:06 +0900 Subject: [PATCH 24/62] test(governance): reject synthetic required reviewers --- tests/test_solo_maintainer_ruleset_policy.py | 27 ++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/test_solo_maintainer_ruleset_policy.py b/tests/test_solo_maintainer_ruleset_policy.py index 125f3ce1ca..bc5d426622 100644 --- a/tests/test_solo_maintainer_ruleset_policy.py +++ b/tests/test_solo_maintainer_ruleset_policy.py @@ -73,6 +73,7 @@ def _repository_ruleset_payload() -> dict: "dismiss_stale_reviews_on_push": True, "require_last_push_approval": False, "required_review_thread_resolution": True, + "required_reviewers": [], "require_extra_approval_for_unattributed_changes": True, "allowed_merge_methods": ["merge", "squash"], }, @@ -91,3 +92,29 @@ def test_central_ruleset_accepts_zero_approvals_without_last_push_approval() -> def test_repository_ruleset_accepts_zero_approvals_without_last_push_approval() -> None: """The control-plane repository must use the same satisfiable admission model.""" assert audit.audit_repository_ruleset(_repository_ruleset_payload()) == [] + + +def test_central_ruleset_rejects_synthetic_required_reviewer() -> None: + """A named reviewer cannot manufacture independence in a one-human fleet.""" + payload = _central_ruleset_payload() + review_rule = next(rule for rule in payload["rules"] if rule["type"] == "pull_request") + review_rule["parameters"]["required_reviewers"] = [ + {"reviewer_id": 1234, "reviewer_type": "User"} + ] + + assert audit.audit_ruleset(payload) == [ + "central solo-maintainer ruleset must not configure required reviewers" + ] + + +def test_repository_ruleset_rejects_synthetic_required_reviewer() -> None: + """The owner repository cannot reintroduce the same deadlock by reviewer identity.""" + payload = _repository_ruleset_payload() + review_rule = next(rule for rule in payload["rules"] if rule["type"] == "pull_request") + review_rule["parameters"]["required_reviewers"] = [ + {"reviewer_id": 1234, "reviewer_type": "User"} + ] + + assert audit.audit_repository_ruleset(payload) == [ + "repository solo-maintainer ruleset must not configure required reviewers" + ] From 1f0d0e8adbe39145c5a9302dbe9e6b3e63d7d0fd Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 1 Sep 2026 21:48:42 +0900 Subject: [PATCH 25/62] ci(governance): exercise synthetic-reviewer RED --- .github/workflows/solo-maintainer-ruleset-contract.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/solo-maintainer-ruleset-contract.yml b/.github/workflows/solo-maintainer-ruleset-contract.yml index 41637e8d6e..303d3f71b6 100644 --- a/.github/workflows/solo-maintainer-ruleset-contract.yml +++ b/.github/workflows/solo-maintainer-ruleset-contract.yml @@ -6,6 +6,7 @@ on: paths: - ".github/workflows/solo-maintainer-ruleset-contract.yml" - "scripts/ci/audit_central_required_workflows.py" + - "tests/test_central_required_workflow_ruleset_audit.py" - "tests/test_solo_maintainer_ruleset_policy.py" permissions: @@ -27,6 +28,9 @@ jobs: import importlib.util from pathlib import Path + regression_path = Path("tests/test_central_required_workflow_ruleset_audit.py") + compile(regression_path.read_text(encoding="utf-8"), str(regression_path), "exec") + test_path = Path("tests/test_solo_maintainer_ruleset_policy.py") spec = importlib.util.spec_from_file_location("solo_ruleset_contract", test_path) if spec is None or spec.loader is None: @@ -35,4 +39,6 @@ jobs: spec.loader.exec_module(module) module.test_central_ruleset_accepts_zero_approvals_without_last_push_approval() module.test_repository_ruleset_accepts_zero_approvals_without_last_push_approval() + module.test_central_ruleset_rejects_synthetic_required_reviewer() + module.test_repository_ruleset_rejects_synthetic_required_reviewer() PY From 033d6ecfa41e3736c7843ab4eb17f7dc41d906b9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 1 Sep 2026 22:00:42 +0900 Subject: [PATCH 26/62] fix(governance): reject synthetic required reviewers --- scripts/ci/audit_central_required_workflows.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/ci/audit_central_required_workflows.py b/scripts/ci/audit_central_required_workflows.py index aed797271f..4f3e39989c 100644 --- a/scripts/ci/audit_central_required_workflows.py +++ b/scripts/ci/audit_central_required_workflows.py @@ -186,6 +186,8 @@ def audit_ruleset(payload: dict[str, Any]) -> list[str]: approving_reviews = parameters.get("required_approving_review_count") 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") 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 False: @@ -295,6 +297,8 @@ def audit_repository_ruleset(payload: dict[str, Any]) -> list[str]: 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("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: From 1c9c8311ebe6bb26380261e397bc198fd80cf3db Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 1 Sep 2026 22:21:09 +0900 Subject: [PATCH 27/62] test(governance): require executable ruleset regressions --- tests/test_solo_maintainer_ruleset_policy.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/test_solo_maintainer_ruleset_policy.py b/tests/test_solo_maintainer_ruleset_policy.py index bc5d426622..8d1fb99672 100644 --- a/tests/test_solo_maintainer_ruleset_policy.py +++ b/tests/test_solo_maintainer_ruleset_policy.py @@ -1,7 +1,12 @@ """Regression contract for solo-maintainer protected-branch governance.""" +from pathlib import Path + from scripts.ci import audit_central_required_workflows as audit +REPO_ROOT = Path(__file__).resolve().parents[1] +FOCUSED_WORKFLOW_PATH = REPO_ROOT / ".github/workflows/solo-maintainer-ruleset-contract.yml" + def _central_ruleset_payload() -> dict: """Return the desired organization ruleset for a one-human-maintainer fleet.""" @@ -118,3 +123,14 @@ def test_repository_ruleset_rejects_synthetic_required_reviewer() -> None: assert audit.audit_repository_ruleset(payload) == [ "repository solo-maintainer ruleset must not configure required reviewers" ] + + +def test_focused_workflow_executes_main_ruleset_regressions() -> None: + """The temporary proof workflow must execute, not merely parse, both suites.""" + workflow = " ".join(FOCUSED_WORKFLOW_PATH.read_text(encoding="utf-8").split()) + + assert ( + "python -m pytest -q tests/test_central_required_workflow_ruleset_audit.py " + "tests/test_solo_maintainer_ruleset_policy.py" + ) in workflow + assert "compile(regression_path.read_text" not in workflow From 06e1ba2ad9230d4b8cffcd95de898774b8cbb4af Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 1 Sep 2026 22:21:41 +0900 Subject: [PATCH 28/62] fix(governance): execute full focused ruleset suite --- .../solo-maintainer-ruleset-contract.yml | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/.github/workflows/solo-maintainer-ruleset-contract.yml b/.github/workflows/solo-maintainer-ruleset-contract.yml index 303d3f71b6..2f0bb4875b 100644 --- a/.github/workflows/solo-maintainer-ruleset-contract.yml +++ b/.github/workflows/solo-maintainer-ruleset-contract.yml @@ -22,23 +22,23 @@ jobs: with: persist-credentials: false - - name: Execute focused contract without third-party test dependencies + - name: Execute focused ruleset contract + env: + PIP_DISABLE_PIP_VERSION_CHECK: "1" + PIP_NO_INPUT: "1" + shell: bash --noprofile --norc -e -o pipefail {0} run: | - python3 - <<'PY' - import importlib.util - from pathlib import Path - - regression_path = Path("tests/test_central_required_workflow_ruleset_audit.py") - compile(regression_path.read_text(encoding="utf-8"), str(regression_path), "exec") - - test_path = Path("tests/test_solo_maintainer_ruleset_policy.py") - spec = importlib.util.spec_from_file_location("solo_ruleset_contract", test_path) - if spec is None or spec.loader is None: - raise RuntimeError(f"unable to load {test_path}") - module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(module) - module.test_central_ruleset_accepts_zero_approvals_without_last_push_approval() - module.test_repository_ruleset_accepts_zero_approvals_without_last_push_approval() - module.test_central_ruleset_rejects_synthetic_required_reviewer() - module.test_repository_ruleset_rejects_synthetic_required_reviewer() - PY + cat >"${RUNNER_TEMP}/solo-maintainer-ruleset-requirements.txt" <<'EOF' + iniconfig==2.1.0 --hash=sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760 + packaging==26.2 --hash=sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e + pluggy==1.6.0 --hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746 + pygments==2.20.0 --hash=sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176 + pytest==9.1.1 --hash=sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c + EOF + python -m pip install \ + --only-binary=:all: \ + --require-hashes \ + -r "${RUNNER_TEMP}/solo-maintainer-ruleset-requirements.txt" + python -m pytest -q \ + tests/test_central_required_workflow_ruleset_audit.py \ + tests/test_solo_maintainer_ruleset_policy.py From c3a057142f7b9084e3483674811c1f6ccea06b22 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 1 Sep 2026 22:26:47 +0900 Subject: [PATCH 29/62] test(governance): detach temporary proof from permanent suite --- tests/test_solo_maintainer_ruleset_policy.py | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/tests/test_solo_maintainer_ruleset_policy.py b/tests/test_solo_maintainer_ruleset_policy.py index 8d1fb99672..bc5d426622 100644 --- a/tests/test_solo_maintainer_ruleset_policy.py +++ b/tests/test_solo_maintainer_ruleset_policy.py @@ -1,12 +1,7 @@ """Regression contract for solo-maintainer protected-branch governance.""" -from pathlib import Path - from scripts.ci import audit_central_required_workflows as audit -REPO_ROOT = Path(__file__).resolve().parents[1] -FOCUSED_WORKFLOW_PATH = REPO_ROOT / ".github/workflows/solo-maintainer-ruleset-contract.yml" - def _central_ruleset_payload() -> dict: """Return the desired organization ruleset for a one-human-maintainer fleet.""" @@ -123,14 +118,3 @@ def test_repository_ruleset_rejects_synthetic_required_reviewer() -> None: assert audit.audit_repository_ruleset(payload) == [ "repository solo-maintainer ruleset must not configure required reviewers" ] - - -def test_focused_workflow_executes_main_ruleset_regressions() -> None: - """The temporary proof workflow must execute, not merely parse, both suites.""" - workflow = " ".join(FOCUSED_WORKFLOW_PATH.read_text(encoding="utf-8").split()) - - assert ( - "python -m pytest -q tests/test_central_required_workflow_ruleset_audit.py " - "tests/test_solo_maintainer_ruleset_policy.py" - ) in workflow - assert "compile(regression_path.read_text" not in workflow From 2b381e131017387a5e178776ff4a01b8ac68d99c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 1 Sep 2026 22:37:47 +0900 Subject: [PATCH 30/62] fix(governance): pin focused contract Python --- .github/workflows/solo-maintainer-ruleset-contract.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/solo-maintainer-ruleset-contract.yml b/.github/workflows/solo-maintainer-ruleset-contract.yml index 2f0bb4875b..c4157f584e 100644 --- a/.github/workflows/solo-maintainer-ruleset-contract.yml +++ b/.github/workflows/solo-maintainer-ruleset-contract.yml @@ -22,6 +22,11 @@ jobs: with: persist-credentials: false + - name: Set up pinned Python + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: "3.14" + - name: Execute focused ruleset contract env: PIP_DISABLE_PIP_VERSION_CHECK: "1" From 3ec4abfbf478c68fa71f9f362664a12434cc008c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 1 Sep 2026 23:11:36 +0900 Subject: [PATCH 31/62] ci(governance): move focused ruleset contract off saturated latest queue --- .github/workflows/solo-maintainer-ruleset-contract.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/solo-maintainer-ruleset-contract.yml b/.github/workflows/solo-maintainer-ruleset-contract.yml index c4157f584e..115f4998d5 100644 --- a/.github/workflows/solo-maintainer-ruleset-contract.yml +++ b/.github/workflows/solo-maintainer-ruleset-contract.yml @@ -15,7 +15,7 @@ permissions: jobs: focused-contract: name: Focused solo-maintainer ruleset contract - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Checkout exact pushed head uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 From 81a7afbc2ebbc52b11fe83a02b43948ab2aa5188 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 1 Sep 2026 23:14:07 +0900 Subject: [PATCH 32/62] ci(governance): retire proven focused ruleset contract lane --- .../solo-maintainer-ruleset-contract.yml | 49 ------------------- 1 file changed, 49 deletions(-) delete mode 100644 .github/workflows/solo-maintainer-ruleset-contract.yml diff --git a/.github/workflows/solo-maintainer-ruleset-contract.yml b/.github/workflows/solo-maintainer-ruleset-contract.yml deleted file mode 100644 index 115f4998d5..0000000000 --- a/.github/workflows/solo-maintainer-ruleset-contract.yml +++ /dev/null @@ -1,49 +0,0 @@ -name: Solo Maintainer Ruleset Contract - -on: - push: - branches: [fix/stacked-pr-central-required-workflows] - paths: - - ".github/workflows/solo-maintainer-ruleset-contract.yml" - - "scripts/ci/audit_central_required_workflows.py" - - "tests/test_central_required_workflow_ruleset_audit.py" - - "tests/test_solo_maintainer_ruleset_policy.py" - -permissions: - contents: read - -jobs: - focused-contract: - name: Focused solo-maintainer ruleset contract - runs-on: ubuntu-24.04 - steps: - - name: Checkout exact pushed head - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - persist-credentials: false - - - name: Set up pinned Python - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 - with: - python-version: "3.14" - - - name: Execute focused ruleset contract - env: - PIP_DISABLE_PIP_VERSION_CHECK: "1" - PIP_NO_INPUT: "1" - shell: bash --noprofile --norc -e -o pipefail {0} - run: | - cat >"${RUNNER_TEMP}/solo-maintainer-ruleset-requirements.txt" <<'EOF' - iniconfig==2.1.0 --hash=sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760 - packaging==26.2 --hash=sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e - pluggy==1.6.0 --hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746 - pygments==2.20.0 --hash=sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176 - pytest==9.1.1 --hash=sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c - EOF - python -m pip install \ - --only-binary=:all: \ - --require-hashes \ - -r "${RUNNER_TEMP}/solo-maintainer-ruleset-requirements.txt" - python -m pytest -q \ - tests/test_central_required_workflow_ruleset_audit.py \ - tests/test_solo_maintainer_ruleset_policy.py From 66757ecf7789fb08491931c8bb2e109c3b4b1e51 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 1 Sep 2026 23:18:51 +0900 Subject: [PATCH 33/62] ci(governance): restore unproven focused contract lane --- .../solo-maintainer-ruleset-contract.yml | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 .github/workflows/solo-maintainer-ruleset-contract.yml diff --git a/.github/workflows/solo-maintainer-ruleset-contract.yml b/.github/workflows/solo-maintainer-ruleset-contract.yml new file mode 100644 index 0000000000..2f0bb4875b --- /dev/null +++ b/.github/workflows/solo-maintainer-ruleset-contract.yml @@ -0,0 +1,44 @@ +name: Solo Maintainer Ruleset Contract + +on: + push: + branches: [fix/stacked-pr-central-required-workflows] + paths: + - ".github/workflows/solo-maintainer-ruleset-contract.yml" + - "scripts/ci/audit_central_required_workflows.py" + - "tests/test_central_required_workflow_ruleset_audit.py" + - "tests/test_solo_maintainer_ruleset_policy.py" + +permissions: + contents: read + +jobs: + focused-contract: + name: Focused solo-maintainer ruleset contract + runs-on: ubuntu-latest + steps: + - name: Checkout exact pushed head + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + + - name: Execute focused ruleset contract + env: + PIP_DISABLE_PIP_VERSION_CHECK: "1" + PIP_NO_INPUT: "1" + shell: bash --noprofile --norc -e -o pipefail {0} + run: | + cat >"${RUNNER_TEMP}/solo-maintainer-ruleset-requirements.txt" <<'EOF' + iniconfig==2.1.0 --hash=sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760 + packaging==26.2 --hash=sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e + pluggy==1.6.0 --hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746 + pygments==2.20.0 --hash=sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176 + pytest==9.1.1 --hash=sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c + EOF + python -m pip install \ + --only-binary=:all: \ + --require-hashes \ + -r "${RUNNER_TEMP}/solo-maintainer-ruleset-requirements.txt" + python -m pytest -q \ + tests/test_central_required_workflow_ruleset_audit.py \ + tests/test_solo_maintainer_ruleset_policy.py From c52470b797c2eb17555a688a889397fbf45bd33c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 1 Sep 2026 23:25:05 +0900 Subject: [PATCH 34/62] test(governance): cover complete ruleset drift evidence --- ...t_ruleset_audit_completeness_regression.py | 120 ++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 tests/test_ruleset_audit_completeness_regression.py diff --git a/tests/test_ruleset_audit_completeness_regression.py b/tests/test_ruleset_audit_completeness_regression.py new file mode 100644 index 0000000000..36d50cd055 --- /dev/null +++ b/tests/test_ruleset_audit_completeness_regression.py @@ -0,0 +1,120 @@ +"""Regression tests for complete ruleset drift evidence and rollout policy.""" + +from pathlib import Path + +from scripts.ci import audit_central_required_workflows as audit + +REPO_ROOT = Path(__file__).resolve().parents[1] + + +def _central_payload() -> dict: + """Return a minimal payload satisfying the declared central policy.""" + return { + "id": audit.RULESET_ID, + "name": audit.RULESET_NAME, + "target": "branch", + "enforcement": "active", + "bypass_actors": [], + "conditions": { + "repository_name": { + "include": ["~ALL"], + "exclude": [".github", "IRT-bibliography-set", "noema"], + }, + "ref_name": {"include": ["~DEFAULT_BRANCH"], "exclude": []}, + }, + "rules": [ + { + "type": "workflows", + "parameters": { + "do_not_enforce_on_create": True, + "workflows": [ + { + "repository_id": audit.SOURCE_REPOSITORY_ID, + "path": path, + "ref": audit.SOURCE_REF, + } + for path in audit.REQUIRED_WORKFLOW_PATHS + ], + }, + }, + { + "type": "pull_request", + "parameters": { + "required_approving_review_count": 0, + "required_reviewers": [], + "dismiss_stale_reviews_on_push": True, + "require_last_push_approval": False, + "required_review_thread_resolution": True, + "allowed_merge_methods": ["merge", "squash"], + }, + }, + {"type": "deletion"}, + {"type": "non_fast_forward"}, + ], + } + + +def _repository_payload() -> dict: + """Return a minimal payload satisfying the owner-repository policy.""" + return { + "id": audit.REPOSITORY_RULESET_ID, + "name": audit.REPOSITORY_RULESET_NAME, + "source_type": "Repository", + "source": audit.REPOSITORY_RULESET_SOURCE, + "target": "branch", + "enforcement": "active", + "bypass_actors": [], + "conditions": { + "ref_name": {"include": ["~DEFAULT_BRANCH"], "exclude": []}, + }, + "rules": [ + { + "type": "pull_request", + "parameters": { + "required_approving_review_count": 0, + "required_reviewers": [], + "dismiss_stale_reviews_on_push": True, + "require_last_push_approval": False, + "required_review_thread_resolution": True, + "allowed_merge_methods": ["merge", "squash"], + }, + }, + {"type": "deletion"}, + {"type": "non_fast_forward"}, + ], + } + + +def test_central_ruleset_rejects_creation_or_other_undeclared_rule_types() -> None: + """A creation rule must not silently defeat the branch-create transition.""" + payload = _central_payload() + payload["rules"].extend([{"type": "creation"}, {"type": "required_signatures"}]) + + assert "central ruleset has forbidden rule types: ['creation', 'required_signatures']" in audit.audit_ruleset(payload) + + +def test_repository_ruleset_rejects_undeclared_rule_types() -> None: + """The repository pass result must describe the complete protected policy.""" + payload = _repository_payload() + payload["rules"].append({"type": "creation"}) + + assert "repository ruleset has forbidden rule types: ['creation']" in audit.audit_repository_ruleset(payload) + + +def test_live_audit_collects_all_available_ruleset_drift_before_failing() -> None: + """One ruleset failure must not suppress other already-fetched audit results.""" + workflow = (REPO_ROOT / ".github/workflows/audit-central-ruleset.yml").read_text(encoding="utf-8") + + assert "audit_status=0" in workflow + assert workflow.count("if ! python3 scripts/ci/audit_central_required_workflows.py") == 3 + assert 'if [[ "$audit_status" -ne 0 ]]; then' in workflow + + +def test_rollout_guide_declares_solo_maintainer_review_policy() -> None: + """Operator documentation must not reintroduce a fictional second human approval.""" + rollout = (REPO_ROOT / "docs/org-required-workflow-rollout.md").read_text(encoding="utf-8") + + assert "required_approving_review_count = 0" in rollout + assert "require_last_push_approval = false" in rollout + assert "The org's two-reviewer merge rule" not in rollout + assert "two distinct approvals" not in rollout From a9505c3a8ee9381552ca195fb355761edc3b781f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 1 Sep 2026 23:26:40 +0900 Subject: [PATCH 35/62] fix(governance): reject undeclared ruleset controls --- .../ci/audit_central_required_workflows.py | 91 ++++++++++++++++--- 1 file changed, 79 insertions(+), 12 deletions(-) diff --git a/scripts/ci/audit_central_required_workflows.py b/scripts/ci/audit_central_required_workflows.py index 4f3e39989c..e8154e4187 100644 --- a/scripts/ci/audit_central_required_workflows.py +++ b/scripts/ci/audit_central_required_workflows.py @@ -34,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" @@ -49,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 [] + forbidden: set[str] = set() + for rule in rules: + if not isinstance(rule, dict): + forbidden.add("") + continue + rule_type = rule.get("type") + if not isinstance(rule_type, str) or not rule_type: + forbidden.add("") + 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] = [] @@ -77,7 +108,9 @@ def audit_ruleset(payload: dict[str, Any]) -> list[str]: ) 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( @@ -136,7 +169,10 @@ def audit_ruleset(payload: dict[str, Any]) -> list[str]: workflows = workflow_parameters.get("workflows") workflows = workflows if isinstance(workflows, list) else [] - if len(workflow_rules) == 1 and workflow_parameters.get("do_not_enforce_on_create") is not True: + 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( @@ -166,7 +202,9 @@ def audit_ruleset(payload: dict[str, Any]) -> list[str]: 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 @@ -185,13 +223,19 @@ def audit_ruleset(payload: dict[str, Any]) -> list[str]: parameters = parameters if isinstance(parameters, dict) else {} approving_reviews = parameters.get("required_approving_review_count") if approving_reviews != 0: - errors.append("central solo-maintainer ruleset must not require approving reviews") + 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") + errors.append( + "central solo-maintainer ruleset must not configure required reviewers" + ) 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 False: - errors.append("central solo-maintainer ruleset must not require last-push approval") + 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 []) @@ -203,6 +247,10 @@ def audit_ruleset(payload: dict[str, Any]) -> list[str]: 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 @@ -273,7 +321,10 @@ def audit_repository_ruleset(payload: dict[str, Any]) -> list[str]: 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: + if ( + payload.get("source_type") != "Repository" + or payload.get("source") != REPOSITORY_RULESET_SOURCE + ): errors.append("repository ruleset source is not ContextualWisdomLab/.github") if payload.get("target") != "branch": errors.append("repository ruleset target is not branch") @@ -296,15 +347,23 @@ def audit_repository_ruleset(payload: dict[str, Any]) -> list[str]: 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") + 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") + errors.append( + "repository solo-maintainer ruleset must not configure required reviewers" + ) 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") + 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") + errors.append( + "repository ruleset review-thread resolution protection is disabled" + ) allowed_methods = set(parameters.get("allowed_merge_methods") or []) if allowed_methods != {"merge", "squash"}: errors.append("repository ruleset must allow only merge and squash") @@ -313,6 +372,14 @@ def audit_repository_ruleset(payload: dict[str, Any]) -> list[str]: 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") + + 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 @@ -385,4 +452,4 @@ def main(argv: list[str] | None = None) -> int: if __name__ == "__main__": # pragma: no cover - exercised through main() - raise SystemExit(main()) + raise SystemExit(main()) \ No newline at end of file From c3ec79a15fbb349b829b86d031976067f76ae655 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 1 Sep 2026 23:27:11 +0900 Subject: [PATCH 36/62] fix(governance): report all fetched ruleset drift --- .github/workflows/audit-central-ruleset.yml | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/.github/workflows/audit-central-ruleset.yml b/.github/workflows/audit-central-ruleset.yml index 1486f4162e..c2e85f8683 100644 --- a/.github/workflows/audit-central-ruleset.yml +++ b/.github/workflows/audit-central-ruleset.yml @@ -45,6 +45,7 @@ jobs: 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" @@ -90,7 +91,9 @@ 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" @@ -100,7 +103,9 @@ jobs: sed 's/^/ /' "$repository_ruleset_error" exit 1 fi - python3 scripts/ci/audit_central_required_workflows.py --repository "$repository_ruleset_json" + if ! python3 scripts/ci/audit_central_required_workflows.py --repository "$repository_ruleset_json"; then + audit_status=1 + fi stacked_ruleset_json="$RUNNER_TEMP/stacked-opencode-ruleset.json" stacked_ruleset_error="$RUNNER_TEMP/stacked-opencode-ruleset.error" @@ -110,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 \ No newline at end of file From d1c0b7cb29fbcddbe7fe3728e7762feb8f0f7887 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 1 Sep 2026 23:27:24 +0900 Subject: [PATCH 37/62] ci(governance): execute completeness regressions --- .github/workflows/solo-maintainer-ruleset-contract.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/solo-maintainer-ruleset-contract.yml b/.github/workflows/solo-maintainer-ruleset-contract.yml index 2f0bb4875b..3779dfc591 100644 --- a/.github/workflows/solo-maintainer-ruleset-contract.yml +++ b/.github/workflows/solo-maintainer-ruleset-contract.yml @@ -5,9 +5,12 @@ on: branches: [fix/stacked-pr-central-required-workflows] paths: - ".github/workflows/solo-maintainer-ruleset-contract.yml" + - ".github/workflows/audit-central-ruleset.yml" - "scripts/ci/audit_central_required_workflows.py" + - "docs/org-required-workflow-rollout.md" - "tests/test_central_required_workflow_ruleset_audit.py" - "tests/test_solo_maintainer_ruleset_policy.py" + - "tests/test_ruleset_audit_completeness_regression.py" permissions: contents: read @@ -41,4 +44,5 @@ jobs: -r "${RUNNER_TEMP}/solo-maintainer-ruleset-requirements.txt" python -m pytest -q \ tests/test_central_required_workflow_ruleset_audit.py \ - tests/test_solo_maintainer_ruleset_policy.py + tests/test_solo_maintainer_ruleset_policy.py \ + tests/test_ruleset_audit_completeness_regression.py \ No newline at end of file From c2ab699af84d4ea297107059a07300696e4b5a3c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 1 Sep 2026 23:29:26 +0900 Subject: [PATCH 38/62] docs(governance): align rollout with solo-maintainer policy --- docs/org-required-workflow-rollout.md | 432 +++++++------------------- 1 file changed, 107 insertions(+), 325 deletions(-) diff --git a/docs/org-required-workflow-rollout.md b/docs/org-required-workflow-rollout.md index 36edcd29dd..4e5be81978 100644 --- a/docs/org-required-workflow-rollout.md +++ b/docs/org-required-workflow-rollout.md @@ -1,18 +1,22 @@ # ContextualWisdomLab central required workflow rollout -Updated: 2026-08-28 KST +Updated: 2026-09-01 KST -## Decision +## Purpose -Use an organization repository ruleset instead of copying workflow files into each repository. +This document is the operator-facing current-state contract for organization-wide pull-request admission. Historical rollout experiments, exact transient PR heads, runner IDs, and superseded review-count policies remain available in Git history and the linked governance issues; they are intentionally not repeated here because stale operational snapshots can recreate a closed governance defect. + +## Canonical organization ruleset + +Use the organization ruleset rather than copying central review/security workflow files into each product repository. - Ruleset: `CWL Central required workflows` - Ruleset ID: `18156473` -- Enforcement: `active` -- Target: branch rules on every repository's default branch (`repository_name.include=["~ALL"]`, `ref_name.include=["~DEFAULT_BRANCH"]`) -- Required workflow source repository: `ContextualWisdomLab/.github` -- Required workflow source repository ID: `1274066402` -- Active required workflow paths: +- Enforcement target: active branch rules on each non-excluded repository's default branch only. +- Repository selector: all organization repositories except `.github`, `IRT-bibliography-set`, and `noema`. +- Ref selector: `ref_name.include=["~DEFAULT_BRANCH"]`, `ref_name.exclude=[]`. +- Required workflow source: `ContextualWisdomLab/.github` repository ID `1274066402` at `refs/heads/main`. +- Required workflow paths: - `.github/workflows/close-empty-pr.yml` - `.github/workflows/noema-review.yml` - `.github/workflows/opencode-review.yml` @@ -20,320 +24,98 @@ Use an organization repository ruleset instead of copying workflow files into ea - `.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: `050e6d59b0de9e62c8413d5f8f26f4f2f9ebea09` (`#584`) -- Required workflow trigger support: `pull_request`, `pull_request_target`, `push`, `workflow_run` - -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 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. - -The central `security-scan.yml` and `sast-semgrep.yml` pull-request triggers are -base-ref agnostic. They therefore also run for stacked pull requests targeting a -feature branch; the organization ruleset's protected-ref scope remains an -independent administrative control and is not weakened by this trigger -coverage. - -Stacked pull requests are audited by organization ruleset -`CWL Stacked OpenCode required workflow` (`21732164`) in `evaluate` mode. It -targets every non-default branch and references only -`.github/workflows/opencode-review.yml` from `.github@refs/heads/main`. -Its observed scope is `ref_name.include=["~ALL"]` with -`ref_name.exclude=["~DEFAULT_BRANCH"]`. -Active enforcement over every non-default ref is prohibited: GitHub evaluates -the ref update before a `pull_request_target.synchronize` run can exist for the -new commit, so it rejects both initial branch creation and later review fixes. -Exact-head OpenCode evidence remains a merge requirement enforced by the -normal PR procedure while a target-ref-scoped enforcement design is developed. - -## OpenCode required workflow posture - -The central `.github/workflows/opencode-review.yml` is now part of the active organization required workflow ruleset. - -- Required workflow trigger support: metadata-only `pull_request_target`; the file contains no checkout, PR-head execution, or secret expression -- Stable branch-protection job names: `required-workflow-bootstrap`, `coverage-source-tree`, `coverage-evidence`, and `opencode-review`; these jobs are data-only sentinels, while approval remains a separate current-head PR-review requirement -- Trusted source: `ContextualWisdomLab/.github` -- PR-head handling: authenticated current-head `repository_dispatch` runs `.github/workflows/opencode-review-dispatch.yml` from the protected default branch; that workflow owns metadata validation, bounded coverage, source-as-data inspection, model review, and publication -- Manual target support: the central scheduler sends exact repository, PR, base, and head metadata through `repository_dispatch`; the dispatch workflow rejects an unauthorized actor, an unallowlisted base repository, a malformed head-repository identity, or any live base/head metadata mismatch. A canonical fork head remains reviewable as untrusted source data. -- Model token posture: use the organization `STRIX_GITHUB_MODELS_TOKEN` secret for GitHub Models calls, with `github.token` as the fallback; live workflow evidence showed `github.token` alone can return 403 from `models.github.ai/inference` -- Write posture: OpenCode may create review/comment side effects through the OpenCode app token when available; the workflow token is limited to the same-repository PR context and publication failures remain visible -- Coverage execution posture: PR-controlled package, test, build, R, Rust, and Docker inputs are never executed from `pull_request_target`; the dispatch workflow runs bounded low-privilege coverage only after exact live metadata and scheduler identity validation -- Fork posture: PR heads are fetched through `refs/pull//head` when direct head-SHA fetch is not available, so review can inspect fork PR source as data without executing it in the trusted workflow context -- Runtime posture: pre-model failed-check evidence waits are capped at about five minutes; the later approval gate rechecks current-head peer checks and extends its bounded wait only while image-validation checks remain pending, logging the reason before approval -- Model-exhaustion posture: command exit codes and deterministic checks cannot synthesize an approval. Exhaustion remains `MODEL_OUTPUT_UNAVAILABLE`; only a prior real-model approval bound to the exact current head can satisfy the review gate after all checks, alerts, and threads are revalidated. -- Adversarial-evidence posture: every probe must cite its exact changed path and positive in-range line in the materialized current-head source tree. Unrelated paths, nonexistent lines, circular claims, and missing observed results fail closed with a concrete rejection reason. - -For a bounded current-head retry in one repository, dispatch `merge-scheduler` -to the central repository with `target_repository`, `pr_number`, and the live -`base_branch`. The target must exactly match -`OPENCODE_REPOSITORY_DISPATCH_TARGETS`; the scheduler then re-reads the open PR -and rejects a noncanonical base or head repository name, base mismatch, -malformed head SHA, or changed/closed PR before using cross-repository -credentials. A fork head may pass this read/review boundary, but the scheduler -still excludes it from direct merge, auto-merge, and non-maintainer branch -mutation: - -```bash -jq -n '{ - event_type: "merge-scheduler", - client_payload: { - target_repository: "ContextualWisdomLab/naruon", - pr_number: 1179, - base_branch: "develop", - trigger_reviews: true, - review_dispatch_limit: "1", - enable_auto_merge: false, - update_branches: false, - merge_mode: "disabled" - } -}' | gh api --method POST \ - repos/ContextualWisdomLab/.github/dispatches --input - -``` - -Use the canonical `full_name` returned by the GitHub repository API. Keep -mutation options disabled for an evidence-only retry; enabling branch updates -or merge behavior is a separate operational decision. - -Keep the OpenCode required workflow active only while the central workflow keeps proving current-head coverage, CodeGraph initialization, bounded evidence, model review output, and approval-gate publication on the current head. - -## Code scanning required workflow posture - -The central `.github/workflows/codeql-pr.yml`, `.github/workflows/scorecard-pr.yml`, -and `.github/workflows/osv-scanner-pr.yml` workflows supply PR-head and merge-preview -code scanning analyses for ruleset `18156473` `code_scanning` (CodeQL, Scorecard, -osv-scanner). They trigger on pull requests to `main`, `master`, and `develop` so -Git Flow repositories on `develop` inherit the same merge gate as GitHub Flow repos. - -CodeQL merge preview checks out `refs/pull//merge` and uploads SARIF with -`sha: pull_request.merge_commit_sha` because the ruleset evaluates that commit, -not the ephemeral merge ref OID. - -Repository-local `codeql.yml` push/default-branch scans may remain for branch -history, but PR merge gates should rely on the central `codeql-pr.yml` workflow. - -### Repository-local CodeQL inventory (2026-07-04) - -Org audit of default-branch workflow files. Repos without any local CodeQL -workflow depend entirely on central `codeql-pr.yml` once ruleset `18156473` -includes that path; they are the most exposed to -`Code scanning is waiting for results from CodeQL` until the ruleset update -lands. - -| Repository | Default branch | Local CodeQL workflow | PR trigger | merge_commit_sha SARIF | -| --- | --- | --- | ---: | ---: | -| `aFIPC` | `master` | `codeql.yml` | yes | no | -| `bandscope` | `develop` | `codeql.yml` | yes | no | -| `newsdom-api` | `develop` | `codeql.yml` | yes | no | -| `pg-erd-cloud` | `main` | `codeql.yml`, `codeql-backfill.yml` | yes (`codeql.yml`) | no | -| `xtrmLLMBatchPython` | `develop` | `codeql.yml` | yes | no | -| `naruon` | `develop` | `codeql.yml` | yes (temporary; PR `#916` retires PR trigger) | yes (repo-local interim fix) | -| all other public non-fork org repos | varies | none observed | — | — | - -No repository-local PR CodeQL workflow besides `naruon` uploads merge-preview -SARIF on `merge_commit_sha`. Centralizing through `codeql-pr.yml` fixes every -inherited repository in one ruleset change; per-repo deletion of PR triggers is -optional cleanup to avoid duplicate scans. - -## Scheduler required workflow posture - -The central `.github/workflows/pr-review-merge-scheduler.yml` is now part of the active organization required workflow ruleset. - -- Required workflow trigger support: `pull_request_target` -- Stable required check job name: `scan-pr-queue` -- Trusted source: `ContextualWisdomLab/.github` -- PR-event scope: when GitHub invokes the workflow for a PR, the scheduler passes `--pr-number` and inspects only that PR instead of scanning or mutating the whole repository queue -- Token posture: the workflow passes the first available mutation credential in this order: `PR_REVIEW_MERGE_TOKEN`, `OPENCODE_APPROVE_TOKEN`, exchanged OpenCode GitHub App token, then the target repository workflow token. The scheduler reports the non-secret token source and expected actor class in every mutation decision. -- Flow posture: default branches named `main` or `master` are treated as GitHub Flow; default branches named `develop` are treated as Git Flow unless a repository explicitly sets `PROJECT_FLOW` -- Merge posture: the default merge mode is `direct_or_auto`. When a current-head approved PR is same-repository and the scheduler has no failed-check, action-required, unresolved-thread, or conflict blocker, it requests an immediate guarded squash merge with `--match-head-commit`. This includes PRs where native GitHub auto-merge is already enabled; native auto-merge is a fallback queue, not the scheduler's first stop when direct merge is possible. -- Fork posture: fork or external-head PRs remain reviewable, but the scheduler does not direct-merge them and does not enable auto-merge for them. A maintainer must make the final merge decision after same-head OpenCode approval, same-head Strix evidence, required checks, and unresolved-thread checks are clean. -- Branch freshness posture: the scheduler also runs after protected base-branch pushes to `main`, `develop`, or `master`, because those pushes can create the GitHub UI state where reviews are satisfied, auto-merge is enabled, checks are stale or failed, and the PR shows `Update branch` without a PR `synchronize` event. -- Auto-merge posture: `auto_merge_enabled` PR events trigger the scheduler so an already stale branch is refreshed immediately after native auto-merge is turned on instead of waiting for the periodic schedule. If the same PR is already mergeable, the scheduler attempts the guarded direct merge immediately. -- Automation boundary: current-head failed checks and `ACTION_REQUIRED` checks are reported before branch updates, so an update attempt does not hide the concrete reason a PR cannot merge. `update-branch` handles approved `BEHIND` PRs and already queued auto-merge PRs only when there is no current-head failed or action-required check to diagnose first. `DIRTY` or `CONFLICTING` PRs still require author or maintainer conflict resolution guidance; current-head approved conflicts may keep or queue native GitHub auto-merge as a wait state while the conflict is repaired, but the scheduler must not treat queued auto-merge as a conflict resolver. -- Retry posture: before retrying OpenCode, the scheduler force-cancels older active OpenCode runs for the same PR number and a previous head SHA. It does not automatically cancel Strix runs because security evidence should not be silently discarded by force-push churn. - -Do not centralize the scheduler by running a `.github` scheduled job against other repositories with the `.github` repository token. That would either fail permission checks or use the wrong mutation actor. The central path is a required workflow executed in each target repository context. - -- Heartbeat fallback posture: event-driven target-repository runs stop retrying once their triggering event is consumed, so a PR that becomes mergeable AFTER its last event (approval published after the scheduler pass, merge-preview checks landing late, a temporary base-branch policy blocker clearing) has no later trigger and sits approved-but-unmerged. The `org-queue-sweep` job in the central scheduler workflow closes this gap: it runs every 15 minutes (`*/15 * * * *`) only in `ContextualWisdomLab/.github`, re-runs the same trusted scheduler script against every non-archived organization repository, and merges/updates through the identical guarded contract. Stacked PRs, which do not receive injected required workflows, use a separate bounded OpenCode dispatch budget so ordinary default-branch traffic cannot leave them at `OpenCode review absent`. It never uses the `.github` repository `github.token` for sibling mutations — it requires `PR_REVIEW_MERGE_TOKEN`, `OPENCODE_APPROVE_TOKEN`, or the exchanged OpenCode app token, and fails with a visible `::error` reason when no cross-repository mutation credential is available instead of silently no-opping. Every swept repository prints its per-PR decision log, so an unmerged PR always has a concrete logged reason at most 15 minutes old. -- Queue hygiene posture: during the sweep, workflow runs still `queued` after `ORG_SWEEP_STALE_QUEUE_HOURS` (default 24h) are cancelled with their run id, workflow name, head branch, and age logged. A run queued that long belongs to a head that PR events will never revisit (closed PR, force-pushed branch, or a previous runner outage), and leaving it keeps the Actions queue holding non-current-head work. -- Inaccessible-repository posture: a sibling repository the sweep credential structurally cannot read — the OpenCode app is not installed there, or `PR_REVIEW_MERGE_TOKEN` does not cover it — returns HTTP 403 `Resource not accessible by integration` on every read. That is an access-grant fact the automation can never resolve, so the sweep classifies it as a skipped, non-fatal **unavailable** repository (a `::warning` naming the repository and the remediation) instead of a hard failure. Without this, a handful of un-enrolled repositories keeps the scheduled sweep heartbeat (the org sweep's `*/15 * * * *` cron) permanently red and masks a genuinely new repository that starts failing. Fail-closed is preserved on both sides: any non-403 scheduler failure still fails the sweep with its per-PR reason, and if more than `ORG_SWEEP_MAX_UNAVAILABLE` (default 5) repositories become unreachable in one pass — a credential-scope regression rather than a few un-enrolled repos — the job fails loudly. Remediation for a listed repository is to install the OpenCode app on it or grant `PR_REVIEW_MERGE_TOKEN` access. - -## Second-reviewer (Noema) posture - -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 -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, -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 - `actions/create-github-app-token` pinned to an immutable SHA. The OIDC Worker - exchange remains a compatibility fallback. If none of these identities is - configured, the required check fails with the exact missing-credential reason; - an unconfigured reviewer can never pass by skipping. -- Honesty posture: `noema_review_gate.py` refuses to review as a primary review - actor (`opencode-agent`, `github-actions`), so a `NOEMA_REVIEW_TOKEN` that - resolves to one of those identities cannot manufacture a fake second review — - it must be a distinct write-access identity. -- Required admin config: install `cwl-noema-review` on the organization, set - `NOEMA_GITHUB_APP_CLIENT_ID` plus `NOEMA_GITHUB_APP_PRIVATE_KEY`, and configure - `NOEMA_LLM_MODEL`, `NOEMA_LLM_API_URL`, and either `NOEMA_LLM_API_KEY` or the - shared `OPENAI_API_KEY`. Every missing setting is a visible failed-check reason. - -## Scope - -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 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. - -| Repository | Visibility | Default branch | Flow | Open PRs | Local central-workflow copies on default branch | Rollout status | -| --- | --- | --- | --- | ---: | --- | --- | -| `ContextualWisdomLab/.github` | public | `main` | GitHub Flow | 27 | central source; keep | single source of truth; central PRs through `#283` merged; PR `#286` current head queued after review-thread fixes | -| `ContextualWisdomLab/aFIPC` | public | `master` | GitHub Flow | 22 | none | central checks proven on PR `#78`; active queue still needs per-PR review | -| `ContextualWisdomLab/pg-erd-cloud` | public | `main` | GitHub Flow | 81 | none | repo-local autofix worker removed by PR `#393`; default branch now keeps only repository-owned application and security workflows | -| `ContextualWisdomLab/fast-mlsirm` | public | `main` | GitHub Flow | 25 | none | migrated; re-verify inherited checks on current open PRs | -| `ContextualWisdomLab/bandscope` | public | `develop` | Git Flow | 36 | none | no local central copies observed; verify inherited checks on active PRs | -| `ContextualWisdomLab/contextual-orchestrator` | public | `main` | GitHub Flow | 2 | none | default branch has no local central copies; current open PRs are runtime proof fixtures | -| `ContextualWisdomLab/naruon` | public | `develop` | Git Flow | 7 | none | default branch has no repo-local OpenCode, Strix, or scheduler copies; application/security workflows remain repository-owned | -| `ContextualWisdomLab/newsdom-api` | public | `develop` | Git Flow | 3 | none | local workflows already gone; re-verify inherited checks on current open PRs | -| `ContextualWisdomLab/appguardrail` | public | `develop` | Git Flow | 9 | none | migrated; re-verify inherited checks before final closure | -| `ContextualWisdomLab/scopeweave` | public | `develop` | Git Flow | 2 | none | local workflows already gone; re-verify inherited checks on current open PRs | -| `ContextualWisdomLab/ContextualWisdomLab.github.io` | public | `main` | GitHub Flow | 19 | none | migrated; re-verify inherited checks on current open PRs | -| `ContextualWisdomLab/codec-carver` | public | `main` | GitHub Flow | 42 | none | local workflows already gone; quality uplift still needs 100% test/docstring evidence before closure | -| `ContextualWisdomLab/clearfolio` | public | `main` | GitHub Flow | 57 | none | migrated; re-verify inherited checks before final closure | -| `ContextualWisdomLab/semantic-data-portal` | public | `main` | GitHub Flow | 3 | none | PR `#3` merged; default branch has no local central copies | -| `ContextualWisdomLab/hyosung-itx-slogan-brief` | public | `main` | GitHub Flow | 1 | none | migrated; re-verify inherited checks on current open PR | -| `ContextualWisdomLab/kaefa` | public | `develop` | Git Flow | 6 | none | newly discovered public non-fork target; ruleset inherited but current PR #60 lacked central check runs in status rollup | -| `ContextualWisdomLab/waf-ids-ai-soc` | public | `main` | GitHub Flow | 1 | none | newly discovered public non-fork target; PR #6 merged after central workflow proof; PR #8 is now the open current-head runtime proof fixture | - -## 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, 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, 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. - -## Evidence from this rollout - -- On 2026-08-28 21:43 KST, ruleset `21732164` was created with active enforcement for every non-default branch. Reproduction on an existing LineageWeave PR head and a new branch returned GH013 before either ref could emit the required workflow event. The ruleset was returned to `evaluate` mode at 21:49 KST; the audit now fails if this impossible all-ref contract is reactivated. - -- On 2026-06-30 08:33 KST, organization ruleset `18156473` was changed from an explicit repository-name list to `repository_name.include=["~ALL"]` while keeping `ref_name.include=["~DEFAULT_BRANCH"]` and the same three central required workflow paths from `.github@refs/heads/main`. -- On 2026-07-01 02:52 KST, ruleset `18156473` still reported `enforcement=active`, `repository_name.include=["~ALL"]`, `ref_name.include=["~DEFAULT_BRANCH"]`, and the three required workflow paths from `ContextualWisdomLab/.github@refs/heads/main`. -- On 2026-07-01 06:30 KST, organization ruleset `18156473` still reported `enforcement=active`, `repository_name.include=["~ALL"]`, `ref_name.include=["~DEFAULT_BRANCH"]`, and the three required workflow paths from `ContextualWisdomLab/.github@refs/heads/main`. -- 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 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`. -- `.github` PR `#232` removed the workflow-only deterministic approval fallback introduced by PR `#231`; model-pool exhaustion now stays on the fail-closed `REQUEST_CHANGES` path, and reasoning-capable OpenCode model candidates must have `reasoningEffort: high` before execution. It merged at `f545a9917933f8f81a76ea0044cbce0aae1ac5bd`. -- `.github` PR `#233` blocks false trivial approval reasons such as `Typo fix in documentation string` when current-head changed files include workflow, script/source, or test surfaces. It merged at `4ff660c8396b78a1b82aef8c316b26527864d450`. -- `.github` PR `#234` made approval-summary repair parse bullet-form changed-file evidence from bounded review logs, so changed-file evidence is not lost when the evidence section is rendered as a Markdown list. It merged at `da3a4a5788e7019229d66247c360b258b1a5b1f7`. -- `.github` PR `#235` changed the post-approval OpenCode merge-scheduler follow-up to prefer the workflow `github.token` for same-repository mechanical merge/update mutations, keeping secret/app fallbacks for cross-repository manual dispatch. It merged at `482b05c6c11d9da9895246406aca1c3bd8f6a691`. -- `.github` PR `#239` centralized the OpenCode reasoning-effort guard into `scripts/ci/assert_opencode_reasoning_effort.py`, reused it for the review model pool and failed-check diagnosis path, and merged at `2aa1fa36255a558bafca05567125ef7e44571976` after required OpenCode, Strix, Noema, coverage, and scheduler checks passed. -- `.github` PR `#242` added REST fallbacks for transient scheduler GraphQL read failures in open-PR and single-PR lookup paths, then merged at `0d2c6d9e7ae1bad947e7ee3629e2a412ac2ce248`. -- `.github` PR `#244` added the central `PR Review Autofix` worker and changed the fix scheduler to dispatch the central `.github` autofix worker by default while preserving explicit target-repository overrides. It merged at `4d2dd64028231b1154642bfe23b822fc3403e217`. -- `.github` PR `#246` hardened the OpenCode model pool after `pg-erd-cloud` PR `#393` exposed model exhaustion: full review policy is kept on disk behind a compact launcher prompt, context-window overflow skips same-model retries, additional cataloged tool-calling models are included, reasoning-capable candidates keep `reasoningEffort: high`, and the model pool now has a five-hour total retry budget. It merged at `f5f00b782ae4f7806f0e3197bf9b49c9c5a2cb91`. -- `.github` PR `#247` was closed without merge because its reviewed-merge-update fallback would have approved a current head from previous-parent approval evidence after model exhaustion. That path conflicts with the current fail-closed policy: model timeout, model-pool exhaustion, or missing usable control output must lead to retry, alternate model execution, or a source-backed request for changes, not deterministic approval. -- `.github` PR `#249` guarded the central PR Review Fix Scheduler so `CHANGES_REQUESTED` review states dispatch the central autofix worker only when the latest OpenCode review is on the current head, the merge state is `CLEAN` or `HAS_HOOKS`, and the review body does not indicate process-only blockers such as merge conflict, model-pool exhaustion, unresolved human review threads, failed checks, `coverage-evidence`, or failed Strix evidence. It merged at `dbd33b3a0384de0129aa082a210383188d012415` after current-head `coverage-evidence`, `strix`, `opencode-review`, `noema-review`, and `scan-pr-queue` all completed successfully. -- `.github` PR `#255` removed the remaining deterministic low-risk approval fallback from the OpenCode approval gate and changed `coverage-evidence` blocker handling to publish a `REQUEST_CHANGES` review event, producing the PR review state `CHANGES_REQUESTED`, instead of leaving only a failed check/log. It merged at `e2beae72b87a8817cd57f9f51bab3947353baa61`; the first current-head OpenCode run reached an `APPROVE` gate result but hit the OpenCode GitHub App installation rate limit while publishing the review, then a rerun published approval and native auto-merge completed. -- `.github` PR `#283` refreshed the central OpenCode model configuration so every reasoning-capable review candidate sets `reasoning=true`, `options.reasoningEffort: high`, and `variants.high.reasoningEffort: high`; non-reasoning fallback candidates remain available without a false effort claim. It merged at `ef9950e6b55bf943c0295e1df3e34c94210d21cc`. -- After PR `#255` merged, `ContextualWisdomLab/bandscope` PRs `#493`, `#494`, `#495`, and `#500` were rechecked for branch freshness. Merge simulation against `develop` found real conflicts rather than update-branch candidates: `#493` conflicts in `apps/desktop/src/App.tsx` plus the design-system docs, while `#494`, `#495`, and `#500` conflict in `docs/design-system/README.md`, `docs/design-system/component-contract.md`, and `docs/design-system/figma-to-code-workflow.md`. Each PR received a corrected conflict-resolution comment with the exact file list and merge/rebase repair commands. -- `ContextualWisdomLab/aFIPC` PR `#78` is no longer a target-coverage gap. It merged after current-head central `coverage-evidence`, `opencode-review`, `strix`, and `scan-pr-queue` checks all passed on head `b1ddafced86302f461e95259699f1efde5ec87c9`; the OpenCode review approved the same head on 2026-06-30 06:02:55Z. -- `ContextualWisdomLab/pg-erd-cloud` PR `#393` removed the repo-local `pr-review-autofix.yml` worker after the central autofix worker merged. - The first OpenCode run on head `9d8eed5be47670b1b46f413295d9a6044d7327b2` exhausted the older model pool and requested changes. - After `.github` PR `#246` merged, central OpenCode run `28485070313` approved the same head and the PR merged at `1e0d6a3dda5ea9afcd74dcd8380689672e1c8ef1` on 2026-07-01 00:33:50Z. - Live default-branch content lookup returned 404 for `.github/workflows/pr-review-autofix.yml` after merge. -- Live non-fork inventory on 2026-07-02 18:15 KST found 17 public non-fork repositories, inherited ruleset `18156473` on `kaefa` and `waf-ids-ai-soc`, and no default-branch copies of `opencode-review.yml`, `strix.yml`, or `pr-review-merge-scheduler.yml` outside `.github`. -- `ContextualWisdomLab/waf-ids-ai-soc` PR `#6` merged at `e1c0a85fd4a8e6dd67039be43eb7f659fec22abd` after central required workflow proof on head `43b62b5f347d1532c81b5ae38d8e41b4494fd486`; PR `#8` current head `48d8b56a0f995829fc95de4fed129d1c33aaadff` is now the open runtime proof fixture with central and local Rust checks queued at the 2026-07-02 18:15 KST refresh. -- `ContextualWisdomLab/kaefa` inherits ruleset `18156473`, but PR `#60` current head `13c9089855fcdd34391173560ccf6935bac1eebe` showed only repo-local R-CMD-check, dependency-review, and CodeQL signals in status rollup. Treat this as a runtime proof gap until a new PR event or manual dispatch proves central OpenCode, Strix, and scheduler checks on a kaefa current head. -- `.github` scheduler default merge mode is now `direct_or_auto`: approved same-repository `CLEAN` PRs request immediate guarded merge, approved non-clean same-repository PRs can queue native auto-merge, and fork or external-head PRs are left for maintainer merge. -- OpenCode approval runs the trusted central merge scheduler script directly with `pr_number` and `max_prs=1`, so the just-reviewed PR is inspected immediately even when organization required workflows are not repo-local `workflow_dispatch` targets. -- `.github` PR `#74` changed OpenCode review model order to DeepSeek R1 first and added a catalog fallback pool. -- `.github` PR `#75` removed the Strix finding against the scheduler command wrapper by using `subprocess.run(..., check=True)` and preserving the existing scrubbed failure contract. -- `.github` main Strix run `28218982899` passed after PR `#75` merged. -- `.github` PR `#77` merged the central OpenCode required-workflow path. -- `.github` PR `#77` same-head OpenCode proof run `28224085121` passed coverage evidence, CodeGraph initialization, bounded evidence preparation, model review, review comment publication, and approval-gate publication on head `59a8da0b2f56b862f6c5a0c69885f4045d6dc732`. -- `.github` PR `#77` central Strix required workflow run `28223698075` passed on the same head before merge. -- Organization ruleset `18156473` was renamed to `CWL Central required workflows` and required `.github/workflows/strix.yml` and `.github/workflows/opencode-review.yml` from `.github@main` SHA `6440d493816f8a4d66e32f2e5e8e6a9156d7f488`. -- `.github` PR `#79` merged the central scheduler `pull_request_target` path and PR-scoped `--pr-number` lookup. -- `.github` PR `#79` second current-head proof passed coverage evidence in 10s, Strix in 8m33s, and OpenCode review in 8m57s on head `17c62f3809c57ca4b1a9a63e14f325c9f2a1acdb`. -- Organization ruleset `18156473` now requires `.github/workflows/strix.yml`, `.github/workflows/opencode-review.yml`, and `.github/workflows/pr-review-merge-scheduler.yml` from `.github@main` SHA `807254a04efafd5f806e0f70cb067ecf050cfd11`. -- `.github` PR `#85` installed target repository `requirements.txt` before Python coverage evidence, so central coverage measurement can run repo tests that require project dependencies. -- `.github` PR `#88` hardened the OpenCode output normalizer so the Python normalizer is part of the trusted approval gate path. -- `.github` PR `#94` hardened the central OpenCode prompt and generated review DAG contract so Mermaid labels are quoted and render safely. -- `.github` PR `#95` blocks OpenCode approvals that claim no source, test, or executable changes when exact changed-file evidence lists workflow, script, source, or test files. -- On 2026-06-28 20:09 KST, ruleset `18156473` was re-pinned to `.github@main` SHA `531482764986bf7da98c1317d59e6e51e7c61d02` for all three required workflow paths. -- `ContextualWisdomLab/naruon` reports inherited active ruleset `18156473` with all three required workflow paths, proving target-repository inheritance after the scheduler ruleset update. -- `ContextualWisdomLab/ContextualWisdomLab.github.io` PR `#25` merged the thin central scheduler caller and repository-local bootstrap fixes. Its main Strix run `28217860369` passed. -- The organization ruleset API reports the central required workflows ruleset as `active` and inherited by each public non-fork target repository. -- `.github` PR `#100` added required-workflow job rerun support and cancels older same-PR OpenCode runs before retrying the current head. Local verification on head `3c62c37a4deabdb0c6ed4ddf0951c1987f09866b`: `pytest -q` passed 38 tests, `coverage report --fail-under=100` reported 100%, `interrogate --fail-under=100 .` reported 100%. -- `.github` PR `#100` merged at 2026-06-29 05:45 KST with merge commit `81408f3dbe0a3c43dc4b76133f72a5e314df8a10`. A follow-up admin check should verify organization ruleset `18156473` is no longer pinned to `refs/heads/codex/rerun-required-opencode-job`. -- The earlier 2026-06-29 KST `aFIPC` PR `#78` target-coverage gap is closed. A later current-head run on `b1ddafced86302f461e95259699f1efde5ec87c9` produced central `coverage-evidence`, `opencode-review`, `strix`, and `scan-pr-queue` success before merge. -- `.github` PR `#136` changed approved stale PR handling so `BEHIND` branches are updated before failed-check or `ACTION_REQUIRED` decisions disable auto-merge. -- `.github` PR `#137` made the central `PR Review Fix Scheduler` target-repository aware through `workflow_call`, `workflow_dispatch`, schedule, and `.github` repository variables. `.github` variables currently target `ContextualWisdomLab/pg-erd-cloud` on `main`. The follow-up central autofix worker makes `ContextualWisdomLab/.github` the default `autofix_repository`, so target repositories no longer need to copy a full `pr-review-autofix.yml` worker to participate. -- `.github` PR `#138` added compare-API branch freshness evidence so approved PRs with auto-merge enabled can still receive `update-branch` when GitHub reports `BLOCKED` but the base branch is ahead. Local verification passed `pytest -q`, scheduler self-test, `py_compile`, 100% coverage, 100% docstring coverage, `actionlint`, `bash -n`, and `git diff --check`. -- `.github` PR `#140` extended `update-branch` handling to PRs where auto-merge is already enabled even if the scheduler cannot find a current-head OpenCode approval node, so queued auto-merge PRs with failed checks can still be refreshed when compare evidence shows the base branch is ahead. Local verification passed `pytest -q`, `coverage report` at 100%, `interrogate` at 100%, `py_compile`, `bash -n`, and `git diff --check`. -- `.github` PR `#145` treats compare API `status: behind` as branch-staleness evidence even when `behind_by` is missing or zero, so an auto-merge-enabled PR with failed checks and a visible GitHub "Update branch" action requests `update_branch` before disabling auto-merge. It merged at 2026-06-29 23:14 KST with merge commit `1ec0f3dcc7250fdf4a5a3ec6c26feaa98cce4f48`. -- Live dry runs on 2026-06-30 00:40 KST found update-branch candidates in `.github` PR `#147` and `naruon` PR `#803`. The follow-up scheduler trigger change runs the central queue scan after base-branch pushes and `auto_merge_enabled` events, so those UI-visible stale-branch states are not left waiting only for the periodic schedule. -- `.github` PR `#151` added protected base-branch `push` triggers and the `auto_merge_enabled` PR event to the central scheduler, then merged at 2026-06-30 00:56 KST with merge commit `00018f7783522447a71acd08a946e3504e18ff74`. The merge created push-triggered scheduler run `28385177585`, proving the new trigger path is registered; the job remained queued because runner assignment was still pending. -- The earlier compare API `behind` handling is superseded by the current immediate-action order: `CLEAN` and current-head approved PRs merge before update-branch, failed or `ACTION_REQUIRED` checks are surfaced before any update attempt, and only approved `BEHIND` PRs without current-head check blockers request `update-branch` through the configured scheduler mutation credential. -- `.github` PR `#146` taught central OpenCode `coverage-evidence` to discover nested requirements-only Python test projects such as `backend/requirements.txt` plus `backend/tests`, install those requirements, and run tests from that project directory. It merged at 2026-06-29 23:24 KST with merge commit `0393bc1c48b80597d6d35c336aca43aee18e22b9`. -- `.github` PR `#149` tightened the central OpenCode model-failure path and merged at 2026-06-30 00:26 KST with merge commit `919b83faf29237803cfdd0cfd6febbe5ae1a8a3c`. The follow-up commit `6fdffe43b50a2246b3db2790a0ab532618a89c2b` fixed the fallback approval path so pending-check and human-thread evidence are written to real temporary files instead of empty paths. Local verification passed `pytest -q`, `coverage report --fail-under=100`, `interrogate --fail-under=100`, `actionlint -shellcheck=`, targeted OpenCode quick-gate assertions, `bash -n`, and `git diff --check`; the full quick-gate script exceeded the local 300s timeout in this environment. -- Organization ruleset `18156473` previously targeted all live non-fork repositories, including private `aFIPC`, `linux-cluster-ops`, and `xtrmLLMBatchPython`; this has been superseded by the all-repository `~ALL` condition above. -- `ContextualWisdomLab/semantic-data-portal` PR `#3` removed repo-local OpenCode, Strix, and scheduler workflows; the default branch now has no `.github/workflows` directory. -- `ContextualWisdomLab/pg-erd-cloud` PR `#361` removed the repo-local `pr-review-fix-scheduler.yml` wrapper after central `.github` gained target repository support. It merged at 2026-06-29 22:40 KST with merge commit `21cbc14b21d59ac28ac789de58502816cc8df6ad`; live default-branch content lookup returned 404 for that wrapper path after merge. -- `ContextualWisdomLab/naruon` classic branch protection no longer requires direct `strix` or `opencode-review` status checks on `develop`; after deletion, `branches/develop/protection/required_status_checks` returns `404 Required status checks not enabled`, while org ruleset `18156473` remains `active` and still targets `naruon`. -- `ContextualWisdomLab/naruon` PR `#852` rewrites `backend/tests/test_release_governance.py` and `docs/development/merge-gate-policy.md` to make the central scheduler the contract, then deletes the repo-local `pr-review-merge-scheduler.yml`. The first current-head central `coverage-evidence` failed because nested `backend/requirements.txt` was not installed; `.github` PR `#146` fixed that central path. PR `#852` was pushed to head `2c8257ce0d02838b80650997d65e85569f4ab27f` to generate fresh required workflows from the updated central main. The stale OpenCode `CHANGES_REQUESTED` review `4592643416` on previous head `0f103836f15d9055c4ed85152f925a6e9514adb2` was dismissed on 2026-06-30 00:25 KST; the PR now requires fresh current-head OpenCode/coverage evidence and still has queued `coverage-evidence`. - -## Good patterns to keep - -- `naruon`: separates PR Governance, OpenCode review, Strix evidence, and application CI into explicit checks. -- `.github`: centralizes reusable workflow logic and review/merge scheduler code. -- `pg-erd-cloud`: its previous repo-local autofix worker was folded into the central `PR Review Autofix` worker and removed from the repository by PR `#393`; keep only repository-specific application and security checks locally. -- `ContextualWisdomLab.github.io`: thin caller pattern is acceptable for repository-local workflows only when GitHub does not offer an organization-level control. It should not be the default rollout mechanism. - -## Risks and follow-up - -- Existing open PRs may need a new push or base update before the latest required workflow SHA appears on their current head. -- The central OpenCode workflow now retries DeepSeek R1, DeepSeek V3, GPT-5, and a catalog fallback pool. Keep model/tooling failures out of PR comments unless there is a source-backed failed-check diagnosis. -- The central OpenCode config includes a read-only `code-reviewer` subagent for focused review passes. The subagent may read, grep, glob, and run safe local verification commands, but it must not edit files, stage changes, commit, push, install dependencies, mutate branches, or touch production state. -- OpenCode execution evidence must be sandboxed in the CI workspace or an isolated temporary directory, with a credential-scrubbed environment by default and no persistent mutation outside test caches or scratch files. Prefer `python3 scripts/ci/sandboxed_verify.py --repo-root -- ` when the central helper is available, and cite its `SANDBOXED_VERIFY_RESULT` line. When repo-native verification legitimately needs network access or GitHub Secrets, pass only the needed names with `--allow-env`, record `--network required`, and explain it with `--evidence-note` without printing secret values. The helper does not replace existing bash, task, webfetch, websearch, lsp, CodeGraph, DeepWiki, Context7, or web_search review policy. If a verification cannot be sandboxed without changing the result, the review must say so instead of presenting an unsafe run as evidence. -- Web application reviews should run backend, frontend, and repository-native E2E checks together through `python3 scripts/ci/sandboxed_web_e2e.py --repo-root --backend-cmd --frontend-cmd --e2e-cmd ` when those contracts exist, then cite `SANDBOXED_WEB_E2E_RESULT`. If backend/frontend/E2E/readiness contracts are missing, the review must name the gap instead of treating unit or lint evidence as full E2E proof. -- Bounded OpenCode evidence includes `Review execution contracts`, which inventories runtime matrices, package manifests, test, coverage, docstring, E2E, lint, security, Docker, and unpackaged-source gaps before the model chooses verification commands. -- Generated OpenCode review DAGs must use quoted Mermaid labels such as `A["text"]`; unquoted labels with spaces, punctuation, parentheses, or file counts can fail to render. -- OpenCode approval summaries must not contradict exact changed-file evidence by saying no source, test, or executable files changed when workflow, script, source, or test files are present. -- OpenCode approval reasons must not trivialize material workflow, script/source, or test changes as docs-only, typo-only, or string-only changes. The normalizer now rejects those approvals before publication. -- Same-repository post-approval merge/update follow-up should use the workflow `github.token` first so the mechanical actor is `github-actions[bot]`; cross-repository manual dispatch may still fall back to configured secrets or the OpenCode app token when the workflow token cannot mutate the target repository. -- Do not copy central Strix, OpenCode, merge scheduler, fix scheduler, or autofix worker workflows into repositories. Repository-local application CI and security CI may remain when they are not substitutes for the central workflows. -- The central autofix worker is for source-actionable current-head review findings. It must not treat model-pool exhaustion, missing approval evidence, unresolved human threads, failed checks, `coverage-evidence`, Strix failures, `DIRTY`, or `CONFLICTING` merge states as code-autofix requests; those states need retry, failed-check explanation, branch update, or conflict guidance instead. -- `pg-erd-cloud` no longer has a repository-local `pr-review-autofix.yml` worker on its default branch. Live default-branch workflows after PR `#393` are `ci.yml`, `codeql-backfill.yml`, `codeql.yml`, `dependency-review.yml`, and `scorecard.yml`. -- Some repositories use classic branch protection while others use rulesets. Normalize branch protection into rulesets without removing repository-specific required application checks. -- Existing PRs may not show newly inherited required workflows until a new PR event or branch update occurs, even though the org ruleset now uses the all-repository condition. +- The workflows rule must set `do_not_enforce_on_create=true`; proposal-branch creation must remain possible. +- The central ruleset must contain only the declared `workflows`, `pull_request`, `deletion`, and `non_fast_forward` rule types. An added `creation` or other undeclared rule is governance drift because it can defeat the proposal-branch transition or add an unreviewed protected-branch constraint. + +The scheduled audit reads the inherited organization ruleset through a consumer repository, enumerates repository inheritance, reads the owner `.github` repository ruleset, and reads the stacked OpenCode ruleset. Once a payload is fetched successfully, drift in one payload must not suppress the remaining available audits; all fetched drift reasons are emitted before the job fails. API/read failures still fail immediately because the missing payload cannot be audited. + +## Solo-maintainer review policy + +ContextualWisdomLab currently has one human maintainer. A generic positive human-approval count is therefore structurally unsatisfiable for a maintainer-authored PR when self-approval is prohibited. Model and service identities provide deterministic/advisory evidence; they are not independent human reviewers and must not be counted as such. + +The current protected-PR policy is: + +- `required_approving_review_count = 0`; +- `require_last_push_approval = false`; +- `required_reviewers = []` unless governance later identifies a genuinely independent human authority; +- `dismiss_stale_reviews_on_push = true`; +- `required_review_thread_resolution = true`; +- only merge and squash are allowed merge methods; +- deletion and non-fast-forward protection remain enabled; and +- routine bypass actors are forbidden. + +No bot approval, service account, broadened token, self-approval, or `OrganizationAdmin/always` bypass may be used to simulate human independence. Emergency control-plane repair belongs to the separately governed, time-bounded, auditable break-glass process. + +Issue `#772` is the policy decision, issue `#1351` tracks declared-versus-live drift, issue `#1340` owns routine administrator-bypass removal, and PR `#1176` is the canonical audit/test writer. Consumer repositories must not add local shims to work around this central policy. + +## Current live drift + +As of the 2026-09-01 live reads used by PR `#1176`, organization ruleset `18156473` still requires one approving review and still exposes `OrganizationAdmin/always` bypass. The owner `.github` repository ruleset `17921150` already reports zero approving reviews and no last-push requirement but still permits `rebase` and retains routine administrator bypass. These are live settings defects, not permission to weaken the audit. + +Do not claim the rulesets are reconciled until an authorized settings mutation is followed by a fresh full-payload read. A passing source-level auditor only proves the declared contract, not the live GitHub configuration. + +## Model-review gates + +### OpenCode + +`.github/workflows/opencode-review.yml` is a required deterministic review workflow. Its trusted `pull_request_target` surface is metadata-only; PR-controlled source is reviewed as data through the protected dispatch workflow and must not be executed with privileged credentials. Current-head evidence, model output, coverage/source inspection, review publication, and exact repository/base/head binding remain fail-closed. Draft PR handling and model-exhaustion behavior must follow the current central implementation; old approval bodies or predecessor heads never transfer. + +OpenCode review evidence is a model/control-plane gate, not a human approval for `required_approving_review_count`. + +### Noema + +`.github/workflows/noema-review.yml` is a separate required deterministic review workflow backed by the organization-owned Noema review identity and centrally versioned judgement path. It provides independent model evidence and may publish a PR review, but that App-authored review is not represented as a second human maintainer. Missing credentials, missing exact-head evidence, or a failed judgement remain visible failures rather than skipped success. + +The standalone `ContextualWisdomLab/noema` judgement plane remains outside the privileged workflow import boundary unless a separately reviewed integration transfers that responsibility. + +## Security and dependency review + +The required `security-scan.yml` and `sast-semgrep.yml` workflows remain base-ref agnostic so they also observe stacked PRs even though protected-ref rules are a separate control. + +Dependency Review is authoritative only when the exact `BASE_SHA...HEAD_SHA` comparison succeeds with transport success and HTTP `200`, after which the pinned `actions/dependency-review-action` actually executes. HTTP 403/404, malformed/empty status, transport failure, timeout, or missing authoritative comparison evidence fail closed. OSV, Trivy, Scorecard, CodeQL, SAST, or secret-scanning evidence are useful sibling controls but are not semantic substitutes for Dependency Review. Current public-repository HTTP 403 availability is tracked by issue `#810`; product repositories must not add a local fail-open shim. + +## Scheduler and merge boundary + +`.github/workflows/pr-review-merge-scheduler.yml` owns mechanical merge/update orchestration after policy evidence exists. For each candidate PR it must re-fetch the exact current head, base, reviews, unresolved threads, required checks, security evidence, and merge state. Stale, predecessor, queued, pending, skipped-required, cancelled, neutral, model-only, or absent evidence is non-passing. + +The scheduler may directly or automatically merge only through ordinary branch protection when all then-current deterministic requirements are satisfied. Fork/external heads stay reviewable but are excluded from unattended branch mutation. A `DIRTY`/conflicting PR requires source repair; queued auto-merge is not a conflict resolver. + +The organization queue sweep is a heartbeat, not an evidence substitute. It may retry eligible current-head work, but it cannot manufacture approval, downgrade a failed check, transfer evidence between heads, or use a repository-local workaround for a central defect. + +## Stacked PR ruleset + +Ruleset `21732164`, `CWL Stacked OpenCode required workflow`, remains `evaluate`-mode evidence over non-default branches with `ref_name.include=["~ALL"]` and `ref_name.exclude=["~DEFAULT_BRANCH"]`, requiring only the central OpenCode workflow and exempting branch creation. Active enforcement across every non-default ref is prohibited because GitHub can evaluate the ref update before a new exact-head required-workflow run can exist, deadlocking both branch creation and later stack fixes. + +Stacked PRs therefore use exact-head OpenCode evidence and ordinary PR procedure while the organization develops a target-ref-scoped enforcement design. Additional rule types in the stacked ruleset are drift. + +## Owner repository ruleset + +Repository ruleset `17921150`, `Lock default branch`, protects `ContextualWisdomLab/.github` itself. The declared contract is default-branch-only scope, zero generic approvals, no last-push requirement, no required reviewers, stale-review dismissal, thread resolution, merge/squash only, deletion protection, non-fast-forward protection, and no bypass actors. Any extra rule type is rejected unless governance first documents and tests an explicit allowed extension. + +## Validation procedure + +For every policy change: + +1. Re-read live organization and repository rulesets before changing source or settings. +2. Add a failing regression for the exact drift class before changing auditor/workflow behavior. +3. Run the permanent ruleset suites on the exact writer head. A temporary branch-only proof workflow may be used when ordinary hosted execution does not exercise the new path, but it must use least privilege and immutable dependencies. +4. Do not call a queued, cancelled, skipped, neutral, stale, predecessor, or missing run GREEN. +5. Remove temporary proof workflows only after terminal-success evidence exists for the then-current exact head and their durable regressions no longer depend on the temporary file. +6. Apply the authorized live settings change without routine bypass, synthetic reviewers, self-approval, force push, or direct protected-branch write. +7. Re-read the complete live payload after mutation and compare it with the executable audit contract. +8. Re-use an unchanged deterministic-GREEN consumer PR as a canary. Orgmetra PR `#88` is suitable while it remains unchanged and otherwise clean. +9. Merge only through ordinary protection after all current-head deterministic evidence and review-thread requirements are terminal successful. + +## Traceability + +- `.github#772` — solo-maintainer protected-PR policy decision. +- `.github#1351` — declared-versus-live central review-policy drift. +- `.github#1340` — routine administrator bypass / break-glass boundary. +- `.github#1200` — default-branch scope and branch-create transition. +- `.github#810` — Dependency Review fail-closed contract and current availability incident. +- `.github#624` — OpenCode control-plane work. +- `.github#1327` — Strix control-plane work. +- `.github#1399` — shared Noema sidecar/control-plane work. +- `.github` PR `#1176` — canonical executable ruleset audit/test writer. + +This guide is intentionally current-state oriented. Historical rollout tables, old approval counts, transient exact heads, and superseded experiments are preserved by Git history and the linked issues/PRs rather than being presented as present operator instructions. \ No newline at end of file From 49ebfaefeca7a40bcc1f52c362d6d0504a6330e5 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 1 Sep 2026 23:31:29 +0900 Subject: [PATCH 39/62] test(governance): pin focused proof interpreter --- tests/test_ruleset_audit_completeness_regression.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tests/test_ruleset_audit_completeness_regression.py b/tests/test_ruleset_audit_completeness_regression.py index 36d50cd055..8ae16db0e4 100644 --- a/tests/test_ruleset_audit_completeness_regression.py +++ b/tests/test_ruleset_audit_completeness_regression.py @@ -110,6 +110,15 @@ def test_live_audit_collects_all_available_ruleset_drift_before_failing() -> Non assert 'if [[ "$audit_status" -ne 0 ]]; then' in workflow +def test_focused_contract_pins_python_before_hash_verified_install() -> None: + """The disposable proof lane must not depend on the hosted default Python.""" + workflow = (REPO_ROOT / ".github/workflows/solo-maintainer-ruleset-contract.yml").read_text(encoding="utf-8") + + assert "actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97" in workflow + assert 'python-version: "3.14"' in workflow + assert workflow.index("actions/setup-python@") < workflow.index("python -m pip install") + + def test_rollout_guide_declares_solo_maintainer_review_policy() -> None: """Operator documentation must not reintroduce a fictional second human approval.""" rollout = (REPO_ROOT / "docs/org-required-workflow-rollout.md").read_text(encoding="utf-8") @@ -117,4 +126,4 @@ def test_rollout_guide_declares_solo_maintainer_review_policy() -> None: assert "required_approving_review_count = 0" in rollout assert "require_last_push_approval = false" in rollout assert "The org's two-reviewer merge rule" not in rollout - assert "two distinct approvals" not in rollout + assert "two distinct approvals" not in rollout \ No newline at end of file From 1bd407d4211dbd1c48e87661f0cb0cedb5cabe7f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 1 Sep 2026 23:31:45 +0900 Subject: [PATCH 40/62] fix(governance): restore Python 3.14 proof runtime --- .github/workflows/solo-maintainer-ruleset-contract.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/solo-maintainer-ruleset-contract.yml b/.github/workflows/solo-maintainer-ruleset-contract.yml index 3779dfc591..b229870e99 100644 --- a/.github/workflows/solo-maintainer-ruleset-contract.yml +++ b/.github/workflows/solo-maintainer-ruleset-contract.yml @@ -25,6 +25,11 @@ jobs: with: persist-credentials: false + - name: Set up Python + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: "3.14" + - name: Execute focused ruleset contract env: PIP_DISABLE_PIP_VERSION_CHECK: "1" From 76516f40b50e5899f5e4b6f4c588cdbc8bc76ca9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 00:00:33 +0900 Subject: [PATCH 41/62] fix(governance): run focused contract on explicit runner --- .github/workflows/solo-maintainer-ruleset-contract.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/solo-maintainer-ruleset-contract.yml b/.github/workflows/solo-maintainer-ruleset-contract.yml index b229870e99..892f81635c 100644 --- a/.github/workflows/solo-maintainer-ruleset-contract.yml +++ b/.github/workflows/solo-maintainer-ruleset-contract.yml @@ -11,6 +11,7 @@ on: - "tests/test_central_required_workflow_ruleset_audit.py" - "tests/test_solo_maintainer_ruleset_policy.py" - "tests/test_ruleset_audit_completeness_regression.py" + - "tests/test_pr_governance_audit_contract.py" permissions: contents: read @@ -18,7 +19,7 @@ permissions: jobs: focused-contract: name: Focused solo-maintainer ruleset contract - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Checkout exact pushed head uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 @@ -50,4 +51,5 @@ jobs: python -m pytest -q \ tests/test_central_required_workflow_ruleset_audit.py \ tests/test_solo_maintainer_ruleset_policy.py \ - tests/test_ruleset_audit_completeness_regression.py \ No newline at end of file + tests/test_ruleset_audit_completeness_regression.py \ + tests/test_pr_governance_audit_contract.py From af04bacdc6196b0541a090e403c08991fb668192 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 00:01:14 +0900 Subject: [PATCH 42/62] docs(governance): preserve regression fixture history --- docs/org-required-workflow-rollout.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/org-required-workflow-rollout.md b/docs/org-required-workflow-rollout.md index 4e5be81978..bae37bb79c 100644 --- a/docs/org-required-workflow-rollout.md +++ b/docs/org-required-workflow-rollout.md @@ -4,7 +4,11 @@ Updated: 2026-09-01 KST ## Purpose -This document is the operator-facing current-state contract for organization-wide pull-request admission. Historical rollout experiments, exact transient PR heads, runner IDs, and superseded review-count policies remain available in Git history and the linked governance issues; they are intentionally not repeated here because stale operational snapshots can recreate a closed governance defect. +This document is the operator-facing current-state contract for organization-wide pull-request admission. Historical rollout experiments, exact transient PR heads, runner IDs, and superseded review-count policies remain available in Git history and the linked governance issues; they are intentionally not presented as current operator instructions because stale operational snapshots can recreate a closed governance defect. + +## Historical regression fixtures (non-normative) + +The following closed or historical observations are retained only because executable regression tests use them as fixtures for central-governance coverage. They are evidence history, not present-day operating policy: the rollout inventory that established central coverage comprised **17 public non-fork repositories**, including `kaefa` and `waf-ids-ai-soc`. For `aFIPC`, PR `#78` is no longer a target-coverage gap. During that historical rollout, current PR #60 lacked central check runs; PR #6 merged after central workflow proof; and PR #8 is now the open current-head runtime proof fixture for the recorded case. Any live decision must still use fresh exact-head checks, reviews, rulesets, and current repository state rather than these fixtures. ## Canonical organization ruleset @@ -118,4 +122,4 @@ For every policy change: - `.github#1399` — shared Noema sidecar/control-plane work. - `.github` PR `#1176` — canonical executable ruleset audit/test writer. -This guide is intentionally current-state oriented. Historical rollout tables, old approval counts, transient exact heads, and superseded experiments are preserved by Git history and the linked issues/PRs rather than being presented as present operator instructions. \ No newline at end of file +This guide is intentionally current-state oriented. Historical rollout tables, old approval counts, transient exact heads, and superseded experiments are preserved by Git history and the linked issues/PRs rather than being presented as present operator instructions. From 3156622b475965293b46f5db090c58878b91d8a7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 00:03:42 +0900 Subject: [PATCH 43/62] ci(governance): allow exact-head focused redispatch --- .github/workflows/solo-maintainer-ruleset-contract.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/solo-maintainer-ruleset-contract.yml b/.github/workflows/solo-maintainer-ruleset-contract.yml index 892f81635c..63698dbffd 100644 --- a/.github/workflows/solo-maintainer-ruleset-contract.yml +++ b/.github/workflows/solo-maintainer-ruleset-contract.yml @@ -12,6 +12,7 @@ on: - "tests/test_solo_maintainer_ruleset_policy.py" - "tests/test_ruleset_audit_completeness_regression.py" - "tests/test_pr_governance_audit_contract.py" + workflow_dispatch: permissions: contents: read From 74c01484dab6cc9d84a137f89d2c7ac8473bf5b4 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 00:11:53 +0900 Subject: [PATCH 44/62] test(governance): reproduce malformed merge-method audit crash --- ...t_ruleset_merge_method_shape_regression.py | 123 ++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 tests/test_ruleset_merge_method_shape_regression.py diff --git a/tests/test_ruleset_merge_method_shape_regression.py b/tests/test_ruleset_merge_method_shape_regression.py new file mode 100644 index 0000000000..ac0d61fa70 --- /dev/null +++ b/tests/test_ruleset_merge_method_shape_regression.py @@ -0,0 +1,123 @@ +"""Fail-closed regression for malformed ruleset merge-method payloads.""" + +from __future__ import annotations + +from copy import deepcopy + +import pytest + +from scripts.ci import audit_central_required_workflows as audit + + +def _review_parameters() -> dict[str, object]: + return { + "required_approving_review_count": 0, + "dismiss_stale_reviews_on_push": True, + "require_last_push_approval": False, + "required_review_thread_resolution": True, + "required_reviewers": [], + "require_extra_approval_for_unattributed_changes": True, + "allowed_merge_methods": ["merge", "squash"], + } + + +def _central_payload() -> dict[str, object]: + return { + "id": audit.RULESET_ID, + "name": audit.RULESET_NAME, + "target": "branch", + "enforcement": "active", + "bypass_actors": [], + "conditions": { + "repository_name": { + "include": ["~ALL"], + "exclude": sorted(audit.EXPECTED_EXCLUSIONS), + }, + "ref_name": {"include": ["~DEFAULT_BRANCH"], "exclude": []}, + }, + "rules": [ + { + "type": "workflows", + "parameters": { + "do_not_enforce_on_create": True, + "workflows": [ + { + "repository_id": audit.SOURCE_REPOSITORY_ID, + "path": path, + "ref": audit.SOURCE_REF, + } + for path in audit.REQUIRED_WORKFLOW_PATHS + ], + }, + }, + {"type": "pull_request", "parameters": _review_parameters()}, + {"type": "deletion"}, + {"type": "non_fast_forward"}, + ], + } + + +def _repository_payload() -> dict[str, object]: + return { + "id": audit.REPOSITORY_RULESET_ID, + "name": audit.REPOSITORY_RULESET_NAME, + "target": "branch", + "source_type": "Repository", + "source": audit.REPOSITORY_RULESET_SOURCE, + "enforcement": "active", + "bypass_actors": [], + "conditions": { + "ref_name": {"include": ["~DEFAULT_BRANCH"], "exclude": []}, + }, + "rules": [ + {"type": "pull_request", "parameters": _review_parameters()}, + {"type": "deletion"}, + {"type": "non_fast_forward"}, + ], + } + + +def _set_allowed_merge_methods(payload: dict[str, object], value: object) -> None: + rules = payload["rules"] + assert isinstance(rules, list) + review_rule = next( + rule for rule in rules if isinstance(rule, dict) and rule.get("type") == "pull_request" + ) + parameters = review_rule["parameters"] + assert isinstance(parameters, dict) + parameters["allowed_merge_methods"] = value + + +@pytest.mark.parametrize( + "malformed", + [None, 7, "merge", {"merge": True}, ("merge", "squash")], +) +def test_central_audit_reports_malformed_merge_method_shape_without_raising( + malformed: object, +) -> None: + payload = deepcopy(_central_payload()) + _set_allowed_merge_methods(payload, malformed) + + errors = audit.audit_ruleset(payload) + + assert "only merge and squash may be allowed merge methods" in errors + + +@pytest.mark.parametrize( + "malformed", + [None, 7, "merge", {"merge": True}, ("merge", "squash")], +) +def test_repository_audit_reports_malformed_merge_method_shape_without_raising( + malformed: object, +) -> None: + payload = deepcopy(_repository_payload()) + _set_allowed_merge_methods(payload, malformed) + + errors = audit.audit_repository_ruleset(payload) + + assert "repository ruleset must allow only merge and squash" in errors + + +def test_valid_merge_method_list_remains_accepted() -> None: + assert audit.audit_ruleset(_central_payload()) == [] + assert audit.audit_repository_ruleset(_repository_payload()) == [] From 9b5d822050f32ddc144eede91e3bd802f6a02cc6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 00:12:12 +0900 Subject: [PATCH 45/62] test(governance): reject malformed merge methods --- tests/test_solo_maintainer_ruleset_policy.py | 30 +++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/tests/test_solo_maintainer_ruleset_policy.py b/tests/test_solo_maintainer_ruleset_policy.py index bc5d426622..f05f22498f 100644 --- a/tests/test_solo_maintainer_ruleset_policy.py +++ b/tests/test_solo_maintainer_ruleset_policy.py @@ -84,6 +84,12 @@ def _repository_ruleset_payload() -> dict: } +def _review_parameters(payload: dict) -> dict: + """Return the unique pull-request rule parameters from ``payload``.""" + review_rule = next(rule for rule in payload["rules"] if rule["type"] == "pull_request") + return review_rule["parameters"] + + def test_central_ruleset_accepts_zero_approvals_without_last_push_approval() -> None: """A one-human fleet must not require an approval its sole author cannot give.""" assert audit.audit_ruleset(_central_ruleset_payload()) == [] @@ -97,8 +103,7 @@ def test_repository_ruleset_accepts_zero_approvals_without_last_push_approval() def test_central_ruleset_rejects_synthetic_required_reviewer() -> None: """A named reviewer cannot manufacture independence in a one-human fleet.""" payload = _central_ruleset_payload() - review_rule = next(rule for rule in payload["rules"] if rule["type"] == "pull_request") - review_rule["parameters"]["required_reviewers"] = [ + _review_parameters(payload)["required_reviewers"] = [ {"reviewer_id": 1234, "reviewer_type": "User"} ] @@ -110,11 +115,28 @@ def test_central_ruleset_rejects_synthetic_required_reviewer() -> None: def test_repository_ruleset_rejects_synthetic_required_reviewer() -> None: """The owner repository cannot reintroduce the same deadlock by reviewer identity.""" payload = _repository_ruleset_payload() - review_rule = next(rule for rule in payload["rules"] if rule["type"] == "pull_request") - review_rule["parameters"]["required_reviewers"] = [ + _review_parameters(payload)["required_reviewers"] = [ {"reviewer_id": 1234, "reviewer_type": "User"} ] assert audit.audit_repository_ruleset(payload) == [ "repository solo-maintainer ruleset must not configure required reviewers" ] + + +def test_central_ruleset_rejects_malformed_allowed_merge_methods() -> None: + """Malformed API payloads must report drift rather than abort central auditing.""" + expected = ["only merge and squash may be allowed merge methods"] + for malformed in (None, 7, "merge", {"merge": True, "squash": True}): + payload = _central_ruleset_payload() + _review_parameters(payload)["allowed_merge_methods"] = malformed + assert audit.audit_ruleset(payload) == expected + + +def test_repository_ruleset_rejects_malformed_allowed_merge_methods() -> None: + """Malformed API payloads must report drift rather than abort repository auditing.""" + expected = ["repository ruleset must allow only merge and squash"] + for malformed in (None, 7, "merge", {"merge": True, "squash": True}): + payload = _repository_ruleset_payload() + _review_parameters(payload)["allowed_merge_methods"] = malformed + assert audit.audit_repository_ruleset(payload) == expected From b3c2f6df9e5cd7ecc3a3fa1e7b88628a5b3fa663 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 00:12:31 +0900 Subject: [PATCH 46/62] ci(governance): repair malformed merge-method finding on exact head --- .../repair-pr1176-merge-method-shape.yml | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 .github/workflows/repair-pr1176-merge-method-shape.yml diff --git a/.github/workflows/repair-pr1176-merge-method-shape.yml b/.github/workflows/repair-pr1176-merge-method-shape.yml new file mode 100644 index 0000000000..820ac14430 --- /dev/null +++ b/.github/workflows/repair-pr1176-merge-method-shape.yml @@ -0,0 +1,96 @@ +name: Repair PR1176 merge-method shape audit + +on: + push: + branches: [fix/stacked-pr-central-required-workflows] + paths: + - ".github/workflows/repair-pr1176-merge-method-shape.yml" + +permissions: + contents: write + +jobs: + repair: + runs-on: ubuntu-24.04 + steps: + - name: Checkout exact writer head + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + ref: fix/stacked-pr-central-required-workflows + fetch-depth: 0 + + - name: Fail closed on malformed allowed_merge_methods payloads + shell: bash --noprofile --norc -e -o pipefail {0} + run: | + python3 - <<'PY' + from pathlib import Path + + audit_path = Path("scripts/ci/audit_central_required_workflows.py") + text = audit_path.read_text(encoding="utf-8") + old = ' allowed_methods = set(parameters.get("allowed_merge_methods") or [])\n' + new = ''' raw_allowed_methods = parameters.get("allowed_merge_methods")\n allowed_methods = (\n set(raw_allowed_methods)\n if isinstance(raw_allowed_methods, list)\n and all(isinstance(method, str) for method in raw_allowed_methods)\n else set()\n )\n''' + if text.count(old) != 2: + raise SystemExit( + f"expected exactly two merge-method parsers at the exact writer head; found {text.count(old)}" + ) + audit_path.write_text(text.replace(old, new), encoding="utf-8") + + workflow_path = Path(".github/workflows/solo-maintainer-ruleset-contract.yml") + workflow = workflow_path.read_text(encoding="utf-8") + trigger_anchor = ' - "tests/test_solo_maintainer_ruleset_policy.py"\n' + test_anchor = ' tests/test_solo_maintainer_ruleset_policy.py \\\n' + new_test = "tests/test_ruleset_merge_method_shape_regression.py" + if new_test not in workflow: + if trigger_anchor not in workflow or test_anchor not in workflow: + raise SystemExit("focused workflow anchors changed; refusing a stale patch") + workflow = workflow.replace( + trigger_anchor, + trigger_anchor + f' - "{new_test}"\n', + 1, + ) + workflow = workflow.replace( + test_anchor, + test_anchor + f" {new_test} \\\n", + 1, + ) + workflow_path.write_text(workflow, encoding="utf-8") + PY + + - name: Set up Python + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: "3.14" + + - name: Verify malformed-shape regression and focused governance suites + env: + PIP_DISABLE_PIP_VERSION_CHECK: "1" + PIP_NO_INPUT: "1" + shell: bash --noprofile --norc -e -o pipefail {0} + run: | + cat >"${RUNNER_TEMP}/ruleset-repair-requirements.txt" <<'EOF' + iniconfig==2.1.0 --hash=sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760 + packaging==26.2 --hash=sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e + pluggy==1.6.0 --hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746 + pygments==2.20.0 --hash=sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176 + pytest==9.1.1 --hash=sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c + EOF + python -m pip install --only-binary=:all: --require-hashes -r "${RUNNER_TEMP}/ruleset-repair-requirements.txt" + python -m pytest -q \ + tests/test_ruleset_merge_method_shape_regression.py \ + tests/test_central_required_workflow_ruleset_audit.py \ + tests/test_solo_maintainer_ruleset_policy.py \ + tests/test_ruleset_audit_completeness_regression.py \ + tests/test_pr_governance_audit_contract.py + + - name: Commit verified repair and retire helper + shell: bash --noprofile --norc -e -o pipefail {0} + run: | + rm .github/workflows/repair-pr1176-merge-method-shape.yml + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add scripts/ci/audit_central_required_workflows.py \ + .github/workflows/solo-maintainer-ruleset-contract.yml \ + tests/test_ruleset_merge_method_shape_regression.py \ + .github/workflows/repair-pr1176-merge-method-shape.yml + git commit -m "fix(governance): reject malformed merge-method payloads" + git push origin HEAD:fix/stacked-pr-central-required-workflows From ce752a411e49ec698a5435f8e0acabe2671136c4 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 00:20:21 +0900 Subject: [PATCH 47/62] fix(governance): reject malformed merge-method payloads --- scripts/ci/audit_central_required_workflows.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/scripts/ci/audit_central_required_workflows.py b/scripts/ci/audit_central_required_workflows.py index e8154e4187..8d42ede32c 100644 --- a/scripts/ci/audit_central_required_workflows.py +++ b/scripts/ci/audit_central_required_workflows.py @@ -238,7 +238,13 @@ def audit_ruleset(payload: dict[str, Any]) -> list[str]: ) 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 []) + 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") @@ -364,7 +370,13 @@ def audit_repository_ruleset(payload: dict[str, Any]) -> list[str]: errors.append( "repository ruleset review-thread resolution protection is disabled" ) - allowed_methods = set(parameters.get("allowed_merge_methods") or []) + 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") From 7e8246214460ce6943f041cb019751688d488530 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 00:20:33 +0900 Subject: [PATCH 48/62] chore(governance): retire source-fix helper --- .../repair-pr1176-merge-method-shape.yml | 96 ------------------- 1 file changed, 96 deletions(-) delete mode 100644 .github/workflows/repair-pr1176-merge-method-shape.yml diff --git a/.github/workflows/repair-pr1176-merge-method-shape.yml b/.github/workflows/repair-pr1176-merge-method-shape.yml deleted file mode 100644 index 820ac14430..0000000000 --- a/.github/workflows/repair-pr1176-merge-method-shape.yml +++ /dev/null @@ -1,96 +0,0 @@ -name: Repair PR1176 merge-method shape audit - -on: - push: - branches: [fix/stacked-pr-central-required-workflows] - paths: - - ".github/workflows/repair-pr1176-merge-method-shape.yml" - -permissions: - contents: write - -jobs: - repair: - runs-on: ubuntu-24.04 - steps: - - name: Checkout exact writer head - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - ref: fix/stacked-pr-central-required-workflows - fetch-depth: 0 - - - name: Fail closed on malformed allowed_merge_methods payloads - shell: bash --noprofile --norc -e -o pipefail {0} - run: | - python3 - <<'PY' - from pathlib import Path - - audit_path = Path("scripts/ci/audit_central_required_workflows.py") - text = audit_path.read_text(encoding="utf-8") - old = ' allowed_methods = set(parameters.get("allowed_merge_methods") or [])\n' - new = ''' raw_allowed_methods = parameters.get("allowed_merge_methods")\n allowed_methods = (\n set(raw_allowed_methods)\n if isinstance(raw_allowed_methods, list)\n and all(isinstance(method, str) for method in raw_allowed_methods)\n else set()\n )\n''' - if text.count(old) != 2: - raise SystemExit( - f"expected exactly two merge-method parsers at the exact writer head; found {text.count(old)}" - ) - audit_path.write_text(text.replace(old, new), encoding="utf-8") - - workflow_path = Path(".github/workflows/solo-maintainer-ruleset-contract.yml") - workflow = workflow_path.read_text(encoding="utf-8") - trigger_anchor = ' - "tests/test_solo_maintainer_ruleset_policy.py"\n' - test_anchor = ' tests/test_solo_maintainer_ruleset_policy.py \\\n' - new_test = "tests/test_ruleset_merge_method_shape_regression.py" - if new_test not in workflow: - if trigger_anchor not in workflow or test_anchor not in workflow: - raise SystemExit("focused workflow anchors changed; refusing a stale patch") - workflow = workflow.replace( - trigger_anchor, - trigger_anchor + f' - "{new_test}"\n', - 1, - ) - workflow = workflow.replace( - test_anchor, - test_anchor + f" {new_test} \\\n", - 1, - ) - workflow_path.write_text(workflow, encoding="utf-8") - PY - - - name: Set up Python - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 - with: - python-version: "3.14" - - - name: Verify malformed-shape regression and focused governance suites - env: - PIP_DISABLE_PIP_VERSION_CHECK: "1" - PIP_NO_INPUT: "1" - shell: bash --noprofile --norc -e -o pipefail {0} - run: | - cat >"${RUNNER_TEMP}/ruleset-repair-requirements.txt" <<'EOF' - iniconfig==2.1.0 --hash=sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760 - packaging==26.2 --hash=sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e - pluggy==1.6.0 --hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746 - pygments==2.20.0 --hash=sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176 - pytest==9.1.1 --hash=sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c - EOF - python -m pip install --only-binary=:all: --require-hashes -r "${RUNNER_TEMP}/ruleset-repair-requirements.txt" - python -m pytest -q \ - tests/test_ruleset_merge_method_shape_regression.py \ - tests/test_central_required_workflow_ruleset_audit.py \ - tests/test_solo_maintainer_ruleset_policy.py \ - tests/test_ruleset_audit_completeness_regression.py \ - tests/test_pr_governance_audit_contract.py - - - name: Commit verified repair and retire helper - shell: bash --noprofile --norc -e -o pipefail {0} - run: | - rm .github/workflows/repair-pr1176-merge-method-shape.yml - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add scripts/ci/audit_central_required_workflows.py \ - .github/workflows/solo-maintainer-ruleset-contract.yml \ - tests/test_ruleset_merge_method_shape_regression.py \ - .github/workflows/repair-pr1176-merge-method-shape.yml - git commit -m "fix(governance): reject malformed merge-method payloads" - git push origin HEAD:fix/stacked-pr-central-required-workflows From 2fb3a489497644ece0bad902eeb151a8418169b3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 00:23:55 +0900 Subject: [PATCH 49/62] chore(governance): retire focused proof workflow --- .../solo-maintainer-ruleset-contract.yml | 56 ------------------- 1 file changed, 56 deletions(-) delete mode 100644 .github/workflows/solo-maintainer-ruleset-contract.yml diff --git a/.github/workflows/solo-maintainer-ruleset-contract.yml b/.github/workflows/solo-maintainer-ruleset-contract.yml deleted file mode 100644 index 63698dbffd..0000000000 --- a/.github/workflows/solo-maintainer-ruleset-contract.yml +++ /dev/null @@ -1,56 +0,0 @@ -name: Solo Maintainer Ruleset Contract - -on: - push: - branches: [fix/stacked-pr-central-required-workflows] - paths: - - ".github/workflows/solo-maintainer-ruleset-contract.yml" - - ".github/workflows/audit-central-ruleset.yml" - - "scripts/ci/audit_central_required_workflows.py" - - "docs/org-required-workflow-rollout.md" - - "tests/test_central_required_workflow_ruleset_audit.py" - - "tests/test_solo_maintainer_ruleset_policy.py" - - "tests/test_ruleset_audit_completeness_regression.py" - - "tests/test_pr_governance_audit_contract.py" - workflow_dispatch: - -permissions: - contents: read - -jobs: - focused-contract: - name: Focused solo-maintainer ruleset contract - runs-on: ubuntu-24.04 - steps: - - name: Checkout exact pushed head - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - persist-credentials: false - - - name: Set up Python - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 - with: - python-version: "3.14" - - - name: Execute focused ruleset contract - env: - PIP_DISABLE_PIP_VERSION_CHECK: "1" - PIP_NO_INPUT: "1" - shell: bash --noprofile --norc -e -o pipefail {0} - run: | - cat >"${RUNNER_TEMP}/solo-maintainer-ruleset-requirements.txt" <<'EOF' - iniconfig==2.1.0 --hash=sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760 - packaging==26.2 --hash=sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e - pluggy==1.6.0 --hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746 - pygments==2.20.0 --hash=sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176 - pytest==9.1.1 --hash=sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c - EOF - python -m pip install \ - --only-binary=:all: \ - --require-hashes \ - -r "${RUNNER_TEMP}/solo-maintainer-ruleset-requirements.txt" - python -m pytest -q \ - tests/test_central_required_workflow_ruleset_audit.py \ - tests/test_solo_maintainer_ruleset_policy.py \ - tests/test_ruleset_audit_completeness_regression.py \ - tests/test_pr_governance_audit_contract.py From 5d1e416956e49545283a2eb581b6eb0186f5613d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 02:12:28 +0900 Subject: [PATCH 50/62] fix(governance): retire stale temporary-workflow regression --- tests/test_ruleset_audit_completeness_regression.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/tests/test_ruleset_audit_completeness_regression.py b/tests/test_ruleset_audit_completeness_regression.py index 8ae16db0e4..2343c839da 100644 --- a/tests/test_ruleset_audit_completeness_regression.py +++ b/tests/test_ruleset_audit_completeness_regression.py @@ -110,13 +110,11 @@ def test_live_audit_collects_all_available_ruleset_drift_before_failing() -> Non assert 'if [[ "$audit_status" -ne 0 ]]; then' in workflow -def test_focused_contract_pins_python_before_hash_verified_install() -> None: - """The disposable proof lane must not depend on the hosted default Python.""" - workflow = (REPO_ROOT / ".github/workflows/solo-maintainer-ruleset-contract.yml").read_text(encoding="utf-8") +def test_disposable_focused_contract_is_removed_after_terminal_proof() -> None: + """The temporary proof workflow must not survive its proven source-fix lifecycle.""" + proof_workflow = REPO_ROOT / ".github/workflows/solo-maintainer-ruleset-contract.yml" - assert "actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97" in workflow - assert 'python-version: "3.14"' in workflow - assert workflow.index("actions/setup-python@") < workflow.index("python -m pip install") + assert not proof_workflow.exists() def test_rollout_guide_declares_solo_maintainer_review_policy() -> None: @@ -126,4 +124,4 @@ def test_rollout_guide_declares_solo_maintainer_review_policy() -> None: assert "required_approving_review_count = 0" in rollout assert "require_last_push_approval = false" in rollout assert "The org's two-reviewer merge rule" not in rollout - assert "two distinct approvals" not in rollout \ No newline at end of file + assert "two distinct approvals" not in rollout From 214b0bd8c6f8554123e0ba2fad62b0b233384344 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 03:26:03 +0900 Subject: [PATCH 51/62] fix(governance): preserve protected-main review runner repair Restore the exact protected-main #1632 tree for Strix/OpenCode/Noema runner pins, queue regressions, changelog and product-gap record after ancestry integration. The governance writer retains only its seven intended audit/documentation/test deltas over main. --- .github/workflows/noema-review.yml | 4 +- .github/workflows/opencode-review.yml | 10 ++--- .github/workflows/strix.yml | 6 +-- CHANGELOG.md | 1 + docs/product-technical-gap-baseline.md | 13 +++++++ ...t_required_review_runner_image_contract.py | 37 +++++++++++++++++++ .../test_required_workflow_queue_contract.py | 8 ++-- 7 files changed, 65 insertions(+), 14 deletions(-) create mode 100644 tests/test_required_review_runner_image_contract.py diff --git a/.github/workflows/noema-review.yml b/.github/workflows/noema-review.yml index 6b2e3fcede..2c941983f9 100644 --- a/.github/workflows/noema-review.yml +++ b/.github/workflows/noema-review.yml @@ -35,7 +35,7 @@ permissions: jobs: cancel-closed-pr-runs: if: github.event_name == 'pull_request_target' && github.event.action == 'closed' - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 permissions: actions: write contents: read @@ -179,7 +179,7 @@ jobs: noema-review: name: noema-review - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 if: >- github.event_name == 'repository_dispatch' || ( diff --git a/.github/workflows/opencode-review.yml b/.github/workflows/opencode-review.yml index 87827f5322..0864993179 100644 --- a/.github/workflows/opencode-review.yml +++ b/.github/workflows/opencode-review.yml @@ -44,7 +44,7 @@ permissions: jobs: required-workflow-bootstrap: name: required-workflow-bootstrap - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Materialize the required review workflow run: >- @@ -250,7 +250,7 @@ jobs: coverage-source-tree: name: coverage-source-tree needs: [required-workflow-bootstrap] - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - run: >- echo "PR-head source and coverage execution are delegated to the @@ -259,7 +259,7 @@ jobs: coverage-evidence: name: coverage-evidence needs: [coverage-source-tree] - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - run: >- echo "This required-workflow job preserves the stable branch-protection @@ -268,7 +268,7 @@ jobs: opencode-review-target: name: opencode-review needs: [coverage-evidence] - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 permissions: contents: read pull-requests: read @@ -475,7 +475,7 @@ jobs: # immediately beforehand, so a run for this job that is itself somehow # delayed/stale cannot wrongly cancel a still-authoritative run. if: github.event_name == 'pull_request_target' && github.event.action == 'synchronize' - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 permissions: actions: write contents: read diff --git a/.github/workflows/strix.yml b/.github/workflows/strix.yml index 26d5d8b2cb..7674b3040f 100644 --- a/.github/workflows/strix.yml +++ b/.github/workflows/strix.yml @@ -79,7 +79,7 @@ permissions: jobs: cancel-superseded-pr-runs: if: github.event_name == 'pull_request_target' && (github.event.action == 'synchronize' || github.event.action == 'closed') - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 # Prefer the established scheduler credential, but let the close event use # its job-scoped token so abandoned scans are cancelled even when that # optional secret is unavailable. This job never checks out PR code. @@ -190,7 +190,7 @@ jobs: # scans may take more than two hours per model (docs/product-goal-directive.md). # Inference has no wall-clock deadline; cancellation is reserved for an # explicit operator action or a superseded head. - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 # Least-privilege token scoped to this job (Scorecard alert #43): the scan # exchanges an OIDC token (id-token) and publishes same-repo status evidence # from the scan job only. @@ -1019,7 +1019,7 @@ jobs: name: publish-manual-pr-evidence-status needs: strix if: ${{ always() && !cancelled() && github.event_name == 'repository_dispatch' && github.event.client_payload.pr_head_sha != '' }} - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 permissions: id-token: write statuses: write # Required for downscoped OIDC status publication. diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f980f794d..4b661772cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ this file. The format follows Keep a Changelog, and versioned releases follow Semantic Versioning where the repository publishes a release. ## [Unreleased] +- **Pin the three central required review workflows (Strix, OpenCode Review, Noema Review) off the observed starved floating `ubuntu-latest` runner image.** Following the same repair already rolled out to security gates (`#1618`) and the merge scheduler (`#1609`), `strix.yml`, `opencode-review.yml`, and `noema-review.yml` now request the explicit `ubuntu-24.04` image on every job. These three workflows are the org's own required-workflow gate for every sibling repository, so a starved floating image here directly contributes to organization-wide required-check queuing. New `tests/test_required_review_runner_image_contract.py` asserts no job in any of the three files still requests the floating image. Also fixed 4 pre-existing, unrelated test failures on `main` left by `#1630`'s organization-sweep rotation cadence change (every 15 minutes to hourly, to reduce control-plane pressure under the same Actions saturation): `tests/test_required_workflow_queue_contract.py`'s rotation-index tests still asserted the old `/ 900` (15-minute) divisor against the new `/ 3600` (hourly) production value. - **Refresh Noema reviewer App authority after long model work (`#1616`).** A real `naruon#1497` review outlived its repository-scoped GitHub App installation token and failed the next exact-head GitHub operation with HTTP 401. The trusted workflow now prepares the validated verdict into a private runner-local envelope, remints the same least-privilege repository-scoped App authority after model work, independently re-fetches exact live head/reviewer identity, and only then publishes. Skipped preparation creates no envelope, predecessor App tokens cannot authorize publication, PAT/OIDC remain explicit fail-closed sources, malformed handoffs are cleaned up, and executable plus step-scoped regressions cover stale-head, identity, alias, workflow wiring, and migration of legacy broader-suite contracts away from the retired single-process reviewer path. - Fix `existing_noema_review()` treating a "legacy" Noema review (one posted before `NOEMA_REVIEW_FOOTER_MARKER` existed) as proof the current head was already reviewed. diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index 7ba1d7cd41..41d95b6f57 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -2576,3 +2576,16 @@ Higgins, S. S., Crepalde, N., & Fernandes, L. (2021). Segmented multiplexity: A **Regression-suite consistency.** Legacy broader-suite assertions that still named the retired single-process Noema step/module are migrated to the two-phase prepare/publish contract, including step-scoped helper and envelope-argument evidence. This closes the false-GREEN gap where focused token-lifetime CI could pass while unchanged broader contracts described an impossible execution path. **Residual external verification.** After this central change reaches protected `main`, replay Required Noema Review for unchanged `naruon#1497@152d1998c4e8024be9dc7026c8789d343c884fd0`. Closure evidence requires a current-head schema-valid review or typed review-unavailable outcome without expired-token 401; a pre-merge run cannot prove the merged workflow-source path and is not promoted to release evidence. + + +## 2026-09-01 central required review workflows: floating runner image contributing to organization-wide queuing + +**Observed gap.** `#1618` (required security gates) and `#1609` (merge scheduler) already pinned their jobs off `ubuntu-latest` after this session found it to be, in that fix's own words, "the observed starved floating image" — GitHub-hosted runners requesting the floating `ubuntu-latest` label were being left `queued` with no runner assignment for hours, well beyond ordinary scheduling latency, while identical jobs on other repositories/workflows completed normally. `strix.yml`, `opencode-review.yml`, and `noema-review.yml` — the three workflows the org's own required-workflow ruleset runs against every PR in every sibling repository — still requested `ubuntu-latest` on every job (9 occurrences total: 3 in `strix.yml`, 5 in `opencode-review.yml`, 2 in `noema-review.yml`; `pr-review-merge-scheduler.yml` was already covered by `#1609`). Since these three are the actual required-check gate blocking merge across the whole organization, a starved image here is a direct, high-leverage contributor to the sustained multi-hour organization-wide queuing observed throughout this session (independently corroborated by `#1630`'s own record of 822 queued Actions runs at merge time). + +**Fix.** Pinned all 9 occurrences to the explicit `ubuntu-24.04` image, matching the pattern already established by `#1618`/`#1609` exactly (a literal `runs-on:` value swap, no other job semantics touched). New `tests/test_required_review_runner_image_contract.py` asserts no job in any of the three files requests the floating image and pins the expected per-file occurrence count, mirroring `test_required_security_runner_image_contract.py`'s existing structure. + +**Unrelated pre-existing failures fixed in the same pass.** `#1630` (merged shortly before this fix, itself an owner-authorized `QUEUE_SATURATION_CHICKEN_EGG` bypass addressing the same 822-run backlog) moved the organization sweep's rotation cadence from every 15 minutes to hourly to reduce control-plane pressure, changing `pr-review-merge-scheduler.yml`'s `ORG_SWEEP_ROTATION_INDEX` wall-clock fallback divisor from `900` (15 minutes in seconds) to `3600` (1 hour), but left `tests/test_required_workflow_queue_contract.py`'s four rotation-index tests asserting the old `900` divisor and the old literal workflow string. Confirmed these 4 failures reproduce identically on a clean `origin/main` checkout with no changes from this branch, independent of and pre-dating this fix. Updated all four to the new `3600` divisor/string, preserving each test's original intent (wall-clock fallback on total counter unavailability, transient-read-failure-does-not-reset, successful-read-but-failed-patch-falls-back, and the documentation/input-validation contract) unchanged. + +**Validation.** Full suite `2407 passed, 1 skipped, 21 subtests`; `coverage` 100% on `scripts/ci`; `interrogate` 100%; all four touched/added workflow files re-parse as valid YAML; `test_opencode_workflow_shell_syntax.py` and related shell-syntax tests pass unchanged. + +**Residual.** This closes the specific floating-image contribution from these three central workflows; it does not by itself guarantee the organization-wide Actions queue is fully drained, since other repositories' own workflows and any remaining unpinned central workflows may still request the floating image. Worth a follow-up sweep across the rest of `.github/workflows/` and sibling-repo workflows if queuing persists after this lands. diff --git a/tests/test_required_review_runner_image_contract.py b/tests/test_required_review_runner_image_contract.py new file mode 100644 index 0000000000..c173716e3e --- /dev/null +++ b/tests/test_required_review_runner_image_contract.py @@ -0,0 +1,37 @@ +"""Contract tests for central required review workflow runner images.""" + +from __future__ import annotations + +from pathlib import Path +import unittest + + +STRIX = Path(".github/workflows/strix.yml") +OPENCODE_REVIEW = Path(".github/workflows/opencode-review.yml") +NOEMA_REVIEW = Path(".github/workflows/noema-review.yml") + + +class RequiredReviewRunnerImageContract(unittest.TestCase): + """Keep required review jobs off the observed starved floating image.""" + + def test_strix_uses_explicit_supported_image(self) -> None: + """Require every Strix job to use explicit Ubuntu 24.04.""" + workflow = STRIX.read_text(encoding="utf-8") + self.assertNotIn("runs-on: ubuntu-latest", workflow) + self.assertEqual(workflow.count("runs-on: ubuntu-24.04"), 3) + + def test_opencode_review_uses_explicit_supported_image(self) -> None: + """Require every OpenCode Review job to use explicit Ubuntu 24.04.""" + workflow = OPENCODE_REVIEW.read_text(encoding="utf-8") + self.assertNotIn("runs-on: ubuntu-latest", workflow) + self.assertEqual(workflow.count("runs-on: ubuntu-24.04"), 5) + + def test_noema_review_uses_explicit_supported_image(self) -> None: + """Require every Noema Review job to use explicit Ubuntu 24.04.""" + workflow = NOEMA_REVIEW.read_text(encoding="utf-8") + self.assertNotIn("runs-on: ubuntu-latest", workflow) + self.assertEqual(workflow.count("runs-on: ubuntu-24.04"), 2) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_required_workflow_queue_contract.py b/tests/test_required_workflow_queue_contract.py index e18e021fae..8614d02903 100644 --- a/tests/test_required_workflow_queue_contract.py +++ b/tests/test_required_workflow_queue_contract.py @@ -1292,7 +1292,7 @@ def test_org_queue_sweep_rotation_index_falls_back_to_wall_clock(tmp_path: Path) assert result.returncode == 0, result.stderr stdout_lines = result.stdout.strip().splitlines() computed_tick = int(stdout_lines[-1]) # last line: the printed value; earlier: the warning - expected_tick = int(time.time()) // 900 + expected_tick = int(time.time()) // 3600 assert abs(computed_tick - expected_tick) <= 1 # tolerate a tick boundary race assert "could not read/write" in result.stdout # a `::warning::` workflow command @@ -1318,7 +1318,7 @@ def test_org_queue_sweep_rotation_index_transient_read_failure_does_not_reset_co assert result.returncode == 0, result.stderr stdout_lines = result.stdout.strip().splitlines() computed_tick = int(stdout_lines[-1]) - expected_tick = int(time.time()) // 900 + expected_tick = int(time.time()) // 3600 assert abs(computed_tick - expected_tick) <= 1 # Critically: never "1" -- that would mean the failed read was treated # as a fresh-start reset rather than an unreadable existing value. @@ -1341,7 +1341,7 @@ def test_org_queue_sweep_rotation_index_successful_read_but_failed_patch_falls_b assert result.returncode == 0, result.stderr stdout_lines = result.stdout.strip().splitlines() computed_tick = int(stdout_lines[-1]) - expected_tick = int(time.time()) // 900 + expected_tick = int(time.time()) // 3600 assert abs(computed_tick - expected_tick) <= 1 assert "read ORG_SWEEP_ROTATION_COUNTER=41 but could not PATCH it" in result.stdout @@ -1386,7 +1386,7 @@ def test_org_queue_sweep_documents_rotation_leverage_and_validates_input() -> No assert "ContextualWisdomLab/.github#1219" in workflow assert ( - 'ORG_SWEEP_ROTATION_INDEX=$(( $(date -u +%s) / 900 ))' + 'ORG_SWEEP_ROTATION_INDEX=$(( $(date -u +%s) / 3600 ))' ) in workflow assert ( 'if ! [[ "$ORG_SWEEP_ROTATION_INDEX" =~ ^[0-9]+$ ]]; then' From 9a33eccb377b1c2ad3af9a67f11df3eb46453027 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 05:11:54 +0900 Subject: [PATCH 52/62] chore: preserve current label taxonomy --- config/repository-label-taxonomy.json | 102 +++++++++++++++++++++++++- 1 file changed, 101 insertions(+), 1 deletion(-) diff --git a/config/repository-label-taxonomy.json b/config/repository-label-taxonomy.json index a1831221ed..6bfea75e56 100644 --- a/config/repository-label-taxonomy.json +++ b/config/repository-label-taxonomy.json @@ -11,6 +11,21 @@ "issue": 1582, "type": "feature" }, + { + "repository": ".github", + "issue": 1622, + "type": "feature" + }, + { + "repository": ".github", + "issue": 1625, + "type": "bug" + }, + { + "repository": ".github", + "issue": 1634, + "type": "documentation" + }, { "repository": "CalendarWeave", "issue": 1, @@ -100,6 +115,91 @@ "repository": "noema", "issue": 530, "type": "feature" + }, + { + "repository": "bandscope", + "issue": 1125, + "type": "documentation" + }, + { + "repository": "saju-caldav", + "issue": 44, + "type": "documentation" + }, + { + "repository": "OriginWeave", + "issue": 274, + "type": "documentation" + }, + { + "repository": "semantic-data-portal", + "issue": 90, + "type": "documentation" + }, + { + "repository": "accounting-information-platform", + "issue": 45, + "type": "documentation" + }, + { + "repository": "clearfolio", + "issue": 538, + "type": "documentation" + }, + { + "repository": "pg-erd-cloud", + "issue": 1046, + "type": "documentation" + }, + { + "repository": "DiagramWeave", + "issue": 34, + "type": "documentation" + }, + { + "repository": "keyverse", + "issue": 127, + "type": "documentation" + }, + { + "repository": "mhtml-etl-gateway", + "issue": 56, + "type": "documentation" + }, + { + "repository": "j-planner", + "issue": 2, + "type": "documentation" + }, + { + "repository": "learning-record-store", + "issue": 1, + "type": "documentation" + }, + { + "repository": "learning-content-studio", + "issue": 1, + "type": "documentation" + }, + { + "repository": "learning-management-platform", + "issue": 1, + "type": "documentation" + }, + { + "repository": "metering-billing-platform", + "issue": 157, + "type": "documentation" + }, + { + "repository": "PolicyWeave", + "issue": 1, + "type": "feature" + }, + { + "repository": "supply-chain-control-plane", + "issue": 1, + "type": "feature" } ] -} +} \ No newline at end of file From 37f7f777b2013b764f2893d582c54a0c50597d19 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 05:12:26 +0900 Subject: [PATCH 53/62] chore: preserve current repository metadata --- config/repository-metadata.json | 86 ++++++++++++++++++++++++++++++++- 1 file changed, 85 insertions(+), 1 deletion(-) diff --git a/config/repository-metadata.json b/config/repository-metadata.json index fcf8471236..b1cebcde96 100644 --- a/config/repository-metadata.json +++ b/config/repository-metadata.json @@ -49,6 +49,90 @@ "topics": ["psychometrics", "assessment", "measurement", "longitudinal", "research", "privacy", "rust", "contextualwisdomlab"], "deepwiki": true, "pages": true + }, + "keyverse": { + "description": "Keyverse — passwordless identity, federation, provisioning, account unification, and authorization services for ContextualWisdomLab.", + "topics": ["identity", "openid-connect", "oauth2", "scim", "keycloak", "python", "contextualwisdomlab"], + "deepwiki": true, + "pages": true + }, + "OriginWeave": { + "description": "Let agents use the web without losing control. OriginWeave gives AI agents a Chromium-compatible web runtime with isolated sessions, typed actions, resource governance, and verifiable evidence.", + "topics": ["browser-automation", "ai-agents", "chromium", "security", "rust", "web", "contextualwisdomlab"], + "deepwiki": true, + "pages": true + }, + "accounting-information-platform": { + "description": "Accounting Information Platform — statutory accounting, journal posting, period control, reconciliation, and financial reporting authority for ContextualWisdomLab.", + "topics": ["accounting", "ledger", "journal", "reconciliation", "financial-reporting", "postgresql", "python", "contextualwisdomlab"], + "deepwiki": true, + "pages": true + }, + "pg-erd-cloud": { + "description": "PostgreSQL 스키마를 리버스 엔지니어링하고 ERD·DDL 공유 흐름으로 관리하는 클라우드 서비스.", + "topics": ["cloud", "database-schema", "ddl", "erd", "postgresql", "reverse-engineering", "saas", "python", "javascript", "contextualwisdomlab"], + "deepwiki": true, + "pages": true + }, + "clearfolio": { + "description": "Clearfolio — secure document conversion, tenant-scoped viewing, and controlled artifact delivery.", + "topics": ["document-viewer", "document-conversion", "file-preview", "pdf", "java", "spring-boot", "javascript", "web-app", "contextualwisdomlab"], + "deepwiki": true, + "pages": true + }, + "DiagramWeave": { + "description": "DiagramWeave — a source-first, AI-assisted editor and tooling platform for PlantUML diagrams.", + "topics": ["diagram-editor", "plantuml", "developer-tools", "language-server", "javascript", "ai-assisted", "contextualwisdomlab"], + "deepwiki": true, + "pages": true + }, + "semantic-data-portal": { + "description": "Semantic Data Portal — governed discovery, graph traversal, and semantic search for enterprise data catalogs.", + "topics": ["data-catalog", "knowledge-graph", "ontology", "semantic-web", "semantic-search", "data-governance", "postgresql", "python", "contextualwisdomlab"], + "deepwiki": true, + "pages": true + }, + "contextual-orchestrator": { + "description": "Contextual Orchestrator — an OpenAI-compatible control plane for model routing, delegation, verification, and multi-agent orchestration.", + "topics": ["enterprise-admin", "llm-orchestration", "model-orchestration", "model-routing", "ai-agents", "openai-compatible", "research", "python", "contextualwisdomlab"], + "deepwiki": true, + "pages": true + }, + "mhtml-etl-gateway": { + "description": "Enterprise MHTML ingestion gateway that converts browser, SAP ALV, and Excel Web Archive exports into governed PostgreSQL data assets.", + "topics": ["mhtml", "etl", "data-ingestion", "sap", "postgresql", "data-governance", "python", "contextualwisdomlab"], + "deepwiki": true, + "pages": true + }, + "PolicyWeave": { + "description": "PolicyWeave — local-first privacy-policy fact authoring, completeness review, and deterministic draft generation for web and app operators.", + "topics": ["privacy", "privacy-policy", "privacy-engineering", "policy-authoring", "local-first", "react", "typescript", "vite", "contextualwisdomlab"], + "deepwiki": true, + "pages": true + }, + "supply-chain-control-plane": { + "description": "Supply Chain Control Plane — evidence-backed supply-network dependency modeling and deterministic downstream disruption-impact analysis.", + "topics": ["supply-chain", "disruption-management", "dependency-graph", "provenance", "risk-analysis", "rust", "contextualwisdomlab"], + "deepwiki": true, + "pages": true + }, + "learning-management-platform": { + "description": "Learning Management Platform — enrollment, learning-journey, completion, and credential orchestration for employee and external learners.", + "topics": ["learning-management-system", "learning-platform", "enrollment", "completion", "credentialing", "rust", "postgresql", "contextualwisdomlab"], + "deepwiki": true, + "pages": true + }, + "learning-content-studio": { + "description": "Learning Content Studio — evidence-bound LCMS for authoring, approving, releasing, and deterministically publishing reusable learning content.", + "topics": ["lcms", "learning-content", "content-authoring", "content-management", "accessibility", "scorm", "cmi5", "rust", "contextualwisdomlab"], + "deepwiki": true, + "pages": true + }, + "learning-record-store": { + "description": "Authoritative xAPI learning-record persistence for the CWL Learning Platform.", + "topics": ["learning-record-store", "xapi", "cmi5", "learning-technology", "interoperability", "contextualwisdomlab"], + "deepwiki": true, + "pages": true } } -} +} \ No newline at end of file From 8f66c9a4ded2555faeefaee9ea3f1649197be40f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 05:13:04 +0900 Subject: [PATCH 54/62] docs: preserve current public-surface reconciliation --- .../repository-public-surface-reconciliation.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/docs/doctoring/repository-public-surface-reconciliation.md b/docs/doctoring/repository-public-surface-reconciliation.md index 6fa36c5ddc..b3090af6e5 100644 --- a/docs/doctoring/repository-public-surface-reconciliation.md +++ b/docs/doctoring/repository-public-surface-reconciliation.md @@ -42,7 +42,7 @@ The fleet loop is deliberately non-blocking. Every repository or label assignmen - Pull-request validation has `contents: read` only. It cannot mutate repository settings or labels. - Apply runs only when the scheduled workflow is executing from trusted `refs/heads/main` after validation. -- The apply step uses the established maintainer credential rather than widening the ordinary workflow token. +- The apply step uses the established maintainer credential rather than widening the ordinary workflow token. PR #1625 owns the reviewed migration to the dedicated `CWL_REPOSITORY_METADATA_TOKEN`; until that reaches protected main, no documentation may claim the dedicated credential is live. - Repository README changes remain leaf-owned. The central reconciler verifies exact DeepWiki linkage but never fabricates or silently edits customer-facing README copy. - Pages has two reviewed deployment modes. Legacy mode requires a regular `docs/index.md` file on the live default branch. Explicit `pages_mode: workflow` requires a regular `.github/workflows/pages.yml` file **and** an already-configured live Pages site whose `build_type` is `workflow`. - Workflow mode is preserve-only: the reconciler does not create or convert the Pages configuration. Missing Pages, a legacy live configuration, a directory at the required workflow path, or a missing workflow file fails before description/topic/Page writes for that repository. @@ -54,11 +54,15 @@ The fleet loop is deliberately non-blocking. Every repository or label assignmen ## Desired-state fleet in this increment -The repository metadata manifest currently covers eight repositories selected because their public-surface work already has a concrete leaf source or active writer: `CalendarWeave`, `ConceptWeave`, `context-graph-contracts`, `ThreadWeave`, `RankWeave`, `fast-mlsirm`, `EgressWeave`, and `psychometrics-commons`. EgressWeave and Psychometrics Commons joined the fleet after their exact-cased DeepWiki badges and bounded `docs/index.md` Pages sources reached their protected default branches. +The repository metadata manifest covers 22 reviewed repositories whose public-surface work has a concrete leaf source or active writer: `CalendarWeave`, `ConceptWeave`, `context-graph-contracts`, `ThreadWeave`, `RankWeave`, `fast-mlsirm`, `EgressWeave`, `psychometrics-commons`, `keyverse`, `OriginWeave`, `accounting-information-platform`, `pg-erd-cloud`, `clearfolio`, `DiagramWeave`, `semantic-data-portal`, `contextual-orchestrator`, `mhtml-etl-gateway`, `PolicyWeave`, `supply-chain-control-plane`, `learning-management-platform`, `learning-content-studio`, and `learning-record-store`. + +EgressWeave and Psychometrics Commons joined the original fleet after their exact-cased DeepWiki badges and bounded `docs/index.md` Pages sources reached their protected default branches. Later entries are deliberately declared before live convergence only when an owned leaf lane exists for the required badge and Pages source. Until those prerequisites reach each protected default branch, that repository fails closed while sibling repositories remain independently actionable. The `semantic-data-portal` desired description also removes the internal `(PRD/TRD draft implementation)` qualifier rather than propagating it to the customer-facing repository surface. + +The newest cohort has explicit source ownership: `ContextualWisdomLab/PolicyWeave#1` carries its exact-cased badge and `docs/index.md`; `ContextualWisdomLab/supply-chain-control-plane#1` carries its exact badge and bounded Pages landing source on the active product writer; `ContextualWisdomLab/learning-management-platform#1` owns the product-first README badge and `docs/index.md`; `ContextualWisdomLab/learning-content-studio#1` now owns its product-first README, exact badge, Apache-2.0 grant, and the `docs/index.md` content folded from closed child #8; and `ContextualWisdomLab/learning-record-store#1` now owns its product-first README, exact badge, Apache-2.0 grant, and the bounded `docs/index.md` content folded from closed child #7. The closed child PRs retain discussion history but no longer own unique public-surface source. Their live repositories still report Pages disabled until protected integration and trusted reconciliation complete. An Actions-backed repository is not enrolled merely because `pages_mode: workflow` is supported. Enrollment requires an explicit reviewed manifest change after the repository's standard Pages workflow and live `build_type: workflow` configuration both exist. This preserves the deployment architecture of repositories such as ScopeWeave instead of silently rewriting them to legacy `/docs`. -The explicit label assignments now cover 19 evidence-backed targets: `.github#1582`, `CalendarWeave#1`, `ConceptWeave#1`, `context-graph-contracts#20`, `RankWeave#40`, `fast-mlsirm#1717`, `EgressWeave#231`, `psychometrics-commons#442`, `contextual-orchestrator#994`, `contextual-orchestrator#1003`, `appguardrail#1077`, `naruon#1513`, `LineageWeave#908`, `ContextualWisdomLab.github.io#203`, `TEPP#435`, `semantic-data-portal#72`, `Orgmetra#160`, `learning-interoperability-contracts#1`, and `noema#530`. The assignment reconciler preserves richer repository-local labels such as priority, status, and `type: maintenance` when those labels are outside the managed semantic set. +The explicit label assignments now cover 39 active evidence-backed targets: `ContextualWisdomLab/.github#1582`, `ContextualWisdomLab/.github#1622`, `ContextualWisdomLab/.github#1625`, `ContextualWisdomLab/.github#1634`, `ContextualWisdomLab/CalendarWeave#1`, `ContextualWisdomLab/ConceptWeave#1`, `ContextualWisdomLab/context-graph-contracts#20`, `ContextualWisdomLab/RankWeave#40`, `ContextualWisdomLab/fast-mlsirm#1717`, `ContextualWisdomLab/EgressWeave#231`, `ContextualWisdomLab/psychometrics-commons#442`, `ContextualWisdomLab/contextual-orchestrator#994`, `ContextualWisdomLab/contextual-orchestrator#1003`, `ContextualWisdomLab/appguardrail#1077`, `ContextualWisdomLab/naruon#1513`, `ContextualWisdomLab/LineageWeave#908`, `ContextualWisdomLab/ContextualWisdomLab.github.io#203`, `ContextualWisdomLab/TEPP#435`, `ContextualWisdomLab/semantic-data-portal#72`, `ContextualWisdomLab/Orgmetra#160`, `ContextualWisdomLab/learning-interoperability-contracts#1`, `ContextualWisdomLab/noema#530`, `ContextualWisdomLab/bandscope#1125`, `ContextualWisdomLab/saju-caldav#44`, `ContextualWisdomLab/OriginWeave#274`, `ContextualWisdomLab/semantic-data-portal#90`, `ContextualWisdomLab/accounting-information-platform#45`, `ContextualWisdomLab/clearfolio#538`, `ContextualWisdomLab/pg-erd-cloud#1046`, `ContextualWisdomLab/DiagramWeave#34`, `ContextualWisdomLab/keyverse#127`, `ContextualWisdomLab/mhtml-etl-gateway#56`, `ContextualWisdomLab/j-planner#2`, `ContextualWisdomLab/learning-record-store#1`, `ContextualWisdomLab/learning-content-studio#1`, `ContextualWisdomLab/learning-management-platform#1`, `ContextualWisdomLab/metering-billing-platform#157`, `ContextualWisdomLab/PolicyWeave#1`, and `ContextualWisdomLab/supply-chain-control-plane#1`. Closed superseded child PRs `learning-record-store#7`, `learning-content-studio#8`, and `metering-billing-platform#175` are deliberately absent from the active reconciliation target list because their unique documentation deltas were folded into their authoritative parent writers. Historical labels on those closed PRs are not erased by this desired-state change. The assignment reconciler preserves richer repository-local labels such as priority, status, and `type: maintenance` when those labels are outside the managed semantic set. ## Verification contract @@ -72,7 +76,7 @@ A central source commit is not completion. After protected integration and apply 6. the Pages status is `built`, its URL remains under `https://contextualwisdomlab.github.io`, and the published endpoint returns non-empty content before publication is claimed; 7. reviewed issue/PR targets carry the desired managed label while unrelated labels remain intact. -GitHub's current REST Pages contract supports `build_type` values `legacy` and `workflow`, and branch sources with `/` or `/docs`. Legacy desired-state records continue to use `/docs`. The explicit workflow mode exists to preserve a repository whose deployment is already owned by a reviewed GitHub Actions workflow; it is not a central creation/conversion mechanism. +GitHub's current REST Pages contract supports `build_type` values `legacy` and `workflow`, and branch sources with `/` or `/docs`. Current fleet entries use the legacy `/docs` contract unless an entry explicitly declares `pages_mode: workflow`. The workflow mode exists to preserve a repository whose deployment is already owned by a reviewed GitHub Actions workflow; it is not a central creation/conversion mechanism. ## Workflow-mode operating procedure @@ -86,4 +90,4 @@ GitHub's current REST Pages contract supports `build_type` values `legacy` and ` ## Known integration boundary -Until the central PR is merged through normal governance or a verified queue-saturation chicken-and-egg exception, the workflow-mode preservation contract cannot run from trusted `.github/main`; leaf PRs whose badge or Pages source is still branch-only also remain repository-local precondition blockers. These are integration states, not reasons to stop independent repository work. The same run should continue classifying labels, preparing other leaf public surfaces, and re-checking earlier lanes when exact-head evidence becomes available. +The 22-repository desired-state extension is now protected on `.github/main@611feef038ad52d7ee1214d03ea3527289ebf711`; this label-taxonomy lane is the remaining central source delta in the metadata stack. Live settings convergence still requires repository-local badge/Pages prerequisites, trusted protected-main apply, and postcondition re-reads. The dedicated settings-credential migration remains owned by #1625 and is not claimed live before that PR lands. These integration states are not reasons to idle the fleet: blocked leaves fail closed while eligible siblings, labels, and independent public-surface work continue. \ No newline at end of file From c69b254373e53a2340432fe7b9e9d33b6c427519 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 05:13:26 +0900 Subject: [PATCH 55/62] test: preserve current label taxonomy contract --- tests/test_repository_label_taxonomy.py | 54 ++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/tests/test_repository_label_taxonomy.py b/tests/test_repository_label_taxonomy.py index 0a9161c803..a149ec7fc6 100644 --- a/tests/test_repository_label_taxonomy.py +++ b/tests/test_repository_label_taxonomy.py @@ -24,6 +24,9 @@ def test_repository_label_taxonomy_maps_evidence_backed_types() -> None: # Keep assignments exact so reviewed target drift cannot silently escape CI. assert payload["assignments"] == [ {"repository": ".github", "issue": 1582, "type": "feature"}, + {"repository": ".github", "issue": 1622, "type": "feature"}, + {"repository": ".github", "issue": 1625, "type": "bug"}, + {"repository": ".github", "issue": 1634, "type": "documentation"}, {"repository": "CalendarWeave", "issue": 1, "type": "documentation"}, {"repository": "ConceptWeave", "issue": 1, "type": "feature"}, { @@ -70,5 +73,54 @@ def test_repository_label_taxonomy_maps_evidence_backed_types() -> None: "type": "feature", }, {"repository": "noema", "issue": 530, "type": "feature"}, + {"repository": "bandscope", "issue": 1125, "type": "documentation"}, + {"repository": "saju-caldav", "issue": 44, "type": "documentation"}, + {"repository": "OriginWeave", "issue": 274, "type": "documentation"}, + { + "repository": "semantic-data-portal", + "issue": 90, + "type": "documentation", + }, + { + "repository": "accounting-information-platform", + "issue": 45, + "type": "documentation", + }, + {"repository": "clearfolio", "issue": 538, "type": "documentation"}, + {"repository": "pg-erd-cloud", "issue": 1046, "type": "documentation"}, + {"repository": "DiagramWeave", "issue": 34, "type": "documentation"}, + {"repository": "keyverse", "issue": 127, "type": "documentation"}, + { + "repository": "mhtml-etl-gateway", + "issue": 56, + "type": "documentation", + }, + {"repository": "j-planner", "issue": 2, "type": "documentation"}, + { + "repository": "learning-record-store", + "issue": 1, + "type": "documentation", + }, + { + "repository": "learning-content-studio", + "issue": 1, + "type": "documentation", + }, + { + "repository": "learning-management-platform", + "issue": 1, + "type": "documentation", + }, + { + "repository": "metering-billing-platform", + "issue": 157, + "type": "documentation", + }, + {"repository": "PolicyWeave", "issue": 1, "type": "feature"}, + { + "repository": "supply-chain-control-plane", + "issue": 1, + "type": "feature", + }, ] - assert len(set(payload["type"].values())) == len(payload["type"]) + assert len(set(payload["type"].values())) == len(payload["type"]) \ No newline at end of file From a001ec25af88a54f895f366951c1efee46dd5b6b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 05:15:21 +0900 Subject: [PATCH 56/62] test: preserve current repository metadata contract --- tests/test_repository_metadata_reconciliation.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/tests/test_repository_metadata_reconciliation.py b/tests/test_repository_metadata_reconciliation.py index 2122c5d070..2bfc9d1386 100644 --- a/tests/test_repository_metadata_reconciliation.py +++ b/tests/test_repository_metadata_reconciliation.py @@ -71,6 +71,20 @@ def test_metadata_manifest_declares_exact_casing_and_public_surfaces() -> None: "fast-mlsirm": ("psychometrics", "rust"), "EgressWeave": ("ssrf", "python"), "psychometrics-commons": ("psychometrics", "rust"), + "keyverse": ("identity", "openid-connect"), + "OriginWeave": ("browser-automation", "ai-agents"), + "accounting-information-platform": ("accounting", "ledger"), + "pg-erd-cloud": ("erd", "postgresql"), + "clearfolio": ("document-viewer", "document-conversion"), + "DiagramWeave": ("diagram-editor", "plantuml"), + "semantic-data-portal": ("data-catalog", "semantic-search"), + "contextual-orchestrator": ("llm-orchestration", "model-routing"), + "mhtml-etl-gateway": ("mhtml", "etl"), + "PolicyWeave": ("privacy-policy", "typescript"), + "supply-chain-control-plane": ("supply-chain", "rust"), + "learning-management-platform": ("learning-management-system", "rust"), + "learning-content-studio": ("lcms", "content-authoring"), + "learning-record-store": ("learning-record-store", "xapi"), } assert set(repositories) == set(expected) for repository, required_topics in expected.items(): @@ -227,7 +241,7 @@ def test_pages_and_docs_probes(monkeypatch) -> None: responses = iter( [ - completed(out='{"type": "file"}'), + completed(out='{"type":"file"}'), completed(code=1, out="Not Found"), completed(code=1, err="boom"), ] From 48d977235ab3f4f4dc3a7c3e20564d696799c1e8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 05:15:59 +0900 Subject: [PATCH 57/62] chore: restore exact protected-main metadata blobs --- config/repository-label-taxonomy.json | 2 +- config/repository-metadata.json | 2 +- docs/doctoring/repository-public-surface-reconciliation.md | 2 +- tests/test_repository_label_taxonomy.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/config/repository-label-taxonomy.json b/config/repository-label-taxonomy.json index 6bfea75e56..0dd7ac6ee4 100644 --- a/config/repository-label-taxonomy.json +++ b/config/repository-label-taxonomy.json @@ -202,4 +202,4 @@ "type": "feature" } ] -} \ No newline at end of file +} diff --git a/config/repository-metadata.json b/config/repository-metadata.json index b1cebcde96..bb95527ee7 100644 --- a/config/repository-metadata.json +++ b/config/repository-metadata.json @@ -135,4 +135,4 @@ "pages": true } } -} \ No newline at end of file +} diff --git a/docs/doctoring/repository-public-surface-reconciliation.md b/docs/doctoring/repository-public-surface-reconciliation.md index b3090af6e5..0f5fc0e370 100644 --- a/docs/doctoring/repository-public-surface-reconciliation.md +++ b/docs/doctoring/repository-public-surface-reconciliation.md @@ -90,4 +90,4 @@ GitHub's current REST Pages contract supports `build_type` values `legacy` and ` ## Known integration boundary -The 22-repository desired-state extension is now protected on `.github/main@611feef038ad52d7ee1214d03ea3527289ebf711`; this label-taxonomy lane is the remaining central source delta in the metadata stack. Live settings convergence still requires repository-local badge/Pages prerequisites, trusted protected-main apply, and postcondition re-reads. The dedicated settings-credential migration remains owned by #1625 and is not claimed live before that PR lands. These integration states are not reasons to idle the fleet: blocked leaves fail closed while eligible siblings, labels, and independent public-surface work continue. \ No newline at end of file +The 22-repository desired-state extension is now protected on `.github/main@611feef038ad52d7ee1214d03ea3527289ebf711`; this label-taxonomy lane is the remaining central source delta in the metadata stack. Live settings convergence still requires repository-local badge/Pages prerequisites, trusted protected-main apply, and postcondition re-reads. The dedicated settings-credential migration remains owned by #1625 and is not claimed live before that PR lands. These integration states are not reasons to idle the fleet: blocked leaves fail closed while eligible siblings, labels, and independent public-surface work continue. diff --git a/tests/test_repository_label_taxonomy.py b/tests/test_repository_label_taxonomy.py index a149ec7fc6..67762347ef 100644 --- a/tests/test_repository_label_taxonomy.py +++ b/tests/test_repository_label_taxonomy.py @@ -123,4 +123,4 @@ def test_repository_label_taxonomy_maps_evidence_backed_types() -> None: "type": "feature", }, ] - assert len(set(payload["type"].values())) == len(payload["type"]) \ No newline at end of file + assert len(set(payload["type"].values())) == len(payload["type"]) From 8339b9b1e1c3bdfeb07e436ae353c4be35cf5727 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 05:17:23 +0900 Subject: [PATCH 58/62] test: reject code-owner review in solo-maintainer rulesets --- tests/test_solo_maintainer_ruleset_policy.py | 21 ++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/test_solo_maintainer_ruleset_policy.py b/tests/test_solo_maintainer_ruleset_policy.py index f05f22498f..d11a7675a0 100644 --- a/tests/test_solo_maintainer_ruleset_policy.py +++ b/tests/test_solo_maintainer_ruleset_policy.py @@ -71,6 +71,7 @@ def _repository_ruleset_payload() -> dict: "parameters": { "required_approving_review_count": 0, "dismiss_stale_reviews_on_push": True, + "require_code_owner_review": False, "require_last_push_approval": False, "required_review_thread_resolution": True, "required_reviewers": [], @@ -124,6 +125,26 @@ def test_repository_ruleset_rejects_synthetic_required_reviewer() -> None: ] +def test_central_ruleset_rejects_code_owner_review_deadlock() -> None: + """Code-owner approval cannot be mandatory when the only owner authors the change.""" + payload = _central_ruleset_payload() + _review_parameters(payload)["require_code_owner_review"] = True + + assert audit.audit_ruleset(payload) == [ + "central solo-maintainer ruleset must not require code-owner review" + ] + + +def test_repository_ruleset_rejects_code_owner_review_deadlock() -> None: + """The control plane cannot reintroduce independence through CODEOWNERS.""" + payload = _repository_ruleset_payload() + _review_parameters(payload)["require_code_owner_review"] = True + + assert audit.audit_repository_ruleset(payload) == [ + "repository solo-maintainer ruleset must not require code-owner review" + ] + + def test_central_ruleset_rejects_malformed_allowed_merge_methods() -> None: """Malformed API payloads must report drift rather than abort central auditing.""" expected = ["only merge and squash may be allowed merge methods"] From 2887bb66c1c30d6e8c5fa8e5b4aab8e879557f0d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 05:18:58 +0900 Subject: [PATCH 59/62] fix: reject code-owner approval deadlocks --- scripts/ci/audit_central_required_workflows.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/scripts/ci/audit_central_required_workflows.py b/scripts/ci/audit_central_required_workflows.py index 8d42ede32c..393a23a3d6 100755 --- a/scripts/ci/audit_central_required_workflows.py +++ b/scripts/ci/audit_central_required_workflows.py @@ -230,6 +230,10 @@ def audit_ruleset(payload: dict[str, Any]) -> list[str]: errors.append( "central solo-maintainer ruleset must not configure required reviewers" ) + 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 False: @@ -360,6 +364,10 @@ def audit_repository_ruleset(payload: dict[str, Any]) -> list[str]: 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" + ) 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: @@ -464,4 +472,4 @@ def main(argv: list[str] | None = None) -> int: if __name__ == "__main__": # pragma: no cover - exercised through main() - raise SystemExit(main()) \ No newline at end of file + raise SystemExit(main()) From 9457e66b9bbe29e8166f58bc6bc8ac57eff94005 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 05:44:51 +0900 Subject: [PATCH 60/62] fix(governance): preserve current-main Pingora evidence Restore the six protected-main #1466 paths byte-for-byte after ancestry integration exposed that the writer tree still carried predecessor content. This keeps the governance delta limited to its intended seven paths without force-push or history rewrite. --- .../0019-cloudflare-pingora-edge-standard.md | 6 + .../pingora-documentation-image-evidence.md | 17 ++ docs/policies/PINGORA_EDGE_POLICY.md | 11 +- docs/product-technical-gap-baseline.md | 1 + scripts/ci/pingora_edge_policy.py | 197 ++++++++++++++++-- tests/test_pingora_edge_policy.py | 188 ++++++++++++++++- 6 files changed, 394 insertions(+), 26 deletions(-) create mode 100644 docs/doctoring/pingora-documentation-image-evidence.md diff --git a/docs/adr/0019-cloudflare-pingora-edge-standard.md b/docs/adr/0019-cloudflare-pingora-edge-standard.md index 805e538b86..9f92f0f046 100644 --- a/docs/adr/0019-cloudflare-pingora-edge-standard.md +++ b/docs/adr/0019-cloudflare-pingora-edge-standard.md @@ -33,6 +33,12 @@ so a governed shared implementation is required. 6. Initial migration does not use Pingora's experimental cache integration. 7. PHP workloads move to an HTTP application server or reviewed FastCGI adapter behind Pingora before the public listener changes. +8. Documentation PNG screenshots and PDF papers without a text diff are verified + from bounded format evidence (a complete CRC-valid PNG chunk stream with + conforming chunk names, palette bounds, and palette indices whose bounded null- or + Adam7-interlaced decompressed scanlines match IHDR, or a PDF signature) and excluded + from runtime-content scanning; + runtime paths and malformed or unsupported binary evidence still fail closed. ## Consequences diff --git a/docs/doctoring/pingora-documentation-image-evidence.md b/docs/doctoring/pingora-documentation-image-evidence.md new file mode 100644 index 0000000000..af10942cd8 --- /dev/null +++ b/docs/doctoring/pingora-documentation-image-evidence.md @@ -0,0 +1,17 @@ +# Pingora documentation image evidence + +The required Pingora gate previously sent a changed PNG screenshot through its +UTF-8 runtime-content decoder because GitHub omits text patches for binary files. +That rejected UI evidence before the policy could determine whether it described +an active edge runtime. + +ADR-0019 now admits documentation PNG screenshots only when the bounded final +file is a complete CRC-valid PNG chunk stream ending at IEND with no trailing +payload, conforming chunk names, palette bounds and indices, and bounded null- or +Adam7-interlaced decompressed scanlines that match IHDR. A signature or +CRC-valid arbitrary IDAT is insufficient. Files in a runtime path, malformed signatures, +unsupported binary formats, and unavailable evidence continue to fail closed. +The gate establishes bounded binary evidence rather than general image-rendering +fidelity; optional ancillary-chunk semantics are outside this policy boundary. +`tests/test_pingora_edge_policy.py` covers the accepted PNG and the existing fake +PDF/runtime cases; targeted branch coverage remains 100%. diff --git a/docs/policies/PINGORA_EDGE_POLICY.md b/docs/policies/PINGORA_EDGE_POLICY.md index 4d4c0752e1..619374a13d 100644 --- a/docs/policies/PINGORA_EDGE_POLICY.md +++ b/docs/policies/PINGORA_EDGE_POLICY.md @@ -53,8 +53,15 @@ The organization-required `required-workflow-bootstrap` job runs trusted base-branch scanner code at the immutable required-workflow SHA. It reads bounded changed-file metadata and final UTF-8 content through GitHub's REST API. It does not check out or execute pull-request content and receives only read permissions. -Malformed, truncated, binary, symlink, oversized, or unavailable evidence fails -closed. +Malformed, truncated, symlinked, oversized, or unavailable runtime evidence fails +closed. Documentation PNG screenshots and PDF papers without a text diff are +excluded only after bounded format verification; PNG evidence must be a complete +CRC-valid chunk stream ending at IEND with conforming chunk names, palette +bounds, and palette indices whose bounded null- or Adam7-interlaced decompressed +scanlines match IHDR. +This is a bounded binary-evidence classifier, not a general image renderer; +visual fidelity and optional ancillary-chunk semantics are outside this gate. +Other binary files remain unavailable evidence and fail closed. ## Exception process diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index 41d95b6f57..2a8f4c7b54 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -93,6 +93,7 @@ flowchart LR | G-13 | hourly scheduler는 존재하지만 no-op/credential unavailable/queued Checks의 customer next action을 모든 caller가 동일한 receipt로 내는지 미확인이다 | 자동화가 실패해도 운영자가 무엇을 고쳐야 하는지 알 수 없다 | `skipped_credential_unavailable` receipt와 다음 행동 문구를 exact-head Checks로 검증하고, bounded receipt schema, retry floor, single-flight, no secret fallback을 모든 caller contract test로 고정한다 | | G-14 | release/changelog/version 증거가 각 PR에 분산되고 현재 central repo 보호 main의 release candidate가 명확하지 않다 | 운영자는 어떤 기능이 supportable release인지 확인할 수 없다 | merge 후 release readiness ledger, CHANGELOG, semantic version/tag, rollback/operability evidence를 함께 갱신한다 | | G-15 | 첨부파일 처리 경계가 제품별로 다르고, 1MB 상한은 업무 데이터와 맞지 않으며 미지원 MIME/컨테이너가 parser registry에서 명시적으로 pending/quarantine 되는지 확인되지 않았다. 현재 20MB 초과 파일 가능성과 PDF/HWP/HWPX·이미지·압축파일의 parse/sidecar 흐름을 하나의 exact contract로 묶지 못했다 | 큰 업무 첨부를 거부하거나 파싱 실패를 조용히 잃으면 고객의 메일·문서 업무가 중단된다 | naruon/newsdom-api 소유 PR에서 streaming upload, configurable bounded limit above 20MB, MIME sniffing, parser capability registry, quarantine/retry, source-position provenance, and ADR를 추가하고 size/unsupported-type/zip-bomb tests를 required evidence로 만든다 | +| G-16 | Required Pingora policy treated a changed documentation PNG screenshot as UTF-8 runtime evidence | Valid UI evidence blocked otherwise valid product PRs before policy evaluation | This branch verifies bounded PNG magic before exemption while runtime paths and malformed assets continue to fail closed; protected-main delivery remains the release gate | ## 4. 열린 PR live inventory diff --git a/scripts/ci/pingora_edge_policy.py b/scripts/ci/pingora_edge_policy.py index 823e17fbe5..33e58ed876 100644 --- a/scripts/ci/pingora_edge_policy.py +++ b/scripts/ci/pingora_edge_policy.py @@ -15,6 +15,7 @@ import os import re import sys +import zlib from dataclasses import dataclass from pathlib import PurePosixPath from typing import Callable, Mapping, Sequence @@ -38,7 +39,11 @@ # 1 MiB base64 ceiling -- rejecting a legitimate research-paper citation # (this org's own "attach the relevant paper PDF" convention) for a reason # that has nothing to do with the Nginx runtime policy this module enforces. -BINARY_DOCUMENT_SUFFIXES = frozenset({".pdf"}) +BINARY_DOCUMENT_MAGIC = { + ".pdf": (b"%PDF-",), + ".png": (b"\x89PNG\r\n\x1a\n",), +} +PNG_SIGNATURE = BINARY_DOCUMENT_MAGIC[".png"][0] SOURCE_TEST_SUFFIXES = frozenset({".py", ".pyi", ".js", ".mjs", ".cjs", ".ts", ".tsx", ".rs"}) LICENSE_NAMES = frozenset({"license", "license.md", "copying", "copyrights", "notice"}) DOCUMENTATION_DIRECTORIES = frozenset({"doc", "docs", "documentation"}) @@ -171,7 +176,7 @@ def _is_documentation_or_source_fixture(path: str) -> bool: """Return whether *path* is prose, license text, or scanner source fixture. Textual suffixes only: a ``.pdf`` is handled separately by - ``_is_binary_documentation_pdf`` and gated on GitHub reporting no diff + ``_is_binary_documentation_asset`` and gated on GitHub reporting no diff ``patch`` for it, so a textual file merely named with a ``.pdf`` suffix (one GitHub *can* diff, meaning it could carry inspectable content) is never exempted here. @@ -206,15 +211,15 @@ def _is_documentation_or_source_fixture(path: str) -> bool: return False -def _is_binary_documentation_pdf(changed: ChangedFile) -> bool: - """Return whether *changed* is a plausibly binary documentation PDF. +def _is_binary_documentation_asset(changed: ChangedFile) -> bool: + """Return whether *changed* is a plausibly binary documentation asset. This is only the cheap, patch-presence pre-filter: GitHub's changed-files API never returns a diff ``patch`` for a true binary file, so a missing ``patch`` is *necessary* but not *sufficient* evidence -- GitHub also omits one for a textual diff that merely exceeds its own rendering limit. A caller with network access (``evaluate_pull_request``) must - still confirm this with ``_pdf_evidence_confirms_binary`` before + still confirm this with ``_binary_documentation_evidence_confirms`` before trusting it; a caller without one (this module's own unit tests calling this function directly) is only checking the necessary condition. """ @@ -223,8 +228,9 @@ def _is_binary_documentation_pdf(changed: ChangedFile) -> bool: return False pure = PurePosixPath(changed.path) return ( - pure.suffix.lower() in BINARY_DOCUMENT_SUFFIXES + pure.suffix.lower() in BINARY_DOCUMENT_MAGIC and _is_known_documentation_path(pure) + and _runtime_path_rule(changed.path) is None ) @@ -414,10 +420,7 @@ def _load_file_content(api_url: str, repository: str, path: str, head_sha: str, raise PolicyError(f"Runtime policy candidate {path} is not valid UTF-8") from exc -_PDF_MAGIC_PREFIX = b"%PDF-" - - -def _pdf_evidence_confirms_binary( +def _binary_documentation_evidence_confirms( changed: ChangedFile, *, api_url: str, @@ -426,17 +429,18 @@ def _pdf_evidence_confirms_binary( token: str, opener: OpenJson, ) -> bool: - """Return whether a claimed binary documentation PDF is genuinely binary. + """Return whether a claimed binary documentation asset is genuine. A missing diff ``patch`` alone is not proof of binary content: GitHub also omits a patch for a textual diff that exceeds its own rendering limit, well under this module's ``MAX_FILE_BYTES`` content-fetch ceiling. Whenever the file's raw bytes can be fetched at all, this - verifies the real ``%PDF-`` magic prefix instead of trusting + verifies the declared format's magic prefix instead of trusting patch-presence alone. Only a file whose content evidently exceeds the - Contents API's size ceiling -- the exact case ``_is_binary_documentation_pdf`` + Contents API's size ceiling -- the exact case ``_is_binary_documentation_asset`` exists for, a cited, large research paper -- falls back to trusting the - path+suffix convention; every other content-evidence failure (a + path+suffix convention for oversized PDFs only; every other + content-evidence failure (a malformed API response, corrupt base64, a declared size that does not match the decoded bytes) propagates and fails the whole check closed, same as for any other file that needs scanning. @@ -445,22 +449,169 @@ def _pdf_evidence_confirms_binary( try: raw = _load_raw_file_bytes(api_url, repository, changed.path, head_sha, token, opener) except ContentSizeExceededError: - return True - return raw.startswith(_PDF_MAGIC_PREFIX) + return PurePosixPath(changed.path).suffix.lower() == ".pdf" + suffix = PurePosixPath(changed.path).suffix.lower() + if suffix == ".png": + return _is_complete_png(raw) + return raw.startswith(BINARY_DOCUMENT_MAGIC[suffix]) + + +def _png_unfilter_row(filtered: bytes, previous: bytes, filter_type: int, bytes_per_pixel: int) -> bytes: + """Reconstruct one PNG scanline for bounded indexed-pixel validation.""" + + reconstructed = bytearray(len(filtered)) + for index, value in enumerate(filtered): + left = reconstructed[index - bytes_per_pixel] if index >= bytes_per_pixel else 0 + above = previous[index] if previous else 0 + upper_left = previous[index - bytes_per_pixel] if previous and index >= bytes_per_pixel else 0 + if filter_type == 0: + predictor = 0 + elif filter_type == 1: + predictor = left + elif filter_type == 2: + predictor = above + elif filter_type == 3: + predictor = (left + above) // 2 + else: + estimate = left + above - upper_left + distances = (abs(estimate - left), abs(estimate - above), abs(estimate - upper_left)) + predictor = (left, above, upper_left)[distances.index(min(distances))] + reconstructed[index] = (value + predictor) & 0xFF + return bytes(reconstructed) + + +def _is_complete_png(raw: bytes) -> bool: + """Validate one bounded PNG including its null- or Adam7-interlaced stream.""" + + if not raw.startswith(PNG_SIGNATURE): + return False + offset = len(PNG_SIGNATURE) + header: tuple[int, int, int, int, int] | None = None + palette_entries = 0 + image_data: list[bytes] = [] + image_data_closed = False + while offset + 12 <= len(raw): + length = int.from_bytes(raw[offset : offset + 4], "big") + chunk_end = offset + 12 + length + if chunk_end > len(raw): + return False + chunk_type = raw[offset + 4 : offset + 8] + chunk_data = raw[offset + 8 : offset + 8 + length] + expected_crc = int.from_bytes(raw[offset + 8 + length : chunk_end], "big") + if ( + any(not (65 <= byte <= 90 or 97 <= byte <= 122) for byte in chunk_type) + or chunk_type[2] & 0x20 + or zlib.crc32(chunk_type + chunk_data) != expected_crc + ): + return False + if header is None: + if chunk_type != b"IHDR" or length != 13 or offset != len(PNG_SIGNATURE): + return False + width = int.from_bytes(chunk_data[0:4], "big") + height = int.from_bytes(chunk_data[4:8], "big") + bit_depth, color_type, compression, filtering, interlace = chunk_data[8:13] + allowed_depths = { + 0: {1, 2, 4, 8, 16}, 2: {8, 16}, 3: {1, 2, 4, 8}, + 4: {8, 16}, 6: {8, 16}, + } + if ( + width == 0 or height == 0 + or bit_depth not in allowed_depths.get(color_type, set()) + or compression != 0 or filtering != 0 or interlace not in {0, 1} + ): + return False + header = (width, height, bit_depth, color_type, interlace) + elif chunk_type == b"IHDR": + return False + elif chunk_type == b"PLTE": + if palette_entries or image_data or length == 0 or length > 768 or length % 3: + return False + _width, _height, bit_depth, color_type, _interlace = header + if color_type == 3 and length // 3 > 1 << bit_depth: + return False + palette_entries = length // 3 + elif chunk_type == b"IDAT": + if image_data_closed: + return False + image_data.append(chunk_data) + elif chunk_type == b"IEND": + if length != 0 or not image_data or chunk_end != len(raw): + return False + width, height, bit_depth, color_type, interlace = header + if (color_type == 3 and not palette_entries) or ( + color_type in {0, 4} and palette_entries + ): + return False + channels = {0: 1, 2: 3, 3: 1, 4: 2, 6: 4}[color_type] + passes = ( + ((0, 0, 8, 8), (4, 0, 8, 8), (0, 4, 4, 8), (2, 0, 4, 4), + (0, 2, 2, 4), (1, 0, 2, 2), (0, 1, 1, 2)) + if interlace else ((0, 0, 1, 1),) + ) + scanlines: list[tuple[int, int, int]] = [] + expected_size = 0 + for x_start, y_start, x_step, y_step in passes: + if width <= x_start or height <= y_start: + continue + pass_width = (width - x_start + x_step - 1) // x_step + pass_height = (height - y_start + y_step - 1) // y_step + row_bytes = (pass_width * channels * bit_depth + 7) // 8 + expected_size += pass_height * (row_bytes + 1) + if expected_size > MAX_RESPONSE_BYTES: + return False + scanlines.append((pass_height, row_bytes, pass_width)) + decoder = zlib.decompressobj() + try: + decoded = decoder.decompress(b"".join(image_data), expected_size + 1) + except zlib.error: + return False + if ( + len(decoded) != expected_size or not decoder.eof + or decoder.unused_data or decoder.unconsumed_tail + ): + return False + decoded_offset = 0 + for row_count, row_bytes, pass_width in scanlines: + previous = b"" + for _ in range(row_count): + filter_type = decoded[decoded_offset] + if filter_type > 4: + return False + filtered = decoded[decoded_offset + 1 : decoded_offset + row_bytes + 1] + if color_type == 3: + reconstructed = _png_unfilter_row(filtered, previous, filter_type, 1) + mask = (1 << bit_depth) - 1 + for pixel in range(pass_width): + bit_offset = pixel * bit_depth + palette_index = ( + reconstructed[bit_offset // 8] + >> (8 - bit_depth - bit_offset % 8) + ) & mask + if palette_index >= palette_entries: + return False + previous = reconstructed + decoded_offset += row_bytes + 1 + return decoded_offset == len(decoded) + elif chunk_type[0] & 0x20 == 0: + return False + elif image_data: + image_data_closed = True + offset = chunk_end + return False def _needs_content_scan(changed: ChangedFile) -> bool: """Return whether a changed final file can carry an active edge runtime. - A claimed binary documentation PDF (``_is_binary_documentation_pdf``) + A claimed binary documentation asset (``_is_binary_documentation_asset``) exempts here on the cheap, offline pre-filter alone; ``evaluate_pull_request`` - never actually relies on that -- it runs ``_pdf_evidence_confirms_binary`` + never actually relies on that -- it runs ``_binary_documentation_evidence_confirms`` for that case before this function is even consulted. """ if changed.status == "removed" or _is_documentation_or_source_fixture(changed.path): return False - if _is_binary_documentation_pdf(changed): + if _is_binary_documentation_asset(changed): return False if not changed.patch_available: return True @@ -499,17 +650,17 @@ def evaluate_pull_request( changed_files = _load_changed_files(api_url.rstrip("/"), repository, pull_request, token, opener) violations: list[Violation] = [] for changed in changed_files: - # A claimed binary documentation PDF gets its own network-verified + # A claimed binary documentation asset gets its own network-verified # check ahead of _needs_content_scan's patch-presence-only signal: # a missing patch does not by itself prove binary content (GitHub # also omits one for an oversized textual diff), so this confirms - # the real %PDF- magic prefix whenever the bytes can be fetched at + # the format's magic prefix whenever the bytes can be fetched at # all, falling back to the path+suffix convention only when the # content genuinely exceeds the Contents API's size ceiling. A # removed file has no head content to fetch at all -- _needs_content_scan # already special-cases this the same way for every other file. - if changed.status != "removed" and _is_binary_documentation_pdf(changed): - if _pdf_evidence_confirms_binary( + if changed.status != "removed" and _is_binary_documentation_asset(changed): + if _binary_documentation_evidence_confirms( changed, api_url=api_url.rstrip("/"), repository=repository, diff --git a/tests/test_pingora_edge_policy.py b/tests/test_pingora_edge_policy.py index 96692b4430..c5d4e9d7a3 100644 --- a/tests/test_pingora_edge_policy.py +++ b/tests/test_pingora_edge_policy.py @@ -7,6 +7,7 @@ import inspect import re import sys +import zlib from io import BytesIO from pathlib import Path from urllib.error import HTTPError, URLError @@ -383,10 +384,195 @@ def opener(url: str, _token: str) -> object: assert result == () +def test_evaluate_pull_request_exempts_a_real_documentation_png() -> None: + """A screenshot is verified by PNG magic instead of decoded as UTF-8.""" + + def opener(url: str, _token: str) -> object: + if "/pulls/15/files" in url: + return [{"filename": "docs/screenshots/dashboard.png", "status": "added"}] + assert "/contents/docs/screenshots/dashboard.png" in url + raw = base64.b64decode( + "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=" + ) + return { + "type": "file", + "encoding": "base64", + "size": len(raw), + "content": base64.b64encode(raw).decode("ascii"), + } + + assert policy.evaluate_pull_request( + api_url="https://api.github.test", + repository="ContextualWisdomLab/example", + pull_request=15, + head_sha="a" * 40, + event_action="opened", + token="token", + opener=opener, + ) == () + + +def test_evaluate_pull_request_rejects_a_fake_documentation_png() -> None: + """A PNG suffix without PNG magic remains runtime-content evidence.""" + + def opener(url: str, _token: str) -> object: + if "/pulls/16/files" in url: + return [{"filename": "docs/screenshots/fake.png", "status": "added"}] + return encoded_file("cat /etc/nginx/nginx.conf\n") + + result = policy.evaluate_pull_request( + api_url="https://api.github.test", + repository="ContextualWisdomLab/example", + pull_request=16, + head_sha="b" * 40, + event_action="opened", + token="token", + opener=opener, + ) + assert [item.rule for item in result] == ["nginx_runtime_path"] + + +def test_evaluate_pull_request_rejects_png_with_appended_runtime_text() -> None: + """A valid image prefix cannot hide bytes appended after the IEND chunk.""" + + image = base64.b64decode( + "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=" + ) + + def opener(url: str, _token: str) -> object: + if "/pulls/17/files" in url: + return [{"filename": "docs/screenshots/forged.png", "status": "added"}] + raw = image + b"\ncat /etc/nginx/nginx.conf\n" + return { + "type": "file", "encoding": "base64", "size": len(raw), + "content": base64.b64encode(raw).decode("ascii"), + } + + with pytest.raises(policy.PolicyError, match="not valid UTF-8"): + policy.evaluate_pull_request( + api_url="https://api.github.test", + repository="ContextualWisdomLab/example", + pull_request=17, + head_sha="c" * 40, + event_action="opened", + token="token", + opener=opener, + ) + + +def test_png_structure_validation_fails_closed_on_malformed_chunks() -> None: + """Every malformed PNG boundary returns false without parsing past bounds.""" + + def chunk(kind: bytes, data: bytes) -> bytes: + payload = kind + data + return len(data).to_bytes(4, "big") + payload + zlib.crc32(payload).to_bytes(4, "big") + + signature = policy.PNG_SIGNATURE + header = chunk(b"IHDR", b"\0" * 13) + assert not policy._is_complete_png(b"not-png") + assert not policy._is_complete_png(signature) + assert not policy._is_complete_png( + signature + (99).to_bytes(4, "big") + b"IHDR" + b"\0" * 4 + ) + assert not policy._is_complete_png(signature + header[:-1] + b"\0") + assert not policy._is_complete_png(signature + chunk(b"TEXT", b"")) + assert not policy._is_complete_png(signature + header + chunk(b"IEND", b"")) + assert not policy._is_complete_png(signature + header + chunk(b"TEXT", b"")) + + +def test_png_semantic_validation_fails_closed() -> None: + """CRC-valid chunks still need a valid bounded PNG image stream.""" + + def chunk(kind: bytes, data: bytes) -> bytes: + payload = kind + data + return len(data).to_bytes(4, "big") + payload + zlib.crc32(payload).to_bytes(4, "big") + + def png(header: bytes, *chunks: bytes) -> bytes: + return policy.PNG_SIGNATURE + chunk(b"IHDR", header) + b"".join(chunks) + + def indexed_png( + width: int, + height: int, + bit_depth: int, + palette_entries: int, + decoded: bytes, + *, + interlace: int = 0, + ) -> bytes: + header = width.to_bytes(4, "big") + height.to_bytes(4, "big") + bytes((bit_depth, 3, 0, 0, interlace)) + return png( + header, + chunk(b"PLTE", b"\0\0\0" * palette_entries), + chunk(b"IDAT", zlib.compress(decoded)), + chunk(b"IEND", b""), + ) + + rgba = (1).to_bytes(4, "big") * 2 + bytes((8, 6, 0, 0, 0)) + indexed = (1).to_bytes(4, "big") * 2 + bytes((8, 3, 0, 0, 0)) + gray = (1).to_bytes(4, "big") * 2 + bytes((8, 0, 0, 0, 0)) + image = chunk(b"IDAT", zlib.compress(b"\0\0\0\0\0")) + end = chunk(b"IEND", b"") + + invalid_headers = ( + b"\0" * 13, + (1).to_bytes(4, "big") * 2 + bytes((4, 2, 0, 0, 0)), + (1).to_bytes(4, "big") * 2 + bytes((8, 6, 1, 0, 0)), + (1).to_bytes(4, "big") * 2 + bytes((8, 6, 0, 1, 0)), + (1).to_bytes(4, "big") * 2 + bytes((8, 6, 0, 0, 2)), + ) + assert all(not policy._is_complete_png(png(header, image, end)) for header in invalid_headers) + assert not policy._is_complete_png(png(rgba, chunk(b"IHDR", rgba), image, end)) + assert not policy._is_complete_png(png(rgba, chunk(b"PLTE", b""), image, end)) + assert not policy._is_complete_png(png(rgba, chunk(b"PLTE", b"x" * 769), image, end)) + assert not policy._is_complete_png(png(rgba, chunk(b"PLTE", b"x"), image, end)) + assert not policy._is_complete_png(png(rgba, chunk(b"1EXt", b""), image, end)) + assert not policy._is_complete_png(png(rgba, chunk(b"tExt", b""), image, end)) + assert not policy._is_complete_png(png(rgba, chunk(b"ABCD", b""), image, end)) + assert policy._is_complete_png(png(rgba, chunk(b"tEXt", b"x"), image, end)) + assert not policy._is_complete_png(png(rgba, image, chunk(b"tEXt", b"x"), image, end)) + assert not policy._is_complete_png(png(indexed, image, end)) + indexed_one_bit = (1).to_bytes(4, "big") * 2 + bytes((1, 3, 0, 0, 0)) + assert not policy._is_complete_png( + png(indexed_one_bit, chunk(b"PLTE", b"\0" * 9), chunk(b"IDAT", zlib.compress(b"\0\0")), end) + ) + for filter_type in range(5): + second_row = b"\1\0" if filter_type == 0 else b"\1\xff" + assert policy._is_complete_png( + indexed_png(2, 2, 8, 2, bytes((filter_type, 0, 1, filter_type)) + second_row) + ) + assert not policy._is_complete_png(indexed_png(2, 1, 8, 1, b"\0\0\1")) + assert not policy._is_complete_png(indexed_png(2, 2, 8, 2, b"\0\0\1\4\2\xfe")) + assert policy._is_complete_png(indexed_png(2, 1, 1, 2, b"\0\x40")) + assert not policy._is_complete_png(indexed_png(2, 1, 1, 1, b"\0\x40")) + assert policy._is_complete_png(indexed_png(1, 1, 8, 1, b"\0\0", interlace=1)) + assert not policy._is_complete_png(indexed_png(1, 1, 8, 1, b"\0\1", interlace=1)) + assert not policy._is_complete_png(png(gray, chunk(b"PLTE", b"\0\0\0"), chunk(b"IDAT", zlib.compress(b"\0\0")), end)) + assert not policy._is_complete_png(png(rgba, chunk(b"IDAT", b"not-zlib"), end)) + assert not policy._is_complete_png(png(rgba, chunk(b"IDAT", zlib.compress(b"\0")), end)) + assert not policy._is_complete_png(png(rgba, chunk(b"IDAT", zlib.compress(b"\0\0\0\0\0") + b"x"), end)) + assert not policy._is_complete_png(png(rgba, chunk(b"IDAT", zlib.compress(b"\5\0\0\0\0")), end)) + huge = (policy.MAX_RESPONSE_BYTES).to_bytes(4, "big") + (1).to_bytes(4, "big") + bytes((8, 6, 0, 0, 0)) + assert not policy._is_complete_png(png(huge, image, end)) + + adam7 = (8).to_bytes(4, "big") * 2 + bytes((8, 6, 0, 0, 1)) + adam7_scanlines = b"".join( + b"\0" + b"\0" * (pass_width * 4) + for pass_width, pass_height in ((1, 1), (1, 1), (2, 1), (2, 2), (4, 2), (4, 4), (8, 4)) + for _ in range(pass_height) + ) + assert policy._is_complete_png( + png(adam7, chunk(b"IDAT", zlib.compress(adam7_scanlines)), end) + ) + adam7_one_pixel = (1).to_bytes(4, "big") * 2 + bytes((8, 6, 0, 0, 1)) + assert policy._is_complete_png( + png(adam7_one_pixel, chunk(b"IDAT", zlib.compress(b"\0\0\0\0\0")), end) + ) + + def test_evaluate_pull_request_does_not_fetch_a_removed_binary_pdf() -> None: """A removed documentation PDF has no head content to fetch at all. - Regression coverage for Devin Review's finding: _is_binary_documentation_pdf + Regression coverage for Devin Review's finding: _is_binary_documentation_asset does not itself check status, so without an explicit removed-status guard in evaluate_pull_request's own loop, a deleted PDF would try to fetch its (nonexistent) head content and fail evidence collection for every such From 65be10bdf394b160a94b3a94c48874ca07b29ba1 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 05:52:57 +0900 Subject: [PATCH 61/62] fix(governance): preserve current-main NIM retirement Restore protected main #1640 documentation retirement byte-for-byte after ancestry integration exposed two predecessor documentation paths still present in the writer tree. Keep the final delta limited to the seven intended governance paths. --- .../nvidia-nim-opencode-hotfix-retirement.md | 21 ++++++++ docs/nvidia-nim-opencode-hotfix.md | 53 ------------------- 2 files changed, 21 insertions(+), 53 deletions(-) create mode 100644 docs/doctoring/nvidia-nim-opencode-hotfix-retirement.md delete mode 100644 docs/nvidia-nim-opencode-hotfix.md diff --git a/docs/doctoring/nvidia-nim-opencode-hotfix-retirement.md b/docs/doctoring/nvidia-nim-opencode-hotfix-retirement.md new file mode 100644 index 0000000000..0036bf10c2 --- /dev/null +++ b/docs/doctoring/nvidia-nim-opencode-hotfix-retirement.md @@ -0,0 +1,21 @@ +# NVIDIA NIM OpenCode hotfix retirement + +## Decision + +The legacy direct-provider OpenCode hotfix is retired. Protected `main` now enables only the `contextual-orchestrator` provider in `opencode.jsonc`, with both normal and small-model review requests routed through `contextual-orchestrator/orchestrator/free`. Direct NVIDIA NIM provider selection is therefore not part of the OpenCode review contract. + +The removed `docs/nvidia-nim-opencode-hotfix.md` described a superseded architecture: direct `nvidia-nim` provider configuration, `NVIDIA_API_KEY` binding, and an administrator-bypass hotfix window. Keeping that document beside the current gateway-only configuration created an operational contradiction and could mislead a maintainer into restoring a retired direct-provider path. + +## Current authority boundary + +- `ContextualWisdomLab/.github` owns the review workflows and gateway integration. +- `opencode.jsonc` enables only `contextual-orchestrator` and denies direct-provider fallback. +- NVIDIA NIM credentials may be registered into contextual-orchestrator's provider-discovery boundary; they are not an OpenCode provider credential or a direct workflow model binding. +- The write-capable scheduled autofix path follows the same gateway-only boundary documented in `docs/doctoring/hourly-nvidia-nim-autofix.md` and ADR-0003. +- Queue-saturation administrator bypass, when separately proven under the current control-plane contract, is an admission-recovery mechanism and must not be documented as a provider-specific hotfix permission. + +## Verification + +This record was created from protected `main@81b6f20d7f701bd2e50642ab107ab0f187ae6dc9`. At that revision, `opencode.jsonc` declares `enabled_providers: ["contextual-orchestrator"]`, uses `contextual-orchestrator/orchestrator/free`, and contains no live `nvidia-nim` provider block. The existing `docs/doctoring/hourly-nvidia-nim-autofix.md` already records the corrected gateway-only provider contract. + +No runtime source, credential, model-selection rule, security threshold, branch-protection rule, or review authority is changed by this documentation cleanup. \ No newline at end of file diff --git a/docs/nvidia-nim-opencode-hotfix.md b/docs/nvidia-nim-opencode-hotfix.md deleted file mode 100644 index df8c193b28..0000000000 --- a/docs/nvidia-nim-opencode-hotfix.md +++ /dev/null @@ -1,53 +0,0 @@ -# NVIDIA NIM OpenCode model priority (hotfix) - -## Why - -OpenCode Agent failed to produce a usable review on the PR thread starting at -ContextualWisdomLab/fast-mlsirm#290 (`opencode-review` check **skipped**, no -`opencode-agent[bot]` review comment). Central review therefore prioritizes -**NVIDIA NIM** models as additional catalog candidates so the model pool can -still emit APPROVE / REQUEST_CHANGES when GitHub Models / free tiers stall. - -## Changes - -1. `opencode.jsonc` - - `enabled_providers`: `nvidia-nim` first, then `github-models` - - default `model` / `small_model` prefer NIM Nemotron / Llama 3.3 - - new OpenAI-compatible provider `nvidia-nim` → `https://integrate.api.nvidia.com/v1` - with `apiKey: {env:NVIDIA_API_KEY}` -2. `.github/workflows/opencode-review-dispatch.yml` - - `OPENCODE_MODEL_CANDIDATES` prefixes six NIM models before existing pool - - binds `NVIDIA_API_KEY: ${{ secrets.NVIDIA_API_KEY }}` -3. `scripts/ci/run_opencode_review_model_pool.sh` - - skips `nvidia-nim/*` when `NVIDIA_API_KEY` is unset (same pattern as OpenRouter) - -## Temporary permission bypass (hotfix only) - -For this merge-aid hotfix only: - -- Branch-protection / ruleset admin override may be used to land the central - `.github` change if required checks conflict during the hotfix window. -- **Do not** permanently weaken Security Scan, trivy-fs, osv-scan, or - CodeQL gates. -- **Do not** flip OpenCode agent `permission.edit` / `bash` from `deny` to - `allow` permanently; review agents remain read-only. -- Org secret `NVIDIA_API_KEY` must be set on ContextualWisdomLab for NIM pool - entries to execute; without it the pool falls through to prior candidates. - -## Rollback - -Remove the `nvidia-nim/*` prefixes from `OPENCODE_MODEL_CANDIDATES`, drop the -`nvidia-nim` provider block, and delete this note once GitHub Models / OpenCode -catalog reliability is restored. - -## Secret name - -Org secret is **`NVIDIA_NIM_API_KEY`**. Workflows bind it to process env `NVIDIA_API_KEY` -(fallback: `secrets.NVIDIA_API_KEY` if present) so `opencode.jsonc` `{env:NVIDIA_API_KEY}` resolves. - -## Large-repo OpenCode timeouts (~1 hour) - -Primary/default run timeouts and the dynamic queue timeout cap default to -**3600s** (hour-class) so large repositories are not cut off by the old 600s -default when env is unset. Free-tier failover remains capped at 600s. -Workflow-provided values (e.g. 5400s) still win over defaults. From 41b0c971be3d9f0eee51c8be611e6318675a9715 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 07:44:28 +0900 Subject: [PATCH 62/62] test(governance): make code-owner policy explicit in passing fixture --- tests/test_central_required_workflow_ruleset_audit.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_central_required_workflow_ruleset_audit.py b/tests/test_central_required_workflow_ruleset_audit.py index 908c465a24..87e3d63684 100644 --- a/tests/test_central_required_workflow_ruleset_audit.py +++ b/tests/test_central_required_workflow_ruleset_audit.py @@ -130,6 +130,7 @@ def repository_ruleset_payload() -> dict: "parameters": { "required_approving_review_count": 0, "dismiss_stale_reviews_on_push": True, + "require_code_owner_review": False, "require_last_push_approval": False, "required_review_thread_resolution": True, "allowed_merge_methods": ["merge", "squash"],