test(release): [REL-04] checkpoint renamed rollback and mainline conflict recovery

This commit is contained in:
Harvey Zhao committed 2026-09-15 00:28:33 +08:00
1 parent aa29dc9212
commit d0c33f6258
14 files changed
+1718 -8

No files matched your search

+5 -2
View File
@@ -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/pages-artifact.test.js test/site-notices.test.js test/vconsole-lifecycle.test.js test/browser-validation.test.js test/rollback-files.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 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 check:vconsole && yarn check:site-notices && 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 check:rollback-inventory && yarn lint && yarn typecheck:library && yarn typecheck:scaffold && yarn typecheck:docs-tools && yarn check:docs-smoke && yarn check:editor-types && yarn check:llm && yarn check:vconsole && yarn check:site-notices && 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:vconsole && yarn build:site-notices && 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",
@@ -153,7 +153,10 @@
"test:browser:installed": "node scripts/browser-check.mjs installed",
"test:vast-types": "node --test refactor/scripts/vast-types.test.mjs",
"test:vast-types-package": "node refactor/scripts/vast-package-types.mjs",
"test:vast-native": "playwright test --config playwright.vast-native.config.js"
"test:vast-native": "playwright test --config playwright.vast-native.config.js",
"test:rollback:iframe": "node scripts/iframe-rollback.mjs",
"test:rollback:mainline": "node scripts/mainline-rehearsal.mjs",
"check:rollback-inventory": "node refactor/scripts/rollback-inventory.mjs --check"
},
"browserslist": "last 1 Chrome version",
"devDependencies": {
@@ -1,5 +1,13 @@
# Iframe maintenance map
Package-name rollback must restore the application imports and saved dependency
lock together. The old plugin package exposes a CommonJS namespace with `.default`
and distinct helper files; installing its tarball under the current tool dependency
key does not preserve the current constructor or `/legacy` imports. The isolated
build/install/type rehearsal is `yarn test:rollback:iframe`; see
[rollback maintenance](../../refactor/rollback-rehearsal.md). This does not replace
the package's cross-window, playback or device acceptance.
`src/index.ts` owns the existing class, constructor validation, bound parent
message listener, child injection/execution protocol and public callbacks.
`src/requests.ts` owns request IDs, pending promises, injection polling and
+1
View File
@@ -30,6 +30,7 @@
| [多轮复盘与 npm 准入](release-reviews.md) | Chrome 验证分工、三轮全局复盘、问题闭环和候选发布门槛 |
| [逐包发布准入台账](release-ledger.md) | 22包候选/证据绑定、源码和产物指纹、任务风险/许可阻断及严格预检 |
| [回退演练与维护](rollback-rehearsal.md) | 独立核心/插件回退、冻结安装文件检查、真实播放及剩余发布门槛 |
| [逐包恢复清单](rollback-inventory.md) | 全包精确旧版本、依赖及完整回退产物的缺口 |
| [工具链与发布](toolchain-release.md) | TypeScript、Bun、构建、版本管理和发布回退 |
| [已实现的开发环境](toolchain-setup.md) | Node/Yarn 固定版本、锁文件、安装命令和实际验证范围 |
| [全包大版本策略](version-policy.md) | 每包分别升级一个 major 的目标清单、兼容要求和版本落地步骤 |
+647
View File
@@ -0,0 +1,647 @@
{
"task": "REL-04",
"source": "refactor/release-ledger.json and frozen historical archives",
"registryAvailabilityRefreshed": false,
"mutableTagPolicy": "Use exact versions and saved lock/artifact hashes. No latest/next/rollback tag is assumed or changed.",
"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."
},
"currentDependencies": {
"option-validator": "^2.0.6"
},
"currentPeers": {},
"currentOptionalDependencies": {},
"previous": {
"name": "artplayer",
"version": "5.4.0",
"tarball": "https://registry.npmjs.org/artplayer/-/artplayer-5.4.0.tgz",
"integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==",
"sha256": "42b269a66b9658d53f9a92572bb36d656ea9af56254a387b3a69c7ddea43979e",
"memberCount": 41,
"dependencies": {
"option-validator": "^2.0.6"
},
"peerDependencies": {},
"optionalDependencies": {}
},
"exactDependency": {
"artplayer": "5.4.0"
},
"restore": "Restore the saved application manifest/lock and matching artifacts, then frozen-install the previous exact version with its tested core/plugin combination.",
"status": "archive-verified-batch-rehearsal-required"
},
{
"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."
},
"currentDependencies": {},
"currentPeers": {},
"currentOptionalDependencies": {},
"previous": {
"name": "artplayer-plugin-ads",
"version": "1.0.6",
"tarball": "https://registry.npmjs.org/artplayer-plugin-ads/-/artplayer-plugin-ads-1.0.6.tgz",
"integrity": "sha512-KMGGf7lhgj6DQXHPOg28jWYkVmDfOZJ4Gr4iF28Wy2GXNPh2ZJtlsFQ2IuWqXCElRPvSLzYK5HugF0OUnVszaw==",
"sha256": "ff132ff8a7108d8b26421621e4aac1d7edf8c99099a5ab4fd2190bd79b8636cd",
"memberCount": 6,
"dependencies": {},
"peerDependencies": {},
"optionalDependencies": {}
},
"exactDependency": {
"artplayer-plugin-ads": "1.0.6"
},
"restore": "Restore the saved application manifest/lock and matching artifacts, then frozen-install the previous exact version with its tested core/plugin combination.",
"status": "archive-verified-batch-rehearsal-required"
},
{
"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."
},
"currentDependencies": {},
"currentPeers": {},
"currentOptionalDependencies": {},
"previous": {
"name": "artplayer-plugin-ambilight",
"version": "1.1.0",
"tarball": "https://registry.npmjs.org/artplayer-plugin-ambilight/-/artplayer-plugin-ambilight-1.1.0.tgz",
"integrity": "sha512-FkMI7bn9HovJMfEdgkKbo7/vPt6vrzcpELTaC/vM7B99FPSMWzAf6GAE0xnNxU3KibYcBV5gXnBgvHgpG2xq2A==",
"sha256": "e95bd60fd7bce52142cf0041bad811452305029cbe1129c7d89246eb260c5c86",
"memberCount": 6,
"dependencies": {},
"peerDependencies": {},
"optionalDependencies": {}
},
"exactDependency": {
"artplayer-plugin-ambilight": "1.1.0"
},
"restore": "Restore the saved application manifest/lock and matching artifacts, then frozen-install the previous exact version with its tested core/plugin combination.",
"status": "archive-verified-batch-rehearsal-required"
},
{
"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."
},
"currentDependencies": {},
"currentPeers": {},
"currentOptionalDependencies": {},
"previous": {
"name": "artplayer-plugin-asr",
"version": "2.1.0",
"tarball": "https://registry.npmjs.org/artplayer-plugin-asr/-/artplayer-plugin-asr-2.1.0.tgz",
"integrity": "sha512-15gXff458PAx9LeJxKzZEvmpFhfwKUfSLgb1Mfr7Xb/ZGsnE+6EA7ezTnO5S55W5mFExjv/e95iOIXP4ijuruQ==",
"sha256": "8bc3cd93f78ce99e5c09cc546c770a63e8aea5729d3d584134ce04d826b7708b",
"memberCount": 6,
"dependencies": {},
"peerDependencies": {},
"optionalDependencies": {}
},
"exactDependency": {
"artplayer-plugin-asr": "2.1.0"
},
"restore": "Restore the saved application manifest/lock and matching artifacts, then frozen-install the previous exact version with its tested core/plugin combination.",
"status": "archive-verified-batch-rehearsal-required"
},
{
"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."
},
"currentDependencies": {},
"currentPeers": {},
"currentOptionalDependencies": {},
"previous": {
"name": "artplayer-plugin-audio-track",
"version": "1.1.0",
"tarball": "https://registry.npmjs.org/artplayer-plugin-audio-track/-/artplayer-plugin-audio-track-1.1.0.tgz",
"integrity": "sha512-pvvHvTSqC2+sBelwYyjAv2CVmaQgG9KpX+i96t/e8oyZRX/JuepC9iSVIp7GzB3AtliFzhVamksT+8lXvD3RCg==",
"sha256": "7e84f6a2f4bfada428a77ef04b749bf3db53e0149fa1afe7bf5749d925e801ff",
"memberCount": 6,
"dependencies": {},
"peerDependencies": {},
"optionalDependencies": {}
},
"exactDependency": {
"artplayer-plugin-audio-track": "1.1.0"
},
"restore": "Restore the saved application manifest/lock and matching artifacts, then frozen-install the previous exact version with its tested core/plugin combination.",
"status": "archive-verified-batch-rehearsal-required"
},
{
"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."
},
"currentDependencies": {},
"currentPeers": {},
"currentOptionalDependencies": {},
"previous": {
"name": "artplayer-plugin-auto-thumbnail",
"version": "1.1.0",
"tarball": "https://registry.npmjs.org/artplayer-plugin-auto-thumbnail/-/artplayer-plugin-auto-thumbnail-1.1.0.tgz",
"integrity": "sha512-ALrcLv+qEDime+R8p4628RZTfMGlrpX6d7PLIQaYWsQEN3lPzVjcVcARCFpqnFI8gvRdqt21w72sJphf50xtVw==",
"sha256": "5ea83d4e45d42fdaf42d3e37b6c666857872adaa276ab329c74edcdaa3c1a978",
"memberCount": 6,
"dependencies": {},
"peerDependencies": {},
"optionalDependencies": {}
},
"exactDependency": {
"artplayer-plugin-auto-thumbnail": "1.1.0"
},
"restore": "Restore the saved application manifest/lock and matching artifacts, then frozen-install the previous exact version with its tested core/plugin combination.",
"status": "archive-verified-batch-rehearsal-required"
},
{
"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."
},
"currentDependencies": {},
"currentPeers": {},
"currentOptionalDependencies": {},
"previous": {
"name": "artplayer-plugin-chapter",
"version": "1.1.0",
"tarball": "https://registry.npmjs.org/artplayer-plugin-chapter/-/artplayer-plugin-chapter-1.1.0.tgz",
"integrity": "sha512-ji66Lr2K2S+OjFMuMby3yq+pefWlOC51Sbpop/DIr2N6w3F0smrF+YZbshN1Dy8/Tx1TYpT7N6WkkZWy5Jix1w==",
"sha256": "4c3173661854ff96f643a479c0f1c2db7ce9e54681523bd059a7dd9bd77eeada",
"memberCount": 6,
"dependencies": {},
"peerDependencies": {},
"optionalDependencies": {}
},
"exactDependency": {
"artplayer-plugin-chapter": "1.1.0"
},
"restore": "Restore the saved application manifest/lock and matching artifacts, then frozen-install the previous exact version with its tested core/plugin combination.",
"status": "archive-verified-batch-rehearsal-required"
},
{
"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."
},
"currentDependencies": {},
"currentPeers": {},
"currentOptionalDependencies": {},
"previous": {
"name": "artplayer-plugin-chromecast",
"version": "1.1.0",
"tarball": "https://registry.npmjs.org/artplayer-plugin-chromecast/-/artplayer-plugin-chromecast-1.1.0.tgz",
"integrity": "sha512-3xrHj1CHe0Tt8gNMUB9A1epuW4OrbPsa1T9MNFy4AfQsjhYAhWAmef6hYu1GdHdADDybXBtVHP50gQ+EW+ddcw==",
"sha256": "1941d41055ee02deaa343cbf9eaf714495cb9f04883f57966f5b27f128117c30",
"memberCount": 6,
"dependencies": {},
"peerDependencies": {},
"optionalDependencies": {}
},
"exactDependency": {
"artplayer-plugin-chromecast": "1.1.0"
},
"restore": "Restore the saved application manifest/lock and matching artifacts, then frozen-install the previous exact version with its tested core/plugin combination.",
"status": "archive-verified-batch-rehearsal-required"
},
{
"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."
},
"currentDependencies": {},
"currentPeers": {},
"currentOptionalDependencies": {},
"previous": {
"name": "artplayer-plugin-danmuku",
"version": "5.3.0",
"tarball": "https://registry.npmjs.org/artplayer-plugin-danmuku/-/artplayer-plugin-danmuku-5.3.0.tgz",
"integrity": "sha512-a76bU1cWEZr53hD1X8IHfOzS4QawGHn8dQ3fIYyJ5TsyhTSnznhi2WzkS8M7LL3H02ekXRZTUFan9+9EERk12g==",
"sha256": "1abb76460790d85a26590b8b911eb1301fd720885f96c73432060416a38dcb0e",
"memberCount": 6,
"dependencies": {},
"peerDependencies": {},
"optionalDependencies": {}
},
"exactDependency": {
"artplayer-plugin-danmuku": "5.3.0"
},
"restore": "Restore the saved application manifest/lock and matching artifacts, then frozen-install the previous exact version with its tested core/plugin combination.",
"status": "archive-verified-batch-rehearsal-required"
},
{
"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."
},
"currentDependencies": {
"@mediapipe/selfie_segmentation": "^0.1.1675465747",
"@tensorflow-models/body-segmentation": "^1.0.2",
"@tensorflow/tfjs-backend-cpu": "^4.21.0",
"@tensorflow/tfjs-backend-webgl": "^4.21.0",
"@tensorflow/tfjs-converter": "^4.21.0",
"@tensorflow/tfjs-core": "^4.21.0"
},
"currentPeers": {},
"currentOptionalDependencies": {},
"previous": {
"name": "artplayer-plugin-danmuku-mask",
"version": "1.1.0",
"tarball": "https://registry.npmjs.org/artplayer-plugin-danmuku-mask/-/artplayer-plugin-danmuku-mask-1.1.0.tgz",
"integrity": "sha512-VBmmwtNLj0TKYz9lWbyQ7A8osQHdyDiPMazP9g1UZbLyw/rMJ3qRclMgszRpZHN9hod0dQl2iYSJDoaqu3hGOQ==",
"sha256": "96682e46cb140a84b164818dd118480d7be42b24afaab390027a25e52e8ae31c",
"memberCount": 6,
"dependencies": {
"@mediapipe/selfie_segmentation": "^0.1.1675465747",
"@tensorflow-models/body-segmentation": "^1.0.2",
"@tensorflow/tfjs-backend-cpu": "^4.21.0",
"@tensorflow/tfjs-backend-webgl": "^4.21.0",
"@tensorflow/tfjs-converter": "^4.21.0",
"@tensorflow/tfjs-core": "^4.21.0"
},
"peerDependencies": {},
"optionalDependencies": {}
},
"exactDependency": {
"artplayer-plugin-danmuku-mask": "1.1.0"
},
"restore": "Restore the saved application manifest/lock and matching artifacts, then frozen-install the previous exact version with its tested core/plugin combination.",
"status": "archive-verified-batch-rehearsal-required"
},
{
"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."
},
"currentDependencies": {},
"currentPeers": {},
"currentOptionalDependencies": {},
"previous": {
"name": "artplayer-plugin-dash-control",
"version": "1.1.0",
"tarball": "https://registry.npmjs.org/artplayer-plugin-dash-control/-/artplayer-plugin-dash-control-1.1.0.tgz",
"integrity": "sha512-EssRkzYXWcln3a6nAzi4ESacBhzMih8s0bzdjXuvBBCh2UFmvK99ITD5E+0cuU8i5ZzIQGQSdce1/gANTrX0BQ==",
"sha256": "f4047a569d99b6a7de57b3719b7ad40109f2a1d360566a840f50ab32aacefcd9",
"memberCount": 6,
"dependencies": {},
"peerDependencies": {},
"optionalDependencies": {}
},
"exactDependency": {
"artplayer-plugin-dash-control": "1.1.0"
},
"restore": "Restore the saved application manifest/lock and matching artifacts, then frozen-install the previous exact version with its tested core/plugin combination.",
"status": "archive-verified-batch-rehearsal-required"
},
{
"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."
},
"currentDependencies": {},
"currentPeers": {},
"currentOptionalDependencies": {},
"previous": {
"name": "artplayer-plugin-document-pip",
"version": "1.1.0",
"tarball": "https://registry.npmjs.org/artplayer-plugin-document-pip/-/artplayer-plugin-document-pip-1.1.0.tgz",
"integrity": "sha512-+HCrlO+bQwBrig1bI/IIxgGvbcG7i+aNHAnR5vLqB+RdyIY+FKCHyEe0o9yZREnbYOe0XJVqjv2buEVrouzdoQ==",
"sha256": "b864befea9aff84b3352782a9670ebe72ff073606fec7cdc31d31e92a5ad6d8a",
"memberCount": 6,
"dependencies": {},
"peerDependencies": {},
"optionalDependencies": {}
},
"exactDependency": {
"artplayer-plugin-document-pip": "1.1.0"
},
"restore": "Restore the saved application manifest/lock and matching artifacts, then frozen-install the previous exact version with its tested core/plugin combination.",
"status": "archive-verified-batch-rehearsal-required"
},
{
"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."
},
"currentDependencies": {},
"currentPeers": {},
"currentOptionalDependencies": {},
"previous": {
"name": "artplayer-plugin-hls-control",
"version": "1.1.0",
"tarball": "https://registry.npmjs.org/artplayer-plugin-hls-control/-/artplayer-plugin-hls-control-1.1.0.tgz",
"integrity": "sha512-lWN5oUOWDorWFHdzKcmy9Z8eHcf5f5EcUGDrxBsZBX7aEL5dGRlHPQ5+wqQEizr38D9PYPfTI/nCyun2yFopEw==",
"sha256": "598c354d78d3cb77965136fa61dfc0f94cdea6906a91a30781c1acef2b0c3e8b",
"memberCount": 6,
"dependencies": {},
"peerDependencies": {},
"optionalDependencies": {}
},
"exactDependency": {
"artplayer-plugin-hls-control": "1.1.0"
},
"restore": "Restore the saved application manifest/lock and matching artifacts, then frozen-install the previous exact version with its tested core/plugin combination.",
"status": "archive-verified-batch-rehearsal-required"
},
{
"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."
},
"currentDependencies": {},
"currentPeers": {},
"currentOptionalDependencies": {},
"previous": {
"name": "artplayer-plugin-jassub",
"version": "1.1.0",
"tarball": "https://registry.npmjs.org/artplayer-plugin-jassub/-/artplayer-plugin-jassub-1.1.0.tgz",
"integrity": "sha512-vQwEtZMmkwr5utbxfUAeE/gRccl2nfcz+SYP2Subn/L8cVIFdMXWMgicm7ZkPVtAoBbCav7p0yP4pwXkCKMzAg==",
"sha256": "00713b4eee4b90e7dc13d1f2c88bc785f9b62f3903b00be6f7bec738fbb78c57",
"memberCount": 6,
"dependencies": {},
"peerDependencies": {},
"optionalDependencies": {}
},
"exactDependency": {
"artplayer-plugin-jassub": "1.1.0"
},
"restore": "Restore the saved application manifest/lock and matching artifacts, then frozen-install the previous exact version with its tested core/plugin combination.",
"status": "archive-verified-batch-rehearsal-required"
},
{
"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."
},
"currentDependencies": {},
"currentPeers": {},
"currentOptionalDependencies": {},
"previous": {
"name": "artplayer-plugin-multiple-subtitles",
"version": "1.2.0",
"tarball": "https://registry.npmjs.org/artplayer-plugin-multiple-subtitles/-/artplayer-plugin-multiple-subtitles-1.2.0.tgz",
"integrity": "sha512-3mzoHkgKx3KVdxKQBwLkjetPF6WjJtro5uQNdse8FqUeiV0afbywLnP6H8lcDN/Fwgp2VlBknrs2+COiyFdV8g==",
"sha256": "a013fbe0772bf560d177449fc80e848c41f16ae2f72ace86f8fb0d5b1000a54e",
"memberCount": 6,
"dependencies": {},
"peerDependencies": {},
"optionalDependencies": {}
},
"exactDependency": {
"artplayer-plugin-multiple-subtitles": "1.2.0"
},
"restore": "Restore the saved application manifest/lock and matching artifacts, then frozen-install the previous exact version with its tested core/plugin combination.",
"status": "archive-verified-batch-rehearsal-required"
},
{
"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."
},
"currentDependencies": {
"@alugha/ima": "2.1.0",
"@glomex/vast-ima-player": "^1.21.2"
},
"currentPeers": {},
"currentOptionalDependencies": {},
"previous": {
"name": "artplayer-plugin-vast",
"version": "1.0.0",
"tarball": "https://registry.npmjs.org/artplayer-plugin-vast/-/artplayer-plugin-vast-1.0.0.tgz",
"integrity": "sha512-6ia0BYlZMBUAbvfqPq6RD3kvCP/Vpnqjs4zgzcio0CUh3a/iJkp+caKFOFjROfilbL+B0lXUiA7U1kS9KqEwTg==",
"sha256": "35681d60fa3fa09b92f0f30887e39c73cf55e7b14d51a1c38fccbc6c1f253bab",
"memberCount": 6,
"dependencies": {
"@glomex/vast-ima-player": "^1.21.0"
},
"peerDependencies": {},
"optionalDependencies": {}
},
"exactDependency": {
"artplayer-plugin-vast": "1.0.0"
},
"restore": "Restore the saved application manifest/lock and matching artifacts, then frozen-install the previous exact version with its tested core/plugin combination.",
"status": "archive-verified-batch-rehearsal-required"
},
{
"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."
},
"currentDependencies": {},
"currentPeers": {},
"currentOptionalDependencies": {},
"previous": {
"name": "artplayer-plugin-vtt-thumbnail",
"version": "1.1.0",
"tarball": "https://registry.npmjs.org/artplayer-plugin-vtt-thumbnail/-/artplayer-plugin-vtt-thumbnail-1.1.0.tgz",
"integrity": "sha512-p5HIiaxBEN0CV4FWOWXCfy6hcQL59JuFIGUbQtVg9AcunPg2gJ1cJ8P0HEQ/ngH6tl1/puxrJIKydoAdX5L2dw==",
"sha256": "e2df36283538bbffc2c37fe28a8af0fe6fc0fa68a5f6cf93042929ee64d55765",
"memberCount": 6,
"dependencies": {},
"peerDependencies": {},
"optionalDependencies": {}
},
"exactDependency": {
"artplayer-plugin-vtt-thumbnail": "1.1.0"
},
"restore": "Restore the saved application manifest/lock and matching artifacts, then frozen-install the previous exact version with its tested core/plugin combination.",
"status": "archive-verified-batch-rehearsal-required"
},
{
"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."
},
"currentDependencies": {},
"currentPeers": {},
"currentOptionalDependencies": {},
"previous": {
"name": "artplayer-proxy-canvas",
"version": "1.1.0",
"tarball": "https://registry.npmjs.org/artplayer-proxy-canvas/-/artplayer-proxy-canvas-1.1.0.tgz",
"integrity": "sha512-lOq22z+xX2DcpokDSMQrDTk4VGDwUaQm9SovQ0GlhgmvMBdxRBDlvv6QrVxFUFsiGUjXEdx9TmtzdiW38dlxiQ==",
"sha256": "348c1d6671f79d0e0dbf2bb15875a1e6d60d00b73cde833c018bec7e4be96c8e",
"memberCount": 6,
"dependencies": {},
"peerDependencies": {},
"optionalDependencies": {}
},
"exactDependency": {
"artplayer-proxy-canvas": "1.1.0"
},
"restore": "Restore the saved application manifest/lock and matching artifacts, then frozen-install the previous exact version with its tested core/plugin combination.",
"status": "archive-verified-batch-rehearsal-required"
},
{
"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."
},
"currentDependencies": {
"mediabunny": "^1.43.1"
},
"currentPeers": {},
"currentOptionalDependencies": {},
"previous": {
"name": "artplayer-proxy-mediabunny",
"version": "1.2.0",
"tarball": "https://registry.npmjs.org/artplayer-proxy-mediabunny/-/artplayer-proxy-mediabunny-1.2.0.tgz",
"integrity": "sha512-uSKkJFvd5Y2XxPPhwtPM5k9qq3xScbWnO/9fOfC8S+Liz8f8nT/iyA2eynU3maVcHZbJgA3PHZm0r6RRmSkefg==",
"sha256": "acb016166059183917a6de2e03a65ad77b09997fc9fb62513d751a24467ca76c",
"memberCount": 6,
"dependencies": {
"mediabunny": "^1.43.1"
},
"peerDependencies": {},
"optionalDependencies": {}
},
"exactDependency": {
"artplayer-proxy-mediabunny": "1.2.0"
},
"restore": "Restore the saved application manifest/lock and matching artifacts, then frozen-install the previous exact version with its tested core/plugin combination.",
"status": "archive-verified-batch-rehearsal-required"
},
{
"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."
},
"currentDependencies": {},
"currentPeers": {},
"currentOptionalDependencies": {},
"previous": {
"name": "artplayer-plugin-iframe",
"version": "1.0.0",
"tarball": "https://registry.npmjs.org/artplayer-plugin-iframe/-/artplayer-plugin-iframe-1.0.0.tgz",
"integrity": "sha512-9eh5NVjZ8/Vxl0ojZxnMY6IlYxCVVfyGH2Rzl7uYDJnGc8NQ4y9vMN/gVh1h8R2m5qaYm7/XDo/45O9a7hDiKg==",
"sha256": "16dc92aa84d93bd99bc0a34815dc49f1609fafc1ff255eb353ea893e70b5f91f",
"memberCount": 8,
"dependencies": {},
"peerDependencies": {},
"optionalDependencies": {}
},
"exactDependency": {
"artplayer-plugin-iframe": "1.0.0"
},
"restore": "Restore application import code, old package name and saved lock together; npm alias alone does not restore the old export/helper surface.",
"status": "archive-verified-batch-rehearsal-required"
},
{
"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."
},
"currentDependencies": {},
"currentPeers": {},
"currentOptionalDependencies": {},
"restore": "No intact historical npm tarball. Preserve a complete pre-upgrade application/artifact snapshot and verify it before any candidate deployment.",
"status": "complete-rollback-artifact-required",
"evidence": "refactor/baselines/thumbnail-release.json"
},
{
"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."
},
"currentDependencies": {},
"currentPeers": {},
"currentOptionalDependencies": {},
"restore": "Previously verified Pages artifact, exact source SHA and URL map; npm rollback does not apply.",
"status": "previous-deployment-artifact-required",
"evidence": "refactor/pages-deployment.md"
}
]
}
@@ -0,0 +1,642 @@
{
"task": "REL-04",
"status": "checkpoint",
"capturedAt": "2026-09-14T16:27:46.622Z",
"source": "aa29dc9212cca5ec61d90ee8145e2b2c173cf23b",
"toolchain": {
"node": "24.21.0",
"yarn": "1.22.22"
},
"iframe": {
"command": "yarn test:rollback:iframe",
"report": "refactor/.cache/iframe-rollback/run-6AFtyo/report.json",
"sha256": "c93fe7a9ffea5653ad3b5769ee70e69082da37bbd883fd0f1641ab848d494811",
"task": "REL-04",
"capturedAt": "2026-09-14T16:26:50.781Z",
"source": "aa29dc9212cca5ec61d90ee8145e2b2c173cf23b",
"node": "24.21.0",
"yarn": "1.22.22",
"passed": true,
"scope": "Old package name and application imports restored together; independent renamed-package installation rehearsal",
"profiles": {
"old": {
"name": "artplayer-plugin-iframe",
"version": "1.0.0",
"archive": "refactor/.cache/releases/artplayer-plugin-iframe-1.0.0.tgz",
"sha256": "16dc92aa84d93bd99bc0a34815dc49f1609fafc1ff255eb353ea893e70b5f91f",
"lockSha256": "a2d4f7ebecc41729792b66392971384eb497cf27c261d87c3f6cfe7c29b03e6a",
"applicationSha256": "9e869c95f4907957ff820828afd1ba6f13457e46aeb790f93c5bb619a725a315",
"files": {
"package/README.md": "52c0a1199b6529d60ce8f4414984f9bd1bc3a8daa5bb6cfec8f0aae0c8338e46",
"package/dist/artplayer-helper-iframe.js": "47730ed608f0637594549b9762c01191686a4a55b3bd467132304a016d227f4a",
"package/dist/artplayer-helper-iframe.legacy.js": "f4c3b11806cad6d984f03a2d9de63deba78cbe538ffe4044a106b0dddc5d4197",
"package/dist/artplayer-plugin-iframe.js": "df469c3e4582b127f35798494d0845f1fc4ca2d19bbaf01b918492e035479745",
"package/dist/artplayer-plugin-iframe.legacy.js": "15299cb5c848cc61614f7573a802443d69b7c6d0a938a1867835c7cbb4884edd",
"package/package.json": "563a6c8407753b92b86319a5e6d9d567525146c32cb010b45b0d88ff481859f6",
"package/src/index.js": "2e0b9efeedef0d6c4f2d2b526db35f634cd02af044e4e28dd1a0b26a5b9a02bf",
"package/types/artplayer-plugin-iframe.d.ts": "d55d99a2488d60892a0ab82882dc96a0f373dab0c3d2a3cee978bc35aa7c2d0c"
}
},
"candidate": {
"name": "artplayer-tool-iframe",
"version": "1.1.0",
"archive": "refactor/.cache/iframe-rollback/run-6AFtyo/candidate.tgz",
"sha256": "0e803ab0c653e82f9e310712d9929a56d4b781d9ccfadc50fe741c28fd2d25f1",
"lockSha256": "657b0a2f29c651b10f6ceb37eb84ac3d27b3ce5de949b74dc59c748b564b2634",
"applicationSha256": "360e8113b39489aef5a4ecafc0ce798db77b75035694653514c82da671d4782e",
"files": {
"package/ARCHITECTURE.md": "dcb0d58fd02730a7a2cc620e4e7e934fae22dbbf85cc9a6eae21ee5ac2cba167",
"package/README.md": "ef5af60bcb831667a78cc3421f6e3c7b5d9a50b3d2e571c5cd02d46fc533e9ce",
"package/dist/artplayer-tool-iframe.js": "7408fceccb000e1b304abe30ac9bc364da7860c3896ad5aaa6af427804c237e6",
"package/dist/artplayer-tool-iframe.legacy.js": "4e8bd39cdc37e98bff9cd19b5e0c9453ebe5231264242fc1c573aa96edb5ea8a",
"package/dist/artplayer-tool-iframe.mjs": "ac4c5efa670c0429fc95bdab7902df60f5b5540f0dad9c7e65c2bd660796dc83",
"package/package.json": "0de9c86eeb3f9a2fb46f9297105b5beddb8d6164163ffaa9874cf5cd68ba7a58",
"package/types/artplayer-tool-iframe.d.cts": "66850135f45c81d4bd7a0de33b6a2f63a655de9000f2698f43660039727919f3",
"package/types/artplayer-tool-iframe.d.mts": "cd394fd68e61fab3c4474daab29852a3f87dd0086b007e7937106871272676fb",
"package/types/artplayer-tool-iframe.d.ts": "e760de22559caf7ffff48d7c5713485bf9f0ddbe876a93080295fb6106535bfa"
}
}
},
"steps": [
{
"action": "install-predecessor",
"profile": "old",
"installed": {
"name": "artplayer-plugin-iframe",
"version": "1.0.0",
"archiveSha256": "16dc92aa84d93bd99bc0a34815dc49f1609fafc1ff255eb353ea893e70b5f91f",
"verifiedMembers": 8
},
"runtime": "old import and helper restored",
"types": {
"compiler": "5.9.3",
"mode": "node10-commonjs",
"diagnostics": []
},
"otherPackageRemoved": true,
"applicationRestored": true
},
{
"action": "upgrade-renamed-package",
"profile": "candidate",
"installed": {
"name": "artplayer-tool-iframe",
"version": "1.1.0",
"archiveSha256": "0e803ab0c653e82f9e310712d9929a56d4b781d9ccfadc50fe741c28fd2d25f1",
"verifiedMembers": 9
},
"runtime": "candidate import and legacy verified",
"types": {
"compiler": "5.9.3",
"mode": "node10-commonjs",
"diagnostics": []
},
"otherPackageRemoved": true,
"applicationRestored": true
},
{
"action": "restore-predecessor",
"profile": "old",
"installed": {
"name": "artplayer-plugin-iframe",
"version": "1.0.0",
"archiveSha256": "16dc92aa84d93bd99bc0a34815dc49f1609fafc1ff255eb353ea893e70b5f91f",
"verifiedMembers": 8
},
"runtime": "old import and helper restored",
"types": {
"compiler": "5.9.3",
"mode": "node10-commonjs",
"diagnostics": []
},
"otherPackageRemoved": true,
"applicationRestored": true
}
],
"aliasNegative": {
"passed": true,
"result": "old tarball under new dependency key does not preserve candidate constructor/legacy",
"scope": "Local file dependency key alias, not npm registry alias resolution"
}
},
"mainline": {
"command": "yarn test:rollback:mainline",
"report": "refactor/.cache/mainline-rehearsal/run-zTEWy5/report.json",
"sha256": "59ee81a3ead7d04a8c8cbb6f097ba4755643ec49a7bf710ab092e13f4f617fcb",
"patch": {
"path": "refactor/.cache/mainline-rehearsal/run-zTEWy5/resolved.patch",
"sha256": "debee370777d5d3175affe6c9406c4e1897c5b11d60590c12dabc41a533bdfc3"
},
"bundle": {
"path": "refactor/.cache/mainline-rehearsal/run-zTEWy5/rehearsal.bundle",
"sha256": "a5fe84987a8efa44b70693b08c91e0d0d955ea53f194291c156153fe080c72f2"
},
"task": "REL-04",
"capturedAt": "2026-09-14T16:24:22.347Z",
"source": "aa29dc9212cca5ec61d90ee8145e2b2c173cf23b",
"node": "24.21.0",
"passed": true,
"scope": "Synthetic isolated JS-to-TS modify/delete conflict; no production fix or remote mutation",
"steps": [
{
"executable": "git",
"args": [
"init",
"--initial-branch=master"
],
"status": 0,
"stdout": "Initialized empty Git repository in C:/Users/laozh/AppData/Local/Temp/artplayer-consumer-Etvdz1/.git/\n",
"stderr": ""
},
{
"executable": "git",
"args": [
"config",
"user.name",
"ArtPlayer isolated rehearsal"
],
"status": 0,
"stdout": "",
"stderr": ""
},
{
"executable": "git",
"args": [
"config",
"user.email",
"rehearsal@example.invalid"
],
"status": 0,
"stdout": "",
"stderr": ""
},
{
"executable": "git",
"args": [
"config",
"commit.gpgsign",
"false"
],
"status": 0,
"stdout": "",
"stderr": ""
},
{
"executable": "git",
"args": [
"config",
"core.autocrlf",
"false"
],
"status": 0,
"stdout": "",
"stderr": ""
},
{
"executable": "git",
"args": [
"add",
"."
],
"status": 0,
"stdout": "",
"stderr": ""
},
{
"executable": "git",
"args": [
"commit",
"-m",
"fixture: original JavaScript implementation"
],
"status": 0,
"stdout": "[master (root-commit) a10e48e] fixture: original JavaScript implementation\n 3 files changed, 9 insertions(+)\n create mode 100644 consumer.mjs\n create mode 100644 package.json\n create mode 100644 src/retry.js\n",
"stderr": ""
},
{
"executable": "git",
"args": [
"rev-parse",
"HEAD"
],
"status": 0,
"stdout": "a10e48e0c1f7a712fd602d5d8afcacef9a5e68b0\n",
"stderr": ""
},
{
"executable": "node",
"args": [
"consumer.mjs"
],
"status": 1,
"stdout": "",
"stderr": "node:internal/modules/run_main:107\r\n triggerUncaughtException(\r\n ^\r\n\r\nAssertionError [ERR_ASSERTION]: Expected values to be strictly equal:\r\n\r\n-2 !== 0\r\n\r\n at file:///C:/Users/laozh/AppData/Local/Temp/artplayer-consumer-Etvdz1/consumer.mjs:4:8\r\n at ModuleJob.run (node:internal/modules/esm/module_job:561:25)\r\n at async node:internal/modules/esm/loader:647:26\r\n at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:101:5) {\r\n generatedMessage: true,\r\n code: 'ERR_ASSERTION',\r\n actual: -2,\r\n expected: 0,\r\n operator: 'strictEqual',\r\n diff: 'simple'\r\n}\r\n\r\nNode.js v24.21.0\r\n"
},
{
"executable": "git",
"args": [
"switch",
"-c",
"codex/rehearsal"
],
"status": 0,
"stdout": "",
"stderr": "Switched to a new branch 'codex/rehearsal'\n"
},
{
"executable": "git",
"args": [
"add",
"."
],
"status": 0,
"stdout": "",
"stderr": ""
},
{
"executable": "git",
"args": [
"commit",
"-m",
"fixture: split retry policy into TypeScript modules"
],
"status": 0,
"stdout": "[codex/rehearsal 61a9230] fixture: split retry policy into TypeScript modules\n 4 files changed, 4 insertions(+), 4 deletions(-)\n create mode 100644 src/normalize.ts\n delete mode 100644 src/retry.js\n create mode 100644 src/retry.ts\n",
"stderr": ""
},
{
"executable": "git",
"args": [
"rev-parse",
"HEAD"
],
"status": 0,
"stdout": "61a92304ff91bfb87f6c1a08765276187b227cf3\n",
"stderr": ""
},
{
"executable": "node",
"args": [
"consumer.mjs"
],
"status": 1,
"stdout": "",
"stderr": "node:internal/modules/run_main:107\r\n triggerUncaughtException(\r\n ^\r\n\r\nAssertionError [ERR_ASSERTION]: Expected values to be strictly equal:\r\n\r\n-2 !== 0\r\n\r\n at file:///C:/Users/laozh/AppData/Local/Temp/artplayer-consumer-Etvdz1/consumer.mjs:4:8\r\n at ModuleJob.run (node:internal/modules/esm/module_job:561:25)\r\n at async node:internal/modules/esm/loader:647:26\r\n at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:101:5) {\r\n generatedMessage: true,\r\n code: 'ERR_ASSERTION',\r\n actual: -2,\r\n expected: 0,\r\n operator: 'strictEqual',\r\n diff: 'simple'\r\n}\r\n\r\nNode.js v24.21.0\r\n"
},
{
"executable": "git",
"args": [
"switch",
"master"
],
"status": 0,
"stdout": "",
"stderr": "Switched to branch 'master'\n"
},
{
"executable": "git",
"args": [
"add",
"src/retry.js"
],
"status": 0,
"stdout": "",
"stderr": ""
},
{
"executable": "git",
"args": [
"commit",
"-m",
"fixture: clamp negative retry limits"
],
"status": 0,
"stdout": "[master fb62f91] fixture: clamp negative retry limits\n 1 file changed, 1 insertion(+), 1 deletion(-)\n",
"stderr": ""
},
{
"executable": "git",
"args": [
"rev-parse",
"HEAD"
],
"status": 0,
"stdout": "fb62f918310b9cd01ca6deddff6acbb1eecd55c9\n",
"stderr": ""
},
{
"executable": "node",
"args": [
"consumer.mjs"
],
"status": 0,
"stdout": "retry limit regression passed\n",
"stderr": ""
},
{
"executable": "git",
"args": [
"switch",
"codex/rehearsal"
],
"status": 0,
"stdout": "",
"stderr": "Switched to branch 'codex/rehearsal'\n"
},
{
"executable": "git",
"args": [
"status",
"--porcelain"
],
"status": 0,
"stdout": "",
"stderr": ""
},
{
"executable": "git",
"args": [
"rev-parse",
"HEAD^{tree}"
],
"status": 0,
"stdout": "9ed1ebc390513ca0f09c3c4fc20700b38fc2e614\n",
"stderr": ""
},
{
"executable": "git",
"args": [
"cherry-pick",
"-x",
"fb62f918310b9cd01ca6deddff6acbb1eecd55c9"
],
"status": 1,
"stdout": "CONFLICT (modify/delete): src/retry.js deleted in HEAD and modified in fb62f91 (fixture: clamp negative retry limits). Version fb62f91 (fixture: clamp negative retry limits) of src/retry.js left in tree.\n",
"stderr": "error: could not apply fb62f91... fixture: clamp negative retry limits\nhint: After resolving the conflicts, mark them with\nhint: \"git add/rm <pathspec>\", then run\nhint: \"git cherry-pick --continue\".\nhint: You can instead skip this commit with \"git cherry-pick --skip\".\nhint: To abort and get back to the state before \"git cherry-pick\",\nhint: run \"git cherry-pick --abort\".\nhint: Disable this message with \"git config set advice.mergeConflict false\"\n"
},
{
"executable": "git",
"args": [
"ls-files",
"--unmerged"
],
"status": 0,
"stdout": "100644 44a1d3c1960ca191cb02699472e845df31381af2 1\tsrc/retry.js\n100644 826028d2df226a0d8c67fd275fbe689ae86b39c7 3\tsrc/retry.js\n",
"stderr": ""
},
{
"executable": "git",
"args": [
"rev-parse",
"CHERRY_PICK_HEAD"
],
"status": 0,
"stdout": "fb62f918310b9cd01ca6deddff6acbb1eecd55c9\n",
"stderr": ""
},
{
"executable": "git",
"args": [
"cherry-pick",
"--abort"
],
"status": 0,
"stdout": "",
"stderr": ""
},
{
"executable": "git",
"args": [
"rev-parse",
"HEAD"
],
"status": 0,
"stdout": "61a92304ff91bfb87f6c1a08765276187b227cf3\n",
"stderr": ""
},
{
"executable": "git",
"args": [
"rev-parse",
"HEAD^{tree}"
],
"status": 0,
"stdout": "9ed1ebc390513ca0f09c3c4fc20700b38fc2e614\n",
"stderr": ""
},
{
"executable": "git",
"args": [
"status",
"--porcelain"
],
"status": 0,
"stdout": "",
"stderr": ""
},
{
"executable": "git",
"args": [
"cherry-pick",
"-x",
"fb62f918310b9cd01ca6deddff6acbb1eecd55c9"
],
"status": 1,
"stdout": "CONFLICT (modify/delete): src/retry.js deleted in HEAD and modified in fb62f91 (fixture: clamp negative retry limits). Version fb62f91 (fixture: clamp negative retry limits) of src/retry.js left in tree.\n",
"stderr": "error: could not apply fb62f91... fixture: clamp negative retry limits\nhint: After resolving the conflicts, mark them with\nhint: \"git add/rm <pathspec>\", then run\nhint: \"git cherry-pick --continue\".\nhint: You can instead skip this commit with \"git cherry-pick --skip\".\nhint: To abort and get back to the state before \"git cherry-pick\",\nhint: run \"git cherry-pick --abort\".\nhint: Disable this message with \"git config set advice.mergeConflict false\"\n"
},
{
"executable": "git",
"args": [
"rm",
"src/retry.js"
],
"status": 0,
"stdout": "rm 'src/retry.js'\n",
"stderr": ""
},
{
"executable": "git",
"args": [
"add",
"src/normalize.ts"
],
"status": 0,
"stdout": "",
"stderr": ""
},
{
"executable": "node",
"args": [
"consumer.mjs"
],
"status": 0,
"stdout": "retry limit regression passed\n",
"stderr": ""
},
{
"executable": "git",
"args": [
"-c",
"core.editor=true",
"cherry-pick",
"--continue"
],
"status": 0,
"stdout": "[codex/rehearsal db16876] fixture: clamp negative retry limits\n Date: Tue Sep 15 00:24:23 2026 +0800\n 1 file changed, 1 insertion(+), 1 deletion(-)\n",
"stderr": ""
},
{
"executable": "git",
"args": [
"rev-parse",
"HEAD"
],
"status": 0,
"stdout": "db16876b0622d8dd6337a2bebcf7bca9e743fa31\n",
"stderr": ""
},
{
"executable": "git",
"args": [
"log",
"-1",
"--format=%B"
],
"status": 0,
"stdout": "fixture: clamp negative retry limits\n\n(cherry picked from commit fb62f918310b9cd01ca6deddff6acbb1eecd55c9)\n\n",
"stderr": ""
},
{
"executable": "git",
"args": [
"status",
"--porcelain"
],
"status": 0,
"stdout": "",
"stderr": ""
},
{
"executable": "git",
"args": [
"ls-files",
"src/retry.js"
],
"status": 0,
"stdout": "",
"stderr": ""
},
{
"executable": "node",
"args": [
"consumer.mjs"
],
"status": 0,
"stdout": "retry limit regression passed\n",
"stderr": ""
},
{
"executable": "git",
"args": [
"show",
"--format=fuller",
"--binary",
"db16876b0622d8dd6337a2bebcf7bca9e743fa31"
],
"status": 0,
"stdout": "commit db16876b0622d8dd6337a2bebcf7bca9e743fa31\nAuthor: ArtPlayer isolated rehearsal <rehearsal@example.invalid>\nAuthorDate: Tue Sep 15 00:24:23 2026 +0800\nCommit: ArtPlayer isolated rehearsal <rehearsal@example.invalid>\nCommitDate: Tue Sep 15 00:24:24 2026 +0800\n\n fixture: clamp negative retry limits\n \n (cherry picked from commit fb62f918310b9cd01ca6deddff6acbb1eecd55c9)\n\ndiff --git a/src/normalize.ts b/src/normalize.ts\nindex 74a0d7c..2251bcd 100644\n--- a/src/normalize.ts\n+++ b/src/normalize.ts\n@@ -1 +1 @@\n-export const normalizeLimit = (value: number): number => Math.trunc(value);\n+export const normalizeLimit = (value: number): number => Math.max(0, Math.trunc(value));\n",
"stderr": ""
},
{
"executable": "git",
"args": [
"bundle",
"create",
"D:\\github\\ArtPlayer\\refactor\\.cache\\mainline-rehearsal\\run-zTEWy5\\rehearsal.bundle",
"--all"
],
"status": 0,
"stdout": "",
"stderr": ""
},
{
"executable": "git",
"args": [
"bundle",
"verify",
"D:\\github\\ArtPlayer\\refactor\\.cache\\mainline-rehearsal\\run-zTEWy5\\rehearsal.bundle"
],
"status": 0,
"stdout": "The bundle contains these 3 refs:\ndb16876b0622d8dd6337a2bebcf7bca9e743fa31 refs/heads/codex/rehearsal\nfb62f918310b9cd01ca6deddff6acbb1eecd55c9 refs/heads/master\ndb16876b0622d8dd6337a2bebcf7bca9e743fa31 HEAD\nThe bundle records a complete history.\nThe bundle uses this hash algorithm: sha1\n",
"stderr": "D:/github/ArtPlayer/refactor/.cache/mainline-rehearsal/run-zTEWy5/rehearsal.bundle is okay\n"
}
],
"commits": {
"base": "a10e48e0c1f7a712fd602d5d8afcacef9a5e68b0",
"migrated": "61a92304ff91bfb87f6c1a08765276187b227cf3",
"fix": "fb62f918310b9cd01ca6deddff6acbb1eecd55c9",
"resolved": "db16876b0622d8dd6337a2bebcf7bca9e743fa31"
},
"abort": {
"restoredCommit": "61a92304ff91bfb87f6c1a08765276187b227cf3",
"restoredTree": "9ed1ebc390513ca0f09c3c4fc20700b38fc2e614",
"clean": true
},
"resolution": {
"source": "src/retry.js",
"destination": "src/normalize.ts",
"obsoleteFileAbsent": true,
"regressionPassed": true,
"attributed": true
}
},
"inventory": {
"command": "yarn check:rollback-inventory",
"packages": 22,
"verifiedArchives": 20,
"file": "refactor/baselines/rollback-inventory.json",
"sha256": "33d083badfc54f4be5be42518b03fb499062ab724bc4f4ebb3c1dec32b9bfb8a",
"deferredArtifacts": [
{
"name": "artplayer-tool-thumbnail",
"status": "complete-rollback-artifact-required"
},
{
"name": "artplayer-vitepress",
"status": "previous-deployment-artifact-required"
}
]
},
"realRepository": {
"command": "git fetch origin master",
"log": "refactor/.cache/rel04-mainline-fetch-final.log",
"exitCode": 0,
"master": "40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f",
"ahead": 257,
"behind": 0,
"incomingFixes": [],
"cherryPicksApplied": []
},
"engineering": {
"status": "passed",
"log": "refactor/.cache/rel04-workflows-engineering.log",
"exitCode": 0,
"commands": [
"scoped ESLint for three workflow modules",
"node --test test/rollback-files.test.js test/package-runtime.test.js refactor/scripts/ci-workflow.test.mjs",
"yarn check:rollback-inventory",
"yarn check:toolchain --strict",
"node refactor/scripts/plan.mjs --check",
"git diff --check"
]
},
"limitations": [
"REL-04 remains doing: complete Thumbnail fallback and Pages recovery evidence remain.",
"Iframe browser/device acceptance remains in PKG-IFRAME-05; these are real installation/import/type checks.",
"Conflict exercise is synthetic and does not claim a production retry bug or repair.",
"Registry mutable tags and current availability were not queried or changed.",
"All final publication batches must bind actual next-major candidates and repeat relevant rollback checks.",
"No dependencies, production source, root lock, distribution outputs, remote CI runs, push, tag or deployment changes."
]
}
@@ -0,0 +1,30 @@
# REL-04 更名回退、全包清单和主线同步检查点
本次不修改生产运行时代码。新增三个有明确边界的工程入口,无新依赖:
- `test:rollback:iframe`:正常隔离构建 tool 包,旧 plugin→新 tool→恢复旧 plugin 的实际冻结安装、成员检查、应用导入恢复、旧包清理、strict TS 消费者及本地依赖键映射负例。
- `test:rollback:mainline`:独立临时 Git 仓库内实际执行 JS→TS modify/delete 冲突、abort 恢复、手动转移修复和带来源的 continue;保留命令输出、patch 与 bundle。
- `check:rollback-inventory`:验证 22 包生成清单,20 份完整历史 tarball 的 SHA-512/SHA-256、旧/新 manifest 依赖与目标版本。加入 `ci:check` 防止清单漂移。
实际命令均通过,具体记录/哈希见
[机器证据](../baselines/rollback-workflows-validation.json)。scoped lint、生成清单
检查、严格工具链和相关 Node 工程回归通过;没有将本地 CI 配置描述成远端运行通过。
## 兼容边界
旧 iframe 的 require 返回对象、新版返回构造函数;旧 helper 独立。负例确认只把
旧 tarball 映射到新依赖键不能透明回退。已测试方案同时恢复旧应用导入、manifest
和 lock;没有改生产 API,也没有引入未经授权的新包名兼容策略。
主线负数修复是合成 fixture,不是 ArtPlayer 生产缺陷。先验证旧/迁移实现的准确
断言失败,再验证主线和迁移后修复通过;不能用任意 Node 报错代替缺陷复现。
真实仓库 fetch 后无新增 master 修复,未修改其历史/分支/远端。
## 交接
维护职责和完整执行步骤在[回退维护](../rollback-rehearsal.md);包内架构说明已
补更名导出差异和演练入口。恢复表从发布台账生成,不额外维护第二份版本来源。
REL-04 继续 doing:Thumbnail 原完整包缺失,完整替代回退产物和站点恢复证据仍
需处理。全部正式批次需重新绑定最终候选,不把早期核心/Chapter/iframe 演练推广
为其他包或物理设备的完成证据。撤销本工程检查点不会改变生产行为或远端设置。
+1 -1
View File
@@ -658,7 +658,7 @@
- MOD-DEV-01: [记录](changes/2026-09-14-MOD-DEV-01-dev-server.md) [记录](baselines/dev-server-validation.json)
- MOD-03: [记录](changes/2026-09-14-MOD-03-slider-updates.md) [记录](baselines/slider-updates-validation.json) [记录](changes/2026-09-14-MOD-03-performance-acceptance.md) [记录](baselines/core-performance-validation.json)
- REL-08: [记录](changes/2026-09-14-REL-08-release-ledger.md) [记录](baselines/release-ledger-validation.json) [记录](release-ledger.md) [记录](release-ledger.json)
- REL-04: [记录](changes/2026-09-15-REL-04-consumer-checkpoint.md) [记录](rollback-rehearsal.md) [记录](baselines/rollback-consumer-validation.json)
- REL-04: [记录](changes/2026-09-15-REL-04-consumer-checkpoint.md) [记录](rollback-rehearsal.md) [记录](baselines/rollback-consumer-validation.json) [记录](changes/2026-09-15-REL-04-workflows-checkpoint.md) [记录](rollback-inventory.md) [记录](baselines/rollback-workflows-validation.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)
+9
View File
@@ -1,5 +1,14 @@
# 进度与证据
## REL-04 更名回退与主线工作流检查点
完成旧 iframe 包→新 tool 包→恢复旧包的实际冻结安装、应用导入/类型/成员核验,
并复现仅映射包名无法保持新构造/legacy 用法。独立 Git 仓库内 JS→TS 冲突的
中止恢复与修复同步均通过;真实 master 无新增待同步提交。22 包恢复清单生成,
20 份完整旧 archive 的双哈希验证通过,新增清单 CI 检查。详见
[记录](changes/2026-09-15-REL-04-workflows-checkpoint.md)。Thumbnail 完整替代
回退产物和站点恢复证据尚缺,REL-04 继续 doing,任务完成数不变。
## REL-04 核心/Chapter 回退演练检查点
新增实际隔离安装回退脚本,七步安装/升级/分别回退/整体恢复通过,237 项运行时
+40
View File
@@ -0,0 +1,40 @@
# 逐包回退清单
由 `node refactor/scripts/rollback-inventory.mjs --write` 生成;`--check` 校验同步并复核冻结 tarball 完整性。
本表核对 20 份完整历史 archive 的 SHA-512/SHA-256 和 manifest。它不表示当前 registry 可用,
也不表示全部包的实际回退已验收。机器清单同时保存旧/新 dependencies、peerDependencies 与精确 integrity。
| 当前包 | 目标版本 | 恢复版本/对象 | 旧包运行依赖 | 恢复状态 |
| --- | --- | --- | --- | --- |
| artplayer | 6.0.0 | artplayer@5.4.0 | option-validator@^2.0.6 | archive-verified-batch-rehearsal-required |
| artplayer-plugin-ads | 3.0.0 | artplayer-plugin-ads@1.0.6 | 无声明 | archive-verified-batch-rehearsal-required |
| artplayer-plugin-ambilight | 2.0.0 | artplayer-plugin-ambilight@1.1.0 | 无声明 | archive-verified-batch-rehearsal-required |
| artplayer-plugin-asr | 3.0.0 | artplayer-plugin-asr@2.1.0 | 无声明 | archive-verified-batch-rehearsal-required |
| artplayer-plugin-audio-track | 2.0.0 | artplayer-plugin-audio-track@1.1.0 | 无声明 | archive-verified-batch-rehearsal-required |
| artplayer-plugin-auto-thumbnail | 2.0.0 | artplayer-plugin-auto-thumbnail@1.1.0 | 无声明 | archive-verified-batch-rehearsal-required |
| artplayer-plugin-chapter | 2.0.0 | artplayer-plugin-chapter@1.1.0 | 无声明 | archive-verified-batch-rehearsal-required |
| artplayer-plugin-chromecast | 2.0.0 | artplayer-plugin-chromecast@1.1.0 | 无声明 | archive-verified-batch-rehearsal-required |
| artplayer-plugin-danmuku | 6.0.0 | artplayer-plugin-danmuku@5.3.0 | 无声明 | archive-verified-batch-rehearsal-required |
| artplayer-plugin-danmuku-mask | 2.0.0 | artplayer-plugin-danmuku-mask@1.1.0 | @mediapipe/selfie_segmentation@^0.1.1675465747, @tensorflow-models/body-segmentation@^1.0.2, @tensorflow/tfjs-backend-cpu@^4.21.0, @tensorflow/tfjs-backend-webgl@^4.21.0, @tensorflow/tfjs-converter@^4.21.0, @tensorflow/tfjs-core@^4.21.0 | archive-verified-batch-rehearsal-required |
| artplayer-plugin-dash-control | 2.0.0 | artplayer-plugin-dash-control@1.1.0 | 无声明 | archive-verified-batch-rehearsal-required |
| artplayer-plugin-document-pip | 2.0.0 | artplayer-plugin-document-pip@1.1.0 | 无声明 | archive-verified-batch-rehearsal-required |
| artplayer-plugin-hls-control | 2.0.0 | artplayer-plugin-hls-control@1.1.0 | 无声明 | archive-verified-batch-rehearsal-required |
| artplayer-plugin-jassub | 2.0.0 | artplayer-plugin-jassub@1.1.0 | 无声明 | archive-verified-batch-rehearsal-required |
| artplayer-plugin-multiple-subtitles | 2.0.0 | artplayer-plugin-multiple-subtitles@1.2.0 | 无声明 | archive-verified-batch-rehearsal-required |
| artplayer-plugin-vast | 2.0.0 | artplayer-plugin-vast@1.0.0 | @glomex/vast-ima-player@^1.21.0 | archive-verified-batch-rehearsal-required |
| artplayer-plugin-vtt-thumbnail | 2.0.0 | artplayer-plugin-vtt-thumbnail@1.1.0 | 无声明 | archive-verified-batch-rehearsal-required |
| artplayer-proxy-canvas | 2.0.0 | artplayer-proxy-canvas@1.1.0 | 无声明 | archive-verified-batch-rehearsal-required |
| artplayer-proxy-mediabunny | 2.0.0 | artplayer-proxy-mediabunny@1.2.0 | mediabunny@^1.43.1 | archive-verified-batch-rehearsal-required |
| artplayer-tool-iframe | 2.0.0 | artplayer-plugin-iframe@1.0.0 | 无声明 | archive-verified-batch-rehearsal-required |
| artplayer-tool-thumbnail | 5.0.0 | 完整旧产物尚缺 | 见原有门槛 | complete-rollback-artifact-required |
| artplayer-vitepress | 2.0.0 | 已验证 Pages 产物 | 见原有门槛 | previous-deployment-artifact-required |
每包优先恢复升级前已验证的应用代码、package.json 和 yarn.lock,再执行 `yarn install --frozen-lockfile`;
离线恢复还需保存所需 tarball/依赖缓存。单独把版本改回去并重新解析依赖,不能替代已测试锁文件。
外部播放器 SDK、worker/WASM、远端媒体和平台能力也要恢复对应配置,npm 依赖表不覆盖它们。
iframe 必须同步恢复旧包名、CommonJS `.default`、helper 路径和应用代码;不将 npm alias 当作兼容门面。
Thumbnail 完整原包缺失以及站点部署恢复仍是发布门槛。正式每批重新绑定候选和回退内容,不能直接复用早期演练结果。
来源:[机器清单](baselines/rollback-inventory.json)、[维护入口](rollback-rehearsal.md)、[发布台账](release-ledger.md)。
+49 -4
View File
@@ -61,10 +61,55 @@ ENG-07 而被拒绝;保留该门槛,回退使用独立验证入口。
详见 [机器证据](baselines/rollback-consumer-validation.json) 和
[检查点](changes/2026-09-15-REL-04-consumer-checkpoint.md)。
尚需:22 包逐包旧版本/tag/依赖恢复表、iframe 更名前后消费者回退、主线修改与
JS→TS 冲突同步/中止演练。已执行 `git fetch origin master`,当时 master 没有
新增待同步提交;这不等于验证过冲突解决。
后续检查点已补齐[22 包恢复清单](rollback-inventory.md)、iframe 更名安装回退以及
JS→TS 冲突同步/中止演练。`yarn check:rollback-inventory` 校验生成表并重新检查
20 份完整冻结 archive;CI 的 `ci:check` 同样运行该检查。缺失 tarball 时只按
冻结 registry URL/完整性恢复缓存,不查询或跟随 latest。
## iframe 更名回退
运行 `yarn test:rollback:iframe`。`scripts/iframe-rollback.mjs` 在隔离构建快照内
用正常构建器打包当前 tool 包,并从冻结来源验证旧 plugin 包。先准备两套应用
manifest、lock 和导入代码,再在同一仓库外消费者中依次安装旧包、升级新包、恢复
旧包。检查完整成员、冻结锁、应用代码哈希和被替换包目录确实消失;各步另有实际
require/helper/legacy 及 TS 5.9.3 strict 消费者验证。
实际旧包 `artplayer-plugin-iframe@1.0.0` 的根 require 返回对象,构造类位于
`.default`,并有独立 helper 文件;新 tool 包根 require 直接返回类。负例已实际
把旧 tarball 安装在新的依赖键下,确认构造和 `/legacy` 均无法沿用新版写法。
这是本地 file 依赖键映射验证,没有声称执行过 registry 的 npm alias 协议。
回退要恢复应用导入代码和包名/锁,不能只换版本或别名。
输出为 `refactor/.cache/iframe-rollback/run-*`,包括两套锁、应用代码、构建及
安装日志和报告。该演练覆盖安装/导出/类型恢复,iframe 真机和跨窗媒体测试仍由
PKG-IFRAME-05 承担,不借此关闭其门槛。
## 主线修复同步与中止
运行 `yarn test:rollback:mainline`。`scripts/mainline-rehearsal.mjs` 在仓库外创建
独立 Git 仓库,合成 JS 计算模块迁移至两个 TS 模块后,主线修复旧文件的情形。
它实际复现负数计算的断言失败、cherry-pick 的 modify/delete 冲突;先中止并验证
HEAD/tree/工作区完全恢复,再次应用并把修复移入 TS 计算模块。最终回归通过、旧 JS
文件不存在、提交中保留 `cherry-pick -x` 来源。原仓库没有被切换分支或 cherry-pick。
Git fixture 只使用目录内身份配置,禁用全局配置;所有命令/退出码、断言、冲突内容、
最终 patch 和可验证 Git bundle 保存到 `refactor/.cache/mainline-rehearsal/run-*`。
失败也写报告,并清理已验证的临时消费者目录。该合成演练验证工作流,不代表发现或
修复了真实 ArtPlayer 重试逻辑问题。
实际同步时先 `git fetch origin master`,记录 `git rev-list --left-right --count
HEAD...origin/master` 及修复 SHA,再审查每个上游 diff。找出迁移后的责任模块,
为原缺陷补可重跑回归,在隔离工作树应用 `git cherry-pick -x <已核实修复SHA>`。
发生冲突时审查 `git status`、`git ls-files --unmerged` 和上游完整 diff,不能为
结束冲突而直接恢复已淘汰 JS 文件。修复迁移后的对应模块、暂存明确文件、运行相关
单元/类型/浏览器检查,再 `git cherry-pick --continue`。无法正确落地则
`git cherry-pick --abort`,核对原 HEAD/tree 和工作区状态后登记待同步项。
每个实际修复仍建立任务、保留上游归属、独立提交并通过提交审计。
本次再次 fetch 后 master 仍无新增提交;真实仓库没有需要应用的修复。冻结起点和
当前差分记录在[后续检查点证据](baselines/rollback-workflows-validation.json)。
Thumbnail 的完整旧 npm tarball 缺失、文档站的真实部署恢复继续受原有发布门槛
约束。正式每批需要重新绑定实际 next-major 候选及旧包产物并重跑对应验证。
约束,REL-04 保持 doing,继续准备完整回退替代产物/站点恢复证据。正式每批需要
重新绑定实际 next-major 候选及旧包产物并重跑对应验证。
不执行 npm tag 修改、unpublish、推送或部署,不把本演练当作这些动作的授权。
+75
View File
@@ -0,0 +1,75 @@
import assert from 'node:assert/strict'
import fs from 'node:fs'
import path from 'node:path'
import process from 'node:process'
import { fileURLToPath } from 'node:url'
import { ensureArchive, readMember } from './releases.mjs'
const root = fileURLToPath(new URL('../../', import.meta.url))
assert(['--write', '--check'].includes(process.argv[2]) && process.argv.length === 3, 'Use --write or --check')
const read = file => JSON.parse(fs.readFileSync(path.join(root, file), 'utf8'))
const ledger = read('refactor/release-ledger.json')
const rows = []
for (const pkg of ledger.packages) {
const baseline = read(pkg.history.file)
const release = pkg.history.selector?.split('.').reduce((value, key) => value?.[key], baseline)
const current = read(`packages/${pkg.name}/package.json`)
const row = { name: pkg.name, targetVersion: pkg.targetVersion, distribution: pkg.distribution, history: pkg.history, currentDependencies: current.dependencies || {}, currentPeers: current.peerDependencies || {}, currentOptionalDependencies: current.optionalDependencies || {} }
if (pkg.distribution === 'site') {
rows.push({ ...row, restore: 'Previously verified Pages artifact, exact source SHA and URL map; npm rollback does not apply.', status: 'previous-deployment-artifact-required', evidence: 'refactor/pages-deployment.md' })
continue
}
if (pkg.distribution === 'recovered-npm') {
assert(release, 'Missing recovered history')
rows.push({ ...row, restore: 'No intact historical npm tarball. Preserve a complete pre-upgrade application/artifact snapshot and verify it before any candidate deployment.', status: 'complete-rollback-artifact-required', evidence: pkg.history.file })
continue
}
assert(release?.integrity && release.sha256 && release.files, `Missing frozen rollback archive: ${pkg.name}`)
const archive = await ensureArchive(release)
const manifest = JSON.parse(readMember(archive, 'package/package.json'))
assert.equal(manifest.name, release.name)
assert.equal(manifest.version, release.version)
rows.push({
...row,
previous: { name: release.name, version: release.version, tarball: release.tarball, integrity: release.integrity, sha256: release.sha256, memberCount: Object.keys(release.files).length, dependencies: manifest.dependencies || {}, peerDependencies: manifest.peerDependencies || {}, optionalDependencies: manifest.optionalDependencies || {} },
exactDependency: { [release.name]: release.version },
restore: pkg.distribution === 'renamed-npm'
? 'Restore application import code, old package name and saved lock together; npm alias alone does not restore the old export/helper surface.'
: 'Restore the saved application manifest/lock and matching artifacts, then frozen-install the previous exact version with its tested core/plugin combination.',
status: 'archive-verified-batch-rehearsal-required',
})
}
assert.equal(rows.length, 22)
const result = { task: 'REL-04', source: 'refactor/release-ledger.json and frozen historical archives', registryAvailabilityRefreshed: false, mutableTagPolicy: 'Use exact versions and saved lock/artifact hashes. No latest/next/rollback tag is assumed or changed.', packages: rows }
const json = `${JSON.stringify(result, null, 2)}\n`
const dependencies = value => Object.entries(value).map(([name, version]) => `${name}@${version}`).join(', ') || '无声明'
const lines = [
'# 逐包回退清单',
'',
'由 `node refactor/scripts/rollback-inventory.mjs --write` 生成;`--check` 校验同步并复核冻结 tarball 完整性。',
'',
'本表核对 20 份完整历史 archive 的 SHA-512/SHA-256 和 manifest。它不表示当前 registry 可用,',
'也不表示全部包的实际回退已验收。机器清单同时保存旧/新 dependencies、peerDependencies 与精确 integrity。',
'',
'| 当前包 | 目标版本 | 恢复版本/对象 | 旧包运行依赖 | 恢复状态 |',
'| --- | --- | --- | --- | --- |',
...rows.map(row => `| ${row.name} | ${row.targetVersion} | ${row.previous ? `${row.previous.name}@${row.previous.version}` : row.distribution === 'site' ? '已验证 Pages 产物' : '完整旧产物尚缺'} | ${row.previous ? dependencies(row.previous.dependencies) : '见原有门槛'} | ${row.status} |`),
'',
'每包优先恢复升级前已验证的应用代码、package.json 和 yarn.lock,再执行 `yarn install --frozen-lockfile`;',
'离线恢复还需保存所需 tarball/依赖缓存。单独把版本改回去并重新解析依赖,不能替代已测试锁文件。',
'外部播放器 SDK、worker/WASM、远端媒体和平台能力也要恢复对应配置,npm 依赖表不覆盖它们。',
'',
'iframe 必须同步恢复旧包名、CommonJS `.default`、helper 路径和应用代码;不将 npm alias 当作兼容门面。',
'Thumbnail 完整原包缺失以及站点部署恢复仍是发布门槛。正式每批重新绑定候选和回退内容,不能直接复用早期演练结果。',
'',
'来源:[机器清单](baselines/rollback-inventory.json)、[维护入口](rollback-rehearsal.md)、[发布台账](release-ledger.md)。',
'',
]
const markdown = lines.join('\n')
for (const [file, contents] of [['refactor/baselines/rollback-inventory.json', json], ['refactor/rollback-inventory.md', markdown]]) {
if (process.argv[2] === '--write')
fs.writeFileSync(path.join(root, file), contents)
else
assert.equal(fs.readFileSync(path.join(root, file), 'utf8').replaceAll('\r\n', '\n'), contents, `Stale rollback inventory: ${file}`)
}
console.log(`Rollback inventory: ${rows.length} packages, ${rows.filter(row => row.previous).length} verified historical archives, remaining batch gates retained`)
+4 -1
View File
@@ -4805,7 +4805,10 @@
"evidence": [
"changes/2026-09-15-REL-04-consumer-checkpoint.md",
"rollback-rehearsal.md",
"baselines/rollback-consumer-validation.json"
"baselines/rollback-consumer-validation.json",
"changes/2026-09-15-REL-04-workflows-checkpoint.md",
"rollback-inventory.md",
"baselines/rollback-workflows-validation.json"
]
},
{
+106
View File
@@ -0,0 +1,106 @@
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 ts from 'typescript'
import { verifyIframeContract } from '../refactor/scripts/iframe-contract.mjs'
import { hash, readMember } from '../refactor/scripts/releases.mjs'
import { checkFiles, packedFiles } from './package-check.mjs'
import { consumerDirectory, removeConsumer, run, workspace, writeJson } from './package-consumer.mjs'
import { verifyRollbackFiles } from './rollback-files.mjs'
assert.equal(process.env.npm_config_user_agent?.split(' ')[0], 'yarn/1.22.22', 'Use pinned Yarn')
assert.equal(process.versions.node, fs.readFileSync(path.join(workspace, '.node-version'), 'utf8').trim(), 'Use pinned Node')
const yarn = process.env.npm_execpath
assert(yarn && fs.existsSync(yarn))
const name = 'artplayer-tool-iframe'
const parent = path.join(workspace, 'refactor/.cache/iframe-rollback')
fs.mkdirSync(parent, { recursive: true })
const output = fs.mkdtempSync(path.join(parent, 'run-'))
const directory = consumerDirectory()
const source = execFileSync('git', ['rev-parse', 'HEAD'], { cwd: workspace, encoding: 'utf8', windowsHide: true }).trim()
const report = { task: 'REL-04', capturedAt: new Date().toISOString(), source, node: process.versions.node, yarn: '1.22.22', passed: false, scope: 'Old package name and application imports restored together; independent renamed-package installation rehearsal', profiles: {}, steps: [] }
function checkTypes(consumer, source) {
const file = path.join(consumer, 'consumer.ts')
fs.writeFileSync(file, source)
const program = ts.createProgram([file], { strict: true, noEmit: true, skipLibCheck: false, types: [], esModuleInterop: true, target: ts.ScriptTarget.ES2020, module: ts.ModuleKind.CommonJS, moduleResolution: ts.ModuleResolutionKind.NodeJs, lib: ['lib.es2020.d.ts', 'lib.dom.d.ts'] })
for (const input of program.getSourceFiles()) {
const actual = fs.realpathSync(input.fileName)
assert(actual.startsWith(fs.realpathSync(consumer) + path.sep) || (program.isSourceFileDefaultLibrary(input) && path.dirname(actual) === fs.realpathSync(path.dirname(ts.sys.getExecutingFilePath()))), 'Type resolution escaped isolated consumer')
}
const diagnostics = ts.getPreEmitDiagnostics(program).map(item => ts.flattenDiagnosticMessageText(item.messageText, '\n'))
assert.deepEqual(diagnostics, [])
return { compiler: ts.version, mode: 'node10-commonjs', diagnostics }
}
try {
const { baseline, archive } = await verifyIframeContract()
const snapshot = path.join(output, 'build')
fs.mkdirSync(snapshot)
for (const file of ['package.json', 'yarn.lock', 'tsconfig.json', 'tsconfig.base.json']) fs.copyFileSync(path.join(workspace, file), path.join(snapshot, file))
fs.cpSync(path.join(workspace, 'scripts'), path.join(snapshot, 'scripts'), { recursive: true })
fs.symlinkSync(path.join(workspace, 'node_modules'), path.join(snapshot, 'node_modules'), process.platform === 'win32' ? 'junction' : 'dir')
fs.cpSync(path.join(workspace, 'packages', name), path.join(snapshot, 'packages', name), { recursive: true, filter: file => !['dist', 'node_modules'].includes(path.basename(file)) })
fs.writeFileSync(path.join(output, 'build.log'), run(['scripts/build.js', name], snapshot))
const candidateArchive = path.join(output, 'candidate.tgz')
fs.writeFileSync(path.join(output, 'pack.log'), run([yarn, 'pack', '--filename', candidateArchive], path.join(snapshot, 'packages', name)))
const files = packedFiles(candidateArchive)
const manifest = JSON.parse(readMember(candidateArchive, 'package/package.json'))
checkFiles(manifest, files)
assert.equal(manifest.name, name)
assert(!['preinstall', 'install', 'postinstall'].some(hook => manifest.scripts?.[hook]), 'Installation hooks need a separate fixture')
const profiles = {
old: { ...baseline.release, archive },
candidate: { name, version: manifest.version, archive: candidateArchive, sha256: hash(fs.readFileSync(candidateArchive)), files: Object.fromEntries(files.map(member => [member, hash(readMember(candidateArchive, member))])) },
}
for (const [id, pkg] of Object.entries(profiles)) {
const consumer = consumerDirectory()
try {
const application = id === 'old'
? `const assert = require('node:assert/strict');\nconst Iframe = require('${pkg.name}').default;\nconst Helper = require('${pkg.name}/dist/artplayer-helper-iframe.js').default;\nassert.equal(typeof Iframe, 'function');\nassert.equal(typeof Helper.destroy, 'function');\nconsole.log('old import and helper restored');\n`
: `const assert = require('node:assert/strict');\nconst Iframe = require('${pkg.name}');\nassert.equal(typeof Iframe, 'function');\nassert.equal(typeof require('${pkg.name}/legacy'), 'function');\nconsole.log('candidate import and legacy verified');\n`
writeJson(path.join(consumer, 'package.json'), { name: 'iframe-rollback-consumer', private: true, dependencies: { [pkg.name]: `file:${pkg.archive.replaceAll('\\', '/')}` } })
fs.writeFileSync(path.join(output, `${id}-prepare.log`), run([yarn, 'install', '--offline', '--ignore-scripts', '--non-interactive'], consumer))
verifyRollbackFiles(consumer, pkg)
for (const file of ['package.json', 'yarn.lock']) fs.copyFileSync(path.join(consumer, file), path.join(output, `${id}-${file}`))
fs.writeFileSync(path.join(output, `${id}-application.cjs`), application)
report.profiles[id] = { name: pkg.name, version: pkg.version, archive: path.relative(workspace, pkg.archive).replaceAll('\\', '/'), sha256: pkg.sha256, lockSha256: hash(fs.readFileSync(path.join(consumer, 'yarn.lock'))), applicationSha256: hash(application), files: pkg.files }
}
finally { removeConsumer(consumer) }
}
for (const [id, action] of [['old', 'install-predecessor'], ['candidate', 'upgrade-renamed-package'], ['old', 'restore-predecessor']]) {
const pkg = profiles[id]
for (const file of ['package.json', 'yarn.lock']) fs.copyFileSync(path.join(output, `${id}-${file}`), path.join(directory, file))
fs.copyFileSync(path.join(output, `${id}-application.cjs`), path.join(directory, 'application.cjs'))
fs.writeFileSync(path.join(output, `${action}-install.log`), run([yarn, 'install', '--offline', '--frozen-lockfile', '--force', '--ignore-scripts', '--non-interactive'], directory))
assert.equal(hash(fs.readFileSync(path.join(directory, 'yarn.lock'))), report.profiles[id].lockSha256)
const installed = verifyRollbackFiles(directory, pkg)
assert(!fs.existsSync(path.join(directory, 'node_modules', id === 'old' ? name : baseline.release.name)), 'Renamed package remained after restoration')
assert.equal(hash(fs.readFileSync(path.join(directory, 'application.cjs'))), report.profiles[id].applicationSha256)
const runtime = run(['application.cjs'], directory).trim()
const types = checkTypes(directory, `import Iframe from '${pkg.name}';\nconst iframe = new Iframe({ iframe: document.createElement('iframe'), url: '/child.html' });\niframe.commit(() => 42);\niframe.destroy();\n`)
report.steps.push({ action, profile: id, installed, runtime, types, otherPackageRemoved: true, applicationRestored: true })
}
const aliasConsumer = consumerDirectory()
try {
writeJson(path.join(aliasConsumer, 'package.json'), { name: 'iframe-alias-negative', private: true, dependencies: { [name]: `file:${archive.replaceAll('\\', '/')}` } })
fs.writeFileSync(path.join(output, 'alias-install.log'), run([yarn, 'install', '--offline', '--ignore-scripts', '--non-interactive'], aliasConsumer))
const installed = path.join(aliasConsumer, 'node_modules', name)
for (const [member, digest] of Object.entries(baseline.release.files)) assert.equal(hash(fs.readFileSync(path.join(installed, member.slice(8)))), digest)
fs.writeFileSync(path.join(aliasConsumer, 'alias.cjs'), `const assert = require('node:assert/strict');\nconst Iframe = require('${name}');\nassert.equal(typeof Iframe, 'object');\nassert.equal(typeof Iframe.default, 'function');\nassert.throws(() => new Iframe({}), TypeError);\nassert.throws(() => require('${name}/legacy'), error => error.code === 'MODULE_NOT_FOUND');\nconsole.log('old tarball under new dependency key does not preserve candidate constructor/legacy');\n`)
report.aliasNegative = { passed: true, result: run(['alias.cjs'], aliasConsumer).trim(), scope: 'Local file dependency key alias, not npm registry alias resolution' }
}
finally { removeConsumer(aliasConsumer) }
report.passed = true
}
catch (error) {
report.error = { name: error.name, message: error.message }
fs.writeFileSync(path.join(output, 'failure.log'), `${error.stack}\n${error.stdout || ''}\n${error.stderr || ''}`)
throw error
}
finally {
writeJson(path.join(output, 'report.json'), report)
writeJson(path.join(parent, 'latest.json'), { output: path.relative(workspace, output).replaceAll('\\', '/'), passed: report.passed })
removeConsumer(directory)
console.log(`Iframe rollback: ${path.relative(workspace, output)}; passed=${report.passed}`)
}
+101
View File
@@ -0,0 +1,101 @@
import assert from 'node:assert/strict'
import { execFileSync, spawnSync } from 'node:child_process'
import fs from 'node:fs'
import path from 'node:path'
import process from 'node:process'
import { consumerDirectory, removeConsumer, workspace, writeJson } from './package-consumer.mjs'
assert.equal(process.versions.node, fs.readFileSync(path.join(workspace, '.node-version'), 'utf8').trim(), 'Use pinned Node')
const parent = path.join(workspace, 'refactor/.cache/mainline-rehearsal')
fs.mkdirSync(parent, { recursive: true })
const output = fs.mkdtempSync(path.join(parent, 'run-'))
const directory = consumerDirectory()
const source = execFileSync('git', ['rev-parse', 'HEAD'], { cwd: workspace, encoding: 'utf8', windowsHide: true }).trim()
const report = { task: 'REL-04', capturedAt: new Date().toISOString(), source, node: process.versions.node, passed: false, scope: 'Synthetic isolated JS-to-TS modify/delete conflict; no production fix or remote mutation', steps: [] }
function command(executable, args, expected = 0) {
const result = spawnSync(executable, args, { cwd: directory, encoding: 'utf8', timeout: 30000, windowsHide: true, env: { ...process.env, NODE_PATH: '', GIT_CONFIG_NOSYSTEM: '1', GIT_CONFIG_GLOBAL: process.platform === 'win32' ? 'NUL' : '/dev/null', GIT_TERMINAL_PROMPT: '0' } })
report.steps.push({ executable: executable === process.execPath ? 'node' : executable, args, status: result.status, stdout: result.stdout, stderr: result.stderr })
assert(!result.error, result.error?.message)
assert.equal(result.status, expected, `${executable} ${args.join(' ')}: ${result.stderr}`)
if (executable === process.execPath && expected === 1)
assert(result.stderr.includes('ERR_ASSERTION') && result.stderr.includes('actual: -2') && result.stderr.includes('expected: 0'), 'Expected the negative-limit regression, not an unrelated Node failure')
return result.stdout.trim()
}
const git = (...args) => command('git', args)
function write(file, text) {
const target = path.join(directory, file)
fs.mkdirSync(path.dirname(target), { recursive: true })
fs.writeFileSync(target, text)
}
try {
git('init', '--initial-branch=master')
git('config', 'user.name', 'ArtPlayer isolated rehearsal')
git('config', 'user.email', 'rehearsal@example.invalid')
git('config', 'commit.gpgsign', 'false')
git('config', 'core.autocrlf', 'false')
write('package.json', '{"private":true,"type":"module"}\n')
const original = 'export default function retryLimit(value) {\n return Math.trunc(value);\n}\n'
write('src/retry.js', original)
const consumer = extension => `import assert from 'node:assert/strict';\nimport retryLimit from './src/retry.${extension}';\nassert.equal(retryLimit(2.9), 2);\nassert.equal(retryLimit(-2.9), 0);\nconsole.log('retry limit regression passed');\n`
write('consumer.mjs', consumer('js'))
git('add', '.')
git('commit', '-m', 'fixture: original JavaScript implementation')
const base = git('rev-parse', 'HEAD')
command(process.execPath, ['consumer.mjs'], 1)
git('switch', '-c', 'codex/rehearsal')
// This fixture deliberately moves the calculation into another responsibility.
fs.unlinkSync(path.join(directory, 'src/retry.js'))
write('src/retry.ts', 'import { normalizeLimit } from \'./normalize.ts\';\nexport default (value: number): number => normalizeLimit(value);\n')
write('src/normalize.ts', 'export const normalizeLimit = (value: number): number => Math.trunc(value);\n')
write('consumer.mjs', consumer('ts'))
git('add', '.')
git('commit', '-m', 'fixture: split retry policy into TypeScript modules')
const migrated = git('rev-parse', 'HEAD')
command(process.execPath, ['consumer.mjs'], 1)
git('switch', 'master')
write('src/retry.js', original.replace('Math.trunc(value)', 'Math.max(0, Math.trunc(value))'))
git('add', 'src/retry.js')
git('commit', '-m', 'fixture: clamp negative retry limits')
const fix = git('rev-parse', 'HEAD')
command(process.execPath, ['consumer.mjs'])
git('switch', 'codex/rehearsal')
assert.equal(git('status', '--porcelain'), '')
const beforeTree = git('rev-parse', 'HEAD^{tree}')
command('git', ['cherry-pick', '-x', fix], 1)
const conflict = git('ls-files', '--unmerged')
assert(conflict.includes('src/retry.js'), 'Expected the removed JavaScript file to conflict')
assert.equal(git('rev-parse', 'CHERRY_PICK_HEAD'), fix)
git('cherry-pick', '--abort')
assert.equal(git('rev-parse', 'HEAD'), migrated)
assert.equal(git('rev-parse', 'HEAD^{tree}'), beforeTree)
assert.equal(git('status', '--porcelain'), '')
assert(!fs.existsSync(path.join(directory, 'src/retry.js')), 'Abort left an obsolete JavaScript file')
command('git', ['cherry-pick', '-x', fix], 1)
git('rm', 'src/retry.js')
write('src/normalize.ts', 'export const normalizeLimit = (value: number): number => Math.max(0, Math.trunc(value));\n')
git('add', 'src/normalize.ts')
command(process.execPath, ['consumer.mjs'])
git('-c', 'core.editor=true', 'cherry-pick', '--continue')
const resolved = git('rev-parse', 'HEAD')
assert(git('log', '-1', '--format=%B').includes(fix), 'Resolved commit must retain upstream attribution')
assert.equal(git('status', '--porcelain'), '')
assert.equal(git('ls-files', 'src/retry.js'), '')
command(process.execPath, ['consumer.mjs'])
report.commits = { base, migrated, fix, resolved }
report.abort = { restoredCommit: migrated, restoredTree: beforeTree, clean: true }
report.resolution = { source: 'src/retry.js', destination: 'src/normalize.ts', obsoleteFileAbsent: true, regressionPassed: true, attributed: true }
fs.writeFileSync(path.join(output, 'resolved.patch'), `${git('show', '--format=fuller', '--binary', resolved)}\n`)
command('git', ['bundle', 'create', path.join(output, 'rehearsal.bundle'), '--all'])
git('bundle', 'verify', path.join(output, 'rehearsal.bundle'))
report.passed = true
}
catch (error) {
report.error = { name: error.name, message: error.message }
throw error
}
finally {
writeJson(path.join(output, 'report.json'), report)
writeJson(path.join(parent, 'latest.json'), { output: path.relative(workspace, output).replaceAll('\\', '/'), passed: report.passed })
removeConsumer(directory)
console.log(`Mainline rehearsal: ${path.relative(workspace, output)}; passed=${report.passed}`)
}