diff --git a/.github/workflows/hourly-nvidia-nim-review-repair.yml b/.github/workflows/hourly-nvidia-nim-review-repair.yml index 9eb450619..d2bd29240 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/html4tree-hourly-review-repair.yml - .github/workflows/nonnest2-hourly-review-repair.yml - .github/workflows/orgmetra-hourly-review-repair.yml - .github/workflows/originweave-hourly-review-repair.yml @@ -29,6 +30,7 @@ on: - tests/test_orgmetra_hourly_review_caller.py - tests/test_originweave_hourly_review_caller.py - tests/test_quarantine_sandbox_hourly_review_caller.py + - tests/test_html4tree_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 @@ -54,6 +56,7 @@ on: - docs/doctoring/orgmetra-hourly-review-caller.md - docs/doctoring/originweave-hourly-review-caller.md - docs/doctoring/quarantine-sandbox-hourly-review-caller.md + - docs/doctoring/html4tree-hourly-review-caller.md push: paths: - .github/workflows/pr-review-fix-scheduler.yml @@ -66,6 +69,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/html4tree-hourly-review-repair.yml - .github/workflows/nonnest2-hourly-review-repair.yml - .github/workflows/orgmetra-hourly-review-repair.yml - .github/workflows/originweave-hourly-review-repair.yml @@ -82,6 +86,7 @@ on: - tests/test_orgmetra_hourly_review_caller.py - tests/test_originweave_hourly_review_caller.py - tests/test_quarantine_sandbox_hourly_review_caller.py + - tests/test_html4tree_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 @@ -107,6 +112,7 @@ on: - docs/doctoring/orgmetra-hourly-review-caller.md - docs/doctoring/originweave-hourly-review-caller.md - docs/doctoring/quarantine-sandbox-hourly-review-caller.md + - docs/doctoring/html4tree-hourly-review-caller.md permissions: contents: read @@ -164,6 +170,7 @@ jobs: tests/test_orgmetra_hourly_review_caller.py \ tests/test_originweave_hourly_review_caller.py \ tests/test_quarantine_sandbox_hourly_review_caller.py \ + tests/test_html4tree_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/html4tree-hourly-review-repair.yml b/.github/workflows/html4tree-hourly-review-repair.yml new file mode 100644 index 000000000..08202ecab --- /dev/null +++ b/.github/workflows/html4tree-hourly-review-repair.yml @@ -0,0 +1,37 @@ +name: html4tree Hourly Review Repair + +on: + schedule: + # Minute 15 avoids pg-llm-batch (1), aFIPC (2), kaefa (3), LineageWeave (4), + # codec-carver (5), life-os (6), Wardnet (7), mightyETL (8), + # psychometrics-commons (9), OriginWeave (10), naruon (11), + # DiagramWeave (12), pg-erd-cloud (13), mhtml-etl-gateway (14), + # orchestrator (17), noema (19), Clearfolio (23), Keyverse (29), + # Scopeweave (31), DiskSage (37), Appguardrail (41), newsdom-api (43), + # Inkspan (47), fast-mlsirm (49), BandScope (53), and + # semantic-data-portal (59). + - cron: "15 * * * *" + +concurrency: + group: html4tree-hourly-review-repair + # A later heartbeat must not cancel an in-flight directory-index RCA. + 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/html4tree + base_branch: master + 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/ARCHITECTURE.md b/ARCHITECTURE.md index c48db831f..557556f57 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -38,6 +38,14 @@ only established scheduler credentials, and grants job-scoped 16. It names `ContextualWisdomLab/nonnest2` and protected `master`, maps only established scheduler credentials, and grants job-scoped `id-token: write`. The reusable engine stays product-neutral. +## html4tree hourly caller + +`html4tree-hourly-review-repair.yml` is a thin, read-only caller at +minute 15. It names `ContextualWisdomLab/html4tree` and protected +`master`, maps only established scheduler credentials, and grants +job-scoped `id-token: write`. The reusable engine stays product-neutral. +html4tree remains a standalone public-fork module; mutation stays +capability-gated per head. ## Hourly NVIDIA NIM repair gate @@ -66,9 +74,10 @@ The worker checks out helpers at `${{ github.sha }}` so a later default-branch push cannot replace privileged scripts after dispatch (CWE-367). Repair binds `NVIDIA_NIM_API_KEY`, never `COPILOT_GITHUB_TOKEN`. -Product callers stagger Clearfolio at minute 23, DiskSage at minute 37, and -fast-mlsirm at minute 49. Each caller is read-only, dispatches at most one -repair, and delegates all privileged logic to the same sealed scheduler. +Product callers stagger html4tree at minute 15, Clearfolio at minute 23, +DiskSage at minute 37, and fast-mlsirm at minute 49. Each caller is +read-only, dispatches at most one repair, and delegates all privileged +logic to the same sealed scheduler. ## Exact-artifact SBOM attestation @@ -149,3 +158,5 @@ trusted `uv` exporter is downloaded from the literal GitHub Releases URL for — product-specific psychometric repair heartbeat and scientific gates. - [`docs/doctoring/exact-artifact-sbom-attestation.md`](docs/doctoring/exact-artifact-sbom-attestation.md) — current increment's attestation decision and APA 7th citations. +- [`docs/doctoring/html4tree-hourly-review-caller.md`](docs/doctoring/html4tree-hourly-review-caller.md) + — public-fork directory-index repair heartbeat on protected `master`. diff --git a/CHANGELOG.md b/CHANGELOG.md index e42afe76a..3e045452c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ Semantic Versioning where the repository publishes a release. - Added an hourly organization commercial-readiness coordinator that discovers writable repositories, honors enabled dedicated writer leases and fully paginated live writer runs, refetches exact repository/workflow/run/PR state before dispatch, rotates bounded review-repair and opt-in NVIDIA OpenCode product-development targets, fails nonzero on fleet-wide inspection or dispatch outages, retains three-day JSON receipts, and keeps the existing 15-minute merge scheduler authoritative. - Added a dedicated Quarantine Sandbox Runtime hourly caller at minute 14 that targets protected `develop`, dispatches at most one exact-head repair, applies a two-hour same-head retry floor, preserves non-cancelling single-flight execution, and maps only the established scheduler credentials with job-scoped OIDC. - Added a dedicated OriginWeave hourly caller that invokes the product-neutral central scheduler with the exact repository, protected `main` branch, one-dispatch budget, two-hour same-head retry floor, non-cancelling single-flight heartbeat, job-scoped OIDC, and only the established scheduler credentials. +- Added a dedicated html4tree hourly caller that invokes the product-neutral central scheduler with the exact public-fork repository, protected `master` branch, one-dispatch budget, two-hour same-head retry floor, non-cancelling single-flight heartbeat, job-scoped OIDC, and only the established scheduler credentials. - Added a trusted pull-request comment router for `@cwl-noema-review` and review-only `@opencode-agent` dispatches, with an organization sweep, exact-head receipts, repository allowlisting, fixed runners, immutable checkout pins, and a permanent 100% statement/branch/docstring quality gate. - Added an organization-owned reusable exact-artifact SBOM attestation boundary that validates inert six-file wheel/sdist evidence, binds CycloneDX 1.7 predicates to exact SHA-256 subjects, signs through least-privilege GitHub artifact attestations, and exports online and offline verification bundles. - 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. @@ -27,6 +28,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 html4tree repair heartbeat at minute 15 of every hour against protected `master`, dispatch no more than one exact-head repair, and keep mutation capability-gated on the public fork. - 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. @@ -64,6 +66,7 @@ Semantic Versioning where the repository publishes a release. - Keep the Clearfolio caller and reusable scheduler read-only at workflow and job scope; authorize mutation only through explicitly mapped `PR_REVIEW_MERGE_TOKEN`, `OPENCODE_APPROVE_TOKEN`, or the short-lived OpenCode GitHub App token exchanged from OIDC, with explicit pre-write guards and no `github.token` mutation fallback. - Keep the DiskSage caller read-only and pass only the established scheduler credentials; do not inherit secrets, expose the NVIDIA NIM model credential to the queue scanner, use a GitHub Copilot token, or grant the caller repository mutation permissions. - Keep the fast-mlsirm caller read-only and model-secret-free; preserve independent approval, exact-head evidence, and Rust production-arithmetic ownership while centralizing only bounded review repair. +- Keep the html4tree caller read-only and model-secret-free; name the public fork and protected `master` explicitly so directory-index heads receive the same sealed scheduler without widening workflow-token write. - 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. The decision record now cites CWE-367 so a later default-branch push cannot replace privileged repair helpers after `repository_dispatch` has already selected the workflow revision. - Recorded the org control-plane architecture, including the hourly NVIDIA NIM repair gate, so agents reconstruct the write-capable worker trust boundary from the repo instead of private memory. - Deny unnecessary non-file OpenCode interactions and preserve the independent read-only reviewer workflow and its credential/model-pool contract byte-for-byte. diff --git a/docs/doctoring/html4tree-hourly-review-caller.md b/docs/doctoring/html4tree-hourly-review-caller.md new file mode 100644 index 000000000..a6102fc7b --- /dev/null +++ b/docs/doctoring/html4tree-hourly-review-caller.md @@ -0,0 +1,142 @@ +# html4tree hourly review-repair caller + +검토 기준일: **2026-08-17** + +## Decision + +ContextualWisdomLab operates one protected hourly caller for +`ContextualWisdomLab/html4tree` (standalone MIT directory-index generator +that writes `index.html` trees in the style of Apache `mod_autoindex`). +The caller runs at minute 15, delegates to the product-neutral central +review-fix scheduler, inspects at most 50 open pull requests targeting +protected `master`, and dispatches at most one bounded repair per +heartbeat. + +A paying buyer of governed directory listings would feel live html4tree +pull requests stalling while hourly NVIDIA NIM repair scanned only +Clearfolio, DiskSage, and fast-mlsirm. Live heads such as +ContextualWisdomLab/html4tree#475 (localized navigation labels), +ContextualWisdomLab/html4tree#472 (TOCTOU on `.html4ignore`), and +ContextualWisdomLab/html4tree#454 (BiDi spoof / Trojan Source) target +`master` and never enter those other callers. + +html4tree is a public fork of `yencarnacion/html4tree`. Fork status is +not a categorical exclusion: onboarding is an explicit repository +decision, and pull-request mutation remains capability-gated per head. +The caller does not implement review or mutation logic itself. +html4tree remains standalone; naruon and other CWL services may consume +generated indexes without owning the crawler. 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 directory- +listing 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 BiDi or TOCTOU 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-master activation, the repository variable +`OPENCODE_REPOSITORY_DISPATCH_TARGETS` must contain the exact +`ContextualWisdomLab/html4tree` target. Missing or mismatched +configuration fails before mutation credential materialization. + +## Security, standalone operation, and modularity + +The caller adds no html4tree runtime dependency, database object, +network endpoint, tenant authority, or product credential. html4tree +continues to run as a standalone directory-index generator. Naruon and +other CWL services may consume its `index.html` trees, but they cannot +weaken its exact-head, approval, or security gates. + +## Verification and rollback + +Machine-checkable contracts require the exact target/base, minute 15 +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-master consumer run proving the exact html4tree repository +and `master` base. Source checks alone are not protected-master operational acceptance. +Merge still requires zero unresolved valid findings and a +qualifying independent non-author approval. + +Rollback removes the html4tree 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/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_html4tree_hourly_review_caller.py b/tests/test_html4tree_hourly_review_caller.py new file mode 100644 index 000000000..563eb5fd1 --- /dev/null +++ b/tests/test_html4tree_hourly_review_caller.py @@ -0,0 +1,171 @@ +"""Contract tests for html4tree's bounded hourly review-repair caller.""" + +from pathlib import Path + + +CALLER = Path(".github/workflows/html4tree-hourly-review-repair.yml") +DOCTORING = Path("docs/doctoring/html4tree-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_html4tree_caller_is_hourly_bounded_and_non_cancelling() -> None: + """html4tree receives one realistic directory-index repair without cancellation.""" + caller = _read(CALLER) + + assert 'cron: "15 * * * *"' in caller + assert "group: html4tree-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/html4tree" in caller + assert "base_branch: master" in caller + assert 'max_prs: "50"' in caller + assert 'max_dispatches: "1"' in caller + assert 'retry_hours: "2"' in caller + + +def test_html4tree_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_html4tree_target_is_not_hard_coded_in_shared_scheduler() -> None: + """Product identity remains in the thin caller rather than the engine.""" + assert "ContextualWisdomLab/html4tree" not in _read(SCHEDULER) + + +def test_html4tree_doctoring_records_fork_activation_and_credentials() -> None: + """Operators retain target-allowlist, fork, and approval prerequisites.""" + doctoring = _read(DOCTORING) + + for phrase in ( + "ContextualWisdomLab/html4tree", + "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-master operational acceptance", + "APA 7th references", + "ContextualWisdomLab/html4tree#475", + "ContextualWisdomLab/html4tree#472", + "ContextualWisdomLab/html4tree#454", + "capability-gated per head", + ): + 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/html4tree-hourly-review-repair.yml\n" + " push:\n" + " paths:\n" + " - docs/doctoring/html4tree-hourly-review-caller.md\n" + " python -m compileall -q \\\n" + " tests/test_html4tree_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/html4tree-hourly-review-repair.yml" + } + assert push_paths == {"docs/doctoring/html4tree-hourly-review-caller.md"} + assert compileall_paths == {"tests/test_html4tree_hourly_review_caller.py"} + assert "docs/doctoring/html4tree-hourly-review-caller.md" not in pull_request_paths + assert ( + ".github/workflows/html4tree-hourly-review-repair.yml" + not in compileall_paths + ) + + +def test_focused_quality_workflow_tracks_html4tree_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/html4tree-hourly-review-repair.yml" + doctoring = "docs/doctoring/html4tree-hourly-review-caller.md" + contract = "tests/test_html4tree_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