From 04060aa8b6b5cf070bac0c6a9d50eb329fab9719 Mon Sep 17 00:00:00 2001 From: Harvey Zhao Date: Mon, 14 Sep 2026 15:08:26 +0800 Subject: [PATCH] feat(REL-08): bind package release gates to candidate evidence --- package.json | 8 +- refactor/README.md | 1 + .../baselines/release-ledger-validation.json | 2127 +++++++++++++++++ .../2026-09-14-REL-08-release-ledger.md | 84 + refactor/ci-setup.md | 2 + refactor/plan.md | 5 +- refactor/progress.md | 14 + refactor/release-ledger.json | 509 ++++ refactor/release-ledger.md | 132 + refactor/release-reviews.md | 5 + refactor/scripts/release-ledger-model.mjs | 114 + refactor/scripts/release-ledger.mjs | 201 ++ refactor/scripts/release-ledger.test.mjs | 178 ++ refactor/tasks.json | 9 +- 14 files changed, 3383 insertions(+), 6 deletions(-) create mode 100644 refactor/baselines/release-ledger-validation.json create mode 100644 refactor/changes/2026-09-14-REL-08-release-ledger.md create mode 100644 refactor/release-ledger.json create mode 100644 refactor/release-ledger.md create mode 100644 refactor/scripts/release-ledger-model.mjs create mode 100644 refactor/scripts/release-ledger.mjs create mode 100644 refactor/scripts/release-ledger.test.mjs 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": "提前建立逐包发布准入台账",