Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
5bb2de0
ci: pin npm runtime in production D1 migration workflow
azumag Sep 20, 2026
1164c2e
chore: refresh D1 migration npm pin branch from main
azumag Sep 22, 2026
edf66e5
Merge main into ci/3476-pin-d1-npm-runtime
azumag Sep 22, 2026
e64c653
Merge main into ci/3476-pin-d1-npm-runtime
azumag Sep 22, 2026
d3f05c6
Merge main into ci/3476-pin-d1-npm-runtime
azumag Sep 22, 2026
e63faa0
Merge main into ci/3476-pin-d1-npm-runtime
azumag Sep 22, 2026
08efe4b
Merge branch 'main' into ci/3476-pin-d1-npm-runtime
azumag Sep 22, 2026
9945270
Merge main into ci/3476-pin-d1-npm-runtime
azumag Sep 22, 2026
e91d0ab
Merge main into ci/3476-pin-d1-npm-runtime
azumag Sep 22, 2026
944b71b
Merge main into ci/3476-pin-d1-npm-runtime
azumag Sep 22, 2026
d70f3d3
Merge main into ci/3476-pin-d1-npm-runtime
azumag Sep 22, 2026
adb4148
Merge main into codex/d1-migrate-npm-pin
azumag Sep 23, 2026
8f671f8
Merge main into ci/3476-pin-d1-npm-runtime
azumag Sep 23, 2026
583fc58
Merge main into ci/3476-pin-d1-npm-runtime
azumag Sep 23, 2026
767bfad
Merge main into ci/3476-pin-d1-npm-runtime
azumag Sep 23, 2026
28d12be
Merge main into ci/3476-pin-d1-npm-runtime
azumag Sep 23, 2026
afb36f4
Merge main into ci/3476-pin-d1-npm-runtime
azumag Sep 23, 2026
bbb174f
Merge branch 'main' into ci/3476-pin-d1-npm-runtime
azumag Sep 23, 2026
234f7ee
Merge branch 'main' into ci/3476-pin-d1-npm-runtime
azumag Sep 23, 2026
cc0a462
Merge branch 'main' into ci/3476-pin-d1-npm-runtime
azumag Sep 23, 2026
388c9dd
Merge main into ci/3476-pin-d1-npm-runtime
azumag Sep 23, 2026
d2ea0ca
Merge main into ci/3476-pin-d1-npm-runtime
azumag Sep 23, 2026
4b3c298
Merge main into ci/3476-pin-d1-npm-runtime
azumag Sep 23, 2026
ecc758c
Merge main into ci/3476-pin-d1-npm-runtime
azumag Sep 23, 2026
7095299
Merge main into ci/3476-pin-d1-npm-runtime
azumag Sep 23, 2026
ab1ca39
Merge main into ci/3476-pin-d1-npm-runtime
azumag Sep 23, 2026
9b20792
Merge main into ci/3476-pin-d1-npm-runtime
azumag Sep 23, 2026
6a83d93
Merge main into ci/3476-pin-d1-npm-runtime
azumag Sep 23, 2026
bbaf0b0
Merge main into ci/3476-pin-d1-npm-runtime
azumag Sep 23, 2026
dceb5c8
Merge branch 'main' into ci/3476-pin-d1-npm-runtime
azumag Sep 23, 2026
fad95fb
Merge main into ci/3476-pin-d1-npm-runtime
azumag Sep 23, 2026
ce8211a
Merge main into ci/3476-pin-d1-npm-runtime
azumag Sep 23, 2026
273d48f
Merge main into ci/3476-pin-d1-npm-runtime
azumag Sep 23, 2026
5760290
Merge main into ci/3476-pin-d1-npm-runtime
azumag Sep 23, 2026
7734c1e
Merge main into ci/3476-pin-d1-npm-runtime
azumag Sep 23, 2026
d81f5ea
Merge main into ci/3476-pin-d1-npm-runtime
azumag Sep 23, 2026
c9cc7ee
Merge main into ci/3476-pin-d1-npm-runtime
azumag Sep 23, 2026
2fc537c
Merge main into ci/3476-pin-d1-npm-runtime
azumag Sep 23, 2026
3e22bf9
Merge main into ci/3476-pin-d1-npm-runtime
azumag Sep 23, 2026
b99f36f
Merge main into ci/3476-pin-d1-npm-runtime
azumag Sep 23, 2026
27eb671
Merge main into ci/3476-pin-d1-npm-runtime
azumag Sep 24, 2026
673ca3e
Merge main into ci/3476-pin-d1-npm-runtime
azumag Sep 24, 2026
be47074
Merge main into ci/3476-pin-d1-npm-runtime
azumag Sep 24, 2026
a6c1ec9
Merge main into ci/3476-pin-d1-npm-runtime
azumag Sep 24, 2026
bae9eeb
Merge main into ci/3476-pin-d1-npm-runtime
azumag Sep 24, 2026
67fcf16
Merge main into ci/3476-pin-d1-npm-runtime
azumag Sep 24, 2026
1e324bb
Merge main into ci/3476-pin-d1-npm-runtime
azumag Sep 24, 2026
7c6fb06
Merge main into ci/3476-pin-d1-npm-runtime
azumag Sep 24, 2026
2e03e6b
Merge branch 'main' into ci/3476-pin-d1-npm-runtime
azumag Sep 24, 2026
d2caf45
Merge main into ci/3476-pin-d1-npm-runtime
azumag Sep 24, 2026
25b1926
Merge main into ci/3476-pin-d1-npm-runtime
azumag Sep 24, 2026
c42a4af
Merge main into ci/3476-pin-d1-npm-runtime
azumag Sep 24, 2026
fb27a28
Merge branch 'main' into ci/3476-pin-d1-npm-runtime
azumag Sep 24, 2026
5c49a44
Merge main into ci/3476-pin-d1-npm-runtime
azumag Sep 24, 2026
94b0a31
Merge main into ci/3476-pin-d1-npm-runtime
azumag Sep 24, 2026
454f3fe
Merge main into ci/3476-pin-d1-npm-runtime
azumag Sep 24, 2026
ad5d9a1
Merge main into ci/3476-pin-d1-npm-runtime
azumag Sep 24, 2026
67bd502
Merge main into ci/3476-pin-d1-npm-runtime
azumag Sep 24, 2026
00b4f62
Merge main into ci/3476-pin-d1-npm-runtime
azumag Sep 24, 2026
1a78e9d
Merge main into ci/3476-pin-d1-npm-runtime
azumag Sep 24, 2026
252161b
Merge main into ci/3476-pin-d1-npm-runtime
azumag Sep 24, 2026
d73cb7a
Merge main into ci/3476-pin-d1-npm-runtime
azumag Sep 28, 2026
4ef986b
Merge main into ci/3476-pin-d1-npm-runtime
azumag Sep 28, 2026
f2c0971
Merge main into ci/3476-pin-d1-npm-runtime
azumag Sep 29, 2026
2ad14e8
Merge main into ci/3476-pin-d1-npm-runtime
azumag Sep 29, 2026
9ed4bd7
Merge main into ci/3476-pin-d1-npm-runtime
azumag Oct 1, 2026
ad3a00b
Merge main into ci/3476-pin-d1-npm-runtime
azumag Oct 1, 2026
d0d4184
Merge main into ci/3476-pin-d1-npm-runtime
azumag Oct 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .github/workflows/d1-migrate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,16 @@ jobs:
cache: "npm"
cache-dependency-path: smkc-score-app/package-lock.json

