# Soak

> Manages the repo's supply-chain soak window (SOAK_DAYS) — checks and fixes the derived surfaces, bumps or disables the window, adds dated per-package exclusions, and bumps pinned external tools. Use when a task touches minimumReleaseAge, min-release-age, min-publish-age, external-tools.json, renovate.json, or taze cooldowns, or when investigating why a freshly published version won't install.

- Skill: `nubjs/soak` (Agent Skill)
- Install (CLI): `npx skillmds@latest add nubjs/soak`
- Raw SKILL.md: https://api.skillmd.com/api/skills/nubjs/soak/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Productivity
- Author: nubjs (https://skillmd.com/u/nubjs)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/nubjs/soak

---


# The soak window

One rule: a release must be at least `SOAK_DAYS` old before this repo adopts it. The delay gives the ecosystem time to catch a malicious or yanked release before we ever install it. The window is defined exactly once — read the current value from `scripts/soak/constants.mts` and never hardcode it elsewhere. Every surface derives from or is parity-checked against it:

| Surface | Key | Units |
|---|---|---|
| `.cargo/config.toml` | `global-min-publish-age` | `"N days"` |
| `tools/pnpm-workspace.yaml` | `minimumReleaseAge` | minutes |
| `.npmrc` | `min-release-age` | days |
| `tools/taze.config.mts` | `maturityPeriod` | imports `SOAK_DAYS` |
| `external-tools.json` | `soakBypass` annotations | days |
| `.github/renovate.json` | `minimumReleaseAge` (explicit — an `extends:` preset doesn't count) | `"N days"` |

## Commands (package.json scripts — the code lives in `scripts/soak/`)

- `pnpm run soak` — parity-check every surface (CI-gated in docs-links)
- `pnpm run soak:fix` — rewrite drifted windows, prune expired exclusions
- `pnpm run deps:update` — bump npm (taze) + cargo deps through the window
- `pnpm run tools:check` / `tools:fix` / `tools:install` — validate / prune-expired-bypasses / install the SRI-pinned external tools (`external-tools.json`)
- `pnpm run test:scripts` — the scripts' own unit tests

The gates fail closed on invalid states (missing, malformed, or wrong-arithmetic annotations) and WARN on expired ones — stale is not unsafe, and nobody has to watch for it: the scheduled `soak-autofix` workflow runs `soak:fix` + `tools:fix` daily and commits the pruning as a bot PR.

A soak change is done when `pnpm run soak` and `pnpm run test:scripts` both exit 0 — the same gates CI runs. Re-run them after every fix.

## Change the window (one place)

1. Edit `SOAK_DAYS` in `scripts/soak/constants.mts`.
2. `pnpm run soak:fix` (rewrites cargo/npmrc/yaml; taze follows by import).
3. `pnpm run soak` + `pnpm run test:scripts` — existing exclusion annotations encode the old window and will be flagged; re-date or remove them, then re-run until both pass.

**Opt out entirely**: set `SOAK_DAYS = 0` and run the same two steps — cargo, pnpm/nub (`minimumReleaseAge: 0`), npm, and taze all treat zero as disabled. There is deliberately no env-var bypass: opting out is a committed, reviewable change, never a silent one.

## Skip the soak for ONE package (dated, temporary)

Add to `minimumReleaseAgeExclude` in `tools/pnpm-workspace.yaml` with the annotation on the line above (block list only — flow `[..]` is rejected because a comment line can't attach to an inline entry):

```yaml
# published: YYYY-MM-DD | removable: YYYY-MM-DD
- 'name@1.2.3'
```

`removable` = `published + SOAK_DAYS`; `published` must be the real registry publish date (the placeholders above are schematic — copying them verbatim is rejected). Once `removable` passes, `pnpm run soak` warns until the pin is pruned (`soak:fix` or the soak-autofix workflow does it). Bare names / `@scope/*` globs are standing trust and need no annotation. External tools use the same shape via a `soakBypass` object in `external-tools.json`.

## The cargo soak needs nightly — the repo still must not pin one

`min-publish-age` is an `[unstable]` cargo feature: a stable cargo ignores it silently. The repo deliberately ships **no** `rust-toolchain.toml`, because a repo-root toolchain file outranks `rustup default` and would silently redirect the version-pinned CI jobs (the MSRV `Check` legs) and build released binaries on nightly.

The nightly is instead requested per-invocation, at the only step that picks versions: `scripts/soak/update-deps.mts` runs `cargo +nightly update`. Everything else — every CI job, every shipped binary — builds on stable. If you need the cargo soak somewhere new, call `cargo +nightly` there; do not add a toolchain file.

**Keep the nightly current — a merely-old one silently disables the window.** Cargo treats an `[unstable]` key it does not implement as a warning and exits 0, so an old nightly resolves with NO window while looking successful. Measured both sides: nightly 2026-03-21 (cargo 1.96.0-nightly) has no such `-Z` and skips the window silently; nightly 2026-07-27 (cargo 1.99.0-nightly) supports `-Z min-publish-age` and visibly holds a too-fresh release back (`available: v0.2.189, published 7 days ago`). `deps:update` detects the warning and fails with the fix (`rustup update nightly`) — if you see it, the lockfile changes it just made are unsoaked.

## Maintaining this skill

`scripts/soak/` is the law; this file only documents it — when they disagree, fix this file. Keep it concise (goal + constraints, not step enumeration), and keep the window value in `constants.mts` rather than restating it here.

