Skip to content
Merged
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
9 changes: 9 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,15 @@ jobs:
- run: bun run typecheck
- run: bun test

# dashboard/ carries its own package.json and its own bun.lock, and the
# install above does not reach it. Without this step nothing in CI ever
# installs or builds that tree, so a drifted dashboard/bun.lock stayed
# invisible until `npm publish` — which now fails on it, because
# build:dashboard runs --frozen-lockfile. Exercising it on the pull request
# turns a late release failure into an ordinary red check on the PR that
# caused it. This is the same command the release workflow runs.
- run: bun run build:dashboard

# The macOS shell in Sources/ is 500+ lines of Swift the job above cannot see:
# it runs on ubuntu and never invokes `swift`, so its green tick was green
# regardless of what Sources/ contained — up to and including code that does not
Expand Down
43 changes: 43 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,49 @@ jobs:
- name: Build
run: bun run build

# dashboard/ is a SECOND dependency tree with its own lockfile, and no gate
# above touches it: ci.yml installs the root only, and `bun run build` does
# not enter dashboard/. Until this step existed, that tree's first and only
# install happened inside `npm publish` below, via prepublishOnly — after
# Typecheck, Test and Build had all gone green, so the reviewed thing and
# the published thing were separated by a dependency resolution that no
# gate could see.
#
# It ran unprotected in two distinct ways, both measured on bun 1.3.14:
# * unpinned — plain `bun install` on a drifted lockfile exits 0, silently
# re-resolves, and rewrites bun.lock ("Saved lockfile"). With
# --frozen-lockfile the same state exits 1.
# * unquarantined — the release-age quarantine on a workstation comes
# ENTIRELY from ~/.bunfig.toml, which does not exist on this runner.
# The identical install of a 5-day-old package exits 1 with a real HOME
# and exits 0 with an empty one. That is precisely why the root install
# above passes --minimum-release-age explicitly rather than relying on
# the environment, and the dashboard install needs the same treatment.
#
# Running it here, before the publish boundary, means the tree that ships is
# resolved inside the gated part of the workflow and is visible in these logs.
# prepublishOnly still rebuilds it, now under the same flags, so any other
# publish path is protected too.
#
# Note what each flag actually buys, because they are not interchangeable:
# --frozen-lockfile is load-bearing — it removes resolution from publish
# entirely. --minimum-release-age is defence in depth only; it is enforced
# at RESOLUTION time and does NOT re-validate versions already pinned in the
# lockfile (measured: a frozen install of a lock pinning a 5-day-old version
# exits 0). A too-new pin is caught by review of the lockfile diff, not here.
- name: Build dashboard with locked, quarantined dependencies
run: bun run build:dashboard

# `files` in package.json ships dashboard/dist/. npm silently OMITS a listed
# path that does not exist rather than failing, so a dashboard that never
# built would publish as a tarball quietly missing its web UI — an artefact
# defect with no error anywhere upstream. Turn that silence into a failure.
- name: Require the dashboard build output
run: |
set -euo pipefail
test -f dashboard/dist/index.html
echo "dashboard/dist/index.html present"

# No NODE_AUTH_TOKEN, and no token of any kind. npm detects the Actions
# OIDC environment and exchanges the id-token for a short-lived,
# publish-scoped credential. --provenance is passed explicitly: npm
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"scripts": {
"clean": "rm -rf dist bin",
"build": "bun run clean && bun build ./src/cli/index.tsx --outdir ./bin --target bun --external ink --external react --external chalk && bun build ./src/mcp/index.ts --outfile ./bin/mcp.js --target bun && bun build ./src/server/serve-entry.ts --outfile ./bin/serve.js --target bun && bun build ./src/hooks/blocker-hook.ts --outfile ./bin/hook.js --target bun && bun build ./src/index.ts ./src/sdk/index.ts --outdir ./dist --target bun && (tsc --emitDeclarationOnly --declaration --outDir dist || true)",
"build:dashboard": "cd dashboard && bun install && bun run build",
"build:dashboard": "cd dashboard && bun install --frozen-lockfile --minimum-release-age 604800 && bun run build",
"test": "bun test",
"dev": "bun run ./src/cli/index.tsx",
"serve": "bun run ./src/server/serve-entry.ts",
Expand Down
Loading