Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 35 additions & 2 deletions .buildkite/linux-agent.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,43 @@ RUN set -eux; \
sh /tmp/mise-install.sh; \
rm /tmp/mise-install.sh; \
/root/.local/bin/mise --version
# Minimal-profile toolchains only (the default profile ships rust-docs,
# ~600 MB per toolchain): stable as the default, plus the MSRV floors the
# CI legs verify against (1.91 workspace rust-version; 1.93 leg). NO dated
# nightly is baked — the repo deliberately ships no rust-toolchain.toml
# (#1039), and the one nightly consumer (`cargo +nightly update` in
# scripts/soak/update-deps.mts) runs on dev machines, not this agent.
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \
| sh -s -- -y --profile minimal --default-toolchain stable \
&& /root/.cargo/bin/rustup toolchain install 1.93.0 --profile minimal \
&& /root/.cargo/bin/rustup toolchain install 1.91.0 1.93.0 --profile minimal \
&& /root/.cargo/bin/rustup component add rustfmt clippy --toolchain stable \
&& rm -rf /root/.rustup/downloads /root/.rustup/tmp

ENV PATH="/root/.cargo/bin:/root/.local/bin:/root/.local/share/mise/shims:${PATH}"
# Pre-bake Socket Firewall (free tier — keyless; jobs holding a Socket key
# select enterprise at job time) plus its package-manager shims, pinned +
# integrity-checked against external-tools.json (sfw-free 1.14.0, sha512
# hex below = that file's SRI decoded). scripts/soak/external-tools.mts
# --check asserts these embedded copies never drift from the manifest.
COPY sfw-shim-template.sh /tmp/sfw-shim-template.sh
RUN set -eux; \
arch="$(uname -m)"; \
case "$arch" in \
x86_64) asset=sfw-free-linux-x86_64; sha=85b108842381b948c50e93580d8149d63fc8c080226b1287b06690e1448e8b004ad72d69a2ea46948462aad0e4563e0ff71fbd2341a48b7c6856ae82da7c816f ;; \
aarch64) asset=sfw-free-linux-arm64; sha=098a790b829eca08576b4494b53e96afe5fce75857217d377234c52b7a80c94ff1f874245bb65a9c07ef5a13becc29aff0306b5c7556a38365bfa5673fabd75d ;; \
*) echo "unsupported arch $arch" >&2; exit 1 ;; \
esac; \
mkdir -p /root/.local/share/aube/dev-tools/rack/sfw-free/1.14.0 /root/.local/share/aube/dev-tools/bin; \
curl -fsSL -o /root/.local/share/aube/dev-tools/rack/sfw-free/1.14.0/sfw \
"https://github.com/SocketDev/sfw-free/releases/download/v1.14.0/$asset"; \
echo "$sha /root/.local/share/aube/dev-tools/rack/sfw-free/1.14.0/sfw" | sha512sum -c -; \
chmod 0755 /root/.local/share/aube/dev-tools/rack/sfw-free/1.14.0/sfw; \
ln -sf /root/.local/share/aube/dev-tools/rack/sfw-free/1.14.0/sfw /root/.local/share/aube/dev-tools/bin/sfw; \
for cmd in npm yarn pnpm pip pip3 uv cargo; do \
sentinel="SFW_SHIM_ACTIVE_$(printf '%s' "$cmd" | tr '[:lower:]' '[:upper:]' | tr -c 'A-Z0-9' '_')"; \
sed -e "s/__CMD__/$cmd/g" -e "s/__SENTINEL__/$sentinel/g" \
/tmp/sfw-shim-template.sh > "/root/.local/share/aube/dev-tools/bin/$cmd"; \
chmod 0755 "/root/.local/share/aube/dev-tools/bin/$cmd"; \
done; \
rm /tmp/sfw-shim-template.sh

