Skip to content

ci: dependabot NOTICE auto-heal, always-on size gate, honest hermetic matrix, remove-by wired - #554

Merged
runyourempire merged 6 commits into
mainfrom
ci/dependabot-notice-autoheal-and-gates
Aug 31, 2026
Merged

ci: dependabot NOTICE auto-heal, always-on size gate, honest hermetic matrix, remove-by wired#554
runyourempire merged 6 commits into
mainfrom
ci/dependabot-notice-autoheal-and-gates

Conversation

@runyourempire

Copy link
Copy Markdown
Collaborator

Lane D of the 2026-08-31 audit remediation: four known CI holes, each re-verified against live state before touching anything. Two needed changes (shipped here); two turned out to be already closed on the current base — documented below so the audit trail reflects what was actually found, not what the brief assumed.

Hole 1 — Dependabot NOTICE starvation (FIXED: new dependabot-notice job)

The hole. Repo guards requires node scripts/generate-notice.cjs --check --require to pass on every PR: third-party attribution must be regenerated in the same change as any dependency move. Dependabot cannot run repo scripts, so every cargo or production-npm bump it opens fails Third-party attribution is current (NOTICE) and rots. Verified live at step level before fixing: PRs #457 (cargo zip) and #516 (npm react) both fail exactly that step, every other repo-guards step green. 27 Dependabot PRs are currently open, several ecosystems pinned at their open-pull-requests-limit — the gate had starved automated dependency updates entirely.

The fix. A dependabot-notice job in validate.yml, gated github.event_name == 'pull_request' && github.actor == 'dependabot[bot]', that completes the commit on Dependabot's behalf:

  • Checks out the PR branch (github.event.pull_request.head.ref), not the unpushable detached merge ref. Dependabot branches always live in this repo, never a fork, so the job token can push them.
  • Rebuilds NOTICE with the exact recipe the gate itself uses (stable Rust toolchain for cargo metadata, pnpm install --frozen-lockfile --ignore-scripts for pnpm licenses list), so what it generates is byte-for-byte what --check will verify. Verified end-to-end locally: that recipe followed by write-mode generate-notice.cjs reproduces the committed NOTICE with zero diff on a current tree, and git diff --quiet -- NOTICE is the correct changed-detector.
  • If NOTICE changed: commits it, pushes to the branch, and re-dispatches Validate on the healed head.

Three GitHub mechanics are load-bearing and documented in the job comment:

  1. Dependabot-triggered runs get a read-only GITHUB_TOKEN regardless of repo defaults. The job-level permissions: block (contents: write, actions: write) elevates this job only; every other job keeps its defaults.
  2. Pushes made with GITHUB_TOKEN never trigger workflow runs (GitHub's recursion guard). Without compensation the healed commit would sit forever with no Validate Success on it — and the main ruleset's sole required check (verified via rules/branches/main: Validate Success, strict) reads the PR's head SHA. workflow_dispatch is the documented exception to the recursion guard, and validate.yml already runs its full suite on dispatch (the heavy legs' github.event_name == 'workflow_dispatch' clauses), so the job dispatches Validate on the healed branch tip. Its check runs attach to the new head SHA, which is what the merge gate reads. Hermetic is deliberately NOT re-dispatched: the healed commit differs from the already-hermetic-tested head by NOTICE text only, Hermetic Success is not a required check, and a ~20-min two-OS cacheless matrix would spend metered hosted minutes to learn nothing.
  3. No loop is possible. The heal push triggers nothing (recursion guard); in the dispatched run the job skips (neither pull_request nor the Dependabot actor test holds). If Dependabot force-pushes a rebase, the NOTICE commit is dropped — but that push is a Dependabot-actored pull_request event, so the heal simply runs again on the new head. A push race with a Dependabot rebase is rejected non-fast-forward and self-corrects the same way.

validate-success now includes dependabot-notice in needs: it skips (== pass) on every non-Dependabot PR, but a heal failure on a Dependabot PR is visible in the aggregate instead of vanishing.

