From 1dc5c90bf8153969587a2bb7182b2e248e3f76cf Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 25 Aug 2026 21:59:36 +0900 Subject: [PATCH 1/4] feat(inkspan): add protected hourly review-repair caller at minute 56 Delegates to the product-neutral pr-review-fix-scheduler with the shared two-hour same-head writer floor; bounded to 50 open PRs and one repair per heartbeat. Doctoring record documents parity rationale and minute selection. --- .../inkspan-hourly-review-repair.yml | 32 +++++++++++++ .../doctoring/inkspan-hourly-review-caller.md | 47 +++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 .github/workflows/inkspan-hourly-review-repair.yml create mode 100644 docs/doctoring/inkspan-hourly-review-caller.md diff --git a/.github/workflows/inkspan-hourly-review-repair.yml b/.github/workflows/inkspan-hourly-review-repair.yml new file mode 100644 index 0000000000..2da6ec23fa --- /dev/null +++ b/.github/workflows/inkspan-hourly-review-repair.yml @@ -0,0 +1,32 @@ +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 + 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/docs/doctoring/inkspan-hourly-review-caller.md b/docs/doctoring/inkspan-hourly-review-caller.md new file mode 100644 index 0000000000..3d643efd84 --- /dev/null +++ b/docs/doctoring/inkspan-hourly-review-caller.md @@ -0,0 +1,47 @@ +# 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 inkspan#299 (stacked-PR CI +gates), 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 avoids every existing hourly heartbeat minute (2, 10, 14, 16, 21, +23, 27, 37, 43, 49, 53, 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. From 813401fec54584de328a52f7f440a44200f5ffd7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 26 Aug 2026 08:43:07 +0900 Subject: [PATCH 2/4] fix(inkspan): wire caller into focused quality gate --- .../hourly-nvidia-nim-review-repair.yml | 7 ++ .../inkspan-hourly-review-repair.yml | 3 + .../doctoring/inkspan-hourly-review-caller.md | 18 ++++- tests/test_inkspan_hourly_review_caller.py | 77 +++++++++++++++++++ 4 files changed, 102 insertions(+), 3 deletions(-) create mode 100644 tests/test_inkspan_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 add6d70c23..f63b1ce5a0 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 index 2da6ec23fa..835369fed2 100644 --- a/.github/workflows/inkspan-hourly-review-repair.yml +++ b/.github/workflows/inkspan-hourly-review-repair.yml @@ -18,6 +18,9 @@ permissions: 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 diff --git a/docs/doctoring/inkspan-hourly-review-caller.md b/docs/doctoring/inkspan-hourly-review-caller.md index 3d643efd84..8546d8b1f9 100644 --- a/docs/doctoring/inkspan-hourly-review-caller.md +++ b/docs/doctoring/inkspan-hourly-review-caller.md @@ -13,8 +13,8 @@ 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 inkspan#299 (stacked-PR CI -gates), inkspan#362 (editor contrast and keyboard focus), and the +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. @@ -33,7 +33,7 @@ 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 avoids every existing hourly heartbeat minute (2, 10, 14, 16, 21, +Minute 56 avoids every existing hourly heartbeat minute (2, 7, 10, 14, 16, 21, 23, 27, 37, 43, 49, 53, 58) so runner capacity is not contested at dispatch time. @@ -45,3 +45,15 @@ time. 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 0000000000..fb8f903694 --- /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 From 8c916dce07fa6dcecc1a8cfea752fc7aa2a0ed5a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 26 Aug 2026 08:44:15 +0900 Subject: [PATCH 3/4] fix(inkspan): reconcile hourly cadence ledger --- .github/workflows/afipc-hourly-review-repair.yml | 2 +- .github/workflows/nonnest2-hourly-review-repair.yml | 2 +- .github/workflows/originweave-hourly-review-repair.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/afipc-hourly-review-repair.yml b/.github/workflows/afipc-hourly-review-repair.yml index a9c0608433..24ac708137 100644 --- a/.github/workflows/afipc-hourly-review-repair.yml +++ b/.github/workflows/afipc-hourly-review-repair.yml @@ -9,7 +9,7 @@ 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), + # governance-risk-compliance (43), fast-mlsirm (49), Inkspan (56), # BandScope (53), 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 1b9fbfdb68..2ed0d5fdf0 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 c81f501270..7473afdb15 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 * * * *" From 7d4440ca6c2e83fbb502b891125093a60385ce91 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 26 Aug 2026 08:50:18 +0900 Subject: [PATCH 4/4] docs(inkspan): correct heartbeat-minute ledger per Devin review - Doctoring: occupied minutes are 2, 10, 14, 16, 21, 23, 27, 34, 37, 43, 49, 53, 58 (minute 7 has no caller; minute 34 was missing; 56 is this caller) - afipc collision comment: restore ascending minute order (BandScope 53 before Inkspan 56) --- .github/workflows/afipc-hourly-review-repair.yml | 4 ++-- docs/doctoring/inkspan-hourly-review-caller.md | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/afipc-hourly-review-repair.yml b/.github/workflows/afipc-hourly-review-repair.yml index 24ac708137..3191e5ea03 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), fast-mlsirm (49), Inkspan (56), - # 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/docs/doctoring/inkspan-hourly-review-caller.md b/docs/doctoring/inkspan-hourly-review-caller.md index 8546d8b1f9..21417c28a4 100644 --- a/docs/doctoring/inkspan-hourly-review-caller.md +++ b/docs/doctoring/inkspan-hourly-review-caller.md @@ -33,9 +33,9 @@ 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 avoids every existing hourly heartbeat minute (2, 7, 10, 14, 16, 21, -23, 27, 37, 43, 49, 53, 58) so runner capacity is not contested at dispatch -time. +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