From d3376a482e090c6f6803826ed8d7a782371e69b1 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 09:50:49 +0900 Subject: [PATCH 1/4] feat(automation): run Wardnet hourly NVIDIA NIM review repair Buyer-facing AI-SOC PRs on ContextualWisdomLab/wardnet#76 and #72 target main while hourly repair scanned other products. Add a minute-7 caller with job-scoped id-token: write, two-hour retry, and no COPILOT_GITHUB_TOKEN. --- .../hourly-nvidia-nim-review-repair.yml | 7 + .../wardnet-hourly-review-repair.yml | 31 ++++ AGENTS.md | 1 + ARCHITECTURE.md | 4 +- CHANGELOG.md | 2 + CLAUDE.md | 1 + docs/automation/hourly-review-repair.md | 19 ++ .../doctoring/wardnet-hourly-review-caller.md | 130 ++++++++++++++ tests/test_hourly_scheduler_runtime_budget.py | 5 +- tests/test_wardnet_hourly_review_caller.py | 163 ++++++++++++++++++ 10 files changed, 360 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/wardnet-hourly-review-repair.yml create mode 100644 docs/doctoring/wardnet-hourly-review-caller.md create mode 100644 tests/test_wardnet_hourly_review_caller.py diff --git a/.github/workflows/hourly-nvidia-nim-review-repair.yml b/.github/workflows/hourly-nvidia-nim-review-repair.yml index 702942708..a699f6b8b 100644 --- a/.github/workflows/hourly-nvidia-nim-review-repair.yml +++ b/.github/workflows/hourly-nvidia-nim-review-repair.yml @@ -13,6 +13,7 @@ on: - .github/workflows/github-hourly-review-repair.yml - .github/workflows/governance-risk-compliance-hourly-review-repair.yml - .github/workflows/hourly-nvidia-nim-review-repair.yml + - .github/workflows/wardnet-hourly-review-repair.yml - .github/workflows/nonnest2-hourly-review-repair.yml - .github/workflows/originweave-hourly-review-repair.yml - .github/workflows/quarantine-sandbox-hourly-review-repair.yml @@ -27,6 +28,7 @@ on: - tests/test_nonnest2_hourly_review_caller.py - tests/test_originweave_hourly_review_caller.py - tests/test_quarantine_sandbox_hourly_review_caller.py + - tests/test_wardnet_hourly_review_caller.py - tests/test_hourly_autofix_context_quality_gate.py - tests/test_pr_review_conflict_scope.py - tests/test_pr_review_conflict_scope_control_files.py @@ -51,6 +53,7 @@ on: - docs/doctoring/nonnest2-hourly-review-caller.md - docs/doctoring/originweave-hourly-review-caller.md - docs/doctoring/quarantine-sandbox-hourly-review-caller.md + - docs/doctoring/wardnet-hourly-review-caller.md push: paths: - .github/workflows/pr-review-fix-scheduler.yml @@ -63,6 +66,7 @@ on: - .github/workflows/github-hourly-review-repair.yml - .github/workflows/governance-risk-compliance-hourly-review-repair.yml - .github/workflows/hourly-nvidia-nim-review-repair.yml + - .github/workflows/wardnet-hourly-review-repair.yml - .github/workflows/nonnest2-hourly-review-repair.yml - .github/workflows/originweave-hourly-review-repair.yml - .github/workflows/quarantine-sandbox-hourly-review-repair.yml @@ -77,6 +81,7 @@ on: - tests/test_nonnest2_hourly_review_caller.py - tests/test_originweave_hourly_review_caller.py - tests/test_quarantine_sandbox_hourly_review_caller.py + - tests/test_wardnet_hourly_review_caller.py - tests/test_hourly_autofix_context_quality_gate.py - tests/test_pr_review_conflict_scope.py - tests/test_pr_review_conflict_scope_control_files.py @@ -101,6 +106,7 @@ on: - docs/doctoring/nonnest2-hourly-review-caller.md - docs/doctoring/originweave-hourly-review-caller.md - docs/doctoring/quarantine-sandbox-hourly-review-caller.md + - docs/doctoring/wardnet-hourly-review-caller.md permissions: contents: read @@ -157,6 +163,7 @@ jobs: tests/test_nonnest2_hourly_review_caller.py \ tests/test_originweave_hourly_review_caller.py \ tests/test_quarantine_sandbox_hourly_review_caller.py \ + tests/test_wardnet_hourly_review_caller.py \ tests/test_pr_review_conflict_scope_control_files.py \ tests/test_hourly_autofix_context_quality_gate.py \ tests/test_pr_review_conflict_scope_git_executable.py \ diff --git a/.github/workflows/wardnet-hourly-review-repair.yml b/.github/workflows/wardnet-hourly-review-repair.yml new file mode 100644 index 000000000..2a1258660 --- /dev/null +++ b/.github/workflows/wardnet-hourly-review-repair.yml @@ -0,0 +1,31 @@ +name: Wardnet Hourly Review Repair + +on: + schedule: + # Minute 7 avoids Clearfolio (23), DiskSage (37), fast-mlsirm (49), + # BandScope (53), naruon (11), Inkspan (47), and orchestrator (17). + - cron: "7 * * * *" + +concurrency: + group: wardnet-hourly-review-repair + # A later heartbeat must not cancel an in-flight SOC RCA or repair. + 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/wardnet + base_branch: main + max_prs: "50" + max_dispatches: "1" + retry_hours: "2" + secrets: + PR_REVIEW_MERGE_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN }} + OPENCODE_APPROVE_TOKEN: ${{ secrets.OPENCODE_APPROVE_TOKEN }} diff --git a/AGENTS.md b/AGENTS.md index bd6a96a11..e6aa2c8ce 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,6 +4,7 @@ > **Agents: read the master context FIRST.** Before any work, read [`docs/CWL-MASTER-CONTEXT.md`](docs/CWL-MASTER-CONTEXT.md) (mission · naruon-as-platform + inter-component UML · cross-cutting disciplines · conventions · roadmap · current state), the live **GitHub Project #1** (work/roadmap source of truth), the full spec **ContextualWisdomLab/naruon#974**, and operate the Project per [`docs/agent-github-project-protocol.md`](docs/agent-github-project-protocol.md). The repo/Project — not any private agent memory — is the source of truth. Materialize accepts only exact SHA-256 pins or a bounded relative `-r` include (no `.`/`..`); a lone `--require-hashes` directive is not trust evidence. See [`docs/doctoring/hourly-nvidia-nim-autofix.md`](docs/doctoring/hourly-nvidia-nim-autofix.md). +Wardnet's AI-SOC hourly caller scans `ContextualWisdomLab/wardnet` `main` via the product-neutral NVIDIA NIM scheduler (`NVIDIA_NIM_API_KEY` stays on the worker; never `COPILOT_GITHUB_TOKEN`). See [`docs/doctoring/wardnet-hourly-review-caller.md`](docs/doctoring/wardnet-hourly-review-caller.md). Conflict-scope roots fail closed when the immediate parent directory is a symbolic link. OriginWeave hourly NVIDIA NIM repair is a thin caller at minute 10. See [`docs/doctoring/originweave-hourly-review-caller.md`](docs/doctoring/originweave-hourly-review-caller.md). nonnest2 hourly NVIDIA NIM repair is a thin caller at minute 16. See [`docs/doctoring/nonnest2-hourly-review-caller.md`](docs/doctoring/nonnest2-hourly-review-caller.md). diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 3e2e70b58..955b8e557 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -123,4 +123,6 @@ trusted `uv` exporter is downloaded from the literal GitHub Releases URL for - [`docs/doctoring/hourly-nvidia-nim-autofix.md`](docs/doctoring/hourly-nvidia-nim-autofix.md) — current increment's repair-worker decision and APA 7th citations. - [`docs/doctoring/fast-mlsirm-hourly-review-caller.md`](docs/doctoring/fast-mlsirm-hourly-review-caller.md) - — product-specific psychometric repair heartbeat and scientific gates. \ No newline at end of file + — product-specific psychometric repair heartbeat and scientific gates. +- [`docs/doctoring/wardnet-hourly-review-caller.md`](docs/doctoring/wardnet-hourly-review-caller.md) + — Wardnet AI-SOC hourly NVIDIA NIM repair heartbeat. \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index fd1aebf43..e32ce00c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ Semantic Versioning where the repository publishes a release. - Added a dedicated Clearfolio hourly caller that invokes the product-neutral central scheduler with the exact repository, protected base branch, one-dispatch budget, one-hour retry floor, single-flight concurrency, and only the established scheduler credentials. - Added a dedicated DiskSage hourly caller that invokes the same product-neutral RCA and remediation-feasibility scheduler with an exact repository target, one-dispatch budget, two-hour same-head retry floor, non-cancelling single-flight heartbeat, and explicit established scheduler credentials. - Added a dedicated fast-mlsirm hourly caller that preserves Rust-owned psychometric arithmetic while dispatching at most one exact-head, root-cause-driven repair with a two-hour same-head retry floor. +- Added a dedicated Wardnet hourly caller that scans `ContextualWisdomLab/wardnet` `main` at minute 7 with one dispatch, a two-hour same-head retry floor, job-scoped `id-token: write` for the OpenCode OIDC fallback, and only the established scheduler credentials so AI-SOC pull requests receive NVIDIA NIM repair. ### Changed @@ -26,6 +27,7 @@ Semantic Versioning where the repository publishes a release. - Run the bounded Clearfolio PR review-feedback repair caller at minute 23 of every hour while keeping the shared scheduler free of product-specific timers and repository names for modular reuse by naruon, contextual-orchestrator, Inkspan, and other CWL services. - Run the bounded DiskSage repair heartbeat at minute 37 of every hour, dispatch no more than one exact-head repair, and wait two hours before redispatching an unchanged head so legitimate OpenCode or NVIDIA NIM latency does not create duplicate writers. - Run the bounded fast-mlsirm repair heartbeat at minute 49 of every hour with one-dispatch scope and a two-hour same-head floor, without weakening true-parameter recovery, CPU/GPU parity, skipped-test, or Rust-ownership gates. +- Run the bounded Wardnet AI-SOC repair heartbeat at minute 7 of every hour against protected `main`, dispatching at most one exact-head repair and waiting two hours before redispatching an unchanged head. - Use NVIDIA NIM `mistralai/mistral-small-4-119b-2603` with explicit high reasoning for scheduled repair and `nvidia/nemotron-3-nano-30b-a3b` for bounded helper work instead of GitHub Models in the write-capable autofix worker. - Apply one NUL-delimited exact-path and complete pre/post-worktree verification contract to both ordinary review repair and merge-conflict repair rather than relying on a visible post-model diff for the ordinary path. diff --git a/CLAUDE.md b/CLAUDE.md index d73a5c169..0e66cd16e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -69,6 +69,7 @@ Details: `README.md` and `PR_GOVERNANCE_AUDIT.md`. `scorecard-governance.md`, SBOM inventory. Doctoring records live under `docs/doctoring/`. [`ARCHITECTURE.md`](ARCHITECTURE.md) is the control-plane diagram for review, hourly NVIDIA NIM repair, and merge trust boundaries. + Wardnet's AI-SOC caller is `wardnet-hourly-review-repair.yml` (minute 7, `main`). - `.jules/` — recorded performance (`bolt.md`) and security (`sentinel.md`) learnings from past work on `scripts/ci/`; worth scanning before optimizing or hardening those scripts. diff --git a/docs/automation/hourly-review-repair.md b/docs/automation/hourly-review-repair.md index 7f15e42c3..e284b17d7 100644 --- a/docs/automation/hourly-review-repair.md +++ b/docs/automation/hourly-review-repair.md @@ -5,6 +5,8 @@ engine**. - `clearfolio-hourly-review-repair.yml` owns Clearfolio's heartbeat at minute 23 of every hour. +- `wardnet-hourly-review-repair.yml` owns Wardnet's AI-SOC heartbeat at minute 7 + of every hour. - `pr-review-fix-scheduler.yml` is the reusable, product-neutral scheduler module. It has no product-specific timer and can be called by naruon, contextual-orchestrator, Inkspan, or another CWL service with an explicit @@ -34,6 +36,23 @@ The scheduled heartbeat is `23 * * * *`. Repository-scoped concurrency and `cancel-in-progress: true` ensure that a superseded Clearfolio queue scan does not overlap its successor. At most one repair dispatch is created per run. +## Wardnet execution contract + +The Wardnet caller provides the following immutable operating parameters: + +```yaml +target_repository: ContextualWisdomLab/wardnet +base_branch: main +max_prs: "50" +max_dispatches: "1" +retry_hours: "2" +``` + +The scheduled heartbeat is `7 * * * *`. The caller job grants `id-token: write` +so the reusable NVIDIA NIM scheduler can mint its OpenCode App fallback from +GitHub OIDC. It does not receive or forward `NVIDIA_NIM_API_KEY` and never +introduces `COPILOT_GITHUB_TOKEN`. + 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 diff --git a/docs/doctoring/wardnet-hourly-review-caller.md b/docs/doctoring/wardnet-hourly-review-caller.md new file mode 100644 index 000000000..eb8076770 --- /dev/null +++ b/docs/doctoring/wardnet-hourly-review-caller.md @@ -0,0 +1,130 @@ +# Wardnet hourly review-repair caller + +검토 기준일: **2026-08-17** + +## Decision + +ContextualWisdomLab operates one protected hourly caller for +`ContextualWisdomLab/wardnet` (wardnet.io — WAF / IDS / AI SOC / software LB / +APIM). The caller runs at minute 7, delegates to the product-neutral central +review-fix scheduler, inspects at most 50 open pull requests targeting +protected `main`, and dispatches at most one bounded repair per heartbeat. + +A paying buyer of the AI SOC would feel live wardnet pull requests stalling +while hourly NVIDIA NIM repair scanned only Clearfolio, DiskSage, and +fast-mlsirm. Live heads such as ContextualWisdomLab/wardnet#76 (adaptive +orchestrator default) and #72 (externally provisioned admin secret) target +`main` and never enter those other callers. + +The caller does not implement review or mutation logic itself. Wardnet remains +standalone; naruon and noema consume its SOC verdicts without owning its +runtime. Privileged automation stays in `ContextualWisdomLab/.github`. + +## Root-cause analysis and remediation feasibility + +The reusable worker performs exact-head root-cause analysis and tests +remediation feasibility before it edits. The reusable worker must: + +1. Refetch the exact live head, base, reviews, checks, changed paths, and + writer state. +2. Establish the causal chain rather than repeat the terminal symptom. +3. Enumerate materially distinct minimal remedies. +4. Reject remedies that lack writer authority, cross sealed paths, require + unavailable credentials or protected-setting changes, violate stack order, + cannot be verified, or do not alter the diagnosed cause. +5. Dispatch at most one feasible repair. Otherwise leave the tree unchanged. + +A queued or pending check remains a merge blocker but is not itself a code +finding. The independent non-author approval remains an external authorization +gate and is never synthesized by the repair worker. The worker cannot approve, +merge, release, resolve review findings by inference, change protection, or +manufacture passing checks. + +## Cadence and concurrency + +The caller uses a single concurrency group and `cancel-in-progress: false`. +This preserves an in-flight bounded RCA instead of discarding SOC evidence when +the next hourly heartbeat arrives. The reusable scheduler cancels only its own +superseded short queue scan. + +The caller sets a **two-hour same-head retry floor**. Central OpenCode and +NVIDIA NIM work, plus wardnet's adaptive-orchestration analysis, can +legitimately approach two hours. An hourly redispatch of the same unchanged +head would create duplicate writer pressure rather than faster remediation. + +GitHub scheduled workflows can be delayed under load and execute only from the +default branch. The cron expression is a heartbeat, not a real-time SLA. + +## Credential and model boundary + +The caller keeps workflow `GITHUB_TOKEN` at `contents: read` and grants the +reusable job `id-token: write` so the central scheduler can mint the OpenCode +GitHub App token from GitHub OIDC when the mapped PAT is absent (GitHub, +n.d.-c). It maps only `PR_REVIEW_MERGE_TOKEN` and `OPENCODE_APPROVE_TOKEN`. +It never uses `secrets: inherit`, receives `NVIDIA_NIM_API_KEY`, or introduces +`COPILOT_GITHUB_TOKEN`. CWE-250 forbids executing the caller with write or +model privileges it does not need (MITRE, 2026). + +Model execution remains inside the central worker. The model credential is the +GitHub Secret `NVIDIA_NIM_API_KEY`; the caller does not receive or forward it. + +Before protected-main activation, the repository variable +`OPENCODE_REPOSITORY_DISPATCH_TARGETS` must contain the exact +`ContextualWisdomLab/wardnet` target. Missing or mismatched configuration +fails before mutation credential materialization. + +## Security, standalone operation, and modularity + +The caller adds no wardnet runtime dependency, database object, network +endpoint, tenant authority, or product credential. Wardnet continues to run as +a standalone AI SOC. Naruon, noema, and other CWL services may consume wardnet +verdicts, but they cannot weaken its local validation, protected-branch, +exact-head, approval, or security gates. + +## Verification and rollback + +Machine-checkable contracts require the exact target/base, minute 7 cadence, +non-cancelling single-flight group, one dispatch, two-hour retry floor, +explicit secret mapping, read-only contents plus job-scoped `id-token: write`, +focused path-filter coverage, and absence of model or Copilot credentials. +Independent `pull_request`, `push`, and `compileall` path blocks must each +name the caller, doctoring, or contract they own. + +After source integration, closure requires a scheduled or manual +protected-main consumer run proving the exact wardnet repository and `main` +base. Source checks alone are not protected-main operational acceptance. +Merge still requires zero unresolved valid findings and a qualifying +independent non-author approval. + +Rollback removes the wardnet caller, its focused test, doctoring, and central +path-filter/documentation entries. It must not remove scheduler dispatch +validation or affect independent product callers. + +## APA 7th references + +GitHub, Inc. (n.d.-a). *Events that trigger workflows*. GitHub Docs. Retrieved +August 17, 2026, from +https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows#schedule + +GitHub, Inc. (n.d.-b). *Reuse workflows*. GitHub Docs. Retrieved August 17, +2026, from +https://docs.github.com/en/actions/how-tos/sharing-automations/reuse-workflows + +GitHub, Inc. (n.d.-c). *Automatic token authentication*. GitHub Docs. +Retrieved August 17, 2026, from +https://docs.github.com/en/actions/security-for-github-actions/security-guides/automatic-token-authentication#permissions-for-the-github_token + +MITRE. (2026). *CWE-250: Execution with unnecessary privileges*. +https://cwe.mitre.org/data/definitions/250.html + +National Institute of Standards and Technology. (2022). *Secure software +development framework (SSDF) version 1.1: Recommendations for mitigating the +risk of software vulnerabilities* (NIST Special Publication 800-218). +https://doi.org/10.6028/NIST.SP.800-218 + +NVIDIA. (n.d.). *NVIDIA NIM for large language models documentation*. +Retrieved August 17, 2026, from +https://docs.nvidia.com/nim/large-language-models/latest/ + +OpenCode. (n.d.). *OpenCode documentation*. Retrieved August 17, 2026, from +https://opencode.ai/docs/ diff --git a/tests/test_hourly_scheduler_runtime_budget.py b/tests/test_hourly_scheduler_runtime_budget.py index eacf7eb55..9f03a9b58 100644 --- a/tests/test_hourly_scheduler_runtime_budget.py +++ b/tests/test_hourly_scheduler_runtime_budget.py @@ -6,6 +6,7 @@ REUSABLE = Path(".github/workflows/pr-review-fix-scheduler.yml") CLEARFOLIO = Path(".github/workflows/clearfolio-hourly-review-repair.yml") DISKSAGE = Path(".github/workflows/disksage-hourly-review-repair.yml") +WARDNET = Path(".github/workflows/wardnet-hourly-review-repair.yml") QUALITY = Path(".github/workflows/hourly-nvidia-nim-review-repair.yml") @@ -25,8 +26,8 @@ def test_queue_scanner_has_a_bounded_superseding_runtime() -> None: def test_product_callers_do_not_cancel_an_in_flight_rca() -> None: - """Clearfolio and DiskSage preserve the non-cancelling product lease.""" - for caller_path in (CLEARFOLIO, DISKSAGE): + """Clearfolio, DiskSage, and Wardnet preserve the non-cancelling product lease.""" + for caller_path in (CLEARFOLIO, DISKSAGE, WARDNET): caller = _read(caller_path) assert "cancel-in-progress: false" in caller assert "cancel-in-progress: true" not in caller diff --git a/tests/test_wardnet_hourly_review_caller.py b/tests/test_wardnet_hourly_review_caller.py new file mode 100644 index 000000000..ae714659d --- /dev/null +++ b/tests/test_wardnet_hourly_review_caller.py @@ -0,0 +1,163 @@ +"""Contract tests for Wardnet's bounded hourly review-repair caller.""" + +from pathlib import Path + + +CALLER = Path(".github/workflows/wardnet-hourly-review-repair.yml") +DOCTORING = Path("docs/doctoring/wardnet-hourly-review-caller.md") +QUALITY_WORKFLOW = Path(".github/workflows/hourly-nvidia-nim-review-repair.yml") +SCHEDULER = Path(".github/workflows/pr-review-fix-scheduler.yml") + + +def _read(path: Path) -> str: + """Return one repository contract file as UTF-8 text.""" + return path.read_text(encoding="utf-8") + + +def _yaml_path_entries(block: str) -> set[str]: + """Return dashed YAML path entries from one trigger or compileall block.""" + entries: set[str] = set() + for raw_line in block.splitlines(): + stripped = raw_line.strip() + if stripped.startswith("- "): + entries.add(stripped[2:].strip()) + elif stripped.startswith("tests/") or stripped.startswith("scripts/"): + entries.add(stripped.rstrip(" \\")) + return entries + + +def _trigger_path_block(quality: str, trigger: str) -> str: + """Return the dashed path list under one named workflow trigger.""" + marker = f" {trigger}:\n paths:\n" + start = quality.index(marker) + len(marker) + lines: list[str] = [] + for line in quality[start:].splitlines(): + if line.startswith(" - "): + lines.append(line) + continue + if line.strip() == "": + continue + break + return "\n".join(lines) + + +def _compileall_block(quality: str) -> str: + """Return the compileall argument list from the focused quality job.""" + marker = "python -m compileall -q \\" + start = quality.index(marker) + remainder = quality[start:] + end = remainder.find("\n git ") + return remainder if end < 0 else remainder[:end] + + +def test_wardnet_caller_is_hourly_bounded_and_non_cancelling() -> None: + """Wardnet receives one realistic SOC repair opportunity without cancellation.""" + caller = _read(CALLER) + + assert 'cron: "7 * * * *"' in caller + assert "group: wardnet-hourly-review-repair" in caller + assert "cancel-in-progress: false" in caller + assert "uses: ./.github/workflows/pr-review-fix-scheduler.yml" in caller + assert "target_repository: ContextualWisdomLab/wardnet" 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 + + +def test_wardnet_caller_preserves_oidc_and_explicit_secret_scope() -> None: + """The queue scanner maps established credentials without model secrets.""" + caller = _read(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 "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 "NVIDIA_NIM_API_KEY" not in caller + assert "COPILOT_GITHUB_TOKEN" not in caller + for forbidden in ( + "actions: write", + "contents: write", + "issues: write", + "pull-requests: write", + "statuses: write", + ): + assert forbidden not in caller + + +def test_wardnet_target_is_not_hard_coded_in_shared_scheduler() -> None: + """Product identity remains in the thin caller rather than the engine.""" + assert "ContextualWisdomLab/wardnet" not in _read(SCHEDULER) + + +def test_wardnet_doctoring_records_soc_activation_and_credentials() -> None: + """Operators retain target-allowlist, SOC, and approval prerequisites.""" + doctoring = _read(DOCTORING) + + for phrase in ( + "ContextualWisdomLab/wardnet", + "OPENCODE_REPOSITORY_DISPATCH_TARGETS", + "independent non-author approval", + "NVIDIA_NIM_API_KEY", + "COPILOT_GITHUB_TOKEN", + "id-token: write", + "two-hour same-head retry floor", + "root-cause analysis", + "remediation feasibility", + "protected-main operational acceptance", + "APA 7th references", + "ContextualWisdomLab/wardnet#76", + ): + assert phrase in doctoring + + +def test_path_block_helpers_keep_trigger_and_compileall_sets_disjoint() -> None: + """A path listed only under push or compileall must not satisfy pull_request.""" + quality = ( + "on:\n" + " pull_request:\n" + " paths:\n" + " - .github/workflows/wardnet-hourly-review-repair.yml\n" + " push:\n" + " paths:\n" + " - docs/doctoring/wardnet-hourly-review-caller.md\n" + " python -m compileall -q \\\n" + " tests/test_wardnet_hourly_review_caller.py\n" + " git diff --check\n" + ) + + pull_request_paths = _yaml_path_entries(_trigger_path_block(quality, "pull_request")) + push_paths = _yaml_path_entries(_trigger_path_block(quality, "push")) + compileall_paths = _yaml_path_entries(_compileall_block(quality)) + + assert pull_request_paths == {".github/workflows/wardnet-hourly-review-repair.yml"} + assert push_paths == {"docs/doctoring/wardnet-hourly-review-caller.md"} + assert compileall_paths == {"tests/test_wardnet_hourly_review_caller.py"} + assert "docs/doctoring/wardnet-hourly-review-caller.md" not in pull_request_paths + assert ".github/workflows/wardnet-hourly-review-repair.yml" not in compileall_paths + + +def test_focused_quality_workflow_tracks_wardnet_contracts() -> None: + """Caller, test, and doctoring edits always rerun the focused gate.""" + quality = _read(QUALITY_WORKFLOW) + pull_request_paths = _yaml_path_entries(_trigger_path_block(quality, "pull_request")) + push_paths = _yaml_path_entries(_trigger_path_block(quality, "push")) + compileall_paths = _yaml_path_entries(_compileall_block(quality)) + caller = ".github/workflows/wardnet-hourly-review-repair.yml" + doctoring = "docs/doctoring/wardnet-hourly-review-caller.md" + contract = "tests/test_wardnet_hourly_review_caller.py" + + assert caller in pull_request_paths + assert doctoring in pull_request_paths + assert contract in pull_request_paths + assert caller in push_paths + assert doctoring in push_paths + assert contract in push_paths + assert contract in compileall_paths + assert caller not in compileall_paths + assert doctoring not in compileall_paths From 9c752db19fa91b320a74da6c8bd0fbe6d03bce1e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 04:24:42 +0900 Subject: [PATCH 2/4] ci: refresh pip audit runtime --- requirements-pip-audit-ci-hashes.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements-pip-audit-ci-hashes.txt b/requirements-pip-audit-ci-hashes.txt index ade197a49..0ae099d8f 100644 --- a/requirements-pip-audit-ci-hashes.txt +++ b/requirements-pip-audit-ci-hashes.txt @@ -213,9 +213,9 @@ packaging==26.2 \ # via # pip-audit # pip-requirements-parser -pip==26.1.2 \ - --hash=sha256:382ff9f685ee3bc25864f820aa50505825f10f5458ffff07e30a6d96e5715cab \ - --hash=sha256:f49cd134c61cf2fd75e0ce2676db03e4054504a5a4986d00f8299ae632dc4605 +pip==26.2.1 \ + --hash=sha256:71138adf1f4ca900cdb7d289c21b7494329f2332b6d85f0e1c42108c0384ed3e \ + --hash=sha256:f6ad667e89a1fe78046c8f13232b247200f5258d7828f3f7883d660878e0813f # via pip-api pip-api==0.0.34 \ --hash=sha256:8b2d7d7c37f2447373aa2cf8b1f60a2f2b27a84e1e9e0294a3f6ef10eb3ba6bb \ From e1ad150f4f6f83dee1469d68c0d32a7b3f1bf946 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 27 Aug 2026 01:23:46 +0900 Subject: [PATCH 3/4] ci: restack Wardnet hourly repair caller --- .../workflows/wardnet-hourly-review-repair.yml | 5 ++--- ARCHITECTURE.md | 2 -- CHANGELOG.md | 1 - CLAUDE.md | 2 -- docs/automation/hourly-review-repair.md | 17 ----------------- docs/doctoring/wardnet-hourly-review-caller.md | 4 ++-- tests/test_hourly_scheduler_runtime_budget.py | 5 ++--- tests/test_wardnet_hourly_review_caller.py | 2 +- 8 files changed, 7 insertions(+), 31 deletions(-) diff --git a/.github/workflows/wardnet-hourly-review-repair.yml b/.github/workflows/wardnet-hourly-review-repair.yml index 2a1258660..2daf9eefc 100644 --- a/.github/workflows/wardnet-hourly-review-repair.yml +++ b/.github/workflows/wardnet-hourly-review-repair.yml @@ -2,9 +2,8 @@ name: Wardnet Hourly Review Repair on: schedule: - # Minute 7 avoids Clearfolio (23), DiskSage (37), fast-mlsirm (49), - # BandScope (53), naruon (11), Inkspan (47), and orchestrator (17). - - cron: "7 * * * *" + # Minute 46 avoids every existing sibling heartbeat. + - cron: "46 * * * *" concurrency: group: wardnet-hourly-review-repair diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index c88043df6..6310abcfe 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -171,7 +171,5 @@ resolver conflict. — import-only exact source dependencies for networkless coverage. - [`docs/doctoring/fast-mlsirm-hourly-review-caller.md`](docs/doctoring/fast-mlsirm-hourly-review-caller.md) — product-specific psychometric repair heartbeat and scientific gates. -- [`docs/doctoring/wardnet-hourly-review-caller.md`](docs/doctoring/wardnet-hourly-review-caller.md) - — Wardnet AI-SOC hourly NVIDIA NIM repair heartbeat. - [`docs/doctoring/exact-artifact-sbom-attestation.md`](docs/doctoring/exact-artifact-sbom-attestation.md) — current increment's attestation decision and APA 7th citations. diff --git a/CHANGELOG.md b/CHANGELOG.md index fd49ceeac..04f023e4a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -75,7 +75,6 @@ Semantic Versioning where the repository publishes a release. - Run the bounded Clearfolio PR review-feedback repair caller at minute 23 of every hour while keeping the shared scheduler free of product-specific timers and repository names for modular reuse by naruon, contextual-orchestrator, Inkspan, and other CWL services. - Run the bounded DiskSage repair heartbeat at minute 37 of every hour, dispatch no more than one exact-head repair, and wait two hours before redispatching an unchanged head so legitimate OpenCode or NVIDIA NIM latency does not create duplicate writers. - Run the bounded fast-mlsirm repair heartbeat at minute 49 of every hour with one-dispatch scope and a two-hour same-head floor, without weakening true-parameter recovery, CPU/GPU parity, skipped-test, or Rust-ownership gates. -- Run the bounded Wardnet AI-SOC repair heartbeat at minute 7 of every hour against protected `main`, dispatching at most one exact-head repair and waiting two hours before redispatching an unchanged head. - Use NVIDIA NIM `mistralai/mistral-small-4-119b-2603` with explicit high reasoning for scheduled repair and `nvidia/nemotron-3-nano-30b-a3b` for bounded helper work instead of GitHub Models in the write-capable autofix worker. - Apply one NUL-delimited exact-path and complete pre/post-worktree verification contract to both ordinary review repair and merge-conflict repair rather than relying on a visible post-model diff for the ordinary path. diff --git a/CLAUDE.md b/CLAUDE.md index cedc94489..4b32c05c1 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -74,8 +74,6 @@ Details: `docs/pr-review-and-merge-procedure.md` and `PR_GOVERNANCE_AUDIT.md`. - `docs/` — master context, Project protocol, `org-required-workflow-rollout.md`, `scorecard-governance.md`, SBOM inventory. Doctoring records live under `docs/doctoring/`. [`ARCHITECTURE.md`](ARCHITECTURE.md) is the control-plane - diagram for review, hourly NVIDIA NIM repair, and merge trust boundaries. - Wardnet's AI-SOC caller is `wardnet-hourly-review-repair.yml` (minute 7, `main`). diagram for review, hourly NVIDIA NIM repair, exact-artifact SBOM attestation, and merge trust boundaries. - `.jules/` — recorded performance (`bolt.md`) and security (`sentinel.md`) learnings from past work diff --git a/docs/automation/hourly-review-repair.md b/docs/automation/hourly-review-repair.md index 999126bb8..722724958 100644 --- a/docs/automation/hourly-review-repair.md +++ b/docs/automation/hourly-review-repair.md @@ -42,23 +42,6 @@ The scheduled heartbeat is `23 * * * *`. Repository-scoped concurrency and `cancel-in-progress: true` ensure that a superseded Clearfolio queue scan does not overlap its successor. At most one repair dispatch is created per run. -## Wardnet execution contract - -The Wardnet caller provides the following immutable operating parameters: - -```yaml -target_repository: ContextualWisdomLab/wardnet -base_branch: main -max_prs: "50" -max_dispatches: "1" -retry_hours: "2" -``` - -The scheduled heartbeat is `7 * * * *`. The caller job grants `id-token: write` -so the reusable NVIDIA NIM scheduler can mint its OpenCode App fallback from -GitHub OIDC. It does not receive or forward `NVIDIA_NIM_API_KEY` and never -introduces `COPILOT_GITHUB_TOKEN`. - 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 diff --git a/docs/doctoring/wardnet-hourly-review-caller.md b/docs/doctoring/wardnet-hourly-review-caller.md index eb8076770..8fcfc0695 100644 --- a/docs/doctoring/wardnet-hourly-review-caller.md +++ b/docs/doctoring/wardnet-hourly-review-caller.md @@ -6,7 +6,7 @@ ContextualWisdomLab operates one protected hourly caller for `ContextualWisdomLab/wardnet` (wardnet.io — WAF / IDS / AI SOC / software LB / -APIM). The caller runs at minute 7, delegates to the product-neutral central +APIM). The caller runs at minute 46, delegates to the product-neutral central review-fix scheduler, inspects at most 50 open pull requests targeting protected `main`, and dispatches at most one bounded repair per heartbeat. @@ -83,7 +83,7 @@ exact-head, approval, or security gates. ## Verification and rollback -Machine-checkable contracts require the exact target/base, minute 7 cadence, +Machine-checkable contracts require the exact target/base, minute 46 cadence, non-cancelling single-flight group, one dispatch, two-hour retry floor, explicit secret mapping, read-only contents plus job-scoped `id-token: write`, focused path-filter coverage, and absence of model or Copilot credentials. diff --git a/tests/test_hourly_scheduler_runtime_budget.py b/tests/test_hourly_scheduler_runtime_budget.py index b39547473..02b4fa05b 100644 --- a/tests/test_hourly_scheduler_runtime_budget.py +++ b/tests/test_hourly_scheduler_runtime_budget.py @@ -6,7 +6,6 @@ REUSABLE = Path(".github/workflows/pr-review-fix-scheduler.yml") CLEARFOLIO = Path(".github/workflows/clearfolio-hourly-review-repair.yml") DISKSAGE = Path(".github/workflows/disksage-hourly-review-repair.yml") -WARDNET = Path(".github/workflows/wardnet-hourly-review-repair.yml") QUALITY = Path(".github/workflows/hourly-nvidia-nim-review-repair.yml") @@ -26,8 +25,8 @@ def test_queue_scanner_has_a_bounded_superseding_runtime() -> None: def test_product_callers_do_not_cancel_an_in_flight_rca() -> None: - """Clearfolio, DiskSage, and Wardnet preserve the non-cancelling product lease.""" - for caller_path in (CLEARFOLIO, DISKSAGE, WARDNET): + """Clearfolio and DiskSage preserve the non-cancelling product lease.""" + for caller_path in (CLEARFOLIO, DISKSAGE): caller = _read(caller_path) assert "cancel-in-progress: false" in caller assert "cancel-in-progress: true" not in caller diff --git a/tests/test_wardnet_hourly_review_caller.py b/tests/test_wardnet_hourly_review_caller.py index ae714659d..a6017ffaf 100644 --- a/tests/test_wardnet_hourly_review_caller.py +++ b/tests/test_wardnet_hourly_review_caller.py @@ -54,7 +54,7 @@ def test_wardnet_caller_is_hourly_bounded_and_non_cancelling() -> None: """Wardnet receives one realistic SOC repair opportunity without cancellation.""" caller = _read(CALLER) - assert 'cron: "7 * * * *"' in caller + assert 'cron: "46 * * * *"' in caller assert "group: wardnet-hourly-review-repair" in caller assert "cancel-in-progress: false" in caller assert "uses: ./.github/workflows/pr-review-fix-scheduler.yml" in caller From 79b50c45dba7f26751e4b967b1b0f4fb9284ec9b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 27 Aug 2026 03:23:54 +0900 Subject: [PATCH 4/4] fix(automation): reserve Wardnet minute 46 org-wide --- .github/workflows/afipc-hourly-review-repair.yml | 4 ++-- .github/workflows/nonnest2-hourly-review-repair.yml | 4 ++-- .../workflows/originweave-hourly-review-repair.yml | 4 ++-- tests/test_wardnet_hourly_review_caller.py | 13 +++++++++++++ 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/.github/workflows/afipc-hourly-review-repair.yml b/.github/workflows/afipc-hourly-review-repair.yml index 3191e5ea0..e689131a3 100644 --- a/.github/workflows/afipc-hourly-review-repair.yml +++ b/.github/workflows/afipc-hourly-review-repair.yml @@ -3,13 +3,13 @@ name: aFIPC Hourly Review Repair on: schedule: # Minute 2 avoids pg-llm-batch (1), kaefa (3), LineageWeave (4), - # codec-carver (5), life-os (6), Wardnet (7), mightyETL (8), + # codec-carver (5), life-os (6), mightyETL (8), # psychometrics-commons (9), OriginWeave (10), naruon (11), # DiagramWeave (12), pg-erd-cloud (13), mhtml-etl-gateway (14), # html4tree (15), nonnest2 (16), orchestrator (17), newsdom-api (18), # noema (19), github (21), Clearfolio (23), accounting-information-platform (27), # Keyverse (29), Scopeweave (31), contextual-orchestrator (34), DiskSage (37), Appguardrail (41), - # governance-risk-compliance (43), fast-mlsirm (49), BandScope (53), + # governance-risk-compliance (43), Wardnet (46), fast-mlsirm (49), BandScope (53), # Inkspan (56), orgmetra (58), and semantic-data-portal (59). - cron: "2 * * * *" diff --git a/.github/workflows/nonnest2-hourly-review-repair.yml b/.github/workflows/nonnest2-hourly-review-repair.yml index 2ed0d5fdf..45b216423 100644 --- a/.github/workflows/nonnest2-hourly-review-repair.yml +++ b/.github/workflows/nonnest2-hourly-review-repair.yml @@ -3,12 +3,12 @@ name: nonnest2 Hourly Review Repair on: schedule: # Minute 16 avoids pg-llm-batch (1), aFIPC (2), kaefa (3), LineageWeave (4), - # codec-carver (5), life-os (6), Wardnet (7), mightyETL (8), + # codec-carver (5), life-os (6), mightyETL (8), # psychometrics-commons (9), OriginWeave (10), naruon (11), # DiagramWeave (12), pg-erd-cloud (13), mhtml-etl-gateway (14), # html4tree (15), orchestrator (17), noema (19), Clearfolio (23), # Keyverse (29), Scopeweave (31), contextual-orchestrator (34), DiskSage (37), Appguardrail (41), - # newsdom-api (43), fast-mlsirm (49), BandScope (53), Inkspan (56), + # newsdom-api (43), Wardnet (46), fast-mlsirm (49), BandScope (53), Inkspan (56), # and semantic-data-portal (59). - cron: "16 * * * *" diff --git a/.github/workflows/originweave-hourly-review-repair.yml b/.github/workflows/originweave-hourly-review-repair.yml index 7473afdb1..ef9043634 100644 --- a/.github/workflows/originweave-hourly-review-repair.yml +++ b/.github/workflows/originweave-hourly-review-repair.yml @@ -3,10 +3,10 @@ name: OriginWeave Hourly Review Repair on: schedule: # Minute 10 avoids pg-llm-batch (1), aFIPC (2), kaefa (3), LineageWeave (4), - # codec-carver (5), life-os (6), Wardnet (7), mightyETL (8), + # codec-carver (5), life-os (6), mightyETL (8), # psychometrics-commons (9), naruon (11), pg-erd-cloud (13), # orchestrator (17), noema (19), Clearfolio (23), Keyverse (29), - # Scopeweave (31), contextual-orchestrator (34), DiskSage (37), Appguardrail (41), newsdom-api (43), + # Scopeweave (31), contextual-orchestrator (34), DiskSage (37), Appguardrail (41), newsdom-api (43), Wardnet (46), # fast-mlsirm (49), BandScope (53), Inkspan (56), and # semantic-data-portal (59). - cron: "10 * * * *" diff --git a/tests/test_wardnet_hourly_review_caller.py b/tests/test_wardnet_hourly_review_caller.py index a6017ffaf..0d10d8ebb 100644 --- a/tests/test_wardnet_hourly_review_caller.py +++ b/tests/test_wardnet_hourly_review_caller.py @@ -7,6 +7,11 @@ DOCTORING = Path("docs/doctoring/wardnet-hourly-review-caller.md") QUALITY_WORKFLOW = Path(".github/workflows/hourly-nvidia-nim-review-repair.yml") SCHEDULER = Path(".github/workflows/pr-review-fix-scheduler.yml") +RESERVATION_MAPS = ( + Path(".github/workflows/afipc-hourly-review-repair.yml"), + Path(".github/workflows/nonnest2-hourly-review-repair.yml"), + Path(".github/workflows/originweave-hourly-review-repair.yml"), +) def _read(path: Path) -> str: @@ -65,6 +70,14 @@ def test_wardnet_caller_is_hourly_bounded_and_non_cancelling() -> None: assert 'retry_hours: "2"' in caller +def test_org_reservation_maps_assign_wardnet_minute_46() -> None: + """Duplicated sibling maps retain Wardnet's collision-free minute.""" + for path in RESERVATION_MAPS: + reservation_map = _read(path) + assert "Wardnet (46)" in reservation_map + assert "Wardnet (7)" not in reservation_map + + def test_wardnet_caller_preserves_oidc_and_explicit_secret_scope() -> None: """The queue scanner maps established credentials without model secrets.""" caller = _read(CALLER)