- name: Pin npm
run: |
npm install --global --ignore-scripts --no-audit --no-fund npm@10.9.4
test "$(npm --version)" = "10.9.4"

- name: Install dependencies
run: npm ci
env:
# Production migration validation never needs local Git hooks; keep install parity with CI.
HUSKY: "0"

- name: List pending migrations
run: npm run db:migrations:list
Expand Down
10 changes: 5 additions & 5 deletions docs/ci-npm-toolchain.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
# CI の npm バージョン管理

JSMKC の `lint-and-test` CI と nightly E2E は npm を **10.9.4** に固定する。
JSMKC の `lint-and-test` CI、nightly E2E、production D1 migration workflow は npm を **10.9.4** に固定する。

`smkc-score-app/scripts/security-audit.js` は `npm audit --json` の report version、field、severity summary、dependency summary、exit status を fail-closed で検証している。Node.js 22 に同梱される npm をそのまま利用すると、GitHub Actions の runner / Node 配布物更新だけで npm の patch / major が変わり、アプリケーションの変更と無関係に audit の意味論や JSON 形状がドリフトする可能性がある。そのため CI では依存インストールより前に `npm@10.9.4` を明示的に導入し、実際の `npm --version` も確認する。

通常 CI、Claude Code Review の validation job、nightly E2E は npm 自体の bootstrap を `--ignore-scripts --no-audit --no-fund` 付きで実行する。bootstrap 対象は固定済みの npm CLI そのものなので lifecycle script を実行する必要はなく、この段階で application dependency audit や funding lookup を暗黙に発生させない。実際の application dependency install は後続の `npm ci` に限定し、通常 CI と nightly E2E の `npm ci` では local Git hook を必要としないため `HUSKY=0` で prepare-time の hook setup/noise も抑止する。
通常 CI、Claude Code Review の validation job、nightly E2E、D1 migration workflow は npm 自体の bootstrap を `--ignore-scripts --no-audit --no-fund` 付きで実行する。bootstrap 対象は固定済みの npm CLI そのものなので lifecycle script を実行する必要はなく、この段階で application dependency audit や funding lookup を暗黙に発生させない。実際の application dependency install は後続の `npm ci` に限定し、通常 CI、nightly E2E、D1 migration workflow の `npm ci` では local Git hook を必要としないため `HUSKY=0` で prepare-time の hook setup/noise も抑止する。

