From b55ec8616851456b39f78965ee379307839d74f2 Mon Sep 17 00:00:00 2001 From: Dominik Ottenbreit Date: Sun, 12 Jul 2026 17:11:55 +0200 Subject: [PATCH 1/2] deps: consolidate Dependabot updates (cargo + github-actions) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Supersedes #8 and #9. cargo: the full #9 group (green as filed). github-actions: - actions/checkout v4 -> v7 - actions/upload-artifact v4 -> v7 - actions/download-artifact v4 -> v8 - softprops/action-gh-release v2 -> v3 Drops #8's dtolnay/rust-toolchain 1.82.0 -> 1.100.0 hunk, which is what made #8 fail: Rust 1.100.0 does not exist. That pin is the MSRV from Cargo.toml (rust-version = 1.82), not an action release, so Dependabot must not touch it — now ignored. --- .../roadmap_completion_plan_e99fa144.plan.md | 272 ++++++++++++++++++ .cursor/settings.json | 7 + .github/dependabot.yml | 6 + .github/workflows/action-smoke.yml | 2 +- .github/workflows/ci.yml | 10 +- .github/workflows/release.yml | 8 +- Cargo.lock | 42 +-- Cargo.toml | 2 +- 8 files changed, 305 insertions(+), 44 deletions(-) create mode 100644 .cursor/plans/roadmap_completion_plan_e99fa144.plan.md create mode 100644 .cursor/settings.json diff --git a/.cursor/plans/roadmap_completion_plan_e99fa144.plan.md b/.cursor/plans/roadmap_completion_plan_e99fa144.plan.md new file mode 100644 index 0000000..c60548c --- /dev/null +++ b/.cursor/plans/roadmap_completion_plan_e99fa144.plan.md @@ -0,0 +1,272 @@ +--- +name: Roadmap Completion Plan +overview: Complete the existing architecture roadmap and accepted ADRs by turning each decision into verifiable acceptance criteria, closing remaining implementation/proof gaps, and documenting what is intentionally future scope. +todos: + - id: adr-matrix + content: Map each accepted ADR to concrete code, test, docs, and workflow acceptance criteria. + status: pending + - id: close-gaps + content: Close implementation and proof gaps for skip semantics, repeated-run lossy safety, CI/release hardening, and JSON contract. + status: pending + - id: workflow-review + content: Review CI, release, and action smoke workflows for local-vs-release validation limitations. + status: pending + - id: docs-finalize + content: Update roadmap and README so completed, accepted, and future-only scope are unambiguous. + status: pending + - id: validate + content: Run the full validation checklist and summarize results. + status: pending +isProject: false +--- + +# Roadmap Completion Plan + +## Goal + +Complete the previous architecture plan and all accepted ADRs for the current product direction: a repo-local Rust CLI plus composite GitHub Action used by developers and CI systems. Completion means every accepted decision is represented in code, tests, CI/release workflows, documentation, and local validation. + +This plan does not add a hosted API, Docker/cloud infrastructure, or GUI. Those are explicitly future-only under ADR-0001 and ADR-0002 unless the product direction changes. + +## Completion Definition + +The roadmap is complete when: + +- Every accepted ADR has implementation evidence in the repo. +- Every roadmap phase has acceptance criteria and proof commands. +- CI workflows can validate architecture promises without relying on manual interpretation. +- README and architecture docs explain the product contract for local and CI users. +- Remaining future work is explicitly marked as future by decision, not left as ambiguous unfinished scope. + +## ADR Completion Matrix + +### ADR-0001: Keep `imageopt-core` Frontend-Agnostic + +Files: + +- [docs/architecture/adr/0001-frontend-agnostic-core.md](/Users/dominik/Development/newmatik/image-optimizer/docs/architecture/adr/0001-frontend-agnostic-core.md) +- [crates/core/src/lib.rs](/Users/dominik/Development/newmatik/image-optimizer/crates/core/src/lib.rs) +- [crates/cli/src/main.rs](/Users/dominik/Development/newmatik/image-optimizer/crates/cli/src/main.rs) +- [crates/cli/tests/cli.rs](/Users/dominik/Development/newmatik/image-optimizer/crates/cli/tests/cli.rs) + +Acceptance criteria: + +- `crates/core` remains free of CLI, shell, progress-bar UI, GitHub Action, HTTP, async runtime, and hosted-service dependencies. +- CLI-only behavior stays in `crates/cli`: path expansion, process exit codes, JSON/table output, and progress display. +- CLI behavior has integration tests through the binary, not only unit tests. +- Architecture docs clearly state that server/GUI frontends must be separate frontends around `imageopt-core`. + +Completion work: + +- Add/verify a dependency-boundary check in docs or CI to ensure `imageopt-core` does not gain CLI-only dependencies such as `clap`, `indicatif`, `anstream`, `walkdir`, or `globset`. +- Ensure CLI integration tests cover `--json`, `--check`, skipped files, and non-mutating check mode. +- Update the roadmap to say server/GUI diagnostics are future scope by ADR, not incomplete current scope. + +### ADR-0002: Treat GitHub Releases as the Infrastructure Layer + +Files: + +- [docs/architecture/adr/0002-github-releases-as-infrastructure.md](/Users/dominik/Development/newmatik/image-optimizer/docs/architecture/adr/0002-github-releases-as-infrastructure.md) +- [.github/workflows/release.yml](/Users/dominik/Development/newmatik/image-optimizer/.github/workflows/release.yml) +- [action.yml](/Users/dominik/Development/newmatik/image-optimizer/action.yml) +- [.github/workflows/action-smoke.yml](/Users/dominik/Development/newmatik/image-optimizer/.github/workflows/action-smoke.yml) + +Acceptance criteria: + +- Release matrix builds artifacts but does not publish directly from each matrix job. +- A single publish job uploads release assets after all matrix builds pass. +- Release tag must match crate version before artifacts publish. +- Unix and Windows checksums use a consistent `hash filename` format. +- Composite action verifies downloaded checksums before execution. +- Composite action supports Linux, macOS, and Windows release assets. +- Action smoke workflow validates consumer usage patterns. + +Completion work: + +- Verify `release.yml` publish job has enough permissions and uses the resolved tag consistently for both push tags and `workflow_dispatch`. +- Strengthen `action-smoke.yml`: because the current action downloads release assets, local `uses: ./` smoke tests must either pin `version` to a release known to exist or include a documented limitation that it validates action plumbing against released binaries, not the just-built local binary. +- Add a release dry-run or workflow-level note documenting that full release behavior is validated on tag builds. +- Consider adding artifact attestations only if required by project policy; otherwise record it as future hardening. + +### ADR-0003: Make Skipped Semantics Explicit + +Files: + +- [docs/architecture/adr/0003-explicit-skipped-semantics.md](/Users/dominik/Development/newmatik/image-optimizer/docs/architecture/adr/0003-explicit-skipped-semantics.md) +- [crates/core/src/codecs/mod.rs](/Users/dominik/Development/newmatik/image-optimizer/crates/core/src/codecs/mod.rs) +- [crates/core/src/engine.rs](/Users/dominik/Development/newmatik/image-optimizer/crates/core/src/engine.rs) +- [crates/core/src/codecs/gif.rs](/Users/dominik/Development/newmatik/image-optimizer/crates/core/src/codecs/gif.rs) +- [crates/core/src/codecs/svg.rs](/Users/dominik/Development/newmatik/image-optimizer/crates/core/src/codecs/svg.rs) +- [crates/cli/src/report.rs](/Users/dominik/Development/newmatik/image-optimizer/crates/cli/src/report.rs) +- [crates/core/tests/engine.rs](/Users/dominik/Development/newmatik/image-optimizer/crates/core/tests/engine.rs) + +Acceptance criteria: + +- Codec API supports both candidate outputs and intentional non-fatal skips. +- Animated GIFs report `Skipped` with a reason. +- Unsafe SVGs report `Skipped` with a reason. +- Non-UTF-8 SVGs report `Skipped` with a reason. +- Unsupported detected formats such as AVIF report `Skipped` with a reason. +- CLI table and JSON output preserve skipped reasons. +- `--check` fails on optimizable or failed files, but not on intentionally skipped files. + +Completion work: + +- Add or verify tests for all explicit skip cases listed above. +- Add a CLI JSON test that asserts skipped reason text appears in `results[].error`. +- Verify README documents `Skipped` status and `--check` semantics clearly. + +### ADR-0004: Treat Repeated-Run Lossy Safety as a Core Invariant + +Files: + +- [docs/architecture/adr/0004-repeated-run-lossy-safety.md](/Users/dominik/Development/newmatik/image-optimizer/docs/architecture/adr/0004-repeated-run-lossy-safety.md) +- [crates/cli/src/args.rs](/Users/dominik/Development/newmatik/image-optimizer/crates/cli/src/args.rs) +- [crates/core/src/codecs/jpeg.rs](/Users/dominik/Development/newmatik/image-optimizer/crates/core/src/codecs/jpeg.rs) +- [crates/core/tests/engine.rs](/Users/dominik/Development/newmatik/image-optimizer/crates/core/tests/engine.rs) +- [README.md](/Users/dominik/Development/newmatik/image-optimizer/README.md) + +Acceptance criteria: + +- Lossless remains the default. +- `--lossy` remains explicit opt-in. +- CLI lossy mode defaults `min_savings_percent` to `10.0`. +- `--quality` implies lossy and uses the same convergence threshold. +- Explicit `--min-savings` overrides the default. +- JPEG lossy mode skips destructive re-encoding when source quantization appears at or below target quality. +- Repeated lossy-mode runs do not apply destructive JPEG recompression for marginal savings. +- README explains the CI risk and safe defaults. + +Completion work: + +- Keep the current JPEG quantization guard, but add small unit tests for the parser/estimator if the helpers remain private but testable within `jpeg.rs`. +- Keep integration coverage that checks decoded-pixel stability on repeated JPEG lossy-mode runs. +- Add a WebP repeated-run test if feasible; if WebP source-quality detection is not possible with current dependencies, document that min-savings threshold is the current WebP guard. + +## Roadmap Phase Completion Plan + +```mermaid +flowchart TD + auditCurrent["Audit current branch"] --> adrMatrix["Map ADRs to code, tests, docs, CI"] + adrMatrix --> implementationGaps["Close implementation gaps"] + implementationGaps --> proofGaps["Close proof gaps"] + proofGaps --> ciRelease["Validate CI and release workflows"] + ciRelease --> docsTruth["Update roadmap status and README contract"] + docsTruth --> finalValidation["Run full local validation"] +``` + +### Phase 1: Correctness and Product Truth + +Acceptance criteria: + +- README no longer claims AVIF optimization exists. +- AVIF detection is documented as detected but skipped until optimizer support exists. +- Intentional skips are visible in CLI and JSON. +- Invalid globs and directory walk errors are reported to stderr. + +Completion tasks: + +- Verify AVIF documentation in README and roadmap. +- Verify path expansion diagnostics do not break JSON stdout contract. +- Add CLI tests if current test coverage does not assert stderr/JSON separation. + +### Phase 2: Test Coverage Expansion + +Acceptance criteria: + +- Core integration tests cover PNG, JPEG, WebP, static GIF, animated GIF, safe SVG, unsafe SVG, corrupt JPEG, unknown format, AVIF, backup/no-clobber, dry-run, in-place writes, batch ordering, progress events, and max-pixels rejection. +- CLI integration tests cover JSON summary, `--check`, skipped files, and no mutation in check mode. +- Fuzz target exists for `optimize_bytes`. + +Completion tasks: + +- Add missing tests from the acceptance list. +- Add skipped-reason JSON assertion. +- Add `cargo fuzz` instructions to architecture docs and README or contributor docs. + +### Phase 3: CI/CD Hardening + +Acceptance criteria: + +- CI has concurrency cancellation. +- CI runs stable multi-OS fmt/clippy/test. +- CI runs MSRV tests. +- CI runs `cargo audit` and `cargo deny check` using committed policy files. +- CI uploads coverage artifact from `cargo llvm-cov`. +- Action smoke workflow validates consumer action usage. + +Completion tasks: + +- Verify `cargo deny check` and `cargo audit` pass locally. +- Ensure `.cargo/audit.toml` and `deny.toml` include rationale for every ignore or allowance. +- Decide whether duplicate dependency versions are allowed, warned, or denied; keep CI output actionable. +- Review action smoke workflow for the released-binary vs local-action limitation. + +### Phase 4: Release and Supply-Chain Hardening + +Acceptance criteria: + +- Release workflow runs tests before packaging. +- Release workflow verifies tag equals crate version. +- Release workflow publishes from one final job. +- Composite action verifies checksums before execution. +- Composite action supports Windows `.zip` and `.exe` assets. + +Completion tasks: + +- Verify release workflow syntax and tag handling. +- Review Windows extraction path in `action.yml` and ensure the shell used by composite action supports `uname`, `find`, checksum commands, and PowerShell fallback. +- Optionally add artifact attestation if project policy requires it; otherwise document as deferred. + +### Phase 5: Observability and CI Consumer Contract + +Acceptance criteria: + +- JSON output includes a top-level `summary` and per-file `results`. +- JSON summary includes totals, optimized/skipped/failed counts, original and optimized bytes, saved bytes/percent, elapsed time, and per-format counts. +- README documents JSON schema and status values. +- Server/GUI structured diagnostics are explicitly future-only. + +Completion tasks: + +- Add or verify CLI integration test for JSON summary shape. +- Add skipped-reason JSON test. +- Ensure README says skipped files do not fail `--check`, failed files do. + +## Final Validation Checklist + +Run before declaring completion: + +```bash +cargo fmt --all --check +cargo fmt --all --check --manifest-path fuzz/Cargo.toml +cargo clippy --all-targets --all-features -- -D warnings +cargo test --all-features +cargo check --manifest-path fuzz/Cargo.toml +cargo deny check +cargo audit +cargo metadata --no-deps --format-version 1 +``` + +Review before declaring completion: + +- `git diff --stat` +- `git diff -- .github/workflows/ci.yml .github/workflows/release.yml .github/workflows/action-smoke.yml action.yml` +- `git diff -- docs/architecture README.md` +- `git diff -- crates/core crates/cli` + +## Explicit Non-Goals + +- Do not add a hosted API in this completion pass. +- Do not add Docker, Terraform, Cloudflare, Supabase, or Sentry infrastructure. +- Do not add a GUI frontend. +- Do not preserve compatibility with incorrect unshipped branch behavior if it conflicts with accepted ADRs. + +## Completion Output + +When done, provide: + +- A short ADR-by-ADR completion summary. +- The exact validation commands and results. +- Any remaining deferred items, only if they are explicitly future scope by ADR. +- A note about workflow behavior that cannot be fully proven locally, such as tag-triggered GitHub Release publishing. \ No newline at end of file diff --git a/.cursor/settings.json b/.cursor/settings.json new file mode 100644 index 0000000..fff67ff --- /dev/null +++ b/.cursor/settings.json @@ -0,0 +1,7 @@ +{ + "plugins": { + "sentry": { + "enabled": true + } + } +} diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 23adcc9..df6a5dd 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -31,3 +31,9 @@ updates: groups: github-actions: patterns: ["*"] + ignore: + # dtolnay/rust-toolchain is pinned to the MSRV from Cargo.toml (rust-version), not to + # an action release. Dependabot reads that pin as an action tag and "bumps" it to a + # Rust version that does not exist (it proposed 1.100.0), which both breaks the job and + # defeats the point of the MSRV gate. The pin moves when the MSRV moves — by hand. + - dependency-name: "dtolnay/rust-toolchain" diff --git a/.github/workflows/action-smoke.yml b/.github/workflows/action-smoke.yml index cf3a183..ced3a96 100644 --- a/.github/workflows/action-smoke.yml +++ b/.github/workflows/action-smoke.yml @@ -14,7 +14,7 @@ jobs: name: Composite action runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 with: persist-credentials: false diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0da6859..c6c3932 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,7 @@ jobs: matrix: os: [ubuntu-latest, macos-latest, windows-latest] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 with: persist-credentials: false @@ -60,7 +60,7 @@ jobs: name: MSRV runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 with: persist-credentials: false @@ -81,7 +81,7 @@ jobs: name: Supply Chain runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 with: persist-credentials: false @@ -107,7 +107,7 @@ jobs: name: Coverage runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 with: persist-credentials: false @@ -130,7 +130,7 @@ jobs: run: cargo llvm-cov --workspace --all-features --lcov --output-path lcov.info - name: Upload coverage artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: coverage-lcov path: lcov.info diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c570540..ce6b940 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -38,7 +38,7 @@ jobs: - os: windows-latest target: x86_64-pc-windows-msvc steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 with: # Build from the requested tag (workflow_dispatch can run from any # ref), and don't leave the job token in .git/config for cargo build. @@ -119,7 +119,7 @@ jobs: "$hash $name.zip" | Out-File "dist/$name.zip.sha256" -Encoding ascii - name: Upload packaged artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: imageopt-${{ matrix.target }} path: dist/* @@ -131,13 +131,13 @@ jobs: runs-on: ubuntu-latest steps: - name: Download packaged artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: path: dist merge-multiple: true - name: Upload to release - uses: softprops/action-gh-release@v2 + uses: softprops/action-gh-release@v3 with: tag_name: ${{ github.event.inputs.tag || github.ref_name }} generate_release_notes: true diff --git a/Cargo.lock b/Cargo.lock index 52cfd6f..cd896e9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,21 +17,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "anstream" -version = "0.6.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" -dependencies = [ - "anstyle", - "anstyle-parse 0.2.7", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", -] - [[package]] name = "anstream" version = "1.0.0" @@ -39,7 +24,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" dependencies = [ "anstyle", - "anstyle-parse 1.0.0", + "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", @@ -53,15 +38,6 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" -[[package]] -name = "anstyle-parse" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" -dependencies = [ - "utf8parse", -] - [[package]] name = "anstyle-parse" version = "1.0.0" @@ -195,7 +171,7 @@ version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" dependencies = [ - "anstream 1.0.0", + "anstream", "anstyle", "clap_lex", "strsim", @@ -203,9 +179,9 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.6.5" +version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7a9bfdb35811f9e59832f0f05975114d2251b415fb534108e6f34060fd772" +checksum = "db8b397918185f0161ff3d6fcaa9e4bfc09b8367caf6e1d4a2848e5477ed027b" dependencies = [ "clap", ] @@ -242,9 +218,9 @@ checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "console" -version = "0.16.3" +version = "0.16.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d64e8af5551369d19cf50138de61f1c42074ab970f74e99be916646777f8fc87" +checksum = "4fe5f465a4f6fee88fad41b85d990f84c835335e85b5d9e6e63e0d06d28cba7c" dependencies = [ "encode_unicode", "libc", @@ -554,7 +530,7 @@ dependencies = [ name = "imageopt" version = "0.2.1" dependencies = [ - "anstream 0.6.21", + "anstream", "clap", "clap_complete", "globset", @@ -620,9 +596,9 @@ dependencies = [ [[package]] name = "indicatif" -version = "0.18.5" +version = "0.18.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "993f007684f2e9727160da8b960ec161264703bfd1af084fd2e34d040c9a0dd4" +checksum = "9433806cd6b4ec1aba79c021c7e4c58fb4c3b9977c085062e611ac929998fb0c" dependencies = [ "console", "portable-atomic", diff --git a/Cargo.toml b/Cargo.toml index 33694c8..ed75c7d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,7 +40,7 @@ walkdir = "2" globset = "0.4" indicatif = "0.18" owo-colors = "4" -anstream = "0.6" +anstream = "1.0" # --- shared --- serde = { version = "1", features = ["derive"] } From 7ed0ea0759f21daa2ebf9e5c35f5d3569f3283f4 Mon Sep 17 00:00:00 2001 From: Dominik Ottenbreit Date: Sun, 12 Jul 2026 17:16:08 +0200 Subject: [PATCH 2/2] ci: ignore RUSTSEC-2026-0206 (rustybuzz unmaintained) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Newly published advisory on the same unfixable chain as RUSTSEC-2026-0192, already ignored just above it: rustybuzz is pinned by usvg 0.47, so there is nothing to upgrade to. Present on main too (rustybuzz 0.20.1 is identical there) — cargo-deny fetches the advisory DB live, so main would fail this today as well. --- deny.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/deny.toml b/deny.toml index 68038aa..10a081c 100644 --- a/deny.toml +++ b/deny.toml @@ -8,6 +8,9 @@ ignore = [ # at the time of writing; keep this visible so it can be removed when usvg # moves to a maintained font parser stack. "RUSTSEC-2026-0192", + # Same chain: rustybuzz is now flagged unmaintained. usvg 0.47 pins it, so there is + # nothing to upgrade to. Drop this together with the line above when usvg moves off it. + "RUSTSEC-2026-0206", ] [licenses]