ENV PATH="/root/.local/share/aube/dev-tools/bin:/root/.cargo/bin:/root/.local/bin:/root/.local/share/mise/shims:${PATH}"
28 changes: 28 additions & 0 deletions .buildkite/sfw-shim-template.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#!/usr/bin/env bash
# sfw shim for __CMD__ — pre-baked into the agent image; mirrors what
# scripts/soak/external-tools.mts --shims writes on dev machines. Routes the
# real package-manager invocation through Socket Firewall; the sentinel env
# var breaks recursion when sfw re-invokes the tool, and the real binary is
# found by stripping the tool-rack bin dir out of PATH.
set -euo pipefail
CLEAN_PATH=$(printf '%s' "$PATH" | tr ':' '\n' | grep -vFx '/root/.local/share/aube/dev-tools/bin' | paste -sd ':' -)
REAL=$(PATH="$CLEAN_PATH" command -v '__CMD__' || true)
if [ -n "${__SENTINEL__:-}" ] || [ -z "$REAL" ] || ! command -v sfw >/dev/null 2>&1; then
# Fail-open must not be SILENT-open: say so once on stderr when the
# firewall is missing (never on the sentinel re-entry path, where sfw
# itself is the caller).
if [ -z "${__SENTINEL__:-}" ] && [ -n "$REAL" ]; then
echo "[sfw-shim] sfw not on PATH — running __CMD__ unfirewalled" >&2
fi
[ -n "$REAL" ] && exec "$REAL" "$@"
echo "__CMD__: not found" >&2
exit 127
fi
export __SENTINEL__=1
# Enterprise sfw defaults to BLOCK for non-registry hosts
# (SFW_UNKNOWN_HOST_ACTION, parsed by the enterprise config), which
# breaks ordinary dev flows the day a Socket key lands. Only the
# enterprise build reads the var — it is inert for the free tier — so
# setting it unconditionally is safe.
export SFW_UNKNOWN_HOST_ACTION=ignore
exec sfw '__CMD__' "$@"
15 changes: 15 additions & 0 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,18 @@
# (7.07 s → 9.57 s) — acceptable given the safety guarantee.
[env]
RUST_TEST_THREADS = "1"

# Supply-chain soak for cargo's own dependency resolution — the same
# soak-window rule aube offers npm users via minimumReleaseAge: crate
# versions younger than the window are pubtime-incompatible and the
# resolver skips them unless already in Cargo.lock
# (resolver.incompatible-publish-age defaults to "deny"). min-publish-age
# is an [unstable] feature and the repo deliberately ships NO
# rust-toolchain.toml (#1039) — the soak bites only where a nightly is
# requested per-invocation: `cargo +nightly update` in
# scripts/soak/update-deps.mts, the one step that picks crate versions.
[unstable]
min-publish-age = true

[registry]
global-min-publish-age = "7 days"
122 changes: 122 additions & 0 deletions .claude/skills/soak/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
---
name: soak
description: 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, bumps pinned external tools, and enforces the Ubuntu-PPA MSRV ceiling. Use when a task touches minimumReleaseAge, min-release-age, min-publish-age, rust-version / MSRV, external-tools.json, sfw shims, renovate.json, or taze cooldowns, or when investigating why a freshly published version won't install.
---

# 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"` |
| `docs/pnpm-workspace.yaml` | `minimumReleaseAge` | minutes |
| `docs/.npmrc` | `min-release-age` | days |
| `docs/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"` |
| `Cargo.toml` | `rust-version` ≤ the PPA rustc ceiling (not window-derived — see below) | version |

## Commands (mise tasks — the code lives in `scripts/soak/`)

- `mise run soak` — parity-check every surface (CI-gated, always-run)
- `mise run soak:fix` — rewrite drifted windows, prune expired exclusions
- `mise run deps:update` — bump npm (taze) + cargo deps through the window
- `mise run tools:check` / `tools:fix` / `tools:install` — validate /
prune-expired-bypasses / install the SRI-pinned external tools
(`external-tools.json`); `tools:install` also writes the sfw firewall
shims into the dev-tools bin dir
- `mise run test:scripts` — the scripts' own unit tests

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

