Repository navigation
Stress line fix, first release 9000.0.1, hardened CI on Node 26 - #64
Merged
Merged
Conversation
phroi
force-pushed
the
stress-line
branch
2 times, most recently
from
October 25, 2026 16:40
b647269 to
4860578
Compare
phroi
marked this pull request as ready for review
October 25, 2026 17:37
pnpm audit fails the gate on follow-redirects below 1.16.1 (two high, reached through CCC spore and axios) and on vite 8.3.0 to 8.3.2 (six advisories in vite and launch-editor). An override pins the transitive follow-redirects range to 1.16.1, following the existing security pins; vite moves to 8.3.4, the newest release past the seven-day age rule.
The 2026-10-21 Node.js security release fixes two high TLS hostname checks, a Brotli decoder out-of-bounds write and undici, c-ares and CA-bundle issues in 22.x, 24.x and 26.x; 20.x got no release. The bot is a long-running fetch-over-TLS client, so the floor moves to the first fixed 22.x in every engines field, the CI pin and the READMEs.
…estores it The line was one deposit's cost, receipt included, beyond the reserve, and stress compared spendable CKB plus requests in flight against it. A cap anchor in flight carries its capacity plus the owner marker that came out of the liquid cells, and matured it returns about the capacity less fees: 208 CKB plus fees under the cost. From the floor the line was never restored, in flight or after maturity (journal 2026-10-19 08:47 to 09:49: pending 119,537 CKB against a cost of about 119,647, spendable near zero, stress true throughout), so each dip took a second anchor and a quiet bot would settle near two deposits of CKB, the line already rejected. Stress now compares total CKB, reserve included, against the same cost, so the reserve is the slack: in flight and matured, a cap anchor clears the line by about 790 CKB. Coverage still keeps two deposits spendable, above the line. A cap-sized seller right after restoration may be served short by up to the reserve less the receipt, above the minimum match; the tail follows. The replaced test used a pending amount of one shannon against a balance one shannon short, which hid the gap; the new one uses the journal's shape. Reviewed independently by Fable 5.1 and Opus 5.5 at d4a5b0a, both arriving at this change over comparing against the capacity (zero margin), counting a request as a full line (short after maturity) or dropping the guard.
The bot's request window is twenty to sixty minutes before a deposit's claim, so a deposit maturing during an outage waits a full cycle; nothing said so. Two days of a laptop host at 23% uptime showed it. The alerting example forwarded every bot.turn.failed while the prose said consecutive; each single failure in the journal recovered on the next turn. The watcher also ran on the same host and died with it, which was the actual failure, so the example is now an off-host staleness check. `systemctl --user --failed` never lists a Restart=always unit, so that line is gone.
The ring section said the coverage rule repairs a thin segment once per cycle without its precondition. Two days of testnet sell pressure showed the tip walking empty segments for a whole window with no repair possible, since matured CKB went to sellers and spendable never reached two deposits.
…ling The supply model counts every pool deposit at its claim date. Wallets take surplus deposits days before the bot can see them, downtime rolls deposits a cycle without warning, and the bot's iCKB float caps a claim cluster. Recorded as accepted limits; a model change needs mainnet evidence.
…says what that means "Converts at a variable time" hid the dependency: an order is filled only when a bot holds the other side, at the one price the interface asks, with no priority to buy and no promise of eventual service, and an unfilled sell returns at the age cutoff. The intent line now names the bot; the README owns the rest, next to the fee the form already cites.
The apps/ layout predates the workspace; the link 404ed since the rewrite.
… point Vite's default base assumed the domain root, so the deploy that lands before ickb.org moves would serve a page whose assets 404 at ickb.github.io/stack/. The app has no client-side routes, so relative URLs cost nothing.
… deposits 0049726 moved the stress line onto total CKB and reworded the funding bullet to put the reserve inside the 3.2 deposits. That was wrong: a coverage deposit still needs two deposits of CKB spendable beyond the reserve (spendable >= 2 * depositCost), so a bot funded with the reserve inside never seeds the ring. Restore master's wording and say why.
Transitive through @ckb-ccc/core > @joyid/ckb > @joyid/common > abitype. The audit step also reports four fast-uri advisories fixed in 3.1.9, under @microsoft/api-extractor > @microsoft/tsdoc-config > ajv; that version was published 2026-10-19 and is inside the seven-day minimumReleaseAge until 2026-10-27, so the gate stays red on audit until then.
pnpm skips the minimumReleaseAge check when registry metadata lacks a time field (minimumReleaseAgeIgnoreMissingTime, added in 11.0.0 with default true, unchanged in the pinned 12.4.2; see pnpm.io/settings/dependency-resolution). The seven-day rule is meant to hold for every package, so such a package now fails instead. Lockfile unchanged; only future resolutions are affected.
…ctober advisories Seven days was chosen on September 14 so that a compromised release has time to be caught before it can be installed. The wait is also how long a fix stays unadopted, and three days is what a pulled or regressed release needs to show (user decision 2026-10-25). The first fix it admits: fast-uri 3.1.9, published 2026-10-19, closes GHSA-w74x-v6f9-j3xg, GHSA-wjr9-j795-jxcr (moderate), GHSA-xhq7-xmqj-769h and GHSA-333x-w4r6-6crp (low), which the audit reported.
The first master push (9582ab4, run 38780788044) tried to release 9000.0.0 before GitHub Pages was enabled and the npm trusted publisher existed, so both jobs failed with 404 and nothing was published. That run cannot be replayed: its Pages artifact expired and a full rerun would fail today's audit. The release rule only fires on a version change against the previous commit, so this bump makes the merge of this PR the first release; 9000.0.0 is skipped rather than consumed, as the README now records.
…nores The legacy site kept public/CNAME because a branch-sourced Pages site reads the domain from the published branch. This repository publishes through actions/deploy-pages, and GitHub's custom-domain guide states that for a custom workflow "any existing CNAME file is ignored and is not required": the domain is the repository's Pages setting alone.
pnpm 12.4.2 (pnpm/pnpm@9502f3c4, crates/publish/src/publish_options.rs) falls back to publishConfig.access when --access is absent and never reads publishConfig.provenance, so the flag and the field each duplicated or duplicated nothing. The access setting stays in publishConfig; the explicit --provenance flag stays because without it pnpm decides from the OIDC token and proceeds silently without provenance when the visibility lookup fails.
…g dependencies The maintainer's first-release steps (register the npm trusted publisher, enable Pages, move the domain, archive the legacy repository) are being carried out and would go stale once done. What a future maintainer needs is what the two release jobs depend on outside the tree: the trusted publisher's organization, repository and workflow name, and the Pages source and domain.
check.yaml also published the SDK and deployed the site. One file stays, because a separate release workflow would either rerun the gate or trust a workflow_run hand-off from it. The npm trusted publisher pins the workflow filename, so the rename lands before that registration.
…setup At its pinned commit the action runs a vendored pnpm to install pnpm@<version> with --no-lockfile; npm, present once setup-node runs, does the same from the packageManager field with the registry's integrity check. The workflow now uses GitHub-owned actions only, which lets the repository's Actions policy allow nothing else.
…ace from its own job The publish job installed the whole workspace (736 packages) to run tsgo, and the deploy job shipped the bundle the gate built in a job where eslint, vitest and the rest had already executed. The publish job now resolves only the SDK's declared dependencies from the lockfile with `pnpm deploy` (47 packages), moves that node_modules into place because deploy refuses the package directory as a target, and runs the build script with npm: `pnpm run` would first reinstall the whole workspace, since verifyDepsBeforeRun defaults to install in pnpm 11+. The SDK declares tsgo itself, through the catalog so the gate's compiler and the release's compiler stay the same version. The interface job installs the workspace in a fresh job instead: its bundle pulls the SDK from source, and the minimal tree cannot resolve that without a second deploy and a typecheck-free build. The gate's tooling is never run in either job. Verified in a scratch clone: no root node_modules in the SDK job and a dist byte-identical to the full-workspace build. The same README paragraph and the workflow comment said each push to master is a squash commit; the ruleset allows only the merge method, so the release rule compares with the merge commit's first parent, and they now say so. The README also gains the recovery rule for a failed release.
npm's trusted publisher matches organization, repository and workflow file, not the branch, so without an environment a workflow edit on any branch could mint an accepted token. The `npm` environment exists in the repository with a deployment branch policy of master and no reviewers, so it adds no step; the trusted publisher is to be registered with that environment name. The README also records that the package disallows tokens (Opus audit of b647269, D2/D3).
Three majors behind on both. The majors moved the actions to the node24 runtime and, for checkout v6, persist credentials in a separate file; this workflow sets persist-credentials: false on every checkout, so neither changes its behaviour.
devEngines.runtime pins Node once, in the root package.json; pnpm 12 resolves it like a dependency, records each platform's tarball URL and sha256 in the lockfile, installs it under node_modules on `pnpm install`, and runs every script on it. The workflow no longer needs setup-node or a version of its own: the runner image's npm only installs pnpm. 26 becomes the LTS line on October 28; 26.12.1 is the October security release, four days old, so it passes the release age. The published SDK promises the major, `engines.node >=26`, the same kind of claim as a dependency range; the private packages declare no floor, since they run on the pin; the @types/node line follows. The tracked unit examples run the checkout's node_modules/node/bin/node, so a pin change reaches a host with pull and install (user decision 2026-10-25: an exact pin rather than an `lts` alias, since the host gets the locked version either way). Two notes: npm 11 enforces devEngines in the repository root, so inside the checkout npm runs as `pnpm exec npm ...`; the SDK publish job builds in a deploy tree without devEngines, so its two build commands run on the image's Node, which cannot affect the artifact (tsgo is native, the rewrite script edits specifiers).
…ts only Nothing audited the actions or asked for newer dependencies, and the audit sat inside `pnpm check`, whose result therefore moved with the advisory database while the tree stood still: the base commit passed the gate on October 15 and fails it today. A release push running that gate could fail on an advisory or a release that appeared after review, with no rerun able to recover and the next merge releasing nothing. `pnpm check` is now a function of the tree. `pnpm fresh` holds the date-dependent checks and CI runs it on pull_request events only, where the required `build` check still enforces it: the audit, and `pnpm -r outdated --include-github-actions`, which reports every newer release of a dependency past the three-day age, majors included, and any newer release of a pinned action (pnpm applies no age to actions; the Actions policy limits them to GitHub-owned ones). The project tracks the latest majors; the one hold, TypeScript 6 while typescript-eslint declares `typescript <6.1.0`, is an update.ignoreDeps entry with its reason beside it. Verified at pnpm 12.4.2: outdated honours minimumReleaseAge and exits 1 when outdated; a bare SHA pin is matched to its tag through git, with no API or token.
pnpm outdated does not report devEngines, and the resolver applies no release age to it (a ^22.23.0 range resolved a five-day-old release under a seven-day rule), so the runtime had no freshness check: the October security release was found by reading the announcement. scripts/tooling/pins.ts, run by `pnpm fresh`, reads the pin from package.json, requires the SDK's engines floor to be the pin's major, and fails when the Node project lists a newer release of that major flagged as a security release that is at least three days old (user decision 2026-10-25). Non-security releases are not flagged.
Two majors behind. The new no-top-level-side-effects rule is off for config modules, which exist to build a value at top level; a test helper's fallback expression becomes a default parameter.
The exact 19.3.0 went stale when the app moved to 19.3.1; a caret range keeps the connector on the same single React without an edit per patch release.
The emitted maps pointed at the source through a fixed `sourceRoot` of `../src`, which the dist-only tarball did not contain and which, resolved against a nested map's own location, named `dist/src/...` for 46 of the 60 references. `src` now ships beside `dist`, so an integrator's debugger shows the code and anyone can read what they installed, and `sourceRoot` is gone, so each map carries its own relative path. The build's exclude list and the matching `files` negations guarded against test files under `src`, where none live (tests are under `test/`); both are deleted (Fable and Opus reviews of e3d220b).
The lockfile is regenerated from scratch under the three-day age and the direct ranges move to the newest admitted releases; the freshness check requires this state.
…as run Rerunning an older failed deploy or publish would put a superseded bundle or version back in front (Opus audit of 78f4aef, B).
…age-size ignores None matches an installed package: no esbuild or @esbuild/* and no image-size appears in the lockfile, so the override only rewrote vite's optional peer range and the two advisory ignores silenced nothing (Opus review of 748389f). The braces ignore under the same React Native chain stays while that package is present.
The regenerated lockfile resolves patched versions of both, so the audit without these entries reports only the elliptic advisory that stays ignored (Fable review of 748389f).
Member
Author
|
Merged!! The diff is one bot fix plus a lot of plumbing, so here is the record. What the diff doesn't show: three days of work and a history rewritten twice to read as one story. Read in order, I see four phases:
Now live: Phroi %378 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The only behaviour change here is the bot stress line. Everything else clears the way for the first release of the rewrite: the audit advisories that turned the gate red, the two release jobs that never worked, a CI that now pins and checks everything it depends on, the move to Node 26 and the latest majors. One commit per concern.
What each reader gets:
@ickb/sdk9000.0.1 on npm, for Node 26 and later, with its source beside the build and maps that reach it, published by this workflow with provenance. No registry token exists anywhere.The stress line
The bot kept the cost of one deposit beyond the reserve. It compared spendable CKB plus requests in flight against that line. A cap-sized anchor returns about the deposit capacity, which is the receipt plus fees short of the cost. So from the floor one withdrawal never restored the line: stress stayed true in flight and after maturity, each dip took a second anchor, a quiet bot would settle near two deposits of CKB.
The line now counts the reserve, so the reserve is the slack and one anchor restores it. Coverage still keeps two deposits spendable. The accepted cost: a cap-sized seller right after restoration can be served short by up to the reserve less the receipt, still above the minimum match. The replaced test compared one shannon pending against a balance one shannon short. The new one uses the shape the journal shows and fails against the old code. Two independent reviews converged on this over comparing against the capacity, counting a request as a full line, or dropping the guard. The rationale is in the bot policy.
Testnet evidence, from this branch running on the bot since October 23: that evening a ready anchor during an in-flight request drew no withdrawal plan under the new line, where the old line would have taken it. On October 25, after the queued sells had left the pool, one maturity put the bot back above the line. The next ready anchor sat there for six turns and drew no plan.
The release
The first master push tried to release 9000.0.0 before GitHub Pages and the npm trusted publisher existed. That run cannot be replayed: its Pages artifact expired, the audit at that commit fails today. Both released packages move to 9000.0.1, so the merge of this PR is the first release. 9000.0.0 was never published. The one-time setup checklists became one paragraph naming what the two release jobs depend on outside the tree.
CI
One workflow,
ci.yaml, checks and releases. A separate release workflow would either rerun the gate or trust a hand-off from it, so I kept one file and named it for what it does.What runs:
devEngines.runtime, one sha256 per platform) and runs every script on it.setup-nodeandpnpm/action-setupare gone.pnpm deploytree holding its 47 declared packages, built withnpm runbecausepnpm runwould reinstall the whole workspace first. The interface builds in a fresh job. The gate tooling never runs where an artifact is produced.npmenvironment, which admits master only. The npm trusted publisher matches organization, repository and workflow file, not the branch.pull_requestonly.What pull requests check beyond the gate, as
pnpm fresh. These depend on the date, so release pushes skip them and a release cannot fail on something that appeared after review:pnpm outdated: a newer release of a dependency, majors included, once past the release age, now three days; and any newer release of a pinned action, since pnpm applies no age to actions. The one hold, TypeScript 6 until typescript-eslint supports 7, is declared with its reason;scripts/tooling/pins.ts: a security release of the pinned Node major at least three days old. Nothing else audits the runtime. The October one was found by reading the announcement.Repository settings changed alongside: Pages enabled with the Actions source, GitHub-owned SHA-pinned actions only, read-only default token, approval for every workflow run from an outside contributor, the
npmenvironment restricted to master, thebuildcheck required to merge.Four independent reviews, two critics over three rounds and two fresh sessions after them, went over the CI and release changes; every finding is fixed in the commits.
Also in here
devEngines; the SDK promises>=26, the private packages run on the pin. The tracked unit examples run the checkout'snode_modules/node/bin/node, so a pin change reaches a host with pull and install.