From de99ff6c9b3c6297c3a08a0149749e68d2a89bad Mon Sep 17 00:00:00 2001 From: "MaineCoon-GPT-5.5" Date: Sun, 2 Aug 2026 00:16:50 +0800 Subject: [PATCH 01/15] fix(node): guard direct validation scripts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Why: direct package validation commands could run under unsupported Node 22 and produce misleading partial test results instead of failing at the runtime boundary. The install guard already enforced Node 24, but build/test/lint/check/gate entrypoints could bypass it. Validation: - PATH="/opt/homebrew/opt/node@24/bin:$PATH" node --test scripts/node-runtime-guard.test.mjs - pnpm --filter @cat-cafe/mcp-server test -- --test-name-pattern "workflow-mandated|cat_cafe_register_scheduled_task" (expected fail-fast on Node 22) - PATH="/opt/homebrew/opt/node@24/bin:$PATH" pnpm --filter @cat-cafe/mcp-server test -- --test-name-pattern "workflow-mandated|cat_cafe_register_scheduled_task" - PATH="/opt/homebrew/opt/node@24/bin:$PATH" pnpm check - PATH="/opt/homebrew/opt/node@24/bin:$PATH" pnpm -r --workspace-concurrency=1 --if-present run prebuild - git diff --check [砚砚/gpt-5.5🐾] Thread-Context: threadId=thread_mqcj45byxoka2z7u catId=codex --- package.json | 5 +++ packages/api/package.json | 3 ++ packages/mcp-server/package.json | 3 ++ packages/shared/package.json | 3 ++ packages/web/package.json | 4 +- scripts/check-node-runtime.mjs | 3 +- scripts/check-validation-node-runtime.mjs | 5 +++ scripts/node-runtime-guard.test.mjs | 45 +++++++++++++++++++++++ 8 files changed, 69 insertions(+), 2 deletions(-) create mode 100644 scripts/check-validation-node-runtime.mjs diff --git a/package.json b/package.json index bc07b27b4b..95a1d4555b 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,12 @@ "scripts": { "init": "./scripts/init-cafe.sh", "preinstall": "node scripts/check-node-runtime.mjs", + "pregate": "node scripts/check-validation-node-runtime.mjs", "gate": "bash ./scripts/pre-merge-check.sh", + "prebuild": "node scripts/check-validation-node-runtime.mjs", + "pretest": "node scripts/check-validation-node-runtime.mjs", + "prelint": "node scripts/check-validation-node-runtime.mjs", + "precheck": "node scripts/check-validation-node-runtime.mjs", "guards:install": "bash ./scripts/install-git-guards.sh", "start": "node ./scripts/start-entry.mjs start", "stop": "./scripts/start-dev.sh --stop", diff --git a/packages/api/package.json b/packages/api/package.json index 2a37cbc684..cf324c748c 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -7,8 +7,10 @@ "scripts": { "dev": "while true; do NODE_OPTIONS=\"--import $PWD/scripts/sigusr1-guard.mjs${NODE_OPTIONS:+ $NODE_OPTIONS}\" tsx watch --exclude \"dist/**\" --exclude \"../shared/dist/**\" src/index.ts; ec=$?; if [ $ec -eq 0 ] || [ $ec -eq 130 ]; then break; fi; echo \"[api] dev exited ($ec) — auto-restarting in 1s\"; sleep 1; done", "verify:sigusr1": "node scripts/verify-sigusr1-guard.mjs", + "prebuild": "node ../../scripts/check-validation-node-runtime.mjs", "build": "pnpm --dir ../shared build && tsc && node ./scripts/copy-marketplace-catalog-data.mjs", "start": "node dist/index.js", + "pretest": "node ../../scripts/check-validation-node-runtime.mjs", "test": "pnpm --filter @cat-cafe/mcp-server... build && pnpm run build && CAT_CAFE_DISABLE_SHARED_STATE_PREFLIGHT=1 bash ./scripts/with-test-home.sh node --import $(pwd)/test/helpers/setup-cat-registry.js --test --test-timeout=60000 test/*.test.js test/**/*.test.js && pnpm run test:cli", "test:public": "pnpm --dir ../shared build && pnpm --dir ../mcp-server build && pnpm run build && CAT_CAFE_DISABLE_SHARED_STATE_PREFLIGHT=1 bash ./scripts/with-test-home.sh bash ./scripts/run-public-tests.sh", "test:antigravity-smoke": "RUN_ANTIGRAVITY_SMOKE=true pnpm run build && RUN_ANTIGRAVITY_SMOKE=true bash ./scripts/with-test-home.sh node --test test/antigravity-smoke.test.js", @@ -22,6 +24,7 @@ "test:integration": "bash ./scripts/with-test-home.sh node --test test/integration/*.test.js", "test:pty": "pnpm run build && node --import $(pwd)/test/helpers/setup-cat-registry.js --test --test-timeout=120000 test/f230-pty-driver.test.js", "test:cli": "node --import tsx --test test/cli/*.test.ts", + "prelint": "node ../../scripts/check-validation-node-runtime.mjs", "lint": "tsc --noEmit", "clean": "rm -rf dist" }, diff --git a/packages/mcp-server/package.json b/packages/mcp-server/package.json index fd061802f4..cbad6ee9a1 100644 --- a/packages/mcp-server/package.json +++ b/packages/mcp-server/package.json @@ -5,11 +5,14 @@ "type": "module", "main": "./dist/index.js", "scripts": { + "prebuild": "node ../../scripts/check-validation-node-runtime.mjs", "build": "tsc", "dev": "tsx watch src/index.ts", "start": "node dist/index.js", "clean": "rm -rf dist", + "prelint": "node ../../scripts/check-validation-node-runtime.mjs", "lint": "tsc --noEmit", + "pretest": "node ../../scripts/check-validation-node-runtime.mjs", "test": "tsc && node --import tsx --test test/*.test.js test/*.test.ts" }, "dependencies": { diff --git a/packages/shared/package.json b/packages/shared/package.json index 485c357008..04d01c8565 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -32,11 +32,14 @@ } }, "scripts": { + "prebuild": "node ../../scripts/check-validation-node-runtime.mjs", "build": "tsc", "prepare": "tsc", "dev": "tsc --watch", "clean": "rm -rf dist", + "prelint": "node ../../scripts/check-validation-node-runtime.mjs", "lint": "tsc --noEmit", + "pretest": "node ../../scripts/check-validation-node-runtime.mjs", "test": "vitest run" }, "dependencies": { diff --git a/packages/web/package.json b/packages/web/package.json index d202a3eeca..06eb7b4c8f 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -6,11 +6,13 @@ "sync:vendor-assets": "node scripts/sync-vendor-assets.mjs", "predev": "pnpm run sync:vendor-assets", "dev": "node scripts/sync-vendor-assets.mjs --watch -- next dev", - "prebuild": "pnpm run sync:vendor-assets", + "prebuild": "node ../../scripts/check-validation-node-runtime.mjs && pnpm run sync:vendor-assets", "build": "next build", "prestart": "pnpm run sync:vendor-assets", "start": "next start", + "prelint": "node ../../scripts/check-validation-node-runtime.mjs", "lint": "next lint", + "pretest": "node ../../scripts/check-validation-node-runtime.mjs", "test": "node scripts/run-with-node-env-test.mjs pnpm exec vitest run && node scripts/run-with-node-env-test.mjs node --test test/next-config.test.cjs && node eslint-plugins/no-hardcoded-colors.test.js", "test:lint-rules": "node eslint-plugins/no-hardcoded-colors.test.js" }, diff --git a/scripts/check-node-runtime.mjs b/scripts/check-node-runtime.mjs index f75b4dbb5b..9dd1f29d35 100644 --- a/scripts/check-node-runtime.mjs +++ b/scripts/check-node-runtime.mjs @@ -16,10 +16,11 @@ if (process.env.CAT_CAFE_SKIP_NODE_RUNTIME_GUARD === '1') { // bypass with CAT_CAFE_SKIP_NODE_RUNTIME_GUARD=1. // This catches the recurring worktree build failure that has hit every cat // for months (Claude Code shell inherits NODE_ENV=production). +const skipProductionInstallGuard = process.env.CAT_CAFE_SKIP_PRODUCTION_INSTALL_GUARD === '1'; const prodEnv = process.env.NODE_ENV === 'production'; const prodFlag = process.env.npm_config_production === 'true' || process.env.NPM_CONFIG_PRODUCTION === 'true'; -if (prodEnv || prodFlag) { +if (!skipProductionInstallGuard && (prodEnv || prodFlag)) { const reason = prodEnv ? 'NODE_ENV=production' : 'npm_config_production=true'; console.error(''); console.error(`[cat-cafe] ❌ ${reason} detected — pnpm will skip devDependencies!`); diff --git a/scripts/check-validation-node-runtime.mjs b/scripts/check-validation-node-runtime.mjs new file mode 100644 index 0000000000..26cd6c127f --- /dev/null +++ b/scripts/check-validation-node-runtime.mjs @@ -0,0 +1,5 @@ +#!/usr/bin/env node + +process.env.CAT_CAFE_SKIP_PRODUCTION_INSTALL_GUARD = '1'; + +await import('./check-node-runtime.mjs'); diff --git a/scripts/node-runtime-guard.test.mjs b/scripts/node-runtime-guard.test.mjs index cf6a7d907f..8fe2dbc1a4 100644 --- a/scripts/node-runtime-guard.test.mjs +++ b/scripts/node-runtime-guard.test.mjs @@ -238,6 +238,29 @@ test('preinstall guard allows NODE_ENV=production when SKIP guard is set', () => assert.equal(result.status, 0, `stdout:\n${result.stdout}\nstderr:\n${result.stderr}`); }); +test('validation scripts can skip production-install guard while keeping Node version guard', () => { + const supported = spawnSync(process.execPath, ['scripts/check-validation-node-runtime.mjs'], { + cwd: resolve(import.meta.dirname, '..'), + encoding: 'utf8', + env: { + CAT_CAFE_TEST_NODE_VERSION: '24.16.0', + NODE_ENV: 'production', + }, + }); + assert.equal(supported.status, 0, `stdout:\n${supported.stdout}\nstderr:\n${supported.stderr}`); + + const unsupported = spawnSync(process.execPath, ['scripts/check-validation-node-runtime.mjs'], { + cwd: resolve(import.meta.dirname, '..'), + encoding: 'utf8', + env: { + CAT_CAFE_TEST_NODE_VERSION: '23.11.0', + NODE_ENV: 'production', + }, + }); + assert.equal(unsupported.status, 1); + assert.match(unsupported.stderr, /Node 23\.11\.0 is not supported/); +}); + test('package engines advertise the Node 24 floor required by recursive workspace tests', () => { const pkg = JSON.parse(readFileSync(resolve(import.meta.dirname, '..', 'package.json'), 'utf8')); @@ -245,6 +268,28 @@ test('package engines advertise the Node 24 floor required by recursive workspac assert.doesNotMatch(pkg.engines.node, /<\s*26/); }); +test('direct validation scripts fail fast on unsupported Node before running package work', () => { + const packages = [ + { path: 'package.json', guard: 'node scripts/check-validation-node-runtime.mjs' }, + { path: 'packages/api/package.json', guard: 'node ../../scripts/check-validation-node-runtime.mjs' }, + { path: 'packages/mcp-server/package.json', guard: 'node ../../scripts/check-validation-node-runtime.mjs' }, + { path: 'packages/shared/package.json', guard: 'node ../../scripts/check-validation-node-runtime.mjs' }, + { path: 'packages/web/package.json', guard: 'node ../../scripts/check-validation-node-runtime.mjs' }, + ]; + + for (const { path, guard } of packages) { + const pkg = JSON.parse(readFileSync(resolve(import.meta.dirname, '..', path), 'utf8')); + const scriptNames = + path === 'package.json' ? ['build', 'test', 'lint', 'check', 'gate'] : ['build', 'test', 'lint']; + for (const scriptName of scriptNames) { + if (!pkg.scripts?.[scriptName]) continue; + const preScript = pkg.scripts[`pre${scriptName}`]; + assert.ok(preScript?.includes(guard), `${path} pre${scriptName} must run ${guard}`); + assert.doesNotMatch(preScript, /\b[A-Z_]+=1\s+node\b/, `${path} pre${scriptName} must be shell-portable`); + } + } +}); + test('desktop release workflows install with Node 24 to satisfy the root preinstall guard', () => { const workflowPaths = ['.github/workflows/build-mac-dmg.yml', '.github/workflows/build-windows-desktop.yml']; From 442d0c7b58c46c5d439e5ea3c5373f20ecc195d7 Mon Sep 17 00:00:00 2001 From: "MaineCoon-GPT-5.5" Date: Sun, 2 Aug 2026 00:20:39 +0800 Subject: [PATCH 02/15] docs(review): request node24 runtime guard review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Why: the patrol fix needs a traceable non-author review handoff with original requirement, architecture ownership, and validation evidence attached before merge flow continues. [砚砚/gpt-5.5🐾] --- ...-02-node24-runtime-guard-review-request.md | 121 ++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 review-notes/2026-08-02-node24-runtime-guard-review-request.md diff --git a/review-notes/2026-08-02-node24-runtime-guard-review-request.md b/review-notes/2026-08-02-node24-runtime-guard-review-request.md new file mode 100644 index 0000000000..d27550c5ec --- /dev/null +++ b/review-notes/2026-08-02-node24-runtime-guard-review-request.md @@ -0,0 +1,121 @@ +# Review Request: Node 24 Runtime Guard for Direct Validation Scripts + +Review-Target-ID: fix-node24-runtime-guard +Branch: fix/node24-runtime-guard +Commit: de99ff6c9b3c6297c3a08a0149749e68d2a89bad +Worktree: `/Users/xxx/workspace/AI/cat-cafe-node24-runtime-guard` + +## What + +Added a cross-platform validation wrapper, `scripts/check-validation-node-runtime.mjs`, and wired it into root/package `prebuild`, `pretest`, `prelint`, plus root `precheck`/`pregate`. + +The existing install-time guard still rejects `NODE_ENV=production` for installs. Direct validation scripts now skip only that install-specific check while keeping the Node major check, so unsupported Node versions fail before running package work. + +## Why + +The 2026-08-02 scheduled patrol reproduced a false-negative path: `pnpm --filter @cat-cafe/mcp-server test ...` under default Node v22.22.3 ran part of the suite and ended with cancelled tests, while the same command under Node v24.18.0 passed. The repo already declares `engines.node >=24`, but pnpm only warned during direct package scripts. + +## Original Requirements + +> 每轮必须先查真相源和证据,再给风险/价值判断与下一步动作。发现可执行事项后主导闭环:按家规走 feature lifecycle(定位真相源、立项、实现/协调、质量门禁、review、完成记录)。 + +- 来源:scheduled patrol dispatch in `thread_mqcj45byxoka2z7u`, 2026-08-02 00:00 Asia/Shanghai +- Task: `0001785600245033-001028-850d142c` (`[巡检] 固化 Cat Cafe 验证命令的 Node 24 运行时`) +- Please judge whether this closes the patrol finding without over-expanding runtime/tooling scope. + +## Tradeoff + +I did not add a new dependency such as `cross-env`, and I did not change install semantics. A small Node wrapper avoids POSIX-only `VAR=1 node ...` scripts and keeps Windows package-script compatibility. + +This is fail-fast, not auto-reexec, for package validation scripts. Startup scripts already have reexec behavior through `scripts/lib/node-runtime-guard.sh`; package scripts should be deterministic and explicit when the shell runtime is wrong. + +## Architecture Ownership + +Architecture cell: harness-eval +Map delta: none +Why: This only extends validation/preflight harness behavior around existing package scripts and the existing Node runtime guard. It does not add a new Store, Queue, Router, Adapter, Dispatcher, Binding, service boundary, or runtime ownership cell. + +Please reviewer-check that `Map delta: none` matches the diff and that package lifecycle hooks do not create a parallel tooling/runtime control plane. + +## Open Questions + +### 技术 OQ(给 reviewer) + +1. Is `CAT_CAFE_SKIP_PRODUCTION_INSTALL_GUARD` scoped narrowly enough, or should the wrapper use a more validation-specific API to avoid future misuse? +2. Are the selected lifecycle hooks sufficient: root `build/test/lint/check/gate` and package `build/test/lint` for api/mcp/shared/web? +3. Any concern that package `pre*` hooks create surprising behavior for downstream/open-source users? + +### 价值 OQ(给 operator,如有) + +无。 + +## Next Action + +Please review commit `de99ff6c9b3c6297c3a08a0149749e68d2a89bad` in `/Users/xxx/workspace/AI/cat-cafe-node24-runtime-guard`. + +Verdict requested: APPROVE or REQUEST-CHANGES. No GitHub PR exists yet; if approved, I will open/push the PR or continue merge-gate according to the current house flow. + +## Review Sandbox + +- Path: `/tmp/cat-cafe-review/fix-node24-runtime-guard/sol` (or reviewer handle) +- Start Command: not needed; no runtime/server changes +- Ports: not applicable + +### Sandbox Bootstrap + +```bash +unset NODE_ENV +PATH="/opt/homebrew/opt/node@24/bin:$PATH" pnpm install --frozen-lockfile +``` + +## Self-Check Evidence + +### Spec 合规 + +- Patrol finding reproduced under default Node v22.22.3. +- Red test added first in `scripts/node-runtime-guard.test.mjs`; it failed on missing lifecycle guards. +- Implementation adds validation wrapper and lifecycle hooks. +- Dogfood verified default Node v22 now fails fast before test execution, while Node v24 executes the focused mcp suite. +- No frontend/runtime server change; no browser evidence required. +- Root artifact hygiene: both root media checks returned no matches. + +### Test Results + +```bash +PATH="/opt/homebrew/opt/node@24/bin:$PATH" node --test scripts/node-runtime-guard.test.mjs +# 14 pass / 0 fail / 0 cancelled + +pnpm --filter @cat-cafe/mcp-server test -- --test-name-pattern "workflow-mandated|cat_cafe_register_scheduled_task" +# Expected fail-fast on Node v22.22.3: +# [node-runtime] Node 22.22.3 is not supported... + +PATH="/opt/homebrew/opt/node@24/bin:$PATH" pnpm --filter @cat-cafe/mcp-server test -- --test-name-pattern "workflow-mandated|cat_cafe_register_scheduled_task" +# 381 pass / 0 fail / 0 cancelled + +PATH="/opt/homebrew/opt/node@24/bin:$PATH" pnpm check +# PASS; advisory warnings only from existing capability-tip / skill manifest checks + +PATH="/opt/homebrew/opt/node@24/bin:$PATH" pnpm -r --workspace-concurrency=1 --if-present run prebuild +# PASS; web vendor asset sync preserved + +git diff --check +# PASS +``` + +### Root Artifact Hygiene + +```bash +git status --short | rg '^.. [^/]+\.(png|jpe?g|webp|gif|webm|mp4|mov|wav|pdf|pen)$' +# no output + +git diff --name-only origin/develop...HEAD | rg '^[^/]+\.(png|jpe?g|webp|gif|webm|mp4|mov|wav|pdf|pen)$' +# no output +``` + +### Related Evidence + +- Existing root guard: `scripts/check-node-runtime.mjs` +- New validation wrapper: `scripts/check-validation-node-runtime.mjs` +- Regression coverage: `scripts/node-runtime-guard.test.mjs` + +[砚砚/gpt-5.5🐾] From 82078353ba2f023e40bf246a8f6e12d3f788347b Mon Sep 17 00:00:00 2001 From: "MaineCoon-GPT-5.5" Date: Mon, 3 Aug 2026 00:10:16 +0800 Subject: [PATCH 03/15] fix(node): enforce engine strict validation guard Why: Sol review showed package lifecycle hooks only protect exact script names, leaving finance lint and namespaced validation scripts such as api test:public able to run under unsupported Node. A central pnpm engine-strict guard fails before any package work, while the dynamic regression test discovers workspace validation entrypoints so future packages or test:* scripts cannot silently escape coverage. Evidence: RED node-runtime-guard test listed unprotected finance/API/web/root validation entrypoints; GREEN node-runtime-guard 14/14, default Node v22 finance/API commands fail with ERR_PNPM_UNSUPPORTED_ENGINE, Node 24 finance/web/mcp targeted checks pass, pnpm check passes. --- .npmrc | 3 + scripts/node-runtime-guard.test.mjs | 118 +++++++++++++++++++++++----- 2 files changed, 103 insertions(+), 18 deletions(-) diff --git a/.npmrc b/.npmrc index 70e2cb3f26..013e480ee8 100644 --- a/.npmrc +++ b/.npmrc @@ -1,6 +1,9 @@ # Required for Next.js and some packages that expect flat node_modules shamefully-hoist=true +# Fail fast before running validation scripts under an unsupported Node runtime. +engine-strict=true + # Reduce peer dependency noise during development # Review these settings before production deployment strict-peer-dependencies=false diff --git a/scripts/node-runtime-guard.test.mjs b/scripts/node-runtime-guard.test.mjs index 8fe2dbc1a4..ab54f91ad2 100644 --- a/scripts/node-runtime-guard.test.mjs +++ b/scripts/node-runtime-guard.test.mjs @@ -1,10 +1,21 @@ import assert from 'node:assert/strict'; import { spawnSync } from 'node:child_process'; -import { mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync, writeFileSync } from 'node:fs'; +import { + existsSync, + mkdirSync, + mkdtempSync, + readdirSync, + readFileSync, + realpathSync, + rmSync, + writeFileSync, +} from 'node:fs'; import { tmpdir } from 'node:os'; -import { join, resolve } from 'node:path'; +import { dirname, join, relative, resolve } from 'node:path'; import test from 'node:test'; +const repoRoot = resolve(import.meta.dirname, '..'); + function runBash(snippet, env = {}) { return spawnSync('/bin/bash', ['--noprofile', '--norc', '-c', snippet], { cwd: resolve(import.meta.dirname, '..'), @@ -41,6 +52,58 @@ printf 'fake node ${version}\\n' return path; } +function readJson(relPath) { + return JSON.parse(readFileSync(resolve(repoRoot, relPath), 'utf8')); +} + +function workspacePackageJsonPaths() { + const workspace = readFileSync(resolve(repoRoot, 'pnpm-workspace.yaml'), 'utf8'); + const packageJsonPaths = ['package.json']; + + for (const line of workspace.split(/\r?\n/)) { + const match = line.match(/^\s*-\s*['"]?([^'"]+)['"]?\s*$/); + if (!match) continue; + const pattern = match[1]; + if (!pattern.endsWith('/*')) { + throw new Error(`Unsupported workspace package pattern in test: ${pattern}`); + } + + const parent = pattern.slice(0, -2); + for (const entry of readdirSync(resolve(repoRoot, parent), { withFileTypes: true })) { + if (!entry.isDirectory()) continue; + const relPath = join(parent, entry.name, 'package.json'); + if (existsSync(resolve(repoRoot, relPath))) packageJsonPaths.push(relPath); + } + } + + return packageJsonPaths.sort(); +} + +function isValidationEntrypoint(scriptName) { + if (/^(?:pre|post)/.test(scriptName)) return false; + return ( + scriptName === 'build' || + scriptName === 'test' || + scriptName === 'lint' || + scriptName === 'check' || + scriptName === 'gate' || + scriptName.startsWith('test:') || + scriptName.startsWith('check:') + ); +} + +function validationGuardForPackageJson(relPath) { + if (relPath === 'package.json') return 'node scripts/check-validation-node-runtime.mjs'; + const fromDir = dirname(relPath); + const rootPrefix = relative(fromDir, '.'); + return `node ${rootPrefix}/scripts/check-validation-node-runtime.mjs`; +} + +function pnpmEngineStrictEnabled() { + const npmrc = readFileSync(resolve(repoRoot, '.npmrc'), 'utf8'); + return npmrc.split(/\r?\n/).some((line) => /^\s*engine-strict\s*=\s*true\s*(?:#.*)?$/.test(line)); +} + test('node runtime guard rejects Node 26 and accepts Node 24', () => { const tmp = mkdtempSync(join(tmpdir(), 'cat-cafe-node-guard-')); try { @@ -269,25 +332,44 @@ test('package engines advertise the Node 24 floor required by recursive workspac }); test('direct validation scripts fail fast on unsupported Node before running package work', () => { - const packages = [ - { path: 'package.json', guard: 'node scripts/check-validation-node-runtime.mjs' }, - { path: 'packages/api/package.json', guard: 'node ../../scripts/check-validation-node-runtime.mjs' }, - { path: 'packages/mcp-server/package.json', guard: 'node ../../scripts/check-validation-node-runtime.mjs' }, - { path: 'packages/shared/package.json', guard: 'node ../../scripts/check-validation-node-runtime.mjs' }, - { path: 'packages/web/package.json', guard: 'node ../../scripts/check-validation-node-runtime.mjs' }, - ]; - - for (const { path, guard } of packages) { - const pkg = JSON.parse(readFileSync(resolve(import.meta.dirname, '..', path), 'utf8')); - const scriptNames = - path === 'package.json' ? ['build', 'test', 'lint', 'check', 'gate'] : ['build', 'test', 'lint']; - for (const scriptName of scriptNames) { - if (!pkg.scripts?.[scriptName]) continue; + const centralGuard = pnpmEngineStrictEnabled(); + const entries = []; + const missingProtection = []; + + for (const path of workspacePackageJsonPaths()) { + const pkg = readJson(path); + const guard = validationGuardForPackageJson(path); + for (const scriptName of Object.keys(pkg.scripts ?? {}) + .filter(isValidationEntrypoint) + .sort()) { + entries.push(`${path}#${scriptName}`); const preScript = pkg.scripts[`pre${scriptName}`]; - assert.ok(preScript?.includes(guard), `${path} pre${scriptName} must run ${guard}`); - assert.doesNotMatch(preScript, /\b[A-Z_]+=1\s+node\b/, `${path} pre${scriptName} must be shell-portable`); + if (preScript) { + assert.ok(preScript.includes(guard), `${path} pre${scriptName} must run ${guard}`); + assert.doesNotMatch(preScript, /\b[A-Z_]+=1\s+node\b/, `${path} pre${scriptName} must be shell-portable`); + } else if (!centralGuard) { + missingProtection.push(`${path}#${scriptName}`); + } } } + + assert.ok( + entries.includes('packages/finance/package.json#lint'), + 'validation entrypoint audit must discover finance lint', + ); + assert.ok( + entries.includes('packages/api/package.json#test:public'), + 'validation entrypoint audit must discover API test:public', + ); + assert.ok( + entries.includes('packages/web/package.json#test:lint-rules'), + 'validation entrypoint audit must discover web lint-rule tests', + ); + assert.deepEqual( + missingProtection, + [], + `validation entrypoints without node runtime guard:\n${missingProtection.join('\n')}`, + ); }); test('desktop release workflows install with Node 24 to satisfy the root preinstall guard', () => { From 4e37805077826cf1299bfff4486ff0cafb969532 Mon Sep 17 00:00:00 2001 From: "MaineCoon-GPT-5.5" Date: Mon, 3 Aug 2026 00:11:34 +0800 Subject: [PATCH 04/15] docs(review): request node24 runtime guard r2 review Why: Sol requested changes on the first node runtime guard patch. The R2 packet records the verified failure-mode sweep, the central engine-strict fix, validation evidence, and the separate public-test exclusion follow-up so the non-author reviewer can assess the current head without reconstructing evidence from chat. --- ...-node24-runtime-guard-r2-review-request.md | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 review-notes/2026-08-03-node24-runtime-guard-r2-review-request.md diff --git a/review-notes/2026-08-03-node24-runtime-guard-r2-review-request.md b/review-notes/2026-08-03-node24-runtime-guard-r2-review-request.md new file mode 100644 index 0000000000..5ca44b8657 --- /dev/null +++ b/review-notes/2026-08-03-node24-runtime-guard-r2-review-request.md @@ -0,0 +1,84 @@ +# Review Request R2: Node 24 Runtime Guard Coverage + +Review-Target-ID: fix-node24-runtime-guard +Branch: fix/node24-runtime-guard +Base: origin/develop +Code Commit: 82078353b354bf8a779fe2d60f45c9a81bd2b902 +Worktree: `/Users/xxx/workspace/AI/cat-cafe-node24-runtime-guard` + +## What + +R1 reviewer finding from @sol was correct: package lifecycle hooks only protect exact script names. The first implementation protected canonical `build/test/lint` for a hardcoded package list, but left supported commands such as `packages/finance lint`, API `test:public`, and web `test:lint-rules` able to run under default Node v22. + +R2 adds a central pnpm guard: + +- `.npmrc`: `engine-strict=true` +- `scripts/node-runtime-guard.test.mjs`: replaces fixed package/script assertions with workspace package discovery plus validation entrypoint discovery (`build`, `test`, `lint`, `check`, `gate`, `test:*`, `check:*`) + +The invariant is now: either a validation entrypoint has its exact `pre` lifecycle guard, or the repo has central pnpm engine-strict protection before any package work runs. + +## Why + +This closes the original failure mode without growing a brittle list of `pretest:*` and `precheck:*` hooks. Future workspace packages or namespaced validation commands are discovered by the regression test. + +## Tradeoff + +`engine-strict=true` changes default pnpm behavior under unsupported Node from a warning to a hard failure. That is intentional for this repo because direct validation output under the wrong Node is not trustworthy. Existing Node 24 paths continue to run. + +## Failure-Mode Sweep + +Pattern: exact lifecycle hook inventory was used as the guard boundary. + +Sweep result: + +- Root `check:*` and `test:*` entrypoints were unprotected. +- API `test:*` entrypoints including `test:public` were unprotected. +- `packages/finance` canonical `build/test/lint` was unprotected. +- Web `test:lint-rules` was unprotected. + +Fix shape: central engine-strict plus dynamic test, not per-entry patching. + +## Validation Evidence + +```bash +PATH="/opt/homebrew/opt/node@24/bin:$PATH" node --test scripts/node-runtime-guard.test.mjs +# 14 pass / 0 fail / 0 cancelled + +pnpm --filter @cat-cafe/finance run lint +# default Node v22.22.3: ERR_PNPM_UNSUPPORTED_ENGINE before tsc + +pnpm --filter @cat-cafe/api run test:public +# default Node v22.22.3: ERR_PNPM_UNSUPPORTED_ENGINE before package script + +PATH="/opt/homebrew/opt/node@24/bin:$PATH" pnpm --filter @cat-cafe/finance run lint +# PASS + +PATH="/opt/homebrew/opt/node@24/bin:$PATH" pnpm --filter @cat-cafe/web run test:lint-rules +# PASS + +PATH="/opt/homebrew/opt/node@24/bin:$PATH" pnpm --filter @cat-cafe/mcp-server test -- --test-name-pattern "workflow-mandated|cat_cafe_register_scheduled_task" +# 381 pass / 0 fail / 0 cancelled + +PATH="/opt/homebrew/opt/node@24/bin:$PATH" pnpm check +# PASS; existing advisory warnings only + +git diff --check +# PASS +``` + +Note: under Node 24, `pnpm --filter @cat-cafe/api run test:public` reaches the public-test resolver but fails because `packages/api/config/public-test-exclusions.json` entries expired on 2026-07-31. That is a separate existing gate-health issue, tracked as task `0001785687002134-001212-84b1606d`. + +## Quality-Gate Notes + +- Artifact hygiene: no root media/design artifacts in worktree or branch diff. +- Fallback layer script: unavailable in this checkout (`scripts/check-fallback-layers.mjs` missing); manual diff scan found no >=3 fallback-layer additions. +- Architecture ownership script: unavailable in this checkout (`check:architecture-ownership` intentionally absent from public package scripts). +- Architecture cell: harness-eval +- Map delta: none +- Why: validation/preflight behavior only; no Store, Queue, Router, Adapter, Dispatcher, Binding, or service boundary change. + +## Next Action + +Please re-review current branch HEAD after `82078353b` and give APPROVE or REQUEST-CHANGES. If approved, I will continue PR/merge-gate flow. + +[砚砚/gpt-5.5🐾] From 69a2e402af0b96f5e2def45bfc5dedb2ca942257 Mon Sep 17 00:00:00 2001 From: "MaineCoon-GPT-5.5" Date: Mon, 3 Aug 2026 00:25:08 +0800 Subject: [PATCH 05/15] fix(node): scope validation runtime guards Why: global engine-strict blocked startup auto-reexec before start-entry could recover unsupported Node; explicit validation prehooks preserve startup semantics while still failing validation work fast under Node <24. The dynamic audit now uses POSIX package paths so package.json script comparisons are platform-stable. --- .npmrc | 3 --- package.json | 30 ++++++++++++++++++++++++++++- packages/api/package.json | 9 ++++++++- packages/finance/package.json | 5 ++++- packages/web/package.json | 3 ++- scripts/node-runtime-guard.test.mjs | 23 ++++++++++++++++------ 6 files changed, 60 insertions(+), 13 deletions(-) diff --git a/.npmrc b/.npmrc index 013e480ee8..70e2cb3f26 100644 --- a/.npmrc +++ b/.npmrc @@ -1,9 +1,6 @@ # Required for Next.js and some packages that expect flat node_modules shamefully-hoist=true -# Fail fast before running validation scripts under an unsupported Node runtime. -engine-strict=true - # Reduce peer dependency noise during development # Review these settings before production deployment strict-peer-dependencies=false diff --git a/package.json b/package.json index 95a1d4555b..894128583c 100644 --- a/package.json +++ b/package.json @@ -101,7 +101,35 @@ "clean": "pnpm -r run clean && rm -rf node_modules", "check:start-profile-isolation": "node --test scripts/start-dev-profile-isolation.test.mjs", "check:brand-dictionary": "node --test scripts/brand-dictionary-helper.test.mjs", - "check:brand-guard": "node --test scripts/intake-from-opensource.test.mjs scripts/lib/intake-gh-retry.test.mjs" + "check:brand-guard": "node --test scripts/intake-from-opensource.test.mjs scripts/lib/intake-gh-retry.test.mjs", + "precheck:biome-review-worktrees": "node scripts/check-validation-node-runtime.mjs", + "precheck:biome-version": "node scripts/check-validation-node-runtime.mjs", + "precheck:brand-dictionary": "node scripts/check-validation-node-runtime.mjs", + "precheck:brand-guard": "node scripts/check-validation-node-runtime.mjs", + "precheck:capability-tips": "node scripts/check-validation-node-runtime.mjs", + "precheck:capability-tips:stale": "node scripts/check-validation-node-runtime.mjs", + "precheck:deps": "node scripts/check-validation-node-runtime.mjs", + "precheck:dir-size": "node scripts/check-validation-node-runtime.mjs", + "precheck:env-example": "node scripts/check-validation-node-runtime.mjs", + "precheck:env-ports": "node scripts/check-validation-node-runtime.mjs", + "precheck:env-registry": "node scripts/check-validation-node-runtime.mjs", + "precheck:features": "node scripts/check-validation-node-runtime.mjs", + "precheck:fix": "node scripts/check-validation-node-runtime.mjs", + "precheck:followup-tails": "node scripts/check-validation-node-runtime.mjs", + "precheck:guides": "node scripts/check-validation-node-runtime.mjs", + "precheck:hotfix-pattern": "node scripts/check-validation-node-runtime.mjs", + "precheck:lockfile": "node scripts/check-validation-node-runtime.mjs", + "precheck:pre-merge-gate": "node scripts/check-validation-node-runtime.mjs", + "precheck:scripts-ascii-only": "node scripts/check-validation-node-runtime.mjs", + "precheck:skills": "node scripts/check-validation-node-runtime.mjs", + "precheck:skills:manifest": "node scripts/check-validation-node-runtime.mjs", + "precheck:skills:surfaces": "node scripts/check-validation-node-runtime.mjs", + "precheck:sop-definitions": "node scripts/check-validation-node-runtime.mjs", + "precheck:start-profile-isolation": "node scripts/check-validation-node-runtime.mjs", + "precheck:video-new": "node scripts/check-validation-node-runtime.mjs", + "precheck:worktree-port-offset": "node scripts/check-validation-node-runtime.mjs", + "pretest:api:redis": "node scripts/check-validation-node-runtime.mjs", + "pretest:api:redis:repeat": "node scripts/check-validation-node-runtime.mjs" }, "devDependencies": { "@biomejs/biome": "^2.4.1", diff --git a/packages/api/package.json b/packages/api/package.json index cf324c748c..a4e584fd78 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -26,7 +26,14 @@ "test:cli": "node --import tsx --test test/cli/*.test.ts", "prelint": "node ../../scripts/check-validation-node-runtime.mjs", "lint": "tsc --noEmit", - "clean": "rm -rf dist" + "clean": "rm -rf dist", + "pretest:antigravity-smoke": "node ../../scripts/check-validation-node-runtime.mjs", + "pretest:cli": "node ../../scripts/check-validation-node-runtime.mjs", + "pretest:integration": "node ../../scripts/check-validation-node-runtime.mjs", + "pretest:pty": "node ../../scripts/check-validation-node-runtime.mjs", + "pretest:public": "node ../../scripts/check-validation-node-runtime.mjs", + "pretest:redis": "node ../../scripts/check-validation-node-runtime.mjs", + "pretest:redis:repeat": "node ../../scripts/check-validation-node-runtime.mjs" }, "dependencies": { "@cat-cafe/shared": "workspace:*", diff --git a/packages/finance/package.json b/packages/finance/package.json index e94418aca0..00a42edc58 100644 --- a/packages/finance/package.json +++ b/packages/finance/package.json @@ -16,7 +16,10 @@ "prepare": "tsc", "lint": "tsc --noEmit", "test": "pnpm run build && node --test test/*.test.js", - "clean": "rm -rf dist" + "clean": "rm -rf dist", + "prebuild": "node ../../scripts/check-validation-node-runtime.mjs", + "prelint": "node ../../scripts/check-validation-node-runtime.mjs", + "pretest": "node ../../scripts/check-validation-node-runtime.mjs" }, "devDependencies": { "typescript": "^5.3.3" diff --git a/packages/web/package.json b/packages/web/package.json index 06eb7b4c8f..584934e053 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -14,7 +14,8 @@ "lint": "next lint", "pretest": "node ../../scripts/check-validation-node-runtime.mjs", "test": "node scripts/run-with-node-env-test.mjs pnpm exec vitest run && node scripts/run-with-node-env-test.mjs node --test test/next-config.test.cjs && node eslint-plugins/no-hardcoded-colors.test.js", - "test:lint-rules": "node eslint-plugins/no-hardcoded-colors.test.js" + "test:lint-rules": "node eslint-plugins/no-hardcoded-colors.test.js", + "pretest:lint-rules": "node ../../scripts/check-validation-node-runtime.mjs" }, "dependencies": { "@cat-cafe/shared": "workspace:*", diff --git a/scripts/node-runtime-guard.test.mjs b/scripts/node-runtime-guard.test.mjs index ab54f91ad2..0f52e0c104 100644 --- a/scripts/node-runtime-guard.test.mjs +++ b/scripts/node-runtime-guard.test.mjs @@ -11,7 +11,7 @@ import { writeFileSync, } from 'node:fs'; import { tmpdir } from 'node:os'; -import { dirname, join, relative, resolve } from 'node:path'; +import { join, posix as posixPath, resolve } from 'node:path'; import test from 'node:test'; const repoRoot = resolve(import.meta.dirname, '..'); @@ -71,7 +71,7 @@ function workspacePackageJsonPaths() { const parent = pattern.slice(0, -2); for (const entry of readdirSync(resolve(repoRoot, parent), { withFileTypes: true })) { if (!entry.isDirectory()) continue; - const relPath = join(parent, entry.name, 'package.json'); + const relPath = posixPath.join(parent, entry.name, 'package.json'); if (existsSync(resolve(repoRoot, relPath))) packageJsonPaths.push(relPath); } } @@ -94,8 +94,8 @@ function isValidationEntrypoint(scriptName) { function validationGuardForPackageJson(relPath) { if (relPath === 'package.json') return 'node scripts/check-validation-node-runtime.mjs'; - const fromDir = dirname(relPath); - const rootPrefix = relative(fromDir, '.'); + const fromDir = posixPath.dirname(relPath); + const rootPrefix = posixPath.relative(fromDir, '.'); return `node ${rootPrefix}/scripts/check-validation-node-runtime.mjs`; } @@ -332,7 +332,6 @@ test('package engines advertise the Node 24 floor required by recursive workspac }); test('direct validation scripts fail fast on unsupported Node before running package work', () => { - const centralGuard = pnpmEngineStrictEnabled(); const entries = []; const missingProtection = []; @@ -347,7 +346,7 @@ test('direct validation scripts fail fast on unsupported Node before running pac if (preScript) { assert.ok(preScript.includes(guard), `${path} pre${scriptName} must run ${guard}`); assert.doesNotMatch(preScript, /\b[A-Z_]+=1\s+node\b/, `${path} pre${scriptName} must be shell-portable`); - } else if (!centralGuard) { + } else { missingProtection.push(`${path}#${scriptName}`); } } @@ -372,6 +371,18 @@ test('direct validation scripts fail fast on unsupported Node before running pac ); }); +test('pnpm engine strict remains off so startup scripts can reach the auto-reexec guard', () => { + const pkg = readJson('package.json'); + + assert.equal(pnpmEngineStrictEnabled(), false); + assert.equal(isValidationEntrypoint('start'), false); + assert.equal(isValidationEntrypoint('start:status'), false); + assert.equal(isValidationEntrypoint('start:direct'), false); + assert.equal(isValidationEntrypoint('dev:direct'), false); + assert.match(pkg.scripts.start, /^node \.\/scripts\/start-entry\.mjs start\b/); + assert.match(pkg.scripts['start:status'], /^node \.\/scripts\/start-entry\.mjs status\b/); +}); + test('desktop release workflows install with Node 24 to satisfy the root preinstall guard', () => { const workflowPaths = ['.github/workflows/build-mac-dmg.yml', '.github/workflows/build-windows-desktop.yml']; From f2b0d6bf48d4ed5f5d17d005e8daeda4f91909bb Mon Sep 17 00:00:00 2001 From: "MaineCoon-GPT-5.5" Date: Mon, 3 Aug 2026 00:25:59 +0800 Subject: [PATCH 06/15] docs(review): request node24 runtime guard r3 review Why: Sol R2 requested proof that validation runtime enforcement no longer blocks startup auto-reexec and that the dynamic package audit is path-stable; this packet records the new code commit and verification evidence for re-review. --- ...-node24-runtime-guard-r3-review-request.md | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 review-notes/2026-08-03-node24-runtime-guard-r3-review-request.md diff --git a/review-notes/2026-08-03-node24-runtime-guard-r3-review-request.md b/review-notes/2026-08-03-node24-runtime-guard-r3-review-request.md new file mode 100644 index 0000000000..ba309d8700 --- /dev/null +++ b/review-notes/2026-08-03-node24-runtime-guard-r3-review-request.md @@ -0,0 +1,105 @@ +# Review Request R3: Node 24 Runtime Guard Coverage + +Review-Target-ID: fix-node24-runtime-guard +Branch: fix/node24-runtime-guard +Base: origin/develop +Code Commit: 69a2e402af0b96f5e2def45bfc5dedb2ca942257 +Worktree: `/Users/xxx/workspace/AI/cat-cafe-node24-runtime-guard` +Reviewer: `@sol` + +## What + +R3 addresses Sol R2 findings against `4e37805077826cf1299bfff4486ff0cafb969532`. + +- P1 fixed: removed global `.npmrc` `engine-strict=true`, so startup and operational scripts can still reach `scripts/start-entry.mjs` and its Node 24 auto-reexec guard under default Node 22. +- P2 fixed: `scripts/node-runtime-guard.test.mjs` now uses POSIX path helpers for workspace package discovery and guard path construction, matching package.json script text on Windows and Unix. +- Coverage kept: validation entrypoints are still discovered dynamically and must have exact `pre` guards. The test no longer accepts central `engine-strict` as a substitute. + +## Why + +The original patrol finding is still valid: validation output from this checkout is not trustworthy under Node 22 because the repo requires Node >=24. R2's central `engine-strict` approach closed that gap, but it expanded the blast radius into startup recovery. R3 narrows enforcement back to validation entrypoints while preserving startup semantics. + +## Tradeoff + +This is more verbose than a global pnpm setting because package-level `test:*` / `check:*` commands need explicit prehooks. The dynamic audit is the guard against future drift: new validation entrypoints fail `node-runtime-guard.test.mjs` until their matching lifecycle hook is added. + +## Failure-Mode Sweep + +Pattern: a broad package-manager guard fixed validation drift but crossed a runtime boundary. + +Sweep result: + +- Startup and operational scripts are explicitly classified as non-validation entrypoints in the regression test: `start`, `start:status`, `start:direct`, `dev:direct`. +- Root `check:*` and Redis test wrappers now have explicit guards. +- API `test:*`, finance `build/lint/test`, and web `test:lint-rules` now have explicit guards. +- Path comparison code now stays in POSIX form when comparing against package.json scripts. + +## Validation Evidence + +```bash +node -v && pnpm -v +# v22.22.3 +# 9.15.4 + +PATH="/opt/homebrew/opt/node@24/bin:$PATH" node --test scripts/node-runtime-guard.test.mjs +# 15 pass / 0 fail / 0 cancelled + +pnpm --filter @cat-cafe/finance run lint +# default Node v22.22.3: fails in @cat-cafe/finance prelint via check-validation-node-runtime.mjs before tsc + +pnpm --filter @cat-cafe/api run test:public +# default Node v22.22.3: fails in @cat-cafe/api pretest:public via check-validation-node-runtime.mjs before package work + +pnpm check:features +# default Node v22.22.3: fails in root precheck:features via check-validation-node-runtime.mjs + +env -u CAT_CAFE_NODE_RUNTIME_GUARD_REEXEC \ + CAT_CAFE_NODE_BIN=/opt/homebrew/opt/node@24/bin/node \ + pnpm start:status +# reaches node ./scripts/start-entry.mjs status +# re-execs with /opt/homebrew/opt/node@24/bin/node (24.18.0) +# exits 1 only because the local daemon is not running; no ERR_PNPM_UNSUPPORTED_ENGINE + +PATH="/opt/homebrew/opt/node@24/bin:$PATH" pnpm --filter @cat-cafe/finance run lint +# PASS + +PATH="/opt/homebrew/opt/node@24/bin:$PATH" pnpm --filter @cat-cafe/web run test:lint-rules +# PASS + +PATH="/opt/homebrew/opt/node@24/bin:$PATH" pnpm --filter @cat-cafe/mcp-server test -- --test-name-pattern "workflow-mandated|cat_cafe_register_scheduled_task" +# 381 pass / 0 fail / 0 cancelled + +PATH="/opt/homebrew/opt/node@24/bin:$PATH" pnpm check +# PASS; existing advisory warnings only + +git diff --check +# PASS +``` + +Known unrelated gate-health issue: + +```bash +PATH="/opt/homebrew/opt/node@24/bin:$PATH" pnpm --filter @cat-cafe/api run test:public +# Reaches real public-test resolver under Node 24, then fails because +# packages/api/config/public-test-exclusions.json has expired "redis" exclusion: +# 2026-07-31 < 2026-08-02 +``` + +That is tracked separately as task `0001785687002134-001212-84b1606d`. + +## Quality-Gate Notes + +- Artifact hygiene: no root media/design artifacts in worktree or branch diff. +- Design check: no `designs/` directory in this checkout. +- Dogfood scope: internal validation/startup guard bugfix; startup status path was manually exercised as the user-visible recovery path. +- Fallback layer script: unavailable in this checkout (`scripts/check-fallback-layers.mjs` missing); diff adds no fallback stack. +- Architecture ownership script: unavailable in this checkout (`check:architecture-ownership` missing). +- Architecture cell: harness-eval +- Map delta: none +- Why: validation/preflight behavior only; no Store, Queue, Router, Adapter, Dispatcher, Binding, or service boundary change. + +## Next Action + +Please re-review `69a2e402af0b96f5e2def45bfc5dedb2ca942257` and the R3 packet, then give APPROVE or REQUEST-CHANGES. If approved, I will continue PR/merge-gate flow. + +[砚砚/gpt-5.5🐾] From d5d174773526c95d291d053e0685564f98158175 Mon Sep 17 00:00:00 2001 From: "MaineCoon-GPT-5.5" Date: Mon, 3 Aug 2026 00:40:05 +0800 Subject: [PATCH 07/15] fix(node): audit verification runtime entrypoints Why: R3 review found verify:sigusr1 still produced a PASS under unsupported Node 22 because validation discovery only covered a narrow name list. Broaden the scoped audit to verify/audit/smoke/*:test style entrypoints and add exact prehooks without restoring global engine-strict, preserving startup auto-reexec. --- package.json | 4 ++++ packages/api/package.json | 2 ++ scripts/node-runtime-guard.test.mjs | 25 ++++++++++++++++--------- 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 894128583c..d91eb63bc9 100644 --- a/package.json +++ b/package.json @@ -24,8 +24,11 @@ "alpha:start": "./scripts/alpha-worktree.sh start", "alpha:status": "./scripts/alpha-worktree.sh status", "f210:agy-profile-smoke": "pnpm --filter @cat-cafe/api run smoke:f210-agy-profiles", + "pref210:agy-profile-smoke": "node scripts/check-validation-node-runtime.mjs", "alpha:test": "bash ./scripts/alpha-worktree.test.sh", + "prealpha:test": "node scripts/check-validation-node-runtime.mjs", "runtime:test": "bash ./scripts/runtime-worktree.test.sh", + "preruntime:test": "node scripts/check-validation-node-runtime.mjs", "develop:init": "./scripts/develop-worktree.sh init", "develop:sync": "./scripts/develop-worktree.sh sync", "develop:start": "./scripts/develop-worktree.sh start", @@ -84,6 +87,7 @@ "check:sop-definitions": "node --test scripts/sop-definitions.test.mjs && node scripts/sop-definitions.mjs --check", "check:followup-tails": "node scripts/check-followup-tails.mjs", "audit:feature-docs": "node scripts/audit-feature-doc-template.mjs", + "preaudit:feature-docs": "node scripts/check-validation-node-runtime.mjs", "check:skills": "bash scripts/check-skills-mount.sh", "check:skills:manifest": "node scripts/check-skills-manifest.mjs", "check:skills:surfaces": "node --test scripts/check-skill-first-party-surfaces.test.mjs && node scripts/check-skill-first-party-surfaces.mjs", diff --git a/packages/api/package.json b/packages/api/package.json index a4e584fd78..31351e7b36 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -7,6 +7,7 @@ "scripts": { "dev": "while true; do NODE_OPTIONS=\"--import $PWD/scripts/sigusr1-guard.mjs${NODE_OPTIONS:+ $NODE_OPTIONS}\" tsx watch --exclude \"dist/**\" --exclude \"../shared/dist/**\" src/index.ts; ec=$?; if [ $ec -eq 0 ] || [ $ec -eq 130 ]; then break; fi; echo \"[api] dev exited ($ec) — auto-restarting in 1s\"; sleep 1; done", "verify:sigusr1": "node scripts/verify-sigusr1-guard.mjs", + "preverify:sigusr1": "node ../../scripts/check-validation-node-runtime.mjs", "prebuild": "node ../../scripts/check-validation-node-runtime.mjs", "build": "pnpm --dir ../shared build && tsc && node ./scripts/copy-marketplace-catalog-data.mjs", "start": "node dist/index.js", @@ -15,6 +16,7 @@ "test:public": "pnpm --dir ../shared build && pnpm --dir ../mcp-server build && pnpm run build && CAT_CAFE_DISABLE_SHARED_STATE_PREFLIGHT=1 bash ./scripts/with-test-home.sh bash ./scripts/run-public-tests.sh", "test:antigravity-smoke": "RUN_ANTIGRAVITY_SMOKE=true pnpm run build && RUN_ANTIGRAVITY_SMOKE=true bash ./scripts/with-test-home.sh node --test test/antigravity-smoke.test.js", "smoke:f210-agy-profiles": "pnpm run build && node dist/scripts/f210-agy-profile-smoke.js", + "presmoke:f210-agy-profiles": "node ../../scripts/check-validation-node-runtime.mjs", "fetch-signals": "node dist/scripts/fetch-signals.js", "migrate-signals": "node dist/scripts/migrate-signals.js", "rebuild-index": "node dist/scripts/rebuild-index.js", diff --git a/scripts/node-runtime-guard.test.mjs b/scripts/node-runtime-guard.test.mjs index 0f52e0c104..4587216766 100644 --- a/scripts/node-runtime-guard.test.mjs +++ b/scripts/node-runtime-guard.test.mjs @@ -81,15 +81,8 @@ function workspacePackageJsonPaths() { function isValidationEntrypoint(scriptName) { if (/^(?:pre|post)/.test(scriptName)) return false; - return ( - scriptName === 'build' || - scriptName === 'test' || - scriptName === 'lint' || - scriptName === 'check' || - scriptName === 'gate' || - scriptName.startsWith('test:') || - scriptName.startsWith('check:') - ); + const validationTokens = new Set(['audit', 'build', 'check', 'gate', 'lint', 'smoke', 'test', 'verify']); + return scriptName.split(':').some((segment) => validationTokens.has(segment) || segment.endsWith('-smoke')); } function validationGuardForPackageJson(relPath) { @@ -371,6 +364,20 @@ test('direct validation scripts fail fast on unsupported Node before running pac ); }); +test('validation entrypoint discovery includes verify, audit, smoke, and suffix test scripts', () => { + assert.equal(isValidationEntrypoint('verify:sigusr1'), true); + assert.equal(isValidationEntrypoint('audit:feature-docs'), true); + assert.equal(isValidationEntrypoint('smoke:f210-agy-profiles'), true); + assert.equal(isValidationEntrypoint('f210:agy-profile-smoke'), true); + assert.equal(isValidationEntrypoint('alpha:test'), true); + assert.equal(isValidationEntrypoint('runtime:test'), true); + + assert.equal(isValidationEntrypoint('start'), false); + assert.equal(isValidationEntrypoint('start:status'), false); + assert.equal(isValidationEntrypoint('start:direct'), false); + assert.equal(isValidationEntrypoint('dev:direct'), false); +}); + test('pnpm engine strict remains off so startup scripts can reach the auto-reexec guard', () => { const pkg = readJson('package.json'); From 08b239781668d9d6c3fa34fe6b508165f641f6da Mon Sep 17 00:00:00 2001 From: "MaineCoon-GPT-5.5" Date: Mon, 3 Aug 2026 00:41:01 +0800 Subject: [PATCH 08/15] docs(review): request node24 runtime guard r4 review Why: Sol R3 found the validation-entrypoint audit still missed verify/audit/smoke/test-style scripts; this packet records the scoped fix, Red-to-Green evidence, and quality-gate results for re-review. --- ...-node24-runtime-guard-r4-review-request.md | 128 ++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 review-notes/2026-08-03-node24-runtime-guard-r4-review-request.md diff --git a/review-notes/2026-08-03-node24-runtime-guard-r4-review-request.md b/review-notes/2026-08-03-node24-runtime-guard-r4-review-request.md new file mode 100644 index 0000000000..9b2fc4e32b --- /dev/null +++ b/review-notes/2026-08-03-node24-runtime-guard-r4-review-request.md @@ -0,0 +1,128 @@ +# Review Request R4: Node 24 Runtime Guard Coverage + +Review-Target-ID: fix-node24-runtime-guard +Branch: fix/node24-runtime-guard +Base: origin/develop +Code Commit: d5d174773526c95d291d053e0685564f98158175 +Worktree: `/Users/xxx/workspace/AI/cat-cafe-node24-runtime-guard` +Reviewer: `@sol` + +## What + +R4 addresses Sol R3 finding against `f2b0d6bf48d4ed5f5d17d005e8daeda4f91909bb`. + +- `isValidationEntrypoint()` now recognizes validation intent by colon-delimited tokens: `audit`, `build`, `check`, `gate`, `lint`, `smoke`, `test`, `verify`, plus suffix `*-smoke`. +- Added exact prehooks for every existing R3 sweep miss: + - `package.json#f210:agy-profile-smoke` + - `package.json#alpha:test` + - `package.json#runtime:test` + - `package.json#audit:feature-docs` + - `packages/api/package.json#verify:sigusr1` + - `packages/api/package.json#smoke:f210-agy-profiles` +- Added an explicit regression test that keeps `verify:sigusr1`, `audit:*`, `smoke:*`, `*:test`, and `*-smoke` in the validation-entrypoint set while keeping startup scripts out. + +## Why + +R3 showed the previous dynamic audit still depended on a narrow name whitelist. `verify:sigusr1` could run under default Node 22 and print a trustworthy-looking `PASS`, which violates the patrol objective: validation output from this checkout must not be produced under unsupported Node. + +## Tradeoff + +This stays with scoped lifecycle hooks. I did not restore global `.npmrc engine-strict=true`, so startup/operational commands retain their Node 24 auto-reexec path. + +## Red -> Green + +Red step: + +```bash +PATH="/opt/homebrew/opt/node@24/bin:$PATH" node --test scripts/node-runtime-guard.test.mjs +# FAIL: validation entrypoints without node runtime guard: +# package.json#alpha:test +# package.json#audit:feature-docs +# package.json#f210:agy-profile-smoke +# package.json#runtime:test +# packages/api/package.json#smoke:f210-agy-profiles +# packages/api/package.json#verify:sigusr1 +``` + +Green step: + +```bash +PATH="/opt/homebrew/opt/node@24/bin:$PATH" node --test scripts/node-runtime-guard.test.mjs +# 16 pass / 0 fail / 0 cancelled +``` + +## Failure-Mode Sweep + +Pattern: validation semantics were inferred from an incomplete exact/prefix list. + +Sweep result: + +- `verify:*`: protected (`verify:sigusr1`) +- `audit:*`: protected (`audit:feature-docs`) +- `smoke:*`: protected (`smoke:f210-agy-profiles`) +- `*-smoke`: protected (`f210:agy-profile-smoke`) +- `*:test`: protected (`alpha:test`, `runtime:test`) +- startup/operational scripts remain excluded (`start`, `start:status`, `start:direct`, `dev:direct`) + +## Validation Evidence + +Default Node 22 fail-fast: + +```bash +pnpm --filter @cat-cafe/api run verify:sigusr1 +# fails in preverify:sigusr1 via check-validation-node-runtime.mjs before printing PASS + +pnpm --filter @cat-cafe/api run smoke:f210-agy-profiles +# fails in presmoke:f210-agy-profiles via check-validation-node-runtime.mjs + +pnpm run f210:agy-profile-smoke +# fails in pref210:agy-profile-smoke via check-validation-node-runtime.mjs + +pnpm run alpha:test +# fails in prealpha:test via check-validation-node-runtime.mjs + +pnpm run runtime:test +# fails in preruntime:test via check-validation-node-runtime.mjs + +pnpm run audit:feature-docs +# fails in preaudit:feature-docs via check-validation-node-runtime.mjs +``` + +Node 24 positive paths and regression gates: + +```bash +PATH="/opt/homebrew/opt/node@24/bin:$PATH" pnpm --filter @cat-cafe/api run verify:sigusr1 +# PASS — guard suppresses SIGUSR1 inspector across the tsx watch tree + +PATH="/opt/homebrew/opt/node@24/bin:$PATH" pnpm run audit:feature-docs +# PASS: docs=256 green=240 yellow=16 red=0 +# generated audit artifacts were cleaned and are not part of this diff + +env -u CAT_CAFE_NODE_RUNTIME_GUARD_REEXEC \ + CAT_CAFE_NODE_BIN=/opt/homebrew/opt/node@24/bin/node \ + pnpm start:status +# reaches start-entry.mjs, re-execs Node 24, exits only because daemon is not running + +PATH="/opt/homebrew/opt/node@24/bin:$PATH" pnpm check +# PASS; existing advisory warnings only + +git diff --check +# PASS +``` + +## Quality-Gate Notes + +- Artifact hygiene: no root media/design artifacts in worktree or branch diff. +- Design check: no `designs/` directory in this checkout. +- Dogfood scope: internal validation/startup guard bugfix; exercised actual validation commands plus startup status recovery. +- Fallback layer script: unavailable in this checkout (`scripts/check-fallback-layers.mjs` missing); diff adds no fallback stack. +- Architecture ownership script: unavailable in this checkout (`check:architecture-ownership` missing). +- Architecture cell: harness-eval +- Map delta: none +- Why: validation/preflight behavior only; no Store, Queue, Router, Adapter, Dispatcher, Binding, or service boundary change. + +## Next Action + +Please re-review `d5d174773526c95d291d053e0685564f98158175` and the R4 packet, then give APPROVE or REQUEST-CHANGES. If approved, I will continue PR/merge-gate flow. + +[砚砚/gpt-5.5🐾] From 43f0d9beb63b8e607e627c580196b316fece0b79 Mon Sep 17 00:00:00 2001 From: "MaineCoon-GPT-5.5" Date: Mon, 3 Aug 2026 01:02:29 +0800 Subject: [PATCH 09/15] fix(test): renew public test exclusion registry Why: merge-gate public test resolution is intentionally fail-closed on expired exclusions; the existing source-only/private/flaky exclusions still match current resolver output, so renew the registry and metadata expectation before PR gate. --- .../api/config/public-test-exclusions.json | 82 +++++++++---------- .../api/test/public-test-exclusions.test.js | 2 +- 2 files changed, 42 insertions(+), 42 deletions(-) diff --git a/packages/api/config/public-test-exclusions.json b/packages/api/config/public-test-exclusions.json index e19a2b5a0c..c1c057ef45 100644 --- a/packages/api/config/public-test-exclusions.json +++ b/packages/api/config/public-test-exclusions.json @@ -8,7 +8,7 @@ "reason": "Redis isolation and persistence tests are internal harness coverage, not part of the public gate.", "owner": "@zts212653", "introducedBy": "78f3bc57c", - "expiresOn": "2026-07-31" + "expiresOn": "2026-09-30" }, { "id": "concurrent-fault-drill", @@ -17,7 +17,7 @@ "reason": "Fault-drill stress coverage is too heavy for the public gate and stays in internal validation.", "owner": "@zts212653", "introducedBy": "78f3bc57c", - "expiresOn": "2026-07-31" + "expiresOn": "2026-09-30" }, { "id": "task-progress-store", @@ -26,7 +26,7 @@ "reason": "Task progress store behavior depends on internal persistence surfaces not exported to the public gate.", "owner": "@zts212653", "introducedBy": "78f3bc57c", - "expiresOn": "2026-07-31" + "expiresOn": "2026-09-30" }, { "id": "session-strategy-phase3", @@ -35,7 +35,7 @@ "reason": "Phase 3 session strategy assertions cover internal rollout behavior outside the public gate contract.", "owner": "@zts212653", "introducedBy": "78f3bc57c", - "expiresOn": "2026-07-31" + "expiresOn": "2026-09-30" }, { "id": "signal-article-store", @@ -44,7 +44,7 @@ "reason": "Signal article store cases exercise source-only data plumbing not guaranteed in the public export.", "owner": "@zts212653", "introducedBy": "78f3bc57c", - "expiresOn": "2026-07-31" + "expiresOn": "2026-09-30" }, { "id": "persistence-fault-drill", @@ -53,7 +53,7 @@ "reason": "Persistence fault drills are heavyweight stress scenarios reserved for internal validation.", "owner": "@zts212653", "introducedBy": "78f3bc57c", - "expiresOn": "2026-07-31" + "expiresOn": "2026-09-30" }, { "id": "cursor-store-atomicity", @@ -62,7 +62,7 @@ "reason": "Atomic cursor store checks cover internal durability mechanics not enforced by the public gate.", "owner": "@zts212653", "introducedBy": "78f3bc57c", - "expiresOn": "2026-07-31" + "expiresOn": "2026-09-30" }, { "id": "workflow-sop-store", @@ -71,7 +71,7 @@ "reason": "Workflow SOP store tests rely on internal governance persistence surfaces excluded from the public gate.", "owner": "@zts212653", "introducedBy": "78f3bc57c", - "expiresOn": "2026-07-31" + "expiresOn": "2026-09-30" }, { "id": "codex-agent-service", @@ -80,7 +80,7 @@ "reason": "Codex agent runtime service tests depend on internal carrier/harness wiring outside the public gate.", "owner": "@zts212653", "introducedBy": "78f3bc57c", - "expiresOn": "2026-07-31" + "expiresOn": "2026-09-30" }, { "id": "kimi-agent-service", @@ -89,7 +89,7 @@ "reason": "Kimi agent service coverage is internal runtime behavior, not public gate contract.", "owner": "@zts212653", "introducedBy": "78f3bc57c", - "expiresOn": "2026-07-31" + "expiresOn": "2026-09-30" }, { "id": "claude-settings-hooks", @@ -98,7 +98,7 @@ "reason": "Claude settings hook assertions depend on private runtime fixtures unavailable in public export.", "owner": "@zts212653", "introducedBy": "78f3bc57c", - "expiresOn": "2026-07-31" + "expiresOn": "2026-09-30" }, { "id": "game-store", @@ -107,7 +107,7 @@ "reason": "Game store tests rely on non-exported local fixtures and are kept out of the public gate.", "owner": "@zts212653", "introducedBy": "78f3bc57c", - "expiresOn": "2026-07-31" + "expiresOn": "2026-09-30" }, { "id": "memory-tests", @@ -116,7 +116,7 @@ "reason": "Memory package tests cover internal recall/index behavior outside the public gate promise.", "owner": "@zts212653", "introducedBy": "78f3bc57c", - "expiresOn": "2026-07-31" + "expiresOn": "2026-09-30" }, { "id": "cross-cat-context", @@ -125,7 +125,7 @@ "reason": "Cross-cat context routing is internal collaboration harness behavior, not public gate surface.", "owner": "@zts212653", "introducedBy": "78f3bc57c", - "expiresOn": "2026-07-31" + "expiresOn": "2026-09-30" }, { "id": "thread-wiring", @@ -134,7 +134,7 @@ "reason": "Thread wiring assertions cover internal callback plumbing outside the public gate contract.", "owner": "@zts212653", "introducedBy": "78f3bc57c", - "expiresOn": "2026-07-31" + "expiresOn": "2026-09-30" }, { "id": "integration-wiring", @@ -143,7 +143,7 @@ "reason": "Integration wiring checks depend on internal connection topology not exported to public gate.", "owner": "@zts212653", "introducedBy": "78f3bc57c", - "expiresOn": "2026-07-31" + "expiresOn": "2026-09-30" }, { "id": "shared-state-wiring", @@ -152,7 +152,7 @@ "reason": "Shared-state wiring is internal runtime glue not part of the public gate guarantee.", "owner": "@zts212653", "introducedBy": "78f3bc57c", - "expiresOn": "2026-07-31" + "expiresOn": "2026-09-30" }, { "id": "signal-fetcher-launchd", @@ -161,7 +161,7 @@ "reason": "launchd-specific signal fetcher coverage depends on private macOS fixtures and stays internal.", "owner": "@zts212653", "introducedBy": "78f3bc57c", - "expiresOn": "2026-07-31" + "expiresOn": "2026-09-30" }, { "id": "reflection-capsule-m3", @@ -170,7 +170,7 @@ "reason": "Reflection capsule M3 tests cover internal memory/harness behavior outside the public gate.", "owner": "@zts212653", "introducedBy": "78f3bc57c", - "expiresOn": "2026-07-31" + "expiresOn": "2026-09-30" }, { "id": "workspace-project-context", @@ -179,7 +179,7 @@ "reason": "Workspace project context assertions depend on source-only repo structure and local project wiring.", "owner": "@zts212653", "introducedBy": "78f3bc57c", - "expiresOn": "2026-07-31" + "expiresOn": "2026-09-30" }, { "id": "projects-setup", @@ -188,7 +188,7 @@ "reason": "Project setup tests cover internal bootstrap flows not guaranteed in the public gate.", "owner": "@zts212653", "introducedBy": "78f3bc57c", - "expiresOn": "2026-07-31" + "expiresOn": "2026-09-30" }, { "id": "projects-mkdir", @@ -197,7 +197,7 @@ "reason": "Project directory creation coverage depends on source-only filesystem conventions.", "owner": "@zts212653", "introducedBy": "78f3bc57c", - "expiresOn": "2026-07-31" + "expiresOn": "2026-09-30" }, { "id": "governance-status", @@ -206,7 +206,7 @@ "reason": "Governance status assertions are source-owned workflow coverage, not public gate behavior.", "owner": "@zts212653", "introducedBy": "78f3bc57c", - "expiresOn": "2026-07-31" + "expiresOn": "2026-09-30" }, { "id": "governance-pack", @@ -215,7 +215,7 @@ "reason": "Governance pack assertions validate source-only sync content and are intentionally excluded from public gate.", "owner": "@zts212653", "introducedBy": "069d0f0fb", - "expiresOn": "2026-07-31" + "expiresOn": "2026-09-30" }, { "id": "pack-integration", @@ -224,7 +224,7 @@ "reason": "Pack integration coverage exercises internal source packaging rules outside the public gate.", "owner": "@zts212653", "introducedBy": "78f3bc57c", - "expiresOn": "2026-07-31" + "expiresOn": "2026-09-30" }, { "id": "project-setup-flow", @@ -233,7 +233,7 @@ "reason": "Project setup flow behavior is internal bootstrap coverage, not public gate contract.", "owner": "@zts212653", "introducedBy": "78f3bc57c", - "expiresOn": "2026-07-31" + "expiresOn": "2026-09-30" }, { "id": "process-liveness-probe", @@ -242,7 +242,7 @@ "reason": "Process liveness probe timing is too contention-sensitive for the public gate.", "owner": "@zts212653", "introducedBy": "78f3bc57c", - "expiresOn": "2026-07-31" + "expiresOn": "2026-09-30" }, { "id": "expedition-bootstrap", @@ -251,7 +251,7 @@ "reason": "Expedition bootstrap coverage depends on source-owned harness flows not exported publicly.", "owner": "@zts212653", "introducedBy": "78f3bc57c", - "expiresOn": "2026-07-31" + "expiresOn": "2026-09-30" }, { "id": "rules-route", @@ -260,7 +260,7 @@ "reason": "Rules route assertions depend on source-only rule artifacts stripped from public export.", "owner": "@zts212653", "introducedBy": "4243948da", - "expiresOn": "2026-07-31" + "expiresOn": "2026-09-30" }, { "id": "root-md-slim", @@ -269,7 +269,7 @@ "reason": "Root markdown slim tests rely on source-specific Chinese anchors not preserved in public export.", "owner": "@zts212653", "introducedBy": "7a300704a", - "expiresOn": "2026-07-31" + "expiresOn": "2026-09-30" }, { "id": "audit-cc-system-prompt", @@ -278,7 +278,7 @@ "reason": "System prompt audit depends on internal L0 prompt assets not shipped in public export.", "owner": "@zts212653", "introducedBy": "e9bb56052", - "expiresOn": "2026-07-31" + "expiresOn": "2026-09-30" }, { "id": "f188-cold-start-fixtures", @@ -287,7 +287,7 @@ "reason": "F188 cold-start fixture coverage depends on internal fixture files absent from public export.", "owner": "@zts212653", "introducedBy": "e9bb56052", - "expiresOn": "2026-07-31" + "expiresOn": "2026-09-30" }, { "id": "f188-harness-consistency", @@ -296,7 +296,7 @@ "reason": "F188 harness consistency checks rely on internal fixtures and remain source-only.", "owner": "@zts212653", "introducedBy": "e9bb56052", - "expiresOn": "2026-07-31" + "expiresOn": "2026-09-30" }, { "id": "orphan-chrome-cleaner", @@ -305,7 +305,7 @@ "reason": "Orphan Chrome cleaner assertions are sensitive to local path sanitization and private fixtures.", "owner": "@zts212653", "introducedBy": "e9bb56052", - "expiresOn": "2026-07-31" + "expiresOn": "2026-09-30" }, { "id": "capabilities-route", @@ -314,7 +314,7 @@ "reason": "Managed MCP path realignment currently fails in public gate and must be tracked as a real product regression.", "owner": "@zts212653", "introducedBy": "e9bb56052", - "expiresOn": "2026-07-31" + "expiresOn": "2026-09-30" }, { "id": "antigravity-run-command-executor", @@ -323,7 +323,7 @@ "reason": "Antigravity run-command executor timing is unstable on CI and remains out of the public gate until hardened.", "owner": "@zts212653", "introducedBy": "0340783c6", - "expiresOn": "2026-07-31" + "expiresOn": "2026-09-30" }, { "id": "f203-phase-i-opencode-l0", @@ -332,7 +332,7 @@ "reason": "F203 opencode L0 coverage depends on internal runtime files not exported publicly.", "owner": "@zts212653", "introducedBy": "9c4f26fde", - "expiresOn": "2026-07-31" + "expiresOn": "2026-09-30" }, { "id": "f236-cc-anchor-hook", @@ -341,7 +341,7 @@ "reason": "F236 cc anchor hook coverage imports the source-only root .claude hook implementation that is not part of the public export.", "owner": "@zts212653", "introducedBy": "68dd499d9", - "expiresOn": "2026-07-31" + "expiresOn": "2026-09-30" }, { "id": "github-schedule-factories", @@ -350,7 +350,7 @@ "reason": "GitHub schedule factory assertions are source-owned public-sync governance coverage, not runtime gate behavior.", "owner": "@zts212653", "introducedBy": "bd8823b99", - "expiresOn": "2026-07-31" + "expiresOn": "2026-09-30" }, { "id": "harness-eval-hub-read-model", @@ -359,7 +359,7 @@ "reason": "Eval hub read-model coverage exercises source-owned harness governance surfaces outside the public gate.", "owner": "@zts212653", "introducedBy": "bd8823b99", - "expiresOn": "2026-07-31" + "expiresOn": "2026-09-30" }, { "id": "harness-eval-merge-gate-provenance-contract", @@ -368,7 +368,7 @@ "reason": "Merge-gate provenance contract tests are source-owned governance coverage, not public runtime behavior.", "owner": "@zts212653", "introducedBy": "bd8823b99", - "expiresOn": "2026-07-31" + "expiresOn": "2026-09-30" } ] } diff --git a/packages/api/test/public-test-exclusions.test.js b/packages/api/test/public-test-exclusions.test.js index df55e171e8..9e42bff7ed 100644 --- a/packages/api/test/public-test-exclusions.test.js +++ b/packages/api/test/public-test-exclusions.test.js @@ -98,7 +98,7 @@ test('registry preserves metadata for active legacy exclusions and drops stale o category: 'source_only', owner: '@zts212653', introducedBy: '069d0f0fb', - expiresOn: '2026-07-31', + expiresOn: '2026-09-30', }, ); }); From 8e506c93def3bb867b8c9dc12d7017acb01fd7e7 Mon Sep 17 00:00:00 2001 From: "MaineCoon-GPT-5.5" Date: Mon, 3 Aug 2026 01:31:47 +0800 Subject: [PATCH 10/15] fix(node): guard prepare validation scripts Why: prepare is a standalone npm lifecycle script that runs tsc in shared and finance; treating every pre* name as a hook let Node 22 run validation work after only an engine warning. --- packages/finance/package.json | 1 + packages/shared/package.json | 1 + scripts/node-runtime-guard.test.mjs | 15 +++++++++++++-- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/packages/finance/package.json b/packages/finance/package.json index 00a42edc58..bdf9d0fd61 100644 --- a/packages/finance/package.json +++ b/packages/finance/package.json @@ -13,6 +13,7 @@ }, "scripts": { "build": "tsc", + "preprepare": "node ../../scripts/check-validation-node-runtime.mjs", "prepare": "tsc", "lint": "tsc --noEmit", "test": "pnpm run build && node --test test/*.test.js", diff --git a/packages/shared/package.json b/packages/shared/package.json index 04d01c8565..704ec0423e 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -34,6 +34,7 @@ "scripts": { "prebuild": "node ../../scripts/check-validation-node-runtime.mjs", "build": "tsc", + "preprepare": "node ../../scripts/check-validation-node-runtime.mjs", "prepare": "tsc", "dev": "tsc --watch", "clean": "rm -rf dist", diff --git a/scripts/node-runtime-guard.test.mjs b/scripts/node-runtime-guard.test.mjs index 4587216766..e492ed81bf 100644 --- a/scripts/node-runtime-guard.test.mjs +++ b/scripts/node-runtime-guard.test.mjs @@ -80,8 +80,8 @@ function workspacePackageJsonPaths() { } function isValidationEntrypoint(scriptName) { - if (/^(?:pre|post)/.test(scriptName)) return false; - const validationTokens = new Set(['audit', 'build', 'check', 'gate', 'lint', 'smoke', 'test', 'verify']); + if (scriptName !== 'prepare' && /^(?:pre|post)/.test(scriptName)) return false; + const validationTokens = new Set(['audit', 'build', 'check', 'gate', 'lint', 'prepare', 'smoke', 'test', 'verify']); return scriptName.split(':').some((segment) => validationTokens.has(segment) || segment.endsWith('-smoke')); } @@ -349,6 +349,14 @@ test('direct validation scripts fail fast on unsupported Node before running pac entries.includes('packages/finance/package.json#lint'), 'validation entrypoint audit must discover finance lint', ); + assert.ok( + entries.includes('packages/finance/package.json#prepare'), + 'validation entrypoint audit must discover finance prepare', + ); + assert.ok( + entries.includes('packages/shared/package.json#prepare'), + 'validation entrypoint audit must discover shared prepare', + ); assert.ok( entries.includes('packages/api/package.json#test:public'), 'validation entrypoint audit must discover API test:public', @@ -365,6 +373,7 @@ test('direct validation scripts fail fast on unsupported Node before running pac }); test('validation entrypoint discovery includes verify, audit, smoke, and suffix test scripts', () => { + assert.equal(isValidationEntrypoint('prepare'), true); assert.equal(isValidationEntrypoint('verify:sigusr1'), true); assert.equal(isValidationEntrypoint('audit:feature-docs'), true); assert.equal(isValidationEntrypoint('smoke:f210-agy-profiles'), true); @@ -372,6 +381,8 @@ test('validation entrypoint discovery includes verify, audit, smoke, and suffix assert.equal(isValidationEntrypoint('alpha:test'), true); assert.equal(isValidationEntrypoint('runtime:test'), true); + assert.equal(isValidationEntrypoint('preprepare'), false); + assert.equal(isValidationEntrypoint('prebuild'), false); assert.equal(isValidationEntrypoint('start'), false); assert.equal(isValidationEntrypoint('start:status'), false); assert.equal(isValidationEntrypoint('start:direct'), false); From 21b85467394f5982fd7fef9af4fdcc0711f7b460 Mon Sep 17 00:00:00 2001 From: "MaineCoon-GPT-5.5" Date: Mon, 3 Aug 2026 01:53:28 +0800 Subject: [PATCH 11/15] fix(node): guard mcp doctor validation Why: mcp:doctor validates MCP capability declarations and can otherwise run under unsupported Node after only a pnpm engine warning; keep process:doctor outside validation because it is an operational cleanup diagnostic. --- package.json | 1 + scripts/node-runtime-guard.test.mjs | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/package.json b/package.json index d91eb63bc9..82fa4e1689 100644 --- a/package.json +++ b/package.json @@ -93,6 +93,7 @@ "check:skills:surfaces": "node --test scripts/check-skill-first-party-surfaces.test.mjs && node scripts/check-skill-first-party-surfaces.mjs", "check:pre-merge-gate": "node --test scripts/pre-merge-check.test.mjs scripts/pre-merge-gate-guard.test.mjs scripts/test-bash-runtime.test.mjs", "check:hotfix-pattern": "node --test scripts/check-hotfix-pattern.test.mjs", + "premcp:doctor": "node scripts/check-validation-node-runtime.mjs", "mcp:doctor": "node scripts/mcp-doctor.mjs", "convention-graph": "pnpm --filter @cat-cafe/convention-graph convention-graph", "convention-graph:index": "pnpm --filter @cat-cafe/convention-graph graph:index", diff --git a/scripts/node-runtime-guard.test.mjs b/scripts/node-runtime-guard.test.mjs index e492ed81bf..78c8d5ae94 100644 --- a/scripts/node-runtime-guard.test.mjs +++ b/scripts/node-runtime-guard.test.mjs @@ -80,6 +80,8 @@ function workspacePackageJsonPaths() { } function isValidationEntrypoint(scriptName) { + const validationEntrypointNames = new Set(['mcp:doctor']); + if (validationEntrypointNames.has(scriptName)) return true; if (scriptName !== 'prepare' && /^(?:pre|post)/.test(scriptName)) return false; const validationTokens = new Set(['audit', 'build', 'check', 'gate', 'lint', 'prepare', 'smoke', 'test', 'verify']); return scriptName.split(':').some((segment) => validationTokens.has(segment) || segment.endsWith('-smoke')); @@ -357,6 +359,7 @@ test('direct validation scripts fail fast on unsupported Node before running pac entries.includes('packages/shared/package.json#prepare'), 'validation entrypoint audit must discover shared prepare', ); + assert.ok(entries.includes('package.json#mcp:doctor'), 'validation entrypoint audit must discover MCP doctor'); assert.ok( entries.includes('packages/api/package.json#test:public'), 'validation entrypoint audit must discover API test:public', @@ -374,6 +377,7 @@ test('direct validation scripts fail fast on unsupported Node before running pac test('validation entrypoint discovery includes verify, audit, smoke, and suffix test scripts', () => { assert.equal(isValidationEntrypoint('prepare'), true); + assert.equal(isValidationEntrypoint('mcp:doctor'), true); assert.equal(isValidationEntrypoint('verify:sigusr1'), true); assert.equal(isValidationEntrypoint('audit:feature-docs'), true); assert.equal(isValidationEntrypoint('smoke:f210-agy-profiles'), true); @@ -383,6 +387,7 @@ test('validation entrypoint discovery includes verify, audit, smoke, and suffix assert.equal(isValidationEntrypoint('preprepare'), false); assert.equal(isValidationEntrypoint('prebuild'), false); + assert.equal(isValidationEntrypoint('process:doctor'), false); assert.equal(isValidationEntrypoint('start'), false); assert.equal(isValidationEntrypoint('start:status'), false); assert.equal(isValidationEntrypoint('start:direct'), false); From 7aa77362f429516e1e1f4abd527b77e30257383f Mon Sep 17 00:00:00 2001 From: "MaineCoon-GPT-5.5" Date: Mon, 3 Aug 2026 02:26:44 +0800 Subject: [PATCH 12/15] fix(node): register runtime guard env vars Why: cloud review found CAT_CAFE_SKIP_PRODUCTION_INSTALL_GUARD was introduced in runtime scripts without env-registry coverage, so operators could not discover the install-only bypass and the completeness gate missed scripts/*.mjs references. Red: node --test scripts/check-env-registry.test.mjs failed after adding runtime guard scripts to the env scan, reporting the unregistered Cat Cafe runtime guard env vars. Green: registered the runtime guard env vars as non-runtime-editable server config and kept npm/test-only values allowlisted with reasons. --- packages/api/src/config/env-registry.ts | 32 ++++++++++ scripts/check-env-registry.test.mjs | 82 ++++++++++++++----------- 2 files changed, 79 insertions(+), 35 deletions(-) diff --git a/packages/api/src/config/env-registry.ts b/packages/api/src/config/env-registry.ts index 6098fb19ce..ca7a782680 100644 --- a/packages/api/src/config/env-registry.ts +++ b/packages/api/src/config/env-registry.ts @@ -198,6 +198,38 @@ export const ENV_VARS: EnvDefinition[] = [ sensitive: false, runtimeEditable: false, }, + { + name: 'CAT_CAFE_NODE_MIN_MAJOR', + defaultValue: '24', + description: 'Node runtime guard 最低允许主版本(安装/验证脚本启动时读取)', + category: 'server', + sensitive: false, + runtimeEditable: false, + }, + { + name: 'CAT_CAFE_NODE_MAX_MAJOR_EXCLUSIVE', + defaultValue: '26', + description: 'Node runtime guard 排他的最高主版本边界(安装/验证脚本启动时读取)', + category: 'server', + sensitive: false, + runtimeEditable: false, + }, + { + name: 'CAT_CAFE_SKIP_NODE_RUNTIME_GUARD', + defaultValue: '0', + description: '跳过 Node runtime guard 的安装期应急开关;仅在明确知道风险时使用', + category: 'server', + sensitive: false, + runtimeEditable: false, + }, + { + name: 'CAT_CAFE_SKIP_PRODUCTION_INSTALL_GUARD', + defaultValue: '0', + description: '仅跳过 production install guard,保留 Node 版本检查;供验证脚本绕过安装模式检查使用', + category: 'server', + sensitive: false, + runtimeEditable: false, + }, { name: 'CAT_CAFE_INVOCATION_REGISTRY', defaultValue: '(自动:有 Redis 用 redis,否则 memory)', diff --git a/scripts/check-env-registry.test.mjs b/scripts/check-env-registry.test.mjs index 7c7c018323..3d605ac9be 100644 --- a/scripts/check-env-registry.test.mjs +++ b/scripts/check-env-registry.test.mjs @@ -1,8 +1,8 @@ /** * check:env-registry — CI gate for env var registration completeness. * - * Scans `packages/api/src` and `packages/mcp-server/src` for `process.env.XXX` - * references and verifies each is either: + * Scans `packages/api/src`, `packages/mcp-server/src`, and runtime guard scripts + * for `process.env.XXX` references and verifies each is either: * 1. Registered in `env-registry.ts` ENV_VARS array, OR * 2. Listed in the ALLOWLIST below (with a reason). * @@ -39,8 +39,11 @@ const ALLOWLIST = new Map([ ['all_proxy', 'Standard proxy convention (lowercase variant of ALL_PROXY)'], ['npm_execpath', 'Package-manager metadata injected by npm/pnpm; not user-configurable'], ['npm_config_user_agent', 'Package-manager metadata injected by npm/pnpm; not user-configurable'], + ['npm_config_production', 'Package-manager production-mode flag read by install/runtime guards'], + ['NPM_CONFIG_PRODUCTION', 'Package-manager production-mode flag read by install/runtime guards'], ['INIT_CWD', 'Package-manager metadata injected by npm/pnpm; original invocation directory'], ['COGVIDEO_API_KEY', 'F139 MediaHub CogVideoX provider — mcp-server-local credential'], + ['CAT_CAFE_TEST_NODE_VERSION', 'Test-only override for scripts/check-node-runtime.mjs'], // F240: Per-connector env vars migrated to YAML manifests (connector.yaml / plugin.yaml). // Runtime still reads process.env as fallback in resolveConnectorEnv() chain, but // documentation/display is now driven by the YAML config.fields declarations. @@ -95,10 +98,13 @@ function collectTsFiles(dir) { return results; } +const EXTRA_ENV_REF_FILES = ['scripts/check-node-runtime.mjs', 'scripts/check-validation-node-runtime.mjs']; + // ── Extract process.env references from source files ── -function extractEnvRefs(dirs) { +function extractEnvRefs(dirs, extraFiles = []) { /** @type {Map} varName → [file:line, ...] */ const refs = new Map(); + const files = []; for (const dir of dirs) { const absDir = join(ROOT, dir); @@ -107,38 +113,44 @@ function extractEnvRefs(dirs) { } catch { continue; } - for (const file of collectTsFiles(absDir)) { - const lines = readFileSync(file, 'utf-8').split('\n'); - let inBlockComment = false; - for (let i = 0; i < lines.length; i++) { - const line = lines[i]; - const trimmed = line.trimStart(); - // Track multi-line block comments - if (inBlockComment) { - if (line.includes('*/')) { - inBlockComment = false; - } - continue; - } - // Single-line block comment: /** ... */ or /* ... */ on one line - if (trimmed.startsWith('/*') && line.includes('*/')) continue; - // Start of multi-line block comment (no closing on same line) - if (trimmed.startsWith('/*')) { - inBlockComment = true; - continue; - } - // Skip pure line comments - if (trimmed.startsWith('//')) continue; - // Strip inline comments before matching (trailing // and inline /* */) - const code = line.replace(/\/\/.*$/, '').replace(/\/\*.*?\*\//g, ''); - // Match process.env.VAR_NAME and process.env['VAR_NAME'] - const dotMatches = code.matchAll(/process\.env\.([A-Za-z_][A-Za-z0-9_]*)/g); - const bracketMatches = code.matchAll(/process\.env\[['"]([A-Za-z_][A-Za-z0-9_]*)['"]\]/g); - for (const m of [...dotMatches, ...bracketMatches]) { - const name = m[1]; - if (!refs.has(name)) refs.set(name, []); - refs.get(name).push(`${file.replace(ROOT + '/', '')}:${i + 1}`); + files.push(...collectTsFiles(absDir)); + } + + for (const file of extraFiles) { + files.push(join(ROOT, file)); + } + + for (const file of files) { + const lines = readFileSync(file, 'utf-8').split('\n'); + let inBlockComment = false; + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + const trimmed = line.trimStart(); + // Track multi-line block comments + if (inBlockComment) { + if (line.includes('*/')) { + inBlockComment = false; } + continue; + } + // Single-line block comment: /** ... */ or /* ... */ on one line + if (trimmed.startsWith('/*') && line.includes('*/')) continue; + // Start of multi-line block comment (no closing on same line) + if (trimmed.startsWith('/*')) { + inBlockComment = true; + continue; + } + // Skip pure line comments + if (trimmed.startsWith('//')) continue; + // Strip inline comments before matching (trailing // and inline /* */) + const code = line.replace(/\/\/.*$/, '').replace(/\/\*.*?\*\//g, ''); + // Match process.env.VAR_NAME and process.env['VAR_NAME'] + const dotMatches = code.matchAll(/process\.env\.([A-Za-z_][A-Za-z0-9_]*)/g); + const bracketMatches = code.matchAll(/process\.env\[['"]([A-Za-z_][A-Za-z0-9_]*)['"]\]/g); + for (const m of [...dotMatches, ...bracketMatches]) { + const name = m[1]; + if (!refs.has(name)) refs.set(name, []); + refs.get(name).push(`${file.replace(ROOT + '/', '')}:${i + 1}`); } } } @@ -149,7 +161,7 @@ function extractEnvRefs(dirs) { // ── Tests ── describe('env-registry completeness', () => { const registeredNames = loadRegisteredNames(); - const envRefs = extractEnvRefs(['packages/api/src', 'packages/mcp-server/src']); + const envRefs = extractEnvRefs(['packages/api/src', 'packages/mcp-server/src'], EXTRA_ENV_REF_FILES); const repoInboxEnvNames = ['GITHUB_WEBHOOK_SECRET', 'GITHUB_REPO_ALLOWLIST', 'GITHUB_REPO_INBOX_CAT_ID']; const githubSelfFilterEnvNames = ['GITHUB_SELF_LOGIN']; const weixinRuntimeFlagNames = [ From 8e570abbc4ad883be6c84108d25132c33617c3b3 Mon Sep 17 00:00:00 2001 From: "MaineCoon-GPT-5.5" Date: Sun, 9 Aug 2026 00:12:54 +0800 Subject: [PATCH 13/15] fix(node): cover shell runtime guard env vars MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Why: the env-registry completeness gate only scanned JS runtime guard scripts, leaving the shell startup guard variables undiscoverable even though CAT_CAFE_NODE_BIN is the documented recovery knob for Node runtime drift. Red: node --test scripts/check-env-registry.test.mjs failed after adding scripts/lib/node-runtime-guard.sh to the env scan, reporting four missing CAT_CAFE_NODE_* vars. Green: registered the user-configurable shell guard vars and allowlisted the internal reexec sentinel; env-registry and node-runtime guard tests pass. [砚砚/gpt-5.5🐾] --- packages/api/src/config/env-registry.ts | 24 ++++++++++++++++++++++++ scripts/check-env-registry.test.mjs | 17 +++++++++++++---- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/packages/api/src/config/env-registry.ts b/packages/api/src/config/env-registry.ts index ca7a782680..24b24e57a2 100644 --- a/packages/api/src/config/env-registry.ts +++ b/packages/api/src/config/env-registry.ts @@ -214,6 +214,30 @@ export const ENV_VARS: EnvDefinition[] = [ sensitive: false, runtimeEditable: false, }, + { + name: 'CAT_CAFE_NODE_PINNED_MAJOR', + defaultValue: '24', + description: '启动脚本优先 re-exec 到的 Node 主版本;用于避免 Homebrew/node alias 自动漂移', + category: 'server', + sensitive: false, + runtimeEditable: false, + }, + { + name: 'CAT_CAFE_NODE_PREFERRED_MAJORS', + defaultValue: '24 25', + description: '启动脚本搜索本机 Node runtime 的主版本候选顺序(空格分隔)', + category: 'server', + sensitive: false, + runtimeEditable: false, + }, + { + name: 'CAT_CAFE_NODE_BIN', + defaultValue: '(未设置)', + description: '启动脚本优先使用的 Node 可执行文件绝对路径;用于多 Node 环境或手动指定受支持 runtime', + category: 'server', + sensitive: false, + runtimeEditable: false, + }, { name: 'CAT_CAFE_SKIP_NODE_RUNTIME_GUARD', defaultValue: '0', diff --git a/scripts/check-env-registry.test.mjs b/scripts/check-env-registry.test.mjs index 3d605ac9be..863209b870 100644 --- a/scripts/check-env-registry.test.mjs +++ b/scripts/check-env-registry.test.mjs @@ -2,7 +2,7 @@ * check:env-registry — CI gate for env var registration completeness. * * Scans `packages/api/src`, `packages/mcp-server/src`, and runtime guard scripts - * for `process.env.XXX` references and verifies each is either: + * for `process.env.XXX` / shell `$ENV_VAR` references and verifies each is either: * 1. Registered in `env-registry.ts` ENV_VARS array, OR * 2. Listed in the ALLOWLIST below (with a reason). * @@ -44,6 +44,7 @@ const ALLOWLIST = new Map([ ['INIT_CWD', 'Package-manager metadata injected by npm/pnpm; original invocation directory'], ['COGVIDEO_API_KEY', 'F139 MediaHub CogVideoX provider — mcp-server-local credential'], ['CAT_CAFE_TEST_NODE_VERSION', 'Test-only override for scripts/check-node-runtime.mjs'], + ['CAT_CAFE_NODE_RUNTIME_GUARD_REEXEC', 'Internal recursion guard exported by scripts/lib/node-runtime-guard.sh'], // F240: Per-connector env vars migrated to YAML manifests (connector.yaml / plugin.yaml). // Runtime still reads process.env as fallback in resolveConnectorEnv() chain, but // documentation/display is now driven by the YAML config.fields declarations. @@ -98,7 +99,11 @@ function collectTsFiles(dir) { return results; } -const EXTRA_ENV_REF_FILES = ['scripts/check-node-runtime.mjs', 'scripts/check-validation-node-runtime.mjs']; +const EXTRA_ENV_REF_FILES = [ + 'scripts/check-node-runtime.mjs', + 'scripts/check-validation-node-runtime.mjs', + 'scripts/lib/node-runtime-guard.sh', +]; // ── Extract process.env references from source files ── function extractEnvRefs(dirs, extraFiles = []) { @@ -147,8 +152,12 @@ function extractEnvRefs(dirs, extraFiles = []) { // Match process.env.VAR_NAME and process.env['VAR_NAME'] const dotMatches = code.matchAll(/process\.env\.([A-Za-z_][A-Za-z0-9_]*)/g); const bracketMatches = code.matchAll(/process\.env\[['"]([A-Za-z_][A-Za-z0-9_]*)['"]\]/g); - for (const m of [...dotMatches, ...bracketMatches]) { - const name = m[1]; + // Match shell ${ENV_VAR:-default} and $ENV_VAR references in guard shell scripts. + const shellMatches = file.endsWith('.sh') + ? code.matchAll(/\$\{([A-Z_][A-Z0-9_]*)[^}]*\}|\$([A-Z_][A-Z0-9_]*)\b/g) + : []; + for (const m of [...dotMatches, ...bracketMatches, ...shellMatches]) { + const name = m[1] ?? m[2]; if (!refs.has(name)) refs.set(name, []); refs.get(name).push(`${file.replace(ROOT + '/', '')}:${i + 1}`); } From fca09e43d4a4eba377ec5a5781770a773ab5126f Mon Sep 17 00:00:00 2001 From: "MaineCoon-GPT-5.5" Date: Sun, 9 Aug 2026 00:13:40 +0800 Subject: [PATCH 14/15] docs(review): request node24 runtime guard r5 review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Why: the patrol found R4's review packet was stale after the shell runtime guard env registry closure, so the reviewer needs a packet that points at the new code commit and evidence. Includes: finding, red-green evidence, quality-gate notes, and review focus for the shell env scanner scope. [砚砚/gpt-5.5🐾] --- ...-node24-runtime-guard-r5-review-request.md | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 review-notes/2026-08-09-node24-runtime-guard-r5-review-request.md diff --git a/review-notes/2026-08-09-node24-runtime-guard-r5-review-request.md b/review-notes/2026-08-09-node24-runtime-guard-r5-review-request.md new file mode 100644 index 0000000000..4bc63c8466 --- /dev/null +++ b/review-notes/2026-08-09-node24-runtime-guard-r5-review-request.md @@ -0,0 +1,79 @@ +# Review Request R5: Node 24 Runtime Guard Env Registry Closure + +Review-Target-ID: fix-node24-runtime-guard +Branch: fix/node24-runtime-guard +Base: origin/develop +Code Commit: 8e570abbc4ad883be6c84108d25132c33617c3b3 +Worktree: `/Users/xxx/workspace/AI/cat-cafe-node24-runtime-guard` +Reviewer: `@sol` + +## What + +Daily patrol found that R4's env-registry closure only scanned JS runtime guard scripts. The actual startup re-exec guard in `scripts/lib/node-runtime-guard.sh` still had undiscovered `CAT_CAFE_NODE_*` configuration, including the documented `CAT_CAFE_NODE_BIN` recovery knob. + +R5 closes that gap: + +- `scripts/check-env-registry.test.mjs` now scans `scripts/lib/node-runtime-guard.sh` for shell `$ENV_VAR` / `${ENV_VAR:-default}` references. +- `CAT_CAFE_NODE_PINNED_MAJOR`, `CAT_CAFE_NODE_PREFERRED_MAJORS`, and `CAT_CAFE_NODE_BIN` are registered in `packages/api/src/config/env-registry.ts`. +- Internal sentinel `CAT_CAFE_NODE_RUNTIME_GUARD_REEXEC` is allowlisted with an explicit reason instead of being surfaced as user config. + +## Why + +The runtime guard tells operators to set `CAT_CAFE_NODE_BIN=/absolute/path/to/node`, but the registry did not expose that variable and the completeness test could not catch shell guard drift. That made the previous "register runtime guard env vars" fix incomplete. + +## Tradeoff + +This keeps scanning scoped to the Node runtime guard shell script instead of broadly parsing every shell script in the repo. Broad shell parsing is useful later, but not necessary for this bug and likely to produce noisy false positives. + +## Red -> Green + +Red step: + +```bash +node --test scripts/check-env-registry.test.mjs +# FAIL: 4 env var(s) used in code but not registered in env-registry.ts: +# CAT_CAFE_NODE_PINNED_MAJOR +# CAT_CAFE_NODE_PREFERRED_MAJORS +# CAT_CAFE_NODE_BIN +# CAT_CAFE_NODE_RUNTIME_GUARD_REEXEC +``` + +Green steps: + +```bash +node --test scripts/check-env-registry.test.mjs +# pass 6 / fail 0 + +node --test scripts/node-runtime-guard.test.mjs +# pass 16 / fail 0 + +git diff --check +# PASS + +pnpm biome check packages/api/src/config/env-registry.ts scripts/check-env-registry.test.mjs --diagnostic-level=error +# Checked 2 files. No fixes applied. +# Note: pnpm emitted the expected Node engine warning because this shell is Node v22.22.3. +``` + +## Quality Gate + +Spec / intent source: R4 review packet + patrol finding against current `scripts/lib/node-runtime-guard.sh`. + +- Vision coverage: operator/cat-visible startup recovery knobs must be discoverable; `CAT_CAFE_NODE_BIN` is now registered. +- Design check: no UI/design changes; no `.pen` applicable. +- Dogfood: internal guard bugfix; exercised the actual registry gate and runtime guard tests. +- Artifact hygiene: no root media/design artifacts added. +- Architecture ownership: no cell delta; env registry + test gate only. +- Fallback layer check: no fallback stack added. + +## Review Focus + +- Confirm shell env extraction is appropriately scoped and not too broad. +- Confirm `CAT_CAFE_NODE_RUNTIME_GUARD_REEXEC` belongs in allowlist, while `CAT_CAFE_NODE_BIN` / pinned / preferred majors belong in registry. +- Confirm R5 supersedes stale R4 `Code Commit: d5d1747...`; current code target is `8e570abbc4ad883be6c84108d25132c33617c3b3`. + +## Next Action + +Please review `8e570abbc4ad883be6c84108d25132c33617c3b3` and this R5 packet. If approved, I will continue PR/merge-gate flow. + +[砚砚/gpt-5.5🐾] From 17f22adeb65189c3408788e487a75c8516d8219a Mon Sep 17 00:00:00 2001 From: "MaineCoon-GPT-5.5" Date: Mon, 10 Aug 2026 00:02:33 +0800 Subject: [PATCH 15/15] test(node): assert runtime guard env scan invariants MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Why: Sol R5 review found the env-registry gate only checked extracted env refs as a subset, so deleting shell guard scanning or misclassifying runtime guard variables could still pass. Red evidence: temporary mutation runs show removing scripts/lib/node-runtime-guard.sh scanning, allowlisting CAT_CAFE_NODE_BIN, or registering CAT_CAFE_NODE_RUNTIME_GUARD_REEXEC now fails the new assertions. Green: node --test scripts/check-env-registry.test.mjs, node --test scripts/node-runtime-guard.test.mjs, and PATH=/opt/homebrew/opt/node@24/bin:/Users/xxx/.local/bin:/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin:/opt/pmk/env/global/bin:/Users/xxx/.local/bin:/opt/homebrew/Caskroom/codex/0.144.1/codex-path:/Users/xxx/.codex/tmp/arg0/codex-arg0SQPiXV:/Users/xxx/workspace/AI/cat-cafe-develop/packages/api/node_modules/.bin:/Users/xxx/Library/pnpm/.tools/pnpm/9.15.4_tmp_20256/node_modules/pnpm/dist/node-gyp-bin:/Users/xxx/workspace/AI/cat-cafe-develop/node_modules/.bin:/opt/homebrew/Cellar/node@24/24.18.0/bin:/Users/xxx/workspace/AI/clowder-ai/node_modules/.bin:/Users/xxx/Library/pnpm/.tools/pnpm/9.15.4/bin:/Users/xxx/Library/pnpm:/Users/xxx/.cargo/bin pnpm check:env-registry pass. [砚砚/gpt-5.5🐾] --- scripts/check-env-registry.test.mjs | 41 ++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/scripts/check-env-registry.test.mjs b/scripts/check-env-registry.test.mjs index 863209b870..de58450863 100644 --- a/scripts/check-env-registry.test.mjs +++ b/scripts/check-env-registry.test.mjs @@ -178,6 +178,17 @@ describe('env-registry completeness', () => { 'WEIXIN_ENABLE_UNSAFE_VOICE_MODES', 'WEIXIN_CAPTURE_INBOUND_VOICE_MEDIA', ]; + const nodeRuntimeShellEnvNames = [ + 'CAT_CAFE_NODE_PINNED_MAJOR', + 'CAT_CAFE_NODE_PREFERRED_MAJORS', + 'CAT_CAFE_NODE_BIN', + 'CAT_CAFE_NODE_RUNTIME_GUARD_REEXEC', + ]; + const nodeRuntimeOperatorKnobNames = [ + 'CAT_CAFE_NODE_PINNED_MAJOR', + 'CAT_CAFE_NODE_PREFERRED_MAJORS', + 'CAT_CAFE_NODE_BIN', + ]; it('every allowlist entry has a non-empty reason', () => { for (const [name, reason] of ALLOWLIST) { @@ -206,7 +217,35 @@ describe('env-registry completeness', () => { } }); - it('every process.env.XXX is registered or allowlisted', () => { + it('keeps Node runtime shell guard env vars covered by the env scan', () => { + for (const name of nodeRuntimeShellEnvNames) { + const locations = envRefs.get(name) ?? []; + assert.ok( + locations.some((location) => location.startsWith('scripts/lib/node-runtime-guard.sh:')), + `${name} should be discovered from scripts/lib/node-runtime-guard.sh`, + ); + } + }); + + it('keeps operator-facing Node runtime guard knobs in env-registry', () => { + for (const name of nodeRuntimeOperatorKnobNames) { + assert.ok(registeredNames.has(name), `${name} should be registered for operator discovery`); + assert.ok(!ALLOWLIST.has(name), `${name} is operator-facing config and must not be allowlisted`); + } + }); + + it('keeps the Node runtime guard re-exec sentinel internal', () => { + assert.ok( + ALLOWLIST.has('CAT_CAFE_NODE_RUNTIME_GUARD_REEXEC'), + 'CAT_CAFE_NODE_RUNTIME_GUARD_REEXEC should stay allowlisted as an internal sentinel', + ); + assert.ok( + !registeredNames.has('CAT_CAFE_NODE_RUNTIME_GUARD_REEXEC'), + 'CAT_CAFE_NODE_RUNTIME_GUARD_REEXEC must not be exposed as operator-facing config', + ); + }); + + it('every env reference is registered or allowlisted', () => { const missing = []; for (const [name, locations] of envRefs) { if (!registeredNames.has(name) && !ALLOWLIST.has(name)) {