The gates fail closed on invalid states (missing/malformed/wrong-math
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.

## The MSRV ceiling (the #1039 guard)

The Ubuntu PPA builds aube's source package on Launchpad with the DISTRO
rustc. The first soak landing (#1020) bumped the workspace MSRV past it
(1.95 > 1.93) and the whole port was reverted (#1039) to unbreak the
release. `mise run soak` now fails any `Cargo.toml` `rust-version` above
`PPA_RUST_CEILING` (`scripts/soak/paths.mts`, with a dated `checked:`
note). Raise the ceiling ONLY after confirming Launchpad's rustc moved:
https://launchpad.net/ubuntu/+source/rustc.

## 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 legs and the PPA/release builds.
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.

## Change the window (one place)

1. Edit `SOAK_DAYS` in `scripts/soak/constants.mts`.
2. `mise run soak:fix` (rewrites cargo/npmrc/yaml/renovate; taze follows
by import).
3. `mise run soak` + `mise 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/aube (`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 `docs/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, `mise 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`.

## Maintaining this skill

`scripts/soak/` is the law; this file only documents it — when they
disagree, fix this file. When editing, follow Anthropic's guidance:

- [Prompting best practices](https://platform.claude.com/docs/en/build-with-claude/prompt-engineering/claude-prompting-best-practices)
- [Prompting Claude Fable 5](https://platform.claude.com/docs/en/build-with-claude/prompt-engineering/prompting-claude-fable-5)
- [Skill authoring best practices](https://platform.claude.com/docs/en/agents-and-tools/agent-skills/best-practices)
- [Write an effective AGENTS.md](https://code.claude.com/docs/en/best-practices#write-an-effective-claude-md)

Keep it concise (goal + constraints, not step enumeration), keep the
description in third person with explicit "use when" triggers, and keep
the window value in `constants.mts` rather than restating it here.
4 changes: 3 additions & 1 deletion .github/renovate.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,7 @@
"matchPackageNames": ["decmpfs"],
"allowedVersions": "<=0.1.0"
}
]
],
"minimumReleaseAge": "7 days",
"internalChecksFilter": "strict"
}
54 changes: 54 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,32 @@ jobs:
git diff
exit 1
fi
# Firewall + pinned PMs are staged HERE, after the cargo suite: the
# aube-registry tests hit local mocks ADDRESSED AS
# registry.npmjs.org:<port>, and sfw's loopback NO_PROXY exemption
# keys on the address, not the socket — a hostname-addressed mock
# still routes into the proxy, whose absolute-form plain-HTTP
# handling 405s the mock traffic (fixed upstream in
# SocketDev/firewall#147; the ordering stays as defense in depth).
# GITHUB_PATH only affects steps after this one, so the firewall
# covers exactly the network-installing step below (docs:build).
- name: pinned package managers + socket firewall
env:
# Until the SOCKET_SECURITY_KEY repo secret lands this is empty
# and sfw runs its keyless free tier — the firewall is active
# either way and upgrades itself to enterprise when the secret
# appears.
SOCKET_SECURITY_KEY: ${{ secrets.SOCKET_SECURITY_KEY }}
# download() attaches this to github.com fetches only (release
# assets) — avoids unauthenticated rate limits.
GITHUB_TOKEN: ${{ github.token }}
run: |
node scripts/soak/external-tools.mts --install pnpm --install npm --install sfw --shims
node scripts/soak/external-tools.mts --print-bin >> "$GITHUB_PATH"
- name: assert pinned pms resolve through the shims
run: |
pnpm --version
npm --version
- run: mise run docs:build

# Guards the embedding API surfaces `embed` introduced against
Expand Down Expand Up @@ -340,6 +366,33 @@ jobs:
# blocked. Lets the branch-protection rule depend on one name instead of ten.
# Exception: jobs in SKIP_OK below (api-stability, a PR-only gate) are allowed
# to skip on push/tag/dispatch runs without failing the aggregate.
# Supply-chain soak parity + external-tool pin gates + the #1039 MSRV
# ceiling. Always-run (deliberately NOT path-filtered — a gate that only
# runs when tooling files change misses the drift it exists to catch)
# and required via `final`.
soak-gate:
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: "24"
# v7 caches by default; this workflow also runs on tag pushes
# (release artifacts), so no cache — the soak scripts are
# dep-free and never npm-install anyway.
package-manager-cache: false
- name: soak window parity (mise run soak)
run: node scripts/soak/soak.mts --check
- name: external-tool pins valid (mise run tools:check)
run: node scripts/soak/external-tools.mts --check
- name: soak script unit tests (mise run test:scripts)
run: node --test scripts/soak/*.test.mts

final:
needs:
- build
Expand All @@ -348,6 +401,7 @@ jobs:
- bats-serial
- windows
- api-stability
- soak-gate
runs-on: namespace-profile-endev-linux-amd64
timeout-minutes: 2
permissions: {}
Expand Down
Loading
Loading