From 4a07e123ac737349fe601d39c2add10dfdc54ee6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 24 Aug 2026 13:55:56 +0900 Subject: [PATCH 01/13] ci: add LineageWeave hourly review-repair scheduler Route the hourly PR queue sweep through the shared pr-review-fix-scheduler with a two-hour same-head redispatch floor so Strix, coverage-source-tree, and the full backend suite can finish before the next autofix dispatch. --- .../lineageweave-hourly-review-repair.yml | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 .github/workflows/lineageweave-hourly-review-repair.yml diff --git a/.github/workflows/lineageweave-hourly-review-repair.yml b/.github/workflows/lineageweave-hourly-review-repair.yml new file mode 100644 index 000000000..eb3de97ca --- /dev/null +++ b/.github/workflows/lineageweave-hourly-review-repair.yml @@ -0,0 +1,32 @@ +name: LineageWeave Hourly Review Repair + +on: + schedule: + # Minute 47 avoids established product-specific heartbeat minutes. + - cron: "47 * * * *" + +concurrency: + group: lineageweave-hourly-review-repair + # Preserve a legitimate long-running root-cause analysis across heartbeats. + cancel-in-progress: false + +permissions: + contents: read + +jobs: + dispatch-review-repair: + permissions: + contents: read + id-token: write + uses: ./.github/workflows/pr-review-fix-scheduler.yml + with: + target_repository: ContextualWisdomLab/LineageWeave + base_branch: main + max_prs: "50" + max_dispatches: "1" + # Strix, coverage-source-tree, and the full backend suite can exceed one + # hour on a single head; do not redispatch for the same head sooner. + retry_hours: "2" + secrets: + PR_REVIEW_MERGE_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN }} + OPENCODE_APPROVE_TOKEN: ${{ secrets.OPENCODE_APPROVE_TOKEN }} From f9e2cbfe3d9be087a8c0045f4079622367d436b4 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 24 Aug 2026 14:14:37 +0900 Subject: [PATCH 02/13] fix(scheduler): use LineageWeave heartbeat slot --- .../lineageweave-hourly-review-repair.yml | 4 +-- .../lineageweave-hourly-review-caller.md | 21 ++++++++++++ .../test_lineageweave_hourly_review_caller.py | 32 +++++++++++++++++++ 3 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 docs/doctoring/lineageweave-hourly-review-caller.md create mode 100644 tests/test_lineageweave_hourly_review_caller.py diff --git a/.github/workflows/lineageweave-hourly-review-repair.yml b/.github/workflows/lineageweave-hourly-review-repair.yml index eb3de97ca..fa0463eb6 100644 --- a/.github/workflows/lineageweave-hourly-review-repair.yml +++ b/.github/workflows/lineageweave-hourly-review-repair.yml @@ -2,8 +2,8 @@ name: LineageWeave Hourly Review Repair on: schedule: - # Minute 47 avoids established product-specific heartbeat minutes. - - cron: "47 * * * *" + # Minute 4 is LineageWeave's reserved heartbeat; minute 47 belongs to Inkspan. + - cron: "4 * * * *" concurrency: group: lineageweave-hourly-review-repair diff --git a/docs/doctoring/lineageweave-hourly-review-caller.md b/docs/doctoring/lineageweave-hourly-review-caller.md new file mode 100644 index 000000000..4c00462d5 --- /dev/null +++ b/docs/doctoring/lineageweave-hourly-review-caller.md @@ -0,0 +1,21 @@ +# LineageWeave hourly review-repair caller + +The central repository owns the bounded hourly review-repair caller for +`ContextualWisdomLab/LineageWeave`. It invokes the reusable scheduler with the +protected `main` branch, inspects at most 50 open pull requests, and dispatches +at most one repair per heartbeat. + +LineageWeave uses minute `4`, which is reserved for this product in the shared +heartbeat registry. Minute `47` belongs to Inkspan and must not be reused here. +The caller preserves an in-flight repair and waits two hours before retrying +the same exact head. + +The reusable scheduler remains fail-closed: the organization variable +`OPENCODE_REPOSITORY_DISPATCH_TARGETS` must contain the exact target +`ContextualWisdomLab/LineageWeave`, and the forwarded scheduler credentials +must be available. A missing allowlist entry or mutation credential is an +operational configuration action, not a reason to weaken the workflow. + +The repair worker reviews and proposes source changes only. Required checks, +independent approval, unresolved-thread policy, protected merge, release, and +rollback remain governed by the target repository and central merge scheduler. diff --git a/tests/test_lineageweave_hourly_review_caller.py b/tests/test_lineageweave_hourly_review_caller.py new file mode 100644 index 000000000..8cf9caa80 --- /dev/null +++ b/tests/test_lineageweave_hourly_review_caller.py @@ -0,0 +1,32 @@ +from pathlib import Path + + +CALLER = Path(".github/workflows/lineageweave-hourly-review-repair.yml") +DOCTORING = Path("docs/doctoring/lineageweave-hourly-review-caller.md") + + +def test_lineageweave_uses_reserved_heartbeat_and_target() -> None: + """The caller schedules the real LineageWeave repair boundary.""" + caller = CALLER.read_text(encoding="utf-8") + + assert 'cron: "4 * * * *"' in caller + assert "group: lineageweave-hourly-review-repair" in caller + assert "cancel-in-progress: false" in caller + assert "target_repository: ContextualWisdomLab/LineageWeave" in caller + assert "base_branch: main" in caller + assert 'max_prs: "50"' in caller + assert 'max_dispatches: "1"' in caller + assert 'retry_hours: "2"' in caller + assert 'PR_REVIEW_MERGE_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN }}' in caller + assert 'OPENCODE_APPROVE_TOKEN: ${{ secrets.OPENCODE_APPROVE_TOKEN }}' in caller + + +def test_lineageweave_doctoring_preserves_allowlist_and_protected_merge_boundary() -> None: + """The operational record tells maintainers what must be configured next.""" + doctoring = DOCTORING.read_text(encoding="utf-8") + + assert "minute `4`" in doctoring + assert "OPENCODE_REPOSITORY_DISPATCH_TARGETS" in doctoring + assert "ContextualWisdomLab/LineageWeave" in doctoring + assert "independent approval" in doctoring + assert "protected merge" in doctoring From d8eff1c8fe8116ea84fdc5f9bf6bb0a38d6856ed Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 25 Aug 2026 20:23:04 +0900 Subject: [PATCH 03/13] ci(lineageweave): register hourly caller contract --- .github/workflows/hourly-nvidia-nim-review-repair.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/hourly-nvidia-nim-review-repair.yml b/.github/workflows/hourly-nvidia-nim-review-repair.yml index 16c53522e..1835291f0 100644 --- a/.github/workflows/hourly-nvidia-nim-review-repair.yml +++ b/.github/workflows/hourly-nvidia-nim-review-repair.yml @@ -9,6 +9,7 @@ on: - .github/workflows/bandscope-hourly-review-repair.yml - .github/workflows/clearfolio-hourly-review-repair.yml - .github/workflows/disksage-hourly-review-repair.yml + - .github/workflows/lineageweave-hourly-review-repair.yml - .github/workflows/fast-mlsirm-hourly-review-repair.yml - .github/workflows/github-hourly-review-repair.yml - .github/workflows/governance-risk-compliance-hourly-review-repair.yml @@ -22,6 +23,7 @@ on: - scripts/ci/pr_review_autofix_context.py - tests/test_bandscope_hourly_review_caller.py - tests/test_disksage_hourly_review_caller.py + - tests/test_lineageweave_hourly_review_caller.py - tests/test_fast_mlsirm_hourly_review_caller.py - tests/test_github_hourly_conflict_repair.py - tests/test_governance_risk_compliance_hourly_review_caller.py @@ -48,6 +50,7 @@ on: - docs/doctoring/clearfolio-hourly-review-caller.md - docs/doctoring/conflict-control-evidence-isolation.md - docs/doctoring/disksage-hourly-review-caller.md + - docs/doctoring/lineageweave-hourly-review-caller.md - docs/doctoring/fast-mlsirm-hourly-review-caller.md - docs/doctoring/github-hourly-conflict-repair.md - docs/doctoring/governance-risk-compliance-hourly-review-caller.md @@ -65,6 +68,7 @@ on: - .github/workflows/bandscope-hourly-review-repair.yml - .github/workflows/clearfolio-hourly-review-repair.yml - .github/workflows/disksage-hourly-review-repair.yml + - .github/workflows/lineageweave-hourly-review-repair.yml - .github/workflows/fast-mlsirm-hourly-review-repair.yml - .github/workflows/github-hourly-review-repair.yml - .github/workflows/governance-risk-compliance-hourly-review-repair.yml @@ -78,6 +82,7 @@ on: - scripts/ci/pr_review_autofix_context.py - tests/test_bandscope_hourly_review_caller.py - tests/test_disksage_hourly_review_caller.py + - tests/test_lineageweave_hourly_review_caller.py - tests/test_fast_mlsirm_hourly_review_caller.py - tests/test_github_hourly_conflict_repair.py - tests/test_governance_risk_compliance_hourly_review_caller.py @@ -104,6 +109,7 @@ on: - docs/doctoring/clearfolio-hourly-review-caller.md - docs/doctoring/conflict-control-evidence-isolation.md - docs/doctoring/disksage-hourly-review-caller.md + - docs/doctoring/lineageweave-hourly-review-caller.md - docs/doctoring/fast-mlsirm-hourly-review-caller.md - docs/doctoring/github-hourly-conflict-repair.md - docs/doctoring/governance-risk-compliance-hourly-review-caller.md @@ -162,6 +168,7 @@ jobs: tests/test_pr_review_conflict_scope.py \ tests/test_bandscope_hourly_review_caller.py \ tests/test_disksage_hourly_review_caller.py \ + tests/test_lineageweave_hourly_review_caller.py \ tests/test_fast_mlsirm_hourly_review_caller.py \ tests/test_github_hourly_conflict_repair.py \ tests/test_governance_risk_compliance_hourly_review_caller.py \ From 57179bb11bc31e58b32997c5e3e7017fd8f9c881 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 25 Aug 2026 20:27:07 +0900 Subject: [PATCH 04/13] ci: watch accounting hourly caller changes --- .github/workflows/hourly-nvidia-nim-review-repair.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/hourly-nvidia-nim-review-repair.yml b/.github/workflows/hourly-nvidia-nim-review-repair.yml index 1835291f0..2cec9cc0c 100644 --- a/.github/workflows/hourly-nvidia-nim-review-repair.yml +++ b/.github/workflows/hourly-nvidia-nim-review-repair.yml @@ -6,6 +6,7 @@ on: - .github/workflows/pr-review-fix-scheduler.yml - scripts/ci/pr_review_fix_scheduler.py - .github/workflows/pr-review-autofix.yml + - .github/workflows/accounting-information-platform-hourly-review-repair.yml - .github/workflows/bandscope-hourly-review-repair.yml - .github/workflows/clearfolio-hourly-review-repair.yml - .github/workflows/disksage-hourly-review-repair.yml @@ -65,6 +66,7 @@ on: - .github/workflows/pr-review-fix-scheduler.yml - scripts/ci/pr_review_fix_scheduler.py - .github/workflows/pr-review-autofix.yml + - .github/workflows/accounting-information-platform-hourly-review-repair.yml - .github/workflows/bandscope-hourly-review-repair.yml - .github/workflows/clearfolio-hourly-review-repair.yml - .github/workflows/disksage-hourly-review-repair.yml From 2f795bdab75b2a278c20743d5555a2e1d6af4177 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 25 Aug 2026 22:19:57 +0900 Subject: [PATCH 05/13] fix(ci): keep LineageWeave caller scope isolated --- .github/workflows/hourly-nvidia-nim-review-repair.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/hourly-nvidia-nim-review-repair.yml b/.github/workflows/hourly-nvidia-nim-review-repair.yml index 2cec9cc0c..1835291f0 100644 --- a/.github/workflows/hourly-nvidia-nim-review-repair.yml +++ b/.github/workflows/hourly-nvidia-nim-review-repair.yml @@ -6,7 +6,6 @@ on: - .github/workflows/pr-review-fix-scheduler.yml - scripts/ci/pr_review_fix_scheduler.py - .github/workflows/pr-review-autofix.yml - - .github/workflows/accounting-information-platform-hourly-review-repair.yml - .github/workflows/bandscope-hourly-review-repair.yml - .github/workflows/clearfolio-hourly-review-repair.yml - .github/workflows/disksage-hourly-review-repair.yml @@ -66,7 +65,6 @@ on: - .github/workflows/pr-review-fix-scheduler.yml - scripts/ci/pr_review_fix_scheduler.py - .github/workflows/pr-review-autofix.yml - - .github/workflows/accounting-information-platform-hourly-review-repair.yml - .github/workflows/bandscope-hourly-review-repair.yml - .github/workflows/clearfolio-hourly-review-repair.yml - .github/workflows/disksage-hourly-review-repair.yml From b0cb5d8fc42f5a7d1601081b83ec389d469c578f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 26 Aug 2026 05:15:29 +0900 Subject: [PATCH 06/13] docs(security): state reusable workflow trust boundary --- .github/workflows/pr-review-fix-scheduler.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/pr-review-fix-scheduler.yml b/.github/workflows/pr-review-fix-scheduler.yml index cc9d3e60e..a92e8b0cd 100644 --- a/.github/workflows/pr-review-fix-scheduler.yml +++ b/.github/workflows/pr-review-fix-scheduler.yml @@ -1,6 +1,8 @@ name: PR Review Fix Scheduler on: + # Same-repository callers resolve this file from their own immutable commit; + # only the explicitly declared optional secrets below cross that boundary. workflow_call: inputs: dry_run: From 3990a78844a2a0172383c33433aa6cd19505ffea Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 26 Aug 2026 05:21:06 +0900 Subject: [PATCH 07/13] test(ci): require LineageWeave scheduler target --- tests/test_lineageweave_hourly_review_caller.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_lineageweave_hourly_review_caller.py b/tests/test_lineageweave_hourly_review_caller.py index 8cf9caa80..e14903b21 100644 --- a/tests/test_lineageweave_hourly_review_caller.py +++ b/tests/test_lineageweave_hourly_review_caller.py @@ -2,6 +2,7 @@ CALLER = Path(".github/workflows/lineageweave-hourly-review-repair.yml") +REUSABLE_SCHEDULER = Path(".github/workflows/pr-review-fix-scheduler.yml") DOCTORING = Path("docs/doctoring/lineageweave-hourly-review-caller.md") @@ -9,6 +10,7 @@ def test_lineageweave_uses_reserved_heartbeat_and_target() -> None: """The caller schedules the real LineageWeave repair boundary.""" caller = CALLER.read_text(encoding="utf-8") + assert REUSABLE_SCHEDULER.is_file() assert 'cron: "4 * * * *"' in caller assert "group: lineageweave-hourly-review-repair" in caller assert "cancel-in-progress: false" in caller From d1385c29f86771c69fa95343f18961eca910c301 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 26 Aug 2026 06:48:28 +0900 Subject: [PATCH 08/13] test(ci): pin LineageWeave caller authority --- tests/test_lineageweave_hourly_review_caller.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/test_lineageweave_hourly_review_caller.py b/tests/test_lineageweave_hourly_review_caller.py index e14903b21..5a508639c 100644 --- a/tests/test_lineageweave_hourly_review_caller.py +++ b/tests/test_lineageweave_hourly_review_caller.py @@ -14,6 +14,13 @@ def test_lineageweave_uses_reserved_heartbeat_and_target() -> None: assert 'cron: "4 * * * *"' in caller assert "group: lineageweave-hourly-review-repair" in caller assert "cancel-in-progress: false" in caller + assert "uses: ./.github/workflows/pr-review-fix-scheduler.yml" in caller + workflow_scope, jobs_scope = caller.split("\njobs:\n", maxsplit=1) + assert "\npermissions:\n contents: read\n" in workflow_scope + assert ( + "\n permissions:\n contents: read\n id-token: write\n" + in jobs_scope + ) assert "target_repository: ContextualWisdomLab/LineageWeave" in caller assert "base_branch: main" in caller assert 'max_prs: "50"' in caller @@ -21,6 +28,8 @@ def test_lineageweave_uses_reserved_heartbeat_and_target() -> None: assert 'retry_hours: "2"' in caller assert 'PR_REVIEW_MERGE_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN }}' in caller assert 'OPENCODE_APPROVE_TOKEN: ${{ secrets.OPENCODE_APPROVE_TOKEN }}' in caller + assert "secrets: inherit" not in caller + assert "CLOUDFLARE_API_TOKEN" not in caller def test_lineageweave_doctoring_preserves_allowlist_and_protected_merge_boundary() -> None: From a5f65737e1cb39aa58598c8b1c6d8f02791d6141 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 26 Aug 2026 08:24:32 +0900 Subject: [PATCH 09/13] fix(ci): repair LineageWeave stacked pull requests --- .../lineageweave-hourly-review-repair.yml | 3 ++- .../lineageweave-hourly-review-caller.md | 6 ++++-- scripts/ci/pr_review_fix_scheduler.py | 9 +++++++-- tests/test_lineageweave_hourly_review_caller.py | 5 +++-- tests/test_pr_review_fix_scheduler.py | 16 ++++++++++++++++ 5 files changed, 32 insertions(+), 7 deletions(-) diff --git a/.github/workflows/lineageweave-hourly-review-repair.yml b/.github/workflows/lineageweave-hourly-review-repair.yml index fa0463eb6..76dfa5168 100644 --- a/.github/workflows/lineageweave-hourly-review-repair.yml +++ b/.github/workflows/lineageweave-hourly-review-repair.yml @@ -21,7 +21,8 @@ jobs: uses: ./.github/workflows/pr-review-fix-scheduler.yml with: target_repository: ContextualWisdomLab/LineageWeave - base_branch: main + # LineageWeave uses stacked PRs; repair reviewed heads on every base. + base_branch: "*" max_prs: "50" max_dispatches: "1" # Strix, coverage-source-tree, and the full backend suite can exceed one diff --git a/docs/doctoring/lineageweave-hourly-review-caller.md b/docs/doctoring/lineageweave-hourly-review-caller.md index 4c00462d5..693953b2f 100644 --- a/docs/doctoring/lineageweave-hourly-review-caller.md +++ b/docs/doctoring/lineageweave-hourly-review-caller.md @@ -2,8 +2,10 @@ The central repository owns the bounded hourly review-repair caller for `ContextualWisdomLab/LineageWeave`. It invokes the reusable scheduler with the -protected `main` branch, inspects at most 50 open pull requests, and dispatches -at most one repair per heartbeat. +explicit all-base selector because LineageWeave uses stacked pull requests, +inspects at most 50 open pull requests, and dispatches at most one repair per +heartbeat. Merge automation remains protected-`main`-only in the separate +central merge scheduler. LineageWeave uses minute `4`, which is reserved for this product in the shared heartbeat registry. Minute `47` belongs to Inkspan and must not be reused here. diff --git a/scripts/ci/pr_review_fix_scheduler.py b/scripts/ci/pr_review_fix_scheduler.py index 2c9745d09..153b2ff2a 100755 --- a/scripts/ci/pr_review_fix_scheduler.py +++ b/scripts/ci/pr_review_fix_scheduler.py @@ -104,6 +104,11 @@ def same_repository_head(repo: str, pr: dict[str, Any]) -> bool: return ((pr.get("headRepository") or {}).get("nameWithOwner") or "") == repo +def matches_base_branch(pr: dict[str, Any], base_branch: str) -> bool: + """Match one base branch, or every base when the caller explicitly uses ``*``.""" + return base_branch == "*" or pr.get("baseRefName") == base_branch + + def latest_current_head_opencode_review(pr: dict[str, Any]) -> dict[str, Any] | None: """Return the newest OpenCode review for the current head, if present.""" for review in reversed((pr.get("reviews") or {}).get("nodes") or []): @@ -293,7 +298,7 @@ def inspect_pr( number = int(pr["number"]) if pr.get("isDraft"): return "skip", ("draft PR",) - if pr.get("baseRefName") != args.base_branch: + if not matches_base_branch(pr, args.base_branch): return "skip", ( f"base branch is {pr.get('baseRefName')}; expected {args.base_branch}", ) @@ -363,7 +368,7 @@ def process_queue(args: argparse.Namespace) -> int: for pr in prs: if pr.get("isDraft"): continue - if pr.get("baseRefName") != args.base_branch: + if not matches_base_branch(pr, args.base_branch): continue if not same_repository_head(args.repo, pr): continue diff --git a/tests/test_lineageweave_hourly_review_caller.py b/tests/test_lineageweave_hourly_review_caller.py index 5a508639c..8e5155294 100644 --- a/tests/test_lineageweave_hourly_review_caller.py +++ b/tests/test_lineageweave_hourly_review_caller.py @@ -1,5 +1,6 @@ -from pathlib import Path +"""Contracts for the LineageWeave stacked-PR hourly repair caller.""" +from pathlib import Path CALLER = Path(".github/workflows/lineageweave-hourly-review-repair.yml") REUSABLE_SCHEDULER = Path(".github/workflows/pr-review-fix-scheduler.yml") @@ -22,7 +23,7 @@ def test_lineageweave_uses_reserved_heartbeat_and_target() -> None: in jobs_scope ) assert "target_repository: ContextualWisdomLab/LineageWeave" in caller - assert "base_branch: main" in caller + assert 'base_branch: "*"' in caller assert 'max_prs: "50"' in caller assert 'max_dispatches: "1"' in caller assert 'retry_hours: "2"' in caller diff --git a/tests/test_pr_review_fix_scheduler.py b/tests/test_pr_review_fix_scheduler.py index 74366f686..ea1229b59 100644 --- a/tests/test_pr_review_fix_scheduler.py +++ b/tests/test_pr_review_fix_scheduler.py @@ -163,6 +163,22 @@ def test_process_queue_dispatches_same_repo_current_head(monkeypatch, capsys): assert payload["autofix_dispatches"] == 1 +def test_process_queue_can_repair_stacked_prs_when_caller_selects_all_bases(monkeypatch, capsys): + """An explicit wildcard includes reviewed stacked heads without changing other callers.""" + pr = make_pr(baseRefName="feature-base") + calls = [] + + monkeypatch.setattr(fix, "fetch_open_prs", lambda repo, max_prs: [pr]) + monkeypatch.setattr(fix, "needs_autofix", lambda pr: (True, ("current-head OpenCode requested changes",))) + monkeypatch.setattr(fix, "issue_comments", lambda repo, number: []) + monkeypatch.setattr(fix, "dispatch_autofix", lambda *args, **kwargs: calls.append((args, kwargs))) + monkeypatch.setattr(fix, "create_fix_marker", lambda repo, pr, dry_run: None) + + assert fix.main(["--repo", "owner/repo", "--base-branch", "*", "--dry-run"]) == 0 + assert len(calls) == 1 + assert json.loads(capsys.readouterr().out.strip().splitlines()[-1])["autofix_dispatches"] == 1 + + def test_autofix_context_filters_outdated_threads_and_renders_checks(): """The context helper filters stale threads and renders compact checks.""" assert context.repo_parts("owner/repo") == ("owner", "repo") From 265fd169c9d304ef8ff89ee78344e90b9b8c844c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 26 Aug 2026 14:22:53 +0900 Subject: [PATCH 10/13] fix(automation): route autofix through orchestrator --- .github/workflows/pr-review-autofix.yml | 180 +++++++++++++++--- CHANGELOG.md | 7 + .../0002-product-technical-gap-baseline.md | 12 ++ docs/automation/hourly-review-repair.md | 21 +- docs/doctoring/hourly-nvidia-nim-autofix.md | 47 ++--- .../lineageweave-hourly-review-caller.md | 7 + ...t_pr_review_autofix_nvidia_nim_contract.py | 75 +++++--- 7 files changed, 262 insertions(+), 87 deletions(-) diff --git a/.github/workflows/pr-review-autofix.yml b/.github/workflows/pr-review-autofix.yml index acafaea91..f58ce369d 100644 --- a/.github/workflows/pr-review-autofix.yml +++ b/.github/workflows/pr-review-autofix.yml @@ -222,6 +222,15 @@ jobs: install -m 0755 "${RUNNER_TEMP}/opencode" "${install_dir}/opencode" echo "$install_dir" >>"$GITHUB_PATH" + - name: Checkout pinned contextual-orchestrator gateway + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + repository: ContextualWisdomLab/contextual-orchestrator + ref: 838b3de160c341a6f36bf588ae9fcc09989c040c + fetch-depth: 1 + persist-credentials: false + path: trusted-contextual-orchestrator + - name: Collect review feedback context env: GH_TOKEN: ${{ steps.target_app_token.outputs.token || github.token }} @@ -279,9 +288,9 @@ jobs: EOF jq -n --arg workspace "$TARGET_WORKSPACE" '{ "$schema": "https://opencode.ai/config.json", - "model": "nvidia-nim/mistralai/mistral-small-4-119b-2603", - "small_model": "nvidia-nim/nvidia/nemotron-3-nano-30b-a3b", - "enabled_providers": ["nvidia-nim"], + "model": "contextual-orchestrator/contextual-orchestrator", + "small_model": "contextual-orchestrator/contextual-orchestrator", + "enabled_providers": ["contextual-orchestrator"], "permission": { "edit": { "*": "allow", @@ -306,7 +315,7 @@ jobs: "ci-autofix": { "description": "Conservative CI pull request review autofix agent", "mode": "primary", - "model": "nvidia-nim/mistralai/mistral-small-4-119b-2603", + "model": "contextual-orchestrator/contextual-orchestrator", "reasoningEffort": "high", "prompt": "{file:./autofix-prompt.md}", "steps": 12, @@ -333,32 +342,23 @@ jobs: } }, "provider": { - "nvidia-nim": { + "contextual-orchestrator": { "npm": "@ai-sdk/openai-compatible", - "name": "NVIDIA NIM", + "name": "Contextual Orchestrator", "options": { - "baseURL": "https://integrate.api.nvidia.com/v1", - "apiKey": "{env:NVIDIA_API_KEY}" + "baseURL": "{env:CONTEXTUAL_ORCHESTRATOR_BASE_URL}", + "apiKey": "{env:CONTEXTUAL_ORCHESTRATOR_TOKEN}" }, "models": { - "mistralai/mistral-small-4-119b-2603": { - "name": "Mistral Small 4 119B 2603", + "contextual-orchestrator": { + "name": "Contextual Orchestrator (auto-discovered)", "tool_call": true, "reasoning": true, "options": { "reasoningEffort": "high" }, "limit": { - "context": 128000, - "output": 4096 - } - }, - "nvidia/nemotron-3-nano-30b-a3b": { - "name": "Nemotron 3 Nano 30B A3B", - "tool_call": true, - "reasoning": true, - "limit": { - "context": 128000, + "context": 200000, "output": 32768 } } @@ -370,18 +370,79 @@ jobs: - name: Run OpenCode review autofix if: env.RESOLVE_CONFLICT != 'true' env: - NVIDIA_API_KEY: ${{ secrets.NVIDIA_NIM_API_KEY }} - MODEL: nvidia-nim/mistralai/mistral-small-4-119b-2603 + BYTEZ_API_KEY: ${{ secrets.BYTEZ_API_KEY }} + NVIDIA_NIM_API_KEY: ${{ secrets.NVIDIA_NIM_API_KEY }} + NVIDIA_NIM_API_KEY_SUB: ${{ secrets.NVIDIA_NIM_API_KEY_SUB }} + OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }} + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + MODEL: contextual-orchestrator/contextual-orchestrator SHARE: "false" NPM_CONFIG_IGNORE_SCRIPTS: "true" NO_COLOR: "1" OPENCODE_AUTOFIX_WORKDIR: ${{ runner.temp }}/opencode-autofix-project run: | set -euo pipefail - if [ -z "${NVIDIA_API_KEY:-}" ]; then - echo "::error::NVIDIA_NIM_API_KEY is required for scheduled OpenCode autofix." + contextual_gateway_pid="" + cleanup_contextual_gateway() { + if [ -n "$contextual_gateway_pid" ]; then + kill "$contextual_gateway_pid" 2>/dev/null || true + for _ in 1 2 3 4 5; do + if ! kill -0 "$contextual_gateway_pid" 2>/dev/null; then + wait "$contextual_gateway_pid" 2>/dev/null || true + contextual_gateway_pid="" + return + fi + sleep 1 + done + kill -KILL "$contextual_gateway_pid" 2>/dev/null || true + wait "$contextual_gateway_pid" 2>/dev/null || true + contextual_gateway_pid="" + fi + } + trap cleanup_contextual_gateway EXIT + gateway_license="$GITHUB_WORKSPACE/trusted-contextual-orchestrator/LICENSE" + if [ "$(sed -n '1p' "$gateway_license")" != "MIT License" ] || + [ "$(git hash-object --no-filters "$gateway_license")" != "591bbf197b355e60604618c8a8a50bc5a839b204" ]; then + echo "::error::Pinned contextual-orchestrator license identity did not match the reviewed MIT source." + exit 1 + fi + contextual_gateway_token="$(python3 -c 'import secrets; print(secrets.token_urlsafe(32))')" + contextual_gateway_base_url="http://127.0.0.1:18080/v1" + echo "::add-mask::$contextual_gateway_token" + env -i \ + PATH="$PATH" HOME="$HOME" \ + PYTHONPATH="$GITHUB_WORKSPACE/trusted-contextual-orchestrator" \ + BYTEZ_API_KEY="${BYTEZ_API_KEY:-}" \ + NVIDIA_NIM_API_KEY="${NVIDIA_NIM_API_KEY:-}" \ + NVIDIA_NIM_API_KEY_SUB="${NVIDIA_NIM_API_KEY_SUB:-}" \ + OPENROUTER_API_KEY="${OPENROUTER_API_KEY:-}" \ + OPENAI_API_KEY="${OPENAI_API_KEY:-}" \ + CONTEXTUAL_ORCHESTRATOR_TOKEN="$contextual_gateway_token" \ + GITHUB_ENV=/dev/null GITHUB_OUTPUT=/dev/null GITHUB_PATH=/dev/null \ + GITHUB_STEP_SUMMARY=/dev/null GITHUB_STATE=/dev/null BASH_ENV=/dev/null \ + python3 -m contextual_orchestrator.review_gateway \ + --host 127.0.0.1 --port 18080 \ + >"${RUNNER_TEMP}/contextual-orchestrator-autofix.log" 2>&1 & + contextual_gateway_pid=$! + models_file="$(mktemp "${RUNNER_TEMP}/contextual-orchestrator-models.XXXXXX")" + gateway_ready=false + for _ in 1 2 3 4 5 6 7 8 9 10; do + if curl -fsS --max-time 3 "${contextual_gateway_base_url%/v1}/healthz" >/dev/null && + curl -fsS --max-time 3 -H "Authorization: Bearer ${contextual_gateway_token}" \ + -o "$models_file" "${contextual_gateway_base_url}/models" && + python3 -c 'import json,sys; data=json.load(open(sys.argv[1], encoding="utf-8")).get("data", []); raise SystemExit(0 if any(str(item.get("id") or "").strip() for item in data if isinstance(item, dict)) else 1)' "$models_file"; then + gateway_ready=true + break + fi + sleep 1 + done + if [ "$gateway_ready" != "true" ]; then + echo "::error::Pinned contextual-orchestrator gateway did not expose a usable auto-discovered model catalog." exit 1 fi + export CONTEXTUAL_ORCHESTRATOR_TOKEN="$contextual_gateway_token" + export CONTEXTUAL_ORCHESTRATOR_BASE_URL="$contextual_gateway_base_url" + unset BYTEZ_API_KEY NVIDIA_NIM_API_KEY NVIDIA_NIM_API_KEY_SUB OPENROUTER_API_KEY OPENAI_API_KEY prompt_file="${RUNNER_TEMP}/opencode-autofix-prompt.md" allowed_paths_zlist="${RUNNER_TEMP}/pr-review-autofix-allowed-paths.zlist" allowed_paths_context="$( @@ -447,6 +508,7 @@ jobs: else rm -f "$TARGET_WORKSPACE/autofix-prompt.md" fi + cleanup_contextual_gateway } trap restore_workspace_config EXIT cd "$TARGET_WORKSPACE" @@ -569,11 +631,15 @@ jobs: - name: Merge base branch and resolve conflicts with OpenCode if: env.RESOLVE_CONFLICT == 'true' env: - NVIDIA_API_KEY: ${{ secrets.NVIDIA_NIM_API_KEY }} + BYTEZ_API_KEY: ${{ secrets.BYTEZ_API_KEY }} + NVIDIA_NIM_API_KEY: ${{ secrets.NVIDIA_NIM_API_KEY }} + NVIDIA_NIM_API_KEY_SUB: ${{ secrets.NVIDIA_NIM_API_KEY_SUB }} + OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }} + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} GITHUB_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN || secrets.OPENCODE_APPROVE_TOKEN || steps.target_app_token.outputs.token }} GH_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN || secrets.OPENCODE_APPROVE_TOKEN || steps.target_app_token.outputs.token }} MUTATION_CREDENTIAL_AVAILABLE: ${{ secrets.PR_REVIEW_MERGE_TOKEN != '' || secrets.OPENCODE_APPROVE_TOKEN != '' || steps.target_app_token.outputs.available == 'true' }} - MODEL: nvidia-nim/mistralai/mistral-small-4-119b-2603 + MODEL: contextual-orchestrator/contextual-orchestrator SHARE: "false" NPM_CONFIG_IGNORE_SCRIPTS: "true" NO_COLOR: "1" @@ -584,10 +650,67 @@ jobs: echo "::error::Conflict-resolution mutation requires PR_REVIEW_MERGE_TOKEN, OPENCODE_APPROVE_TOKEN, or the exchanged OpenCode app token; github.token remains read-only." exit 1 fi - if [ -z "${NVIDIA_API_KEY:-}" ]; then - echo "::error::NVIDIA_NIM_API_KEY is required for scheduled OpenCode autofix." + contextual_gateway_pid="" + cleanup_contextual_gateway() { + if [ -n "$contextual_gateway_pid" ]; then + kill "$contextual_gateway_pid" 2>/dev/null || true + for _ in 1 2 3 4 5; do + if ! kill -0 "$contextual_gateway_pid" 2>/dev/null; then + wait "$contextual_gateway_pid" 2>/dev/null || true + contextual_gateway_pid="" + return + fi + sleep 1 + done + kill -KILL "$contextual_gateway_pid" 2>/dev/null || true + wait "$contextual_gateway_pid" 2>/dev/null || true + contextual_gateway_pid="" + fi + } + trap cleanup_contextual_gateway EXIT + gateway_license="$GITHUB_WORKSPACE/trusted-contextual-orchestrator/LICENSE" + if [ "$(sed -n '1p' "$gateway_license")" != "MIT License" ] || + [ "$(git hash-object --no-filters "$gateway_license")" != "591bbf197b355e60604618c8a8a50bc5a839b204" ]; then + echo "::error::Pinned contextual-orchestrator license identity did not match the reviewed MIT source." + exit 1 + fi + contextual_gateway_token="$(python3 -c 'import secrets; print(secrets.token_urlsafe(32))')" + contextual_gateway_base_url="http://127.0.0.1:18080/v1" + echo "::add-mask::$contextual_gateway_token" + env -i \ + PATH="$PATH" HOME="$HOME" \ + PYTHONPATH="$GITHUB_WORKSPACE/trusted-contextual-orchestrator" \ + BYTEZ_API_KEY="${BYTEZ_API_KEY:-}" \ + NVIDIA_NIM_API_KEY="${NVIDIA_NIM_API_KEY:-}" \ + NVIDIA_NIM_API_KEY_SUB="${NVIDIA_NIM_API_KEY_SUB:-}" \ + OPENROUTER_API_KEY="${OPENROUTER_API_KEY:-}" \ + OPENAI_API_KEY="${OPENAI_API_KEY:-}" \ + CONTEXTUAL_ORCHESTRATOR_TOKEN="$contextual_gateway_token" \ + GITHUB_ENV=/dev/null GITHUB_OUTPUT=/dev/null GITHUB_PATH=/dev/null \ + GITHUB_STEP_SUMMARY=/dev/null GITHUB_STATE=/dev/null BASH_ENV=/dev/null \ + python3 -m contextual_orchestrator.review_gateway \ + --host 127.0.0.1 --port 18080 \ + >"${RUNNER_TEMP}/contextual-orchestrator-autofix-conflict.log" 2>&1 & + contextual_gateway_pid=$! + models_file="$(mktemp "${RUNNER_TEMP}/contextual-orchestrator-models.XXXXXX")" + gateway_ready=false + for _ in 1 2 3 4 5 6 7 8 9 10; do + if curl -fsS --max-time 3 "${contextual_gateway_base_url%/v1}/healthz" >/dev/null && + curl -fsS --max-time 3 -H "Authorization: Bearer ${contextual_gateway_token}" \ + -o "$models_file" "${contextual_gateway_base_url}/models" && + python3 -c 'import json,sys; data=json.load(open(sys.argv[1], encoding="utf-8")).get("data", []); raise SystemExit(0 if any(str(item.get("id") or "").strip() for item in data if isinstance(item, dict)) else 1)' "$models_file"; then + gateway_ready=true + break + fi + sleep 1 + done + if [ "$gateway_ready" != "true" ]; then + echo "::error::Pinned contextual-orchestrator gateway did not expose a usable auto-discovered model catalog." exit 1 fi + export CONTEXTUAL_ORCHESTRATOR_TOKEN="$contextual_gateway_token" + export CONTEXTUAL_ORCHESTRATOR_BASE_URL="$contextual_gateway_base_url" + unset BYTEZ_API_KEY NVIDIA_NIM_API_KEY NVIDIA_NIM_API_KEY_SUB OPENROUTER_API_KEY OPENAI_API_KEY cd "$TARGET_WORKSPACE" # Merge the base branch into the detached head. A clean merge stays @@ -651,6 +774,7 @@ jobs: else rm -f "$TARGET_WORKSPACE/opencode.jsonc" fi + cleanup_contextual_gateway } trap restore_workspace_config EXIT env -u GITHUB_TOKEN -u GH_TOKEN -u ACTIONS_ID_TOKEN_REQUEST_TOKEN -u ACTIONS_ID_TOKEN_REQUEST_URL \ diff --git a/CHANGELOG.md b/CHANGELOG.md index cef0acda6..d181a38ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ this file. The format follows Keep a Changelog, and versioned releases follow Semantic Versioning where the repository publishes a release. ## [Unreleased] +- Route write-capable PR autofix model traffic through a SHA-pinned, + loopback-only contextual-orchestrator sidecar with auto-discovered providers. + Provider credentials remain scoped to the sidecar and are removed before + OpenCode executes; the existing review, OIDC, GitHub mutation, and protected + merge credentials are unchanged. Document that LineageWeave's existing + repository-local commercialization loop, not a duplicate central writer, + owns the zero-open-PR product-gap continuation. - Route Strix cross-provider fallbacks to explicit direct-OpenAI models (`openai-direct/...`) through the OpenAI inference endpoint instead of inheriting a provider-specific primary base: the workflow now provisions diff --git a/docs/adr/0002-product-technical-gap-baseline.md b/docs/adr/0002-product-technical-gap-baseline.md index 4caf92257..aaedd1a27 100644 --- a/docs/adr/0002-product-technical-gap-baseline.md +++ b/docs/adr/0002-product-technical-gap-baseline.md @@ -19,3 +19,15 @@ ContextualWisdomLab/disksage#247, before any target-repository security analysis ran. The workflow, smoke contract, model-pool configuration, and regression tests now share `gpt-5.4`; the change does not weaken provider failure or vulnerability fail-closed behavior. + +## Amendment: autofix model ownership boundary (2026-08-26) + +The write-capable PR autofix worker enables only the OpenAI-compatible +contextual-orchestrator gateway. The central workflow pins the gateway source, +verifies its reviewed license identity, binds it to loopback, and fails closed +unless authenticated model discovery returns a usable catalog. Provider keys +are passed only to the isolated sidecar and are removed before OpenCode runs; +the established review, OIDC, branch-write, independent-approval, and protected +merge credential contracts are unchanged. Product-specific hourly callers do +not duplicate a repository-local product-development writer when one already +owns the zero-open-PR continuation. diff --git a/docs/automation/hourly-review-repair.md b/docs/automation/hourly-review-repair.md index 722724958..31fcccef6 100644 --- a/docs/automation/hourly-review-repair.md +++ b/docs/automation/hourly-review-repair.md @@ -11,12 +11,13 @@ engine**. module. It has no product-specific timer and can be called by naruon, contextual-orchestrator, Inkspan, or another CWL service with an explicit repository and base branch. -- `pr-review-autofix.yml` is the bounded write-capable worker. It uses OpenCode - with NVIDIA NIM and does not approve or merge pull requests. +- `pr-review-autofix.yml` is the bounded write-capable worker. OpenCode reaches + every model through a pinned, loopback-only contextual-orchestrator gateway + and does not approve or merge pull requests. -Orgmetra's caller remains provider-neutral. The intended model boundary is the -contextual-orchestrator gateway: provider keys stay in its KV registry and -automatic model discovery selects upstream models. A caller schedule is not +All callers remain provider-neutral. Provider credentials are visible only to +the isolated contextual-orchestrator sidecar; they are removed before OpenCode +starts, and automatic model discovery selects upstream models. A caller schedule is not evidence that gateway credentials, discovery, or a live OpenCode tool loop are available; those facts require exact worker-run evidence. @@ -44,8 +45,8 @@ not overlap its successor. At most one repair dispatch is created per run. The caller passes only the established `PR_REVIEW_MERGE_TOKEN` and `OPENCODE_APPROVE_TOKEN` scheduler credentials. It does not receive or forward -`NVIDIA_NIM_API_KEY`; the model credential is scoped exclusively to the two -OpenCode execution steps in the separately reviewed autofix worker. +provider credentials; those credentials are scoped exclusively to the two +gateway-owning execution steps in the separately reviewed autofix worker. ## Orgmetra execution contract @@ -199,7 +200,9 @@ organization-level queue inspection and bounded repair dispatch. When a scheduled run fails, classify the result before rerunning: - no actionable file-scoped feedback: expected no-op; -- missing `NVIDIA_NIM_API_KEY`: central secret configuration failure; +- unavailable pinned contextual-orchestrator source, invalid reviewed-license + identity, or an empty auto-discovered model catalog: fail-closed gateway + configuration failure; - head changed: safe optimistic-concurrency refusal; inspect the new head rather than retrying predecessor evidence; - out-of-scope or ignored-path change: treat as a security failure and preserve @@ -225,7 +228,7 @@ Permanent tests prove: - the dispatch budget and same-head retry floor remain one; - caller and reusable-workflow secrets are explicit and never use `secrets: inherit`; -- immutable source, NVIDIA-only model authentication, child-process credential +- immutable source, contextual-orchestrator-only model authentication, child-process credential stripping, live-head guards, and independent reviewer identity remain intact; - ordinary and conflict repair share the complete ignored-inclusive snapshot and NUL-delimited allowlist boundary; diff --git a/docs/doctoring/hourly-nvidia-nim-autofix.md b/docs/doctoring/hourly-nvidia-nim-autofix.md index 6b05c6bd6..f6bbf3066 100644 --- a/docs/doctoring/hourly-nvidia-nim-autofix.md +++ b/docs/doctoring/hourly-nvidia-nim-autofix.md @@ -1,11 +1,12 @@ -# Hourly NVIDIA NIM Review-Autofix Boundary +# Hourly Contextual-Orchestrator Review-Autofix Boundary ## Decision Materialize accepts only exact SHA-256 pins or a bounded relative `-r` include; a lone `--require-hashes` line is not lock evidence. -The write-capable scheduled pull-request autofix agent uses OpenCode with the -NVIDIA NIM API and the organization Actions secret `NVIDIA_NIM_API_KEY`. The +The write-capable scheduled pull-request autofix agent uses OpenCode only +through contextual-orchestrator. The gateway auto-discovers the organization +provider credentials, including `NVIDIA_NIM_API_KEY`, while the independent read-only review agent remains unchanged and continues to use its existing credential and model-pool contract. @@ -58,23 +59,22 @@ The client payload remains untrusted metadata. It identifies a target only after the worker re-reads live pull-request state and verifies the exact repository, open state, same-repository branch, base ref and SHA, and head ref and SHA. -## Provider contract +## Orchestration contract -The pinned OpenCode runtime enables only `nvidia-nim` through the -OpenAI-compatible adapter and NVIDIA hosted endpoint: +The worker checks out contextual-orchestrator at commit +`838b3de160c341a6f36bf588ae9fcc09989c040c`, verifies the reviewed MIT license +blob `591bbf197b355e60604618c8a8a50bc5a839b204`, and starts its review gateway on +loopback. OpenCode enables only this provider-neutral endpoint: ```text -https://integrate.api.nvidia.com/v1 +http://127.0.0.1:18080/v1 ``` -The primary repair model is `mistralai/mistral-small-4-119b-2603`. The -`ci-autofix` agent and its model configuration both request high reasoning -through OpenCode's provider-option contract (`reasoningEffort: "high"`). NVIDIA's -Mistral Small 4 NIM API documents the corresponding request behavior as -`reasoning_effort: "high"`, which enables the model's reasoning mode. The small -model used for bounded helper work remains `nvidia/nemotron-3-nano-30b-a3b` and -is not a fallback provider. GitHub Models configuration, identifiers, base URLs, -and model-auth fallbacks are absent from the scheduled autofix execution path. +The worker validates `/healthz` and the authenticated `/v1/models` catalog +before model execution. An unavailable or empty catalog fails closed; OpenCode +never falls back to a direct provider URL. The `ci-autofix` agent requests high +reasoning through OpenCode's provider-option contract, while model discovery, +provider selection, and cost lineage remain contextual-orchestrator concerns. The high-reasoning setting is deliberate for write-capable review repair. This workflow optimizes correctness, evidence quality, and controllability rather than @@ -84,17 +84,20 @@ writer role and remains subject to exact-head regression evidence. ## Credential boundary -The organization secret is bound as: +The organization provider secrets are bound only to the gateway-owning steps: ```yaml -NVIDIA_API_KEY: ${{ secrets.NVIDIA_NIM_API_KEY }} +NVIDIA_NIM_API_KEY: ${{ secrets.NVIDIA_NIM_API_KEY }} +NVIDIA_NIM_API_KEY_SUB: ${{ secrets.NVIDIA_NIM_API_KEY_SUB }} +BYTEZ_API_KEY: ${{ secrets.BYTEZ_API_KEY }} +OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }} +OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} ``` -It is present only on the two steps that execute OpenCode: ordinary -review-feedback repair and merge-conflict repair. Metadata collection, -checkout, context preparation, validation, commit, and push do not receive the -NVIDIA credential. A missing key is a fatal configuration error rather than a -signal to choose another provider. +They are passed to the sidecar through an isolated `env -i` launch and unset +before OpenCode starts. OpenCode receives only the masked, process-local gateway +bearer token and loopback base URL. Metadata collection, checkout, context +preparation, validation, commit, and push do not receive provider credentials. The ordinary model execution step does not bind a GitHub write token. Its later commit-and-push step may mutate only with `PR_REVIEW_MERGE_TOKEN`, diff --git a/docs/doctoring/lineageweave-hourly-review-caller.md b/docs/doctoring/lineageweave-hourly-review-caller.md index 693953b2f..2f7a7c569 100644 --- a/docs/doctoring/lineageweave-hourly-review-caller.md +++ b/docs/doctoring/lineageweave-hourly-review-caller.md @@ -21,3 +21,10 @@ operational configuration action, not a reason to weaken the workflow. The repair worker reviews and proposes source changes only. Required checks, independent approval, unresolved-thread policy, protected merge, release, and rollback remain governed by the target repository and central merge scheduler. + +When the open-PR queue is empty, product-gap development is not duplicated in +this caller. LineageWeave's active repository-local +`.github/workflows/hourly-commercialization-loop.yml` owns that hourly writer +boundary. The central organization commercial-readiness coordinator recognizes +the dedicated schedule as a writer lease, so it cannot create a second +concurrent gap-development writer for the same repository. diff --git a/tests/test_pr_review_autofix_nvidia_nim_contract.py b/tests/test_pr_review_autofix_nvidia_nim_contract.py index a5d25379a..f3cb8c9b0 100644 --- a/tests/test_pr_review_autofix_nvidia_nim_contract.py +++ b/tests/test_pr_review_autofix_nvidia_nim_contract.py @@ -35,21 +35,21 @@ def test_review_fix_caller_runs_once_each_hour() -> None: assert "uses: ./.github/workflows/pr-review-fix-scheduler.yml" in caller -def test_scheduled_autofix_uses_only_nvidia_nim() -> None: - """Require the write-capable OpenCode autofix agent to use NVIDIA NIM only.""" +def test_scheduled_autofix_uses_only_contextual_orchestrator() -> None: + """Require all write-capable OpenCode model traffic to cross the gateway.""" workflow = _workflow_text(AUTOFIX_WORKFLOW) required_fragments = ( - '"model": "nvidia-nim/mistralai/mistral-small-4-119b-2603"', - '"small_model": "nvidia-nim/nvidia/nemotron-3-nano-30b-a3b"', - '"enabled_providers": ["nvidia-nim"]', - '"nvidia-nim": {', - '"mistralai/mistral-small-4-119b-2603": {', + '"model": "contextual-orchestrator/contextual-orchestrator"', + '"small_model": "contextual-orchestrator/contextual-orchestrator"', + '"enabled_providers": ["contextual-orchestrator"]', + '"contextual-orchestrator": {', '"reasoningEffort": "high"', '"npm": "@ai-sdk/openai-compatible"', - '"baseURL": "https://integrate.api.nvidia.com/v1"', - '"apiKey": "{env:NVIDIA_API_KEY}"', - 'NVIDIA_API_KEY: ${{ secrets.NVIDIA_NIM_API_KEY }}', - 'MODEL: nvidia-nim/mistralai/mistral-small-4-119b-2603', + '"baseURL": "{env:CONTEXTUAL_ORCHESTRATOR_BASE_URL}"', + '"apiKey": "{env:CONTEXTUAL_ORCHESTRATOR_TOKEN}"', + 'MODEL: contextual-orchestrator/contextual-orchestrator', + "ContextualWisdomLab/contextual-orchestrator", + "838b3de160c341a6f36bf588ae9fcc09989c040c", ) for fragment in required_fragments: assert fragment in workflow, fragment @@ -62,6 +62,9 @@ def test_scheduled_autofix_uses_only_nvidia_nim() -> None: '"apiKey": "{env:STRIX_GITHUB_MODELS_TOKEN}"', '"baseURL": "https://models.github.ai/inference"', 'COPILOT_GITHUB_TOKEN', + 'https://integrate.api.nvidia.com/v1', + '"enabled_providers": ["nvidia-nim"]', + '"apiKey": "{env:NVIDIA_API_KEY}"', ) for fragment in forbidden_fragments: assert fragment not in workflow, fragment @@ -98,20 +101,29 @@ def test_opencode_agent_denies_non_file_interactions() -> None: assert workflow.count(f'"{permission_name}": "deny"') == 2 -def test_nvidia_nim_secret_is_scoped_to_agent_execution_steps() -> None: - """Prevent the NVIDIA credential from leaking beyond the two OpenCode runs.""" +def test_provider_secrets_are_scoped_to_gateway_execution_steps() -> None: + """Keep provider credentials inside the two gateway-owning shell steps.""" workflow = _workflow_text(AUTOFIX_WORKFLOW) - binding = 'NVIDIA_API_KEY: ${{ secrets.NVIDIA_NIM_API_KEY }}' + bindings = ( + 'BYTEZ_API_KEY: ${{ secrets.BYTEZ_API_KEY }}', + 'NVIDIA_NIM_API_KEY: ${{ secrets.NVIDIA_NIM_API_KEY }}', + 'NVIDIA_NIM_API_KEY_SUB: ${{ secrets.NVIDIA_NIM_API_KEY_SUB }}', + 'OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}', + 'OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}', + ) ordinary_start = workflow.index(" - name: Run OpenCode review autofix") ordinary_end = workflow.index(" - name: Validate changed files", ordinary_start) conflict_start = workflow.index( " - name: Merge base branch and resolve conflicts with OpenCode" ) - assert workflow.count(binding) == 2 - assert binding in workflow[ordinary_start:ordinary_end] - assert binding in workflow[conflict_start:] - assert binding not in workflow[:ordinary_start] - assert binding not in workflow[ordinary_end:conflict_start] + for binding in bindings: + assert workflow.count(binding) == 2 + assert binding in workflow[ordinary_start:ordinary_end] + assert binding in workflow[conflict_start:] + assert binding not in workflow[:ordinary_start] + assert binding not in workflow[ordinary_end:conflict_start] + unset = "unset BYTEZ_API_KEY NVIDIA_NIM_API_KEY NVIDIA_NIM_API_KEY_SUB OPENROUTER_API_KEY OPENAI_API_KEY" + assert workflow.count(unset) == 2 def test_model_subprocesses_receive_no_github_or_oidc_write_credentials() -> None: @@ -135,16 +147,10 @@ def test_model_subprocesses_receive_no_github_or_oidc_write_credentials() -> Non assert workflow.count(sanitized_invocation) == 2 -def test_missing_nvidia_nim_secret_fails_closed_before_model_execution() -> None: - """Reject an empty model credential instead of falling back to another provider.""" +def test_unavailable_gateway_fails_closed_before_model_execution() -> None: + """Reject an empty auto-discovered catalog instead of calling a provider directly.""" workflow = _workflow_text(AUTOFIX_WORKFLOW) - guard = ( - 'if [ -z "${NVIDIA_API_KEY:-}" ]; then\n' - ' echo "::error::NVIDIA_NIM_API_KEY is required for scheduled ' - 'OpenCode autofix."\n' - " exit 1\n" - " fi" - ) + guard = 'if [ "$gateway_ready" != "true" ]; then' ordinary_start = workflow.index(" - name: Run OpenCode review autofix") ordinary_end = workflow.index(" - name: Validate changed files", ordinary_start) conflict_start = workflow.index( @@ -155,6 +161,19 @@ def test_missing_nvidia_nim_secret_fails_closed_before_model_execution() -> None assert guard in workflow[conflict_start:] +def test_gateway_is_loopback_pinned_and_receives_no_github_credentials() -> None: + """Bind the reviewed gateway source to loopback and an isolated environment.""" + workflow = _workflow_text(AUTOFIX_WORKFLOW) + assert workflow.count("--host 127.0.0.1 --port 18080") == 2 + assert workflow.count("env -i \\") == 2 + assert workflow.count("591bbf197b355e60604618c8a8a50bc5a839b204") == 2 + for block in workflow.split("env -i \\")[1:]: + launch = block.split("python3 -m contextual_orchestrator.review_gateway", 1)[0] + assert "GITHUB_TOKEN" not in launch + assert "GH_TOKEN" not in launch + assert "ACTIONS_ID_TOKEN" not in launch + + def test_independent_review_agent_workflow_matches_reviewed_blob() -> None: """Pin the reviewed read-only reviewer workflow byte-for-byte.""" result = subprocess.run( From 18ce105f55a238d903d483be2efac4e9d0fa75bd Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 26 Aug 2026 14:25:49 +0900 Subject: [PATCH 11/13] test(autofix): align writer gateway contract --- ...test_pr_review_autofix_writer_security_contract.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/test_pr_review_autofix_writer_security_contract.py b/tests/test_pr_review_autofix_writer_security_contract.py index 58ea05877..7d9edad1a 100644 --- a/tests/test_pr_review_autofix_writer_security_contract.py +++ b/tests/test_pr_review_autofix_writer_security_contract.py @@ -6,7 +6,7 @@ _AUTOFIX_WORKFLOW = Path(".github/workflows/pr-review-autofix.yml") -_TARGET_MODEL = "nvidia-nim/mistralai/mistral-small-4-119b-2603" +_TARGET_MODEL = "contextual-orchestrator/contextual-orchestrator" def _workflow_text() -> str: @@ -30,15 +30,16 @@ def _step_header(workflow: str, step_name: str) -> str: return step[:run_start] -def test_writer_uses_supported_nvidia_mistral_small_with_high_reasoning() -> None: - """Pin the write-capable model and its deliberate high-reasoning budget.""" +def test_writer_uses_contextual_orchestrator_with_high_reasoning() -> None: + """Pin the writer to the provider-neutral gateway and high reasoning.""" workflow = _workflow_text() assert f'"model": "{_TARGET_MODEL}"' in workflow - assert '"mistralai/mistral-small-4-119b-2603": {' in workflow + assert '"contextual-orchestrator": {' in workflow assert workflow.count(f"MODEL: {_TARGET_MODEL}") == 2 assert '"reasoningEffort": "high"' in workflow - assert "nvidia-nim/mistralai/mistral-nemotron" not in workflow + assert '"enabled_providers": ["contextual-orchestrator"]' in workflow + assert "https://integrate.api.nvidia.com/v1" not in workflow assert "COPILOT_GITHUB_TOKEN" not in workflow From a11a2e5347a4fef29bf829fc157d72cc70686701 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 26 Aug 2026 14:57:05 +0900 Subject: [PATCH 12/13] docs: correct autofix credential boundary --- docs/doctoring/hourly-nvidia-nim-autofix.md | 11 ++++++----- tests/test_pr_review_autofix_nvidia_nim_contract.py | 4 ++++ 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/docs/doctoring/hourly-nvidia-nim-autofix.md b/docs/doctoring/hourly-nvidia-nim-autofix.md index f6bbf3066..2475bf2cc 100644 --- a/docs/doctoring/hourly-nvidia-nim-autofix.md +++ b/docs/doctoring/hourly-nvidia-nim-autofix.md @@ -116,11 +116,12 @@ env -u GITHUB_TOKEN -u GH_TOKEN \ -u ACTIONS_ID_TOKEN_REQUEST_TOKEN -u ACTIONS_ID_TOKEN_REQUEST_URL ``` -The child receives the NVIDIA model credential and non-secret execution -controls, but cannot call GitHub APIs or mint an Actions OIDC token. GitHub -credentials remain available only to reviewed shell logic before or after the -child process. The key is never written to repository files, generated prompts, -command arguments, or ordinary logs. +The child receives only the gateway bearer token, loopback base URL, and +non-secret execution controls; it cannot call GitHub APIs or mint an Actions +OIDC token. GitHub credentials remain available only to reviewed shell logic +before or after the child process. Provider keys are never passed to OpenCode +or written to repository files, generated prompts, command arguments, or +ordinary logs. ## OpenCode repair sandbox diff --git a/tests/test_pr_review_autofix_nvidia_nim_contract.py b/tests/test_pr_review_autofix_nvidia_nim_contract.py index f3cb8c9b0..278a170cc 100644 --- a/tests/test_pr_review_autofix_nvidia_nim_contract.py +++ b/tests/test_pr_review_autofix_nvidia_nim_contract.py @@ -125,6 +125,10 @@ def test_provider_secrets_are_scoped_to_gateway_execution_steps() -> None: unset = "unset BYTEZ_API_KEY NVIDIA_NIM_API_KEY NVIDIA_NIM_API_KEY_SUB OPENROUTER_API_KEY OPENAI_API_KEY" assert workflow.count(unset) == 2 + doctoring = _workflow_text(DOCTORING_RECORD) + assert "The child receives the NVIDIA model credential" not in doctoring + assert "Provider keys are never passed to OpenCode" in doctoring + def test_model_subprocesses_receive_no_github_or_oidc_write_credentials() -> None: """Strip GitHub write and OIDC credentials from both OpenCode processes.""" From 069f445e88266f66117b65cfa9a4fb65e70dab46 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 26 Aug 2026 22:01:47 +0900 Subject: [PATCH 13/13] fix: correct LineageWeave heartbeat registry note --- .github/workflows/lineageweave-hourly-review-repair.yml | 2 +- docs/doctoring/lineageweave-hourly-review-caller.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/lineageweave-hourly-review-repair.yml b/.github/workflows/lineageweave-hourly-review-repair.yml index 76dfa5168..fe246dd48 100644 --- a/.github/workflows/lineageweave-hourly-review-repair.yml +++ b/.github/workflows/lineageweave-hourly-review-repair.yml @@ -2,7 +2,7 @@ name: LineageWeave Hourly Review Repair on: schedule: - # Minute 4 is LineageWeave's reserved heartbeat; minute 47 belongs to Inkspan. + # Minute 4 is LineageWeave's reserved heartbeat; minute 56 belongs to Inkspan. - cron: "4 * * * *" concurrency: diff --git a/docs/doctoring/lineageweave-hourly-review-caller.md b/docs/doctoring/lineageweave-hourly-review-caller.md index 2f7a7c569..fdaf1b51f 100644 --- a/docs/doctoring/lineageweave-hourly-review-caller.md +++ b/docs/doctoring/lineageweave-hourly-review-caller.md @@ -8,7 +8,7 @@ heartbeat. Merge automation remains protected-`main`-only in the separate central merge scheduler. LineageWeave uses minute `4`, which is reserved for this product in the shared -heartbeat registry. Minute `47` belongs to Inkspan and must not be reused here. +heartbeat registry. Minute `56` belongs to Inkspan and must not be reused here. The caller preserves an in-flight repair and waits two hours before retrying the same exact head.