さらに `node scripts/security-audit.js` 自身が audit subprocess を起動する直前に npm runtime verifier を呼び、`package.json` の `packageManager` が exact `npm@x.y.z` 形式であることと、実行時の `npm --version` がその固定値と一致することを再検証する。CI の shell chain に verifier を別コマンドとして置かないため、audit helper をローカル・別CIから直接実行しても runtime guard を迂回できない。

#3114 の期限付き例外については、CI の Security audit step で `security-audit-lockfile.js` の後、ネットワークへ接続する本監査の前に `security-audit-status.js` を実行する。これにより、既知の例外文脈が期限切れ・依存更新・manifest/lockfile drift で変化した場合は、`npm audit` の結果だけで自動的に「解消」と判断せず fail-closed で停止する。status が `active` の場合だけ通常の `security-audit.js` へ進み、例外削除や期限更新は #3114 で再評価したうえで明示的に行う。

upstream の修正確認や再レビュー期限前の再評価を、アプリ変更用 PR を作らずに実行したい場合は GitHub Actions の **Security audit review** workflow を手動起動する。この workflow は `workflow_dispatch` のみで定期実行は行わず、read-only の repository permission で checkout した後、CI と同じ Node.js 22 / npm 10.9.4 を使用する。ただし application dependency tree はインストールせず、npm 自体の bootstrap も `--ignore-scripts --no-audit --no-fund` 付きで行う。その後、lockfile preflight → temporary exception status → network-backed canonical audit → compatible upstream probe の順に証拠を収集する。review 用 workflow では lockfile preflight が成功している限り、temporary exception status が `context-changed` / `expired` で fail-closed になっても canonical audit を続けて実行する。これにより forward update 候補が現れた回で「例外文脈は変わったが audit は clean か」を同じ run から確認できる一方、status step の失敗は保持されるため workflow 全体は green にならない。各 check の outcome は run summary に残す。結果が green でも #3114 の例外条件を自動変更・延長・削除はしないため、upstream の状態と audit 結果を確認してから明示的に判断する。

