Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
ed12cd6
chore(deps): clear the follow-redirects and vite audit advisories
phroi Oct 23, 2026
723cc81
chore: require Node 22.23.4, the October 21 security release
phroi Oct 23, 2026
ab43dd6
fix(bot): the stress line counts the reserve, so one matured anchor r…
phroi Oct 23, 2026
c6e03f0
docs(node): the host must stay up, and what to watch from another host
phroi Oct 23, 2026
9e0c484
docs(bot): coverage repair waits for two deposits of spendable CKB
phroi Oct 23, 2026
f5800a8
docs(sdk): the seller estimate's optimistic cases under sustained sel…
phroi Oct 23, 2026
3c840f8
fix(interface): the order intent says a bot fills it, and the README …
phroi Oct 23, 2026
ed0e91c
fix(interface): the source link points at interface/, not apps/interface
phroi Oct 25, 2026
d0a4968
fix(interface): relative asset URLs, so the bundle works at any mount…
phroi Oct 25, 2026
a7d53be
docs(bot): the funding recommendation keeps the reserve on top of 3.2…
phroi Oct 24, 2026
df8b1b9
chore(deps): pin zod to 4.6.6 for GHSA-mrhx-c7xc-m3w5
phroi Oct 24, 2026
2f15129
chore(deps): fail resolution for packages without a publish time
phroi Oct 24, 2026
19decf1
chore(deps): three-day release age, and fast-uri 3.1.9 for the four O…
phroi Oct 25, 2026
7980ce1
release: 9000.0.1 for the SDK and the interface
phroi Oct 25, 2026
c790928
chore(interface): drop the CNAME file, which Actions-sourced Pages ig…
phroi Oct 25, 2026
1acd56f
chore(release): one owner for each publish setting
phroi Oct 25, 2026
2017738
docs(release): replace the one-time setup checklists with the standin…
phroi Oct 25, 2026
aed863f
chore(ci): name the workflow for what it does
phroi Oct 25, 2026
d8d707a
chore(ci): install pnpm with the runner's npm instead of pnpm/action-…
phroi Oct 25, 2026
bc1dc25
chore(release): the SDK publishes from a pnpm deploy tree, the interf…
phroi Oct 25, 2026
3e4a302
chore(ci): the publish job runs in the npm environment, master only
phroi Oct 25, 2026
96893b7
chore(ci): checkout v7.0.1 and setup-node v7.1.0
phroi Oct 25, 2026
a321e9b
chore: pnpm installs the pinned Node, 26.12.1
phroi Oct 25, 2026
7d852e1
chore(ci): pnpm fresh holds the date-dependent checks, on pull reques…
phroi Oct 25, 2026
9a24a47
chore(ci): the Node check, against security releases of the pinned major
phroi Oct 25, 2026
5e18295
chore(deps): eslint-plugin-unicorn 78
phroi Oct 25, 2026
ad6edc7
chore(deps): the connector's React override follows the app's range
phroi Oct 25, 2026
e551209
fix(sdk): the package ships its source, and the maps reach it
phroi Oct 25, 2026
b302429
chore(deps): update to the newest versions the release age admits
phroi Oct 25, 2026
04e991c
docs(release): a rerun is wrong once a later release of the package h…
phroi Oct 25, 2026
3f69d9b
chore(deps): drop the esbuild override, its build approval and the im…
phroi Oct 25, 2026
00e25fa
chore(deps): drop the braces and sprintf-js audit ignores
phroi Oct 25, 2026
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
1 change: 1 addition & 0 deletions .dependency-cruiser.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"^scripts/run-node-tests[.](?:mts|ts)$",
"^scripts/tooling/build/rewrite-dts-imports[.](?:mts|ts)$",
"^scripts/tooling/dead-members[.]ts$",
"^scripts/tooling/pins[.]ts$",
"^scripts/tooling/eslint/plugins[.]ts$",
"^scripts/test/.+[.](?:mts|ts)$",
"^sdk/node/test/.+[.]ts$",
Expand Down
94 changes: 0 additions & 94 deletions .github/workflows/check.yaml

