* feat(ci): an advisory dependency-age check (cmd/depages) (TASK-1391) Companion to Dependabot's cooldown (TASK-1390), which holds a release for 7 days before PROPOSING it but cannot see a version added by hand. cmd/depages dates every Go module (go.sum), npm package (web/package-lock.json, registry packages) and action pin (`uses: owner/repo@<sha>`) a change adds, from the Go module proxy, the npm packument and the GitHub commits API, and fails when one is younger than --days and not allowed in .github/dep-age-allow.txt (which requires a reason per line). --base <ref> checks only what is new since the ref; a dependency that cannot be dated is a warning, not a failure. CI runs it on pull requests as "Dependency age (advisory)", against the merge commit's first parent. NOT a required check (lead ruling): red means wait or allow with a reason. Rewritten rather than promoted: the /tmp scanner the task names is gone. Its edge cases are kept (pseudo-versions included, scoped npm names, sub-actions sharing a repo SHA, the full packument for per-version times). A real run against main as of 2026-09-01 dated 185 dependencies and flagged one: golang.org/x/net v0.60.0, #1922's security bump, the case the allowlist is for. * fix(ci): depages covers aliases, composite actions and lockfile versions; the token stays on GitHub (TASK-1391) Codex round 1: - An npm alias is checked as the package it installs (the entry's name). - A lockfile older than v2 is refused instead of read as empty. - Every YAML file under .github is scanned, so a local composite action's uses: pins count too. docker:// images are out of scope and documented. - GH_TOKEN is sent only to api.github.com, whatever DEPAGES_GITHUB_API says. - The base-diff test now covers a go.sum module, a bumped npm package and a composite action's new pin in one change. * fix(ci): depages scans action.yml anywhere in the repo; its README tables the coverage (TASK-1391) Codex round 2: a local composite action can live outside .github. Every action.yml / action.yaml in the repository is scanned too (node_modules and .git skipped). Not changed, with the reasons in the README's coverage table: docker:// images (no SHA, no publish time) and dating a pin by its commit (the threat is fresh code, and an old commit is old code). * feat(ci): depages prints the allowlist line to paste for each young version (TASK-1391) Lead, on the first real run flagging #1922's x/net security bump: the job's output now says the check is advisory and prints, per flagged version, the exact .github/dep-age-allow.txt line with a <reason> placeholder. * test(models): classify cmd/depages' getJSON in the raw-decode guard (TASK-1391) CI: TestReservedFieldDecodeSitesAreClassified found cmd/depages/main.go's getJSON decoding into an interface. It decodes registry and GitHub API answers into their own structs, never a collection definition; allow-listed with that reason, and the receipt count goes 26 -> 27.
depages
Flags dependencies younger than a threshold (TASK-1391). It is the companion
to Dependabot's cooldown (TASK-1390, .github/dependabot.yml), which holds a
release for 7 days (14 for a major) before proposing it but cannot see a
version added by hand.
go run ./cmd/depages --days 7 # every dependency in the tree
go run ./cmd/depages --days 7 --base origin/main # only what is new since origin/main
It checks Go modules (go.sum), npm packages (web/package-lock.json) and
GitHub Actions pins (uses: owner/repo@<sha> in .github/workflows), dating
each from the Go module proxy, the npm registry packument and the GitHub
commits API (set GH_TOKEN to avoid the unauthenticated rate limit).
What it covers
| Source | Covered | Dated by |
|---|---|---|
Go modules (go.sum, content lines; pseudo-versions included) |
yes | the module proxy's .info time |
npm packages from the registry (web/package-lock.json, lockfile v2/v3; an alias as the package it installs) |
yes | the packument's per-version time |
| npm git or tarball dependencies | no | they have no registry publish time |
| A v1 lockfile | refused, exit 2 | |
Action pins owner/repo[/path]@<sha> in any YAML under .github, and in any action.yml / action.yaml in the repo |
yes | the commit's committer date |
docker:// action images |
no | not pinned by a commit SHA; no publish time here |
An action is dated by its COMMIT, deliberately: the threat is freshly published code, and a pin to an old commit is old code whatever tag points at it.
Exit status: 0 when nothing is too young (a dependency that cannot be dated
is printed as a warning, not a failure); 1 when one is younger than --days
and not allowed; 2 on a usage or local error.
CI runs it on every pull request as Dependency age (advisory), against
the base branch (--base HEAD^1 on the merge commit). It is not a required
check: a red result is a prompt, not a block.
Allowing a young version
A red run prints the exact line for each flagged version. Add it to
.github/dep-age-allow.txt, putting the reason in place of <...>:
<go|npm|action> <name>@<version> <reason>
for example npm @tiptap/core@3.31.5 coordinated Tiptap bump (security fix).
The reason is required, so every exception says why. Remove the line once the
version is older than the threshold.