固定値の正本は `smkc-score-app/package.json` の `packageManager` と `.github/workflows/ci.yml` の Pin npm step で、`smkc-score-app/__tests__/docs/ci-config.test.ts` が両者の一致、side-effect-free bootstrap、`npm ci` より前の pin、lockfile preflight → temporary exception status → Security audit entrypoint の順序を回帰テストする。`smkc-score-app/__tests__/docs/claude-code-review-workflow.test.ts` と `smkc-score-app/__tests__/docs/e2e-nightly-npm-toolchain.test.ts` も同じ bootstrap flags と npm pin を固定し、後者は `HUSKY=0` で hook setup を抑止することも検証する。`smkc-score-app/__tests__/docs/security-audit-review-workflow.test.ts` は手動 review workflow が定期起動されないこと、read-only permission、同じ npm pin を lifecycle script / implicit audit / fund 通信なしで bootstrap すること、application dependency tree をインストールしないこと、audit 順序、status が変化しても preflight 成功時は canonical audit の証拠を取得すること、各 outcome を read-only summary に残すことを検証する。`smkc-score-app/__tests__/scripts/verify-npm-version.test.ts` は exact pin の解釈・runtime process failure・version mismatch の fail-closed 条件を検証し、`security-audit-runtime-guard.test.ts` は audit helper が `npm audit` より前に verifier を必ず呼ぶことを固定する。
固定値の正本は `smkc-score-app/package.json` の `packageManager` と `.github/workflows/ci.yml` の Pin npm step で、`smkc-score-app/__tests__/docs/ci-config.test.ts` が両者の一致、side-effect-free bootstrap、`npm ci` より前の pin、lockfile preflight → temporary exception status → Security audit entrypoint の順序を回帰テストする。`smkc-score-app/__tests__/docs/claude-code-review-workflow.test.ts` と `smkc-score-app/__tests__/docs/e2e-nightly-npm-toolchain.test.ts` も同じ bootstrap flags と npm pin を固定し、後者は `HUSKY=0` で hook setup を抑止することも検証する。`smkc-score-app/__tests__/docs/d1-migrate-npm-toolchain.test.ts` は production D1 migration workflow が同じ npm pin を `npm ci` より前に適用し、`HUSKY=0` を設定し、pending migration の確認 → migration apply という既存順序を変えないことを固定する。`smkc-score-app/__tests__/docs/security-audit-review-workflow.test.ts` は手動 review workflow が定期起動されないこと、read-only permission、同じ npm pin を lifecycle script / implicit audit / fund 通信なしで bootstrap すること、application dependency tree をインストールしないこと、audit 順序、status が変化しても preflight 成功時は canonical audit の証拠を取得すること、各 outcome を read-only summary に残すことを検証する。`smkc-score-app/__tests__/scripts/verify-npm-version.test.ts` は exact pin の解釈・runtime process failure・version mismatch の fail-closed 条件を検証し、`security-audit-runtime-guard.test.ts` は audit helper が `npm audit` より前に verifier を必ず呼ぶことを固定する。

`d1-migrate.yml` も `npm ci` を実行するが、この workflow 自体の変更は `main` merge 後に production D1 migration job を起動する。そのため npm pin の適用は production 側の pending migration / 実行証跡を確認できる作業回で別途行い、単なる CI 保守変更として自動マージしない。
`d1-migrate.yml` は自身の変更が `main` に入ると production environment の D1 migration job を起動する。したがってこの workflow の PR は通常の CI が green であっても自動マージせず、merge 前に production D1 の pending migration 状態と実行予定を確認する。merge 後は意図した `D1 Migrate` run が1回だけ起動し、`List pending migrations`、`Apply migrations`、`Confirm no migrations remain pending` が成功したことを実行証跡として残す。