Expected behavior on a healed PR: the original run's Repo guards still fails against the pre-heal commit (correct — that SHA is stale), and the dispatched run on the healed head goes green and satisfies the ruleset. Bumps that don't move the shipped closure (dev-dep and actions bumps — verified on #517/#518) regenerate to no diff, push nothing, and dispatch nothing.

Hole 2 — file-size gate skippable on Rust-only PRs (VERIFIED ALREADY CLOSED — no change)

The brief said Check file sizes runs inside the path-filtered Frontend job. Checked against the current base before acting: it was moved to repo-guards on 2026-08-14 (the job's own comment documents the #423/#430 incident), the Frontend job carries a tombstone comment pointing there, repo-guards has no if: and no needs: so it runs on every PR and dispatch, and live Dependabot runs show Check file sizes executing and passing inside Repo guards. node scripts/check-file-sizes.cjs --ci exits 0 on this tree. Nothing to do; recorded here so the hole is closed with evidence rather than by assumption.

Hole 3 — Hermetic vacuously green on main (HALF ALREADY CLOSED, other half FIXED: install retry)

Vacuous-green half: already closed. The !cancelled() wrapper on fresh-clone's if: (documented in-file, with the 2026-08-13/14 observations of 7-9s "successes") already forces the full matrix on push/schedule/dispatch; the path filter applies only to pull_request. Verified against live runs, not just the YAML: the latest push run built for ~19 min and the latest schedule run for ~23 min — real two-OS cold builds, not 1-second skips. No change.

Retry half: shipped. pnpm install --frozen-lockfile now retries up to 3 attempts (20s backoff, ::warning:: per miss, ::error:: on exhaustion) in both places that build better-sqlite3 natively:

better-sqlite3 falls back to a node-gyp source build whenever no prebuilt binary matches the image's Node ABI (the 13.x line in particular), and that native build flakes on windows-latest. Three attempts separates network/toolchain weather from a genuine fresh-clone break; anything still failing after 3 tries IS the finding.

Hole 4 — check-remove-by.cjs unenforced (FIXED: wired into Repo guards)

Shipped in #421 and wired to nothing — verified by grep across package.json, .husky/, and .github/: zero references. (.husky/pre-commit runs the staged-files-only cousin check-dead-code-expiry.cjs, which covers only #[allow(dead_code)]; the whole-tree REMOVE BY <date> gate never ran anywhere, so an expired marker could never fail anything.) Now a step in repo-guards — the unfiltered job that runs on every PR — invoked as node scripts/check-remove-by.cjs --ci for GitHub annotations. Exits 0 on the current tree (49 markers, all future-dated, 0 expired, 0 allowlisted), so this ratchets from a clean baseline.

Verification performed

Out of scope (deliberately untouched)

Frontend job's npx/audit steps and its 30-min timeout; release.yml (signing owned elsewhere).

Residual risks, disclosed

  • The heal costs ~4-8 hosted-runner minutes per Dependabot PR sync (toolchain + install + cargo metadata); dev-dep bumps pay it only to conclude "no diff". Acceptable against 27 rotting PRs.
  • After the bot pushes to a Dependabot branch, Dependabot treats the PR as externally modified and stops auto-rebasing it; @dependabot rebase still works and simply re-triggers the heal on the recreated branch.
  • The dispatched Validate run executes the FULL suite (dispatch runs all heavy legs) — heavier than the path-filtered PR run, but the only dispatch-shaped trigger that exists, and correctness beats economy at a required gate.

🤖 Generated with Claude Code

https://claude.ai/code/session_01LrXvdHoDGUj99Fqf1fCYJY

… matrix, remove-by wired

Closes the 2026-08-31 audit's four CI holes (two by change, two verified
already closed on this base):

1. Dependabot NOTICE starvation: new `dependabot-notice` job in validate.yml
   regenerates NOTICE on Dependabot's PR branch with the exact recipe the
   repo-guards gate uses, pushes it back (contents: write, job-scoped), and
   re-dispatches Validate on the healed head because GITHUB_TOKEN pushes
   never trigger workflow runs. 27 Dependabot PRs were rotting on the gate.

2. File-size gate on Rust-only PRs: verified ALREADY CLOSED on this base -
   `Check file sizes` lives in the unfiltered repo-guards job (moved there
   2026-08-14 after #423/#430); confirmed green in live runs. No change.

3. Hermetic vacuous green on push/schedule: the unconditional-matrix half is
   ALREADY CLOSED (the `!cancelled()` wrapper; live push/schedule runs build
   for 19-23 min). Remaining half shipped here: 3-attempt retry around both
   pnpm installs (root + mcp-4da-server) - better-sqlite3 is a direct dep of
   both, and its 13.x node-gyp fallback flakes on windows-latest.

4. check-remove-by.cjs (#421) wired into repo-guards with --ci annotations;
   it previously ran nowhere (package.json, husky, and CI all clean of it).

Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LrXvdHoDGUj99Fqf1fCYJY
@runyourempire
runyourempire enabled auto-merge (squash) August 30, 2026 16:02
@runyourempire
runyourempire merged commit ef13e95 into main Aug 31, 2026
16 checks passed
@runyourempire
runyourempire deleted the ci/dependabot-notice-autoheal-and-gates branch August 31, 2026 05:02
runyourempire added a commit that referenced this pull request Aug 31, 2026
…l cause, and dev kills stop hitting peers (#566)

Three items from the approved 2026-08-31 recommendation set (recs #4 and
#6; #1/#2/#5 are sequenced behind in-flight lanes, #3 behind #554).

## 1. Truthful AI usage panel

`UsageStatsSection` rendered `settings.usage` — the **rerank-only
ledger** — as "cost today". After #553 split usage recording per
feature, that under-reported real spend ~10-30x while the true totals
sat dark (`get_ai_usage_summary` existed with zero frontend callers).
The panel now shows:

- **Cost today vs the configured daily cap** from `get_llm_usage`
(global ledger: all features, restart-seeded, millicent-backed), with an
80%/limit color ramp
- **Tokens today** from the same ledger
- **This month total + per-feature breakdown** from
`get_ai_usage_summary` — "what is burning tokens" becomes answerable
from Settings instead of SQL

New `usage` i18n namespace translated for all 13 locale dirs; unknown
`task_type` tags render raw rather than hiding spend. 4 component tests
pin the rerank-ledger regression (absurd sentinel values in
`settings.usage` must never render).

## 2. Sentinel classifies by meaning, not text

Live false positive: a cold `tsc` exceeded the scan's 60s timeout → zero
`error TS` lines → "TypeScript compilation failed **(0 errors)**"
reported as CRITICAL, deploying an expert against a compiler that never
finished. `classifyTscResult` (pure, exported) now goes critical only on
actual error lines; timeouts and spawn failures become "check
inconclusive — <real cause>" warnings. Same treatment for the Rust
branch's non-zero-exit-no-errors path. 5 negative tests per the
gate-precision doctrine (`recipe-gate-precision-negative-test`); the
script gains a `require.main` guard so tests import it without executing
a scan.

## 3. Path-scoped dev kills

`taskkill /F /IM fourda.exe` kills every fourda on the machine — two
scheduled background-refresh engine runs were killed mid-cycle **today**
as collateral from a peer lane's dev restart (task result 0xFFFFFFFF).
`scripts/stop-fourda.ps1` stops only instances launched from the
caller's tree's `src-tauri/target/`, supports `-Root`/`-DryRun`, and
lists what it deliberately left running. CLAUDE.md now prescribes it and
bans the image-name kill.

## Verification

- vitest: **1,248/1,248**; `tsc --noEmit` clean
- `node --test scripts/sentinel-scan.test.cjs`: 5/5; live `--quick` scan
runs green through the modified script
- `i18n-guard.cjs` clean across all 13 locales; dry-run of
`stop-fourda.ps1` verified against live processes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_019RTCgzAcaopjz3KZnaqNrj

Co-authored-by: Claude Fable 5 <[email protected]>
runyourempire added a commit that referenced this pull request Aug 31, 2026
`.husky/commit-msg` guards locally authored messages; `.husky/pre-push` guards
outgoing commits. Neither runs when GitHub performs a SQUASH MERGE, because
that commit message is composed server-side from the PR title and body.

That gap has already fired. Two commits on public `main` — `d5df9e34` (#409)
and `053e5813` (#362) — carry the private external-verifier name in their
MESSAGES. Removing those two messages now needs a public-history rewrite. This
stops the next one, which is the part still in our control.

The check runs on `pull_request`, where the title/body are known BEFORE the
merge button, and scans title + body + every commit message in the range via
the existing hashed detector in scripts/private-asset-guard.cjs (no literal
name is added anywhere). Wired into `validate-success`'s `needs`, so it gates
through the existing required check — no branch-protection change required.

Rebased 2026-08-31 onto the post-#554/#569 validate.yml: the job is re-sited
at the end-of-`changes` boundary (the exact seam #554's comment reserved for
this PR), pins match main's checkout SHA, and `validate-success.needs` now
carries all eight legs including `pr-metadata`. MERGE-QUEUE SAFE, verified
explicitly: `github.event.pull_request` does not exist on merge_group entries,
so the job's `if: github.event_name == 'pull_request'` SKIPS it there — the
identical guard the `changes` detector has run live on every queue entry since
#569 — and Validate Success's allowlist counts skipped as pass. The queue
never waits on metadata that does not exist; the pull_request run that armed
the merge has already scanned it.

Two deliberate choices, unchanged from the original:

* FAIL-CLOSED. A guard that silently cannot run is not a guard — and silent
  failure is the exact bug class this check exists to stop.
* Title and body reach the script through the ENVIRONMENT, never `${{ }}`
  inlined into the `run:` block. PR text is attacker-controlled; inlining it
  is a script-injection primitive.

Verified end-to-end against the real detector on 2026-08-31:
  * range containing `d5df9e34` -> BLOCKED, naming the offending commit (exit 1)
  * clean range (`adfb68ff`)    -> passes (exit 0)
  * 7/7 unit tests (injected predicate, so no private literal in a tracked test)
  * workflow YAML parses; 53 inline scripts parse; `validate-success.needs`
    includes `pr-metadata`; #554's dependabot-notice/repo-guards and #569's
    merge_group trigger intact

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LrXvdHoDGUj99Fqf1fCYJY
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant