diff --git a/package.json b/package.json
index 68a4bf55e..b9e999f12 100644
--- a/package.json
+++ b/package.json
@@ -47,7 +47,7 @@
"check:plan": "node refactor/scripts/plan.mjs --check",
"test:node": "yarn test:unit && node --test test/toolchain.test.js test/build-docs.test.js test/package-check.test.js test/declarations.test.js test/editor-types.test.js test/coverage.test.js test/performance-report.test.js test/media-gate.test.js test/ci-summary.test.js test/package-runtime.test.js test/library-build.test.js test/site-loading.test.js test/documentation-pipeline.test.js test/site-build.test.js test/site-markdown.test.js test/site-editor.test.js test/plugin-scaffold.test.js test/dev-server.test.js",
"test:baseline": "node --test refactor/scripts/*.test.mjs",
- "ci:check": "yarn check:toolchain --strict && yarn check:commits --report && yarn check:impact --report && yarn check:ci && yarn test:contracts && yarn check:contracts --report && yarn check:plan && yarn lint && yarn typecheck:library && yarn typecheck:scaffold && yarn typecheck:docs-tools && yarn check:docs-smoke && yarn check:editor-types && yarn check:llm && yarn typecheck:site-assets && yarn check:site-assets && yarn check:types && yarn typecheck && yarn typecheck:react && yarn lint:react && yarn typecheck:vue && yarn lint:vue && yarn test",
+ "ci:check": "yarn check:toolchain --strict && yarn check:commits --report && yarn check:impact --report && yarn check:ci && yarn test:contracts && yarn check:contracts --report && yarn check:plan && yarn check:release-ledger && yarn lint && yarn typecheck:library && yarn typecheck:scaffold && yarn typecheck:docs-tools && yarn check:docs-smoke && yarn check:editor-types && yarn check:llm && yarn typecheck:site-assets && yarn check:site-assets && yarn check:types && yarn typecheck && yarn typecheck:react && yarn lint:react && yarn typecheck:vue && yarn lint:vue && yarn test",
"ci:build": "yarn build:types && yarn build all && yarn build:i18n && yarn build:ts && yarn build:llm && yarn build:test && yarn build:docs && yarn test:imports",
"test:imports": "node --test test/esm.test.js test/i18n.test.js test/ssr.test.js test/asr-distribution.test.js",
"typecheck": "node scripts/typecheck.mjs",
@@ -133,7 +133,11 @@
"typecheck:library": "node node_modules/typescript/bin/tsc -p scripts/tsconfig.library.json --noEmit",
"test:library": "node --test refactor/scripts/build.test.mjs test/library-build.test.js test/performance-report.test.js test/plugin-scaffold.test.js test/dev-server.test.js",
"test:dev-server": "node --test test/dev-server.test.js",
- "probe:auto-thumbnail-native": "node refactor/scripts/auto-thumbnail-native-probe.mjs"
+ "probe:auto-thumbnail-native": "node refactor/scripts/auto-thumbnail-native-probe.mjs",
+ "check:release-ledger": "node refactor/scripts/release-ledger.mjs",
+ "report:release-ledger": "node refactor/scripts/release-ledger.mjs --report",
+ "release:preflight": "yarn check:toolchain --strict && node refactor/scripts/release-ledger.mjs --report --strict",
+ "test:release-ledger": "node --test refactor/scripts/release-ledger.test.mjs"
},
"browserslist": "last 1 Chrome version",
"devDependencies": {
diff --git a/refactor/README.md b/refactor/README.md
index a8b85fcc1..4c5a6a54b 100644
--- a/refactor/README.md
+++ b/refactor/README.md
@@ -28,6 +28,7 @@
| [已落实的测试可靠性规则](test-reliability.md) | 历史失败、候选回归、设备缺口、精确异常及等待/重试的执行规则 |
| [docs 页面与编辑器测试](docs-browser-testing.md) | 复用已有 HTML、加载版本、隔离状态和补强文档 smoke |
| [多轮复盘与 npm 准入](release-reviews.md) | Chrome 验证分工、三轮全局复盘、问题闭环和候选发布门槛 |
+| [逐包发布准入台账](release-ledger.md) | 22包候选/证据绑定、源码和产物指纹、任务风险/许可阻断及严格预检 |
| [工具链与发布](toolchain-release.md) | TypeScript、Bun、构建、版本管理和发布回退 |
| [已实现的开发环境](toolchain-setup.md) | Node/Yarn 固定版本、锁文件、安装命令和实际验证范围 |
| [全包大版本策略](version-policy.md) | 每包分别升级一个 major 的目标清单、兼容要求和版本落地步骤 |
diff --git a/refactor/baselines/release-ledger-validation.json b/refactor/baselines/release-ledger-validation.json
new file mode 100644
index 000000000..db2e8f2b0
--- /dev/null
+++ b/refactor/baselines/release-ledger-validation.json
@@ -0,0 +1,2127 @@
+{
+ "task": "REL-08",
+ "date": "2026-09-14",
+ "baselineCommit": "3316b8a867565846b37588b3043919ed8634f19b",
+ "scope": "Executable release ledger and evidence binding, not package release acceptance. All candidate bindings intentionally empty.",
+ "sources": [
+ {
+ "path": "package.json",
+ "sha256": "0adbcb5bc6c14b91a9575361175d3ce3853b9f761dc116e0d81bf6a0f8ff5e7a"
+ },
+ {
+ "path": "refactor/release-ledger.json",
+ "sha256": "5dec1d96ffae29f3e5f3e5136bbc9c8f7ba21aa10b6c9f018da1063638753a60"
+ },
+ {
+ "path": "refactor/scripts/release-ledger.mjs",
+ "sha256": "661ea6a38355927989fb9edbef3691ec8ed74e39147bf2563590268121906e80"
+ },
+ {
+ "path": "refactor/scripts/release-ledger-model.mjs",
+ "sha256": "f9979626b7ce01d290dbc3fe6e6a126fd38f6dd0b92934583f8edc80e723cebd"
+ },
+ {
+ "path": "refactor/scripts/release-ledger.test.mjs",
+ "sha256": "fd59c48f76d324284143091d244d2780149a6ae8411283c362ee7d6acb5334d8"
+ }
+ ],
+ "tests": {
+ "regression": {
+ "log": {
+ "path": "refactor/.cache/release-ledger-final-tests.log",
+ "sha256": "0d79ddc1a7dd2c3655a78a22e4b3b936971ac7846052150d261d29859220b269"
+ },
+ "passed": 30,
+ "failed": 0
+ },
+ "ci": {
+ "log": {
+ "path": "refactor/.cache/release-ledger-ci-tests.log",
+ "sha256": "193928854c9f2292d8bf6bdac5babe5ed2fb957365faf45b34e8efadc790f984"
+ },
+ "passed": 50,
+ "failed": 0
+ },
+ "lint": "Targeted read-only lint passes; ordinary style fixes applied during implementation"
+ },
+ "currentReport": {
+ "file": {
+ "path": "refactor/.cache/release-ledger-Uu7RBx/report.json",
+ "sha256": "6a40a3b0d6cda5fc630a93c4a0c8a901a8b6cc88aec744cfa97bc6ebfb0dc9f7"
+ },
+ "log": {
+ "path": "refactor/.cache/release-ledger-maintained-report.log",
+ "sha256": "ce16991a224c21eff3c126eca8d6b0795243b9d8ea4110cc96e0a67c685c4aee"
+ },
+ "exitCode": 0,
+ "publicationAuthorized": false,
+ "toolchain": {
+ "node": "v24.21.0",
+ "canonicalNode": "24.21.0",
+ "packageManager": "yarn@1.22.22",
+ "observedUserAgent": "yarn/1.22.22 npm/? node/v24.21.0 win32 x64",
+ "declaredTools": {
+ "@antfu/eslint-config": "5.4.1",
+ "@playwright/test": "1.63.0",
+ "@types/markdown-it": "14.1.2",
+ "@types/node": "24.10.0",
+ "@types/react": "19.1.10",
+ "@types/react-dom": "19.1.7",
+ "@vitejs/plugin-react": "5.0.0",
+ "@vitejs/plugin-vue": "6.0.1",
+ "@vue/compiler-sfc": "3.5.28",
+ "@yarnpkg/lockfile": "1.1.0",
+ "c8": "12.0.0",
+ "cpy": "13.2.3",
+ "cross-env": "10.1.0",
+ "cross-spawn": "7.0.6",
+ "dotenv": "17.2.4",
+ "dts-bundle-generator": "9.5.1",
+ "esbuild": "0.27.7",
+ "eslint": "9.39.2",
+ "eslint-plugin-format": "2.0.1",
+ "glob": "13.0.6",
+ "lerna": "8.2.4",
+ "less": "4.5.1",
+ "linkedom": "0.18.13",
+ "monaco-editor": "0.30.1",
+ "mrmime": "2.0.1",
+ "pngjs": "7.0.0",
+ "prompts": "2.4.2",
+ "react": "19.1.1",
+ "react-dom": "19.1.1",
+ "semver": "7.7.4",
+ "servor": "4.0.2",
+ "svgo": "4.1.0",
+ "terser": "5.51.2",
+ "typescript": "5.9.3",
+ "typescript-compat": "npm:typescript@4.3.5",
+ "typescript-runtime-compat": "npm:typescript@5.1.6",
+ "vite": "7.3.6",
+ "vue": "3.5.28",
+ "vue-tsc": "3.3.11",
+ "yaml": "2.8.2"
+ },
+ "lock": {
+ "path": "yarn.lock",
+ "sha256": "aa33cfc38c7933ab53ce626a911fdafd7b14023e45f93ca37bf7f2f6a1d3ecce"
+ }
+ },
+ "packages": [
+ {
+ "name": "artplayer",
+ "version": "5.4.1",
+ "targetVersion": "6.0.0",
+ "distribution": "npm",
+ "inputFingerprint": "78b8bd035a4cba8bc5bfb3c4e7fde35b38916925d3063956116e39a727403a78",
+ "status": "blocked",
+ "candidate": null,
+ "history": {
+ "verified": true,
+ "kind": "npm",
+ "file": "refactor/baselines/releases.json",
+ "name": "artplayer",
+ "version": "5.4.0",
+ "integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==",
+ "sha256": "42b269a66b9658d53f9a92572bb36d656ea9af56254a387b3a69c7ddea43979e",
+ "tarball": "https://registry.npmjs.org/artplayer/-/artplayer-5.4.0.tgz",
+ "rationale": "Frozen npm tarball provenance; not a new registry availability observation.",
+ "limitation": "Frozen historical distribution mapping; not a fresh registry/version-availability check or rollback rehearsal"
+ },
+ "rollback": {
+ "basis": "refactor/baselines/releases.json",
+ "strategy": "Keep the frozen previous package integrity and tested consumer lock; publish a forward fix without overwriting npm versions. REL-04 rehearsal must cover any package rename."
+ },
+ "dependencies": [
+ "artplayer"
+ ],
+ "blockedTasks": [
+ "CI-01",
+ "CI-03",
+ "CI-04",
+ "REL-01",
+ "REL-09",
+ "REL-02",
+ "REL-03",
+ "REL-04",
+ "REVIEW-01",
+ "REVIEW-02",
+ "REVIEW-03"
+ ],
+ "openRisks": [
+ "ENG-PERF-01",
+ "ENG-PERF-02",
+ "BASE-SOURCE-01",
+ "BASE-SOURCE-02",
+ "SDK-03",
+ "SDK-04",
+ "SDK-05",
+ "BASE-ENV-01",
+ "MULTI-SUB-SWITCH-01"
+ ],
+ "requiredAssetReviews": [
+ "screenfull",
+ "hint.css",
+ "flv.js",
+ "mpegts.js",
+ "webtorrent",
+ "option-validator",
+ "dependency:artplayer:option-validator"
+ ],
+ "missingEvidence": [
+ "build",
+ "runtime",
+ "types",
+ "combinations",
+ "browser",
+ "devices",
+ "licenses",
+ "rollback",
+ "remote-ci",
+ "review-01",
+ "review-02",
+ "review-03"
+ ],
+ "blockerCount": 34
+ },
+ {
+ "name": "artplayer-plugin-ads",
+ "version": "2.1.0",
+ "targetVersion": "3.0.0",
+ "distribution": "npm",
+ "inputFingerprint": "8e164ac4d972fbf24e5bbfc1da3c9b96349b3d7813732e41928c91bed5824235",
+ "status": "blocked",
+ "candidate": null,
+ "history": {
+ "verified": true,
+ "kind": "npm",
+ "file": "refactor/baselines/ads-release.json",
+ "name": "artplayer-plugin-ads",
+ "version": "1.0.6",
+ "integrity": "sha512-KMGGf7lhgj6DQXHPOg28jWYkVmDfOZJ4Gr4iF28Wy2GXNPh2ZJtlsFQ2IuWqXCElRPvSLzYK5HugF0OUnVszaw==",
+ "sha256": "ff132ff8a7108d8b26421621e4aac1d7edf8c99099a5ab4fd2190bd79b8636cd",
+ "tarball": "https://registry.npmjs.org/artplayer-plugin-ads/-/artplayer-plugin-ads-1.0.6.tgz",
+ "rationale": "Frozen npm tarball provenance; not a new registry availability observation.",
+ "limitation": "Frozen historical distribution mapping; not a fresh registry/version-availability check or rollback rehearsal"
+ },
+ "rollback": {
+ "basis": "refactor/baselines/ads-release.json",
+ "strategy": "Keep the frozen previous package integrity and tested consumer lock; publish a forward fix without overwriting npm versions. REL-04 rehearsal must cover any package rename."
+ },
+ "dependencies": [
+ "artplayer",
+ "artplayer-plugin-ads"
+ ],
+ "blockedTasks": [
+ "CI-01",
+ "CI-03",
+ "CI-04",
+ "PKG-ADS-05",
+ "PKG-ADS-06",
+ "REL-01",
+ "REL-09",
+ "REL-02",
+ "REL-03",
+ "REL-04",
+ "REVIEW-01",
+ "REVIEW-02",
+ "REVIEW-03"
+ ],
+ "openRisks": [
+ "ENG-PERF-01",
+ "ENG-PERF-02",
+ "BASE-SOURCE-01",
+ "BASE-SOURCE-02",
+ "SDK-03",
+ "SDK-04",
+ "SDK-05",
+ "BASE-ENV-01",
+ "ADS-DIST-01",
+ "ADS-MEDIA-01",
+ "MULTI-SUB-SWITCH-01"
+ ],
+ "requiredAssetReviews": [
+ "screenfull",
+ "hint.css",
+ "flv.js",
+ "mpegts.js",
+ "webtorrent",
+ "option-validator",
+ "dependency:artplayer:option-validator"
+ ],
+ "missingEvidence": [
+ "build",
+ "runtime",
+ "types",
+ "combinations",
+ "browser",
+ "devices",
+ "licenses",
+ "rollback",
+ "remote-ci",
+ "review-01",
+ "review-02",
+ "review-03"
+ ],
+ "blockerCount": 38
+ },
+ {
+ "name": "artplayer-plugin-ambilight",
+ "version": "1.1.0",
+ "targetVersion": "2.0.0",
+ "distribution": "npm",
+ "inputFingerprint": "5ddec3a08bb7855855ef221b3717206de6abaf3c7b6a3cdf84e274f228a284bc",
+ "status": "blocked",
+ "candidate": null,
+ "history": {
+ "verified": true,
+ "kind": "npm",
+ "file": "refactor/baselines/ambilight-release.json",
+ "name": "artplayer-plugin-ambilight",
+ "version": "1.1.0",
+ "integrity": "sha512-FkMI7bn9HovJMfEdgkKbo7/vPt6vrzcpELTaC/vM7B99FPSMWzAf6GAE0xnNxU3KibYcBV5gXnBgvHgpG2xq2A==",
+ "sha256": "e95bd60fd7bce52142cf0041bad811452305029cbe1129c7d89246eb260c5c86",
+ "tarball": "https://registry.npmjs.org/artplayer-plugin-ambilight/-/artplayer-plugin-ambilight-1.1.0.tgz",
+ "rationale": "Frozen npm tarball provenance; not a new registry availability observation.",
+ "limitation": "Frozen historical distribution mapping; not a fresh registry/version-availability check or rollback rehearsal"
+ },
+ "rollback": {
+ "basis": "refactor/baselines/ambilight-release.json",
+ "strategy": "Keep the frozen previous package integrity and tested consumer lock; publish a forward fix without overwriting npm versions. REL-04 rehearsal must cover any package rename."
+ },
+ "dependencies": [
+ "artplayer",
+ "artplayer-plugin-ambilight"
+ ],
+ "blockedTasks": [
+ "CI-01",
+ "CI-03",
+ "CI-04",
+ "PKG-AMBILIGHT-05",
+ "PKG-AMBILIGHT-06",
+ "REL-01",
+ "REL-09",
+ "REL-02",
+ "REL-03",
+ "REL-04",
+ "REVIEW-01",
+ "REVIEW-02",
+ "REVIEW-03"
+ ],
+ "openRisks": [
+ "ENG-PERF-01",
+ "ENG-PERF-02",
+ "BASE-SOURCE-01",
+ "BASE-SOURCE-02",
+ "SDK-03",
+ "SDK-04",
+ "SDK-05",
+ "BASE-ENV-01",
+ "AMBILIGHT-LIFE-01",
+ "MULTI-SUB-SWITCH-01"
+ ],
+ "requiredAssetReviews": [
+ "screenfull",
+ "hint.css",
+ "flv.js",
+ "mpegts.js",
+ "webtorrent",
+ "option-validator",
+ "dependency:artplayer:option-validator"
+ ],
+ "missingEvidence": [
+ "build",
+ "runtime",
+ "types",
+ "combinations",
+ "browser",
+ "devices",
+ "licenses",
+ "rollback",
+ "remote-ci",
+ "review-01",
+ "review-02",
+ "review-03"
+ ],
+ "blockerCount": 37
+ },
+ {
+ "name": "artplayer-plugin-asr",
+ "version": "2.1.0",
+ "targetVersion": "3.0.0",
+ "distribution": "npm",
+ "inputFingerprint": "fdc1a000fbc22d06cda62995a62f3a88ad09438e53b653250b08e324d97bca4a",
+ "status": "blocked",
+ "candidate": null,
+ "history": {
+ "verified": true,
+ "kind": "npm",
+ "file": "refactor/baselines/asr-release.json",
+ "name": "artplayer-plugin-asr",
+ "version": "2.1.0",
+ "integrity": "sha512-15gXff458PAx9LeJxKzZEvmpFhfwKUfSLgb1Mfr7Xb/ZGsnE+6EA7ezTnO5S55W5mFExjv/e95iOIXP4ijuruQ==",
+ "sha256": "8bc3cd93f78ce99e5c09cc546c770a63e8aea5729d3d584134ce04d826b7708b",
+ "tarball": "https://registry.npmjs.org/artplayer-plugin-asr/-/artplayer-plugin-asr-2.1.0.tgz",
+ "rationale": "Frozen npm tarball provenance; not a new registry availability observation.",
+ "limitation": "Frozen historical distribution mapping; not a fresh registry/version-availability check or rollback rehearsal"
+ },
+ "rollback": {
+ "basis": "refactor/baselines/asr-release.json",
+ "strategy": "Keep the frozen previous package integrity and tested consumer lock; publish a forward fix without overwriting npm versions. REL-04 rehearsal must cover any package rename."
+ },
+ "dependencies": [
+ "artplayer",
+ "artplayer-plugin-asr"
+ ],
+ "blockedTasks": [
+ "CI-01",
+ "CI-03",
+ "CI-04",
+ "PKG-ASR-05",
+ "PKG-ASR-06",
+ "REL-01",
+ "REL-09",
+ "REL-02",
+ "REL-03",
+ "REL-04",
+ "REVIEW-01",
+ "REVIEW-02",
+ "REVIEW-03"
+ ],
+ "openRisks": [
+ "ENG-PERF-01",
+ "ENG-PERF-02",
+ "BASE-SOURCE-01",
+ "BASE-SOURCE-02",
+ "SDK-03",
+ "SDK-04",
+ "SDK-05",
+ "SDK-11",
+ "BASE-ENV-01",
+ "ASR-CORS-01",
+ "MULTI-SUB-SWITCH-01"
+ ],
+ "requiredAssetReviews": [
+ "screenfull",
+ "hint.css",
+ "flv.js",
+ "mpegts.js",
+ "webtorrent",
+ "asr-caller-service",
+ "option-validator",
+ "dependency:artplayer:option-validator"
+ ],
+ "missingEvidence": [
+ "build",
+ "runtime",
+ "types",
+ "combinations",
+ "browser",
+ "devices",
+ "licenses",
+ "rollback",
+ "remote-ci",
+ "review-01",
+ "review-02",
+ "review-03"
+ ],
+ "blockerCount": 38
+ },
+ {
+ "name": "artplayer-plugin-audio-track",
+ "version": "1.1.0",
+ "targetVersion": "2.0.0",
+ "distribution": "npm",
+ "inputFingerprint": "346d79498d8b6ee0f855aab6dbb73e756158d1a53635e2c6fff624bbde0fc0d1",
+ "status": "blocked",
+ "candidate": null,
+ "history": {
+ "verified": true,
+ "kind": "npm",
+ "file": "refactor/baselines/audio-track-release.json",
+ "name": "artplayer-plugin-audio-track",
+ "version": "1.1.0",
+ "integrity": "sha512-pvvHvTSqC2+sBelwYyjAv2CVmaQgG9KpX+i96t/e8oyZRX/JuepC9iSVIp7GzB3AtliFzhVamksT+8lXvD3RCg==",
+ "sha256": "7e84f6a2f4bfada428a77ef04b749bf3db53e0149fa1afe7bf5749d925e801ff",
+ "tarball": "https://registry.npmjs.org/artplayer-plugin-audio-track/-/artplayer-plugin-audio-track-1.1.0.tgz",
+ "rationale": "Frozen npm tarball provenance; not a new registry availability observation.",
+ "limitation": "Frozen historical distribution mapping; not a fresh registry/version-availability check or rollback rehearsal"
+ },
+ "rollback": {
+ "basis": "refactor/baselines/audio-track-release.json",
+ "strategy": "Keep the frozen previous package integrity and tested consumer lock; publish a forward fix without overwriting npm versions. REL-04 rehearsal must cover any package rename."
+ },
+ "dependencies": [
+ "artplayer",
+ "artplayer-plugin-audio-track"
+ ],
+ "blockedTasks": [
+ "CI-01",
+ "CI-03",
+ "CI-04",
+ "PKG-AUDIO-05",
+ "PKG-AUDIO-06",
+ "REL-01",
+ "REL-09",
+ "REL-02",
+ "REL-03",
+ "REL-04",
+ "REVIEW-01",
+ "REVIEW-02",
+ "REVIEW-03"
+ ],
+ "openRisks": [
+ "ENG-PERF-01",
+ "ENG-PERF-02",
+ "BASE-SOURCE-01",
+ "BASE-SOURCE-02",
+ "SDK-03",
+ "SDK-04",
+ "SDK-05",
+ "BASE-ENV-01",
+ "AUDIO-SYNC-01",
+ "AUDIO-DEMO-01",
+ "AUDIO-BUFFER-01",
+ "MULTI-SUB-SWITCH-01"
+ ],
+ "requiredAssetReviews": [
+ "screenfull",
+ "hint.css",
+ "flv.js",
+ "mpegts.js",
+ "webtorrent",
+ "option-validator",
+ "dependency:artplayer:option-validator"
+ ],
+ "missingEvidence": [
+ "build",
+ "runtime",
+ "types",
+ "combinations",
+ "browser",
+ "devices",
+ "licenses",
+ "rollback",
+ "remote-ci",
+ "review-01",
+ "review-02",
+ "review-03"
+ ],
+ "blockerCount": 39
+ },
+ {
+ "name": "artplayer-plugin-auto-thumbnail",
+ "version": "1.1.0",
+ "targetVersion": "2.0.0",
+ "distribution": "npm",
+ "inputFingerprint": "004da6f8074eab3f002279ba78d5ba1f164d36c6c9dd8e394485ea21ec545347",
+ "status": "blocked",
+ "candidate": null,
+ "history": {
+ "verified": true,
+ "kind": "npm",
+ "file": "refactor/baselines/auto-thumbnail-release.json",
+ "name": "artplayer-plugin-auto-thumbnail",
+ "version": "1.1.0",
+ "integrity": "sha512-ALrcLv+qEDime+R8p4628RZTfMGlrpX6d7PLIQaYWsQEN3lPzVjcVcARCFpqnFI8gvRdqt21w72sJphf50xtVw==",
+ "sha256": "5ea83d4e45d42fdaf42d3e37b6c666857872adaa276ab329c74edcdaa3c1a978",
+ "tarball": "https://registry.npmjs.org/artplayer-plugin-auto-thumbnail/-/artplayer-plugin-auto-thumbnail-1.1.0.tgz",
+ "rationale": "Frozen npm tarball provenance; not a new registry availability observation.",
+ "limitation": "Frozen historical distribution mapping; not a fresh registry/version-availability check or rollback rehearsal"
+ },
+ "rollback": {
+ "basis": "refactor/baselines/auto-thumbnail-release.json",
+ "strategy": "Keep the frozen previous package integrity and tested consumer lock; publish a forward fix without overwriting npm versions. REL-04 rehearsal must cover any package rename."
+ },
+ "dependencies": [
+ "artplayer",
+ "artplayer-plugin-auto-thumbnail"
+ ],
+ "blockedTasks": [
+ "CI-01",
+ "CI-03",
+ "CI-04",
+ "PKG-AUTO-THUMB-03",
+ "PKG-AUTO-THUMB-04",
+ "PKG-AUTO-THUMB-05",
+ "PKG-AUTO-THUMB-06",
+ "REL-01",
+ "REL-09",
+ "REL-02",
+ "REL-03",
+ "REL-04",
+ "REVIEW-01",
+ "REVIEW-02",
+ "REVIEW-03"
+ ],
+ "openRisks": [
+ "ENG-PERF-01",
+ "ENG-PERF-02",
+ "BASE-SOURCE-01",
+ "BASE-SOURCE-02",
+ "SDK-03",
+ "SDK-04",
+ "SDK-05",
+ "BASE-ENV-01",
+ "AUTO-THUMB-TYPE-01",
+ "AUTO-THUMB-EXPORT-01",
+ "AUTO-THUMB-DIST-01",
+ "AUTO-THUMB-LIFE-01",
+ "AUTO-THUMB-ENCODE-01",
+ "AUTO-THUMB-PIXEL-01",
+ "MULTI-SUB-SWITCH-01"
+ ],
+ "requiredAssetReviews": [
+ "screenfull",
+ "hint.css",
+ "flv.js",
+ "mpegts.js",
+ "webtorrent",
+ "option-validator",
+ "dependency:artplayer:option-validator"
+ ],
+ "missingEvidence": [
+ "build",
+ "runtime",
+ "types",
+ "combinations",
+ "browser",
+ "devices",
+ "licenses",
+ "rollback",
+ "remote-ci",
+ "review-01",
+ "review-02",
+ "review-03"
+ ],
+ "blockerCount": 44
+ },
+ {
+ "name": "artplayer-plugin-chapter",
+ "version": "1.1.0",
+ "targetVersion": "2.0.0",
+ "distribution": "npm",
+ "inputFingerprint": "f030af981d14fc1ae006a53355b676faefde9d7744f9dada182aafc3da932e05",
+ "status": "blocked",
+ "candidate": null,
+ "history": {
+ "verified": true,
+ "kind": "npm",
+ "file": "refactor/baselines/releases.json",
+ "name": "artplayer-plugin-chapter",
+ "version": "1.1.0",
+ "integrity": "sha512-ji66Lr2K2S+OjFMuMby3yq+pefWlOC51Sbpop/DIr2N6w3F0smrF+YZbshN1Dy8/Tx1TYpT7N6WkkZWy5Jix1w==",
+ "sha256": "4c3173661854ff96f643a479c0f1c2db7ce9e54681523bd059a7dd9bd77eeada",
+ "tarball": "https://registry.npmjs.org/artplayer-plugin-chapter/-/artplayer-plugin-chapter-1.1.0.tgz",
+ "rationale": "Frozen npm tarball provenance; not a new registry availability observation.",
+ "limitation": "Frozen historical distribution mapping; not a fresh registry/version-availability check or rollback rehearsal"
+ },
+ "rollback": {
+ "basis": "refactor/baselines/releases.json",
+ "strategy": "Keep the frozen previous package integrity and tested consumer lock; publish a forward fix without overwriting npm versions. REL-04 rehearsal must cover any package rename."
+ },
+ "dependencies": [
+ "artplayer",
+ "artplayer-plugin-chapter"
+ ],
+ "blockedTasks": [
+ "CI-01",
+ "CI-03",
+ "CI-04",
+ "PKG-CHAPTER-05",
+ "PKG-CHAPTER-06",
+ "REL-01",
+ "REL-09",
+ "REL-02",
+ "REL-03",
+ "REL-04",
+ "REVIEW-01",
+ "REVIEW-02",
+ "REVIEW-03"
+ ],
+ "openRisks": [
+ "ENG-PERF-01",
+ "ENG-PERF-02",
+ "BASE-SOURCE-01",
+ "BASE-SOURCE-02",
+ "SDK-03",
+ "SDK-04",
+ "SDK-05",
+ "BASE-ENV-01",
+ "CHAPTER-TIMING-01",
+ "MULTI-SUB-SWITCH-01"
+ ],
+ "requiredAssetReviews": [
+ "screenfull",
+ "hint.css",
+ "flv.js",
+ "mpegts.js",
+ "webtorrent",
+ "option-validator",
+ "dependency:artplayer:option-validator"
+ ],
+ "missingEvidence": [
+ "build",
+ "runtime",
+ "types",
+ "combinations",
+ "browser",
+ "devices",
+ "licenses",
+ "rollback",
+ "remote-ci",
+ "review-01",
+ "review-02",
+ "review-03"
+ ],
+ "blockerCount": 37
+ },
+ {
+ "name": "artplayer-plugin-chromecast",
+ "version": "1.1.0",
+ "targetVersion": "2.0.0",
+ "distribution": "npm",
+ "inputFingerprint": "cb8a8eb230cebaba0e29231a056a786743dc088979372362409f03333b5de220",
+ "status": "blocked",
+ "candidate": null,
+ "history": {
+ "verified": true,
+ "kind": "npm",
+ "file": "refactor/baselines/chromecast-release.json",
+ "name": "artplayer-plugin-chromecast",
+ "version": "1.1.0",
+ "integrity": "sha512-3xrHj1CHe0Tt8gNMUB9A1epuW4OrbPsa1T9MNFy4AfQsjhYAhWAmef6hYu1GdHdADDybXBtVHP50gQ+EW+ddcw==",
+ "sha256": "1941d41055ee02deaa343cbf9eaf714495cb9f04883f57966f5b27f128117c30",
+ "tarball": "https://registry.npmjs.org/artplayer-plugin-chromecast/-/artplayer-plugin-chromecast-1.1.0.tgz",
+ "rationale": "Frozen npm tarball provenance; not a new registry availability observation.",
+ "limitation": "Frozen historical distribution mapping; not a fresh registry/version-availability check or rollback rehearsal"
+ },
+ "rollback": {
+ "basis": "refactor/baselines/chromecast-release.json",
+ "strategy": "Keep the frozen previous package integrity and tested consumer lock; publish a forward fix without overwriting npm versions. REL-04 rehearsal must cover any package rename."
+ },
+ "dependencies": [
+ "artplayer",
+ "artplayer-plugin-chromecast"
+ ],
+ "blockedTasks": [
+ "CI-01",
+ "CI-03",
+ "CI-04",
+ "PKG-CAST-05",
+ "PKG-CAST-06",
+ "REL-01",
+ "REL-09",
+ "REL-02",
+ "REL-03",
+ "REL-04",
+ "REVIEW-01",
+ "REVIEW-02",
+ "REVIEW-03"
+ ],
+ "openRisks": [
+ "ENG-PERF-01",
+ "ENG-PERF-02",
+ "BASE-SOURCE-01",
+ "BASE-SOURCE-02",
+ "SDK-03",
+ "SDK-04",
+ "SDK-05",
+ "SDK-06",
+ "BASE-ENV-01",
+ "MULTI-SUB-SWITCH-01"
+ ],
+ "requiredAssetReviews": [
+ "screenfull",
+ "hint.css",
+ "chromecast-cast-icon",
+ "flv.js",
+ "mpegts.js",
+ "webtorrent",
+ "google-cast",
+ "option-validator",
+ "dependency:artplayer:option-validator"
+ ],
+ "missingEvidence": [
+ "build",
+ "runtime",
+ "types",
+ "combinations",
+ "browser",
+ "devices",
+ "licenses",
+ "rollback",
+ "remote-ci",
+ "review-01",
+ "review-02",
+ "review-03"
+ ],
+ "blockerCount": 37
+ },
+ {
+ "name": "artplayer-plugin-danmuku",
+ "version": "5.3.0",
+ "targetVersion": "6.0.0",
+ "distribution": "npm",
+ "inputFingerprint": "b29fbd663ce2fe3ee214bb35820f86e14d17224c2267c6d699cd91ce7edcce18",
+ "status": "blocked",
+ "candidate": null,
+ "history": {
+ "verified": true,
+ "kind": "npm",
+ "file": "refactor/baselines/danmuku-release.json",
+ "name": "artplayer-plugin-danmuku",
+ "version": "5.3.0",
+ "integrity": "sha512-a76bU1cWEZr53hD1X8IHfOzS4QawGHn8dQ3fIYyJ5TsyhTSnznhi2WzkS8M7LL3H02ekXRZTUFan9+9EERk12g==",
+ "sha256": "1abb76460790d85a26590b8b911eb1301fd720885f96c73432060416a38dcb0e",
+ "tarball": "https://registry.npmjs.org/artplayer-plugin-danmuku/-/artplayer-plugin-danmuku-5.3.0.tgz",
+ "rationale": "Frozen npm tarball provenance; not a new registry availability observation.",
+ "limitation": "Frozen historical distribution mapping; not a fresh registry/version-availability check or rollback rehearsal"
+ },
+ "rollback": {
+ "basis": "refactor/baselines/danmuku-release.json",
+ "strategy": "Keep the frozen previous package integrity and tested consumer lock; publish a forward fix without overwriting npm versions. REL-04 rehearsal must cover any package rename."
+ },
+ "dependencies": [
+ "artplayer",
+ "artplayer-plugin-danmuku"
+ ],
+ "blockedTasks": [
+ "CI-01",
+ "CI-03",
+ "CI-04",
+ "PKG-DANMUKU-08",
+ "PKG-DANMUKU-09",
+ "REL-01",
+ "REL-09",
+ "REL-02",
+ "REL-03",
+ "REL-04",
+ "REVIEW-01",
+ "REVIEW-02",
+ "REVIEW-03"
+ ],
+ "openRisks": [
+ "ENG-PERF-01",
+ "ENG-PERF-02",
+ "BASE-SOURCE-01",
+ "BASE-SOURCE-02",
+ "SDK-03",
+ "SDK-04",
+ "SDK-05",
+ "BASE-ENV-01",
+ "DANMUKU-SOURCE-01",
+ "MULTI-SUB-SWITCH-01"
+ ],
+ "requiredAssetReviews": [
+ "screenfull",
+ "hint.css",
+ "flv.js",
+ "mpegts.js",
+ "webtorrent",
+ "option-validator",
+ "dependency:artplayer:option-validator"
+ ],
+ "missingEvidence": [
+ "build",
+ "runtime",
+ "types",
+ "combinations",
+ "browser",
+ "devices",
+ "licenses",
+ "rollback",
+ "remote-ci",
+ "review-01",
+ "review-02",
+ "review-03"
+ ],
+ "blockerCount": 37
+ },
+ {
+ "name": "artplayer-plugin-danmuku-mask",
+ "version": "1.1.0",
+ "targetVersion": "2.0.0",
+ "distribution": "npm",
+ "inputFingerprint": "107ca385b9ce8c76f506617d6a72eb0b8736269886776586b00fad1e20686e43",
+ "status": "blocked",
+ "candidate": null,
+ "history": {
+ "verified": true,
+ "kind": "npm",
+ "file": "refactor/baselines/danmuku-mask-release.json",
+ "name": "artplayer-plugin-danmuku-mask",
+ "version": "1.1.0",
+ "integrity": "sha512-VBmmwtNLj0TKYz9lWbyQ7A8osQHdyDiPMazP9g1UZbLyw/rMJ3qRclMgszRpZHN9hod0dQl2iYSJDoaqu3hGOQ==",
+ "sha256": "96682e46cb140a84b164818dd118480d7be42b24afaab390027a25e52e8ae31c",
+ "tarball": "https://registry.npmjs.org/artplayer-plugin-danmuku-mask/-/artplayer-plugin-danmuku-mask-1.1.0.tgz",
+ "rationale": "Frozen npm tarball provenance; not a new registry availability observation.",
+ "limitation": "Frozen historical distribution mapping; not a fresh registry/version-availability check or rollback rehearsal"
+ },
+ "rollback": {
+ "basis": "refactor/baselines/danmuku-mask-release.json",
+ "strategy": "Keep the frozen previous package integrity and tested consumer lock; publish a forward fix without overwriting npm versions. REL-04 rehearsal must cover any package rename."
+ },
+ "dependencies": [
+ "artplayer",
+ "artplayer-plugin-danmuku",
+ "artplayer-plugin-danmuku-mask"
+ ],
+ "blockedTasks": [
+ "CI-01",
+ "CI-03",
+ "CI-04",
+ "PKG-MASK-05",
+ "PKG-MASK-06",
+ "PKG-DANMUKU-08",
+ "PKG-DANMUKU-09",
+ "REL-01",
+ "REL-09",
+ "REL-02",
+ "REL-03",
+ "REL-04",
+ "REVIEW-01",
+ "REVIEW-02",
+ "REVIEW-03"
+ ],
+ "openRisks": [
+ "ENG-PERF-01",
+ "ENG-PERF-02",
+ "BASE-SOURCE-01",
+ "BASE-SOURCE-02",
+ "SDK-03",
+ "SDK-04",
+ "SDK-05",
+ "SDK-08",
+ "BASE-ENV-01",
+ "MASK-LIFETIME-01",
+ "MASK-MODEL-01",
+ "MASK-BACKEND-01",
+ "MASK-DOM-01",
+ "MASK-NOTICE-01",
+ "DANMUKU-SOURCE-01",
+ "MASK-SCHEDULING-01",
+ "MULTI-SUB-SWITCH-01"
+ ],
+ "requiredAssetReviews": [
+ "screenfull",
+ "hint.css",
+ "flv.js",
+ "mpegts.js",
+ "webtorrent",
+ "mediapipe-tensorflow",
+ "option-validator",
+ "dependency:artplayer:option-validator",
+ "dependency:artplayer-plugin-danmuku-mask:@mediapipe/selfie_segmentation",
+ "dependency:artplayer-plugin-danmuku-mask:@tensorflow-models/body-segmentation",
+ "dependency:artplayer-plugin-danmuku-mask:@tensorflow/tfjs-backend-cpu",
+ "dependency:artplayer-plugin-danmuku-mask:@tensorflow/tfjs-backend-webgl",
+ "dependency:artplayer-plugin-danmuku-mask:@tensorflow/tfjs-converter",
+ "dependency:artplayer-plugin-danmuku-mask:@tensorflow/tfjs-core"
+ ],
+ "missingEvidence": [
+ "build",
+ "runtime",
+ "types",
+ "combinations",
+ "browser",
+ "devices",
+ "licenses",
+ "rollback",
+ "remote-ci",
+ "review-01",
+ "review-02",
+ "review-03"
+ ],
+ "blockerCount": 46
+ },
+ {
+ "name": "artplayer-plugin-dash-control",
+ "version": "1.1.0",
+ "targetVersion": "2.0.0",
+ "distribution": "npm",
+ "inputFingerprint": "e3f955bc0d8cb70975c5122d60587fe73e90a5f14abf334e689e8863d8924430",
+ "status": "blocked",
+ "candidate": null,
+ "history": {
+ "verified": true,
+ "kind": "npm",
+ "file": "refactor/baselines/dash-control-release.json",
+ "name": "artplayer-plugin-dash-control",
+ "version": "1.1.0",
+ "integrity": "sha512-EssRkzYXWcln3a6nAzi4ESacBhzMih8s0bzdjXuvBBCh2UFmvK99ITD5E+0cuU8i5ZzIQGQSdce1/gANTrX0BQ==",
+ "sha256": "f4047a569d99b6a7de57b3719b7ad40109f2a1d360566a840f50ab32aacefcd9",
+ "tarball": "https://registry.npmjs.org/artplayer-plugin-dash-control/-/artplayer-plugin-dash-control-1.1.0.tgz",
+ "rationale": "Frozen npm tarball provenance; not a new registry availability observation.",
+ "limitation": "Frozen historical distribution mapping; not a fresh registry/version-availability check or rollback rehearsal"
+ },
+ "rollback": {
+ "basis": "refactor/baselines/dash-control-release.json",
+ "strategy": "Keep the frozen previous package integrity and tested consumer lock; publish a forward fix without overwriting npm versions. REL-04 rehearsal must cover any package rename."
+ },
+ "dependencies": [
+ "artplayer",
+ "artplayer-plugin-dash-control"
+ ],
+ "blockedTasks": [
+ "CI-01",
+ "CI-03",
+ "CI-04",
+ "PKG-DASH-05",
+ "PKG-DASH-06",
+ "REL-01",
+ "REL-09",
+ "REL-02",
+ "REL-03",
+ "REL-04",
+ "REVIEW-01",
+ "REVIEW-02",
+ "REVIEW-03"
+ ],
+ "openRisks": [
+ "ENG-PERF-01",
+ "ENG-PERF-02",
+ "BASE-SOURCE-01",
+ "BASE-SOURCE-02",
+ "SDK-02",
+ "SDK-03",
+ "SDK-04",
+ "SDK-05",
+ "BASE-ENV-01",
+ "DASH-SDK-01",
+ "DASH-DEMO-01",
+ "DASH-SEEK-01",
+ "MULTI-SUB-SWITCH-01"
+ ],
+ "requiredAssetReviews": [
+ "screenfull",
+ "hint.css",
+ "dash.js",
+ "flv.js",
+ "mpegts.js",
+ "webtorrent",
+ "option-validator",
+ "dependency:artplayer:option-validator"
+ ],
+ "missingEvidence": [
+ "build",
+ "runtime",
+ "types",
+ "combinations",
+ "browser",
+ "devices",
+ "licenses",
+ "rollback",
+ "remote-ci",
+ "review-01",
+ "review-02",
+ "review-03"
+ ],
+ "blockerCount": 40
+ },
+ {
+ "name": "artplayer-plugin-document-pip",
+ "version": "1.1.0",
+ "targetVersion": "2.0.0",
+ "distribution": "npm",
+ "inputFingerprint": "c047d7c8f3f30000093de6784d5b104bed5b78aa550b81489ecfa4daa74cf996",
+ "status": "blocked",
+ "candidate": null,
+ "history": {
+ "verified": true,
+ "kind": "npm",
+ "file": "refactor/baselines/dpip-release.json",
+ "name": "artplayer-plugin-document-pip",
+ "version": "1.1.0",
+ "integrity": "sha512-+HCrlO+bQwBrig1bI/IIxgGvbcG7i+aNHAnR5vLqB+RdyIY+FKCHyEe0o9yZREnbYOe0XJVqjv2buEVrouzdoQ==",
+ "sha256": "b864befea9aff84b3352782a9670ebe72ff073606fec7cdc31d31e92a5ad6d8a",
+ "tarball": "https://registry.npmjs.org/artplayer-plugin-document-pip/-/artplayer-plugin-document-pip-1.1.0.tgz",
+ "rationale": "Frozen npm tarball provenance; not a new registry availability observation.",
+ "limitation": "Frozen historical distribution mapping; not a fresh registry/version-availability check or rollback rehearsal"
+ },
+ "rollback": {
+ "basis": "refactor/baselines/dpip-release.json",
+ "strategy": "Keep the frozen previous package integrity and tested consumer lock; publish a forward fix without overwriting npm versions. REL-04 rehearsal must cover any package rename."
+ },
+ "dependencies": [
+ "artplayer",
+ "artplayer-plugin-document-pip"
+ ],
+ "blockedTasks": [
+ "CI-01",
+ "CI-03",
+ "CI-04",
+ "PKG-DPIP-05",
+ "PKG-DPIP-06",
+ "REL-01",
+ "REL-09",
+ "REL-02",
+ "REL-03",
+ "REL-04",
+ "REVIEW-01",
+ "REVIEW-02",
+ "REVIEW-03"
+ ],
+ "openRisks": [
+ "ENG-PERF-01",
+ "ENG-PERF-02",
+ "BASE-SOURCE-01",
+ "BASE-SOURCE-02",
+ "SDK-03",
+ "SDK-04",
+ "SDK-05",
+ "BASE-ENV-01",
+ "DPIP-PACK-01",
+ "DPIP-LIFE-01",
+ "DPIP-DOM-01",
+ "DPIP-MEDIA-01",
+ "MULTI-SUB-SWITCH-01"
+ ],
+ "requiredAssetReviews": [
+ "screenfull",
+ "hint.css",
+ "flv.js",
+ "mpegts.js",
+ "webtorrent",
+ "option-validator",
+ "dependency:artplayer:option-validator"
+ ],
+ "missingEvidence": [
+ "build",
+ "runtime",
+ "types",
+ "combinations",
+ "browser",
+ "devices",
+ "licenses",
+ "rollback",
+ "remote-ci",
+ "review-01",
+ "review-02",
+ "review-03"
+ ],
+ "blockerCount": 40
+ },
+ {
+ "name": "artplayer-plugin-hls-control",
+ "version": "1.1.0",
+ "targetVersion": "2.0.0",
+ "distribution": "npm",
+ "inputFingerprint": "5b75a543f2784f4b067f8b642a0eeec28125532bb509a107eeb0c4b35e3d3778",
+ "status": "blocked",
+ "candidate": null,
+ "history": {
+ "verified": true,
+ "kind": "npm",
+ "file": "refactor/baselines/hls-control-release.json",
+ "name": "artplayer-plugin-hls-control",
+ "version": "1.1.0",
+ "integrity": "sha512-lWN5oUOWDorWFHdzKcmy9Z8eHcf5f5EcUGDrxBsZBX7aEL5dGRlHPQ5+wqQEizr38D9PYPfTI/nCyun2yFopEw==",
+ "sha256": "598c354d78d3cb77965136fa61dfc0f94cdea6906a91a30781c1acef2b0c3e8b",
+ "tarball": "https://registry.npmjs.org/artplayer-plugin-hls-control/-/artplayer-plugin-hls-control-1.1.0.tgz",
+ "rationale": "Frozen npm tarball provenance; not a new registry availability observation.",
+ "limitation": "Frozen historical distribution mapping; not a fresh registry/version-availability check or rollback rehearsal"
+ },
+ "rollback": {
+ "basis": "refactor/baselines/hls-control-release.json",
+ "strategy": "Keep the frozen previous package integrity and tested consumer lock; publish a forward fix without overwriting npm versions. REL-04 rehearsal must cover any package rename."
+ },
+ "dependencies": [
+ "artplayer",
+ "artplayer-plugin-hls-control"
+ ],
+ "blockedTasks": [
+ "CI-01",
+ "CI-03",
+ "CI-04",
+ "PKG-HLS-SDK-01",
+ "PKG-HLS-05",
+ "PKG-HLS-06",
+ "REL-01",
+ "REL-09",
+ "REL-02",
+ "REL-03",
+ "REL-04",
+ "REVIEW-01",
+ "REVIEW-02",
+ "REVIEW-03"
+ ],
+ "openRisks": [
+ "ENG-PERF-01",
+ "ENG-PERF-02",
+ "BASE-SOURCE-01",
+ "BASE-SOURCE-02",
+ "SDK-01",
+ "SDK-03",
+ "SDK-04",
+ "SDK-05",
+ "BASE-ENV-01",
+ "HLS-ENV-01",
+ "HLS-CRASH-01",
+ "HLS-PLAYBACK-01",
+ "MULTI-SUB-SWITCH-01"
+ ],
+ "requiredAssetReviews": [
+ "screenfull",
+ "hint.css",
+ "hls.js",
+ "flv.js",
+ "mpegts.js",
+ "webtorrent",
+ "option-validator",
+ "dependency:artplayer:option-validator"
+ ],
+ "missingEvidence": [
+ "build",
+ "runtime",
+ "types",
+ "combinations",
+ "browser",
+ "devices",
+ "licenses",
+ "rollback",
+ "remote-ci",
+ "review-01",
+ "review-02",
+ "review-03"
+ ],
+ "blockerCount": 41
+ },
+ {
+ "name": "artplayer-plugin-jassub",
+ "version": "1.1.0",
+ "targetVersion": "2.0.0",
+ "distribution": "npm",
+ "inputFingerprint": "ad1a2e5fe8760180c69ad9c91152c3e63f14c5c08b7476aca859ca6fce1af175",
+ "status": "blocked",
+ "candidate": null,
+ "history": {
+ "verified": true,
+ "kind": "npm",
+ "file": "refactor/baselines/jassub-release.json",
+ "name": "artplayer-plugin-jassub",
+ "version": "1.1.0",
+ "integrity": "sha512-vQwEtZMmkwr5utbxfUAeE/gRccl2nfcz+SYP2Subn/L8cVIFdMXWMgicm7ZkPVtAoBbCav7p0yP4pwXkCKMzAg==",
+ "sha256": "00713b4eee4b90e7dc13d1f2c88bc785f9b62f3903b00be6f7bec738fbb78c57",
+ "tarball": "https://registry.npmjs.org/artplayer-plugin-jassub/-/artplayer-plugin-jassub-1.1.0.tgz",
+ "rationale": "Frozen npm tarball provenance; not a new registry availability observation.",
+ "limitation": "Frozen historical distribution mapping; not a fresh registry/version-availability check or rollback rehearsal"
+ },
+ "rollback": {
+ "basis": "refactor/baselines/jassub-release.json",
+ "strategy": "Keep the frozen previous package integrity and tested consumer lock; publish a forward fix without overwriting npm versions. REL-04 rehearsal must cover any package rename."
+ },
+ "dependencies": [
+ "artplayer",
+ "artplayer-plugin-jassub"
+ ],
+ "blockedTasks": [
+ "CI-01",
+ "CI-03",
+ "CI-04",
+ "PKG-JASSUB-05",
+ "PKG-JASSUB-06",
+ "REL-01",
+ "REL-09",
+ "REL-02",
+ "REL-03",
+ "REL-04",
+ "REVIEW-01",
+ "REVIEW-02",
+ "REVIEW-03",
+ "PKG-JASSUB-09"
+ ],
+ "openRisks": [
+ "ENG-PERF-01",
+ "ENG-PERF-02",
+ "BASE-SOURCE-01",
+ "BASE-SOURCE-02",
+ "VENDOR-04",
+ "VENDOR-05",
+ "SDK-03",
+ "SDK-04",
+ "SDK-05",
+ "SDK-09",
+ "BASE-ENV-01",
+ "JASSUB-TYPE-01",
+ "JASSUB-EXPORT-01",
+ "JASSUB-HYBRID-01",
+ "JASSUB-FIREFOX-OFFSCREEN-01",
+ "MULTI-SUB-SWITCH-01"
+ ],
+ "requiredAssetReviews": [
+ "screenfull",
+ "hint.css",
+ "jassub-code-and-workers",
+ "jassub-font-assets",
+ "flv.js",
+ "mpegts.js",
+ "webtorrent",
+ "jassub-worker-wasm-fonts",
+ "option-validator",
+ "dependency:artplayer:option-validator"
+ ],
+ "missingEvidence": [
+ "build",
+ "runtime",
+ "types",
+ "combinations",
+ "browser",
+ "devices",
+ "licenses",
+ "rollback",
+ "remote-ci",
+ "review-01",
+ "review-02",
+ "review-03"
+ ],
+ "blockerCount": 44
+ },
+ {
+ "name": "artplayer-plugin-multiple-subtitles",
+ "version": "1.2.0",
+ "targetVersion": "2.0.0",
+ "distribution": "npm",
+ "inputFingerprint": "426a429508be5d54cbd200b3d7f9836f19a13ffacde1e66581d8a7706a894a6b",
+ "status": "blocked",
+ "candidate": null,
+ "history": {
+ "verified": true,
+ "kind": "npm",
+ "file": "refactor/baselines/multiple-subtitles-release.json",
+ "name": "artplayer-plugin-multiple-subtitles",
+ "version": "1.2.0",
+ "integrity": "sha512-3mzoHkgKx3KVdxKQBwLkjetPF6WjJtro5uQNdse8FqUeiV0afbywLnP6H8lcDN/Fwgp2VlBknrs2+COiyFdV8g==",
+ "sha256": "a013fbe0772bf560d177449fc80e848c41f16ae2f72ace86f8fb0d5b1000a54e",
+ "tarball": "https://registry.npmjs.org/artplayer-plugin-multiple-subtitles/-/artplayer-plugin-multiple-subtitles-1.2.0.tgz",
+ "rationale": "Frozen npm tarball provenance; not a new registry availability observation.",
+ "limitation": "Frozen historical distribution mapping; not a fresh registry/version-availability check or rollback rehearsal"
+ },
+ "rollback": {
+ "basis": "refactor/baselines/multiple-subtitles-release.json",
+ "strategy": "Keep the frozen previous package integrity and tested consumer lock; publish a forward fix without overwriting npm versions. REL-04 rehearsal must cover any package rename."
+ },
+ "dependencies": [
+ "artplayer",
+ "artplayer-plugin-multiple-subtitles"
+ ],
+ "blockedTasks": [
+ "CI-01",
+ "CI-03",
+ "CI-04",
+ "PKG-MULTI-SUB-05",
+ "PKG-MULTI-SUB-06",
+ "REL-01",
+ "REL-09",
+ "REL-02",
+ "REL-03",
+ "REL-04",
+ "REVIEW-01",
+ "REVIEW-02",
+ "REVIEW-03"
+ ],
+ "openRisks": [
+ "ENG-PERF-01",
+ "ENG-PERF-02",
+ "BASE-SOURCE-01",
+ "BASE-SOURCE-02",
+ "SDK-03",
+ "SDK-04",
+ "SDK-05",
+ "BASE-ENV-01",
+ "MULTI-SUB-MERGE-01",
+ "MULTI-SUB-LIFE-01",
+ "MULTI-SUB-EXPORT-01",
+ "MULTI-SUB-SWITCH-01"
+ ],
+ "requiredAssetReviews": [
+ "screenfull",
+ "hint.css",
+ "webvtt-parser",
+ "flv.js",
+ "mpegts.js",
+ "webtorrent",
+ "option-validator",
+ "dependency:artplayer:option-validator"
+ ],
+ "missingEvidence": [
+ "build",
+ "runtime",
+ "types",
+ "combinations",
+ "browser",
+ "devices",
+ "licenses",
+ "rollback",
+ "remote-ci",
+ "review-01",
+ "review-02",
+ "review-03"
+ ],
+ "blockerCount": 39
+ },
+ {
+ "name": "artplayer-plugin-vast",
+ "version": "1.2.0",
+ "targetVersion": "2.0.0",
+ "distribution": "npm",
+ "inputFingerprint": "4a9ef3e71ad6fb52ac6cde0bd98410e10cbdfeed0a51b3918d7aefcb5aeedaf8",
+ "status": "blocked",
+ "candidate": null,
+ "history": {
+ "verified": true,
+ "kind": "npm",
+ "file": "refactor/baselines/vast-release.json",
+ "name": "artplayer-plugin-vast",
+ "version": "1.0.0",
+ "integrity": "sha512-6ia0BYlZMBUAbvfqPq6RD3kvCP/Vpnqjs4zgzcio0CUh3a/iJkp+caKFOFjROfilbL+B0lXUiA7U1kS9KqEwTg==",
+ "sha256": "35681d60fa3fa09b92f0f30887e39c73cf55e7b14d51a1c38fccbc6c1f253bab",
+ "tarball": "https://registry.npmjs.org/artplayer-plugin-vast/-/artplayer-plugin-vast-1.0.0.tgz",
+ "rationale": "Frozen npm tarball provenance; not a new registry availability observation.",
+ "limitation": "Frozen historical distribution mapping; not a fresh registry/version-availability check or rollback rehearsal"
+ },
+ "rollback": {
+ "basis": "refactor/baselines/vast-release.json",
+ "strategy": "Keep the frozen previous package integrity and tested consumer lock; publish a forward fix without overwriting npm versions. REL-04 rehearsal must cover any package rename."
+ },
+ "dependencies": [
+ "artplayer",
+ "artplayer-plugin-vast"
+ ],
+ "blockedTasks": [
+ "CI-01",
+ "CI-03",
+ "CI-04",
+ "PKG-VAST-03",
+ "PKG-VAST-04",
+ "PKG-VAST-05",
+ "PKG-VAST-06",
+ "REL-01",
+ "REL-09",
+ "REL-02",
+ "REL-03",
+ "REL-04",
+ "REVIEW-01",
+ "REVIEW-02",
+ "REVIEW-03"
+ ],
+ "openRisks": [
+ "ENG-PERF-01",
+ "ENG-PERF-02",
+ "BASE-SOURCE-01",
+ "BASE-SOURCE-02",
+ "SDK-03",
+ "SDK-04",
+ "SDK-05",
+ "SDK-07",
+ "BASE-ENV-01",
+ "VAST-CONTEXT-01",
+ "VAST-TYPE-01",
+ "VAST-LIFE-01",
+ "VAST-DIST-01",
+ "MULTI-SUB-SWITCH-01"
+ ],
+ "requiredAssetReviews": [
+ "screenfull",
+ "hint.css",
+ "flv.js",
+ "mpegts.js",
+ "webtorrent",
+ "ima-via-glomex",
+ "option-validator",
+ "dependency:artplayer:option-validator",
+ "dependency:artplayer-plugin-vast:@glomex/vast-ima-player",
+ "dependency:artplayer-plugin-vast:@alugha/ima"
+ ],
+ "missingEvidence": [
+ "build",
+ "runtime",
+ "types",
+ "combinations",
+ "browser",
+ "devices",
+ "licenses",
+ "rollback",
+ "remote-ci",
+ "review-01",
+ "review-02",
+ "review-03"
+ ],
+ "blockerCount": 43
+ },
+ {
+ "name": "artplayer-plugin-vtt-thumbnail",
+ "version": "1.1.0",
+ "targetVersion": "2.0.0",
+ "distribution": "npm",
+ "inputFingerprint": "dc828a8773a90e0502e5d085ecf7ec22ac184242c2b10f117644c42cd31ec0f0",
+ "status": "blocked",
+ "candidate": null,
+ "history": {
+ "verified": true,
+ "kind": "npm",
+ "file": "refactor/baselines/vtt-thumbnail-release.json",
+ "name": "artplayer-plugin-vtt-thumbnail",
+ "version": "1.1.0",
+ "integrity": "sha512-p5HIiaxBEN0CV4FWOWXCfy6hcQL59JuFIGUbQtVg9AcunPg2gJ1cJ8P0HEQ/ngH6tl1/puxrJIKydoAdX5L2dw==",
+ "sha256": "e2df36283538bbffc2c37fe28a8af0fe6fc0fa68a5f6cf93042929ee64d55765",
+ "tarball": "https://registry.npmjs.org/artplayer-plugin-vtt-thumbnail/-/artplayer-plugin-vtt-thumbnail-1.1.0.tgz",
+ "rationale": "Frozen npm tarball provenance; not a new registry availability observation.",
+ "limitation": "Frozen historical distribution mapping; not a fresh registry/version-availability check or rollback rehearsal"
+ },
+ "rollback": {
+ "basis": "refactor/baselines/vtt-thumbnail-release.json",
+ "strategy": "Keep the frozen previous package integrity and tested consumer lock; publish a forward fix without overwriting npm versions. REL-04 rehearsal must cover any package rename."
+ },
+ "dependencies": [
+ "artplayer",
+ "artplayer-plugin-vtt-thumbnail"
+ ],
+ "blockedTasks": [
+ "CI-01",
+ "CI-03",
+ "CI-04",
+ "PKG-VTT-THUMB-05",
+ "PKG-VTT-THUMB-06",
+ "REL-01",
+ "REL-09",
+ "REL-02",
+ "REL-03",
+ "REL-04",
+ "REVIEW-01",
+ "REVIEW-02",
+ "REVIEW-03"
+ ],
+ "openRisks": [
+ "ENG-PERF-01",
+ "ENG-PERF-02",
+ "BASE-SOURCE-01",
+ "BASE-SOURCE-02",
+ "SDK-03",
+ "SDK-04",
+ "SDK-05",
+ "BASE-ENV-01",
+ "VTT-THUMB-DIST-01",
+ "VTT-THUMB-LIFE-01",
+ "VTT-THUMB-EXPORT-01",
+ "VTT-CORE-NAME-01",
+ "MULTI-SUB-SWITCH-01"
+ ],
+ "requiredAssetReviews": [
+ "screenfull",
+ "hint.css",
+ "flv.js",
+ "mpegts.js",
+ "webtorrent",
+ "option-validator",
+ "dependency:artplayer:option-validator"
+ ],
+ "missingEvidence": [
+ "build",
+ "runtime",
+ "types",
+ "combinations",
+ "browser",
+ "devices",
+ "licenses",
+ "rollback",
+ "remote-ci",
+ "review-01",
+ "review-02",
+ "review-03"
+ ],
+ "blockerCount": 40
+ },
+ {
+ "name": "artplayer-proxy-canvas",
+ "version": "1.1.0",
+ "targetVersion": "2.0.0",
+ "distribution": "npm",
+ "inputFingerprint": "447cce38e39b08ed402a7820eb8f27b4fb043fd05be5ef18ddda5be668e05a96",
+ "status": "blocked",
+ "candidate": null,
+ "history": {
+ "verified": true,
+ "kind": "npm",
+ "file": "refactor/baselines/canvas-release.json",
+ "name": "artplayer-proxy-canvas",
+ "version": "1.1.0",
+ "integrity": "sha512-lOq22z+xX2DcpokDSMQrDTk4VGDwUaQm9SovQ0GlhgmvMBdxRBDlvv6QrVxFUFsiGUjXEdx9TmtzdiW38dlxiQ==",
+ "sha256": "348c1d6671f79d0e0dbf2bb15875a1e6d60d00b73cde833c018bec7e4be96c8e",
+ "tarball": "https://registry.npmjs.org/artplayer-proxy-canvas/-/artplayer-proxy-canvas-1.1.0.tgz",
+ "rationale": "Frozen npm tarball provenance; not a new registry availability observation.",
+ "limitation": "Frozen historical distribution mapping; not a fresh registry/version-availability check or rollback rehearsal"
+ },
+ "rollback": {
+ "basis": "refactor/baselines/canvas-release.json",
+ "strategy": "Keep the frozen previous package integrity and tested consumer lock; publish a forward fix without overwriting npm versions. REL-04 rehearsal must cover any package rename."
+ },
+ "dependencies": [
+ "artplayer",
+ "artplayer-proxy-canvas"
+ ],
+ "blockedTasks": [
+ "CI-01",
+ "CI-03",
+ "CI-04",
+ "PKG-CANVAS-05",
+ "PKG-CANVAS-06",
+ "REL-01",
+ "REL-09",
+ "REL-02",
+ "REL-03",
+ "REL-04",
+ "REVIEW-01",
+ "REVIEW-02",
+ "REVIEW-03"
+ ],
+ "openRisks": [
+ "ENG-PERF-01",
+ "ENG-PERF-02",
+ "BASE-SOURCE-01",
+ "BASE-SOURCE-02",
+ "SDK-03",
+ "SDK-04",
+ "SDK-05",
+ "BASE-ENV-01",
+ "MULTI-SUB-SWITCH-01"
+ ],
+ "requiredAssetReviews": [
+ "screenfull",
+ "hint.css",
+ "flv.js",
+ "mpegts.js",
+ "webtorrent",
+ "option-validator",
+ "dependency:artplayer:option-validator"
+ ],
+ "missingEvidence": [
+ "build",
+ "runtime",
+ "types",
+ "combinations",
+ "browser",
+ "devices",
+ "licenses",
+ "rollback",
+ "remote-ci",
+ "review-01",
+ "review-02",
+ "review-03"
+ ],
+ "blockerCount": 36
+ },
+ {
+ "name": "artplayer-proxy-mediabunny",
+ "version": "1.2.0",
+ "targetVersion": "2.0.0",
+ "distribution": "npm",
+ "inputFingerprint": "d51bcdc435a0c3ba129ca16600fc0b07a5348307a58140a7d2f5025ef0b5f4d1",
+ "status": "blocked",
+ "candidate": null,
+ "history": {
+ "verified": true,
+ "kind": "npm",
+ "file": "refactor/baselines/mb-release.json",
+ "name": "artplayer-proxy-mediabunny",
+ "version": "1.2.0",
+ "integrity": "sha512-uSKkJFvd5Y2XxPPhwtPM5k9qq3xScbWnO/9fOfC8S+Liz8f8nT/iyA2eynU3maVcHZbJgA3PHZm0r6RRmSkefg==",
+ "sha256": "acb016166059183917a6de2e03a65ad77b09997fc9fb62513d751a24467ca76c",
+ "tarball": "https://registry.npmjs.org/artplayer-proxy-mediabunny/-/artplayer-proxy-mediabunny-1.2.0.tgz",
+ "rationale": "Frozen npm tarball provenance; not a new registry availability observation.",
+ "limitation": "Frozen historical distribution mapping; not a fresh registry/version-availability check or rollback rehearsal"
+ },
+ "rollback": {
+ "basis": "refactor/baselines/mb-release.json",
+ "strategy": "Keep the frozen previous package integrity and tested consumer lock; publish a forward fix without overwriting npm versions. REL-04 rehearsal must cover any package rename."
+ },
+ "dependencies": [
+ "artplayer",
+ "artplayer-plugin-hls-control",
+ "artplayer-proxy-mediabunny"
+ ],
+ "blockedTasks": [
+ "CI-01",
+ "CI-03",
+ "CI-04",
+ "PKG-HLS-SDK-01",
+ "PKG-HLS-05",
+ "PKG-HLS-06",
+ "PKG-MB-09",
+ "PKG-MB-10",
+ "REL-01",
+ "REL-09",
+ "REL-02",
+ "REL-03",
+ "REL-04",
+ "REVIEW-01",
+ "REVIEW-02",
+ "REVIEW-03"
+ ],
+ "openRisks": [
+ "ENG-PERF-01",
+ "ENG-PERF-02",
+ "BASE-SOURCE-01",
+ "BASE-SOURCE-02",
+ "SDK-01",
+ "SDK-03",
+ "SDK-04",
+ "SDK-05",
+ "SDK-10",
+ "BASE-ENV-01",
+ "HLS-ENV-01",
+ "HLS-CRASH-01",
+ "HLS-PLAYBACK-01",
+ "MB-LICENSE-01",
+ "MB-LIFE-01",
+ "MB-CAP-01",
+ "MULTI-SUB-SWITCH-01"
+ ],
+ "requiredAssetReviews": [
+ "screenfull",
+ "hint.css",
+ "hls.js",
+ "flv.js",
+ "mpegts.js",
+ "webtorrent",
+ "mediabunny",
+ "option-validator",
+ "dependency:artplayer:option-validator",
+ "dependency:artplayer-proxy-mediabunny:mediabunny"
+ ],
+ "missingEvidence": [
+ "build",
+ "runtime",
+ "types",
+ "combinations",
+ "browser",
+ "devices",
+ "licenses",
+ "rollback",
+ "remote-ci",
+ "review-01",
+ "review-02",
+ "review-03"
+ ],
+ "blockerCount": 47
+ },
+ {
+ "name": "artplayer-tool-iframe",
+ "version": "1.1.0",
+ "targetVersion": "2.0.0",
+ "distribution": "renamed-npm",
+ "inputFingerprint": "1bfbe759e75dddbdc028ff8ff2e6e69316c99d6fde741844ebe692edc86fa193",
+ "status": "blocked",
+ "candidate": null,
+ "history": {
+ "verified": true,
+ "kind": "renamed-npm",
+ "file": "refactor/baselines/iframe-release.json",
+ "name": "artplayer-plugin-iframe",
+ "version": "1.0.0",
+ "integrity": "sha512-9eh5NVjZ8/Vxl0ojZxnMY6IlYxCVVfyGH2Rzl7uYDJnGc8NQ4y9vMN/gVh1h8R2m5qaYm7/XDo/45O9a7hDiKg==",
+ "sha256": "16dc92aa84d93bd99bc0a34815dc49f1609fafc1ff255eb353ea893e70b5f91f",
+ "tarball": "https://registry.npmjs.org/artplayer-plugin-iframe/-/artplayer-plugin-iframe-1.0.0.tgz",
+ "rationale": "Frozen published predecessor and documented rename commit; candidate keeps current workspace name. Registry target availability still requires REL-01.",
+ "limitation": "Frozen historical distribution mapping; not a fresh registry/version-availability check or rollback rehearsal"
+ },
+ "rollback": {
+ "basis": "refactor/baselines/iframe-release.json",
+ "strategy": "Keep the frozen previous package integrity and tested consumer lock; publish a forward fix without overwriting npm versions. REL-04 rehearsal must cover any package rename."
+ },
+ "dependencies": [
+ "artplayer",
+ "artplayer-tool-iframe"
+ ],
+ "blockedTasks": [
+ "CI-01",
+ "CI-03",
+ "CI-04",
+ "PKG-IFRAME-05",
+ "PKG-IFRAME-06",
+ "REL-01",
+ "REL-09",
+ "REL-02",
+ "REL-03",
+ "REL-04",
+ "REVIEW-01",
+ "REVIEW-02",
+ "REVIEW-03"
+ ],
+ "openRisks": [
+ "ENG-PERF-01",
+ "ENG-PERF-02",
+ "BASE-SOURCE-01",
+ "BASE-SOURCE-02",
+ "SDK-03",
+ "SDK-04",
+ "SDK-05",
+ "BASE-ENV-01",
+ "IFRAME-LIFE-01",
+ "IFRAME-TRUST-01",
+ "IFRAME-DIST-01",
+ "MULTI-SUB-SWITCH-01"
+ ],
+ "requiredAssetReviews": [
+ "screenfull",
+ "hint.css",
+ "flv.js",
+ "mpegts.js",
+ "webtorrent",
+ "option-validator",
+ "dependency:artplayer:option-validator"
+ ],
+ "missingEvidence": [
+ "build",
+ "runtime",
+ "types",
+ "combinations",
+ "browser",
+ "devices",
+ "licenses",
+ "rollback",
+ "remote-ci",
+ "review-01",
+ "review-02",
+ "review-03"
+ ],
+ "blockerCount": 39
+ },
+ {
+ "name": "artplayer-tool-thumbnail",
+ "version": "4.4.0",
+ "targetVersion": "5.0.0",
+ "distribution": "recovered-npm",
+ "inputFingerprint": "45d11e35a062fe5f9dccb55d31260b727f83042a557896d09da02cfb03966ffb",
+ "status": "blocked",
+ "candidate": null,
+ "history": {
+ "verified": false,
+ "reason": "Historical archive unavailable; recovered content is not a complete rollback tarball",
+ "file": "refactor/baselines/thumbnail-release.json"
+ },
+ "rollback": {
+ "basis": "refactor/baselines/thumbnail-release.json",
+ "strategy": "Original tarball is unavailable. Resolve a complete rollback alternative and migration scope under REL-04 before release."
+ },
+ "dependencies": [
+ "artplayer",
+ "artplayer-tool-thumbnail"
+ ],
+ "blockedTasks": [
+ "CI-01",
+ "CI-03",
+ "CI-04",
+ "PKG-TOOL-THUMB-04",
+ "PKG-TOOL-THUMB-05",
+ "PKG-TOOL-THUMB-06",
+ "REL-01",
+ "REL-09",
+ "REL-02",
+ "REL-03",
+ "REL-04",
+ "REVIEW-01",
+ "REVIEW-02",
+ "REVIEW-03"
+ ],
+ "openRisks": [
+ "ENG-PERF-01",
+ "ENG-PERF-02",
+ "BASE-DIST-01",
+ "BASE-SOURCE-01",
+ "BASE-SOURCE-02",
+ "SDK-03",
+ "SDK-04",
+ "SDK-05",
+ "BASE-ENV-01",
+ "THUMB-COMPAT-01",
+ "THUMB-LIFE-01",
+ "THUMB-MEDIA-01",
+ "MULTI-SUB-SWITCH-01"
+ ],
+ "requiredAssetReviews": [
+ "screenfull",
+ "hint.css",
+ "thumbnail-tiny-emitter",
+ "flv.js",
+ "mpegts.js",
+ "webtorrent",
+ "option-validator",
+ "dependency:artplayer:option-validator"
+ ],
+ "missingEvidence": [
+ "build",
+ "runtime",
+ "types",
+ "combinations",
+ "browser",
+ "devices",
+ "licenses",
+ "rollback",
+ "remote-ci",
+ "review-01",
+ "review-02",
+ "review-03"
+ ],
+ "blockerCount": 42
+ },
+ {
+ "name": "artplayer-vitepress",
+ "version": "1.1.0",
+ "targetVersion": "2.0.0",
+ "distribution": "site",
+ "inputFingerprint": "0fc3a84ebb741d2dc60be693840991c6309e07d4919c6bcffd00378793527b96",
+ "status": "blocked",
+ "candidate": null,
+ "history": {
+ "verified": true,
+ "kind": "site-only",
+ "file": "refactor/baselines/site-provenance.json",
+ "rationale": "Existing docs/site build and URL distribution. No npm publication is inferred.",
+ "npmPublication": false
+ },
+ "rollback": {
+ "basis": "refactor/baselines/site-provenance.json",
+ "strategy": "Restore the previously verified Pages artifact and old URL map; rehearsal evidence required."
+ },
+ "dependencies": [
+ "artplayer",
+ "artplayer-plugin-ads",
+ "artplayer-plugin-ambilight",
+ "artplayer-plugin-asr",
+ "artplayer-plugin-audio-track",
+ "artplayer-plugin-auto-thumbnail",
+ "artplayer-plugin-chapter",
+ "artplayer-plugin-chromecast",
+ "artplayer-plugin-danmuku",
+ "artplayer-plugin-danmuku-mask",
+ "artplayer-plugin-dash-control",
+ "artplayer-plugin-document-pip",
+ "artplayer-plugin-hls-control",
+ "artplayer-plugin-jassub",
+ "artplayer-plugin-multiple-subtitles",
+ "artplayer-plugin-vast",
+ "artplayer-plugin-vtt-thumbnail",
+ "artplayer-proxy-canvas",
+ "artplayer-proxy-mediabunny",
+ "artplayer-tool-iframe",
+ "artplayer-tool-thumbnail",
+ "artplayer-vitepress"
+ ],
+ "blockedTasks": [
+ "CI-01",
+ "CI-03",
+ "CI-04",
+ "PKG-CHAPTER-05",
+ "PKG-CHAPTER-06",
+ "PKG-AMBILIGHT-05",
+ "PKG-AMBILIGHT-06",
+ "PKG-AUDIO-05",
+ "PKG-AUDIO-06",
+ "PKG-AUTO-THUMB-03",
+ "PKG-AUTO-THUMB-04",
+ "PKG-AUTO-THUMB-05",
+ "PKG-AUTO-THUMB-06",
+ "PKG-VTT-THUMB-05",
+ "PKG-VTT-THUMB-06",
+ "PKG-HLS-SDK-01",
+ "PKG-HLS-05",
+ "PKG-HLS-06",
+ "PKG-DASH-05",
+ "PKG-DASH-06",
+ "PKG-MULTI-SUB-05",
+ "PKG-MULTI-SUB-06",
+ "PKG-JASSUB-05",
+ "PKG-JASSUB-06",
+ "PKG-MASK-05",
+ "PKG-MASK-06",
+ "PKG-ASR-05",
+ "PKG-ASR-06",
+ "PKG-ADS-05",
+ "PKG-ADS-06",
+ "PKG-VAST-03",
+ "PKG-VAST-04",
+ "PKG-VAST-05",
+ "PKG-VAST-06",
+ "PKG-CAST-05",
+ "PKG-CAST-06",
+ "PKG-DPIP-05",
+ "PKG-DPIP-06",
+ "PKG-DANMUKU-08",
+ "PKG-DANMUKU-09",
+ "PKG-CANVAS-05",
+ "PKG-CANVAS-06",
+ "PKG-MB-09",
+ "PKG-MB-10",
+ "PKG-IFRAME-05",
+ "PKG-IFRAME-06",
+ "PKG-TOOL-THUMB-04",
+ "PKG-TOOL-THUMB-05",
+ "PKG-TOOL-THUMB-06",
+ "SITE-04",
+ "SITE-05",
+ "SITE-06",
+ "SITE-07",
+ "REL-01",
+ "REL-09",
+ "REL-02",
+ "REL-03",
+ "REL-04",
+ "REVIEW-01",
+ "REVIEW-02",
+ "REVIEW-03",
+ "PKG-JASSUB-09"
+ ],
+ "openRisks": [
+ "ENG-PERF-01",
+ "ENG-PERF-02",
+ "BASE-DEMO-01",
+ "BASE-DIST-01",
+ "BASE-SOURCE-01",
+ "BASE-SOURCE-02",
+ "BASE-SITE-01",
+ "BASE-MEDIA-01",
+ "VENDOR-04",
+ "VENDOR-05",
+ "VENDOR-06",
+ "VENDOR-07",
+ "VENDOR-08",
+ "SDK-01",
+ "SDK-02",
+ "SDK-03",
+ "SDK-04",
+ "SDK-05",
+ "SDK-06",
+ "SDK-07",
+ "SDK-08",
+ "SDK-09",
+ "SDK-10",
+ "SDK-11",
+ "BASE-ENV-01",
+ "HLS-ENV-01",
+ "HLS-CRASH-01",
+ "HLS-PLAYBACK-01",
+ "AUDIO-SYNC-01",
+ "AUDIO-DEMO-01",
+ "AUDIO-BUFFER-01",
+ "CHAPTER-TIMING-01",
+ "DASH-SDK-01",
+ "DASH-DEMO-01",
+ "DASH-SEEK-01",
+ "ADS-DIST-01",
+ "ADS-MEDIA-01",
+ "VAST-CONTEXT-01",
+ "VAST-TYPE-01",
+ "VAST-LIFE-01",
+ "VAST-DIST-01",
+ "AMBILIGHT-LIFE-01",
+ "DPIP-PACK-01",
+ "DPIP-LIFE-01",
+ "DPIP-DOM-01",
+ "DPIP-MEDIA-01",
+ "MB-LICENSE-01",
+ "MB-LIFE-01",
+ "MB-CAP-01",
+ "IFRAME-LIFE-01",
+ "IFRAME-TRUST-01",
+ "IFRAME-DIST-01",
+ "THUMB-COMPAT-01",
+ "THUMB-LIFE-01",
+ "THUMB-MEDIA-01",
+ "AUTO-THUMB-TYPE-01",
+ "AUTO-THUMB-EXPORT-01",
+ "AUTO-THUMB-DIST-01",
+ "AUTO-THUMB-LIFE-01",
+ "AUTO-THUMB-ENCODE-01",
+ "AUTO-THUMB-PIXEL-01",
+ "VTT-THUMB-DIST-01",
+ "VTT-THUMB-LIFE-01",
+ "VTT-THUMB-EXPORT-01",
+ "MULTI-SUB-MERGE-01",
+ "MULTI-SUB-LIFE-01",
+ "MULTI-SUB-EXPORT-01",
+ "JASSUB-TYPE-01",
+ "JASSUB-EXPORT-01",
+ "ASR-CORS-01",
+ "MASK-LIFETIME-01",
+ "MASK-MODEL-01",
+ "MASK-BACKEND-01",
+ "MASK-DOM-01",
+ "MASK-NOTICE-01",
+ "DANMUKU-SOURCE-01",
+ "MASK-SCHEDULING-01",
+ "JASSUB-HYBRID-01",
+ "JASSUB-FIREFOX-OFFSCREEN-01",
+ "VTT-CORE-NAME-01",
+ "MULTI-SUB-SWITCH-01"
+ ],
+ "requiredAssetReviews": [
+ "screenfull",
+ "hint.css",
+ "webvtt-parser",
+ "jassub-code-and-workers",
+ "jassub-font-assets",
+ "monaco-static-assets",
+ "vconsole",
+ "console-bundle",
+ "thumbnail-tiny-emitter",
+ "chromecast-cast-icon",
+ "hls.js",
+ "dash.js",
+ "flv.js",
+ "mpegts.js",
+ "webtorrent",
+ "google-cast",
+ "ima-via-glomex",
+ "mediapipe-tensorflow",
+ "jassub-worker-wasm-fonts",
+ "mediabunny",
+ "asr-caller-service",
+ "option-validator",
+ "dependency:artplayer:option-validator",
+ "dependency:artplayer-plugin-danmuku-mask:@mediapipe/selfie_segmentation",
+ "dependency:artplayer-plugin-danmuku-mask:@tensorflow-models/body-segmentation",
+ "dependency:artplayer-plugin-danmuku-mask:@tensorflow/tfjs-backend-cpu",
+ "dependency:artplayer-plugin-danmuku-mask:@tensorflow/tfjs-backend-webgl",
+ "dependency:artplayer-plugin-danmuku-mask:@tensorflow/tfjs-converter",
+ "dependency:artplayer-plugin-danmuku-mask:@tensorflow/tfjs-core",
+ "dependency:artplayer-plugin-vast:@glomex/vast-ima-player",
+ "dependency:artplayer-plugin-vast:@alugha/ima",
+ "dependency:artplayer-proxy-mediabunny:mediabunny"
+ ],
+ "missingEvidence": [
+ "build",
+ "site-urls",
+ "site-assets",
+ "editor",
+ "devices",
+ "licenses",
+ "rollback",
+ "remote-ci",
+ "review-01",
+ "review-02",
+ "review-03"
+ ],
+ "blockerCount": 156
+ }
+ ]
+ },
+ "strictRejection": {
+ "log": {
+ "path": "refactor/.cache/release-ledger-strict-final.log",
+ "sha256": "87c08904c6a28cb348769ef186e702872e66cdcb50cd06451d91e44e14abee42"
+ },
+ "report": {
+ "path": "refactor/.cache/release-ledger-wHQYIr/report.json",
+ "sha256": "dd129d871f17798f673752eb900bb0fddaf6a64bd8e2e350de9e998b309d2a15"
+ },
+ "exitCode": 1,
+ "scope": "Actual HLS single-package preflight correctly rejects unprepared version, absent candidate and required evidence. This run predates formatting-only expansion of output object literals; its original input fingerprint is preserved."
+ },
+ "limitations": [
+ "Synthetic complete-control tests do not attest product behavior or devices",
+ "No candidate archive or release report is fabricated or promoted from historical validation",
+ "No npm/site deployment, remote workflow run or real device validation executed in this task",
+ "No package version, public API, dependency or lockfile change"
+ ]
+}
diff --git a/refactor/changes/2026-09-14-REL-08-release-ledger.md b/refactor/changes/2026-09-14-REL-08-release-ledger.md
new file mode 100644
index 000000000..e3b0907eb
--- /dev/null
+++ b/refactor/changes/2026-09-14-REL-08-release-ledger.md
@@ -0,0 +1,84 @@
+# REL-08: candidate-bound package release ledger
+
+The previous plan described batch release gates but had no executable per-package
+binding between current source, candidate archives and reviewed evidence. Added
+release-ledger.json for22 packages, a pure decision model, a filesystem/CLI adapter
+and a maintained procedure in release-ledger.md. This completes the ledger task,
+not package verification, version preparation, remote workflow activation or release.
+
+## Scope and ownership
+
+The JSON registry owns target versions, explicit historical distribution mappings,
+capability requirements, rollback basis and future candidate/evidence bindings. It
+does not duplicate task/risk status: the adapter reads tasks.json, risks.json,
+third-party.json and the existing dependency/impact model. Open package-specific
+risks propagate through dependencies; a generic review owner alone does not widen
+a concrete HLS finding to unrelated packages. Truly global risks stay global.
+
+Source fingerprints include own/dependent packages and shared scripts, tests,
+toolchain, contracts and frozen release/SDK inputs. Text CRLF is normalized with
+explicit algorithm labels and separate raw-byte hashes; binary fonts/WASM/media,
+candidate SRI and evidence attachments use exact bytes. A per-report input table
+avoids duplicating hashes across22 package rows. Unrelated package source changes
+can leave another batch unchanged; shared engineering/test/provenance changes are
+conservatively revalidated. Unknown dynamic imports widen the dependency scope.
+
+Actual tarball name/version, source commit existence, SRI and archive members are
+checked. Site manifests additionally require an exact build tree inventory and
+matching bytes. Evidence envelopes bind package/version/gate, candidate integrity,
+input fingerprint, actual environment, command, reviewer, successful check IDs and
+hashed underlying reports. Device requirements reject missing device identity,
+mock/emulation and skipped results. Remote CI requires a remote run reference;
+license coverage includes relevant vendor/SDK and direct runtime dependency IDs.
+
+This is mechanical bookkeeping, not a verifier of the truth of prose. Candidate
+build origin, complete scenario coverage, report interpretation and device/vendor
+evidence still require the three independent reviews. A source fingerprint must
+be captured in the actual build/validation snapshot, not copied onto an old package.
+No historical done task or old report is automatically accepted for a new candidate.
+
+## Distribution and compatibility
+
+All22 independent next-major targets are recorded without changing manifests. The
+iframe row explicitly maps its frozen artplayer-plugin-iframe predecessor; the
+recovered Thumbnail tool remains blocked for missing complete historical archive.
+VitePress stays a site/URL/build gate, without inferred new npm publication.
+Rollback links the historical basis and requires an actual rehearsal report.
+
+No core/plugin public API, runtime, declaration, dependency or lockfile changed.
+Four Yarn scripts expose structural check, immutable report output, strict
+preflight and tests. ci:check includes the structural check; test:baseline already
+discovers the new test. Strict preflight runs the existing pinned toolchain check.
+Normal check exit0 explicitly permits incomplete package evidence; only strict
+mode is a release gate. publicationAuthorized stays false even in the synthetic
+evidence-complete test. CI-03 already depends on REL-08 and will integrate strict
+preflight into the later publication workflow.
+
+## Validation
+
+See [recorded validation](../baselines/release-ledger-validation.json).
+
+- 30 regression cases pass. They reject stale source/test/SDK/lock inputs, wrong
+ version/package/gate, old tarball reports, tampered reports/attachments, missing
+ license/capability/review evidence, simulated devices and local-as-remote CI.
+ Real temporary archives/site files exercise SRI, manifest, complete output
+ inventory and byte checks; these are synthetic tooling fixtures, not product
+ package/browser/device acceptance.
+- Package source isolation, dependency propagation, unknown import fallback,
+ 22-package coverage and text/binary hashing are tested. Site cannot silently be
+ changed to npm scope or lose mandatory review gates.
+- CI script suite50 passes; targeted lint passes after ordinary formatting fixes.
+ Strict Node24.21.0/Yarn1.22.22 toolchain check passes with unchanged dependencies.
+- Current all-package report has22 blocked packages and zero candidate bindings.
+ The explicit HLS strict preflight exits1 for the expected missing gates; this is
+ correct rejection, not a failed tooling implementation. Generated reports use
+ separate cache directories and previous evidence remains intact.
+
+## Rollback and next work
+
+Revert this task commit to remove the new scripts, registry, tests and ci:check
+step. No package rebuild is necessary. Continue outstanding runtime/device/source
+tasks; REL-01/09 prepare final versions, REL-02 generates real candidates, REL-04
+executes rollback and CI-03/04 plus the reviews supply candidate-bound evidence.
+Do not fill pass envelopes from plans or rename existing historical report fields
+to manufacture a ready state.
diff --git a/refactor/ci-setup.md b/refactor/ci-setup.md
index 43a9ba8be..b5062f12d 100644
--- a/refactor/ci-setup.md
+++ b/refactor/ci-setup.md
@@ -8,6 +8,8 @@
| --- | --- |
| `yarn lint` | 只读 ESLint,覆盖包源码/声明、JS/MJS 工具、docs-smoke TS 模块、测试和编辑器声明 |
| `yarn lint:fix` | 显式自动修复相同范围 |
+| `yarn check:release-ledger` | ci:check中的逐包准入登记结构检查;当前blocked不导致结构检查失败,不是发布准入通过 |
+| `yarn release:preflight --packages ...` | 严格候选/证据/任务/设备/许可预检,任一缺口退出1;CI-03后续发布工作流使用此入口 |
| `yarn typecheck` | 根/迁移包严格检查、当前与兼容 TS 消费;历史 NodeNext ESM 错误单独核对,见 typechecking.md |
| `yarn typecheck:react` / `yarn typecheck:vue` | 原 React TSX / Vue SFC 示例严格检查,ci:check 同时执行对应 lint |
| `yarn typecheck:docs-tools` / `yarn check:docs-smoke` | 严格检查 TS 示例/声明生成器及 JS/MJS 门面;只读核对确定性生成的 readiness smoke,ci:check 执行 |
diff --git a/refactor/plan.md b/refactor/plan.md
index 4ad127867..450aa036d 100644
--- a/refactor/plan.md
+++ b/refactor/plan.md
@@ -4,7 +4,7 @@
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 253 项,范围 22 个包及工作区/示例。
-状态:todo 55 / doing 17 / blocked 0 / done 181 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
+状态:todo 54 / doing 17 / blocked 0 / done 182 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。
@@ -419,7 +419,7 @@
| ID | 范围 / 步骤 | 前置依赖 | 交付物 | 验收条件 | 风险 | 状态 |
| --- | --- | --- | --- | --- | --- | --- |
-| REL-08 | workspace
提前建立逐包发布准入台账 | BASE-08, ENG-07 | 每批包/版本/源码/锁文件/工具/tarball integrity、必需测试/设备证据、限制和回退映射 | 受影响能力缺证据明确阻止对应批次;无关批次可独立准备,旧证据在候选内容变化后失效;站点采用真实构建/URL/资源验收,npm 分发依据历史核实,不从版本清单推断新增发布范围;关联 risks.json 和 third-party.json;受影响 bundle/worker/WASM/font/模型的来源与许可通知缺口必须有审查结论,未决项阻止对应批次 | H | todo |
+| REL-08 | workspace
提前建立逐包发布准入台账 | BASE-08, ENG-07 | 每批包/版本/源码/锁文件/工具/tarball integrity、必需测试/设备证据、限制和回退映射 | 受影响能力缺证据明确阻止对应批次;无关批次可独立准备,旧证据在候选内容变化后失效;站点采用真实构建/URL/资源验收,npm 分发依据历史核实,不从版本清单推断新增发布范围;关联 risks.json 和 third-party.json;受影响 bundle/worker/WASM/font/模型的来源与许可通知缺口必须有审查结论,未决项阻止对应批次 | H | done |
| REL-01 | workspace
提前确定分包版本与差异方案 | REL-08, CORE-21, SITE-03, PKG-CHAPTER-04, PKG-AMBILIGHT-04, PKG-AUDIO-04, PKG-AUTO-THUMB-04, PKG-VTT-THUMB-04, PKG-HLS-04, PKG-DASH-04, PKG-MULTI-SUB-04, PKG-JASSUB-04, PKG-MASK-04, PKG-ASR-04, PKG-ADS-04, PKG-VAST-04, PKG-CAST-04, PKG-DPIP-04, PKG-CANVAS-04, PKG-IFRAME-04, PKG-TOOL-THUMB-04, PKG-DANMUKU-06, PKG-MB-08, PKG-FACTORY-01 | 每包版本/变更日志/依赖/类型差异方案和独立准入状态;按 version-policy.md 冻结各包下一 major(minor/patch 归零)并核实 registry 占用 | 所有包有方案和剩余门槛,未决项明确阻止相应发布;本步骤不声称已经可发布;全部包有 major 目标,版本冲突明确处理,独立准备任务同步 manifest/锁/依赖/日志;按 BASE-05 区分 21 库与文档站分发,不机械把文档站当库上传 npm | H | todo |
| REL-09 | workspace
落实全包下一 major 版本及依赖元数据 | REL-01, DOC-11 | 22 包版本目标落实、适用锁文件/依赖范围/示例/变更日志同步,核实 registry 版本占用 | 版本与 policy 一致,旧核心支持保留;必要拆子任务各自提交,候选构建前完成,不执行 publish | H | todo |
| REL-02 | workspace
生成候选 tarball 并验证新旧组合 | REL-01, CORE-22, ENG-07, REL-09 | 各候选本地 tarball/integrity 与新旧核心/插件消费者验证报告,外部门槛单独标注 | 隔离安装和可自动化组合通过;设备结论不伪造,最终发布绑定同一候选内容;在目标 major 版本确定后构建 pack,不在测试后改版本 | H | todo |
@@ -635,6 +635,7 @@
- MOD-PLUGIN-01: [记录](changes/2026-09-14-MOD-PLUGIN-01-scaffold.md) [记录](baselines/scaffold-validation.json)
- MOD-02: [记录](changes/2026-09-14-MOD-02-library-tooling.md) [记录](baselines/library-tooling-validation.json)
- MOD-DEV-01: [记录](changes/2026-09-14-MOD-DEV-01-dev-server.md) [记录](baselines/dev-server-validation.json)
+- REL-08: [记录](changes/2026-09-14-REL-08-release-ledger.md) [记录](baselines/release-ledger-validation.json) [记录](release-ledger.md) [记录](release-ledger.json)
- PKG-FACTORY-01: [记录](baselines/factory-assignment-gaps.json) [记录](baselines/factory-compatibility-proposals.json) [记录](factory-compatibility-decision.md) [记录](changes/2026-09-12-PKG-FACTORY-01-decision.md) [记录](type-compatibility-policy.md) [记录](baselines/factory-compatibility-validation.json) [记录](changes/2026-09-13-PKG-FACTORY-01-compatible-types.md)
- CORE-25: [记录](changes/2026-09-13-CORE-25-defaults-ssr.md) [记录](baselines/defaults-ssr-validation.json)
- ENG-12: [记录](changes/2026-09-13-ENG-12-library-public.md) [记录](baselines/library-public-validation.json)
diff --git a/refactor/progress.md b/refactor/progress.md
index 4c6c443b7..a02495f61 100644
--- a/refactor/progress.md
+++ b/refactor/progress.md
@@ -1,5 +1,19 @@
# 进度与证据
+## REL-08 逐包发布准入台账完成
+
+建立22包历史分发、独立major目标、能力/回退和候选证据登记。新增纯模型及只读
+文件/归档CLI,按实际依赖和共享输入计算指纹,核验tarball/站点完整输出及底层报告
+字节。旧done和历史报告不自动变成当前候选证据;设备替身、许可缺口、未决风险、
+缺失远端CI或三轮复盘都会阻止相应批次。站点不隐含新增npm发布,改名iframe和
+旧tarball缺失的Thumbnail工具明确区分。30项反向/文件回归、CI脚本50项、定向lint
+通过;HLS单包strict预检按预期退出1,当前22包都blocked且无候选绑定。
+见[维护说明](release-ledger.md)、[变更](changes/2026-09-14-REL-08-release-ledger.md)和[证据](baselines/release-ledger-validation.json)。
+已加入ci:check结构检查,test:baseline自动发现测试,strict发布预检与普通检查分开。
+253项:182 done、17 doing、54 todo。没有改包版本/依赖/公开API,也没有发布。
+下一步回到包的剩余源码与真实环境验收;REL-01/09/02、REL-04及CI-03可按台账逐步
+补真实候选、回退和流程证据,不能用当前历史报告填造pass。VAST选择仍待回复。
+
## PKG-HLS-SDK-01 普通HTTP销毁诊断检查点(仍未完成)
直接原生video、Hls1.5.17、Firefox155.0、普通HTTP且无ArtPlayer/插件/Worker观察器,
diff --git a/refactor/release-ledger.json b/refactor/release-ledger.json
new file mode 100644
index 000000000..496dd688c
--- /dev/null
+++ b/refactor/release-ledger.json
@@ -0,0 +1,509 @@
+{
+ "schemaVersion": 1,
+ "libraryGates": [
+ "build",
+ "runtime",
+ "types",
+ "combinations",
+ "browser",
+ "devices",
+ "licenses",
+ "rollback",
+ "remote-ci",
+ "review-01",
+ "review-02",
+ "review-03"
+ ],
+ "siteGates": [
+ "build",
+ "site-urls",
+ "site-assets",
+ "editor",
+ "devices",
+ "licenses",
+ "rollback",
+ "remote-ci",
+ "review-01",
+ "review-02",
+ "review-03"
+ ],
+ "sharedTasks": [
+ "CI-01",
+ "CI-03",
+ "CI-04",
+ "REL-01",
+ "REL-09",
+ "REL-02",
+ "REL-03",
+ "REL-04",
+ "REVIEW-01",
+ "REVIEW-02",
+ "REVIEW-03"
+ ],
+ "packages": [
+ {
+ "name": "artplayer",
+ "targetVersion": "6.0.0",
+ "distribution": "npm",
+ "history": {
+ "file": "refactor/baselines/releases.json",
+ "selector": "releases.0",
+ "rationale": "Frozen npm tarball provenance; not a new registry availability observation."
+ },
+ "extraRequirements": [
+ "apple-safari-device",
+ "android-chrome-device",
+ "apple-safari-media",
+ "android-media",
+ "native-fullscreen-pip"
+ ],
+ "rollback": {
+ "basis": "refactor/baselines/releases.json",
+ "strategy": "Keep the frozen previous package integrity and tested consumer lock; publish a forward fix without overwriting npm versions. REL-04 rehearsal must cover any package rename."
+ },
+ "candidate": null,
+ "evidence": {}
+ },
+ {
+ "name": "artplayer-plugin-ads",
+ "targetVersion": "3.0.0",
+ "distribution": "npm",
+ "history": {
+ "file": "refactor/baselines/ads-release.json",
+ "selector": "release",
+ "rationale": "Frozen npm tarball provenance; not a new registry availability observation."
+ },
+ "extraRequirements": [
+ "apple-safari-device",
+ "android-chrome-device",
+ "ad-skip-content-restore"
+ ],
+ "rollback": {
+ "basis": "refactor/baselines/ads-release.json",
+ "strategy": "Keep the frozen previous package integrity and tested consumer lock; publish a forward fix without overwriting npm versions. REL-04 rehearsal must cover any package rename."
+ },
+ "candidate": null,
+ "evidence": {}
+ },
+ {
+ "name": "artplayer-plugin-ambilight",
+ "targetVersion": "2.0.0",
+ "distribution": "npm",
+ "history": {
+ "file": "refactor/baselines/ambilight-release.json",
+ "selector": "release",
+ "rationale": "Frozen npm tarball provenance; not a new registry availability observation."
+ },
+ "extraRequirements": [
+ "apple-safari-device",
+ "android-chrome-device",
+ "canvas-cors-background"
+ ],
+ "rollback": {
+ "basis": "refactor/baselines/ambilight-release.json",
+ "strategy": "Keep the frozen previous package integrity and tested consumer lock; publish a forward fix without overwriting npm versions. REL-04 rehearsal must cover any package rename."
+ },
+ "candidate": null,
+ "evidence": {}
+ },
+ {
+ "name": "artplayer-plugin-asr",
+ "targetVersion": "3.0.0",
+ "distribution": "npm",
+ "history": {
+ "file": "refactor/baselines/asr-release.json",
+ "selector": "release",
+ "rationale": "Frozen npm tarball provenance; not a new registry availability observation."
+ },
+ "extraRequirements": [
+ "apple-safari-device",
+ "android-chrome-device",
+ "audio-context-input-lifecycle"
+ ],
+ "rollback": {
+ "basis": "refactor/baselines/asr-release.json",
+ "strategy": "Keep the frozen previous package integrity and tested consumer lock; publish a forward fix without overwriting npm versions. REL-04 rehearsal must cover any package rename."
+ },
+ "candidate": null,
+ "evidence": {}
+ },
+ {
+ "name": "artplayer-plugin-audio-track",
+ "targetVersion": "2.0.0",
+ "distribution": "npm",
+ "history": {
+ "file": "refactor/baselines/audio-track-release.json",
+ "selector": "release",
+ "rationale": "Frozen npm tarball provenance; not a new registry availability observation."
+ },
+ "extraRequirements": [
+ "apple-safari-device",
+ "android-chrome-device",
+ "separate-audio-source-switch"
+ ],
+ "rollback": {
+ "basis": "refactor/baselines/audio-track-release.json",
+ "strategy": "Keep the frozen previous package integrity and tested consumer lock; publish a forward fix without overwriting npm versions. REL-04 rehearsal must cover any package rename."
+ },
+ "candidate": null,
+ "evidence": {}
+ },
+ {
+ "name": "artplayer-plugin-auto-thumbnail",
+ "targetVersion": "2.0.0",
+ "distribution": "npm",
+ "history": {
+ "file": "refactor/baselines/auto-thumbnail-release.json",
+ "selector": "release",
+ "rationale": "Frozen npm tarball provenance; not a new registry availability observation."
+ },
+ "extraRequirements": [
+ "apple-safari-device",
+ "android-chrome-device",
+ "native-first-frame-jpeg"
+ ],
+ "rollback": {
+ "basis": "refactor/baselines/auto-thumbnail-release.json",
+ "strategy": "Keep the frozen previous package integrity and tested consumer lock; publish a forward fix without overwriting npm versions. REL-04 rehearsal must cover any package rename."
+ },
+ "candidate": null,
+ "evidence": {}
+ },
+ {
+ "name": "artplayer-plugin-chapter",
+ "targetVersion": "2.0.0",
+ "distribution": "npm",
+ "history": {
+ "file": "refactor/baselines/releases.json",
+ "selector": "releases.1",
+ "rationale": "Frozen npm tarball provenance; not a new registry availability observation."
+ },
+ "extraRequirements": [
+ "apple-safari-device",
+ "android-chrome-device",
+ "touch-fullscreen-thumbnails"
+ ],
+ "rollback": {
+ "basis": "refactor/baselines/releases.json",
+ "strategy": "Keep the frozen previous package integrity and tested consumer lock; publish a forward fix without overwriting npm versions. REL-04 rehearsal must cover any package rename."
+ },
+ "candidate": null,
+ "evidence": {}
+ },
+ {
+ "name": "artplayer-plugin-chromecast",
+ "targetVersion": "2.0.0",
+ "distribution": "npm",
+ "history": {
+ "file": "refactor/baselines/chromecast-release.json",
+ "selector": "release",
+ "rationale": "Frozen npm tarball provenance; not a new registry availability observation."
+ },
+ "extraRequirements": [
+ "apple-safari-device",
+ "android-chrome-device",
+ "cast-receiver-session"
+ ],
+ "rollback": {
+ "basis": "refactor/baselines/chromecast-release.json",
+ "strategy": "Keep the frozen previous package integrity and tested consumer lock; publish a forward fix without overwriting npm versions. REL-04 rehearsal must cover any package rename."
+ },
+ "candidate": null,
+ "evidence": {}
+ },
+ {
+ "name": "artplayer-plugin-danmuku",
+ "targetVersion": "6.0.0",
+ "distribution": "npm",
+ "history": {
+ "file": "refactor/baselines/danmuku-release.json",
+ "selector": "release",
+ "rationale": "Frozen npm tarball provenance; not a new registry availability observation."
+ },
+ "extraRequirements": [
+ "apple-safari-device",
+ "android-chrome-device",
+ "mask-fullscreen-pip"
+ ],
+ "rollback": {
+ "basis": "refactor/baselines/danmuku-release.json",
+ "strategy": "Keep the frozen previous package integrity and tested consumer lock; publish a forward fix without overwriting npm versions. REL-04 rehearsal must cover any package rename."
+ },
+ "candidate": null,
+ "evidence": {}
+ },
+ {
+ "name": "artplayer-plugin-danmuku-mask",
+ "targetVersion": "2.0.0",
+ "distribution": "npm",
+ "history": {
+ "file": "refactor/baselines/danmuku-mask-release.json",
+ "selector": "release",
+ "rationale": "Frozen npm tarball provenance; not a new registry availability observation."
+ },
+ "extraRequirements": [
+ "apple-safari-device",
+ "android-chrome-device",
+ "real-model-gpu-cleanup"
+ ],
+ "rollback": {
+ "basis": "refactor/baselines/danmuku-mask-release.json",
+ "strategy": "Keep the frozen previous package integrity and tested consumer lock; publish a forward fix without overwriting npm versions. REL-04 rehearsal must cover any package rename."
+ },
+ "candidate": null,
+ "evidence": {}
+ },
+ {
+ "name": "artplayer-plugin-dash-control",
+ "targetVersion": "2.0.0",
+ "distribution": "npm",
+ "history": {
+ "file": "refactor/baselines/dash-control-release.json",
+ "selector": "release",
+ "rationale": "Frozen npm tarball provenance; not a new registry availability observation."
+ },
+ "extraRequirements": [
+ "apple-safari-device",
+ "android-chrome-device",
+ "real-dash-sdk-tracks"
+ ],
+ "rollback": {
+ "basis": "refactor/baselines/dash-control-release.json",
+ "strategy": "Keep the frozen previous package integrity and tested consumer lock; publish a forward fix without overwriting npm versions. REL-04 rehearsal must cover any package rename."
+ },
+ "candidate": null,
+ "evidence": {}
+ },
+ {
+ "name": "artplayer-plugin-document-pip",
+ "targetVersion": "2.0.0",
+ "distribution": "npm",
+ "history": {
+ "file": "refactor/baselines/dpip-release.json",
+ "selector": "release",
+ "rationale": "Frozen npm tarball provenance; not a new registry availability observation."
+ },
+ "extraRequirements": [
+ "apple-safari-device",
+ "android-chrome-device",
+ "document-pip-native-window"
+ ],
+ "rollback": {
+ "basis": "refactor/baselines/dpip-release.json",
+ "strategy": "Keep the frozen previous package integrity and tested consumer lock; publish a forward fix without overwriting npm versions. REL-04 rehearsal must cover any package rename."
+ },
+ "candidate": null,
+ "evidence": {}
+ },
+ {
+ "name": "artplayer-plugin-hls-control",
+ "targetVersion": "2.0.0",
+ "distribution": "npm",
+ "history": {
+ "file": "refactor/baselines/hls-control-release.json",
+ "selector": "release",
+ "rationale": "Frozen npm tarball provenance; not a new registry availability observation."
+ },
+ "extraRequirements": [
+ "apple-safari-device",
+ "android-chrome-device",
+ "native-hls-and-worker"
+ ],
+ "rollback": {
+ "basis": "refactor/baselines/hls-control-release.json",
+ "strategy": "Keep the frozen previous package integrity and tested consumer lock; publish a forward fix without overwriting npm versions. REL-04 rehearsal must cover any package rename."
+ },
+ "candidate": null,
+ "evidence": {}
+ },
+ {
+ "name": "artplayer-plugin-jassub",
+ "targetVersion": "2.0.0",
+ "distribution": "npm",
+ "history": {
+ "file": "refactor/baselines/jassub-release.json",
+ "selector": "release",
+ "rationale": "Frozen npm tarball provenance; not a new registry availability observation."
+ },
+ "extraRequirements": [
+ "apple-safari-device",
+ "android-chrome-device",
+ "real-worker-wasm-hybrid"
+ ],
+ "rollback": {
+ "basis": "refactor/baselines/jassub-release.json",
+ "strategy": "Keep the frozen previous package integrity and tested consumer lock; publish a forward fix without overwriting npm versions. REL-04 rehearsal must cover any package rename."
+ },
+ "candidate": null,
+ "evidence": {}
+ },
+ {
+ "name": "artplayer-plugin-multiple-subtitles",
+ "targetVersion": "2.0.0",
+ "distribution": "npm",
+ "history": {
+ "file": "refactor/baselines/multiple-subtitles-release.json",
+ "selector": "release",
+ "rationale": "Frozen npm tarball provenance; not a new registry availability observation."
+ },
+ "extraRequirements": [
+ "apple-safari-device",
+ "android-chrome-device",
+ "subtitle-offset-fullscreen"
+ ],
+ "rollback": {
+ "basis": "refactor/baselines/multiple-subtitles-release.json",
+ "strategy": "Keep the frozen previous package integrity and tested consumer lock; publish a forward fix without overwriting npm versions. REL-04 rehearsal must cover any package rename."
+ },
+ "candidate": null,
+ "evidence": {}
+ },
+ {
+ "name": "artplayer-plugin-vast",
+ "targetVersion": "2.0.0",
+ "distribution": "npm",
+ "history": {
+ "file": "refactor/baselines/vast-release.json",
+ "selector": "release",
+ "rationale": "Frozen npm tarball provenance; not a new registry availability observation."
+ },
+ "extraRequirements": [
+ "apple-safari-device",
+ "android-chrome-device",
+ "google-ima-ad-content-restore"
+ ],
+ "rollback": {
+ "basis": "refactor/baselines/vast-release.json",
+ "strategy": "Keep the frozen previous package integrity and tested consumer lock; publish a forward fix without overwriting npm versions. REL-04 rehearsal must cover any package rename."
+ },
+ "candidate": null,
+ "evidence": {}
+ },
+ {
+ "name": "artplayer-plugin-vtt-thumbnail",
+ "targetVersion": "2.0.0",
+ "distribution": "npm",
+ "history": {
+ "file": "refactor/baselines/vtt-thumbnail-release.json",
+ "selector": "release",
+ "rationale": "Frozen npm tarball provenance; not a new registry availability observation."
+ },
+ "extraRequirements": [
+ "apple-safari-device",
+ "android-chrome-device",
+ "touch-fullscreen-chapter"
+ ],
+ "rollback": {
+ "basis": "refactor/baselines/vtt-thumbnail-release.json",
+ "strategy": "Keep the frozen previous package integrity and tested consumer lock; publish a forward fix without overwriting npm versions. REL-04 rehearsal must cover any package rename."
+ },
+ "candidate": null,
+ "evidence": {}
+ },
+ {
+ "name": "artplayer-proxy-canvas",
+ "targetVersion": "2.0.0",
+ "distribution": "npm",
+ "history": {
+ "file": "refactor/baselines/canvas-release.json",
+ "selector": "release",
+ "rationale": "Frozen npm tarball provenance; not a new registry availability observation."
+ },
+ "extraRequirements": [
+ "apple-safari-device",
+ "android-chrome-device",
+ "canvas-subtitle-document-pip"
+ ],
+ "rollback": {
+ "basis": "refactor/baselines/canvas-release.json",
+ "strategy": "Keep the frozen previous package integrity and tested consumer lock; publish a forward fix without overwriting npm versions. REL-04 rehearsal must cover any package rename."
+ },
+ "candidate": null,
+ "evidence": {}
+ },
+ {
+ "name": "artplayer-proxy-mediabunny",
+ "targetVersion": "2.0.0",
+ "distribution": "npm",
+ "history": {
+ "file": "refactor/baselines/mb-release.json",
+ "selector": "release",
+ "rationale": "Frozen npm tarball provenance; not a new registry availability observation."
+ },
+ "extraRequirements": [
+ "apple-safari-device",
+ "android-chrome-device",
+ "webcodecs-hls-audio-video"
+ ],
+ "rollback": {
+ "basis": "refactor/baselines/mb-release.json",
+ "strategy": "Keep the frozen previous package integrity and tested consumer lock; publish a forward fix without overwriting npm versions. REL-04 rehearsal must cover any package rename."
+ },
+ "candidate": null,
+ "evidence": {}
+ },
+ {
+ "name": "artplayer-tool-iframe",
+ "targetVersion": "2.0.0",
+ "distribution": "renamed-npm",
+ "history": {
+ "file": "refactor/baselines/iframe-release.json",
+ "selector": "release",
+ "publishedName": "artplayer-plugin-iframe",
+ "rationale": "Frozen published predecessor and documented rename commit; candidate keeps current workspace name. Registry target availability still requires REL-01."
+ },
+ "extraRequirements": [
+ "apple-safari-device",
+ "android-chrome-device",
+ "cross-origin-bfcache-device"
+ ],
+ "rollback": {
+ "basis": "refactor/baselines/iframe-release.json",
+ "strategy": "Keep the frozen previous package integrity and tested consumer lock; publish a forward fix without overwriting npm versions. REL-04 rehearsal must cover any package rename."
+ },
+ "candidate": null,
+ "evidence": {}
+ },
+ {
+ "name": "artplayer-tool-thumbnail",
+ "targetVersion": "5.0.0",
+ "distribution": "recovered-npm",
+ "history": {
+ "file": "refactor/baselines/thumbnail-release.json",
+ "selector": "recovered",
+ "rationale": "Recovered CDN/Git bytes only; original registry tarball and complete archive unavailable. Not an intact rollback artifact."
+ },
+ "extraRequirements": [
+ "apple-safari-device",
+ "android-chrome-device",
+ "native-file-decode-encode"
+ ],
+ "rollback": {
+ "basis": "refactor/baselines/thumbnail-release.json",
+ "strategy": "Original tarball is unavailable. Resolve a complete rollback alternative and migration scope under REL-04 before release."
+ },
+ "candidate": null,
+ "evidence": {}
+ },
+ {
+ "name": "artplayer-vitepress",
+ "targetVersion": "2.0.0",
+ "distribution": "site",
+ "history": {
+ "file": "refactor/baselines/site-provenance.json",
+ "rationale": "Existing docs/site build and URL distribution. No npm publication is inferred."
+ },
+ "extraRequirements": [
+ "apple-safari-device",
+ "android-chrome-device",
+ "mobile-editor-and-site"
+ ],
+ "rollback": {
+ "basis": "refactor/baselines/site-provenance.json",
+ "strategy": "Restore the previously verified Pages artifact and old URL map; rehearsal evidence required."
+ },
+ "candidate": null,
+ "evidence": {}
+ }
+ ]
+}
diff --git a/refactor/release-ledger.md b/refactor/release-ledger.md
new file mode 100644
index 000000000..6c6196a8a
--- /dev/null
+++ b/refactor/release-ledger.md
@@ -0,0 +1,132 @@
+# 逐包发布准入台账
+
+REL-08 建立台账及机械校验,不代表任何包已经可以发布。唯一登记源是
+[release-ledger.json](release-ledger.json),覆盖全部22个workspace包。任务状态仍由
+[tasks.json](tasks.json)维护,问题和来源继续使用[风险](risks.json)及
+[第三方清单](third-party.json)。不要再创建一份手工维护的“已通过”状态表。
+
+## 命令与退出语义
+
+使用固定Node/Yarn,所有命令只读生产文件,不安装、构建、部署或发布:
+
+| 命令 | 行为 |
+| --- | --- |
+| `yarn check:release-ledger` | 验证登记结构并计算当前缺口;合法台账即退出0,包仍可显示blocked;已加入ci:check |
+| `yarn report:release-ledger` | 同样计算,并在新的`.cache/release-ledger-*/report.json`保存报告,不覆盖旧报告 |
+| `yarn release:preflight --packages artplayer,artplayer-plugin-chapter` | 严格工具链后检查明确批次;任一包缺证据或不匹配即退出1;遗漏参数检查全部包 |
+| `yarn test:release-ledger` | 反向回归,包括过期/错误候选、设备替身、缺失许可和站点输出漂移;同时由test:baseline发现 |
+
+空选择、未知包、重复包会拒绝执行,不能意外通过空批次。普通结构检查通过和严格
+准入通过含义不同,CI-03必须使用strict入口,不能使用check的退出0代替发布判断。
+即使全部机械条件满足,状态也只叫`evidence-complete`,`publicationAuthorized`
+恒为false。三轮复盘仍需实读底层证据;本工具不能判断一份人为编造的报告是否真实,
+也不能自动取得设备、执行测试或授权npm发布。
+
+## 分发和版本边界
+
+每行明确记录独立下一major目标,初始依据是package-inventory.json。正式版本占用、
+预发布标识和变更日志仍由REL-01/09落实;本步不修改任何包版本。
+
+- 普通npm包使用各自冻结发布记录的name/version/integrity/SHA-256/URL作为历史依据。
+ core和chapter引用releases.json各自成员;不把工作区版本当作npm版本。
+- iframe明确关联旧发布名`artplayer-plugin-iframe`和改名记录。新候选仍核验现有
+ workspace名`artplayer-tool-iframe`;改名范围、目标可用性和回退演练不能省略。
+- Thumbnail工具只有恢复的CDN/Git内容,旧tarball和完整归档缺失。历史分发门槛保持
+ blocked,不能将缺失CSS/归档解释为从未发布;REL-04需要完整、已验证的回退方案。
+- artplayer-vitepress按站点构建、URL、编辑器、资源和设备验收。列入版本策略不授权
+ 新增npm发布。站点候选是构建文件清单,不能绑定一个npm tarball冒充站点验收。
+
+历史依据映射不联网刷新,也不代替现有冻结归档验证器、registry检查或回退演练。
+每包都有rollback.basis和strategy;严格准入另要求候选绑定的rollback执行报告。
+
+## 候选绑定
+
+所有初始candidate均为null,evidence为空。先完成目标版本准备和对应实际构建/pack,
+在构建快照中计算输入指纹,再登记真实文件;不能只复制当前指纹到旧产物冒充重建。
+build报告和REVIEW-03负责审查构建来源及实际内容对应关系。
+
+候选登记字段:
+
+```text
+candidate.kind = npm-tarball | site-manifest
+candidate.path = 仓库内真实候选文件路径
+candidate.version = 已准备的目标版本
+candidate.sourceCommit = 实际构建来源的40位Git提交
+candidate.inputFingerprint = 构建/验证输入指纹
+candidate.integrity = 候选文件实际sha512-base64 SRI
+```
+
+npm候选会读取真实tarball并检查路径、重复成员、package.json名称和版本。站点
+site-manifest文件包含package、version、outputRoot和files,每个file为path/sha256。
+outputRoot必须在仓库内;目录完整文件集须等于files,新增、缺失、替换或符号链接输出
+均拒绝。清单SRI和每个实际部署文件字节分别验证;URL正确性仍必须有site-urls报告。
+
+`inputFingerprint`包含本包、其实际依赖闭包,以及共享构建/工具链/测试/CI输入。
+依赖来自impact-model的显式关系和源码/声明/manifest扫描;未解释动态导入按全部包
+保守处理。站点还包含docs及example。第三方清单、本校验器、兼容/环境/版本/复盘
+契约以及固定release/SDK/历史core配置也参与输入指纹;不断新增的普通validation
+报告不作为构建输入,避免报告包含自己而无法完成绑定。
+
+文本按明确扩展名和已知文本文件归一CRLF为LF,使Windows/Linux文本签出可以比较;
+报告同时保留原始字节SHA-256。字体/WASM/媒体等其他输入不作文本转换,候选SRI、
+站点实际输出、报告及其底层附件始终按原始字节校验。指纹不是单独的HEAD比较:
+未提交修改也进入计算,无关包源码的变化不使其他依赖闭包失效。共享测试或工程输入
+变化则保守地使所有相关候选失效,这是有意避免漏验;不用于省略现有CI检查。
+
+报告包含完整sourceCommit、锁文件哈希、实际Node、配置packageManager、实际Yarn
+user-agent、声明工具版本和输入文件表。声明工具版本不是自动证明所有已安装依赖
+正确;strict命令先运行现有check:toolchain,并要求真正通过Yarn1.22.22和固定Node执行。
+
+## 必需报告
+
+库的必需门槛:build/runtime/types/combinations/browser/devices/licenses/rollback/
+remote-ci/review-01/review-02/review-03。站点用site-urls/site-assets/editor替换库接口
+门槛;三轮复盘、真实设备、许可和回退仍然必需。
+
+每个`evidence.`只登记`{ path, sha256 }`,指向审阅后的标准JSON封套。不能
+直接把任意历史validation.json挂入。封套必须包含:
+
+| 字段 | 约束 |
+| --- | --- |
+| schemaVersion | 1 |
+| package/version/gate | 与被验收包、当前版本和门槛精确匹配 |
+| candidateIntegrity/inputFingerprint | 与实际候选及当前输入一致 |
+| result | pass;未知、失败和skip不能转为通过 |
+| checks | 非空`{ id, result, mode }`列表,全部必要项pass;不能通过删除原失败项缩小范围 |
+| environment | 实际OS/浏览器/执行环境;设备项须有device、os、browser、emulated:false |
+| command/reviewedBy | 实际执行命令及审阅归属,不填计划命令或假身份 |
+| artifacts | 非空底层结果`{ path, sha256 }`列表,读取文件验证实际字节 |
+
+devices还必须逐项覆盖该包extraRequirements,所有模式为native;viewport或mock不
+能冒充设备/能力通过。remote-ci须有`remote:true`和实际GitHub Actions run URL,
+且仍需审阅远端结果附件。URL本身不证明远端成功,本工具不会联网读取它。
+
+licenses须覆盖所有适用vendor、SDK/外部集成及直接运行依赖ID,例如
+`dependency:artplayer:option-validator`。哈希只能说明来源字节,不能证明授权;
+来源/完整通知和实际bundle、worker、WASM、字体、模型分发的结论必须在附件中审查。
+
+三轮报告不能复用同一封套充数,因为gate字段必须精确匹配。旧任务done或旧报告存在
+不自动提供候选证据:历史任务索引会显示,但historicalEvidenceAcceptedForCandidate
+恒为false。候选内容、源码、锁文件、共享测试或工程输入变化后,相关封套会失效。
+
+## 任务、风险和批次范围
+
+每包需关闭其依赖闭包的实施/组合/分发任务和共享准备、远端CI、回退及三轮复盘。
+风险先按具体包责任定位,只有没有具体包责任的全局风险才扩展全包;例如HLS问题
+同时由REVIEW-02负责,不会仅因此直接阻止无关Cast包的局部准备。核心问题通过依赖
+闭包影响其消费者。第三方资产使用同样范围映射,未决风险阻止相应批次。
+
+当前共享三轮完整复盘尚未完成,所以任何包都没有发布就绪结论。逐包可独立准备
+候选和收集材料,不能借分批删除项目整体要求。未来如正式拆分独立批次复盘,须按
+release-reviews.md新建任务和明确范围,并更新本台账模型及反向测试,不能删共享门槛。
+
+## 维护地图
+
+- `scripts/release-ledger-model.mjs`:纯门槛、范围、依赖闭包和阻断计算。
+- `scripts/release-ledger.mjs`:只读文件/归档/输入指纹,装配当前报告和CLI。
+- `scripts/release-ledger.test.mjs`:合成合格控制与逐项失败反例,真实临时tar/site文件
+ 验证;这些测试不算任何产品包的设备或发布验收。
+- `release-ledger.json`:历史分发、目标、能力、回退及候选/证据绑定,初始无候选。
+
+更改门槛先核对兼容、环境、版本和复盘契约;不要把当前红灯改成绿色来证明工具有效。
+ci:check只做结构校验,strict红灯是当前产品验收尚未完成的正确结果。
diff --git a/refactor/release-reviews.md b/refactor/release-reviews.md
index 5f3e849c7..cf557200d 100644
--- a/refactor/release-reviews.md
+++ b/refactor/release-reviews.md
@@ -57,4 +57,9 @@ REVIEW-01 -> REVIEW-02 -> REVIEW-03 -> REL-05(经授权候选发布)-> REL-0
用户在复盘后审阅具体发布批次:包/版本、目标 registry/tag、兼容结论、候选 integrity、剩余非阻断项和回退方法。表达将来发布到 npm 的目标不等同当前执行 publish;可以自主完成本地准备与复盘。
+REL-08的[逐包台账](release-ledger.md)负责候选和证据的机械绑定。CI-03及REVIEW-03应对
+具体批次运行`yarn release:preflight --packages ...`,缺口会退出1;普通
+`check:release-ledger`的结构检查通过不能代替它。封套和实际附件仍须逐项审阅,
+脚本不会验证报告叙述真实性、执行设备测试或授权发布。
+
候选反馈导致修复时,先创建修复和复验任务,重新核对前三轮受影响结论及最终候选内容,再进入正式发布。即使前三轮任务历史已 done,发生变化后也不能直接发布。分批交付遵循 execution-gates.md,每批具备同等三轮审查和必要环境证据,不以分批绕过全项目要求。
diff --git a/refactor/scripts/release-ledger-model.mjs b/refactor/scripts/release-ledger-model.mjs
new file mode 100644
index 000000000..67250c432
--- /dev/null
+++ b/refactor/scripts/release-ledger-model.mjs
@@ -0,0 +1,114 @@
+import assert from 'node:assert/strict'
+
+export const libraryGates = ['build', 'runtime', 'types', 'combinations', 'browser', 'devices', 'licenses', 'rollback', 'remote-ci', 'review-01', 'review-02', 'review-03']
+export const siteGates = ['build', 'site-urls', 'site-assets', 'editor', 'devices', 'licenses', 'rollback', 'remote-ci', 'review-01', 'review-02', 'review-03']
+export const sharedTasks = ['CI-01', 'CI-03', 'CI-04', 'REL-01', 'REL-09', 'REL-02', 'REL-03', 'REL-04', 'REVIEW-01', 'REVIEW-02', 'REVIEW-03']
+
+export function dependencyClosure(name, names, edges, dynamicImports = []) {
+ if (dynamicImports.length)
+ return [...names].sort()
+ const dependencies = new Set([name])
+ let count = -1
+ while (count !== dependencies.size) {
+ count = dependencies.size
+ for (const edge of edges) {
+ if (dependencies.has(edge.consumer))
+ dependencies.add(edge.dependency)
+ }
+ }
+ return [...dependencies].sort()
+}
+
+export function findingPackages(item, tasks, names) {
+ const explicit = new Set(item.owners.flatMap(id => tasks.get(id)?.scope || []).filter(scope => names.includes(scope)))
+ // A generic review owner must not turn a package-specific finding into a global one.
+ return explicit.size ? [...explicit] : [...names]
+}
+
+export function validateLedger(ledger, inventory, tasks) {
+ assert.equal(ledger.schemaVersion, 1)
+ const expected = inventory.map(pkg => pkg.name).sort()
+ assert.deepEqual(ledger.packages.map(pkg => pkg.name).sort(), expected, 'Ledger must cover exactly every workspace package once')
+ assert.deepEqual(ledger.libraryGates, libraryGates, 'Missing mandatory library gate')
+ assert.deepEqual(ledger.siteGates, siteGates, 'Missing mandatory site gate')
+ assert.deepEqual(ledger.sharedTasks, sharedTasks, 'Missing mandatory shared release/review task')
+ for (const row of ledger.packages) {
+ const initial = inventory.find(pkg => pkg.name === row.name)
+ assert.equal(row.targetVersion, `${Number(initial.version.split('.')[0]) + 1}.0.0`, `Unexpected next major: ${row.name}`)
+ assert(['npm', 'renamed-npm', 'recovered-npm', 'site'].includes(row.distribution), `Unknown distribution: ${row.name}`)
+ assert.equal(row.distribution === 'site', initial.kind === 'docs', 'A site must not become a new npm publication implicitly')
+ assert(row.history?.file && row.history.rationale, `Missing distribution basis: ${row.name}`)
+ assert(row.rollback?.basis && row.rollback.strategy, `Missing rollback mapping: ${row.name}`)
+ assert(Array.isArray(row.extraRequirements) && row.extraRequirements.length, `Missing capability requirements: ${row.name}`)
+ assert(row.extraRequirements.every(item => typeof item === 'string' && item.trim()), 'Invalid capability requirement')
+ assert(new Set(row.extraRequirements).size === row.extraRequirements.length, 'Duplicate requirement')
+ assert(row.evidence && !Array.isArray(row.evidence) && typeof row.evidence === 'object')
+ assert(Object.keys(row.evidence).every(gate => (row.distribution === 'site' ? siteGates : libraryGates).includes(gate)), 'Unknown evidence gate')
+ assert(row.candidate === null || typeof row.candidate === 'object', 'Invalid candidate')
+ }
+ for (const id of ledger.sharedTasks)
+ assert(tasks.has(id), `Unknown release task: ${id}`)
+}
+
+export function evaluatePackage({ row, fingerprint, version, candidate, evidence, taskGaps, risks, assets, history }) {
+ const gates = row.distribution === 'site' ? siteGates : libraryGates
+ const blockers = []
+ const add = (kind, detail) => blockers.push({ kind, detail })
+ if (version !== row.targetVersion)
+ add('version', `${version} must be prepared as ${row.targetVersion} before final candidate validation`)
+ if (!history.verified)
+ add('distribution-history', history.reason)
+ if (!candidate) {
+ add('candidate', 'No candidate artifact is bound')
+ }
+ else {
+ for (const error of candidate.errors)
+ add('candidate', error)
+ if (candidate.inputFingerprint !== fingerprint)
+ add('stale-candidate', 'Source, dependencies, tooling or test inputs changed')
+ if (candidate.version !== version)
+ add('candidate-version', 'Artifact version differs from current manifest')
+ }
+ for (const id of taskGaps) add('task', id)
+ for (const risk of risks.filter(risk => risk.status === 'open')) add('risk', risk.id)
+ const checks = {}
+ for (const gate of gates) {
+ const report = evidence[gate]
+ const errors = []
+ if (!report) {
+ errors.push('Missing candidate-bound evidence')
+ }
+ else {
+ errors.push(...report.errors)
+ if (report.result !== 'pass')
+ errors.push(`Result is ${report.result}`)
+ if (report.package !== row.name || report.gate !== gate)
+ errors.push('Package/gate scope mismatch')
+ if (report.version !== version)
+ errors.push('Evidence version mismatch')
+ if (!candidate || report.candidateIntegrity !== candidate.integrity)
+ errors.push('Candidate integrity mismatch')
+ if (report.inputFingerprint !== fingerprint)
+ errors.push('Evidence input fingerprint is stale')
+ if (!report.checks?.length || report.checks.some(check => check.result !== 'pass'))
+ errors.push('Required checks contain missing, failed, skipped or unknown results')
+ if (!report.environment?.length || !report.command || !report.reviewedBy)
+ errors.push('Missing environment, command or review attribution')
+ if (gate === 'devices' && report.environment?.some(env => env.emulated !== false || !env.device || !env.os || !env.browser))
+ errors.push('Device evidence must explicitly identify real devices, OS and browser')
+ if (gate === 'devices' && report.checks?.some(check => check.mode !== 'native'))
+ errors.push('Device/capability checks cannot use mocks or emulation')
+ if (gate === 'remote-ci' && !report.environment?.some(env => env.remote === true && /^https:\/\/github\.com\/[^/]+\/[^/]+\/actions\/runs\/\d+$/.test(env.runUrl)))
+ errors.push('Remote CI evidence needs an actual GitHub Actions run reference')
+ const required = gate === 'devices' ? row.extraRequirements : gate === 'licenses' ? assets.map(asset => asset.id) : []
+ const covered = new Set(report.checks?.filter(check => check.result === 'pass').map(check => check.id))
+ for (const requirement of required) {
+ if (!covered.has(requirement))
+ errors.push(`Missing coverage: ${requirement}`)
+ }
+ }
+ checks[gate] = errors.length ? { status: 'blocked', errors } : { status: 'evidence-recorded' }
+ for (const error of errors) add(`evidence:${gate}`, error)
+ }
+ return { name: row.name, version, targetVersion: row.targetVersion, distribution: row.distribution, fingerprint, candidate, history, rollback: row.rollback, risks, assets, checks, blockers, status: blockers.length ? 'blocked' : 'evidence-complete', publicationAuthorized: false }
+}
diff --git a/refactor/scripts/release-ledger.mjs b/refactor/scripts/release-ledger.mjs
new file mode 100644
index 000000000..2df3f1926
--- /dev/null
+++ b/refactor/scripts/release-ledger.mjs
@@ -0,0 +1,201 @@
+import assert from 'node:assert/strict'
+import { execFileSync } from 'node:child_process'
+import fs from 'node:fs'
+import path from 'node:path'
+import process from 'node:process'
+import { fileURLToPath, pathToFileURL } from 'node:url'
+import { parseArgs } from 'node:util'
+import { readImpactModel, repositoryPath } from './impact-model.mjs'
+import { dependencyClosure, evaluatePackage, findingPackages, validateLedger } from './release-ledger-model.mjs'
+import { archiveFiles, hash, readMember } from './releases.mjs'
+
+export const root = fileURLToPath(new URL('../../', import.meta.url))
+
+export function localFile(directory, relative) {
+ const normalized = repositoryPath(relative)
+ const resolved = fs.realpathSync(path.resolve(directory, normalized))
+ assert(resolved.startsWith(fs.realpathSync(directory) + path.sep), `File escapes repository: ${relative}`)
+ assert(fs.statSync(resolved).isFile(), `Not a file: ${relative}`)
+ return resolved
+}
+
+function read(directory, relative) {
+ return JSON.parse(fs.readFileSync(localFile(directory, relative), 'utf8'))
+}
+
+export function checkedReport(directory, binding) {
+ if (!binding)
+ return null
+ const errors = []
+ try {
+ const bytes = fs.readFileSync(localFile(directory, binding.path))
+ assert.equal(hash(bytes), binding.sha256, 'Report bytes changed')
+ const report = JSON.parse(bytes)
+ assert.equal(report.schemaVersion, 1, 'Unsupported evidence schema')
+ assert(Array.isArray(report.artifacts) && report.artifacts.length, 'No underlying result artifacts')
+ for (const artifact of report.artifacts) {
+ assert.equal(hash(fs.readFileSync(localFile(directory, artifact.path))), artifact.sha256, `Underlying evidence changed: ${artifact.path}`)
+ }
+ return { ...report, errors }
+ }
+ catch (error) {
+ return { errors: [error.message] }
+ }
+}
+
+export function checkedCandidate(directory, row) {
+ const binding = row.candidate
+ if (!binding)
+ return null
+ const result = { ...binding, errors: [] }
+ try {
+ assert(/^[a-f\d]{40}$/.test(binding.sourceCommit), 'Missing candidate source commit')
+ execFileSync('git', ['cat-file', '-e', `${binding.sourceCommit}^{commit}`], { cwd: directory, stdio: 'pipe' })
+ const artifact = localFile(directory, binding.path)
+ const bytes = fs.readFileSync(artifact)
+ assert.equal(`sha512-${hash(bytes, 'sha512', 'base64')}`, binding.integrity, 'Candidate integrity mismatch')
+ if (row.distribution === 'site') {
+ assert.equal(binding.kind, 'site-manifest', 'Site requires a build-file manifest, not an inferred npm tarball')
+ const manifest = JSON.parse(bytes)
+ assert.equal(manifest.package, row.name)
+ assert.equal(manifest.version, binding.version)
+ assert(Array.isArray(manifest.files) && manifest.files.length, 'Empty site output')
+ assert.equal(new Set(manifest.files.map(file => file.path)).size, manifest.files.length, 'Duplicate site outputs')
+ const outputRoot = fs.realpathSync(path.resolve(directory, repositoryPath(manifest.outputRoot)))
+ assert(outputRoot.startsWith(fs.realpathSync(directory) + path.sep), 'Site output escapes repository')
+ const walk = folder => fs.readdirSync(folder, { withFileTypes: true }).flatMap((entry) => {
+ assert(!entry.isSymbolicLink(), 'Site output contains a redirected file')
+ const child = path.join(folder, entry.name)
+ return entry.isDirectory() ? walk(child) : [path.relative(directory, child).replaceAll('\\', '/')]
+ })
+ assert.deepEqual(walk(outputRoot).sort(), manifest.files.map(file => repositoryPath(file.path)).sort(), 'Site output inventory differs')
+ for (const file of manifest.files)
+ assert.equal(hash(fs.readFileSync(localFile(directory, file.path))), file.sha256, `Site build changed: ${file.path}`)
+ }
+ else {
+ assert.equal(binding.kind, 'npm-tarball')
+ archiveFiles(artifact)
+ const manifest = JSON.parse(readMember(artifact, 'package/package.json'))
+ assert.equal(manifest.name, row.name, 'Tarball package name mismatch')
+ assert.equal(manifest.version, binding.version, 'Tarball version mismatch')
+ }
+ }
+ catch (error) {
+ result.errors.push(error.message)
+ }
+ return result
+}
+
+function historyFor(directory, row) {
+ const data = read(directory, row.history.file)
+ const selected = row.history.selector?.split('.').reduce((object, key) => object?.[key], data)
+ if (row.distribution === 'site')
+ return { verified: true, kind: 'site-only', file: row.history.file, rationale: row.history.rationale, npmPublication: false }
+ if (!selected?.integrity || !selected?.tarball || !selected?.sha256)
+ return { verified: false, reason: 'Historical archive unavailable; recovered content is not a complete rollback tarball', file: row.history.file }
+ const expectedName = row.history.publishedName || row.name
+ assert.equal(selected.name, expectedName, `Wrong historical package: ${row.name}`)
+ return { verified: true, kind: row.distribution, file: row.history.file, name: selected.name, version: selected.version, integrity: selected.integrity, sha256: selected.sha256, tarball: selected.tarball, rationale: row.history.rationale, limitation: 'Frozen historical distribution mapping; not a fresh registry/version-availability check or rollback rehearsal' }
+}
+
+export function fingerprintInputs(directory, files, dependencies, site) {
+ const selected = files.filter(file => dependencies.some(name => file.startsWith(`packages/${name}/`))
+ || /^(?:scripts\/|types\/|test\/|\.github\/workflows\/)/.test(file)
+ || /^(?:package\.json|yarn\.lock|\.node-version|\.gitattributes|\.yarnrc|\.npmrc|tsconfig[^/]*\.json|playwright[^/]*\.js|eslint\.config\.js|lerna\.json)$/.test(file)
+ || /^refactor\/(?:third-party\.json|scripts\/release-ledger[^/]*\.mjs)$/.test(file)
+ || /^refactor\/(?:compatibility\.md|quality-contract\.md|environment-matrix\.md|release-reviews\.md|version-policy\.md|package-inventory\.json|impact-policy\.json)$/.test(file)
+ || /^refactor\/baselines\/(?:releases|.*-release|.*-sdk(?:-matrix)?|.*-core)\.json$/.test(file)
+ || (site && /^(?:docs\/|example\/)/.test(file)))
+ return selected.sort().map((file) => {
+ const bytes = fs.readFileSync(localFile(directory, file))
+ const basename = path.basename(file)
+ const text = /\.(?:[cm]?[jt]sx?|vue|json|md|txt|html|css|less|svg|ya?ml|lock)$/.test(file)
+ || ['.node-version', '.npmignore', '.gitignore', '.yarnrc', '.npmrc', '.gitattributes'].includes(basename)
+ || /^(?:LICENSE|NOTICE|README)(?:\..*)?$/i.test(basename)
+ return { path: file, algorithm: text ? 'sha256-lf' : 'sha256', sha256: hash(text ? bytes.toString('utf8').replaceAll('\r\n', '\n') : bytes), rawSha256: hash(bytes) }
+ })
+}
+
+export function fingerprintOf(inputs) {
+ return hash(JSON.stringify(inputs.map(({ path, algorithm, sha256 }) => ({ path, algorithm, sha256 }))))
+}
+
+export function buildLedger(directory = root, selectedNames) {
+ const ledger = read(directory, 'refactor/release-ledger.json')
+ const inventory = read(directory, 'refactor/package-inventory.json').packages
+ const tasks = new Map(read(directory, 'refactor/tasks.json').tasks.map(task => [task.id, task]))
+ validateLedger(ledger, inventory, tasks)
+ const names = inventory.map(pkg => pkg.name)
+ const selected = selectedNames || names
+ assert(selected.length && new Set(selected).size === selected.length && selected.every(name => names.includes(name)), 'Unknown, empty or duplicate batch package selection')
+ const model = readImpactModel(directory)
+ const risks = read(directory, 'refactor/risks.json').items
+ const thirdParty = read(directory, 'refactor/third-party.json')
+ const assets = [...thirdParty.vendored, ...thirdParty.integrations]
+ const files = [...new Set(execFileSync('git', ['ls-files', '--cached', '--others', '--exclude-standard', '-z'], { cwd: directory, encoding: 'utf8', maxBuffer: 32 * 1024 * 1024 }).split('\0').filter(file => file && fs.existsSync(path.join(directory, file))))]
+ const head = execFileSync('git', ['rev-parse', 'HEAD'], { cwd: directory, encoding: 'utf8' }).trim()
+ const inputFiles = {}
+ const rows = selected.map((name) => {
+ const row = ledger.packages.find(pkg => pkg.name === name)
+ const dependencies = dependencyClosure(name, names, model.edges, model.dynamicImports)
+ const applies = item => findingPackages(item, tasks, names).some(pkg => dependencies.includes(pkg))
+ const relevantRisks = risks.filter(applies)
+ const relevantAssets = [
+ ...assets.filter(applies),
+ ...thirdParty.packages.filter(pkg => dependencies.includes(pkg.name)).flatMap(pkg => pkg.resolvedRuntimeDependencies.map(dependency => ({ id: `dependency:${pkg.name}:${dependency.name}`, sourceStatus: `Locked ${dependency.lockedVersion}; declared ${dependency.range}`, licenseStatus: dependency.licenseEvidence }))),
+ ]
+ const inputs = fingerprintInputs(directory, files, dependencies, row.distribution === 'site')
+ Object.assign(inputFiles, Object.fromEntries(inputs.map(({ path, ...input }) => [path, input])))
+ const fingerprint = fingerprintOf(inputs)
+ const taskGaps = [...tasks.values()].filter(task => (ledger.sharedTasks.includes(task.id)
+ || (task.scope.some(scope => dependencies.includes(scope)) && /^(?:PKG-|CORE-|SITE-|EX-)/.test(task.id))) && task.status !== 'done').map(task => task.id)
+ const evidence = Object.fromEntries(Object.entries(row.evidence).map(([gate, binding]) => [gate, checkedReport(directory, binding)]))
+ const result = evaluatePackage({
+ row,
+ fingerprint,
+ version: read(directory, `packages/${name}/package.json`).version,
+ candidate: checkedCandidate(directory, row),
+ evidence,
+ taskGaps,
+ risks: relevantRisks.map(({ id, status, owners, evidence, compatibleResolution, closureCriteria }) => ({ id, status, owners, evidence, compatibleResolution, closureCriteria })),
+ assets: relevantAssets.map(({ id, riskId, sourceStatus, licenseStatus, validationStatus, reviewEvidence }) => ({ id, riskId, sourceStatus, licenseStatus, validationStatus, reviewEvidence })),
+ history: historyFor(directory, row),
+ })
+ return { ...result, dependencies, inputs: inputs.map(input => input.path), historicalTaskEvidence: [...tasks.values()].filter(task => task.scope.includes(name)).map(({ id, status, evidence }) => ({ id, status, evidence })), historicalEvidenceAcceptedForCandidate: false }
+ })
+ return {
+ schemaVersion: 1,
+ sourceCommit: head,
+ toolchain: {
+ node: process.version,
+ canonicalNode: fs.readFileSync(localFile(directory, '.node-version'), 'utf8').trim(),
+ packageManager: read(directory, 'package.json').packageManager,
+ observedUserAgent: process.env.npm_config_user_agent || null,
+ declaredTools: read(directory, 'package.json').devDependencies,
+ lock: { path: 'yarn.lock', sha256: hash(fs.readFileSync(localFile(directory, 'yarn.lock'))) },
+ },
+ ledger: { path: 'refactor/release-ledger.json', sha256: hash(fs.readFileSync(localFile(directory, 'refactor/release-ledger.json'))) },
+ inputFiles,
+ packages: rows,
+ evidenceComplete: rows.every(row => row.status === 'evidence-complete'),
+ publicationAuthorized: false,
+ limitation: 'Mechanical evidence binding and blocking report. Report contents still require the three reviews; this tool neither executes tests nor verifies devices nor authorizes publishing.',
+ }
+}
+
+if (process.argv[1] && import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href) {
+ const { values } = parseArgs({ options: { packages: { type: 'string' }, report: { type: 'boolean' }, strict: { type: 'boolean' } } })
+ const result = buildLedger(root, values.packages?.split(','))
+ if (values.strict) {
+ assert.equal(process.version, `v${result.toolchain.canonicalNode}`, 'Strict release preflight requires canonical Node')
+ assert(process.env.npm_config_user_agent?.startsWith('yarn/1.22.22 '), 'Strict release preflight requires Yarn Classic1.22.22')
+ }
+ if (values.report) {
+ const directory = fs.mkdtempSync(path.join(root, 'refactor/.cache/release-ledger-'))
+ fs.writeFileSync(path.join(directory, 'report.json'), `${JSON.stringify(result, null, 2)}\n`)
+ console.log(`Release ledger report: ${directory}`)
+ }
+ console.log(JSON.stringify({ packages: result.packages.map(({ name, status, blockers }) => ({ name, status, blockers: blockers.length })), publicationAuthorized: false }))
+ if (values.strict && !result.evidenceComplete)
+ process.exitCode = 1
+}
diff --git a/refactor/scripts/release-ledger.test.mjs b/refactor/scripts/release-ledger.test.mjs
new file mode 100644
index 000000000..b19e172fa
--- /dev/null
+++ b/refactor/scripts/release-ledger.test.mjs
@@ -0,0 +1,178 @@
+import assert from 'node:assert/strict'
+import { execFileSync } from 'node:child_process'
+import fs from 'node:fs'
+import os from 'node:os'
+import path from 'node:path'
+// eslint-disable-next-line test/no-import-node-test -- Release evidence must fail closed under stale or mis-scoped input.
+import test from 'node:test'
+import { dependencyClosure, evaluatePackage, findingPackages, libraryGates, sharedTasks, siteGates, validateLedger } from './release-ledger-model.mjs'
+import { checkedCandidate, checkedReport, fingerprintInputs, fingerprintOf, localFile, root } from './release-ledger.mjs'
+import { hash } from './releases.mjs'
+
+function fixture() {
+ const row = { name: 'example', distribution: 'npm', targetVersion: '2.0.0', extraRequirements: ['cast-session'], rollback: { strategy: 'restore previous tested candidate' } }
+ const candidate = { version: '2.0.0', integrity: 'candidate-digest', inputFingerprint: 'current-inputs', errors: [] }
+ const evidence = Object.fromEntries(libraryGates.map(gate => [gate, { errors: [], result: 'pass', package: 'example', version: '2.0.0', gate, candidateIntegrity: candidate.integrity, inputFingerprint: 'current-inputs', checks: [{ id: gate === 'devices' ? 'cast-session' : 'normal-path', result: 'pass', mode: 'native' }], environment: [{ os: 'device-os', browser: 'browser-version', device: 'physical-model', emulated: false, remote: true, runUrl: 'https://github.com/example/repo/actions/runs/1' }], command: 'actual test command', reviewedBy: 'review record' }]))
+ return { row, fingerprint: 'current-inputs', version: '2.0.0', candidate, evidence, taskGaps: [], risks: [], assets: [], history: { verified: true } }
+}
+
+test('Release ledger synthetic complete evidence remains publication-unauthorized', () => {
+ const result = evaluatePackage(fixture())
+ assert.equal(result.status, 'evidence-complete')
+ assert.equal(result.publicationAuthorized, false)
+})
+
+for (const [name, mutate, kind] of [
+ ['missing candidate', input => input.candidate = null, 'candidate'],
+ ['changed sources', input => input.fingerprint = 'changed-source', 'stale-candidate'],
+ ['unprepared major', input => input.version = '1.1.0', 'version'],
+ ['wrong artifact version', input => input.candidate.version = '9.0.0', 'candidate-version'],
+ ['archive bytes differ', input => input.candidate.errors.push('digest differs'), 'candidate'],
+ ['missing rollback archive', input => input.history = { verified: false, reason: 'not recovered' }, 'distribution-history'],
+ ['open affected risk', input => input.risks.push({ id: 'CAST-01', status: 'open' }), 'risk'],
+ ['unfinished review', input => input.taskGaps.push('REVIEW-03'), 'task'],
+ ['missing format report', input => delete input.evidence.runtime, 'evidence:runtime'],
+ ['wrong package scope', input => input.evidence.runtime.package = 'other-package', 'evidence:runtime'],
+ ['wrong gate scope', input => input.evidence.runtime.gate = 'types', 'evidence:runtime'],
+ ['wrong report version', input => input.evidence.runtime.version = '1.0.0', 'evidence:runtime'],
+ ['old tarball report', input => input.evidence.runtime.candidateIntegrity = 'old-digest', 'evidence:runtime'],
+ ['stale type report', input => input.evidence.types.inputFingerprint = 'old-inputs', 'evidence:types'],
+ ['skipped native check', input => input.evidence.devices.checks[0].result = 'skipped', 'evidence:devices'],
+ ['emulated device', input => input.evidence.devices.environment[0].emulated = true, 'evidence:devices'],
+ ['unidentified device', input => delete input.evidence.devices.environment[0].device, 'evidence:devices'],
+ ['mock capability on real device', input => input.evidence.devices.checks[0].mode = 'mock', 'evidence:devices'],
+ ['local CI as remote proof', input => input.evidence['remote-ci'].environment[0].remote = false, 'evidence:remote-ci'],
+ ['missing capability', input => input.evidence.devices.checks[0].id = 'viewport-only', 'evidence:devices'],
+ ['missing source/license coverage', input => input.assets.push({ id: 'worker-wasm-font' }), 'evidence:licenses'],
+ ['empty successful report', input => input.evidence.browser.checks = [], 'evidence:browser'],
+]) {
+ test(`Release ledger blocks ${name}`, () => {
+ const input = fixture()
+ mutate(input)
+ const result = evaluatePackage(input)
+ assert.equal(result.status, 'blocked')
+ assert(result.blockers.some(blocker => blocker.kind === kind))
+ })
+}
+
+test('Release ledger maps dependency risks without generic review owners tainting unrelated packages', () => {
+ const names = ['core', 'hls', 'cast', 'site']
+ const edges = [{ consumer: 'hls', dependency: 'core' }, { consumer: 'cast', dependency: 'core' }, { consumer: 'site', dependency: 'hls' }]
+ assert.deepEqual(dependencyClosure('cast', names, edges), ['cast', 'core'])
+ assert.deepEqual(dependencyClosure('site', names, edges), ['core', 'hls', 'site'])
+ assert.deepEqual(dependencyClosure('cast', names, edges, ['unknown-import']), [...names].sort())
+ const tasks = new Map([['HLS-01', { scope: ['hls'] }], ['REVIEW-02', { scope: ['workspace'] }]])
+ assert.deepEqual(findingPackages({ owners: ['HLS-01', 'REVIEW-02'] }, tasks, names), ['hls'])
+ assert.deepEqual(findingPackages({ owners: ['REVIEW-02'] }, tasks, names), names)
+})
+
+function temp(t) {
+ const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'artplayer-ledger-test-'))
+ t.after(() => {
+ const resolved = fs.realpathSync(directory)
+ assert.equal(path.dirname(resolved), fs.realpathSync(os.tmpdir()))
+ assert(path.basename(resolved).startsWith('artplayer-ledger-test-'))
+ fs.rmSync(resolved, { recursive: true, force: true })
+ })
+ const save = (file, value) => {
+ fs.mkdirSync(path.dirname(path.join(directory, file)), { recursive: true })
+ fs.writeFileSync(path.join(directory, file), value)
+ }
+ return { directory, save }
+}
+
+test('Release ledger hashes actual report and underlying artifacts; replaced bytes cannot pass', (t) => {
+ const { directory, save } = temp(t)
+ save('actual.log', 'actual observed output')
+ const report = { schemaVersion: 1, result: 'pass', artifacts: [{ path: 'actual.log', sha256: hash('actual observed output') }] }
+ save('report.json', JSON.stringify(report))
+ const binding = { path: 'report.json', sha256: hash(JSON.stringify(report)) }
+ assert.deepEqual(checkedReport(directory, binding).errors, [])
+ save('actual.log', 'different output')
+ assert.match(checkedReport(directory, binding).errors[0], /Underlying evidence changed/)
+ save('report.json', '{}')
+ assert.match(checkedReport(directory, binding).errors[0], /Report bytes changed/)
+ assert.throws(() => localFile(directory, '../outside'), /escapes/)
+})
+
+test('Release input hashing isolates unrelated package edits but invalidates shared lock/test and dependencies', (t) => {
+ const { directory, save } = temp(t)
+ const files = ['packages/core/src.js', 'packages/hls/src.js', 'packages/cast/src.js', 'yarn.lock', 'test/shared.js', 'refactor/baselines/hls-sdk-matrix.json', 'refactor/release-reviews.md']
+ files.forEach(file => save(file, 'original'))
+ const fingerprint = () => fingerprintOf(fingerprintInputs(directory, files, ['core', 'cast'], false))
+ const before = fingerprint()
+ save('packages/hls/src.js', 'unrelated')
+ assert.equal(fingerprint(), before)
+ for (const file of ['packages/core/src.js', 'packages/cast/src.js', 'yarn.lock', 'test/shared.js', 'refactor/baselines/hls-sdk-matrix.json', 'refactor/release-reviews.md']) {
+ save(file, 'changed')
+ assert.notEqual(fingerprint(), before)
+ save(file, 'original')
+ }
+})
+
+test('Release input fingerprint normalizes text EOL but records raw bytes and preserves binary differences', (t) => {
+ const { directory, save } = temp(t)
+ const files = ['packages/core/src.ts', 'packages/core/font.woff2']
+ save(files[0], 'line1\r\nline2\r\n')
+ save(files[1], 'binary\r\n')
+ const before = fingerprintInputs(directory, files, ['core'], false)
+ save(files[0], 'line1\nline2\n')
+ const after = fingerprintInputs(directory, files, ['core'], false)
+ assert.equal(fingerprintOf(before), fingerprintOf(after))
+ assert.notDeepEqual(before, after)
+ save(files[1], 'binary\n')
+ assert.notEqual(fingerprintOf(before), fingerprintOf(fingerprintInputs(directory, files, ['core'], false)))
+})
+
+test('Release ledger requires exactly22 packages, all reviews and site-specific distribution', () => {
+ const ledger = JSON.parse(fs.readFileSync(path.join(root, 'refactor/release-ledger.json')))
+ const inventory = JSON.parse(fs.readFileSync(path.join(root, 'refactor/package-inventory.json'))).packages
+ const tasks = new Map(sharedTasks.map(id => [id, {}]))
+ validateLedger(ledger, inventory, tasks)
+ assert.equal(ledger.packages.length, 22)
+ assert.equal(ledger.packages.find(row => row.name === 'artplayer-vitepress').distribution, 'site')
+ assert.deepEqual(ledger.siteGates, siteGates)
+ for (const mutate of [data => data.packages.pop(), data => data.libraryGates.pop(), data => data.sharedTasks.pop(), data => data.packages.find(row => row.distribution === 'site').distribution = 'npm']) {
+ const changed = structuredClone(ledger)
+ mutate(changed)
+ assert.throws(() => validateLedger(changed, inventory, tasks))
+ }
+})
+
+test('Release candidate verifies site output bytes and refuses using an npm artifact for site scope', (t) => {
+ const { directory, save } = temp(t)
+ execFileSync('git', ['init', '-q', directory])
+ execFileSync('git', ['-c', 'user.name=Ledger test', '-c', 'user.email=ledger@example.invalid', 'commit', '--allow-empty', '-qm', 'fixture'], { cwd: directory })
+ const sourceCommit = execFileSync('git', ['rev-parse', 'HEAD'], { cwd: directory, encoding: 'utf8' }).trim()
+ save('output/index.html', 'verified build')
+ const manifest = JSON.stringify({ package: 'site', version: '2.0.0', outputRoot: 'output', files: [{ path: 'output/index.html', sha256: hash('verified build') }] })
+ save('manifest.json', manifest)
+ const row = { name: 'site', distribution: 'site', candidate: { kind: 'site-manifest', path: 'manifest.json', sourceCommit, version: '2.0.0', integrity: `sha512-${hash(manifest, 'sha512', 'base64')}` } }
+ assert.deepEqual(checkedCandidate(directory, row).errors, [])
+ save('output/index.html', 'stale')
+ assert.match(checkedCandidate(directory, row).errors[0], /Site build changed/)
+ save('output/extra.js', 'unlisted build output')
+ assert.match(checkedCandidate(directory, row).errors[0], /Site output inventory differs/)
+ row.candidate.kind = 'npm-tarball'
+ assert.match(checkedCandidate(directory, row).errors[0], /Site requires/)
+})
+
+test('Release candidate checks actual tarball manifest and detects digest/version/name mismatches', (t) => {
+ const { directory, save } = temp(t)
+ execFileSync('git', ['init', '-q', directory])
+ execFileSync('git', ['-c', 'user.name=Ledger test', '-c', 'user.email=ledger@example.invalid', 'commit', '--allow-empty', '-qm', 'fixture'], { cwd: directory })
+ const sourceCommit = execFileSync('git', ['rev-parse', 'HEAD'], { cwd: directory, encoding: 'utf8' }).trim()
+ save('staging/package/package.json', JSON.stringify({ name: 'example', version: '2.0.0' }))
+ execFileSync('tar', ['-czf', path.join(directory, 'candidate.tgz'), '-C', path.join(directory, 'staging'), 'package'])
+ const integrity = `sha512-${hash(fs.readFileSync(path.join(directory, 'candidate.tgz')), 'sha512', 'base64')}`
+ const row = { name: 'example', distribution: 'npm', candidate: { kind: 'npm-tarball', path: 'candidate.tgz', integrity, version: '2.0.0', sourceCommit } }
+ assert.deepEqual(checkedCandidate(directory, row).errors, [])
+ row.candidate.version = '2.1.0'
+ assert.match(checkedCandidate(directory, row).errors[0], /Tarball version mismatch/)
+ row.candidate.version = '2.0.0'
+ row.name = 'wrong'
+ assert.match(checkedCandidate(directory, row).errors[0], /Tarball package name mismatch/)
+ row.name = 'example'
+ save('candidate.tgz', 'tampered')
+ assert.match(checkedCandidate(directory, row).errors[0], /Candidate integrity mismatch/)
+})
diff --git a/refactor/tasks.json b/refactor/tasks.json
index 402ea4f26..4f86eefc8 100644
--- a/refactor/tasks.json
+++ b/refactor/tasks.json
@@ -4585,9 +4585,14 @@
"scope": [
"workspace"
],
- "status": "todo",
+ "status": "done",
"risk": "H",
- "evidence": [],
+ "evidence": [
+ "changes/2026-09-14-REL-08-release-ledger.md",
+ "baselines/release-ledger-validation.json",
+ "release-ledger.md",
+ "release-ledger.json"
+ ],
"id": "REL-08",
"phase": "8 发布验收",
"title": "提前建立逐包发布准入台账",