From 5384182759950b469932c03e23c087a1555c1109 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 20:06:21 +0900 Subject: [PATCH 01/21] test(automation): define hourly scheduler credential boundary --- ..._hourly_maintenance_credential_boundary.py | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 tests/test_hourly_maintenance_credential_boundary.py diff --git a/tests/test_hourly_maintenance_credential_boundary.py b/tests/test_hourly_maintenance_credential_boundary.py new file mode 100644 index 000000000..d10fe7381 --- /dev/null +++ b/tests/test_hourly_maintenance_credential_boundary.py @@ -0,0 +1,68 @@ +# SPDX-License-Identifier: Apache-2.0 +"""Contracts for the hourly maintenance scheduler credential boundary.""" + +from __future__ import annotations + +from pathlib import Path + + +WORKFLOW_PATH = Path(".github/workflows/hourly-maintenance.yml") +FIX_SCHEDULER_SHA = "673f0900f89a65c2dd8219c176f45f35cac4431d" +MERGE_SCHEDULER_SHA = "5983b41ace75040c1d81818171ca7d0f3653254e" + + +def _job_block(workflow: str, job_name: str, next_job_name: str | None) -> str: + """Return one exact top-level job block from the hourly workflow.""" + start = workflow.index(f" {job_name}:\n") + if next_job_name is None: + return workflow[start:] + end = workflow.index(f" {next_job_name}:\n", start) + return workflow[start:end] + + +def test_review_fix_uses_immutable_current_central_scheduler() -> None: + """The repair job pins the reviewed central scheduler prerequisite exactly.""" + workflow = WORKFLOW_PATH.read_text(encoding="utf-8") + review_fix = _job_block(workflow, "review-fix", "review-merge") + + assert ( + "uses: ContextualWisdomLab/.github/.github/workflows/" + f"pr-review-fix-scheduler.yml@{FIX_SCHEDULER_SHA}" + in review_fix + ) + assert f"canonical_ref: {FIX_SCHEDULER_SHA}" in review_fix + assert f"@{MERGE_SCHEDULER_SHA}" not in review_fix + + +def test_review_fix_never_elevates_or_inherits_workflow_token() -> None: + """Repair mutation uses only explicit established secrets, not GITHUB_TOKEN.""" + workflow = WORKFLOW_PATH.read_text(encoding="utf-8") + review_fix = _job_block(workflow, "review-fix", "review-merge") + + assert "\n permissions:\n" not in review_fix + assert "secrets: inherit" not in review_fix + assert ( + "PR_REVIEW_MERGE_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN }}" + in review_fix + ) + assert ( + "OPENCODE_APPROVE_TOKEN: ${{ secrets.OPENCODE_APPROVE_TOKEN }}" + in review_fix + ) + assert "COPILOT_GITHUB_TOKEN" not in review_fix + assert "NVIDIA_NIM_API_KEY" not in review_fix + + +def test_merge_scheduler_contract_is_unchanged_by_repair_hardening() -> None: + """The independent merge plane retains its existing reviewed credential chain.""" + workflow = WORKFLOW_PATH.read_text(encoding="utf-8") + review_merge = _job_block(workflow, "review-merge", None) + + assert ( + "uses: ContextualWisdomLab/.github/.github/workflows/" + f"pr-review-merge-scheduler.yml@{MERGE_SCHEDULER_SHA}" + in review_merge + ) + assert "secrets: inherit" in review_merge + assert "merge_mode: direct_or_auto" in review_merge + assert "enable_auto_merge: true" in review_merge From ec6191f44286ce8363b4b169c9a303e1a65fcba1 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 20:07:30 +0900 Subject: [PATCH 02/21] docs(adr): define hourly maintenance credential boundary --- ...-hourly-maintenance-credential-boundary.md | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 docs/adr/0013-hourly-maintenance-credential-boundary.md diff --git a/docs/adr/0013-hourly-maintenance-credential-boundary.md b/docs/adr/0013-hourly-maintenance-credential-boundary.md new file mode 100644 index 000000000..f00f0bb93 --- /dev/null +++ b/docs/adr/0013-hourly-maintenance-credential-boundary.md @@ -0,0 +1,82 @@ +# ADR 0013: Hourly maintenance uses explicit scheduler credentials + +- **Status:** Proposed +- **Date:** 2026-08-07 +- **Decision owners:** ContextualWisdomLab maintainers + +## Context + +`pg-llm-batch` already runs an hourly pull-request maintenance workflow. Its +review-repair job pins an older central scheduler revision, grants write +permissions to the workflow-generated GitHub token, and forwards all repository +secrets through `secrets: inherit`. + +The central scheduler hardening in `ContextualWisdomLab/.github#782` separates +model authentication from GitHub mutation authority, binds privileged source to +an immutable called-workflow SHA, keeps `github.token` read-only, and permits +repository mutation only through the established explicitly mapped +`PR_REVIEW_MERGE_TOKEN` or `OPENCODE_APPROVE_TOKEN`. OpenCode model execution +continues to use `NVIDIA_NIM_API_KEY` inside the separately reviewed worker and +never uses `COPILOT_GITHUB_TOKEN`. + +The merge scheduler is a distinct review and merge plane. This change must not +alter its credential chain, permissions, or merge policy while repairing the +review-fix caller. + +## Decision + +The hourly `review-fix` job will: + +1. pin the central review-fix scheduler to the exact reviewed prerequisite SHA; +2. pass only `PR_REVIEW_MERGE_TOKEN` and `OPENCODE_APPROVE_TOKEN` explicitly; +3. remove job-level GitHub-token write permissions; +4. avoid `secrets: inherit`; +5. retain the existing one-hour cadence, one-dispatch bound, and single-flight + concurrency; and +6. keep the independent `review-merge` job byte-stable except for unavoidable + formatting generated by the same edit. + +The feature PR remains Draft and must not merge until the central prerequisite +is protected on `main`. Before promotion, its temporary prerequisite SHA must be +replaced by the resulting protected merge SHA and every exact-head check must be +rerun. + +## Consequences + +### Positive + +- The workflow-generated GitHub token stays read-only. +- Product callers no longer expose unrelated repository secrets to the reusable + repair scheduler. +- Standalone and MSA deployments share the same central scheduler contract. +- The model credential remains isolated inside the OpenCode worker. +- The existing reviewer and merge credential systems are not rewritten. + +### Trade-offs + +- Missing established scheduler credentials now cause an explicit fail-closed + run rather than a GitHub-token fallback. +- The caller depends on the central prerequisite merging before it can be + promoted. +- Operators must provision the two existing scheduler secrets where + cross-repository repair is required. + +## Verification + +Permanent tests require the exact central scheduler SHA, explicit secret +mapping, absence of review-fix token elevation and inheritance, and byte-stable +merge-plane policy. The complete repository CI, security, coverage, packaging, +and independent-review gates remain mandatory on the final protected-main base. + +## References + +GitHub. (n.d.). *Controlling permissions for GITHUB_TOKEN*. GitHub Docs. +Retrieved August 7, 2026, from +https://docs.github.com/en/actions/security-for-github-actions/security-guides/automatic-token-authentication#modifying-the-permissions-for-the-github_token + +GitHub. (n.d.). *Reusing workflows*. GitHub Docs. Retrieved August 7, 2026, from +https://docs.github.com/en/actions/how-tos/reuse-automations/reuse-workflows + +GitHub. (n.d.). *Use secrets in GitHub Actions*. GitHub Docs. Retrieved August +7, 2026, from +https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions From df7a51333684ee6230a92696f9180cf39bbba5f5 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 20:08:07 +0900 Subject: [PATCH 03/21] docs(plan): prepare hourly maintenance credential hardening --- ...-hourly-maintenance-credential-boundary.md | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 docs/superpowers/plans/2026-08-07-hourly-maintenance-credential-boundary.md diff --git a/docs/superpowers/plans/2026-08-07-hourly-maintenance-credential-boundary.md b/docs/superpowers/plans/2026-08-07-hourly-maintenance-credential-boundary.md new file mode 100644 index 000000000..0246fdaea --- /dev/null +++ b/docs/superpowers/plans/2026-08-07-hourly-maintenance-credential-boundary.md @@ -0,0 +1,55 @@ +# Hourly Maintenance Credential Boundary Implementation Plan + +> **Required workflow:** Use test-driven development and verification before +> completion. Do not add a temporary write-capable workflow. + +## Goal + +Migrate the `pg-llm-batch` hourly review-repair caller to the hardened central +scheduler contract while preserving the independent merge plane. + +## Dependency + +The implementation is stacked logically on `ContextualWisdomLab/.github#782`. +The current prerequisite SHA is used only for Draft verification. Final promotion +requires the protected merge SHA from central `main` and fresh exact-head gates. + +## Task 1: Prove the current caller is unsafe + +- [x] Add a focused test requiring the exact current central scheduler SHA. +- [x] Require explicit mapping of only the two established scheduler secrets. +- [x] Reject job-level GitHub-token write permissions in the review-fix job. +- [x] Reject `secrets: inherit` in the review-fix job. +- [x] Preserve the existing independent review-merge contract. +- [ ] Record the failing normal CI run on the test-only head. + +## Task 2: Implement the bounded caller migration + +- [ ] Pin `pr-review-fix-scheduler.yml` to the exact prerequisite SHA. +- [ ] Update `canonical_ref` to the same immutable SHA. +- [ ] Remove the review-fix job permission elevation. +- [ ] Replace inherited secrets with explicit scheduler secret mapping. +- [ ] Keep cadence, retry floor, dispatch bound, and concurrency unchanged. +- [ ] Keep the merge scheduler pin, permissions, inputs, and secret inheritance + unchanged. + +## Task 3: Document the operator and trust boundary + +- [x] Add ADR 0013 with GitHub primary-documentation references in APA 7 form. +- [ ] Update README or operator automation documentation if the final diff needs + a caller-facing setup note. +- [ ] Record the change under `CHANGELOG.md` Unreleased. + +## Task 4: Verify and promote + +- [ ] Run focused workflow-contract tests. +- [ ] Run the complete non-integration suite. +- [ ] Require Ruff and 100% production statement/branch coverage. +- [ ] Require 100% production docstring coverage. +- [ ] Require lock freshness, package builds, Compose validation, and both + container builds. +- [ ] Require current-head security, SAST, dependency, SBOM, and review gates. +- [ ] After central #782 merges, replace the temporary SHA with its protected + merge SHA and rerun every gate. +- [ ] Merge only with zero unresolved valid findings and a qualifying independent + non-author approval. From 1e47e31cc9a7087c76d183e024d3fcb48b841dc0 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 21:11:53 +0900 Subject: [PATCH 04/21] test(automation): track current scheduler prerequisite --- tests/test_hourly_maintenance_credential_boundary.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_hourly_maintenance_credential_boundary.py b/tests/test_hourly_maintenance_credential_boundary.py index d10fe7381..a37f0e2ec 100644 --- a/tests/test_hourly_maintenance_credential_boundary.py +++ b/tests/test_hourly_maintenance_credential_boundary.py @@ -7,7 +7,7 @@ WORKFLOW_PATH = Path(".github/workflows/hourly-maintenance.yml") -FIX_SCHEDULER_SHA = "673f0900f89a65c2dd8219c176f45f35cac4431d" +FIX_SCHEDULER_SHA = "b921e26854f1b0fd367c76a32af6db966374bcef" MERGE_SCHEDULER_SHA = "5983b41ace75040c1d81818171ca7d0f3653254e" From 94e270f5f9e71cad936e70e9f33cf5ece7a93375 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 21:12:30 +0900 Subject: [PATCH 05/21] fix(automation): narrow hourly repair credentials --- .github/workflows/hourly-maintenance.yml | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/.github/workflows/hourly-maintenance.yml b/.github/workflows/hourly-maintenance.yml index c141f4693..1fbe994c3 100644 --- a/.github/workflows/hourly-maintenance.yml +++ b/.github/workflows/hourly-maintenance.yml @@ -15,21 +15,17 @@ permissions: jobs: review-fix: name: Repair actionable pull-request feedback - permissions: - actions: write - contents: read - issues: write - pull-requests: read - statuses: read - uses: ContextualWisdomLab/.github/.github/workflows/pr-review-fix-scheduler.yml@5983b41ace75040c1d81818171ca7d0f3653254e # main + uses: ContextualWisdomLab/.github/.github/workflows/pr-review-fix-scheduler.yml@b921e26854f1b0fd367c76a32af6db966374bcef # prerequisite .github#782 with: target_repository: ContextualWisdomLab/pg-llm-batch base_branch: main max_prs: "100" max_dispatches: "1" retry_hours: "1" - canonical_ref: 5983b41ace75040c1d81818171ca7d0f3653254e - secrets: inherit + canonical_ref: b921e26854f1b0fd367c76a32af6db966374bcef + secrets: + PR_REVIEW_MERGE_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN }} + OPENCODE_APPROVE_TOKEN: ${{ secrets.OPENCODE_APPROVE_TOKEN }} review-merge: name: Revalidate and merge eligible pull requests From e4bb16b10a3ebaab244d8e0d4463b8e7ae031598 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 21:13:44 +0900 Subject: [PATCH 06/21] docs(plan): record hourly credential implementation --- ...-hourly-maintenance-credential-boundary.md | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/docs/superpowers/plans/2026-08-07-hourly-maintenance-credential-boundary.md b/docs/superpowers/plans/2026-08-07-hourly-maintenance-credential-boundary.md index 0246fdaea..26166e8e2 100644 --- a/docs/superpowers/plans/2026-08-07-hourly-maintenance-credential-boundary.md +++ b/docs/superpowers/plans/2026-08-07-hourly-maintenance-credential-boundary.md @@ -21,28 +21,33 @@ requires the protected merge SHA from central `main` and fresh exact-head gates. - [x] Reject job-level GitHub-token write permissions in the review-fix job. - [x] Reject `secrets: inherit` in the review-fix job. - [x] Preserve the existing independent review-merge contract. -- [ ] Record the failing normal CI run on the test-only head. +- [x] Record RED CI `31172887367` on test-only head + `df7a51333684ee6230a92696f9180cf39bbba5f5`. Its two credential-boundary + failures are the intended RED evidence; the other failures came from a + stale synthetic merge base and were fixed independently on protected + `main`, so they are not reused as feature evidence. ## Task 2: Implement the bounded caller migration -- [ ] Pin `pr-review-fix-scheduler.yml` to the exact prerequisite SHA. -- [ ] Update `canonical_ref` to the same immutable SHA. -- [ ] Remove the review-fix job permission elevation. -- [ ] Replace inherited secrets with explicit scheduler secret mapping. -- [ ] Keep cadence, retry floor, dispatch bound, and concurrency unchanged. -- [ ] Keep the merge scheduler pin, permissions, inputs, and secret inheritance +- [x] Pin `pr-review-fix-scheduler.yml` to current `.github#782` head + `b921e26854f1b0fd367c76a32af6db966374bcef` for Draft verification. +- [x] Update `canonical_ref` to the same immutable SHA. +- [x] Remove the review-fix job permission elevation. +- [x] Replace inherited secrets with explicit scheduler secret mapping. +- [x] Keep cadence, retry floor, dispatch bound, and concurrency unchanged. +- [x] Keep the merge scheduler pin, permissions, inputs, and secret inheritance unchanged. ## Task 3: Document the operator and trust boundary - [x] Add ADR 0013 with GitHub primary-documentation references in APA 7 form. -- [ ] Update README or operator automation documentation if the final diff needs - a caller-facing setup note. +- [x] Keep the existing secret names as the operator contract; no new credential + or model-secret setup is introduced by this caller-only migration. - [ ] Record the change under `CHANGELOG.md` Unreleased. ## Task 4: Verify and promote -- [ ] Run focused workflow-contract tests. +- [ ] Run focused workflow-contract tests on the implementation head. - [ ] Run the complete non-integration suite. - [ ] Require Ruff and 100% production statement/branch coverage. - [ ] Require 100% production docstring coverage. From 64240433e3b80f308e862b368872b883ada206c7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 21:14:26 +0900 Subject: [PATCH 07/21] docs(changelog): record hourly repair least privilege --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 83fd56b75..6db1e1e36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -69,6 +69,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Narrowed the hourly review-repair caller to the immutable central OpenCode + scheduler prerequisite and only the established `PR_REVIEW_MERGE_TOKEN` and + `OPENCODE_APPROVE_TOKEN` secrets; the caller no longer elevates its generated + GitHub token or inherits unrelated secrets, while the independent merge plane + remains unchanged. - Migrated package licensing to PEP 639 with an SPDX `Apache-2.0` expression, explicit `LICENSE` and `NOTICE` files, and a compatible setuptools backend floor so built artifacts expose normalized legal metadata without warnings. From 4dfe185fc2cffdbc08daa28e6fb3275d47f63ec2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 21:15:49 +0900 Subject: [PATCH 08/21] test: reconcile bounded response double from main --- tests/bounded_response_double.py | 36 ++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 tests/bounded_response_double.py diff --git a/tests/bounded_response_double.py b/tests/bounded_response_double.py new file mode 100644 index 000000000..bc2974cd9 --- /dev/null +++ b/tests/bounded_response_double.py @@ -0,0 +1,36 @@ +# SPDX-License-Identifier: Apache-2.0 +"""Shared bounded-stream response doubles for provider-facing tests.""" + +from __future__ import annotations + +import json +from collections.abc import AsyncIterator, Mapping +from typing import Any + + +class BoundedJsonByteStream: + """Expose one canonical JSON object through aiohttp-style byte chunks.""" + + def __init__(self, payload: Mapping[str, Any]) -> None: + """Serialize one mapping once so tests exercise the production byte boundary.""" + self.payload_bytes = json.dumps( + dict(payload), + ensure_ascii=False, + separators=(",", ":"), + ).encode("utf-8") + self.requested_sizes: list[int] = [] + + async def iter_chunked(self, size: int) -> AsyncIterator[bytes]: + """Yield bounded chunks no larger than the requested positive size.""" + if isinstance(size, bool) or not isinstance(size, int) or size <= 0: + raise AssertionError("bounded response chunk size must be a positive integer") + self.requested_sizes.append(size) + for offset in range(0, len(self.payload_bytes), size): + yield self.payload_bytes[offset : offset + size] + + +def bind_bounded_json_response(response: Any, payload: Mapping[str, Any]) -> None: + """Attach deterministic aiohttp-compatible content metadata to a response double.""" + content = BoundedJsonByteStream(payload) + response.content = content + response.content_length = len(content.payload_bytes) From 70d45335579852e53746191ef6b42b9da17b1aef Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 21:16:31 +0900 Subject: [PATCH 09/21] test: reconcile remote batch identity double from main --- tests/test_remote_batch_identity_contract.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/test_remote_batch_identity_contract.py b/tests/test_remote_batch_identity_contract.py index 6fb9d2661..20c171c50 100644 --- a/tests/test_remote_batch_identity_contract.py +++ b/tests/test_remote_batch_identity_contract.py @@ -12,6 +12,8 @@ from pg_llm_batch.durable_client import DurableBatchAPIClient from pg_llm_batch.exceptions import GatewayError +from tests.bounded_response_double import bind_bounded_json_response + class _Response: """Expose one successful provider response through the aiohttp contract.""" @@ -19,6 +21,18 @@ class _Response: status = 200 headers: dict[str, str] = {} + def __init__(self) -> None: + payload = { + "id": "batch-other", + "status": "in_progress", + "request_counts": { + "total": 2, + "completed": 1, + "failed": 0, + }, + } + bind_bounded_json_response(self, payload) + async def json(self) -> dict[str, Any]: """Return a syntactically valid but identity-mismatched provider payload.""" return { From 1e5ac03fe991ac9c348a3a6e3d4a843139b5bbd6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 21:18:00 +0900 Subject: [PATCH 10/21] test: keep main reconciliation out of credential slice --- tests/bounded_response_double.py | 36 -------------------------------- 1 file changed, 36 deletions(-) delete mode 100644 tests/bounded_response_double.py diff --git a/tests/bounded_response_double.py b/tests/bounded_response_double.py deleted file mode 100644 index bc2974cd9..000000000 --- a/tests/bounded_response_double.py +++ /dev/null @@ -1,36 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -"""Shared bounded-stream response doubles for provider-facing tests.""" - -from __future__ import annotations - -import json -from collections.abc import AsyncIterator, Mapping -from typing import Any - - -class BoundedJsonByteStream: - """Expose one canonical JSON object through aiohttp-style byte chunks.""" - - def __init__(self, payload: Mapping[str, Any]) -> None: - """Serialize one mapping once so tests exercise the production byte boundary.""" - self.payload_bytes = json.dumps( - dict(payload), - ensure_ascii=False, - separators=(",", ":"), - ).encode("utf-8") - self.requested_sizes: list[int] = [] - - async def iter_chunked(self, size: int) -> AsyncIterator[bytes]: - """Yield bounded chunks no larger than the requested positive size.""" - if isinstance(size, bool) or not isinstance(size, int) or size <= 0: - raise AssertionError("bounded response chunk size must be a positive integer") - self.requested_sizes.append(size) - for offset in range(0, len(self.payload_bytes), size): - yield self.payload_bytes[offset : offset + size] - - -def bind_bounded_json_response(response: Any, payload: Mapping[str, Any]) -> None: - """Attach deterministic aiohttp-compatible content metadata to a response double.""" - content = BoundedJsonByteStream(payload) - response.content = content - response.content_length = len(content.payload_bytes) From 577408684e46bf9c2621a2724f91fca35660136f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 21:18:38 +0900 Subject: [PATCH 11/21] test: keep current-main repair out of credential slice --- tests/test_remote_batch_identity_contract.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/tests/test_remote_batch_identity_contract.py b/tests/test_remote_batch_identity_contract.py index 20c171c50..6fb9d2661 100644 --- a/tests/test_remote_batch_identity_contract.py +++ b/tests/test_remote_batch_identity_contract.py @@ -12,8 +12,6 @@ from pg_llm_batch.durable_client import DurableBatchAPIClient from pg_llm_batch.exceptions import GatewayError -from tests.bounded_response_double import bind_bounded_json_response - class _Response: """Expose one successful provider response through the aiohttp contract.""" @@ -21,18 +19,6 @@ class _Response: status = 200 headers: dict[str, str] = {} - def __init__(self) -> None: - payload = { - "id": "batch-other", - "status": "in_progress", - "request_counts": { - "total": 2, - "completed": 1, - "failed": 0, - }, - } - bind_bounded_json_response(self, payload) - async def json(self) -> dict[str, Any]: """Return a syntactically valid but identity-mismatched provider payload.""" return { From d50bc9b2855a801ad8031bcf0ff329882b719c83 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 21:19:40 +0900 Subject: [PATCH 12/21] docs(plan): close hourly credential documentation task --- .../plans/2026-08-07-hourly-maintenance-credential-boundary.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/superpowers/plans/2026-08-07-hourly-maintenance-credential-boundary.md b/docs/superpowers/plans/2026-08-07-hourly-maintenance-credential-boundary.md index 26166e8e2..b8e57d4f6 100644 --- a/docs/superpowers/plans/2026-08-07-hourly-maintenance-credential-boundary.md +++ b/docs/superpowers/plans/2026-08-07-hourly-maintenance-credential-boundary.md @@ -43,7 +43,7 @@ requires the protected merge SHA from central `main` and fresh exact-head gates. - [x] Add ADR 0013 with GitHub primary-documentation references in APA 7 form. - [x] Keep the existing secret names as the operator contract; no new credential or model-secret setup is introduced by this caller-only migration. -- [ ] Record the change under `CHANGELOG.md` Unreleased. +- [x] Record the change under `CHANGELOG.md` Unreleased. ## Task 4: Verify and promote From d769be8ac29a01bf1abb1b79ab5b4dd4598f75e7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 22:08:42 +0900 Subject: [PATCH 13/21] test(automation): split review fix and merge scheduler pins --- tests/test_workflow_contracts.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/tests/test_workflow_contracts.py b/tests/test_workflow_contracts.py index b130b234a..708f56702 100644 --- a/tests/test_workflow_contracts.py +++ b/tests/test_workflow_contracts.py @@ -43,26 +43,28 @@ def test_ci_workflow_enforces_supported_versions_and_quality_gates() -> None: def test_hourly_workflow_repairs_revalidates_and_merges_pull_requests() -> None: workflow = _read(".github/workflows/hourly-maintenance.yml") - scheduler_sha = "5983b41ace75040c1d81818171ca7d0f3653254e" + review_fix_scheduler_sha = "355d9e545fd971638066e90654fdfaa105431968" + review_merge_scheduler_sha = "5983b41ace75040c1d81818171ca7d0f3653254e" assert 'cron: "17 * * * *"' in workflow assert "workflow_dispatch:" in workflow assert ( "uses: ContextualWisdomLab/.github/.github/workflows/" - "pr-review-fix-scheduler.yml@" + f"pr-review-fix-scheduler.yml@{review_fix_scheduler_sha}" ) in workflow assert "target_repository: ContextualWisdomLab/pg-llm-batch" in workflow assert 'retry_hours: "1"' in workflow - assert f"canonical_ref: {scheduler_sha}" in workflow + assert f"canonical_ref: {review_fix_scheduler_sha}" in workflow assert ( "uses: ContextualWisdomLab/.github/.github/workflows/" - "pr-review-merge-scheduler.yml@" + f"pr-review-merge-scheduler.yml@{review_merge_scheduler_sha}" ) in workflow assert "merge_mode: direct_or_auto" in workflow assert "trigger_reviews: true" in workflow assert "enable_auto_merge: true" in workflow assert "update_branches: true" in workflow - assert workflow.count(f"@{scheduler_sha}") == 2 + assert workflow.count(f"@{review_fix_scheduler_sha}") == 1 + assert workflow.count(f"@{review_merge_scheduler_sha}") == 1 _assert_external_actions_are_pinned(workflow) From 93525065d34f01f7e40f4e17b3ce4fe3ecec9a7e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 22:09:18 +0900 Subject: [PATCH 14/21] test(automation): require current central scheduler prerequisite --- tests/test_hourly_maintenance_credential_boundary.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_hourly_maintenance_credential_boundary.py b/tests/test_hourly_maintenance_credential_boundary.py index a37f0e2ec..1646cac22 100644 --- a/tests/test_hourly_maintenance_credential_boundary.py +++ b/tests/test_hourly_maintenance_credential_boundary.py @@ -7,7 +7,7 @@ WORKFLOW_PATH = Path(".github/workflows/hourly-maintenance.yml") -FIX_SCHEDULER_SHA = "b921e26854f1b0fd367c76a32af6db966374bcef" +FIX_SCHEDULER_SHA = "355d9e545fd971638066e90654fdfaa105431968" MERGE_SCHEDULER_SHA = "5983b41ace75040c1d81818171ca7d0f3653254e" From 5d38b00bbbb6bd96685c870febbe916da2101a4d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 22:10:15 +0900 Subject: [PATCH 15/21] fix(automation): pin current central repair scheduler --- .github/workflows/hourly-maintenance.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/hourly-maintenance.yml b/.github/workflows/hourly-maintenance.yml index 1fbe994c3..d273fa2fd 100644 --- a/.github/workflows/hourly-maintenance.yml +++ b/.github/workflows/hourly-maintenance.yml @@ -15,14 +15,14 @@ permissions: jobs: review-fix: name: Repair actionable pull-request feedback - uses: ContextualWisdomLab/.github/.github/workflows/pr-review-fix-scheduler.yml@b921e26854f1b0fd367c76a32af6db966374bcef # prerequisite .github#782 + uses: ContextualWisdomLab/.github/.github/workflows/pr-review-fix-scheduler.yml@355d9e545fd971638066e90654fdfaa105431968 # prerequisite .github#782 with: target_repository: ContextualWisdomLab/pg-llm-batch base_branch: main max_prs: "100" max_dispatches: "1" retry_hours: "1" - canonical_ref: b921e26854f1b0fd367c76a32af6db966374bcef + canonical_ref: 355d9e545fd971638066e90654fdfaa105431968 secrets: PR_REVIEW_MERGE_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN }} OPENCODE_APPROVE_TOKEN: ${{ secrets.OPENCODE_APPROVE_TOKEN }} From a5551259b6000a5727391c227f7a360d03291dd9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 22:17:03 +0900 Subject: [PATCH 16/21] docs(plan): synchronize central scheduler prerequisite --- ...07-hourly-maintenance-credential-boundary.md | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/docs/superpowers/plans/2026-08-07-hourly-maintenance-credential-boundary.md b/docs/superpowers/plans/2026-08-07-hourly-maintenance-credential-boundary.md index b8e57d4f6..35ccc2758 100644 --- a/docs/superpowers/plans/2026-08-07-hourly-maintenance-credential-boundary.md +++ b/docs/superpowers/plans/2026-08-07-hourly-maintenance-credential-boundary.md @@ -30,7 +30,7 @@ requires the protected merge SHA from central `main` and fresh exact-head gates. ## Task 2: Implement the bounded caller migration - [x] Pin `pr-review-fix-scheduler.yml` to current `.github#782` head - `b921e26854f1b0fd367c76a32af6db966374bcef` for Draft verification. + `355d9e545fd971638066e90654fdfaa105431968` for Draft verification. - [x] Update `canonical_ref` to the same immutable SHA. - [x] Remove the review-fix job permission elevation. - [x] Replace inherited secrets with explicit scheduler secret mapping. @@ -47,14 +47,19 @@ requires the protected merge SHA from central `main` and fresh exact-head gates. ## Task 4: Verify and promote -- [ ] Run focused workflow-contract tests on the implementation head. -- [ ] Run the complete non-integration suite. -- [ ] Require Ruff and 100% production statement/branch coverage. -- [ ] Require 100% production docstring coverage. +- [ ] Run focused workflow-contract tests on the exact source head. +- [ ] Run the complete non-integration suite on the exact source head. +- [ ] Require Ruff and 100% production statement/branch coverage on the exact + source head. +- [ ] Require 100% production docstring coverage on the exact source head. - [ ] Require lock freshness, package builds, Compose validation, and both - container builds. + container builds on the exact source head. - [ ] Require current-head security, SAST, dependency, SBOM, and review gates. - [ ] After central #782 merges, replace the temporary SHA with its protected merge SHA and rerun every gate. - [ ] Merge only with zero unresolved valid findings and a qualifying independent non-author approval. + +The current pull-request CI may validate GitHub's synthetic merge commit rather +than the source head. Such runs are useful regression evidence but do not satisfy +the exact-source-head acceptance items above. From e11352584dcc990a60192f014c2bc57ae705fd46 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 23:12:58 +0900 Subject: [PATCH 17/21] test(ci): require exact source-head checkout --- tests/test_workflow_contracts.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/test_workflow_contracts.py b/tests/test_workflow_contracts.py index 708f56702..14937b9b3 100644 --- a/tests/test_workflow_contracts.py +++ b/tests/test_workflow_contracts.py @@ -41,6 +41,18 @@ def test_ci_workflow_enforces_supported_versions_and_quality_gates() -> None: _assert_external_actions_are_pinned(workflow) +def test_ci_checks_out_and_verifies_the_exact_source_head_in_every_job() -> None: + """Required CI evidence must execute the exact PR head rather than a merge ref.""" + workflow = _read(".github/workflows/ci.yml") + exact_source_expression = "${{ github.event.pull_request.head.sha || github.sha }}" + + assert workflow.count(f"ref: {exact_source_expression}") == 4 + assert workflow.count("name: Verify exact source head") == 4 + assert workflow.count( + f'test "$(git rev-parse HEAD)" = "{exact_source_expression}"' + ) == 4 + + def test_hourly_workflow_repairs_revalidates_and_merges_pull_requests() -> None: workflow = _read(".github/workflows/hourly-maintenance.yml") review_fix_scheduler_sha = "355d9e545fd971638066e90654fdfaa105431968" From 7c785b1868c57a8b8bc4c7f6640cac886afd16e2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 23:15:45 +0900 Subject: [PATCH 18/21] test(ci): bind exact-head contract to every checkout --- tests/test_workflow_contracts.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/test_workflow_contracts.py b/tests/test_workflow_contracts.py index 14937b9b3..0a363561c 100644 --- a/tests/test_workflow_contracts.py +++ b/tests/test_workflow_contracts.py @@ -42,15 +42,17 @@ def test_ci_workflow_enforces_supported_versions_and_quality_gates() -> None: def test_ci_checks_out_and_verifies_the_exact_source_head_in_every_job() -> None: - """Required CI evidence must execute the exact PR head rather than a merge ref.""" + """Every CI checkout must bind and verify the exact source head.""" workflow = _read(".github/workflows/ci.yml") exact_source_expression = "${{ github.event.pull_request.head.sha || github.sha }}" + checkout_count = workflow.count("uses: actions/checkout@") - assert workflow.count(f"ref: {exact_source_expression}") == 4 - assert workflow.count("name: Verify exact source head") == 4 + assert checkout_count > 0 + assert workflow.count(f"ref: {exact_source_expression}") == checkout_count + assert workflow.count("name: Verify exact source head") == checkout_count assert workflow.count( f'test "$(git rev-parse HEAD)" = "{exact_source_expression}"' - ) == 4 + ) == checkout_count def test_hourly_workflow_repairs_revalidates_and_merges_pull_requests() -> None: From fffb04b5020271d1d31182605377dac81ab8bc67 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 23:16:22 +0900 Subject: [PATCH 19/21] ci: verify exact pull-request source head --- .github/workflows/ci.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3af39697e..f66395c62 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,7 +30,10 @@ jobs: - name: Checkout uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} persist-credentials: false + - name: Verify exact source head + run: test "$(git rev-parse HEAD)" = "${{ github.event.pull_request.head.sha || github.sha }}" - name: Set up Python uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: @@ -56,7 +59,10 @@ jobs: - name: Checkout uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} persist-credentials: false + - name: Verify exact source head + run: test "$(git rev-parse HEAD)" = "${{ github.event.pull_request.head.sha || github.sha }}" - name: Set up Python uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: @@ -94,7 +100,10 @@ jobs: - name: Checkout uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} persist-credentials: false + - name: Verify exact source head + run: test "$(git rev-parse HEAD)" = "${{ github.event.pull_request.head.sha || github.sha }}" - name: Validate Compose configuration run: docker compose config >/dev/null - name: Build component image From db9d01c82413ade243bdf226bb1e14df84c14d66 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 23:19:12 +0900 Subject: [PATCH 20/21] test(http): reconcile bounded response double from main --- tests/bounded_response_double.py | 36 ++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 tests/bounded_response_double.py diff --git a/tests/bounded_response_double.py b/tests/bounded_response_double.py new file mode 100644 index 000000000..bc2974cd9 --- /dev/null +++ b/tests/bounded_response_double.py @@ -0,0 +1,36 @@ +# SPDX-License-Identifier: Apache-2.0 +"""Shared bounded-stream response doubles for provider-facing tests.""" + +from __future__ import annotations + +import json +from collections.abc import AsyncIterator, Mapping +from typing import Any + + +class BoundedJsonByteStream: + """Expose one canonical JSON object through aiohttp-style byte chunks.""" + + def __init__(self, payload: Mapping[str, Any]) -> None: + """Serialize one mapping once so tests exercise the production byte boundary.""" + self.payload_bytes = json.dumps( + dict(payload), + ensure_ascii=False, + separators=(",", ":"), + ).encode("utf-8") + self.requested_sizes: list[int] = [] + + async def iter_chunked(self, size: int) -> AsyncIterator[bytes]: + """Yield bounded chunks no larger than the requested positive size.""" + if isinstance(size, bool) or not isinstance(size, int) or size <= 0: + raise AssertionError("bounded response chunk size must be a positive integer") + self.requested_sizes.append(size) + for offset in range(0, len(self.payload_bytes), size): + yield self.payload_bytes[offset : offset + size] + + +def bind_bounded_json_response(response: Any, payload: Mapping[str, Any]) -> None: + """Attach deterministic aiohttp-compatible content metadata to a response double.""" + content = BoundedJsonByteStream(payload) + response.content = content + response.content_length = len(content.payload_bytes) From 02aa6efe447389cafca3d891fcfd394832d34cfe Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 23:20:04 +0900 Subject: [PATCH 21/21] test(http): reconcile identity response double from main --- tests/test_remote_batch_identity_contract.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/test_remote_batch_identity_contract.py b/tests/test_remote_batch_identity_contract.py index 6fb9d2661..20c171c50 100644 --- a/tests/test_remote_batch_identity_contract.py +++ b/tests/test_remote_batch_identity_contract.py @@ -12,6 +12,8 @@ from pg_llm_batch.durable_client import DurableBatchAPIClient from pg_llm_batch.exceptions import GatewayError +from tests.bounded_response_double import bind_bounded_json_response + class _Response: """Expose one successful provider response through the aiohttp contract.""" @@ -19,6 +21,18 @@ class _Response: status = 200 headers: dict[str, str] = {} + def __init__(self) -> None: + payload = { + "id": "batch-other", + "status": "in_progress", + "request_counts": { + "total": 2, + "completed": 1, + "failed": 0, + }, + } + bind_bounded_json_response(self, payload) + async def json(self) -> dict[str, Any]: """Return a syntactically valid but identity-mismatched provider payload.""" return {