From 315cd93a58821e143154adab359ff388f89d28f7 Mon Sep 17 00:00:00 2001 From: Pigbibi <20649888+Pigbibi@users.noreply.github.com> Date: Thu, 15 Oct 2026 01:59:47 +0800 Subject: [PATCH 1/2] fix: align evidence review roles and fail closed on release readiness Co-Authored-By: Codex --- .github/workflows/evidence-gate.yml | 3 +++ docs/ai-review-role-contract.md | 23 +++++++++++++++++++++++ scripts/gate_evidence_package.py | 5 ++++- tests/test_evidence_gate_review.py | 29 ++++++++++++++++++++++++++++- 4 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 docs/ai-review-role-contract.md diff --git a/.github/workflows/evidence-gate.yml b/.github/workflows/evidence-gate.yml index 5573c87..bec2dcc 100644 --- a/.github/workflows/evidence-gate.yml +++ b/.github/workflows/evidence-gate.yml @@ -10,6 +10,8 @@ on: - "src/**/combo_manifests.py" - "docs/evidence/**" - "evidence/**" + - "scripts/gate_evidence_package.py" + - ".github/workflows/evidence-gate.yml" permissions: contents: read @@ -74,6 +76,7 @@ jobs: - name: Evaluate Evidence Gate env: GITHUB_BASE_REF: ${{ github.base_ref }} + AI_SERVICE_RELEASE_READY: ${{ vars.AI_SERVICE_RELEASE_READY }} AI_SERVICE_URL: ${{ vars.AI_SERVICE_URL }} AI_SERVICE_AUDIENCE: ${{ vars.AI_SERVICE_AUDIENCE }} AI_SERVICE_REVIEWERS_JSON: ${{ vars.AI_SERVICE_REVIEWERS_JSON }} diff --git a/docs/ai-review-role-contract.md b/docs/ai-review-role-contract.md new file mode 100644 index 0000000..12628ce --- /dev/null +++ b/docs/ai-review-role-contract.md @@ -0,0 +1,23 @@ +# AI review role contract + +Drift review and the PR Evidence Gate use the same three role labels: +`primary`, `secondary`, and `verification`. These must match the labels in +`AI_SERVICE_REVIEWERS_JSON`; the old `reviewer-primary`, `reviewer-secondary`, +and `reviewer-independent` labels were incompatible with the configured routes. + +Evidence promotion now requires `AI_SERVICE_RELEASE_READY=true` before any +model dispatch. Missing, false, or invalid readiness fails closed with exit 3; +`DUAL_REVIEW_GATE_SKIP` cannot bypass readiness. PRs without a status promotion +still pass the deterministic no-promotion check. Draft PRs do not dispatch the +Evidence Gate. The reusable drift workflow already gates on the same readiness +variable. No repository variables, grants, IAM, or service configuration change +is part of this patch. + +On 2026-10-15, the repository variables were read back as `RELEASE_READY=false` +and all three review roles use `mode=agent`, `model=configured-dot`. Their +profile labels differ, but all routes currently go to Dot. Three labels or +three task IDs do not prove provider/model independence. The `verification` +role therefore does **not** satisfy an independent reviewer requirement. +Readiness must remain false until routing, admission, and actual independent +review evidence have been verified by the owner. Renaming roles does not +establish independence or authorize adoption, deployment, or trading. diff --git a/scripts/gate_evidence_package.py b/scripts/gate_evidence_package.py index 6da4295..a51d257 100755 --- a/scripts/gate_evidence_package.py +++ b/scripts/gate_evidence_package.py @@ -97,6 +97,9 @@ def _validate_with_promotion_standard(path: Path) -> tuple[bool, list[str]]: def _run_promotion_dual_review(evidence_files: list[Path]) -> int: """Advisory V2 task-service quorum; any unavailable or pending result fails closed.""" + if os.environ.get("AI_SERVICE_RELEASE_READY", "").strip().lower() != "true": + print("[evidence-gate] AI service release is not ready; promotion blocked", file=sys.stderr) + return 3 if str(os.environ.get("DUAL_REVIEW_GATE_SKIP", "")).strip().lower() in {"1", "true", "yes"}: print("[evidence-gate] dual-review skipped by DUAL_REVIEW_GATE_SKIP") return 0 @@ -118,7 +121,7 @@ def _run_promotion_dual_review(evidence_files: list[Path]) -> int: raise ValueError("caller revision required") result = review_material( material, operation_id="crypto-evidence:" + caller_ref + ":" + path.as_posix(), - required_roles=("reviewer-primary", "reviewer-secondary", "reviewer-independent"), + required_roles=("primary", "secondary", "verification"), ) except Exception: print("[evidence-gate] review task unavailable", file=sys.stderr) diff --git a/tests/test_evidence_gate_review.py b/tests/test_evidence_gate_review.py index 535da2a..1a32784 100644 --- a/tests/test_evidence_gate_review.py +++ b/tests/test_evidence_gate_review.py @@ -34,6 +34,7 @@ def review_material(material, *, operation_id, required_roles): @pytest.fixture def evidence(tmp_path, monkeypatch): monkeypatch.setenv("GITHUB_SHA", "a" * 40) + monkeypatch.setenv("AI_SERVICE_RELEASE_READY", "true") monkeypatch.delenv("DUAL_REVIEW_GATE_SKIP", raising=False) path = tmp_path / "synthetic.json" path.write_text(json.dumps({"strategy_profile": "synthetic"}), encoding="utf-8") @@ -43,7 +44,8 @@ def evidence(tmp_path, monkeypatch): def test_unanimous_advisory_approval_passes(monkeypatch, evidence): calls = _install_reviewer(monkeypatch, {"status": "completed", "outcome": "agree_approve", "advisory_only": True}) assert _load_gate()._run_promotion_dual_review([evidence]) == 0 - assert len(calls) == 1 and len(set(calls[0][1])) == 3 + assert len(calls) == 1 + assert calls[0][1] == ("primary", "secondary", "verification") @pytest.mark.parametrize("result,code", [ @@ -71,3 +73,28 @@ def test_workflow_uses_v2_task_service_only(): assert retired not in workflow assert "AI_SERVICE_URL: ${{ vars.AI_SERVICE_URL }}" in workflow assert "id-token: write" in workflow + + +@pytest.mark.parametrize("ready", [None, "", "false", "1", "yes", "invalid"]) +@pytest.mark.parametrize("skip", ["", "true"]) +def test_release_not_ready_blocks_before_dispatch(monkeypatch, evidence, ready, skip): + calls = _install_reviewer(monkeypatch, {"status": "completed", "outcome": "agree_approve", "advisory_only": True}) + if ready is None: + monkeypatch.delenv("AI_SERVICE_RELEASE_READY", raising=False) + else: + monkeypatch.setenv("AI_SERVICE_RELEASE_READY", ready) + monkeypatch.setenv("DUAL_REVIEW_GATE_SKIP", skip) + assert _load_gate()._run_promotion_dual_review([evidence]) == 3 + assert calls == [] + + +def test_nonpromotion_diff_needs_no_ai_release(monkeypatch): + gate = _load_gate() + monkeypatch.setattr(gate, "_git_diff", lambda _: "+# documentation only") + monkeypatch.delenv("AI_SERVICE_RELEASE_READY", raising=False) + assert gate.main() == 0 + + +def test_workflow_passes_release_readiness(): + workflow = (ROOT / ".github" / "workflows" / "evidence-gate.yml").read_text(encoding="utf-8") + assert "AI_SERVICE_RELEASE_READY: ${{ vars.AI_SERVICE_RELEASE_READY }}" in workflow From e95d16d922c157f94fa4590d1b56e99da9698518 Mon Sep 17 00:00:00 2001 From: Pigbibi <20649888+Pigbibi@users.noreply.github.com> Date: Thu, 15 Oct 2026 02:34:25 +0800 Subject: [PATCH 2/2] feat: opt advisory evidence reviews into safe disposition and dead-man monitoring Co-Authored-By: Codex --- .github/workflows/advisory-review-health.yml | 20 ++++++++++++++++++++ .github/workflows/evidence-gate.yml | 14 +++++++++++++- docs/advisory-review-disposition.md | 9 +++++++++ 3 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/advisory-review-health.yml create mode 100644 docs/advisory-review-disposition.md diff --git a/.github/workflows/advisory-review-health.yml b/.github/workflows/advisory-review-health.yml new file mode 100644 index 0000000..137e96b --- /dev/null +++ b/.github/workflows/advisory-review-health.yml @@ -0,0 +1,20 @@ +name: Advisory Review Health +on: + schedule: + - cron: '35 8 * * *' + workflow_run: + workflows: [Drift Check, Evidence Gate] + types: [completed] + workflow_dispatch: +permissions: + contents: read + actions: read + issues: write +jobs: + check: + uses: QuantStrategyLab/QuantPlatformKit/.github/workflows/review-loop-deadman.yml@feat/advisory-review-closed-loop + with: + lanes_json: >- + [{"workflow":"drift-check.yml","job":"drift","interval_hours":24,"enabled_at":"2026-10-15T00:00:00Z"}, + {"workflow":"evidence-gate.yml","job":"gate","interval_hours":0}] + threshold: '3' diff --git a/.github/workflows/evidence-gate.yml b/.github/workflows/evidence-gate.yml index bec2dcc..c0513c7 100644 --- a/.github/workflows/evidence-gate.yml +++ b/.github/workflows/evidence-gate.yml @@ -16,6 +16,7 @@ on: permissions: contents: read pull-requests: read + issues: write id-token: write concurrency: @@ -26,7 +27,7 @@ jobs: gate: if: github.event.pull_request.draft == false runs-on: ubuntu-latest - timeout-minutes: 10 + timeout-minutes: 50 env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" @@ -75,9 +76,20 @@ jobs: - name: Evaluate Evidence Gate env: + AI_REVIEW_CLOSED_LOOP: 'true' + GH_TOKEN: ${{ github.token }} GITHUB_BASE_REF: ${{ github.base_ref }} AI_SERVICE_RELEASE_READY: ${{ vars.AI_SERVICE_RELEASE_READY }} AI_SERVICE_URL: ${{ vars.AI_SERVICE_URL }} AI_SERVICE_AUDIENCE: ${{ vars.AI_SERVICE_AUDIENCE }} AI_SERVICE_REVIEWERS_JSON: ${{ vars.AI_SERVICE_REVIEWERS_JSON }} run: python scripts/gate_evidence_package.py + + - name: Archive advisory evidence verdicts + if: always() + uses: actions/upload-artifact@v7 + with: + name: evidence-review-verdicts-${{ github.run_id }}-${{ github.run_attempt }} + path: data/output/review_verdicts + if-no-files-found: warn + retention-days: 90 diff --git a/docs/advisory-review-disposition.md b/docs/advisory-review-disposition.md new file mode 100644 index 0000000..eb01a6f --- /dev/null +++ b/docs/advisory-review-disposition.md @@ -0,0 +1,9 @@ +# Advisory review disposition (draft) + +This draft opts the existing Evidence Gate and drift review into QPK's issue-only review disposition. Complete approvals archive JSON and close only the bot-owned review issue. Flags receive at most one deeper Dot task under existing grants. Owner alerts require confirmation, severe consensus or a three-cycle dead-man failure. Original evidence, release-readiness, role and promotion checks remain unchanged. No PR is auto-closed or merged by a review verdict. + +An independent scheduled/workflow-run monitor observes review-job failures/skips and missing scheduled drift cycles. Verdict artifacts are retained for 90 days. The extra review timeout allows the existing role calls and one deeper task; it adds no retries or model authority. GitHub issue writes use the current workflow token and never authorize trading or server access. + +Depends on the QPK advisory-review draft and promotion of its reviewed approved runtime wheel/reference. The monitor intentionally references that draft branch; pin the reviewed merge SHA during adoption. Missing existing model routes, grants, wheel admission or READY remain blockers. No secret, IAM, release variable, production wheel digest, live setting or trading actuator is changed. No cycle-bound pause is available here; severe consensus reports that gap. These drafts are not runtime acceptance. + +HK/Crypto branches are stacked on harden's role-name/READY drafts; those edits are preserved. No in-flight job worktree was modified. Tests use fakes; CI and workflow syntax must pass before review.