diff --git a/.github/dependabot.yml b/.github/dependabot.yml index ebbc689..722eb1a 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,22 +1,41 @@ -# To get started with Dependabot version updates, you'll need to specify which -# package ecosystems to update and where the package manifests are located. -# Please see the documentation for all configuration options: -# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file +# SPDX-License-Identifier: MPL-2.0 +# Estate dependabot policy: hyperpolymath/standards docs/DEPENDABOT-POLICY.adoc +# (standards#297). Local history that shaped this file: #63/#64/#68/#69 and +# issue #67. +# +# READ BEFORE MERGING ANY PR THIS FILE PRODUCES: +# Dependabot rewrites `uses:` refs in the workflow YAML but CANNOT touch +# .github/workflows/actions.lock. A merged bump therefore desyncs the lock, +# and GitHub then refuses EVERY affected workflow at startup (jobs=0, +# "startup_failure", no logs). That is what killed all 9 workflows on main +# after #69/#70 (issue #67). The `Lock Sync Gate` check goes red on exactly +# such a PR; a red gate on a dependabot PR means "regenerate the lock in the +# same PR", never "merge anyway". See docs/ci/CHECK-DETERMINATIONS.adoc. version: 2 updates: - package-ecosystem: "github-actions" directory: "/" schedule: - interval: "daily" + # Weekly, not daily: every actions bump needs a paired lockfile + # regeneration by a human, so the cadence is set to what a human + # reviews, not what a bot can emit (estate policy). + interval: "weekly" groups: actions: patterns: - "*" + open-pull-requests-limit: 2 ignore: - # HOLD: github/codeql-action at v4.38.0 (SHA-pinned). v4.38.1 fails - # GitHub workflow-startup validation estate-wide (nexia-list#100; - # SHA-form re-bump bypassed versions-scoped ignores - nexia-list#101). - # Hold until upstream clears 4.38.1 or a new release verifies green. - - dependency-name: "github/codeql-action" - + # HOLD: github/codeql-action at v4.38.0 (SHA-pinned b96794f0). v4.38.1 + # (1c5b6756) fails GitHub workflow-startup validation estate-wide + # (nexia-list#100). Hold until upstream clears 4.38.1 or a newer release + # verifies green; revisit deliberately, not weekly. + # + # The trailing * is load-bearing. Workflows reference the SUBPATH actions + # (github/codeql-action/init, /analyze, /upload-sarif) and Dependabot + # treats each subpath as its own dependency name -- so the previous bare + # "github/codeql-action" entry matched NONE of them. That is how #69 + # re-bumped 4.38.0 -> 4.38.1 straight through the hold that #64 set and + # #68 re-asserted. + - dependency-name: "github/codeql-action*" diff --git a/.github/workflows/actions.lock b/.github/workflows/actions.lock index 0737c5f..46aa75d 100644 --- a/.github/workflows/actions.lock +++ b/.github/workflows/actions.lock @@ -11,12 +11,10 @@ workflows: - 'actions/configure-pages@v6.0.0' - 'actions/deploy-pages@v5.0.1' - 'actions/upload-pages-artifact@v5.0.0' - - 'haskell-actions/setup@v2.12.0' + - 'haskell-actions/setup@v2.12.1' '.github/workflows/ci.yml': - 'actions/checkout@v7.0.1' - - 'denoland/setup-deno@v2.0.5' - 'dtolnay/rust-toolchain@v1' - - 'jetli/wasm-pack-action@v0.4.0' '.github/workflows/codeql.yml': - 'actions/checkout@v7.0.1' - 'github/codeql-action@b96794f015dfd88f77b49b1c93e0fa7110f94c63' @@ -96,11 +94,6 @@ dependencies: repo_id: 496012378 uses: - 'actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f' - 'denoland/setup-deno@v2.0.5': - ref: 'v2.0.5' - commit: 'sha1-22d081ff2d3a40755e97629de92e3bcbfa7cf2ed' - owner_id: 42048915 - repo_id: 356423100 'dtolnay/rust-toolchain@6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772': ref: '6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772' commit: 'sha1-6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772' @@ -131,14 +124,9 @@ dependencies: commit: 'sha1-b96794f015dfd88f77b49b1c93e0fa7110f94c63' owner_id: 9919 repo_id: 259445878 - 'github/codeql-action@v4.38.0': - ref: 'v4.38.0' - commit: 'sha1-b96794f015dfd88f77b49b1c93e0fa7110f94c63' - owner_id: 9919 - repo_id: 259445878 - 'haskell-actions/setup@v2.12.0': - ref: 'v2.12.0' - commit: 'sha1-6037f33647c3f17758a2356c80fc4a53d7e0685d' + 'haskell-actions/setup@v2.12.1': + ref: 'v2.12.1' + commit: 'sha1-0f8e8c99d88aeb3fbfd523f1ef2c6f762d10d64d' owner_id: 75048950 repo_id: 623796603 'hyperpolymath/smtp-notify-action@v0.3.0': @@ -165,11 +153,6 @@ dependencies: - 'actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1' - 'editorconfig-checker/action-editorconfig-checker@840e866d93b8e032123c23bac69dece044d4d84c' - 'erlef/setup-beam@54075bcc5e249e4758d363f27d099f55d843f124' - 'jetli/wasm-pack-action@v0.4.0': - ref: 'v0.4.0' - commit: 'sha1-0d096b08b4e5a7de8c28de67e11e945404e9eefa' - owner_id: 1142758 - repo_id: 244578171 'peter-evans/repository-dispatch@v4.0.1': ref: 'v4.0.1' commit: 'sha1-28959ce8df70de7be546dd1250a005dd32156697' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3a4c55c..1d59a34 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,17 +3,37 @@ # This workflow is managed by gh actions-lock. # This workflow is managed by gh actions-lock. # This workflow is managed by gh actions-lock. +# +# CI for the implemented part of this repository: the Rust PDF core. +# +# History (issue #67): this file used to carry `core-fill-tests` and +# `extension-build`, which ran `deno task test:core-fill`, `deno task build:wasm` +# and `deno task build`. Those tasks lived in deno.json, which #43 deleted on +# 2026-08-24; the two jobs were red on every run from then on and were RETIRED +# by #71. `test:core-fill` was `cargo test ... fill_blocks_`, a strict subset of +# the `rust-core` job below. The extension bundle has no toolchain in this +# checkout (see README "Current status"); when one lands, add its build job +# here and record it in docs/ci/CHECK-DETERMINATIONS.adoc. +# +# There is deliberately NO lockfile-verification job in this file. A job that +# checks actions.lock from inside a workflow that GitHub refuses to start when +# actions.lock is out of sync can never report the fault it exists to catch. +# That check lives in lock-sync-gate.yml, which carries no `uses:` at all and +# is therefore immune to the failure it detects. name: CI permissions: actions: read contents: read on: - push: branches: ["main"] pull_request: +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: rust-core: name: Rust core (tests, formatting, lint) @@ -31,14 +51,3 @@ jobs: run: cargo test --manifest-path rust/pdftool_core/Cargo.toml --locked - name: Run Clippy run: cargo clippy --manifest-path rust/pdftool_core/Cargo.toml --locked --all-targets -- -D warnings - - workflow-lock: - name: Verify Actions lockfile - runs-on: ubuntu-latest - timeout-minutes: 5 - steps: - - uses: actions/checkout@v7.0.1 - - name: Install GNU awk - run: sudo apt-get update && sudo apt-get install -y gawk - - name: Verify workflow lockfile - run: ./scripts/check-lock-sync.sh diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index ffbb58a..1f92f02 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -83,7 +83,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@1c5b675653bb5c22dbe9b12b556ec555138e09fd # v4.38.0 (4.38.1 blocked estate-wide; nexia-list#100) + uses: github/codeql-action/init@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4.38.0 -- HOLD: do not bump; 4.38.1 (1c5b675) is blocked estate-wide (nexia-list#100); dependabot ignore in .github/dependabot.yml with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} @@ -112,6 +112,6 @@ jobs: exit 1 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@1c5b675653bb5c22dbe9b12b556ec555138e09fd # v4.38.0 (4.38.1 blocked estate-wide; nexia-list#100) + uses: github/codeql-action/analyze@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4.38.0 -- HOLD: do not bump; 4.38.1 (1c5b675) is blocked estate-wide (nexia-list#100); dependabot ignore in .github/dependabot.yml with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/hypatia-scan.yml b/.github/workflows/hypatia-scan.yml index f3bcaa8..1743616 100644 --- a/.github/workflows/hypatia-scan.yml +++ b/.github/workflows/hypatia-scan.yml @@ -246,7 +246,7 @@ jobs: always() && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork != true) - uses: github/codeql-action/upload-sarif@1c5b675653bb5c22dbe9b12b556ec555138e09fd # v4.38.0 (4.38.1 blocked estate-wide; nexia-list#100) + uses: github/codeql-action/upload-sarif@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4.38.0 -- HOLD: do not bump; 4.38.1 (1c5b675) is blocked estate-wide (nexia-list#100); dependabot ignore in .github/dependabot.yml with: sarif_file: hypatia.sarif # Distinct category so Hypatia results coexist with CodeQL's diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 4f410b1..ab27262 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -31,6 +31,10 @@ noreply@anthropic.com ==== Fixed +* fix(ci): resync actions.lock after #69/#70/#71 desync that startup-killed +every workflow on main; re-pin codeql-action to the held v4.38.0; correct the +dependabot codeql ignore glob; retire `core-fill-tests`/`extension-build` with +a written determination in docs/ci/CHECK-DETERMINATIONS.adoc (#67) * fix(ci): sync hypatia-scan.yml to canonical (#5) * fix(ci): adopt canonical hypatia-scan.yml (#4) * fix(scorecard): enforce granular permissions and add fuzzing diff --git a/TEST-NEEDS.adoc b/TEST-NEEDS.adoc index f278047..a6e05b9 100644 --- a/TEST-NEEDS.adoc +++ b/TEST-NEEDS.adoc @@ -7,18 +7,20 @@ [cols=",,",options="header",] |=== |Category |Count |Notes -|Test directories |1 |Location(s): /tests -|CI workflows |15 |Running tests on GitHub Actions -|Unit tests |Configured |AffineScript Jest/Vitest setup +|Test directories |1 |Location(s): /tests (aspect + fuzz placeholder) +|CI workflows |15 |`ci.yml` runs the Rust core checks; see docs/ci/CHECK-DETERMINATIONS.adoc +|Unit tests |Implemented |Rust `pdftool_core` (`cargo test --locked`) |=== === What’s Covered -* [x] AffineScript unit tests -* [x] JavaScript interop tests +* [x] Rust core unit tests (block detection, `fill_blocks_*` AcroForm writeback, taxonomy errors) +* [x] `cargo fmt --check` and `cargo clippy -D warnings` in CI === Still Missing (for CRG B+) +* [ ] Extension frontend build + tests — no AffineScript/bundle toolchain in this checkout + (retired CI job `extension-build`; see docs/ci/CHECK-DETERMINATIONS.adoc) * [ ] Code coverage reports (codecov integration) * [ ] Detailed test documentation in CONTRIBUTING.md * [ ] Integration tests beyond unit tests @@ -28,5 +30,7 @@ [source,bash] ---- -npm run test # or: affinescript build && npm run test +cargo fmt --manifest-path rust/pdftool_core/Cargo.toml -- --check +cargo test --manifest-path rust/pdftool_core/Cargo.toml --locked +cargo clippy --manifest-path rust/pdftool_core/Cargo.toml --locked --all-targets -- -D warnings ---- diff --git a/TOPOLOGY.adoc b/TOPOLOGY.adoc index 4334b0b..412decc 100644 --- a/TOPOLOGY.adoc +++ b/TOPOLOGY.adoc @@ -58,7 +58,8 @@ CORE (RUST/WASM) Error Taxonomy ██████████ 100% Structured error codes active REPO INFRASTRUCTURE - Deno Build Tasks ██████████ 100% deno task build verified + Rust CI (fmt/test/clippy) ██████████ 100% ci.yml rust-core job + Extension bundle pipeline ░░░░░░░░░░ 0% deno.json removed in #43; no toolchain .machine_readable/ ██████████ 100% STATE.a2ml tracking Containerfile ██████████ 100% Reproducible dev env diff --git a/docs/ci/CHECK-DETERMINATIONS.adoc b/docs/ci/CHECK-DETERMINATIONS.adoc new file mode 100644 index 0000000..ff9fe57 --- /dev/null +++ b/docs/ci/CHECK-DETERMINATIONS.adoc @@ -0,0 +1,205 @@ +// SPDX-License-Identifier: MPL-2.0 += CI check determinations +:toc: +:toc-placement: preamble + +The record required by the estate stopping rule (hyperpolymath/standards#968, +#994): every CI check that has ever been red on `main` has exactly one of three +determinations -- *fixed*, *retired*, or *exempt* -- and none is ever closed by +muting it (`continue-on-error`, demotion to a warning, or silent removal). + +== Ledger + +[cols="2,1,1,4",options="header"] +|=== +| Check | Determination | Date | Basis + +| `core-fill-tests` (CI) +| *Retired* +| 2026-09-27 +| Ran `deno task test:core-fill`. That task lived in `deno.json`, which #43 + ("eradicate TypeScript and NPM/Deno configs", 2026-08-24) deleted. From that + commit on the job could not have passed: the task was `cargo test + --manifest-path rust/pdftool_core/Cargo.toml fill_blocks_`, and CI never + recorded a single green run of `ci.yml`. The coverage it intended is a + strict subset of the `rust-core` job, which runs the crate's full + `cargo test --locked`. Removed from `ci.yml` in #71; the lockfile was + brought into line in the fix for #67. + +| `extension-build` (CI) +| *Retired* +| 2026-09-27 +| Ran `deno task build:wasm` and `deno task build` (AffineScript + webpack + bundle). Same #43 deletion; the bundle toolchain is not in this checkout at + all (README "Current status"). There is nothing to build until the frontend + gets a reproducible pipeline. When it does, the build job comes back to + `ci.yml` under a new name with a new row here -- it is not "exempt", it is + gone until the thing it would build exists. + +| `rust-core` (CI) +| *Fixed -- verification pending an owner-actor run* +| 2026-09-27 +| Replacement for both retired jobs: `cargo fmt --check`, `cargo test + --locked`, `cargo clippy -D warnings` on `rust/pdftool_core`. It has never + once executed: every `ci.yml` run before #71 died on the Deno tasks, #71's + own PR and merge were bot-triggered and refused at startup (<>), and + the fix PR for #67 is bot-triggered too. The first run that can execute it + is one triggered by `hyperpolymath` (re-run, close/reopen, or push). If that + run is red, the crate has a real defect and this row becomes a repair item; + it is not closed until a green run on `main` exists. + +| `Lock Sync Gate` / `actions.lock is in sync with the workflow YAML` +| *Fixed* +| 2026-09-27 +| Red on `main` from #69 (2026-09-24) onward, and the cause of every other + workflow on `main` reporting `startup_failure` at d50a857 -- see + <>. Resynchronised in the fix for #67. +|=== + +== Also red on `main`? + +Yes, for both checks named in #67, and for reasons that have nothing to do +with the `actions.lock` cure (#66) that surfaced them: + +* `core-fill-tests` and `extension-build` were red on every run since #43, + which predates #66 by a month. #66 revived the workflow that runs them; it + did not break them. +* At `main` = d50a857 (after #71) *all nine* push-triggered workflows were + `startup_failure` with zero jobs. That includes `CI`, so neither the retired + jobs nor their replacement could report at all. Two independent causes + overlap there; they are separated in <> and <>, because a fix + for one is invisible while the other is in force. + +[[desync]] +== Root cause of the `main` outage, and why it recurred four times + +GitHub refuses to start a workflow whose step-level `uses:` refs are not +recorded, under that workflow's own path, in `.github/workflows/actions.lock`. +The refusal is silent: `startup_failure`, jobs=0, no log. Four consecutive +merges after #66 each rewrote a `uses:` without touching the lock: + +[cols="1,3,3",options="header"] +|=== +| Merge | Rewrote | Lock impact + +| #68 | codeql-action `1c5b675` -> `b96794f` (true v4.38.0) +| Lock already recorded `b96794f`; this one was in sync. + +| #69 (Dependabot) | codeql-action `b96794f` -> `1c5b675` (v4.38.1) +| Killed `codeql.yml` and `hypatia-scan.yml`. Merged with `Lock Sync Gate` + red. Got through the codeql hold because the ignore was + `github/codeql-action`, which does not match the subpath dependency names + `github/codeql-action/init` etc. that Dependabot actually tracks. + +| #70 (Dependabot) | haskell-actions/setup v2.12.0 -> v2.12.1 +| Killed `casket-pages.yml`. Merged with `Lock Sync Gate` red. + +| #71 | `ci.yml` jobs replaced; `setup-deno` and `wasm-pack-action` no longer used +| Killed `ci.yml` (stale entries, clause 2). No check could flag it: the + gate itself was already startup-dead from #69/#70. +|=== + +The last row is the important one. A drift merged into `main` does not only +break the workflows that use the drifted ref -- it can take the gate down too, +after which further drift lands unobserved. That is why the fix for #67 does +four things rather than one. + +The run annotation for this class reads, verbatim: +`Invalid lockfile: .github/workflows/actions.lock#L1 -- The lockfile could not +be validated. Regenerate it by running gh actions-lock.` (e.g. run 36204815451, +Hypatia on a0e8674, actor `hyperpolymath`). The annotation is visible only on +the run's web page; the REST API exposes neither it nor any job. + +[[actor]] +== The second cause: the triggering actor is refused + +Every run whose triggering actor is `arena-ai-coding-agent[bot]` is refused at +startup, regardless of what the workflow or the lockfile contain: + +[cols="2,2,1,3",options="header"] +|=== +| Runs | Actor | Result | Annotation + +| all 9 push runs at d50a857 (#71 merged by the bot) +| `arena-ai-coding-agent[bot]` +| `startup_failure`, jobs=0 +| `Actor is not allowed to trigger Actions workflows. Workflow file: '...'` + +| all 6 pull_request runs on the #67 fix branch -- including + `lock-sync-gate.yml`, which has no `uses:` and a verified-clean lockfile +| `arena-ai-coding-agent[bot]` +| `startup_failure`, jobs=0 +| same (run 36295605950) + +| push runs at a0e8674 +| `hyperpolymath` +| 6 of 9 ran; the 3 that did not were exactly the lock-drifted workflows +| `Invalid lockfile` on those 3 only +|=== + +This is an org-side Actions policy, not anything in the repository; the same +signature is recorded estate-wide in hyperpolymath/wordpress-tools#96 and +hyperpolymath/statistikles#112. Consequences for this repository: + +* A PR opened or merged by the bot shows *no* repository checks at all -- not + red, absent -- because a startup failure creates no check run. That is how + #71 merged with no CI signal, and why a bot merge leaves `main` looking + fully red even when the files are correct. +* No file change can cure it. The owner-side options are (a) permit the App to + trigger workflows in org/repo Settings -> Actions, (b) re-run the failed + workflows as the owner after each bot merge, or (c) merge bot PRs as the + owner so the merge push carries an allowed actor. + +== What the fix for #67 changed + +. `actions.lock` resynchronised and transitively closed (verified by + `scripts/check-lock-sync.sh`, all four clauses): `ci.yml` entries pruned to + what it uses; `haskell-actions/setup@v2.12.1` recorded with its commit; + unreferenced alias records pruned. +. `codeql-action` re-pinned to `b96794f` (v4.38.0) in `codeql.yml` and + `hypatia-scan.yml`, restoring the estate hold that #64 set and #68 re-asserted. + The `# v4.38.0` comment Dependabot left beside the 4.38.1 SHA was false; it + is now true. +. `.github/dependabot.yml`: ignore pattern corrected to `github/codeql-action*` + (the estate-canonical form from hyperpolymath/standards), cadence weekly, + open-PR limit 2, and a header explaining the lock contract. +. `ci.yml`: the duplicate `workflow-lock` job removed. It ran from inside a + workflow that GitHub refuses to start on exactly the fault it checked, so it + could never report. `lock-sync-gate.yml` carries no `uses:` and is the + authoritative gate. + +== Standing rules for this repository + +. *Read the annotation before diagnosing a `startup_failure`.* Zero jobs and + "workflow file issue" is one symptom with (at least) three causes in this + repository's history -- lockfile drift, unlisted workflow, refused actor -- + and only the web-page annotation distinguishes them. Fixing the lock while + the actor is refused proves nothing, and vice versa. +. *A red `Lock Sync Gate` is never merged over.* On a Dependabot PR it means + "regenerate the lock in this PR" (`gh actions-lock --no-migrate-local-actions`, + then hand-check job-level reusable refs and pinned SHAs, then run + `scripts/check-lock-sync.sh`). On any other PR it means the author changed a + `uses:` and must record it. +. *Any change to a `uses:` line ships with the matching `actions.lock` change + in the same commit.* This includes removals: a stale lock entry is as fatal + as a missing one (clause 2). +. *Retire or repair; never silence.* No `continue-on-error`, no + `if: false`, no deletion of a job without a row in this ledger. +. *Required-check set.* The repository's rulesets (`Base`, `Branch-Floor`) + currently carry no `required_status_checks` rule, so "removed from the + required set" is vacuously satisfied for the retired jobs. The permanent + cure for rows two and three of the <> table is owner-side: add + `actions.lock is in sync with the workflow YAML` and + `Rust core (tests, formatting, lint)` as required checks on `main`. Until + that is done rule 2 is a convention, not an enforcement. Note the + interaction with <>: a required check that never runs blocks every + bot-authored PR from merging, which is the correct outcome -- it converts + "CI-silent merge" into "cannot merge until an allowed actor triggers CI". + +== Review date + +2026-12-27, or on the first Dependabot actions PR after that date, whichever +comes first: confirm the codeql hold is still needed (has upstream cleared +4.38.1 estate-wide?), confirm no row above has regressed, confirm whether the +required-check rule has been added, and confirm whether the actor refusal in +<> has been lifted or is being worked around by owner-actor merges.