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
28 changes: 16 additions & 12 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -284,10 +284,12 @@ jobs:
release-verify:
name: Release Verification Gate
runs-on: ubuntu-latest
# This gate runs the same fully serial suite covered by the matrix's
# measured degraded-runner policy above. Run 33299144173 reached the old
# 15-minute cap while Vitest was still running; no test failure had been
# reported before GitHub cancelled the job.
# --skip-suite: every Build & Test leg already runs typecheck (inside
# verify:release) and the full suite, so this gate runs only the build and
# the CLI checks: doctor under a throwaway HOME with no check at fail, the
# install-hooks dry run, the feedback URL and the demo seed. The 40-minute
# cap is kept from when it ran the suite too (run 33299144173 reached the
# old 15-minute cap mid-Vitest).
timeout-minutes: 40

steps:
Expand All @@ -306,20 +308,22 @@ jobs:
run: npm ci

- name: Run release verification
run: bash scripts/release-verify.sh
run: bash scripts/release-verify.sh --skip-suite

# `node scripts/verify.mjs` runs the same steps a person runs locally as
# `npm run verify` — build, verify:release, the isolated suite, the
# packaged smoke, the dashboard e2e — and logs the git tree hash it
# verified. A receipt quoted in a PR (.verify/receipt.json, bound to that
# `node scripts/verify.mjs --journeys` runs the journey steps of
# `npm run verify` — build, the packaged smoke, the dashboard e2e — and
# logs the git tree hash it verified. verify:release and the suite are not
# repeated here: every Build & Test leg runs them, one on this OS and Node.
#
# A receipt quoted in a PR (.verify/receipt.json, bound to that
# hash) can therefore be compared with what CI saw for the same tree; a
# receipt for a different tree is a finding. The test step before it keeps
# scripts/verify.mjs and scripts/verify-receipt.mjs honest about themselves.
#
# This is the only job that runs the packaged dashboard e2e on a pull
# request. A separate job used to run the same command on the same OS and
# Node; tests/release-scripts-safety.test.ts pins that this job runs the
# full verify and that the e2e step is still in scripts/verify.config.json.
# journeys and that the packaged and e2e steps are still marked as journeys.
sdlc-verify:
name: SDLC verify
runs-on: ubuntu-latest
Expand Down Expand Up @@ -355,8 +359,8 @@ jobs:
- name: Verify module tests (tree hash, receipt, verify runner)
run: node --test scripts/verify.test.mjs scripts/lib/verify-core.test.mjs

- name: Golden journeys (node scripts/verify.mjs, logs the tree hash)
run: node scripts/verify.mjs
- name: Golden journeys (node scripts/verify.mjs --journeys, logs the tree hash)
run: node scripts/verify.mjs --journeys