## 更新手順

npm を更新するときは、単に version を上げず、候補版で `npm ci` と `node scripts/security-audit-lockfile.js && node scripts/security-audit-status.js && node scripts/security-audit.js` を実行し、runtime version guard と audit report shape / exit status の検証が引き続き成立することを確認する。必要なら security-audit helper と policy を同じ PR で更新する。

確認後、`package.json` の `packageManager` と CI / nightly E2E の Pin npm step を同じ version に更新し、lint、format、unit tests、security audit、Cloudflare build を通す。npm の変更だけを理由に #3114 の high/critical gate や temporary exception 条件を緩めない。
確認後、`package.json` の `packageManager` と CI / nightly E2E / D1 migration workflow の Pin npm step を同じ version に更新し、lint、format、unit tests、security audit、Cloudflare build を通す。D1 workflow を変更する場合は上記の production merge gate も満たす。npm の変更だけを理由に #3114 の high/critical gate や temporary exception 条件を緩めない。
69 changes: 69 additions & 0 deletions smkc-score-app/__tests__/docs/d1-migrate-npm-toolchain.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import fs from 'fs';
import path from 'path';
import { parse } from 'yaml';

interface WorkflowStep {
name?: string;
run?: string;
env?: Record<string, string>;
}

interface WorkflowJob {
defaults?: {
run?: {
'working-directory'?: string;
};
};
steps?: WorkflowStep[];
}

interface WorkflowDocument {
jobs?: Record<string, WorkflowJob>;
}

interface PackageManifest {
packageManager?: string;
}

describe('D1 migration npm toolchain', () => {
const workflowPath = path.resolve(__dirname, '..', '..', '..', '.github', 'workflows', 'd1-migrate.yml');
const packageJsonPath = path.resolve(__dirname, '..', '..', 'package.json');

it('pins packageManager npm before npm ci and skips Husky hooks without changing migration order', () => {
const workflow = parse(fs.readFileSync(workflowPath, 'utf8')) as WorkflowDocument;
const manifest = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')) as PackageManifest;
const applyJob = workflow.jobs?.apply;
const steps = applyJob?.steps;
const packageManager = manifest.packageManager;

expect(steps?.length).toBeGreaterThan(0);
expect(packageManager).toMatch(/^npm@\d+\.\d+\.\d+$/);
expect(applyJob?.defaults?.run?.['working-directory']).toBe('smkc-score-app');

const pinStep = steps?.find((step) => step.name === 'Pin npm');
const installStep = steps?.find((step) => step.name === 'Install dependencies');
const listStep = steps?.find((step) => step.name === 'List pending migrations');
const applyStep = steps?.find((step) => step.name === 'Apply migrations');

expect(pinStep).toBeDefined();
expect(installStep).toBeDefined();
expect(listStep).toBeDefined();
expect(applyStep).toBeDefined();
expect(pinStep?.run).toContain(`npm install --global --ignore-scripts --no-audit --no-fund ${packageManager}`);

const expectedVersion = packageManager?.replace(/^npm@/, '');
expect(pinStep?.run).toContain(`test "$(npm --version)" = "${expectedVersion}"`);
expect(installStep?.run?.trim()).toBe('npm ci');
expect(installStep?.env?.HUSKY).toBe('0');

const pinIndex = steps?.indexOf(pinStep as WorkflowStep) ?? -1;
const installIndex = steps?.indexOf(installStep as WorkflowStep) ?? -1;
const listIndex = steps?.indexOf(listStep as WorkflowStep) ?? -1;
const applyIndex = steps?.indexOf(applyStep as WorkflowStep) ?? -1;

expect(pinIndex).toBeGreaterThanOrEqual(0);
expect(pinIndex).toBeLessThan(installIndex);
expect(installIndex).toBeLessThan(listIndex);
expect(listIndex).toBeLessThan(applyIndex);
});
});
Loading