From 4294ddc6c549ba805032e6d828ffbb2ddb9b43a2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 16:26:08 +0900 Subject: [PATCH 001/161] test(supply-chain): require deterministic npm lock generator --- .../tests/test_npm_toolchain_contract.py | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 services/analysis-engine/tests/test_npm_toolchain_contract.py diff --git a/services/analysis-engine/tests/test_npm_toolchain_contract.py b/services/analysis-engine/tests/test_npm_toolchain_contract.py new file mode 100644 index 000000000..89301fd9e --- /dev/null +++ b/services/analysis-engine/tests/test_npm_toolchain_contract.py @@ -0,0 +1,61 @@ +"""Contracts for deterministic npm lockfile generation and CI provenance.""" + +from __future__ import annotations + +import json +from pathlib import Path + + +_REPOSITORY_ROOT = Path(__file__).resolve().parents[3] +_EXPECTED_NPM_VERSION = "10.9.8" +_EXPECTED_NODE_VERSION = "22.22.3" + + +def _root_manifest() -> dict[str, object]: + """Return the checked-in root package manifest as a JSON object.""" + manifest = json.loads( + (_REPOSITORY_ROOT / "package.json").read_text(encoding="utf-8") + ) + assert isinstance(manifest, dict) + return manifest + + +def test_root_manifest_pins_the_lockfile_generator_and_fails_on_drift() -> None: + """Require npm and source-tree commands to reject a different generator.""" + manifest = _root_manifest() + + assert manifest["packageManager"] == f"npm@{_EXPECTED_NPM_VERSION}" + assert manifest["engines"]["npm"] == _EXPECTED_NPM_VERSION # type: ignore[index] + assert manifest["devEngines"] == { + "packageManager": { + "name": "npm", + "version": _EXPECTED_NPM_VERSION, + "onFail": "error", + } + } + + +def test_primary_ci_proves_exact_npm_before_install_and_lock_reproduction() -> None: + """Keep the clean installer and lock reproduction on one explicit toolchain.""" + workflow = (_REPOSITORY_ROOT / ".github" / "workflows" / "ci.yml").read_text( + encoding="utf-8" + ) + + assert f'node-version: "{_EXPECTED_NODE_VERSION}"' in workflow + assert f'EXPECTED_NPM_VERSION: "{_EXPECTED_NPM_VERSION}"' in workflow + assert 'test "$(npm --version)" = "$EXPECTED_NPM_VERSION"' in workflow + assert "npm install --package-lock-only" in workflow + assert "--ignore-scripts" in workflow + assert "--no-audit" in workflow + assert "--no-fund" in workflow + assert "git diff --exit-code -- package-lock.json" in workflow + + +def test_root_lock_uses_the_supported_location_keyed_format() -> None: + """Require the npm-v9-and-newer lock format used by the pinned generator.""" + lock_document = json.loads( + (_REPOSITORY_ROOT / "package-lock.json").read_text(encoding="utf-8") + ) + + assert lock_document["lockfileVersion"] == 3 + assert isinstance(lock_document["packages"], dict) From afb20beded7118dc9072fc3ae13d42af5d0b50c8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 16:27:40 +0900 Subject: [PATCH 002/161] fix(supply-chain): pin npm lock generator metadata --- package.json | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index a71236ed0..276804de1 100644 --- a/package.json +++ b/package.json @@ -3,8 +3,17 @@ "private": true, "version": "0.1.3", "type": "module", + "packageManager": "npm@10.9.8", "engines": { - "node": ">=22.13 <23" + "node": ">=22.13 <23", + "npm": "10.9.8" + }, + "devEngines": { + "packageManager": { + "name": "npm", + "version": "10.9.8", + "onFail": "error" + } }, "workspaces": [ "apps/*", From e64c229c527785278229f9401602e659e8f3d0b6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 16:28:01 +0900 Subject: [PATCH 003/161] ci(supply-chain): prove npm version and lock reproduction --- .github/workflows/ci.yml | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5f99a9c17..2750c5e01 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,6 +17,7 @@ env: GIT_CONFIG_COUNT: "1" GIT_CONFIG_KEY_0: init.defaultBranch GIT_CONFIG_VALUE_0: develop + EXPECTED_NPM_VERSION: "10.9.8" jobs: verify: @@ -26,14 +27,20 @@ jobs: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: - node-version: 22.22.3 + node-version: "22.22.3" cache: npm + - name: Verify exact npm lockfile generator + run: test "$(npm --version)" = "$EXPECTED_NPM_VERSION" - uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2 with: version: "0.8.6" enable-cache: false - name: Install node dependencies run: npm ci + - name: Prove package lock reproduces with the pinned npm + run: | + npm install --package-lock-only --ignore-scripts --no-audit --no-fund + git diff --exit-code -- package-lock.json - name: Sync Python dependencies run: uv sync --project services/analysis-engine --group dev --frozen - name: Install stable Rust toolchain @@ -56,8 +63,10 @@ jobs: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: - node-version: 22.22.3 + node-version: "22.22.3" cache: npm + - name: Verify exact npm lockfile generator + run: test "$(npm --version)" = "$EXPECTED_NPM_VERSION" - name: Install stable Rust toolchain run: rustup toolchain install stable --profile minimal - name: Install node dependencies From ebbde9baf051cd2944d14b141d791ce1777340cb Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 16:28:31 +0900 Subject: [PATCH 004/161] docs(supply-chain): record npm generator provenance --- .../npm-lockfile-generator-provenance.md | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 docs/doctoring/npm-lockfile-generator-provenance.md diff --git a/docs/doctoring/npm-lockfile-generator-provenance.md b/docs/doctoring/npm-lockfile-generator-provenance.md new file mode 100644 index 000000000..83bf73562 --- /dev/null +++ b/docs/doctoring/npm-lockfile-generator-provenance.md @@ -0,0 +1,76 @@ +# npm lockfile generator provenance + +## Decision + +BandScope generates and verifies its root npm workspace lock with exactly npm `10.9.8`. The root manifest records that decision through: + +- `packageManager: npm@10.9.8` as package-manager selection metadata; +- `engines.npm: 10.9.8` as the published source-tree compatibility declaration; and +- `devEngines.packageManager` with `onFail: error` as npm's source-tree command gate. + +The primary GitHub Actions workflow uses Node `22.22.3`, verifies the bundled npm version before any installation, runs `npm ci`, then runs a package-lock-only regeneration with scripts, audit, and funding output disabled. Any `package-lock.json` diff fails the exact head. + +The Node runtime support decision remains separate. This change does not raise the public `>=22.13 <23` Node range; a coordinated Node-floor migration is tracked independently. + +## Why the generator is part of the lock identity + +npm documents `package-lock.json` as the location-keyed description of the exact dependency tree. Lockfile version 3 is intended for npm 9 and newer. npm also notes that different package-manager versions may use different installation algorithms and metadata representations. A committed lockfile therefore is not fully reproducible unless the generator version and install-shaping flags are versioned with it. + +`npm ci` is the immutable consumption path: it requires a lockfile, rejects manifest/lock dependency disagreement, removes an existing `node_modules`, and does not write the manifest or lock. It does not prove that a future dependency update will regenerate byte-identical metadata. The additional package-lock-only replay closes that gap. + +```mermaid +flowchart LR + M[package.json ranges and workspaces] --> G[npm 10.9.8] + C[project npm configuration] --> G + G --> L[package-lock.json v3] + L --> I[npm ci clean install] + I --> R[npm 10.9.8 package-lock-only replay] + R --> D{lock diff?} + D -->|no| A[reproducible exact-head evidence] + D -->|yes| F[fail closed] +``` + +## Security and operational boundary + +- Dependency PRs may change only manifest ranges and the lock records produced by npm `10.9.8`. +- Reviewers must reject unrelated lock metadata that cannot be reproduced by the pinned generator. +- No lock record may be added or removed by hand to satisfy a validator. +- Install-shaping flags that change the tree, such as `legacy-peer-deps` or `install-links`, must be committed in project configuration and used identically by `npm ci` and regeneration. +- Dependency lifecycle scripts remain disabled for the reproduction pass. The normal clean install retains the repository's reviewed execution behavior. +- The exact npm version check occurs before `npm ci`; a different bundled or globally installed npm cannot generate acceptance evidence. +- The lockfile remains the sole npm workspace lock. Nested workspace locks are prohibited. + +`packageManager` alone is not the enforcement boundary for npm because Corepack's npm shim is not enabled by default in Node distributions. Enforcement is provided by npm `devEngines`, the explicit CI version assertion, and the lock replay. + +## Verification + +`services/analysis-engine/tests/test_npm_toolchain_contract.py` verifies the manifest metadata, exact CI Node/npm identity, replay command and flags, clean lock diff, and lockfile version 3. Repository CI then executes the replay using the hosted toolchain. + +A dependency update is mergeable only after: + +1. npm `10.9.8` produces the checked-in lock from the updated manifest; +2. a second package-lock-only replay is byte-clean; +3. `npm ci`, lint, strict typecheck, measured tests, production build, Rust/Tauri checks, and security/supply-chain gates succeed on the same head; and +4. current-head review, unresolved-thread, independent-approval, and branch-protection requirements succeed without bypass. + +## Incident response and rollback + +When replay changes the lock unexpectedly: + +1. preserve the exact head SHA, npm and Node versions, command flags, original lock blob SHA, regenerated lock, and CI run ID; +2. determine whether the manifest changed, npm changed, project configuration changed, or the protected lock was generated by a different toolchain; +3. never accept a partial or hand-edited lock; +4. regenerate from a clean checkout using the reviewed npm version and run the replay twice; +5. if rollback is necessary, restore the prior manifest and complete lock together, then rerun the entire exact-head gate. + +## References + +npm, Inc. (2026). *npm ci*. npm Docs. https://docs.npmjs.com/cli/v11/commands/npm-ci/ + +npm, Inc. (2026). *npm install*. npm Docs. https://docs.npmjs.com/cli/v10/commands/npm-install/ + +npm, Inc. (2026). *package-lock.json*. npm Docs. https://docs.npmjs.com/cli/v11/configuring-npm/package-lock-json/ + +npm, Inc. (2026). *package.json*. npm Docs. https://docs.npmjs.com/cli/configuring-npm/package-json/ + +Node.js contributors. (2026). *Corepack* [Software documentation]. GitHub. https://github.com/nodejs/corepack From 694ca83b4dfd31ef12be464d832b6bb84fb07362 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 16:29:00 +0900 Subject: [PATCH 005/161] docs(changelog): record npm generator contract --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index eea696893..05f555100 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ - Display the analyzed song tempo (BPM) as a badge in the rehearsal workspace. - 각 합주 역할(Role)별 개인 연습 진행도를 0~100% 범위로 기록 및 시각화할 수 있는 연습 진척도(`practiceProgress`) 트래커 기능 추가. UI 컨트롤(슬라이더 및 +/- 버튼)과 한/영 다국어 지원 포함. +### Changed + +- Pinned npm `10.9.8` as the lockfile generator and made primary CI reject a different npm version or any package-lock-only replay diff. + ## [0.1.3] - 2026-04-29 ### Fixed From 90dc2a0248bd5259a24040303368acf7b909f376 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 16:58:59 +0900 Subject: [PATCH 006/161] fix(supply-chain): avoid serializing npm into runtime engines --- package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index 276804de1..779899ede 100644 --- a/package.json +++ b/package.json @@ -5,8 +5,7 @@ "type": "module", "packageManager": "npm@10.9.8", "engines": { - "node": ">=22.13 <23", - "npm": "10.9.8" + "node": ">=22.13 <23" }, "devEngines": { "packageManager": { From fecc36b309f92afcf497a431edef4911dc6104f4 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 16:59:31 +0900 Subject: [PATCH 007/161] test(supply-chain): keep npm enforcement out of runtime engines --- services/analysis-engine/tests/test_npm_toolchain_contract.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/analysis-engine/tests/test_npm_toolchain_contract.py b/services/analysis-engine/tests/test_npm_toolchain_contract.py index 89301fd9e..b4a0ae46d 100644 --- a/services/analysis-engine/tests/test_npm_toolchain_contract.py +++ b/services/analysis-engine/tests/test_npm_toolchain_contract.py @@ -25,7 +25,7 @@ def test_root_manifest_pins_the_lockfile_generator_and_fails_on_drift() -> None: manifest = _root_manifest() assert manifest["packageManager"] == f"npm@{_EXPECTED_NPM_VERSION}" - assert manifest["engines"]["npm"] == _EXPECTED_NPM_VERSION # type: ignore[index] + assert manifest["engines"] == {"node": ">=22.13 <23"} assert manifest["devEngines"] == { "packageManager": { "name": "npm", From 9eb83c39c9978ad730824d6e93218565b5b7d577 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 17:00:13 +0900 Subject: [PATCH 008/161] docs(supply-chain): separate npm generator from runtime engines --- docs/doctoring/npm-lockfile-generator-provenance.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/doctoring/npm-lockfile-generator-provenance.md b/docs/doctoring/npm-lockfile-generator-provenance.md index 83bf73562..1d50f2a5d 100644 --- a/docs/doctoring/npm-lockfile-generator-provenance.md +++ b/docs/doctoring/npm-lockfile-generator-provenance.md @@ -4,10 +4,11 @@ BandScope generates and verifies its root npm workspace lock with exactly npm `10.9.8`. The root manifest records that decision through: -- `packageManager: npm@10.9.8` as package-manager selection metadata; -- `engines.npm: 10.9.8` as the published source-tree compatibility declaration; and +- `packageManager: npm@10.9.8` as package-manager selection metadata; and - `devEngines.packageManager` with `onFail: error` as npm's source-tree command gate. +The npm version is intentionally not repeated under `engines`. npm serializes `engines` into the root lock package, so adding an npm-only source-tool constraint there creates lock metadata churn unrelated to dependency resolution. `devEngines`, the explicit CI assertion, and the replay gate enforce the generator while the published `engines.node` range remains the runtime compatibility contract. + The primary GitHub Actions workflow uses Node `22.22.3`, verifies the bundled npm version before any installation, runs `npm ci`, then runs a package-lock-only regeneration with scripts, audit, and funding output disabled. Any `package-lock.json` diff fails the exact head. The Node runtime support decision remains separate. This change does not raise the public `>=22.13 <23` Node range; a coordinated Node-floor migration is tracked independently. @@ -44,7 +45,7 @@ flowchart LR ## Verification -`services/analysis-engine/tests/test_npm_toolchain_contract.py` verifies the manifest metadata, exact CI Node/npm identity, replay command and flags, clean lock diff, and lockfile version 3. Repository CI then executes the replay using the hosted toolchain. +`services/analysis-engine/tests/test_npm_toolchain_contract.py` verifies the manifest metadata, separation of runtime and generator constraints, exact CI Node/npm identity, replay command and flags, clean lock diff, and lockfile version 3. Repository CI then executes the replay using the hosted toolchain. A dependency update is mergeable only after: From 5609b8828c7cd5e0bec0d0eed0a56d58197b8e86 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 17:04:24 +0900 Subject: [PATCH 009/161] ci(supply-chain): publish deterministic lock reproduction evidence --- .github/workflows/ci.yml | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2750c5e01..8311a1dae 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,8 +20,33 @@ env: EXPECTED_NPM_VERSION: "10.9.8" jobs: + lock-reproduction: + name: gate / ci / npm-lock-reproduction + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: "22.22.3" + cache: npm + - name: Verify exact npm lockfile generator + run: test "$(npm --version)" = "$EXPECTED_NPM_VERSION" + - name: Reproduce package lock without lifecycle execution + run: npm install --package-lock-only --ignore-scripts --no-audit --no-fund + - name: Preserve the exact generated lock as review evidence + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: npm-lock-reproduction-${{ github.event.pull_request.head.sha || github.sha }} + path: package-lock.json + if-no-files-found: error + retention-days: 3 + - name: Reject lockfile drift + run: git diff --exit-code -- package-lock.json + verify: name: ci / build-and-test + needs: lock-reproduction runs-on: ubuntu-latest steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 @@ -37,10 +62,6 @@ jobs: enable-cache: false - name: Install node dependencies run: npm ci - - name: Prove package lock reproduces with the pinned npm - run: | - npm install --package-lock-only --ignore-scripts --no-audit --no-fund - git diff --exit-code -- package-lock.json - name: Sync Python dependencies run: uv sync --project services/analysis-engine --group dev --frozen - name: Install stable Rust toolchain @@ -58,6 +79,7 @@ jobs: rust-check: name: gate / ci / rust-check + needs: lock-reproduction runs-on: macos-15 steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 From 032314d724e0ddc5ce16c7958ee364dd40264699 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 17:04:59 +0900 Subject: [PATCH 010/161] test(supply-chain): require preserved lock reproduction evidence --- .../analysis-engine/tests/test_npm_toolchain_contract.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/services/analysis-engine/tests/test_npm_toolchain_contract.py b/services/analysis-engine/tests/test_npm_toolchain_contract.py index b4a0ae46d..30e00cbdd 100644 --- a/services/analysis-engine/tests/test_npm_toolchain_contract.py +++ b/services/analysis-engine/tests/test_npm_toolchain_contract.py @@ -44,10 +44,15 @@ def test_primary_ci_proves_exact_npm_before_install_and_lock_reproduction() -> N assert f'node-version: "{_EXPECTED_NODE_VERSION}"' in workflow assert f'EXPECTED_NPM_VERSION: "{_EXPECTED_NPM_VERSION}"' in workflow assert 'test "$(npm --version)" = "$EXPECTED_NPM_VERSION"' in workflow + assert "lock-reproduction:" in workflow + assert "needs: lock-reproduction" in workflow assert "npm install --package-lock-only" in workflow assert "--ignore-scripts" in workflow assert "--no-audit" in workflow assert "--no-fund" in workflow + assert "actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a" in workflow + assert "npm-lock-reproduction-${{ github.event.pull_request.head.sha || github.sha }}" in workflow + assert "if-no-files-found: error" in workflow assert "git diff --exit-code -- package-lock.json" in workflow From 0263ad427aa543e42ad9f01be1858074cf43ac52 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 17:08:32 +0900 Subject: [PATCH 011/161] test(security): require coordinated PDF.js and Undici baseline --- .../test_high_security_dependency_baseline.py | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 services/analysis-engine/tests/test_high_security_dependency_baseline.py diff --git a/services/analysis-engine/tests/test_high_security_dependency_baseline.py b/services/analysis-engine/tests/test_high_security_dependency_baseline.py new file mode 100644 index 000000000..01dba0ab8 --- /dev/null +++ b/services/analysis-engine/tests/test_high_security_dependency_baseline.py @@ -0,0 +1,79 @@ +"""Contracts for the coordinated PDF.js and Undici security baseline.""" + +from __future__ import annotations + +import json +from pathlib import Path + + +_REPOSITORY_ROOT = Path(__file__).resolve().parents[3] +_PDFJS_VERSION = "6.2.108" +_UNDICI_VERSION = "7.29.0" +_PDFJS_INTEGRITY = ( + "sha512-YxFb+SQcodN2rnX9Tn3dHYlqfb7NjlzzfONPpJd+AKoKtUjEdevTfbC07d5Tcczz" + "OK6261auRkP/M8OBHs9vFQ==" +) +_UNDICI_INTEGRITY = ( + "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9" + "rWmsreUyf5lwyao+7GNNVw==" +) + + +def _read_json(relative_path: str) -> dict[str, object]: + """Return one repository JSON document as a mapping.""" + document = json.loads( + (_REPOSITORY_ROOT / relative_path).read_text(encoding="utf-8") + ) + assert isinstance(document, dict) + return document + + +def test_manifests_pin_the_security_floors_without_semver_drift() -> None: + """Keep the vulnerable transitive client and PDF parser on exact versions.""" + root_manifest = _read_json("package.json") + desktop_manifest = _read_json("apps/desktop/package.json") + + assert root_manifest["overrides"]["undici"] == _UNDICI_VERSION # type: ignore[index] + assert desktop_manifest["dependencies"]["pdfjs-dist"] == _PDFJS_VERSION # type: ignore[index] + + +def test_lock_records_match_exact_registry_artifacts_and_preserve_peer_metadata() -> None: + """Require the pinned generator's exact graph without unrelated esbuild churn.""" + lock_document = _read_json("package-lock.json") + packages = lock_document["packages"] + assert isinstance(packages, dict) + + desktop = packages["apps/desktop"] + assert isinstance(desktop, dict) + assert desktop["dependencies"]["pdfjs-dist"] == _PDFJS_VERSION # type: ignore[index] + + pdfjs = packages["node_modules/pdfjs-dist"] + assert pdfjs == { + "version": _PDFJS_VERSION, + "resolved": ( + "https://registry.npmjs.org/pdfjs-dist/-/" + f"pdfjs-dist-{_PDFJS_VERSION}.tgz" + ), + "integrity": _PDFJS_INTEGRITY, + "license": "Apache-2.0", + "engines": {"node": ">=22.13.0 || >=24"}, + } + + undici = packages["node_modules/undici"] + assert isinstance(undici, dict) + assert undici["version"] == _UNDICI_VERSION + assert undici["resolved"] == ( + "https://registry.npmjs.org/undici/-/undici-7.29.0.tgz" + ) + assert undici["integrity"] == _UNDICI_INTEGRITY + + esbuild_locations = { + path: metadata + for path, metadata in packages.items() + if isinstance(path, str) and path.startswith("node_modules/@esbuild/") + } + assert esbuild_locations + assert all( + isinstance(metadata, dict) and metadata.get("peer") is True + for metadata in esbuild_locations.values() + ) From 2cbf767f6fb42f7c8034c116c9b038350532b2b9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 17:08:52 +0900 Subject: [PATCH 012/161] test(security): disable PDF expression evaluation --- apps/desktop/src/features/score/pdfjs.test.ts | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 apps/desktop/src/features/score/pdfjs.test.ts diff --git a/apps/desktop/src/features/score/pdfjs.test.ts b/apps/desktop/src/features/score/pdfjs.test.ts new file mode 100644 index 000000000..49392ed66 --- /dev/null +++ b/apps/desktop/src/features/score/pdfjs.test.ts @@ -0,0 +1,42 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { getDocument, GlobalWorkerOptions } from "pdfjs-dist"; +import { configureScorePdfWorker, loadScorePdf } from "./pdfjs"; + +vi.mock("pdfjs-dist", () => ({ + getDocument: vi.fn(() => ({ promise: Promise.resolve(), destroy: vi.fn() })), + GlobalWorkerOptions: { workerSrc: "" } +})); + +vi.mock("pdfjs-dist/build/pdf.worker.min.mjs?url", () => ({ + default: "/assets/pdf.worker.min.mjs" +})); + +describe("score PDF.js boundary", () => { + beforeEach(() => { + vi.mocked(getDocument).mockClear(); + GlobalWorkerOptions.workerSrc = ""; + }); + + it("uses the locally bundled worker asset", () => { + configureScorePdfWorker(); + + expect(GlobalWorkerOptions.workerSrc).toBe("/assets/pdf.worker.min.mjs"); + + configureScorePdfWorker(); + expect(GlobalWorkerOptions.workerSrc).toBe("/assets/pdf.worker.min.mjs"); + }); + + it("copies validated bytes and disables PDF expression evaluation", () => { + const source = new Uint8Array([0x25, 0x50, 0x44, 0x46]); + + loadScorePdf(source); + + expect(getDocument).toHaveBeenCalledTimes(1); + const parameters = vi.mocked(getDocument).mock.calls[0]?.[0]; + expect(parameters).toMatchObject({ isEvalSupported: false }); + expect(parameters).toHaveProperty("data"); + const copiedBytes = (parameters as { data: Uint8Array }).data; + expect(copiedBytes).toEqual(source); + expect(copiedBytes).not.toBe(source); + }); +}); From 475de96fc523bfab739817bb665493b11d0dd8bc Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 17:10:02 +0900 Subject: [PATCH 013/161] fix(security): pin the patched Undici transitive version --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 779899ede..1ad0f15ec 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ }, "overrides": { "brace-expansion": "5.0.9", - "postcss": "8.5.25" + "postcss": "8.5.25", + "undici": "7.29.0" } } From deb74acd7da82fe0696d5d876072455c7bebbec2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 17:10:43 +0900 Subject: [PATCH 014/161] fix(security): pin the patched PDF.js release --- apps/desktop/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/desktop/package.json b/apps/desktop/package.json index e7685d6f0..e09719b22 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -20,7 +20,7 @@ "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "lucide-react": "^1.24.0", - "pdfjs-dist": "6.1.200", + "pdfjs-dist": "6.2.108", "react": "^19.2.4", "react-dom": "^19.2.7", "sonner": "^2.0.7", From dc90d5b0d8bb516df28467d09332256d141de836 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 17:11:01 +0900 Subject: [PATCH 015/161] fix(security): disable PDF expression evaluation --- apps/desktop/src/features/score/pdfjs.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/desktop/src/features/score/pdfjs.ts b/apps/desktop/src/features/score/pdfjs.ts index b62526c89..007cd5727 100644 --- a/apps/desktop/src/features/score/pdfjs.ts +++ b/apps/desktop/src/features/score/pdfjs.ts @@ -22,8 +22,13 @@ export function configureScorePdfWorker(): void { * this helper never fetches arbitrary URLs. The bytes are copied before they * are handed to pdf.js because pdf.js transfers the underlying buffer to its * worker, which would otherwise detach the caller's copy and break retries. + * PDF expression evaluation remains disabled as defense in depth even when + * the installed pdf.js release includes the corresponding security patch. */ export function loadScorePdf(data: Uint8Array): PDFDocumentLoadingTask { configureScorePdfWorker(); - return getDocument({ data: new Uint8Array(data) }); + return getDocument({ + data: new Uint8Array(data), + isEvalSupported: false + }); } From b23b957c2bcca70abcc5bd36d5ae05e343fa0054 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 17:12:13 +0900 Subject: [PATCH 016/161] docs(security): record coordinated PDF and HTTP remediation --- .../high-security-pdf-http-baseline.md | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 docs/doctoring/high-security-pdf-http-baseline.md diff --git a/docs/doctoring/high-security-pdf-http-baseline.md b/docs/doctoring/high-security-pdf-http-baseline.md new file mode 100644 index 000000000..d7b4d4014 --- /dev/null +++ b/docs/doctoring/high-security-pdf-http-baseline.md @@ -0,0 +1,78 @@ +# High-security PDF and HTTP dependency baseline + +## Decision + +BandScope treats the PDF parser and its transitive HTTP client as one security-release boundary: + +- `pdfjs-dist` is pinned exactly to `6.2.108`; +- `undici` is pinned exactly to `7.29.0` through the root npm override; and +- the complete npm workspace lock is generated only by the repository-pinned npm `10.9.8` workflow and imported unchanged from the workflow artifact. + +The desktop PDF loader additionally passes `isEvalSupported: false` to `getDocument`. The dependency patch is the primary remediation; disabling expression evaluation is defense in depth and prevents a future regression or alternate vulnerable execution path from re-enabling dynamic PDF expression compilation. + +```mermaid +flowchart LR + A[Validated local PDF bytes] --> B[Copied Uint8Array] + B --> C[pdfjs-dist 6.2.108] + P[isEvalSupported false] --> C + C --> W[Same-origin bundled worker] + W --> R[Canvas render] + J[jsdom development path] --> U[undici 7.29.0 override] + N[npm 10.9.8] --> L[Exact package-lock artifact] + L --> C + L --> U +``` + +## Threat boundary + +The score viewer accepts only bytes already copied into the app-owned workspace through the native PDF intake boundary. It does not accept a URL and never uses a remote worker. This prevents a PDF from selecting an attacker-controlled fetch origin or script asset. + +PDF bytes remain untrusted after the native magic-byte, size, and path checks. Parser vulnerabilities, malformed object graphs, embedded actions, and expression compilation can still occur inside a syntactically valid PDF. The patched parser and explicit `isEvalSupported: false` therefore remain mandatory even for locally selected files. + +Undici is currently a development dependency reached through jsdom, but development and CI parsers process attacker-controlled fixtures, generated HTML, and network-like request bodies. A dev-only label does not make header injection, shared-cache disclosure, retry desynchronization, or cookie-attribute injection acceptable in the trusted build boundary. + +## Lockfile provenance + +The security manifests are changed before the lock. The exact branch workflow then: + +1. verifies Node `22.22.3` and npm `10.9.8`; +2. runs `npm install --package-lock-only --ignore-scripts --no-audit --no-fund`; +3. uploads the generated `package-lock.json` under a head-SHA-bound artifact name; and +4. fails while the generated lock differs from the branch. + +The maintainer imports that generated artifact byte-for-byte and reruns the workflow. The second run must produce a clean diff. No tarball URL, SRI, dependency range, `peer` classification, or workspace record is edited by hand. + +The lock contract requires the exact public-registry tarball and SHA-512 SRI for both patched packages and requires every existing `node_modules/@esbuild/*` location to retain npm 10.9.8's `peer: true` classification. This distinguishes the intended security graph from unrelated Dependabot generator churn. + +## Verification + +The merge gate includes: + +- exact manifest and lock artifact tests; +- a direct PDF.js wrapper test for copied bytes, the locally bundled worker, and `isEvalSupported: false`; +- valid and malformed local score-PDF component tests; +- desktop lint, strict typecheck, complete measured tests, and production build; +- Tauri/Rust checks and native PDF intake regressions; +- `npm audit --workspaces --audit-level=high` with no high finding; +- repository SAST, CodeQL, security scan, secret scan, SBOM, and dependency evidence; +- current-head central coverage and automated review; +- zero unresolved actionable threads and a qualifying independent non-author approval; and +- normal branch protection without administrative bypass. + +## Failure, rollback, and incident evidence + +On a failed lock replay or parser regression, preserve the exact head SHA, Node/npm versions, generated-lock artifact ID and digest, original and generated lock blob SHA, test output, audit report, and workflow run ID. Do not merge a partially updated graph. + +Rollback restores the previous desktop manifest, root override, complete lock, PDF loader, tests, and CHANGELOG entry together. Because the previous graph contains known high findings, rollback is an emergency availability action only and requires an explicit security exception, compensating controls, owner, expiration, and immediate replacement plan. + +## References + +GitHub. (2026). *PDF.js vulnerable to arbitrary JavaScript execution upon opening a malicious PDF* (GHSA-hq66-cqwq-w95j) [Security advisory]. https://github.com/advisories/GHSA-hq66-cqwq-w95j + +Mozilla. (2026). *PDF.js 6.2.108* [Software release]. https://github.com/mozilla/pdf.js/releases/tag/v6.2.108 + +Node.js contributors. (2026). *Undici 7.29.0* [Software release]. https://github.com/nodejs/undici/releases/tag/v7.29.0 + +npm, Inc. (2026). *npm ci*. npm Docs. https://docs.npmjs.com/cli/v11/commands/npm-ci/ + +npm, Inc. (2026). *package-lock.json*. npm Docs. https://docs.npmjs.com/cli/v11/configuring-npm/package-lock-json/ From 2e99f72d2859250a460bcadd6248b0ea93a0a8d0 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 17:12:46 +0900 Subject: [PATCH 017/161] docs(changelog): record coordinated security remediation --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 05f555100..1d9910145 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,10 @@ - Pinned npm `10.9.8` as the lockfile generator and made primary CI reject a different npm version or any package-lock-only replay diff. +### Fixed + +- Upgraded the local score PDF parser to `pdfjs-dist` 6.2.108, pinned Undici 7.29.0 across the workspace, and disabled PDF expression evaluation while preserving same-origin worker execution and npm-generated lock provenance. + ## [0.1.3] - 2026-04-29 ### Fixed From 8f50fe4ce8bf2440de6fbf55d48bc918bd311ffc Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 17:20:04 +0900 Subject: [PATCH 018/161] fix(security): anchor the Undici override to an exact root floor --- package.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 1ad0f15ec..2c3a4e945 100644 --- a/package.json +++ b/package.json @@ -41,11 +41,12 @@ "@eslint/js": "^10.0.1", "eslint-plugin-jsdoc": "^63.0.13", "react": "^19.2.4", - "react-dom": "^19.2.7" + "react-dom": "^19.2.7", + "undici": "7.29.0" }, "overrides": { "brace-expansion": "5.0.9", "postcss": "8.5.25", - "undici": "7.29.0" + "undici": "$undici" } } From e2c0c2dfee35c19d1e9156ad0a7d9324fdefd1fb Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 17:20:46 +0900 Subject: [PATCH 019/161] test(security): bind exact root floor and npm package locations --- .../test_high_security_dependency_baseline.py | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/services/analysis-engine/tests/test_high_security_dependency_baseline.py b/services/analysis-engine/tests/test_high_security_dependency_baseline.py index 01dba0ab8..0a9e81ab1 100644 --- a/services/analysis-engine/tests/test_high_security_dependency_baseline.py +++ b/services/analysis-engine/tests/test_high_security_dependency_baseline.py @@ -33,7 +33,8 @@ def test_manifests_pin_the_security_floors_without_semver_drift() -> None: root_manifest = _read_json("package.json") desktop_manifest = _read_json("apps/desktop/package.json") - assert root_manifest["overrides"]["undici"] == _UNDICI_VERSION # type: ignore[index] + assert root_manifest["devDependencies"]["undici"] == _UNDICI_VERSION # type: ignore[index] + assert root_manifest["overrides"]["undici"] == "$undici" # type: ignore[index] assert desktop_manifest["dependencies"]["pdfjs-dist"] == _PDFJS_VERSION # type: ignore[index] @@ -43,21 +44,24 @@ def test_lock_records_match_exact_registry_artifacts_and_preserve_peer_metadata( packages = lock_document["packages"] assert isinstance(packages, dict) + root_package = packages[""] + assert isinstance(root_package, dict) + assert root_package["devDependencies"]["undici"] == _UNDICI_VERSION # type: ignore[index] + desktop = packages["apps/desktop"] assert isinstance(desktop, dict) assert desktop["dependencies"]["pdfjs-dist"] == _PDFJS_VERSION # type: ignore[index] - pdfjs = packages["node_modules/pdfjs-dist"] - assert pdfjs == { - "version": _PDFJS_VERSION, - "resolved": ( - "https://registry.npmjs.org/pdfjs-dist/-/" - f"pdfjs-dist-{_PDFJS_VERSION}.tgz" - ), - "integrity": _PDFJS_INTEGRITY, - "license": "Apache-2.0", - "engines": {"node": ">=22.13.0 || >=24"}, - } + pdfjs = packages["apps/desktop/node_modules/pdfjs-dist"] + assert isinstance(pdfjs, dict) + assert pdfjs["version"] == _PDFJS_VERSION + assert pdfjs["resolved"] == ( + "https://registry.npmjs.org/pdfjs-dist/-/" + f"pdfjs-dist-{_PDFJS_VERSION}.tgz" + ) + assert pdfjs["integrity"] == _PDFJS_INTEGRITY + assert pdfjs["license"] == "Apache-2.0" + assert pdfjs["engines"] == {"node": ">=22.13.0 || >=24"} undici = packages["node_modules/undici"] assert isinstance(undici, dict) From dd93f962d367e3a5b56ee171708bf4877a054541 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 18:59:55 +0900 Subject: [PATCH 020/161] ci(pr783): import exact npm 10.9.8 lock artifact --- .../workflows/import-pr783-generated-lock.yml | 125 ++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 .github/workflows/import-pr783-generated-lock.yml diff --git a/.github/workflows/import-pr783-generated-lock.yml b/.github/workflows/import-pr783-generated-lock.yml new file mode 100644 index 000000000..c2ce31af9 --- /dev/null +++ b/.github/workflows/import-pr783-generated-lock.yml @@ -0,0 +1,125 @@ +name: Import PR 783 generated npm lock + +on: + push: + branches: + - fix/high-security-dependency-baseline + paths: + - .github/workflows/import-pr783-generated-lock.yml + +permissions: + contents: read + actions: read + +concurrency: + group: import-pr783-generated-lock + cancel-in-progress: false + +env: + GIT_CONFIG_COUNT: "1" + GIT_CONFIG_KEY_0: init.defaultBranch + GIT_CONFIG_VALUE_0: develop + EXPECTED_SOURCE_HEAD: e2c0c2dfee35c19d1e9156ad0a7d9324fdefd1fb + EXPECTED_NPM_VERSION: "10.9.8" + ARTIFACT_ID: "8989562185" + ARTIFACT_RUN_ID: "31161313485" + ARTIFACT_NAME: npm-lock-reproduction-e2c0c2dfee35c19d1e9156ad0a7d9324fdefd1fb + EXPECTED_LOCK_SHA256: 31dd2661eca864e3da46f86629a2535dc181d01449bd3a50fa3cdbd6c58e7971 + +jobs: + import-and-verify: + if: >- + github.repository == 'ContextualWisdomLab/bandscope' && + github.ref == 'refs/heads/fix/high-security-dependency-baseline' + permissions: + contents: write + actions: read + runs-on: ubuntu-24.04 + timeout-minutes: 45 + steps: + - name: Harden runner + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 + with: + egress-policy: audit + + - name: Checkout exact trigger without persisted credentials + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + ref: ${{ github.sha }} + fetch-depth: 2 + persist-credentials: false + + - name: Verify bounded trigger lineage + shell: bash --noprofile --norc -e -o pipefail {0} + run: | + test "$(git rev-parse HEAD)" = "$GITHUB_SHA" + test "$(git rev-parse HEAD^)" = "$EXPECTED_SOURCE_HEAD" + mapfile -t trigger_delta < <(git diff --name-only "$EXPECTED_SOURCE_HEAD" HEAD) + test "${#trigger_delta[@]}" -eq 1 + test "${trigger_delta[0]}" = ".github/workflows/import-pr783-generated-lock.yml" + + - name: Set up exact Node and npm generator + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: "22.22.3" + cache: npm + + - name: Download exact head-bound lock artifact + env: + GH_TOKEN: ${{ github.token }} + shell: bash --noprofile --norc -e -o pipefail {0} + run: | + test "$(npm --version)" = "$EXPECTED_NPM_VERSION" + mkdir -p "${RUNNER_TEMP}/pr783-lock" + curl --fail --silent --show-error --location \ + --retry 0 \ + --header "Accept: application/vnd.github+json" \ + --header "Authorization: Bearer ${GH_TOKEN}" \ + --header "X-GitHub-Api-Version: 2022-11-28" \ + "https://api.github.com/repos/${GITHUB_REPOSITORY}/actions/artifacts/${ARTIFACT_ID}/zip" \ + --output "${RUNNER_TEMP}/pr783-lock/artifact.zip" + mapfile -t archive_entries < <(zipinfo -1 "${RUNNER_TEMP}/pr783-lock/artifact.zip") + test "${#archive_entries[@]}" -eq 1 + test "${archive_entries[0]}" = "package-lock.json" + unzip -p "${RUNNER_TEMP}/pr783-lock/artifact.zip" package-lock.json \ + > "${RUNNER_TEMP}/pr783-lock/package-lock.json" + test "$(sha256sum "${RUNNER_TEMP}/pr783-lock/package-lock.json" | cut -d' ' -f1)" = "$EXPECTED_LOCK_SHA256" + mv "${RUNNER_TEMP}/pr783-lock/package-lock.json" package-lock.json + + - name: Verify exact generated lock and security baseline + shell: bash --noprofile --norc -e -o pipefail {0} + run: | + test "$(sha256sum package-lock.json | cut -d' ' -f1)" = "$EXPECTED_LOCK_SHA256" + npm install --package-lock-only --ignore-scripts --no-audit --no-fund + test "$(sha256sum package-lock.json | cut -d' ' -f1)" = "$EXPECTED_LOCK_SHA256" + npm ci --ignore-scripts --no-audit --no-fund + npm audit --workspaces --audit-level=high + npm run typecheck --workspace @bandscope/desktop + npm run lint --workspace @bandscope/desktop + npm exec --workspace @bandscope/desktop vitest run \ + src/features/score/pdfjs.test.ts \ + --coverage=false + git diff --check + + - name: Remove one-shot importer and publish verified lock + env: + EXPECTED_HEAD: ${{ github.sha }} + SOURCE_BRANCH: fix/high-security-dependency-baseline + PUSH_TOKEN: ${{ github.token }} + shell: bash --noprofile --norc -e -o pipefail {0} + run: | + rm .github/workflows/import-pr783-generated-lock.yml + test "$(git diff --name-only "$EXPECTED_SOURCE_HEAD" -- | sort)" = "package-lock.json" + test "$(sha256sum package-lock.json | cut -d' ' -f1)" = "$EXPECTED_LOCK_SHA256" + remote_head="$(git ls-remote origin "refs/heads/${SOURCE_BRANCH}" | cut -f1)" + test "$remote_head" = "$EXPECTED_HEAD" + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add package-lock.json .github/workflows/import-pr783-generated-lock.yml + git diff --cached --check + git commit -m "fix(security): import verified npm 10.9.8 lock" + auth_header="$(printf 'x-access-token:%s' "$PUSH_TOKEN" | base64 | tr -d '\n')" + echo "::add-mask::$auth_header" + git -c http.extraheader="AUTHORIZATION: basic ${auth_header}" \ + push --force-with-lease="refs/heads/${SOURCE_BRANCH}:${EXPECTED_HEAD}" \ + origin "HEAD:refs/heads/${SOURCE_BRANCH}" From dd8d1acda0a32ba02809b6a27cd9933815de03d6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 19:07:00 +0900 Subject: [PATCH 021/161] fix(score): align PDF.js boundary with 6.2.108 API --- apps/desktop/src/features/score/pdfjs.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/desktop/src/features/score/pdfjs.ts b/apps/desktop/src/features/score/pdfjs.ts index 007cd5727..e0ce7692d 100644 --- a/apps/desktop/src/features/score/pdfjs.ts +++ b/apps/desktop/src/features/score/pdfjs.ts @@ -22,13 +22,13 @@ export function configureScorePdfWorker(): void { * this helper never fetches arbitrary URLs. The bytes are copied before they * are handed to pdf.js because pdf.js transfers the underlying buffer to its * worker, which would otherwise detach the caller's copy and break retries. - * PDF expression evaluation remains disabled as defense in depth even when - * the installed pdf.js release includes the corresponding security patch. + * + * PDF.js 6.2.108 no longer exposes the legacy `isEvalSupported` initialization + * option. Security therefore relies on the patched parser release plus this + * narrow data-only, same-origin-worker boundary rather than an ignored and + * falsely reassuring unknown option. */ export function loadScorePdf(data: Uint8Array): PDFDocumentLoadingTask { configureScorePdfWorker(); - return getDocument({ - data: new Uint8Array(data), - isEvalSupported: false - }); + return getDocument({ data: new Uint8Array(data) }); } From 988dc1d30878994042cd72b9f7d5b6c38c059f3c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 19:08:02 +0900 Subject: [PATCH 022/161] test(score): prove the supported data-only PDF.js boundary --- apps/desktop/src/features/score/pdfjs.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/desktop/src/features/score/pdfjs.test.ts b/apps/desktop/src/features/score/pdfjs.test.ts index 49392ed66..b225830e5 100644 --- a/apps/desktop/src/features/score/pdfjs.test.ts +++ b/apps/desktop/src/features/score/pdfjs.test.ts @@ -26,15 +26,15 @@ describe("score PDF.js boundary", () => { expect(GlobalWorkerOptions.workerSrc).toBe("/assets/pdf.worker.min.mjs"); }); - it("copies validated bytes and disables PDF expression evaluation", () => { + it("copies validated bytes through the supported data-only API", () => { const source = new Uint8Array([0x25, 0x50, 0x44, 0x46]); loadScorePdf(source); expect(getDocument).toHaveBeenCalledTimes(1); const parameters = vi.mocked(getDocument).mock.calls[0]?.[0]; - expect(parameters).toMatchObject({ isEvalSupported: false }); - expect(parameters).toHaveProperty("data"); + expect(parameters).toBeTypeOf("object"); + expect(Object.keys(parameters as object)).toEqual(["data"]); const copiedBytes = (parameters as { data: Uint8Array }).data; expect(copiedBytes).toEqual(source); expect(copiedBytes).not.toBe(source); From a39e37f3eca2a185feddc142d5ea648c943ce9a5 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 19:09:48 +0900 Subject: [PATCH 023/161] docs(security): record the supported PDF.js 6.2.108 boundary --- docs/doctoring/high-security-pdf-http-baseline.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/docs/doctoring/high-security-pdf-http-baseline.md b/docs/doctoring/high-security-pdf-http-baseline.md index d7b4d4014..d83a63eb7 100644 --- a/docs/doctoring/high-security-pdf-http-baseline.md +++ b/docs/doctoring/high-security-pdf-http-baseline.md @@ -8,13 +8,13 @@ BandScope treats the PDF parser and its transitive HTTP client as one security-r - `undici` is pinned exactly to `7.29.0` through the root npm override; and - the complete npm workspace lock is generated only by the repository-pinned npm `10.9.8` workflow and imported unchanged from the workflow artifact. -The desktop PDF loader additionally passes `isEvalSupported: false` to `getDocument`. The dependency patch is the primary remediation; disabling expression evaluation is defense in depth and prevents a future regression or alternate vulnerable execution path from re-enabling dynamic PDF expression compilation. +PDF.js `6.2.108` no longer exposes the legacy `isEvalSupported` member in its public `DocumentInitParameters` contract, and `getDocument` no longer reads that member. BandScope therefore does not cast or pass an unknown option that would be ignored while creating false assurance. The primary remediation is the patched parser release, reinforced by a narrow data-only call, copied caller-owned bytes, and a same-origin bundled worker. ```mermaid flowchart LR A[Validated local PDF bytes] --> B[Copied Uint8Array] - B --> C[pdfjs-dist 6.2.108] - P[isEvalSupported false] --> C + B --> D[Data-only DocumentInitParameters] + D --> C[pdfjs-dist 6.2.108] C --> W[Same-origin bundled worker] W --> R[Canvas render] J[jsdom development path] --> U[undici 7.29.0 override] @@ -25,9 +25,9 @@ flowchart LR ## Threat boundary -The score viewer accepts only bytes already copied into the app-owned workspace through the native PDF intake boundary. It does not accept a URL and never uses a remote worker. This prevents a PDF from selecting an attacker-controlled fetch origin or script asset. +The score viewer accepts only bytes already copied into the app-owned workspace through the native PDF intake boundary. It does not accept a URL, credentials, custom request headers, or a remote worker. This prevents a PDF from selecting an attacker-controlled fetch origin or script asset. -PDF bytes remain untrusted after the native magic-byte, size, and path checks. Parser vulnerabilities, malformed object graphs, embedded actions, and expression compilation can still occur inside a syntactically valid PDF. The patched parser and explicit `isEvalSupported: false` therefore remain mandatory even for locally selected files. +PDF bytes remain untrusted after the native magic-byte, size, and path checks. Parser vulnerabilities, malformed object graphs, embedded actions, and resource-exhaustion paths can still occur inside a syntactically valid PDF. The patched parser, exact dependency lock, copied data-only input, same-origin worker, and existing native intake limits therefore remain mandatory for locally selected files. Undici is currently a development dependency reached through jsdom, but development and CI parsers process attacker-controlled fixtures, generated HTML, and network-like request bodies. A dev-only label does not make header injection, shared-cache disclosure, retry desynchronization, or cookie-attribute injection acceptable in the trusted build boundary. @@ -49,7 +49,8 @@ The lock contract requires the exact public-registry tarball and SHA-512 SRI for The merge gate includes: - exact manifest and lock artifact tests; -- a direct PDF.js wrapper test for copied bytes, the locally bundled worker, and `isEvalSupported: false`; +- a direct PDF.js wrapper test proving copied bytes, the locally bundled worker, and an exact data-only initialization object; +- TypeScript compilation against the installed PDF.js `DocumentInitParameters` rather than an unsafe cast; - valid and malformed local score-PDF component tests; - desktop lint, strict typecheck, complete measured tests, and production build; - Tauri/Rust checks and native PDF intake regressions; @@ -69,6 +70,8 @@ Rollback restores the previous desktop manifest, root override, complete lock, P GitHub. (2026). *PDF.js vulnerable to arbitrary JavaScript execution upon opening a malicious PDF* (GHSA-hq66-cqwq-w95j) [Security advisory]. https://github.com/advisories/GHSA-hq66-cqwq-w95j +Mozilla. (2026). *Document initialization parameters in PDF.js 6.2.108* [Source code]. GitHub. https://github.com/mozilla/pdf.js/blob/v6.2.108/src/display/api.js + Mozilla. (2026). *PDF.js 6.2.108* [Software release]. https://github.com/mozilla/pdf.js/releases/tag/v6.2.108 Node.js contributors. (2026). *Undici 7.29.0* [Software release]. https://github.com/nodejs/undici/releases/tag/v7.29.0 From 6b753e6b0bacbe250885c20a53728966b81e0009 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 19:11:29 +0900 Subject: [PATCH 024/161] docs(changelog): describe the supported patched PDF boundary --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d9910145..c746043fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ ### Fixed -- Upgraded the local score PDF parser to `pdfjs-dist` 6.2.108, pinned Undici 7.29.0 across the workspace, and disabled PDF expression evaluation while preserving same-origin worker execution and npm-generated lock provenance. +- Upgraded the local score PDF parser to `pdfjs-dist` 6.2.108, pinned Undici 7.29.0 across the workspace, and constrained PDF loading to copied in-memory bytes with a same-origin bundled worker and npm-generated lock provenance. ## [0.1.3] - 2026-04-29 From b773653f76fae5cfbda4a24548bffd4fedfe06ef Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 19:14:08 +0900 Subject: [PATCH 025/161] ci(pr783): rerun lock import after supported API repair --- .github/workflows/import-pr783-generated-lock.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/import-pr783-generated-lock.yml b/.github/workflows/import-pr783-generated-lock.yml index c2ce31af9..c9616f453 100644 --- a/.github/workflows/import-pr783-generated-lock.yml +++ b/.github/workflows/import-pr783-generated-lock.yml @@ -19,7 +19,7 @@ env: GIT_CONFIG_COUNT: "1" GIT_CONFIG_KEY_0: init.defaultBranch GIT_CONFIG_VALUE_0: develop - EXPECTED_SOURCE_HEAD: e2c0c2dfee35c19d1e9156ad0a7d9324fdefd1fb + EXPECTED_SOURCE_HEAD: 6b753e6b0bacbe250885c20a53728966b81e0009 EXPECTED_NPM_VERSION: "10.9.8" ARTIFACT_ID: "8989562185" ARTIFACT_RUN_ID: "31161313485" From 01cb39e9c9f7513fc3410f86b2f1e1332a969da4 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 19:19:01 +0900 Subject: [PATCH 026/161] ci(pr783): publish the verified lock from the bounded importer --- .github/workflows/import-pr783-generated-lock.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/import-pr783-generated-lock.yml b/.github/workflows/import-pr783-generated-lock.yml index c9616f453..f29616e91 100644 --- a/.github/workflows/import-pr783-generated-lock.yml +++ b/.github/workflows/import-pr783-generated-lock.yml @@ -20,6 +20,7 @@ env: GIT_CONFIG_KEY_0: init.defaultBranch GIT_CONFIG_VALUE_0: develop EXPECTED_SOURCE_HEAD: 6b753e6b0bacbe250885c20a53728966b81e0009 + EXPECTED_TRIGGER_PARENT: b773653f76fae5cfbda4a24548bffd4fedfe06ef EXPECTED_NPM_VERSION: "10.9.8" ARTIFACT_ID: "8989562185" ARTIFACT_RUN_ID: "31161313485" @@ -53,8 +54,8 @@ jobs: shell: bash --noprofile --norc -e -o pipefail {0} run: | test "$(git rev-parse HEAD)" = "$GITHUB_SHA" - test "$(git rev-parse HEAD^)" = "$EXPECTED_SOURCE_HEAD" - mapfile -t trigger_delta < <(git diff --name-only "$EXPECTED_SOURCE_HEAD" HEAD) + test "$(git rev-parse HEAD^)" = "$EXPECTED_TRIGGER_PARENT" + mapfile -t trigger_delta < <(git diff --name-only "$EXPECTED_TRIGGER_PARENT" HEAD) test "${#trigger_delta[@]}" -eq 1 test "${trigger_delta[0]}" = ".github/workflows/import-pr783-generated-lock.yml" @@ -109,7 +110,9 @@ jobs: shell: bash --noprofile --norc -e -o pipefail {0} run: | rm .github/workflows/import-pr783-generated-lock.yml - test "$(git diff --name-only "$EXPECTED_SOURCE_HEAD" -- | sort)" = "package-lock.json" + actual_delta="$(git diff --name-only "$EXPECTED_SOURCE_HEAD" -- | sort)" + expected_delta="$(printf '%s\n' .github/workflows/import-pr783-generated-lock.yml package-lock.json | sort)" + test "$actual_delta" = "$expected_delta" test "$(sha256sum package-lock.json | cut -d' ' -f1)" = "$EXPECTED_LOCK_SHA256" remote_head="$(git ls-remote origin "refs/heads/${SOURCE_BRANCH}" | cut -f1)" test "$remote_head" = "$EXPECTED_HEAD" From ad558ace02522f15c27705fff97c87c6661f0695 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 19:24:21 +0900 Subject: [PATCH 027/161] ci(pr783): fetch complete lineage for verified lock publication --- .github/workflows/import-pr783-generated-lock.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/import-pr783-generated-lock.yml b/.github/workflows/import-pr783-generated-lock.yml index f29616e91..762bb7cd9 100644 --- a/.github/workflows/import-pr783-generated-lock.yml +++ b/.github/workflows/import-pr783-generated-lock.yml @@ -20,7 +20,7 @@ env: GIT_CONFIG_KEY_0: init.defaultBranch GIT_CONFIG_VALUE_0: develop EXPECTED_SOURCE_HEAD: 6b753e6b0bacbe250885c20a53728966b81e0009 - EXPECTED_TRIGGER_PARENT: b773653f76fae5cfbda4a24548bffd4fedfe06ef + EXPECTED_TRIGGER_PARENT: 01cb39e9c9f7513fc3410f86b2f1e1332a969da4 EXPECTED_NPM_VERSION: "10.9.8" ARTIFACT_ID: "8989562185" ARTIFACT_RUN_ID: "31161313485" @@ -47,7 +47,7 @@ jobs: uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: ref: ${{ github.sha }} - fetch-depth: 2 + fetch-depth: 0 persist-credentials: false - name: Verify bounded trigger lineage From 83865dce76bf158ae444a23a85f7036ccfc9663b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 7 Aug 2026 10:25:15 +0000 Subject: [PATCH 028/161] fix(security): import verified npm 10.9.8 lock --- .../workflows/import-pr783-generated-lock.yml | 128 ------------------ package-lock.json | 35 ++--- 2 files changed, 18 insertions(+), 145 deletions(-) delete mode 100644 .github/workflows/import-pr783-generated-lock.yml diff --git a/.github/workflows/import-pr783-generated-lock.yml b/.github/workflows/import-pr783-generated-lock.yml deleted file mode 100644 index 762bb7cd9..000000000 --- a/.github/workflows/import-pr783-generated-lock.yml +++ /dev/null @@ -1,128 +0,0 @@ -name: Import PR 783 generated npm lock - -on: - push: - branches: - - fix/high-security-dependency-baseline - paths: - - .github/workflows/import-pr783-generated-lock.yml - -permissions: - contents: read - actions: read - -concurrency: - group: import-pr783-generated-lock - cancel-in-progress: false - -env: - GIT_CONFIG_COUNT: "1" - GIT_CONFIG_KEY_0: init.defaultBranch - GIT_CONFIG_VALUE_0: develop - EXPECTED_SOURCE_HEAD: 6b753e6b0bacbe250885c20a53728966b81e0009 - EXPECTED_TRIGGER_PARENT: 01cb39e9c9f7513fc3410f86b2f1e1332a969da4 - EXPECTED_NPM_VERSION: "10.9.8" - ARTIFACT_ID: "8989562185" - ARTIFACT_RUN_ID: "31161313485" - ARTIFACT_NAME: npm-lock-reproduction-e2c0c2dfee35c19d1e9156ad0a7d9324fdefd1fb - EXPECTED_LOCK_SHA256: 31dd2661eca864e3da46f86629a2535dc181d01449bd3a50fa3cdbd6c58e7971 - -jobs: - import-and-verify: - if: >- - github.repository == 'ContextualWisdomLab/bandscope' && - github.ref == 'refs/heads/fix/high-security-dependency-baseline' - permissions: - contents: write - actions: read - runs-on: ubuntu-24.04 - timeout-minutes: 45 - steps: - - name: Harden runner - uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 - with: - egress-policy: audit - - - name: Checkout exact trigger without persisted credentials - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - ref: ${{ github.sha }} - fetch-depth: 0 - persist-credentials: false - - - name: Verify bounded trigger lineage - shell: bash --noprofile --norc -e -o pipefail {0} - run: | - test "$(git rev-parse HEAD)" = "$GITHUB_SHA" - test "$(git rev-parse HEAD^)" = "$EXPECTED_TRIGGER_PARENT" - mapfile -t trigger_delta < <(git diff --name-only "$EXPECTED_TRIGGER_PARENT" HEAD) - test "${#trigger_delta[@]}" -eq 1 - test "${trigger_delta[0]}" = ".github/workflows/import-pr783-generated-lock.yml" - - - name: Set up exact Node and npm generator - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 - with: - node-version: "22.22.3" - cache: npm - - - name: Download exact head-bound lock artifact - env: - GH_TOKEN: ${{ github.token }} - shell: bash --noprofile --norc -e -o pipefail {0} - run: | - test "$(npm --version)" = "$EXPECTED_NPM_VERSION" - mkdir -p "${RUNNER_TEMP}/pr783-lock" - curl --fail --silent --show-error --location \ - --retry 0 \ - --header "Accept: application/vnd.github+json" \ - --header "Authorization: Bearer ${GH_TOKEN}" \ - --header "X-GitHub-Api-Version: 2022-11-28" \ - "https://api.github.com/repos/${GITHUB_REPOSITORY}/actions/artifacts/${ARTIFACT_ID}/zip" \ - --output "${RUNNER_TEMP}/pr783-lock/artifact.zip" - mapfile -t archive_entries < <(zipinfo -1 "${RUNNER_TEMP}/pr783-lock/artifact.zip") - test "${#archive_entries[@]}" -eq 1 - test "${archive_entries[0]}" = "package-lock.json" - unzip -p "${RUNNER_TEMP}/pr783-lock/artifact.zip" package-lock.json \ - > "${RUNNER_TEMP}/pr783-lock/package-lock.json" - test "$(sha256sum "${RUNNER_TEMP}/pr783-lock/package-lock.json" | cut -d' ' -f1)" = "$EXPECTED_LOCK_SHA256" - mv "${RUNNER_TEMP}/pr783-lock/package-lock.json" package-lock.json - - - name: Verify exact generated lock and security baseline - shell: bash --noprofile --norc -e -o pipefail {0} - run: | - test "$(sha256sum package-lock.json | cut -d' ' -f1)" = "$EXPECTED_LOCK_SHA256" - npm install --package-lock-only --ignore-scripts --no-audit --no-fund - test "$(sha256sum package-lock.json | cut -d' ' -f1)" = "$EXPECTED_LOCK_SHA256" - npm ci --ignore-scripts --no-audit --no-fund - npm audit --workspaces --audit-level=high - npm run typecheck --workspace @bandscope/desktop - npm run lint --workspace @bandscope/desktop - npm exec --workspace @bandscope/desktop vitest run \ - src/features/score/pdfjs.test.ts \ - --coverage=false - git diff --check - - - name: Remove one-shot importer and publish verified lock - env: - EXPECTED_HEAD: ${{ github.sha }} - SOURCE_BRANCH: fix/high-security-dependency-baseline - PUSH_TOKEN: ${{ github.token }} - shell: bash --noprofile --norc -e -o pipefail {0} - run: | - rm .github/workflows/import-pr783-generated-lock.yml - actual_delta="$(git diff --name-only "$EXPECTED_SOURCE_HEAD" -- | sort)" - expected_delta="$(printf '%s\n' .github/workflows/import-pr783-generated-lock.yml package-lock.json | sort)" - test "$actual_delta" = "$expected_delta" - test "$(sha256sum package-lock.json | cut -d' ' -f1)" = "$EXPECTED_LOCK_SHA256" - remote_head="$(git ls-remote origin "refs/heads/${SOURCE_BRANCH}" | cut -f1)" - test "$remote_head" = "$EXPECTED_HEAD" - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add package-lock.json .github/workflows/import-pr783-generated-lock.yml - git diff --cached --check - git commit -m "fix(security): import verified npm 10.9.8 lock" - auth_header="$(printf 'x-access-token:%s' "$PUSH_TOKEN" | base64 | tr -d '\n')" - echo "::add-mask::$auth_header" - git -c http.extraheader="AUTHORIZATION: basic ${auth_header}" \ - push --force-with-lease="refs/heads/${SOURCE_BRANCH}:${EXPECTED_HEAD}" \ - origin "HEAD:refs/heads/${SOURCE_BRANCH}" diff --git a/package-lock.json b/package-lock.json index cf1c991c1..792ca5179 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,8 @@ "@eslint/js": "^10.0.1", "eslint-plugin-jsdoc": "^63.0.13", "react": "^19.2.4", - "react-dom": "^19.2.7" + "react-dom": "^19.2.7", + "undici": "7.29.0" }, "engines": { "node": ">=22.13 <23" @@ -32,7 +33,7 @@ "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "lucide-react": "^1.24.0", - "pdfjs-dist": "6.1.200", + "pdfjs-dist": "6.2.108", "react": "^19.2.4", "react-dom": "^19.2.7", "sonner": "^2.0.7", @@ -212,6 +213,18 @@ "url": "https://opencollective.com/vitest" } }, + "apps/desktop/node_modules/pdfjs-dist": { + "version": "6.2.108", + "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-6.2.108.tgz", + "integrity": "sha512-YxFb+SQcodN2rnX9Tn3dHYlqfb7NjlzzfONPpJd+AKoKtUjEdevTfbC07d5TcczzOK6261auRkP/M8OBHs9vFQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=22.13.0 || >=24" + }, + "optionalDependencies": { + "@napi-rs/canvas": "^1.0.0" + } + }, "apps/desktop/node_modules/vitest": { "version": "4.1.10", "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.10.tgz", @@ -6367,18 +6380,6 @@ "node": ">= 14.16" } }, - "node_modules/pdfjs-dist": { - "version": "6.1.200", - "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-6.1.200.tgz", - "integrity": "sha512-o8MolyzirkkLrcdsae/HEOiIcXWI7DS5zGpvqW8xTC2YUsW30rltFw2bDGvw/fskUdEMrQm2br68jzDS5BH2vw==", - "license": "Apache-2.0", - "engines": { - "node": ">=22.13.0 || >=24" - }, - "optionalDependencies": { - "@napi-rs/canvas": "^1.0.0" - } - }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -7179,9 +7180,9 @@ } }, "node_modules/undici": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.28.0.tgz", - "integrity": "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.29.0.tgz", + "integrity": "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==", "dev": true, "license": "MIT", "engines": { From e6b48ca58e5c481e4b0bef8961338b5a3967e6c6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 19:29:20 +0900 Subject: [PATCH 029/161] test(ci): preserve canonical npm provenance formatting --- .../analysis-engine/tests/test_npm_toolchain_contract.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/services/analysis-engine/tests/test_npm_toolchain_contract.py b/services/analysis-engine/tests/test_npm_toolchain_contract.py index 30e00cbdd..bdfc1e45f 100644 --- a/services/analysis-engine/tests/test_npm_toolchain_contract.py +++ b/services/analysis-engine/tests/test_npm_toolchain_contract.py @@ -51,7 +51,10 @@ def test_primary_ci_proves_exact_npm_before_install_and_lock_reproduction() -> N assert "--no-audit" in workflow assert "--no-fund" in workflow assert "actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a" in workflow - assert "npm-lock-reproduction-${{ github.event.pull_request.head.sha || github.sha }}" in workflow + assert ( + "npm-lock-reproduction-${{ github.event.pull_request.head.sha || github.sha }}" + in workflow + ) assert "if-no-files-found: error" in workflow assert "git diff --exit-code -- package-lock.json" in workflow From 3edf17356cea74b8fceac50fed8fb3b958dfc1ac Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 19:48:49 +0900 Subject: [PATCH 030/161] ci(pr783): diagnose Ruff import ordering --- .../workflows/diagnose-pr783-ruff-imports.yml | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 .github/workflows/diagnose-pr783-ruff-imports.yml diff --git a/.github/workflows/diagnose-pr783-ruff-imports.yml b/.github/workflows/diagnose-pr783-ruff-imports.yml new file mode 100644 index 000000000..f800fdaae --- /dev/null +++ b/.github/workflows/diagnose-pr783-ruff-imports.yml @@ -0,0 +1,64 @@ +name: Diagnose PR 783 Ruff import ordering + +on: + push: + branches: + - fix/high-security-dependency-baseline + paths: + - .github/workflows/diagnose-pr783-ruff-imports.yml + +permissions: + contents: read + +concurrency: + group: diagnose-pr783-ruff-imports + cancel-in-progress: false + +env: + GIT_CONFIG_COUNT: "1" + GIT_CONFIG_KEY_0: init.defaultBranch + GIT_CONFIG_VALUE_0: develop + +jobs: + diagnose: + if: >- + github.repository == 'ContextualWisdomLab/bandscope' && + github.ref == 'refs/heads/fix/high-security-dependency-baseline' + runs-on: ubuntu-24.04 + timeout-minutes: 15 + steps: + - name: Harden runner + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 + with: + egress-policy: audit + + - name: Checkout exact trigger + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + ref: ${{ github.sha }} + persist-credentials: false + + - name: Set up Python + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.0.0 + with: + python-version: "3.12" + + - name: Set up uv + uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2 + with: + version: "0.8.6" + enable-cache: false + + - name: Print Ruff's exact import repair + shell: bash --noprofile --norc -e -o pipefail {0} + run: | + uv sync --project services/analysis-engine --group dev --frozen + cd services/analysis-engine + uv run ruff check \ + tests/test_high_security_dependency_baseline.py \ + tests/test_npm_toolchain_contract.py \ + --fix-only + git diff -- \ + tests/test_high_security_dependency_baseline.py \ + tests/test_npm_toolchain_contract.py + test -n "$(git diff --name-only -- tests/test_high_security_dependency_baseline.py tests/test_npm_toolchain_contract.py)" From 234521906a99fb786847cfacd0425f9527bd455b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 19:53:39 +0900 Subject: [PATCH 031/161] style(ci): normalize security test imports --- .../tests/test_high_security_dependency_baseline.py | 1 - 1 file changed, 1 deletion(-) diff --git a/services/analysis-engine/tests/test_high_security_dependency_baseline.py b/services/analysis-engine/tests/test_high_security_dependency_baseline.py index 0a9e81ab1..1df9d6ab2 100644 --- a/services/analysis-engine/tests/test_high_security_dependency_baseline.py +++ b/services/analysis-engine/tests/test_high_security_dependency_baseline.py @@ -5,7 +5,6 @@ import json from pathlib import Path - _REPOSITORY_ROOT = Path(__file__).resolve().parents[3] _PDFJS_VERSION = "6.2.108" _UNDICI_VERSION = "7.29.0" From dc7e8b4aa4b18e3a20db3eab9b65fbd71503417d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 19:54:37 +0900 Subject: [PATCH 032/161] style(ci): normalize npm provenance test imports --- services/analysis-engine/tests/test_npm_toolchain_contract.py | 1 - 1 file changed, 1 deletion(-) diff --git a/services/analysis-engine/tests/test_npm_toolchain_contract.py b/services/analysis-engine/tests/test_npm_toolchain_contract.py index bdfc1e45f..72a34305a 100644 --- a/services/analysis-engine/tests/test_npm_toolchain_contract.py +++ b/services/analysis-engine/tests/test_npm_toolchain_contract.py @@ -5,7 +5,6 @@ import json from pathlib import Path - _REPOSITORY_ROOT = Path(__file__).resolve().parents[3] _EXPECTED_NPM_VERSION = "10.9.8" _EXPECTED_NODE_VERSION = "22.22.3" From c5ee630fd73decab457450d629d11aecb756637a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 19:55:01 +0900 Subject: [PATCH 033/161] chore(ci): remove completed Ruff diagnostic --- .../workflows/diagnose-pr783-ruff-imports.yml | 64 ------------------- 1 file changed, 64 deletions(-) delete mode 100644 .github/workflows/diagnose-pr783-ruff-imports.yml diff --git a/.github/workflows/diagnose-pr783-ruff-imports.yml b/.github/workflows/diagnose-pr783-ruff-imports.yml deleted file mode 100644 index f800fdaae..000000000 --- a/.github/workflows/diagnose-pr783-ruff-imports.yml +++ /dev/null @@ -1,64 +0,0 @@ -name: Diagnose PR 783 Ruff import ordering - -on: - push: - branches: - - fix/high-security-dependency-baseline - paths: - - .github/workflows/diagnose-pr783-ruff-imports.yml - -permissions: - contents: read - -concurrency: - group: diagnose-pr783-ruff-imports - cancel-in-progress: false - -env: - GIT_CONFIG_COUNT: "1" - GIT_CONFIG_KEY_0: init.defaultBranch - GIT_CONFIG_VALUE_0: develop - -jobs: - diagnose: - if: >- - github.repository == 'ContextualWisdomLab/bandscope' && - github.ref == 'refs/heads/fix/high-security-dependency-baseline' - runs-on: ubuntu-24.04 - timeout-minutes: 15 - steps: - - name: Harden runner - uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 - with: - egress-policy: audit - - - name: Checkout exact trigger - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - ref: ${{ github.sha }} - persist-credentials: false - - - name: Set up Python - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.0.0 - with: - python-version: "3.12" - - - name: Set up uv - uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2 - with: - version: "0.8.6" - enable-cache: false - - - name: Print Ruff's exact import repair - shell: bash --noprofile --norc -e -o pipefail {0} - run: | - uv sync --project services/analysis-engine --group dev --frozen - cd services/analysis-engine - uv run ruff check \ - tests/test_high_security_dependency_baseline.py \ - tests/test_npm_toolchain_contract.py \ - --fix-only - git diff -- \ - tests/test_high_security_dependency_baseline.py \ - tests/test_npm_toolchain_contract.py - test -n "$(git diff --name-only -- tests/test_high_security_dependency_baseline.py tests/test_npm_toolchain_contract.py)" From f0c9ad11566b5b57f09eac56289647a4560d4b73 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 20:13:19 +0900 Subject: [PATCH 034/161] ci(pr783): finalize exact Ruff formatting --- .../finalize-pr783-python-format.yml | 130 ++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 .github/workflows/finalize-pr783-python-format.yml diff --git a/.github/workflows/finalize-pr783-python-format.yml b/.github/workflows/finalize-pr783-python-format.yml new file mode 100644 index 000000000..14f869cb4 --- /dev/null +++ b/.github/workflows/finalize-pr783-python-format.yml @@ -0,0 +1,130 @@ +name: Finalize PR 783 Python formatting + +on: + push: + branches: + - fix/high-security-dependency-baseline + paths: + - .github/workflows/finalize-pr783-python-format.yml + +permissions: + contents: read + +concurrency: + group: finalize-pr783-python-format + cancel-in-progress: false + +env: + GIT_CONFIG_COUNT: "1" + GIT_CONFIG_KEY_0: init.defaultBranch + GIT_CONFIG_VALUE_0: develop + EXPECTED_SOURCE_HEAD: c5ee630fd73decab457450d629d11aecb756637a + +jobs: + format-verify-publish: + if: >- + github.repository == 'ContextualWisdomLab/bandscope' && + github.ref == 'refs/heads/fix/high-security-dependency-baseline' + permissions: + contents: write + runs-on: ubuntu-24.04 + timeout-minutes: 45 + steps: + - name: Harden runner + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 + with: + egress-policy: audit + + - name: Checkout exact trigger without persisted credentials + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + ref: ${{ github.sha }} + fetch-depth: 0 + persist-credentials: false + + - name: Verify bounded trigger lineage + shell: bash --noprofile --norc -e -o pipefail {0} + run: | + test "$(git rev-parse HEAD)" = "$GITHUB_SHA" + test "$(git rev-parse HEAD^)" = "$EXPECTED_SOURCE_HEAD" + mapfile -t trigger_delta < <(git diff --name-only "$EXPECTED_SOURCE_HEAD" HEAD) + test "${#trigger_delta[@]}" -eq 1 + test "${trigger_delta[0]}" = ".github/workflows/finalize-pr783-python-format.yml" + + - name: Set up Node and npm + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: "22.22.3" + cache: npm + + - name: Set up Python + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.0.0 + with: + python-version: "3.12" + + - name: Set up uv + uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2 + with: + version: "0.8.6" + enable-cache: false + + - name: Install Rust stable + shell: bash --noprofile --norc -e -o pipefail {0} + run: rustup toolchain install stable --profile minimal + + - name: Install exact dependencies + shell: bash --noprofile --norc -e -o pipefail {0} + run: | + test "$(npm --version)" = "10.9.8" + npm ci + uv sync --project services/analysis-engine --group dev --frozen + + - name: Apply Ruff's exact formatter result + shell: bash --noprofile --norc -e -o pipefail {0} + run: | + cd services/analysis-engine + uv run ruff format \ + tests/test_high_security_dependency_baseline.py \ + tests/test_npm_toolchain_contract.py + uv run ruff format --check \ + tests/test_high_security_dependency_baseline.py \ + tests/test_npm_toolchain_contract.py + uv run ruff check \ + tests/test_high_security_dependency_baseline.py \ + tests/test_npm_toolchain_contract.py + cd ../.. + actual_delta="$(git diff --name-only -- | sort)" + expected_delta="$(printf '%s\n' services/analysis-engine/tests/test_high_security_dependency_baseline.py services/analysis-engine/tests/test_npm_toolchain_contract.py | sort)" + test "$actual_delta" = "$expected_delta" + git diff --check + + - name: Run the complete release harness + shell: bash --noprofile --norc -e -o pipefail {0} + run: ./scripts/harness/quickcheck.sh + + - name: Remove one-shot formatter and publish verified source + env: + EXPECTED_HEAD: ${{ github.sha }} + SOURCE_BRANCH: fix/high-security-dependency-baseline + PUSH_TOKEN: ${{ github.token }} + shell: bash --noprofile --norc -e -o pipefail {0} + run: | + rm .github/workflows/finalize-pr783-python-format.yml + actual_delta="$(git diff --name-only "$EXPECTED_SOURCE_HEAD" -- | sort)" + expected_delta="$(printf '%s\n' \ + .github/workflows/finalize-pr783-python-format.yml \ + services/analysis-engine/tests/test_high_security_dependency_baseline.py \ + services/analysis-engine/tests/test_npm_toolchain_contract.py | sort)" + test "$actual_delta" = "$expected_delta" + remote_head="$(git ls-remote origin "refs/heads/${SOURCE_BRANCH}" | cut -f1)" + test "$remote_head" = "$EXPECTED_HEAD" + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add -A + git diff --cached --check + git commit -m "style(ci): apply canonical Ruff formatting" + auth_header="$(printf 'x-access-token:%s' "$PUSH_TOKEN" | base64 | tr -d '\n')" + echo "::add-mask::$auth_header" + git -c http.extraheader="AUTHORIZATION: basic ${auth_header}" \ + push --force-with-lease="refs/heads/${SOURCE_BRANCH}:${EXPECTED_HEAD}" \ + origin "HEAD:refs/heads/${SOURCE_BRANCH}" From 102a89f91d8e4418a4af489b82bd19d4fe1bd77f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 20:16:52 +0900 Subject: [PATCH 035/161] chore(ci): remove temporary branch writer --- .../finalize-pr783-python-format.yml | 130 ------------------ 1 file changed, 130 deletions(-) delete mode 100644 .github/workflows/finalize-pr783-python-format.yml diff --git a/.github/workflows/finalize-pr783-python-format.yml b/.github/workflows/finalize-pr783-python-format.yml deleted file mode 100644 index 14f869cb4..000000000 --- a/.github/workflows/finalize-pr783-python-format.yml +++ /dev/null @@ -1,130 +0,0 @@ -name: Finalize PR 783 Python formatting - -on: - push: - branches: - - fix/high-security-dependency-baseline - paths: - - .github/workflows/finalize-pr783-python-format.yml - -permissions: - contents: read - -concurrency: - group: finalize-pr783-python-format - cancel-in-progress: false - -env: - GIT_CONFIG_COUNT: "1" - GIT_CONFIG_KEY_0: init.defaultBranch - GIT_CONFIG_VALUE_0: develop - EXPECTED_SOURCE_HEAD: c5ee630fd73decab457450d629d11aecb756637a - -jobs: - format-verify-publish: - if: >- - github.repository == 'ContextualWisdomLab/bandscope' && - github.ref == 'refs/heads/fix/high-security-dependency-baseline' - permissions: - contents: write - runs-on: ubuntu-24.04 - timeout-minutes: 45 - steps: - - name: Harden runner - uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 - with: - egress-policy: audit - - - name: Checkout exact trigger without persisted credentials - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - ref: ${{ github.sha }} - fetch-depth: 0 - persist-credentials: false - - - name: Verify bounded trigger lineage - shell: bash --noprofile --norc -e -o pipefail {0} - run: | - test "$(git rev-parse HEAD)" = "$GITHUB_SHA" - test "$(git rev-parse HEAD^)" = "$EXPECTED_SOURCE_HEAD" - mapfile -t trigger_delta < <(git diff --name-only "$EXPECTED_SOURCE_HEAD" HEAD) - test "${#trigger_delta[@]}" -eq 1 - test "${trigger_delta[0]}" = ".github/workflows/finalize-pr783-python-format.yml" - - - name: Set up Node and npm - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 - with: - node-version: "22.22.3" - cache: npm - - - name: Set up Python - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.0.0 - with: - python-version: "3.12" - - - name: Set up uv - uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2 - with: - version: "0.8.6" - enable-cache: false - - - name: Install Rust stable - shell: bash --noprofile --norc -e -o pipefail {0} - run: rustup toolchain install stable --profile minimal - - - name: Install exact dependencies - shell: bash --noprofile --norc -e -o pipefail {0} - run: | - test "$(npm --version)" = "10.9.8" - npm ci - uv sync --project services/analysis-engine --group dev --frozen - - - name: Apply Ruff's exact formatter result - shell: bash --noprofile --norc -e -o pipefail {0} - run: | - cd services/analysis-engine - uv run ruff format \ - tests/test_high_security_dependency_baseline.py \ - tests/test_npm_toolchain_contract.py - uv run ruff format --check \ - tests/test_high_security_dependency_baseline.py \ - tests/test_npm_toolchain_contract.py - uv run ruff check \ - tests/test_high_security_dependency_baseline.py \ - tests/test_npm_toolchain_contract.py - cd ../.. - actual_delta="$(git diff --name-only -- | sort)" - expected_delta="$(printf '%s\n' services/analysis-engine/tests/test_high_security_dependency_baseline.py services/analysis-engine/tests/test_npm_toolchain_contract.py | sort)" - test "$actual_delta" = "$expected_delta" - git diff --check - - - name: Run the complete release harness - shell: bash --noprofile --norc -e -o pipefail {0} - run: ./scripts/harness/quickcheck.sh - - - name: Remove one-shot formatter and publish verified source - env: - EXPECTED_HEAD: ${{ github.sha }} - SOURCE_BRANCH: fix/high-security-dependency-baseline - PUSH_TOKEN: ${{ github.token }} - shell: bash --noprofile --norc -e -o pipefail {0} - run: | - rm .github/workflows/finalize-pr783-python-format.yml - actual_delta="$(git diff --name-only "$EXPECTED_SOURCE_HEAD" -- | sort)" - expected_delta="$(printf '%s\n' \ - .github/workflows/finalize-pr783-python-format.yml \ - services/analysis-engine/tests/test_high_security_dependency_baseline.py \ - services/analysis-engine/tests/test_npm_toolchain_contract.py | sort)" - test "$actual_delta" = "$expected_delta" - remote_head="$(git ls-remote origin "refs/heads/${SOURCE_BRANCH}" | cut -f1)" - test "$remote_head" = "$EXPECTED_HEAD" - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add -A - git diff --cached --check - git commit -m "style(ci): apply canonical Ruff formatting" - auth_header="$(printf 'x-access-token:%s' "$PUSH_TOKEN" | base64 | tr -d '\n')" - echo "::add-mask::$auth_header" - git -c http.extraheader="AUTHORIZATION: basic ${auth_header}" \ - push --force-with-lease="refs/heads/${SOURCE_BRANCH}:${EXPECTED_HEAD}" \ - origin "HEAD:refs/heads/${SOURCE_BRANCH}" From d4887ec31bd6f48475ef5ac7832946649721db74 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 20:29:43 +0900 Subject: [PATCH 036/161] style(test): apply Ruff formatting to security contracts --- .../tests/test_high_security_dependency_baseline.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/services/analysis-engine/tests/test_high_security_dependency_baseline.py b/services/analysis-engine/tests/test_high_security_dependency_baseline.py index 1df9d6ab2..7be5df944 100644 --- a/services/analysis-engine/tests/test_high_security_dependency_baseline.py +++ b/services/analysis-engine/tests/test_high_security_dependency_baseline.py @@ -20,9 +20,7 @@ def _read_json(relative_path: str) -> dict[str, object]: """Return one repository JSON document as a mapping.""" - document = json.loads( - (_REPOSITORY_ROOT / relative_path).read_text(encoding="utf-8") - ) + document = json.loads((_REPOSITORY_ROOT / relative_path).read_text(encoding="utf-8")) assert isinstance(document, dict) return document @@ -65,9 +63,7 @@ def test_lock_records_match_exact_registry_artifacts_and_preserve_peer_metadata( undici = packages["node_modules/undici"] assert isinstance(undici, dict) assert undici["version"] == _UNDICI_VERSION - assert undici["resolved"] == ( - "https://registry.npmjs.org/undici/-/undici-7.29.0.tgz" - ) + assert undici["resolved"] == "https://registry.npmjs.org/undici/-/undici-7.29.0.tgz" assert undici["integrity"] == _UNDICI_INTEGRITY esbuild_locations = { From 459abdd9b30ee32adb60ecb66f0efae8ac25219b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 20:30:19 +0900 Subject: [PATCH 037/161] style(test): finish Ruff formatting for npm provenance --- .../tests/test_npm_toolchain_contract.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/services/analysis-engine/tests/test_npm_toolchain_contract.py b/services/analysis-engine/tests/test_npm_toolchain_contract.py index 72a34305a..5e9a65dbc 100644 --- a/services/analysis-engine/tests/test_npm_toolchain_contract.py +++ b/services/analysis-engine/tests/test_npm_toolchain_contract.py @@ -12,9 +12,7 @@ def _root_manifest() -> dict[str, object]: """Return the checked-in root package manifest as a JSON object.""" - manifest = json.loads( - (_REPOSITORY_ROOT / "package.json").read_text(encoding="utf-8") - ) + manifest = json.loads((_REPOSITORY_ROOT / "package.json").read_text(encoding="utf-8")) assert isinstance(manifest, dict) return manifest @@ -36,9 +34,7 @@ def test_root_manifest_pins_the_lockfile_generator_and_fails_on_drift() -> None: def test_primary_ci_proves_exact_npm_before_install_and_lock_reproduction() -> None: """Keep the clean installer and lock reproduction on one explicit toolchain.""" - workflow = (_REPOSITORY_ROOT / ".github" / "workflows" / "ci.yml").read_text( - encoding="utf-8" - ) + workflow = (_REPOSITORY_ROOT / ".github" / "workflows" / "ci.yml").read_text(encoding="utf-8") assert f'node-version: "{_EXPECTED_NODE_VERSION}"' in workflow assert f'EXPECTED_NPM_VERSION: "{_EXPECTED_NPM_VERSION}"' in workflow @@ -60,9 +56,7 @@ def test_primary_ci_proves_exact_npm_before_install_and_lock_reproduction() -> N def test_root_lock_uses_the_supported_location_keyed_format() -> None: """Require the npm-v9-and-newer lock format used by the pinned generator.""" - lock_document = json.loads( - (_REPOSITORY_ROOT / "package-lock.json").read_text(encoding="utf-8") - ) + lock_document = json.loads((_REPOSITORY_ROOT / "package-lock.json").read_text(encoding="utf-8")) assert lock_document["lockfileVersion"] == 3 assert isinstance(lock_document["packages"], dict) From ace4257be06754d78b6f6eca2207d7cdd1bf52e5 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 03:07:40 +0900 Subject: [PATCH 038/161] fix(security): refresh vulnerable npm transitive pins --- package-lock.json | 3062 +++++++++++++++++++++++---------------------- 1 file changed, 1573 insertions(+), 1489 deletions(-) diff --git a/package-lock.json b/package-lock.json index 792ca5179..973344f3b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,12 +11,16 @@ "apps/*", "packages/*" ], + "dependencies": { + "nanoid": "^3.3.18", + "pdfjs-dist": "^6.2.108", + "undici": "^7.29.0" + }, "devDependencies": { "@eslint/js": "^10.0.1", "eslint-plugin-jsdoc": "^63.0.13", "react": "^19.2.4", - "react-dom": "^19.2.7", - "undici": "7.29.0" + "react-dom": "^19.2.7" }, "engines": { "node": ">=22.13 <23" @@ -61,264 +65,10 @@ "vitest": "^4.1.10" } }, - "apps/desktop/node_modules/@types/react-dom": { - "version": "19.2.3", - "dev": true, - "license": "MIT", - "peerDependencies": { - "@types/react": "^19.2.0" - } - }, - "apps/desktop/node_modules/@vitest/coverage-v8": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.10.tgz", - "integrity": "sha512-IM49HmthevbgAO4anp1hwtoT9wYe59w0LR00gr+eagHE+ZJ5lK4sLPeO0ubgoJcwLk6dehU3R24N+FbEEKDc8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@bcoe/v8-coverage": "^1.0.2", - "@vitest/utils": "4.1.10", - "ast-v8-to-istanbul": "^1.0.0", - "istanbul-lib-coverage": "^3.2.2", - "istanbul-lib-report": "^3.0.1", - "istanbul-reports": "^3.2.0", - "magicast": "^0.5.2", - "obug": "^2.1.1", - "std-env": "^4.0.0-rc.1", - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "@vitest/browser": "4.1.10", - "vitest": "4.1.10" - }, - "peerDependenciesMeta": { - "@vitest/browser": { - "optional": true - } - } - }, - "apps/desktop/node_modules/@vitest/expect": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.10.tgz", - "integrity": "sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@standard-schema/spec": "^1.1.0", - "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.10", - "@vitest/utils": "4.1.10", - "chai": "^6.2.2", - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "apps/desktop/node_modules/@vitest/mocker": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.10.tgz", - "integrity": "sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/spy": "4.1.10", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.21" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "msw": "^2.4.9", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "msw": { - "optional": true - }, - "vite": { - "optional": true - } - } - }, - "apps/desktop/node_modules/@vitest/pretty-format": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.10.tgz", - "integrity": "sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "apps/desktop/node_modules/@vitest/runner": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.10.tgz", - "integrity": "sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/utils": "4.1.10", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "apps/desktop/node_modules/@vitest/snapshot": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.10.tgz", - "integrity": "sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.1.10", - "@vitest/utils": "4.1.10", - "magic-string": "^0.30.21", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "apps/desktop/node_modules/@vitest/spy": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.10.tgz", - "integrity": "sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "apps/desktop/node_modules/@vitest/utils": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.10.tgz", - "integrity": "sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.1.10", - "convert-source-map": "^2.0.0", - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "apps/desktop/node_modules/pdfjs-dist": { - "version": "6.2.108", - "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-6.2.108.tgz", - "integrity": "sha512-YxFb+SQcodN2rnX9Tn3dHYlqfb7NjlzzfONPpJd+AKoKtUjEdevTfbC07d5TcczzOK6261auRkP/M8OBHs9vFQ==", - "license": "Apache-2.0", - "engines": { - "node": ">=22.13.0 || >=24" - }, - "optionalDependencies": { - "@napi-rs/canvas": "^1.0.0" - } - }, - "apps/desktop/node_modules/vitest": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.10.tgz", - "integrity": "sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/expect": "4.1.10", - "@vitest/mocker": "4.1.10", - "@vitest/pretty-format": "4.1.10", - "@vitest/runner": "4.1.10", - "@vitest/snapshot": "4.1.10", - "@vitest/spy": "4.1.10", - "@vitest/utils": "4.1.10", - "es-module-lexer": "^2.0.0", - "expect-type": "^1.3.0", - "magic-string": "^0.30.21", - "obug": "^2.1.1", - "pathe": "^2.0.3", - "picomatch": "^4.0.3", - "std-env": "^4.0.0-rc.1", - "tinybench": "^2.9.0", - "tinyexec": "^1.0.2", - "tinyglobby": "^0.2.15", - "tinyrainbow": "^3.1.0", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", - "why-is-node-running": "^2.3.0" - }, - "bin": { - "vitest": "vitest.mjs" - }, - "engines": { - "node": "^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "@edge-runtime/vm": "*", - "@opentelemetry/api": "^1.9.0", - "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.1.10", - "@vitest/browser-preview": "4.1.10", - "@vitest/browser-webdriverio": "4.1.10", - "@vitest/coverage-istanbul": "4.1.10", - "@vitest/coverage-v8": "4.1.10", - "@vitest/ui": "4.1.10", - "happy-dom": "*", - "jsdom": "*", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "@edge-runtime/vm": { - "optional": true - }, - "@opentelemetry/api": { - "optional": true - }, - "@types/node": { - "optional": true - }, - "@vitest/browser-playwright": { - "optional": true - }, - "@vitest/browser-preview": { - "optional": true - }, - "@vitest/browser-webdriverio": { - "optional": true - }, - "@vitest/coverage-istanbul": { - "optional": true - }, - "@vitest/coverage-v8": { - "optional": true - }, - "@vitest/ui": { - "optional": true - }, - "happy-dom": { - "optional": true - }, - "jsdom": { - "optional": true - }, - "vite": { - "optional": false - } - } - }, "node_modules/@adobe/css-tools": { - "version": "4.4.4", - "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz", - "integrity": "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.5.0.tgz", + "integrity": "sha512-6OzddxPio9UiWTCemp4N8cYLV2ZN1ncRnV1cVGtve7dhPOtRkleRyx32GQCYSwDYgaHU3USMm84tNsvKzRCa1Q==", "dev": true, "license": "MIT" }, @@ -440,14 +190,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", - "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.29.7", - "@babel/types": "^7.29.7", + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -580,13 +330,13 @@ } }, "node_modules/@babel/parser": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", - "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.29.7" + "@babel/types": "^7.29.8" }, "bin": { "parser": "bin/babel-parser.js" @@ -596,9 +346,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", - "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -620,18 +370,18 @@ } }, "node_modules/@babel/traverse": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", - "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz", + "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.29.7", - "@babel/generator": "^7.29.7", + "@babel/generator": "^7.29.8", "@babel/helper-globals": "^7.29.7", - "@babel/parser": "^7.29.7", + "@babel/parser": "^7.29.8", "@babel/template": "^7.29.7", - "@babel/types": "^7.29.7", + "@babel/types": "^7.29.8", "debug": "^4.3.1" }, "engines": { @@ -639,9 +389,9 @@ } }, "node_modules/@babel/types": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", - "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", "dev": true, "license": "MIT", "dependencies": { @@ -661,15 +411,15 @@ "link": true }, "node_modules/@base-ui/react": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@base-ui/react/-/react-1.5.0.tgz", - "integrity": "sha512-z1gSAlced1yY+iM+mHDEtIkD8UI3Ebs52MuBPxvV6f5hRutk+xvCH/wuB7hDqDzK9JG5FoMz5nhrqtSs1wjt1A==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@base-ui/react/-/react-1.7.0.tgz", + "integrity": "sha512-j+8QjX44C32jrXD/qyEAGpFr70FRpGL2CY61mQd9nBPWN737CK0xxD1ceJ055rW4RtdvFDT1e7otzdlfxvsYug==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.29.2", - "@base-ui/utils": "0.2.9", - "@floating-ui/react-dom": "^2.1.8", - "@floating-ui/utils": "^0.2.11", + "@base-ui/utils": "0.3.2", + "@floating-ui/react-dom": "^2.1.9", + "@floating-ui/utils": "^0.2.12", "use-sync-external-store": "^1.6.0" }, "engines": { @@ -699,14 +449,14 @@ } }, "node_modules/@base-ui/utils": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/@base-ui/utils/-/utils-0.2.9.tgz", - "integrity": "sha512-x/PDDCYzoqPpjrdyb3VcyylTI2IjUXEtYDGi5foh7KsnmNJIIaVwA2GLgDH1dps1GgXiJbA60hM+AyuTfQzIvw==", + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@base-ui/utils/-/utils-0.3.2.tgz", + "integrity": "sha512-oWy1aq/I2GmYjpl4PhEAhzflF8VPGKgZeq0xAWTbfD5KBWyxcN0ZP2+WHSUm/5Z6lVMBDLReLcoXwSYoRc/zNQ==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.29.2", - "@floating-ui/utils": "^0.2.11", - "reselect": "^5.1.1", + "@floating-ui/utils": "^0.2.12", + "reselect": "^5.2.0", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { @@ -744,9 +494,9 @@ } }, "node_modules/@csstools/color-helpers": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.2.tgz", - "integrity": "sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.1.0.tgz", + "integrity": "sha512-064IFJdjTfUqnjpCVpMOdbr8FLQBhinbZj6yRv2An2E41O/pLEXqfFRWqGq/SxlE5PEUYTlvWsG2r8MswAVvkg==", "dev": true, "funding": [ { @@ -764,9 +514,9 @@ } }, "node_modules/@csstools/css-calc": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.2.1.tgz", - "integrity": "sha512-DtdHlgXh5ZkA43cwBcAm+huzgJiwx3ZTWVjBs94kwz2xKqSimDA3lBgCjphYgwgVUMWatSM0pDd8TILB1yrVVg==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.3.0.tgz", + "integrity": "sha512-c5ihYsPkdG6JCkU2zTMm4+k6r7RXuGxtWYhu5DHMIiF1FHzrfmHL5so11AoFpUv/tu61xfcmT4AmKoFfMPoqdQ==", "dev": true, "funding": [ { @@ -788,9 +538,9 @@ } }, "node_modules/@csstools/css-color-parser": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.1.3.tgz", - "integrity": "sha512-DOgvIPkikIOixQRlD4YF31VN6fLLUTdrzhfRbis8vm0kMTgIbEPX0Ip/YX9fOeV9iywAS4sUUbTclpan7yYP8Q==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.1.10.tgz", + "integrity": "sha512-UZhQLIUyJaaMepqehrCODwCg2KW25vFvLWBmqYFaPclYvvxzj/sG8LBOhBFCp11i9uE7t1EyS+RAoV9tztPFyw==", "dev": true, "funding": [ { @@ -804,8 +554,8 @@ ], "license": "MIT", "dependencies": { - "@csstools/color-helpers": "^6.0.2", - "@csstools/css-calc": "^3.2.1" + "@csstools/color-helpers": "^6.1.0", + "@csstools/css-calc": "^3.3.0" }, "engines": { "node": ">=20.19.0" @@ -839,9 +589,9 @@ } }, "node_modules/@csstools/css-syntax-patches-for-csstree": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.5.tgz", - "integrity": "sha512-oNjBvzLq2GPZtJphCjLqXow/cHySHSgtxvKZb7OqSZ/xHgw6NWNhfad+6AB9cLeVm6eA9d/qMll3JdEHjy6M+A==", + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.7.tgz", + "integrity": "sha512-fQ+05118eQS1cofO3aJpB5efgpBZMvIzwr/sbC8kDLVA5XLG8q1kJV5yzrUAI1f7lvhPnm8fgIjzFB8/O/5Dig==", "dev": true, "funding": [ { @@ -884,32 +634,21 @@ } }, "node_modules/@emnapi/core": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", - "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.2", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/core/node_modules/@emnapi/wasi-threads": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", - "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz", + "integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==", "dev": true, "license": "MIT", "optional": true, "dependencies": { + "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "node_modules/@emnapi/runtime": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", - "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz", + "integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==", "dev": true, "license": "MIT", "optional": true, @@ -929,17 +668,17 @@ } }, "node_modules/@es-joy/jsdoccomment": { - "version": "0.88.0", - "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.88.0.tgz", - "integrity": "sha512-GK/HL/claLLNo5KG705auIlZMwEtmn88ofSGuLsmVZwKBqMPJhW9DiznYNq07QEqz9BPtA3LBfYImtZmhVvRAw==", + "version": "0.91.0", + "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.91.0.tgz", + "integrity": "sha512-vgqlMGNNhZxwDYbUNIHj3Hskb4R28iqdXx90ufHyt/NeuTQkeqjTDslAs9I0/GCAfbxP5BpH5WsL1R1fht5Lxg==", "dev": true, "license": "MIT", "dependencies": { "@types/estree": "^1.0.9", - "@typescript-eslint/types": "^8.59.4", + "@typescript-eslint/types": "^8.65.0", "comment-parser": "1.4.7", "esquery": "^1.7.0", - "jsdoc-type-pratt-parser": "~7.2.0" + "jsdoc-type-pratt-parser": "~8.0.0" }, "engines": { "node": "^20.19.0 || ^22.13.0 || >=24" @@ -956,9 +695,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", - "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", + "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", "cpu": [ "ppc64" ], @@ -968,15 +707,14 @@ "os": [ "aix" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/android-arm": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", - "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz", + "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", "cpu": [ "arm" ], @@ -986,15 +724,14 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/android-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", - "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", + "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", "cpu": [ "arm64" ], @@ -1004,15 +741,14 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/android-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", - "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz", + "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", "cpu": [ "x64" ], @@ -1022,15 +758,14 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", - "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", + "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", "cpu": [ "arm64" ], @@ -1040,15 +775,14 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", - "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", + "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", "cpu": [ "x64" ], @@ -1058,15 +792,14 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", - "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", + "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", "cpu": [ "arm64" ], @@ -1076,15 +809,14 @@ "os": [ "freebsd" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", - "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", + "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", "cpu": [ "x64" ], @@ -1094,15 +826,14 @@ "os": [ "freebsd" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/linux-arm": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", - "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", + "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", "cpu": [ "arm" ], @@ -1112,15 +843,14 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", - "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", + "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", "cpu": [ "arm64" ], @@ -1130,15 +860,14 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", - "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", + "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", "cpu": [ "ia32" ], @@ -1148,15 +877,14 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", - "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", + "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", "cpu": [ "loong64" ], @@ -1166,15 +894,14 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", - "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", + "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", "cpu": [ "mips64el" ], @@ -1184,15 +911,14 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", - "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", + "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", "cpu": [ "ppc64" ], @@ -1202,15 +928,14 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", - "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", + "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", "cpu": [ "riscv64" ], @@ -1220,15 +945,14 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", - "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", + "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", "cpu": [ "s390x" ], @@ -1238,15 +962,14 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/linux-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", - "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", + "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==", "cpu": [ "x64" ], @@ -1256,15 +979,14 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", - "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", + "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", "cpu": [ "arm64" ], @@ -1274,15 +996,14 @@ "os": [ "netbsd" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", - "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", + "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", "cpu": [ "x64" ], @@ -1292,15 +1013,14 @@ "os": [ "netbsd" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", - "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", + "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", "cpu": [ "arm64" ], @@ -1310,15 +1030,14 @@ "os": [ "openbsd" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", - "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", + "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", "cpu": [ "x64" ], @@ -1328,15 +1047,14 @@ "os": [ "openbsd" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", - "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", + "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", "cpu": [ "arm64" ], @@ -1346,15 +1064,14 @@ "os": [ "openharmony" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", - "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", + "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", "cpu": [ "x64" ], @@ -1364,15 +1081,14 @@ "os": [ "sunos" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", - "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", + "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", "cpu": [ "arm64" ], @@ -1382,15 +1098,14 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", - "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", + "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", "cpu": [ "ia32" ], @@ -1400,15 +1115,14 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/win32-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", - "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", + "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", "cpu": [ "x64" ], @@ -1418,15 +1132,14 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", - "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", + "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==", "dev": true, "license": "MIT", "dependencies": { @@ -1481,9 +1194,9 @@ } }, "node_modules/@eslint/config-helpers": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.6.0.tgz", - "integrity": "sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.7.0.tgz", + "integrity": "sha512-DObd/KKUsU+FaFv4PLxSRenpXfQWmPXXP3pPZ6/K1PCrMu2vQpMDMuQe/BqYeoLcz8ro0bVDF1RxOJgfVEdhUw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -1552,9 +1265,9 @@ } }, "node_modules/@exodus/bytes": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.0.tgz", - "integrity": "sha512-UY0nlA+feH81UGSHv92sLEPLCeZFjXOuHhrIo0HQydScuQc8s0A7kL/UdgwgDq8g8ilksmuoF35YVTNphV2aBQ==", + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.1.tgz", + "integrity": "sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==", "dev": true, "license": "MIT", "engines": { @@ -1570,31 +1283,31 @@ } }, "node_modules/@floating-ui/core": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz", - "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.8.0.tgz", + "integrity": "sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==", "license": "MIT", "dependencies": { - "@floating-ui/utils": "^0.2.11" + "@floating-ui/utils": "^0.2.12" } }, "node_modules/@floating-ui/dom": { - "version": "1.7.6", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz", - "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.8.0.tgz", + "integrity": "sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==", "license": "MIT", "dependencies": { - "@floating-ui/core": "^1.7.5", - "@floating-ui/utils": "^0.2.11" + "@floating-ui/core": "^1.8.0", + "@floating-ui/utils": "^0.2.12" } }, "node_modules/@floating-ui/react-dom": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.8.tgz", - "integrity": "sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==", + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.9.tgz", + "integrity": "sha512-JDjEFGCpImxDCA7JJKviA0M9+RtmJdj0m/NVU5IMgBK+AmZouAQQ7/+2GLH0GXXY0YMw9oXPB8hKdbPYg5QLYg==", "license": "MIT", "dependencies": { - "@floating-ui/dom": "^1.7.6" + "@floating-ui/dom": "^1.8.0" }, "peerDependencies": { "react": ">=16.8.0", @@ -1602,44 +1315,58 @@ } }, "node_modules/@floating-ui/utils": { - "version": "0.2.11", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz", - "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==", + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.12.tgz", + "integrity": "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==", "license": "MIT" }, "node_modules/@fontsource-variable/geist": { - "version": "5.2.9", - "resolved": "https://registry.npmjs.org/@fontsource-variable/geist/-/geist-5.2.9.tgz", - "integrity": "sha512-TP+QSBG3wxKGPE33CbMy/L0Nu3qvJ6Fy81Yc4LnQ95xH+i+cfEp8fyU8/kfV14YwszxIFPhnoMTbjL71waVpyQ==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@fontsource-variable/geist/-/geist-5.3.0.tgz", + "integrity": "sha512-j0m+vLQuG5XAYoHtGCVu0spvlGreR3EzpECUVzkFmI1mTVnAO38l/NEPDCFgZ177JxzYJCLSmTQibIiYPilGrA==", "license": "OFL-1.1", "funding": { "url": "https://github.com/sponsors/ayuhito" } }, "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", "dev": true, "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, "engines": { "node": ">=18.18.0" } }, "node_modules/@humanfs/node": { - "version": "0.16.7", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", - "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@humanfs/core": "^0.19.1", + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", "@humanwhocodes/retry": "^0.4.0" }, "engines": { "node": ">=18.18.0" } }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", @@ -1739,9 +1466,9 @@ } }, "node_modules/@napi-rs/canvas": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-1.0.2.tgz", - "integrity": "sha512-EYEqlMYaCbpZDz+IgDH5xp9MTd3ui4dmGqbQYryhMLnSRxrhHKq5KQWHHKxFUcEP4Hp8/BWgvqXocX4j7iSbOQ==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-1.0.5.tgz", + "integrity": "sha512-GaPlicMtnvgPr5SowFRprkEJicDSrV3qCq17U4jiF5u0kNORZo3IbdN2Bk4SfcZJAMYFHbMVJ81O3w21CYxazg==", "license": "MIT", "optional": true, "workspaces": [ @@ -1755,23 +1482,23 @@ "url": "https://github.com/sponsors/Brooooooklyn" }, "optionalDependencies": { - "@napi-rs/canvas-android-arm64": "1.0.2", - "@napi-rs/canvas-darwin-arm64": "1.0.2", - "@napi-rs/canvas-darwin-x64": "1.0.2", - "@napi-rs/canvas-linux-arm-gnueabihf": "1.0.2", - "@napi-rs/canvas-linux-arm64-gnu": "1.0.2", - "@napi-rs/canvas-linux-arm64-musl": "1.0.2", - "@napi-rs/canvas-linux-riscv64-gnu": "1.0.2", - "@napi-rs/canvas-linux-x64-gnu": "1.0.2", - "@napi-rs/canvas-linux-x64-musl": "1.0.2", - "@napi-rs/canvas-win32-arm64-msvc": "1.0.2", - "@napi-rs/canvas-win32-x64-msvc": "1.0.2" + "@napi-rs/canvas-android-arm64": "1.0.5", + "@napi-rs/canvas-darwin-arm64": "1.0.5", + "@napi-rs/canvas-darwin-x64": "1.0.5", + "@napi-rs/canvas-linux-arm-gnueabihf": "1.0.5", + "@napi-rs/canvas-linux-arm64-gnu": "1.0.5", + "@napi-rs/canvas-linux-arm64-musl": "1.0.5", + "@napi-rs/canvas-linux-riscv64-gnu": "1.0.5", + "@napi-rs/canvas-linux-x64-gnu": "1.0.5", + "@napi-rs/canvas-linux-x64-musl": "1.0.5", + "@napi-rs/canvas-win32-arm64-msvc": "1.0.5", + "@napi-rs/canvas-win32-x64-msvc": "1.0.5" } }, "node_modules/@napi-rs/canvas-android-arm64": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-1.0.2.tgz", - "integrity": "sha512-IMXKVQod0ol4vt3gmClUfXz4JAgHYESGPCUqmH3lQxBoL0K/2greJaQE1HVBVxWWFKfLc4OLZVdxg7kXVyXv+g==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-1.0.5.tgz", + "integrity": "sha512-ZzDlpKQocwFfCwhMh17UWre6Qt5yZN3kNIJoUpGfRZqwDDZ164IKOsPOHsRd3d8Tuj5KM6bDjGuPmZxuPuG3NQ==", "cpu": [ "arm64" ], @@ -1789,9 +1516,9 @@ } }, "node_modules/@napi-rs/canvas-darwin-arm64": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-1.0.2.tgz", - "integrity": "sha512-Sc8tPi6cF+5lqOzCCKFALJHhDiRwyMzTPYm3bbhdXsOunU0lQO5f05ucyOzN2r55I23Hg5bsjH63uSCvWp3EgQ==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-1.0.5.tgz", + "integrity": "sha512-Hr8v6CA/TBe+OJOePdV3sXWxzQHQKfQsTKPbc8wG7iPqVeAx6MMdzKGXlYID6SVvpfwV/zqkvGcdImYWSlhrZg==", "cpu": [ "arm64" ], @@ -1809,9 +1536,9 @@ } }, "node_modules/@napi-rs/canvas-darwin-x64": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-1.0.2.tgz", - "integrity": "sha512-niDXZ9LhKB1zLrUdYB64RHQFDGz9rr0eGx061qtJJU3U20EMMIx28ADF5fVYbhtOgkWQrBjFicfaye1yM0U62A==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-1.0.5.tgz", + "integrity": "sha512-9BXlLHBXpYnK4jSae1MdFdyPq09Xi1I3PeCNpvRzqgmUUBhgJS7aC1z7SZEP8JUXDHtbfIrolCS3sFuT9IGP2A==", "cpu": [ "x64" ], @@ -1829,9 +1556,9 @@ } }, "node_modules/@napi-rs/canvas-linux-arm-gnueabihf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-1.0.2.tgz", - "integrity": "sha512-sgatQL9JxGRH/Amzcvu0P3t8Am3duou74CisfuJ41Dwt8cWy723z/9KZ8LlgmxfypEwEZxSTNFJtU8d281lmhQ==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-1.0.5.tgz", + "integrity": "sha512-zEW4fgvtYsOJ/N56Us4TQPfaFrUf0shGr9CgGSj3GACc+NHfUM3ci0YF9xFTjwJWGDmaEhYPL6KqCfFCxwm/qg==", "cpu": [ "arm" ], @@ -1849,12 +1576,15 @@ } }, "node_modules/@napi-rs/canvas-linux-arm64-gnu": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-1.0.2.tgz", - "integrity": "sha512-dgKuX0peF3xwY6ZF5QxGS4wbfDqpoFAJYXiLSp+guZKARQUKMkRqZSDrXKj7nfrec3UCMzC0PFCPte0ES98AiA==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-1.0.5.tgz", + "integrity": "sha512-HFprwLspelJxCEtZvdMcz95Mwvfs63GzFVedLFmC/wslHnaOXhjXxgYxPCM/VdM4Jhx3CV4Lk0vVmh6hJv2etQ==", "cpu": [ "arm64" ], + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1869,12 +1599,15 @@ } }, "node_modules/@napi-rs/canvas-linux-arm64-musl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-1.0.2.tgz", - "integrity": "sha512-qwROoDIC9upfvDoRLuPn2aNg9CGW1x0Ygr4k2Or+8paA9d0qBLwk87U+g8KQpoOviKoPoiwl97kvBYuYD7qZoA==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-1.0.5.tgz", + "integrity": "sha512-FNMGFAx8DvtDwlLfWyBJ+oQjgPXoIAqCnNTJYqtJCFRwwzK3AyAe5B1Ll3NZ6hcOzqw7HylZsq4nQxVyPCQX1Q==", "cpu": [ "arm64" ], + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1889,12 +1622,15 @@ } }, "node_modules/@napi-rs/canvas-linux-riscv64-gnu": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-riscv64-gnu/-/canvas-linux-riscv64-gnu-1.0.2.tgz", - "integrity": "sha512-fXRjnPihdnbO6qy1QQOgxAonb68A0TCEG7rj1x7v7rxNElsE8EVIKIEUTvyDtU+sthYSbX+8e7g3oZiLGnOmxw==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-riscv64-gnu/-/canvas-linux-riscv64-gnu-1.0.5.tgz", + "integrity": "sha512-2vd5v8Lui+37Hh/spITKIvTT384ip4dnUc5XBn0E+sMNS4b7au7IswyT5YDG2udPTjSh/eLUs3sGuB5YHooFOA==", "cpu": [ "riscv64" ], + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1909,12 +1645,15 @@ } }, "node_modules/@napi-rs/canvas-linux-x64-gnu": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-1.0.2.tgz", - "integrity": "sha512-nPR97DXhbWIAy7yazF3jc06kEPMqYMLmPzFOVNlwKPfIoSChnI+x7dc0hTLaihz3jxrjL6j4BbA7earxfx4X3g==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-1.0.5.tgz", + "integrity": "sha512-iQIPy+Uey0expZTOszLri5n8rY7x4WUpMaY82mcXNIgbil30iHvc01OsiizhZC1KQHTK90RFMFWSpwFU+ON3aA==", "cpu": [ "x64" ], + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1929,12 +1668,15 @@ } }, "node_modules/@napi-rs/canvas-linux-x64-musl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-1.0.2.tgz", - "integrity": "sha512-l7zZY5+jL5qnBZtDz7CoBtY6p7EkHu422g/0zWwrOrzIwWyWxZFRfZZORY1UG7YApymPLx+UbOkN206xXn/c1Q==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-1.0.5.tgz", + "integrity": "sha512-Npthji25t7FUqIAKsoEkFS0qY5CYVkHjvI3tuZjDTG92wX8g4dst+Lfb4hhubdqPazlDcIwalPzInsFCtf3FFg==", "cpu": [ "x64" ], + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1949,9 +1691,9 @@ } }, "node_modules/@napi-rs/canvas-win32-arm64-msvc": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-arm64-msvc/-/canvas-win32-arm64-msvc-1.0.2.tgz", - "integrity": "sha512-yE0koHCFF4PIbMc2o2SEALhnipz7WBISh5glLvQiomtIoCcW0np3H4Lw93ceJAfJttTTeIIWFbwH84F7EVzjMQ==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-arm64-msvc/-/canvas-win32-arm64-msvc-1.0.5.tgz", + "integrity": "sha512-bi+JsdCdbfVJDoAQybTYmkLKwh1xpYpptg5j/BNr2BB56u4/R26jrVvtjqff+CxYMXV6Kz1/jeDVhZCuj6bDng==", "cpu": [ "arm64" ], @@ -1969,9 +1711,9 @@ } }, "node_modules/@napi-rs/canvas-win32-x64-msvc": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-1.0.2.tgz", - "integrity": "sha512-okU8/t2foV6C31n0GtvEMbfD5rOFc70+/6xUNME9Guld29sgSOIGUEDScAWFlcP3k5TYQRl9TNkwJEEjh15w8A==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-1.0.5.tgz", + "integrity": "sha512-KQQwG9/sBmcGxqaLFIQf+k2OefREGoyEaIBmRuTM8bUuFKOEE9Xk5pel90hE6pmBwk/vo4RB0OdX+FxobJGMFw==", "cpu": [ "x64" ], @@ -1989,22 +1731,25 @@ } }, "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", - "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.2.tgz", + "integrity": "sha512-JfB4kuJQjaoHuCTseIINHtHWeJnvgEcxjwA5t/Y00ZgaOO1Crz3fjT/p8kT28zA/Caz7oiUMn3d6H2yOVCVwuw==", "dev": true, "license": "MIT", "optional": true, "dependencies": { "@tybys/wasm-util": "^0.10.3" }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=23.5.0" + }, "funding": { "type": "github", "url": "https://github.com/sponsors/Brooooooklyn" }, "peerDependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1" + "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.3", + "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.3" } }, "node_modules/@oxc-parser/binding-android-arm-eabi": { @@ -2134,6 +1879,9 @@ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -2151,6 +1899,9 @@ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -2168,6 +1919,9 @@ "ppc64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -2185,6 +1939,9 @@ "riscv64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -2202,6 +1959,9 @@ "riscv64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -2219,6 +1979,9 @@ "s390x" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -2236,6 +1999,9 @@ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -2253,6 +2019,9 @@ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -2298,29 +2067,6 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@oxc-parser/binding-wasm32-wasi/node_modules/@emnapi/core": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz", - "integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.1", - "tslib": "^2.4.0" - } - }, - "node_modules/@oxc-parser/binding-wasm32-wasi/node_modules/@emnapi/runtime": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz", - "integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@oxc-parser/binding-win32-arm64-msvc": { "version": "0.127.0", "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.127.0.tgz", @@ -2373,9 +2119,9 @@ } }, "node_modules/@oxc-project/types": { - "version": "0.139.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.139.0.tgz", - "integrity": "sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==", + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.127.0.tgz", + "integrity": "sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==", "dev": true, "license": "MIT", "funding": { @@ -2383,9 +2129,9 @@ } }, "node_modules/@oxc-resolver/binding-android-arm-eabi": { - "version": "11.23.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm-eabi/-/binding-android-arm-eabi-11.23.0.tgz", - "integrity": "sha512-8IJyWRLVAyhTfe9/TIEbQqSQnl5rUqYJrUOS6Dkr+Mq9FGHMxDGeiEmwkBqCvDP5KckpPh/GYSgbag66O6JsCw==", + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm-eabi/-/binding-android-arm-eabi-11.24.2.tgz", + "integrity": "sha512-y09e0L0SRI2OA2tUIrjBgoV3eH5hvUKXNkJqXmNo5V2WxIjyC7I7aJfRLMEVpA8yi95f90gFDvO0VMgrDw+vwA==", "cpu": [ "arm" ], @@ -2397,9 +2143,9 @@ ] }, "node_modules/@oxc-resolver/binding-android-arm64": { - "version": "11.23.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm64/-/binding-android-arm64-11.23.0.tgz", - "integrity": "sha512-pprVojnNhHxupwTT2gdeUlkxll6XEvWWBk3oVicOSNVWQC99OBnDhMQDoirqnzrE1bScQSMS2JgPpqdlrhz/Fg==", + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm64/-/binding-android-arm64-11.24.2.tgz", + "integrity": "sha512-cl4icWaZFnLdg8m6qtnh5rBMuGbxc/ptStFHLeCNwr+2cZjkjNwQu/jYRS0CHlnPecOJMpuS5M6/BH+0J/YkEg==", "cpu": [ "arm64" ], @@ -2411,9 +2157,9 @@ ] }, "node_modules/@oxc-resolver/binding-darwin-arm64": { - "version": "11.23.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-arm64/-/binding-darwin-arm64-11.23.0.tgz", - "integrity": "sha512-mbIrWIMAJeytyee36OyUP5XH92TP7FaKaQ2m5AjokKy7STgjrhRt7SMXqpqLjhGm6Xn721Xmsg6H3Rtd9YQETw==", + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-arm64/-/binding-darwin-arm64-11.24.2.tgz", + "integrity": "sha512-At29QEMF6HajbQvgY8K6OXnHD1x9rad74xBEfmCB6ZqCGsdq75aK7tOYcTbOanMy8qdIBrfL3SMr3p/lfSlb9w==", "cpu": [ "arm64" ], @@ -2425,9 +2171,9 @@ ] }, "node_modules/@oxc-resolver/binding-darwin-x64": { - "version": "11.23.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-x64/-/binding-darwin-x64-11.23.0.tgz", - "integrity": "sha512-UnIphmZ1LazUCr9DXWaKYWtKDefPMbgLsywaoYxRqVCNHhq4MM6d2q1Nz1i9Vzxt5i+cE2nRUYpAUHr/lijNYA==", + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-x64/-/binding-darwin-x64-11.24.2.tgz", + "integrity": "sha512-A5Kqr1EUj4oIL5CF4WRssq/o5P0Y11cwoFouMRmQ7YnC/A8V93nv1nb7aSU8HwcgmXropjLNkVTl4MN87cu28Q==", "cpu": [ "x64" ], @@ -2439,9 +2185,9 @@ ] }, "node_modules/@oxc-resolver/binding-freebsd-x64": { - "version": "11.23.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-freebsd-x64/-/binding-freebsd-x64-11.23.0.tgz", - "integrity": "sha512-aaZ/cSEYFkSxgS2hOrobT6RQcsWNviOX8dW6CEkVx2/UYkAf9MeHbjl3W0usWV53rVV//ndBdn2nb1y7jsu4lw==", + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-freebsd-x64/-/binding-freebsd-x64-11.24.2.tgz", + "integrity": "sha512-R5xkRBRRz7ceH/P5Jrc6G7FmdUdgpLYyESFAUDVTNQ9K0sGPxcp4ljiwEwEqsvNcQ4sYbMRrWcHHBCu7ksAJVw==", "cpu": [ "x64" ], @@ -2453,9 +2199,9 @@ ] }, "node_modules/@oxc-resolver/binding-linux-arm-gnueabihf": { - "version": "11.23.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-11.23.0.tgz", - "integrity": "sha512-IoJLvO5SjLSVMaq83BNTrPCb1FppvoJc1IhZ5CoUVl3PykUBku7D+LK1j0GSurhJcIc6zfjghsvaZNpq5ev6Mg==", + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-11.24.2.tgz", + "integrity": "sha512-k/RuYL4L/R58IBn3wT5ma3Wh4k62bp1eYCFRWCmMsasUOqL+H6sW0VGFadEzKWXFFlz+2uIMoeMk9ySSZJHgbg==", "cpu": [ "arm" ], @@ -2467,9 +2213,9 @@ ] }, "node_modules/@oxc-resolver/binding-linux-arm-musleabihf": { - "version": "11.23.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-11.23.0.tgz", - "integrity": "sha512-vskFpwg44T/LFsfjSCnVZ5ygcuqzPC1yUzVEiKa8BgHAQz0+QLQQW3EGWLPVi8EXFghzjR4EtgPBtOhCjU4jdw==", + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-11.24.2.tgz", + "integrity": "sha512-bnHAak3ujYfH5pKk4NieFNbvYvernfoQDgwLddbZ3OtMYrem87/qjlA+u+aKG0oZcqSLGCful/6/CEA+aeAgaA==", "cpu": [ "arm" ], @@ -2481,13 +2227,16 @@ ] }, "node_modules/@oxc-resolver/binding-linux-arm64-gnu": { - "version": "11.23.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-11.23.0.tgz", - "integrity": "sha512-//TcHVhrChyw5RYtgts6WO7KcWq9387c1Z5Zvhqpk/ktAbyaRYgBZrpSY1GDCFq50ASt6B6jhh+JxB1rB45IAg==", + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-11.24.2.tgz", + "integrity": "sha512-vDT3KHgzYp47gmtNOqL2VNhCyl5Zv643eyxm//A68J8DeUGXrvD1pZFiaT4jSfe+RInfnn1R2yVHye4enx6RnA==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -2495,13 +2244,16 @@ ] }, "node_modules/@oxc-resolver/binding-linux-arm64-musl": { - "version": "11.23.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-musl/-/binding-linux-arm64-musl-11.23.0.tgz", - "integrity": "sha512-ZFqlwiTf7CXLLSGyAR9tYiO33LiaeIEXW+xm42d8mnUGpDgPltyrCGYtQezyMMEXvjhOgCz1X+i7sbDTJEx+bg==", + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-musl/-/binding-linux-arm64-musl-11.24.2.tgz", + "integrity": "sha512-+kMlQvbzfyEYtu5FcjE4p+ttBLpKW4d/AsAsuE69BxV6V4twZJeIQZFfD8gh/wqglY0MkPSezWXQH0jBV13MUw==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -2509,13 +2261,16 @@ ] }, "node_modules/@oxc-resolver/binding-linux-ppc64-gnu": { - "version": "11.23.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-11.23.0.tgz", - "integrity": "sha512-oZ5LeN5+H1R19dRjTAxKrxQguH+AsemHcnthEfFxf4OjmBSty2doHLeSmMunKy3zpTHJQ3lh3Af+dNS+W6dYeA==", + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-11.24.2.tgz", + "integrity": "sha512-shjfMhmZ3gq9fv/w7bi3PnZlgOPG+2QAOFf0BJF0EgBSIGZ6PMLN2zbGEblTUYB/NKVDRyYhE2ff3dJ1QqNPkA==", "cpu": [ "ppc64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -2523,13 +2278,16 @@ ] }, "node_modules/@oxc-resolver/binding-linux-riscv64-gnu": { - "version": "11.23.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-11.23.0.tgz", - "integrity": "sha512-O4ciFDyX5ebQd0qkb1bjAIg8IEfiLT03GbSeylwlwlUMK9KwBWaALwrxSbc0Msaz4U6iPj+T9eRXpD5mxBfmvA==", + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-11.24.2.tgz", + "integrity": "sha512-zGelwFR5oRo+b69k8Lrzun86DyUHzfKN6cnjbR9l7Z7NIRznOE/2ZvPa1IUKqAL2PzAXOdwkfVqNvO1H2RlpAw==", "cpu": [ "riscv64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -2537,13 +2295,16 @@ ] }, "node_modules/@oxc-resolver/binding-linux-riscv64-musl": { - "version": "11.23.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-11.23.0.tgz", - "integrity": "sha512-P3o8Y9kISYjcxadmbO+94ThRwLhwGuDAbA7dcdd4+YLpfeF+mmobz8fXf4NmSdfSqjyRSkceJDBRZha9NVYkiQ==", + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-11.24.2.tgz", + "integrity": "sha512-qxZ1SWCXJY0eyhAlP6Lmo9F2Nrtx7EkYj9oCgL8apDPCwXwCEDA2U697bbT81JIc2IrVjxO4KX6WU2N+oN9Z4w==", "cpu": [ "riscv64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -2551,13 +2312,16 @@ ] }, "node_modules/@oxc-resolver/binding-linux-s390x-gnu": { - "version": "11.23.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-11.23.0.tgz", - "integrity": "sha512-oj03m1E3RmTFczKhcKJDzHaEDKJnPIsDcQFVxBJsSdXGSuIPdt5TvcM332FfMQgzI6yDJqyl4InrnFfXrmUTKQ==", + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-11.24.2.tgz", + "integrity": "sha512-sGCecF3cx2DFlH4t/z7ApnOnXqN48p5p5mlHDEnHTAukQa2P+qMVE4CwyWE9W+q/m3QJ7kKfGrIjax31f44oFQ==", "cpu": [ "s390x" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -2565,13 +2329,16 @@ ] }, "node_modules/@oxc-resolver/binding-linux-x64-gnu": { - "version": "11.23.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-gnu/-/binding-linux-x64-gnu-11.23.0.tgz", - "integrity": "sha512-BqJxbSC8FdP7mSuSpRePTGHm0hXWV+dfz//f7SjsteZncLaBgWTBmi/OZNv7sX6CyG/Pt/eJkPorP+DkMOhMwQ==", + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-gnu/-/binding-linux-x64-gnu-11.24.2.tgz", + "integrity": "sha512-k/VlMMcSzMlahb3/fENM4rTlsJ0s3fFROA0KXPBmKggqmTSaE383sl8F3KCOXPLmVsYfW6hCitMhXCEtNeZxxg==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -2579,13 +2346,16 @@ ] }, "node_modules/@oxc-resolver/binding-linux-x64-musl": { - "version": "11.23.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-musl/-/binding-linux-x64-musl-11.23.0.tgz", - "integrity": "sha512-utmw+VmUrW4K8LI5/6jhg4aGYKJHOIjQ9syYOOA6pF3w7haKu4r4enTe2U0C04/HbUvkq/Zif43xFsKW1Pnq9w==", + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-musl/-/binding-linux-x64-musl-11.24.2.tgz", + "integrity": "sha512-8hbnZyNi97b/8wapYaIF9+t9GmZKBW2vunaOc3h9HGJptH7b7XpvZqOTBSm/MpTjr7H497BlgOaSfLUdhmy2bw==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -2593,9 +2363,9 @@ ] }, "node_modules/@oxc-resolver/binding-openharmony-arm64": { - "version": "11.23.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-openharmony-arm64/-/binding-openharmony-arm64-11.23.0.tgz", - "integrity": "sha512-V6lbRrthHa4TbvsLjPtg+EkXT1tRY+s4I8rYLXUfiHlZzGx3sLv1EH9CEOOevjvUYHLsbe/gqCIc73XnQfPb9A==", + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-openharmony-arm64/-/binding-openharmony-arm64-11.24.2.tgz", + "integrity": "sha512-MvyGik3a6pVgZ0t/kWlbmFxFLmXQJwgLsY2eYFHLpy0wGwRbfzeIGgDwQ3kXqE30z+kSXennRkCrT7TUvkptNg==", "cpu": [ "arm64" ], @@ -2607,9 +2377,9 @@ ] }, "node_modules/@oxc-resolver/binding-wasm32-wasi": { - "version": "11.23.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-wasm32-wasi/-/binding-wasm32-wasi-11.23.0.tgz", - "integrity": "sha512-gRoOxQPdnAmIAjxcuQNBxfihvx+wjTaQM/9/eP12xwnGNawOG/+Zz9RHN4WNSxT45b5CrscK4NB8aPh+oZQXAQ==", + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-wasm32-wasi/-/binding-wasm32-wasi-11.24.2.tgz", + "integrity": "sha512-vHcssMPwO08RTvj/c0iOBz90attxyG3wQJ0dTcyEQK43LRpcdLWZlV5feBhv6Isn6ahbQIzHbCgfa81+RiML0Q==", "cpu": [ "wasm32" ], @@ -2617,18 +2387,52 @@ "license": "MIT", "optional": true, "dependencies": { - "@emnapi/core": "1.11.1", - "@emnapi/runtime": "1.11.1", + "@emnapi/core": "1.11.2", + "@emnapi/runtime": "1.11.2", "@napi-rs/wasm-runtime": "^1.1.6" }, "engines": { "node": ">=14.0.0" } }, + "node_modules/@oxc-resolver/binding-wasm32-wasi/node_modules/@emnapi/core": { + "version": "1.11.2", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.2.tgz", + "integrity": "sha512-TC8MkTuZUtcTSiFeuC0ksCh9QIJ5+F21MvZ4Wn4ORfYaFJ/0dsiudv5tVkejgwZlwQ39jL9WWDe2lz8x0WglOA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@oxc-resolver/binding-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "1.11.2", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.2.tgz", + "integrity": "sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@oxc-resolver/binding-wasm32-wasi/node_modules/@emnapi/wasi-threads": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", + "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@oxc-resolver/binding-win32-arm64-msvc": { - "version": "11.23.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-11.23.0.tgz", - "integrity": "sha512-CgTGMYsJVe1eUiCdJTpGw21svXw79ITsemN1h0hcNkiswasDbN5MoibSLY+gRMWP5syfEz5iffrjZnwEP8xeUA==", + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-11.24.2.tgz", + "integrity": "sha512-uokJqro2iBqkFvJdKQLP7d8/BUmFwESQFVmIJUQKj1Xn1a/LysJoe1vmeECLF5b3jsV8CAL5sEMJXX6SdK9Nhg==", "cpu": [ "arm64" ], @@ -2640,9 +2444,9 @@ ] }, "node_modules/@oxc-resolver/binding-win32-x64-msvc": { - "version": "11.23.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-x64-msvc/-/binding-win32-x64-msvc-11.23.0.tgz", - "integrity": "sha512-gUGJpr+Rn6zMxm5juApV0K3U845i8t47o8k+rbO0BHbi4PoJIfSPeQmrE2dgohQm2g5k6iviNFyXCGqvmaYUpw==", + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-x64-msvc/-/binding-win32-x64-msvc-11.24.2.tgz", + "integrity": "sha512-UqGPmo56KDfLlfXFAFIrNflHT8tFxWGEivWg3Zeyp4Uy2NlKN1FGPr6/BxcLGG3+kZ6Wp14g5Uj+n71boqZfiw==", "cpu": [ "x64" ], @@ -2654,9 +2458,9 @@ ] }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.5.tgz", - "integrity": "sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.3.tgz", + "integrity": "sha512-zrJtHDcaZJ1Fp7xf4hNl+7seH9Cn/N5TwLYkhgXREtBwAd/jaqW3uqeHxpDugJLVICWg4eW44kOQEGJ1r6jCGw==", "cpu": [ "arm64" ], @@ -2671,9 +2475,9 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.5.tgz", - "integrity": "sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.3.tgz", + "integrity": "sha512-ieIiibVCp0tX7TLu2cafoNPv8wJyYi01ekXpbf8q2j7F4rGAhhXb/eQh7ge9DRBY78GwmRQtvjZDux7EDbA8kA==", "cpu": [ "arm64" ], @@ -2688,9 +2492,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.5.tgz", - "integrity": "sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.3.tgz", + "integrity": "sha512-Zh9tCon19eDXJoihx0rqKhMUlMYqzwj3aPsSuHmI4RWZh62dWUL+DJN4C5YQya5TcQBJU/Fe8+rY0jhXTQITqA==", "cpu": [ "x64" ], @@ -2705,9 +2509,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.5.tgz", - "integrity": "sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.3.tgz", + "integrity": "sha512-nGbJWewA1wrXXZiQhjAT5rhibGfns5ZNkDVqxsO6zJ3f3YvpoDNNmGMSbbhLuXKjNScaBJVOAboztAWVespQMg==", "cpu": [ "x64" ], @@ -2722,9 +2526,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.5.tgz", - "integrity": "sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.3.tgz", + "integrity": "sha512-QNniJr5Kml0kDEB98jiDOJjXNroxIIi0IXIbdYzY26Xt1pVbeP62+KnoIZLwirOymX/0jDk/2gI/bNUv7A7OIw==", "cpu": [ "arm" ], @@ -2739,13 +2543,16 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.5.tgz", - "integrity": "sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.3.tgz", + "integrity": "sha512-TkqEAcmmvH3I/q4114NB4RVt6241Dao48pF45uLcFGrwAaIn0iITgTAKP/dLjbN0R4buJjGb91+UHSoFmpgIWw==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -2756,13 +2563,16 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.5.tgz", - "integrity": "sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.3.tgz", + "integrity": "sha512-NHqjnxpsndf4MPymxteFAWHHfkTL8HjWh1KB7z23ofZ6QO2euONuxDXjat69dKZRALnGypg8k8SsK8vZJoXv1Q==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -2773,13 +2583,16 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.5.tgz", - "integrity": "sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.3.tgz", + "integrity": "sha512-6tbrbwfz5GB9DQ4Jwo6hy9v+vR31xZlvzZ6n5Xut6Hhx5PvrA9q/HsK8KMaYQp063iqZGXwNvZtYNLD7EM/x0w==", "cpu": [ "ppc64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -2790,13 +2603,16 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.5.tgz", - "integrity": "sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.3.tgz", + "integrity": "sha512-oyuXxXmoZHjXC917IAPFAAv4wWAa0cM9afk8nx1+9/jNNOX1uPf8yDA6p7G0RypOfw/X0PQt5IfoquY1um+zSg==", "cpu": [ "s390x" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -2807,13 +2623,16 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.5.tgz", - "integrity": "sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.3.tgz", + "integrity": "sha512-TytMwF2KVGqP2tgd0I1OY0PAv78dZRAYcF5ssDzjM34SUXCED3uXvSd5+lHoC0bTD6eEdFz7LdQNCO1y0oVk9w==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -2824,13 +2643,16 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.5.tgz", - "integrity": "sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.3.tgz", + "integrity": "sha512-/E9m3qstrJFVPoULV25mVQblSNExY2+kBsYe4sy0Tn0yOOgJ8wZbZt3KnRbF/XeU2Gl1STKUQnDNTqhIE5MD4A==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -2841,9 +2663,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.5.tgz", - "integrity": "sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.3.tgz", + "integrity": "sha512-Kr0OcsoQI816i6HOl3vFHpd1K0eZyh76zgfj4c1nTyaTsd5r2Mj1lwM4R90y/qaCfmTn9eHy0SKwi98eitRxug==", "cpu": [ "arm64" ], @@ -2857,29 +2679,10 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.5.tgz", - "integrity": "sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "1.11.1", - "@emnapi/runtime": "1.11.1", - "@napi-rs/wasm-runtime": "^1.1.6" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.5.tgz", - "integrity": "sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.3.tgz", + "integrity": "sha512-hOtMwTqnME+/gJcH/PCZ0wn0zPUjiWOgkHpxbSJpfGKMezHltx1S7/k1SitzVa7Ww2cqrDDaFbZEhcJZO8o+Jw==", "cpu": [ "arm64" ], @@ -2894,9 +2697,9 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.5.tgz", - "integrity": "sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.3.tgz", + "integrity": "sha512-ekcqMMkI2PlhYnfzQnB/cEdYUVVJViWvoUyLrbzgDoi3Snfc1mVBwdnc306ufA5ejy8JSPjT2RlW1nQSjW7efg==", "cpu": [ "x64" ], @@ -2940,13 +2743,6 @@ } } }, - "node_modules/@rollup/pluginutils/node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true, - "license": "MIT" - }, "node_modules/@sindresorhus/base62": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@sindresorhus/base62/-/base62-1.0.0.tgz", @@ -2968,13 +2764,13 @@ "license": "MIT" }, "node_modules/@storybook/builder-vite": { - "version": "10.4.6", - "resolved": "https://registry.npmjs.org/@storybook/builder-vite/-/builder-vite-10.4.6.tgz", - "integrity": "sha512-BHBtD81HiXUiDQz/CaFynLtWmm7AFUQn8VnXuHipZ8KlnUANopa4yqdVuy/Gwz8ub254uFI5NMZsW/KlgWNgNg==", + "version": "10.5.7", + "resolved": "https://registry.npmjs.org/@storybook/builder-vite/-/builder-vite-10.5.7.tgz", + "integrity": "sha512-fShF/aQaITqcJuMCLr42BGNUAbhDi4IboqvlbZqXAwgrrTslnZEUnY8GcEcvpZmjl11VwlmazhMJdH50fIgBPg==", "dev": true, "license": "MIT", "dependencies": { - "@storybook/csf-plugin": "10.4.6", + "@storybook/csf-plugin": "10.5.7", "ts-dedent": "^2.0.0" }, "funding": { @@ -2982,14 +2778,14 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^10.4.6", + "storybook": "^10.5.7", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, "node_modules/@storybook/csf-plugin": { - "version": "10.4.6", - "resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-10.4.6.tgz", - "integrity": "sha512-NILLxDqpA/JR/AazGWpsz+4fadJwRU4uhHephGtYpVOWnQA/DkJfKT6zpcJVq8+QA8A2zKMLX3GVKsXIrxjuDA==", + "version": "10.5.7", + "resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-10.5.7.tgz", + "integrity": "sha512-IaX8FlM0H36HNFhJ2+4L9bCldqfvHGqcLg841SJNyK/DhfMlM7JsvY/GDH2ZFuWrUf8FSOx96GRRnHq6XfRKag==", "dev": true, "license": "MIT", "dependencies": { @@ -3002,7 +2798,7 @@ "peerDependencies": { "esbuild": "*", "rollup": "*", - "storybook": "^10.4.6", + "storybook": "^10.5.7", "vite": "*", "webpack": "*" }, @@ -3039,14 +2835,14 @@ } }, "node_modules/@storybook/react": { - "version": "10.4.6", - "resolved": "https://registry.npmjs.org/@storybook/react/-/react-10.4.6.tgz", - "integrity": "sha512-9Y7YecrVFe1/01KYjfOLxVqTg2Aq+IO6TEv6sC2U0PfD0AWCSCmQ91QqgBpN/XW4aFFWoiZNinyXMUlU8zxy2w==", + "version": "10.5.7", + "resolved": "https://registry.npmjs.org/@storybook/react/-/react-10.5.7.tgz", + "integrity": "sha512-uFvty2MMdFXzW5PcQe1JqDAZkz6cQq7q/9G/cbGVnBEvP6zsOVeL+bmrQ0/WBlFQN0Ko9+ZoCTvaQ9s65zBa5g==", "dev": true, "license": "MIT", "dependencies": { "@storybook/global": "^5.0.0", - "@storybook/react-dom-shim": "10.4.6", + "@storybook/react-dom-shim": "10.5.7", "react-docgen": "^8.0.2", "react-docgen-typescript": "^2.2.2" }, @@ -3059,7 +2855,7 @@ "@types/react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "storybook": "^10.4.6", + "storybook": "^10.5.7", "typescript": ">= 4.9.x" }, "peerDependenciesMeta": { @@ -3075,9 +2871,9 @@ } }, "node_modules/@storybook/react-dom-shim": { - "version": "10.4.6", - "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-10.4.6.tgz", - "integrity": "sha512-iGNmKzrq9vgl2PDrYAnZKI+yvac3Ym+lJXXuQaqlFRS23zA5MNm4EBX+rAG7WulqchoK6NaZ0KQOs2mAgEpTMg==", + "version": "10.5.7", + "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-10.5.7.tgz", + "integrity": "sha512-lxOkyh+wu/MiBXvYQHjZfD+DRKOa4bHBzbuGuiHXnHXmdOcTRdcrQTsoeN2FPtfugmmOG66cZUEgDwNX+k5eRA==", "dev": true, "license": "MIT", "funding": { @@ -3089,7 +2885,7 @@ "@types/react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "storybook": "^10.4.6" + "storybook": "^10.5.7" }, "peerDependenciesMeta": { "@types/react": { @@ -3101,19 +2897,19 @@ } }, "node_modules/@storybook/react-vite": { - "version": "10.4.6", - "resolved": "https://registry.npmjs.org/@storybook/react-vite/-/react-vite-10.4.6.tgz", - "integrity": "sha512-0arEQtybqGYXHbXpTot+Wv9YtG+V5Vp43QayXavPKQ20M8mpEzhyCPKd0EhqMGSC1Z1UEt0hm365WUBhI9LfKA==", + "version": "10.5.7", + "resolved": "https://registry.npmjs.org/@storybook/react-vite/-/react-vite-10.5.7.tgz", + "integrity": "sha512-eEo3eVa2pvqrzQukKxAzx7YvswDAA1s6k/y+tdMxmRvWyHX6QEOsb9Tda6wcVaa7c8BeJM7Ggq+289cRMTH6Iw==", "dev": true, "license": "MIT", "dependencies": { "@joshwooding/vite-plugin-react-docgen-typescript": "^0.7.0", "@rollup/pluginutils": "^5.0.2", - "@storybook/builder-vite": "10.4.6", - "@storybook/react": "10.4.6", + "@storybook/builder-vite": "10.5.7", + "@storybook/react": "10.5.7", "empathic": "^2.0.0", "magic-string": "^0.30.0", - "react-docgen": "^8.0.0", + "react-docgen": "^8.0.2", "resolve": "^1.22.8", "tsconfig-paths": "^4.2.0" }, @@ -3124,54 +2920,60 @@ "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "storybook": "^10.4.6", + "storybook": "^10.5.7", + "typescript": ">= 4.9.x", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, "node_modules/@tailwindcss/node": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.2.tgz", - "integrity": "sha512-yWP/sqEcBLaD8JuA6zNwxoYKr75qxTioYwlRwekj5Jr/I5GXnoJfjetH/psLUIv74cYTH2lBUEzBkinthoYcBg==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.3.tgz", + "integrity": "sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==", "dev": true, "license": "MIT", "dependencies": { "@jridgewell/remapping": "^2.3.5", - "enhanced-resolve": "5.21.6", + "enhanced-resolve": "^5.24.1", "jiti": "^2.7.0", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", - "tailwindcss": "4.3.2" + "tailwindcss": "4.3.3" } }, "node_modules/@tailwindcss/oxide": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.2.tgz", - "integrity": "sha512-z8ZgnzX8gdNoWLBLqBPoh/sjnxkwvf9ZuWjnO0l0yIzbLa5/9S+eC5QxGZKRobVHIC3/1BoMWjHblqWjcgFgag==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.3.tgz", + "integrity": "sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA==", "dev": true, "license": "MIT", "engines": { "node": ">= 20" }, "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.3.2", - "@tailwindcss/oxide-darwin-arm64": "4.3.2", - "@tailwindcss/oxide-darwin-x64": "4.3.2", - "@tailwindcss/oxide-freebsd-x64": "4.3.2", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.2", - "@tailwindcss/oxide-linux-arm64-gnu": "4.3.2", - "@tailwindcss/oxide-linux-arm64-musl": "4.3.2", - "@tailwindcss/oxide-linux-x64-gnu": "4.3.2", - "@tailwindcss/oxide-linux-x64-musl": "4.3.2", - "@tailwindcss/oxide-wasm32-wasi": "4.3.2", - "@tailwindcss/oxide-win32-arm64-msvc": "4.3.2", - "@tailwindcss/oxide-win32-x64-msvc": "4.3.2" + "@tailwindcss/oxide-android-arm64": "4.3.3", + "@tailwindcss/oxide-darwin-arm64": "4.3.3", + "@tailwindcss/oxide-darwin-x64": "4.3.3", + "@tailwindcss/oxide-freebsd-x64": "4.3.3", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.3", + "@tailwindcss/oxide-linux-arm64-gnu": "4.3.3", + "@tailwindcss/oxide-linux-arm64-musl": "4.3.3", + "@tailwindcss/oxide-linux-x64-gnu": "4.3.3", + "@tailwindcss/oxide-linux-x64-musl": "4.3.3", + "@tailwindcss/oxide-wasm32-wasi": "4.3.3", + "@tailwindcss/oxide-win32-arm64-msvc": "4.3.3", + "@tailwindcss/oxide-win32-x64-msvc": "4.3.3" } }, "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.2.tgz", - "integrity": "sha512-WHxqIuHpvZ5VtdX6GTl1Ik/Vp2YuN42Et+0CdeaVd/frQ9jAvGmvR8vLT+jk3e8/Q3x8kECB9+R17pgpp2BulA==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.3.tgz", + "integrity": "sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw==", "cpu": [ "arm64" ], @@ -3186,9 +2988,9 @@ } }, "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.2.tgz", - "integrity": "sha512-GZypeUY/IDJW3877KeM+O67vbXr3MBnbtEL4aYhNErv/JWZhye2vGSWWG9tB6iiqR2MqRNkY8IOUy4NdSZV26w==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.3.tgz", + "integrity": "sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw==", "cpu": [ "arm64" ], @@ -3203,9 +3005,9 @@ } }, "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.2.tgz", - "integrity": "sha512-UIIzmefR6KO1sDU7MzRqAxC8iBpft/VhkGjTjnhoS6k7Z3rQ9wEgA1ODSiyH/tcSYssulNm4Ci3hOeK1jH7ccQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.3.tgz", + "integrity": "sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw==", "cpu": [ "x64" ], @@ -3220,9 +3022,9 @@ } }, "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.2.tgz", - "integrity": "sha512-GN+uAmcI6DNspnCDwtOAZrTz6oukJnp337qZvxqCGLd3BHBzJpO0ZbTLRvJNdztOeAmTzewewGIMPb0tk2R4WA==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.3.tgz", + "integrity": "sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw==", "cpu": [ "x64" ], @@ -3237,9 +3039,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.2.tgz", - "integrity": "sha512-4ABn7qSbdHRwTiDiuWNegCyb5+2FJ4vKIKc3DmKrvAFw7MU1Lm11dIkTPwUaFdTzc7IsOpDbqBrlh0x6y36U/w==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.3.tgz", + "integrity": "sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ==", "cpu": [ "arm" ], @@ -3254,13 +3056,16 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.2.tgz", - "integrity": "sha512-wDgEIGwoM8w8pufh9LVt1PahDgNdKXrLC2qfAnV3vAmococ9RWbxeAw4pxPttd/TsJfwjyLf90Dg1y9y8I6Emw==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.3.tgz", + "integrity": "sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -3271,13 +3076,16 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.2.tgz", - "integrity": "sha512-J5Nuk0uZQIiMTJj3LEx4sAA9tMFUoXQZFv1J6An+QGYe53HKRJuFDi0rpq/tuouCZeAbOBY3kQ6g8qeD4TUjtA==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.3.tgz", + "integrity": "sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -3288,13 +3096,16 @@ } }, "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.2.tgz", - "integrity": "sha512-kqCZpSKOBEJO4mz7OqWoofBZeXTAwaVGPj0ErAj7CojmhKpWVWVOnrt9dE8odoIraZq4oj3ausM37kXi+Tow8w==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.3.tgz", + "integrity": "sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -3305,13 +3116,16 @@ } }, "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.2.tgz", - "integrity": "sha512-cixpqbh2toJDmkuCRI68nXA8ZxNmdK9Y+9v5h3MC3ZQKy/0BO8AWzlkWyRM7JAFSGBlfig4YVTPsK6MVgqz1uw==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.3.tgz", + "integrity": "sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -3322,9 +3136,9 @@ } }, "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.2.tgz", - "integrity": "sha512-4ec2Z/LOmRsAgU23CS4xeJfcJlmRg94A/XrbGRCF1gyU/zdDfRLYDVsS+ynSZCmGNxQ1jQriQOKMQeQxBA3Isw==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.3.tgz", + "integrity": "sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ==", "bundleDependencies": [ "@napi-rs/wasm-runtime", "@emnapi/core", @@ -3351,76 +3165,10 @@ "node": ">=14.0.0" } }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": { - "version": "1.11.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.2", - "tslib": "^2.4.0" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": { - "version": "1.11.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": { - "version": "1.2.2", - "dev": true, - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.4", - "dev": true, - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@tybys/wasm-util": "^0.10.1" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - }, - "peerDependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": { - "version": "0.10.2", - "dev": true, - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib": { - "version": "2.8.1", - "dev": true, - "inBundle": true, - "license": "0BSD", - "optional": true - }, "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.2.tgz", - "integrity": "sha512-Zyr/M0+XcYZu3bZrUytc7TXvrk0ftWfl8gN2MwekNDzhqhKRUucMPSeOzM0o0wH5AWOU49BsKRrfKxI2atCPMQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.3.tgz", + "integrity": "sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ==", "cpu": [ "arm64" ], @@ -3435,9 +3183,9 @@ } }, "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.2.tgz", - "integrity": "sha512-QI9BO7KlNZsp2GuO0jwAAj5jCDABOKXRkCk2XuKTSaNEFSdfzqswYVTtCHBNKHLsqyjFyFkqlDiwkNbTYSssMQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.3.tgz", + "integrity": "sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw==", "cpu": [ "x64" ], @@ -3452,24 +3200,24 @@ } }, "node_modules/@tailwindcss/vite": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.2.tgz", - "integrity": "sha512-eHpMeX4JXfVNJDEcsouTeCBubJBTcTLigeaw/NTUW6PB5ATKKXdyonnXgTBX2VuRbjz1hjfz6C5XAhr52ImQXA==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.3.tgz", + "integrity": "sha512-yYU8cogLeSh/ms2jh8Fj7jaba/EWa7Ja6GoUqYZaraEuCI5YS6ms6ObZgjjedm+jm6XZjdNRWBpPP6Z86oOxcw==", "dev": true, "license": "MIT", "dependencies": { - "@tailwindcss/node": "4.3.2", - "@tailwindcss/oxide": "4.3.2", - "tailwindcss": "4.3.2" + "@tailwindcss/node": "4.3.3", + "@tailwindcss/oxide": "4.3.3", + "tailwindcss": "4.3.3" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7 || ^8" } }, "node_modules/@tauri-apps/api": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.11.0.tgz", - "integrity": "sha512-7CinYODhky9lmO23xHnUFv0Xt43fbtWMyxZcLcRBlFkcgXKuEirBvHpmtJ89YMhyeGcq20Wuc47Fa4XjyniywA==", + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.11.1.tgz", + "integrity": "sha512-M2FPuYND2m+wh5hfW9ZpSdxMPdEJovPBWwoHJmwUpysTYNHaOkVFN419m/K0LIgjb/7KU2vBgsUepJWugQCvAA==", "license": "Apache-2.0 OR MIT", "funding": { "type": "opencollective", @@ -3565,6 +3313,9 @@ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "Apache-2.0 OR MIT", "optional": true, "os": [ @@ -3582,6 +3333,9 @@ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "Apache-2.0 OR MIT", "optional": true, "os": [ @@ -3599,6 +3353,9 @@ "riscv64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "Apache-2.0 OR MIT", "optional": true, "os": [ @@ -3616,6 +3373,9 @@ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "Apache-2.0 OR MIT", "optional": true, "os": [ @@ -3633,6 +3393,9 @@ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "Apache-2.0 OR MIT", "optional": true, "os": [ @@ -3699,7 +3462,6 @@ "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", @@ -3770,9 +3532,9 @@ } }, "node_modules/@testing-library/user-event": { - "version": "14.6.1", - "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.1.tgz", - "integrity": "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==", + "version": "14.6.3", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.3.tgz", + "integrity": "sha512-6dBq67jT8lE+JTE8Exm02Kt6ze43hz1jdiSpSJwtTZiT1xQQ6b7nZYTTQ9njdArdU8XklOwaDp/AbT/eYSKF4g==", "dev": true, "license": "MIT", "engines": { @@ -3799,8 +3561,7 @@ "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/babel__core": { "version": "7.20.5", @@ -3894,9 +3655,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "26.1.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.1.tgz", - "integrity": "sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw==", + "version": "26.2.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.2.0.tgz", + "integrity": "sha512-5IviulTZeRNp2vAJ514cc/HUlY5nZ9fCbq9DMyC52BrhFZACo3nI0R7qBxhQmo/d27NFe96ur/b7Wwxklda+kg==", "dev": true, "license": "MIT", "dependencies": { @@ -3904,15 +3665,25 @@ } }, "node_modules/@types/react": { - "version": "19.2.17", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", - "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", + "version": "19.2.18", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz", + "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==", "devOptional": true, "license": "MIT", "dependencies": { "csstype": "^3.2.2" } }, + "node_modules/@types/react-dom": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-Bsc+QHgp+P/F02XDzNCY9jnZNCUuLki36KT7VKrTXXLdHf+vHMNZnW1rVu5DNW/rCK+fya3DATySbLM4yhtKUw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, "node_modules/@types/resolve": { "version": "1.20.6", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.6.tgz", @@ -3921,17 +3692,17 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.63.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.63.0.tgz", - "integrity": "sha512-rvwSgqT+DHpWdzfSzPatRLm02a0GlESt++9iy3hLCDY4BgkaLcl8LBi9Yh7XGFBpwcBE/K3024QuXWTpbz4FfQ==", + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.66.0.tgz", + "integrity": "sha512-p088eaGrzYz1s+7cov0aMOCkNGTJlVxF4jgubf28c8L0Cv9Rloj8YBHnv4hXLq6IIEE1AsjNWavO+k+8kP2Y0A==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.63.0", - "@typescript-eslint/type-utils": "8.63.0", - "@typescript-eslint/utils": "8.63.0", - "@typescript-eslint/visitor-keys": "8.63.0", + "@typescript-eslint/scope-manager": "8.66.0", + "@typescript-eslint/type-utils": "8.66.0", + "@typescript-eslint/utils": "8.66.0", + "@typescript-eslint/visitor-keys": "8.66.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" @@ -3944,7 +3715,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.63.0", + "@typescript-eslint/parser": "^8.66.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } @@ -3960,16 +3731,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.63.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.63.0.tgz", - "integrity": "sha512-gwh4gvvlaVDKKxyfxMG+Gnu1u9X0OQBwyGLkbwB65dIzBKnxeRiJlNFqlI3zwVhNXJIs6qV7mlFCn/BIajlVig==", + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.66.0.tgz", + "integrity": "sha512-X6ypGChaWYk6PBtUg2BwuTZEFFcHJAtGTVJ9/lCTOufhZ4i9fNolQNnktq+kkMCwMj7V8Svsq7+TxSDslmhE0g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.63.0", - "@typescript-eslint/types": "8.63.0", - "@typescript-eslint/typescript-estree": "8.63.0", - "@typescript-eslint/visitor-keys": "8.63.0", + "@typescript-eslint/scope-manager": "8.66.0", + "@typescript-eslint/types": "8.66.0", + "@typescript-eslint/typescript-estree": "8.66.0", + "@typescript-eslint/visitor-keys": "8.66.0", "debug": "^4.4.3" }, "engines": { @@ -3985,14 +3756,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.63.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.63.0.tgz", - "integrity": "sha512-e5dh0/UI0ok53AlZ5wRkXCB32z/f2jUZqPR/ygAw5WYaSw8j9EoJWlS7wQjr/dmOaqWjnPIn2m+HhVPCMWGZVQ==", + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.66.0.tgz", + "integrity": "sha512-7MthGPTt4BP69lSryqpqq8HQqxuzynssckL/jyDyk3+TNMQ3y2jFWkptCrktWvBrP+EH787Nl5N5Qpw7WZg+5g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.63.0", - "@typescript-eslint/types": "^8.63.0", + "@typescript-eslint/tsconfig-utils": "^8.66.0", + "@typescript-eslint/types": "^8.66.0", "debug": "^4.4.3" }, "engines": { @@ -4007,14 +3778,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.63.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.63.0.tgz", - "integrity": "sha512-uUyfMWCnDSN8bCpcrY8nGP2BLkQ9Xn0GsipcONcpIDWhwhO4ZSyHvyS14U3X75mzxWxL3I2UZIrenTzdzcJO8A==", + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.66.0.tgz", + "integrity": "sha512-8TGcH25j9zqJ/IULB/ppyhRvxA8QYfFEZ7nfbg6/BN9spDgb8fPWQXlE5l8TWBL50EtUx007uZ1o9VOwrq2/9g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.63.0", - "@typescript-eslint/visitor-keys": "8.63.0" + "@typescript-eslint/types": "8.66.0", + "@typescript-eslint/visitor-keys": "8.66.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4025,9 +3796,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.63.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.63.0.tgz", - "integrity": "sha512-sUAbkulqBAsncKnbRP3+7CtQFRKicexnj7ZwNC6ddCR7EmrXvjvdCYMJbUIqMd6lwoEriZjwLo08aS5tSjVMHg==", + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.66.0.tgz", + "integrity": "sha512-9D5gLYZG4rOjcoag8MQ/fWI8WqA9wcPDyOGyWtWFhvM1lHRbliqUSPIY5J3zqCU1tvSwzXxnnjhQhz5Ne7mJ4g==", "dev": true, "license": "MIT", "engines": { @@ -4042,15 +3813,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.63.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.63.0.tgz", - "integrity": "sha512-Nzzh/OGxVCOjObjaj1CQF2RUasyYy2Jfuh+zZ3PjLzG2fYRriAiZLib9UKtO+CpQAS3YHiAS+ckZDclwqI1TPA==", + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.66.0.tgz", + "integrity": "sha512-LG2dWfjZQQp0ADtAu/EWJVayefGL2UEZ3CDeI44D9v3rXB/WYUqE/jpO28KrEKul5AySrmI+Zh1v6v+xW2U9+g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.63.0", - "@typescript-eslint/typescript-estree": "8.63.0", - "@typescript-eslint/utils": "8.63.0", + "@typescript-eslint/types": "8.66.0", + "@typescript-eslint/typescript-estree": "8.66.0", + "@typescript-eslint/utils": "8.66.0", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, @@ -4067,9 +3838,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.63.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.63.0.tgz", - "integrity": "sha512-xyLtl9DUBBFrcJS4x2pIqGLH68/tC2uOa4Z7pUteW09D3bXnnXUom4dyPikzWgB7llmIc1zoeI3aoUdC4rPK/Q==", + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.66.0.tgz", + "integrity": "sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ==", "dev": true, "license": "MIT", "engines": { @@ -4081,16 +3852,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.63.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.63.0.tgz", - "integrity": "sha512-ygBkU+B7ex5UI/gKhaqexWev79uISfIv7XQCRNYO/jmD8rGLPyWLAb3KMRT6nd8Gt9bmUBi9+iX6tBdYfOY81Q==", + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.66.0.tgz", + "integrity": "sha512-8/x4INiiQb10jGgXYD7116/zQ+OL84ZIFn0za68wwFHCanT/VLbBEroWht8RV8fn0/ZCAoazHLQgwUC0UQcDfg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.63.0", - "@typescript-eslint/tsconfig-utils": "8.63.0", - "@typescript-eslint/types": "8.63.0", - "@typescript-eslint/visitor-keys": "8.63.0", + "@typescript-eslint/project-service": "8.66.0", + "@typescript-eslint/tsconfig-utils": "8.66.0", + "@typescript-eslint/types": "8.66.0", + "@typescript-eslint/visitor-keys": "8.66.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", @@ -4109,16 +3880,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.63.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.63.0.tgz", - "integrity": "sha512-fUKaeAvrTuQg/Tgt3nliAUSZHJM6DlCcfyEmxCvlX8kieWSStBX+5O5Fnidtc3i2JrH+9c/GL4RY2iasd/GPTA==", + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.66.0.tgz", + "integrity": "sha512-jasearZPolBw5NJNYGMwxzHMF83niVWmMU1VdHzG1CyfI2VS7f7nZltnKtHcg20hW+7Uo5GfK4MeDPoU3qI8EA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.63.0", - "@typescript-eslint/types": "8.63.0", - "@typescript-eslint/typescript-estree": "8.63.0" + "@typescript-eslint/scope-manager": "8.66.0", + "@typescript-eslint/types": "8.66.0", + "@typescript-eslint/typescript-estree": "8.66.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4133,13 +3904,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.63.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.63.0.tgz", - "integrity": "sha512-UexrHGnGTpbuQHct2ExOc2ZcFbGUS9FOesCxxqdBGcpI1BxYu/LZ6U8Aq6/72XtF/qRBk9nhuGHFJIXXMhPMdw==", + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.66.0.tgz", + "integrity": "sha512-dkKR8q+lKciskj1Y3vthHktl+3cMLWGyVUP23bRiPZ5O9BRT++4EqDDV+TVeIKBL1VXVEqrJlz8MYbcnvJcAlg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.63.0", + "@typescript-eslint/types": "8.66.0", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -4151,13 +3922,13 @@ } }, "node_modules/@vitejs/plugin-react": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.2.tgz", - "integrity": "sha512-DlSMqo4WhThw4vB8Mpn0Woe9J+Jfq1geJ61AKW0QEgLzGMNwtIMdxbDUzLxcun8W7NbJO0e2Jg/Nxm3cCSVzzg==", + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.5.tgz", + "integrity": "sha512-BOVzne/NL162sMdResB25mUv+vWMF5NoAjNf09TeGlE7ZpszZWSD3winycicLJw72yeVsoCn/2kOhEuCvEShMA==", "dev": true, "license": "MIT", "dependencies": { - "@rolldown/pluginutils": "^1.0.0" + "@rolldown/pluginutils": "^1.0.1" }, "engines": { "node": "^20.19.0 || >=22.12.0" @@ -4176,64 +3947,83 @@ } } }, - "node_modules/@vitest/expect": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", - "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", + "node_modules/@vitest/coverage-v8": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.10.tgz", + "integrity": "sha512-IM49HmthevbgAO4anp1hwtoT9wYe59w0LR00gr+eagHE+ZJ5lK4sLPeO0ubgoJcwLk6dehU3R24N+FbEEKDc8g==", "dev": true, "license": "MIT", "dependencies": { - "@types/chai": "^5.2.2", - "@vitest/spy": "3.2.4", - "@vitest/utils": "3.2.4", - "chai": "^5.2.0", - "tinyrainbow": "^2.0.0" + "@bcoe/v8-coverage": "^1.0.2", + "@vitest/utils": "4.1.10", + "ast-v8-to-istanbul": "^1.0.0", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.2.0", + "magicast": "^0.5.2", + "obug": "^2.1.1", + "std-env": "^4.0.0-rc.1", + "tinyrainbow": "^3.1.0" }, "funding": { "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/expect/node_modules/chai": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", - "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + }, + "peerDependencies": { + "@vitest/browser": "4.1.10", + "vitest": "4.1.10" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, + "node_modules/@vitest/expect": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", + "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", "dev": true, "license": "MIT", "dependencies": { - "assertion-error": "^2.0.1", - "check-error": "^2.1.1", - "deep-eql": "^5.0.1", - "loupe": "^3.1.0", - "pathval": "^2.0.0" + "@types/chai": "^5.2.2", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" }, - "engines": { - "node": ">=18" + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/@vitest/expect/node_modules/tinyrainbow": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", - "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "node_modules/@vitest/expect/node_modules/@vitest/pretty-format": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", + "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", "dev": true, "license": "MIT", - "engines": { - "node": ">=14.0.0" + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/@vitest/pretty-format": { + "node_modules/@vitest/expect/node_modules/@vitest/utils": { "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", - "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", + "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", "dev": true, "license": "MIT", "dependencies": { + "@vitest/pretty-format": "3.2.4", + "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, - "node_modules/@vitest/pretty-format/node_modules/tinyrainbow": { + "node_modules/@vitest/expect/node_modules/tinyrainbow": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", @@ -4243,6 +4033,96 @@ "node": ">=14.0.0" } }, + "node_modules/@vitest/mocker": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.10.tgz", + "integrity": "sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.10", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/mocker/node_modules/@vitest/spy": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.10.tgz", + "integrity": "sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.10.tgz", + "integrity": "sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.10.tgz", + "integrity": "sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.10", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.10.tgz", + "integrity": "sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.10", + "@vitest/utils": "4.1.10", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/@vitest/spy": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", @@ -4257,30 +4137,20 @@ } }, "node_modules/@vitest/utils": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", - "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.10.tgz", + "integrity": "sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.2.4", - "loupe": "^3.1.4", - "tinyrainbow": "^2.0.0" + "@vitest/pretty-format": "4.1.10", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, - "node_modules/@vitest/utils/node_modules/tinyrainbow": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", - "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, "node_modules/@webcontainer/env": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@webcontainer/env/-/env-1.1.1.tgz", @@ -4289,9 +4159,9 @@ "license": "MIT" }, "node_modules/acorn": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", - "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", + "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", "dev": true, "license": "MIT", "bin": { @@ -4312,9 +4182,9 @@ } }, "node_modules/ajv": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", - "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "dev": true, "license": "MIT", "dependencies": { @@ -4334,7 +4204,6 @@ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -4345,7 +4214,6 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -4397,9 +4265,9 @@ } }, "node_modules/ast-v8-to-istanbul": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.0.tgz", - "integrity": "sha512-1fSfIwuDICFA4LKkCzRPO7F0hzFf0B7+Xqrl27ynQaa+Rh0e1Es0v6kWHPott3lU10AyAr7oKHa65OppjLn3Rg==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.5.tgz", + "integrity": "sha512-UPAgKJFSEGMWSDr3LX4tqnAb4f7KGT8O40Tyx8wbYmmZ/yn58lNCm8h3svs3eXgiGd5AXxz8NDOvXWvicq+rJA==", "dev": true, "license": "MIT", "dependencies": { @@ -4408,6 +4276,16 @@ "js-tokens": "^10.0.0" } }, + "node_modules/ast-v8-to-istanbul/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, "node_modules/ast-v8-to-istanbul/node_modules/js-tokens": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", @@ -4426,9 +4304,9 @@ } }, "node_modules/baseline-browser-mapping": { - "version": "2.10.42", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.42.tgz", - "integrity": "sha512-c/jurFrDLyui7o1J86yLkRu4LMsTYcBohveus7/I2Hzdn9KIP2bdJPTue/lR1KH46enoPbD77GKeSYNdyPoD3Q==", + "version": "2.11.13", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.13.tgz", + "integrity": "sha512-k9HNuUVMlqVjQ9UHzfPjIqiDbWw7WqT1AoT7GL8VwvF3r0ZfArtgiSPAlmupyNquNgOJHTuH4CKYf8ttMTWBTQ==", "dev": true, "license": "Apache-2.0", "bin": { @@ -4462,9 +4340,9 @@ } }, "node_modules/browserslist": { - "version": "4.28.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.4.tgz", - "integrity": "sha512-MTc8i/x9jBQd1iMw2CFGS+rwMa07eYjLR0CCTLDACl9xhxy+nIs3KeML/biicXtk9JrZ6dnnTatmc7ErPXIxqw==", + "version": "4.28.8", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.8.tgz", + "integrity": "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==", "dev": true, "funding": [ { @@ -4482,11 +4360,11 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.10.38", - "caniuse-lite": "^1.0.30001799", - "electron-to-chromium": "^1.5.376", - "node-releases": "^2.0.48", - "update-browserslist-db": "^1.2.3" + "baseline-browser-mapping": "^2.11.12", + "caniuse-lite": "^1.0.30001809", + "electron-to-chromium": "^1.5.402", + "node-releases": "^2.0.53", + "update-browserslist-db": "^1.3.0" }, "bin": { "browserslist": "cli.js" @@ -4512,9 +4390,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001800", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001800.tgz", - "integrity": "sha512-MMHtuAz9Ys840zAY5F4k6fV5GaivZ9sPk+nz0mY+GYVzRBnYkN0mpqkSR92oWRQ19yQWo4HvBV/FnC16AJX8MA==", + "version": "1.0.30001809", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001809.tgz", + "integrity": "sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ==", "dev": true, "funding": [ { @@ -4533,11 +4411,18 @@ "license": "CC-BY-4.0" }, "node_modules/chai": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", - "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", "dev": true, "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, "engines": { "node": ">=18" } @@ -4770,13 +4655,12 @@ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.387", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.387.tgz", - "integrity": "sha512-TaxwufTFDufvPEoXdhwVrA3UdFWBeWGkYoJ1K8ldF1xe6gKfth6iRNS5lTQ5JPNOHdGQm8PT1QYKUqFLCiUefQ==", + "version": "1.5.403", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.403.tgz", + "integrity": "sha512-MQsYmdaLzvaCX5j+ZZBr5Fm6uCCnPQcRtlvmvRlWqrXy+BH2O4ffXIAScF+JQznQWB9brWp4lSD9Z4yNmaf2BA==", "dev": true, "license": "ISC" }, @@ -4791,9 +4675,9 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.21.6", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.6.tgz", - "integrity": "sha512-aNnGCvbJ/RIyWo1IuhNdVjnNF+EjH9wpzpNHt+ci/m9He9LJvUN8wrCcXjp9cWsGNAuvSpVFTx/vraAFQ8qGjQ==", + "version": "5.24.5", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.5.tgz", + "integrity": "sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A==", "dev": true, "license": "MIT", "dependencies": { @@ -4828,16 +4712,16 @@ } }, "node_modules/es-module-lexer": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", - "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.1.tgz", + "integrity": "sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==", "dev": true, "license": "MIT" }, "node_modules/esbuild": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", - "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", + "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -4848,32 +4732,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.28.1", - "@esbuild/android-arm": "0.28.1", - "@esbuild/android-arm64": "0.28.1", - "@esbuild/android-x64": "0.28.1", - "@esbuild/darwin-arm64": "0.28.1", - "@esbuild/darwin-x64": "0.28.1", - "@esbuild/freebsd-arm64": "0.28.1", - "@esbuild/freebsd-x64": "0.28.1", - "@esbuild/linux-arm": "0.28.1", - "@esbuild/linux-arm64": "0.28.1", - "@esbuild/linux-ia32": "0.28.1", - "@esbuild/linux-loong64": "0.28.1", - "@esbuild/linux-mips64el": "0.28.1", - "@esbuild/linux-ppc64": "0.28.1", - "@esbuild/linux-riscv64": "0.28.1", - "@esbuild/linux-s390x": "0.28.1", - "@esbuild/linux-x64": "0.28.1", - "@esbuild/netbsd-arm64": "0.28.1", - "@esbuild/netbsd-x64": "0.28.1", - "@esbuild/openbsd-arm64": "0.28.1", - "@esbuild/openbsd-x64": "0.28.1", - "@esbuild/openharmony-arm64": "0.28.1", - "@esbuild/sunos-x64": "0.28.1", - "@esbuild/win32-arm64": "0.28.1", - "@esbuild/win32-ia32": "0.28.1", - "@esbuild/win32-x64": "0.28.1" + "@esbuild/aix-ppc64": "0.28.2", + "@esbuild/android-arm": "0.28.2", + "@esbuild/android-arm64": "0.28.2", + "@esbuild/android-x64": "0.28.2", + "@esbuild/darwin-arm64": "0.28.2", + "@esbuild/darwin-x64": "0.28.2", + "@esbuild/freebsd-arm64": "0.28.2", + "@esbuild/freebsd-x64": "0.28.2", + "@esbuild/linux-arm": "0.28.2", + "@esbuild/linux-arm64": "0.28.2", + "@esbuild/linux-ia32": "0.28.2", + "@esbuild/linux-loong64": "0.28.2", + "@esbuild/linux-mips64el": "0.28.2", + "@esbuild/linux-ppc64": "0.28.2", + "@esbuild/linux-riscv64": "0.28.2", + "@esbuild/linux-s390x": "0.28.2", + "@esbuild/linux-x64": "0.28.2", + "@esbuild/netbsd-arm64": "0.28.2", + "@esbuild/netbsd-x64": "0.28.2", + "@esbuild/openbsd-arm64": "0.28.2", + "@esbuild/openbsd-x64": "0.28.2", + "@esbuild/openharmony-arm64": "0.28.2", + "@esbuild/sunos-x64": "0.28.2", + "@esbuild/win32-arm64": "0.28.2", + "@esbuild/win32-ia32": "0.28.2", + "@esbuild/win32-x64": "0.28.2" } }, "node_modules/escalade": { @@ -4900,9 +4784,9 @@ } }, "node_modules/eslint": { - "version": "10.7.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.7.0.tgz", - "integrity": "sha512-GVTD7s1vdIl6UYvAfriOPeY1Df8LIZjfofLvHwde+erDHGGuHyuM6xoxRxmHiebhYuD2p1vN4wWh0XzPARSGDQ==", + "version": "10.8.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.8.1.tgz", + "integrity": "sha512-wqA7W2jbsC/BnV9Iv1UZpKVFkO1AdNoSmYW8NWG4HNOBbkAMvIqDZ27pI2f07dqn583NcIC44ckjAcOXDL1QbQ==", "dev": true, "license": "MIT", "workspaces": [ @@ -4912,7 +4796,7 @@ "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", "@eslint/config-array": "^0.23.5", - "@eslint/config-helpers": "^0.6.0", + "@eslint/config-helpers": "^0.7.0", "@eslint/core": "^1.2.1", "@eslint/plugin-kit": "^0.7.2", "@humanfs/node": "^0.16.6", @@ -4936,7 +4820,7 @@ "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", - "minimatch": "^10.2.4", + "minimatch": "^10.2.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, @@ -4959,13 +4843,13 @@ } }, "node_modules/eslint-plugin-jsdoc": { - "version": "63.0.13", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-63.0.13.tgz", - "integrity": "sha512-ahG1kWA8jYNwaQJtzJlnF+v4Gb9w5r+WL98gp+L8qjLN9ErpL5sevGuemN+fCYsU3Np27F36KmDc8UPi1ml/dg==", + "version": "63.3.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-63.3.3.tgz", + "integrity": "sha512-xI4IeVRzRFA2DGHrPLIxF3U+oJHU3FE+P9Zb27fVs5dPHgfcpoAs0PyCbznVhK7pwR+9BPUztFeXSgpw/CL4Yg==", "dev": true, "license": "BSD-3-Clause", "dependencies": { - "@es-joy/jsdoccomment": "~0.88.0", + "@es-joy/jsdoccomment": "~0.91.0", "@es-joy/resolve.exports": "1.2.0", "are-docs-informative": "^0.0.2", "comment-parser": "1.4.7", @@ -4977,7 +4861,7 @@ "object-deep-merge": "^2.0.1", "parse-imports-exports": "^0.2.4", "semver": "^7.8.5", - "spdx-expression-parse": "^4.0.0", + "spdx-expression-parse": "^5.0.0", "to-valid-identifier": "^1.0.0" }, "engines": { @@ -5088,14 +4972,11 @@ } }, "node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - } + "license": "MIT" }, "node_modules/esutils": { "version": "2.0.3", @@ -5108,9 +4989,9 @@ } }, "node_modules/expect-type": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", - "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", + "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -5118,9 +4999,9 @@ } }, "node_modules/fast-check": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-4.8.0.tgz", - "integrity": "sha512-GOJ158CUMnN6cSahsv4+ExARvIDuzzinFjkp0E9WtiBa5zcVeLozVkWaE4IzFcc+Y48Wp1EDlUZsXRyAztQcSg==", + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-4.9.0.tgz", + "integrity": "sha512-7ms6T7SybUev/PQITciI0yLM2pOSFy5zpG8Ty7tQofcVaQUvrMXp6CBwqF6fThLCLOrfBtuHAtwq6Yu4XPCllg==", "dev": true, "funding": [ { @@ -5224,9 +5105,9 @@ } }, "node_modules/flatted": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", - "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz", + "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==", "dev": true, "license": "ISC" }, @@ -5554,9 +5435,9 @@ "license": "MIT" }, "node_modules/jsdoc-type-pratt-parser": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-7.2.0.tgz", - "integrity": "sha512-dh140MMgjyg3JhJZY/+iEzW+NO5xR2gpbDFKHqotCmexElVntw7GjWjt511+C/Ef02RU5TKYrJo/Xlzk+OLaTw==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-8.0.0.tgz", + "integrity": "sha512-uQu/fXVqVaMg6gM8/E5G5+eygVcZ1NV0Z51CvqhNa2bDWxvHMl484ETr6vph4oPyC+KUcbP/w2W2pewfCiR9aQ==", "dev": true, "license": "MIT", "engines": { @@ -5651,6 +5532,13 @@ "node": ">=6" } }, + "node_modules/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "dev": true, + "license": "MIT" + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -5818,6 +5706,9 @@ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -5839,6 +5730,9 @@ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -5860,6 +5754,9 @@ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -5881,6 +5778,9 @@ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -5960,9 +5860,9 @@ "license": "MIT" }, "node_modules/lru-cache": { - "version": "11.5.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.1.tgz", - "integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==", + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", "dev": true, "license": "BlueOak-1.0.0", "engines": { @@ -5970,9 +5870,9 @@ } }, "node_modules/lucide-react": { - "version": "1.24.0", - "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.24.0.tgz", - "integrity": "sha512-YT6mBD8lGKkg4nM39enlm94/sfJIiW0YKUT60fBy4YK8tai31ylg1VhGNWxkpSKHo9UagfnZqwIff3HTDQwXeA==", + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.30.0.tgz", + "integrity": "sha512-tUIr2jXLbWpCkdtH8XP7P7YppM9ueWgTky99lpWDY6z5REs6B+O6ZQ3U5tHkUUY59ANyOv/PBcs8E4Fe3KO3eA==", "license": "ISC", "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" @@ -5984,7 +5884,6 @@ "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", "dev": true, "license": "MIT", - "peer": true, "bin": { "lz-string": "bin/bin.js" } @@ -6000,14 +5899,14 @@ } }, "node_modules/magicast": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.2.tgz", - "integrity": "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.4.tgz", + "integrity": "sha512-llBEhWm1SacoRwgHUoQJYtwp4PBLF4faQi5TCpIGyGs9n4y5+juI0tDgyKIfpqxckRHaHzouUEph3THklWh03w==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", "source-map-js": "^1.2.1" } }, @@ -6045,13 +5944,13 @@ } }, "node_modules/minimatch": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", - "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^5.0.2" + "brace-expansion": "^5.0.8" }, "engines": { "node": "18 || 20 || >=22" @@ -6088,10 +5987,9 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.16", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", - "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", - "dev": true, + "version": "3.3.18", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", "funding": [ { "type": "github", @@ -6114,9 +6012,9 @@ "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.50", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.50.tgz", - "integrity": "sha512-J6l92tKHX6w8Jy5nO1Vuc01NoIiRGi/d6qBKVxh+IQ8Cr3b6HbVNfKiF8ZpFKufTwpwxMmce2W3iQZ861ZRyTg==", + "version": "2.0.53", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.53.tgz", + "integrity": "sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ==", "dev": true, "license": "MIT", "engines": { @@ -6131,15 +6029,18 @@ "license": "MIT" }, "node_modules/obug": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", - "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.4.tgz", + "integrity": "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==", "dev": true, "funding": [ "https://github.com/sponsors/sxzz", "https://opencollective.com/debug" ], - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } }, "node_modules/open": { "version": "10.2.0", @@ -6216,45 +6117,35 @@ "@oxc-parser/binding-win32-x64-msvc": "0.127.0" } }, - "node_modules/oxc-parser/node_modules/@oxc-project/types": { - "version": "0.127.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.127.0.tgz", - "integrity": "sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/Boshen" - } - }, "node_modules/oxc-resolver": { - "version": "11.23.0", - "resolved": "https://registry.npmjs.org/oxc-resolver/-/oxc-resolver-11.23.0.tgz", - "integrity": "sha512-f0+l598CJMOLnYPXsXxttJALH0ljtivdRMKtvHhxRuWa5FYmw5+qODARl8oYjMC/brpzKcrpdORsOBrTqhBZ9A==", + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/oxc-resolver/-/oxc-resolver-11.24.2.tgz", + "integrity": "sha512-FY91FiDBj7ls5MsFS9jN3tjz2o0/zsdSsymlakySaBwVJZorHhkWyICLZMKxlu1R9vYo+sd3z1jwb4J8x7bNDw==", "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/Boshen" }, "optionalDependencies": { - "@oxc-resolver/binding-android-arm-eabi": "11.23.0", - "@oxc-resolver/binding-android-arm64": "11.23.0", - "@oxc-resolver/binding-darwin-arm64": "11.23.0", - "@oxc-resolver/binding-darwin-x64": "11.23.0", - "@oxc-resolver/binding-freebsd-x64": "11.23.0", - "@oxc-resolver/binding-linux-arm-gnueabihf": "11.23.0", - "@oxc-resolver/binding-linux-arm-musleabihf": "11.23.0", - "@oxc-resolver/binding-linux-arm64-gnu": "11.23.0", - "@oxc-resolver/binding-linux-arm64-musl": "11.23.0", - "@oxc-resolver/binding-linux-ppc64-gnu": "11.23.0", - "@oxc-resolver/binding-linux-riscv64-gnu": "11.23.0", - "@oxc-resolver/binding-linux-riscv64-musl": "11.23.0", - "@oxc-resolver/binding-linux-s390x-gnu": "11.23.0", - "@oxc-resolver/binding-linux-x64-gnu": "11.23.0", - "@oxc-resolver/binding-linux-x64-musl": "11.23.0", - "@oxc-resolver/binding-openharmony-arm64": "11.23.0", - "@oxc-resolver/binding-wasm32-wasi": "11.23.0", - "@oxc-resolver/binding-win32-arm64-msvc": "11.23.0", - "@oxc-resolver/binding-win32-x64-msvc": "11.23.0" + "@oxc-resolver/binding-android-arm-eabi": "11.24.2", + "@oxc-resolver/binding-android-arm64": "11.24.2", + "@oxc-resolver/binding-darwin-arm64": "11.24.2", + "@oxc-resolver/binding-darwin-x64": "11.24.2", + "@oxc-resolver/binding-freebsd-x64": "11.24.2", + "@oxc-resolver/binding-linux-arm-gnueabihf": "11.24.2", + "@oxc-resolver/binding-linux-arm-musleabihf": "11.24.2", + "@oxc-resolver/binding-linux-arm64-gnu": "11.24.2", + "@oxc-resolver/binding-linux-arm64-musl": "11.24.2", + "@oxc-resolver/binding-linux-ppc64-gnu": "11.24.2", + "@oxc-resolver/binding-linux-riscv64-gnu": "11.24.2", + "@oxc-resolver/binding-linux-riscv64-musl": "11.24.2", + "@oxc-resolver/binding-linux-s390x-gnu": "11.24.2", + "@oxc-resolver/binding-linux-x64-gnu": "11.24.2", + "@oxc-resolver/binding-linux-x64-musl": "11.24.2", + "@oxc-resolver/binding-openharmony-arm64": "11.24.2", + "@oxc-resolver/binding-wasm32-wasi": "11.24.2", + "@oxc-resolver/binding-win32-arm64-msvc": "11.24.2", + "@oxc-resolver/binding-win32-x64-msvc": "11.24.2" } }, "node_modules/p-limit": { @@ -6380,6 +6271,18 @@ "node": ">= 14.16" } }, + "node_modules/pdfjs-dist": { + "version": "6.2.108", + "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-6.2.108.tgz", + "integrity": "sha512-YxFb+SQcodN2rnX9Tn3dHYlqfb7NjlzzfONPpJd+AKoKtUjEdevTfbC07d5TcczzOK6261auRkP/M8OBHs9vFQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=22.13.0 || >=24" + }, + "optionalDependencies": { + "@napi-rs/canvas": "^1.0.0" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -6445,7 +6348,6 @@ "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", @@ -6466,9 +6368,9 @@ } }, "node_modules/pure-rand": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-8.4.0.tgz", - "integrity": "sha512-IoM8YF/jY0hiugFo/wOWqfmarlE6J0wc6fDK1PhftMk7MGhVZl88sZimmqBBFomLOCSmcCCpsfj7wXASCpvK9A==", + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-8.4.2.tgz", + "integrity": "sha512-vvuOGgcuPJAirlHvuQw1TrOiw7ptaIXXmIbNuiNOY6lNGJJH49PQ1Kj4nd783nPdQhQdicgOjVI2yI/9BD6/Ng==", "dev": true, "funding": [ { @@ -6483,9 +6385,9 @@ "license": "MIT" }, "node_modules/react": { - "version": "19.2.7", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", - "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==", + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz", + "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -6523,29 +6425,16 @@ "typescript": ">= 4.3.x" } }, - "node_modules/react-docgen/node_modules/strip-indent": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-4.1.1.tgz", - "integrity": "sha512-SlyRoSkdh1dYP0PzclLE7r0M9sgbFKKMFXpFRUMNuKhQSbC6VQIGzq3E0qsfvGJaUFJPGv6Ws1NZ/haTAjfbMA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/react-dom": { - "version": "19.2.7", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz", - "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==", + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz", + "integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==", "license": "MIT", "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { - "react": "^19.2.7" + "react": "^19.2.8" } }, "node_modules/react-is": { @@ -6553,13 +6442,12 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/recast": { - "version": "0.23.12", - "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.12.tgz", - "integrity": "sha512-dEWRjcINDu/F4l2dYx57ugBtD7HV9KXESyxhzw/MqWLeglJrsjJKqACPyUPg+6AF8mIgm+Zi0dZ3ACoIg+QtpA==", + "version": "0.23.19", + "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.19.tgz", + "integrity": "sha512-T98lym7kH+pnZmRaD8yDRdaNqyUbwnbEBx0MuchrzMFOEMray4AO3ZJoTUZ5r78Ao78X/OhzW0DL8GB85w/I2w==", "dev": true, "license": "MIT", "dependencies": { @@ -6587,12 +6475,25 @@ "node": ">=8" } }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, - "license": "MIT", + "node_modules/redent/node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -6639,13 +6540,13 @@ } }, "node_modules/rolldown": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.5.tgz", - "integrity": "sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.3.tgz", + "integrity": "sha512-rn9wpmxplLf7NLNyCk9FyWh3FM43DbY8jOzCdEPzH7uflhTftRbCEpqi6Ly2osgoU8OwObtmavMbWLaWy4LX7A==", "dev": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.139.0", + "@oxc-project/types": "=0.143.0", "@rolldown/pluginutils": "^1.0.0" }, "bin": { @@ -6655,21 +6556,30 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.1.5", - "@rolldown/binding-darwin-arm64": "1.1.5", - "@rolldown/binding-darwin-x64": "1.1.5", - "@rolldown/binding-freebsd-x64": "1.1.5", - "@rolldown/binding-linux-arm-gnueabihf": "1.1.5", - "@rolldown/binding-linux-arm64-gnu": "1.1.5", - "@rolldown/binding-linux-arm64-musl": "1.1.5", - "@rolldown/binding-linux-ppc64-gnu": "1.1.5", - "@rolldown/binding-linux-s390x-gnu": "1.1.5", - "@rolldown/binding-linux-x64-gnu": "1.1.5", - "@rolldown/binding-linux-x64-musl": "1.1.5", - "@rolldown/binding-openharmony-arm64": "1.1.5", - "@rolldown/binding-wasm32-wasi": "1.1.5", - "@rolldown/binding-win32-arm64-msvc": "1.1.5", - "@rolldown/binding-win32-x64-msvc": "1.1.5" + "@rolldown/binding-android-arm64": "1.2.3", + "@rolldown/binding-darwin-arm64": "1.2.3", + "@rolldown/binding-darwin-x64": "1.2.3", + "@rolldown/binding-freebsd-x64": "1.2.3", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.3", + "@rolldown/binding-linux-arm64-gnu": "1.2.3", + "@rolldown/binding-linux-arm64-musl": "1.2.3", + "@rolldown/binding-linux-ppc64-gnu": "1.2.3", + "@rolldown/binding-linux-s390x-gnu": "1.2.3", + "@rolldown/binding-linux-x64-gnu": "1.2.3", + "@rolldown/binding-linux-x64-musl": "1.2.3", + "@rolldown/binding-openharmony-arm64": "1.2.3", + "@rolldown/binding-win32-arm64-msvc": "1.2.3", + "@rolldown/binding-win32-x64-msvc": "1.2.3" + } + }, + "node_modules/rolldown/node_modules/@oxc-project/types": { + "version": "0.143.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.143.0.tgz", + "integrity": "sha512-u6JZdLBTLotrNC9Vd6vPssINdzcCzleKAH6EJKImQb7GtYvX5keN2dxkoK44stCc4tffE6QQRtZTXVSzsLUlWA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" } }, "node_modules/run-applescript": { @@ -6748,13 +6658,19 @@ "license": "ISC" }, "node_modules/sonner": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.7.tgz", - "integrity": "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.8.tgz", + "integrity": "sha512-UM/ByIoFra8yzV75n1o0Puu0bw5U/9UNnDacrJNspekBewIfsQ3D6ez1nvlWpt7aTsO6rujQtifBpycwIivqlg==", "license": "MIT", "peerDependencies": { + "@types/react": "^18.0.0 || ^19.0.0", "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, "node_modules/source-map": { @@ -6785,9 +6701,9 @@ "license": "CC-BY-3.0" }, "node_modules/spdx-expression-parse": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-4.0.0.tgz", - "integrity": "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-5.0.0.tgz", + "integrity": "sha512-vngmw3Rgn+o2arXNbnZaj5UtOEBuWBfvaI+Wc8GFfykIhA5/vdK9/Sp/XkLv63dykz2rxKDvKEHupF5P0FORcQ==", "dev": true, "license": "MIT", "dependencies": { @@ -6810,34 +6726,36 @@ "license": "MIT" }, "node_modules/std-env": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz", - "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.2.0.tgz", + "integrity": "sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==", "dev": true, "license": "MIT" }, "node_modules/storybook": { - "version": "10.4.6", - "resolved": "https://registry.npmjs.org/storybook/-/storybook-10.4.6.tgz", - "integrity": "sha512-6wkA6LxfDSSilloITsrFOJfsnw0mDUP2h8Ls+lRt8oRsudtz2RWFhLv+Toiwg6NW7hUpdTDc2hzR7DztJid6+A==", + "version": "10.5.7", + "resolved": "https://registry.npmjs.org/storybook/-/storybook-10.5.7.tgz", + "integrity": "sha512-oiKvWIwIoOhFP1i6dASYyMXwPHKEtVZMshqSB7EvIVYjWRh0l9H7gHEt1z4Gh2rLGFMekWdsm4s94rvwpR7gkg==", "dev": true, "license": "MIT", "dependencies": { "@storybook/global": "^5.0.0", "@storybook/icons": "^2.0.2", - "@testing-library/jest-dom": "^6.9.1", + "@testing-library/dom": "^10.4.1", + "@testing-library/jest-dom": "6.9.1", "@testing-library/user-event": "^14.6.1", "@vitest/expect": "3.2.4", "@vitest/spy": "3.2.4", "@webcontainer/env": "^1.1.1", "esbuild": "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0 || ^0.26.0 || ^0.27.0 || ^0.28.0", + "jsonc-parser": "^3.3.1", "open": "^10.2.0", "oxc-parser": "^0.127.0", "oxc-resolver": "^11.19.1", "recast": "^0.23.5", "semver": "^7.7.3", "use-sync-external-store": "^1.5.0", - "ws": "^8.18.0" + "ws": "^8.21.1" }, "bin": { "storybook": "dist/bin/dispatcher.js" @@ -6849,7 +6767,7 @@ "peerDependencies": { "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "prettier": "^2 || ^3", - "vite-plus": "^0.1.15" + "vite-plus": "^0.1.15 || ^0.2.0" }, "peerDependenciesMeta": { "@types/react": { @@ -6874,16 +6792,16 @@ } }, "node_modules/strip-indent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", - "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-4.1.1.tgz", + "integrity": "sha512-SlyRoSkdh1dYP0PzclLE7r0M9sgbFKKMFXpFRUMNuKhQSbC6VQIGzq3E0qsfvGJaUFJPGv6Ws1NZ/haTAjfbMA==", "dev": true, "license": "MIT", - "dependencies": { - "min-indent": "^1.0.0" - }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/supports-color": { @@ -6930,9 +6848,9 @@ } }, "node_modules/tailwindcss": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.2.tgz", - "integrity": "sha512-WtctNNSH8A9jlMIqxzuYumOHU5uGZyRv0Q5svQl+oEPy5w84YpBxdb7MdqyiSPQge5jTJ6zFQLq0PFygdccSBA==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.3.tgz", + "integrity": "sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==", "dev": true, "license": "MIT" }, @@ -6965,9 +6883,9 @@ "license": "MIT" }, "node_modules/tinyexec": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.2.tgz", - "integrity": "sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.0.tgz", + "integrity": "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==", "dev": true, "license": "MIT", "engines": { @@ -6992,9 +6910,9 @@ } }, "node_modules/tinyrainbow": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", - "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.1.tgz", + "integrity": "sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==", "dev": true, "license": "MIT", "engines": { @@ -7012,22 +6930,22 @@ } }, "node_modules/tldts": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.27.tgz", - "integrity": "sha512-I4FZcVFcqCRuT0ph6dCDpPuO4Xgzvh+spkcTr1gK7peIvxWauoloVO0vuy1FQnijT63ss6AsHB6+OIM4aXHbPg==", + "version": "7.4.10", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.4.10.tgz", + "integrity": "sha512-GgouD1B+sWwvkaEq8vXC15DjQitxbvs12oIXELpconwm+Tg3zfcEv4jgzq3vtKverDXsg3VI8aRgNL2Nra0Iog==", "dev": true, "license": "MIT", "dependencies": { - "tldts-core": "^7.0.27" + "tldts-core": "^7.4.10" }, "bin": { "tldts": "bin/cli.js" } }, "node_modules/tldts-core": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.27.tgz", - "integrity": "sha512-YQ7uPjgWUibIK6DW5lrKujGwUKhLevU4hcGbP5O6TcIUb+oTjJYJVWPS4nZsIHrEEEG6myk/oqAJUEQmpZrHsg==", + "version": "7.4.10", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.4.10.tgz", + "integrity": "sha512-KnQjp53ZekKgm/r3l+u8kJGGzYgrWdP8+Mql7a4vijh2WE0IrZWspQj/TpTxDho/YxO+AnOZnIjQcCD+q6iJsw==", "dev": true, "license": "MIT" }, @@ -7049,9 +6967,9 @@ } }, "node_modules/tough-cookie": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.1.tgz", - "integrity": "sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.2.tgz", + "integrity": "sha512-exgYmnmL/sJpR3upZfXG5PoatXQii55xAiXGXzY+sROLZ/Y+SLcp9PgJNI9Vz37HpQ74WvDcLT8eqm+kV3FzrA==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -7156,16 +7074,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.63.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.63.0.tgz", - "integrity": "sha512-xgwXyzG4sK9ALkBxbyGkTMMOS+imnW65iPhxCQMK83KhxyoDNW7l+IDqEf9vMdoUidHpOoS967RCq4eMiTexwQ==", + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.66.0.tgz", + "integrity": "sha512-QlEbBPz/RuJ1XUHj29nm3t0F/O/cSlEnntozqPOYHnnTGAXFamnMBu5i9Vn6vhUPHGAjR+Vl+5J8vPN/BMUrJw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.63.0", - "@typescript-eslint/parser": "8.63.0", - "@typescript-eslint/typescript-estree": "8.63.0", - "@typescript-eslint/utils": "8.63.0" + "@typescript-eslint/eslint-plugin": "8.66.0", + "@typescript-eslint/parser": "8.66.0", + "@typescript-eslint/typescript-estree": "8.66.0", + "@typescript-eslint/utils": "8.66.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -7183,7 +7101,6 @@ "version": "7.29.0", "resolved": "https://registry.npmjs.org/undici/-/undici-7.29.0.tgz", "integrity": "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==", - "dev": true, "license": "MIT", "engines": { "node": ">=20.18.1" @@ -7213,9 +7130,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.0.tgz", + "integrity": "sha512-x/M6q3w4Ybp91CNaS4S69UnliqR3BzRpOT6LWbksjth0S/+jhfaPJsWjt/TewpT8j9eLIojUf5jr29WextHroA==", "dev": true, "funding": [ { @@ -7263,16 +7180,16 @@ } }, "node_modules/vite": { - "version": "8.1.4", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.4.tgz", - "integrity": "sha512-bTT9PsdWO+MQMNG9ZXIP/qM9wGh37DFxTV/sPq9cFpHr3w4jkgef032PkAL9jAqhk3Nz8NQw3O8n6/xFkqO4QQ==", + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.1.tgz", + "integrity": "sha512-EU/eS7BH3XROHh2YnBefjM6DBKA6ZeMZEYQbj7NLWg5wHYlhB8B/Mayd5XsgWq+NFYccDOTemRpdETWR6Ka/lw==", "dev": true, "license": "MIT", "dependencies": { - "lightningcss": "^1.32.0", + "lightningcss": "^1.33.0", "picomatch": "^4.0.5", - "postcss": "^8.5.16", - "rolldown": "~1.1.4", + "postcss": "^8.5.25", + "rolldown": "~1.2.1", "tinyglobby": "^0.2.17" }, "bin": { @@ -7289,7 +7206,7 @@ }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.3.0", + "@vitejs/devtools": "^0.4.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", @@ -7340,337 +7257,280 @@ } } }, - "node_modules/w3c-xmlserializer": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", - "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "node_modules/vite/node_modules/lightningcss": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", "dev": true, - "license": "MIT", + "license": "MPL-2.0", "dependencies": { - "xml-name-validator": "^5.0.0" + "detect-libc": "^2.0.3" }, "engines": { - "node": ">=18" - } - }, - "node_modules/webidl-conversions": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", - "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" + } + }, + "node_modules/vite/node_modules/lightningcss-android-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "BSD-2-Clause", + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=20" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/webpack-virtual-modules": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", - "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/whatwg-mimetype": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", - "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", + "node_modules/vite/node_modules/lightningcss-darwin-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT", + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=20" - } - }, - "node_modules/whatwg-url": { - "version": "16.0.1", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", - "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@exodus/bytes": "^1.11.0", - "tr46": "^6.0.0", - "webidl-conversions": "^8.0.1" + "node": ">= 12.0.0" }, - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "node_modules/vite/node_modules/lightningcss-darwin-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", + "cpu": [ + "x64" + ], "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">= 8" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/why-is-node-running": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", - "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "node_modules/vite/node_modules/lightningcss-freebsd-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT", - "dependencies": { - "siginfo": "^2.0.0", - "stackback": "0.0.2" - }, - "bin": { - "why-is-node-running": "cli.js" - }, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">=8" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "node_modules/vite/node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", + "cpu": [ + "arm" + ], "dev": true, - "license": "MIT", + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=0.10.0" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/ws": { - "version": "8.21.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", - "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", + "node_modules/vite/node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT", + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" + "node": ">= 12.0.0" }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/wsl-utils": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", - "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", + "node_modules/vite/node_modules/lightningcss-linux-arm64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT", - "dependencies": { - "is-wsl": "^3.1.0" - }, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=18" + "node": ">= 12.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/xml-name-validator": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", - "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/xmlchars": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", - "dev": true, - "license": "MIT" - }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, - "license": "ISC" - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "node_modules/vite/node_modules/lightningcss-linux-x64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT", + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "packages/shared-types": { - "name": "@bandscope/shared-types", - "version": "0.1.0", - "devDependencies": { - "@types/node": "^26.1.1", - "@vitest/coverage-v8": "^4.1.10", - "eslint": "^10.7.0", - "fast-check": "^4.8.0", - "typescript": "^6.0.3", - "typescript-eslint": "^8.63.0", - "vitest": "^4.1.10" - } - }, - "packages/shared-types/node_modules/@vitest/coverage-v8": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.10.tgz", - "integrity": "sha512-IM49HmthevbgAO4anp1hwtoT9wYe59w0LR00gr+eagHE+ZJ5lK4sLPeO0ubgoJcwLk6dehU3R24N+FbEEKDc8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@bcoe/v8-coverage": "^1.0.2", - "@vitest/utils": "4.1.10", - "ast-v8-to-istanbul": "^1.0.0", - "istanbul-lib-coverage": "^3.2.2", - "istanbul-lib-report": "^3.0.1", - "istanbul-reports": "^3.2.0", - "magicast": "^0.5.2", - "obug": "^2.1.1", - "std-env": "^4.0.0-rc.1", - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "@vitest/browser": "4.1.10", - "vitest": "4.1.10" - }, - "peerDependenciesMeta": { - "@vitest/browser": { - "optional": true - } - } - }, - "packages/shared-types/node_modules/@vitest/expect": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.10.tgz", - "integrity": "sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@standard-schema/spec": "^1.1.0", - "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.10", - "@vitest/utils": "4.1.10", - "chai": "^6.2.2", - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "packages/shared-types/node_modules/@vitest/mocker": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.10.tgz", - "integrity": "sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/spy": "4.1.10", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.21" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "msw": "^2.4.9", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "msw": { - "optional": true - }, - "vite": { - "optional": true - } - } - }, - "packages/shared-types/node_modules/@vitest/pretty-format": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.10.tgz", - "integrity": "sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "tinyrainbow": "^3.1.0" + "node": ">= 12.0.0" }, "funding": { - "url": "https://opencollective.com/vitest" + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "packages/shared-types/node_modules/@vitest/runner": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.10.tgz", - "integrity": "sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==", + "node_modules/vite/node_modules/lightningcss-linux-x64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/utils": "4.1.10", - "pathe": "^2.0.3" + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" }, "funding": { - "url": "https://opencollective.com/vitest" + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "packages/shared-types/node_modules/@vitest/snapshot": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.10.tgz", - "integrity": "sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==", + "node_modules/vite/node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.1.10", - "@vitest/utils": "4.1.10", - "magic-string": "^0.30.21", - "pathe": "^2.0.3" + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" }, "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "packages/shared-types/node_modules/@vitest/spy": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.10.tgz", - "integrity": "sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://opencollective.com/vitest" + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "packages/shared-types/node_modules/@vitest/utils": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.10.tgz", - "integrity": "sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==", + "node_modules/vite/node_modules/lightningcss-win32-x64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.1.10", - "convert-source-map": "^2.0.0", - "tinyrainbow": "^3.1.0" + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" }, "funding": { - "url": "https://opencollective.com/vitest" + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "packages/shared-types/node_modules/vitest": { + "node_modules/vitest": { "version": "4.1.10", "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.10.tgz", "integrity": "sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==", @@ -7759,6 +7619,230 @@ "optional": false } } + }, + "node_modules/vitest/node_modules/@vitest/expect": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.10.tgz", + "integrity": "sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.10", + "@vitest/utils": "4.1.10", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest/node_modules/@vitest/spy": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.10.tgz", + "integrity": "sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest/node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/webidl-conversions": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20" + } + }, + "node_modules/webpack-virtual-modules": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", + "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/whatwg-mimetype": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/whatwg-url": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", + "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.11.0", + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ws": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.3.tgz", + "integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/wsl-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", + "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/shared-types": { + "name": "@bandscope/shared-types", + "version": "0.1.0", + "devDependencies": { + "@types/node": "^26.1.1", + "@vitest/coverage-v8": "^4.1.10", + "eslint": "^10.7.0", + "fast-check": "^4.8.0", + "typescript": "^6.0.3", + "typescript-eslint": "^8.63.0", + "vitest": "^4.1.10" + } } } } From 63d0ae6c11ad674f2b06560ad562644670fa06fc Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 03:54:00 +0900 Subject: [PATCH 039/161] fix(deps): restore manifest-lock consistency --- package-lock.json | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 973344f3b..c85ae3cea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,16 +11,12 @@ "apps/*", "packages/*" ], - "dependencies": { - "nanoid": "^3.3.18", - "pdfjs-dist": "^6.2.108", - "undici": "^7.29.0" - }, "devDependencies": { "@eslint/js": "^10.0.1", "eslint-plugin-jsdoc": "^63.0.13", "react": "^19.2.4", - "react-dom": "^19.2.7" + "react-dom": "^19.2.7", + "undici": "7.29.0" }, "engines": { "node": ">=22.13 <23" From 6bba612e76b64060c968eb2caf7936724377db22 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 14 Aug 2026 16:48:40 +0900 Subject: [PATCH 040/161] ci: add one-shot canonical lock repair for PR 783 --- .github/workflows/repair-pr-783-lock.yml | 90 ++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 .github/workflows/repair-pr-783-lock.yml diff --git a/.github/workflows/repair-pr-783-lock.yml b/.github/workflows/repair-pr-783-lock.yml new file mode 100644 index 000000000..f83ff8d0a --- /dev/null +++ b/.github/workflows/repair-pr-783-lock.yml @@ -0,0 +1,90 @@ +name: repair-pr-783-lock + +on: + push: + branches: + - fix/high-security-dependency-baseline + workflow_dispatch: + +permissions: + contents: write + +concurrency: + group: repair-pr-783-lock + cancel-in-progress: false + +jobs: + regenerate-lock: + name: regenerate canonical npm lock + runs-on: ubuntu-latest + timeout-minutes: 15 + env: + EXPECTED_NODE_VERSION: "v22.22.3" + EXPECTED_NPM_VERSION: "10.9.8" + steps: + - name: Check out the exact feature branch + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + ref: fix/high-security-dependency-baseline + fetch-depth: 1 + + - name: Install the pinned Node runtime + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: "22.22.3" + cache: npm + + - name: Verify the lock generator contract + shell: bash + run: | + set -euo pipefail + test "$(node --version)" = "$EXPECTED_NODE_VERSION" + test "$(npm --version)" = "$EXPECTED_NPM_VERSION" + python3 - <<'PY' + import json + from pathlib import Path + + root = json.loads(Path("package.json").read_text(encoding="utf-8")) + desktop = json.loads(Path("apps/desktop/package.json").read_text(encoding="utf-8")) + assert root["packageManager"] == "npm@10.9.8" + assert root["devDependencies"]["undici"] == "7.29.0" + assert root["overrides"]["undici"] == "$undici" + assert desktop["dependencies"]["pdfjs-dist"] == "6.2.108" + PY + + - name: Regenerate without lifecycle execution + shell: bash + run: | + set -euo pipefail + npm install --package-lock-only --ignore-scripts --no-audit --no-fund + test -z "$(git diff --name-only -- package.json apps/desktop/package.json)" + + - name: Verify generated dependency identities and audit + shell: bash + run: | + set -euo pipefail + python3 - <<'PY' + import json + from pathlib import Path + + lock = json.loads(Path("package-lock.json").read_text(encoding="utf-8")) + packages = lock["packages"] + assert lock["lockfileVersion"] == 3 + assert packages[""]["devDependencies"]["undici"] == "7.29.0" + assert packages["apps/desktop"]["dependencies"]["pdfjs-dist"] == "6.2.108" + assert packages["node_modules/undici"]["version"] == "7.29.0" + assert packages["node_modules/pdfjs-dist"]["version"] == "6.2.108" + PY + npm audit --workspaces --audit-level=high + + - name: Commit the canonical artifact and remove this one-shot workflow + shell: bash + run: | + set -euo pipefail + rm .github/workflows/repair-pr-783-lock.yml + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add package-lock.json .github/workflows/repair-pr-783-lock.yml + git diff --cached --check + git commit -m "fix(deps): regenerate canonical npm 10.9.8 lock" + git push origin HEAD:fix/high-security-dependency-baseline From ef938fa7450485bf5dbdb56fe1e5e6fea1ddf2a5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 14 Aug 2026 07:48:55 +0000 Subject: [PATCH 041/161] fix(deps): regenerate canonical npm 10.9.8 lock --- .github/workflows/repair-pr-783-lock.yml | 90 ------------- package-lock.json | 160 ++++------------------- 2 files changed, 28 insertions(+), 222 deletions(-) delete mode 100644 .github/workflows/repair-pr-783-lock.yml diff --git a/.github/workflows/repair-pr-783-lock.yml b/.github/workflows/repair-pr-783-lock.yml deleted file mode 100644 index f83ff8d0a..000000000 --- a/.github/workflows/repair-pr-783-lock.yml +++ /dev/null @@ -1,90 +0,0 @@ -name: repair-pr-783-lock - -on: - push: - branches: - - fix/high-security-dependency-baseline - workflow_dispatch: - -permissions: - contents: write - -concurrency: - group: repair-pr-783-lock - cancel-in-progress: false - -jobs: - regenerate-lock: - name: regenerate canonical npm lock - runs-on: ubuntu-latest - timeout-minutes: 15 - env: - EXPECTED_NODE_VERSION: "v22.22.3" - EXPECTED_NPM_VERSION: "10.9.8" - steps: - - name: Check out the exact feature branch - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - ref: fix/high-security-dependency-baseline - fetch-depth: 1 - - - name: Install the pinned Node runtime - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 - with: - node-version: "22.22.3" - cache: npm - - - name: Verify the lock generator contract - shell: bash - run: | - set -euo pipefail - test "$(node --version)" = "$EXPECTED_NODE_VERSION" - test "$(npm --version)" = "$EXPECTED_NPM_VERSION" - python3 - <<'PY' - import json - from pathlib import Path - - root = json.loads(Path("package.json").read_text(encoding="utf-8")) - desktop = json.loads(Path("apps/desktop/package.json").read_text(encoding="utf-8")) - assert root["packageManager"] == "npm@10.9.8" - assert root["devDependencies"]["undici"] == "7.29.0" - assert root["overrides"]["undici"] == "$undici" - assert desktop["dependencies"]["pdfjs-dist"] == "6.2.108" - PY - - - name: Regenerate without lifecycle execution - shell: bash - run: | - set -euo pipefail - npm install --package-lock-only --ignore-scripts --no-audit --no-fund - test -z "$(git diff --name-only -- package.json apps/desktop/package.json)" - - - name: Verify generated dependency identities and audit - shell: bash - run: | - set -euo pipefail - python3 - <<'PY' - import json - from pathlib import Path - - lock = json.loads(Path("package-lock.json").read_text(encoding="utf-8")) - packages = lock["packages"] - assert lock["lockfileVersion"] == 3 - assert packages[""]["devDependencies"]["undici"] == "7.29.0" - assert packages["apps/desktop"]["dependencies"]["pdfjs-dist"] == "6.2.108" - assert packages["node_modules/undici"]["version"] == "7.29.0" - assert packages["node_modules/pdfjs-dist"]["version"] == "6.2.108" - PY - npm audit --workspaces --audit-level=high - - - name: Commit the canonical artifact and remove this one-shot workflow - shell: bash - run: | - set -euo pipefail - rm .github/workflows/repair-pr-783-lock.yml - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add package-lock.json .github/workflows/repair-pr-783-lock.yml - git diff --cached --check - git commit -m "fix(deps): regenerate canonical npm 10.9.8 lock" - git push origin HEAD:fix/high-security-dependency-baseline diff --git a/package-lock.json b/package-lock.json index c85ae3cea..1b2ceef69 100644 --- a/package-lock.json +++ b/package-lock.json @@ -703,6 +703,7 @@ "os": [ "aix" ], + "peer": true, "engines": { "node": ">=18" } @@ -720,6 +721,7 @@ "os": [ "android" ], + "peer": true, "engines": { "node": ">=18" } @@ -737,6 +739,7 @@ "os": [ "android" ], + "peer": true, "engines": { "node": ">=18" } @@ -754,6 +757,7 @@ "os": [ "android" ], + "peer": true, "engines": { "node": ">=18" } @@ -771,6 +775,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": ">=18" } @@ -788,6 +793,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": ">=18" } @@ -805,6 +811,7 @@ "os": [ "freebsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -822,6 +829,7 @@ "os": [ "freebsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -839,6 +847,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -856,6 +865,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -873,6 +883,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -890,6 +901,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -907,6 +919,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -924,6 +937,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -941,6 +955,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -958,6 +973,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -975,6 +991,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -992,6 +1009,7 @@ "os": [ "netbsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -1009,6 +1027,7 @@ "os": [ "netbsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -1026,6 +1045,7 @@ "os": [ "openbsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -1043,6 +1063,7 @@ "os": [ "openbsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -1060,6 +1081,7 @@ "os": [ "openharmony" ], + "peer": true, "engines": { "node": ">=18" } @@ -1077,6 +1099,7 @@ "os": [ "sunos" ], + "peer": true, "engines": { "node": ">=18" } @@ -1094,6 +1117,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">=18" } @@ -1111,6 +1135,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">=18" } @@ -1128,6 +1153,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">=18" } @@ -1578,9 +1604,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1601,9 +1624,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1624,9 +1644,6 @@ "cpu": [ "riscv64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1647,9 +1664,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1670,9 +1684,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1875,9 +1886,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1895,9 +1903,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1915,9 +1920,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1935,9 +1937,6 @@ "riscv64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1955,9 +1954,6 @@ "riscv64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1975,9 +1971,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1995,9 +1988,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2015,9 +2005,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2230,9 +2217,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2247,9 +2231,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2264,9 +2245,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2281,9 +2259,6 @@ "riscv64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2298,9 +2273,6 @@ "riscv64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2315,9 +2287,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2332,9 +2301,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2349,9 +2315,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2546,9 +2509,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2566,9 +2526,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2586,9 +2543,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2606,9 +2560,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2626,9 +2577,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2646,9 +2594,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -3059,9 +3004,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -3079,9 +3021,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -3099,9 +3038,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -3119,9 +3055,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -3309,9 +3242,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "Apache-2.0 OR MIT", "optional": true, "os": [ @@ -3329,9 +3259,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "Apache-2.0 OR MIT", "optional": true, "os": [ @@ -3349,9 +3276,6 @@ "riscv64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "Apache-2.0 OR MIT", "optional": true, "os": [ @@ -3369,9 +3293,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "Apache-2.0 OR MIT", "optional": true, "os": [ @@ -3389,9 +3310,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "Apache-2.0 OR MIT", "optional": true, "os": [ @@ -5702,9 +5620,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -5726,9 +5641,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -5750,9 +5662,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -5774,9 +5683,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -5986,6 +5892,7 @@ "version": "3.3.18", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", + "dev": true, "funding": [ { "type": "github", @@ -7097,6 +7004,7 @@ "version": "7.29.0", "resolved": "https://registry.npmjs.org/undici/-/undici-7.29.0.tgz", "integrity": "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==", + "dev": true, "license": "MIT", "engines": { "node": ">=20.18.1" @@ -7396,9 +7304,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -7420,9 +7325,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -7444,9 +7346,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -7468,9 +7367,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ From a48ff96be362e8915c976867b9c2ef1b5c807433 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 14 Aug 2026 16:56:10 +0900 Subject: [PATCH 042/161] ci: retrigger exact-head validation after canonical lock generation From fd9389a91b73e651888799cae58e201b1ecd7360 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 14 Aug 2026 17:04:15 +0900 Subject: [PATCH 043/161] ci: add one-shot Ruff formatter for PR 783 --- .github/workflows/repair-pr-783-format.yml | 60 ++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 .github/workflows/repair-pr-783-format.yml diff --git a/.github/workflows/repair-pr-783-format.yml b/.github/workflows/repair-pr-783-format.yml new file mode 100644 index 000000000..b6ad6cb18 --- /dev/null +++ b/.github/workflows/repair-pr-783-format.yml @@ -0,0 +1,60 @@ +name: repair-pr-783-format + +on: + push: + branches: + - fix/high-security-dependency-baseline + workflow_dispatch: + +permissions: + contents: write + +concurrency: + group: repair-pr-783-format + cancel-in-progress: false + +jobs: + format: + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Check out the exact feature branch + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + ref: fix/high-security-dependency-baseline + fetch-depth: 1 + + - name: Install pinned uv + uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v6.7.0 + with: + version: "0.8.6" + enable-cache: false + + - name: Format only the failing test files + shell: bash + run: | + set -euo pipefail + uvx ruff@0.15.5 format \ + services/analysis-engine/tests/test_high_security_dependency_baseline.py \ + services/analysis-engine/tests/test_npm_toolchain_contract.py + changed="$(git diff --name-only)" + expected=$'services/analysis-engine/tests/test_high_security_dependency_baseline.py\nservices/analysis-engine/tests/test_npm_toolchain_contract.py' + test "$changed" = "$expected" + uvx ruff@0.15.5 format --check \ + services/analysis-engine/tests/test_high_security_dependency_baseline.py \ + services/analysis-engine/tests/test_npm_toolchain_contract.py + + - name: Commit the formatter result and remove this one-shot workflow + shell: bash + run: | + set -euo pipefail + rm .github/workflows/repair-pr-783-format.yml + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add \ + services/analysis-engine/tests/test_high_security_dependency_baseline.py \ + services/analysis-engine/tests/test_npm_toolchain_contract.py \ + .github/workflows/repair-pr-783-format.yml + git diff --cached --check + git commit -m "style(test): apply canonical Ruff formatting" + git push origin HEAD:fix/high-security-dependency-baseline From afba132610da0c2bd0c943bd43f823a588d7a343 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 14 Aug 2026 08:08:24 +0000 Subject: [PATCH 044/161] style(test): apply canonical Ruff formatting --- .github/workflows/repair-pr-783-format.yml | 60 ------------------- .../test_high_security_dependency_baseline.py | 3 +- .../tests/test_npm_toolchain_contract.py | 3 +- 3 files changed, 2 insertions(+), 64 deletions(-) delete mode 100644 .github/workflows/repair-pr-783-format.yml diff --git a/.github/workflows/repair-pr-783-format.yml b/.github/workflows/repair-pr-783-format.yml deleted file mode 100644 index b6ad6cb18..000000000 --- a/.github/workflows/repair-pr-783-format.yml +++ /dev/null @@ -1,60 +0,0 @@ -name: repair-pr-783-format - -on: - push: - branches: - - fix/high-security-dependency-baseline - workflow_dispatch: - -permissions: - contents: write - -concurrency: - group: repair-pr-783-format - cancel-in-progress: false - -jobs: - format: - runs-on: ubuntu-latest - timeout-minutes: 10 - steps: - - name: Check out the exact feature branch - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - ref: fix/high-security-dependency-baseline - fetch-depth: 1 - - - name: Install pinned uv - uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v6.7.0 - with: - version: "0.8.6" - enable-cache: false - - - name: Format only the failing test files - shell: bash - run: | - set -euo pipefail - uvx ruff@0.15.5 format \ - services/analysis-engine/tests/test_high_security_dependency_baseline.py \ - services/analysis-engine/tests/test_npm_toolchain_contract.py - changed="$(git diff --name-only)" - expected=$'services/analysis-engine/tests/test_high_security_dependency_baseline.py\nservices/analysis-engine/tests/test_npm_toolchain_contract.py' - test "$changed" = "$expected" - uvx ruff@0.15.5 format --check \ - services/analysis-engine/tests/test_high_security_dependency_baseline.py \ - services/analysis-engine/tests/test_npm_toolchain_contract.py - - - name: Commit the formatter result and remove this one-shot workflow - shell: bash - run: | - set -euo pipefail - rm .github/workflows/repair-pr-783-format.yml - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add \ - services/analysis-engine/tests/test_high_security_dependency_baseline.py \ - services/analysis-engine/tests/test_npm_toolchain_contract.py \ - .github/workflows/repair-pr-783-format.yml - git diff --cached --check - git commit -m "style(test): apply canonical Ruff formatting" - git push origin HEAD:fix/high-security-dependency-baseline diff --git a/services/analysis-engine/tests/test_high_security_dependency_baseline.py b/services/analysis-engine/tests/test_high_security_dependency_baseline.py index 7be5df944..259b5e58f 100644 --- a/services/analysis-engine/tests/test_high_security_dependency_baseline.py +++ b/services/analysis-engine/tests/test_high_security_dependency_baseline.py @@ -53,8 +53,7 @@ def test_lock_records_match_exact_registry_artifacts_and_preserve_peer_metadata( assert isinstance(pdfjs, dict) assert pdfjs["version"] == _PDFJS_VERSION assert pdfjs["resolved"] == ( - "https://registry.npmjs.org/pdfjs-dist/-/" - f"pdfjs-dist-{_PDFJS_VERSION}.tgz" + f"https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-{_PDFJS_VERSION}.tgz" ) assert pdfjs["integrity"] == _PDFJS_INTEGRITY assert pdfjs["license"] == "Apache-2.0" diff --git a/services/analysis-engine/tests/test_npm_toolchain_contract.py b/services/analysis-engine/tests/test_npm_toolchain_contract.py index 5e9a65dbc..d68532826 100644 --- a/services/analysis-engine/tests/test_npm_toolchain_contract.py +++ b/services/analysis-engine/tests/test_npm_toolchain_contract.py @@ -47,8 +47,7 @@ def test_primary_ci_proves_exact_npm_before_install_and_lock_reproduction() -> N assert "--no-fund" in workflow assert "actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a" in workflow assert ( - "npm-lock-reproduction-${{ github.event.pull_request.head.sha || github.sha }}" - in workflow + "npm-lock-reproduction-${{ github.event.pull_request.head.sha || github.sha }}" in workflow ) assert "if-no-files-found: error" in workflow assert "git diff --exit-code -- package-lock.json" in workflow From e214e968c1bca2bc632c70b7addacfe4be355227 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 14 Aug 2026 17:09:22 +0900 Subject: [PATCH 045/161] ci: retrigger exact-head validation after canonical Ruff formatting From 0cd68360103375f35536e6b8c929ff8c15aebfc4 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 14 Aug 2026 17:35:25 +0900 Subject: [PATCH 046/161] fix(test): verify canonical hoisted PDF.js lock record --- .../tests/test_high_security_dependency_baseline.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/analysis-engine/tests/test_high_security_dependency_baseline.py b/services/analysis-engine/tests/test_high_security_dependency_baseline.py index 259b5e58f..80a4eb1ff 100644 --- a/services/analysis-engine/tests/test_high_security_dependency_baseline.py +++ b/services/analysis-engine/tests/test_high_security_dependency_baseline.py @@ -49,7 +49,7 @@ def test_lock_records_match_exact_registry_artifacts_and_preserve_peer_metadata( assert isinstance(desktop, dict) assert desktop["dependencies"]["pdfjs-dist"] == _PDFJS_VERSION # type: ignore[index] - pdfjs = packages["apps/desktop/node_modules/pdfjs-dist"] + pdfjs = packages["node_modules/pdfjs-dist"] assert isinstance(pdfjs, dict) assert pdfjs["version"] == _PDFJS_VERSION assert pdfjs["resolved"] == ( @@ -74,4 +74,4 @@ def test_lock_records_match_exact_registry_artifacts_and_preserve_peer_metadata( assert all( isinstance(metadata, dict) and metadata.get("peer") is True for metadata in esbuild_locations.values() - ) + ) \ No newline at end of file From f4a5a19201069a3e2be6895345ce43387ed6ef6c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 14 Aug 2026 17:44:33 +0900 Subject: [PATCH 047/161] ci: add final one-shot Ruff formatter for PR 783 --- .../workflows/repair-pr-783-format-final.yml | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 .github/workflows/repair-pr-783-format-final.yml diff --git a/.github/workflows/repair-pr-783-format-final.yml b/.github/workflows/repair-pr-783-format-final.yml new file mode 100644 index 000000000..8c9dc2c96 --- /dev/null +++ b/.github/workflows/repair-pr-783-format-final.yml @@ -0,0 +1,51 @@ +name: repair-pr-783-format-final + +on: + push: + branches: + - fix/high-security-dependency-baseline + workflow_dispatch: + +permissions: + contents: write + +concurrency: + group: repair-pr-783-format-final + cancel-in-progress: false + +jobs: + format: + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Check out the exact feature branch + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + ref: fix/high-security-dependency-baseline + fetch-depth: 1 + + - name: Install pinned uv + uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v6.7.0 + with: + version: "0.8.6" + enable-cache: false + + - name: Format the exact failing file + shell: bash + run: | + set -euo pipefail + uvx ruff@0.15.5 format services/analysis-engine/tests/test_high_security_dependency_baseline.py + test "$(git diff --name-only)" = "services/analysis-engine/tests/test_high_security_dependency_baseline.py" + uvx ruff@0.15.5 format --check services/analysis-engine/tests/test_high_security_dependency_baseline.py + + - name: Commit the formatter result and remove this one-shot workflow + shell: bash + run: | + set -euo pipefail + rm .github/workflows/repair-pr-783-format-final.yml + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add services/analysis-engine/tests/test_high_security_dependency_baseline.py .github/workflows/repair-pr-783-format-final.yml + git diff --cached --check + git commit -m "style(test): apply final canonical Ruff formatting" + git push origin HEAD:fix/high-security-dependency-baseline From 219267e0279cd153632520b270501315b69f78b6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 14 Aug 2026 08:53:57 +0000 Subject: [PATCH 048/161] style(test): apply final canonical Ruff formatting --- .../workflows/repair-pr-783-format-final.yml | 51 ------------------- .../test_high_security_dependency_baseline.py | 2 +- 2 files changed, 1 insertion(+), 52 deletions(-) delete mode 100644 .github/workflows/repair-pr-783-format-final.yml diff --git a/.github/workflows/repair-pr-783-format-final.yml b/.github/workflows/repair-pr-783-format-final.yml deleted file mode 100644 index 8c9dc2c96..000000000 --- a/.github/workflows/repair-pr-783-format-final.yml +++ /dev/null @@ -1,51 +0,0 @@ -name: repair-pr-783-format-final - -on: - push: - branches: - - fix/high-security-dependency-baseline - workflow_dispatch: - -permissions: - contents: write - -concurrency: - group: repair-pr-783-format-final - cancel-in-progress: false - -jobs: - format: - runs-on: ubuntu-latest - timeout-minutes: 10 - steps: - - name: Check out the exact feature branch - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - ref: fix/high-security-dependency-baseline - fetch-depth: 1 - - - name: Install pinned uv - uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v6.7.0 - with: - version: "0.8.6" - enable-cache: false - - - name: Format the exact failing file - shell: bash - run: | - set -euo pipefail - uvx ruff@0.15.5 format services/analysis-engine/tests/test_high_security_dependency_baseline.py - test "$(git diff --name-only)" = "services/analysis-engine/tests/test_high_security_dependency_baseline.py" - uvx ruff@0.15.5 format --check services/analysis-engine/tests/test_high_security_dependency_baseline.py - - - name: Commit the formatter result and remove this one-shot workflow - shell: bash - run: | - set -euo pipefail - rm .github/workflows/repair-pr-783-format-final.yml - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add services/analysis-engine/tests/test_high_security_dependency_baseline.py .github/workflows/repair-pr-783-format-final.yml - git diff --cached --check - git commit -m "style(test): apply final canonical Ruff formatting" - git push origin HEAD:fix/high-security-dependency-baseline diff --git a/services/analysis-engine/tests/test_high_security_dependency_baseline.py b/services/analysis-engine/tests/test_high_security_dependency_baseline.py index 80a4eb1ff..8ed27aea6 100644 --- a/services/analysis-engine/tests/test_high_security_dependency_baseline.py +++ b/services/analysis-engine/tests/test_high_security_dependency_baseline.py @@ -74,4 +74,4 @@ def test_lock_records_match_exact_registry_artifacts_and_preserve_peer_metadata( assert all( isinstance(metadata, dict) and metadata.get("peer") is True for metadata in esbuild_locations.values() - ) \ No newline at end of file + ) From db7b1c138a105042f64427748112708a362bdd87 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 14 Aug 2026 18:38:06 +0900 Subject: [PATCH 049/161] chore(ci): retrigger exact-head protected checks From f4d8850c66a49713f0f42ac79f00ae4097ed4735 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 14 Aug 2026 20:08:12 +0900 Subject: [PATCH 050/161] fix(ci): validate npm lock without mutable resolution --- .github/workflows/ci.yml | 24 ++---- CHANGELOG.md | 4 +- .../npm-lockfile-generator-provenance.md | 78 ++++++++++--------- .../tests/test_npm_toolchain_contract.py | 66 ++++++++++++---- 4 files changed, 103 insertions(+), 69 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8311a1dae..abb7336c2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,8 +20,8 @@ env: EXPECTED_NPM_VERSION: "10.9.8" jobs: - lock-reproduction: - name: gate / ci / npm-lock-reproduction + lock-validation: + name: gate / ci / npm-lock-validation runs-on: ubuntu-latest steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 @@ -31,22 +31,14 @@ jobs: cache: npm - name: Verify exact npm lockfile generator run: test "$(npm --version)" = "$EXPECTED_NPM_VERSION" - - name: Reproduce package lock without lifecycle execution - run: npm install --package-lock-only --ignore-scripts --no-audit --no-fund - - name: Preserve the exact generated lock as review evidence - if: always() - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: npm-lock-reproduction-${{ github.event.pull_request.head.sha || github.sha }} - path: package-lock.json - if-no-files-found: error - retention-days: 3 - - name: Reject lockfile drift - run: git diff --exit-code -- package-lock.json + - name: Validate the frozen package lock without lifecycle execution + run: npm ci --ignore-scripts --no-audit --no-fund + - name: Reject manifest or lockfile drift + run: git diff --exit-code -- package.json package-lock.json verify: name: ci / build-and-test - needs: lock-reproduction + needs: lock-validation runs-on: ubuntu-latest steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 @@ -79,7 +71,7 @@ jobs: rust-check: name: gate / ci / rust-check - needs: lock-reproduction + needs: lock-validation runs-on: macos-15 steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 diff --git a/CHANGELOG.md b/CHANGELOG.md index c746043fc..9029bf8b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ ### Changed -- Pinned npm `10.9.8` as the lockfile generator and made primary CI reject a different npm version or any package-lock-only replay diff. +- Pinned npm `10.9.8` as the approved lockfile generator and made primary CI consume the committed lock only through frozen `npm ci` validation, rejecting mutable npm resolution in the lock gate and requiring integrity evidence for public-registry lock entries. ### Fixed @@ -73,4 +73,4 @@ - `ChordsFeature` (코드 분석) 화면에서 각 파트(Role)의 `transpositionPlan`(이조/조옮김 계획)을 표시하는 기능을 추가했습니다. - `RangesFeature` (음역대 분석) 화면에서 겹침 경고(Overlap warning) 외에 해당 파트의 채보(Transcription) 가능 노드 수를 요약하여 보여주는 기능을 추가했습니다. -- 신규 UI 요소에 대한 100% 테스트 커버리지를 보장하는 단위 테스트를 추가했습니다 (`apps/desktop/src/features/chords/index.test.tsx`, `apps/desktop/src/features/ranges/index.test.tsx`). +- 신규 UI 요소에 대한 100% 테스트 커버리지를 보장하는 단위 테스트를 추가했습니다 (`apps/desktop/src/features/chords/index.test.tsx`, `apps/desktop/src/features/ranges/index.test.tsx`). \ No newline at end of file diff --git a/docs/doctoring/npm-lockfile-generator-provenance.md b/docs/doctoring/npm-lockfile-generator-provenance.md index 1d50f2a5d..45345f3be 100644 --- a/docs/doctoring/npm-lockfile-generator-provenance.md +++ b/docs/doctoring/npm-lockfile-generator-provenance.md @@ -2,75 +2,83 @@ ## Decision -BandScope generates and verifies its root npm workspace lock with exactly npm `10.9.8`. The root manifest records that decision through: +BandScope records npm `10.9.8` as the approved generator for root workspace dependency updates. The root manifest records that decision through: - `packageManager: npm@10.9.8` as package-manager selection metadata; and - `devEngines.packageManager` with `onFail: error` as npm's source-tree command gate. -The npm version is intentionally not repeated under `engines`. npm serializes `engines` into the root lock package, so adding an npm-only source-tool constraint there creates lock metadata churn unrelated to dependency resolution. `devEngines`, the explicit CI assertion, and the replay gate enforce the generator while the published `engines.node` range remains the runtime compatibility contract. +The npm version is intentionally not repeated under `engines`. npm serializes `engines` into the root lock package, so adding an npm-only source-tool constraint there creates lock metadata churn unrelated to dependency resolution. `devEngines` and the explicit CI assertion enforce the approved generator while the published `engines.node` range remains the runtime compatibility contract. -The primary GitHub Actions workflow uses Node `22.22.3`, verifies the bundled npm version before any installation, runs `npm ci`, then runs a package-lock-only regeneration with scripts, audit, and funding output disabled. Any `package-lock.json` diff fails the exact head. +Primary CI does **not** regenerate or update `package-lock.json`. It uses Node `22.22.3`, verifies npm `10.9.8`, and validates the committed lock with `npm ci --ignore-scripts --no-audit --no-fund`. The gate then rejects any manifest or lockfile working-tree change. The normal verification job performs the repository's reviewed `npm ci` installation before lint, typecheck, tests, build, and security checks. The Node runtime support decision remains separate. This change does not raise the public `>=22.13 <23` Node range; a coordinated Node-floor migration is tracked independently. -## Why the generator is part of the lock identity +## Why generator provenance still matters -npm documents `package-lock.json` as the location-keyed description of the exact dependency tree. Lockfile version 3 is intended for npm 9 and newer. npm also notes that different package-manager versions may use different installation algorithms and metadata representations. A committed lockfile therefore is not fully reproducible unless the generator version and install-shaping flags are versioned with it. +npm documents `package-lock.json` as the location-keyed description of the exact dependency tree. Lockfile version 3 is intended for npm 9 and newer. npm also notes that package-manager versions and tree-shaping configuration can affect the generated dependency graph and metadata. Dependency updates therefore use the reviewed npm `10.9.8` toolchain, and reviewers examine the complete generated lock diff together with its manifest change. -`npm ci` is the immutable consumption path: it requires a lockfile, rejects manifest/lock dependency disagreement, removes an existing `node_modules`, and does not write the manifest or lock. It does not prove that a future dependency update will regenerate byte-identical metadata. The additional package-lock-only replay closes that gap. +That provenance is distinct from CI validation. `npm ci` is the immutable consumption path: it requires a lockfile, rejects manifest/lock dependency disagreement, removes an existing `node_modules`, and never writes the manifest or lock. CI relies on that frozen behavior instead of running `npm install`, `npm update`, or `npx` commands that may perform mutable resolution. + +The repository additionally requires a Subresource Integrity value for every package-lock entry resolved from the public npm registry. npm documents `integrity` as the SHA-512 or SHA-1 SRI string for the artifact unpacked at that location. ```mermaid flowchart LR - M[package.json ranges and workspaces] --> G[npm 10.9.8] - C[project npm configuration] --> G - G --> L[package-lock.json v3] - L --> I[npm ci clean install] - I --> R[npm 10.9.8 package-lock-only replay] - R --> D{lock diff?} - D -->|no| A[reproducible exact-head evidence] + M[package.json dependency intent] --> G[approved npm 10.9.8 update toolchain] + G --> L[reviewed package-lock.json v3] + L --> V[npm ci frozen validation, lifecycle disabled] + V --> D{manifest or lock drift?} D -->|yes| F[fail closed] + D -->|no| S[verify public-registry SRI evidence] + S --> C[normal npm ci and repository checks] ``` ## Security and operational boundary -- Dependency PRs may change only manifest ranges and the lock records produced by npm `10.9.8`. -- Reviewers must reject unrelated lock metadata that cannot be reproduced by the pinned generator. -- No lock record may be added or removed by hand to satisfy a validator. -- Install-shaping flags that change the tree, such as `legacy-peer-deps` or `install-links`, must be committed in project configuration and used identically by `npm ci` and regeneration. -- Dependency lifecycle scripts remain disabled for the reproduction pass. The normal clean install retains the repository's reviewed execution behavior. -- The exact npm version check occurs before `npm ci`; a different bundled or globally installed npm cannot generate acceptance evidence. -- The lockfile remains the sole npm workspace lock. Nested workspace locks are prohibited. +- CI lock validation must not run `npm install`, `npm update`, `npx`, or another mutable dependency-resolution command. +- Dependency PRs change manifest intent and the complete lock artifact produced by the approved npm `10.9.8` update toolchain; reviewers reject unexplained lock churn rather than hand-editing records. +- The lock-validation job disables dependency lifecycle scripts. The normal clean install retains the repository's reviewed execution behavior. +- The exact npm version check occurs before either frozen install; a different bundled or globally installed npm cannot provide acceptance evidence. +- Registry-resolved package records require SRI evidence in the committed lock. +- Install-shaping flags that affect the dependency tree, such as `legacy-peer-deps` or `install-links`, must be committed in project configuration and applied consistently to generation and `npm ci`. +- The root `package-lock.json` remains the sole npm workspace lock. Nested workspace locks are prohibited. -`packageManager` alone is not the enforcement boundary for npm because Corepack's npm shim is not enabled by default in Node distributions. Enforcement is provided by npm `devEngines`, the explicit CI version assertion, and the lock replay. +`packageManager` alone is not the enforcement boundary for npm because Corepack's npm shim is not enabled by default in Node distributions. Enforcement is provided by npm `devEngines`, the explicit CI version assertion, the frozen `npm ci` contract, and repository tests that prohibit mutable resolution in the lock gate. ## Verification -`services/analysis-engine/tests/test_npm_toolchain_contract.py` verifies the manifest metadata, separation of runtime and generator constraints, exact CI Node/npm identity, replay command and flags, clean lock diff, and lockfile version 3. Repository CI then executes the replay using the hosted toolchain. +`services/analysis-engine/tests/test_npm_toolchain_contract.py` verifies: + +1. the manifest's approved npm metadata and Node/runtime separation; +2. the exact Node/npm identity used by primary CI; +3. frozen `npm ci` lock validation with lifecycle execution disabled; +4. absence of `npm install`, `npm update`, and `npx` from the lock-validation job; +5. a clean manifest/lock working tree after validation; +6. package-lock version 3; and +7. SRI evidence for every public npm-registry artifact in the root lock. + +The exact PDF.js and Undici baseline is covered separately by `test_high_security_dependency_baseline.py` and the desktop PDF loader tests. -A dependency update is mergeable only after: +A dependency update is mergeable only after the updated manifest and complete generated lock are reviewed together and the exact current head passes frozen lock validation, normal install, lint, strict typecheck, measured tests, production build, Rust/Tauri checks, security/supply-chain gates, current review, independent approval, and branch protection without bypass. -1. npm `10.9.8` produces the checked-in lock from the updated manifest; -2. a second package-lock-only replay is byte-clean; -3. `npm ci`, lint, strict typecheck, measured tests, production build, Rust/Tauri checks, and security/supply-chain gates succeed on the same head; and -4. current-head review, unresolved-thread, independent-approval, and branch-protection requirements succeed without bypass. +## Claim boundary + +CI proves that the committed manifest and lock can be consumed as a frozen pair by the approved toolchain and that public-registry lock entries carry integrity evidence. It does **not** claim that resolving mutable manifest ranges again at a later time will reproduce byte-identical lock metadata. When a dependency update is needed, npm `10.9.8` remains the approved generator and its entire resulting lock diff is review evidence. ## Incident response and rollback -When replay changes the lock unexpectedly: +When an update produces unexpected lock churn: -1. preserve the exact head SHA, npm and Node versions, command flags, original lock blob SHA, regenerated lock, and CI run ID; -2. determine whether the manifest changed, npm changed, project configuration changed, or the protected lock was generated by a different toolchain; -3. never accept a partial or hand-edited lock; -4. regenerate from a clean checkout using the reviewed npm version and run the replay twice; +1. preserve the exact head SHA, npm and Node versions, project npm configuration, original lock blob SHA, generated lock, and relevant CI run IDs; +2. determine whether manifest intent, npm, project configuration, registry metadata, or transitive dependency resolution changed; +3. never accept a partial or hand-edited lock to satisfy a validator; +4. regenerate the complete lock in a dedicated update branch using the reviewed npm version, then review the full diff before relying on it; and 5. if rollback is necessary, restore the prior manifest and complete lock together, then rerun the entire exact-head gate. ## References npm, Inc. (2026). *npm ci*. npm Docs. https://docs.npmjs.com/cli/v11/commands/npm-ci/ -npm, Inc. (2026). *npm install*. npm Docs. https://docs.npmjs.com/cli/v10/commands/npm-install/ - -npm, Inc. (2026). *package-lock.json*. npm Docs. https://docs.npmjs.com/cli/v11/configuring-npm/package-lock-json/ +npm, Inc. (2026). *package-lock.json*. npm Docs. https://docs.npmjs.com/cli/v10/configuring-npm/package-lock-json/ npm, Inc. (2026). *package.json*. npm Docs. https://docs.npmjs.com/cli/configuring-npm/package-json/ diff --git a/services/analysis-engine/tests/test_npm_toolchain_contract.py b/services/analysis-engine/tests/test_npm_toolchain_contract.py index d68532826..857a27497 100644 --- a/services/analysis-engine/tests/test_npm_toolchain_contract.py +++ b/services/analysis-engine/tests/test_npm_toolchain_contract.py @@ -17,6 +17,20 @@ def _root_manifest() -> dict[str, object]: return manifest +def _primary_ci_workflow() -> str: + """Return the primary CI workflow as source text.""" + return (_REPOSITORY_ROOT / ".github" / "workflows" / "ci.yml").read_text( + encoding="utf-8" + ) + + +def _lock_validation_job(workflow: str) -> str: + """Return only the frozen npm lock-validation job from the CI workflow.""" + start = workflow.index(" lock-validation:") + end = workflow.index("\n verify:", start) + return workflow[start:end] + + def test_root_manifest_pins_the_lockfile_generator_and_fails_on_drift() -> None: """Require npm and source-tree commands to reject a different generator.""" manifest = _root_manifest() @@ -32,25 +46,20 @@ def test_root_manifest_pins_the_lockfile_generator_and_fails_on_drift() -> None: } -def test_primary_ci_proves_exact_npm_before_install_and_lock_reproduction() -> None: - """Keep the clean installer and lock reproduction on one explicit toolchain.""" - workflow = (_REPOSITORY_ROOT / ".github" / "workflows" / "ci.yml").read_text(encoding="utf-8") +def test_primary_ci_consumes_the_lock_without_mutable_resolution() -> None: + """Keep lock validation frozen while retaining exact Node and npm provenance.""" + workflow = _primary_ci_workflow() + lock_job = _lock_validation_job(workflow) assert f'node-version: "{_EXPECTED_NODE_VERSION}"' in workflow assert f'EXPECTED_NPM_VERSION: "{_EXPECTED_NPM_VERSION}"' in workflow - assert 'test "$(npm --version)" = "$EXPECTED_NPM_VERSION"' in workflow - assert "lock-reproduction:" in workflow - assert "needs: lock-reproduction" in workflow - assert "npm install --package-lock-only" in workflow - assert "--ignore-scripts" in workflow - assert "--no-audit" in workflow - assert "--no-fund" in workflow - assert "actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a" in workflow - assert ( - "npm-lock-reproduction-${{ github.event.pull_request.head.sha || github.sha }}" in workflow - ) - assert "if-no-files-found: error" in workflow - assert "git diff --exit-code -- package-lock.json" in workflow + assert 'test "$(npm --version)" = "$EXPECTED_NPM_VERSION"' in lock_job + assert "npm ci --ignore-scripts --no-audit --no-fund" in lock_job + assert "git diff --exit-code -- package.json package-lock.json" in lock_job + assert "needs: lock-validation" in workflow + assert "npm install " not in lock_job + assert "npm update " not in lock_job + assert "npx " not in lock_job def test_root_lock_uses_the_supported_location_keyed_format() -> None: @@ -59,3 +68,28 @@ def test_root_lock_uses_the_supported_location_keyed_format() -> None: assert lock_document["lockfileVersion"] == 3 assert isinstance(lock_document["packages"], dict) + + +def test_public_registry_lock_entries_have_integrity_evidence() -> None: + """Require SRI for every public npm-registry artifact recorded in the root lock.""" + lock_document = json.loads((_REPOSITORY_ROOT / "package-lock.json").read_text(encoding="utf-8")) + packages = lock_document["packages"] + assert isinstance(packages, dict) + + for location, package_record in packages.items(): + assert isinstance(location, str) + assert isinstance(package_record, dict) + resolved = package_record.get("resolved") + if not isinstance(resolved, str): + continue + if not ( + resolved == "registry.npmjs.org" + or resolved.startswith("registry.npmjs.org/") + or resolved.startswith("https://registry.npmjs.org/") + ): + continue + integrity = package_record.get("integrity") + assert isinstance(integrity, str), f"missing integrity for {location}" + assert integrity.startswith(("sha512-", "sha1-")), ( + f"unsupported integrity for {location}" + ) From 8a143ab8f06cd8733c0076fa3cf38aedeeaf4d82 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 14 Aug 2026 20:32:35 +0900 Subject: [PATCH 051/161] test(supply-chain): format npm toolchain contract --- .../analysis-engine/tests/test_npm_toolchain_contract.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/services/analysis-engine/tests/test_npm_toolchain_contract.py b/services/analysis-engine/tests/test_npm_toolchain_contract.py index 857a27497..b3639a595 100644 --- a/services/analysis-engine/tests/test_npm_toolchain_contract.py +++ b/services/analysis-engine/tests/test_npm_toolchain_contract.py @@ -64,7 +64,9 @@ def test_primary_ci_consumes_the_lock_without_mutable_resolution() -> None: def test_root_lock_uses_the_supported_location_keyed_format() -> None: """Require the npm-v9-and-newer lock format used by the pinned generator.""" - lock_document = json.loads((_REPOSITORY_ROOT / "package-lock.json").read_text(encoding="utf-8")) + lock_document = json.loads( + (_REPOSITORY_ROOT / "package-lock.json").read_text(encoding="utf-8") + ) assert lock_document["lockfileVersion"] == 3 assert isinstance(lock_document["packages"], dict) @@ -72,7 +74,9 @@ def test_root_lock_uses_the_supported_location_keyed_format() -> None: def test_public_registry_lock_entries_have_integrity_evidence() -> None: """Require SRI for every public npm-registry artifact recorded in the root lock.""" - lock_document = json.loads((_REPOSITORY_ROOT / "package-lock.json").read_text(encoding="utf-8")) + lock_document = json.loads( + (_REPOSITORY_ROOT / "package-lock.json").read_text(encoding="utf-8") + ) packages = lock_document["packages"] assert isinstance(packages, dict) From b3ac82665f727a2653ac71b9ab92f22dec25d96c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 14 Aug 2026 21:13:34 +0900 Subject: [PATCH 052/161] test(supply-chain): satisfy ruff formatting gate --- .../analysis-engine/tests/test_npm_toolchain_contract.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/services/analysis-engine/tests/test_npm_toolchain_contract.py b/services/analysis-engine/tests/test_npm_toolchain_contract.py index b3639a595..2b76336b9 100644 --- a/services/analysis-engine/tests/test_npm_toolchain_contract.py +++ b/services/analysis-engine/tests/test_npm_toolchain_contract.py @@ -94,6 +94,5 @@ def test_public_registry_lock_entries_have_integrity_evidence() -> None: continue integrity = package_record.get("integrity") assert isinstance(integrity, str), f"missing integrity for {location}" - assert integrity.startswith(("sha512-", "sha1-")), ( - f"unsupported integrity for {location}" - ) + supported_algorithm = integrity.startswith(("sha512-", "sha1-")) + assert supported_algorithm, f"unsupported integrity for {location}" From 885c03759aee436e4f81a1e7884d8335bd58dc0b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 14 Aug 2026 22:09:38 +0900 Subject: [PATCH 053/161] test(supply-chain): format npm toolchain contract --- services/analysis-engine/tests/test_npm_toolchain_contract.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/services/analysis-engine/tests/test_npm_toolchain_contract.py b/services/analysis-engine/tests/test_npm_toolchain_contract.py index 2b76336b9..79b570fc0 100644 --- a/services/analysis-engine/tests/test_npm_toolchain_contract.py +++ b/services/analysis-engine/tests/test_npm_toolchain_contract.py @@ -12,7 +12,9 @@ def _root_manifest() -> dict[str, object]: """Return the checked-in root package manifest as a JSON object.""" - manifest = json.loads((_REPOSITORY_ROOT / "package.json").read_text(encoding="utf-8")) + manifest = json.loads( + (_REPOSITORY_ROOT / "package.json").read_text(encoding="utf-8") + ) assert isinstance(manifest, dict) return manifest From de0cd6beea8fab1ee26deb4c733e50ea3d37d1d1 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 14 Aug 2026 22:39:41 +0900 Subject: [PATCH 054/161] test(supply-chain): apply repository Ruff width --- .../tests/test_npm_toolchain_contract.py | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/services/analysis-engine/tests/test_npm_toolchain_contract.py b/services/analysis-engine/tests/test_npm_toolchain_contract.py index 79b570fc0..2eafcbe19 100644 --- a/services/analysis-engine/tests/test_npm_toolchain_contract.py +++ b/services/analysis-engine/tests/test_npm_toolchain_contract.py @@ -12,18 +12,14 @@ def _root_manifest() -> dict[str, object]: """Return the checked-in root package manifest as a JSON object.""" - manifest = json.loads( - (_REPOSITORY_ROOT / "package.json").read_text(encoding="utf-8") - ) + manifest = json.loads((_REPOSITORY_ROOT / "package.json").read_text(encoding="utf-8")) assert isinstance(manifest, dict) return manifest def _primary_ci_workflow() -> str: """Return the primary CI workflow as source text.""" - return (_REPOSITORY_ROOT / ".github" / "workflows" / "ci.yml").read_text( - encoding="utf-8" - ) + return (_REPOSITORY_ROOT / ".github" / "workflows" / "ci.yml").read_text(encoding="utf-8") def _lock_validation_job(workflow: str) -> str: @@ -66,9 +62,7 @@ def test_primary_ci_consumes_the_lock_without_mutable_resolution() -> None: def test_root_lock_uses_the_supported_location_keyed_format() -> None: """Require the npm-v9-and-newer lock format used by the pinned generator.""" - lock_document = json.loads( - (_REPOSITORY_ROOT / "package-lock.json").read_text(encoding="utf-8") - ) + lock_document = json.loads((_REPOSITORY_ROOT / "package-lock.json").read_text(encoding="utf-8")) assert lock_document["lockfileVersion"] == 3 assert isinstance(lock_document["packages"], dict) @@ -76,9 +70,7 @@ def test_root_lock_uses_the_supported_location_keyed_format() -> None: def test_public_registry_lock_entries_have_integrity_evidence() -> None: """Require SRI for every public npm-registry artifact recorded in the root lock.""" - lock_document = json.loads( - (_REPOSITORY_ROOT / "package-lock.json").read_text(encoding="utf-8") - ) + lock_document = json.loads((_REPOSITORY_ROOT / "package-lock.json").read_text(encoding="utf-8")) packages = lock_document["packages"] assert isinstance(packages, dict) From f3cfae42db0529a80f032bf8657e17a0610ae1c7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 15 Aug 2026 00:35:15 +0900 Subject: [PATCH 055/161] docs(security): align npm lock provenance with frozen validation --- .../high-security-pdf-http-baseline.md | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/docs/doctoring/high-security-pdf-http-baseline.md b/docs/doctoring/high-security-pdf-http-baseline.md index d83a63eb7..5e56be8bf 100644 --- a/docs/doctoring/high-security-pdf-http-baseline.md +++ b/docs/doctoring/high-security-pdf-http-baseline.md @@ -6,7 +6,7 @@ BandScope treats the PDF parser and its transitive HTTP client as one security-r - `pdfjs-dist` is pinned exactly to `6.2.108`; - `undici` is pinned exactly to `7.29.0` through the root npm override; and -- the complete npm workspace lock is generated only by the repository-pinned npm `10.9.8` workflow and imported unchanged from the workflow artifact. +- npm `10.9.8` is the approved generator for reviewed root-workspace dependency updates, while primary CI consumes the committed lock through frozen validation rather than re-resolving it. PDF.js `6.2.108` no longer exposes the legacy `isEvalSupported` member in its public `DocumentInitParameters` contract, and `getDocument` no longer reads that member. BandScope therefore does not cast or pass an unknown option that would be ignored while creating false assurance. The primary remediation is the patched parser release, reinforced by a narrow data-only call, copied caller-owned bytes, and a same-origin bundled worker. @@ -18,9 +18,10 @@ flowchart LR C --> W[Same-origin bundled worker] W --> R[Canvas render] J[jsdom development path] --> U[undici 7.29.0 override] - N[npm 10.9.8] --> L[Exact package-lock artifact] - L --> C - L --> U + N[npm 10.9.8 approved update toolchain] --> L[Reviewed package-lock artifact] + L --> V[npm ci frozen validation] + V --> C + V --> U ``` ## Threat boundary @@ -33,16 +34,18 @@ Undici is currently a development dependency reached through jsdom, but developm ## Lockfile provenance -The security manifests are changed before the lock. The exact branch workflow then: +The dependency manifests and complete lock artifact were generated and reconciled on this branch with the approved Node `22.22.3` / npm `10.9.8` toolchain before the current frozen-validation gate was finalized. The historical generation run and artifact are provenance evidence only; they do **not** satisfy a later head's merge gate and primary CI intentionally does not repeat mutable dependency resolution. -1. verifies Node `22.22.3` and npm `10.9.8`; -2. runs `npm install --package-lock-only --ignore-scripts --no-audit --no-fund`; -3. uploads the generated `package-lock.json` under a head-SHA-bound artifact name; and -4. fails while the generated lock differs from the branch. +For every current head, primary CI instead: -The maintainer imports that generated artifact byte-for-byte and reruns the workflow. The second run must produce a clean diff. No tarball URL, SRI, dependency range, `peer` classification, or workspace record is edited by hand. +1. verifies npm `10.9.8` before dependency consumption; +2. runs `npm ci --ignore-scripts --no-audit --no-fund` in the dedicated lock-validation job; +3. rejects any `package.json` or `package-lock.json` working-tree drift; and +4. proceeds to normal repository verification only after the frozen lock is consumable by the approved toolchain. -The lock contract requires the exact public-registry tarball and SHA-512 SRI for both patched packages and requires every existing `node_modules/@esbuild/*` location to retain npm 10.9.8's `peer: true` classification. This distinguishes the intended security graph from unrelated Dependabot generator churn. +Future dependency updates must use npm `10.9.8` to generate the complete lock in a dedicated update branch, review the entire resulting manifest/lock diff, and then prove frozen consumption on the resulting exact head. No tarball URL, SRI, dependency range, `peer` classification, or workspace record may be hand-edited merely to satisfy a validator. + +The lock contract requires the exact public-registry tarball and SHA-512 SRI for both patched packages and requires every existing `node_modules/@esbuild/*` location to retain npm 10.9.8's `peer: true` classification. This distinguishes the intended security graph from unrelated Dependabot generator churn. The narrower provenance and validation contract is specified in `docs/doctoring/npm-lockfile-generator-provenance.md`. ## Verification @@ -62,7 +65,7 @@ The merge gate includes: ## Failure, rollback, and incident evidence -On a failed lock replay or parser regression, preserve the exact head SHA, Node/npm versions, generated-lock artifact ID and digest, original and generated lock blob SHA, test output, audit report, and workflow run ID. Do not merge a partially updated graph. +On a failed frozen-lock validation or parser regression, preserve the exact head SHA, Node/npm versions, original lock blob SHA, test output, audit report, and workflow run ID. If the incident concerns a dependency-generation change, also preserve the generated complete lock and the generation environment/configuration. Do not merge a partially updated graph. Rollback restores the previous desktop manifest, root override, complete lock, PDF loader, tests, and CHANGELOG entry together. Because the previous graph contains known high findings, rollback is an emergency availability action only and requires an explicit security exception, compensating controls, owner, expiration, and immediate replacement plan. @@ -78,4 +81,4 @@ Node.js contributors. (2026). *Undici 7.29.0* [Software release]. https://github npm, Inc. (2026). *npm ci*. npm Docs. https://docs.npmjs.com/cli/v11/commands/npm-ci/ -npm, Inc. (2026). *package-lock.json*. npm Docs. https://docs.npmjs.com/cli/v11/configuring-npm/package-lock-json/ +npm, Inc. (2026). *package-lock.json*. npm Docs. https://docs.npmjs.com/cli/v11/configuring-npm/package-lock-json/ \ No newline at end of file From de75178205db244ca44cb105262398585a189881 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 15 Aug 2026 02:11:13 +0900 Subject: [PATCH 056/161] fix(ci): disable persisted checkout credentials --- .github/workflows/ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index abb7336c2..4ec39fc9d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,6 +42,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: "22.22.3" @@ -75,6 +77,8 @@ jobs: runs-on: macos-15 steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: "22.22.3" From 164e6f2e7d8deec07d885d2ef00a0f684bc6ef47 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 15 Aug 2026 02:11:26 +0900 Subject: [PATCH 057/161] docs(changelog): correct UI test coverage claim --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9029bf8b2..9fee41aec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -73,4 +73,4 @@ - `ChordsFeature` (코드 분석) 화면에서 각 파트(Role)의 `transpositionPlan`(이조/조옮김 계획)을 표시하는 기능을 추가했습니다. - `RangesFeature` (음역대 분석) 화면에서 겹침 경고(Overlap warning) 외에 해당 파트의 채보(Transcription) 가능 노드 수를 요약하여 보여주는 기능을 추가했습니다. -- 신규 UI 요소에 대한 100% 테스트 커버리지를 보장하는 단위 테스트를 추가했습니다 (`apps/desktop/src/features/chords/index.test.tsx`, `apps/desktop/src/features/ranges/index.test.tsx`). \ No newline at end of file +- 신규 UI 요소에 대한 단위 테스트를 추가했습니다 (`apps/desktop/src/features/chords/index.test.tsx`, `apps/desktop/src/features/ranges/index.test.tsx`). \ No newline at end of file From 07c1dd2d5bf8109d0d2c74f8c8b6120e0eae188e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 15 Aug 2026 16:07:59 +0900 Subject: [PATCH 058/161] test(lock): reject esbuild peer metadata drift --- .../tests/test_npm_toolchain_contract.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/services/analysis-engine/tests/test_npm_toolchain_contract.py b/services/analysis-engine/tests/test_npm_toolchain_contract.py index 2eafcbe19..6f38db8c0 100644 --- a/services/analysis-engine/tests/test_npm_toolchain_contract.py +++ b/services/analysis-engine/tests/test_npm_toolchain_contract.py @@ -90,3 +90,21 @@ def test_public_registry_lock_entries_have_integrity_evidence() -> None: assert isinstance(integrity, str), f"missing integrity for {location}" supported_algorithm = integrity.startswith(("sha512-", "sha1-")) assert supported_algorithm, f"unsupported integrity for {location}" + + +def test_root_lock_preserves_esbuild_peer_metadata() -> None: + """Reject serializer drift that strips the root @esbuild peer markers.""" + lock_document = json.loads((_REPOSITORY_ROOT / "package-lock.json").read_text(encoding="utf-8")) + packages = lock_document["packages"] + assert isinstance(packages, dict) + + esbuild_records = { + location: package_record + for location, package_record in packages.items() + if isinstance(location, str) and location.startswith("node_modules/@esbuild/") + } + assert esbuild_records, "root lock must contain @esbuild platform packages" + + for location, package_record in esbuild_records.items(): + assert isinstance(package_record, dict) + assert package_record.get("peer") is True, f"missing peer metadata for {location}" From ad4551bdc4e91206a14f3825430663ee19c1176f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 15 Aug 2026 16:09:53 +0900 Subject: [PATCH 059/161] docs(lock): record esbuild metadata sentinel --- docs/doctoring/npm-lockfile-generator-provenance.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/docs/doctoring/npm-lockfile-generator-provenance.md b/docs/doctoring/npm-lockfile-generator-provenance.md index 45345f3be..53488de48 100644 --- a/docs/doctoring/npm-lockfile-generator-provenance.md +++ b/docs/doctoring/npm-lockfile-generator-provenance.md @@ -21,6 +21,8 @@ That provenance is distinct from CI validation. `npm ci` is the immutable consum The repository additionally requires a Subresource Integrity value for every package-lock entry resolved from the public npm registry. npm documents `integrity` as the SHA-512 or SHA-1 SRI string for the artifact unpacked at that location. +The root lock also retains `peer: true` on the platform-specific `node_modules/@esbuild/*` records produced by the approved tree. Multiple dependency-update branches generated with a different serialization path were observed removing those markers even when the requested package change was unrelated to esbuild. Because frozen `npm ci` consumes rather than regenerates the lock, ordinary frozen-install validation alone cannot prove that this generator-sensitive metadata was preserved. The repository therefore treats those markers as a regression sentinel: a dependency PR that strips them must be regenerated with the approved npm toolchain rather than normalizing the unrelated churn by hand. + ```mermaid flowchart LR M[package.json dependency intent] --> G[approved npm 10.9.8 update toolchain] @@ -28,7 +30,7 @@ flowchart LR L --> V[npm ci frozen validation, lifecycle disabled] V --> D{manifest or lock drift?} D -->|yes| F[fail closed] - D -->|no| S[verify public-registry SRI evidence] + D -->|no| S[verify SRI and generator-sensitive metadata] S --> C[normal npm ci and repository checks] ``` @@ -36,6 +38,7 @@ flowchart LR - CI lock validation must not run `npm install`, `npm update`, `npx`, or another mutable dependency-resolution command. - Dependency PRs change manifest intent and the complete lock artifact produced by the approved npm `10.9.8` update toolchain; reviewers reject unexplained lock churn rather than hand-editing records. +- Platform-specific root `@esbuild/*` lock records must retain their expected `peer: true` metadata. Missing markers are treated as generator drift, not as an acceptable side effect of an unrelated dependency update. - The lock-validation job disables dependency lifecycle scripts. The normal clean install retains the repository's reviewed execution behavior. - The exact npm version check occurs before either frozen install; a different bundled or globally installed npm cannot provide acceptance evidence. - Registry-resolved package records require SRI evidence in the committed lock. @@ -53,8 +56,9 @@ flowchart LR 3. frozen `npm ci` lock validation with lifecycle execution disabled; 4. absence of `npm install`, `npm update`, and `npx` from the lock-validation job; 5. a clean manifest/lock working tree after validation; -6. package-lock version 3; and -7. SRI evidence for every public npm-registry artifact in the root lock. +6. package-lock version 3; +7. SRI evidence for every public npm-registry artifact in the root lock; and +8. preservation of `peer: true` on every root `node_modules/@esbuild/*` platform record. The exact PDF.js and Undici baseline is covered separately by `test_high_security_dependency_baseline.py` and the desktop PDF loader tests. @@ -62,7 +66,7 @@ A dependency update is mergeable only after the updated manifest and complete ge ## Claim boundary -CI proves that the committed manifest and lock can be consumed as a frozen pair by the approved toolchain and that public-registry lock entries carry integrity evidence. It does **not** claim that resolving mutable manifest ranges again at a later time will reproduce byte-identical lock metadata. When a dependency update is needed, npm `10.9.8` remains the approved generator and its entire resulting lock diff is review evidence. +CI proves that the committed manifest and lock can be consumed as a frozen pair by the approved toolchain, that public-registry lock entries carry integrity evidence, and that the known generator-sensitive `@esbuild/*` peer markers remain present. It does **not** claim that resolving mutable manifest ranges again at a later time will reproduce byte-identical lock metadata. When a dependency update is needed, npm `10.9.8` remains the approved generator and its entire resulting lock diff is review evidence. ## Incident response and rollback From 6f81f52c193c1e327d078eba7a2ea3bdbfbc87c2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 15 Aug 2026 16:13:07 +0900 Subject: [PATCH 060/161] docs(changelog): record lock metadata drift gate --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9fee41aec..cfe599265 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ ### Changed -- Pinned npm `10.9.8` as the approved lockfile generator and made primary CI consume the committed lock only through frozen `npm ci` validation, rejecting mutable npm resolution in the lock gate and requiring integrity evidence for public-registry lock entries. +- Pinned npm `10.9.8` as the approved lockfile generator and made primary CI consume the committed lock only through frozen `npm ci` validation, rejecting mutable npm resolution in the lock gate, requiring integrity evidence for public-registry lock entries, and rejecting generator-sensitive loss of root `@esbuild/*` peer metadata. ### Fixed From 6665f029564e2eb5b82b009ef90aa5242de7d043 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 16 Aug 2026 05:06:25 +0900 Subject: [PATCH 061/161] fix(security): make PDF XML boundary explicit --- apps/desktop/src/features/score/pdfjs.ts | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/apps/desktop/src/features/score/pdfjs.ts b/apps/desktop/src/features/score/pdfjs.ts index e0ce7692d..ec622d42d 100644 --- a/apps/desktop/src/features/score/pdfjs.ts +++ b/apps/desktop/src/features/score/pdfjs.ts @@ -19,16 +19,23 @@ export function configureScorePdfWorker(): void { * Start parsing validated in-memory score PDF bytes with pdf.js. * * Only caller-provided bytes are accepted (validated-resource-only rule); - * this helper never fetches arbitrary URLs. The bytes are copied before they - * are handed to pdf.js because pdf.js transfers the underlying buffer to its + * this helper never supplies a URL. The bytes are copied before they are + * handed to pdf.js because pdf.js transfers the underlying buffer to its * worker, which would otherwise detach the caller's copy and break retries. * - * PDF.js 6.2.108 no longer exposes the legacy `isEvalSupported` initialization - * option. Security therefore relies on the patched parser release plus this - * narrow data-only, same-origin-worker boundary rather than an ignored and - * falsely reassuring unknown option. + * XFA rendering is explicitly disabled even though pdf.js 6.2.108 defaults it + * to `false`, and worker-side resource fetching is explicitly disabled. These + * settings make the parser boundary fail closed against XML-form activation + * and remote helper-resource acquisition instead of relying on upstream + * defaults. In the pinned pdf.js XML parser, DOCTYPE declarations are reported + * to a no-op hook and unknown named entities are preserved literally rather + * than dereferenced, so no external-entity resolver is exposed by this API. */ export function loadScorePdf(data: Uint8Array): PDFDocumentLoadingTask { configureScorePdfWorker(); - return getDocument({ data: new Uint8Array(data) }); + return getDocument({ + data: new Uint8Array(data), + enableXfa: false, + useWorkerFetch: false + }); } From bf5a3a3e296a34c84c18775409ebae71f354c23c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 16 Aug 2026 05:06:33 +0900 Subject: [PATCH 062/161] test(security): lock down PDF parser options --- apps/desktop/src/features/score/pdfjs.test.ts | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/apps/desktop/src/features/score/pdfjs.test.ts b/apps/desktop/src/features/score/pdfjs.test.ts index b225830e5..8ef36a785 100644 --- a/apps/desktop/src/features/score/pdfjs.test.ts +++ b/apps/desktop/src/features/score/pdfjs.test.ts @@ -26,7 +26,7 @@ describe("score PDF.js boundary", () => { expect(GlobalWorkerOptions.workerSrc).toBe("/assets/pdf.worker.min.mjs"); }); - it("copies validated bytes through the supported data-only API", () => { + it("copies validated bytes through the hardened data-only API", () => { const source = new Uint8Array([0x25, 0x50, 0x44, 0x46]); loadScorePdf(source); @@ -34,9 +34,19 @@ describe("score PDF.js boundary", () => { expect(getDocument).toHaveBeenCalledTimes(1); const parameters = vi.mocked(getDocument).mock.calls[0]?.[0]; expect(parameters).toBeTypeOf("object"); - expect(Object.keys(parameters as object)).toEqual(["data"]); - const copiedBytes = (parameters as { data: Uint8Array }).data; - expect(copiedBytes).toEqual(source); - expect(copiedBytes).not.toBe(source); + expect(Object.keys(parameters as object)).toEqual([ + "data", + "enableXfa", + "useWorkerFetch" + ]); + const hardenedParameters = parameters as { + data: Uint8Array; + enableXfa: boolean; + useWorkerFetch: boolean; + }; + expect(hardenedParameters.data).toEqual(source); + expect(hardenedParameters.data).not.toBe(source); + expect(hardenedParameters.enableXfa).toBe(false); + expect(hardenedParameters.useWorkerFetch).toBe(false); }); }); From c65f5b12274caf046c6da05b1633baaae21199c2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 16 Aug 2026 05:09:42 +0900 Subject: [PATCH 063/161] docs(security): record PDF parser hardening evidence --- .../high-security-pdf-http-baseline.md | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/docs/doctoring/high-security-pdf-http-baseline.md b/docs/doctoring/high-security-pdf-http-baseline.md index 5e56be8bf..ab2d2035e 100644 --- a/docs/doctoring/high-security-pdf-http-baseline.md +++ b/docs/doctoring/high-security-pdf-http-baseline.md @@ -8,13 +8,16 @@ BandScope treats the PDF parser and its transitive HTTP client as one security-r - `undici` is pinned exactly to `7.29.0` through the root npm override; and - npm `10.9.8` is the approved generator for reviewed root-workspace dependency updates, while primary CI consumes the committed lock through frozen validation rather than re-resolving it. -PDF.js `6.2.108` no longer exposes the legacy `isEvalSupported` member in its public `DocumentInitParameters` contract, and `getDocument` no longer reads that member. BandScope therefore does not cast or pass an unknown option that would be ignored while creating false assurance. The primary remediation is the patched parser release, reinforced by a narrow data-only call, copied caller-owned bytes, and a same-origin bundled worker. +PDF.js `6.2.108` no longer exposes the legacy `isEvalSupported` member in its public `DocumentInitParameters` contract, and `getDocument` no longer reads that member. BandScope therefore does not cast or pass an unknown option that would be ignored while creating false assurance. The primary remediation is the patched parser release, reinforced by a narrow data-only call, copied caller-owned bytes, a same-origin bundled worker, explicit `enableXfa: false`, and explicit `useWorkerFetch: false`. ```mermaid flowchart LR A[Validated local PDF bytes] --> B[Copied Uint8Array] B --> D[Data-only DocumentInitParameters] - D --> C[pdfjs-dist 6.2.108] + D --> X[XFA disabled] + D --> F[Worker helper fetch disabled] + X --> C[pdfjs-dist 6.2.108] + F --> C C --> W[Same-origin bundled worker] W --> R[Canvas render] J[jsdom development path] --> U[undici 7.29.0 override] @@ -26,12 +29,20 @@ flowchart LR ## Threat boundary -The score viewer accepts only bytes already copied into the app-owned workspace through the native PDF intake boundary. It does not accept a URL, credentials, custom request headers, or a remote worker. This prevents a PDF from selecting an attacker-controlled fetch origin or script asset. +The score viewer accepts only bytes already copied into the app-owned workspace through the native PDF intake boundary. It does not accept a URL, credentials, custom request headers, or a remote worker. It also disables XFA rendering and PDF.js worker-side fetching of helper resources at this wrapper boundary. These controls prevent the caller from selecting an attacker-controlled document origin or worker asset and make the intended no-XML-form/no-worker-fetch policy explicit rather than relying on upstream defaults. -PDF bytes remain untrusted after the native magic-byte, size, and path checks. Parser vulnerabilities, malformed object graphs, embedded actions, and resource-exhaustion paths can still occur inside a syntactically valid PDF. The patched parser, exact dependency lock, copied data-only input, same-origin worker, and existing native intake limits therefore remain mandatory for locally selected files. +PDF bytes remain untrusted after the native magic-byte, size, and path checks. Parser vulnerabilities, malformed object graphs, embedded actions, metadata/XML parsing, and resource-exhaustion paths can still occur inside a syntactically valid PDF. The patched parser, exact dependency lock, copied data-only input, explicit parser options, same-origin worker, and existing native intake limits therefore remain mandatory for locally selected files. + +The pinned PDF.js XML parser does not expose an external-entity resolver through this wrapper: its default `onDoctype()` hook is a no-op, and `onResolveEntity()` resolves only the built-in XML entities before returning an unknown named entity literally. This source-level observation narrows what BandScope can claim; it is not a general assertion that every future PDF.js XML path is immune to entity-processing defects. Any parser upgrade must re-check the upstream implementation and repeat adversarial PDF verification. Undici is currently a development dependency reached through jsdom, but development and CI parsers process attacker-controlled fixtures, generated HTML, and network-like request bodies. A dev-only label does not make header injection, shared-cache disclosure, retry desynchronization, or cookie-attribute injection acceptable in the trusted build boundary. +## Strix finding adjudication boundary + +Strix run `31871388084` on predecessor head `6f81f52c193c1e327d078eba7a2ea3bdbfbc87c2` reported a possible XXE path through `loadScorePdf`. Its attached proof-of-concept returned only a four-byte `%PDF` prefix and stated that construction of an actual PDF containing the alleged XML payload remained necessary. It did not demonstrate entity expansion, local-file disclosure, a network request, or parser output containing an external entity. + +The finding was therefore not suppressed and was not treated as proven exploitation. Instead, the exact dependency source was inspected and the wrapper was hardened at the narrowest supported API boundary: XFA rendering and worker-side helper fetching are now explicitly disabled and regression-locked. A fresh exact-head Strix result remains mandatory; a predecessor report, whether pass or fail, is not transferable merge evidence. + ## Lockfile provenance The dependency manifests and complete lock artifact were generated and reconciled on this branch with the approved Node `22.22.3` / npm `10.9.8` toolchain before the current frozen-validation gate was finalized. The historical generation run and artifact are provenance evidence only; they do **not** satisfy a later head's merge gate and primary CI intentionally does not repeat mutable dependency resolution. @@ -52,13 +63,14 @@ The lock contract requires the exact public-registry tarball and SHA-512 SRI for The merge gate includes: - exact manifest and lock artifact tests; -- a direct PDF.js wrapper test proving copied bytes, the locally bundled worker, and an exact data-only initialization object; +- a direct PDF.js wrapper test proving copied bytes, the locally bundled worker, `enableXfa: false`, `useWorkerFetch: false`, and no URL-bearing initialization member; - TypeScript compilation against the installed PDF.js `DocumentInitParameters` rather than an unsafe cast; - valid and malformed local score-PDF component tests; - desktop lint, strict typecheck, complete measured tests, and production build; - Tauri/Rust checks and native PDF intake regressions; - `npm audit --workspaces --audit-level=high` with no high finding; - repository SAST, CodeQL, security scan, secret scan, SBOM, and dependency evidence; +- current-head Strix evidence rather than predecessor-head scanner output; - current-head central coverage and automated review; - zero unresolved actionable threads and a qualifying independent non-author approval; and - normal branch protection without administrative bypass. @@ -75,10 +87,12 @@ GitHub. (2026). *PDF.js vulnerable to arbitrary JavaScript execution upon openin Mozilla. (2026). *Document initialization parameters in PDF.js 6.2.108* [Source code]. GitHub. https://github.com/mozilla/pdf.js/blob/v6.2.108/src/display/api.js +Mozilla. (2026). *PDF.js XML parser in version 6.2.108* [Source code]. GitHub. https://github.com/mozilla/pdf.js/blob/v6.2.108/src/core/xml_parser.js + Mozilla. (2026). *PDF.js 6.2.108* [Software release]. https://github.com/mozilla/pdf.js/releases/tag/v6.2.108 Node.js contributors. (2026). *Undici 7.29.0* [Software release]. https://github.com/nodejs/undici/releases/tag/v7.29.0 npm, Inc. (2026). *npm ci*. npm Docs. https://docs.npmjs.com/cli/v11/commands/npm-ci/ -npm, Inc. (2026). *package-lock.json*. npm Docs. https://docs.npmjs.com/cli/v11/configuring-npm/package-lock-json/ \ No newline at end of file +npm, Inc. (2026). *package-lock.json*. npm Docs. https://docs.npmjs.com/cli/v11/configuring-npm/package-lock-json/ From dae5d3c8d055381d57c0971d8e40456711499476 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 16 Aug 2026 05:11:40 +0900 Subject: [PATCH 064/161] docs(security): correct PDF.js advisory attribution --- docs/doctoring/high-security-pdf-http-baseline.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/doctoring/high-security-pdf-http-baseline.md b/docs/doctoring/high-security-pdf-http-baseline.md index ab2d2035e..87dbd83cf 100644 --- a/docs/doctoring/high-security-pdf-http-baseline.md +++ b/docs/doctoring/high-security-pdf-http-baseline.md @@ -8,7 +8,9 @@ BandScope treats the PDF parser and its transitive HTTP client as one security-r - `undici` is pinned exactly to `7.29.0` through the root npm override; and - npm `10.9.8` is the approved generator for reviewed root-workspace dependency updates, while primary CI consumes the committed lock through frozen validation rather than re-resolving it. -PDF.js `6.2.108` no longer exposes the legacy `isEvalSupported` member in its public `DocumentInitParameters` contract, and `getDocument` no longer reads that member. BandScope therefore does not cast or pass an unknown option that would be ignored while creating false assurance. The primary remediation is the patched parser release, reinforced by a narrow data-only call, copied caller-owned bytes, a same-origin bundled worker, explicit `enableXfa: false`, and explicit `useWorkerFetch: false`. +Repository dependency/security tooling reported the protected-base `pdfjs-dist@6.1.200` as requiring a newer floor. That finding is kept distinct from the older, GitHub-reviewed CVE-2024-4367 / GHSA-wgrm-67xf-hhpq: the 2024 advisory affected `pdfjs-dist <=4.1.392` and was fixed in `4.2.67`, so it is historical parser-risk context and is **not** evidence that `6.1.200` was affected by that CVE. BandScope pins the current `6.2.108` artifact selected by the repository security baseline and requires current-head audit/security evidence rather than misattributing a scanner result to an unrelated advisory. + +PDF.js `6.2.108` no longer exposes the legacy `isEvalSupported` member in its public `DocumentInitParameters` contract, and `getDocument` no longer reads that member. BandScope therefore does not cast or pass an unknown option that would be ignored while creating false assurance. The parser boundary is reinforced by a narrow data-only call, copied caller-owned bytes, a same-origin bundled worker, explicit `enableXfa: false`, and explicit `useWorkerFetch: false`. ```mermaid flowchart LR @@ -31,7 +33,7 @@ flowchart LR The score viewer accepts only bytes already copied into the app-owned workspace through the native PDF intake boundary. It does not accept a URL, credentials, custom request headers, or a remote worker. It also disables XFA rendering and PDF.js worker-side fetching of helper resources at this wrapper boundary. These controls prevent the caller from selecting an attacker-controlled document origin or worker asset and make the intended no-XML-form/no-worker-fetch policy explicit rather than relying on upstream defaults. -PDF bytes remain untrusted after the native magic-byte, size, and path checks. Parser vulnerabilities, malformed object graphs, embedded actions, metadata/XML parsing, and resource-exhaustion paths can still occur inside a syntactically valid PDF. The patched parser, exact dependency lock, copied data-only input, explicit parser options, same-origin worker, and existing native intake limits therefore remain mandatory for locally selected files. +PDF bytes remain untrusted after the native magic-byte, size, and path checks. Parser vulnerabilities, malformed object graphs, embedded actions, metadata/XML parsing, and resource-exhaustion paths can still occur inside a syntactically valid PDF. The exact dependency lock, copied data-only input, explicit parser options, same-origin worker, and existing native intake limits therefore remain mandatory for locally selected files. The pinned PDF.js XML parser does not expose an external-entity resolver through this wrapper: its default `onDoctype()` hook is a no-op, and `onResolveEntity()` resolves only the built-in XML entities before returning an unknown named entity literally. This source-level observation narrows what BandScope can claim; it is not a general assertion that every future PDF.js XML path is immune to entity-processing defects. Any parser upgrade must re-check the upstream implementation and repeat adversarial PDF verification. @@ -83,7 +85,7 @@ Rollback restores the previous desktop manifest, root override, complete lock, P ## References -GitHub. (2026). *PDF.js vulnerable to arbitrary JavaScript execution upon opening a malicious PDF* (GHSA-hq66-cqwq-w95j) [Security advisory]. https://github.com/advisories/GHSA-hq66-cqwq-w95j +GitHub. (2024). *PDF.js vulnerable to arbitrary JavaScript execution upon opening a malicious PDF* (GHSA-wgrm-67xf-hhpq) [Security advisory]. https://github.com/advisories/GHSA-wgrm-67xf-hhpq Mozilla. (2026). *Document initialization parameters in PDF.js 6.2.108* [Source code]. GitHub. https://github.com/mozilla/pdf.js/blob/v6.2.108/src/display/api.js From afdecc5ef4f501c32975e599f81e1b1e1bf49686 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 16:28:26 +0900 Subject: [PATCH 065/161] test(build): require coordinated Node 22.22.2 and jsdom 30 floor --- .../tests/test_node_runtime_contract.py | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 services/analysis-engine/tests/test_node_runtime_contract.py diff --git a/services/analysis-engine/tests/test_node_runtime_contract.py b/services/analysis-engine/tests/test_node_runtime_contract.py new file mode 100644 index 000000000..e7913ab4f --- /dev/null +++ b/services/analysis-engine/tests/test_node_runtime_contract.py @@ -0,0 +1,99 @@ +"""Regression tests for the supported Node.js and jsdom compatibility floor.""" + +from __future__ import annotations + +import json +import re +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[3] +EXPECTED_NODE_ENGINE = ">=22.22.2 <23" +EXPECTED_NODE_FLOOR = (22, 22, 2) +EXPECTED_NPM_VERSION = "10.9.7" +EXPECTED_JSDOM_RANGE = "^30.0.1" + + +def _load_json(path: str) -> dict[str, object]: + """Load one repository JSON file for an exact contract assertion.""" + return json.loads((ROOT / path).read_text(encoding="utf-8")) + + +def _supports_band_node(version: tuple[int, int, int]) -> bool: + """Model the deliberately narrow supported Node 22 patch interval.""" + return EXPECTED_NODE_FLOOR <= version < (23, 0, 0) + + +def test_node_engine_floor_matches_jsdom_30_runtime_contract() -> None: + """Root manifest and lock metadata must publish the same Node compatibility floor.""" + package = _load_json("package.json") + package_lock = _load_json("package-lock.json") + + assert package["engines"] == {"node": EXPECTED_NODE_ENGINE} + assert package["packageManager"] == f"npm@{EXPECTED_NPM_VERSION}" + assert package_lock["packages"][""]["engines"] == {"node": EXPECTED_NODE_ENGINE} + + +def test_node_floor_rejects_pre_floor_patch_and_accepts_exact_minimum() -> None: + """Node 22.22.1 is unsupported while the exact 22.22.2 floor is supported.""" + assert not _supports_band_node((22, 22, 1)) + assert _supports_band_node((22, 22, 2)) + assert _supports_band_node((22, 99, 0)) + assert not _supports_band_node((23, 0, 0)) + + +def test_jsdom_30_is_adopted_in_manifest_and_lock() -> None: + """The coordinated compatibility slice must carry jsdom 30 in both package graphs.""" + desktop = _load_json("apps/desktop/package.json") + package_lock = _load_json("package-lock.json") + + assert desktop["devDependencies"]["jsdom"] == EXPECTED_JSDOM_RANGE + assert package_lock["packages"]["apps/desktop"]["devDependencies"]["jsdom"] == EXPECTED_JSDOM_RANGE + assert package_lock["packages"]["node_modules/jsdom"]["version"] == "30.0.1" + + +def test_build_baseline_runs_the_complete_suite_on_exact_node_floor() -> None: + """A dedicated build job must exercise the real toolchain on Node 22.22.2.""" + workflow = (ROOT / ".github/workflows/build-baseline.yml").read_text(encoding="utf-8") + + match = re.search( + r"(?ms)^ node-minimum-compatibility:\n(?P.*?)(?=^ [a-zA-Z0-9_-]+:\n|\Z)", + workflow, + ) + assert match is not None, "build-baseline must define node-minimum-compatibility" + body = match.group("body") + + required_fragments = ( + "node-version: 22.22.2", + f'EXPECTED_NPM_VERSION: "{EXPECTED_NPM_VERSION}"', + 'test "$(npm --version)" = "$EXPECTED_NPM_VERSION"', + "npm ci", + "npm run lint", + "npm run typecheck", + "npm run test", + "npm run build", + "npm run build-storybook --workspace @bandscope/desktop", + "cargo +stable check --manifest-path apps/desktop/src-tauri/Cargo.toml --locked", + "cargo +stable test --manifest-path apps/desktop/src-tauri/Cargo.toml --locked", + ) + for fragment in required_fragments: + assert fragment in body, f"minimum-version job is missing: {fragment}" + + +def test_repository_no_longer_advertises_node_22_13_floor() -> None: + """Canonical runtime/build documentation must not retain the superseded 22.13 floor.""" + audited_paths = ( + "package.json", + "package-lock.json", + "README.md", + "CONTRIBUTING.md", + "CLAUDE.md", + "docs/TEST_STRATEGY.md", + "docs/OPERABILITY.md", + ) + + stale = [ + path + for path in audited_paths + if "22.13" in (ROOT / path).read_text(encoding="utf-8") + ] + assert stale == [] From 1dbe037ad95a787e9a778e22d54b8f61fcac1856 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 17:01:33 +0900 Subject: [PATCH 066/161] test(build): align Node floor contract with canonical npm generator --- services/analysis-engine/tests/test_node_runtime_contract.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/analysis-engine/tests/test_node_runtime_contract.py b/services/analysis-engine/tests/test_node_runtime_contract.py index e7913ab4f..bbca3f228 100644 --- a/services/analysis-engine/tests/test_node_runtime_contract.py +++ b/services/analysis-engine/tests/test_node_runtime_contract.py @@ -9,7 +9,7 @@ ROOT = Path(__file__).resolve().parents[3] EXPECTED_NODE_ENGINE = ">=22.22.2 <23" EXPECTED_NODE_FLOOR = (22, 22, 2) -EXPECTED_NPM_VERSION = "10.9.7" +EXPECTED_NPM_VERSION = "10.9.8" EXPECTED_JSDOM_RANGE = "^30.0.1" From 32cf47c5926fc517881019149080da3b49dfd33b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 17:04:03 +0900 Subject: [PATCH 067/161] build(node): raise supported Node 22 floor --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2c3a4e945..733bef022 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "type": "module", "packageManager": "npm@10.9.8", "engines": { - "node": ">=22.13 <23" + "node": ">=22.22.2 <23" }, "devEngines": { "packageManager": { From f74beb0818e8240786c6a91010ea18baa9567390 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 17:04:20 +0900 Subject: [PATCH 068/161] build(deps-dev): adopt jsdom 30 on coordinated Node floor --- apps/desktop/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/desktop/package.json b/apps/desktop/package.json index e09719b22..49ee6919c 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -39,7 +39,7 @@ "@vitejs/plugin-react": "^6.0.2", "@vitest/coverage-v8": "^4.1.10", "eslint": "^10.7.0", - "jsdom": "^29.1.1", + "jsdom": "^30.0.1", "storybook": "^10.4.6", "tailwindcss": "^4.2.4", "typescript": "^6.0.3", From 2affed55c8c8eb98891173d5930751ca2cbb7af4 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 17:04:49 +0900 Subject: [PATCH 069/161] docs(build): publish Node 22.22.2 minimum --- CLAUDE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CLAUDE.md b/CLAUDE.md index 82c2c704a..58303dcf2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -10,7 +10,7 @@ Agent execution and delegation rules live in `docs/agents/README.md`. PR canonic ## Common commands -Setup (Node >=22.13 <23, Python >=3.12 via `uv`, Rust stable only for the Tauri shell): +Setup (Node >=22.22.2 <23, Python >=3.12 via `uv`, Rust stable only for the Tauri shell): ```bash npm install From 65d199d1b8e093fc51d8991b6dc57ed4f81562ab Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 17:06:28 +0900 Subject: [PATCH 070/161] test(build): align npm provenance with Node compatibility floor --- services/analysis-engine/tests/test_npm_toolchain_contract.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/analysis-engine/tests/test_npm_toolchain_contract.py b/services/analysis-engine/tests/test_npm_toolchain_contract.py index 6f38db8c0..1c0e810ca 100644 --- a/services/analysis-engine/tests/test_npm_toolchain_contract.py +++ b/services/analysis-engine/tests/test_npm_toolchain_contract.py @@ -34,7 +34,7 @@ def test_root_manifest_pins_the_lockfile_generator_and_fails_on_drift() -> None: manifest = _root_manifest() assert manifest["packageManager"] == f"npm@{_EXPECTED_NPM_VERSION}" - assert manifest["engines"] == {"node": ">=22.13 <23"} + assert manifest["engines"] == {"node": ">=22.22.2 <23"} assert manifest["devEngines"] == { "packageManager": { "name": "npm", From c721ceebd9274fe6d0ca174ca83b80941f8fe95f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 17:06:53 +0900 Subject: [PATCH 071/161] test(build): require explicit minimum-Node npm bootstrap lane --- .../analysis-engine/tests/test_node_runtime_contract.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/services/analysis-engine/tests/test_node_runtime_contract.py b/services/analysis-engine/tests/test_node_runtime_contract.py index bbca3f228..ea02fdef7 100644 --- a/services/analysis-engine/tests/test_node_runtime_contract.py +++ b/services/analysis-engine/tests/test_node_runtime_contract.py @@ -51,20 +51,21 @@ def test_jsdom_30_is_adopted_in_manifest_and_lock() -> None: assert package_lock["packages"]["node_modules/jsdom"]["version"] == "30.0.1" -def test_build_baseline_runs_the_complete_suite_on_exact_node_floor() -> None: - """A dedicated build job must exercise the real toolchain on Node 22.22.2.""" - workflow = (ROOT / ".github/workflows/build-baseline.yml").read_text(encoding="utf-8") +def test_minimum_node_lane_runs_complete_suite_with_pinned_npm() -> None: + """Exercise the exact Node floor after bootstrapping the canonical npm generator.""" + workflow = (ROOT / ".github/workflows/node-minimum-compatibility.yml").read_text(encoding="utf-8") match = re.search( r"(?ms)^ node-minimum-compatibility:\n(?P.*?)(?=^ [a-zA-Z0-9_-]+:\n|\Z)", workflow, ) - assert match is not None, "build-baseline must define node-minimum-compatibility" + assert match is not None, "minimum-version workflow must define node-minimum-compatibility" body = match.group("body") required_fragments = ( "node-version: 22.22.2", f'EXPECTED_NPM_VERSION: "{EXPECTED_NPM_VERSION}"', + 'npm install --global "npm@$EXPECTED_NPM_VERSION" --ignore-scripts --no-audit --no-fund', 'test "$(npm --version)" = "$EXPECTED_NPM_VERSION"', "npm ci", "npm run lint", From fcb0b278fc664d1da6ea3c11990966abed06d112 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 17:07:14 +0900 Subject: [PATCH 072/161] ci(build): add exact Node 22.22.2 compatibility lane --- .../workflows/node-minimum-compatibility.yml | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 .github/workflows/node-minimum-compatibility.yml diff --git a/.github/workflows/node-minimum-compatibility.yml b/.github/workflows/node-minimum-compatibility.yml new file mode 100644 index 000000000..d3fb9757c --- /dev/null +++ b/.github/workflows/node-minimum-compatibility.yml @@ -0,0 +1,69 @@ +name: node-minimum-compatibility + +on: + pull_request: + push: + branches: + - develop + - main + +permissions: + contents: read + +env: + EXPECTED_NPM_VERSION: "10.9.8" + +jobs: + node-minimum-compatibility: + name: gate / build / node-minimum-compatibility + runs-on: macos-15 + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: 22.22.2 + cache: npm + - name: Bootstrap exact repository npm generator + shell: bash + run: | + cd "$RUNNER_TEMP" + npm install --global "npm@$EXPECTED_NPM_VERSION" --ignore-scripts --no-audit --no-fund + test "$(npm --version)" = "$EXPECTED_NPM_VERSION" + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: "3.12" + - uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2 + with: + version: "0.8.6" + enable-cache: false + - name: Install stable Rust toolchain + run: rustup toolchain install stable --profile minimal + - name: Install frozen Node dependencies + run: npm ci + - name: Sync frozen Python dependencies + run: uv sync --project services/analysis-engine --group dev --frozen + - name: Build and install Rust numeric extension + shell: bash + run: | + VENV_PY="$PWD/services/analysis-engine/.venv/bin/python" + uvx maturin@1.9.6 build --release \ + --manifest-path services/analysis-engine/rust/Cargo.toml \ + --interpreter "$VENV_PY" \ + --out services/analysis-engine/rust/dist + uv pip install --python "$VENV_PY" services/analysis-engine/rust/dist/*.whl + - name: Lint + run: npm run lint + - name: Typecheck + run: npm run typecheck + - name: Test with measured coverage + run: npm run test + - name: Build production workspaces + run: npm run build + - name: Build Storybook + run: npm run build-storybook --workspace @bandscope/desktop + - name: Check Tauri shell + run: cargo +stable check --manifest-path apps/desktop/src-tauri/Cargo.toml --locked + - name: Test Tauri shell + run: cargo +stable test --manifest-path apps/desktop/src-tauri/Cargo.toml --locked From aa527bb8e227a2ed960f2ba343e7768831c8f85f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 17:08:23 +0900 Subject: [PATCH 073/161] test(build): audit canonical runtime documentation paths --- services/analysis-engine/tests/test_node_runtime_contract.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/services/analysis-engine/tests/test_node_runtime_contract.py b/services/analysis-engine/tests/test_node_runtime_contract.py index ea02fdef7..6273285d9 100644 --- a/services/analysis-engine/tests/test_node_runtime_contract.py +++ b/services/analysis-engine/tests/test_node_runtime_contract.py @@ -88,8 +88,9 @@ def test_repository_no_longer_advertises_node_22_13_floor() -> None: "README.md", "CONTRIBUTING.md", "CLAUDE.md", - "docs/TEST_STRATEGY.md", - "docs/OPERABILITY.md", + "docs/engineering/harness-engineering.md", + "docs/security/cross-platform-build-policy.md", + "docs/operations/deploy-runbook.md", ) stale = [ From 5b50c59cef0d5a984352fab8bf34b683f2dedacc Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 17:12:15 +0900 Subject: [PATCH 074/161] test(ci): prevent pre-bootstrap npm cache discovery --- .../analysis-engine/tests/test_node_runtime_contract.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/services/analysis-engine/tests/test_node_runtime_contract.py b/services/analysis-engine/tests/test_node_runtime_contract.py index 6273285d9..798290442 100644 --- a/services/analysis-engine/tests/test_node_runtime_contract.py +++ b/services/analysis-engine/tests/test_node_runtime_contract.py @@ -79,6 +79,13 @@ def test_minimum_node_lane_runs_complete_suite_with_pinned_npm() -> None: for fragment in required_fragments: assert fragment in body, f"minimum-version job is missing: {fragment}" + setup_node = body.split("- uses: actions/setup-node@", maxsplit=1)[1].split( + "- name: Bootstrap exact repository npm generator", maxsplit=1 + )[0] + assert "cache: npm" not in setup_node, ( + "setup-node must not invoke bundled npm cache discovery before npm 10.9.8 is bootstrapped" + ) + def test_repository_no_longer_advertises_node_22_13_floor() -> None: """Canonical runtime/build documentation must not retain the superseded 22.13 floor.""" From 783112ba6625f655dfa43bab537d8f282d48e76d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 17:12:44 +0900 Subject: [PATCH 075/161] fix(ci): bootstrap canonical npm before cache discovery --- .github/workflows/node-minimum-compatibility.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/node-minimum-compatibility.yml b/.github/workflows/node-minimum-compatibility.yml index d3fb9757c..f8835d945 100644 --- a/.github/workflows/node-minimum-compatibility.yml +++ b/.github/workflows/node-minimum-compatibility.yml @@ -24,7 +24,6 @@ jobs: - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 22.22.2 - cache: npm - name: Bootstrap exact repository npm generator shell: bash run: | From e00476527c99cfc49c68ea62bb3033d46d5956c1 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 17:13:50 +0900 Subject: [PATCH 076/161] docs(build): reconcile Node floor with npm generator provenance --- .../npm-lockfile-generator-provenance.md | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/docs/doctoring/npm-lockfile-generator-provenance.md b/docs/doctoring/npm-lockfile-generator-provenance.md index 53488de48..c5412af22 100644 --- a/docs/doctoring/npm-lockfile-generator-provenance.md +++ b/docs/doctoring/npm-lockfile-generator-provenance.md @@ -11,7 +11,7 @@ The npm version is intentionally not repeated under `engines`. npm serializes `e Primary CI does **not** regenerate or update `package-lock.json`. It uses Node `22.22.3`, verifies npm `10.9.8`, and validates the committed lock with `npm ci --ignore-scripts --no-audit --no-fund`. The gate then rejects any manifest or lockfile working-tree change. The normal verification job performs the repository's reviewed `npm ci` installation before lint, typecheck, tests, build, and security checks. -The Node runtime support decision remains separate. This change does not raise the public `>=22.13 <23` Node range; a coordinated Node-floor migration is tracked independently. +The coordinated jsdom 30 migration raises BandScope's Node 22 runtime floor to `>=22.22.2 <23`. This runtime floor and the npm generator identity remain separate contracts: upstream Node `v22.22.2` bundles npm `10.9.7`, while BandScope's reviewed lock generator remains npm `10.9.8`. The exact-minimum compatibility lane therefore installs Node `22.22.2` without npm-cache discovery, explicitly bootstraps npm `10.9.8` before repository dependency consumption, verifies that generator identity, and only then runs the frozen install and full product verification. This preserves the exact runtime floor without weakening lockfile provenance. ## Why generator provenance still matters @@ -34,6 +34,14 @@ flowchart LR S --> C[normal npm ci and repository checks] ``` +## Minimum-runtime verification + +jsdom `30.0.1` declares Node `^22.22.2 || ^24.15.0 || >=26.0.0`. BandScope intentionally remains on the Node 22 line for this migration, so its supported interval is `>=22.22.2 <23`. The dedicated `.github/workflows/node-minimum-compatibility.yml` lane exists to prove the exact lower boundary rather than inferring compatibility from a newer CI patch release. + +The first exact-minimum run exposed an ordering defect: `actions/setup-node` with npm caching enabled executes `npm config get cache` during setup. On Node `22.22.2`, that command runs the bundled npm `10.9.7` inside the checked-out BandScope tree, where `devEngines.packageManager` correctly rejects any npm other than `10.9.8`. The repaired lane therefore does not enable setup-node's npm cache before bootstrap. It installs the approved npm in runner-owned temporary context, verifies `10.9.8`, and only then allows npm to consume repository state. + +This is not a relaxation of `devEngines`; the failure demonstrated that the gate was working as intended. The correction moves tool bootstrap ahead of the first repository-scoped npm invocation while preserving read-only checkout credentials, exact action pins, frozen dependency consumption, and the full lint/typecheck/test/build/Storybook/Tauri acceptance surface. + ## Security and operational boundary - CI lock validation must not run `npm install`, `npm update`, `npx`, or another mutable dependency-resolution command. @@ -41,6 +49,7 @@ flowchart LR - Platform-specific root `@esbuild/*` lock records must retain their expected `peer: true` metadata. Missing markers are treated as generator drift, not as an acceptable side effect of an unrelated dependency update. - The lock-validation job disables dependency lifecycle scripts. The normal clean install retains the repository's reviewed execution behavior. - The exact npm version check occurs before either frozen install; a different bundled or globally installed npm cannot provide acceptance evidence. +- The exact-minimum Node lane must not invoke repository-scoped npm cache discovery before npm `10.9.8` is bootstrapped. - Registry-resolved package records require SRI evidence in the committed lock. - Install-shaping flags that affect the dependency tree, such as `legacy-peer-deps` or `install-links`, must be committed in project configuration and applied consistently to generation and `npm ci`. - The root `package-lock.json` remains the sole npm workspace lock. Nested workspace locks are prohibited. @@ -60,6 +69,8 @@ flowchart LR 7. SRI evidence for every public npm-registry artifact in the root lock; and 8. preservation of `peer: true` on every root `node_modules/@esbuild/*` platform record. +`services/analysis-engine/tests/test_node_runtime_contract.py` separately verifies the `>=22.22.2 <23` runtime interval, jsdom `30.0.1` manifest/lock alignment, the exact-minimum workflow, npm-before-cache bootstrap ordering, the full compatibility acceptance surface, and absence of the superseded Node floor from canonical runtime/build documentation. + The exact PDF.js and Undici baseline is covered separately by `test_high_security_dependency_baseline.py` and the desktop PDF loader tests. A dependency update is mergeable only after the updated manifest and complete generated lock are reviewed together and the exact current head passes frozen lock validation, normal install, lint, strict typecheck, measured tests, production build, Rust/Tauri checks, security/supply-chain gates, current review, independent approval, and branch protection without bypass. @@ -68,6 +79,8 @@ A dependency update is mergeable only after the updated manifest and complete ge CI proves that the committed manifest and lock can be consumed as a frozen pair by the approved toolchain, that public-registry lock entries carry integrity evidence, and that the known generator-sensitive `@esbuild/*` peer markers remain present. It does **not** claim that resolving mutable manifest ranges again at a later time will reproduce byte-identical lock metadata. When a dependency update is needed, npm `10.9.8` remains the approved generator and its entire resulting lock diff is review evidence. +The Node-minimum lane proves only the repository's selected Node 22 lower boundary with the approved npm generator and current product checks. It does not broaden BandScope support to Node 24 or 26 merely because upstream jsdom supports those lines. + ## Incident response and rollback When an update produces unexpected lock churn: @@ -78,8 +91,14 @@ When an update produces unexpected lock churn: 4. regenerate the complete lock in a dedicated update branch using the reviewed npm version, then review the full diff before relying on it; and 5. if rollback is necessary, restore the prior manifest and complete lock together, then rerun the entire exact-head gate. +For an exact-minimum runtime failure, preserve the setup-node environment detail, bundled npm identity, first npm invocation, bootstrap command, and exact workflow job log. Do not weaken `devEngines` to accommodate a bundled npm mismatch; repair ordering so the approved generator is authoritative before repository-scoped npm commands execute. + ## References +jsdom contributors. (2026). *jsdom 30.0.1 package manifest* [Source code]. GitHub. https://github.com/jsdom/jsdom/blob/v30.0.1/package.json + +Node.js contributors. (2026). *Node.js v22.22.2 bundled npm package manifest* [Source code]. GitHub. https://github.com/nodejs/node/blob/v22.22.2/deps/npm/package.json + npm, Inc. (2026). *npm ci*. npm Docs. https://docs.npmjs.com/cli/v11/commands/npm-ci/ npm, Inc. (2026). *package-lock.json*. npm Docs. https://docs.npmjs.com/cli/v10/configuring-npm/package-lock-json/ From b3a3c910f23468c979be31e477306450fe6997df Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 17:17:06 +0900 Subject: [PATCH 077/161] test(ci): require setup-node automatic cache opt-out --- services/analysis-engine/tests/test_node_runtime_contract.py | 1 + 1 file changed, 1 insertion(+) diff --git a/services/analysis-engine/tests/test_node_runtime_contract.py b/services/analysis-engine/tests/test_node_runtime_contract.py index 798290442..5e9db163c 100644 --- a/services/analysis-engine/tests/test_node_runtime_contract.py +++ b/services/analysis-engine/tests/test_node_runtime_contract.py @@ -64,6 +64,7 @@ def test_minimum_node_lane_runs_complete_suite_with_pinned_npm() -> None: required_fragments = ( "node-version: 22.22.2", + "package-manager-cache: false", f'EXPECTED_NPM_VERSION: "{EXPECTED_NPM_VERSION}"', 'npm install --global "npm@$EXPECTED_NPM_VERSION" --ignore-scripts --no-audit --no-fund', 'test "$(npm --version)" = "$EXPECTED_NPM_VERSION"', From feb41363c98c6b4e96ff95b0ce0df95f444c40c4 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 17:17:33 +0900 Subject: [PATCH 078/161] fix(ci): disable setup-node auto cache before npm bootstrap --- .github/workflows/node-minimum-compatibility.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/node-minimum-compatibility.yml b/.github/workflows/node-minimum-compatibility.yml index f8835d945..09eb9e8c4 100644 --- a/.github/workflows/node-minimum-compatibility.yml +++ b/.github/workflows/node-minimum-compatibility.yml @@ -24,6 +24,7 @@ jobs: - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 22.22.2 + package-manager-cache: false - name: Bootstrap exact repository npm generator shell: bash run: | From a25660654f9f26db7eebeffbde022ad6e6129b3d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 19:03:50 +0900 Subject: [PATCH 079/161] test(node): require Corepack npm bootstrap and lock provenance --- .../tests/test_node_runtime_contract.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/services/analysis-engine/tests/test_node_runtime_contract.py b/services/analysis-engine/tests/test_node_runtime_contract.py index 5e9db163c..a15f2a9e2 100644 --- a/services/analysis-engine/tests/test_node_runtime_contract.py +++ b/services/analysis-engine/tests/test_node_runtime_contract.py @@ -66,8 +66,13 @@ def test_minimum_node_lane_runs_complete_suite_with_pinned_npm() -> None: "node-version: 22.22.2", "package-manager-cache: false", f'EXPECTED_NPM_VERSION: "{EXPECTED_NPM_VERSION}"', - 'npm install --global "npm@$EXPECTED_NPM_VERSION" --ignore-scripts --no-audit --no-fund', - 'test "$(npm --version)" = "$EXPECTED_NPM_VERSION"', + 'mkdir -p "$RUNNER_TEMP/corepack-bin"', + 'corepack enable npm --install-directory "$RUNNER_TEMP/corepack-bin"', + "corepack install", + 'echo "$RUNNER_TEMP/corepack-bin" >> "$GITHUB_PATH"', + 'test "$(PATH="$RUNNER_TEMP/corepack-bin:$PATH" npm --version)" = "$EXPECTED_NPM_VERSION"', + "npm install --package-lock-only --ignore-scripts --no-audit --no-fund", + "git diff --exit-code -- package-lock.json", "npm ci", "npm run lint", "npm run typecheck", @@ -80,6 +85,10 @@ def test_minimum_node_lane_runs_complete_suite_with_pinned_npm() -> None: for fragment in required_fragments: assert fragment in body, f"minimum-version job is missing: {fragment}" + assert "npm install --global" not in body, ( + "minimum-version workflow must not bootstrap npm through an unpinned npm install command" + ) + setup_node = body.split("- uses: actions/setup-node@", maxsplit=1)[1].split( "- name: Bootstrap exact repository npm generator", maxsplit=1 )[0] From 9eb5c10d7ad4d30d3afb36178ad767d6321852c9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 19:04:14 +0900 Subject: [PATCH 080/161] fix(node): harden npm bootstrap and capture lock provenance --- .../workflows/node-minimum-compatibility.yml | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/.github/workflows/node-minimum-compatibility.yml b/.github/workflows/node-minimum-compatibility.yml index 09eb9e8c4..fe74ef407 100644 --- a/.github/workflows/node-minimum-compatibility.yml +++ b/.github/workflows/node-minimum-compatibility.yml @@ -28,9 +28,30 @@ jobs: - name: Bootstrap exact repository npm generator shell: bash run: | - cd "$RUNNER_TEMP" - npm install --global "npm@$EXPECTED_NPM_VERSION" --ignore-scripts --no-audit --no-fund - test "$(npm --version)" = "$EXPECTED_NPM_VERSION" + mkdir -p "$RUNNER_TEMP/corepack-bin" + corepack enable npm --install-directory "$RUNNER_TEMP/corepack-bin" + corepack install + echo "$RUNNER_TEMP/corepack-bin" >> "$GITHUB_PATH" + test "$(PATH="$RUNNER_TEMP/corepack-bin:$PATH" npm --version)" = "$EXPECTED_NPM_VERSION" + - name: Generate exact lockfile provenance candidate + id: npm_lock + shell: bash + run: | + npm install --package-lock-only --ignore-scripts --no-audit --no-fund + if git diff --quiet -- package-lock.json; then + echo "changed=false" >> "$GITHUB_OUTPUT" + else + echo "changed=true" >> "$GITHUB_OUTPUT" + fi + - name: Upload lockfile drift evidence + if: steps.npm_lock.outputs.changed == 'true' + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: node-minimum-package-lock-candidate + path: package-lock.json + retention-days: 1 + - name: Verify committed lock matches exact generator + run: git diff --exit-code -- package-lock.json - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: "3.12" From 6f5d1e40c13eb7a07d651798ff7fa1fe64bcd1b6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 19:08:32 +0900 Subject: [PATCH 081/161] test(node): assert direct workspace jsdom resolution --- services/analysis-engine/tests/test_node_runtime_contract.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/analysis-engine/tests/test_node_runtime_contract.py b/services/analysis-engine/tests/test_node_runtime_contract.py index a15f2a9e2..624969e98 100644 --- a/services/analysis-engine/tests/test_node_runtime_contract.py +++ b/services/analysis-engine/tests/test_node_runtime_contract.py @@ -48,7 +48,7 @@ def test_jsdom_30_is_adopted_in_manifest_and_lock() -> None: assert desktop["devDependencies"]["jsdom"] == EXPECTED_JSDOM_RANGE assert package_lock["packages"]["apps/desktop"]["devDependencies"]["jsdom"] == EXPECTED_JSDOM_RANGE - assert package_lock["packages"]["node_modules/jsdom"]["version"] == "30.0.1" + assert package_lock["packages"]["apps/desktop/node_modules/jsdom"]["version"] == "30.0.1" def test_minimum_node_lane_runs_complete_suite_with_pinned_npm() -> None: From 0766cdbee0f2af044b6fc058d907493bf0d66cc7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 19:38:16 +0900 Subject: [PATCH 082/161] fix(ci): keep Node floor lane frozen-install only --- .../workflows/node-minimum-compatibility.yml | 23 ++----------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/.github/workflows/node-minimum-compatibility.yml b/.github/workflows/node-minimum-compatibility.yml index fe74ef407..de540ba94 100644 --- a/.github/workflows/node-minimum-compatibility.yml +++ b/.github/workflows/node-minimum-compatibility.yml @@ -33,25 +33,8 @@ jobs: corepack install echo "$RUNNER_TEMP/corepack-bin" >> "$GITHUB_PATH" test "$(PATH="$RUNNER_TEMP/corepack-bin:$PATH" npm --version)" = "$EXPECTED_NPM_VERSION" - - name: Generate exact lockfile provenance candidate - id: npm_lock - shell: bash - run: | - npm install --package-lock-only --ignore-scripts --no-audit --no-fund - if git diff --quiet -- package-lock.json; then - echo "changed=false" >> "$GITHUB_OUTPUT" - else - echo "changed=true" >> "$GITHUB_OUTPUT" - fi - - name: Upload lockfile drift evidence - if: steps.npm_lock.outputs.changed == 'true' - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: node-minimum-package-lock-candidate - path: package-lock.json - retention-days: 1 - - name: Verify committed lock matches exact generator - run: git diff --exit-code -- package-lock.json + - name: Install frozen Node dependencies + run: npm ci --ignore-scripts --no-audit --no-fund - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: "3.12" @@ -61,8 +44,6 @@ jobs: enable-cache: false - name: Install stable Rust toolchain run: rustup toolchain install stable --profile minimal - - name: Install frozen Node dependencies - run: npm ci - name: Sync frozen Python dependencies run: uv sync --project services/analysis-engine --group dev --frozen - name: Build and install Rust numeric extension From 8596bd1c4459d039ae87dfc165ba132ed2c7cca4 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 19:41:46 +0900 Subject: [PATCH 083/161] fix(ci): satisfy workflow supply-chain baseline --- .github/workflows/node-minimum-compatibility.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/node-minimum-compatibility.yml b/.github/workflows/node-minimum-compatibility.yml index de540ba94..b82910160 100644 --- a/.github/workflows/node-minimum-compatibility.yml +++ b/.github/workflows/node-minimum-compatibility.yml @@ -11,6 +11,9 @@ permissions: contents: read env: + GIT_CONFIG_COUNT: "1" + GIT_CONFIG_KEY_0: init.defaultBranch + GIT_CONFIG_VALUE_0: develop EXPECTED_NPM_VERSION: "10.9.8" jobs: From ce45742e6c0c44e08a015d16ea012b4d6b2beac7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 21:03:26 -0700 Subject: [PATCH 084/161] test(ci): preserve credential-free lock validation in node floor --- services/analysis-engine/tests/test_npm_toolchain_contract.py | 1 + 1 file changed, 1 insertion(+) diff --git a/services/analysis-engine/tests/test_npm_toolchain_contract.py b/services/analysis-engine/tests/test_npm_toolchain_contract.py index 1c0e810ca..09f8c9f70 100644 --- a/services/analysis-engine/tests/test_npm_toolchain_contract.py +++ b/services/analysis-engine/tests/test_npm_toolchain_contract.py @@ -55,6 +55,7 @@ def test_primary_ci_consumes_the_lock_without_mutable_resolution() -> None: assert "npm ci --ignore-scripts --no-audit --no-fund" in lock_job assert "git diff --exit-code -- package.json package-lock.json" in lock_job assert "needs: lock-validation" in workflow + assert "persist-credentials: false" in lock_job assert "npm install " not in lock_job assert "npm update " not in lock_job assert "npx " not in lock_job From c07e51639dbb7b44f57ff0aabc26ad793e691add Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 21:03:44 -0700 Subject: [PATCH 085/161] fix(ci): inherit credential-free lock validation --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4ec39fc9d..64b045d53 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,6 +25,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: "22.22.3" From 5108c9ad52056c1960d99d382bceb1bd72c6de98 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 16:19:14 -0700 Subject: [PATCH 086/161] fix(node): inherit protected npm runtime authority --- .../workflows/node-minimum-compatibility.yml | 14 +++---- package.json | 5 ++- .../tests/test_node_runtime_contract.py | 40 ++++++++----------- .../tests/test_npm_toolchain_contract.py | 6 +-- 4 files changed, 28 insertions(+), 37 deletions(-) diff --git a/.github/workflows/node-minimum-compatibility.yml b/.github/workflows/node-minimum-compatibility.yml index b82910160..35f9ff2b4 100644 --- a/.github/workflows/node-minimum-compatibility.yml +++ b/.github/workflows/node-minimum-compatibility.yml @@ -14,7 +14,7 @@ env: GIT_CONFIG_COUNT: "1" GIT_CONFIG_KEY_0: init.defaultBranch GIT_CONFIG_VALUE_0: develop - EXPECTED_NPM_VERSION: "10.9.8" + EXPECTED_NPM_VERSION: "10.9.9" jobs: node-minimum-compatibility: @@ -28,14 +28,12 @@ jobs: with: node-version: 22.22.2 package-manager-cache: false - - name: Bootstrap exact repository npm generator - shell: bash + - name: Activate pinned npm runtime + run: corepack enable npm + - name: Verify exact npm lockfile generator and bundled tar run: | - mkdir -p "$RUNNER_TEMP/corepack-bin" - corepack enable npm --install-directory "$RUNNER_TEMP/corepack-bin" - corepack install - echo "$RUNNER_TEMP/corepack-bin" >> "$GITHUB_PATH" - test "$(PATH="$RUNNER_TEMP/corepack-bin:$PATH" npm --version)" = "$EXPECTED_NPM_VERSION" + test "$(npm --version)" = "$EXPECTED_NPM_VERSION" + npm run check:npm-runtime - name: Install frozen Node dependencies run: npm ci --ignore-scripts --no-audit --no-fund - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 diff --git a/package.json b/package.json index 733bef022..174929213 100644 --- a/package.json +++ b/package.json @@ -3,14 +3,14 @@ "private": true, "version": "0.1.3", "type": "module", - "packageManager": "npm@10.9.8", + "packageManager": "npm@10.9.9", "engines": { "node": ">=22.22.2 <23" }, "devEngines": { "packageManager": { "name": "npm", - "version": "10.9.8", + "version": "10.9.9", "onFail": "error" } }, @@ -26,6 +26,7 @@ "check:security-gates": "python3 scripts/checks/security_gates.py", "check:supply-chain": "python3 scripts/checks/verify_supply_chain.py", "check:github-bootstrap": "python3 scripts/checks/verify_github_bootstrap_policy.py", + "check:npm-runtime": "node scripts/checks/verify_npm_runtime.mjs", "check:python-docstrings": "python3 scripts/checks/run_analysis_command.py ruff check src tests ../../scripts --select D100,D101,D102,D103,D104,D105,D106,D107", "ruff:check": "python3 scripts/checks/run_analysis_command.py ruff check src tests", "ruff:format:check": "python3 scripts/checks/run_analysis_command.py ruff format --check src tests", diff --git a/services/analysis-engine/tests/test_node_runtime_contract.py b/services/analysis-engine/tests/test_node_runtime_contract.py index 624969e98..d67ef7819 100644 --- a/services/analysis-engine/tests/test_node_runtime_contract.py +++ b/services/analysis-engine/tests/test_node_runtime_contract.py @@ -9,7 +9,7 @@ ROOT = Path(__file__).resolve().parents[3] EXPECTED_NODE_ENGINE = ">=22.22.2 <23" EXPECTED_NODE_FLOOR = (22, 22, 2) -EXPECTED_NPM_VERSION = "10.9.8" +EXPECTED_NPM_VERSION = "10.9.9" EXPECTED_JSDOM_RANGE = "^30.0.1" @@ -52,8 +52,10 @@ def test_jsdom_30_is_adopted_in_manifest_and_lock() -> None: def test_minimum_node_lane_runs_complete_suite_with_pinned_npm() -> None: - """Exercise the exact Node floor after bootstrapping the canonical npm generator.""" - workflow = (ROOT / ".github/workflows/node-minimum-compatibility.yml").read_text(encoding="utf-8") + """Exercise the exact Node floor after activating the reviewed npm runtime.""" + workflow = (ROOT / ".github/workflows/node-minimum-compatibility.yml").read_text( + encoding="utf-8" + ) match = re.search( r"(?ms)^ node-minimum-compatibility:\n(?P.*?)(?=^ [a-zA-Z0-9_-]+:\n|\Z)", @@ -66,14 +68,10 @@ def test_minimum_node_lane_runs_complete_suite_with_pinned_npm() -> None: "node-version: 22.22.2", "package-manager-cache: false", f'EXPECTED_NPM_VERSION: "{EXPECTED_NPM_VERSION}"', - 'mkdir -p "$RUNNER_TEMP/corepack-bin"', - 'corepack enable npm --install-directory "$RUNNER_TEMP/corepack-bin"', - "corepack install", - 'echo "$RUNNER_TEMP/corepack-bin" >> "$GITHUB_PATH"', - 'test "$(PATH="$RUNNER_TEMP/corepack-bin:$PATH" npm --version)" = "$EXPECTED_NPM_VERSION"', - "npm install --package-lock-only --ignore-scripts --no-audit --no-fund", - "git diff --exit-code -- package-lock.json", - "npm ci", + "corepack enable npm", + 'test "$(npm --version)" = "$EXPECTED_NPM_VERSION"', + "npm run check:npm-runtime", + "npm ci --ignore-scripts --no-audit --no-fund", "npm run lint", "npm run typecheck", "npm run test", @@ -85,16 +83,16 @@ def test_minimum_node_lane_runs_complete_suite_with_pinned_npm() -> None: for fragment in required_fragments: assert fragment in body, f"minimum-version job is missing: {fragment}" - assert "npm install --global" not in body, ( - "minimum-version workflow must not bootstrap npm through an unpinned npm install command" - ) + for mutable_command in ("npm install ", "npm update ", "npx "): + assert mutable_command not in body, ( + f"minimum-version workflow must not resolve dependencies mutably: {mutable_command.strip()}" + ) setup_node = body.split("- uses: actions/setup-node@", maxsplit=1)[1].split( - "- name: Bootstrap exact repository npm generator", maxsplit=1 + "- name: Activate pinned npm runtime", maxsplit=1 )[0] - assert "cache: npm" not in setup_node, ( - "setup-node must not invoke bundled npm cache discovery before npm 10.9.8 is bootstrapped" - ) + assert "cache: npm" not in setup_node + assert "package-manager-cache: false" in setup_node def test_repository_no_longer_advertises_node_22_13_floor() -> None: @@ -110,9 +108,5 @@ def test_repository_no_longer_advertises_node_22_13_floor() -> None: "docs/operations/deploy-runbook.md", ) - stale = [ - path - for path in audited_paths - if "22.13" in (ROOT / path).read_text(encoding="utf-8") - ] + stale = [path for path in audited_paths if "22.13" in (ROOT / path).read_text(encoding="utf-8")] assert stale == [] diff --git a/services/analysis-engine/tests/test_npm_toolchain_contract.py b/services/analysis-engine/tests/test_npm_toolchain_contract.py index f50c9adef..a5adebfce 100644 --- a/services/analysis-engine/tests/test_npm_toolchain_contract.py +++ b/services/analysis-engine/tests/test_npm_toolchain_contract.py @@ -120,7 +120,7 @@ def test_root_manifest_pins_the_lockfile_generator_and_fails_on_drift() -> None: manifest = _root_manifest() assert manifest["packageManager"] == f"npm@{_EXPECTED_NPM_VERSION}" - assert manifest["engines"] == {"node": ">=22.13 <23"} + assert manifest["engines"] == {"node": ">=22.22.2 <23"} assert manifest["devEngines"] == { "packageManager": { "name": "npm", @@ -243,9 +243,7 @@ def test_npm_consuming_workflows_activate_pinned_runtime_before_dependency_reads assert len(setup_node_steps) == 1, f"{workflow_name}:{job_name} setup-node ownership" setup_options = setup_node_steps[0].get("with") assert isinstance(setup_options, dict) - assert "cache" not in setup_options, ( - f"{workflow_name}:{job_name} pre-Corepack npm cache" - ) + assert "cache" not in setup_options, f"{workflow_name}:{job_name} pre-Corepack npm cache" assert setup_options.get("package-manager-cache") is False, ( f"{workflow_name}:{job_name} must disable setup-node package-manager cache" ) From 8e6a73879dac8aebd70f77aa80d55fc9c670a8f5 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 16:20:46 -0700 Subject: [PATCH 087/161] docs(node): align npm provenance with raised runtime floor --- .../npm-lockfile-generator-provenance.md | 99 ++++++++++--------- 1 file changed, 52 insertions(+), 47 deletions(-) diff --git a/docs/doctoring/npm-lockfile-generator-provenance.md b/docs/doctoring/npm-lockfile-generator-provenance.md index 72a31befc..4bead33d7 100644 --- a/docs/doctoring/npm-lockfile-generator-provenance.md +++ b/docs/doctoring/npm-lockfile-generator-provenance.md @@ -2,34 +2,27 @@ ## Decision -BandScope records npm `10.9.9` as the approved generator for root workspace dependency updates. The root manifest records that decision through: +BandScope records npm `10.9.9` as the approved generator for root workspace dependency updates. The root manifest records that decision through `packageManager: npm@10.9.9` and `devEngines.packageManager` with `onFail: error`. npm is intentionally not repeated under runtime `engines`; the package-manager generator and the supported Node runtime are separate contracts. -- `packageManager: npm@10.9.9` as package-manager selection metadata; and -- `devEngines.packageManager` with `onFail: error` as npm's source-tree command gate. +The #779/jsdom 30 compatibility slice raises the supported Node 22 interval to `>=22.22.2 <23`. Primary CI continues to exercise Node `22.22.3`, while `.github/workflows/node-minimum-compatibility.yml` exercises the exact `22.22.2` lower boundary. Both lanes activate the repository-pinned npm runtime with Corepack, verify npm `10.9.9` and its bundled `tar` security floor before dependency extraction, and consume only the committed lock with frozen `npm ci`. -The npm version is intentionally not repeated under `engines`. npm serializes `engines` into the root lock package, so adding an npm-only source-tool constraint there creates lock metadata churn unrelated to dependency resolution. `devEngines` and the explicit CI assertion enforce the approved generator while the published `engines.node` range remains the runtime compatibility contract. +The current #779 branch is intentionally fail-closed until `package-lock.json` is regenerated as one complete artifact with the approved npm `10.9.9` generator after the jsdom 30 manifest change. The lock must not be hand-edited or partially transplanted from predecessor dependency PRs. Until the generated lock carries the Node floor and jsdom 30 graph and passes exact-head frozen consumption, the branch is not mergeable. -Primary CI does **not** regenerate or update `package-lock.json`. It uses Node `22.22.3`, enables the npm shim supplied by the Node-bundled Corepack, resolves the project-pinned npm `10.9.9`, verifies that exact npm runtime and its own bundled `tar` package before dependency consumption, and validates the committed lock with `npm ci --ignore-scripts --no-audit --no-fund`. The gate then rejects any manifest or lockfile working-tree change. The normal verification jobs repeat the same runtime provenance gate before the repository's reviewed `npm ci` installation. +## Why npm 10.9.9 is authoritative -The Node runtime support decision remains separate. This change does not raise the public `>=22.13 <23` Node range; a coordinated Node-floor migration is tracked independently. +The prior approved npm `10.9.8` bundled `tar 7.5.11`. GitHub advisory GHSA-23hp-3jrh-7fpw / CVE-2026-59873 records `tar <=7.5.18` as affected by an unbounded decompression/parse denial-of-service vulnerability and `7.5.19` as the patched floor. npm `10.9.9` bundles `tar 7.5.22`. -## Why the npm runtime was advanced - -The prior approved npm `10.9.8` bundled `tar 7.5.11`. GitHub's reviewed advisory GHSA-23hp-3jrh-7fpw / CVE-2026-59873 marks `tar <=7.5.18` affected by an unbounded decompression/parse denial-of-service vulnerability and records `7.5.19` as the patched floor. npm `10.9.9` updates its bundled `tar` to `7.5.22`. - -The Node 22 distribution line still bundled npm `10.9.8` when this repair was made, so merely advancing the Node 22 patch selector did not remove the vulnerable package-manager runtime. BandScope therefore keeps the supported Node 22 contract and activates the repository-pinned npm `10.9.9` through bundled Corepack before any `npm ci` step. `scripts/checks/verify_npm_runtime.mjs`, executed through that npm runtime, locates the running npm package via `npm_execpath`, verifies npm `10.9.9`, reads npm's own `node_modules/tar/package.json`, and rejects a tar version below `7.5.19` before dependency extraction is allowed. +Node `22.22.2` and `22.22.3` ship an older bundled npm, so merely selecting the Node patch release is not sufficient. BandScope enables the project-pinned npm shim before repository-scoped npm dependency consumption. `scripts/checks/verify_npm_runtime.mjs`, executed by that selected npm runtime, verifies npm `10.9.9` and rejects a bundled `tar` below `7.5.19`. This is a package-manager execution boundary, not an application dependency override. BandScope does not add `tar` to the application graph or suppress the advisory. -## Why generator provenance still matters +## Why generator provenance matters -npm documents `package-lock.json` as the location-keyed description of the exact dependency tree. Lockfile version 3 is intended for npm 9 and newer. npm also notes that package-manager versions and tree-shaping configuration can affect the generated dependency graph and metadata. Dependency updates therefore use the reviewed npm `10.9.9` toolchain, and reviewers examine the complete generated lock diff together with its manifest change. +npm documents `package-lock.json` as the location-keyed description of the exact dependency tree. Package-manager versions and tree-shaping configuration can affect generated graph and metadata. Dependency changes therefore use the reviewed npm `10.9.9` toolchain and reviewers examine the complete generated lock diff together with manifest intent. -That provenance is distinct from CI validation. `npm ci` is the immutable consumption path: it requires a lockfile, rejects manifest/lock dependency disagreement, removes an existing `node_modules`, and never writes the manifest or lock. CI relies on that frozen behavior instead of running `npm install`, `npm update`, or `npx` commands that may perform mutable resolution. +CI validation is deliberately different from generation. `npm ci` requires a lockfile, rejects manifest/lock disagreement, removes an existing `node_modules`, and never rewrites the manifest or lock. Primary CI and the exact-minimum Node lane use this immutable path; they do not run `npm install`, `npm update`, `npx`, or another mutable resolution command to make a stale lock appear green. -The repository additionally requires a Subresource Integrity value for every package-lock entry resolved from the public npm registry. npm documents `integrity` as the SHA-512 or SHA-1 SRI string for the artifact unpacked at that location. - -The root lock also retains `peer: true` on the platform-specific `node_modules/@esbuild/*` records produced by the approved tree. Multiple dependency-update branches generated with a different serialization path were observed removing those markers even when the requested package change was unrelated to esbuild. Because frozen `npm ci` consumes rather than regenerates the lock, ordinary frozen-install validation alone cannot prove that this generator-sensitive metadata was preserved. The repository therefore treats those markers as a regression sentinel: a dependency PR that strips them must be regenerated with the approved npm toolchain rather than normalizing the unrelated churn by hand. +Every package-lock entry resolved from the public npm registry must retain Subresource Integrity evidence. The root lock also retains `peer: true` on platform-specific `node_modules/@esbuild/*` records. Loss of those markers is treated as generator drift and requires regeneration with the approved toolchain rather than manual normalization. ```mermaid flowchart LR @@ -37,63 +30,75 @@ flowchart LR C --> R[verify npm 10.9.9 and bundled tar >= 7.5.19] R --> G[approved npm update toolchain] G --> L[reviewed package-lock.json v3] - L --> V[npm ci frozen validation, lifecycle disabled] + L --> V[npm ci frozen validation] V --> D{manifest or lock drift?} D -->|yes| F[fail closed] D -->|no| S[verify SRI and generator-sensitive metadata] - S --> N[normal npm ci and repository checks] + S --> Q[full product and security gates] ``` +## Exact-minimum Node verification + +jsdom `30.0.1` declares a Node floor compatible with Node `22.22.2`. BandScope intentionally stays on the Node 22 line for this migration, so the repository contract is `>=22.22.2 <23` rather than an implicit expansion to Node 24 or 26. + +The dedicated minimum-runtime workflow must: + +1. check out without persisted credentials; +2. install exact Node `22.22.2` with package-manager cache discovery disabled; +3. run `corepack enable npm` before repository dependency consumption; +4. verify exact npm `10.9.9` and bundled `tar >=7.5.19` through `check:npm-runtime`; +5. run frozen `npm ci --ignore-scripts --no-audit --no-fund`; and +6. run lint, strict typecheck, measured tests, production build, Storybook, and locked Tauri check/test. + +The ordering is security-significant: setup-node must not invoke npm cache discovery through the Node-bundled npm before the reviewed project npm runtime is authoritative. + ## Security and operational boundary - Every primary CI job that consumes npm dependencies activates the project-pinned npm runtime and runs `check:npm-runtime` before its first `npm ci`. -- The runtime check fails closed unless the executing npm is exactly `10.9.9` and its own bundled `tar` is at least `7.5.19`. -- CI lock validation must not run `npm install`, `npm update`, `npx`, or another mutable dependency-resolution command. -- Dependency PRs change manifest intent and the complete lock artifact produced by the approved npm `10.9.9` update toolchain; reviewers reject unexplained lock churn rather than hand-editing records. -- Platform-specific root `@esbuild/*` lock records must retain their expected `peer: true` metadata. Missing markers are treated as generator drift, not as an acceptable side effect of an unrelated dependency update. -- The lock-validation job disables dependency lifecycle scripts. The normal clean install retains the repository's reviewed execution behavior. -- Registry-resolved package records require SRI evidence in the committed lock. -- Install-shaping flags that affect the dependency tree, such as `legacy-peer-deps` or `install-links`, must be committed in project configuration and applied consistently to generation and `npm ci`. -- The root `package-lock.json` remains the sole npm workspace lock. Nested workspace locks are prohibited. - -`packageManager` alone is not the enforcement boundary for npm because Node distributions do not enable Corepack's npm shim by default. Enforcement is provided by explicit `corepack enable npm`, npm `devEngines`, the exact runtime/tar provenance check, the frozen `npm ci` contract, and repository tests that prohibit mutable resolution in the lock gate. +- The runtime check fails closed unless npm is exactly `10.9.9` and its own bundled `tar` is at least `7.5.19`. +- CI lock validation and the exact-minimum lane must not run mutable npm resolution. +- Dependency PRs change manifest intent and the complete lock artifact produced by npm `10.9.9`; unexplained lock churn is rejected rather than hand-edited. +- Registry-resolved lock records require SRI evidence, and root `@esbuild/*` platform records retain expected peer metadata. +- Checkout credentials are not persisted in npm-consuming CI jobs. +- Install-shaping flags that affect the tree must be committed and applied consistently to generation and frozen consumption. +- The root `package-lock.json` remains the sole npm workspace lock; nested workspace locks are prohibited. ## Verification -`services/analysis-engine/tests/test_npm_toolchain_contract.py` verifies: - -1. the manifest's approved npm metadata and Node/runtime separation; -2. the exact Node/npm identity used by primary CI; -3. Corepack activation and npm runtime/tar verification before every primary npm dependency-consumption step; -4. frozen `npm ci` lock validation with lifecycle execution disabled; -5. absence of `npm install`, `npm update`, and `npx` from the lock-validation job; -6. a clean manifest/lock working tree after validation; -7. package-lock version 3; -8. SRI evidence for every public npm-registry artifact in the root lock; and -9. preservation of `peer: true` on every root `node_modules/@esbuild/*` platform record. +`services/analysis-engine/tests/test_npm_toolchain_contract.py` verifies the npm generator metadata, Node/npm identity in primary CI, Corepack/runtime-audit ordering, credential-free checkouts, immutable lock validation, lockfile version 3, SRI evidence, and generator-sensitive esbuild peer metadata. -The exact PDF.js and Undici baseline is covered separately by `test_high_security_dependency_baseline.py` and the desktop PDF loader tests. +`services/analysis-engine/tests/test_node_runtime_contract.py` separately verifies the `>=22.22.2 <23` interval, explicit rejection of Node `22.22.1`, jsdom 30 manifest/lock alignment, the exact-minimum workflow, npm runtime verification before dependency reads, the full compatibility acceptance surface, and removal of the superseded Node floor from canonical runtime/build documentation. -A dependency update is mergeable only after the updated manifest and complete generated lock are reviewed together and the exact current head passes npm runtime provenance, frozen lock validation, normal install, lint, strict typecheck, measured tests, production build, Rust/Tauri checks, security/supply-chain gates, current review, independent approval, and branch protection without bypass. +The PDF.js and Undici baseline remains covered separately by `test_high_security_dependency_baseline.py` and desktop PDF-loader tests. ## Claim boundary -CI proves that the committed manifest and lock can be consumed as a frozen pair by the approved toolchain, that the npm runtime used for dependency extraction is the reviewed version with a non-vulnerable bundled tar floor, that public-registry lock entries carry integrity evidence, and that the known generator-sensitive `@esbuild/*` peer markers remain present. It does **not** claim that resolving mutable manifest ranges again at a later time will reproduce byte-identical lock metadata. When a dependency update is needed, npm `10.9.9` remains the approved generator and its entire resulting lock diff is review evidence. +Passing frozen validation proves only that the committed manifest and lock can be consumed together by the reviewed toolchain and that the package-manager extraction runtime satisfies the pinned security floor. It does not prove that resolving mutable dependency ranges later will reproduce byte-identical lock metadata. + +Likewise, the exact-minimum lane proves only BandScope's selected Node 22 lower boundary. It does not broaden support to other Node major lines because upstream jsdom supports them. + +For the active #779 branch, no success claim is valid until the complete jsdom 30 lock is generated by npm `10.9.9`, reviewed as a whole, and all required exact-head CI, security, supply-chain, coverage, build, release, and independent-review gates pass on an unchanged head. ## Incident response and rollback When an update produces unexpected lock churn or npm runtime provenance fails: 1. preserve the exact head SHA, npm, bundled tar and Node versions, project npm configuration, original lock blob SHA, generated lock, and relevant CI run IDs; -2. determine whether manifest intent, npm, project configuration, registry metadata, transitive dependency resolution, or the package-manager runtime changed; -3. never accept a partial or hand-edited lock or disable the runtime check to satisfy a validator; -4. regenerate the complete lock in a dedicated update branch using the reviewed npm version, then review the full diff before relying on it; and -5. if rollback is necessary, restore the prior manifest and complete lock together, then rerun the entire exact-head gate. Do not roll back to a package-manager runtime with a known unfixed extraction vulnerability without an explicit temporary security exception. +2. determine whether manifest intent, npm, project configuration, registry metadata, transitive resolution, or package-manager runtime changed; +3. never accept a partial/hand-edited lock or disable the runtime check; +4. regenerate the complete lock in the canonical dependency branch using npm `10.9.9`, then review the full diff before relying on it; and +5. if rollback is necessary, restore the prior manifest and complete lock together and rerun the entire exact-head gate. + +For an exact-minimum runtime failure, preserve setup-node details, bundled npm identity, first npm invocation, Corepack activation, and exact workflow job log. Do not weaken `devEngines`; repair ordering so the reviewed npm runtime is authoritative before dependency consumption. ## References GitHub. (2026). *node-tar: Decompression/parse DoS via unlimited input* (GHSA-23hp-3jrh-7fpw; CVE-2026-59873) [Security advisory]. https://github.com/advisories/GHSA-23hp-3jrh-7fpw +jsdom contributors. (2026). *jsdom 30.0.1 package manifest* [Source code]. GitHub. https://github.com/jsdom/jsdom/blob/v30.0.1/package.json + +Node.js contributors. (2026). *Node.js v22.22.2 bundled npm package manifest* [Source code]. GitHub. https://github.com/nodejs/node/blob/v22.22.2/deps/npm/package.json + Node.js contributors. (2026). *Corepack* [Software documentation]. GitHub. https://github.com/nodejs/corepack npm, Inc. (2026). *npm 10.9.9* [Software release]. GitHub. https://github.com/npm/cli/releases/tag/v10.9.9 From 32bbb8ba067ace3365de8ce04a79949457aa7e33 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Sep 2026 04:07:54 +0000 Subject: [PATCH 088/161] build(deps-dev): bump eslint from 10.8.1 to 10.9.1 Bumps [eslint](https://github.com/eslint/eslint) from 10.8.1 to 10.9.1. - [Release notes](https://github.com/eslint/eslint/releases) - [Commits](https://github.com/eslint/eslint/compare/v10.8.1...v10.9.1) --- updated-dependencies: - dependency-name: eslint dependency-version: 10.9.1 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- apps/desktop/package.json | 2 +- package-lock.json | 36 +++++------------------------- packages/shared-types/package.json | 2 +- 3 files changed, 7 insertions(+), 33 deletions(-) diff --git a/apps/desktop/package.json b/apps/desktop/package.json index e09719b22..cc7c8fb8a 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -38,7 +38,7 @@ "@types/react-dom": "^19.2.3", "@vitejs/plugin-react": "^6.0.2", "@vitest/coverage-v8": "^4.1.10", - "eslint": "^10.7.0", + "eslint": "^10.9.1", "jsdom": "^29.1.1", "storybook": "^10.4.6", "tailwindcss": "^4.2.4", diff --git a/package-lock.json b/package-lock.json index 1b2ceef69..a99f0b997 100644 --- a/package-lock.json +++ b/package-lock.json @@ -51,7 +51,7 @@ "@types/react-dom": "^19.2.3", "@vitejs/plugin-react": "^6.0.2", "@vitest/coverage-v8": "^4.1.10", - "eslint": "^10.7.0", + "eslint": "^10.9.1", "jsdom": "^29.1.1", "storybook": "^10.4.6", "tailwindcss": "^4.2.4", @@ -703,7 +703,6 @@ "os": [ "aix" ], - "peer": true, "engines": { "node": ">=18" } @@ -721,7 +720,6 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">=18" } @@ -739,7 +737,6 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">=18" } @@ -757,7 +754,6 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">=18" } @@ -775,7 +771,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">=18" } @@ -793,7 +788,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">=18" } @@ -811,7 +805,6 @@ "os": [ "freebsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -829,7 +822,6 @@ "os": [ "freebsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -847,7 +839,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -865,7 +856,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -883,7 +873,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -901,7 +890,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -919,7 +907,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -937,7 +924,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -955,7 +941,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -973,7 +958,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -991,7 +975,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -1009,7 +992,6 @@ "os": [ "netbsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -1027,7 +1009,6 @@ "os": [ "netbsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -1045,7 +1026,6 @@ "os": [ "openbsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -1063,7 +1043,6 @@ "os": [ "openbsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -1081,7 +1060,6 @@ "os": [ "openharmony" ], - "peer": true, "engines": { "node": ">=18" } @@ -1099,7 +1077,6 @@ "os": [ "sunos" ], - "peer": true, "engines": { "node": ">=18" } @@ -1117,7 +1094,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">=18" } @@ -1135,7 +1111,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">=18" } @@ -1153,7 +1128,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">=18" } @@ -4698,9 +4672,9 @@ } }, "node_modules/eslint": { - "version": "10.8.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.8.1.tgz", - "integrity": "sha512-wqA7W2jbsC/BnV9Iv1UZpKVFkO1AdNoSmYW8NWG4HNOBbkAMvIqDZ27pI2f07dqn583NcIC44ckjAcOXDL1QbQ==", + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.9.1.tgz", + "integrity": "sha512-9VaAkDURekixUQJy0oJYl2DcN6oKMfxay7XzaGYAWQwsb6qfKf+x76R2k1L8kb1boc+FyCAaTA9GmiKaaiaF+A==", "dev": true, "license": "MIT", "workspaces": [ @@ -7729,7 +7703,7 @@ "devDependencies": { "@types/node": "^26.1.1", "@vitest/coverage-v8": "^4.1.10", - "eslint": "^10.7.0", + "eslint": "^10.9.1", "fast-check": "^4.8.0", "typescript": "^6.0.3", "typescript-eslint": "^8.63.0", diff --git a/packages/shared-types/package.json b/packages/shared-types/package.json index f03474284..44b604e60 100644 --- a/packages/shared-types/package.json +++ b/packages/shared-types/package.json @@ -11,7 +11,7 @@ "devDependencies": { "@types/node": "^26.1.1", "@vitest/coverage-v8": "^4.1.10", - "eslint": "^10.7.0", + "eslint": "^10.9.1", "fast-check": "^4.8.0", "typescript": "^6.0.3", "typescript-eslint": "^8.63.0", From f52dc9738f48001acf88384a422db206b26bc727 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Sun, 6 Sep 2026 04:00:23 +0000 Subject: [PATCH 089/161] =?UTF-8?q?=E2=9A=A1=20Bolt:=20[performance=20impr?= =?UTF-8?q?ovement]=20Replace=20O(N^2)=20list=20membership=20checks=20with?= =?UTF-8?q?=20O(1)=20dictionary=20key=20deduplication=20in=20chart=20expor?= =?UTF-8?q?t?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .jules/bolt.md | 3 ++ .../src/bandscope_analysis/exports/chart.py | 31 +++++++++---------- .../tests/test_supply_chain_policy.py | 4 +-- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/.jules/bolt.md b/.jules/bolt.md index d54cf10fc..c0f9a50a7 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -61,3 +61,6 @@ ## 2026-07-13 - Array.from mapping optimization **Learning:** Using `Array.from({ length: N }).map(...)` creates an intermediate array of `undefined` values which requires memory allocation and garbage collection, adding O(N) unnecessary overhead in frequently re-rendered UI components. **Action:** Use `Array.from({ length: N }, (_, index) => ...)` to map elements directly during array creation, avoiding intermediate allocations. +## 2025-02-23 - Python O(N²) List Deduplication Anti-Pattern +**Learning:** Checking list membership (`if item not in lst: lst.append(item)`) inside loops creates a hidden O(N²) algorithmic bottleneck because `not in` on a list requires an O(N) scan. This can become a significant performance issue when analyzing songs with many sections or complex cue roles (e.g., in `chart.py` for chart export). +**Action:** When deduplicating strings or primitive items while preserving order in Python 3.7+, use dictionary key assignment (`dict_obj[item] = None`) inside the loop, and return `list(dict_obj.keys())` at the end. This reduces the complexity to O(N) by utilizing O(1) hashing for membership checks, without sacrificing readability. diff --git a/services/analysis-engine/src/bandscope_analysis/exports/chart.py b/services/analysis-engine/src/bandscope_analysis/exports/chart.py index 3a84b59c8..9a2a6da40 100644 --- a/services/analysis-engine/src/bandscope_analysis/exports/chart.py +++ b/services/analysis-engine/src/bandscope_analysis/exports/chart.py @@ -78,14 +78,14 @@ def _active_role_ids(section: Mapping[str, object]) -> list[str] | None: part_graph = section.get("partGraph") if not isinstance(part_graph, list): return None - active: list[str] = [] + active: dict[str, None] = {} for node in part_graph: if not isinstance(node, Mapping) or node.get("is_active") is not True: continue role_id = node.get("role_id") - if isinstance(role_id, str) and role_id and role_id not in active: - active.append(role_id) - return active + if isinstance(role_id, str) and role_id: + active[role_id] = None + return list(active.keys()) def _active_roles(section: Mapping[str, object]) -> list[Mapping[str, object]]: @@ -121,25 +121,25 @@ def _role_display_name(role: Mapping[str, object]) -> str | None: def _active_role_names(section: Mapping[str, object]) -> list[str]: """Return de-duplicated display names for the section's active roles.""" - names: list[str] = [] + names: dict[str, None] = {} for role in _active_roles(section): name = _role_display_name(role) - if name is not None and name not in names: - names.append(name) - return names + if name is not None: + names[name] = None + return list(names.keys()) def _section_cue(section: Mapping[str, object]) -> str: """Join the active roles' cue values into a single cue string.""" - cues: list[str] = [] + cues: dict[str, None] = {} for role in _active_roles(section): cue = role.get("cue") if not isinstance(cue, Mapping): continue value = cue.get("value") - if isinstance(value, str) and value and value not in cues: - cues.append(value) - return "; ".join(cues) + if isinstance(value, str) and value: + cues[value] = None + return "; ".join(cues.keys()) def _confidence_level(section: Mapping[str, object]) -> str | None: @@ -188,7 +188,7 @@ def _section_lines(sections: list[Mapping[str, object]]) -> list[str]: def _footer_lines(song: Mapping[str, object], sections: list[Mapping[str, object]]) -> list[str]: """Build the footer: per-role rehearsal priorities and the export focus.""" lines: list[str] = [] - priorities: list[str] = [] + priorities: dict[str, None] = {} for section in sections: for role in _section_roles(section): name = _role_display_name(role) @@ -196,11 +196,10 @@ def _footer_lines(song: Mapping[str, object], sections: list[Mapping[str, object if name is None or not isinstance(priority, str) or not priority: continue entry = f" - {name}: {priority}" - if entry not in priorities: - priorities.append(entry) + priorities[entry] = None if priorities: lines.append("Priorities:") - lines.extend(priorities) + lines.extend(priorities.keys()) summary = song.get("exportSummary") if isinstance(summary, Mapping): headline = summary.get("headline") diff --git a/services/analysis-engine/tests/test_supply_chain_policy.py b/services/analysis-engine/tests/test_supply_chain_policy.py index 1d8224c5a..6a0853944 100644 --- a/services/analysis-engine/tests/test_supply_chain_policy.py +++ b/services/analysis-engine/tests/test_supply_chain_policy.py @@ -1275,9 +1275,7 @@ def test_workflow_concurrency_cancels_only_superseded_pr_heads() -> None: workflow = (workflows_dir / workflow_name).read_text(encoding="utf-8") assert "concurrency:" in workflow, workflow_name assert "cancel-in-progress: false" in workflow, workflow_name - assert "contents: read" in workflow or "permissions: read-all" in workflow, ( - workflow_name - ) + assert "contents: read" in workflow or "permissions: read-all" in workflow, workflow_name assert "pull_request:" not in (workflows_dir / "release.yml").read_text(encoding="utf-8") From 0725eb3ce0d5b416464566422839ff61c17b839e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 7 Sep 2026 07:05:17 +0900 Subject: [PATCH 090/161] repair(ci): drop superseded chart note from Ruff owner --- .jules/bolt.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/.jules/bolt.md b/.jules/bolt.md index c0f9a50a7..d54cf10fc 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -61,6 +61,3 @@ ## 2026-07-13 - Array.from mapping optimization **Learning:** Using `Array.from({ length: N }).map(...)` creates an intermediate array of `undefined` values which requires memory allocation and garbage collection, adding O(N) unnecessary overhead in frequently re-rendered UI components. **Action:** Use `Array.from({ length: N }, (_, index) => ...)` to map elements directly during array creation, avoiding intermediate allocations. -## 2025-02-23 - Python O(N²) List Deduplication Anti-Pattern -**Learning:** Checking list membership (`if item not in lst: lst.append(item)`) inside loops creates a hidden O(N²) algorithmic bottleneck because `not in` on a list requires an O(N) scan. This can become a significant performance issue when analyzing songs with many sections or complex cue roles (e.g., in `chart.py` for chart export). -**Action:** When deduplicating strings or primitive items while preserving order in Python 3.7+, use dictionary key assignment (`dict_obj[item] = None`) inside the loop, and return `list(dict_obj.keys())` at the end. This reduces the complexity to O(N) by utilizing O(1) hashing for membership checks, without sacrificing readability. From 1d38e8e62d66ebe6ad14df044cb29ede8e4ea7a6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 7 Sep 2026 07:05:46 +0900 Subject: [PATCH 091/161] repair(ci): return chart optimization to canonical owner --- .../src/bandscope_analysis/exports/chart.py | 35 ++++++++++--------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/services/analysis-engine/src/bandscope_analysis/exports/chart.py b/services/analysis-engine/src/bandscope_analysis/exports/chart.py index 9a2a6da40..44e92005b 100644 --- a/services/analysis-engine/src/bandscope_analysis/exports/chart.py +++ b/services/analysis-engine/src/bandscope_analysis/exports/chart.py @@ -7,7 +7,7 @@ Security Notes: - Pure dict-to-string transformation: no file, network, or process I/O. - Never reads source-path fields and never emits filesystem paths. - - Safe failure: ``None``, empty, or malformed input yields ``""`` / ``[]``; + - Safe failure: ``None``, empty, or malformed input yields ``\"\"`` / ``[]``; missing or malformed keys are skipped and no exceptions escape. """ @@ -78,14 +78,14 @@ def _active_role_ids(section: Mapping[str, object]) -> list[str] | None: part_graph = section.get("partGraph") if not isinstance(part_graph, list): return None - active: dict[str, None] = {} + active: list[str] = [] for node in part_graph: if not isinstance(node, Mapping) or node.get("is_active") is not True: continue role_id = node.get("role_id") - if isinstance(role_id, str) and role_id: - active[role_id] = None - return list(active.keys()) + if isinstance(role_id, str) and role_id and role_id not in active: + active.append(role_id) + return active def _active_roles(section: Mapping[str, object]) -> list[Mapping[str, object]]: @@ -121,25 +121,25 @@ def _role_display_name(role: Mapping[str, object]) -> str | None: def _active_role_names(section: Mapping[str, object]) -> list[str]: """Return de-duplicated display names for the section's active roles.""" - names: dict[str, None] = {} + names: list[str] = [] for role in _active_roles(section): name = _role_display_name(role) - if name is not None: - names[name] = None - return list(names.keys()) + if name is not None and name not in names: + names.append(name) + return names def _section_cue(section: Mapping[str, object]) -> str: """Join the active roles' cue values into a single cue string.""" - cues: dict[str, None] = {} + cues: list[str] = [] for role in _active_roles(section): cue = role.get("cue") if not isinstance(cue, Mapping): continue value = cue.get("value") - if isinstance(value, str) and value: - cues[value] = None - return "; ".join(cues.keys()) + if isinstance(value, str) and value and value not in cues: + cues.append(value) + return "; ".join(cues) def _confidence_level(section: Mapping[str, object]) -> str | None: @@ -188,7 +188,7 @@ def _section_lines(sections: list[Mapping[str, object]]) -> list[str]: def _footer_lines(song: Mapping[str, object], sections: list[Mapping[str, object]]) -> list[str]: """Build the footer: per-role rehearsal priorities and the export focus.""" lines: list[str] = [] - priorities: dict[str, None] = {} + priorities: list[str] = [] for section in sections: for role in _section_roles(section): name = _role_display_name(role) @@ -196,10 +196,11 @@ def _footer_lines(song: Mapping[str, object], sections: list[Mapping[str, object if name is None or not isinstance(priority, str) or not priority: continue entry = f" - {name}: {priority}" - priorities[entry] = None + if entry not in priorities: + priorities.append(entry) if priorities: lines.append("Priorities:") - lines.extend(priorities.keys()) + lines.extend(priorities) summary = song.get("exportSummary") if isinstance(summary, Mapping): headline = summary.get("headline") @@ -215,7 +216,7 @@ def build_chart_text(song: Mapping[str, object] | None) -> str: section (``[mm:ss-mm:ss] LABEL (confidence) roles: ...``), and a footer with rehearsal priorities and the export focus headline. Output is deterministic and never contains filesystem paths. Malformed input - yields ``""``. + yields ``\"\"``. """ if not isinstance(song, Mapping): return "" From a7b0030a3a6cc6296a19ba3f8eaf595d470d05bd Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 7 Sep 2026 07:06:39 +0900 Subject: [PATCH 092/161] repair(ci): restore protected chart bytes exactly --- .../analysis-engine/src/bandscope_analysis/exports/chart.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/analysis-engine/src/bandscope_analysis/exports/chart.py b/services/analysis-engine/src/bandscope_analysis/exports/chart.py index 44e92005b..3a84b59c8 100644 --- a/services/analysis-engine/src/bandscope_analysis/exports/chart.py +++ b/services/analysis-engine/src/bandscope_analysis/exports/chart.py @@ -7,7 +7,7 @@ Security Notes: - Pure dict-to-string transformation: no file, network, or process I/O. - Never reads source-path fields and never emits filesystem paths. - - Safe failure: ``None``, empty, or malformed input yields ``\"\"`` / ``[]``; + - Safe failure: ``None``, empty, or malformed input yields ``""`` / ``[]``; missing or malformed keys are skipped and no exceptions escape. """ @@ -216,7 +216,7 @@ def build_chart_text(song: Mapping[str, object] | None) -> str: section (``[mm:ss-mm:ss] LABEL (confidence) roles: ...``), and a footer with rehearsal priorities and the export focus headline. Output is deterministic and never contains filesystem paths. Malformed input - yields ``\"\"``. + yields ``""``. """ if not isinstance(song, Mapping): return "" From 340b0a343ecfc05f630c7da729b8af40c7da4a2c Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Sun, 6 Sep 2026 22:47:54 +0000 Subject: [PATCH 093/161] Trigger CI retry From 8488a02a1b36a99c94b3e248e948d754ed446750 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Sun, 6 Sep 2026 23:45:35 +0000 Subject: [PATCH 094/161] Trigger CI retry From 8fe6b6d99c009527ef0bcba419e6f6debdb23c23 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Mon, 7 Sep 2026 01:15:16 +0000 Subject: [PATCH 095/161] Trigger CI retry From d0738d724125f9c09170fb3a6f8595d28bb54dce Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 18 Sep 2026 08:04:14 +0900 Subject: [PATCH 096/161] test(node): require bounded fail-closed npm activation --- .../test_npm_runtime_activation_resilience.py | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 services/analysis-engine/tests/test_npm_runtime_activation_resilience.py diff --git a/services/analysis-engine/tests/test_npm_runtime_activation_resilience.py b/services/analysis-engine/tests/test_npm_runtime_activation_resilience.py new file mode 100644 index 000000000..a9f4218ff --- /dev/null +++ b/services/analysis-engine/tests/test_npm_runtime_activation_resilience.py @@ -0,0 +1,67 @@ +"""Regression contracts for fail-closed npm runtime activation in hosted builds.""" + +from __future__ import annotations + +from pathlib import Path + +import yaml + +_REPOSITORY_ROOT = Path(__file__).resolve().parents[3] +_BUILD_BASELINE = _REPOSITORY_ROOT / ".github" / "workflows" / "build-baseline.yml" +_ACTIVATION_HELPER = _REPOSITORY_ROOT / "scripts" / "checks" / "activate_pinned_npm_runtime.sh" +_ACTIVATION_COMMAND = "bash scripts/checks/activate_pinned_npm_runtime.sh" + + +def _job_steps(job: object) -> list[dict[str, object]]: + """Return structurally parsed workflow steps for one job.""" + assert isinstance(job, dict) + steps = job.get("steps") + assert isinstance(steps, list) + parsed: list[dict[str, object]] = [] + for step in steps: + assert isinstance(step, dict) + parsed.append(step) + return parsed + + +def test_build_baseline_uses_retrying_pinned_npm_activation_before_dependency_reads() -> None: + """Require every native build lane to acquire the reviewed npm runtime through one helper.""" + document = yaml.safe_load(_BUILD_BASELINE.read_text(encoding="utf-8")) + assert isinstance(document, dict) + jobs = document.get("jobs") + assert isinstance(jobs, dict) + + npm_consumers = 0 + for job_name, job in jobs.items(): + steps = _job_steps(job) + run_steps = [str(step["run"]) for step in steps if isinstance(step.get("run"), str)] + dependency_index = next( + (index for index, command in enumerate(run_steps) if command.strip() == "npm ci"), + None, + ) + if dependency_index is None: + continue + + npm_consumers += 1 + activation_indexes = [ + index for index, command in enumerate(run_steps) if command.strip() == _ACTIVATION_COMMAND + ] + assert activation_indexes == [dependency_index - 1], f"{job_name} activation ownership" + assert all("corepack enable npm" not in command for command in run_steps), ( + f"{job_name} must not perform unbounded inline Corepack activation" + ) + + assert npm_consumers == 4 + + +def test_pinned_npm_activation_helper_retries_acquisition_but_never_falls_back() -> None: + """Keep transient registry recovery bounded while exact npm provenance remains fail closed.""" + source = _ACTIVATION_HELPER.read_text(encoding="utf-8") + + assert 'MAX_ATTEMPTS="3"' in source + assert 'corepack install --global "$package_manager_spec"' in source + assert "corepack enable npm" in source + assert "npm run check:npm-runtime" in source + assert "sleep_seconds=$((attempt * 5))" in source + assert "|| true" not in source + assert "npm@10.9.8" not in source From da536edae014cbe2b491a2d357a5e217d9f9e43f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 18 Sep 2026 08:04:38 +0900 Subject: [PATCH 097/161] fix(node): add bounded pinned npm runtime acquisition --- scripts/checks/activate_pinned_npm_runtime.sh | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 scripts/checks/activate_pinned_npm_runtime.sh diff --git a/scripts/checks/activate_pinned_npm_runtime.sh b/scripts/checks/activate_pinned_npm_runtime.sh new file mode 100644 index 000000000..d989a2315 --- /dev/null +++ b/scripts/checks/activate_pinned_npm_runtime.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +set -euo pipefail + +readonly MAX_ATTEMPTS="3" + +package_manager_spec="$({ + node --input-type=module <<'NODE' +import { readFileSync } from "node:fs"; + +const manifest = JSON.parse(readFileSync("package.json", "utf8")); +if (typeof manifest.packageManager !== "string" || !/^npm@[0-9]+\.[0-9]+\.[0-9]+$/.test(manifest.packageManager)) { + throw new Error("package.json must pin packageManager to an exact npm version"); +} +process.stdout.write(manifest.packageManager); +NODE +} 2>&1)" || { + printf '%s\n' "$package_manager_spec" >&2 + exit 1 +} + +attempt=1 +while ! corepack install --global "$package_manager_spec"; do + if (( attempt >= MAX_ATTEMPTS )); then + echo "Failed to acquire $package_manager_spec after $MAX_ATTEMPTS attempts; refusing an unpinned npm fallback." >&2 + exit 1 + fi + + sleep_seconds=$((attempt * 5)) + echo "Corepack acquisition attempt $attempt failed; retrying exact $package_manager_spec in ${sleep_seconds}s." >&2 + sleep "$sleep_seconds" + attempt=$((attempt + 1)) +done + +corepack enable npm +npm run check:npm-runtime From 05dba7795753c6e5a57cf47cbb38a460de45c448 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 18 Sep 2026 08:05:16 +0900 Subject: [PATCH 098/161] fix(build): retry exact Corepack npm acquisition --- .github/workflows/build-baseline.yml | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/.github/workflows/build-baseline.yml b/.github/workflows/build-baseline.yml index 13de8e648..64c2c851d 100644 --- a/.github/workflows/build-baseline.yml +++ b/.github/workflows/build-baseline.yml @@ -97,9 +97,7 @@ jobs: Write-AntivirusEvidence "Antivirus check: no explicit antivirus telemetry was available on this hosted runner." - name: Activate and verify pinned npm runtime - run: | - corepack enable npm - npm run check:npm-runtime + run: bash scripts/checks/activate_pinned_npm_runtime.sh - name: Install node dependencies run: npm ci - name: Sync Python dependencies @@ -196,9 +194,7 @@ jobs: Write-AntivirusEvidence "Antivirus check: no explicit antivirus telemetry was available on this hosted runner." - name: Activate and verify pinned npm runtime - run: | - corepack enable npm - npm run check:npm-runtime + run: bash scripts/checks/activate_pinned_npm_runtime.sh - name: Install node dependencies run: npm ci - name: Sync Python dependencies @@ -269,9 +265,7 @@ jobs: - name: Install create-dmg run: brew install create-dmg - name: Activate and verify pinned npm runtime - run: | - corepack enable npm - npm run check:npm-runtime + run: bash scripts/checks/activate_pinned_npm_runtime.sh - name: Install node dependencies run: npm ci - name: Sync Python dependencies @@ -333,9 +327,7 @@ jobs: - name: Install create-dmg run: brew install create-dmg - name: Activate and verify pinned npm runtime - run: | - corepack enable npm - npm run check:npm-runtime + run: bash scripts/checks/activate_pinned_npm_runtime.sh - name: Install node dependencies run: npm ci - name: Sync Python dependencies From b780be88e5da1d6436f6edc9e478b994cd228946 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 18 Sep 2026 08:07:00 +0900 Subject: [PATCH 099/161] test(node): align provenance contract with bounded activation helper --- .../tests/test_npm_toolchain_contract.py | 31 +++++++++++++------ 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/services/analysis-engine/tests/test_npm_toolchain_contract.py b/services/analysis-engine/tests/test_npm_toolchain_contract.py index b51135ab4..384ef4d89 100644 --- a/services/analysis-engine/tests/test_npm_toolchain_contract.py +++ b/services/analysis-engine/tests/test_npm_toolchain_contract.py @@ -13,6 +13,7 @@ _EXPECTED_NODE_VERSION = "22.22.3" _MINIMUM_NPM_TAR_VERSION = "7.5.19" _NPM_RUNTIME_CHECK = "node scripts/checks/verify_npm_runtime.mjs" +_NPM_ACTIVATION_COMMAND = "bash scripts/checks/activate_pinned_npm_runtime.sh" def _root_manifest() -> dict[str, object]: @@ -86,32 +87,44 @@ def _assert_no_mutable_npm_commands(steps: list[dict[str, object]]) -> None: def _assert_patched_npm_precedes_dependency_consumption(steps: list[dict[str, object]]) -> None: - """Require Corepack npm activation and runtime audit before the first npm dependency read.""" + """Require reviewed npm activation and audit before the first npm dependency read.""" run_steps = [str(step["run"]) for step in steps if isinstance(step.get("run"), str)] - activation_index = next( - (index for index, command in enumerate(run_steps) if "corepack enable npm" in command), + consumption_index = next( + ( + index + for index, command in enumerate(run_steps) + if re.search(r"(?:^|\n)\s*npm ci(?:\s|$)", command) + ), None, ) - audit_index = next( + assert consumption_index is not None + + helper_index = next( ( index for index, command in enumerate(run_steps) - if "npm run check:npm-runtime" in command + if command.strip() == _NPM_ACTIVATION_COMMAND ), None, ) - consumption_index = next( + if helper_index is not None: + assert helper_index < consumption_index + return + + activation_index = next( + (index for index, command in enumerate(run_steps) if "corepack enable npm" in command), + None, + ) + audit_index = next( ( index for index, command in enumerate(run_steps) - if re.search(r"(?:^|\n)\s*npm ci(?:\s|$)", command) + if "npm run check:npm-runtime" in command ), None, ) - assert activation_index is not None assert audit_index is not None - assert consumption_index is not None assert activation_index <= audit_index < consumption_index From 0a01aeb427a1b23c8a77f6ad80c0384e2ef31e15 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 18 Sep 2026 08:07:42 +0900 Subject: [PATCH 100/161] docs(node): record bounded pinned npm acquisition RCA --- .../npm-lockfile-generator-provenance.md | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/docs/doctoring/npm-lockfile-generator-provenance.md b/docs/doctoring/npm-lockfile-generator-provenance.md index d93d5b414..41b356b55 100644 --- a/docs/doctoring/npm-lockfile-generator-provenance.md +++ b/docs/doctoring/npm-lockfile-generator-provenance.md @@ -52,9 +52,20 @@ The dedicated minimum-runtime workflow must: The ordering is security-significant: setup-node must not invoke npm cache discovery through the Node-bundled npm before the reviewed project npm runtime is authoritative. +## Hosted build acquisition resilience + +Exact npm provenance and network resilience are separate concerns. Node `22.22.3` supplies npm `10.9.8`, so a hosted build must acquire the repository-pinned npm `10.9.9` before `npm ci`; falling back to the Node-bundled npm would violate the reviewed runtime and `tar` security floor. + +A macOS Intel `build-baseline` run on PR #1232 exposed the acquisition boundary: Corepack attempted to download `npm-10.9.9.tgz` from the public npm registry and the HTTPS read timed out before any repository dependency consumption or product build began. Re-running the whole job would hide the architectural weakness and waste all preceding setup work. + +The canonical #896 owner therefore centralizes native-build activation in `scripts/checks/activate_pinned_npm_runtime.sh`. The helper reads the exact `packageManager` spec from `package.json`, rejects anything other than an exact npm semantic version, and performs at most three `corepack install --global` attempts for that same spec with bounded 5-second then 10-second backoff. After acquisition it enables the npm shim and executes the existing runtime audit. Exhausting the attempts fails closed; there is no `10.9.8`, `latest`, `stable`, or system-npm fallback. + +This retry boundary covers only acquisition of the already-reviewed package-manager artifact. It does not retry `npm ci`, mutable resolution, tests, builds, uploads, or arbitrary failed commands, and it does not convert a reproducible integrity/version failure into success. + ## Security and operational boundary - Every primary CI job that consumes npm dependencies activates the project-pinned npm runtime and runs `check:npm-runtime` before its first `npm ci`. +- Native build-baseline jobs acquire that exact runtime through the bounded helper; transient registry reads may retry, but runtime identity and the bundled `tar` floor never relax. - The runtime check fails closed unless npm is exactly `10.9.9` and its own bundled `tar` is at least `7.5.19`. - CI lock validation and the exact-minimum lane must not run mutable npm resolution. - Dependency PRs change manifest intent and the complete lock artifact produced by npm `10.9.9`; unexplained lock churn is rejected rather than hand-edited. @@ -65,7 +76,9 @@ The ordering is security-significant: setup-node must not invoke npm cache disco ## Verification -`services/analysis-engine/tests/test_npm_toolchain_contract.py` verifies the npm generator metadata, Node/npm identity in primary CI, Corepack/runtime-audit ordering, credential-free checkouts, immutable lock validation, lockfile version 3, SRI evidence, and generator-sensitive esbuild peer metadata. +`services/analysis-engine/tests/test_npm_toolchain_contract.py` verifies the npm generator metadata, Node/npm identity in primary CI, Corepack/runtime-audit ordering, credential-free checkouts, immutable lock validation, lockfile version 3, SRI evidence, and generator-sensitive esbuild peer metadata. It accepts either the explicit activation/audit sequence or the canonical activation helper before dependency consumption, while preserving the same runtime provenance requirement. + +`services/analysis-engine/tests/test_npm_runtime_activation_resilience.py` specifically verifies that all four native build-baseline lanes use the canonical helper immediately before `npm ci`, that inline unbounded Corepack activation is absent there, and that the helper has a three-attempt bounded acquisition loop with no npm `10.9.8` or failure-masking fallback. `services/analysis-engine/tests/test_node_runtime_contract.py` separately verifies the `>=22.22.2 <23` interval, explicit rejection of Node `22.22.1`, jsdom 30 manifest/lock alignment, the exact-minimum workflow, npm runtime verification before dependency reads, the full compatibility acceptance surface, and removal of the superseded Node floor from canonical runtime/build documentation. @@ -77,6 +90,8 @@ Passing frozen validation proves only that the committed manifest and lock can b Likewise, the exact-minimum lane proves only BandScope's selected Node 22 lower boundary. It does not broaden support to other Node major lines because upstream jsdom supports them. +Bounded Corepack acquisition proves neither registry availability nor arbitrary network recovery. It only prevents a short-lived fetch interruption from forcing an immediate whole-job failure while preserving exact npm identity; three failed acquisition attempts still stop the job. + For the active compatibility branch, local generation and frozen-consumption evidence does not substitute for required exact-head CI, security, supply-chain, coverage, build, release, and independent-review gates on an unchanged head. ## Incident response and rollback @@ -91,6 +106,8 @@ When an update produces unexpected lock churn or npm runtime provenance fails: For an exact-minimum runtime failure, preserve setup-node details, bundled npm identity, first npm invocation, Corepack activation, and exact workflow job log. Do not weaken `devEngines`; repair ordering so the reviewed npm runtime is authoritative before dependency consumption. +For a transient Corepack registry read failure in native builds, preserve the job log and the exact pinned package-manager spec. The bounded helper may retry only that acquisition. If all attempts fail, keep the gate failed and investigate registry/network health; do not fall back to the Node-bundled npm or manufacture a no-op commit to obtain a fresh run. + ## References GitHub. (2026). *node-tar: Decompression/parse DoS via unlimited input* (GHSA-23hp-3jrh-7fpw; CVE-2026-59873) [Security advisory]. https://github.com/advisories/GHSA-23hp-3jrh-7fpw From fa73542314ea20c33c9d6bc0e9b7a3026bb3d065 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 18 Sep 2026 08:11:04 +0900 Subject: [PATCH 101/161] test(node): execute bounded npm acquisition failure paths --- .../test_npm_runtime_activation_resilience.py | 126 ++++++++++++++++++ 1 file changed, 126 insertions(+) diff --git a/services/analysis-engine/tests/test_npm_runtime_activation_resilience.py b/services/analysis-engine/tests/test_npm_runtime_activation_resilience.py index a9f4218ff..519423979 100644 --- a/services/analysis-engine/tests/test_npm_runtime_activation_resilience.py +++ b/services/analysis-engine/tests/test_npm_runtime_activation_resilience.py @@ -2,8 +2,11 @@ from __future__ import annotations +import os +import subprocess from pathlib import Path +import pytest import yaml _REPOSITORY_ROOT = Path(__file__).resolve().parents[3] @@ -24,6 +27,93 @@ def _job_steps(job: object) -> list[dict[str, object]]: return parsed +def _write_executable(path: Path, content: str) -> None: + """Create one executable fake command for helper control-flow tests.""" + path.write_text(content, encoding="utf-8") + path.chmod(0o755) + + +def _fake_command_environment( + tmp_path: Path, + *, + acquisition_failures: int, +) -> tuple[dict[str, str], Path, Path, Path]: + """Return a PATH-isolated command harness and its evidence files.""" + fake_bin = tmp_path / "bin" + fake_bin.mkdir() + corepack_count = tmp_path / "corepack-count.txt" + sleep_log = tmp_path / "sleep.log" + npm_log = tmp_path / "npm.log" + corepack_enable_log = tmp_path / "corepack-enable.log" + + _write_executable( + fake_bin / "node", + "#!/usr/bin/env bash\ncat >/dev/null\nprintf 'npm@10.9.9'\n", + ) + _write_executable( + fake_bin / "corepack", + """#!/usr/bin/env bash +set -euo pipefail +if [[ "$1" == "install" ]]; then + count=0 + if [[ -f "$BANDSCOPE_TEST_COREPACK_COUNT" ]]; then + count="$(cat "$BANDSCOPE_TEST_COREPACK_COUNT")" + fi + count=$((count + 1)) + printf '%s' "$count" > "$BANDSCOPE_TEST_COREPACK_COUNT" + if (( count <= BANDSCOPE_TEST_ACQUISITION_FAILURES )); then + exit 1 + fi + exit 0 +fi +if [[ "$1" == "enable" ]]; then + printf 'enable %s\n' "${*:2}" >> "$BANDSCOPE_TEST_COREPACK_ENABLE_LOG" + exit 0 +fi +exit 64 +""", + ) + _write_executable( + fake_bin / "sleep", + "#!/usr/bin/env bash\nprintf '%s\\n' \"$1\" >> \"$BANDSCOPE_TEST_SLEEP_LOG\"\n", + ) + _write_executable( + fake_bin / "npm", + "#!/usr/bin/env bash\nprintf '%s\\n' \"$*\" >> \"$BANDSCOPE_TEST_NPM_LOG\"\n", + ) + + environment = os.environ.copy() + environment["PATH"] = f"{fake_bin}{os.pathsep}{environment['PATH']}" + environment["BANDSCOPE_TEST_COREPACK_COUNT"] = str(corepack_count) + environment["BANDSCOPE_TEST_ACQUISITION_FAILURES"] = str(acquisition_failures) + environment["BANDSCOPE_TEST_COREPACK_ENABLE_LOG"] = str(corepack_enable_log) + environment["BANDSCOPE_TEST_SLEEP_LOG"] = str(sleep_log) + environment["BANDSCOPE_TEST_NPM_LOG"] = str(npm_log) + return environment, corepack_count, sleep_log, npm_log + + +def _run_activation_helper(tmp_path: Path, *, acquisition_failures: int) -> tuple[ + subprocess.CompletedProcess[str], + Path, + Path, + Path, +]: + """Execute the real helper against deterministic fake external commands.""" + environment, corepack_count, sleep_log, npm_log = _fake_command_environment( + tmp_path, + acquisition_failures=acquisition_failures, + ) + completed = subprocess.run( + ["bash", str(_ACTIVATION_HELPER)], + cwd=tmp_path, + env=environment, + text=True, + capture_output=True, + check=False, + ) + return completed, corepack_count, sleep_log, npm_log + + def test_build_baseline_uses_retrying_pinned_npm_activation_before_dependency_reads() -> None: """Require every native build lane to acquire the reviewed npm runtime through one helper.""" document = yaml.safe_load(_BUILD_BASELINE.read_text(encoding="utf-8")) @@ -63,5 +153,41 @@ def test_pinned_npm_activation_helper_retries_acquisition_but_never_falls_back() assert "corepack enable npm" in source assert "npm run check:npm-runtime" in source assert "sleep_seconds=$((attempt * 5))" in source + assert "/^npm@[0-9]+\\.[0-9]+\\.[0-9]+$/" in source assert "|| true" not in source assert "npm@10.9.8" not in source + + +@pytest.mark.skipif(os.name == "nt", reason="shell helper is exercised by hosted Windows build lanes") +def test_pinned_npm_activation_recovers_after_two_transient_acquisition_failures( + tmp_path: Path, +) -> None: + """Retry only exact-runtime acquisition, then audit the acquired npm before success.""" + completed, corepack_count, sleep_log, npm_log = _run_activation_helper( + tmp_path, + acquisition_failures=2, + ) + + assert completed.returncode == 0, completed.stderr + assert corepack_count.read_text(encoding="utf-8") == "3" + assert sleep_log.read_text(encoding="utf-8").splitlines() == ["5", "10"] + assert npm_log.read_text(encoding="utf-8").splitlines() == ["run check:npm-runtime"] + assert "retrying exact npm@10.9.9" in completed.stderr + + +@pytest.mark.skipif(os.name == "nt", reason="shell helper is exercised by hosted Windows build lanes") +def test_pinned_npm_activation_fails_closed_after_bounded_acquisition_exhaustion( + tmp_path: Path, +) -> None: + """Stop after three failed acquisitions without enabling or invoking fallback npm.""" + completed, corepack_count, sleep_log, npm_log = _run_activation_helper( + tmp_path, + acquisition_failures=99, + ) + + assert completed.returncode != 0 + assert corepack_count.read_text(encoding="utf-8") == "3" + assert sleep_log.read_text(encoding="utf-8").splitlines() == ["5", "10"] + assert not npm_log.exists() + assert "after 3 attempts" in completed.stderr + assert "refusing an unpinned npm fallback" in completed.stderr From 8e0b361381a666ede16ee45b97b0390757629026 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 18 Sep 2026 08:12:27 +0900 Subject: [PATCH 102/161] test(node): prove bounded npm acquisition success and exhaustion --- .../test_npm_runtime_activation_resilience.py | 35 +++++++++++++------ 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/services/analysis-engine/tests/test_npm_runtime_activation_resilience.py b/services/analysis-engine/tests/test_npm_runtime_activation_resilience.py index 519423979..38ee438dd 100644 --- a/services/analysis-engine/tests/test_npm_runtime_activation_resilience.py +++ b/services/analysis-engine/tests/test_npm_runtime_activation_resilience.py @@ -37,7 +37,7 @@ def _fake_command_environment( tmp_path: Path, *, acquisition_failures: int, -) -> tuple[dict[str, str], Path, Path, Path]: +) -> tuple[dict[str, str], Path, Path, Path, Path]: """Return a PATH-isolated command harness and its evidence files.""" fake_bin = tmp_path / "bin" fake_bin.mkdir() @@ -89,7 +89,7 @@ def _fake_command_environment( environment["BANDSCOPE_TEST_COREPACK_ENABLE_LOG"] = str(corepack_enable_log) environment["BANDSCOPE_TEST_SLEEP_LOG"] = str(sleep_log) environment["BANDSCOPE_TEST_NPM_LOG"] = str(npm_log) - return environment, corepack_count, sleep_log, npm_log + return environment, corepack_count, sleep_log, npm_log, corepack_enable_log def _run_activation_helper(tmp_path: Path, *, acquisition_failures: int) -> tuple[ @@ -97,11 +97,14 @@ def _run_activation_helper(tmp_path: Path, *, acquisition_failures: int) -> tupl Path, Path, Path, + Path, ]: """Execute the real helper against deterministic fake external commands.""" - environment, corepack_count, sleep_log, npm_log = _fake_command_environment( - tmp_path, - acquisition_failures=acquisition_failures, + environment, corepack_count, sleep_log, npm_log, corepack_enable_log = ( + _fake_command_environment( + tmp_path, + acquisition_failures=acquisition_failures, + ) ) completed = subprocess.run( ["bash", str(_ACTIVATION_HELPER)], @@ -111,7 +114,7 @@ def _run_activation_helper(tmp_path: Path, *, acquisition_failures: int) -> tupl capture_output=True, check=False, ) - return completed, corepack_count, sleep_log, npm_log + return completed, corepack_count, sleep_log, npm_log, corepack_enable_log def test_build_baseline_uses_retrying_pinned_npm_activation_before_dependency_reads() -> None: @@ -134,7 +137,9 @@ def test_build_baseline_uses_retrying_pinned_npm_activation_before_dependency_re npm_consumers += 1 activation_indexes = [ - index for index, command in enumerate(run_steps) if command.strip() == _ACTIVATION_COMMAND + index + for index, command in enumerate(run_steps) + if command.strip() == _ACTIVATION_COMMAND ] assert activation_indexes == [dependency_index - 1], f"{job_name} activation ownership" assert all("corepack enable npm" not in command for command in run_steps), ( @@ -158,12 +163,15 @@ def test_pinned_npm_activation_helper_retries_acquisition_but_never_falls_back() assert "npm@10.9.8" not in source -@pytest.mark.skipif(os.name == "nt", reason="shell helper is exercised by hosted Windows build lanes") +@pytest.mark.skipif( + os.name == "nt", + reason="shell helper is exercised by hosted Windows build lanes", +) def test_pinned_npm_activation_recovers_after_two_transient_acquisition_failures( tmp_path: Path, ) -> None: """Retry only exact-runtime acquisition, then audit the acquired npm before success.""" - completed, corepack_count, sleep_log, npm_log = _run_activation_helper( + completed, corepack_count, sleep_log, npm_log, corepack_enable_log = _run_activation_helper( tmp_path, acquisition_failures=2, ) @@ -172,15 +180,19 @@ def test_pinned_npm_activation_recovers_after_two_transient_acquisition_failures assert corepack_count.read_text(encoding="utf-8") == "3" assert sleep_log.read_text(encoding="utf-8").splitlines() == ["5", "10"] assert npm_log.read_text(encoding="utf-8").splitlines() == ["run check:npm-runtime"] + assert corepack_enable_log.read_text(encoding="utf-8").splitlines() == ["enable npm"] assert "retrying exact npm@10.9.9" in completed.stderr -@pytest.mark.skipif(os.name == "nt", reason="shell helper is exercised by hosted Windows build lanes") +@pytest.mark.skipif( + os.name == "nt", + reason="shell helper is exercised by hosted Windows build lanes", +) def test_pinned_npm_activation_fails_closed_after_bounded_acquisition_exhaustion( tmp_path: Path, ) -> None: """Stop after three failed acquisitions without enabling or invoking fallback npm.""" - completed, corepack_count, sleep_log, npm_log = _run_activation_helper( + completed, corepack_count, sleep_log, npm_log, corepack_enable_log = _run_activation_helper( tmp_path, acquisition_failures=99, ) @@ -189,5 +201,6 @@ def test_pinned_npm_activation_fails_closed_after_bounded_acquisition_exhaustion assert corepack_count.read_text(encoding="utf-8") == "3" assert sleep_log.read_text(encoding="utf-8").splitlines() == ["5", "10"] assert not npm_log.exists() + assert not corepack_enable_log.exists() assert "after 3 attempts" in completed.stderr assert "refusing an unpinned npm fallback" in completed.stderr From 4b64860cc19a0b60a6f768ef1a88e49ea024992d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 18 Sep 2026 08:59:09 +0900 Subject: [PATCH 103/161] test(node): reject retries for npm signature failures --- ...runtime_activation_nontransient_failure.py | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 services/analysis-engine/tests/test_npm_runtime_activation_nontransient_failure.py diff --git a/services/analysis-engine/tests/test_npm_runtime_activation_nontransient_failure.py b/services/analysis-engine/tests/test_npm_runtime_activation_nontransient_failure.py new file mode 100644 index 000000000..234e24795 --- /dev/null +++ b/services/analysis-engine/tests/test_npm_runtime_activation_nontransient_failure.py @@ -0,0 +1,86 @@ +"""Regression for non-transient npm runtime acquisition failures.""" + +from __future__ import annotations + +import os +import subprocess +from pathlib import Path + +import pytest + +_REPOSITORY_ROOT = Path(__file__).resolve().parents[3] +_ACTIVATION_HELPER = _REPOSITORY_ROOT / "scripts" / "checks" / "activate_pinned_npm_runtime.sh" + + +def _write_executable(path: Path, content: str) -> None: + path.write_text(content, encoding="utf-8") + path.chmod(0o755) + + +@pytest.mark.skipif(os.name == "nt", reason="shell helper is exercised by hosted Windows lanes") +def test_pinned_npm_activation_does_not_retry_signature_failure(tmp_path: Path) -> None: + """A provenance/signature failure must fail immediately instead of being retried.""" + fake_bin = tmp_path / "bin" + fake_bin.mkdir() + count_file = tmp_path / "corepack-count.txt" + sleep_log = tmp_path / "sleep.log" + enable_log = tmp_path / "enable.log" + npm_log = tmp_path / "npm.log" + + _write_executable( + fake_bin / "node", + "#!/usr/bin/env bash\ncat >/dev/null\nprintf 'npm@10.9.9'\n", + ) + _write_executable( + fake_bin / "corepack", + """#!/usr/bin/env bash +set -euo pipefail +if [[ "$1" == "install" ]]; then + count=0 + if [[ -f "$BANDSCOPE_TEST_COREPACK_COUNT" ]]; then + count="$(cat "$BANDSCOPE_TEST_COREPACK_COUNT")" + fi + count=$((count + 1)) + printf '%s' "$count" > "$BANDSCOPE_TEST_COREPACK_COUNT" + echo 'Signature does not match the expected keyid' >&2 + exit 1 +fi +if [[ "$1" == "enable" ]]; then + printf '%s\n' "$*" >> "$BANDSCOPE_TEST_ENABLE_LOG" + exit 0 +fi +exit 64 +""", + ) + _write_executable( + fake_bin / "sleep", + "#!/usr/bin/env bash\nprintf '%s\\n' \"$1\" >> \"$BANDSCOPE_TEST_SLEEP_LOG\"\n", + ) + _write_executable( + fake_bin / "npm", + "#!/usr/bin/env bash\nprintf '%s\\n' \"$*\" >> \"$BANDSCOPE_TEST_NPM_LOG\"\n", + ) + + environment = os.environ.copy() + environment["PATH"] = f"{fake_bin}{os.pathsep}{environment['PATH']}" + environment["BANDSCOPE_TEST_COREPACK_COUNT"] = str(count_file) + environment["BANDSCOPE_TEST_SLEEP_LOG"] = str(sleep_log) + environment["BANDSCOPE_TEST_ENABLE_LOG"] = str(enable_log) + environment["BANDSCOPE_TEST_NPM_LOG"] = str(npm_log) + + completed = subprocess.run( + ["bash", str(_ACTIVATION_HELPER)], + cwd=tmp_path, + env=environment, + text=True, + capture_output=True, + check=False, + ) + + assert completed.returncode != 0 + assert count_file.read_text(encoding="utf-8") == "1" + assert not sleep_log.exists() + assert not enable_log.exists() + assert not npm_log.exists() + assert "Signature does not match" in completed.stderr + assert "non-transient" in completed.stderr From 02ae08966b491570ba8f056ac04f1d0e2b285e2c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 18 Sep 2026 08:59:27 +0900 Subject: [PATCH 104/161] fix(node): fail fast on npm provenance errors --- scripts/checks/activate_pinned_npm_runtime.sh | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/scripts/checks/activate_pinned_npm_runtime.sh b/scripts/checks/activate_pinned_npm_runtime.sh index d989a2315..7532159fc 100644 --- a/scripts/checks/activate_pinned_npm_runtime.sh +++ b/scripts/checks/activate_pinned_npm_runtime.sh @@ -19,7 +19,23 @@ NODE } attempt=1 -while ! corepack install --global "$package_manager_spec"; do +while true; do + acquisition_output="" + if acquisition_output="$(corepack install --global "$package_manager_spec" 2>&1)"; then + if [[ -n "$acquisition_output" ]]; then + printf '%s\n' "$acquisition_output" >&2 + fi + break + fi + + printf '%s\n' "$acquisition_output" >&2 + case "$acquisition_output" in + *"Signature does not match"*|*"Cannot find matching keyid"*|*"not signed by any trusted keys"*|*"integrity checksum"*|*"Integrity check failed"*|*"integrity check failed"*) + echo "Corepack reported a non-transient package-manager provenance failure; refusing to retry or weaken verification." >&2 + exit 1 + ;; + esac + if (( attempt >= MAX_ATTEMPTS )); then echo "Failed to acquire $package_manager_spec after $MAX_ATTEMPTS attempts; refusing an unpinned npm fallback." >&2 exit 1 From 61713a2c8e2053476c400b8a16971e21dd0b7fac Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 18 Sep 2026 08:59:54 +0900 Subject: [PATCH 105/161] fix(node): recognize current Corepack signature failure --- scripts/checks/activate_pinned_npm_runtime.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/checks/activate_pinned_npm_runtime.sh b/scripts/checks/activate_pinned_npm_runtime.sh index 7532159fc..232f96d77 100644 --- a/scripts/checks/activate_pinned_npm_runtime.sh +++ b/scripts/checks/activate_pinned_npm_runtime.sh @@ -30,7 +30,7 @@ while true; do printf '%s\n' "$acquisition_output" >&2 case "$acquisition_output" in - *"Signature does not match"*|*"Cannot find matching keyid"*|*"not signed by any trusted keys"*|*"integrity checksum"*|*"Integrity check failed"*|*"integrity check failed"*) + *"Signature does not match"*|*"Cannot find matching keyid"*|*"No compatible signature found"*|*"not signed by any trusted keys"*|*"integrity checksum"*|*"Integrity check failed"*|*"integrity check failed"*) echo "Corepack reported a non-transient package-manager provenance failure; refusing to retry or weaken verification." >&2 exit 1 ;; From 3983dd216d95dc5f78e78f6b17259ad4c5530ebc Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 18 Sep 2026 09:00:11 +0900 Subject: [PATCH 106/161] docs(node): classify npm acquisition trust failures --- ...time-acquisition-failure-classification.md | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 docs/traceability/npm-runtime-acquisition-failure-classification.md diff --git a/docs/traceability/npm-runtime-acquisition-failure-classification.md b/docs/traceability/npm-runtime-acquisition-failure-classification.md new file mode 100644 index 000000000..30f8db170 --- /dev/null +++ b/docs/traceability/npm-runtime-acquisition-failure-classification.md @@ -0,0 +1,56 @@ +# npm runtime acquisition failure classification + +Status: Proposed + +## Problem + +PR #1232 exposed a real `ETIMEDOUT` while Corepack acquired the repository-pinned `npm@10.9.9`. PR #896 added bounded retry for that exact acquisition step, but the first implementation retried every non-zero `corepack install --global` result. That included signature and integrity failures. + +Retrying a provenance failure cannot make the reviewed artifact become valid. It also obscures the distinction between transient transport recovery and a failed trust decision. The acquisition helper must therefore preserve the exact npm version while refusing to retry diagnostics that indicate package-manager provenance verification failed. + +## Constraints + +- `npm@10.9.9` remains the only accepted package-manager runtime for this owner branch. +- Node-bundled npm, `latest`, `stable`, system npm, mutable dependency resolution, tests, builds, uploads, and release actions are not fallback targets. +- The existing three-attempt, 5 s / 10 s bounded acquisition loop remains available for failures that are not identified as provenance failures. +- A signature or integrity diagnostic must stop before `corepack enable npm`, `npm run check:npm-runtime`, or `npm ci` can execute. +- Error classification is intentionally conservative and diagnostic-based because Corepack exposes these failures through the command boundary rather than a stable machine-readable error taxonomy. + +## Decision + +`scripts/checks/activate_pinned_npm_runtime.sh` captures the failing Corepack diagnostic and fails immediately when it contains a known provenance-verification signal, including Corepack's current `Signature does not match` and `No compatible signature found in package metadata` messages and compatibility strings used by older Corepack lines such as `Cannot find matching keyid`. + +The helper still prints the original diagnostic before its own refusal message. It does not disable Corepack verification, alter `COREPACK_INTEGRITY_KEYS`, select another npm version, or treat a provenance failure as a transient registry event. + +Unknown acquisition failures retain the existing bounded retry behavior and still fail after three attempts. This change narrows retry scope for known trust failures; it does not claim to classify every possible network or Corepack failure. + +## Rejected alternatives + +- Retry every Corepack failure three times: rejected because deterministic signature/integrity failures are not transport recovery candidates. +- Disable or weaken Corepack signature verification: rejected because that changes the supply-chain trust boundary rather than repairing availability. +- Fall back to Node-bundled npm 10.9.8: rejected because the repository requires npm 10.9.9 and its bundled patched `tar` floor. +- Retry `npm ci` or later build/test commands: rejected because those operations have different side effects and failure semantics. + +## Evidence and regression + +RED `4b64860cc19a0b60a6f768ef1a88e49ea024992d` adds a hostile command-boundary regression: a fake Corepack returns a signature mismatch, and the helper must stop after one install attempt with no sleep, npm enable, or npm audit invocation. + +GREEN `02ae08966b491570ba8f056ac04f1d0e2b285e2c` captures Corepack stderr and fails immediately on known provenance diagnostics. Follow-up `61713a2c8e2053476c400b8a16971e21dd0b7fac` aligns the classifier with the current Corepack source message `No compatible signature found in package metadata` while preserving older compatibility strings. + +Current Corepack source throws `Signature does not match` when signature verification fails and separately throws `No compatible signature found in package metadata` when compatible package metadata signatures are unavailable. Those are trust/provenance decisions, not evidence of a transient registry timeout. + +## Risks and claim boundary + +Diagnostic matching depends on upstream text and therefore requires maintenance when Corepack changes its messages. The helper still fails closed after bounded exhaustion even when a new non-transient error is not recognized immediately. This mechanism does not prove package-manager authenticity by itself; authenticity remains Corepack's verification responsibility, while BandScope controls retry and fallback behavior around that boundary. + +## Follow-up + +- Keep the hostile signature-failure regression in the exact-head gate. +- If Corepack introduces a stable structured failure classification, replace message matching with that contract. +- Treat any newly observed integrity/signature diagnostic that retries as a repair finding, not as permission to broaden fallback behavior. + +## References + +Node.js contributors. (2026). *Corepack npm registry signature verification* [Source code]. GitHub. https://github.com/nodejs/corepack/blob/d4dcb1f89741603e776bba9d457425750fa26987/sources/npmRegistryUtils.ts + +Node.js contributors. (2026). *Corepack 0.36.0* [Software release]. GitHub. https://github.com/nodejs/corepack/releases/tag/v0.36.0 From b8fcb799ca83c4dbfa56fed6802a647dbf785bfa Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 18 Sep 2026 10:00:53 +0900 Subject: [PATCH 107/161] test(build): fail closed on unclassified npm acquisition errors --- ...runtime_activation_nontransient_failure.py | 52 ++++++++++++++++--- 1 file changed, 44 insertions(+), 8 deletions(-) diff --git a/services/analysis-engine/tests/test_npm_runtime_activation_nontransient_failure.py b/services/analysis-engine/tests/test_npm_runtime_activation_nontransient_failure.py index 234e24795..acce2d267 100644 --- a/services/analysis-engine/tests/test_npm_runtime_activation_nontransient_failure.py +++ b/services/analysis-engine/tests/test_npm_runtime_activation_nontransient_failure.py @@ -17,9 +17,12 @@ def _write_executable(path: Path, content: str) -> None: path.chmod(0o755) -@pytest.mark.skipif(os.name == "nt", reason="shell helper is exercised by hosted Windows lanes") -def test_pinned_npm_activation_does_not_retry_signature_failure(tmp_path: Path) -> None: - """A provenance/signature failure must fail immediately instead of being retried.""" +def _run_corepack_failure( + tmp_path: Path, + *, + diagnostic: str, +) -> tuple[subprocess.CompletedProcess[str], Path, Path, Path, Path]: + """Run the activation helper against one deterministic Corepack failure.""" fake_bin = tmp_path / "bin" fake_bin.mkdir() count_file = tmp_path / "corepack-count.txt" @@ -33,7 +36,7 @@ def test_pinned_npm_activation_does_not_retry_signature_failure(tmp_path: Path) ) _write_executable( fake_bin / "corepack", - """#!/usr/bin/env bash + f"""#!/usr/bin/env bash set -euo pipefail if [[ "$1" == "install" ]]; then count=0 @@ -42,11 +45,11 @@ def test_pinned_npm_activation_does_not_retry_signature_failure(tmp_path: Path) fi count=$((count + 1)) printf '%s' "$count" > "$BANDSCOPE_TEST_COREPACK_COUNT" - echo 'Signature does not match the expected keyid' >&2 + printf '%s\\n' {diagnostic!r} >&2 exit 1 fi if [[ "$1" == "enable" ]]; then - printf '%s\n' "$*" >> "$BANDSCOPE_TEST_ENABLE_LOG" + printf '%s\\n' "$*" >> "$BANDSCOPE_TEST_ENABLE_LOG" exit 0 fi exit 64 @@ -76,11 +79,44 @@ def test_pinned_npm_activation_does_not_retry_signature_failure(tmp_path: Path) capture_output=True, check=False, ) + return completed, count_file, sleep_log, enable_log, npm_log + +def _assert_immediate_failure( + completed: subprocess.CompletedProcess[str], + count_file: Path, + sleep_log: Path, + enable_log: Path, + npm_log: Path, +) -> None: + """Require one acquisition attempt and no downstream activation work.""" assert completed.returncode != 0 assert count_file.read_text(encoding="utf-8") == "1" assert not sleep_log.exists() assert not enable_log.exists() assert not npm_log.exists() - assert "Signature does not match" in completed.stderr - assert "non-transient" in completed.stderr + + +@pytest.mark.skipif(os.name == "nt", reason="shell helper is exercised by hosted Windows lanes") +def test_pinned_npm_activation_does_not_retry_signature_failure(tmp_path: Path) -> None: + """A provenance/signature failure must fail immediately instead of being retried.""" + result = _run_corepack_failure( + tmp_path, + diagnostic="Signature does not match the expected keyid", + ) + + _assert_immediate_failure(*result) + assert "Signature does not match" in result[0].stderr + + +@pytest.mark.skipif(os.name == "nt", reason="shell helper is exercised by hosted Windows lanes") +def test_pinned_npm_activation_does_not_retry_unknown_failure(tmp_path: Path) -> None: + """An unclassified Corepack failure must fail closed instead of being guessed transient.""" + result = _run_corepack_failure( + tmp_path, + diagnostic="Corepack failed while validating package-manager metadata", + ) + + _assert_immediate_failure(*result) + assert "validating package-manager metadata" in result[0].stderr + assert "not classified as transient" in result[0].stderr From 9c39c2a595ac5e192c53ed207df2cec6e188b483 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 18 Sep 2026 10:01:55 +0900 Subject: [PATCH 108/161] fix(build): retry only admitted npm acquisition timeouts --- scripts/checks/activate_pinned_npm_runtime.sh | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/scripts/checks/activate_pinned_npm_runtime.sh b/scripts/checks/activate_pinned_npm_runtime.sh index 232f96d77..ca9b41586 100644 --- a/scripts/checks/activate_pinned_npm_runtime.sh +++ b/scripts/checks/activate_pinned_npm_runtime.sh @@ -30,8 +30,10 @@ while true; do printf '%s\n' "$acquisition_output" >&2 case "$acquisition_output" in - *"Signature does not match"*|*"Cannot find matching keyid"*|*"No compatible signature found"*|*"not signed by any trusted keys"*|*"integrity checksum"*|*"Integrity check failed"*|*"integrity check failed"*) - echo "Corepack reported a non-transient package-manager provenance failure; refusing to retry or weaken verification." >&2 + *"ETIMEDOUT"*) + ;; + *) + echo "Corepack acquisition failure is not classified as transient; refusing to retry or weaken verification." >&2 exit 1 ;; esac @@ -42,7 +44,7 @@ while true; do fi sleep_seconds=$((attempt * 5)) - echo "Corepack acquisition attempt $attempt failed; retrying exact $package_manager_spec in ${sleep_seconds}s." >&2 + echo "Corepack acquisition attempt $attempt failed with an admitted transient timeout; retrying exact $package_manager_spec in ${sleep_seconds}s." >&2 sleep "$sleep_seconds" attempt=$((attempt + 1)) done From 68f71e02d47a5cd90e4c2dce474f6d1b0f8ef5e3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 18 Sep 2026 10:02:26 +0900 Subject: [PATCH 109/161] test(build): classify retry fixture as ETIMEDOUT --- .../tests/test_npm_runtime_activation_resilience.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/services/analysis-engine/tests/test_npm_runtime_activation_resilience.py b/services/analysis-engine/tests/test_npm_runtime_activation_resilience.py index 38ee438dd..f4c65474a 100644 --- a/services/analysis-engine/tests/test_npm_runtime_activation_resilience.py +++ b/services/analysis-engine/tests/test_npm_runtime_activation_resilience.py @@ -62,6 +62,7 @@ def _fake_command_environment( count=$((count + 1)) printf '%s' "$count" > "$BANDSCOPE_TEST_COREPACK_COUNT" if (( count <= BANDSCOPE_TEST_ACQUISITION_FAILURES )); then + echo 'request to registry.npmjs.org failed, reason: connect ETIMEDOUT' >&2 exit 1 fi exit 0 @@ -150,11 +151,13 @@ def test_build_baseline_uses_retrying_pinned_npm_activation_before_dependency_re def test_pinned_npm_activation_helper_retries_acquisition_but_never_falls_back() -> None: - """Keep transient registry recovery bounded while exact npm provenance remains fail closed.""" + """Keep admitted timeout recovery bounded while exact npm provenance remains fail closed.""" source = _ACTIVATION_HELPER.read_text(encoding="utf-8") assert 'MAX_ATTEMPTS="3"' in source assert 'corepack install --global "$package_manager_spec"' in source + assert '"ETIMEDOUT"' in source + assert "not classified as transient" in source assert "corepack enable npm" in source assert "npm run check:npm-runtime" in source assert "sleep_seconds=$((attempt * 5))" in source @@ -170,7 +173,7 @@ def test_pinned_npm_activation_helper_retries_acquisition_but_never_falls_back() def test_pinned_npm_activation_recovers_after_two_transient_acquisition_failures( tmp_path: Path, ) -> None: - """Retry only exact-runtime acquisition, then audit the acquired npm before success.""" + """Retry admitted ETIMEDOUT acquisition, then audit the acquired npm before success.""" completed, corepack_count, sleep_log, npm_log, corepack_enable_log = _run_activation_helper( tmp_path, acquisition_failures=2, @@ -181,6 +184,7 @@ def test_pinned_npm_activation_recovers_after_two_transient_acquisition_failures assert sleep_log.read_text(encoding="utf-8").splitlines() == ["5", "10"] assert npm_log.read_text(encoding="utf-8").splitlines() == ["run check:npm-runtime"] assert corepack_enable_log.read_text(encoding="utf-8").splitlines() == ["enable npm"] + assert "ETIMEDOUT" in completed.stderr assert "retrying exact npm@10.9.9" in completed.stderr @@ -188,10 +192,10 @@ def test_pinned_npm_activation_recovers_after_two_transient_acquisition_failures os.name == "nt", reason="shell helper is exercised by hosted Windows build lanes", ) -def test_pinned_npm_activation_fails_closed_after_bounded_acquisition_exhaustion( +def test_pinned_npm_activation_fails_closed_after_bounded_timeout_exhaustion( tmp_path: Path, ) -> None: - """Stop after three failed acquisitions without enabling or invoking fallback npm.""" + """Stop after three admitted timeout failures without enabling or invoking fallback npm.""" completed, corepack_count, sleep_log, npm_log, corepack_enable_log = _run_activation_helper( tmp_path, acquisition_failures=99, @@ -202,5 +206,6 @@ def test_pinned_npm_activation_fails_closed_after_bounded_acquisition_exhaustion assert sleep_log.read_text(encoding="utf-8").splitlines() == ["5", "10"] assert not npm_log.exists() assert not corepack_enable_log.exists() + assert "ETIMEDOUT" in completed.stderr assert "after 3 attempts" in completed.stderr assert "refusing an unpinned npm fallback" in completed.stderr From f7341ee9268fffa80137bc057ff41a6a6ecae2bf Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 18 Sep 2026 10:02:58 +0900 Subject: [PATCH 110/161] docs(traceability): fail closed on unknown Corepack failures --- ...time-acquisition-failure-classification.md | 45 ++++++++++++------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/docs/traceability/npm-runtime-acquisition-failure-classification.md b/docs/traceability/npm-runtime-acquisition-failure-classification.md index 30f8db170..f2b4615ba 100644 --- a/docs/traceability/npm-runtime-acquisition-failure-classification.md +++ b/docs/traceability/npm-runtime-acquisition-failure-classification.md @@ -4,50 +4,63 @@ Status: Proposed ## Problem -PR #1232 exposed a real `ETIMEDOUT` while Corepack acquired the repository-pinned `npm@10.9.9`. PR #896 added bounded retry for that exact acquisition step, but the first implementation retried every non-zero `corepack install --global` result. That included signature and integrity failures. +PR #1232 exposed a real `ETIMEDOUT` while Corepack acquired the repository-pinned `npm@10.9.9`. PR #896 added bounded retry for that exact acquisition step. The first implementation retried every non-zero `corepack install --global` result, including signature and integrity failures. A follow-up blacklist stopped known provenance diagnostics, but still treated every unrecognized Corepack failure as transient. -Retrying a provenance failure cannot make the reviewed artifact become valid. It also obscures the distinction between transient transport recovery and a failed trust decision. The acquisition helper must therefore preserve the exact npm version while refusing to retry diagnostics that indicate package-manager provenance verification failed. +That blacklist leaves a fail-open classification boundary: a future or differently worded trust/provenance failure would be retried merely because BandScope did not recognize its text. Retryability must be positively established instead. Unknown acquisition failures are not evidence of a transient transport condition. ## Constraints - `npm@10.9.9` remains the only accepted package-manager runtime for this owner branch. - Node-bundled npm, `latest`, `stable`, system npm, mutable dependency resolution, tests, builds, uploads, and release actions are not fallback targets. -- The existing three-attempt, 5 s / 10 s bounded acquisition loop remains available for failures that are not identified as provenance failures. -- A signature or integrity diagnostic must stop before `corepack enable npm`, `npm run check:npm-runtime`, or `npm ci` can execute. -- Error classification is intentionally conservative and diagnostic-based because Corepack exposes these failures through the command boundary rather than a stable machine-readable error taxonomy. +- Only the exact Corepack acquisition step may receive bounded retry. +- The hosted incident actually observed `ETIMEDOUT`; this is the only transient diagnostic admitted by the current policy. +- The admitted timeout path is limited to three attempts with 5 s / 10 s backoff. +- Any unclassified Corepack failure must stop before `corepack enable npm`, `npm run check:npm-runtime`, or `npm ci` can execute. +- Error classification remains diagnostic-based because the current Corepack command boundary does not expose a stable machine-readable failure taxonomy to this script. ## Decision -`scripts/checks/activate_pinned_npm_runtime.sh` captures the failing Corepack diagnostic and fails immediately when it contains a known provenance-verification signal, including Corepack's current `Signature does not match` and `No compatible signature found in package metadata` messages and compatibility strings used by older Corepack lines such as `Cannot find matching keyid`. +`scripts/checks/activate_pinned_npm_runtime.sh` captures and preserves Corepack's failing diagnostic. It retries only when that diagnostic contains the observed transient error code `ETIMEDOUT`. Every other non-zero acquisition result fails immediately as **not classified as transient**. -The helper still prints the original diagnostic before its own refusal message. It does not disable Corepack verification, alter `COREPACK_INTEGRITY_KEYS`, select another npm version, or treat a provenance failure as a transient registry event. +This makes signature/integrity failures fail closed without depending on an exhaustive list of current or future Corepack wording. The helper does not disable Corepack verification, alter `COREPACK_INTEGRITY_KEYS`, select another npm version, or guess that an unknown failure is a network event. -Unknown acquisition failures retain the existing bounded retry behavior and still fail after three attempts. This change narrows retry scope for known trust failures; it does not claim to classify every possible network or Corepack failure. +The retry allowlist is intentionally narrow. Additional error codes such as connection reset, DNS retry, or HTTP/server failures must not be admitted from intuition alone; they require a concrete hosted failure, bounded semantics, and a focused regression before this policy expands. ## Rejected alternatives -- Retry every Corepack failure three times: rejected because deterministic signature/integrity failures are not transport recovery candidates. +- Retry every Corepack failure three times: rejected because deterministic trust, policy, package metadata, permission, and configuration failures are not transport recovery candidates. +- Maintain a blacklist of known signature/integrity strings and retry everything else: rejected because future or differently worded non-transient failures become retryable by default. +- Broadly classify all network-looking diagnostics as transient: rejected because the current hosted evidence proves `ETIMEDOUT`, not every possible transport or HTTP failure. - Disable or weaken Corepack signature verification: rejected because that changes the supply-chain trust boundary rather than repairing availability. - Fall back to Node-bundled npm 10.9.8: rejected because the repository requires npm 10.9.9 and its bundled patched `tar` floor. - Retry `npm ci` or later build/test commands: rejected because those operations have different side effects and failure semantics. ## Evidence and regression -RED `4b64860cc19a0b60a6f768ef1a88e49ea024992d` adds a hostile command-boundary regression: a fake Corepack returns a signature mismatch, and the helper must stop after one install attempt with no sleep, npm enable, or npm audit invocation. +Earlier RED `4b64860cc19a0b60a6f768ef1a88e49ea024992d` proved a fake Corepack signature mismatch must stop after one install attempt with no sleep, npm enable, or npm audit. GREEN `02ae08966b491570ba8f056ac04f1d0e2b285e2c` and alignment `61713a2c8e2053476c400b8a16971e21dd0b7fac` stopped then-known signature/integrity diagnostics. -GREEN `02ae08966b491570ba8f056ac04f1d0e2b285e2c` captures Corepack stderr and fails immediately on known provenance diagnostics. Follow-up `61713a2c8e2053476c400b8a16971e21dd0b7fac` aligns the classifier with the current Corepack source message `No compatible signature found in package metadata` while preserving older compatibility strings. +Fresh review found the remaining default-retry defect. RED `b8fcb799ca83c4dbfa56fed6802a647dbf785bfa` adds an unclassified Corepack failure and requires one attempt only, no sleep, no enable, no npm audit, preserved upstream diagnostic, and an explicit `not classified as transient` refusal. -Current Corepack source throws `Signature does not match` when signature verification fails and separately throws `No compatible signature found in package metadata` when compatible package metadata signatures are unavailable. Those are trust/provenance decisions, not evidence of a transient registry timeout. +GREEN `9c39c2a595ac5e192c53ed207df2cec6e188b483` reverses the classifier: only `ETIMEDOUT` is admitted to the bounded retry loop; any other failed acquisition exits immediately. Fixture alignment `68f71e02d47a5cd90e4c2dce474f6d1b0f8ef5e3` makes the positive retry regression emit the same `ETIMEDOUT` class observed in hosted CI, preserving the two-timeout-then-success and three-timeout-exhaustion contracts without using an unspecified failure as evidence of transience. + +Predecessor exact head `3983dd216d95dc5f78e78f6b17259ad4c5530ebc` completed all four native Windows/macOS build jobs successfully with exact npm activation. That hosted evidence validates the predecessor command path only; it does not transfer to the moved allowlist-classifier head. ## Risks and claim boundary -Diagnostic matching depends on upstream text and therefore requires maintenance when Corepack changes its messages. The helper still fails closed after bounded exhaustion even when a new non-transient error is not recognized immediately. This mechanism does not prove package-manager authenticity by itself; authenticity remains Corepack's verification responsibility, while BandScope controls retry and fallback behavior around that boundary. +The allowlist may reject a future genuinely transient Corepack error that is not `ETIMEDOUT`. That is an availability tradeoff accepted at the package-manager trust boundary: a false negative causes a visible build failure, while a false positive can repeatedly process an unclassified trust or policy failure as though it were harmless network noise. + +Diagnostic matching still depends on upstream text. If Corepack exposes a stable structured error code or typed result, this script should consume that contract instead. This mechanism does not prove package-manager authenticity by itself; authenticity remains Corepack's verification responsibility, while BandScope controls retry and fallback behavior around that boundary. ## Follow-up -- Keep the hostile signature-failure regression in the exact-head gate. -- If Corepack introduces a stable structured failure classification, replace message matching with that contract. -- Treat any newly observed integrity/signature diagnostic that retries as a repair finding, not as permission to broaden fallback behavior. +- Keep both the hostile signature-failure and unclassified-failure regressions in the exact-head gate. +- Expand the transient allowlist only from exact observed evidence plus a focused regression and documented retry safety. +- If Corepack introduces a stable structured failure classification, replace diagnostic-string matching with that contract. +- Treat any unclassified failure that reaches sleep/retry as a repair finding, not as permission to broaden fallback behavior. + +## Security Notes + +The package-manager acquisition diagnostic is untrusted upstream text used only for a bounded classification decision and stderr evidence. It is never evaluated or interpolated into a shell command. The trust boundary is `corepack install --global` returning non-zero: only the exact observed `ETIMEDOUT` token permits another attempt; all other results fail closed before npm activation or dependency extraction. No secret, token, package payload, or mutable version selector is logged by this policy. ## References From b025062bba9971f5bbd50759b146b1c5d0073dd1 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 18 Sep 2026 10:04:35 +0900 Subject: [PATCH 111/161] repair(scope): release baseline and formatter ownership --- docs/product-technical-gap-baseline.md | 29 ------------------- .../tests/test_supply_chain_policy.py | 4 ++- 2 files changed, 3 insertions(+), 30 deletions(-) delete mode 100644 docs/product-technical-gap-baseline.md diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md deleted file mode 100644 index 964698d78..000000000 --- a/docs/product-technical-gap-baseline.md +++ /dev/null @@ -1,29 +0,0 @@ -# Product technical gap baseline - -## Goal and evidence boundary - -BandScope's current dependency-runtime goal is a reproducible desktop build on the supported Node 22 line without weakening supply-chain evidence. This baseline is derived from protected `develop`, the active canonical compatibility pull request, repository contracts, and exact toolchain execution. A branch or pull request is Proposed evidence until ordinary protected-branch integration. - -## Product and technical context - -- **PRD:** a contributor or release operator can install, test, build, and package BandScope from one reviewed workspace lock without manual repair. -- **TRD:** Node `>=22.22.2 <23`, npm `10.9.9`, lockfile version 3, frozen `npm ci`, registry SRI, and generator-sensitive peer metadata are one compatibility contract. -- **Context Map:** Dependency Intent (`package.json` manifests) supplies the Lock Generation context; CI and Release consume only its committed artifact. npm registry data is external and remains behind npm's resolver boundary. -- **UML/runtime flow:** manifests -> pinned npm generator -> complete lock artifact -> frozen install -> lint/type/test/build/Storybook/Tauri -> release evidence. -- **ERD/persistence:** this slice changes no product database, table, column, index, constraint, sequence, view, function, ORM mapping, or data migration. - -## Gap register - -| Gap | Evidence | Action | Status | -| --- | --- | --- | --- | -| Node/jsdom manifest and lock disagreement | Runtime contract requires Node `22.22.2` and jsdom `30.0.1`; the predecessor lock retained Node `22.13` and jsdom 29 | Regenerate the complete root lock with Node `22.22.2` and npm `10.9.9` | Implemented locally; exact-head gates required | -| ESLint update lost generator-sensitive peer metadata | The predecessor dependency PR removed `peer: true` from platform-specific root `@esbuild/*` records | Integrate its manifest intent into the canonical lock owner and regenerate instead of transplanting its lock | Implemented locally; exact-head gates required | -| Dependency ownership was split across overlapping PRs | jsdom/Node and ESLint both write the root workspace lock | Preserve both histories through a two-parent non-force reconciliation in the canonical lock owner | Implemented locally | -| Runtime tests inspected the wrong ownership scope | One assertion searched job-local text for a workflow-global environment value; another rejected transitive packages' valid engine ranges | Assert the global workflow contract globally and the root package engine only at the root lock record | Implemented locally | -| Buyer-visible reproducibility remains unproven remotely | Local generation cannot prove hosted runners, platform builds, security scans, or independent review | Require unchanged-head CI, Windows, macOS, security, SBOM, CodeQL, release, and review evidence | Open | - -## Invariants and rollback - -The root `package-lock.json` remains the sole npm workspace lock. Manifest intent and the complete generated lock move together; partial lock transplantation and hand normalization are forbidden. A failed frozen install, integrity check, peer-metadata check, platform build, or security gate fails closed. - -Rollback restores the previous manifests and their complete lock artifact together, preserves generator/runtime/check evidence, and reruns every exact-head gate. No database rollback is required. diff --git a/services/analysis-engine/tests/test_supply_chain_policy.py b/services/analysis-engine/tests/test_supply_chain_policy.py index 6a0853944..1d8224c5a 100644 --- a/services/analysis-engine/tests/test_supply_chain_policy.py +++ b/services/analysis-engine/tests/test_supply_chain_policy.py @@ -1275,7 +1275,9 @@ def test_workflow_concurrency_cancels_only_superseded_pr_heads() -> None: workflow = (workflows_dir / workflow_name).read_text(encoding="utf-8") assert "concurrency:" in workflow, workflow_name assert "cancel-in-progress: false" in workflow, workflow_name - assert "contents: read" in workflow or "permissions: read-all" in workflow, workflow_name + assert "contents: read" in workflow or "permissions: read-all" in workflow, ( + workflow_name + ) assert "pull_request:" not in (workflows_dir / "release.yml").read_text(encoding="utf-8") From 32928847536a301f7966a20db9420f08cd1b5354 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 18 Sep 2026 11:03:29 +0900 Subject: [PATCH 112/161] test(node): require canonical npm activation in minimum lane --- .../test_npm_runtime_activation_resilience.py | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/services/analysis-engine/tests/test_npm_runtime_activation_resilience.py b/services/analysis-engine/tests/test_npm_runtime_activation_resilience.py index f4c65474a..79e0fb3c4 100644 --- a/services/analysis-engine/tests/test_npm_runtime_activation_resilience.py +++ b/services/analysis-engine/tests/test_npm_runtime_activation_resilience.py @@ -11,6 +11,9 @@ _REPOSITORY_ROOT = Path(__file__).resolve().parents[3] _BUILD_BASELINE = _REPOSITORY_ROOT / ".github" / "workflows" / "build-baseline.yml" +_NODE_MINIMUM_COMPATIBILITY = ( + _REPOSITORY_ROOT / ".github" / "workflows" / "node-minimum-compatibility.yml" +) _ACTIVATION_HELPER = _REPOSITORY_ROOT / "scripts" / "checks" / "activate_pinned_npm_runtime.sh" _ACTIVATION_COMMAND = "bash scripts/checks/activate_pinned_npm_runtime.sh" @@ -150,6 +153,32 @@ def test_build_baseline_uses_retrying_pinned_npm_activation_before_dependency_re assert npm_consumers == 4 +def test_exact_minimum_node_lane_uses_same_pinned_npm_activation_boundary() -> None: + """Keep the exact-minimum Node consumer on the canonical npm acquisition helper.""" + document = yaml.safe_load(_NODE_MINIMUM_COMPATIBILITY.read_text(encoding="utf-8")) + assert isinstance(document, dict) + jobs = document.get("jobs") + assert isinstance(jobs, dict) + assert set(jobs) == {"node-minimum-compatibility"} + + steps = _job_steps(jobs["node-minimum-compatibility"]) + run_steps = [str(step["run"]) for step in steps if isinstance(step.get("run"), str)] + dependency_index = next( + index + for index, command in enumerate(run_steps) + if command.strip() == "npm ci --ignore-scripts --no-audit --no-fund" + ) + activation_indexes = [ + index + for index, command in enumerate(run_steps) + if command.strip() == _ACTIVATION_COMMAND + ] + + assert activation_indexes == [dependency_index - 1] + assert all("corepack enable npm" not in command for command in run_steps) + assert all("npm --version" not in command for command in run_steps) + + def test_pinned_npm_activation_helper_retries_acquisition_but_never_falls_back() -> None: """Keep admitted timeout recovery bounded while exact npm provenance remains fail closed.""" source = _ACTIVATION_HELPER.read_text(encoding="utf-8") From 97042e151f60fe70ab49a7a6e822964bddeed767 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 18 Sep 2026 11:03:50 +0900 Subject: [PATCH 113/161] fix(ci): reuse pinned npm activation in minimum Node lane --- .github/workflows/node-minimum-compatibility.yml | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/.github/workflows/node-minimum-compatibility.yml b/.github/workflows/node-minimum-compatibility.yml index 35f9ff2b4..afbe96a10 100644 --- a/.github/workflows/node-minimum-compatibility.yml +++ b/.github/workflows/node-minimum-compatibility.yml @@ -14,7 +14,6 @@ env: GIT_CONFIG_COUNT: "1" GIT_CONFIG_KEY_0: init.defaultBranch GIT_CONFIG_VALUE_0: develop - EXPECTED_NPM_VERSION: "10.9.9" jobs: node-minimum-compatibility: @@ -28,12 +27,8 @@ jobs: with: node-version: 22.22.2 package-manager-cache: false - - name: Activate pinned npm runtime - run: corepack enable npm - - name: Verify exact npm lockfile generator and bundled tar - run: | - test "$(npm --version)" = "$EXPECTED_NPM_VERSION" - npm run check:npm-runtime + - name: Activate and verify pinned npm runtime + run: bash scripts/checks/activate_pinned_npm_runtime.sh - name: Install frozen Node dependencies run: npm ci --ignore-scripts --no-audit --no-fund - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 From 3b6ddcef40f3edd9660cf463a5fa535024e79197 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 18 Sep 2026 11:04:22 +0900 Subject: [PATCH 114/161] docs(traceability): record single npm activation owner --- ...runtime-acquisition-failure-classification.md | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/docs/traceability/npm-runtime-acquisition-failure-classification.md b/docs/traceability/npm-runtime-acquisition-failure-classification.md index f2b4615ba..59a2cbcbf 100644 --- a/docs/traceability/npm-runtime-acquisition-failure-classification.md +++ b/docs/traceability/npm-runtime-acquisition-failure-classification.md @@ -8,6 +8,8 @@ PR #1232 exposed a real `ETIMEDOUT` while Corepack acquired the repository-pinne That blacklist leaves a fail-open classification boundary: a future or differently worded trust/provenance failure would be retried merely because BandScope did not recognize its text. Retryability must be positively established instead. Unknown acquisition failures are not evidence of a transient transport condition. +Fresh workflow review also found a second ownership defect after the helper existed: the four native `build-baseline` jobs used the canonical activation helper, while the exact-minimum Node 22.22.2 compatibility lane still invoked `corepack enable npm` inline and then triggered package-manager resolution through `npm --version`. That bypass meant the same repository-pinned npm provenance contract had two acquisition behaviors. A timeout in the minimum-version lane could still fail at the old unbounded boundary, and future changes to the helper's trust classification would not automatically apply there. + ## Constraints - `npm@10.9.9` remains the only accepted package-manager runtime for this owner branch. @@ -16,6 +18,7 @@ That blacklist leaves a fail-open classification boundary: a future or different - The hosted incident actually observed `ETIMEDOUT`; this is the only transient diagnostic admitted by the current policy. - The admitted timeout path is limited to three attempts with 5 s / 10 s backoff. - Any unclassified Corepack failure must stop before `corepack enable npm`, `npm run check:npm-runtime`, or `npm ci` can execute. +- Every repository workflow that consumes Node dependencies under this owner must use the same activation helper immediately before its first `npm ci`; workflow-local Corepack activation is not a second owner implementation. - Error classification remains diagnostic-based because the current Corepack command boundary does not expose a stable machine-readable failure taxonomy to this script. ## Decision @@ -24,6 +27,8 @@ That blacklist leaves a fail-open classification boundary: a future or different This makes signature/integrity failures fail closed without depending on an exhaustive list of current or future Corepack wording. The helper does not disable Corepack verification, alter `COREPACK_INTEGRITY_KEYS`, select another npm version, or guess that an unknown failure is a network event. +The helper is also the single workflow-level activation path for the four native `build-baseline` npm consumers and the exact-minimum Node 22.22.2 compatibility consumer. Each lane calls the helper immediately before its frozen `npm ci`. The minimum-version lane no longer keeps a workflow-local `corepack enable npm` plus separate `npm --version` verification path because the helper already performs exact runtime and bundled-`tar` verification before dependency extraction. + The retry allowlist is intentionally narrow. Additional error codes such as connection reset, DNS retry, or HTTP/server failures must not be admitted from intuition alone; they require a concrete hosted failure, bounded semantics, and a focused regression before this policy expands. ## Rejected alternatives @@ -34,6 +39,7 @@ The retry allowlist is intentionally narrow. Additional error codes such as conn - Disable or weaken Corepack signature verification: rejected because that changes the supply-chain trust boundary rather than repairing availability. - Fall back to Node-bundled npm 10.9.8: rejected because the repository requires npm 10.9.9 and its bundled patched `tar` floor. - Retry `npm ci` or later build/test commands: rejected because those operations have different side effects and failure semantics. +- Keep a separate inline Corepack path in the exact-minimum Node workflow: rejected because it duplicates the same package-manager acquisition contract and can drift from the canonical timeout/trust classifier. ## Evidence and regression @@ -43,7 +49,9 @@ Fresh review found the remaining default-retry defect. RED `b8fcb799ca83c4dbfa56 GREEN `9c39c2a595ac5e192c53ed207df2cec6e188b483` reverses the classifier: only `ETIMEDOUT` is admitted to the bounded retry loop; any other failed acquisition exits immediately. Fixture alignment `68f71e02d47a5cd90e4c2dce474f6d1b0f8ef5e3` makes the positive retry regression emit the same `ETIMEDOUT` class observed in hosted CI, preserving the two-timeout-then-success and three-timeout-exhaustion contracts without using an unspecified failure as evidence of transience. -Predecessor exact head `3983dd216d95dc5f78e78f6b17259ad4c5530ebc` completed all four native Windows/macOS build jobs successfully with exact npm activation. That hosted evidence validates the predecessor command path only; it does not transfer to the moved allowlist-classifier head. +A later workflow sweep found that `.github/workflows/node-minimum-compatibility.yml` still bypassed the helper. RED `32928847536a301f7966a20db9420f08cd1b5354` adds a structural regression requiring the exact-minimum Node consumer to use the canonical helper immediately before frozen dependency installation and forbidding inline `corepack enable npm` / `npm --version` ownership. GREEN `97042e151f60fe70ab49a7a6e822964bddeed767` rewires that workflow to the helper and removes the now-redundant workflow-local npm-version environment variable and verification step. + +Predecessor exact head `3983dd216d95dc5f78e78f6b17259ad4c5530ebc` completed all four native Windows/macOS build jobs successfully with exact npm activation. That hosted evidence validates the predecessor command path only; it does not transfer to later moved heads. The minimum-version-lane repair likewise requires fresh exact-head hosted evidence before merge. ## Risks and claim boundary @@ -51,17 +59,23 @@ The allowlist may reject a future genuinely transient Corepack error that is not Diagnostic matching still depends on upstream text. If Corepack exposes a stable structured error code or typed result, this script should consume that contract instead. This mechanism does not prove package-manager authenticity by itself; authenticity remains Corepack's verification responsibility, while BandScope controls retry and fallback behavior around that boundary. +Structural workflow tests prove command ownership and order, not successful hosted acquisition. A workflow can still fail for runner, registry, Corepack, or dependency reasons; those failures remain visible and must be classified from exact-head evidence rather than suppressed or blindly retried. + ## Follow-up - Keep both the hostile signature-failure and unclassified-failure regressions in the exact-head gate. +- Keep every npm-consuming owner workflow on the canonical activation helper; a new inline Corepack activation path is a repair finding. - Expand the transient allowlist only from exact observed evidence plus a focused regression and documented retry safety. - If Corepack introduces a stable structured failure classification, replace diagnostic-string matching with that contract. - Treat any unclassified failure that reaches sleep/retry as a repair finding, not as permission to broaden fallback behavior. +- Require fresh hosted success for the exact-minimum Node 22.22.2 lane and native build lanes on the unchanged merge candidate head. ## Security Notes The package-manager acquisition diagnostic is untrusted upstream text used only for a bounded classification decision and stderr evidence. It is never evaluated or interpolated into a shell command. The trust boundary is `corepack install --global` returning non-zero: only the exact observed `ETIMEDOUT` token permits another attempt; all other results fail closed before npm activation or dependency extraction. No secret, token, package payload, or mutable version selector is logged by this policy. +Centralizing workflow activation does not broaden permissions. The helper operates with the same repository checkout and runner process privileges the inline commands already had; the change removes a duplicate acquisition path rather than introducing a new credential or network capability. + ## References Node.js contributors. (2026). *Corepack npm registry signature verification* [Source code]. GitHub. https://github.com/nodejs/corepack/blob/d4dcb1f89741603e776bba9d457425750fa26987/sources/npmRegistryUtils.ts From e2420beb411da4fce7c13d7d9c427bf652269008 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 18 Sep 2026 11:12:46 +0900 Subject: [PATCH 115/161] test(node): align minimum-lane contract with npm activation owner --- .../tests/test_node_runtime_contract.py | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/services/analysis-engine/tests/test_node_runtime_contract.py b/services/analysis-engine/tests/test_node_runtime_contract.py index f67952978..10876152c 100644 --- a/services/analysis-engine/tests/test_node_runtime_contract.py +++ b/services/analysis-engine/tests/test_node_runtime_contract.py @@ -73,7 +73,7 @@ def test_eslint_10_9_1_intent_is_preserved_in_both_workspaces_and_lock() -> None def test_minimum_node_lane_runs_complete_suite_with_pinned_npm() -> None: - """Exercise the exact Node floor after activating the reviewed npm runtime.""" + """Exercise the exact Node floor through the canonical npm activation owner.""" workflow = (ROOT / ".github/workflows/node-minimum-compatibility.yml").read_text( encoding="utf-8" ) @@ -88,9 +88,7 @@ def test_minimum_node_lane_runs_complete_suite_with_pinned_npm() -> None: required_fragments = ( "node-version: 22.22.2", "package-manager-cache: false", - "corepack enable npm", - 'test "$(npm --version)" = "$EXPECTED_NPM_VERSION"', - "npm run check:npm-runtime", + "bash scripts/checks/activate_pinned_npm_runtime.sh", "npm ci --ignore-scripts --no-audit --no-fund", "npm run lint", "npm run typecheck", @@ -103,7 +101,19 @@ def test_minimum_node_lane_runs_complete_suite_with_pinned_npm() -> None: for fragment in required_fragments: assert fragment in body, f"minimum-version job is missing: {fragment}" - assert f'EXPECTED_NPM_VERSION: "{EXPECTED_NPM_VERSION}"' in workflow + activation_boundary = ( + " - name: Activate and verify pinned npm runtime\n" + " run: bash scripts/checks/activate_pinned_npm_runtime.sh\n" + " - name: Install frozen Node dependencies\n" + " run: npm ci --ignore-scripts --no-audit --no-fund" + ) + assert activation_boundary in body + + for duplicate_activation in ("corepack enable npm", "npm --version"): + assert duplicate_activation not in body, ( + "minimum-version workflow must delegate npm activation to the canonical helper: " + f"{duplicate_activation}" + ) for mutable_command in ("npm install ", "npm update ", "npx "): assert mutable_command not in body, ( @@ -112,7 +122,7 @@ def test_minimum_node_lane_runs_complete_suite_with_pinned_npm() -> None: ) setup_node = body.split("- uses: actions/setup-node@", maxsplit=1)[1].split( - "- name: Activate pinned npm runtime", maxsplit=1 + "- name: Activate and verify pinned npm runtime", maxsplit=1 )[0] assert "cache: npm" not in setup_node assert "package-manager-cache: false" in setup_node From 4830abb4db7b0741ee202582de721af0317750ce Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 18 Sep 2026 11:16:47 +0900 Subject: [PATCH 116/161] test(node): expose duplicate CI npm activation owners --- .../tests/test_node_runtime_contract.py | 78 +++++++++++-------- 1 file changed, 47 insertions(+), 31 deletions(-) diff --git a/services/analysis-engine/tests/test_node_runtime_contract.py b/services/analysis-engine/tests/test_node_runtime_contract.py index 10876152c..0d3ece66b 100644 --- a/services/analysis-engine/tests/test_node_runtime_contract.py +++ b/services/analysis-engine/tests/test_node_runtime_contract.py @@ -12,6 +12,7 @@ EXPECTED_NPM_VERSION = "10.9.9" EXPECTED_JSDOM_RANGE = "^30.0.1" EXPECTED_ESLINT_RANGE = "^10.9.1" +CANONICAL_NPM_ACTIVATION = "bash scripts/checks/activate_pinned_npm_runtime.sh" def _load_json(path: str) -> dict[str, object]: @@ -24,6 +25,16 @@ def _supports_band_node(version: tuple[int, int, int]) -> bool: return EXPECTED_NODE_FLOOR <= version < (23, 0, 0) +def _workflow_job(workflow: str, job_name: str) -> str: + """Return one top-level workflow job body for structural contract checks.""" + match = re.search( + rf"(?ms)^ {re.escape(job_name)}:\n(?P.*?)(?=^ [a-zA-Z0-9_-]+:\n|\Z)", + workflow, + ) + assert match is not None, f"workflow must define {job_name}" + return match.group("body") + + def test_node_engine_floor_matches_jsdom_30_runtime_contract() -> None: """Root manifest and lock metadata must publish the same Node compatibility floor.""" package = _load_json("package.json") @@ -49,7 +60,8 @@ def test_jsdom_30_is_adopted_in_manifest_and_lock() -> None: assert desktop["devDependencies"]["jsdom"] == EXPECTED_JSDOM_RANGE assert ( - package_lock["packages"]["apps/desktop"]["devDependencies"]["jsdom"] == EXPECTED_JSDOM_RANGE + package_lock["packages"]["apps/desktop"]["devDependencies"]["jsdom"] + == EXPECTED_JSDOM_RANGE ) assert package_lock["packages"]["apps/desktop/node_modules/jsdom"]["version"] == "30.0.1" @@ -72,23 +84,20 @@ def test_eslint_10_9_1_intent_is_preserved_in_both_workspaces_and_lock() -> None ) -def test_minimum_node_lane_runs_complete_suite_with_pinned_npm() -> None: - """Exercise the exact Node floor through the canonical npm activation owner.""" - workflow = (ROOT / ".github/workflows/node-minimum-compatibility.yml").read_text( - encoding="utf-8" - ) +def test_minimum_node_lane_runs_in_registered_ci_with_pinned_npm() -> None: + """Exercise the exact Node floor inside the already-registered CI workflow.""" + workflow = (ROOT / ".github/workflows/ci.yml").read_text(encoding="utf-8") + standalone = ROOT / ".github/workflows/node-minimum-compatibility.yml" - match = re.search( - r"(?ms)^ node-minimum-compatibility:\n(?P.*?)(?=^ [a-zA-Z0-9_-]+:\n|\Z)", - workflow, + assert not standalone.exists(), ( + "the exact-minimum lane belongs in registered ci.yml, not a second workflow owner" ) - assert match is not None, "minimum-version workflow must define node-minimum-compatibility" - body = match.group("body") + body = _workflow_job(workflow, "node-minimum-compatibility") required_fragments = ( "node-version: 22.22.2", "package-manager-cache: false", - "bash scripts/checks/activate_pinned_npm_runtime.sh", + CANONICAL_NPM_ACTIVATION, "npm ci --ignore-scripts --no-audit --no-fund", "npm run lint", "npm run typecheck", @@ -101,31 +110,38 @@ def test_minimum_node_lane_runs_complete_suite_with_pinned_npm() -> None: for fragment in required_fragments: assert fragment in body, f"minimum-version job is missing: {fragment}" - activation_boundary = ( - " - name: Activate and verify pinned npm runtime\n" - " run: bash scripts/checks/activate_pinned_npm_runtime.sh\n" - " - name: Install frozen Node dependencies\n" - " run: npm ci --ignore-scripts --no-audit --no-fund" - ) - assert activation_boundary in body - - for duplicate_activation in ("corepack enable npm", "npm --version"): - assert duplicate_activation not in body, ( - "minimum-version workflow must delegate npm activation to the canonical helper: " - f"{duplicate_activation}" - ) - for mutable_command in ("npm install ", "npm update ", "npx "): assert mutable_command not in body, ( "minimum-version workflow must not resolve dependencies mutably: " f"{mutable_command.strip()}" ) - setup_node = body.split("- uses: actions/setup-node@", maxsplit=1)[1].split( - "- name: Activate and verify pinned npm runtime", maxsplit=1 - )[0] - assert "cache: npm" not in setup_node - assert "package-manager-cache: false" in setup_node + +def test_all_registered_ci_npm_consumers_delegate_activation_to_helper() -> None: + """Keep one fail-closed npm acquisition policy across every CI consumer.""" + workflow = (ROOT / ".github/workflows/ci.yml").read_text(encoding="utf-8") + + for job_name in ( + "lock-validation", + "verify", + "rust-check", + "node-minimum-compatibility", + ): + body = _workflow_job(workflow, job_name) + assert body.count(CANONICAL_NPM_ACTIVATION) == 1, ( + f"{job_name} must delegate npm activation exactly once to the canonical helper" + ) + for duplicate_activation in ("corepack enable npm", "npm --version"): + assert duplicate_activation not in body, ( + f"{job_name} must not retain workflow-local npm activation: " + f"{duplicate_activation}" + ) + + activation_offset = body.index(CANONICAL_NPM_ACTIVATION) + install_offset = body.index("npm ci") + assert activation_offset < install_offset, ( + f"{job_name} must verify the exact npm runtime before frozen dependency admission" + ) def test_repository_no_longer_advertises_node_22_13_floor() -> None: From 99b0707c61099a170695b66f644fd90162fb7f8c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 18 Sep 2026 11:17:31 +0900 Subject: [PATCH 117/161] fix(ci): centralize npm activation in registered CI --- .github/workflows/ci.yml | 75 ++++++++++++++----- .../workflows/node-minimum-compatibility.yml | 67 ----------------- 2 files changed, 56 insertions(+), 86 deletions(-) delete mode 100644 .github/workflows/node-minimum-compatibility.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6e743c2ff..483dedc15 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,6 @@ env: GIT_CONFIG_COUNT: "1" GIT_CONFIG_KEY_0: init.defaultBranch GIT_CONFIG_VALUE_0: develop - EXPECTED_NPM_VERSION: "10.9.9" jobs: lock-validation: @@ -37,12 +36,8 @@ jobs: with: node-version: "22.22.3" package-manager-cache: false - - name: Activate pinned npm runtime - run: corepack enable npm - - name: Verify exact npm lockfile generator and bundled tar - run: | - test "$(npm --version)" = "$EXPECTED_NPM_VERSION" - npm run check:npm-runtime + - name: Activate and verify pinned npm runtime + run: bash scripts/checks/activate_pinned_npm_runtime.sh - name: Validate the frozen package lock without lifecycle execution run: npm ci --ignore-scripts --no-audit --no-fund - name: Reject manifest or lockfile drift @@ -60,16 +55,12 @@ jobs: with: node-version: "22.22.3" package-manager-cache: false - - name: Activate pinned npm runtime - run: corepack enable npm - - name: Verify exact npm lockfile generator and bundled tar - run: | - test "$(npm --version)" = "$EXPECTED_NPM_VERSION" - npm run check:npm-runtime - uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2 with: version: "0.8.6" enable-cache: false + - name: Activate and verify pinned npm runtime + run: bash scripts/checks/activate_pinned_npm_runtime.sh - name: Install node dependencies run: npm ci - name: Sync Python dependencies @@ -99,14 +90,10 @@ jobs: with: node-version: "22.22.3" package-manager-cache: false - - name: Activate pinned npm runtime - run: corepack enable npm - - name: Verify exact npm lockfile generator and bundled tar - run: | - test "$(npm --version)" = "$EXPECTED_NPM_VERSION" - npm run check:npm-runtime - name: Install stable Rust toolchain run: rustup toolchain install stable --profile minimal + - name: Activate and verify pinned npm runtime + run: bash scripts/checks/activate_pinned_npm_runtime.sh - name: Install node dependencies run: npm ci - name: Build frontend @@ -115,3 +102,53 @@ jobs: run: cargo +stable check --manifest-path apps/desktop/src-tauri/Cargo.toml --locked - name: Test Tauri shell run: cargo +stable test --manifest-path apps/desktop/src-tauri/Cargo.toml --locked + + node-minimum-compatibility: + name: gate / ci / node-minimum-compatibility + runs-on: macos-15 + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: 22.22.2 + package-manager-cache: false + - name: Activate and verify pinned npm runtime + run: bash scripts/checks/activate_pinned_npm_runtime.sh + - name: Install frozen Node dependencies + run: npm ci --ignore-scripts --no-audit --no-fund + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: "3.12" + - uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2 + with: + version: "0.8.6" + enable-cache: false + - name: Sync Python dependencies + run: uv sync --project services/analysis-engine --group dev --frozen + - name: Install stable Rust toolchain + run: rustup toolchain install stable --profile minimal + - name: Build and install Rust numeric extension + shell: bash + run: | + VENV_PY="$PWD/services/analysis-engine/.venv/bin/python" + uvx maturin@1.9.6 build --release \ + --manifest-path services/analysis-engine/rust/Cargo.toml \ + --interpreter "$VENV_PY" \ + --out services/analysis-engine/rust/dist + uv pip install --python "$VENV_PY" services/analysis-engine/rust/dist/*.whl + - name: Lint + run: npm run lint + - name: Typecheck + run: npm run typecheck + - name: Test with measured coverage + run: npm run test + - name: Build production workspaces + run: npm run build + - name: Build Storybook + run: npm run build-storybook --workspace @bandscope/desktop + - name: Check Tauri shell + run: cargo +stable check --manifest-path apps/desktop/src-tauri/Cargo.toml --locked + - name: Test Tauri shell + run: cargo +stable test --manifest-path apps/desktop/src-tauri/Cargo.toml --locked diff --git a/.github/workflows/node-minimum-compatibility.yml b/.github/workflows/node-minimum-compatibility.yml deleted file mode 100644 index afbe96a10..000000000 --- a/.github/workflows/node-minimum-compatibility.yml +++ /dev/null @@ -1,67 +0,0 @@ -name: node-minimum-compatibility - -on: - pull_request: - push: - branches: - - develop - - main - -permissions: - contents: read - -env: - GIT_CONFIG_COUNT: "1" - GIT_CONFIG_KEY_0: init.defaultBranch - GIT_CONFIG_VALUE_0: develop - -jobs: - node-minimum-compatibility: - name: gate / build / node-minimum-compatibility - runs-on: macos-15 - steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - persist-credentials: false - - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 - with: - node-version: 22.22.2 - package-manager-cache: false - - name: Activate and verify pinned npm runtime - run: bash scripts/checks/activate_pinned_npm_runtime.sh - - name: Install frozen Node dependencies - run: npm ci --ignore-scripts --no-audit --no-fund - - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 - with: - python-version: "3.12" - - uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2 - with: - version: "0.8.6" - enable-cache: false - - name: Install stable Rust toolchain - run: rustup toolchain install stable --profile minimal - - name: Sync frozen Python dependencies - run: uv sync --project services/analysis-engine --group dev --frozen - - name: Build and install Rust numeric extension - shell: bash - run: | - VENV_PY="$PWD/services/analysis-engine/.venv/bin/python" - uvx maturin@1.9.6 build --release \ - --manifest-path services/analysis-engine/rust/Cargo.toml \ - --interpreter "$VENV_PY" \ - --out services/analysis-engine/rust/dist - uv pip install --python "$VENV_PY" services/analysis-engine/rust/dist/*.whl - - name: Lint - run: npm run lint - - name: Typecheck - run: npm run typecheck - - name: Test with measured coverage - run: npm run test - - name: Build production workspaces - run: npm run build - - name: Build Storybook - run: npm run build-storybook --workspace @bandscope/desktop - - name: Check Tauri shell - run: cargo +stable check --manifest-path apps/desktop/src-tauri/Cargo.toml --locked - - name: Test Tauri shell - run: cargo +stable test --manifest-path apps/desktop/src-tauri/Cargo.toml --locked From c010a66fedec3647274a27900a11203e07ee671e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 18 Sep 2026 11:18:23 +0900 Subject: [PATCH 118/161] docs(node): trace registered CI activation ownership --- ...time-acquisition-failure-classification.md | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/docs/traceability/npm-runtime-acquisition-failure-classification.md b/docs/traceability/npm-runtime-acquisition-failure-classification.md index 59a2cbcbf..a7f32ee20 100644 --- a/docs/traceability/npm-runtime-acquisition-failure-classification.md +++ b/docs/traceability/npm-runtime-acquisition-failure-classification.md @@ -8,7 +8,9 @@ PR #1232 exposed a real `ETIMEDOUT` while Corepack acquired the repository-pinne That blacklist leaves a fail-open classification boundary: a future or differently worded trust/provenance failure would be retried merely because BandScope did not recognize its text. Retryability must be positively established instead. Unknown acquisition failures are not evidence of a transient transport condition. -Fresh workflow review also found a second ownership defect after the helper existed: the four native `build-baseline` jobs used the canonical activation helper, while the exact-minimum Node 22.22.2 compatibility lane still invoked `corepack enable npm` inline and then triggered package-manager resolution through `npm --version`. That bypass meant the same repository-pinned npm provenance contract had two acquisition behaviors. A timeout in the minimum-version lane could still fail at the old unbounded boundary, and future changes to the helper's trust classification would not automatically apply there. +Fresh workflow review then found two ownership defects after the helper existed. First, the exact-minimum Node 22.22.2 compatibility lane still invoked `corepack enable npm` inline and triggered package-manager resolution through `npm --version`. Second, after that lane was moved to the helper, the already-registered `ci.yml` still contained three separate inline Corepack/npm-version paths in `lock-validation`, `verify`, and `rust-check`. Those jobs are part of the normal pull-request CI path, so leaving them inline meant the same repository-pinned npm provenance contract still had multiple acquisition behaviors. + +The exact-minimum lane also lived in a newly added standalone workflow. Fresh exact-head pull-request workflow inventories did not materialize that standalone lane while the existing `ci` workflow did materialize. This repository-specific evidence is not promoted into a universal GitHub Actions rule; it is sufficient to show that the intended exact-minimum evidence was absent from the live PR generation. A compatibility gate that is not present in the observed pull-request workflow inventory is not merge evidence. ## Constraints @@ -18,7 +20,8 @@ Fresh workflow review also found a second ownership defect after the helper exis - The hosted incident actually observed `ETIMEDOUT`; this is the only transient diagnostic admitted by the current policy. - The admitted timeout path is limited to three attempts with 5 s / 10 s backoff. - Any unclassified Corepack failure must stop before `corepack enable npm`, `npm run check:npm-runtime`, or `npm ci` can execute. -- Every repository workflow that consumes Node dependencies under this owner must use the same activation helper immediately before its first `npm ci`; workflow-local Corepack activation is not a second owner implementation. +- Every repository workflow job that consumes Node dependencies under this owner must use the same activation helper before its first `npm ci`; workflow-local Corepack activation is not a second owner implementation. +- The exact-minimum Node compatibility job must live in an already-materialized PR CI workflow rather than relying on a second workflow whose pull-request run is absent from the observed inventory. - Error classification remains diagnostic-based because the current Corepack command boundary does not expose a stable machine-readable failure taxonomy to this script. ## Decision @@ -27,7 +30,7 @@ Fresh workflow review also found a second ownership defect after the helper exis This makes signature/integrity failures fail closed without depending on an exhaustive list of current or future Corepack wording. The helper does not disable Corepack verification, alter `COREPACK_INTEGRITY_KEYS`, select another npm version, or guess that an unknown failure is a network event. -The helper is also the single workflow-level activation path for the four native `build-baseline` npm consumers and the exact-minimum Node 22.22.2 compatibility consumer. Each lane calls the helper immediately before its frozen `npm ci`. The minimum-version lane no longer keeps a workflow-local `corepack enable npm` plus separate `npm --version` verification path because the helper already performs exact runtime and bundled-`tar` verification before dependency extraction. +The helper is the single workflow-level activation path for the four native `build-baseline` npm consumers and the four npm-consuming jobs in the registered `ci` workflow: `lock-validation`, `verify`, `rust-check`, and `node-minimum-compatibility`. The three existing CI jobs no longer keep workflow-local `corepack enable npm` plus separate `npm --version` verification. The exact-minimum Node 22.22.2 lane is now a job in `.github/workflows/ci.yml`; the standalone `.github/workflows/node-minimum-compatibility.yml` owner is removed. The retry allowlist is intentionally narrow. Additional error codes such as connection reset, DNS retry, or HTTP/server failures must not be admitted from intuition alone; they require a concrete hosted failure, bounded semantics, and a focused regression before this policy expands. @@ -39,7 +42,8 @@ The retry allowlist is intentionally narrow. Additional error codes such as conn - Disable or weaken Corepack signature verification: rejected because that changes the supply-chain trust boundary rather than repairing availability. - Fall back to Node-bundled npm 10.9.8: rejected because the repository requires npm 10.9.9 and its bundled patched `tar` floor. - Retry `npm ci` or later build/test commands: rejected because those operations have different side effects and failure semantics. -- Keep a separate inline Corepack path in the exact-minimum Node workflow: rejected because it duplicates the same package-manager acquisition contract and can drift from the canonical timeout/trust classifier. +- Keep inline Corepack activation in any CI job: rejected because it duplicates the same package-manager acquisition contract and can drift from the canonical timeout/trust classifier. +- Keep the exact-minimum compatibility check as a second standalone workflow after its PR runs are absent from the observed exact-head workflow inventory: rejected because source presence without live PR execution does not satisfy the compatibility evidence requirement. ## Evidence and regression @@ -49,9 +53,11 @@ Fresh review found the remaining default-retry defect. RED `b8fcb799ca83c4dbfa56 GREEN `9c39c2a595ac5e192c53ed207df2cec6e188b483` reverses the classifier: only `ETIMEDOUT` is admitted to the bounded retry loop; any other failed acquisition exits immediately. Fixture alignment `68f71e02d47a5cd90e4c2dce474f6d1b0f8ef5e3` makes the positive retry regression emit the same `ETIMEDOUT` class observed in hosted CI, preserving the two-timeout-then-success and three-timeout-exhaustion contracts without using an unspecified failure as evidence of transience. -A later workflow sweep found that `.github/workflows/node-minimum-compatibility.yml` still bypassed the helper. RED `32928847536a301f7966a20db9420f08cd1b5354` adds a structural regression requiring the exact-minimum Node consumer to use the canonical helper immediately before frozen dependency installation and forbidding inline `corepack enable npm` / `npm --version` ownership. GREEN `97042e151f60fe70ab49a7a6e822964bddeed767` rewires that workflow to the helper and removes the now-redundant workflow-local npm-version environment variable and verification step. +A later workflow sweep found that the exact-minimum workflow still bypassed the helper. RED `32928847536a301f7966a20db9420f08cd1b5354` required that consumer to use the canonical helper and forbade inline `corepack enable npm` / `npm --version`; GREEN `97042e151f60fe70ab49a7a6e822964bddeed767` rewired the workflow. Exact-tree review then found the structural regression itself still asserted the retired inline path, and `e2420beb411da4fce7c13d7d9c427bf652269008` aligned that regression with the helper contract. + +A fresh live `ci.yml` review then exposed the remaining duplicate owners. RED `4830abb4db7b0741ee202582de721af0317750ce` requires the exact-minimum job to live in registered `ci.yml`, rejects the standalone workflow, and requires `lock-validation`, `verify`, `rust-check`, and `node-minimum-compatibility` to delegate activation to the helper with no inline `corepack enable npm` / `npm --version` path. GREEN `99b0707c61099a170695b66f644fd90162fb7f8c` moves the exact-minimum job into `ci.yml`, converts the three existing CI jobs to the helper, removes the redundant workflow-global npm version variable, and deletes the standalone workflow. -Predecessor exact head `3983dd216d95dc5f78e78f6b17259ad4c5530ebc` completed all four native Windows/macOS build jobs successfully with exact npm activation. That hosted evidence validates the predecessor command path only; it does not transfer to later moved heads. The minimum-version-lane repair likewise requires fresh exact-head hosted evidence before merge. +Predecessor exact head `3983dd216d95dc5f78e78f6b17259ad4c5530ebc` completed all four native Windows/macOS build jobs successfully with exact npm activation. That hosted evidence validates the predecessor command path only; it does not transfer to later moved heads. Exact `99b0707c...` and its traceability descendants require fresh hosted evidence, including the newly registered exact-minimum CI job. ## Risks and claim boundary @@ -59,22 +65,23 @@ The allowlist may reject a future genuinely transient Corepack error that is not Diagnostic matching still depends on upstream text. If Corepack exposes a stable structured error code or typed result, this script should consume that contract instead. This mechanism does not prove package-manager authenticity by itself; authenticity remains Corepack's verification responsibility, while BandScope controls retry and fallback behavior around that boundary. -Structural workflow tests prove command ownership and order, not successful hosted acquisition. A workflow can still fail for runner, registry, Corepack, or dependency reasons; those failures remain visible and must be classified from exact-head evidence rather than suppressed or blindly retried. +Structural workflow tests prove command ownership and order, not successful hosted acquisition. Moving the exact-minimum lane into `ci.yml` is an evidence-topology repair, not proof that Node 22.22.2 or npm acquisition succeeds on a hosted runner. A job can still fail for runner, registry, Corepack, dependency, Rust, Python, or product-test reasons; those failures remain visible and must be classified from exact-head evidence rather than suppressed or blindly retried. ## Follow-up - Keep both the hostile signature-failure and unclassified-failure regressions in the exact-head gate. -- Keep every npm-consuming owner workflow on the canonical activation helper; a new inline Corepack activation path is a repair finding. +- Keep every npm-consuming owner workflow job on the canonical activation helper; a new inline Corepack activation path is a repair finding. +- Keep the exact-minimum Node job in the registered CI workflow unless live evidence demonstrates a different canonical execution topology. - Expand the transient allowlist only from exact observed evidence plus a focused regression and documented retry safety. - If Corepack introduces a stable structured failure classification, replace diagnostic-string matching with that contract. - Treat any unclassified failure that reaches sleep/retry as a repair finding, not as permission to broaden fallback behavior. -- Require fresh hosted success for the exact-minimum Node 22.22.2 lane and native build lanes on the unchanged merge candidate head. +- Require fresh hosted success for the exact-minimum Node 22.22.2 job, normal CI jobs, and native build lanes on the unchanged merge candidate head. ## Security Notes The package-manager acquisition diagnostic is untrusted upstream text used only for a bounded classification decision and stderr evidence. It is never evaluated or interpolated into a shell command. The trust boundary is `corepack install --global` returning non-zero: only the exact observed `ETIMEDOUT` token permits another attempt; all other results fail closed before npm activation or dependency extraction. No secret, token, package payload, or mutable version selector is logged by this policy. -Centralizing workflow activation does not broaden permissions. The helper operates with the same repository checkout and runner process privileges the inline commands already had; the change removes a duplicate acquisition path rather than introducing a new credential or network capability. +Centralizing workflow activation does not broaden permissions. The helper operates with the same repository checkout and runner process privileges the inline commands already had; the change removes duplicate acquisition paths and places the exact-minimum job inside the existing CI execution surface rather than adding a new credential or network capability. ## References From 78bcc37334c512e15289503294bceaff57c5f927 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 18 Sep 2026 11:24:28 +0900 Subject: [PATCH 119/161] fix(node): align activation regressions with stacked CI owner --- .../tests/test_node_runtime_contract.py | 7 ++-- ...runtime_activation_nontransient_failure.py | 14 ++++++-- .../test_npm_runtime_activation_resilience.py | 32 +++++++++++-------- 3 files changed, 35 insertions(+), 18 deletions(-) diff --git a/services/analysis-engine/tests/test_node_runtime_contract.py b/services/analysis-engine/tests/test_node_runtime_contract.py index 0d3ece66b..024075788 100644 --- a/services/analysis-engine/tests/test_node_runtime_contract.py +++ b/services/analysis-engine/tests/test_node_runtime_contract.py @@ -63,7 +63,9 @@ def test_jsdom_30_is_adopted_in_manifest_and_lock() -> None: package_lock["packages"]["apps/desktop"]["devDependencies"]["jsdom"] == EXPECTED_JSDOM_RANGE ) - assert package_lock["packages"]["apps/desktop/node_modules/jsdom"]["version"] == "30.0.1" + assert ( + package_lock["packages"]["apps/desktop/node_modules/jsdom"]["version"] == "30.0.1" + ) def test_eslint_10_9_1_intent_is_preserved_in_both_workspaces_and_lock() -> None: @@ -160,7 +162,8 @@ def test_repository_no_longer_advertises_node_22_13_floor() -> None: stale = [ path for path in audited_paths - if path != "package-lock.json" and "22.13" in (ROOT / path).read_text(encoding="utf-8") + if path != "package-lock.json" + and "22.13" in (ROOT / path).read_text(encoding="utf-8") ] package_lock = _load_json("package-lock.json") if package_lock["packages"][""]["engines"] != {"node": EXPECTED_NODE_ENGINE}: diff --git a/services/analysis-engine/tests/test_npm_runtime_activation_nontransient_failure.py b/services/analysis-engine/tests/test_npm_runtime_activation_nontransient_failure.py index acce2d267..c9592ab0f 100644 --- a/services/analysis-engine/tests/test_npm_runtime_activation_nontransient_failure.py +++ b/services/analysis-engine/tests/test_npm_runtime_activation_nontransient_failure.py @@ -9,7 +9,9 @@ import pytest _REPOSITORY_ROOT = Path(__file__).resolve().parents[3] -_ACTIVATION_HELPER = _REPOSITORY_ROOT / "scripts" / "checks" / "activate_pinned_npm_runtime.sh" +_ACTIVATION_HELPER = ( + _REPOSITORY_ROOT / "scripts" / "checks" / "activate_pinned_npm_runtime.sh" +) def _write_executable(path: Path, content: str) -> None: @@ -97,7 +99,10 @@ def _assert_immediate_failure( assert not npm_log.exists() -@pytest.mark.skipif(os.name == "nt", reason="shell helper is exercised by hosted Windows lanes") +@pytest.mark.skipif( + os.name == "nt", + reason="shell helper is exercised by hosted Windows lanes", +) def test_pinned_npm_activation_does_not_retry_signature_failure(tmp_path: Path) -> None: """A provenance/signature failure must fail immediately instead of being retried.""" result = _run_corepack_failure( @@ -109,7 +114,10 @@ def test_pinned_npm_activation_does_not_retry_signature_failure(tmp_path: Path) assert "Signature does not match" in result[0].stderr -@pytest.mark.skipif(os.name == "nt", reason="shell helper is exercised by hosted Windows lanes") +@pytest.mark.skipif( + os.name == "nt", + reason="shell helper is exercised by hosted Windows lanes", +) def test_pinned_npm_activation_does_not_retry_unknown_failure(tmp_path: Path) -> None: """An unclassified Corepack failure must fail closed instead of being guessed transient.""" result = _run_corepack_failure( diff --git a/services/analysis-engine/tests/test_npm_runtime_activation_resilience.py b/services/analysis-engine/tests/test_npm_runtime_activation_resilience.py index 79e0fb3c4..b77a8311f 100644 --- a/services/analysis-engine/tests/test_npm_runtime_activation_resilience.py +++ b/services/analysis-engine/tests/test_npm_runtime_activation_resilience.py @@ -11,10 +11,10 @@ _REPOSITORY_ROOT = Path(__file__).resolve().parents[3] _BUILD_BASELINE = _REPOSITORY_ROOT / ".github" / "workflows" / "build-baseline.yml" -_NODE_MINIMUM_COMPATIBILITY = ( - _REPOSITORY_ROOT / ".github" / "workflows" / "node-minimum-compatibility.yml" +_CI_WORKFLOW = _REPOSITORY_ROOT / ".github" / "workflows" / "ci.yml" +_ACTIVATION_HELPER = ( + _REPOSITORY_ROOT / "scripts" / "checks" / "activate_pinned_npm_runtime.sh" ) -_ACTIVATION_HELPER = _REPOSITORY_ROOT / "scripts" / "checks" / "activate_pinned_npm_runtime.sh" _ACTIVATION_COMMAND = "bash scripts/checks/activate_pinned_npm_runtime.sh" @@ -131,7 +131,9 @@ def test_build_baseline_uses_retrying_pinned_npm_activation_before_dependency_re npm_consumers = 0 for job_name, job in jobs.items(): steps = _job_steps(job) - run_steps = [str(step["run"]) for step in steps if isinstance(step.get("run"), str)] + run_steps = [ + str(step["run"]) for step in steps if isinstance(step.get("run"), str) + ] dependency_index = next( (index for index, command in enumerate(run_steps) if command.strip() == "npm ci"), None, @@ -153,13 +155,13 @@ def test_build_baseline_uses_retrying_pinned_npm_activation_before_dependency_re assert npm_consumers == 4 -def test_exact_minimum_node_lane_uses_same_pinned_npm_activation_boundary() -> None: +def test_registered_ci_exact_minimum_node_lane_uses_same_pinned_npm_activation_boundary() -> None: """Keep the exact-minimum Node consumer on the canonical npm acquisition helper.""" - document = yaml.safe_load(_NODE_MINIMUM_COMPATIBILITY.read_text(encoding="utf-8")) + document = yaml.safe_load(_CI_WORKFLOW.read_text(encoding="utf-8")) assert isinstance(document, dict) jobs = document.get("jobs") assert isinstance(jobs, dict) - assert set(jobs) == {"node-minimum-compatibility"} + assert "node-minimum-compatibility" in jobs steps = _job_steps(jobs["node-minimum-compatibility"]) run_steps = [str(step["run"]) for step in steps if isinstance(step.get("run"), str)] @@ -203,9 +205,11 @@ def test_pinned_npm_activation_recovers_after_two_transient_acquisition_failures tmp_path: Path, ) -> None: """Retry admitted ETIMEDOUT acquisition, then audit the acquired npm before success.""" - completed, corepack_count, sleep_log, npm_log, corepack_enable_log = _run_activation_helper( - tmp_path, - acquisition_failures=2, + completed, corepack_count, sleep_log, npm_log, corepack_enable_log = ( + _run_activation_helper( + tmp_path, + acquisition_failures=2, + ) ) assert completed.returncode == 0, completed.stderr @@ -225,9 +229,11 @@ def test_pinned_npm_activation_fails_closed_after_bounded_timeout_exhaustion( tmp_path: Path, ) -> None: """Stop after three admitted timeout failures without enabling or invoking fallback npm.""" - completed, corepack_count, sleep_log, npm_log, corepack_enable_log = _run_activation_helper( - tmp_path, - acquisition_failures=99, + completed, corepack_count, sleep_log, npm_log, corepack_enable_log = ( + _run_activation_helper( + tmp_path, + acquisition_failures=99, + ) ) assert completed.returncode != 0 From 636b4776755dba119f0cf68c656a2d7aa3ea85c8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 18 Sep 2026 11:25:22 +0900 Subject: [PATCH 120/161] docs(node): record lint RCA and canonical prerequisite stack --- ...untime-acquisition-failure-classification.md | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/docs/traceability/npm-runtime-acquisition-failure-classification.md b/docs/traceability/npm-runtime-acquisition-failure-classification.md index a7f32ee20..6a7d41510 100644 --- a/docs/traceability/npm-runtime-acquisition-failure-classification.md +++ b/docs/traceability/npm-runtime-acquisition-failure-classification.md @@ -23,6 +23,7 @@ The exact-minimum lane also lived in a newly added standalone workflow. Fresh ex - Every repository workflow job that consumes Node dependencies under this owner must use the same activation helper before its first `npm ci`; workflow-local Corepack activation is not a second owner implementation. - The exact-minimum Node compatibility job must live in an already-materialized PR CI workflow rather than relying on a second workflow whose pull-request run is absent from the observed inventory. - Error classification remains diagnostic-based because the current Corepack command boundary does not expose a stable machine-readable failure taxonomy to this script. +- Protected-base formatting debt owned by another PR is consumed by stack ancestry; it is not copied into this owner as an unrelated patch. ## Decision @@ -44,6 +45,7 @@ The retry allowlist is intentionally narrow. Additional error codes such as conn - Retry `npm ci` or later build/test commands: rejected because those operations have different side effects and failure semantics. - Keep inline Corepack activation in any CI job: rejected because it duplicates the same package-manager acquisition contract and can drift from the canonical timeout/trust classifier. - Keep the exact-minimum compatibility check as a second standalone workflow after its PR runs are absent from the observed exact-head workflow inventory: rejected because source presence without live PR execution does not satisfy the compatibility evidence requirement. +- Copy the protected-base Ruff fix from #1176 into #896: rejected because #1176 is the canonical single writer for that prerequisite and the dependent branch can inherit it through ordinary non-force ancestry. ## Evidence and regression @@ -57,7 +59,11 @@ A later workflow sweep found that the exact-minimum workflow still bypassed the A fresh live `ci.yml` review then exposed the remaining duplicate owners. RED `4830abb4db7b0741ee202582de721af0317750ce` requires the exact-minimum job to live in registered `ci.yml`, rejects the standalone workflow, and requires `lock-validation`, `verify`, `rust-check`, and `node-minimum-compatibility` to delegate activation to the helper with no inline `corepack enable npm` / `npm --version` path. GREEN `99b0707c61099a170695b66f644fd90162fb7f8c` moves the exact-minimum job into `ci.yml`, converts the three existing CI jobs to the helper, removes the redundant workflow-global npm version variable, and deletes the standalone workflow. -Predecessor exact head `3983dd216d95dc5f78e78f6b17259ad4c5530ebc` completed all four native Windows/macOS build jobs successfully with exact npm activation. That hosted evidence validates the predecessor command path only; it does not transfer to later moved heads. Exact `99b0707c...` and its traceability descendants require fresh hosted evidence, including the newly registered exact-minimum CI job. +Exact head `c010a66fedec3647274a27900a11203e07ee671e` then materialized `gate / ci / node-minimum-compatibility` in the live PR CI workflow. On hosted macOS 15 it successfully reached Node 22.22.2, canonical npm activation, verified npm 10.9.9 with bundled tar 7.5.22, frozen dependency installation, Python dependency sync, and the Rust numeric-extension build. Its first source-backed failure was the repository Ruff formatting gate, not package-manager acquisition. + +That Ruff failure named four files. Three were #896-owned regression files; `78bcc37334c512e15289503294bceaff57c5f927` aligns their formatting and removes a stale test dependency on the deleted standalone workflow by reading `node-minimum-compatibility` from registered `ci.yml`. The fourth file, `services/analysis-engine/tests/test_supply_chain_policy.py`, is the canonical formatting delta owned by #1176. Rather than copying it, merge commit `b5dc5bf7834137a8f6b0140b1219e7dbeff7b8db` inherits #1176 exact head `8fe6b6d99c009527ef0bcba419e6f6debdb23c23`, and #896 is retargeted onto that prerequisite branch. + +Predecessor exact head `3983dd216d95dc5f78e78f6b17259ad4c5530ebc` completed all four native Windows/macOS build jobs successfully with exact npm activation. That hosted evidence validates the predecessor command path only; it does not transfer to later moved heads. Current traceability descendants require fresh hosted evidence on the unchanged stacked merge candidate. ## Risks and claim boundary @@ -65,7 +71,9 @@ The allowlist may reject a future genuinely transient Corepack error that is not Diagnostic matching still depends on upstream text. If Corepack exposes a stable structured error code or typed result, this script should consume that contract instead. This mechanism does not prove package-manager authenticity by itself; authenticity remains Corepack's verification responsibility, while BandScope controls retry and fallback behavior around that boundary. -Structural workflow tests prove command ownership and order, not successful hosted acquisition. Moving the exact-minimum lane into `ci.yml` is an evidence-topology repair, not proof that Node 22.22.2 or npm acquisition succeeds on a hosted runner. A job can still fail for runner, registry, Corepack, dependency, Rust, Python, or product-test reasons; those failures remain visible and must be classified from exact-head evidence rather than suppressed or blindly retried. +Structural workflow tests prove command ownership and order, not successful hosted acquisition. Moving the exact-minimum lane into `ci.yml` is an evidence-topology repair, not proof that Node 22.22.2 or npm acquisition succeeds on every hosted run. The c010 run proves that one exact generation reached and passed the npm acquisition boundary before failing later at formatting; source movement after that point requires fresh evidence. + +The #1176 stack does not transfer #1176 approvals or central-gate evidence into #896. It only establishes ancestry for the canonical formatting prerequisite. #896 still requires its own exact-head repository/central gates and current-head independent review. ## Follow-up @@ -75,13 +83,14 @@ Structural workflow tests prove command ownership and order, not successful host - Expand the transient allowlist only from exact observed evidence plus a focused regression and documented retry safety. - If Corepack introduces a stable structured failure classification, replace diagnostic-string matching with that contract. - Treat any unclassified failure that reaches sleep/retry as a repair finding, not as permission to broaden fallback behavior. -- Require fresh hosted success for the exact-minimum Node 22.22.2 job, normal CI jobs, and native build lanes on the unchanged merge candidate head. +- Preserve #1176 as the single writer for the protected-base Ruff prerequisite; consume it by ancestry until normal integration reaches `develop`. +- Require fresh hosted success for the exact-minimum Node 22.22.2 job, normal CI jobs, native build lanes, and applicable central security/SBOM/SAST gates on the unchanged stacked merge candidate head. ## Security Notes The package-manager acquisition diagnostic is untrusted upstream text used only for a bounded classification decision and stderr evidence. It is never evaluated or interpolated into a shell command. The trust boundary is `corepack install --global` returning non-zero: only the exact observed `ETIMEDOUT` token permits another attempt; all other results fail closed before npm activation or dependency extraction. No secret, token, package payload, or mutable version selector is logged by this policy. -Centralizing workflow activation does not broaden permissions. The helper operates with the same repository checkout and runner process privileges the inline commands already had; the change removes duplicate acquisition paths and places the exact-minimum job inside the existing CI execution surface rather than adding a new credential or network capability. +Centralizing workflow activation does not broaden permissions. The helper operates with the same repository checkout and runner process privileges the inline commands already had; the change removes duplicate acquisition paths and places the exact-minimum job inside the existing CI execution surface rather than adding a new credential or network capability. Stacking #1176 adds no new runtime authority; it only inherits the canonical formatting prerequisite by commit ancestry. ## References From 5c601033f42a8d27bf087bcb9b0e43b399366692 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 18 Sep 2026 11:27:25 +0900 Subject: [PATCH 121/161] docs(node): preserve CI visibility across prerequisite ancestry --- ...untime-acquisition-failure-classification.md | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/docs/traceability/npm-runtime-acquisition-failure-classification.md b/docs/traceability/npm-runtime-acquisition-failure-classification.md index 6a7d41510..07b3a7fc9 100644 --- a/docs/traceability/npm-runtime-acquisition-failure-classification.md +++ b/docs/traceability/npm-runtime-acquisition-failure-classification.md @@ -24,6 +24,7 @@ The exact-minimum lane also lived in a newly added standalone workflow. Fresh ex - The exact-minimum Node compatibility job must live in an already-materialized PR CI workflow rather than relying on a second workflow whose pull-request run is absent from the observed inventory. - Error classification remains diagnostic-based because the current Corepack command boundary does not expose a stable machine-readable failure taxonomy to this script. - Protected-base formatting debt owned by another PR is consumed by stack ancestry; it is not copied into this owner as an unrelated patch. +- Repository pull-request workflows currently filter their base branches to `develop`/`main`; #896 must therefore remain based on `develop` while carrying #1176 as a merge-parent prerequisite, otherwise repository CI disappears from the live PR generation. ## Decision @@ -35,6 +36,8 @@ The helper is the single workflow-level activation path for the four native `bui The retry allowlist is intentionally narrow. Additional error codes such as connection reset, DNS retry, or HTTP/server failures must not be admitted from intuition alone; they require a concrete hosted failure, bounded semantics, and a focused regression before this policy expands. +#1176 remains the canonical single writer for the protected-base Ruff formatting prerequisite. #896 consumes that exact head through ordinary merge ancestry but keeps its PR base on protected `develop`, because the repository workflow triggers are scoped to pull requests targeting `develop` or `main`. This keeps owner lineage and exact-head CI simultaneously observable. + ## Rejected alternatives - Retry every Corepack failure three times: rejected because deterministic trust, policy, package metadata, permission, and configuration failures are not transport recovery candidates. @@ -46,6 +49,7 @@ The retry allowlist is intentionally narrow. Additional error codes such as conn - Keep inline Corepack activation in any CI job: rejected because it duplicates the same package-manager acquisition contract and can drift from the canonical timeout/trust classifier. - Keep the exact-minimum compatibility check as a second standalone workflow after its PR runs are absent from the observed exact-head workflow inventory: rejected because source presence without live PR execution does not satisfy the compatibility evidence requirement. - Copy the protected-base Ruff fix from #1176 into #896: rejected because #1176 is the canonical single writer for that prerequisite and the dependent branch can inherit it through ordinary non-force ancestry. +- Keep #896 retargeted directly onto #1176's branch: rejected after live observation because `.github/workflows/ci.yml` and the other repository pull-request workflows filter on base branch `develop`/`main`; the retargeted generation did not materialize fresh repository CI for the moved head. ## Evidence and regression @@ -61,9 +65,11 @@ A fresh live `ci.yml` review then exposed the remaining duplicate owners. RED `4 Exact head `c010a66fedec3647274a27900a11203e07ee671e` then materialized `gate / ci / node-minimum-compatibility` in the live PR CI workflow. On hosted macOS 15 it successfully reached Node 22.22.2, canonical npm activation, verified npm 10.9.9 with bundled tar 7.5.22, frozen dependency installation, Python dependency sync, and the Rust numeric-extension build. Its first source-backed failure was the repository Ruff formatting gate, not package-manager acquisition. -That Ruff failure named four files. Three were #896-owned regression files; `78bcc37334c512e15289503294bceaff57c5f927` aligns their formatting and removes a stale test dependency on the deleted standalone workflow by reading `node-minimum-compatibility` from registered `ci.yml`. The fourth file, `services/analysis-engine/tests/test_supply_chain_policy.py`, is the canonical formatting delta owned by #1176. Rather than copying it, merge commit `b5dc5bf7834137a8f6b0140b1219e7dbeff7b8db` inherits #1176 exact head `8fe6b6d99c009527ef0bcba419e6f6debdb23c23`, and #896 is retargeted onto that prerequisite branch. +That Ruff failure named four files. Three were #896-owned regression files; `78bcc37334c512e15289503294bceaff57c5f927` aligns their formatting and removes a stale test dependency on the deleted standalone workflow by reading `node-minimum-compatibility` from registered `ci.yml`. The fourth file, `services/analysis-engine/tests/test_supply_chain_policy.py`, is the canonical formatting delta owned by #1176. Rather than copying it, merge commit `b5dc5bf7834137a8f6b0140b1219e7dbeff7b8db` inherits #1176 exact head `8fe6b6d99c009527ef0bcba419e6f6debdb23c23`. + +#896 was briefly retargeted onto the #1176 branch to make the dependency stack explicit. Fresh Actions inventory then showed the practical consequence of the repository's base-branch filters: after source moved under that base, no exact moved-head repository pull-request workflows materialized. The PR base was therefore restored to protected `develop`; #1176 remains present as a merge parent, so the formatter delta is still inherited from its canonical writer rather than reimplemented locally. -Predecessor exact head `3983dd216d95dc5f78e78f6b17259ad4c5530ebc` completed all four native Windows/macOS build jobs successfully with exact npm activation. That hosted evidence validates the predecessor command path only; it does not transfer to later moved heads. Current traceability descendants require fresh hosted evidence on the unchanged stacked merge candidate. +Predecessor exact head `3983dd216d95dc5f78e78f6b17259ad4c5530ebc` completed all four native Windows/macOS build jobs successfully with exact npm activation. That hosted evidence validates the predecessor command path only; it does not transfer to later moved heads. Current traceability descendants require fresh hosted evidence on the unchanged merge candidate. ## Risks and claim boundary @@ -73,7 +79,7 @@ Diagnostic matching still depends on upstream text. If Corepack exposes a stable Structural workflow tests prove command ownership and order, not successful hosted acquisition. Moving the exact-minimum lane into `ci.yml` is an evidence-topology repair, not proof that Node 22.22.2 or npm acquisition succeeds on every hosted run. The c010 run proves that one exact generation reached and passed the npm acquisition boundary before failing later at formatting; source movement after that point requires fresh evidence. -The #1176 stack does not transfer #1176 approvals or central-gate evidence into #896. It only establishes ancestry for the canonical formatting prerequisite. #896 still requires its own exact-head repository/central gates and current-head independent review. +The #1176 merge parent does not transfer #1176 approvals or central-gate evidence into #896. It only establishes ancestry for the canonical formatting prerequisite. #896 still requires its own exact-head repository/central gates and current-head independent review. Keeping the PR base on `develop` also means the #1176 file remains visible in the protected-base diff until #1176 integrates normally; that visibility is accepted rather than suppressing CI or copying the delta. ## Follow-up @@ -84,13 +90,14 @@ The #1176 stack does not transfer #1176 approvals or central-gate evidence into - If Corepack introduces a stable structured failure classification, replace diagnostic-string matching with that contract. - Treat any unclassified failure that reaches sleep/retry as a repair finding, not as permission to broaden fallback behavior. - Preserve #1176 as the single writer for the protected-base Ruff prerequisite; consume it by ancestry until normal integration reaches `develop`. -- Require fresh hosted success for the exact-minimum Node 22.22.2 job, normal CI jobs, native build lanes, and applicable central security/SBOM/SAST gates on the unchanged stacked merge candidate head. +- Keep #896 based on protected `develop` while repository workflows remain base-filtered to `develop`/`main`; do not trade away exact-head CI visibility merely to make the stack prettier in the PR UI. +- Require fresh hosted success for the exact-minimum Node 22.22.2 job, normal CI jobs, native build lanes, and applicable central security/SBOM/SAST gates on the unchanged merge candidate head. ## Security Notes The package-manager acquisition diagnostic is untrusted upstream text used only for a bounded classification decision and stderr evidence. It is never evaluated or interpolated into a shell command. The trust boundary is `corepack install --global` returning non-zero: only the exact observed `ETIMEDOUT` token permits another attempt; all other results fail closed before npm activation or dependency extraction. No secret, token, package payload, or mutable version selector is logged by this policy. -Centralizing workflow activation does not broaden permissions. The helper operates with the same repository checkout and runner process privileges the inline commands already had; the change removes duplicate acquisition paths and places the exact-minimum job inside the existing CI execution surface rather than adding a new credential or network capability. Stacking #1176 adds no new runtime authority; it only inherits the canonical formatting prerequisite by commit ancestry. +Centralizing workflow activation does not broaden permissions. The helper operates with the same repository checkout and runner process privileges the inline commands already had; the change removes duplicate acquisition paths and places the exact-minimum job inside the existing CI execution surface rather than adding a new credential or network capability. Consuming #1176 as a merge parent adds no new runtime authority; keeping the PR based on `develop` preserves the repository's existing CI trigger surface. ## References From 81d7cd910deb7b54250b6397844c15e576df5d9c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 18 Sep 2026 11:33:05 +0900 Subject: [PATCH 122/161] ci(python): emit Ruff formatter diff on gate failure --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 174929213..7de2103c4 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "check:npm-runtime": "node scripts/checks/verify_npm_runtime.mjs", "check:python-docstrings": "python3 scripts/checks/run_analysis_command.py ruff check src tests ../../scripts --select D100,D101,D102,D103,D104,D105,D106,D107", "ruff:check": "python3 scripts/checks/run_analysis_command.py ruff check src tests", - "ruff:format:check": "python3 scripts/checks/run_analysis_command.py ruff format --check src tests", + "ruff:format:check": "python3 scripts/checks/run_analysis_command.py ruff format --check --diff src tests", "bandit:check": "python3 scripts/checks/run_analysis_command.py bandit -c pyproject.toml -r src", "lint": "npm run lint:workspaces && npm run check:docs && npm run check:security-notes && npm run check:security-gates && npm run check:supply-chain && npm run check:github-bootstrap && npm run check:python-docstrings && npm run ruff:check && npm run ruff:format:check && npm run bandit:check", "typecheck": "npm run typecheck --workspaces --if-present && python3 scripts/checks/run_analysis_command.py mypy src", From 239bfc76c39811a6a71627a794dae2e1e07488ab Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 18 Sep 2026 11:42:26 +0900 Subject: [PATCH 123/161] fix(node): format npm runtime regression tests --- .../tests/test_node_runtime_contract.py | 13 ++---- ...runtime_activation_nontransient_failure.py | 8 ++-- .../test_npm_runtime_activation_resilience.py | 40 +++++++------------ 3 files changed, 22 insertions(+), 39 deletions(-) diff --git a/services/analysis-engine/tests/test_node_runtime_contract.py b/services/analysis-engine/tests/test_node_runtime_contract.py index 024075788..f16e7a41d 100644 --- a/services/analysis-engine/tests/test_node_runtime_contract.py +++ b/services/analysis-engine/tests/test_node_runtime_contract.py @@ -60,12 +60,9 @@ def test_jsdom_30_is_adopted_in_manifest_and_lock() -> None: assert desktop["devDependencies"]["jsdom"] == EXPECTED_JSDOM_RANGE assert ( - package_lock["packages"]["apps/desktop"]["devDependencies"]["jsdom"] - == EXPECTED_JSDOM_RANGE - ) - assert ( - package_lock["packages"]["apps/desktop/node_modules/jsdom"]["version"] == "30.0.1" + package_lock["packages"]["apps/desktop"]["devDependencies"]["jsdom"] == EXPECTED_JSDOM_RANGE ) + assert package_lock["packages"]["apps/desktop/node_modules/jsdom"]["version"] == "30.0.1" def test_eslint_10_9_1_intent_is_preserved_in_both_workspaces_and_lock() -> None: @@ -135,8 +132,7 @@ def test_all_registered_ci_npm_consumers_delegate_activation_to_helper() -> None ) for duplicate_activation in ("corepack enable npm", "npm --version"): assert duplicate_activation not in body, ( - f"{job_name} must not retain workflow-local npm activation: " - f"{duplicate_activation}" + f"{job_name} must not retain workflow-local npm activation: {duplicate_activation}" ) activation_offset = body.index(CANONICAL_NPM_ACTIVATION) @@ -162,8 +158,7 @@ def test_repository_no_longer_advertises_node_22_13_floor() -> None: stale = [ path for path in audited_paths - if path != "package-lock.json" - and "22.13" in (ROOT / path).read_text(encoding="utf-8") + if path != "package-lock.json" and "22.13" in (ROOT / path).read_text(encoding="utf-8") ] package_lock = _load_json("package-lock.json") if package_lock["packages"][""]["engines"] != {"node": EXPECTED_NODE_ENGINE}: diff --git a/services/analysis-engine/tests/test_npm_runtime_activation_nontransient_failure.py b/services/analysis-engine/tests/test_npm_runtime_activation_nontransient_failure.py index c9592ab0f..649c4b3ce 100644 --- a/services/analysis-engine/tests/test_npm_runtime_activation_nontransient_failure.py +++ b/services/analysis-engine/tests/test_npm_runtime_activation_nontransient_failure.py @@ -9,9 +9,7 @@ import pytest _REPOSITORY_ROOT = Path(__file__).resolve().parents[3] -_ACTIVATION_HELPER = ( - _REPOSITORY_ROOT / "scripts" / "checks" / "activate_pinned_npm_runtime.sh" -) +_ACTIVATION_HELPER = _REPOSITORY_ROOT / "scripts" / "checks" / "activate_pinned_npm_runtime.sh" def _write_executable(path: Path, content: str) -> None: @@ -59,11 +57,11 @@ def _run_corepack_failure( ) _write_executable( fake_bin / "sleep", - "#!/usr/bin/env bash\nprintf '%s\\n' \"$1\" >> \"$BANDSCOPE_TEST_SLEEP_LOG\"\n", + '#!/usr/bin/env bash\nprintf \'%s\\n\' "$1" >> "$BANDSCOPE_TEST_SLEEP_LOG"\n', ) _write_executable( fake_bin / "npm", - "#!/usr/bin/env bash\nprintf '%s\\n' \"$*\" >> \"$BANDSCOPE_TEST_NPM_LOG\"\n", + '#!/usr/bin/env bash\nprintf \'%s\\n\' "$*" >> "$BANDSCOPE_TEST_NPM_LOG"\n', ) environment = os.environ.copy() diff --git a/services/analysis-engine/tests/test_npm_runtime_activation_resilience.py b/services/analysis-engine/tests/test_npm_runtime_activation_resilience.py index b77a8311f..53f43c1a3 100644 --- a/services/analysis-engine/tests/test_npm_runtime_activation_resilience.py +++ b/services/analysis-engine/tests/test_npm_runtime_activation_resilience.py @@ -12,9 +12,7 @@ _REPOSITORY_ROOT = Path(__file__).resolve().parents[3] _BUILD_BASELINE = _REPOSITORY_ROOT / ".github" / "workflows" / "build-baseline.yml" _CI_WORKFLOW = _REPOSITORY_ROOT / ".github" / "workflows" / "ci.yml" -_ACTIVATION_HELPER = ( - _REPOSITORY_ROOT / "scripts" / "checks" / "activate_pinned_npm_runtime.sh" -) +_ACTIVATION_HELPER = _REPOSITORY_ROOT / "scripts" / "checks" / "activate_pinned_npm_runtime.sh" _ACTIVATION_COMMAND = "bash scripts/checks/activate_pinned_npm_runtime.sh" @@ -79,11 +77,11 @@ def _fake_command_environment( ) _write_executable( fake_bin / "sleep", - "#!/usr/bin/env bash\nprintf '%s\\n' \"$1\" >> \"$BANDSCOPE_TEST_SLEEP_LOG\"\n", + '#!/usr/bin/env bash\nprintf \'%s\\n\' "$1" >> "$BANDSCOPE_TEST_SLEEP_LOG"\n', ) _write_executable( fake_bin / "npm", - "#!/usr/bin/env bash\nprintf '%s\\n' \"$*\" >> \"$BANDSCOPE_TEST_NPM_LOG\"\n", + '#!/usr/bin/env bash\nprintf \'%s\\n\' "$*" >> "$BANDSCOPE_TEST_NPM_LOG"\n', ) environment = os.environ.copy() @@ -96,7 +94,9 @@ def _fake_command_environment( return environment, corepack_count, sleep_log, npm_log, corepack_enable_log -def _run_activation_helper(tmp_path: Path, *, acquisition_failures: int) -> tuple[ +def _run_activation_helper( + tmp_path: Path, *, acquisition_failures: int +) -> tuple[ subprocess.CompletedProcess[str], Path, Path, @@ -131,9 +131,7 @@ def test_build_baseline_uses_retrying_pinned_npm_activation_before_dependency_re npm_consumers = 0 for job_name, job in jobs.items(): steps = _job_steps(job) - run_steps = [ - str(step["run"]) for step in steps if isinstance(step.get("run"), str) - ] + run_steps = [str(step["run"]) for step in steps if isinstance(step.get("run"), str)] dependency_index = next( (index for index, command in enumerate(run_steps) if command.strip() == "npm ci"), None, @@ -143,9 +141,7 @@ def test_build_baseline_uses_retrying_pinned_npm_activation_before_dependency_re npm_consumers += 1 activation_indexes = [ - index - for index, command in enumerate(run_steps) - if command.strip() == _ACTIVATION_COMMAND + index for index, command in enumerate(run_steps) if command.strip() == _ACTIVATION_COMMAND ] assert activation_indexes == [dependency_index - 1], f"{job_name} activation ownership" assert all("corepack enable npm" not in command for command in run_steps), ( @@ -171,9 +167,7 @@ def test_registered_ci_exact_minimum_node_lane_uses_same_pinned_npm_activation_b if command.strip() == "npm ci --ignore-scripts --no-audit --no-fund" ) activation_indexes = [ - index - for index, command in enumerate(run_steps) - if command.strip() == _ACTIVATION_COMMAND + index for index, command in enumerate(run_steps) if command.strip() == _ACTIVATION_COMMAND ] assert activation_indexes == [dependency_index - 1] @@ -205,11 +199,9 @@ def test_pinned_npm_activation_recovers_after_two_transient_acquisition_failures tmp_path: Path, ) -> None: """Retry admitted ETIMEDOUT acquisition, then audit the acquired npm before success.""" - completed, corepack_count, sleep_log, npm_log, corepack_enable_log = ( - _run_activation_helper( - tmp_path, - acquisition_failures=2, - ) + completed, corepack_count, sleep_log, npm_log, corepack_enable_log = _run_activation_helper( + tmp_path, + acquisition_failures=2, ) assert completed.returncode == 0, completed.stderr @@ -229,11 +221,9 @@ def test_pinned_npm_activation_fails_closed_after_bounded_timeout_exhaustion( tmp_path: Path, ) -> None: """Stop after three admitted timeout failures without enabling or invoking fallback npm.""" - completed, corepack_count, sleep_log, npm_log, corepack_enable_log = ( - _run_activation_helper( - tmp_path, - acquisition_failures=99, - ) + completed, corepack_count, sleep_log, npm_log, corepack_enable_log = _run_activation_helper( + tmp_path, + acquisition_failures=99, ) assert completed.returncode != 0 From 984d038dc617a0729afaf33e6254c0d8e2323da8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 18 Sep 2026 11:43:23 +0900 Subject: [PATCH 124/161] docs(node): record exact-head Ruff failure repair --- .../npm-runtime-acquisition-failure-classification.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/traceability/npm-runtime-acquisition-failure-classification.md b/docs/traceability/npm-runtime-acquisition-failure-classification.md index 07b3a7fc9..01f52f162 100644 --- a/docs/traceability/npm-runtime-acquisition-failure-classification.md +++ b/docs/traceability/npm-runtime-acquisition-failure-classification.md @@ -50,6 +50,7 @@ The retry allowlist is intentionally narrow. Additional error codes such as conn - Keep the exact-minimum compatibility check as a second standalone workflow after its PR runs are absent from the observed exact-head workflow inventory: rejected because source presence without live PR execution does not satisfy the compatibility evidence requirement. - Copy the protected-base Ruff fix from #1176 into #896: rejected because #1176 is the canonical single writer for that prerequisite and the dependent branch can inherit it through ordinary non-force ancestry. - Keep #896 retargeted directly onto #1176's branch: rejected after live observation because `.github/workflows/ci.yml` and the other repository pull-request workflows filter on base branch `develop`/`main`; the retargeted generation did not materialize fresh repository CI for the moved head. +- Treat the exact-head Ruff failure as a runner or tool problem: rejected because the formatter emitted deterministic source diffs for three #896-owned regression files after the runtime, dependency, and Rust setup steps had already succeeded. ## Evidence and regression @@ -69,6 +70,10 @@ That Ruff failure named four files. Three were #896-owned regression files; `78b #896 was briefly retargeted onto the #1176 branch to make the dependency stack explicit. Fresh Actions inventory then showed the practical consequence of the repository's base-branch filters: after source moved under that base, no exact moved-head repository pull-request workflows materialized. The PR base was therefore restored to protected `develop`; #1176 remains present as a merge parent, so the formatter delta is still inherited from its canonical writer rather than reimplemented locally. +Exact head `81d7cd910deb7b54250b6397844c15e576df5d9c` added `ruff format --check --diff` so the next hosted failure would carry exact repair evidence rather than only an exit code. Its `gate / ci / node-minimum-compatibility` run again passed Node 22.22.2 setup, canonical npm activation, npm 10.9.9 / bundled tar 7.5.22 verification, frozen Node dependencies, Python sync, stable Rust, and the Rust numeric extension. Ruff then reported exactly three files requiring formatting: `test_node_runtime_contract.py`, `test_npm_runtime_activation_nontransient_failure.py`, and `test_npm_runtime_activation_resilience.py`. No #1176-owned file appeared in this exact-head formatter diff. + +Commit `239bfc76c39811a6a71627a794dae2e1e07488ab` applies only that emitted Ruff formatter result to those three #896-owned regression files. It changes no assertion semantics, dependency/runtime contract, workflow behavior, audio/MIR behavior, or foreign-owner file. Because source moved after the failed run, all terminal gate evidence must be collected again from descendants of this repair; the `81d7...` run is causal predecessor evidence only. + Predecessor exact head `3983dd216d95dc5f78e78f6b17259ad4c5530ebc` completed all four native Windows/macOS build jobs successfully with exact npm activation. That hosted evidence validates the predecessor command path only; it does not transfer to later moved heads. Current traceability descendants require fresh hosted evidence on the unchanged merge candidate. ## Risks and claim boundary @@ -77,7 +82,7 @@ The allowlist may reject a future genuinely transient Corepack error that is not Diagnostic matching still depends on upstream text. If Corepack exposes a stable structured error code or typed result, this script should consume that contract instead. This mechanism does not prove package-manager authenticity by itself; authenticity remains Corepack's verification responsibility, while BandScope controls retry and fallback behavior around that boundary. -Structural workflow tests prove command ownership and order, not successful hosted acquisition. Moving the exact-minimum lane into `ci.yml` is an evidence-topology repair, not proof that Node 22.22.2 or npm acquisition succeeds on every hosted run. The c010 run proves that one exact generation reached and passed the npm acquisition boundary before failing later at formatting; source movement after that point requires fresh evidence. +Structural workflow tests prove command ownership and order, not successful hosted acquisition. Moving the exact-minimum lane into `ci.yml` is an evidence-topology repair, not proof that Node 22.22.2 or npm acquisition succeeds on every hosted run. The `81d7...` hosted run proves the exact npm/runtime path and Rust extension build reached the Ruff gate; it does not transfer a GREEN verdict to the later formatting repair or traceability descendant. The #1176 merge parent does not transfer #1176 approvals or central-gate evidence into #896. It only establishes ancestry for the canonical formatting prerequisite. #896 still requires its own exact-head repository/central gates and current-head independent review. Keeping the PR base on `develop` also means the #1176 file remains visible in the protected-base diff until #1176 integrates normally; that visibility is accepted rather than suppressing CI or copying the delta. From 8507c213b2ad3c5f3d1ab49010adc805a4170429 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 18 Sep 2026 11:46:06 +0900 Subject: [PATCH 125/161] fix(node): keep activation index lint-compatible --- .../tests/test_npm_runtime_activation_resilience.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/analysis-engine/tests/test_npm_runtime_activation_resilience.py b/services/analysis-engine/tests/test_npm_runtime_activation_resilience.py index 53f43c1a3..4220baa49 100644 --- a/services/analysis-engine/tests/test_npm_runtime_activation_resilience.py +++ b/services/analysis-engine/tests/test_npm_runtime_activation_resilience.py @@ -141,7 +141,7 @@ def test_build_baseline_uses_retrying_pinned_npm_activation_before_dependency_re npm_consumers += 1 activation_indexes = [ - index for index, command in enumerate(run_steps) if command.strip() == _ACTIVATION_COMMAND + i for i, command in enumerate(run_steps) if command.strip() == _ACTIVATION_COMMAND ] assert activation_indexes == [dependency_index - 1], f"{job_name} activation ownership" assert all("corepack enable npm" not in command for command in run_steps), ( From 2d9428cbc88f240a77d6404682de4bc1e9599bc6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 18 Sep 2026 11:46:53 +0900 Subject: [PATCH 126/161] docs(node): record lint-width repair --- .../npm-runtime-acquisition-failure-classification.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/docs/traceability/npm-runtime-acquisition-failure-classification.md b/docs/traceability/npm-runtime-acquisition-failure-classification.md index 01f52f162..8ecd665ea 100644 --- a/docs/traceability/npm-runtime-acquisition-failure-classification.md +++ b/docs/traceability/npm-runtime-acquisition-failure-classification.md @@ -50,7 +50,8 @@ The retry allowlist is intentionally narrow. Additional error codes such as conn - Keep the exact-minimum compatibility check as a second standalone workflow after its PR runs are absent from the observed exact-head workflow inventory: rejected because source presence without live PR execution does not satisfy the compatibility evidence requirement. - Copy the protected-base Ruff fix from #1176 into #896: rejected because #1176 is the canonical single writer for that prerequisite and the dependent branch can inherit it through ordinary non-force ancestry. - Keep #896 retargeted directly onto #1176's branch: rejected after live observation because `.github/workflows/ci.yml` and the other repository pull-request workflows filter on base branch `develop`/`main`; the retargeted generation did not materialize fresh repository CI for the moved head. -- Treat the exact-head Ruff failure as a runner or tool problem: rejected because the formatter emitted deterministic source diffs for three #896-owned regression files after the runtime, dependency, and Rust setup steps had already succeeded. +- Treat exact-head Ruff failures as runner or tool failures: rejected when the gate emits deterministic file/line repair evidence after runtime and dependency setup succeeded. +- Weaken E501 or alter the repository's 100-column Ruff policy to accommodate one generated line: rejected because the assertion can remain semantically identical and satisfy both formatter and lint contracts with a shorter local index name. ## Evidence and regression @@ -72,7 +73,11 @@ That Ruff failure named four files. Three were #896-owned regression files; `78b Exact head `81d7cd910deb7b54250b6397844c15e576df5d9c` added `ruff format --check --diff` so the next hosted failure would carry exact repair evidence rather than only an exit code. Its `gate / ci / node-minimum-compatibility` run again passed Node 22.22.2 setup, canonical npm activation, npm 10.9.9 / bundled tar 7.5.22 verification, frozen Node dependencies, Python sync, stable Rust, and the Rust numeric extension. Ruff then reported exactly three files requiring formatting: `test_node_runtime_contract.py`, `test_npm_runtime_activation_nontransient_failure.py`, and `test_npm_runtime_activation_resilience.py`. No #1176-owned file appeared in this exact-head formatter diff. -Commit `239bfc76c39811a6a71627a794dae2e1e07488ab` applies only that emitted Ruff formatter result to those three #896-owned regression files. It changes no assertion semantics, dependency/runtime contract, workflow behavior, audio/MIR behavior, or foreign-owner file. Because source moved after the failed run, all terminal gate evidence must be collected again from descendants of this repair; the `81d7...` run is causal predecessor evidence only. +Commit `239bfc76c39811a6a71627a794dae2e1e07488ab` applies only that emitted Ruff formatter result to those three #896-owned regression files. It changes no assertion semantics, dependency/runtime contract, workflow behavior, audio/MIR behavior, or foreign-owner file. + +The next exact head `984d038dc617a0729afaf33e6254c0d8e2323da8` again passed Node 22.22.2 setup, exact npm 10.9.9 activation with bundled tar 7.5.22, frozen Node dependencies, Python sync, stable Rust, and the numeric-extension build. The source-backed failure moved from formatter drift to `ruff check`: E501 rejected one 102-column list-comprehension line in `test_npm_runtime_activation_resilience.py`. The configured Ruff line length is 100. Commit `8507c213b2ad3c5f3d1ab49010adc805a4170429` shortens only the local comprehension index name from `index` to `i`; the list contents, duplicate-detection semantics, workflow contract, and production behavior are unchanged. No lint rule or line-length policy is weakened. + +Because source moved after both hosted failures, their results remain causal predecessor evidence only. The current traceability descendant requires fresh terminal repository and central evidence. Predecessor exact head `3983dd216d95dc5f78e78f6b17259ad4c5530ebc` completed all four native Windows/macOS build jobs successfully with exact npm activation. That hosted evidence validates the predecessor command path only; it does not transfer to later moved heads. Current traceability descendants require fresh hosted evidence on the unchanged merge candidate. @@ -82,7 +87,7 @@ The allowlist may reject a future genuinely transient Corepack error that is not Diagnostic matching still depends on upstream text. If Corepack exposes a stable structured error code or typed result, this script should consume that contract instead. This mechanism does not prove package-manager authenticity by itself; authenticity remains Corepack's verification responsibility, while BandScope controls retry and fallback behavior around that boundary. -Structural workflow tests prove command ownership and order, not successful hosted acquisition. Moving the exact-minimum lane into `ci.yml` is an evidence-topology repair, not proof that Node 22.22.2 or npm acquisition succeeds on every hosted run. The `81d7...` hosted run proves the exact npm/runtime path and Rust extension build reached the Ruff gate; it does not transfer a GREEN verdict to the later formatting repair or traceability descendant. +Structural workflow tests prove command ownership and order, not successful hosted acquisition. Moving the exact-minimum lane into `ci.yml` is an evidence-topology repair, not proof that Node 22.22.2 or npm acquisition succeeds on every hosted run. The `81d7...` and `984d...` hosted runs prove the exact npm/runtime path and Rust extension build reached their respective lint gates; neither transfers a GREEN verdict to a moved descendant. The #1176 merge parent does not transfer #1176 approvals or central-gate evidence into #896. It only establishes ancestry for the canonical formatting prerequisite. #896 still requires its own exact-head repository/central gates and current-head independent review. Keeping the PR base on `develop` also means the #1176 file remains visible in the protected-base diff until #1176 integrates normally; that visibility is accepted rather than suppressing CI or copying the delta. From 86b51c6d2a3eda5996222c36281be7ee1f8d6c44 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 18 Sep 2026 11:58:40 +0900 Subject: [PATCH 127/161] test(node): align CI provenance contract with activation helper --- services/analysis-engine/tests/test_npm_toolchain_contract.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/services/analysis-engine/tests/test_npm_toolchain_contract.py b/services/analysis-engine/tests/test_npm_toolchain_contract.py index 384ef4d89..0a69c43ef 100644 --- a/services/analysis-engine/tests/test_npm_toolchain_contract.py +++ b/services/analysis-engine/tests/test_npm_toolchain_contract.py @@ -160,8 +160,7 @@ def test_primary_ci_consumes_the_lock_without_mutable_resolution() -> None: lock_job = _lock_validation_job(workflow) assert f'node-version: "{_EXPECTED_NODE_VERSION}"' in workflow - assert f'EXPECTED_NPM_VERSION: "{_EXPECTED_NPM_VERSION}"' in workflow - assert 'test "$(npm --version)" = "$EXPECTED_NPM_VERSION"' in lock_job + assert lock_job.count(_NPM_ACTIVATION_COMMAND) == 1 assert "npm ci --ignore-scripts --no-audit --no-fund" in lock_job assert "git diff --exit-code -- package.json package-lock.json" in lock_job assert "needs: lock-validation" in workflow From fb73bc99db83e9c1e243cb45a9d095fd229cd20e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 21 Sep 2026 17:05:26 +0900 Subject: [PATCH 128/161] test(node): require integrity-bound npm packageManager locator --- .../test_npm_package_manager_integrity_pin.py | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 services/analysis-engine/tests/test_npm_package_manager_integrity_pin.py diff --git a/services/analysis-engine/tests/test_npm_package_manager_integrity_pin.py b/services/analysis-engine/tests/test_npm_package_manager_integrity_pin.py new file mode 100644 index 000000000..99f4565ad --- /dev/null +++ b/services/analysis-engine/tests/test_npm_package_manager_integrity_pin.py @@ -0,0 +1,33 @@ +"""Contracts for integrity-bound Corepack acquisition of the reviewed npm runtime.""" + +from __future__ import annotations + +import json +from pathlib import Path + +_REPOSITORY_ROOT = Path(__file__).resolve().parents[3] +_EXPECTED_PACKAGE_MANAGER = ( + "npm@10.9.9+sha512." + "d60fba8cb42f688b81e33c2f1cbef2ad7b977166700ec0ad057f1b6d60ea6ef" + "2524abf673e20c35931cd8305d1dbb8887134d6eefdc0e7b8435bd458bf65b862" +) +_EXPECTED_LOCATOR_PATTERN = ( + r"/^npm@[0-9]+\.[0-9]+\.[0-9]+\+sha512\.[0-9a-f]{128}$/" +) + + +def test_root_manifest_integrity_pins_the_reviewed_npm_artifact() -> None: + """Require Corepack metadata to bind npm 10.9.9 to its reviewed SHA-512 artifact.""" + manifest = json.loads((_REPOSITORY_ROOT / "package.json").read_text(encoding="utf-8")) + + assert manifest["packageManager"] == _EXPECTED_PACKAGE_MANAGER + + +def test_activation_helper_rejects_version_only_package_manager_locators() -> None: + """Prevent an exact version from being mistaken for package-manager artifact integrity.""" + source = ( + _REPOSITORY_ROOT / "scripts" / "checks" / "activate_pinned_npm_runtime.sh" + ).read_text(encoding="utf-8") + + assert _EXPECTED_LOCATOR_PATTERN in source + assert 'corepack install --global "$package_manager_spec"' in source From 29361c98a88472007893cea3949f4444bf0f4f76 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 21 Sep 2026 17:05:41 +0900 Subject: [PATCH 129/161] fix(node): integrity-pin npm 10.9.9 for Corepack --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7de2103c4..617cf0a88 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "private": true, "version": "0.1.3", "type": "module", - "packageManager": "npm@10.9.9", + "packageManager": "npm@10.9.9+sha512.d60fba8cb42f688b81e33c2f1cbef2ad7b977166700ec0ad057f1b6d60ea6ef2524abf673e20c35931cd8305d1dbb8887134d6eefdc0e7b8435bd458bf65b862", "engines": { "node": ">=22.22.2 <23" }, From f0a0c42ef98018b65f96486eab37f673e1189731 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 21 Sep 2026 17:05:55 +0900 Subject: [PATCH 130/161] fix(node): require integrity-bound npm Corepack locator --- scripts/checks/activate_pinned_npm_runtime.sh | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/scripts/checks/activate_pinned_npm_runtime.sh b/scripts/checks/activate_pinned_npm_runtime.sh index ca9b41586..1216e5286 100644 --- a/scripts/checks/activate_pinned_npm_runtime.sh +++ b/scripts/checks/activate_pinned_npm_runtime.sh @@ -8,8 +8,13 @@ package_manager_spec="$({ import { readFileSync } from "node:fs"; const manifest = JSON.parse(readFileSync("package.json", "utf8")); -if (typeof manifest.packageManager !== "string" || !/^npm@[0-9]+\.[0-9]+\.[0-9]+$/.test(manifest.packageManager)) { - throw new Error("package.json must pin packageManager to an exact npm version"); +if ( + typeof manifest.packageManager !== "string" || + !/^npm@[0-9]+\.[0-9]+\.[0-9]+\+sha512\.[0-9a-f]{128}$/.test(manifest.packageManager) +) { + throw new Error( + "package.json must pin packageManager to an exact npm version plus SHA-512 integrity" + ); } process.stdout.write(manifest.packageManager); NODE From bbcdd3134ecc6edc7b994a546859006b49cbe5a5 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 21 Sep 2026 17:06:28 +0900 Subject: [PATCH 131/161] test(node): exercise hashed npm locator in activation harness --- .../tests/test_npm_runtime_activation_resilience.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/services/analysis-engine/tests/test_npm_runtime_activation_resilience.py b/services/analysis-engine/tests/test_npm_runtime_activation_resilience.py index 4220baa49..02b11bc1c 100644 --- a/services/analysis-engine/tests/test_npm_runtime_activation_resilience.py +++ b/services/analysis-engine/tests/test_npm_runtime_activation_resilience.py @@ -14,6 +14,11 @@ _CI_WORKFLOW = _REPOSITORY_ROOT / ".github" / "workflows" / "ci.yml" _ACTIVATION_HELPER = _REPOSITORY_ROOT / "scripts" / "checks" / "activate_pinned_npm_runtime.sh" _ACTIVATION_COMMAND = "bash scripts/checks/activate_pinned_npm_runtime.sh" +_EXPECTED_PACKAGE_MANAGER = ( + "npm@10.9.9+sha512." + "d60fba8cb42f688b81e33c2f1cbef2ad7b977166700ec0ad057f1b6d60ea6ef" + "2524abf673e20c35931cd8305d1dbb8887134d6eefdc0e7b8435bd458bf65b862" +) def _job_steps(job: object) -> list[dict[str, object]]: @@ -49,7 +54,7 @@ def _fake_command_environment( _write_executable( fake_bin / "node", - "#!/usr/bin/env bash\ncat >/dev/null\nprintf 'npm@10.9.9'\n", + f"#!/usr/bin/env bash\ncat >/dev/null\nprintf '%s' {_EXPECTED_PACKAGE_MANAGER!r}\n", ) _write_executable( fake_bin / "corepack", @@ -186,7 +191,7 @@ def test_pinned_npm_activation_helper_retries_acquisition_but_never_falls_back() assert "corepack enable npm" in source assert "npm run check:npm-runtime" in source assert "sleep_seconds=$((attempt * 5))" in source - assert "/^npm@[0-9]+\\.[0-9]+\\.[0-9]+$/" in source + assert r"/^npm@[0-9]+\.[0-9]+\.[0-9]+\+sha512\.[0-9a-f]{128}$/" in source assert "|| true" not in source assert "npm@10.9.8" not in source @@ -210,7 +215,7 @@ def test_pinned_npm_activation_recovers_after_two_transient_acquisition_failures assert npm_log.read_text(encoding="utf-8").splitlines() == ["run check:npm-runtime"] assert corepack_enable_log.read_text(encoding="utf-8").splitlines() == ["enable npm"] assert "ETIMEDOUT" in completed.stderr - assert "retrying exact npm@10.9.9" in completed.stderr + assert f"retrying exact {_EXPECTED_PACKAGE_MANAGER}" in completed.stderr @pytest.mark.skipif( From 8feed8ac6fdef76f3aaf9d8a3059540dd649331b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 21 Sep 2026 17:06:44 +0900 Subject: [PATCH 132/161] test(node): keep nontransient failures on hashed npm locator --- .../test_npm_runtime_activation_nontransient_failure.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/services/analysis-engine/tests/test_npm_runtime_activation_nontransient_failure.py b/services/analysis-engine/tests/test_npm_runtime_activation_nontransient_failure.py index 649c4b3ce..3ac08bbc5 100644 --- a/services/analysis-engine/tests/test_npm_runtime_activation_nontransient_failure.py +++ b/services/analysis-engine/tests/test_npm_runtime_activation_nontransient_failure.py @@ -10,6 +10,11 @@ _REPOSITORY_ROOT = Path(__file__).resolve().parents[3] _ACTIVATION_HELPER = _REPOSITORY_ROOT / "scripts" / "checks" / "activate_pinned_npm_runtime.sh" +_EXPECTED_PACKAGE_MANAGER = ( + "npm@10.9.9+sha512." + "d60fba8cb42f688b81e33c2f1cbef2ad7b977166700ec0ad057f1b6d60ea6ef" + "2524abf673e20c35931cd8305d1dbb8887134d6eefdc0e7b8435bd458bf65b862" +) def _write_executable(path: Path, content: str) -> None: @@ -32,7 +37,7 @@ def _run_corepack_failure( _write_executable( fake_bin / "node", - "#!/usr/bin/env bash\ncat >/dev/null\nprintf 'npm@10.9.9'\n", + f"#!/usr/bin/env bash\ncat >/dev/null\nprintf '%s' {_EXPECTED_PACKAGE_MANAGER!r}\n", ) _write_executable( fake_bin / "corepack", From ae078569857d66c49708c5fcdd822cf916b43106 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 21 Sep 2026 17:07:21 +0900 Subject: [PATCH 133/161] test(node): bind npm toolchain contract to artifact integrity --- .../analysis-engine/tests/test_npm_toolchain_contract.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/services/analysis-engine/tests/test_npm_toolchain_contract.py b/services/analysis-engine/tests/test_npm_toolchain_contract.py index 0a69c43ef..20b040e4e 100644 --- a/services/analysis-engine/tests/test_npm_toolchain_contract.py +++ b/services/analysis-engine/tests/test_npm_toolchain_contract.py @@ -10,6 +10,13 @@ _REPOSITORY_ROOT = Path(__file__).resolve().parents[3] _EXPECTED_NPM_VERSION = "10.9.9" +_EXPECTED_NPM_INTEGRITY = ( + "d60fba8cb42f688b81e33c2f1cbef2ad7b977166700ec0ad057f1b6d60ea6ef" + "2524abf673e20c35931cd8305d1dbb8887134d6eefdc0e7b8435bd458bf65b862" +) +_EXPECTED_PACKAGE_MANAGER = ( + f"npm@{_EXPECTED_NPM_VERSION}+sha512.{_EXPECTED_NPM_INTEGRITY}" +) _EXPECTED_NODE_VERSION = "22.22.3" _MINIMUM_NPM_TAR_VERSION = "7.5.19" _NPM_RUNTIME_CHECK = "node scripts/checks/verify_npm_runtime.mjs" @@ -132,7 +139,7 @@ def test_root_manifest_pins_the_lockfile_generator_and_fails_on_drift() -> None: """Require npm and source-tree commands to reject a different generator.""" manifest = _root_manifest() - assert manifest["packageManager"] == f"npm@{_EXPECTED_NPM_VERSION}" + assert manifest["packageManager"] == _EXPECTED_PACKAGE_MANAGER assert manifest["engines"] == {"node": ">=22.22.2 <23"} assert manifest["devEngines"] == { "packageManager": { From 347530bd4989aa20bdfca320ea5b1dd2e722ccad Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 21 Sep 2026 17:08:04 +0900 Subject: [PATCH 134/161] docs(traceability): record npm artifact integrity pin --- .../npm-package-manager-integrity-pin.md | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 docs/traceability/npm-package-manager-integrity-pin.md diff --git a/docs/traceability/npm-package-manager-integrity-pin.md b/docs/traceability/npm-package-manager-integrity-pin.md new file mode 100644 index 000000000..76fc8f622 --- /dev/null +++ b/docs/traceability/npm-package-manager-integrity-pin.md @@ -0,0 +1,64 @@ +# npm package-manager artifact integrity pin + +Status: Proposed + +## Problem + +BandScope already pins npm `10.9.9`, rejects fallback to Node-bundled/system/latest npm, verifies the acquired npm version and bundled `tar` floor, and fails closed on unclassified Corepack acquisition errors. The remaining trust gap was narrower: root `package.json` named only `npm@10.9.9`, so the reviewed repository metadata selected an exact version but did not bind that selection to one exact package-manager artifact digest. + +Corepack supports an integrity suffix in the `packageManager` locator. Its current documentation states that the hash is optional but strongly recommended as a security practice, and its test suite exercises `+sha512.` locators. Repository policy should therefore carry the artifact digest that Corepack is expected to verify instead of relying on a version-only locator plus post-acquisition version inspection. + +This is defense in depth, not a claim that the previous path had no integrity protection. Corepack retains its own registry-signature/integrity checks. The repository-level hash adds an immutable reviewed artifact identity to BandScope's package-manager contract. + +## Constraints + +- npm `10.9.9` remains the only accepted package-manager version for this owner. +- The locator must use SHA-512 and exactly 128 lowercase hexadecimal digits. +- `devEngines.packageManager.version` remains `10.9.9`; the integrity suffix belongs to the Corepack `packageManager` locator, not the npm semantic version. +- `verify_npm_runtime.mjs` still verifies the executing npm version and bundled `tar` floor after acquisition; artifact pinning does not replace runtime postconditions. +- Retry remains limited to the exact Corepack acquisition step and positively identified `ETIMEDOUT`. Signature, integrity, policy, metadata, and unknown failures are not retryable. +- No bundled/system/latest npm fallback is introduced. +- No dependency version, lockfile dependency graph, application behavior, MIR behavior, release permission, or branch-protection threshold changes in this repair. + +## Decision + +The root manifest now pins: + +`npm@10.9.9+sha512.d60fba8cb42f688b81e33c2f1cbef2ad7b977166700ec0ad057f1b6d60ea6ef2524abf673e20c35931cd8305d1dbb8887134d6eefdc0e7b8435bd458bf65b862` + +`scripts/checks/activate_pinned_npm_runtime.sh` accepts only `npm@..+sha512.<128 hex>` package-manager locators before invoking `corepack install --global`. A version-only locator now fails before acquisition. + +The SHA-512 digest corresponds to the npm `10.9.9` artifact integrity value `sha512-1g+6jLQvaIuB4zwvHL7yrXuXcWZwDsCtBX8bbWDqbvJSSr9nPiDDWTHNgwXR27iIcTTW7v3A57hDW9RYv2W4Yg==` when represented in hexadecimal. The final authority remains actual Corepack verification on the exact hosted head; the encoded value is not treated as GREEN merely because it is documented here. + +## RED → repair lineage + +- RED `fb73bc99db83e9c1e243cb45a9d095fd229cd20e` adds a focused regression requiring the exact integrity-bound npm locator and a helper boundary that rejects version-only package-manager locators. The predecessor source fails both assertions. +- Repair `29361c98a88472007893cea3949f4444bf0f4f76` changes the root manifest from a version-only npm locator to the reviewed SHA-512 locator. +- Repair `f0a0c42ef98018b65f96486eab37f673e1189731` makes the canonical activation helper require an integrity-bound SHA-512 locator before Corepack acquisition. +- Regression alignment `bbcdd3134ecc6edc7b994a546859006b49cbe5a5`, `8feed8ac6fdef76f3aaf9d8a3059540dd649331b`, and `ae078569857d66c49708c5fcdd822cf916b43106` update the deterministic activation harness and existing npm toolchain contract to exercise the hashed locator rather than a retired version-only test fixture. + +No hosted RED is claimed for the test-only head because the causal repair followed before terminal hosted evidence. Fresh exact-head workflow results after this documentation commit are required. + +## Rejected alternatives + +- Keep `npm@10.9.9` only: rejected because it pins semantic version but leaves the repository manifest without the artifact digest Corepack can validate. +- Replace Corepack verification with a home-grown tarball downloader/hash checker: rejected because it would duplicate package-manager acquisition and signature/integrity ownership. +- Accept arbitrary `+sha*` text: rejected because an unbounded algorithm/length grammar weakens the reviewed contract. This owner currently standardizes on SHA-512. +- Derive the hash dynamically from the registry at CI runtime: rejected because mutable network metadata would become the authority for what the repository intended to trust. +- Retry integrity mismatch: rejected because a deterministic trust failure is not a transient availability event. + +## Evidence and claim boundary + +The source contract proves only that BandScope records and requires one expected SHA-512 locator before Corepack acquisition. Hosted acceptance still requires Corepack to acquire that exact locator successfully, the canonical runtime verifier to report npm `10.9.9` with the required bundled `tar`, frozen `npm ci` to succeed, and all applicable current-head CI/security/SBOM/SAST/CodeQL gates to settle. + +A future intentional npm upgrade must update the semantic version, artifact digest, deterministic regressions, and this traceability record together. A digest-only change without an explicit package-manager review is a supply-chain finding. + +## Security Notes + +The package-manager locator is repository-owned policy data, not user input. The helper validates its grammar before passing it as one quoted argument to Corepack. No shell evaluation is introduced. Corepack remains responsible for acquisition and its own upstream verification; BandScope constrains which artifact identity it is willing to request and keeps post-acquisition runtime verification as a second boundary. + +## References + +Node.js contributors. (2026). *Corepack README: Configuring a package* [Documentation]. GitHub. https://github.com/nodejs/corepack/blob/d4dcb1f89741603e776bba9d457425750fa26987/README.md + +Node.js contributors. (2026). *Corepack tests: SHA-512 packageManager locator handling* [Source code]. GitHub. https://github.com/nodejs/corepack/blob/d4dcb1f89741603e776bba9d457425750fa26987/tests/Use.test.ts From ff14b04ad57815c6d6c7ab7bb49077f8cd9a05db Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 21 Sep 2026 18:08:00 +0900 Subject: [PATCH 135/161] test(node): discover every npm-consuming workflow --- .../tests/test_npm_toolchain_contract.py | 33 +++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/services/analysis-engine/tests/test_npm_toolchain_contract.py b/services/analysis-engine/tests/test_npm_toolchain_contract.py index 20b040e4e..a0e2e0b81 100644 --- a/services/analysis-engine/tests/test_npm_toolchain_contract.py +++ b/services/analysis-engine/tests/test_npm_toolchain_contract.py @@ -230,16 +230,19 @@ def test_root_lock_preserves_esbuild_peer_metadata() -> None: def test_npm_consuming_workflows_activate_pinned_runtime_before_dependency_reads() -> None: - """Prevent dependency reads before Corepack selects and verifies the reviewed npm runtime.""" - workflow_names = ("ci.yml", "release.yml", "security-audit.yml", "build-baseline.yml") - - for workflow_name in workflow_names: - workflow_path = _REPOSITORY_ROOT / ".github" / "workflows" / workflow_name + """Discover every npm consumer and require the canonical runtime before dependency reads.""" + workflows_dir = _REPOSITORY_ROOT / ".github" / "workflows" + workflow_paths = sorted((*workflows_dir.glob("*.yml"), *workflows_dir.glob("*.yaml"))) + assert workflow_paths, "repository must contain GitHub Actions workflows" + + npm_consumer_workflows = 0 + npm_consumer_jobs = 0 + for workflow_path in workflow_paths: document = yaml.safe_load(workflow_path.read_text(encoding="utf-8")) assert isinstance(document, dict) jobs = document.get("jobs") assert isinstance(jobs, dict) - npm_consumers = 0 + workflow_consumers = 0 for job_name in jobs: steps = _job_steps(jobs, str(job_name)) @@ -250,7 +253,8 @@ def test_npm_consuming_workflows_activate_pinned_runtime_before_dependency_reads ) if not consumes_npm: continue - npm_consumers += 1 + workflow_consumers += 1 + npm_consumer_jobs += 1 _assert_checkout_credentials_not_persisted(steps) setup_node_steps = [ @@ -259,15 +263,18 @@ def test_npm_consuming_workflows_activate_pinned_runtime_before_dependency_reads if isinstance(step.get("uses"), str) and str(step["uses"]).startswith("actions/setup-node@") ] - assert len(setup_node_steps) == 1, f"{workflow_name}:{job_name} setup-node ownership" + context = f"{workflow_path.name}:{job_name}" + assert len(setup_node_steps) == 1, f"{context} setup-node ownership" setup_options = setup_node_steps[0].get("with") assert isinstance(setup_options, dict) - assert "cache" not in setup_options, ( - f"{workflow_name}:{job_name} pre-Corepack npm cache" - ) + assert "cache" not in setup_options, f"{context} pre-Corepack npm cache" assert setup_options.get("package-manager-cache") is False, ( - f"{workflow_name}:{job_name} must disable setup-node package-manager cache" + f"{context} must disable setup-node package-manager cache" ) _assert_patched_npm_precedes_dependency_consumption(steps) - assert npm_consumers > 0, f"{workflow_name} must contain an npm dependency consumer" + if workflow_consumers: + npm_consumer_workflows += 1 + + assert npm_consumer_workflows > 0, "repository must contain an npm-consuming workflow" + assert npm_consumer_jobs > 0, "repository must contain an npm-consuming workflow job" From ce4d5264542a036906c0485c6082174cd323a918 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 21 Sep 2026 18:09:07 +0900 Subject: [PATCH 136/161] test(node): tolerate reusable workflow call jobs --- .../analysis-engine/tests/test_npm_toolchain_contract.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/services/analysis-engine/tests/test_npm_toolchain_contract.py b/services/analysis-engine/tests/test_npm_toolchain_contract.py index a0e2e0b81..ad5937a5d 100644 --- a/services/analysis-engine/tests/test_npm_toolchain_contract.py +++ b/services/analysis-engine/tests/test_npm_toolchain_contract.py @@ -244,7 +244,12 @@ def test_npm_consuming_workflows_activate_pinned_runtime_before_dependency_reads assert isinstance(jobs, dict) workflow_consumers = 0 - for job_name in jobs: + for job_name, job in jobs.items(): + assert isinstance(job, dict) + if job.get("steps") is None: + # Reusable-workflow call jobs have no local shell steps. Their called workflow + # is scanned independently when it lives in this repository workflow directory. + continue steps = _job_steps(jobs, str(job_name)) consumes_npm = any( isinstance(step.get("run"), str) From 6719ead3230eeddf18f70949c1cc3f9bb364e257 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 21 Sep 2026 18:09:34 +0900 Subject: [PATCH 137/161] docs(node): trace workflow npm consumer discovery --- .../npm-workflow-consumer-discovery.md | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 docs/traceability/npm-workflow-consumer-discovery.md diff --git a/docs/traceability/npm-workflow-consumer-discovery.md b/docs/traceability/npm-workflow-consumer-discovery.md new file mode 100644 index 000000000..f96179686 --- /dev/null +++ b/docs/traceability/npm-workflow-consumer-discovery.md @@ -0,0 +1,71 @@ +# npm workflow consumer discovery + +Status: Proposed + +## Problem + +BandScope has one repository-owned npm runtime acquisition boundary: `scripts/checks/activate_pinned_npm_runtime.sh`. The structural regression in `test_npm_toolchain_contract.py` previously enforced that boundary only for four named workflow files: `ci.yml`, `release.yml`, `security-audit.yml`, and `build-baseline.yml`. + +That allowlist was weaker than the owner invariant. A new workflow could run `npm ci` without the canonical activation path and remain invisible to the regression until someone manually added its filename. + +This is no longer theoretical. Score Storage PR #1241 introduced `.github/workflows/score-storage-native.yml`. Its UI job sets up Node 22.22.3 and immediately executes raw `npm ci`; exact-head run `35543493216`, job `106165239933`, failed at `Install locked JavaScript dependencies` before the focused ScoreView regressions ran. The Score Storage branch must not copy the mutable #896 helper, but its failure is valid downstream evidence that filename allowlisting is not a durable repository policy. + +## Constraints + +- #896 remains the single writer for repository-wide Node/npm runtime acquisition and provenance. +- #1241 remains the Score Storage / Score Attachment owner. Its workflow source is not copied into this branch. +- New workflow files must not require manual enrollment before the npm runtime invariant applies. +- Workflows without local npm dependency reads must not be forced to add Node or Corepack setup. +- Reusable-workflow call jobs have no local `steps`; their called workflow is evaluated independently when it is repository-owned under `.github/workflows`. +- `actions/setup-node` dependency caching remains disabled before the reviewed npm runtime is selected and verified. GitHub documents setup-node as a package-manager-specific dependency-cache surface; the repository therefore keeps `package-manager-cache: false` at this pre-admission boundary. +- A structural policy regression is not hosted runtime proof. The final merge candidate still requires exact-head Actions evidence. + +## Decision + +Commit `ff14b04ad57815c6d6c7ab7bb49077f8cd9a05db` replaces the workflow filename allowlist with discovery of every top-level `.yml` and `.yaml` file under `.github/workflows`. Every job with local shell steps is inspected. When a job executes `npm ci`, the existing owner contract is applied: + +1. exactly one checkout step exists and does not persist credentials; +2. exactly one `actions/setup-node` step exists; +3. setup-node package-manager caching is disabled before runtime admission; +4. the canonical npm activation helper executes before the first `npm ci` dependency read. + +Commit `ce4d5264542a036906c0485c6082174cd323a918` preserves that discovery while explicitly skipping job-level reusable-workflow calls that have no local `steps`; repository-owned called workflows remain discoverable as workflow files themselves. + +The downstream #1241 failure is the realistic RED evidence for the missing discovery policy. These commits repair the canonical owner guardrail; they do not claim that #1241 is fixed before #896 reaches protected truth and #1241 is ordinarily reconciled on top of it. + +## Rejected alternatives + +- Add `score-storage-native.yml` to the existing filename tuple: rejected because the next workflow can bypass the policy again. +- Copy `activate_pinned_npm_runtime.sh` into #1241 before #896 integrates: rejected because that creates a mutable second owner and can drift from retry/integrity policy. +- Require Node setup in every workflow regardless of whether it reads npm dependencies: rejected because unrelated SBOM/security/native-only jobs do not cross this trust boundary. +- Treat the #1241 UI failure as evidence that ScoreView regressions failed: rejected because the regression step was skipped after dependency admission failed. +- Blindly rerun #1241: rejected because the source configuration would be unchanged. + +## Evidence and claim boundary + +At #1241 exact head `b29b7b522478780db44db1c754ab7f660ed2b17b`, `score-storage-native` run `35543493216` produced: + +- macOS native job `106165239883`: success; +- Windows native job `106165239907`: success; +- UI job `106165239933`: failure at `Install locked JavaScript dependencies`; the focused UI regression step was skipped. + +The exact #1241 workflow source contains Node 22.22.3 setup with `package-manager-cache: false` followed directly by `npm ci`, with no canonical activation step. This document does not invent the unavailable npm stderr and does not assert that a particular diagnostic string caused the failure. + +The discovery regression proves repository source topology: once an npm-consuming workflow exists in the same candidate tree, it cannot silently bypass the canonical activation ordering without failing the policy test. It does not prove network availability, Corepack registry behavior, npm extraction, application tests, or release readiness. + +## Follow-up + +- Keep #896 Draft until exact-current-head repository/security/native gates and independent review settle. +- After #896 reaches protected `develop`, ordinary/non-force reconcile #1241 and replace its raw npm admission with the protected canonical activation path. +- Run #1241's focused UI job again on that unchanged reconciled head and require the ScoreView/scoreStorage regressions themselves to execute and pass. +- Treat any future npm-consuming workflow that fails the discovery policy as an owner-path repair finding rather than adding another filename exception. + +## Security Notes + +The repair does not add credentials, network permissions, dependency caches, package registries, or fallback runtimes. It broadens only the static enforcement surface from a hand-maintained filename list to the repository's actual workflow inventory. This reduces the chance that a new workflow consumes dependencies using the Node-bundled/system npm before the integrity-bound repository runtime is selected. + +## References + +GitHub. (2026). *Building and testing Node.js*. GitHub Docs. https://docs.github.com/en/actions/tutorials/build-and-test-code/nodejs + +GitHub. (2026). *Dependency caching reference*. GitHub Docs. https://docs.github.com/en/actions/reference/workflows-and-actions/dependency-caching From 628aa96b0b9d0f87386dbf88fdaed41d2e121c17 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 21 Sep 2026 19:03:46 +0900 Subject: [PATCH 138/161] test(ci): reject inline npm runtime activation --- .../tests/test_npm_activation_single_path.py | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 services/analysis-engine/tests/test_npm_activation_single_path.py diff --git a/services/analysis-engine/tests/test_npm_activation_single_path.py b/services/analysis-engine/tests/test_npm_activation_single_path.py new file mode 100644 index 000000000..38a60432b --- /dev/null +++ b/services/analysis-engine/tests/test_npm_activation_single_path.py @@ -0,0 +1,76 @@ +"""Keep npm-consuming workflows on the canonical runtime activation path.""" + +from __future__ import annotations + +import re +from pathlib import Path + +import yaml + +_REPOSITORY_ROOT = Path(__file__).resolve().parents[3] +_CANONICAL_ACTIVATION = "bash scripts/checks/activate_pinned_npm_runtime.sh" +_NPM_CI = re.compile(r"(?:^|\n)\s*npm ci(?:\s|$)") + + +def test_npm_consumers_use_only_the_canonical_activation_helper() -> None: + """Reject workflow-local Corepack/runtime activation before npm dependency reads.""" + workflow_paths = sorted( + (*(_REPOSITORY_ROOT / ".github" / "workflows").glob("*.yml"),) + + (*(_REPOSITORY_ROOT / ".github" / "workflows").glob("*.yaml"),) + ) + assert workflow_paths + + consumers = 0 + for workflow_path in workflow_paths: + document = yaml.safe_load(workflow_path.read_text(encoding="utf-8")) + assert isinstance(document, dict) + jobs = document.get("jobs") + assert isinstance(jobs, dict) + + for job_name, job in jobs.items(): + assert isinstance(job, dict) + steps = job.get("steps") + if steps is None: + continue + assert isinstance(steps, list) + run_steps = [ + str(step["run"]) + for step in steps + if isinstance(step, dict) and isinstance(step.get("run"), str) + ] + consumption_index = next( + ( + index + for index, command in enumerate(run_steps) + if _NPM_CI.search(command) + ), + None, + ) + if consumption_index is None: + continue + + consumers += 1 + context = f"{workflow_path.name}:{job_name}" + helper_indices = [ + index + for index, command in enumerate(run_steps) + if command.strip() == _CANONICAL_ACTIVATION + ] + assert helper_indices == [helper_indices[0]] if helper_indices else False, ( + f"{context} must use exactly one canonical npm activation helper" + ) + assert helper_indices[0] < consumption_index, ( + f"{context} must activate the pinned npm runtime before npm ci" + ) + + for command in run_steps: + if command.strip() == _CANONICAL_ACTIVATION: + continue + assert "corepack enable npm" not in command, ( + f"{context} must not duplicate Corepack activation inline" + ) + assert "npm run check:npm-runtime" not in command, ( + f"{context} must not duplicate npm runtime verification inline" + ) + + assert consumers > 0 From 2ddd8b151c2223984eaa088f01865427ad04ae0b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 21 Sep 2026 19:04:04 +0900 Subject: [PATCH 139/161] fix(ci): route release npm activation through canonical helper --- .github/workflows/release.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index aa69a973c..449b3e064 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -45,9 +45,7 @@ jobs: - name: Install Rust stable run: rustup toolchain install stable --profile minimal - name: Activate and verify pinned npm runtime - run: | - corepack enable npm - npm run check:npm-runtime + run: bash scripts/checks/activate_pinned_npm_runtime.sh - name: Install node dependencies run: npm ci - name: Sync Python dependencies From ab04754df0f48798bb50d50baf4ab6aa38e89f1e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 21 Sep 2026 19:04:24 +0900 Subject: [PATCH 140/161] fix(ci): route security npm activation through canonical helper --- .github/workflows/security-audit.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/security-audit.yml b/.github/workflows/security-audit.yml index 07754a782..eb2427973 100644 --- a/.github/workflows/security-audit.yml +++ b/.github/workflows/security-audit.yml @@ -42,9 +42,7 @@ jobs: version: "0.8.6" enable-cache: false - name: Activate and verify pinned npm runtime - run: | - corepack enable npm - npm run check:npm-runtime + run: bash scripts/checks/activate_pinned_npm_runtime.sh - name: Install node dependencies run: npm ci - name: Audit npm dependencies From df20e63895968bea105510478ccc04995981d18d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 21 Sep 2026 19:04:54 +0900 Subject: [PATCH 141/161] test(ci): cover every direct npm workflow invocation --- .../tests/test_npm_activation_single_path.py | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/services/analysis-engine/tests/test_npm_activation_single_path.py b/services/analysis-engine/tests/test_npm_activation_single_path.py index 38a60432b..70ef492a6 100644 --- a/services/analysis-engine/tests/test_npm_activation_single_path.py +++ b/services/analysis-engine/tests/test_npm_activation_single_path.py @@ -1,4 +1,4 @@ -"""Keep npm-consuming workflows on the canonical runtime activation path.""" +"""Keep workflow npm execution on the canonical runtime activation path.""" from __future__ import annotations @@ -9,15 +9,19 @@ _REPOSITORY_ROOT = Path(__file__).resolve().parents[3] _CANONICAL_ACTIVATION = "bash scripts/checks/activate_pinned_npm_runtime.sh" -_NPM_CI = re.compile(r"(?:^|\n)\s*npm ci(?:\s|$)") +_DIRECT_NPM = re.compile( + r"(?:^|[;&|])\s*" + r"(?:env\s+(?:[A-Za-z_][A-Za-z0-9_]*=[^\s;&|]+\s+)*)?" + r"(?:[A-Za-z_][A-Za-z0-9_]*=[^\s;&|]+\s+)*" + r"(?:command\s+)?npm(?:\s|$)", + re.MULTILINE, +) def test_npm_consumers_use_only_the_canonical_activation_helper() -> None: - """Reject workflow-local Corepack/runtime activation before npm dependency reads.""" - workflow_paths = sorted( - (*(_REPOSITORY_ROOT / ".github" / "workflows").glob("*.yml"),) - + (*(_REPOSITORY_ROOT / ".github" / "workflows").glob("*.yaml"),) - ) + """Reject workflow-local Corepack/runtime activation before direct npm execution.""" + workflows_dir = _REPOSITORY_ROOT / ".github" / "workflows" + workflow_paths = sorted((*workflows_dir.glob("*.yml"), *workflows_dir.glob("*.yaml"))) assert workflow_paths consumers = 0 @@ -38,15 +42,15 @@ def test_npm_consumers_use_only_the_canonical_activation_helper() -> None: for step in steps if isinstance(step, dict) and isinstance(step.get("run"), str) ] - consumption_index = next( + first_npm_index = next( ( index for index, command in enumerate(run_steps) - if _NPM_CI.search(command) + if _DIRECT_NPM.search(command) ), None, ) - if consumption_index is None: + if first_npm_index is None: continue consumers += 1 @@ -56,11 +60,11 @@ def test_npm_consumers_use_only_the_canonical_activation_helper() -> None: for index, command in enumerate(run_steps) if command.strip() == _CANONICAL_ACTIVATION ] - assert helper_indices == [helper_indices[0]] if helper_indices else False, ( + assert len(helper_indices) == 1, ( f"{context} must use exactly one canonical npm activation helper" ) - assert helper_indices[0] < consumption_index, ( - f"{context} must activate the pinned npm runtime before npm ci" + assert helper_indices[0] < first_npm_index, ( + f"{context} must activate the pinned npm runtime before direct npm execution" ) for command in run_steps: From 8113cbfcffc60af3cdc9a25df0709548d7fc2bd4 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 21 Sep 2026 19:06:29 +0900 Subject: [PATCH 142/161] docs(traceability): record canonical npm activation path --- .../npm-workflow-activation-single-path.md | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 docs/traceability/npm-workflow-activation-single-path.md diff --git a/docs/traceability/npm-workflow-activation-single-path.md b/docs/traceability/npm-workflow-activation-single-path.md new file mode 100644 index 000000000..6158768cd --- /dev/null +++ b/docs/traceability/npm-workflow-activation-single-path.md @@ -0,0 +1,73 @@ +# npm workflow activation single-path contract + +Status: **Proposed** +Owner: BandScope Node/npm runtime vertical (#896) +Last reviewed: 2026-09-21 + +## Problem + +BandScope's canonical npm owner already treated `scripts/checks/activate_pinned_npm_runtime.sh` as the single workflow-level runtime-admission path. That helper owns exact `packageManager` integrity admission, the npm 10.9.9 runtime check, and the bounded `ETIMEDOUT`-only Corepack acquisition policy. + +The repository policy did not fully enforce that ownership. `release.yml` and `security-audit.yml` still duplicated a weaker inline sequence: + +```sh +corepack enable npm +npm run check:npm-runtime +``` + +The existing structural test explicitly accepted that fallback whenever it appeared before `npm ci`. This meant a workflow could bypass the canonical helper's locator validation and failure-classification behavior while still satisfying the repository test. + +## Constraints + +- Node/npm runtime acquisition remains #896 ownership; downstream product owners must not copy a mutable Draft helper. +- Required checks and workflow permissions must not be weakened to make runtime admission pass. +- The repair must preserve existing release/security job behavior except for routing npm activation through the canonical helper. +- Reusable-workflow call jobs with no local `steps` are not direct shell consumers; repository-owned called workflow files are inspected independently. +- Source-level tests are not promoted to hosted GREEN until the unchanged exact head completes its normal repository and central gates. + +## Alternatives considered + +### Keep the inline fallback + +Rejected. It makes the documented single-owner path advisory rather than enforceable and allows helper-specific integrity/failure-classification rules to drift between workflows. + +### Enroll only `release.yml` and `security-audit.yml` + +Rejected. File-name allowlists already proved brittle when Score Storage added a new npm-consuming workflow. The invariant belongs to executable npm consumption, not a fixed workflow inventory. + +### Patch Score Storage #1241 directly + +Rejected. #1241 is a consumer. It must use the protected/released npm runtime owner after #896 integrates instead of vendoring Draft runtime-acquisition source. + +## RED → repair + +1. `628aa96b0b9d0f87386dbf88fdaed41d2e121c17` adds a repository regression requiring npm-consuming workflow jobs to use the canonical activation helper and rejecting workflow-local Corepack/runtime verification. Repair followed immediately, so no hosted terminal RED is claimed for this test-only head. +2. `2ddd8b151c2223984eaa088f01865427ad04ae0b` routes release preflight through `bash scripts/checks/activate_pinned_npm_runtime.sh`. +3. `ab04754df0f48798bb50d50baf4ab6aa38e89f1e` routes the security backstop through the same helper. +4. `df20e63895968bea105510478ccc04995981d18d` strengthens the regression from literal `npm ci` matching to direct `npm` execution at normal shell-command boundaries, including common environment-assignment and `command npm` forms. This prevents npm command aliases or a different direct npm subcommand from silently escaping runtime admission. + +## Authority and evidence + +npm documents `npm ci` as a clean-install command for automated environments and exposes aliases such as `clean-install`, `ic`, and `install-clean`. Therefore the repository contract guards the direct npm executable rather than one spelling of the install subcommand. + +Reference: npm, Inc. (2026). *npm-ci*. https://docs.npmjs.com/cli/commands/npm-ci/ + +The canonical activation helper remains the only place that may acquire/enable the pinned npm runtime. Workflow jobs may execute npm only after that helper returns successfully. + +## Security notes + +The trust boundary is CI dependency-tool execution. A workflow must not reach an npm command under an unreviewed bundled/system/latest runtime or a workflow-local activation sequence that omits the owner helper's integrity and failure-classification policy. + +The structural regression recognizes direct npm execution at ordinary shell boundaries, including environment assignments and `command npm`. Deliberately hiding npm behind another interpreter or generated shell program is outside the current parser and is not an accepted bypass; such a workflow requires explicit policy review and a regression extension before merge. + +## Effect + +- Release and security workflows now consume the same runtime-admission implementation as CI/build owners. +- SHA-512 locator admission, bounded transient acquisition retry, fail-closed nontransient behavior, and runtime verification have one workflow-level owner. +- A future direct npm consumer cannot satisfy the repository test merely by reproducing `corepack enable npm` and `npm run check:npm-runtime` inline. + +## Follow-up + +- Obtain terminal exact-head CI, build, security/SAST/SBOM/CodeQL and independent non-author review for the final #896 head. +- After #896 reaches protected truth, ordinary/non-force reconcile #1241 and replace its raw bundled-npm dependency admission with the protected canonical helper. +- Re-run #1241's focused ScoreView UI regression on that exact consumer head; do not transfer predecessor failures or successes. From 190a052c60e01fec347c3009df3e5a1869823a7d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 21 Sep 2026 19:09:26 +0900 Subject: [PATCH 143/161] style(ci): apply canonical ruff formatting --- .../tests/test_npm_activation_single_path.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/services/analysis-engine/tests/test_npm_activation_single_path.py b/services/analysis-engine/tests/test_npm_activation_single_path.py index 70ef492a6..8fbfa603a 100644 --- a/services/analysis-engine/tests/test_npm_activation_single_path.py +++ b/services/analysis-engine/tests/test_npm_activation_single_path.py @@ -43,11 +43,7 @@ def test_npm_consumers_use_only_the_canonical_activation_helper() -> None: if isinstance(step, dict) and isinstance(step.get("run"), str) ] first_npm_index = next( - ( - index - for index, command in enumerate(run_steps) - if _DIRECT_NPM.search(command) - ), + (index for index, command in enumerate(run_steps) if _DIRECT_NPM.search(command)), None, ) if first_npm_index is None: From a41a2e5b8d3f6e53c7df232dd449b842c866e3c9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 21 Sep 2026 19:09:36 +0900 Subject: [PATCH 144/161] style(ci): format npm integrity regression --- .../tests/test_npm_package_manager_integrity_pin.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/services/analysis-engine/tests/test_npm_package_manager_integrity_pin.py b/services/analysis-engine/tests/test_npm_package_manager_integrity_pin.py index 99f4565ad..3b218c388 100644 --- a/services/analysis-engine/tests/test_npm_package_manager_integrity_pin.py +++ b/services/analysis-engine/tests/test_npm_package_manager_integrity_pin.py @@ -11,9 +11,7 @@ "d60fba8cb42f688b81e33c2f1cbef2ad7b977166700ec0ad057f1b6d60ea6ef" "2524abf673e20c35931cd8305d1dbb8887134d6eefdc0e7b8435bd458bf65b862" ) -_EXPECTED_LOCATOR_PATTERN = ( - r"/^npm@[0-9]+\.[0-9]+\.[0-9]+\+sha512\.[0-9a-f]{128}$/" -) +_EXPECTED_LOCATOR_PATTERN = r"/^npm@[0-9]+\.[0-9]+\.[0-9]+\+sha512\.[0-9a-f]{128}$/" def test_root_manifest_integrity_pins_the_reviewed_npm_artifact() -> None: @@ -25,9 +23,9 @@ def test_root_manifest_integrity_pins_the_reviewed_npm_artifact() -> None: def test_activation_helper_rejects_version_only_package_manager_locators() -> None: """Prevent an exact version from being mistaken for package-manager artifact integrity.""" - source = ( - _REPOSITORY_ROOT / "scripts" / "checks" / "activate_pinned_npm_runtime.sh" - ).read_text(encoding="utf-8") + source = (_REPOSITORY_ROOT / "scripts" / "checks" / "activate_pinned_npm_runtime.sh").read_text( + encoding="utf-8" + ) assert _EXPECTED_LOCATOR_PATTERN in source assert 'corepack install --global "$package_manager_spec"' in source From 6366eb66635bada29fe72ec99e55efdeeeaaecd0 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 21 Sep 2026 19:10:04 +0900 Subject: [PATCH 145/161] style(ci): format npm toolchain regression --- services/analysis-engine/tests/test_npm_toolchain_contract.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/services/analysis-engine/tests/test_npm_toolchain_contract.py b/services/analysis-engine/tests/test_npm_toolchain_contract.py index ad5937a5d..c76eab43d 100644 --- a/services/analysis-engine/tests/test_npm_toolchain_contract.py +++ b/services/analysis-engine/tests/test_npm_toolchain_contract.py @@ -14,9 +14,7 @@ "d60fba8cb42f688b81e33c2f1cbef2ad7b977166700ec0ad057f1b6d60ea6ef" "2524abf673e20c35931cd8305d1dbb8887134d6eefdc0e7b8435bd458bf65b862" ) -_EXPECTED_PACKAGE_MANAGER = ( - f"npm@{_EXPECTED_NPM_VERSION}+sha512.{_EXPECTED_NPM_INTEGRITY}" -) +_EXPECTED_PACKAGE_MANAGER = f"npm@{_EXPECTED_NPM_VERSION}+sha512.{_EXPECTED_NPM_INTEGRITY}" _EXPECTED_NODE_VERSION = "22.22.3" _MINIMUM_NPM_TAR_VERSION = "7.5.19" _NPM_RUNTIME_CHECK = "node scripts/checks/verify_npm_runtime.mjs" From 8eb47f74ad706312b677a3e173f68d75ebbb8599 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 21 Sep 2026 19:10:32 +0900 Subject: [PATCH 146/161] docs(traceability): record npm gate RCA and formatter repair --- .../npm-workflow-activation-single-path.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/traceability/npm-workflow-activation-single-path.md b/docs/traceability/npm-workflow-activation-single-path.md index 6158768cd..53783dae8 100644 --- a/docs/traceability/npm-workflow-activation-single-path.md +++ b/docs/traceability/npm-workflow-activation-single-path.md @@ -46,6 +46,18 @@ Rejected. #1241 is a consumer. It must use the protected/released npm runtime ow 3. `ab04754df0f48798bb50d50baf4ab6aa38e89f1e` routes the security backstop through the same helper. 4. `df20e63895968bea105510478ccc04995981d18d` strengthens the regression from literal `npm ci` matching to direct `npm` execution at normal shell-command boundaries, including common environment-assignment and `command npm` forms. This prevents npm command aliases or a different direct npm subcommand from silently escaping runtime admission. +## Exact-head verification finding + +The first hosted generation on `8113cbfcffc60af3cdc9a25df0709548d7fc2bd4` proved the pinned npm helper itself on Node 22.22.2: Corepack installed the integrity-bound npm 10.9.9 locator, `verify_npm_runtime.mjs` reported npm 10.9.9 with bundled tar 7.5.22, and frozen `npm ci` completed. The job then failed at `ruff format --check --diff` because three #896-owned Python regression files were not in canonical Ruff format. + +This was a repository-source defect, not a runner or npm-acquisition failure. It was repaired immediately from the emitted formatter diff: + +- `190a052c60e01fec347c3009df3e5a1869823a7d` formats `test_npm_activation_single_path.py`. +- `a41a2e5b8d3f6e53c7df232dd449b842c866e3c9` formats `test_npm_package_manager_integrity_pin.py`. +- `6366eb66635bada29fe72ec99e55efdeeeaaecd0` formats `test_npm_toolchain_contract.py`. + +The failed `8113cbfc...` verdict is predecessor evidence only. The repaired final head must obtain its own unchanged exact-head gates. + ## Authority and evidence npm documents `npm ci` as a clean-install command for automated environments and exposes aliases such as `clean-install`, `ic`, and `install-clean`. Therefore the repository contract guards the direct npm executable rather than one spelling of the install subcommand. From a990e7c70b40dae748123d1447c7ae724edc60e6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 21 Sep 2026 19:13:47 +0900 Subject: [PATCH 147/161] test(ci): remove legacy inline npm activation fallback --- .../tests/test_npm_toolchain_contract.py | 40 ++++++------------- 1 file changed, 13 insertions(+), 27 deletions(-) diff --git a/services/analysis-engine/tests/test_npm_toolchain_contract.py b/services/analysis-engine/tests/test_npm_toolchain_contract.py index c76eab43d..53aa5e397 100644 --- a/services/analysis-engine/tests/test_npm_toolchain_contract.py +++ b/services/analysis-engine/tests/test_npm_toolchain_contract.py @@ -92,7 +92,7 @@ def _assert_no_mutable_npm_commands(steps: list[dict[str, object]]) -> None: def _assert_patched_npm_precedes_dependency_consumption(steps: list[dict[str, object]]) -> None: - """Require reviewed npm activation and audit before the first npm dependency read.""" + """Require the canonical npm activation helper before the first dependency read.""" run_steps = [str(step["run"]) for step in steps if isinstance(step.get("run"), str)] consumption_index = next( ( @@ -104,33 +104,19 @@ def _assert_patched_npm_precedes_dependency_consumption(steps: list[dict[str, ob ) assert consumption_index is not None - helper_index = next( - ( - index - for index, command in enumerate(run_steps) - if command.strip() == _NPM_ACTIVATION_COMMAND - ), - None, - ) - if helper_index is not None: - assert helper_index < consumption_index - return + helper_indices = [ + index + for index, command in enumerate(run_steps) + if command.strip() == _NPM_ACTIVATION_COMMAND + ] + assert len(helper_indices) == 1 + assert helper_indices[0] < consumption_index - activation_index = next( - (index for index, command in enumerate(run_steps) if "corepack enable npm" in command), - None, - ) - audit_index = next( - ( - index - for index, command in enumerate(run_steps) - if "npm run check:npm-runtime" in command - ), - None, - ) - assert activation_index is not None - assert audit_index is not None - assert activation_index <= audit_index < consumption_index + for command in run_steps: + if command.strip() == _NPM_ACTIVATION_COMMAND: + continue + assert "corepack enable npm" not in command + assert "npm run check:npm-runtime" not in command def test_root_manifest_pins_the_lockfile_generator_and_fails_on_drift() -> None: From a7500e3613ea695ab806a958b10a357ab351a555 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 21 Sep 2026 19:14:18 +0900 Subject: [PATCH 148/161] docs(traceability): align npm policy regressions --- docs/traceability/npm-workflow-activation-single-path.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/traceability/npm-workflow-activation-single-path.md b/docs/traceability/npm-workflow-activation-single-path.md index 53783dae8..ea543711e 100644 --- a/docs/traceability/npm-workflow-activation-single-path.md +++ b/docs/traceability/npm-workflow-activation-single-path.md @@ -45,6 +45,7 @@ Rejected. #1241 is a consumer. It must use the protected/released npm runtime ow 2. `2ddd8b151c2223984eaa088f01865427ad04ae0b` routes release preflight through `bash scripts/checks/activate_pinned_npm_runtime.sh`. 3. `ab04754df0f48798bb50d50baf4ab6aa38e89f1e` routes the security backstop through the same helper. 4. `df20e63895968bea105510478ccc04995981d18d` strengthens the regression from literal `npm ci` matching to direct `npm` execution at normal shell-command boundaries, including common environment-assignment and `command npm` forms. This prevents npm command aliases or a different direct npm subcommand from silently escaping runtime admission. +5. `a990e7c70b40dae748123d1447c7ae724edc60e6` removes the older inline-activation fallback from `test_npm_toolchain_contract.py`; both structural regressions now describe the same single canonical activation-path invariant instead of carrying contradictory executable policy. ## Exact-head verification finding @@ -77,6 +78,7 @@ The structural regression recognizes direct npm execution at ordinary shell boun - Release and security workflows now consume the same runtime-admission implementation as CI/build owners. - SHA-512 locator admission, bounded transient acquisition retry, fail-closed nontransient behavior, and runtime verification have one workflow-level owner. - A future direct npm consumer cannot satisfy the repository test merely by reproducing `corepack enable npm` and `npm run check:npm-runtime` inline. +- The original npm-consumer discovery test no longer encodes that rejected fallback, preventing future maintenance from reintroducing two conflicting policy definitions. ## Follow-up From 19ddd51d1cb13ed3d783d9e5b81f1ec4f276a787 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 21 Sep 2026 20:06:51 +0900 Subject: [PATCH 149/161] test(ci): expose shell-wrapped npm consumer blind spots --- .../tests/test_npm_activation_single_path.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/services/analysis-engine/tests/test_npm_activation_single_path.py b/services/analysis-engine/tests/test_npm_activation_single_path.py index 8fbfa603a..a3bc376d0 100644 --- a/services/analysis-engine/tests/test_npm_activation_single_path.py +++ b/services/analysis-engine/tests/test_npm_activation_single_path.py @@ -18,6 +18,20 @@ ) +def test_direct_npm_detection_covers_shell_control_flow_boundaries() -> None: + """Treat npm behind ordinary shell control syntax as a workflow consumer.""" + scripts = ( + "if test -f package-lock.json; then npm ci; fi", + "for attempt in 1; do npm --version; done", + "(npm ci)", + "! npm ci", + "exec npm ci", + ) + + for script in scripts: + assert _DIRECT_NPM.search(script), script + + def test_npm_consumers_use_only_the_canonical_activation_helper() -> None: """Reject workflow-local Corepack/runtime activation before direct npm execution.""" workflows_dir = _REPOSITORY_ROOT / ".github" / "workflows" From dc726efbb09d343bacbae0ec273a21390433e01d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 21 Sep 2026 20:07:16 +0900 Subject: [PATCH 150/161] fix(ci): detect npm behind shell control flow --- .../tests/test_npm_activation_single_path.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/services/analysis-engine/tests/test_npm_activation_single_path.py b/services/analysis-engine/tests/test_npm_activation_single_path.py index a3bc376d0..4f84d95eb 100644 --- a/services/analysis-engine/tests/test_npm_activation_single_path.py +++ b/services/analysis-engine/tests/test_npm_activation_single_path.py @@ -10,10 +10,11 @@ _REPOSITORY_ROOT = Path(__file__).resolve().parents[3] _CANONICAL_ACTIVATION = "bash scripts/checks/activate_pinned_npm_runtime.sh" _DIRECT_NPM = re.compile( - r"(?:^|[;&|])\s*" - r"(?:env\s+(?:[A-Za-z_][A-Za-z0-9_]*=[^\s;&|]+\s+)*)?" - r"(?:[A-Za-z_][A-Za-z0-9_]*=[^\s;&|]+\s+)*" - r"(?:command\s+)?npm(?:\s|$)", + r"(?:^|[;&|(){}]|\b(?:then|do)\b)\s*" + r"(?:!\s*)?" + r"(?:env\s+(?:[A-Za-z_][A-Za-z0-9_]*=[^\s;&|(){}]+\s+)*)?" + r"(?:[A-Za-z_][A-Za-z0-9_]*=[^\s;&|(){}]+\s+)*" + r"(?:(?:command|exec)\s+)?npm(?:\s|$)", re.MULTILINE, ) From 8f03e7fb7bababf80adf06bb4e451a8960f75bcb Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 21 Sep 2026 20:07:53 +0900 Subject: [PATCH 151/161] docs(traceability): record shell npm admission boundary --- .../npm-workflow-activation-single-path.md | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/docs/traceability/npm-workflow-activation-single-path.md b/docs/traceability/npm-workflow-activation-single-path.md index ea543711e..17ac91da2 100644 --- a/docs/traceability/npm-workflow-activation-single-path.md +++ b/docs/traceability/npm-workflow-activation-single-path.md @@ -17,6 +17,8 @@ npm run check:npm-runtime The existing structural test explicitly accepted that fallback whenever it appeared before `npm ci`. This meant a workflow could bypass the canonical helper's locator validation and failure-classification behavior while still satisfying the repository test. +A second policy gap remained after direct-npm discovery was introduced. The detector recognized line starts, shell separators, environment assignments and `command npm`, but it did not recognize ordinary shell control-flow forms such as `then npm`, `do npm`, subshell grouping, negation, or `exec npm`. A workflow could therefore execute npm through normal shell syntax while being misclassified as a non-consumer and escape the single-path invariant. + ## Constraints - Node/npm runtime acquisition remains #896 ownership; downstream product owners must not copy a mutable Draft helper. @@ -24,6 +26,7 @@ The existing structural test explicitly accepted that fallback whenever it appea - The repair must preserve existing release/security job behavior except for routing npm activation through the canonical helper. - Reusable-workflow call jobs with no local `steps` are not direct shell consumers; repository-owned called workflow files are inspected independently. - Source-level tests are not promoted to hosted GREEN until the unchanged exact head completes its normal repository and central gates. +- The detector should recognize ordinary shell execution structure without treating arbitrary prose such as `echo npm ci` as executable npm authority. ## Alternatives considered @@ -35,6 +38,14 @@ Rejected. It makes the documented single-owner path advisory rather than enforce Rejected. File-name allowlists already proved brittle when Score Storage added a new npm-consuming workflow. The invariant belongs to executable npm consumption, not a fixed workflow inventory. +### Match only line-start and command-separator forms + +Rejected. Shell control keywords and grouping are ordinary executable syntax. Treating `if ...; then npm ci; fi`, `for ...; do npm ...; done`, `(npm ci)`, `! npm ci`, or `exec npm ci` as non-consumers creates a lexical bypass in the policy gate even though the runner executes npm normally. + +### Treat every textual `npm` occurrence as execution + +Rejected. A fully lexical substring rule would also classify comments or harmless output such as `echo npm ci` as runtime authority. The current detector remains conservative but execution-oriented: it recognizes supported shell command boundaries and wrappers and requires explicit policy extension if a new execution form is introduced. + ### Patch Score Storage #1241 directly Rejected. #1241 is a consumer. It must use the protected/released npm runtime owner after #896 integrates instead of vendoring Draft runtime-acquisition source. @@ -46,6 +57,8 @@ Rejected. #1241 is a consumer. It must use the protected/released npm runtime ow 3. `ab04754df0f48798bb50d50baf4ab6aa38e89f1e` routes the security backstop through the same helper. 4. `df20e63895968bea105510478ccc04995981d18d` strengthens the regression from literal `npm ci` matching to direct `npm` execution at normal shell-command boundaries, including common environment-assignment and `command npm` forms. This prevents npm command aliases or a different direct npm subcommand from silently escaping runtime admission. 5. `a990e7c70b40dae748123d1447c7ae724edc60e6` removes the older inline-activation fallback from `test_npm_toolchain_contract.py`; both structural regressions now describe the same single canonical activation-path invariant instead of carrying contradictory executable policy. +6. RED `19ddd51d1cb13ed3d783d9e5b81f1ec4f276a787` adds focused detector regressions for `then npm`, `do npm`, subshell grouping, shell negation and `exec npm`. The prior detector fails those cases, so an ordinary shell-wrapped npm consumer could be omitted from policy admission. Repair followed immediately; no hosted terminal RED is claimed for the test-only head. +7. `dc726efbb09d343bacbae0ec273a21390433e01d` extends the execution-boundary detector to shell control keywords, grouping, negation and `exec` while preserving the existing environment-assignment and `command npm` forms. `echo npm ci` remains outside the admitted execution forms rather than becoming a false consumer. ## Exact-head verification finding @@ -57,11 +70,11 @@ This was a repository-source defect, not a runner or npm-acquisition failure. It - `a41a2e5b8d3f6e53c7df232dd449b842c866e3c9` formats `test_npm_package_manager_integrity_pin.py`. - `6366eb66635bada29fe72ec99e55efdeeeaaecd0` formats `test_npm_toolchain_contract.py`. -The failed `8113cbfc...` verdict is predecessor evidence only. The repaired final head must obtain its own unchanged exact-head gates. +The failed `8113cbfc...` verdict is predecessor evidence only. Every later source move, including the shell-control-flow detector repair, requires a fresh unchanged-head verdict. ## Authority and evidence -npm documents `npm ci` as a clean-install command for automated environments and exposes aliases such as `clean-install`, `ic`, and `install-clean`. Therefore the repository contract guards the direct npm executable rather than one spelling of the install subcommand. +npm documents `npm ci` as a clean-install command for automated environments and exposes aliases such as `clean-install`, `ic`, and `install-clean`. Therefore the repository contract guards the npm executable and its execution boundary rather than one spelling of the install subcommand. Reference: npm, Inc. (2026). *npm-ci*. https://docs.npmjs.com/cli/commands/npm-ci/ @@ -71,17 +84,19 @@ The canonical activation helper remains the only place that may acquire/enable t The trust boundary is CI dependency-tool execution. A workflow must not reach an npm command under an unreviewed bundled/system/latest runtime or a workflow-local activation sequence that omits the owner helper's integrity and failure-classification policy. -The structural regression recognizes direct npm execution at ordinary shell boundaries, including environment assignments and `command npm`. Deliberately hiding npm behind another interpreter or generated shell program is outside the current parser and is not an accepted bypass; such a workflow requires explicit policy review and a regression extension before merge. +The structural regression recognizes direct npm execution at line starts, command separators, shell control-flow boundaries, grouping, negation, environment assignments, `command`, and `exec`. Deliberately hiding npm behind another interpreter, generated shell program, or an unrecognized command wrapper is not an accepted bypass; such a workflow requires explicit policy review and a regression extension before merge. ## Effect - Release and security workflows now consume the same runtime-admission implementation as CI/build owners. - SHA-512 locator admission, bounded transient acquisition retry, fail-closed nontransient behavior, and runtime verification have one workflow-level owner. - A future direct npm consumer cannot satisfy the repository test merely by reproducing `corepack enable npm` and `npm run check:npm-runtime` inline. -- The original npm-consumer discovery test no longer encodes that rejected fallback, preventing future maintenance from reintroducing two conflicting policy definitions. +- Ordinary shell control flow no longer lets an npm consumer disappear from workflow policy admission. +- The original npm-consumer discovery test no longer encodes the rejected inline fallback, preventing future maintenance from reintroducing two conflicting policy definitions. ## Follow-up - Obtain terminal exact-head CI, build, security/SAST/SBOM/CodeQL and independent non-author review for the final #896 head. +- If a workflow needs npm through another interpreter or wrapper, add an executable regression for that exact form before admitting it; do not silently broaden the bypass surface. - After #896 reaches protected truth, ordinary/non-force reconcile #1241 and replace its raw bundled-npm dependency admission with the protected canonical helper. - Re-run #1241's focused ScoreView UI regression on that exact consumer head; do not transfer predecessor failures or successes. From bc26032fd02033458912e26a897b4e7073301e3b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 21 Sep 2026 21:32:03 +0900 Subject: [PATCH 152/161] test(node): reject mixed integrity timeout acquisition failure --- ...runtime_activation_nontransient_failure.py | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/services/analysis-engine/tests/test_npm_runtime_activation_nontransient_failure.py b/services/analysis-engine/tests/test_npm_runtime_activation_nontransient_failure.py index 3ac08bbc5..b159194b3 100644 --- a/services/analysis-engine/tests/test_npm_runtime_activation_nontransient_failure.py +++ b/services/analysis-engine/tests/test_npm_runtime_activation_nontransient_failure.py @@ -117,6 +117,27 @@ def test_pinned_npm_activation_does_not_retry_signature_failure(tmp_path: Path) assert "Signature does not match" in result[0].stderr +@pytest.mark.skipif( + os.name == "nt", + reason="shell helper is exercised by hosted Windows lanes", +) +def test_pinned_npm_activation_does_not_retry_mixed_integrity_timeout_failure( + tmp_path: Path, +) -> None: + """Integrity failure wins over a timeout token in the same Corepack diagnostic.""" + result = _run_corepack_failure( + tmp_path, + diagnostic=( + "Integrity check failed while validating package-manager metadata; " + "registry request ended with ETIMEDOUT" + ), + ) + + _assert_immediate_failure(*result) + assert "Integrity check failed" in result[0].stderr + assert "not classified as transient" in result[0].stderr + + @pytest.mark.skipif( os.name == "nt", reason="shell helper is exercised by hosted Windows lanes", From 4cee8f5d4b0f7496fce959deefdce7912fcb8963 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 21 Sep 2026 21:32:32 +0900 Subject: [PATCH 153/161] fix(node): prioritize provenance failures over timeout retry --- scripts/checks/activate_pinned_npm_runtime.sh | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/scripts/checks/activate_pinned_npm_runtime.sh b/scripts/checks/activate_pinned_npm_runtime.sh index 1216e5286..348a124e4 100644 --- a/scripts/checks/activate_pinned_npm_runtime.sh +++ b/scripts/checks/activate_pinned_npm_runtime.sh @@ -34,8 +34,13 @@ while true; do fi printf '%s\n' "$acquisition_output" >&2 - case "$acquisition_output" in - *"ETIMEDOUT"*) + normalized_output="${acquisition_output,,}" + case "$normalized_output" in + *"signature"*|*"integrity"*|*"keyid"*|*"metadata"*|*"checksum"*|*"hash mismatch"*) + echo "Corepack acquisition failure is not classified as transient; refusing to retry or weaken verification." >&2 + exit 1 + ;; + *"etimedout"*) ;; *) echo "Corepack acquisition failure is not classified as transient; refusing to retry or weaken verification." >&2 From 61195d90d41df7e56653cc2f4bd74b6f54f2ffea Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 21 Sep 2026 21:33:01 +0900 Subject: [PATCH 154/161] docs(node): trace mixed Corepack failure precedence --- ...acquisition-mixed-diagnostic-precedence.md | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 docs/traceability/npm-runtime-acquisition-mixed-diagnostic-precedence.md diff --git a/docs/traceability/npm-runtime-acquisition-mixed-diagnostic-precedence.md b/docs/traceability/npm-runtime-acquisition-mixed-diagnostic-precedence.md new file mode 100644 index 000000000..7ea89f572 --- /dev/null +++ b/docs/traceability/npm-runtime-acquisition-mixed-diagnostic-precedence.md @@ -0,0 +1,53 @@ +# npm runtime acquisition mixed-diagnostic precedence + +Status: Proposed + +## Problem + +BandScope retries the exact `corepack install --global` acquisition step only for the hosted-observed `ETIMEDOUT` transport condition. The existing classifier admitted a retry whenever the captured Corepack diagnostic contained `ETIMEDOUT`. + +That rule was incomplete when one diagnostic contained both a timeout token and a trust/provenance failure. For example, an integrity or signature failure can be emitted together with transport context. Substring admission made the timeout token dominant, so the helper could sleep and retry even though the same diagnostic already established that the failure was not purely transient. + +A retry is not equivalent to bypassing verification, but at this boundary it weakens the stated fail-closed policy: signature, integrity and package-manager metadata failures are deterministic trust failures and must never become retryable merely because the upstream diagnostic also mentions a timeout. + +## Constraints + +- `npm@10.9.9` plus its repository-pinned SHA-512 identity remains the only admitted package-manager runtime. +- Only `corepack install --global` may receive bounded retry. +- `ETIMEDOUT` remains the only transport token admitted by current hosted evidence. +- Signature, integrity, key-id, package-manager metadata, checksum and hash-mismatch diagnostics take precedence over the timeout token. +- Unknown failures still fail closed. +- The helper must stop before `corepack enable npm` or `npm run check:npm-runtime` on a trust/provenance failure. +- This repair does not disable Corepack verification, alter integrity keys, select another npm version, or add an npm/system fallback. + +## RED and repair + +RED `bc26032fd02033458912e26a897b4e7073301e3b` adds a deterministic Corepack fixture whose single diagnostic contains both `Integrity check failed` and `ETIMEDOUT`. The regression requires one acquisition attempt, no sleep, no `corepack enable`, no npm invocation, preserved upstream diagnostic text, and the existing `not classified as transient` refusal. The predecessor helper would admit the timeout branch and retry. + +Repair `4cee8f5d4b0f7496fce959deefdce7912fcb8963` normalizes only for classification and checks trust/provenance markers before the timeout allowlist. Known provenance failure wins when both classes occur in one diagnostic. Pure `ETIMEDOUT` behavior and the existing three-attempt 5 s / 10 s backoff remain unchanged. + +The test-only head was immediately followed by the production repair, so no hosted terminal RED is claimed for `bc26032f...`. + +## Rejected alternatives + +- Let any diagnostic containing `ETIMEDOUT` retry: rejected because a mixed diagnostic can already prove a non-transient trust failure. +- Disable or weaken Corepack signature/integrity verification: rejected because the availability problem is in BandScope's retry classification, not the trust check. +- Retry unknown failures and maintain only a small fatal blacklist: rejected because unknown is not evidence of transport transience. +- Broaden the transient allowlist to DNS, HTTP, connection-reset or other network-looking failures: rejected because current hosted evidence only supports `ETIMEDOUT`. +- Parse or evaluate diagnostic text as shell: rejected. Upstream text is untrusted evidence and remains data only. + +## Claim boundary and residual risk + +This is still a diagnostic-string classifier because the current Corepack command boundary does not provide a stable machine-readable failure taxonomy to this script. The precedence list therefore cannot prove semantic completeness for every future Corepack wording. A new provenance diagnostic that is not represented by the known markers may still require a focused regression and classifier update. + +That residual risk is narrower than the repaired defect: a known trust/provenance marker can no longer be overridden by the admitted timeout token. If Corepack exposes a stable structured error code or typed result, BandScope should replace text classification rather than expand string heuristics indefinitely. + +## Security Notes + +The captured Corepack diagnostic is untrusted upstream text. It is written to stderr and inspected only as data for a bounded decision. It is never executed, interpolated into a command, or used to choose an alternate package-manager artifact. Trust/provenance evidence has precedence over retry availability. + +## References + +Node.js contributors. (2026, August 28). *Corepack 0.36.0* [Software release]. GitHub. https://github.com/nodejs/corepack/releases/tag/v0.36.0 + +Node.js contributors. (2026). *verifySignature fails when registry returns dist.signatures on package root but not on version endpoint* (Issue #808). GitHub. https://github.com/nodejs/corepack/issues/808 From 237ff476bd9f589c41c3c5a7fe96aa47149d8f19 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 21 Sep 2026 21:59:38 +0900 Subject: [PATCH 155/161] test(node): catch shell continuation npm command bypass --- .../analysis-engine/tests/test_npm_activation_single_path.py | 1 + 1 file changed, 1 insertion(+) diff --git a/services/analysis-engine/tests/test_npm_activation_single_path.py b/services/analysis-engine/tests/test_npm_activation_single_path.py index 4f84d95eb..29ab48b3a 100644 --- a/services/analysis-engine/tests/test_npm_activation_single_path.py +++ b/services/analysis-engine/tests/test_npm_activation_single_path.py @@ -27,6 +27,7 @@ def test_direct_npm_detection_covers_shell_control_flow_boundaries() -> None: "(npm ci)", "! npm ci", "exec npm ci", + "np\\\nm ci", ) for script in scripts: From eee83e3a10cb96e800718e4a8ee4016b7a38e429 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 21 Sep 2026 21:59:58 +0900 Subject: [PATCH 156/161] fix(node): normalize shell continuations before npm admission --- .../tests/test_npm_activation_single_path.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/services/analysis-engine/tests/test_npm_activation_single_path.py b/services/analysis-engine/tests/test_npm_activation_single_path.py index 29ab48b3a..802b8f28d 100644 --- a/services/analysis-engine/tests/test_npm_activation_single_path.py +++ b/services/analysis-engine/tests/test_npm_activation_single_path.py @@ -9,6 +9,7 @@ _REPOSITORY_ROOT = Path(__file__).resolve().parents[3] _CANONICAL_ACTIVATION = "bash scripts/checks/activate_pinned_npm_runtime.sh" +_SHELL_LINE_CONTINUATION = re.compile(r"\\\r?\n") _DIRECT_NPM = re.compile( r"(?:^|[;&|(){}]|\b(?:then|do)\b)\s*" r"(?:!\s*)?" @@ -19,6 +20,12 @@ ) +def _contains_direct_npm(script: str) -> bool: + """Detect npm after applying the shell's escaped-newline joining rule.""" + normalized = _SHELL_LINE_CONTINUATION.sub("", script) + return _DIRECT_NPM.search(normalized) is not None + + def test_direct_npm_detection_covers_shell_control_flow_boundaries() -> None: """Treat npm behind ordinary shell control syntax as a workflow consumer.""" scripts = ( @@ -31,7 +38,7 @@ def test_direct_npm_detection_covers_shell_control_flow_boundaries() -> None: ) for script in scripts: - assert _DIRECT_NPM.search(script), script + assert _contains_direct_npm(script), script def test_npm_consumers_use_only_the_canonical_activation_helper() -> None: @@ -59,7 +66,7 @@ def test_npm_consumers_use_only_the_canonical_activation_helper() -> None: if isinstance(step, dict) and isinstance(step.get("run"), str) ] first_npm_index = next( - (index for index, command in enumerate(run_steps) if _DIRECT_NPM.search(command)), + (index for index, command in enumerate(run_steps) if _contains_direct_npm(command)), None, ) if first_npm_index is None: From 6763d9158cf93798f62d8e42c20ac400b7ca0a1d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 21 Sep 2026 22:00:42 +0900 Subject: [PATCH 157/161] docs(node): trace shell continuation npm admission repair --- .../npm-workflow-activation-single-path.md | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/docs/traceability/npm-workflow-activation-single-path.md b/docs/traceability/npm-workflow-activation-single-path.md index 17ac91da2..153be6c5a 100644 --- a/docs/traceability/npm-workflow-activation-single-path.md +++ b/docs/traceability/npm-workflow-activation-single-path.md @@ -19,6 +19,8 @@ The existing structural test explicitly accepted that fallback whenever it appea A second policy gap remained after direct-npm discovery was introduced. The detector recognized line starts, shell separators, environment assignments and `command npm`, but it did not recognize ordinary shell control-flow forms such as `then npm`, `do npm`, subshell grouping, negation, or `exec npm`. A workflow could therefore execute npm through normal shell syntax while being misclassified as a non-consumer and escape the single-path invariant. +A third lexical gap remained after those control-flow forms were covered. POSIX shell removes an unquoted backslash-newline pair before tokenization. A workflow can therefore spell the executable token as `np\` followed by a newline and `m ci`; the runner executes `npm ci`, while a detector operating on the raw YAML string sees no contiguous `npm` token. This is a normal shell continuation rule rather than a separate interpreter or alias, so the structural policy must normalize it before classifying direct npm execution. + ## Constraints - Node/npm runtime acquisition remains #896 ownership; downstream product owners must not copy a mutable Draft helper. @@ -27,6 +29,7 @@ A second policy gap remained after direct-npm discovery was introduced. The dete - Reusable-workflow call jobs with no local `steps` are not direct shell consumers; repository-owned called workflow files are inspected independently. - Source-level tests are not promoted to hosted GREEN until the unchanged exact head completes its normal repository and central gates. - The detector should recognize ordinary shell execution structure without treating arbitrary prose such as `echo npm ci` as executable npm authority. +- Shell continuation handling must model the runner rule narrowly: remove only `\\\r?\n`; do not broadly rewrite whitespace or quoted text. ## Alternatives considered @@ -42,6 +45,10 @@ Rejected. File-name allowlists already proved brittle when Score Storage added a Rejected. Shell control keywords and grouping are ordinary executable syntax. Treating `if ...; then npm ci; fi`, `for ...; do npm ...; done`, `(npm ci)`, `! npm ci`, or `exec npm ci` as non-consumers creates a lexical bypass in the policy gate even though the runner executes npm normally. +### Ignore escaped-newline joining + +Rejected. Backslash-newline removal happens before shell tokenization. Keeping the raw YAML spelling as policy authority would let a semantically identical `npm` executable evade detection solely because its token crosses a physical source line. + ### Treat every textual `npm` occurrence as execution Rejected. A fully lexical substring rule would also classify comments or harmless output such as `echo npm ci` as runtime authority. The current detector remains conservative but execution-oriented: it recognizes supported shell command boundaries and wrappers and requires explicit policy extension if a new execution form is introduced. @@ -59,6 +66,8 @@ Rejected. #1241 is a consumer. It must use the protected/released npm runtime ow 5. `a990e7c70b40dae748123d1447c7ae724edc60e6` removes the older inline-activation fallback from `test_npm_toolchain_contract.py`; both structural regressions now describe the same single canonical activation-path invariant instead of carrying contradictory executable policy. 6. RED `19ddd51d1cb13ed3d783d9e5b81f1ec4f276a787` adds focused detector regressions for `then npm`, `do npm`, subshell grouping, shell negation and `exec npm`. The prior detector fails those cases, so an ordinary shell-wrapped npm consumer could be omitted from policy admission. Repair followed immediately; no hosted terminal RED is claimed for the test-only head. 7. `dc726efbb09d343bacbae0ec273a21390433e01d` extends the execution-boundary detector to shell control keywords, grouping, negation and `exec` while preserving the existing environment-assignment and `command npm` forms. `echo npm ci` remains outside the admitted execution forms rather than becoming a false consumer. +8. RED `237ff476bd9f589c41c3c5a7fe96aa47149d8f19` adds a direct-npm regression whose executable token is split by the shell continuation `np\\\nm ci`. The predecessor detector sees no contiguous `npm` token even though the shell executes `npm ci`. Repair followed immediately, so no hosted terminal RED is claimed for the test-only head. +9. `eee83e3a10cb96e800718e4a8ee4016b7a38e429` normalizes only unquoted escaped newlines (`\\\r?\n`) before applying the existing execution-boundary matcher. This preserves the prior false-positive boundary while making policy classification agree with shell token joining. ## Exact-head verification finding @@ -70,7 +79,7 @@ This was a repository-source defect, not a runner or npm-acquisition failure. It - `a41a2e5b8d3f6e53c7df232dd449b842c866e3c9` formats `test_npm_package_manager_integrity_pin.py`. - `6366eb66635bada29fe72ec99e55efdeeeaaecd0` formats `test_npm_toolchain_contract.py`. -The failed `8113cbfc...` verdict is predecessor evidence only. Every later source move, including the shell-control-flow detector repair, requires a fresh unchanged-head verdict. +The failed `8113cbfc...` verdict is predecessor evidence only. Every later source move, including the shell-control-flow and escaped-newline detector repairs, requires a fresh unchanged-head verdict. ## Authority and evidence @@ -84,19 +93,19 @@ The canonical activation helper remains the only place that may acquire/enable t The trust boundary is CI dependency-tool execution. A workflow must not reach an npm command under an unreviewed bundled/system/latest runtime or a workflow-local activation sequence that omits the owner helper's integrity and failure-classification policy. -The structural regression recognizes direct npm execution at line starts, command separators, shell control-flow boundaries, grouping, negation, environment assignments, `command`, and `exec`. Deliberately hiding npm behind another interpreter, generated shell program, or an unrecognized command wrapper is not an accepted bypass; such a workflow requires explicit policy review and a regression extension before merge. +The structural regression recognizes direct npm execution at line starts, command separators, shell control-flow boundaries, grouping, negation, environment assignments, `command`, and `exec`, after applying the shell's escaped-newline joining rule. Deliberately hiding npm behind another interpreter, generated shell program, quoted/constructed executable token beyond the modeled shell forms, or an unrecognized command wrapper is not an accepted bypass; such a workflow requires explicit policy review and a regression extension before merge. ## Effect - Release and security workflows now consume the same runtime-admission implementation as CI/build owners. - SHA-512 locator admission, bounded transient acquisition retry, fail-closed nontransient behavior, and runtime verification have one workflow-level owner. - A future direct npm consumer cannot satisfy the repository test merely by reproducing `corepack enable npm` and `npm run check:npm-runtime` inline. -- Ordinary shell control flow no longer lets an npm consumer disappear from workflow policy admission. +- Ordinary shell control flow and escaped-newline token joining no longer let an npm consumer disappear from workflow policy admission. - The original npm-consumer discovery test no longer encodes the rejected inline fallback, preventing future maintenance from reintroducing two conflicting policy definitions. ## Follow-up - Obtain terminal exact-head CI, build, security/SAST/SBOM/CodeQL and independent non-author review for the final #896 head. -- If a workflow needs npm through another interpreter or wrapper, add an executable regression for that exact form before admitting it; do not silently broaden the bypass surface. +- If a workflow needs npm through another interpreter, wrapper, quoted executable construction, or other shell expansion, add an executable regression for that exact form before admitting it; do not silently broaden the bypass surface. - After #896 reaches protected truth, ordinary/non-force reconcile #1241 and replace its raw bundled-npm dependency admission with the protected canonical helper. - Re-run #1241's focused ScoreView UI regression on that exact consumer head; do not transfer predecessor failures or successes. From 2a080035cb66ea1a5a4e1cf9b3682c1a23e35b29 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 22 Sep 2026 07:02:33 +0900 Subject: [PATCH 158/161] fix(node): keep npm classifier compatible with macOS bash --- scripts/checks/activate_pinned_npm_runtime.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/checks/activate_pinned_npm_runtime.sh b/scripts/checks/activate_pinned_npm_runtime.sh index 348a124e4..085da5e0e 100644 --- a/scripts/checks/activate_pinned_npm_runtime.sh +++ b/scripts/checks/activate_pinned_npm_runtime.sh @@ -34,7 +34,7 @@ while true; do fi printf '%s\n' "$acquisition_output" >&2 - normalized_output="${acquisition_output,,}" + normalized_output="$(printf '%s' "$acquisition_output" | LC_ALL=C tr '[:upper:]' '[:lower:]')" case "$normalized_output" in *"signature"*|*"integrity"*|*"keyid"*|*"metadata"*|*"checksum"*|*"hash mismatch"*) echo "Corepack acquisition failure is not classified as transient; refusing to retry or weaken verification." >&2 From e3ecc44a873a44588855312ddcf4f96436d0f86b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 22 Sep 2026 07:02:52 +0900 Subject: [PATCH 159/161] test(node): keep package-manager integrity assertion in its owner --- services/analysis-engine/tests/test_node_runtime_contract.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/services/analysis-engine/tests/test_node_runtime_contract.py b/services/analysis-engine/tests/test_node_runtime_contract.py index f16e7a41d..7430ca59b 100644 --- a/services/analysis-engine/tests/test_node_runtime_contract.py +++ b/services/analysis-engine/tests/test_node_runtime_contract.py @@ -9,7 +9,6 @@ ROOT = Path(__file__).resolve().parents[3] EXPECTED_NODE_ENGINE = ">=22.22.2 <23" EXPECTED_NODE_FLOOR = (22, 22, 2) -EXPECTED_NPM_VERSION = "10.9.9" EXPECTED_JSDOM_RANGE = "^30.0.1" EXPECTED_ESLINT_RANGE = "^10.9.1" CANONICAL_NPM_ACTIVATION = "bash scripts/checks/activate_pinned_npm_runtime.sh" @@ -41,7 +40,6 @@ def test_node_engine_floor_matches_jsdom_30_runtime_contract() -> None: package_lock = _load_json("package-lock.json") assert package["engines"] == {"node": EXPECTED_NODE_ENGINE} - assert package["packageManager"] == f"npm@{EXPECTED_NPM_VERSION}" assert package_lock["packages"][""]["engines"] == {"node": EXPECTED_NODE_ENGINE} From 4974765fd8145eb605637e8a84cf955441b8b3d7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 22 Sep 2026 07:03:18 +0900 Subject: [PATCH 160/161] test(node): lock portable npm timeout classification --- .../tests/test_npm_runtime_activation_resilience.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/services/analysis-engine/tests/test_npm_runtime_activation_resilience.py b/services/analysis-engine/tests/test_npm_runtime_activation_resilience.py index 02b11bc1c..5a2fa99a6 100644 --- a/services/analysis-engine/tests/test_npm_runtime_activation_resilience.py +++ b/services/analysis-engine/tests/test_npm_runtime_activation_resilience.py @@ -186,7 +186,9 @@ def test_pinned_npm_activation_helper_retries_acquisition_but_never_falls_back() assert 'MAX_ATTEMPTS="3"' in source assert 'corepack install --global "$package_manager_spec"' in source - assert '"ETIMEDOUT"' in source + assert '"etimedout"' in source + assert "LC_ALL=C tr '[:upper:]' '[:lower:]'" in source + assert "${acquisition_output,,}" not in source assert "not classified as transient" in source assert "corepack enable npm" in source assert "npm run check:npm-runtime" in source From c37835f150a01b8fc5745b541e51ef79bce830af Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 22 Sep 2026 07:05:25 +0900 Subject: [PATCH 161/161] docs(node): trace hosted npm classifier portability repair --- ...acquisition-mixed-diagnostic-precedence.md | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/docs/traceability/npm-runtime-acquisition-mixed-diagnostic-precedence.md b/docs/traceability/npm-runtime-acquisition-mixed-diagnostic-precedence.md index 7ea89f572..ac612de6d 100644 --- a/docs/traceability/npm-runtime-acquisition-mixed-diagnostic-precedence.md +++ b/docs/traceability/npm-runtime-acquisition-mixed-diagnostic-precedence.md @@ -10,6 +10,8 @@ That rule was incomplete when one diagnostic contained both a timeout token and A retry is not equivalent to bypassing verification, but at this boundary it weakens the stated fail-closed policy: signature, integrity and package-manager metadata failures are deterministic trust failures and must never become retryable merely because the upstream diagnostic also mentions a timeout. +A later exact-head hosted run exposed a second, platform-specific defect in the same classifier. The helper lowercased diagnostics with Bash 4's `${parameter,,}` expansion. GitHub-hosted macOS executes the helper under the system `/bin/bash`, where that expansion is unsupported, so the classifier aborted with `bad substitution` before it could apply trust-first precedence or bounded timeout retry. Linux did not expose the defect because its hosted Bash supports the expansion. The classification contract therefore also requires a portable lowercase operation across the repository's supported hosted shells. + ## Constraints - `npm@10.9.9` plus its repository-pinned SHA-512 identity remains the only admitted package-manager runtime. @@ -18,6 +20,7 @@ A retry is not equivalent to bypassing verification, but at this boundary it wea - Signature, integrity, key-id, package-manager metadata, checksum and hash-mismatch diagnostics take precedence over the timeout token. - Unknown failures still fail closed. - The helper must stop before `corepack enable npm` or `npm run check:npm-runtime` on a trust/provenance failure. +- Diagnostic normalization must work on the hosted macOS Bash used by repository CI; shell-version-specific lowercase expansion is not part of the contract. - This repair does not disable Corepack verification, alter integrity keys, select another npm version, or add an npm/system fallback. ## RED and repair @@ -28,6 +31,20 @@ Repair `4cee8f5d4b0f7496fce959deefdce7912fcb8963` normalizes only for classifica The test-only head was immediately followed by the production repair, so no hosted terminal RED is claimed for `bc26032f...`. +### Hosted macOS portability RCA + +Exact head `6763d9158cf93798f62d8e42c20ac400b7ca0a1d` produced a real hosted RED in `gate / ci / node-minimum-compatibility` on macOS. Node 22.22.2 setup, integrity-bound npm 10.9.9 acquisition, bundled tar verification, frozen Node dependency installation, Python dependency sync, Rust numeric-extension build, lint and typecheck all completed before the Python regression suite exercised the classifier. The mixed-integrity and unknown-failure tests then observed `/bin/bash: ${acquisition_output,,}: bad substitution`; timeout recovery stopped after one acquisition attempt for the same reason. This is a helper portability defect, not a Corepack/npm acquisition failure. + +Linux on the same exact head independently reached the full Python suite with the helper operating normally and exposed only source-contract regressions: `test_node_runtime_contract.py` still expected the pre-integrity `npm@10.9.9` locator, while `test_npm_runtime_activation_resilience.py` still searched the helper source for uppercase `"ETIMEDOUT"` even though classification intentionally lowercases diagnostics before matching. + +Repair `2a080035cb66ea1a5a4e1cf9b3682c1a23e35b29` replaces Bash-specific `${acquisition_output,,}` with `LC_ALL=C tr '[:upper:]' '[:lower:]'`. This keeps the diagnostic as data, preserves trust-first classification, and works under the hosted macOS Bash path exercised by CI. + +Alignment `e3ecc44a873a44588855312ddcf4f96436d0f86b` removes the stale bare-version package-manager assertion from the Node/jsdom compatibility test. Exact package-manager artifact identity remains owned by `test_npm_package_manager_integrity_pin.py`, which asserts the full version-plus-SHA-512 locator; keeping a second bare-version assertion would encode a contradictory contract. + +Regression update `4974765fd8145eb605637e8a84cf955441b8b3d7` changes the structural classifier assertion to the normalized lowercase `"etimedout"`, requires the portable `LC_ALL=C tr` path, and explicitly rejects reintroduction of `${acquisition_output,,}`. Behavioral fixtures continue to emit uppercase `ETIMEDOUT`, so case-insensitive runtime behavior remains exercised rather than being proven only by source text. + +Because these commits move source after the hosted failure, `6763d915...` remains predecessor RED evidence only. A descendant is not GREEN until its own unchanged exact head completes the applicable repository and central gates. + ## Rejected alternatives - Let any diagnostic containing `ETIMEDOUT` retry: rejected because a mixed diagnostic can already prove a non-transient trust failure. @@ -35,16 +52,18 @@ The test-only head was immediately followed by the production repair, so no host - Retry unknown failures and maintain only a small fatal blacklist: rejected because unknown is not evidence of transport transience. - Broaden the transient allowlist to DNS, HTTP, connection-reset or other network-looking failures: rejected because current hosted evidence only supports `ETIMEDOUT`. - Parse or evaluate diagnostic text as shell: rejected. Upstream text is untrusted evidence and remains data only. +- Require a newer Bash on macOS merely to support `${parameter,,}`: rejected because the helper needs only ASCII case-folding for diagnostic tokens and can perform it portably without broadening runtime prerequisites. +- Duplicate the integrity-bound `packageManager` locator in the Node/jsdom compatibility test: rejected because package-manager artifact identity already has a dedicated canonical regression and duplicate assertions had drifted into contradiction. ## Claim boundary and residual risk This is still a diagnostic-string classifier because the current Corepack command boundary does not provide a stable machine-readable failure taxonomy to this script. The precedence list therefore cannot prove semantic completeness for every future Corepack wording. A new provenance diagnostic that is not represented by the known markers may still require a focused regression and classifier update. -That residual risk is narrower than the repaired defect: a known trust/provenance marker can no longer be overridden by the admitted timeout token. If Corepack exposes a stable structured error code or typed result, BandScope should replace text classification rather than expand string heuristics indefinitely. +That residual risk is narrower than the repaired defect: a known trust/provenance marker can no longer be overridden by the admitted timeout token, and the normalization path no longer depends on a Bash feature absent from the hosted macOS shell. If Corepack exposes a stable structured error code or typed result, BandScope should replace text classification rather than expand string heuristics indefinitely. ## Security Notes -The captured Corepack diagnostic is untrusted upstream text. It is written to stderr and inspected only as data for a bounded decision. It is never executed, interpolated into a command, or used to choose an alternate package-manager artifact. Trust/provenance evidence has precedence over retry availability. +The captured Corepack diagnostic is untrusted upstream text. It is written to stderr and inspected only as data for a bounded decision. It is never executed, interpolated into a command, or used to choose an alternate package-manager artifact. Trust/provenance evidence has precedence over retry availability. `tr` receives the diagnostic only through stdin and a fixed translation table; it does not evaluate the diagnostic as shell syntax. ## References