diff --git a/.github/workflows/hourly-commercialization-loop.yml b/.github/workflows/hourly-commercialization-loop.yml index 7e85598..ad4317b 100644 --- a/.github/workflows/hourly-commercialization-loop.yml +++ b/.github/workflows/hourly-commercialization-loop.yml @@ -30,28 +30,18 @@ jobs: enable_auto_merge: true merge_mode: direct_or_auto update_branches: true - secrets: inherit - - repair-review-feedback: - needs: inspect-pr-queue - if: ${{ always() }} - permissions: - actions: write - contents: read - issues: write - pull-requests: read - statuses: read - uses: ContextualWisdomLab/.github/.github/workflows/pr-review-fix-scheduler.yml@21397126d708d2d536ccc1d68b0d333653ce9315 - with: - target_repository: ContextualWisdomLab/RankWeave - base_branch: main - max_prs: "50" - max_dispatches: "1" - retry_hours: "1" - secrets: inherit + # Review-feedback repair is dispatched by the central, always-current + # rankweave-hourly-review-repair.yml caller in ContextualWisdomLab/.github + # (uses: ./.github/workflows/pr-review-fix-scheduler.yml, a same-repository + # reference). A local repair-review-feedback job here previously called + # that reusable workflow cross-repository at a pinned commit SHA; that + # shape can never satisfy pr-review-fix-scheduler.yml's same-repository + # trusted-source check (github.repository == ContextualWisdomLab/.github), + # so every run failed before any job was scheduled ("workflow file issue", + # zero jobs created) for as long as that hardening has been in place. revalidate-pr-queue: - needs: repair-review-feedback + needs: inspect-pr-queue if: ${{ always() }} permissions: actions: write @@ -69,15 +59,13 @@ jobs: enable_auto_merge: true merge_mode: direct_or_auto update_branches: true - secrets: inherit develop-next-product-gap: - needs: [inspect-pr-queue, repair-review-feedback, revalidate-pr-queue] + needs: [inspect-pr-queue, revalidate-pr-queue] if: >- ${{ always() && needs.inspect-pr-queue.result == 'success' && - needs.repair-review-feedback.result == 'success' && needs.revalidate-pr-queue.result == 'success' }} runs-on: ubuntu-latest @@ -156,7 +144,6 @@ jobs: { echo "/opencode.json" echo "/.agent-red-output.txt" - echo "/PR_MESSAGE.md" } >>"$GITHUB_WORKSPACE/.git/info/exclude" - name: Verify the trusted base and network-isolation primitive if: steps.gate.outputs.eligible == 'true' @@ -206,11 +193,18 @@ jobs: "lsp": false, "permission": { "read": { - "*": "allow", - ".git/**": "deny", - "opencode.json": "deny", - ".env": "deny", - ".env.*": "deny" + "*": "deny", + "AGENTS.md": "allow", + "ARCHITECTURE.md": "allow", + "CHANGELOG.md": "allow", + "CLAUDE.md": "allow", + "LICENSE": "allow", + "README.md": "allow", + "pyproject.toml": "allow", + "uv.lock": "allow", + "docs/**": "allow", + "src/**": "allow", + "tests/**": "allow" }, "edit": { "*": "deny", @@ -247,8 +241,7 @@ jobs: Write a concise design under docs/superpowers/specs/ and write the failing pytest regression tests first. During this phase do not modify production code, package metadata, README.md, AGENTS.md, CHANGELOG.md, workflows, or - any file outside tests/ and docs/superpowers/specs/. Do not write - PR_MESSAGE.md yet. + any file outside tests/ and docs/superpowers/specs/. The tests must express the buyer-visible contract, preserve RankWeave's standard-library-only runtime, deterministic and immutable evidence, @@ -420,11 +413,19 @@ jobs: "lsp": false, "permission": { "read": { - "*": "allow", - ".git/**": "deny", - "opencode.json": "deny", - ".env": "deny", - ".env.*": "deny" + "*": "deny", + ".agent-red-output.txt": "allow", + "AGENTS.md": "allow", + "ARCHITECTURE.md": "allow", + "CHANGELOG.md": "allow", + "CLAUDE.md": "allow", + "LICENSE": "allow", + "README.md": "allow", + "pyproject.toml": "allow", + "uv.lock": "allow", + "docs/**": "allow", + "src/**": "allow", + "tests/**": "allow" }, "edit": { "*": "allow", @@ -473,11 +474,9 @@ jobs: statistical or standards claim without a primary source already recorded in the repository. Figma is not applicable because RankWeave has no UI. - Write PR_MESSAGE.md at the repository root. Put a concise PR title on the - first line and a body after it describing buyer impact, evidence, - compatibility, and the exact validation commands. Do not commit, push, - open, approve, merge, publish, or release anything; the workflow performs - deterministic validation and packages one protected pull request. + Do not commit, push, open, approve, merge, publish, or release anything; + the workflow performs deterministic validation and packages one protected + pull request with maintainer-owned metadata. PROMPT )" @@ -509,15 +508,8 @@ jobs: if: steps.gate.outputs.eligible == 'true' run: | set -euo pipefail - pr_message_backup="${RUNNER_TEMP}/agent-pr-message.md" - if [ -f PR_MESSAGE.md ]; then - cp PR_MESSAGE.md "$pr_message_backup" - fi rm -f opencode.json .agent-red-output.txt git clean -fdX - if [ -f "$pr_message_backup" ]; then - cp "$pr_message_backup" PR_MESSAGE.md - fi "$AUTOMATION_VENV/bin/python" - <<'PY' from __future__ import annotations @@ -583,7 +575,6 @@ jobs: "CHANGELOG.md", "README.md", "pyproject.toml", - "PR_MESSAGE.md", } allowed_prefixes = ("src/rankweave/", "tests/", "docs/") allowed_suffixes = { @@ -613,7 +604,7 @@ jobs: and not path_text.startswith(allowed_prefixes) ): raise SystemExit(f"path is outside autonomous scope: {path_text}") - if path_text != "PR_MESSAGE.md" and path.suffix not in allowed_suffixes: + if path.suffix not in allowed_suffixes: raise SystemExit(f"non-text or unsupported path changed: {path_text}") if path_text.startswith("src/rankweave/") and path.suffix == ".py": production_changed = True @@ -913,44 +904,8 @@ jobs: title="RankWeave autonomous commercialization increment" body_file="${RUNNER_TEMP}/pr-body.md" - if [ -f PR_MESSAGE.md ]; then - /usr/bin/python3 -I -S - <<'PY' - from __future__ import annotations - - import os - from pathlib import Path - - source = Path("PR_MESSAGE.md").read_text(encoding="utf-8") - lines = source.splitlines() - candidate = lines[0].lstrip("#").strip() if lines else "" - if ( - 10 <= len(candidate) <= 120 - and not candidate.startswith("-") - and all(character.isprintable() for character in candidate) - ): - title = candidate - else: - title = "RankWeave autonomous commercialization increment" - body = "\n".join(lines[1:]).strip() - if not body: - body = "Autonomous NVIDIA NIM increment; see the diff and CHANGELOG.md." - if len(body.encode("utf-8")) > 20_000: - raise SystemExit("PR body exceeds 20,000 UTF-8 bytes") - Path(os.environ["RUNNER_TEMP"], "pr-title.txt").write_text( - title, - encoding="utf-8", - ) - Path(os.environ["RUNNER_TEMP"], "pr-body.md").write_text( - body + "\n", - encoding="utf-8", - ) - PY - title="$(cat "${RUNNER_TEMP}/pr-title.txt")" - rm -f PR_MESSAGE.md - else - echo "Autonomous NVIDIA NIM increment; see the diff and CHANGELOG.md." \ - >"$body_file" - fi + echo "Autonomous NVIDIA NIM increment; see the diff and CHANGELOG.md." \ + >"$body_file" git reset --soft "$AUTOMATION_BASE_SHA" branch="nim-agent/product-dev-${GITHUB_RUN_ID}" diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b1f61e..f042476 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,21 @@ All notable changes to rankweave are documented here. The format follows [Keep a ## [Unreleased] +### Fixed +- Restricted both autonomous OpenCode phases to explicit repository read paths + and removed agent-authored pull-request metadata, preventing workspace-external + reads or generated text from becoming a pull-request title or body. +- Removed the `repair-review-feedback` job from + `hourly-commercialization-loop.yml`: it called + `ContextualWisdomLab/.github`'s `pr-review-fix-scheduler.yml` + cross-repository at a pinned commit SHA, a shape that reusable workflow's + same-repository trusted-source hardening can never satisfy + (`github.repository == ContextualWisdomLab/.github`), so every hourly run + failed before any job was scheduled for as long as that hardening has been + in place. Review-feedback repair is now dispatched by a central, + always-current `rankweave-hourly-review-repair.yml` caller added to + `ContextualWisdomLab/.github`, matching the pattern already used by every + other product repository in the organization. ### Changed - Bumped the pinned `uv` version from `0.11.29` to `0.12.1` in `pyproject.toml` and every `astral-sh/setup-uv` workflow step (`ci.yml`, `create-release.yml`, diff --git a/tests/test_hourly_commercialization_workflow.py b/tests/test_hourly_commercialization_workflow.py index 806b0a8..a1adc5f 100644 --- a/tests/test_hourly_commercialization_workflow.py +++ b/tests/test_hourly_commercialization_workflow.py @@ -3,7 +3,6 @@ PROJECT_ROOT = Path(__file__).resolve().parents[1] WORKFLOW_PATH = PROJECT_ROOT / ".github/workflows/hourly-commercialization-loop.yml" MERGE_WORKFLOW_SHA = "5983b41ace75040c1d81818171ca7d0f3653254e" -FIX_WORKFLOW_SHA = "21397126d708d2d536ccc1d68b0d333653ce9315" def _workflow_text() -> str: @@ -31,14 +30,16 @@ def test_commercialization_loop_uses_pinned_central_pr_governance(): "ContextualWisdomLab/.github/.github/workflows/" f"pr-review-merge-scheduler.yml@{MERGE_WORKFLOW_SHA}" ) - fix_reference = ( - "ContextualWisdomLab/.github/.github/workflows/" - f"pr-review-fix-scheduler.yml@{FIX_WORKFLOW_SHA}" - ) assert workflow.count(merge_reference) == 2 - assert workflow.count(fix_reference) == 1 - assert 'retry_hours: "1"' in workflow - assert "secrets: inherit" in workflow + uses_references = [ + line.split("uses:", maxsplit=1)[1].strip() + for line in workflow.splitlines() + if line.strip().startswith("uses:") + ] + assert all("pr-review-fix-scheduler.yml" not in ref for ref in uses_references) + # Review-feedback repair is dispatched by the central, always-current + # rankweave-hourly-review-repair.yml caller in ContextualWisdomLab/.github + # instead of a cross-repository pinned-SHA job here. def test_product_development_uses_nvidia_nim_and_fails_closed(): @@ -89,11 +90,23 @@ def test_opencode_permissions_block_execution_network_and_protected_edits(): assert workflow.count(denied_permission) == 2 assert '"tests/**": "allow"' in workflow assert '"docs/superpowers/specs/**": "allow"' in workflow + assert workflow.count('"read": {\n "*": "deny"') == 2 assert '".github/**": "deny"' in workflow assert '".git/**": "deny"' in workflow assert "Do not read GitHub issues, pull requests, external web pages" in workflow +def test_agent_output_never_controls_pull_request_metadata(): + workflow = _workflow_text() + + assert "PR_MESSAGE.md" not in workflow + assert 'title="RankWeave autonomous commercialization increment"' in workflow + assert ( + "packages one protected\n pull request with maintainer-owned metadata" + in workflow + ) + + def test_agent_control_file_is_immutable_to_autonomous_authoring(): workflow = _workflow_text() implementation = workflow[ @@ -222,20 +235,15 @@ def test_final_queue_and_base_are_rechecked_before_pr_creation(): def test_product_development_requires_successful_pr_governance(): workflow = _workflow_text() - assert ( - "needs: [inspect-pr-queue, repair-review-feedback, revalidate-pr-queue]" - in workflow - ) + assert "needs: [inspect-pr-queue, revalidate-pr-queue]" in workflow assert "needs.inspect-pr-queue.result == 'success'" in workflow - assert "needs.repair-review-feedback.result == 'success'" in workflow assert "needs.revalidate-pr-queue.result == 'success'" in workflow def test_governance_permissions_are_scoped_per_calling_job(): workflow = _workflow_text() workflow_default = workflow.split("concurrency:", maxsplit=1)[0] - inspect = _job_section(workflow, "inspect-pr-queue", "repair-review-feedback") - repair = _job_section(workflow, "repair-review-feedback", "revalidate-pr-queue") + inspect = _job_section(workflow, "inspect-pr-queue", "revalidate-pr-queue") revalidate = _job_section( workflow, "revalidate-pr-queue", "develop-next-product-gap" ) @@ -261,17 +269,7 @@ def test_governance_permissions_are_scoped_per_calling_job(): assert permission in merge_job assert "issues: write" not in merge_job assert "statuses: read" not in merge_job - - for permission in ( - "actions: write", - "contents: read", - "issues: write", - "pull-requests: read", - "statuses: read", - ): - assert permission in repair - assert "contents: write" not in repair - assert "id-token: write" not in repair + assert "secrets: inherit" not in merge_job def test_opencode_binary_and_models_are_pinned(): @@ -300,6 +298,4 @@ def test_untrusted_execution_drops_privileges(): assert workflow.count("--no-new-privs") == 3 assert workflow.count("--bounding-set=-all") == 3 assert workflow.count("PYTHONPATH=$GITHUB_WORKSPACE/src") == 2 - assert 'pr_message_backup="${RUNNER_TEMP}/agent-pr-message.md"' in workflow - assert "/usr/bin/python3 -I -S - <<'PY'" in workflow assert "strict UTF-8" in workflow