- name: Keep the verify record on failure
if: failure()
Expand Down
8 changes: 4 additions & 4 deletions scripts/audit/baseline.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@
"reason": "The doctor exit code is captured and rejected first; this grep independently checks the skills and hooks content.",
"triaged": "2026-09-06 provider-vector retirement audit; re-keyed 2026-09-12 (the workflow_dispatch trigger added 23 lines above the unchanged statement); re-keyed 2026-10-01 (the coverage job and the lint step above the unchanged statement were folded away); re-keyed 2026-10-01 251→228 (the matrix comments and steps above reworked to shorten pull request feedback; statement byte-identical)"
},
"C4 scripts/release-verify.sh:106": {
"C4 scripts/release-verify.sh:111": {
"class": "SAFE-DIAGNOSTIC",
"reason": "This tail-to-sed pipeline only formats diagnostics after the test command has already produced the failing return code.",
"triaged": "2026-09-06 provider-vector retirement audit"
"triaged": "2026-09-06 provider-vector retirement audit; re-keyed 2026-10-05 106->111 (--skip-suite flag lines added above; statement byte-identical)"
},
"C4 scripts/release-verify.sh:160": {
"C4 scripts/release-verify.sh:165": {
"class": "SAFE-PIPEFAIL",
"reason": "The script enables pipefail, so grep cannot hide a failed feedback draft command.",
"triaged": "2026-09-06 provider-vector retirement audit"
"triaged": "2026-09-06 provider-vector retirement audit; re-keyed 2026-10-05 160->165 (--skip-suite flag lines added above; statement byte-identical)"
},
"C5 src/core/demo.ts:228": {
"class": "SAFE-OPTIONAL-COLLECTION",
Expand Down
13 changes: 9 additions & 4 deletions scripts/release-verify.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
#
# Use:
# bash scripts/release-verify.sh # full pass
# bash scripts/release-verify.sh --quick # build + tests only (no smoke / install probe)
# bash scripts/release-verify.sh --quick # typecheck + build only (no suite, smoke or install probe)
# bash scripts/release-verify.sh --skip-suite # build + smoke / install probes; for CI, where
# # every Build & Test leg already runs typecheck
# # (in verify:release) and the full suite
#
# Exit code:
# 0 — this script's checks passed; release authorization is separate
Expand All @@ -21,11 +24,13 @@ REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
cd "$REPO_ROOT"

QUICK=0
SKIP_SUITE=0
for arg in "$@"; do
case "$arg" in
--quick) QUICK=1 ;;
--skip-suite) SKIP_SUITE=1 ;;
-h|--help)
sed -n '2,18p' "$0"
sed -n '2,19p' "$0"
exit 0
;;
*)
Expand Down Expand Up @@ -179,11 +184,11 @@ gate_demo_seed_idempotent() {
echo "release-verify @ $(date)"
echo "repo: $REPO_ROOT"

run_gate "typecheck (tsc -p tsconfig.check.json)" gate_typecheck
[ "$SKIP_SUITE" = 0 ] && run_gate "typecheck (tsc -p tsconfig.check.json)" gate_typecheck
run_gate "build (tsc + dashboard)" gate_build

if [ "$QUICK" = 0 ]; then
run_gate "full vitest suite (throwaway HOME, no real config)" gate_full_test_suite
[ "$SKIP_SUITE" = 0 ] && run_gate "full vitest suite (throwaway HOME, no real config)" gate_full_test_suite
run_gate "memesh doctor — overall status not FAIL" gate_doctor_runs
run_gate "memesh install-hooks --dry-run" gate_install_hooks_dryrun
run_gate "feedback draft URL build (no browser/submission)" gate_feedback_url_builds
Expand Down
17 changes: 12 additions & 5 deletions tests/release-scripts-safety.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -355,12 +355,14 @@ describe('Feature: release scripts never edit the real ~/.memesh', () => {
expect(withoutProbeIdentity).not.toMatch(/\d+\.\d+\.\d+/);
});

it('gives the complete release verification job the proven degraded-runner budget', () => {
it('gives the release verification job the proven degraded-runner budget', () => {
const ci = read('.github/workflows/ci.yml');
const releaseJob = ci.match(/\n {2}release-verify:\n[\s\S]*?(?=\n {2}[A-Za-z0-9_-]+:\n|$)/)?.[0] ?? '';
expect(releaseJob).not.toBe('');
expect(releaseJob).toMatch(/timeout-minutes:\s*40/);
expect(releaseJob).toContain('bash scripts/release-verify.sh');
// --skip-suite drops only what every Build & Test leg already runs (typecheck
// inside verify:release, the full suite); the smoke and install probes stay.
expect(releaseJob).toContain('bash scripts/release-verify.sh --skip-suite');
expect(releaseJob).not.toContain('--skip-llm-probe');
expect(releaseJob).not.toContain('--quick');
});
Expand Down Expand Up @@ -422,11 +424,16 @@ describe('Feature: release scripts never edit the real ~/.memesh', () => {
expect(sdlcJob).not.toMatch(/\n {4}if:/);
expect(sdlcJob).not.toMatch(/\n {4}continue-on-error/);
unconditionalStep(sdlcJob, 'npx playwright install --with-deps chromium');
// The full run, not `--journeys`: the full run is the one with the suite.
unconditionalStep(sdlcJob, 'node scripts/verify.mjs');
// Journeys only: every Build & Test leg already runs the build, verify:release
// and the full suite, so the full run here repeated them on the same OS and Node.
unconditionalStep(sdlcJob, 'node scripts/verify.mjs --journeys');
const config = JSON.parse(read('scripts/verify.config.json')) as {
verify: { steps: Array<{ command: string; args?: string[] }> };
verify: { steps: Array<{ id: string; command: string; args?: string[]; journeys?: boolean }> };
};
// --journeys runs only the steps marked as journeys, so these two must stay marked.
for (const id of ['build', 'packaged', 'dashboard-e2e']) {
expect(config.verify.steps.find((step) => step.id === id)?.journeys, id).toBe(true);
}
const commands = config.verify.steps.map((step) => [step.command, ...(step.args ?? [])].join(' '));
expect(commands).toEqual(
expect.arrayContaining([
Expand Down
Loading