diff --git a/.github/workflows/hourly-nvidia-nim-review-repair.yml b/.github/workflows/hourly-nvidia-nim-review-repair.yml index 9eb450619..4f25c787f 100644 --- a/.github/workflows/hourly-nvidia-nim-review-repair.yml +++ b/.github/workflows/hourly-nvidia-nim-review-repair.yml @@ -14,6 +14,7 @@ on: - .github/workflows/governance-risk-compliance-hourly-review-repair.yml - .github/workflows/hourly-nvidia-nim-review-repair.yml - .github/workflows/nonnest2-hourly-review-repair.yml + - .github/workflows/naruon-hourly-review-repair.yml - .github/workflows/orgmetra-hourly-review-repair.yml - .github/workflows/originweave-hourly-review-repair.yml - .github/workflows/quarantine-sandbox-hourly-review-repair.yml @@ -26,6 +27,7 @@ on: - tests/test_governance_risk_compliance_hourly_review_caller.py - tests/test_hourly_scheduler_runtime_budget.py - tests/test_nonnest2_hourly_review_caller.py + - tests/test_naruon_hourly_review_caller.py - tests/test_orgmetra_hourly_review_caller.py - tests/test_originweave_hourly_review_caller.py - tests/test_quarantine_sandbox_hourly_review_caller.py @@ -51,6 +53,7 @@ on: - docs/doctoring/governance-risk-compliance-hourly-review-caller.md - docs/doctoring/hourly-nvidia-nim-autofix.md - docs/doctoring/nonnest2-hourly-review-caller.md + - docs/doctoring/naruon-hourly-review-caller.md - docs/doctoring/orgmetra-hourly-review-caller.md - docs/doctoring/originweave-hourly-review-caller.md - docs/doctoring/quarantine-sandbox-hourly-review-caller.md @@ -67,6 +70,7 @@ on: - .github/workflows/governance-risk-compliance-hourly-review-repair.yml - .github/workflows/hourly-nvidia-nim-review-repair.yml - .github/workflows/nonnest2-hourly-review-repair.yml + - .github/workflows/naruon-hourly-review-repair.yml - .github/workflows/orgmetra-hourly-review-repair.yml - .github/workflows/originweave-hourly-review-repair.yml - .github/workflows/quarantine-sandbox-hourly-review-repair.yml @@ -79,6 +83,7 @@ on: - tests/test_governance_risk_compliance_hourly_review_caller.py - tests/test_hourly_scheduler_runtime_budget.py - tests/test_nonnest2_hourly_review_caller.py + - tests/test_naruon_hourly_review_caller.py - tests/test_orgmetra_hourly_review_caller.py - tests/test_originweave_hourly_review_caller.py - tests/test_quarantine_sandbox_hourly_review_caller.py @@ -104,6 +109,7 @@ on: - docs/doctoring/governance-risk-compliance-hourly-review-caller.md - docs/doctoring/hourly-nvidia-nim-autofix.md - docs/doctoring/nonnest2-hourly-review-caller.md + - docs/doctoring/naruon-hourly-review-caller.md - docs/doctoring/orgmetra-hourly-review-caller.md - docs/doctoring/originweave-hourly-review-caller.md - docs/doctoring/quarantine-sandbox-hourly-review-caller.md @@ -161,6 +167,7 @@ jobs: tests/test_governance_risk_compliance_hourly_review_caller.py \ tests/test_hourly_scheduler_runtime_budget.py \ tests/test_nonnest2_hourly_review_caller.py \ + tests/test_naruon_hourly_review_caller.py \ tests/test_orgmetra_hourly_review_caller.py \ tests/test_originweave_hourly_review_caller.py \ tests/test_quarantine_sandbox_hourly_review_caller.py \ diff --git a/.github/workflows/naruon-hourly-review-repair.yml b/.github/workflows/naruon-hourly-review-repair.yml new file mode 100644 index 000000000..bed75ff92 --- /dev/null +++ b/.github/workflows/naruon-hourly-review-repair.yml @@ -0,0 +1,29 @@ +name: Naruon Hourly Review Repair + +on: + schedule: + # Minute 11 is reserved for naruon in the central hourly queue. + - cron: "11 * * * *" + +concurrency: + group: naruon-hourly-review-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/naruon + base_branch: develop + 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 2df633f49..7dbefba22 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -7,4 +7,5 @@ Materialize accepts only exact SHA-256 pins or a bounded relative `-r` include ( 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). +naruon hourly NVIDIA NIM repair is a thin caller at minute 11. See [`docs/doctoring/naruon-hourly-review-caller.md`](docs/doctoring/naruon-hourly-review-caller.md). The materialization contract is also covered by [`docs/doctoring/exact-artifact-sbom-attestation.md`](docs/doctoring/exact-artifact-sbom-attestation.md). diff --git a/docs/doctoring/naruon-hourly-review-caller.md b/docs/doctoring/naruon-hourly-review-caller.md new file mode 100644 index 000000000..d8685a93f --- /dev/null +++ b/docs/doctoring/naruon-hourly-review-caller.md @@ -0,0 +1,47 @@ +# naruon hourly review-repair caller + +검토 기준일: **2026-08-19** + +## Decision + +ContextualWisdomLab operates one protected hourly caller for +`ContextualWisdomLab/naruon`, the email-first knowledge-graph workspace. The +caller runs at minute 11, delegates to the product-neutral central review-fix +scheduler, inspects at most 50 open pull requests targeting protected +`develop`, and dispatches at most one bounded repair per heartbeat. + +The caller does not implement review or mutation logic. It keeps naruon +standalone while privileged automation remains in `ContextualWisdomLab/.github`. +The two-hour same-head retry floor prevents duplicate writer pressure when +OpenCode, security checks, or attachment-parser validation outlasts one +heartbeat. Queued checks and missing independent approval remain merge gates; +the repair worker cannot manufacture either result. + +## Credential and authority boundary + +The caller exposes only `contents: read` and job-scoped `id-token: write`. It +maps `PR_REVIEW_MERGE_TOKEN` and `OPENCODE_APPROVE_TOKEN` explicitly, and never +forwards `NVIDIA_NIM_API_KEY`, `COPILOT_GITHUB_TOKEN`, or `secrets: inherit`. +The reusable scheduler validates the exact target and dispatch authority before +materializing mutation credentials. + +## Verification and rollback + +Contract tests pin the minute 11 cadence, target repository, `develop` base, +single dispatch, two-hour retry floor, explicit secret scope, and central +quality-workflow path filters. Scheduled execution is the operational +acceptance check; source tests alone do not prove a protected-branch merge. + +Rollback removes this caller, its contract test, doctoring, and path-filter +entries. It does not change the reusable scheduler or other product callers. + +## APA 7th references + +GitHub, Inc. (n.d.). *Events that trigger workflows*. GitHub Docs. +https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows#schedule + +GitHub, Inc. (n.d.). *Reuse workflows*. GitHub Docs. +https://docs.github.com/en/actions/how-tos/sharing-automations/reuse-workflows + +GitHub, Inc. (n.d.). *Automatic token authentication*. GitHub Docs. +https://docs.github.com/en/actions/security-for-github-actions/security-guides/automatic-token-authentication 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 \ diff --git a/tests/test_naruon_hourly_review_caller.py b/tests/test_naruon_hourly_review_caller.py new file mode 100644 index 000000000..f8b36a8d1 --- /dev/null +++ b/tests/test_naruon_hourly_review_caller.py @@ -0,0 +1,73 @@ +"""Contract tests for naruon's bounded hourly review-repair caller.""" + +from pathlib import Path + + +CALLER = Path(".github/workflows/naruon-hourly-review-repair.yml") +DOCTORING = Path("docs/doctoring/naruon-hourly-review-caller.md") +QUALITY_WORKFLOW = Path(".github/workflows/hourly-nvidia-nim-review-repair.yml") + + +def _read(path: Path) -> str: + """Return one repository contract file as UTF-8 text.""" + return path.read_text(encoding="utf-8") + + +def test_naruon_caller_is_hourly_bounded_and_non_cancelling() -> None: + """naruon gets one protected-develop repair heartbeat per hour.""" + caller = _read(CALLER) + + assert 'cron: "11 * * * *"' in caller + assert "group: naruon-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/naruon" in caller + assert "base_branch: develop" in caller + assert 'max_prs: "50"' in caller + assert 'max_dispatches: "1"' in caller + assert 'retry_hours: "2"' in caller + + +def test_naruon_caller_keeps_token_and_secret_scope_explicit() -> None: + """The caller forwards only established scheduler credentials.""" + 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_naruon_contract_and_doctoring_are_path_filtered() -> None: + """The central contract gate follows caller and doctoring changes.""" + quality = _read(QUALITY_WORKFLOW) + for path in ( + ".github/workflows/naruon-hourly-review-repair.yml", + "docs/doctoring/naruon-hourly-review-caller.md", + ): + assert quality.count(path) == 2 + assert quality.count("tests/test_naruon_hourly_review_caller.py") == 3 + + doctoring = _read(DOCTORING) + for phrase in ( + "ContextualWisdomLab/naruon", + "minute 11", + "base", + "two-hour same-head retry floor", + "NVIDIA_NIM_API_KEY", + "COPILOT_GITHUB_TOKEN", + "independent approval", + ): + assert phrase in doctoring