diff --git a/.github/workflows/afipc-hourly-review-repair.yml b/.github/workflows/afipc-hourly-review-repair.yml index a9c060843..3191e5ea0 100644 --- a/.github/workflows/afipc-hourly-review-repair.yml +++ b/.github/workflows/afipc-hourly-review-repair.yml @@ -9,8 +9,8 @@ on: # 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), Inkspan (47), fast-mlsirm (49), - # BandScope (53), orgmetra (58), and semantic-data-portal (59). + # governance-risk-compliance (43), fast-mlsirm (49), BandScope (53), + # Inkspan (56), orgmetra (58), and semantic-data-portal (59). - cron: "2 * * * *" concurrency: diff --git a/.github/workflows/hourly-nvidia-nim-review-repair.yml b/.github/workflows/hourly-nvidia-nim-review-repair.yml index add6d70c2..f63b1ce5a 100644 --- a/.github/workflows/hourly-nvidia-nim-review-repair.yml +++ b/.github/workflows/hourly-nvidia-nim-review-repair.yml @@ -10,6 +10,7 @@ on: - .github/workflows/contextual-orchestrator-hourly-review-repair.yml - .github/workflows/clearfolio-hourly-review-repair.yml - .github/workflows/disksage-hourly-review-repair.yml + - .github/workflows/inkspan-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 @@ -23,6 +24,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_inkspan_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 @@ -50,6 +52,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/inkspan-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 @@ -69,6 +72,7 @@ on: - .github/workflows/contextual-orchestrator-hourly-review-repair.yml - .github/workflows/clearfolio-hourly-review-repair.yml - .github/workflows/disksage-hourly-review-repair.yml + - .github/workflows/inkspan-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 @@ -82,6 +86,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_inkspan_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 @@ -109,6 +114,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/inkspan-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 @@ -168,6 +174,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_inkspan_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 \ diff --git a/.github/workflows/inkspan-hourly-review-repair.yml b/.github/workflows/inkspan-hourly-review-repair.yml new file mode 100644 index 000000000..835369fed --- /dev/null +++ b/.github/workflows/inkspan-hourly-review-repair.yml @@ -0,0 +1,35 @@ +name: Inkspan Hourly Review Repair + +on: + schedule: + # Minute 56 avoids every existing hourly heartbeat minute and the + # half-hourly merge scheduler ticks. + - cron: "56 * * * *" + +concurrency: + group: inkspan-hourly-review-repair + # The queue scan is bounded and the worker has its own exact-head lease. Do not + # discard an in-flight RCA merely because the next hourly heartbeat arrives. + cancel-in-progress: false + +permissions: + contents: read + +jobs: + dispatch-review-repair: + uses: ./.github/workflows/pr-review-fix-scheduler.yml + permissions: + contents: read + id-token: write + with: + target_repository: ContextualWisdomLab/inkspan + base_branch: main + max_prs: "50" + max_dispatches: "1" + # Central OpenCode/NVIDIA NIM work can legitimately approach two hours. + # A two-hour same-head floor avoids duplicate writers without freezing the + # next eligible PR or confusing provider latency with a source-code defect. + retry_hours: "2" + secrets: + PR_REVIEW_MERGE_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN }} + OPENCODE_APPROVE_TOKEN: ${{ secrets.OPENCODE_APPROVE_TOKEN }} diff --git a/.github/workflows/nonnest2-hourly-review-repair.yml b/.github/workflows/nonnest2-hourly-review-repair.yml index 1b9fbfdb6..2ed0d5fdf 100644 --- a/.github/workflows/nonnest2-hourly-review-repair.yml +++ b/.github/workflows/nonnest2-hourly-review-repair.yml @@ -8,7 +8,7 @@ on: # 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), Inkspan (47), fast-mlsirm (49), BandScope (53), + # newsdom-api (43), 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 c81f50127..7473afdb1 100644 --- a/.github/workflows/originweave-hourly-review-repair.yml +++ b/.github/workflows/originweave-hourly-review-repair.yml @@ -7,7 +7,7 @@ on: # 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), - # Inkspan (47), fast-mlsirm (49), BandScope (53), and + # fast-mlsirm (49), BandScope (53), Inkspan (56), and # semantic-data-portal (59). - cron: "10 * * * *" diff --git a/docs/doctoring/inkspan-hourly-review-caller.md b/docs/doctoring/inkspan-hourly-review-caller.md new file mode 100644 index 000000000..21417c28a --- /dev/null +++ b/docs/doctoring/inkspan-hourly-review-caller.md @@ -0,0 +1,59 @@ +# Inkspan hourly review-repair caller + +검토 기준일: **2026-08-25** + +## Decision + +ContextualWisdomLab operates one protected hourly caller for +`ContextualWisdomLab/inkspan` (deterministic Markdown/HTML authoring and +bounded document/evidence contracts). The caller runs at minute 56, 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 commercial-grade editor tooling would feel live Inkspan pull +requests stalling while the hourly NVIDIA NIM repair loop scanned only other +products in the organization. Live heads such as ContextualWisdomLab/inkspan#299 +(stacked-PR CI gates), ContextualWisdomLab/inkspan#362 (editor contrast and keyboard focus), and the +writing-diagnostics stack sit in exactly that blind spot when their checks are +green but central review evidence is missing or stale. + +The caller does not implement review or mutation logic itself. Inkspan remains +standalone and embeddable; hosts consume `@contextualwisdomlab/cwl-editor` +without owning privileged automation. All mutation authority stays sealed in +`ContextualWisdomLab/.github` behind `PR_REVIEW_MERGE_TOKEN` / +`OPENCODE_APPROVE_TOKEN`. + +## Root-cause analysis and remediation feasibility + +The reusable worker performs exact-head root-cause analysis, tests remediation +feasibility, and edits only when one small reversible action can change the +diagnosed cause inside its sealed writer authority. It follows the shared +transitions documented in the central scheduler contract: refetch live state, +establish the causal chain, enumerate materially distinct minimal remedies, +reject infeasible ones, and dispatch at most one repair per heartbeat. + +Minute 56 (this caller) shares no heartbeat minute with any other hourly +caller: the occupied minutes are 2, 10, 14, 16, 21, 23, 27, 34, 37, 43, 49, +53, and 58, so runner capacity is not contested at dispatch time. + +## Consequences + +- Inkspan gains parity with disksage, nonnest2, Clearfolio, afipc, and + fast-mlsirm for bounded hourly unattended review-repair throughput. +- The two-hour same-head retry floor prevents duplicate writers while keeping + unrelated PR lanes moving every hour. +- No COPILOT_GITHUB_TOKEN is used; existing review-agent key chains are + untouched. +- The reusable job receives `id-token: write` and `contents: read`, enabling + its documented OIDC fallback while preserving least privilege. + +The protected merge path still requires an independent non-author approval at +the exact head. The workflow forwards the existing `PR_REVIEW_MERGE_TOKEN` and +`OPENCODE_APPROVE_TOKEN`; the NVIDIA model credential is supplied by the +central worker as `NVIDIA_NIM_API_KEY` and is never embedded in this caller. + +## APA 7th references + +GitHub. (n.d.). *OpenID Connect in GitHub Actions*. Retrieved August 25, 2026, +from https://docs.github.com/en/actions/security-for-github-actions/security-hardening-your-deployments/about-security-hardening-with-openid-connect diff --git a/tests/test_inkspan_hourly_review_caller.py b/tests/test_inkspan_hourly_review_caller.py new file mode 100644 index 000000000..fb8f90369 --- /dev/null +++ b/tests/test_inkspan_hourly_review_caller.py @@ -0,0 +1,77 @@ +"""Contract tests for Inkspan's bounded hourly review-repair caller.""" + +from pathlib import Path + + +CALLER = Path(".github/workflows/inkspan-hourly-review-repair.yml") +DOCTORING = Path("docs/doctoring/inkspan-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_inkspan_caller_is_hourly_bounded_and_non_cancelling() -> None: + """Inkspan receives one bounded repair opportunity without overlap cancellation.""" + caller = _read(CALLER) + + assert 'cron: "56 * * * *"' in caller + assert "group: inkspan-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/inkspan" 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_inkspan_caller_preserves_credentials_and_oidc_scope() -> None: + """The caller grants only reusable-worker read/OIDC permissions.""" + 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_inkspan_doctoring_records_governance_and_research_bounds() -> None: + """Operators retain RCA, credential, approval, and citation contracts.""" + doctoring = _read(DOCTORING) + + for phrase in ( + "root-cause analysis", + "remediation feasibility", + "two-hour same-head retry floor", + "independent non-author approval", + "NVIDIA_NIM_API_KEY", + "COPILOT_GITHUB_TOKEN", + "ContextualWisdomLab/inkspan#299", + "ContextualWisdomLab/inkspan#362", + "APA 7th references", + ): + assert phrase in doctoring + + +def test_focused_quality_workflow_tracks_inkspan_caller_contracts() -> None: + """Caller and doctoring edits rerun exact-head scheduler verification.""" + quality = _read(QUALITY_WORKFLOW) + + assert quality.count(".github/workflows/inkspan-hourly-review-repair.yml") == 2 + assert quality.count("docs/doctoring/inkspan-hourly-review-caller.md") == 2 + assert quality.count("tests/test_inkspan_hourly_review_caller.py") == 3