This file was deleted.

115 changes: 115 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
name: Check and release

on:
pull_request:
push:
branches: [master]

permissions:
contents: read

jobs:
build:
runs-on: ubuntu-latest
outputs:
interface: ${{ steps.releases.outputs.interface }}
sdk: ${{ steps.releases.outputs.sdk }}

steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with:
persist-credentials: false
# The release rule below reads the previous commit.
fetch-depth: 2
# The image's npm installs the pnpm version package.json pins, verified by the
# registry's integrity field (npm ignores the hash in packageManager); pnpm then
# installs the Node the lockfile pins (devEngines.runtime) and runs every script on it.
- run: npm install --global pnpm@"$(node -p "require('./package.json').packageManager.split('@')[1]")"

- name: Install pinned dependencies
run: pnpm install --frozen-lockfile
- name: Run pnpm check
run: pnpm check
# The freshness checks depend on the date, not the tree: an advisory is published, a
# dependency or Node release ages past its waiting period, an action releases. They
# gate pull requests only, so a release push cannot fail on something that appeared
# after the review; the next pull request catches it.
- if: github.event_name == 'pull_request'
run: pnpm fresh

# A release is a push to master (the only push trigger) whose commit changes a
# package's version field. The ruleset admits only merge commits of pull requests,
# whose first parent is the previous tip, so HEAD^ is the comparison; a path absent
# there counts as changed.
- id: releases
if: github.event_name == 'push'
run: |
released() {
now=$(node -p "require('./$1/package.json').version")
before=$(git show "HEAD^:$1/package.json" 2>/dev/null | node -p "JSON.parse(require('fs').readFileSync(0, 'utf8')).version" 2>/dev/null || echo none)
if [ "$now" != "$before" ]; then echo true; else echo false; fi
}
echo "interface=$(released interface)" >> "$GITHUB_OUTPUT"
echo "sdk=$(released sdk)" >> "$GITHUB_OUTPUT"

# The release jobs build again from a fresh checkout in which none of the gate's tooling
# has run; the interface job installs the workspace because its build bundles the SDK
# from source. A failed run leaves the previous release live.
deploy:
needs: build
if: needs.build.outputs.interface == 'true'
runs-on: ubuntu-latest
permissions:
contents: read
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}

steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with:
persist-credentials: false
- run: npm install --global pnpm@"$(node -p "require('./package.json').packageManager.split('@')[1]")"
- run: pnpm install --frozen-lockfile
- run: pnpm --filter @ickb/interface build
- uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9
with:
path: interface/dist
- id: deployment
uses: actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346

# npm trusted publishing: the id token stands in for a registry token, and npm records
# the provenance of this run with the package. The environment's branch policy admits
# master only, so a token minted on another branch is refused.
publish:
needs: build
if: needs.build.outputs.sdk == 'true'
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
environment: npm

steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with:
persist-credentials: false
- run: npm install --global pnpm@"$(node -p "require('./package.json').packageManager.split('@')[1]")"
# `pnpm deploy` resolves only the SDK's dependencies from the lockfile and refuses the
# package directory as its target, so its node_modules moves into place. npm runs the
# build script because `pnpm run` would first install the whole workspace
# (verifyDepsBeforeRun defaults to install).
- run: pnpm --filter @ickb/sdk deploy --frozen-lockfile /tmp/deploy && mv /tmp/deploy/node_modules sdk/node_modules
# This tree has no devEngines, so these two commands run on the image's Node, which
# may trail the floor; tsgo is a native binary and the rewrite script only edits import
# specifiers, so the artifact does not depend on it.
- run: npm run build
working-directory: sdk
# Access comes from publishConfig. The flag keeps provenance explicit: without it pnpm
# decides from the OIDC token and, when the visibility lookup fails, warns and continues
# without provenance. The checkout is a detached commit, so pnpm's branch and remote
# checks do not apply.
- name: Publish @ickb/sdk
run: pnpm --filter @ickb/sdk publish --provenance --no-git-checks
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ For a material revision:

