From d0c33f6258b49eec1937e139789b73a4440cb2e2 Mon Sep 17 00:00:00 2001 From: Harvey Zhao Date: Tue, 15 Sep 2026 00:28:33 +0800 Subject: [PATCH] test(release): [REL-04] checkpoint renamed rollback and mainline conflict recovery --- package.json | 7 +- .../artplayer-tool-iframe/ARCHITECTURE.md | 8 + refactor/README.md | 1 + refactor/baselines/rollback-inventory.json | 647 ++++++++++++++++++ .../rollback-workflows-validation.json | 642 +++++++++++++++++ .../2026-09-15-REL-04-workflows-checkpoint.md | 30 + refactor/plan.md | 2 +- refactor/progress.md | 9 + refactor/rollback-inventory.md | 40 ++ refactor/rollback-rehearsal.md | 53 +- refactor/scripts/rollback-inventory.mjs | 75 ++ refactor/tasks.json | 5 +- scripts/iframe-rollback.mjs | 106 +++ scripts/mainline-rehearsal.mjs | 101 +++ 14 files changed, 1718 insertions(+), 8 deletions(-) create mode 100644 refactor/baselines/rollback-inventory.json create mode 100644 refactor/baselines/rollback-workflows-validation.json create mode 100644 refactor/changes/2026-09-15-REL-04-workflows-checkpoint.md create mode 100644 refactor/rollback-inventory.md create mode 100644 refactor/scripts/rollback-inventory.mjs create mode 100644 scripts/iframe-rollback.mjs create mode 100644 scripts/mainline-rehearsal.mjs diff --git a/package.json b/package.json index 4ffbedac8..c6e43ddef 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/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": { diff --git a/packages/artplayer-tool-iframe/ARCHITECTURE.md b/packages/artplayer-tool-iframe/ARCHITECTURE.md index 4ece5841f..91d46aeba 100644 --- a/packages/artplayer-tool-iframe/ARCHITECTURE.md +++ b/packages/artplayer-tool-iframe/ARCHITECTURE.md @@ -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 diff --git a/refactor/README.md b/refactor/README.md index d7643f316..4a0b132f7 100644 --- a/refactor/README.md +++ b/refactor/README.md @@ -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 的目标清单、兼容要求和版本落地步骤 | diff --git a/refactor/baselines/rollback-inventory.json b/refactor/baselines/rollback-inventory.json new file mode 100644 index 000000000..f541fa6b1 --- /dev/null +++ b/refactor/baselines/rollback-inventory.json @@ -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" + } + ] +} diff --git a/refactor/baselines/rollback-workflows-validation.json b/refactor/baselines/rollback-workflows-validation.json new file mode 100644 index 000000000..ece6f2c5f --- /dev/null +++ b/refactor/baselines/rollback-workflows-validation.json @@ -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 \", 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 \", 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 \nAuthorDate: Tue Sep 15 00:24:23 2026 +0800\nCommit: ArtPlayer isolated rehearsal \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." + ] +} diff --git a/refactor/changes/2026-09-15-REL-04-workflows-checkpoint.md b/refactor/changes/2026-09-15-REL-04-workflows-checkpoint.md new file mode 100644 index 000000000..de415b00e --- /dev/null +++ b/refactor/changes/2026-09-15-REL-04-workflows-checkpoint.md @@ -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 演练推广 +为其他包或物理设备的完成证据。撤销本工程检查点不会改变生产行为或远端设置。 diff --git a/refactor/plan.md b/refactor/plan.md index 9c98059f6..b95d942e0 100644 --- a/refactor/plan.md +++ b/refactor/plan.md @@ -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) diff --git a/refactor/progress.md b/refactor/progress.md index 812297b7a..be4e2c5cb 100644 --- a/refactor/progress.md +++ b/refactor/progress.md @@ -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 项运行时 diff --git a/refactor/rollback-inventory.md b/refactor/rollback-inventory.md new file mode 100644 index 000000000..0a4d28101 --- /dev/null +++ b/refactor/rollback-inventory.md @@ -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)。 diff --git a/refactor/rollback-rehearsal.md b/refactor/rollback-rehearsal.md index 454eb96da..f9cec0ea1 100644 --- a/refactor/rollback-rehearsal.md +++ b/refactor/rollback-rehearsal.md @@ -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、推送或部署,不把本演练当作这些动作的授权。 diff --git a/refactor/scripts/rollback-inventory.mjs b/refactor/scripts/rollback-inventory.mjs new file mode 100644 index 000000000..32b393a62 --- /dev/null +++ b/refactor/scripts/rollback-inventory.mjs @@ -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`) diff --git a/refactor/tasks.json b/refactor/tasks.json index ed2a09bc3..2c830c857 100644 --- a/refactor/tasks.json +++ b/refactor/tasks.json @@ -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" ] }, { diff --git a/scripts/iframe-rollback.mjs b/scripts/iframe-rollback.mjs new file mode 100644 index 000000000..9d14b4e51 --- /dev/null +++ b/scripts/iframe-rollback.mjs @@ -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}`) +} diff --git a/scripts/mainline-rehearsal.mjs b/scripts/mainline-rehearsal.mjs new file mode 100644 index 000000000..0d5a74025 --- /dev/null +++ b/scripts/mainline-rehearsal.mjs @@ -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}`) +}