From a9478aae5d263eaafcd138750f9e72ddd61fbf4a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 6 Aug 2026 10:59:05 +0900 Subject: [PATCH 1/9] feat(automation): add Inkspan hourly review caller --- .../inkspan-hourly-review-repair.yml | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 .github/workflows/inkspan-hourly-review-repair.yml diff --git a/.github/workflows/inkspan-hourly-review-repair.yml b/.github/workflows/inkspan-hourly-review-repair.yml new file mode 100644 index 000000000..938a87bb1 --- /dev/null +++ b/.github/workflows/inkspan-hourly-review-repair.yml @@ -0,0 +1,33 @@ +name: Inkspan Hourly Review Repair + +on: + schedule: + # Offset from other product heartbeats to reduce shared-runner congestion. + - cron: "37 * * * *" + workflow_dispatch: + +concurrency: + group: inkspan-hourly-review-repair + cancel-in-progress: true + +permissions: + contents: read + +jobs: + dispatch-review-repair: + permissions: + actions: write + contents: read + issues: write + pull-requests: read + statuses: read + uses: ./.github/workflows/pr-review-fix-scheduler.yml + with: + target_repository: ContextualWisdomLab/inkspan + base_branch: main + max_prs: "50" + max_dispatches: "1" + retry_hours: "1" + secrets: + PR_REVIEW_MERGE_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN }} + OPENCODE_APPROVE_TOKEN: ${{ secrets.OPENCODE_APPROVE_TOKEN }} From 8e61271b5c109c895caab6685127a30409b8bf22 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 6 Aug 2026 11:00:01 +0900 Subject: [PATCH 2/9] docs(doctoring): record Inkspan hourly caller boundary --- .../doctoring/inkspan-hourly-review-caller.md | 129 ++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 docs/doctoring/inkspan-hourly-review-caller.md diff --git a/docs/doctoring/inkspan-hourly-review-caller.md b/docs/doctoring/inkspan-hourly-review-caller.md new file mode 100644 index 000000000..3145394ed --- /dev/null +++ b/docs/doctoring/inkspan-hourly-review-caller.md @@ -0,0 +1,129 @@ +# Inkspan Hourly Review-Repair Caller Boundary + +## Decision + +Inkspan's one-hour review → repair → revalidation support heartbeat is owned by +a dedicated central caller workflow, +`.github/workflows/inkspan-hourly-review-repair.yml`. The product-neutral engine +remains `.github/workflows/pr-review-fix-scheduler.yml`; it contains no Inkspan +repository literal and no product-specific schedule. + +This split preserves both deployment forms required by CWL. Inkspan remains a +standalone product repository, while the central `.github` control plane owns +shared repair orchestration that naruon and other products may reuse without +copying privileged workflow logic. A scheduled workflow executes in the +repository that contains it, so the caller must name `ContextualWisdomLab/inkspan` +explicitly rather than relying on the central repository as an implicit target. + +## Product caller + +The Inkspan caller runs at minute 37 of every hour. The offset separates its +heartbeat from the Clearfolio caller and reduces avoidable shared-runner bursts. +It invokes the local reusable workflow with explicit, reviewable values: + +```yaml +target_repository: ContextualWisdomLab/inkspan +base_branch: main +max_prs: "50" +max_dispatches: "1" +retry_hours: "1" +``` + +The caller and reusable engine both use `cancel-in-progress: true`. Queue +inspection is therefore single-flight at both the product and engine boundary. +One invocation may dispatch at most one autofix, and the same exact PR head is +not retried more often than once per hour. + +## Modular MSA contract + +The caller contains product identity and cadence only. The reusable scheduler +continues to own PR inventory, exact-head retry bookkeeping, dispatch bounds, +and the handoff to the separately reviewed repair plane. Inkspan does not copy +OpenCode configuration, reviewer identities, model credentials, merge policy, +or branch-update logic into its product repository. + +This architecture allows Inkspan to run independently while remaining suitable +for naruon `compose` and `ui.panel` integration. Product integration changes do +not alter the scheduler's security or credential boundary, and scheduler +changes do not add a runtime dependency to Inkspan. + +## Credential and privilege boundary + +The caller passes exactly the two established optional scheduler credentials: + +- `PR_REVIEW_MERGE_TOKEN`; +- `OPENCODE_APPROVE_TOKEN`. + +It does not use `secrets: inherit`. It does not receive +`NVIDIA_NIM_API_KEY`, because queue inspection and workflow dispatch are not +model execution. The NVIDIA credential remains scoped only to the two model +execution steps in the separately reviewed `PR Review Autofix` workflow. +`COPILOT_GITHUB_TOKEN` and GitHub Models are not introduced. + +Workflow scope is read-only. The single reusable-workflow job receives only the +required Actions and Issues write permissions plus read access to Contents, +Pull Requests, and Statuses. Omitted scopes remain unavailable. No sibling job +inherits write authority. + +The repair worker cannot approve a PR, merge a PR, publish a release, alter a +reviewer credential chain, weaken branch protection, or convert a failed or +missing check into success. + +## Failure behavior + +Missing scheduler credentials cause target inspection or dispatch to fail +closed; they do not redirect work to `.github`. A missing NVIDIA credential +later stops the repair worker before model execution. Neither case weakens +independent review, required checks, unresolved-thread policy, or branch +protection. + +Scheduled workflows become active only from the protected default branch. This +caller is not production automation while its stacked pull request or its +prerequisite scheduler PR remains unmerged. + +## Verification contract + +Permanent tests require all of the following: + +1. the Inkspan caller contains the exact hourly cron; +2. the caller invokes the local reusable scheduler; +3. `ContextualWisdomLab/inkspan` and protected `main` are explicit; +4. dispatch and same-head retry bounds remain one; +5. caller concurrency remains single-flight; +6. only the two established scheduler secrets cross the caller boundary; +7. `secrets: inherit`, `COPILOT_GITHUB_TOKEN`, direct NVIDIA credential binding, + approval, merge, release, and protection mutation are absent; +8. workflow scope remains read-only and required write permissions are confined + to the reusable-scheduler job; and +9. the caller remains independent from the Clearfolio caller while sharing the + same product-neutral engine. + +Repository acceptance still requires exact-current-head workflow, security, +supply-chain, automated-review, independent-review, unresolved-thread, and +branch-protection evidence. + +## Rollback + +Rollback removes only the Inkspan caller, its static contract, doctoring, and +changelog entry. It leaves the reusable scheduler, Clearfolio caller, OpenCode +repair workflow, Noema/OpenCode reviewer identities, and credential chains +unchanged. Rollback must not replace the explicit target with the central +repository fallback or copy privileged scheduler implementation into Inkspan. + +## References (APA 7th edition) + +GitHub, Inc. (n.d.-a). *Events that trigger workflows*. GitHub Docs. Retrieved +August 6, 2026, from +https://docs.github.com/en/enterprise-cloud@latest/actions/reference/workflows-and-actions/events-that-trigger-workflows#schedule + +GitHub, Inc. (n.d.-b). *Reusing workflows*. GitHub Docs. Retrieved August 6, +2026, from +https://docs.github.com/en/enterprise-cloud@latest/actions/how-tos/reuse-automations/reuse-workflows + +GitHub, Inc. (n.d.-c). *Workflow syntax for GitHub Actions: Jobs..secrets*. +GitHub Docs. Retrieved August 6, 2026, from +https://docs.github.com/en/enterprise-cloud@latest/actions/reference/workflows-and-actions/workflow-syntax#jobsjob_idsecrets + +GitHub, Inc. (n.d.-d). *Workflow syntax for GitHub Actions: Jobs..permissions*. +GitHub Docs. Retrieved August 6, 2026, from +https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax#jobsjob_idpermissions From be5ec376fd01d5c6eaee87e3d830c889424f6406 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 6 Aug 2026 11:01:11 +0900 Subject: [PATCH 3/9] test(automation): contract Inkspan hourly review caller --- tests/test_pr_review_fix_hourly_contract.py | 98 +++++++++++++++------ 1 file changed, 73 insertions(+), 25 deletions(-) diff --git a/tests/test_pr_review_fix_hourly_contract.py b/tests/test_pr_review_fix_hourly_contract.py index 47574f8d1..f67fd2a26 100644 --- a/tests/test_pr_review_fix_hourly_contract.py +++ b/tests/test_pr_review_fix_hourly_contract.py @@ -7,6 +7,7 @@ _REUSABLE_WORKFLOW = Path(".github/workflows/pr-review-fix-scheduler.yml") _CLEARFOLIO_CALLER = Path(".github/workflows/clearfolio-hourly-review-repair.yml") +_INKSPAN_CALLER = Path(".github/workflows/inkspan-hourly-review-repair.yml") _CONTRACT_WORKFLOW = Path(".github/workflows/hourly-nvidia-nim-review-repair.yml") @@ -15,6 +16,25 @@ def _read(path: Path) -> str: return path.read_text(encoding="utf-8") +def _assert_caller_permissions(path: Path) -> None: + """Require one product caller to confine writes to the reusable job.""" + text = _read(path) + workflow_scope, jobs_scope = text.split("\njobs:\n", maxsplit=1) + + assert "actions: write" not in workflow_scope + assert "issues: write" not in workflow_scope + assert "contents: write" not in workflow_scope + assert "pull-requests: write" not in workflow_scope + assert "statuses: write" not in workflow_scope + assert "\npermissions:\n contents: read\n" in workflow_scope + assert "\n permissions:\n" in jobs_scope + assert " actions: write\n" in jobs_scope + assert " contents: read\n" in jobs_scope + assert " issues: write\n" in jobs_scope + assert " pull-requests: read\n" in jobs_scope + assert " statuses: read\n" in jobs_scope + + def test_clearfolio_caller_runs_once_each_hour() -> None: """Clearfolio receives the requested hourly bounded repair heartbeat.""" text = _read(_CLEARFOLIO_CALLER) @@ -29,27 +49,45 @@ def test_clearfolio_caller_runs_once_each_hour() -> None: assert "NVIDIA_NIM_API_KEY" not in text +def test_inkspan_caller_runs_once_each_hour() -> None: + """Inkspan receives an offset hourly bounded repair heartbeat.""" + text = _read(_INKSPAN_CALLER) + + assert 'cron: "37 * * * *"' in text + assert "uses: ./.github/workflows/pr-review-fix-scheduler.yml" in text + assert "target_repository: ContextualWisdomLab/inkspan" in text + assert "base_branch: main" in text + assert 'max_dispatches: "1"' in text + assert 'retry_hours: "1"' in text + assert "COPILOT_GITHUB_TOKEN" not in text + assert "NVIDIA_NIM_API_KEY" not in text + + +def test_product_callers_have_distinct_single_flight_groups() -> None: + """Concurrent product heartbeats cannot cancel a sibling product run.""" + clearfolio = _read(_CLEARFOLIO_CALLER) + inkspan = _read(_INKSPAN_CALLER) + + assert "group: clearfolio-hourly-review-repair" in clearfolio + assert "group: inkspan-hourly-review-repair" in inkspan + assert "group: clearfolio-hourly-review-repair" not in inkspan + assert "group: inkspan-hourly-review-repair" not in clearfolio + assert "cancel-in-progress: true" in clearfolio + assert "cancel-in-progress: true" in inkspan + + def test_clearfolio_caller_scopes_write_permissions_to_reusable_job() -> None: - """Only the reusable scheduler job receives its required write permissions.""" - text = _read(_CLEARFOLIO_CALLER) - workflow_scope, jobs_scope = text.split("\njobs:\n", maxsplit=1) + """Clearfolio confines required writes to its reusable scheduler job.""" + _assert_caller_permissions(_CLEARFOLIO_CALLER) - assert "actions: write" not in workflow_scope - assert "issues: write" not in workflow_scope - assert "contents: write" not in workflow_scope - assert "pull-requests: write" not in workflow_scope - assert "statuses: write" not in workflow_scope - assert "\npermissions:\n contents: read\n" in workflow_scope - assert "\n permissions:\n" in jobs_scope - assert " actions: write\n" in jobs_scope - assert " contents: read\n" in jobs_scope - assert " issues: write\n" in jobs_scope - assert " pull-requests: read\n" in jobs_scope - assert " statuses: read\n" in jobs_scope + +def test_inkspan_caller_scopes_write_permissions_to_reusable_job() -> None: + """Inkspan confines required writes to its reusable scheduler job.""" + _assert_caller_permissions(_INKSPAN_CALLER) def test_reusable_scheduler_has_no_product_specific_timer() -> None: - """The shared scheduler stays modular while the caller owns product cadence.""" + """The shared scheduler stays modular while callers own product cadence.""" text = _read(_REUSABLE_WORKFLOW) target_expression = ( "github.event.client_payload.target_repository || " @@ -61,18 +99,26 @@ def test_reusable_scheduler_has_no_product_specific_timer() -> None: assert "\n schedule:\n" not in text assert text.count(target_expression) == 2 assert "ContextualWisdomLab/clearfolio" not in text + assert "ContextualWisdomLab/inkspan" not in text def test_reusable_scheduler_declares_only_required_caller_secrets() -> None: - """The scheduled caller passes only the two established scheduler secrets.""" + """Each scheduled caller passes only the two established scheduler secrets.""" reusable = _read(_REUSABLE_WORKFLOW) - caller = _read(_CLEARFOLIO_CALLER) assert "PR_REVIEW_MERGE_TOKEN:" in reusable assert "OPENCODE_APPROVE_TOKEN:" in reusable - 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 + for caller_path in (_CLEARFOLIO_CALLER, _INKSPAN_CALLER): + caller = _read(caller_path) + 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 def test_review_fix_scheduler_retries_same_head_after_one_hour() -> None: @@ -90,7 +136,6 @@ def test_review_fix_scheduler_retries_same_head_after_one_hour() -> None: def test_review_fix_scheduler_remains_bounded_and_single_flight() -> None: """Higher cadence never expands mutation volume or parallel execution.""" reusable = _read(_REUSABLE_WORKFLOW) - caller = _read(_CLEARFOLIO_CALLER) dispatch_block = reusable.split("max_dispatches:", maxsplit=1)[1].split( "target_repository:", maxsplit=1 @@ -98,11 +143,14 @@ def test_review_fix_scheduler_remains_bounded_and_single_flight() -> None: assert 'default: "1"' in dispatch_block assert "cancel-in-progress: true" in reusable assert "MAX_DISPATCHES" in reusable - assert "cancel-in-progress: true" in caller + assert "cancel-in-progress: true" in _read(_CLEARFOLIO_CALLER) + assert "cancel-in-progress: true" in _read(_INKSPAN_CALLER) -def test_contract_workflow_tracks_the_product_caller() -> None: - """Changes to the active Clearfolio caller always rerun the focused gate.""" +def test_contract_workflow_tracks_product_callers() -> None: + """Changes to either active product caller rerun the focused gate.""" text = _read(_CONTRACT_WORKFLOW) assert text.count(".github/workflows/clearfolio-hourly-review-repair.yml") == 2 + assert text.count(".github/workflows/inkspan-hourly-review-repair.yml") == 2 + assert text.count("docs/doctoring/inkspan-hourly-review-caller.md") == 2 From 3af8651deeb1be3989b8a7d0f9aba25648c48295 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 6 Aug 2026 11:01:43 +0900 Subject: [PATCH 4/9] ci(automation): verify Inkspan hourly caller --- .github/workflows/hourly-nvidia-nim-review-repair.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/hourly-nvidia-nim-review-repair.yml b/.github/workflows/hourly-nvidia-nim-review-repair.yml index f1aea3b36..2a05a8b27 100644 --- a/.github/workflows/hourly-nvidia-nim-review-repair.yml +++ b/.github/workflows/hourly-nvidia-nim-review-repair.yml @@ -6,6 +6,7 @@ on: - .github/workflows/pr-review-fix-scheduler.yml - .github/workflows/pr-review-autofix.yml - .github/workflows/clearfolio-hourly-review-repair.yml + - .github/workflows/inkspan-hourly-review-repair.yml - .github/workflows/hourly-nvidia-nim-review-repair.yml - scripts/ci/pr_review_conflict_scope.py - tests/test_pr_review_conflict_scope.py @@ -14,12 +15,14 @@ on: - tests/test_pr_review_autofix_nvidia_nim_contract.py - docs/automation/hourly-review-repair.md - docs/doctoring/clearfolio-hourly-review-caller.md + - docs/doctoring/inkspan-hourly-review-caller.md - docs/doctoring/hourly-nvidia-nim-autofix.md push: paths: - .github/workflows/pr-review-fix-scheduler.yml - .github/workflows/pr-review-autofix.yml - .github/workflows/clearfolio-hourly-review-repair.yml + - .github/workflows/inkspan-hourly-review-repair.yml - .github/workflows/hourly-nvidia-nim-review-repair.yml - scripts/ci/pr_review_conflict_scope.py - tests/test_pr_review_conflict_scope.py @@ -28,6 +31,7 @@ on: - tests/test_pr_review_autofix_nvidia_nim_contract.py - docs/automation/hourly-review-repair.md - docs/doctoring/clearfolio-hourly-review-caller.md + - docs/doctoring/inkspan-hourly-review-caller.md - docs/doctoring/hourly-nvidia-nim-autofix.md permissions: From d5c4f41770b0c39fd0860d94d74f60d98efa12f5 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 6 Aug 2026 11:02:16 +0900 Subject: [PATCH 5/9] docs(changelog): record Inkspan hourly repair caller --- CHANGELOG.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea51ef1e8..e15c3f2e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,10 +11,12 @@ Semantic Versioning where the repository publishes a release. - Added exact-base `uv.lock` materialization that reconstructs standalone nested projects with a checksum-pinned official `uv` exporter, isolated frozen/offline execution, strict exact-pin and SHA-256 output validation, and complete Python 3.10/3.14 quality evidence. - Added a permanent exact-head contract workflow for the hourly review-repair scheduler, immutable reusable-workflow source, NVIDIA NIM model boundary, credential isolation, and fail-closed unattended-agent permissions. - 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 Inkspan hourly caller at minute 37 with an explicit protected `main` target, one-dispatch budget, one-hour same-head retry floor, product-specific single-flight concurrency, and no copied repair implementation in the product repository. ### Changed - 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, and other CWL services. +- Run the bounded Inkspan PR review-feedback repair caller at minute 37 of every hour, offset from Clearfolio while sharing the same product-neutral scheduler and independent concurrency group. - Use NVIDIA NIM `mistralai/mistral-nemotron` for scheduled repair and `nvidia/nemotron-3-nano-30b-a3b` for bounded helper work instead of GitHub Models in the write-capable autofix worker. ### Fixed @@ -22,17 +24,17 @@ Semantic Versioning where the repository publishes a release. - Bound both trusted-uv quality jobs to `github.event.pull_request.head.sha` and added a permanent two-checkout regression contract so exact-head compatibility, coverage, docstring, and compilation claims cannot silently measure GitHub's generated pull-request merge revision. - Made Strix treat only a single LiteLLM provider-error line containing NVIDIA NIM context and model-catalog 404 evidence as cross-model fallback evidence, rejecting cross-line signal assembly and provider-like target source literals; moved the public default to Nemotron 3 Super 120B and added a second NVIDIA hosted candidate before GitHub Models without neutralizing reported vulnerabilities. - Bind reusable scheduler implementation to the validated called-workflow repository, SHA, ref, and file path, and verify the checked-out commit before executing privileged scheduler logic. -- Removed the ambiguous central-repository schedule fallback that could scan `.github` instead of Clearfolio when no external variable was configured; the active product caller now names Clearfolio explicitly while the reusable engine retains caller and dispatch overrides. +- Removed the ambiguous central-repository schedule fallback that could scan `.github` instead of Clearfolio when no external variable was configured; active product callers now name Clearfolio and Inkspan explicitly while the reusable engine retains caller and dispatch overrides. ### Security - Snapshot the post-merge worktree before OpenCode conflict repair and reject every model-caused changed, created, deleted, or retargeted path outside Git's exact conflict allowlist before staging or push. -- Keep the Clearfolio caller read-only at workflow scope and grant Actions/Issues write access only to the single reusable-scheduler job, preventing future sibling jobs from inheriting mutation authority. +- Keep the Clearfolio and Inkspan callers read-only at workflow scope and grant Actions/Issues write access only to each caller's single reusable-scheduler job, preventing sibling jobs from inheriting mutation authority. - Bind `NVIDIA_NIM_API_KEY` only to the two OpenCode model execution steps, fail closed when the secret is absent, and remove GitHub and Actions OIDC credentials from both model subprocesses. - Deny unnecessary non-file OpenCode interactions and preserve the independent read-only reviewer workflow and its credential/model-pool contract byte-for-byte. - Pin the repository-dispatch autofix helper checkout to the exact workflow-run SHA rather than a moving default branch. -- Pass only `PR_REVIEW_MERGE_TOKEN` and `OPENCODE_APPROVE_TOKEN` from the Clearfolio schedule caller; do not use `secrets: inherit` and do not expose the NVIDIA model credential to the queue-scanning workflow. +- Pass only `PR_REVIEW_MERGE_TOKEN` and `OPENCODE_APPROVE_TOKEN` from product schedule callers; do not use `secrets: inherit` and do not expose the NVIDIA model credential to queue-scanning workflows. ### Documentation -- Added operator and APA 7 doctoring records for the hourly cadence, immutable source identity, NVIDIA NIM provider and secret boundary, model-process credential isolation, modular MSA ownership, product-specific caller activation, verification contract, and rollback. +- Added operator and APA 7 doctoring records for the hourly cadence, immutable source identity, NVIDIA NIM provider and secret boundary, model-process credential isolation, modular MSA ownership, Clearfolio and Inkspan product-caller activation, verification contracts, and rollback. From f7ed1f499c3efbc492035417aae21cc6da70a23e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 6 Aug 2026 13:12:05 +0900 Subject: [PATCH 6/9] fix(automation): keep Inkspan caller schedule-only --- .github/workflows/inkspan-hourly-review-repair.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/inkspan-hourly-review-repair.yml b/.github/workflows/inkspan-hourly-review-repair.yml index 938a87bb1..8a72531db 100644 --- a/.github/workflows/inkspan-hourly-review-repair.yml +++ b/.github/workflows/inkspan-hourly-review-repair.yml @@ -4,7 +4,6 @@ on: schedule: # Offset from other product heartbeats to reduce shared-runner congestion. - cron: "37 * * * *" - workflow_dispatch: concurrency: group: inkspan-hourly-review-repair From f4eb2ebf05d882b50ab788273dff63ec31c0b0f9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 6 Aug 2026 13:13:03 +0900 Subject: [PATCH 7/9] test(automation): require schedule-only product callers --- tests/test_pr_review_fix_hourly_contract.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_pr_review_fix_hourly_contract.py b/tests/test_pr_review_fix_hourly_contract.py index f67fd2a26..354df5093 100644 --- a/tests/test_pr_review_fix_hourly_contract.py +++ b/tests/test_pr_review_fix_hourly_contract.py @@ -40,6 +40,7 @@ def test_clearfolio_caller_runs_once_each_hour() -> None: text = _read(_CLEARFOLIO_CALLER) assert 'cron: "23 * * * *"' in text + assert "workflow_dispatch:" not in text assert "uses: ./.github/workflows/pr-review-fix-scheduler.yml" in text assert "target_repository: ContextualWisdomLab/clearfolio" in text assert "base_branch: main" in text @@ -54,6 +55,7 @@ def test_inkspan_caller_runs_once_each_hour() -> None: text = _read(_INKSPAN_CALLER) assert 'cron: "37 * * * *"' in text + assert "workflow_dispatch:" not in text assert "uses: ./.github/workflows/pr-review-fix-scheduler.yml" in text assert "target_repository: ContextualWisdomLab/inkspan" in text assert "base_branch: main" in text From 063631bd3891f546e13546bba47a9b67b6230f47 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 6 Aug 2026 13:27:54 +0900 Subject: [PATCH 8/9] fix(automation): sync schedule-only prerequisite caller --- .github/workflows/clearfolio-hourly-review-repair.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/clearfolio-hourly-review-repair.yml b/.github/workflows/clearfolio-hourly-review-repair.yml index 265b59af6..989f6cbd1 100644 --- a/.github/workflows/clearfolio-hourly-review-repair.yml +++ b/.github/workflows/clearfolio-hourly-review-repair.yml @@ -4,7 +4,6 @@ on: schedule: # Offset the heartbeat from minute zero to reduce shared-runner congestion. - cron: "23 * * * *" - workflow_dispatch: concurrency: group: clearfolio-hourly-review-repair From 1f2e56e0e517a5cbd9f9302eec8b58e81909231c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 09:25:23 +0900 Subject: [PATCH 9/9] chore(inkspan): reconcile scheduler prerequisite head --- scripts/ci/redact_sensitive_log.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scripts/ci/redact_sensitive_log.py b/scripts/ci/redact_sensitive_log.py index cb89fe67b..16e89f264 100644 --- a/scripts/ci/redact_sensitive_log.py +++ b/scripts/ci/redact_sensitive_log.py @@ -99,14 +99,17 @@ def _redact_assignments(text: str) -> str: """Redact sensitive key/value assignments without backtracking regexes.""" output: list[str] = [] cursor = 0 + last_append = 0 while cursor < len(text): match = _consume_sensitive_assignment(text, cursor) if match is None: - output.append(text[cursor]) cursor += 1 continue + output.append(text[last_append:cursor]) replacement, cursor = match output.append(replacement) + last_append = cursor + output.append(text[last_append:]) return "".join(output)