## The gate

Run the [whole gate](README.md#dependencies-and-checks), `CI=true pnpm check`, once per commit and check its exit code. A green result on that tree makes the commit publishable.
Run the [whole gate](README.md#dependencies-and-checks), `CI=true pnpm check`, once per commit and check its exit code. A green result on that tree makes the commit publishable. Pull requests also run `pnpm fresh`, whose result depends on the date, so run it before opening one.

Keep each commit small and scoped to one concern. Its message explains the concern and what it prevents. Changes share a commit only when they share that reason to change.

Expand Down
23 changes: 8 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Stack assumes user-owned cells have [locks whose signatures bind the whole trans

## Dependencies and checks

Use Node 22.19 or later and the pinned pnpm 12.4.2. From a plain checkout:
Install the pinned pnpm 12.4.2 with npm; `pnpm install` then fetches the Node the lockfile pins (`devEngines.runtime` in package.json) into `node_modules/node` and runs every script on it; a host that runs the bot from source points its units at that binary. Inside the checkout run npm as `pnpm exec npm ...`: npm 11 refuses to run on a host Node that differs from the pin, and `pnpm exec` puts the pinned one first on PATH. From a plain checkout:

```bash
pnpm install --frozen-lockfile
Expand All @@ -39,31 +39,24 @@ CCC is an ordinary dependency resolved through the catalog in `pnpm-workspace.ya

The lockfile holds the CCC 1.23.0 set. CCC 1.23 made `mol.union` dynamic-size, so the order master pointer uses `mol.fixedUnion`, whose two variants are the same 36 bytes. The gate exercises core and the SDK paths; connector changes are only exercised by live wallet testing, so a CCC update that moves the connector is followed by one.

TypeScript stays on 6. The 7.x package ships the native compiler only, without the JavaScript compiler API that typescript-eslint and `scripts/tooling/dead-members.ts` use, and typescript-eslint declares `typescript <6.1.0`. Type checking already runs on the native preview.
TypeScript stays on 6 until typescript-eslint supports 7; the reason sits with the hold in `pnpm-workspace.yaml`. Type checking already runs on the native preview.

pnpm 12 supplies the trusted-publishing support the previous pin lacked. Its lockfile format is not backward-compatible with pnpm 10. The seven-day release age gives newly published dependencies time to be scrutinized; build-script permissions and advisory exceptions live beside their entries in [pnpm-workspace.yaml](pnpm-workspace.yaml).
pnpm 12 supplies the trusted-publishing support the previous pin lacked. Its lockfile format is not backward-compatible with pnpm 10. The release age, build-script permissions and advisory exceptions live with their reasons in [pnpm-workspace.yaml](pnpm-workspace.yaml).

`pnpm check` runs the audit, full lint suite and interface build against installed dependencies. CI first installs from the pinned lockfile, then runs the same gate on the declared Node floor. [Tooling](scripts/README.md#what-the-gate-enforces) explains the checks worth maintaining.
`pnpm check` runs the full lint suite and interface build against installed dependencies; its result is a function of the tree. `pnpm fresh` depends on the date instead: the audit fails on a newly published advisory; `pnpm outdated` fails when a dependency has a newer release past the release age, majors included, and when a pinned action has any newer release, since pnpm applies no release age to actions; the Node check, `scripts/tooling/pins.ts`, fails when the pinned major has a newer security release that is at least three days old. CI first installs from the pinned lockfile, then runs the gate on the pinned Node; pull requests also run the freshness checks, release pushes do not, so a release cannot fail on an advisory or a version that appeared after the review. pnpm itself is pinned by `packageManager` and bumped by hand. [Tooling](scripts/README.md#what-the-gate-enforces) explains the checks worth maintaining.

## Releases

A push to the default branch releases a package only when its version differs from the previous commit. The reviewed version change is the release decision:

- A change to `sdk/package.json` publishes `@ickb/sdk` to npm through trusted publishing, with provenance and no registry token in the workflow.
- A change to `interface/package.json` deploys the bundle that the check job built to GitHub Pages at `ickb.org`.
- A change to `interface/package.json` builds the interface again and deploys it to GitHub Pages at `ickb.org`.

Both jobs require the gate to pass. Other merges release nothing. This assumes each reviewed change lands as a squash commit, so comparing with the previous commit covers it. Tags, a manual trigger or a second confirmation environment would add another release step for the same maintainer without another reviewer. A mistaken publish still consumes a version number and needs a bump.
Both jobs require the gate to pass, then build their package again in a fresh job where none of the gate's tooling runs; the SDK job installs only the SDK's declared dependencies, resolved from the lockfile by `pnpm deploy`. Other merges release nothing. If a release job fails, rerun that run's failed jobs, unless a later release of the same package has already run: the next merge compares with its own parent and releases nothing. The ruleset admits only merge commits of pull requests, whose first parent is the previous tip, so comparing with it covers the whole change. Tags, a manual trigger or a second confirmation environment would add another release step for the same maintainer without another reviewer. A mistaken publish still consumes a version number and needs a bump.

The rewrite's versioned packages start at `9000.0.0`, above the old `@ickb/sdk` line at `1000.0.82`. The new API is not a patch of the old one, and keeping the package name avoids changing integrators' install lines. The high version preserves increasing version order; it is not a stability claim. A future lower-numbered line would need deliberate dist-tag management and would not satisfy a consumer's `^9000` range.
The rewrite's versioned packages start at `9000.0.1`, above the old `@ickb/sdk` line at `1000.0.82` (`9000.0.0` was never published: its release run failed before the settings below existed). The new API is not a patch of the old one, and keeping the package name avoids changing integrators' install lines. The high version preserves increasing version order; it is not a stability claim. A future lower-numbered line would need deliberate dist-tag management and would not satisfy a consumer's `^9000` range.

### First release setup

The maintainer must configure the external services before the release merge:

1. Register the npm trusted publisher for `@ickb/sdk`: organization `ickb`, repository `stack`, workflow `check.yaml`, no environment. Configure the package to require two-factor authentication and disallow tokens.
2. Enable GitHub Pages with GitHub Actions as its source. Move the `ickb.org` domain from `ickb/legacy-lumos-interface` to this repository, then archive the old repository after the new site is live.

Whether to deprecate the old `1000.x` SDK line and its companion packages remains optional. The [workflow](.github/workflows/check.yaml) owns the release mechanics; the [interface README](interface/README.md#deployment) owns the site setup.
The publish job depends on the npm trusted publisher registered for `@ickb/sdk`: organization `ickb`, repository `stack`, workflow `ci.yaml`, environment `npm`, whose branch policy in this repository admits `master` only; the package requires two-factor authentication and disallows tokens, so no token can publish and this workflow is the only unattended path. The deploy job depends on this repository's Pages settings: source GitHub Actions, custom domain `ickb.org`. Renaming the workflow file or moving the repository breaks the release until those settings follow. The [workflow](.github/workflows/ci.yaml) owns the release mechanics.

## Live testnet validation

Expand Down
5 changes: 5 additions & 0 deletions eslint.config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,11 @@ export default defineConfig(
],
},
},
{
// A config module is evaluated for the value it builds; the rule targets libraries.
files: ["**/*.config.{ts,mts}"],
rules: { "unicorn/no-top-level-side-effects": "off" },
},
{
// The oracle's independence from the SDK is a dependency-cruiser rule.
files: ["testkit/src/contract_oracle.ts"],
Expand Down
Loading
Loading