diff --git a/.github/workflows/a11y.yml b/.github/workflows/a11y.yml index e92654f2..c0100a43 100644 --- a/.github/workflows/a11y.yml +++ b/.github/workflows/a11y.yml @@ -50,7 +50,7 @@ jobs: shared-key: a11y-keyboard - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: - node-version: "20" + node-version: "22" cache: npm - name: Install a11y deps run: npm ci @@ -59,8 +59,10 @@ jobs: - name: Install Playwright Chromium run: npx --yes playwright@1.62.1 install --with-deps chromium - name: Run keyboard Tab-cycle - env: - SHARECLI_VISUAL_FIXTURE: "1" + # No SHARECLI_VISUAL_FIXTURE here: the fixture's WebSocket mock never + # dispatches a message, so the dashboard label never reaches exactly + # "connected" and the readiness wait times out. Against the real + # server the periodic snapshot sets the label and the cycle passes. run: | set -euo pipefail ./target/release/sharecli serve --bind 127.0.0.1:9000 & diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bec33e13..ef4f292a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -218,6 +218,11 @@ jobs: continue-on-error: true steps: - uses: actions/checkout@v7 + with: + # gitleaks-action scans `firstCommit^..head`; with the default + # depth-1 fetch the first commit's parent is missing and the scan + # fails with "unknown revision" on any multi-commit PR. + fetch-depth: 0 - name: gitleaks uses: gitleaks/gitleaks-action@v3 env: diff --git a/.github/workflows/mutants.yml b/.github/workflows/mutants.yml index 56732fd3..985a6893 100644 --- a/.github/workflows/mutants.yml +++ b/.github/workflows/mutants.yml @@ -60,14 +60,15 @@ jobs: cargo mutants --timeout 60 --jobs 2 \ -p ${{ matrix.crate }} \ --config ${{ matrix.config }} \ - --json-outfile ${{ matrix.json_out }} \ -- --locked # cargo-mutants exits non-zero if survivors remain — that fails this job. + # The JSON report is always written to mutants.out/outcomes.json. + test -f mutants.out/outcomes.json - name: Upload mutants JSON if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: mutants-hard-${{ matrix.crate }}-${{ github.sha }} - path: ${{ matrix.json_out }} + path: mutants.out/outcomes.json if-no-files-found: ignore retention-days: 14 diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index db717ea1..5f258ad0 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -21,6 +21,8 @@ permissions: # `read-all` (previous value) blocked the token and failed the run with # "error obtaining token: expired_token" during result signing. id-token: write + # Required by the SARIF upload step (github/codeql-action/upload-sarif). + security-events: write jobs: analysis: diff --git a/.github/workflows/trunk-check.yml b/.github/workflows/trunk-check.yml index b89f2205..6e7df2b2 100644 --- a/.github/workflows/trunk-check.yml +++ b/.github/workflows/trunk-check.yml @@ -4,6 +4,15 @@ # Handles: ruff, mypy, clippy, golangci-lint, prettier, eslint, shellcheck, etc. # Free for open source; cached for fast runs # ============================================================================= +# +# NOTE (2026-08): the lane runs the same linters that `.trunk/trunk.yaml` +# enables (actionlint + taplo + yamllint) via direct, pinned installs instead +# of trunk-io/trunk-action. trunk-action's managed tool bootstrap repeatedly +# failed on ubuntu-latest ("Binary not found" / "jq not installed on system!" +# inside its launcher), while the tools themselves install cleanly. actionlint +# runs from the official docker image (no download-and-execute in the lane). +# The `.trunk/` config remains the local-developer source of truth +# (`trunk check`). name: Trunk Check @@ -28,11 +37,29 @@ jobs: - name: Checkout uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - name: Trunk Check - uses: trunk-io/trunk-action@v1 - - - name: Trunk Upgrade (on schedule only) - if: github.event_name == 'schedule' - uses: trunk-io/trunk-action@v1 + - name: Run actionlint + # Official pinned image; avoids curl|tar installs (SonarCloud flags + # download-and-execute patterns as a security issue on new code). + # `-shellcheck=` disables the image's bundled shellcheck: trunk's + # actionlint integration runs without shellcheck too (and the repo's + # workflows carry pre-existing SC2034/SC2086 advisories that trunk + # does not gate on). + uses: docker://rhysd/actionlint:1.7.12 with: - trunk-args: --upgrade + args: -shellcheck= + + - name: Install taplo + run: cargo install taplo-cli --locked --version 0.9.3 + + - name: Install yamllint + run: sudo apt-get update && sudo apt-get install -y yamllint + + - name: Run remaining trunk linters + run: | + set -euo pipefail + # actionlint runs in the docker step above (reads + # .github/actionlint.yaml for ignore rules). + # Same whitespace-only formatting contract as `trunk fmt`. + taplo fmt --check + # Same relaxed ruleset as `.trunk/trunk.yaml`'s yamllint section. + yamllint -c .trunk/configs/.yamllint.yaml . diff --git a/.trunk/configs/.yamllint.yaml b/.trunk/configs/.yamllint.yaml index 3f9e6fb2..0b26b4c4 100644 --- a/.trunk/configs/.yamllint.yaml +++ b/.trunk/configs/.yamllint.yaml @@ -11,6 +11,13 @@ extends: default +# Vendored/generated directories are not linted (gitignored locally; never +# present on CI checkouts, but direct `yamllint .` runs see them). +ignore: | + node_modules/ + target/ + .git/ + rules: line-length: disable comments: diff --git a/crates/sharecli-core/src/speculation.rs b/crates/sharecli-core/src/speculation.rs index 14d8c072..399c12cd 100644 --- a/crates/sharecli-core/src/speculation.rs +++ b/crates/sharecli-core/src/speculation.rs @@ -163,6 +163,12 @@ pub fn spawn_speculation_task( cache: CoalesceCache, thermal_gate: Arc, ) { + // Best-effort background task. The hypervisor constructor may run outside + // a Tokio runtime (sync CLI wiring, unit tests); without a reactor there + // is nothing to spawn onto, so skip silently rather than panic. + if tokio::runtime::Handle::try_current().is_err() { + return; + } tokio::spawn(async move { loop { tokio::time::sleep(SPECULATION_INTERVAL).await; diff --git a/crates/sharecli-session/tests/session_ledger.rs b/crates/sharecli-session/tests/session_ledger.rs index ee77dd4d..6e816cfb 100644 --- a/crates/sharecli-session/tests/session_ledger.rs +++ b/crates/sharecli-session/tests/session_ledger.rs @@ -1,3 +1,6 @@ +//! FR:011 / C10 — session ledger durability: observations survive a store +//! reopen, and heuristic-confidence observations persist without being marked +//! auto-resumable. use sharecli_session::{ AgentSession, ObservationKind, ResolutionConfidence, SessionObservation, SessionStore, SurfaceCapabilities, SurfaceRecord, diff --git a/src/audit_log.rs b/src/audit_log.rs index 2466f337..e8e069e5 100644 --- a/src/audit_log.rs +++ b/src/audit_log.rs @@ -167,9 +167,14 @@ mod tests { use super::*; + /// Serializes all audit tests that mutate process-global env vars. + /// The tests otherwise race on `SHARECLI_AUDIT_LOG` (e.g. one test + /// removes the var while another is mid-emit, so the file is never + /// written and `read_to_string` fails with `NotFound`). + static ENV_LOCK: Mutex<()> = Mutex::new(()); + #[test] fn emit_if_configured_respects_env_gate() { - static ENV_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(()); let _env = ENV_LOCK.lock().unwrap(); let dir = tempfile::tempdir().unwrap(); let path = dir.path().join("gated.jsonl"); @@ -205,9 +210,9 @@ mod tests { #[test] fn path_respects_env_override() { + let _env = ENV_LOCK.lock().unwrap(); let dir = tempfile::tempdir().unwrap(); let path = dir.path().join("custom.jsonl"); - let _guard = WRITE_LOCK.lock().unwrap_or_else(|e| e.into_inner()); unsafe { std::env::set_var("SHARECLI_AUDIT_LOG", &path); } @@ -219,7 +224,6 @@ mod tests { #[test] fn rotates_when_over_max_bytes() { - static ENV_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(()); let _env = ENV_LOCK.lock().unwrap(); let dir = tempfile::tempdir().unwrap(); let path = dir.path().join("audit.jsonl"); diff --git a/tests/visual/dashboard/desktop.png b/tests/visual/dashboard/desktop.png index a44bcf45..cc95926f 100644 Binary files a/tests/visual/dashboard/desktop.png and b/tests/visual/dashboard/desktop.png differ diff --git a/tests/visual/dashboard/manifest.json b/tests/visual/dashboard/manifest.json index a77f4706..9ed6e493 100644 --- a/tests/visual/dashboard/manifest.json +++ b/tests/visual/dashboard/manifest.json @@ -3,13 +3,13 @@ "surface": "src/dashboard.html", "fixture": "empty-pool + operator envelope (fresh sharecli serve / visual WS fixture)", "platform": "ubuntu-24.04 + Playwright Chromium", - "seed_sha": "91448b2", - "seed_date": "2026-07-18", + "seed_sha": "a958f78", + "seed_date": "2026-08-10", "baselines": [ { "name": "mobile", "file": "mobile.png", - "bytes": 11043, + "bytes": 75563, "viewport": { "width": 375, "height": 812 }, "artifact_source": "artifacts/playwright/mobile-375.png", "max_pixel_delta_pct": 0.2 @@ -17,7 +17,7 @@ { "name": "tablet", "file": "tablet.png", - "bytes": 14120, + "bytes": 86045, "viewport": { "width": 768, "height": 1024 }, "artifact_source": "artifacts/playwright/tablet-768.png", "max_pixel_delta_pct": 0.1 @@ -25,11 +25,11 @@ { "name": "desktop", "file": "desktop.png", - "bytes": 14227, + "bytes": 107391, "viewport": { "width": 1280, "height": 800 }, "artifact_source": "artifacts/playwright/desktop-1280.png", "max_pixel_delta_pct": 0.1 } ], - "notes": "Committed Ubuntu PNG baselines with byte-size lock. Hard diff: scripts/visual/compare_screenshots.mjs + visual-soft.yml. Capture with SHARECLI_VISUAL_FIXTURE=1; regen with UPDATE_VISUALS=1 only from the ubuntu-24.04 gate." + "notes": "Committed Ubuntu PNG baselines with byte-size lock. Hard diff: scripts/visual/compare_screenshots.mjs + visual-soft.yml. Capture with SHARECLI_VISUAL_FIXTURE=1; regen with UPDATE_VISUALS=1 only from the ubuntu-24.04 gate. Regenerated 2026-08-10 from workflow_dispatch run 31346443985 (main a958f78, playwright 1.62.1 chromium v1234): the 2026-07-18 goldens predate the dashboard's taller mobile layout (page grew 431px; tablet/desktop drifted ~2.9%)." } diff --git a/tests/visual/dashboard/mobile.png b/tests/visual/dashboard/mobile.png index fba4a776..12b75ca0 100644 Binary files a/tests/visual/dashboard/mobile.png and b/tests/visual/dashboard/mobile.png differ diff --git a/tests/visual/dashboard/tablet.png b/tests/visual/dashboard/tablet.png index 55c0eef1..7bc4350d 100644 Binary files a/tests/visual/dashboard/tablet.png and b/tests/visual/dashboard/tablet.png differ