test(refactor): [ENG-COVERAGE-01] index public contracts and exact candidate evidence

This commit is contained in:
Harvey Zhao committed 2026-09-12 10:38:29 +08:00
1 parent 6abac891ce
commit f35fb87615
22 files changed
+11007 -7

No files matched your search

+4 -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:baseline": "node --test refactor/scripts/*.test.mjs",
"ci:check": "yarn check:toolchain --strict && yarn check:commits --report && yarn check:impact --report && yarn check:plan && yarn lint && yarn check:types && yarn typecheck && yarn test",
"ci:check": "yarn check:toolchain --strict && yarn check:commits --report && yarn check:impact --report && yarn test:contracts && yarn check:contracts --report && yarn check:plan && yarn lint && yarn check:types && yarn typecheck && yarn test",
"ci:build": "yarn build:types && yarn build all && yarn build:i18n && yarn build:ts && yarn build:docs && yarn test:imports",
"test:imports": "node --test test/esm.test.js test/i18n.test.js test/ssr.test.js",
"typecheck": "node scripts/typecheck.mjs",
@@ -67,7 +67,9 @@
"test:ads-native-visibility": "node refactor/scripts/ads-native-visibility.mjs",
"test:vast": "node --test test/vast.test.js test/vast-lifecycle.test.js",
"check:commits": "node refactor/scripts/commit-audit.mjs",
"check:impact": "node refactor/scripts/impact.mjs"
"check:impact": "node refactor/scripts/impact.mjs",
"check:contracts": "node refactor/scripts/contracts.mjs",
"test:contracts": "node refactor/scripts/run-contracts.mjs"
},
"browserslist": "last 1 Chrome version",
"devDependencies": {
+1
View File
@@ -20,6 +20,7 @@
| [阶段路线](roadmap.md) | 执行顺序、里程碑、范围和阶段出口 |
| [执行门槛与分批交付](execution-gates.md) | 最小启动基线、早期试点、历史失败、设备门槛和分包准入 |
| [兼容性契约](compatibility.md) | 不可静默改变的接口、行为、类型、样式与包分发约定 |
| [契约覆盖与证据索引](contract-coverage.md) | 22包逐类责任、稳定测试ID、精确版本依据和候选/历史报告对应 |
| [核心设计](core-design.md) | 核心各模块的目标职责、迁移方式和生命周期设计 |
| [核心阶段验收范围](core-acceptance.md) | 核心契约、已处置差异、性能风险与后续生态/发布门槛 |
| [包迁移规范](packages.md) | 全生态统一流程、组合矩阵及包特有风险 |
+3
View File
@@ -1,5 +1,8 @@
# AI 协作与文档维护
契约修改同时维护 [覆盖索引](contract-coverage.md):固定测试ID、精确版本、断言范围
和候选报告必须对应;旧报告、skip及未索引用例不能折算成当前整包通过。
AI 用于源码分析、任务拆分、实现、测试和差异审查。现代化的验收是可验证的类型、行为和构建质量,不以生成代码量为指标。
## 开始任务
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,107 @@
{
"schemaVersion": 1,
"task": "ENG-COVERAGE-01",
"status": "done",
"sourceCommit": "6abac891ceb42bbff2c285befab3fda85490c3e9",
"model": {
"packages": 22,
"contractFamilies": 12,
"ownerRows": 264,
"workspaceVersionPoints": 22,
"publishedVersionPoints": 7,
"indexedCases": 10,
"rowsAwaitingTestIndexing": 255
},
"checks": [
{
"command": "node --test refactor/scripts/contracts.test.mjs",
"result": "pass",
"tests": 7,
"log": {
"file": "refactor/.cache/contracts-tests-final-guards.log",
"sha256": "3d4c38f9710755ff6f825171d4798b0f53d516e2422c02a688315f10fe0e7d78"
}
},
{
"command": "node --test --test-reporter=D:/github/ArtPlayer/refactor/scripts/contract-reporter.mjs test/chapter.test.js",
"result": "Windows absolute ESM specifier failure reproduced; fixture fixed with file URL",
"log": {
"file": "refactor/.cache/contracts-reporter-path-before.log",
"sha256": "5009c96b9108020b163f72bf9ae50d9e7336616977e023f79e1ff3368c3b205f"
}
},
{
"command": "eslint contract index/model/runner/reporter/tests --no-fix",
"result": "pass",
"log": {
"file": "refactor/.cache/contracts-lint-verified.log",
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
}
},
{
"command": "node refactor/scripts/run-contracts.mjs (final targeted verification)",
"result": "pass",
"executedTests": 44,
"indexedAssertions": 10,
"scope": "Five existing Node files; repeated executions are not new functional coverage",
"report": "refactor/baselines/contract-observations/2026-09-12-source-final.json",
"log": {
"file": "refactor/.cache/contracts-run-verified.log",
"sha256": "d34a6bfaa5bef99e48622886612aef299c8a7c475ab69baf721f951019aa14a4"
}
},
{
"command": "yarn ci:check",
"result": "pass",
"tests": {
"unit": 832,
"engineering": 14,
"baseline": 72,
"mainTestChainTotal": 918,
"additionalRepeatedContractExecutions": 44
},
"log": {
"file": "refactor/.cache/contracts-ci-verified.log",
"sha256": "3695a31e9952fc7d24d4221b306e8e61eaf44e86d945a681e2e05f290362085a"
}
}
],
"inputs": [
{
"file": "package.json",
"sha256LF": "a114d0f1c1532e90984a93cc703dfcb250107f5de56b5a62bf4c30ad4f64c25b"
},
{
"file": "refactor/contract-policy.json",
"sha256LF": "ca545a3e721313f0dcb59c549c14589c108e064ff396a72a2c14e4ee993b1a04"
},
{
"file": "refactor/scripts/contracts-model.mjs",
"sha256LF": "99b3a774de9d4bbfbf6bc7728b74bf8784883e13ac24f5b9b7e5aff81367ca8a"
},
{
"file": "refactor/scripts/contracts.mjs",
"sha256LF": "66dd75e6624574dd2648317d087d67fd17f4a310066fc63b3eb32b90f68fcf36"
},
{
"file": "refactor/scripts/contracts.test.mjs",
"sha256LF": "022c46ac41b87b28fabded74dcda2658039b4487dfc1ff4d4d70edfbc08b8da3"
},
{
"file": "refactor/scripts/contract-reporter.mjs",
"sha256LF": "9fdd6cd1814e12df18a294558da9a4a561b5b8eee8149b21902c1b99b855b8c9"
},
{
"file": "refactor/scripts/run-contracts.mjs",
"sha256LF": "de7a44af890672605d6ce00c142420d146c4ba45b398786957b65edc3c25afd3"
}
],
"limitations": [
"Ownership index covers the twelve declared contract families, not exhaustive testing of every public member",
"255 rows lack exact test indexing; recorded historical tests are not thereby absent or failed",
"Only Node literal assertions normalized so far; browser/type/device/vendor evidence remains in its original reports and package/review tasks",
"Exact baseline versions do not establish continuous support ranges",
"Local CI only, no remote GitHub run; no npm release acceptance",
"Existing generated-doc lint warning and Yarn url.parse deprecation remain outside this engineering change"
]
}
@@ -0,0 +1,22 @@
# ENG-COVERAGE-01 契约覆盖与报告对应
起点6abac891。创建22包×12契约归属、29个版本依据(22工作区/7发布),从真实断言
逐条登记10个稳定ID。其他用例待索引,历史JSON按任务列出,不把旧总数当作当前契约
通过。维护方式见 [契约覆盖维护](../contract-coverage.md)。
check:contracts/test:contracts接入ci:check,五文件44项产生10个精确观察,保留候选
字节指纹、实际事件、当时定义及来源;输入/定义变化标旧,skip/todo/失败不算通过。
没有生产源码或运行时依赖修改。
七组测试覆盖路径/版本/任务/ID/报告反例,并启动真实Node验证失败、skip/todo。首轮
发现Windows绝对路径不能直接作ESM reporter specifier,独立复现后改file URL。生产
runner原本用相对specifier,不受影响。失败日志保留,完整结果在完成前归档。
文档入口明确早期未运行描述是历史目录,后续查progress与包记录,没有批量改成通过。
未运行远端Actions或发布。回退本步移除CI两个命令及相应脚本/策略/报告/文档,原有
包行为和测试不受影响。逐成员映射、浏览器/类型解析和设备/SDK验收由包任务及REVIEW/
REL继续;本任务不批准发布。
最终七组反例通过,主测试链918项及44项重复观察执行通过,专项lint通过。补齐空清单
拒绝和不同ID重复同一断言的拒绝,避免隐式发现或虚增索引。两个阶段实际报告分别
归档,旧候选保持历史;完整记录见 [验证证据](../baselines/contracts-validation.json)。
+2
View File
@@ -15,6 +15,8 @@
| `yarn test:browser` | 隔离服务执行 Chromium/Firefox/WebKit 真实播放;首次先运行 test:browser:install,详见 [入口](../test/browser/README.md) |
| `yarn test:imports` | 3 个既有包导入 smoke,构建后执行以使用新产物;不把 console 示例视为完整行为断言 |
| `yarn test:baseline` | 固定发布包完整性及本地 HTTP 基线测试;首次可能下载已固定归档到缓存 |
| `yarn test:contracts` | 重跑已登记断言的Node文件并采集精确事件/候选指纹,当前44项包含10个索引断言 |
| `yarn check:contracts --report` | 校验12类契约/22包归属、版本及报告对应;--write更新静态表,详见 [维护说明](contract-coverage.md) |
| `yarn ci:check` | 严格 Node/Yarn/锁检查、计划、只读 lint、类型、Node 和基线测试;允许写忽略缓存,不修改源码 |
| `yarn ci:build` | 21 库包、i18n、编辑器声明和文档站构建,以及构建后包导入 smoke;会生成 dist 和 docs 内容 |
| `yarn check:impact --report` | 读取实际依赖/验证关系和Git变更,核对workflow必需命令,写CI影响报告;已接入ci:check,见[影响映射](impact-analysis.md) |
+73
View File
@@ -0,0 +1,73 @@
# 契约、版本与测试证据维护
ENG-COVERAGE-01建立可校验的归属和证据索引,不把索引建成当作全项目兼容通过。
[生成的索引](contract-index.md)覆盖compatibility.md的12类契约及实际22包,共264行。
每行有责任任务;未整理的测试、精确支持范围、设备和发布包验证保持缺口。站点契约
适用性由SITE审查,不给文档站编造播放器构造器。逐成员复盘仍属各包契约及REVIEW-02;
宽泛API编号不是每个方法、参数、事件都已测过的证明。
## 数据与版本
`contract-policy.json`维护逐包逐类责任、版本依据及稳定测试ID。版本用JSON pointer
指向冻结清单,核对包名、版本和发布archive integrity;工作区单独标识。当前22个
工作区、7个发布对照点,不由单个版本推断连续支持区间。
`contract-runs.json`引用归档报告及LF规范化SHA-256。报告保存当时定义、实际事件、
输入指纹、命令、HEAD和Node/OS。其他tasks.json历史JSON证据列为recorded-not-normalized,
保留原文及任务,不能按通过总数猜具体断言已执行。
本批逐条核对10个已有断言,涉及核心配置、注册、事件、存储及chapter归一化。五个
文件执行44项,仅精确匹配的10项作索引观察,其余34项不自动分配契约。255行尚未
录入测试ID,表示索引缺口,不表示这些功能都没有测试;历史事实仍查包记录及changes。
## 命令与CI
```sh
yarn test:contracts
yarn check:contracts --write
yarn check:contracts --report
```
执行器使用独立Node进程,固定文件列表且不接受过滤器;清除ARTPLAYER产物覆盖及
NODE_OPTIONS,避免把临时指定产物误记为当前源码。执行前后核对包源码/声明/manifest、
测试/脚本/夹具、工具锁及版本依据;输入变化拒绝结果。指纹是实际字节快照,换行或
相关脚本变化也会保守判旧。
Reporter记录Node实际pass/fail/summary的文件、名称、skip/todo及整体结果,不按数量
猜通过。接口依据[Node24官方文档](https://nodejs.org/docs/latest-v24.x/api/test.html#custom-reporters)。
测试ID须唯一对应源码中的Node test字面量调用;参数化、浏览器和类型报告仍保留
历史原始记录,后续增加各自解析器及反例,不能用当前适配器假装已经覆盖。
CI先执行观察,再检查索引。`refactor/.cache/ci/contracts-run.json`保存本轮结果,
`contracts.json`保存动态索引,随已有checks artifact收集。原始事件和stderr另存
`refactor/.cache/contracts/run-*/`。原有测试不减少;44项重复执行是链路验证,不算
新增44种功能覆盖。
## 状态
| 状态 | 含义 |
| --- | --- |
| tests-not-indexed | 尚无精确映射,仍有责任任务 |
| planned-not-observed | 映射存在,没有结构化执行报告 |
| partial-observations | 有具体结果,不代表整个契约类或包通过 |
| current-inputs | 定义及输入指纹与当前一致 |
| historical-inputs | 定义一致,源码/测试/工具等输入已变化 |
| historical-definition | 定义已变化,保留旧定义实际范围 |
| skipped / failed | 原始结果原样保留,不折算成通过 |
无效路径、重复ID、错误任务、错包/版本/integrity、无实际测试、报告被修改或结果
矛盾会失败。旧输入、未索引或缺环境明确列为缺口,不阻止无关迁移;对应发布由REL/
REVIEW把关。哈希和结构只能证明对应关系,不能证明断言充分,也不是证据数字签名。
## 后续AI维护
1. 修改前检查包的12行及历史报告,定位具体公开成员/参数/事件并核对适用性。
2. 登记稳定ID、精确文件/标题、命令、版本依据、断言范围和限制。ID不能改归其他包;
旧报告保留旧定义,不能重写历史消除差异。
3. 修改后执行test:contracts。归档时复制实际report.json到baselines/contract-observations,
将LF哈希加入runs;核对输入、skip/todo、整体失败及语义,不手写passed。
4. --write更新静态表,--report检查动态结果,再跑ci:check。done及独立commit遵循
commit-audit;不能拿旧CI总数批准新实现。
支持窗口仍需包契约、旧核心组合和环境证明。VAST的VPN例外不改变类型、生命周期或
其他包结论;当前没有据此新增skip。
+318
View File
@@ -0,0 +1,318 @@
# 公开契约覆盖索引
> 由 contract-policy.json 生成;`yarn check:contracts --write` 更新。动态候选/报告状态见 `yarn check:contracts --report`。
覆盖12类兼容契约与22包的验证归属;不是每个公开成员的穷尽测试证明。没有固定测试ID的行仍需逐成员补齐。
版本均为精确冻结对照点,不能推导连续支持区间。历史报告保留原状态,只有精确用例和候选指纹对应时才列为观察结果。
| 包 | 契约 | 责任任务 | 固定测试ID |
| --- | --- | --- | --- |
| artplayer | API-01 构造与配置 | REVIEW-02 | CT-CORE-OPTIONS-001, CT-CORE-OPTIONS-002 |
| artplayer | API-02 属性与方法 | REVIEW-02 | CT-CORE-PLUGINS-002, CT-CORE-STORAGE-001 |
| artplayer | API-03 属性描述符 | REVIEW-02 | CT-CORE-PLUGINS-001 |
| artplayer | API-04 事件 | REVIEW-02 | CT-CORE-EVENTS-001 |
| artplayer | API-05 生命周期 | REVIEW-02 | CT-CORE-PLUGINS-003 |
| artplayer | API-06 插件注册 | REVIEW-02 | CT-CORE-PLUGINS-001, CT-CORE-PLUGINS-002, CT-CORE-PLUGINS-003 |
| artplayer | API-07 生态集成 | REVIEW-02 | 待索引;不代表没有历史测试 |
| artplayer | API-08 DOM/CSS | REVIEW-02 | 待索引;不代表没有历史测试 |
| artplayer | API-09 包分发 | REVIEW-02 | 待索引;不代表没有历史测试 |
| artplayer | API-10 浏览器能力 | REL-03 | 待索引;不代表没有历史测试 |
| artplayer | API-11 TypeScript | REVIEW-02 | 待索引;不代表没有历史测试 |
| artplayer | API-12 持久状态与协议 | REVIEW-02 | CT-CORE-STORAGE-001, CT-CORE-STORAGE-002 |
| artplayer-plugin-ads | API-01 构造与配置 | PKG-ADS-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-ads | API-02 属性与方法 | PKG-ADS-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-ads | API-03 属性描述符 | PKG-ADS-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-ads | API-04 事件 | PKG-ADS-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-ads | API-05 生命周期 | PKG-ADS-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-ads | API-06 插件注册 | PKG-ADS-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-ads | API-07 生态集成 | PKG-ADS-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-ads | API-08 DOM/CSS | PKG-ADS-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-ads | API-09 包分发 | PKG-ADS-06 | 待索引;不代表没有历史测试 |
| artplayer-plugin-ads | API-10 浏览器能力 | REL-03 | 待索引;不代表没有历史测试 |
| artplayer-plugin-ads | API-11 TypeScript | PKG-ADS-04 | 待索引;不代表没有历史测试 |
| artplayer-plugin-ads | API-12 持久状态与协议 | PKG-ADS-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-ambilight | API-01 构造与配置 | PKG-AMBILIGHT-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-ambilight | API-02 属性与方法 | PKG-AMBILIGHT-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-ambilight | API-03 属性描述符 | PKG-AMBILIGHT-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-ambilight | API-04 事件 | PKG-AMBILIGHT-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-ambilight | API-05 生命周期 | PKG-AMBILIGHT-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-ambilight | API-06 插件注册 | PKG-AMBILIGHT-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-ambilight | API-07 生态集成 | PKG-AMBILIGHT-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-ambilight | API-08 DOM/CSS | PKG-AMBILIGHT-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-ambilight | API-09 包分发 | PKG-AMBILIGHT-06 | 待索引;不代表没有历史测试 |
| artplayer-plugin-ambilight | API-10 浏览器能力 | REL-03 | 待索引;不代表没有历史测试 |
| artplayer-plugin-ambilight | API-11 TypeScript | PKG-AMBILIGHT-04 | 待索引;不代表没有历史测试 |
| artplayer-plugin-ambilight | API-12 持久状态与协议 | PKG-AMBILIGHT-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-asr | API-01 构造与配置 | PKG-ASR-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-asr | API-02 属性与方法 | PKG-ASR-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-asr | API-03 属性描述符 | PKG-ASR-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-asr | API-04 事件 | PKG-ASR-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-asr | API-05 生命周期 | PKG-ASR-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-asr | API-06 插件注册 | PKG-ASR-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-asr | API-07 生态集成 | PKG-ASR-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-asr | API-08 DOM/CSS | PKG-ASR-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-asr | API-09 包分发 | PKG-ASR-06 | 待索引;不代表没有历史测试 |
| artplayer-plugin-asr | API-10 浏览器能力 | REL-03 | 待索引;不代表没有历史测试 |
| artplayer-plugin-asr | API-11 TypeScript | PKG-ASR-04 | 待索引;不代表没有历史测试 |
| artplayer-plugin-asr | API-12 持久状态与协议 | PKG-ASR-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-audio-track | API-01 构造与配置 | PKG-AUDIO-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-audio-track | API-02 属性与方法 | PKG-AUDIO-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-audio-track | API-03 属性描述符 | PKG-AUDIO-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-audio-track | API-04 事件 | PKG-AUDIO-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-audio-track | API-05 生命周期 | PKG-AUDIO-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-audio-track | API-06 插件注册 | PKG-AUDIO-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-audio-track | API-07 生态集成 | PKG-AUDIO-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-audio-track | API-08 DOM/CSS | PKG-AUDIO-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-audio-track | API-09 包分发 | PKG-AUDIO-06 | 待索引;不代表没有历史测试 |
| artplayer-plugin-audio-track | API-10 浏览器能力 | REL-03 | 待索引;不代表没有历史测试 |
| artplayer-plugin-audio-track | API-11 TypeScript | PKG-AUDIO-04 | 待索引;不代表没有历史测试 |
| artplayer-plugin-audio-track | API-12 持久状态与协议 | PKG-AUDIO-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-auto-thumbnail | API-01 构造与配置 | PKG-AUTO-THUMB-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-auto-thumbnail | API-02 属性与方法 | PKG-AUTO-THUMB-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-auto-thumbnail | API-03 属性描述符 | PKG-AUTO-THUMB-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-auto-thumbnail | API-04 事件 | PKG-AUTO-THUMB-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-auto-thumbnail | API-05 生命周期 | PKG-AUTO-THUMB-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-auto-thumbnail | API-06 插件注册 | PKG-AUTO-THUMB-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-auto-thumbnail | API-07 生态集成 | PKG-AUTO-THUMB-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-auto-thumbnail | API-08 DOM/CSS | PKG-AUTO-THUMB-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-auto-thumbnail | API-09 包分发 | PKG-AUTO-THUMB-06 | 待索引;不代表没有历史测试 |
| artplayer-plugin-auto-thumbnail | API-10 浏览器能力 | REL-03 | 待索引;不代表没有历史测试 |
| artplayer-plugin-auto-thumbnail | API-11 TypeScript | PKG-AUTO-THUMB-04 | 待索引;不代表没有历史测试 |
| artplayer-plugin-auto-thumbnail | API-12 持久状态与协议 | PKG-AUTO-THUMB-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-chapter | API-01 构造与配置 | PKG-CHAPTER-02 | CT-CHAPTER-NORMALIZE-001 |
| artplayer-plugin-chapter | API-02 属性与方法 | PKG-CHAPTER-02 | CT-CHAPTER-NORMALIZE-001, CT-CHAPTER-NORMALIZE-002 |
| artplayer-plugin-chapter | API-03 属性描述符 | PKG-CHAPTER-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-chapter | API-04 事件 | PKG-CHAPTER-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-chapter | API-05 生命周期 | PKG-CHAPTER-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-chapter | API-06 插件注册 | PKG-CHAPTER-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-chapter | API-07 生态集成 | PKG-CHAPTER-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-chapter | API-08 DOM/CSS | PKG-CHAPTER-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-chapter | API-09 包分发 | PKG-CHAPTER-06 | 待索引;不代表没有历史测试 |
| artplayer-plugin-chapter | API-10 浏览器能力 | REL-03 | 待索引;不代表没有历史测试 |
| artplayer-plugin-chapter | API-11 TypeScript | PKG-CHAPTER-04 | 待索引;不代表没有历史测试 |
| artplayer-plugin-chapter | API-12 持久状态与协议 | PKG-CHAPTER-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-chromecast | API-01 构造与配置 | PKG-CAST-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-chromecast | API-02 属性与方法 | PKG-CAST-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-chromecast | API-03 属性描述符 | PKG-CAST-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-chromecast | API-04 事件 | PKG-CAST-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-chromecast | API-05 生命周期 | PKG-CAST-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-chromecast | API-06 插件注册 | PKG-CAST-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-chromecast | API-07 生态集成 | PKG-CAST-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-chromecast | API-08 DOM/CSS | PKG-CAST-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-chromecast | API-09 包分发 | PKG-CAST-06 | 待索引;不代表没有历史测试 |
| artplayer-plugin-chromecast | API-10 浏览器能力 | REL-03 | 待索引;不代表没有历史测试 |
| artplayer-plugin-chromecast | API-11 TypeScript | PKG-CAST-04 | 待索引;不代表没有历史测试 |
| artplayer-plugin-chromecast | API-12 持久状态与协议 | PKG-CAST-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-danmuku | API-01 构造与配置 | PKG-DANMUKU-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-danmuku | API-02 属性与方法 | PKG-DANMUKU-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-danmuku | API-03 属性描述符 | PKG-DANMUKU-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-danmuku | API-04 事件 | PKG-DANMUKU-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-danmuku | API-05 生命周期 | PKG-DANMUKU-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-danmuku | API-06 插件注册 | PKG-DANMUKU-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-danmuku | API-07 生态集成 | PKG-DANMUKU-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-danmuku | API-08 DOM/CSS | PKG-DANMUKU-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-danmuku | API-09 包分发 | PKG-DANMUKU-06 | 待索引;不代表没有历史测试 |
| artplayer-plugin-danmuku | API-10 浏览器能力 | REL-03 | 待索引;不代表没有历史测试 |
| artplayer-plugin-danmuku | API-11 TypeScript | PKG-DANMUKU-04 | 待索引;不代表没有历史测试 |
| artplayer-plugin-danmuku | API-12 持久状态与协议 | PKG-DANMUKU-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-danmuku-mask | API-01 构造与配置 | PKG-MASK-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-danmuku-mask | API-02 属性与方法 | PKG-MASK-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-danmuku-mask | API-03 属性描述符 | PKG-MASK-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-danmuku-mask | API-04 事件 | PKG-MASK-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-danmuku-mask | API-05 生命周期 | PKG-MASK-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-danmuku-mask | API-06 插件注册 | PKG-MASK-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-danmuku-mask | API-07 生态集成 | PKG-MASK-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-danmuku-mask | API-08 DOM/CSS | PKG-MASK-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-danmuku-mask | API-09 包分发 | PKG-MASK-06 | 待索引;不代表没有历史测试 |
| artplayer-plugin-danmuku-mask | API-10 浏览器能力 | REL-03 | 待索引;不代表没有历史测试 |
| artplayer-plugin-danmuku-mask | API-11 TypeScript | PKG-MASK-04 | 待索引;不代表没有历史测试 |
| artplayer-plugin-danmuku-mask | API-12 持久状态与协议 | PKG-MASK-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-dash-control | API-01 构造与配置 | PKG-DASH-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-dash-control | API-02 属性与方法 | PKG-DASH-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-dash-control | API-03 属性描述符 | PKG-DASH-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-dash-control | API-04 事件 | PKG-DASH-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-dash-control | API-05 生命周期 | PKG-DASH-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-dash-control | API-06 插件注册 | PKG-DASH-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-dash-control | API-07 生态集成 | PKG-DASH-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-dash-control | API-08 DOM/CSS | PKG-DASH-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-dash-control | API-09 包分发 | PKG-DASH-06 | 待索引;不代表没有历史测试 |
| artplayer-plugin-dash-control | API-10 浏览器能力 | REL-03 | 待索引;不代表没有历史测试 |
| artplayer-plugin-dash-control | API-11 TypeScript | PKG-DASH-04 | 待索引;不代表没有历史测试 |
| artplayer-plugin-dash-control | API-12 持久状态与协议 | PKG-DASH-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-document-pip | API-01 构造与配置 | PKG-DPIP-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-document-pip | API-02 属性与方法 | PKG-DPIP-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-document-pip | API-03 属性描述符 | PKG-DPIP-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-document-pip | API-04 事件 | PKG-DPIP-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-document-pip | API-05 生命周期 | PKG-DPIP-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-document-pip | API-06 插件注册 | PKG-DPIP-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-document-pip | API-07 生态集成 | PKG-DPIP-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-document-pip | API-08 DOM/CSS | PKG-DPIP-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-document-pip | API-09 包分发 | PKG-DPIP-06 | 待索引;不代表没有历史测试 |
| artplayer-plugin-document-pip | API-10 浏览器能力 | REL-03 | 待索引;不代表没有历史测试 |
| artplayer-plugin-document-pip | API-11 TypeScript | PKG-DPIP-04 | 待索引;不代表没有历史测试 |
| artplayer-plugin-document-pip | API-12 持久状态与协议 | PKG-DPIP-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-hls-control | API-01 构造与配置 | PKG-HLS-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-hls-control | API-02 属性与方法 | PKG-HLS-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-hls-control | API-03 属性描述符 | PKG-HLS-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-hls-control | API-04 事件 | PKG-HLS-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-hls-control | API-05 生命周期 | PKG-HLS-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-hls-control | API-06 插件注册 | PKG-HLS-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-hls-control | API-07 生态集成 | PKG-HLS-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-hls-control | API-08 DOM/CSS | PKG-HLS-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-hls-control | API-09 包分发 | PKG-HLS-06 | 待索引;不代表没有历史测试 |
| artplayer-plugin-hls-control | API-10 浏览器能力 | REL-03 | 待索引;不代表没有历史测试 |
| artplayer-plugin-hls-control | API-11 TypeScript | PKG-HLS-04 | 待索引;不代表没有历史测试 |
| artplayer-plugin-hls-control | API-12 持久状态与协议 | PKG-HLS-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-jassub | API-01 构造与配置 | PKG-JASSUB-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-jassub | API-02 属性与方法 | PKG-JASSUB-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-jassub | API-03 属性描述符 | PKG-JASSUB-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-jassub | API-04 事件 | PKG-JASSUB-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-jassub | API-05 生命周期 | PKG-JASSUB-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-jassub | API-06 插件注册 | PKG-JASSUB-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-jassub | API-07 生态集成 | PKG-JASSUB-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-jassub | API-08 DOM/CSS | PKG-JASSUB-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-jassub | API-09 包分发 | PKG-JASSUB-06 | 待索引;不代表没有历史测试 |
| artplayer-plugin-jassub | API-10 浏览器能力 | REL-03 | 待索引;不代表没有历史测试 |
| artplayer-plugin-jassub | API-11 TypeScript | PKG-JASSUB-04 | 待索引;不代表没有历史测试 |
| artplayer-plugin-jassub | API-12 持久状态与协议 | PKG-JASSUB-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-multiple-subtitles | API-01 构造与配置 | PKG-MULTI-SUB-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-multiple-subtitles | API-02 属性与方法 | PKG-MULTI-SUB-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-multiple-subtitles | API-03 属性描述符 | PKG-MULTI-SUB-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-multiple-subtitles | API-04 事件 | PKG-MULTI-SUB-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-multiple-subtitles | API-05 生命周期 | PKG-MULTI-SUB-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-multiple-subtitles | API-06 插件注册 | PKG-MULTI-SUB-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-multiple-subtitles | API-07 生态集成 | PKG-MULTI-SUB-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-multiple-subtitles | API-08 DOM/CSS | PKG-MULTI-SUB-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-multiple-subtitles | API-09 包分发 | PKG-MULTI-SUB-06 | 待索引;不代表没有历史测试 |
| artplayer-plugin-multiple-subtitles | API-10 浏览器能力 | REL-03 | 待索引;不代表没有历史测试 |
| artplayer-plugin-multiple-subtitles | API-11 TypeScript | PKG-MULTI-SUB-04 | 待索引;不代表没有历史测试 |
| artplayer-plugin-multiple-subtitles | API-12 持久状态与协议 | PKG-MULTI-SUB-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-vast | API-01 构造与配置 | PKG-VAST-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-vast | API-02 属性与方法 | PKG-VAST-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-vast | API-03 属性描述符 | PKG-VAST-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-vast | API-04 事件 | PKG-VAST-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-vast | API-05 生命周期 | PKG-VAST-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-vast | API-06 插件注册 | PKG-VAST-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-vast | API-07 生态集成 | PKG-VAST-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-vast | API-08 DOM/CSS | PKG-VAST-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-vast | API-09 包分发 | PKG-VAST-06 | 待索引;不代表没有历史测试 |
| artplayer-plugin-vast | API-10 浏览器能力 | REL-03 | 待索引;不代表没有历史测试 |
| artplayer-plugin-vast | API-11 TypeScript | PKG-VAST-04 | 待索引;不代表没有历史测试 |
| artplayer-plugin-vast | API-12 持久状态与协议 | PKG-VAST-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-vtt-thumbnail | API-01 构造与配置 | PKG-VTT-THUMB-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-vtt-thumbnail | API-02 属性与方法 | PKG-VTT-THUMB-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-vtt-thumbnail | API-03 属性描述符 | PKG-VTT-THUMB-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-vtt-thumbnail | API-04 事件 | PKG-VTT-THUMB-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-vtt-thumbnail | API-05 生命周期 | PKG-VTT-THUMB-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-vtt-thumbnail | API-06 插件注册 | PKG-VTT-THUMB-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-vtt-thumbnail | API-07 生态集成 | PKG-VTT-THUMB-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-vtt-thumbnail | API-08 DOM/CSS | PKG-VTT-THUMB-02 | 待索引;不代表没有历史测试 |
| artplayer-plugin-vtt-thumbnail | API-09 包分发 | PKG-VTT-THUMB-06 | 待索引;不代表没有历史测试 |
| artplayer-plugin-vtt-thumbnail | API-10 浏览器能力 | REL-03 | 待索引;不代表没有历史测试 |
| artplayer-plugin-vtt-thumbnail | API-11 TypeScript | PKG-VTT-THUMB-04 | 待索引;不代表没有历史测试 |
| artplayer-plugin-vtt-thumbnail | API-12 持久状态与协议 | PKG-VTT-THUMB-02 | 待索引;不代表没有历史测试 |
| artplayer-proxy-canvas | API-01 构造与配置 | PKG-CANVAS-02 | 待索引;不代表没有历史测试 |
| artplayer-proxy-canvas | API-02 属性与方法 | PKG-CANVAS-02 | 待索引;不代表没有历史测试 |
| artplayer-proxy-canvas | API-03 属性描述符 | PKG-CANVAS-02 | 待索引;不代表没有历史测试 |
| artplayer-proxy-canvas | API-04 事件 | PKG-CANVAS-02 | 待索引;不代表没有历史测试 |
| artplayer-proxy-canvas | API-05 生命周期 | PKG-CANVAS-02 | 待索引;不代表没有历史测试 |
| artplayer-proxy-canvas | API-06 插件注册 | PKG-CANVAS-02 | 待索引;不代表没有历史测试 |
| artplayer-proxy-canvas | API-07 生态集成 | PKG-CANVAS-02 | 待索引;不代表没有历史测试 |
| artplayer-proxy-canvas | API-08 DOM/CSS | PKG-CANVAS-02 | 待索引;不代表没有历史测试 |
| artplayer-proxy-canvas | API-09 包分发 | PKG-CANVAS-06 | 待索引;不代表没有历史测试 |
| artplayer-proxy-canvas | API-10 浏览器能力 | REL-03 | 待索引;不代表没有历史测试 |
| artplayer-proxy-canvas | API-11 TypeScript | PKG-CANVAS-04 | 待索引;不代表没有历史测试 |
| artplayer-proxy-canvas | API-12 持久状态与协议 | PKG-CANVAS-02 | 待索引;不代表没有历史测试 |
| artplayer-proxy-mediabunny | API-01 构造与配置 | PKG-MB-02 | 待索引;不代表没有历史测试 |
| artplayer-proxy-mediabunny | API-02 属性与方法 | PKG-MB-02 | 待索引;不代表没有历史测试 |
| artplayer-proxy-mediabunny | API-03 属性描述符 | PKG-MB-02 | 待索引;不代表没有历史测试 |
| artplayer-proxy-mediabunny | API-04 事件 | PKG-MB-02 | 待索引;不代表没有历史测试 |
| artplayer-proxy-mediabunny | API-05 生命周期 | PKG-MB-02 | 待索引;不代表没有历史测试 |
| artplayer-proxy-mediabunny | API-06 插件注册 | PKG-MB-02 | 待索引;不代表没有历史测试 |
| artplayer-proxy-mediabunny | API-07 生态集成 | PKG-MB-02 | 待索引;不代表没有历史测试 |
| artplayer-proxy-mediabunny | API-08 DOM/CSS | PKG-MB-02 | 待索引;不代表没有历史测试 |
| artplayer-proxy-mediabunny | API-09 包分发 | PKG-MB-10 | 待索引;不代表没有历史测试 |
| artplayer-proxy-mediabunny | API-10 浏览器能力 | REL-03 | 待索引;不代表没有历史测试 |
| artplayer-proxy-mediabunny | API-11 TypeScript | PKG-MB-08 | 待索引;不代表没有历史测试 |
| artplayer-proxy-mediabunny | API-12 持久状态与协议 | PKG-MB-02 | 待索引;不代表没有历史测试 |
| artplayer-tool-iframe | API-01 构造与配置 | PKG-IFRAME-02 | 待索引;不代表没有历史测试 |
| artplayer-tool-iframe | API-02 属性与方法 | PKG-IFRAME-02 | 待索引;不代表没有历史测试 |
| artplayer-tool-iframe | API-03 属性描述符 | PKG-IFRAME-02 | 待索引;不代表没有历史测试 |
| artplayer-tool-iframe | API-04 事件 | PKG-IFRAME-02 | 待索引;不代表没有历史测试 |
| artplayer-tool-iframe | API-05 生命周期 | PKG-IFRAME-02 | 待索引;不代表没有历史测试 |
| artplayer-tool-iframe | API-06 插件注册 | PKG-IFRAME-02 | 待索引;不代表没有历史测试 |
| artplayer-tool-iframe | API-07 生态集成 | PKG-IFRAME-02 | 待索引;不代表没有历史测试 |
| artplayer-tool-iframe | API-08 DOM/CSS | PKG-IFRAME-02 | 待索引;不代表没有历史测试 |
| artplayer-tool-iframe | API-09 包分发 | PKG-IFRAME-06 | 待索引;不代表没有历史测试 |
| artplayer-tool-iframe | API-10 浏览器能力 | REL-03 | 待索引;不代表没有历史测试 |
| artplayer-tool-iframe | API-11 TypeScript | PKG-IFRAME-04 | 待索引;不代表没有历史测试 |
| artplayer-tool-iframe | API-12 持久状态与协议 | PKG-IFRAME-02 | 待索引;不代表没有历史测试 |
| artplayer-tool-thumbnail | API-01 构造与配置 | PKG-TOOL-THUMB-02 | 待索引;不代表没有历史测试 |
| artplayer-tool-thumbnail | API-02 属性与方法 | PKG-TOOL-THUMB-02 | 待索引;不代表没有历史测试 |
| artplayer-tool-thumbnail | API-03 属性描述符 | PKG-TOOL-THUMB-02 | 待索引;不代表没有历史测试 |
| artplayer-tool-thumbnail | API-04 事件 | PKG-TOOL-THUMB-02 | 待索引;不代表没有历史测试 |
| artplayer-tool-thumbnail | API-05 生命周期 | PKG-TOOL-THUMB-02 | 待索引;不代表没有历史测试 |
| artplayer-tool-thumbnail | API-06 插件注册 | PKG-TOOL-THUMB-02 | 待索引;不代表没有历史测试 |
| artplayer-tool-thumbnail | API-07 生态集成 | PKG-TOOL-THUMB-02 | 待索引;不代表没有历史测试 |
| artplayer-tool-thumbnail | API-08 DOM/CSS | PKG-TOOL-THUMB-02 | 待索引;不代表没有历史测试 |
| artplayer-tool-thumbnail | API-09 包分发 | PKG-TOOL-THUMB-06 | 待索引;不代表没有历史测试 |
| artplayer-tool-thumbnail | API-10 浏览器能力 | REL-03 | 待索引;不代表没有历史测试 |
| artplayer-tool-thumbnail | API-11 TypeScript | PKG-TOOL-THUMB-04 | 待索引;不代表没有历史测试 |
| artplayer-tool-thumbnail | API-12 持久状态与协议 | PKG-TOOL-THUMB-02 | 待索引;不代表没有历史测试 |
| artplayer-vitepress | API-01 构造与配置 | SITE-06 | 待索引;不代表没有历史测试 |
| artplayer-vitepress | API-02 属性与方法 | SITE-06 | 待索引;不代表没有历史测试 |
| artplayer-vitepress | API-03 属性描述符 | SITE-06 | 待索引;不代表没有历史测试 |
| artplayer-vitepress | API-04 事件 | SITE-06 | 待索引;不代表没有历史测试 |
| artplayer-vitepress | API-05 生命周期 | SITE-06 | 待索引;不代表没有历史测试 |
| artplayer-vitepress | API-06 插件注册 | SITE-06 | 待索引;不代表没有历史测试 |
| artplayer-vitepress | API-07 生态集成 | SITE-06 | 待索引;不代表没有历史测试 |
| artplayer-vitepress | API-08 DOM/CSS | SITE-06 | 待索引;不代表没有历史测试 |
| artplayer-vitepress | API-09 包分发 | SITE-06 | 待索引;不代表没有历史测试 |
| artplayer-vitepress | API-10 浏览器能力 | SITE-06 | 待索引;不代表没有历史测试 |
| artplayer-vitepress | API-11 TypeScript | SITE-06 | 待索引;不代表没有历史测试 |
| artplayer-vitepress | API-12 持久状态与协议 | SITE-06 | 待索引;不代表没有历史测试 |
## 精确测试及限制
- CT-CORE-OPTIONS-001: [test/options.test.js](../test/options.test.js) — default values, keys and nested order match the published getter。Current option getter compared with archived published 5.4.0。限制:Source/controlled-host assertions only; no whole-family acceptance, installed tarball, browser, device or continuous support-range claim。命令:`yarn test:contracts`。版本依据:WS-artplayer, PUB-artplayer-5.4.0。
- CT-CORE-OPTIONS-002: [test/options.test.js](../test/options.test.js) — invalid JS input keeps the published validation path, first error and thrown kind。Nine invalid input patches compare first error name/message with published 5.4.0。限制:Source/controlled-host assertions only; no whole-family acceptance, installed tarball, browser, device or continuous support-range claim。命令:`yarn test:contracts`。版本依据:WS-artplayer, PUB-artplayer-5.4.0。
- CT-CORE-PLUGINS-001: [test/plugins.test.js](../test/plugins.test.js) — plugin registration keeps receiver, arguments, id, descriptors and naming precedence。Controlled registry checks receiver, identity, hidden nonwritable descriptor and naming conflicts。限制:Source/controlled-host assertions only; no whole-family acceptance, installed tarball, browser, device or continuous support-range claim。命令:`yarn test:contracts`。版本依据:WS-artplayer。
- CT-CORE-PLUGINS-002: [test/plugins.test.js](../test/plugins.test.js) — async factories preserve pending visibility, completion-time fallback and registry fulfillment。Controlled asynchronous factory visibility and returned registry。限制:Source/controlled-host assertions only; no whole-family acceptance, installed tarball, browser, device or continuous support-range claim。命令:`yarn test:contracts`。版本依据:WS-artplayer。
- CT-CORE-PLUGINS-003: [test/plugins.test.js](../test/plugins.test.js) — closed registries ignore late results without inspecting or destroying them and reject new factories。Disposed scope suppresses late result getters and disallows further factory execution。限制:Source/controlled-host assertions only; no whole-family acceptance, installed tarball, browser, device or continuous support-range claim。命令:`yarn test:contracts`。版本依据:WS-artplayer。
- CT-CORE-EVENTS-001: [test/public-behavior.test.js](../test/public-behavior.test.js) — EVENT.once-nested-snapshot: candidate consumes a once registration across nested dispatch snapshots。Published duplicate nested once dispatch is reproduced; current consumes once exactly once。限制:Source/controlled-host assertions only; no whole-family acceptance, installed tarball, browser, device or continuous support-range claim。命令:`yarn test:contracts`。版本依据:WS-artplayer, PUB-artplayer-5.4.0。
- CT-CORE-STORAGE-001: [test/storage.test.js](../test/storage.test.js) — storage preserves envelope, mutable name/settings fields and synchronous returns。Controlled localStorage verifies exact key/envelope and synchronous returns。限制:Source/controlled-host assertions only; no whole-family acceptance, installed tarball, browser, device or continuous support-range claim。命令:`yarn test:contracts`。版本依据:WS-artplayer。
- CT-CORE-STORAGE-002: [test/storage.test.js](../test/storage.test.js) — storage retains truthy key selection and historical JSON payload shapes。Array/scalar/null/malformed JSON cases retain historical fallback semantics。限制:Source/controlled-host assertions only; no whole-family acceptance, installed tarball, browser, device or continuous support-range claim。命令:`yarn test:contracts`。版本依据:WS-artplayer。
- CT-CHAPTER-NORMALIZE-001: [test/chapter.test.js](../test/chapter.test.js) — Chapter normalization preserves caller array and objects while filling every gap。Current normalizer retains caller array and chapter identity across repeated normalization。限制:Source/controlled-host assertions only; no whole-family acceptance, installed tarball, browser, device or continuous support-range claim。命令:`yarn test:contracts`。版本依据:WS-artplayer-plugin-chapter。
- CT-CHAPTER-NORMALIZE-002: [test/chapter.test.js](../test/chapter.test.js) — Chapter endpoints, adjacency and Infinity normalization retain historical behavior。Current normalizer retains adjacent endpoints and resolves Infinity against duration。限制:Source/controlled-host assertions only; no whole-family acceptance, installed tarball, browser, device or continuous support-range claim。命令:`yarn test:contracts`。版本依据:WS-artplayer-plugin-chapter。
## 版本依据
- WS-artplayer: artplayer@5.4.1 (workspace-baseline);[来源](../refactor/package-inventory.json) JSON pointer `/packages/0`。
- WS-artplayer-plugin-ads: artplayer-plugin-ads@2.1.0 (workspace-baseline);[来源](../refactor/package-inventory.json) JSON pointer `/packages/1`。
- WS-artplayer-plugin-ambilight: artplayer-plugin-ambilight@1.1.0 (workspace-baseline);[来源](../refactor/package-inventory.json) JSON pointer `/packages/2`。
- WS-artplayer-plugin-asr: artplayer-plugin-asr@2.1.0 (workspace-baseline);[来源](../refactor/package-inventory.json) JSON pointer `/packages/3`。
- WS-artplayer-plugin-audio-track: artplayer-plugin-audio-track@1.1.0 (workspace-baseline);[来源](../refactor/package-inventory.json) JSON pointer `/packages/4`。
- WS-artplayer-plugin-auto-thumbnail: artplayer-plugin-auto-thumbnail@1.1.0 (workspace-baseline);[来源](../refactor/package-inventory.json) JSON pointer `/packages/5`。
- WS-artplayer-plugin-chapter: artplayer-plugin-chapter@1.1.0 (workspace-baseline);[来源](../refactor/package-inventory.json) JSON pointer `/packages/6`。
- WS-artplayer-plugin-chromecast: artplayer-plugin-chromecast@1.1.0 (workspace-baseline);[来源](../refactor/package-inventory.json) JSON pointer `/packages/7`。
- WS-artplayer-plugin-danmuku: artplayer-plugin-danmuku@5.3.0 (workspace-baseline);[来源](../refactor/package-inventory.json) JSON pointer `/packages/8`。
- WS-artplayer-plugin-danmuku-mask: artplayer-plugin-danmuku-mask@1.1.0 (workspace-baseline);[来源](../refactor/package-inventory.json) JSON pointer `/packages/9`。
- WS-artplayer-plugin-dash-control: artplayer-plugin-dash-control@1.1.0 (workspace-baseline);[来源](../refactor/package-inventory.json) JSON pointer `/packages/10`。
- WS-artplayer-plugin-document-pip: artplayer-plugin-document-pip@1.1.0 (workspace-baseline);[来源](../refactor/package-inventory.json) JSON pointer `/packages/11`。
- WS-artplayer-plugin-hls-control: artplayer-plugin-hls-control@1.1.0 (workspace-baseline);[来源](../refactor/package-inventory.json) JSON pointer `/packages/12`。
- WS-artplayer-plugin-jassub: artplayer-plugin-jassub@1.1.0 (workspace-baseline);[来源](../refactor/package-inventory.json) JSON pointer `/packages/13`。
- WS-artplayer-plugin-multiple-subtitles: artplayer-plugin-multiple-subtitles@1.2.0 (workspace-baseline);[来源](../refactor/package-inventory.json) JSON pointer `/packages/14`。
- WS-artplayer-plugin-vast: artplayer-plugin-vast@1.2.0 (workspace-baseline);[来源](../refactor/package-inventory.json) JSON pointer `/packages/15`。
- WS-artplayer-plugin-vtt-thumbnail: artplayer-plugin-vtt-thumbnail@1.1.0 (workspace-baseline);[来源](../refactor/package-inventory.json) JSON pointer `/packages/16`。
- WS-artplayer-proxy-canvas: artplayer-proxy-canvas@1.1.0 (workspace-baseline);[来源](../refactor/package-inventory.json) JSON pointer `/packages/17`。
- WS-artplayer-proxy-mediabunny: artplayer-proxy-mediabunny@1.2.0 (workspace-baseline);[来源](../refactor/package-inventory.json) JSON pointer `/packages/18`。
- WS-artplayer-tool-iframe: artplayer-tool-iframe@1.1.0 (workspace-baseline);[来源](../refactor/package-inventory.json) JSON pointer `/packages/19`。
- WS-artplayer-tool-thumbnail: artplayer-tool-thumbnail@4.4.0 (workspace-baseline);[来源](../refactor/package-inventory.json) JSON pointer `/packages/20`。
- WS-artplayer-vitepress: artplayer-vitepress@1.1.0 (workspace-baseline);[来源](../refactor/package-inventory.json) JSON pointer `/packages/21`。
- PUB-artplayer-5.4.0: artplayer@5.4.0 (published-baseline);[来源](../refactor/baselines/releases.json) JSON pointer `/releases/0`。
- PUB-artplayer-plugin-chapter-1.1.0: artplayer-plugin-chapter@1.1.0 (published-baseline);[来源](../refactor/baselines/releases.json) JSON pointer `/releases/1`。
- PUB-artplayer-plugin-audio-track-1.1.0: artplayer-plugin-audio-track@1.1.0 (published-baseline);[来源](../refactor/baselines/audio-track-release.json) JSON pointer `/release`。
- PUB-artplayer-plugin-hls-control-1.1.0: artplayer-plugin-hls-control@1.1.0 (published-baseline);[来源](../refactor/baselines/hls-control-release.json) JSON pointer `/release`。
- PUB-artplayer-plugin-dash-control-1.1.0: artplayer-plugin-dash-control@1.1.0 (published-baseline);[来源](../refactor/baselines/dash-control-release.json) JSON pointer `/release`。
- PUB-artplayer-plugin-ads-1.0.6: artplayer-plugin-ads@1.0.6 (published-baseline);[来源](../refactor/baselines/ads-release.json) JSON pointer `/release`。
- PUB-artplayer-plugin-vast-1.0.0: artplayer-plugin-vast@1.0.0 (published-baseline);[来源](../refactor/baselines/vast-release.json) JSON pointer `/release`。
+957
View File
@@ -0,0 +1,957 @@
{
"schemaVersion": 1,
"families": [
{
"id": "API-01",
"name": "构造与配置"
},
{
"id": "API-02",
"name": "属性与方法"
},
{
"id": "API-03",
"name": "属性描述符"
},
{
"id": "API-04",
"name": "事件"
},
{
"id": "API-05",
"name": "生命周期"
},
{
"id": "API-06",
"name": "插件注册"
},
{
"id": "API-07",
"name": "生态集成"
},
{
"id": "API-08",
"name": "DOM/CSS"
},
{
"id": "API-09",
"name": "包分发"
},
{
"id": "API-10",
"name": "浏览器能力"
},
{
"id": "API-11",
"name": "TypeScript"
},
{
"id": "API-12",
"name": "持久状态与协议"
}
],
"packages": [
{
"name": "artplayer",
"owners": {
"API-01": "REVIEW-02",
"API-02": "REVIEW-02",
"API-03": "REVIEW-02",
"API-04": "REVIEW-02",
"API-05": "REVIEW-02",
"API-06": "REVIEW-02",
"API-07": "REVIEW-02",
"API-08": "REVIEW-02",
"API-09": "REVIEW-02",
"API-10": "REL-03",
"API-11": "REVIEW-02",
"API-12": "REVIEW-02"
},
"versionIds": [
"WS-artplayer",
"PUB-artplayer-5.4.0"
],
"supportWindow": "not-inferred-from-baseline",
"scopeReview": "Each public member, callback, option, event and capability requires package contract review; family ownership alone is not exhaustive coverage"
},
{
"name": "artplayer-plugin-ads",
"owners": {
"API-01": "PKG-ADS-02",
"API-02": "PKG-ADS-02",
"API-03": "PKG-ADS-02",
"API-04": "PKG-ADS-02",
"API-05": "PKG-ADS-02",
"API-06": "PKG-ADS-02",
"API-07": "PKG-ADS-02",
"API-08": "PKG-ADS-02",
"API-09": "PKG-ADS-06",
"API-10": "REL-03",
"API-11": "PKG-ADS-04",
"API-12": "PKG-ADS-02"
},
"versionIds": [
"WS-artplayer-plugin-ads",
"PUB-artplayer-plugin-ads-1.0.6"
],
"supportWindow": "not-inferred-from-baseline",
"scopeReview": "Each public member, callback, option, event and capability requires package contract review; family ownership alone is not exhaustive coverage"
},
{
"name": "artplayer-plugin-ambilight",
"owners": {
"API-01": "PKG-AMBILIGHT-02",
"API-02": "PKG-AMBILIGHT-02",
"API-03": "PKG-AMBILIGHT-02",
"API-04": "PKG-AMBILIGHT-02",
"API-05": "PKG-AMBILIGHT-02",
"API-06": "PKG-AMBILIGHT-02",
"API-07": "PKG-AMBILIGHT-02",
"API-08": "PKG-AMBILIGHT-02",
"API-09": "PKG-AMBILIGHT-06",
"API-10": "REL-03",
"API-11": "PKG-AMBILIGHT-04",
"API-12": "PKG-AMBILIGHT-02"
},
"versionIds": [
"WS-artplayer-plugin-ambilight"
],
"supportWindow": "not-inferred-from-baseline",
"scopeReview": "Each public member, callback, option, event and capability requires package contract review; family ownership alone is not exhaustive coverage"
},
{
"name": "artplayer-plugin-asr",
"owners": {
"API-01": "PKG-ASR-02",
"API-02": "PKG-ASR-02",
"API-03": "PKG-ASR-02",
"API-04": "PKG-ASR-02",
"API-05": "PKG-ASR-02",
"API-06": "PKG-ASR-02",
"API-07": "PKG-ASR-02",
"API-08": "PKG-ASR-02",
"API-09": "PKG-ASR-06",
"API-10": "REL-03",
"API-11": "PKG-ASR-04",
"API-12": "PKG-ASR-02"
},
"versionIds": [
"WS-artplayer-plugin-asr"
],
"supportWindow": "not-inferred-from-baseline",
"scopeReview": "Each public member, callback, option, event and capability requires package contract review; family ownership alone is not exhaustive coverage"
},
{
"name": "artplayer-plugin-audio-track",
"owners": {
"API-01": "PKG-AUDIO-02",
"API-02": "PKG-AUDIO-02",
"API-03": "PKG-AUDIO-02",
"API-04": "PKG-AUDIO-02",
"API-05": "PKG-AUDIO-02",
"API-06": "PKG-AUDIO-02",
"API-07": "PKG-AUDIO-02",
"API-08": "PKG-AUDIO-02",
"API-09": "PKG-AUDIO-06",
"API-10": "REL-03",
"API-11": "PKG-AUDIO-04",
"API-12": "PKG-AUDIO-02"
},
"versionIds": [
"WS-artplayer-plugin-audio-track",
"PUB-artplayer-plugin-audio-track-1.1.0"
],
"supportWindow": "not-inferred-from-baseline",
"scopeReview": "Each public member, callback, option, event and capability requires package contract review; family ownership alone is not exhaustive coverage"
},
{
"name": "artplayer-plugin-auto-thumbnail",
"owners": {
"API-01": "PKG-AUTO-THUMB-02",
"API-02": "PKG-AUTO-THUMB-02",
"API-03": "PKG-AUTO-THUMB-02",
"API-04": "PKG-AUTO-THUMB-02",
"API-05": "PKG-AUTO-THUMB-02",
"API-06": "PKG-AUTO-THUMB-02",
"API-07": "PKG-AUTO-THUMB-02",
"API-08": "PKG-AUTO-THUMB-02",
"API-09": "PKG-AUTO-THUMB-06",
"API-10": "REL-03",
"API-11": "PKG-AUTO-THUMB-04",
"API-12": "PKG-AUTO-THUMB-02"
},
"versionIds": [
"WS-artplayer-plugin-auto-thumbnail"
],
"supportWindow": "not-inferred-from-baseline",
"scopeReview": "Each public member, callback, option, event and capability requires package contract review; family ownership alone is not exhaustive coverage"
},
{
"name": "artplayer-plugin-chapter",
"owners": {
"API-01": "PKG-CHAPTER-02",
"API-02": "PKG-CHAPTER-02",
"API-03": "PKG-CHAPTER-02",
"API-04": "PKG-CHAPTER-02",
"API-05": "PKG-CHAPTER-02",
"API-06": "PKG-CHAPTER-02",
"API-07": "PKG-CHAPTER-02",
"API-08": "PKG-CHAPTER-02",
"API-09": "PKG-CHAPTER-06",
"API-10": "REL-03",
"API-11": "PKG-CHAPTER-04",
"API-12": "PKG-CHAPTER-02"
},
"versionIds": [
"WS-artplayer-plugin-chapter",
"PUB-artplayer-plugin-chapter-1.1.0"
],
"supportWindow": "not-inferred-from-baseline",
"scopeReview": "Each public member, callback, option, event and capability requires package contract review; family ownership alone is not exhaustive coverage"
},
{
"name": "artplayer-plugin-chromecast",
"owners": {
"API-01": "PKG-CAST-02",
"API-02": "PKG-CAST-02",
"API-03": "PKG-CAST-02",
"API-04": "PKG-CAST-02",
"API-05": "PKG-CAST-02",
"API-06": "PKG-CAST-02",
"API-07": "PKG-CAST-02",
"API-08": "PKG-CAST-02",
"API-09": "PKG-CAST-06",
"API-10": "REL-03",
"API-11": "PKG-CAST-04",
"API-12": "PKG-CAST-02"
},
"versionIds": [
"WS-artplayer-plugin-chromecast"
],
"supportWindow": "not-inferred-from-baseline",
"scopeReview": "Each public member, callback, option, event and capability requires package contract review; family ownership alone is not exhaustive coverage"
},
{
"name": "artplayer-plugin-danmuku",
"owners": {
"API-01": "PKG-DANMUKU-02",
"API-02": "PKG-DANMUKU-02",
"API-03": "PKG-DANMUKU-02",
"API-04": "PKG-DANMUKU-02",
"API-05": "PKG-DANMUKU-02",
"API-06": "PKG-DANMUKU-02",
"API-07": "PKG-DANMUKU-02",
"API-08": "PKG-DANMUKU-02",
"API-09": "PKG-DANMUKU-06",
"API-10": "REL-03",
"API-11": "PKG-DANMUKU-04",
"API-12": "PKG-DANMUKU-02"
},
"versionIds": [
"WS-artplayer-plugin-danmuku"
],
"supportWindow": "not-inferred-from-baseline",
"scopeReview": "Each public member, callback, option, event and capability requires package contract review; family ownership alone is not exhaustive coverage"
},
{
"name": "artplayer-plugin-danmuku-mask",
"owners": {
"API-01": "PKG-MASK-02",
"API-02": "PKG-MASK-02",
"API-03": "PKG-MASK-02",
"API-04": "PKG-MASK-02",
"API-05": "PKG-MASK-02",
"API-06": "PKG-MASK-02",
"API-07": "PKG-MASK-02",
"API-08": "PKG-MASK-02",
"API-09": "PKG-MASK-06",
"API-10": "REL-03",
"API-11": "PKG-MASK-04",
"API-12": "PKG-MASK-02"
},
"versionIds": [
"WS-artplayer-plugin-danmuku-mask"
],
"supportWindow": "not-inferred-from-baseline",
"scopeReview": "Each public member, callback, option, event and capability requires package contract review; family ownership alone is not exhaustive coverage"
},
{
"name": "artplayer-plugin-dash-control",
"owners": {
"API-01": "PKG-DASH-02",
"API-02": "PKG-DASH-02",
"API-03": "PKG-DASH-02",
"API-04": "PKG-DASH-02",
"API-05": "PKG-DASH-02",
"API-06": "PKG-DASH-02",
"API-07": "PKG-DASH-02",
"API-08": "PKG-DASH-02",
"API-09": "PKG-DASH-06",
"API-10": "REL-03",
"API-11": "PKG-DASH-04",
"API-12": "PKG-DASH-02"
},
"versionIds": [
"WS-artplayer-plugin-dash-control",
"PUB-artplayer-plugin-dash-control-1.1.0"
],
"supportWindow": "not-inferred-from-baseline",
"scopeReview": "Each public member, callback, option, event and capability requires package contract review; family ownership alone is not exhaustive coverage"
},
{
"name": "artplayer-plugin-document-pip",
"owners": {
"API-01": "PKG-DPIP-02",
"API-02": "PKG-DPIP-02",
"API-03": "PKG-DPIP-02",
"API-04": "PKG-DPIP-02",
"API-05": "PKG-DPIP-02",
"API-06": "PKG-DPIP-02",
"API-07": "PKG-DPIP-02",
"API-08": "PKG-DPIP-02",
"API-09": "PKG-DPIP-06",
"API-10": "REL-03",
"API-11": "PKG-DPIP-04",
"API-12": "PKG-DPIP-02"
},
"versionIds": [
"WS-artplayer-plugin-document-pip"
],
"supportWindow": "not-inferred-from-baseline",
"scopeReview": "Each public member, callback, option, event and capability requires package contract review; family ownership alone is not exhaustive coverage"
},
{
"name": "artplayer-plugin-hls-control",
"owners": {
"API-01": "PKG-HLS-02",
"API-02": "PKG-HLS-02",
"API-03": "PKG-HLS-02",
"API-04": "PKG-HLS-02",
"API-05": "PKG-HLS-02",
"API-06": "PKG-HLS-02",
"API-07": "PKG-HLS-02",
"API-08": "PKG-HLS-02",
"API-09": "PKG-HLS-06",
"API-10": "REL-03",
"API-11": "PKG-HLS-04",
"API-12": "PKG-HLS-02"
},
"versionIds": [
"WS-artplayer-plugin-hls-control",
"PUB-artplayer-plugin-hls-control-1.1.0"
],
"supportWindow": "not-inferred-from-baseline",
"scopeReview": "Each public member, callback, option, event and capability requires package contract review; family ownership alone is not exhaustive coverage"
},
{
"name": "artplayer-plugin-jassub",
"owners": {
"API-01": "PKG-JASSUB-02",
"API-02": "PKG-JASSUB-02",
"API-03": "PKG-JASSUB-02",
"API-04": "PKG-JASSUB-02",
"API-05": "PKG-JASSUB-02",
"API-06": "PKG-JASSUB-02",
"API-07": "PKG-JASSUB-02",
"API-08": "PKG-JASSUB-02",
"API-09": "PKG-JASSUB-06",
"API-10": "REL-03",
"API-11": "PKG-JASSUB-04",
"API-12": "PKG-JASSUB-02"
},
"versionIds": [
"WS-artplayer-plugin-jassub"
],
"supportWindow": "not-inferred-from-baseline",
"scopeReview": "Each public member, callback, option, event and capability requires package contract review; family ownership alone is not exhaustive coverage"
},
{
"name": "artplayer-plugin-multiple-subtitles",
"owners": {
"API-01": "PKG-MULTI-SUB-02",
"API-02": "PKG-MULTI-SUB-02",
"API-03": "PKG-MULTI-SUB-02",
"API-04": "PKG-MULTI-SUB-02",
"API-05": "PKG-MULTI-SUB-02",
"API-06": "PKG-MULTI-SUB-02",
"API-07": "PKG-MULTI-SUB-02",
"API-08": "PKG-MULTI-SUB-02",
"API-09": "PKG-MULTI-SUB-06",
"API-10": "REL-03",
"API-11": "PKG-MULTI-SUB-04",
"API-12": "PKG-MULTI-SUB-02"
},
"versionIds": [
"WS-artplayer-plugin-multiple-subtitles"
],
"supportWindow": "not-inferred-from-baseline",
"scopeReview": "Each public member, callback, option, event and capability requires package contract review; family ownership alone is not exhaustive coverage"
},
{
"name": "artplayer-plugin-vast",
"owners": {
"API-01": "PKG-VAST-02",
"API-02": "PKG-VAST-02",
"API-03": "PKG-VAST-02",
"API-04": "PKG-VAST-02",
"API-05": "PKG-VAST-02",
"API-06": "PKG-VAST-02",
"API-07": "PKG-VAST-02",
"API-08": "PKG-VAST-02",
"API-09": "PKG-VAST-06",
"API-10": "REL-03",
"API-11": "PKG-VAST-04",
"API-12": "PKG-VAST-02"
},
"versionIds": [
"WS-artplayer-plugin-vast",
"PUB-artplayer-plugin-vast-1.0.0"
],
"supportWindow": "not-inferred-from-baseline",
"scopeReview": "Each public member, callback, option, event and capability requires package contract review; family ownership alone is not exhaustive coverage"
},
{
"name": "artplayer-plugin-vtt-thumbnail",
"owners": {
"API-01": "PKG-VTT-THUMB-02",
"API-02": "PKG-VTT-THUMB-02",
"API-03": "PKG-VTT-THUMB-02",
"API-04": "PKG-VTT-THUMB-02",
"API-05": "PKG-VTT-THUMB-02",
"API-06": "PKG-VTT-THUMB-02",
"API-07": "PKG-VTT-THUMB-02",
"API-08": "PKG-VTT-THUMB-02",
"API-09": "PKG-VTT-THUMB-06",
"API-10": "REL-03",
"API-11": "PKG-VTT-THUMB-04",
"API-12": "PKG-VTT-THUMB-02"
},
"versionIds": [
"WS-artplayer-plugin-vtt-thumbnail"
],
"supportWindow": "not-inferred-from-baseline",
"scopeReview": "Each public member, callback, option, event and capability requires package contract review; family ownership alone is not exhaustive coverage"
},
{
"name": "artplayer-proxy-canvas",
"owners": {
"API-01": "PKG-CANVAS-02",
"API-02": "PKG-CANVAS-02",
"API-03": "PKG-CANVAS-02",
"API-04": "PKG-CANVAS-02",
"API-05": "PKG-CANVAS-02",
"API-06": "PKG-CANVAS-02",
"API-07": "PKG-CANVAS-02",
"API-08": "PKG-CANVAS-02",
"API-09": "PKG-CANVAS-06",
"API-10": "REL-03",
"API-11": "PKG-CANVAS-04",
"API-12": "PKG-CANVAS-02"
},
"versionIds": [
"WS-artplayer-proxy-canvas"
],
"supportWindow": "not-inferred-from-baseline",
"scopeReview": "Each public member, callback, option, event and capability requires package contract review; family ownership alone is not exhaustive coverage"
},
{
"name": "artplayer-proxy-mediabunny",
"owners": {
"API-01": "PKG-MB-02",
"API-02": "PKG-MB-02",
"API-03": "PKG-MB-02",
"API-04": "PKG-MB-02",
"API-05": "PKG-MB-02",
"API-06": "PKG-MB-02",
"API-07": "PKG-MB-02",
"API-08": "PKG-MB-02",
"API-09": "PKG-MB-10",
"API-10": "REL-03",
"API-11": "PKG-MB-08",
"API-12": "PKG-MB-02"
},
"versionIds": [
"WS-artplayer-proxy-mediabunny"
],
"supportWindow": "not-inferred-from-baseline",
"scopeReview": "Each public member, callback, option, event and capability requires package contract review; family ownership alone is not exhaustive coverage"
},
{
"name": "artplayer-tool-iframe",
"owners": {
"API-01": "PKG-IFRAME-02",
"API-02": "PKG-IFRAME-02",
"API-03": "PKG-IFRAME-02",
"API-04": "PKG-IFRAME-02",
"API-05": "PKG-IFRAME-02",
"API-06": "PKG-IFRAME-02",
"API-07": "PKG-IFRAME-02",
"API-08": "PKG-IFRAME-02",
"API-09": "PKG-IFRAME-06",
"API-10": "REL-03",
"API-11": "PKG-IFRAME-04",
"API-12": "PKG-IFRAME-02"
},
"versionIds": [
"WS-artplayer-tool-iframe"
],
"supportWindow": "not-inferred-from-baseline",
"scopeReview": "Each public member, callback, option, event and capability requires package contract review; family ownership alone is not exhaustive coverage"
},
{
"name": "artplayer-tool-thumbnail",
"owners": {
"API-01": "PKG-TOOL-THUMB-02",
"API-02": "PKG-TOOL-THUMB-02",
"API-03": "PKG-TOOL-THUMB-02",
"API-04": "PKG-TOOL-THUMB-02",
"API-05": "PKG-TOOL-THUMB-02",
"API-06": "PKG-TOOL-THUMB-02",
"API-07": "PKG-TOOL-THUMB-02",
"API-08": "PKG-TOOL-THUMB-02",
"API-09": "PKG-TOOL-THUMB-06",
"API-10": "REL-03",
"API-11": "PKG-TOOL-THUMB-04",
"API-12": "PKG-TOOL-THUMB-02"
},
"versionIds": [
"WS-artplayer-tool-thumbnail"
],
"supportWindow": "not-inferred-from-baseline",
"scopeReview": "Each public member, callback, option, event and capability requires package contract review; family ownership alone is not exhaustive coverage"
},
{
"name": "artplayer-vitepress",
"owners": {
"API-01": "SITE-06",
"API-02": "SITE-06",
"API-03": "SITE-06",
"API-04": "SITE-06",
"API-05": "SITE-06",
"API-06": "SITE-06",
"API-07": "SITE-06",
"API-08": "SITE-06",
"API-09": "SITE-06",
"API-10": "SITE-06",
"API-11": "SITE-06",
"API-12": "SITE-06"
},
"versionIds": [
"WS-artplayer-vitepress"
],
"supportWindow": "not-inferred-from-baseline",
"scopeReview": "SITE-01/06 must distinguish website behavior from npm library APIs; all families retain review ownership, no invented player constructor"
}
],
"versions": [
{
"id": "WS-artplayer",
"package": "artplayer",
"kind": "workspace-baseline",
"version": "5.4.1",
"file": "refactor/package-inventory.json",
"pointer": "/packages/0"
},
{
"id": "WS-artplayer-plugin-ads",
"package": "artplayer-plugin-ads",
"kind": "workspace-baseline",
"version": "2.1.0",
"file": "refactor/package-inventory.json",
"pointer": "/packages/1"
},
{
"id": "WS-artplayer-plugin-ambilight",
"package": "artplayer-plugin-ambilight",
"kind": "workspace-baseline",
"version": "1.1.0",
"file": "refactor/package-inventory.json",
"pointer": "/packages/2"
},
{
"id": "WS-artplayer-plugin-asr",
"package": "artplayer-plugin-asr",
"kind": "workspace-baseline",
"version": "2.1.0",
"file": "refactor/package-inventory.json",
"pointer": "/packages/3"
},
{
"id": "WS-artplayer-plugin-audio-track",
"package": "artplayer-plugin-audio-track",
"kind": "workspace-baseline",
"version": "1.1.0",
"file": "refactor/package-inventory.json",
"pointer": "/packages/4"
},
{
"id": "WS-artplayer-plugin-auto-thumbnail",
"package": "artplayer-plugin-auto-thumbnail",
"kind": "workspace-baseline",
"version": "1.1.0",
"file": "refactor/package-inventory.json",
"pointer": "/packages/5"
},
{
"id": "WS-artplayer-plugin-chapter",
"package": "artplayer-plugin-chapter",
"kind": "workspace-baseline",
"version": "1.1.0",
"file": "refactor/package-inventory.json",
"pointer": "/packages/6"
},
{
"id": "WS-artplayer-plugin-chromecast",
"package": "artplayer-plugin-chromecast",
"kind": "workspace-baseline",
"version": "1.1.0",
"file": "refactor/package-inventory.json",
"pointer": "/packages/7"
},
{
"id": "WS-artplayer-plugin-danmuku",
"package": "artplayer-plugin-danmuku",
"kind": "workspace-baseline",
"version": "5.3.0",
"file": "refactor/package-inventory.json",
"pointer": "/packages/8"
},
{
"id": "WS-artplayer-plugin-danmuku-mask",
"package": "artplayer-plugin-danmuku-mask",
"kind": "workspace-baseline",
"version": "1.1.0",
"file": "refactor/package-inventory.json",
"pointer": "/packages/9"
},
{
"id": "WS-artplayer-plugin-dash-control",
"package": "artplayer-plugin-dash-control",
"kind": "workspace-baseline",
"version": "1.1.0",
"file": "refactor/package-inventory.json",
"pointer": "/packages/10"
},
{
"id": "WS-artplayer-plugin-document-pip",
"package": "artplayer-plugin-document-pip",
"kind": "workspace-baseline",
"version": "1.1.0",
"file": "refactor/package-inventory.json",
"pointer": "/packages/11"
},
{
"id": "WS-artplayer-plugin-hls-control",
"package": "artplayer-plugin-hls-control",
"kind": "workspace-baseline",
"version": "1.1.0",
"file": "refactor/package-inventory.json",
"pointer": "/packages/12"
},
{
"id": "WS-artplayer-plugin-jassub",
"package": "artplayer-plugin-jassub",
"kind": "workspace-baseline",
"version": "1.1.0",
"file": "refactor/package-inventory.json",
"pointer": "/packages/13"
},
{
"id": "WS-artplayer-plugin-multiple-subtitles",
"package": "artplayer-plugin-multiple-subtitles",
"kind": "workspace-baseline",
"version": "1.2.0",
"file": "refactor/package-inventory.json",
"pointer": "/packages/14"
},
{
"id": "WS-artplayer-plugin-vast",
"package": "artplayer-plugin-vast",
"kind": "workspace-baseline",
"version": "1.2.0",
"file": "refactor/package-inventory.json",
"pointer": "/packages/15"
},
{
"id": "WS-artplayer-plugin-vtt-thumbnail",
"package": "artplayer-plugin-vtt-thumbnail",
"kind": "workspace-baseline",
"version": "1.1.0",
"file": "refactor/package-inventory.json",
"pointer": "/packages/16"
},
{
"id": "WS-artplayer-proxy-canvas",
"package": "artplayer-proxy-canvas",
"kind": "workspace-baseline",
"version": "1.1.0",
"file": "refactor/package-inventory.json",
"pointer": "/packages/17"
},
{
"id": "WS-artplayer-proxy-mediabunny",
"package": "artplayer-proxy-mediabunny",
"kind": "workspace-baseline",
"version": "1.2.0",
"file": "refactor/package-inventory.json",
"pointer": "/packages/18"
},
{
"id": "WS-artplayer-tool-iframe",
"package": "artplayer-tool-iframe",
"kind": "workspace-baseline",
"version": "1.1.0",
"file": "refactor/package-inventory.json",
"pointer": "/packages/19"
},
{
"id": "WS-artplayer-tool-thumbnail",
"package": "artplayer-tool-thumbnail",
"kind": "workspace-baseline",
"version": "4.4.0",
"file": "refactor/package-inventory.json",
"pointer": "/packages/20"
},
{
"id": "WS-artplayer-vitepress",
"package": "artplayer-vitepress",
"kind": "workspace-baseline",
"version": "1.1.0",
"file": "refactor/package-inventory.json",
"pointer": "/packages/21"
},
{
"id": "PUB-artplayer-5.4.0",
"package": "artplayer",
"kind": "published-baseline",
"version": "5.4.0",
"file": "refactor/baselines/releases.json",
"pointer": "/releases/0",
"archiveSha256": "42b269a66b9658d53f9a92572bb36d656ea9af56254a387b3a69c7ddea43979e"
},
{
"id": "PUB-artplayer-plugin-chapter-1.1.0",
"package": "artplayer-plugin-chapter",
"kind": "published-baseline",
"version": "1.1.0",
"file": "refactor/baselines/releases.json",
"pointer": "/releases/1",
"archiveSha256": "4c3173661854ff96f643a479c0f1c2db7ce9e54681523bd059a7dd9bd77eeada"
},
{
"id": "PUB-artplayer-plugin-audio-track-1.1.0",
"package": "artplayer-plugin-audio-track",
"kind": "published-baseline",
"version": "1.1.0",
"file": "refactor/baselines/audio-track-release.json",
"pointer": "/release",
"archiveSha256": "7e84f6a2f4bfada428a77ef04b749bf3db53e0149fa1afe7bf5749d925e801ff"
},
{
"id": "PUB-artplayer-plugin-hls-control-1.1.0",
"package": "artplayer-plugin-hls-control",
"kind": "published-baseline",
"version": "1.1.0",
"file": "refactor/baselines/hls-control-release.json",
"pointer": "/release",
"archiveSha256": "598c354d78d3cb77965136fa61dfc0f94cdea6906a91a30781c1acef2b0c3e8b"
},
{
"id": "PUB-artplayer-plugin-dash-control-1.1.0",
"package": "artplayer-plugin-dash-control",
"kind": "published-baseline",
"version": "1.1.0",
"file": "refactor/baselines/dash-control-release.json",
"pointer": "/release",
"archiveSha256": "f4047a569d99b6a7de57b3719b7ad40109f2a1d360566a840f50ab32aacefcd9"
},
{
"id": "PUB-artplayer-plugin-ads-1.0.6",
"package": "artplayer-plugin-ads",
"kind": "published-baseline",
"version": "1.0.6",
"file": "refactor/baselines/ads-release.json",
"pointer": "/release",
"archiveSha256": "ff132ff8a7108d8b26421621e4aac1d7edf8c99099a5ab4fd2190bd79b8636cd"
},
{
"id": "PUB-artplayer-plugin-vast-1.0.0",
"package": "artplayer-plugin-vast",
"kind": "published-baseline",
"version": "1.0.0",
"file": "refactor/baselines/vast-release.json",
"pointer": "/release",
"archiveSha256": "35681d60fa3fa09b92f0f30887e39c73cf55e7b14d51a1c38fccbc6c1f253bab"
}
],
"cases": [
{
"id": "CT-CORE-OPTIONS-001",
"package": "artplayer",
"contracts": [
"API-01"
],
"runner": "node",
"command": "yarn test:contracts",
"file": "test/options.test.js",
"title": "default values, keys and nested order match the published getter",
"versionIds": [
"WS-artplayer",
"PUB-artplayer-5.4.0"
],
"scope": "Current option getter compared with archived published 5.4.0",
"limitations": "Source/controlled-host assertions only; no whole-family acceptance, installed tarball, browser, device or continuous support-range claim"
},
{
"id": "CT-CORE-OPTIONS-002",
"package": "artplayer",
"contracts": [
"API-01"
],
"runner": "node",
"command": "yarn test:contracts",
"file": "test/options.test.js",
"title": "invalid JS input keeps the published validation path, first error and thrown kind",
"versionIds": [
"WS-artplayer",
"PUB-artplayer-5.4.0"
],
"scope": "Nine invalid input patches compare first error name/message with published 5.4.0",
"limitations": "Source/controlled-host assertions only; no whole-family acceptance, installed tarball, browser, device or continuous support-range claim"
},
{
"id": "CT-CORE-PLUGINS-001",
"package": "artplayer",
"contracts": [
"API-03",
"API-06"
],
"runner": "node",
"command": "yarn test:contracts",
"file": "test/plugins.test.js",
"title": "plugin registration keeps receiver, arguments, id, descriptors and naming precedence",
"versionIds": [
"WS-artplayer"
],
"scope": "Controlled registry checks receiver, identity, hidden nonwritable descriptor and naming conflicts",
"limitations": "Source/controlled-host assertions only; no whole-family acceptance, installed tarball, browser, device or continuous support-range claim"
},
{
"id": "CT-CORE-PLUGINS-002",
"package": "artplayer",
"contracts": [
"API-02",
"API-06"
],
"runner": "node",
"command": "yarn test:contracts",
"file": "test/plugins.test.js",
"title": "async factories preserve pending visibility, completion-time fallback and registry fulfillment",
"versionIds": [
"WS-artplayer"
],
"scope": "Controlled asynchronous factory visibility and returned registry",
"limitations": "Source/controlled-host assertions only; no whole-family acceptance, installed tarball, browser, device or continuous support-range claim"
},
{
"id": "CT-CORE-PLUGINS-003",
"package": "artplayer",
"contracts": [
"API-05",
"API-06"
],
"runner": "node",
"command": "yarn test:contracts",
"file": "test/plugins.test.js",
"title": "closed registries ignore late results without inspecting or destroying them and reject new factories",
"versionIds": [
"WS-artplayer"
],
"scope": "Disposed scope suppresses late result getters and disallows further factory execution",
"limitations": "Source/controlled-host assertions only; no whole-family acceptance, installed tarball, browser, device or continuous support-range claim"
},
{
"id": "CT-CORE-EVENTS-001",
"package": "artplayer",
"contracts": [
"API-04"
],
"runner": "node",
"command": "yarn test:contracts",
"file": "test/public-behavior.test.js",
"title": "EVENT.once-nested-snapshot: candidate consumes a once registration across nested dispatch snapshots",
"versionIds": [
"WS-artplayer",
"PUB-artplayer-5.4.0"
],
"scope": "Published duplicate nested once dispatch is reproduced; current consumes once exactly once",
"limitations": "Source/controlled-host assertions only; no whole-family acceptance, installed tarball, browser, device or continuous support-range claim"
},
{
"id": "CT-CORE-STORAGE-001",
"package": "artplayer",
"contracts": [
"API-02",
"API-12"
],
"runner": "node",
"command": "yarn test:contracts",
"file": "test/storage.test.js",
"title": "storage preserves envelope, mutable name/settings fields and synchronous returns",
"versionIds": [
"WS-artplayer"
],
"scope": "Controlled localStorage verifies exact key/envelope and synchronous returns",
"limitations": "Source/controlled-host assertions only; no whole-family acceptance, installed tarball, browser, device or continuous support-range claim"
},
{
"id": "CT-CORE-STORAGE-002",
"package": "artplayer",
"contracts": [
"API-12"
],
"runner": "node",
"command": "yarn test:contracts",
"file": "test/storage.test.js",
"title": "storage retains truthy key selection and historical JSON payload shapes",
"versionIds": [
"WS-artplayer"
],
"scope": "Array/scalar/null/malformed JSON cases retain historical fallback semantics",
"limitations": "Source/controlled-host assertions only; no whole-family acceptance, installed tarball, browser, device or continuous support-range claim"
},
{
"id": "CT-CHAPTER-NORMALIZE-001",
"package": "artplayer-plugin-chapter",
"contracts": [
"API-01",
"API-02"
],
"runner": "node",
"command": "yarn test:contracts",
"file": "test/chapter.test.js",
"title": "Chapter normalization preserves caller array and objects while filling every gap",
"versionIds": [
"WS-artplayer-plugin-chapter"
],
"scope": "Current normalizer retains caller array and chapter identity across repeated normalization",
"limitations": "Source/controlled-host assertions only; no whole-family acceptance, installed tarball, browser, device or continuous support-range claim"
},
{
"id": "CT-CHAPTER-NORMALIZE-002",
"package": "artplayer-plugin-chapter",
"contracts": [
"API-02"
],
"runner": "node",
"command": "yarn test:contracts",
"file": "test/chapter.test.js",
"title": "Chapter endpoints, adjacency and Infinity normalization retain historical behavior",
"versionIds": [
"WS-artplayer-plugin-chapter"
],
"scope": "Current normalizer retains adjacent endpoints and resolves Infinity against duration",
"limitations": "Source/controlled-host assertions only; no whole-family acceptance, installed tarball, browser, device or continuous support-range claim"
}
]
}
+13
View File
@@ -0,0 +1,13 @@
{
"schemaVersion": 1,
"runs": [
{
"file": "refactor/baselines/contract-observations/2026-09-12-source.json",
"sha256LF": "53e8b6f0483b9fcd555790faf454d17b652f15c30e82997d82fda69012061096"
},
{
"file": "refactor/baselines/contract-observations/2026-09-12-source-final.json",
"sha256LF": "9700e66ba9b33b811d2178c8c6c0c2b58c6cc7d802f33e929d6aa447204b6c42"
}
]
}
+4
View File
@@ -1,5 +1,9 @@
# 消费者与真实环境矩阵
时间范围说明:BASE-08初始状态及分段补充保留采集时事实,不是当前全局进度快照。
早期“未运行”不能覆盖后续证据;当前查progress和包validation。逐包契约、版本、
测试及历史报告入口见 [契约覆盖索引](contract-coverage.md)。
BASE-08;2026-09-10;来源提交 ed293231。矩阵建立完成,矩阵内未运行的检查仍待完成。版本与路径以 [发布冻结](baselines/releases.json)、[包初始快照](package-inventory.json)、[分发观察](baselines/distribution.json)、[demo 清单](baselines/demo-inventory.json) 和 [第三方清单](third-party.json) 为依据。
## 版本和分发维度
+3 -2
View File
@@ -4,7 +4,7 @@
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 220 项,范围 22 个包及工作区/示例。
状态:todo 132 / doing 6 / blocked 0 / done 82 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
状态:todo 131 / doing 6 / blocked 0 / done 83 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。
@@ -84,7 +84,7 @@
| ENG-08 | workspace<br>增加覆盖率、资源与性能报告 | ENG-03, ENG-05, BASE-06 | 覆盖率基线、资源清理断言、性能报告与阈值 | 关键生命周期分支有门槛,报告不靠无意义断言堆数量;将 BASE-06 的同环境多组配对、计时/压缩审查阈值与资源异常分开接入候选,不把历史现象冻结成正常要求 | M | done |
| ENG-AUDIT-01 | workspace<br>核对每任务完成提交的真实Git历史 | ENG-07, DOC-04 | 独立完成提交、初始DOC例外、父分支合并和证据共提交审计;CI完整历史与报告 | 真实仓库及隔离Git反例通过,done任务不允许缺独立commit,固定DOC-01~04例外可追溯;提交后验证自身 | M | done |
| ENG-IMPACT-01 | workspace<br>建立全包依赖与共享变更影响映射 | ENG-07, DOC-04 | 核心/构建/类型/锁文件到受影响包和必需生态检查的映射及CI接入 | 核心变化触发必需生态检查;新增包、未知共享文件或失配依赖不能静默漏检;解释受影响路径和测试命令 | M | done |
| ENG-COVERAGE-01 | workspace<br>建立公开契约与版本测试证据覆盖索引 | BASE-08, CORE-22, ENG-07 | 契约-包-支持版本-固定测试ID-命令-候选/报告-责任任务索引和文档检查 | 计划、已执行和缺证据状态分开,所有公开契约都有验证归属;无效路径、版本依据或报告对应不明确会被识别 | M | todo |
| ENG-COVERAGE-01 | workspace<br>建立公开契约与版本测试证据覆盖索引 | BASE-08, CORE-22, ENG-07 | 契约-包-支持版本-固定测试ID-命令-候选/报告-责任任务索引和文档检查 | 计划、已执行和缺证据状态分开,所有公开契约都有验证归属;无效路径、版本依据或报告对应不明确会被识别 | M | done |
| ENG-09 | workspace<br>建立全包依赖影响和文档检查 | ENG-07, DOC-04, ENG-AUDIT-01, ENG-IMPACT-01, ENG-COVERAGE-01 | 共享核心/构建影响映射、文档及每任务完成提交的 Git 审计接入;契约-支持版本-测试 ID-命令-候选/报告-任务的覆盖索引 | 核心变化触发必需生态检查;原有 DOC-01 至 04 基线例外明确,后续 done 任务不能缺失独立 commit;计划/已执行/缺证据分开,识别缺少验证归属的公开契约 | M | todo |
| ENG-10 | workspace<br>建立历史失败分级和测试可靠性规则 | ENG-03, ENG-04, ENG-05, ENG-07, BASE-07 | 历史失败 ID/环境/旧版复现/负责修复任务、逐模块门槛、受控等待与 trace/retry 规则 | 不靠全局忽略或无理由 skip 隐藏问题,新增回归阻止交付,设备缺口和偶发失败单独可见;以 risks.json 为统一差异索引;关闭必须有 resolutionEvidence/rationale,已复现、源码事实、未验证分开,登记不等于豁免 | M | done |
| ENG-11 | workspace<br>增加不改变产物的模块构建分析 | ENG-03, ENG-08 | 正式构建的可选模块归因报告及源码/产物指纹 | 相同构建开关前后三格式字节一致,报告不进入分发包;区分 Rollup 渲染字节与压缩体积,并保留真实工作区/发布来源 | L | done |
@@ -442,6 +442,7 @@
- ENG-08: [记录](coverage-performance.md) [记录](baselines/quality-validation.json) [记录](changes/2026-09-11-ENG-08-quality-reports.md)
- ENG-AUDIT-01: [记录](commit-audit.md) [记录](changes/2026-09-12-ENG-AUDIT-01-commits.md) [记录](baselines/commit-audit-validation.json)
- ENG-IMPACT-01: [记录](impact-analysis.md) [记录](changes/2026-09-12-ENG-IMPACT-01-mapping.md) [记录](baselines/impact-validation.json)
- ENG-COVERAGE-01: [记录](contract-coverage.md) [记录](contract-index.md) [记录](changes/2026-09-12-ENG-COVERAGE-01-index.md) [记录](baselines/contracts-validation.json)
- ENG-10: [记录](changes/2026-09-10-ENG-10-test-reliability.md) [记录](test-reliability.md)
- ENG-11: [记录](build-analysis.md) [记录](baselines/bundle-attribution.json) [记录](changes/2026-09-11-ENG-11-build-analysis.md)
- PILOT-01: [记录](changes/2026-09-10-PILOT-01-chapter.md) [记录](baselines/pilot-validation.json)
+11
View File
@@ -1,5 +1,16 @@
# 进度与证据
## 最新完成:ENG-COVERAGE-01 契约归属及精确证据索引
22包×12类契约共264行已分配验证任务,29个冻结版本对照点可校验。逐条核对10个已有
断言并接入Node结构化执行;44项重跑通过,不当成新增功能覆盖。旧报告、旧输入、
旧定义、skip/失败和未索引状态分别记录;255行仍待精确测试索引,不表示没有历史测试。
7组反例及完整CI主测试链918项通过;另有44项报告链路重复执行。修复Windows reporter
specifier,补上空清单与重复断言防护,完整失败/通过证据归档。当前220项:83 done、
6 doing、131 todo。父ENG-09仍待整合核验;VAST初始化选择待确认,其他包实施继续。
本步独立本地提交,随后审计自身;未推送/发布。见 [实施记录](changes/2026-09-12-ENG-COVERAGE-01-index.md)
和 [验证记录](baselines/contracts-validation.json)。
## 最新完成:ENG-IMPACT-01 全包影响与必需检查映射
实际22包依赖/验证关系、共享构建/类型/锁文件及示例映射已接入ci:check;Git变更包含
+8
View File
@@ -0,0 +1,8 @@
export default async function* contractReporter(events) {
for await (const event of events) {
if (['test:pass', 'test:fail', 'test:summary'].includes(event.type)) {
const { name, file, nesting, skip, todo, counts, success, details } = event.data
yield `${JSON.stringify({ type: event.type, name, file, nesting, skip, todo, counts, success, kind: details?.type, error: details?.error?.message })}\n`
}
}
}
+200
View File
@@ -0,0 +1,200 @@
import assert from 'node:assert/strict'
import { execFileSync } from 'node:child_process'
import { createHash } from 'node:crypto'
import fs from 'node:fs'
import path from 'node:path'
import ts from 'typescript'
import { repositoryPath } from './impact-model.mjs'
export const sha256 = value => createHash('sha256').update(value).digest('hex')
export const readJson = (directory, file) => JSON.parse(fs.readFileSync(localFile(directory, file), 'utf8'))
export function localFile(directory, file) {
assert.equal(repositoryPath(file), file, `Noncanonical contract path: ${file}`)
const absolute = path.join(directory, file)
const relative = path.relative(fs.realpathSync(directory), fs.realpathSync(absolute))
assert(relative && !relative.startsWith('..') && !path.isAbsolute(relative), `Redirected contract path: ${file}`)
assert(fs.statSync(absolute).isFile(), `Contract evidence is not a file: ${file}`)
return absolute
}
export function pointer(document, value) {
assert(/^\//.test(value), 'Expected a JSON pointer')
return value.slice(1).split('/').reduce((item, part) => {
const key = part.replaceAll('~1', '/').replaceAll('~0', '~')
assert(item && Object.hasOwn(item, key), `Missing version evidence pointer: ${value}`)
return item[key]
}, document)
}
export function candidateInputs(directory, model) {
const files = new Set(['package.json', 'yarn.lock', 'refactor/contract-policy.json', '.node-version'])
const walk = (folder) => {
if (!fs.existsSync(path.join(directory, folder)))
return
for (const entry of fs.readdirSync(path.join(directory, folder), { withFileTypes: true })) {
const file = `${folder}/${entry.name}`
assert(!entry.isSymbolicLink(), `Review redirected contract input: ${file}`)
if (entry.isDirectory())
walk(file)
else files.add(file)
}
}
for (const pkg of model.policy.packages) {
files.add(`packages/${pkg.name}/package.json`)
for (const folder of ['src', 'types', 'public']) walk(`packages/${pkg.name}/${folder}`)
}
for (const folder of ['scripts', 'test', 'types', 'refactor/scripts', 'refactor/fixtures']) walk(folder)
for (const version of model.policy.versions) files.add(version.file)
const inputs = [...files].sort().map(file => ({ file, sha256: sha256(fs.readFileSync(localFile(directory, file))) }))
return { digest: sha256(JSON.stringify(inputs)), inputs }
}
function validateTest(directory, item) {
assert(/^CT-[A-Z0-9-]+$/.test(item.id), 'Invalid stable contract test ID')
assert(item.command === 'yarn test:contracts' && item.runner === 'node', 'Unsupported contract runner/command')
const source = ts.createSourceFile(item.file, fs.readFileSync(localFile(directory, item.file), 'utf8'), ts.ScriptTarget.Latest, true)
assert.equal(source.parseDiagnostics.length, 0, `Malformed contract test: ${item.file}`)
const imported = source.statements.some(statement => ts.isImportDeclaration(statement) && statement.moduleSpecifier.text === 'node:test'
&& (statement.importClause?.name?.text === 'test' || statement.importClause?.namedBindings?.elements?.some(element => element.name.text === 'test' && (!element.propertyName || element.propertyName.text === 'test'))))
assert(imported, `Contract case requires the Node test binding: ${item.id}`)
const matches = []
function visit(node) {
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === 'test' && node.arguments[0] && ts.isStringLiteralLike(node.arguments[0]) && node.arguments[0].text === item.title)
matches.push(node)
ts.forEachChild(node, visit)
}
visit(source)
assert.equal(matches.length, 1, `Contract test title must identify exactly one literal test: ${item.id}`)
assert(item.scope?.length && item.limitations?.length, `Missing assertion scope/limits: ${item.id}`)
}
export function readContractModel(directory) {
const policy = readJson(directory, 'refactor/contract-policy.json')
assert.equal(policy.schemaVersion, 1)
const scripts = readJson(directory, 'package.json').scripts
assert.equal(scripts?.['test:contracts'], 'node refactor/scripts/run-contracts.mjs', 'Contract runner command changed; review its evidence adapter')
assert.equal(scripts?.['check:contracts'], 'node refactor/scripts/contracts.mjs', 'Contract index command changed')
const stages = scripts['ci:check']?.split(' && ') || []
assert(stages.includes('yarn test:contracts') && stages.indexOf('yarn check:contracts --report') > stages.indexOf('yarn test:contracts'), 'CI must run contract observations before checking their report')
const tasks = readJson(directory, 'refactor/tasks.json').tasks
const inventory = readJson(directory, 'refactor/package-inventory.json').packages
const families = [...fs.readFileSync(localFile(directory, 'refactor/compatibility.md'), 'utf8').matchAll(/^\| (API-\d{2}) \| ([^|]+) \|/gm)].map(match => ({ id: match[1], name: match[2].trim() }))
assert(families.length > 0)
assert.deepEqual(policy.families, families, 'Public contract families changed; update their coverage ownership')
const names = inventory.map(pkg => pkg.name).sort()
assert.deepEqual(policy.packages.map(pkg => pkg.name).sort(), names, 'Contract package ownership is incomplete or duplicated')
const actual = fs.readdirSync(path.join(directory, 'packages')).filter(name => fs.existsSync(path.join(directory, 'packages', name, 'package.json'))).sort()
assert.deepEqual(actual, names, 'New package must be registered in inventory and contracts')
const unique = (items, label) => assert.equal(new Set(items.map(item => item.id)).size, items.length, `Duplicate ${label} ID`)
unique(policy.versions, 'version')
unique(policy.cases, 'case')
assert(policy.cases.length > 0, 'Contract runner requires explicit cases; empty selection must not discover unrelated tests')
assert.equal(new Set(policy.cases.map(item => JSON.stringify([item.file, item.title]))).size, policy.cases.length, 'Duplicate file/title under different contract IDs')
for (const version of policy.versions) {
assert(names.includes(version.package), 'Version references unknown package')
const origin = pointer(readJson(directory, version.file), version.pointer)
assert.equal(origin.name, version.package, `Wrong package in version evidence: ${version.id}`)
assert.equal(origin.version, version.version, `Wrong version evidence: ${version.id}`)
if (version.kind === 'published-baseline') {
assert(/^[a-f\d]{64}$/.test(origin.sha256) && origin.integrity?.startsWith('sha512-'), `Published version needs archived integrity: ${version.id}`)
assert.equal(version.archiveSha256, origin.sha256, `Published archive identity changed: ${version.id}`)
}
else {
assert.equal(version.kind, 'workspace-baseline')
assert.equal(version.file, 'refactor/package-inventory.json', 'Workspace version must use the frozen inventory')
}
}
for (const pkg of policy.packages) {
assert.deepEqual(Object.keys(pkg.owners).sort(), families.map(family => family.id).sort(), `Missing contract owner: ${pkg.name}`)
for (const owner of Object.values(pkg.owners)) {
const task = tasks.find(task => task.id === owner)
assert(task && (task.scope.includes(pkg.name) || task.scope.includes('workspace')), `Invalid contract responsibility: ${pkg.name}/${owner}`)
}
assert(pkg.versionIds.length && pkg.versionIds.every(id => policy.versions.some(version => version.id === id && version.package === pkg.name)), `Missing package version basis: ${pkg.name}`)
assert.equal(pkg.supportWindow, 'not-inferred-from-baseline', 'A baseline must not become an invented supported version range')
assert(pkg.scopeReview?.length, 'Contract applicability must remain explicit')
}
for (const item of policy.cases) {
const pkg = policy.packages.find(pkg => pkg.name === item.package)
assert(pkg && item.contracts.length && item.contracts.every(id => families.some(family => family.id === id)), 'Unknown case package/contract')
assert(item.versionIds.length && item.versionIds.every(id => policy.versions.some(version => version.id === id && version.package === item.package)), 'Unknown test version basis')
validateTest(directory, item)
}
const historical = tasks.flatMap(task => task.evidence.filter(file => /\.json$/.test(file)).map(file => ({ task: task.id, scope: task.scope, file: `refactor/${file}` })))
for (const item of historical) localFile(directory, item.file)
return { policy, families, tasks, historical }
}
export function evaluateRun(model, report, currentDigest) {
assert.equal(report.schemaVersion, 1)
assert.equal(report.command, 'yarn test:contracts')
assert(/^[a-f\d]{40}$/.test(report.head) && report.environment?.node && report.environment?.platform, 'Run lacks candidate/environment provenance')
assert.equal(report.candidate.digest, sha256(JSON.stringify(report.candidate.inputs)), 'Candidate input fingerprint mismatch')
assert(report.candidate.inputs.length && report.candidate.inputs.every(input => /^[a-f\d]{64}$/.test(input.sha256)), 'Invalid candidate inputs')
for (const input of report.candidate.inputs) repositoryPath(input.file)
assert.equal(new Set(report.cases.map(item => item.id)).size, report.cases.length, 'Duplicate run case identity')
assert(report.events.length && ['passed', 'failed'].includes(report.outcome), 'Run is missing test events/outcome')
const summaries = report.events.filter(event => event.type === 'test:summary' && !event.file)
assert.equal(summaries.length, 1, 'Run requires one process summary')
assert.equal(report.outcome === 'passed', summaries[0].success === true, 'Run outcome contradicts process summary')
if (report.outcome === 'passed')
assert(!report.events.some(event => event.type === 'test:fail' && !event.todo), 'Passed run contains a failed test')
return report.cases.map((item) => {
const defined = model.policy.cases.find(candidate => candidate.id === item.id)
assert(defined, `Unknown reported test ID: ${item.id}`)
assert.equal(item.definition?.id, item.id, `Wrong recorded definition: ${item.id}`)
assert.equal(item.definitionSha256, sha256(JSON.stringify(item.definition)), `Reported test definition fingerprint changed: ${item.id}`)
assert.equal(item.definition.package, defined.package, `Stable test ID was reassigned to another package: ${item.id}`)
const matches = report.events.filter(event => ['test:pass', 'test:fail'].includes(event.type) && event.file === item.definition.file && event.name === item.definition.title)
assert.equal(matches.length, 1, `Missing or ambiguous executed test: ${item.id}`)
const event = matches[0]
const status = event.skip || event.todo ? 'skipped' : event.type === 'test:fail' ? 'failed' : 'passed'
assert.equal(item.status, status, `Reported test outcome mismatch: ${item.id}`)
return { id: item.id, package: item.definition.package, contracts: item.definition.contracts, versionIds: item.definition.versionIds, status, freshness: item.definitionSha256 !== sha256(JSON.stringify(defined)) ? 'historical-definition' : report.candidate.digest === currentDigest ? 'current-inputs' : 'historical-inputs', scope: item.definition.scope }
})
}
export function contractStatus(directory, model) {
const current = candidateInputs(directory, model)
const registry = readJson(directory, 'refactor/contract-runs.json')
assert.equal(registry.schemaVersion, 1)
assert.equal(new Set(registry.runs.map(run => run.file)).size, registry.runs.length, 'Duplicate registered report')
const observations = []
for (const ref of registry.runs) {
const bytes = fs.readFileSync(localFile(directory, ref.file))
assert.equal(sha256(bytes.toString('utf8').replaceAll('\r\n', '\n')), ref.sha256LF, `Contract report changed: ${ref.file}`)
const report = JSON.parse(bytes)
observations.push({ file: ref.file, outcome: report.outcome, cases: evaluateRun(model, report, current.digest) })
}
const latest = 'refactor/.cache/ci/contracts-run.json'
if (fs.existsSync(path.join(directory, latest))) {
const report = readJson(directory, latest)
observations.push({ file: latest, outcome: report.outcome, cases: evaluateRun(model, report, current.digest) })
}
const rows = model.policy.packages.flatMap(pkg => model.families.map((family) => {
const cases = model.policy.cases.filter(item => item.package === pkg.name && item.contracts.includes(family.id))
const results = observations.flatMap(run => run.cases.filter(item => item.package === pkg.name && item.contracts.includes(family.id)).map(item => ({ ...item, report: run.file, runOutcome: run.outcome })))
return { package: pkg.name, contract: family.id, owner: pkg.owners[family.id], versionIds: pkg.versionIds, supportWindow: pkg.supportWindow, state: results.length ? 'partial-observations' : cases.length ? 'planned-not-observed' : 'tests-not-indexed', cases: cases.map(item => item.id), observations: results, remaining: 'Family-wide acceptance, version combinations and environment scope are not implied by individual test observations' }
}))
return { schemaVersion: 1, candidateDigest: current.digest, rows, observations, historicalRecords: model.historical.map(item => ({ ...item, state: 'recorded-not-normalized' })), limitation: 'Index status is not package compatibility or npm release approval' }
}
export function renderContracts(model) {
const lines = ['# 公开契约覆盖索引', '', '> 由 contract-policy.json 生成;`yarn check:contracts --write` 更新。动态候选/报告状态见 `yarn check:contracts --report`。', '', '覆盖12类兼容契约与22包的验证归属;不是每个公开成员的穷尽测试证明。没有固定测试ID的行仍需逐成员补齐。', '版本均为精确冻结对照点,不能推导连续支持区间。历史报告保留原状态,只有精确用例和候选指纹对应时才列为观察结果。', '', '| 包 | 契约 | 责任任务 | 固定测试ID |', '| --- | --- | --- | --- |']
for (const pkg of model.policy.packages) {
for (const family of model.families) {
const cases = model.policy.cases.filter(item => item.package === pkg.name && item.contracts.includes(family.id))
lines.push(`| ${pkg.name} | ${family.id} ${family.name} | ${pkg.owners[family.id]} | ${cases.map(item => item.id).join(', ') || '待索引;不代表没有历史测试'} |`)
}
}
lines.push('', '## 精确测试及限制', '')
for (const item of model.policy.cases)
lines.push(`- ${item.id}: [${item.file}](../${item.file}) — ${item.title}。${item.scope}。限制:${item.limitations}。命令:\`${item.command}\`。版本依据:${item.versionIds.join(', ')}。`)
lines.push('', '## 版本依据', '')
for (const version of model.policy.versions)
lines.push(`- ${version.id}: ${version.package}@${version.version} (${version.kind});[来源](../${version.file}) JSON pointer \`${version.pointer}\`。`)
return `${lines.join('\n')}\n`
}
export const currentHead = directory => execFileSync('git', ['rev-parse', 'HEAD'], { cwd: directory, encoding: 'utf8' }).trim()
+23
View File
@@ -0,0 +1,23 @@
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 { contractStatus, readContractModel, renderContracts } from './contracts-model.mjs'
const root = fileURLToPath(new URL('../../', import.meta.url))
const args = process.argv.slice(2)
assert(args.every(arg => ['--write', '--report'].includes(arg)), 'Use --write and/or --report')
const model = readContractModel(root)
const markdown = renderContracts(model)
const file = path.join(root, 'refactor/contract-index.md')
if (args.includes('--write'))
fs.writeFileSync(file, markdown)
else assert.equal(fs.readFileSync(file, 'utf8').replaceAll('\r\n', '\n'), markdown, 'Contract index is stale; run yarn check:contracts --write')
const report = contractStatus(root, model)
if (args.includes('--report')) {
fs.mkdirSync(path.join(root, 'refactor/.cache/ci'), { recursive: true })
fs.writeFileSync(path.join(root, 'refactor/.cache/ci/contracts.json'), `${JSON.stringify(report, null, 2)}\n`)
}
console.log(`Contracts: ${model.policy.packages.length} packages, ${report.rows.length} assigned families, ${model.policy.cases.length} indexed cases, ${report.observations.length} structured runs`)
console.log(`${report.rows.filter(row => row.state === 'tests-not-indexed').length} family rows need test indexing; historical records are not auto-promoted to current acceptance`)
+185
View File
@@ -0,0 +1,185 @@
import assert from 'node:assert/strict'
import { spawnSync } from 'node:child_process'
import fs from 'node:fs'
import path from 'node:path'
import process from 'node:process'
// eslint-disable-next-line test/no-import-node-test -- Contract evidence validation uses real Node runner results and isolated files.
import test from 'node:test'
import { fileURLToPath, pathToFileURL } from 'node:url'
import { candidateInputs, contractStatus, evaluateRun, localFile, pointer, readContractModel, renderContracts, sha256 } from './contracts-model.mjs'
const root = fileURLToPath(new URL('../../', import.meta.url))
const actual = readContractModel(root)
const cache = path.join(root, 'refactor/.cache')
function fixture(t) {
fs.mkdirSync(cache, { recursive: true })
const directory = fs.mkdtempSync(path.join(cache, 'contracts-test-'))
t.after(() => {
const relative = path.relative(cache, directory)
assert(relative.startsWith('contracts-test-') && !relative.includes(path.sep))
fs.rmSync(directory, { recursive: true, force: true })
})
const write = (file, value) => {
fs.mkdirSync(path.dirname(path.join(directory, file)), { recursive: true })
fs.writeFileSync(path.join(directory, file), typeof value === 'string' ? value : JSON.stringify(value))
}
const policy = structuredClone(actual.policy)
const save = () => write('refactor/contract-policy.json', policy)
save()
write('refactor/tasks.json', { tasks: actual.tasks.map(task => ({ ...task, evidence: [] })) })
for (const file of new Set(['refactor/package-inventory.json', 'refactor/compatibility.md', ...policy.versions.map(version => version.file)]))
write(file, fs.readFileSync(path.join(root, file), 'utf8'))
for (const pkg of policy.packages) {
write(`packages/${pkg.name}/package.json`, { name: pkg.name })
write(`packages/${pkg.name}/src/index.ts`, 'export const value = 1')
}
for (const file of new Set(policy.cases.map(item => item.file)))
write(file, `import test from 'node:test'\n${policy.cases.filter(item => item.file === file).map(item => `test(${JSON.stringify(item.title)}, () => {})`).join('\n')}`)
for (const file of ['package.json', 'yarn.lock', '.node-version']) write(file, fs.readFileSync(path.join(root, file), 'utf8'))
write('refactor/contract-runs.json', { schemaVersion: 1, runs: [] })
return { directory, policy, save, write, model: () => readContractModel(directory) }
}
test('Actual contract model assigns all 264 family rows and separates exact published versions from workspace snapshots', () => {
assert.equal(actual.policy.packages.length, 22)
assert.equal(actual.families.length, 12)
assert.equal(actual.policy.versions.filter(version => version.kind === 'published-baseline').length, 7)
assert.equal(actual.policy.versions.filter(version => version.kind === 'workspace-baseline').length, 22)
assert.equal(actual.policy.cases.length, 10)
const markdown = renderContracts(actual)
assert.equal(markdown.split('\n').filter(line => /^\| artplayer[^|]* \| API-/.test(line)).length, 264)
assert.equal(pointer({ 'a/b': { '~': 2 } }, '/a~1b/~0'), 2)
})
test('Missing families, package owners, tasks and unsupported version claims cannot silently pass', (t) => {
for (const edit of [
(policy) => { policy.families.pop() },
(policy) => { policy.packages.pop() },
(policy) => { delete policy.packages[0].owners['API-01'] },
(policy) => { policy.packages[0].owners['API-01'] = 'NONEXISTENT' },
(policy) => { policy.packages[0].supportWindow = 'all-historical-versions' },
(policy) => { policy.packages[0].versionIds = ['invented'] },
]) {
const repo = fixture(t)
edit(repo.policy)
repo.save()
assert.throws(repo.model)
}
const repo = fixture(t)
const manifest = JSON.parse(fs.readFileSync(path.join(repo.directory, 'package.json'), 'utf8'))
manifest.scripts['ci:check'] = manifest.scripts['ci:check'].replace('yarn test:contracts && ', '')
repo.write('package.json', manifest)
assert.throws(repo.model, /CI must run/)
})
test('Version evidence checks JSON pointer, package identity, exact version and archive integrity', (t) => {
for (const edit of [
(version) => { version.pointer = '/releases/99' },
(version) => { version.version = '99.0.0' },
(version) => { version.package = 'artplayer-plugin-chapter' },
(version) => { version.archiveSha256 = 'a'.repeat(64) },
(version) => { version.file = '../outside.json' },
]) {
const repo = fixture(t)
edit(repo.policy.versions.find(version => version.kind === 'published-baseline'))
repo.save()
assert.throws(repo.model)
}
})
test('Stable IDs require an actual unique literal test and valid paths; comments, skips and renamed assertions are not enough', (t) => {
for (const source of [
title => `import test from 'node:test'; // test(${JSON.stringify(title)}, () => {})`,
title => `import test from 'node:test'; test.skip(${JSON.stringify(title)}, () => {})`,
() => `import test from 'node:test'; test('renamed', () => {})`,
title => `import test from 'node:test'; test(${JSON.stringify(title)}, () => {}); test(${JSON.stringify(title)}, () => {})`,
]) {
const repo = fixture(t)
repo.write(repo.policy.cases[0].file, source(repo.policy.cases[0].title))
assert.throws(repo.model, /exactly one literal/)
}
const repo = fixture(t)
repo.policy.cases.push(repo.policy.cases[0])
repo.save()
assert.throws(repo.model, /Duplicate case/)
repo.policy.cases[repo.policy.cases.length - 1] = { ...repo.policy.cases[0], id: 'CT-DUPLICATE-ASSERTION' }
repo.save()
assert.throws(repo.model, /Duplicate file\/title/)
repo.policy.cases = []
repo.save()
assert.throws(repo.model, /requires explicit cases/)
for (const file of ['../outside', 'D:/outside', '/absolute', 'test\\outside.js'])
assert.throws(() => localFile(repo.directory, file))
})
function observation(model, candidate, item, status = 'passed') {
const event = { type: status === 'failed' ? 'test:fail' : 'test:pass', name: item.title, file: item.file, ...(status === 'skipped' ? { skip: 'unsupported fixture' } : {}) }
return { schemaVersion: 1, command: 'yarn test:contracts', head: 'a'.repeat(40), environment: { node: process.version, platform: process.platform }, candidate, outcome: status === 'failed' ? 'failed' : 'passed', events: [event, { type: 'test:summary', success: status !== 'failed' }], cases: [{ id: item.id, definition: item, definitionSha256: sha256(JSON.stringify(item)), status }] }
}
test('Evidence distinguishes current, stale, skipped and failed; wrong titles, fingerprints and process outcomes fail', (t) => {
const repo = fixture(t)
const model = repo.model()
const candidate = candidateInputs(repo.directory, model)
const item = model.policy.cases[0]
const passed = observation(model, candidate, item)
assert.equal(evaluateRun(model, passed, candidate.digest)[0].freshness, 'current-inputs')
repo.write('packages/artplayer/src/index.ts', 'export const value = 2')
assert.equal(evaluateRun(model, passed, candidateInputs(repo.directory, model).digest)[0].freshness, 'historical-inputs')
for (const status of ['skipped', 'failed'])
assert.equal(evaluateRun(model, observation(model, candidate, item, status), candidate.digest)[0].status, status)
for (const edit of [
(report) => { report.events[0].name = 'different assertion' },
(report) => { report.cases[0].status = 'skipped' },
(report) => { report.candidate.digest = 'a'.repeat(64) },
(report) => { report.cases[0].definitionSha256 = 'b'.repeat(64) },
(report) => { report.events.at(-1).success = false },
(report) => { report.events.push(report.events[0]) },
]) {
const report = structuredClone(passed)
edit(report)
assert.throws(() => evaluateRun(model, report, candidate.digest))
}
const modified = structuredClone(model)
modified.policy.cases[0].scope = 'A revised assertion scope'
assert.equal(evaluateRun(modified, passed, candidate.digest)[0].freshness, 'historical-definition')
})
test('Report registry checks immutable bytes and never upgrades individual observations into complete families', (t) => {
const repo = fixture(t)
const model = repo.model()
const report = observation(model, candidateInputs(repo.directory, model), model.policy.cases[0])
const file = 'refactor/observation.json'
repo.write(file, report)
const bytes = fs.readFileSync(path.join(repo.directory, file))
repo.write('refactor/contract-runs.json', { schemaVersion: 1, runs: [{ file, sha256LF: sha256(bytes) }] })
const result = contractStatus(repo.directory, model)
assert.equal(result.rows.length, 264)
assert.equal(result.rows.find(row => row.package === 'artplayer' && row.contract === 'API-01').state, 'partial-observations')
assert(result.rows.some(row => row.state === 'planned-not-observed'))
assert(result.rows.some(row => row.state === 'tests-not-indexed'))
repo.policy.cases[0].contracts = ['API-09']
repo.save()
const revised = contractStatus(repo.directory, repo.model())
assert.equal(revised.rows.find(row => row.package === 'artplayer' && row.contract === 'API-09').observations.length, 0)
assert.equal(revised.rows.find(row => row.package === 'artplayer' && row.contract === 'API-01').observations[0].freshness, 'historical-definition')
repo.write(file, { ...report, outcome: 'failed' })
assert.throws(() => contractStatus(repo.directory, model), /Contract report changed/)
})
test('Real Node reporter retains exact file/name, failure, skip, todo and overall process failure', (t) => {
const repo = fixture(t)
repo.write('runner.test.mjs', 'import test from \'node:test\';test(\'passing\',()=>{});test(\'failure\',()=>{throw Error(\'fixture failure\')});test(\'skipped\',{skip:\'no environment\'},()=>{});test(\'todo\',{todo:\'pending\'},()=>{});')
const env = { ...process.env }
delete env.NODE_TEST_CONTEXT
delete env.NODE_OPTIONS
const result = spawnSync(process.execPath, ['--test', `--test-reporter=${pathToFileURL(path.join(root, 'refactor/scripts/contract-reporter.mjs')).href}`, 'runner.test.mjs'], { cwd: repo.directory, env, encoding: 'utf8' })
assert.equal(result.status, 1, result.stderr)
const events = result.stdout.trim().split(/\r?\n/).map(line => JSON.parse(line))
assert(events.some(event => event.name === 'passing' && event.type === 'test:pass' && event.file === path.join(repo.directory, 'runner.test.mjs')))
assert(events.some(event => event.name === 'failure' && event.type === 'test:fail'))
assert(events.some(event => event.name === 'skipped' && event.skip))
assert(events.some(event => event.name === 'todo' && event.todo))
assert.equal(events.find(event => event.type === 'test:summary' && !event.file).success, false)
})
+54
View File
@@ -0,0 +1,54 @@
import assert from 'node:assert/strict'
import { spawnSync } from 'node:child_process'
import fs from 'node:fs'
import path from 'node:path'
import process from 'node:process'
import { fileURLToPath } from 'node:url'
import { candidateInputs, currentHead, evaluateRun, readContractModel, sha256 } from './contracts-model.mjs'
const root = fileURLToPath(new URL('../../', import.meta.url))
assert.equal(process.argv.length, 2, 'Contract runner does not accept filters or alternate artifacts')
const model = readContractModel(root)
const candidate = candidateInputs(root, model)
const head = currentHead(root)
const directory = path.join(root, 'refactor/.cache/contracts')
fs.mkdirSync(directory, { recursive: true })
const output = fs.mkdtempSync(path.join(directory, 'run-'))
const files = [...new Set(model.policy.cases.map(item => item.file))]
const env = { ...process.env }
for (const key of Object.keys(env)) {
if (key.startsWith('ARTPLAYER_') || ['NODE_OPTIONS', 'NODE_TEST_CONTEXT', 'NODE_V8_COVERAGE'].includes(key))
delete env[key]
}
const command = ['--test', '--test-reporter=./refactor/scripts/contract-reporter.mjs', ...files]
const executed = spawnSync(process.execPath, command, { cwd: root, env, encoding: 'utf8', timeout: 180000, maxBuffer: 16 * 1024 * 1024 })
fs.writeFileSync(path.join(output, 'events.jsonl'), executed.stdout || '')
fs.writeFileSync(path.join(output, 'stderr.log'), executed.stderr || '')
assert(!executed.error, `Contract process failed: ${executed.error?.message}; evidence ${output}`)
assert.equal(candidateInputs(root, model).digest, candidate.digest, 'Contract inputs changed during execution')
const events = executed.stdout.split(/\r?\n/).filter(Boolean).map(line => JSON.parse(line)).map(event => ({ ...event, ...(event.file ? { file: path.relative(root, event.file).split(path.sep).join('/') } : {}) }))
const report = {
schemaVersion: 1,
command: 'yarn test:contracts',
invocation: [process.execPath, ...command],
head,
capturedAt: new Date().toISOString(),
environment: { node: process.version, platform: process.platform, arch: process.arch, artifactOverrides: 'cleared' },
candidate,
events,
outcome: executed.status === 0 ? 'passed' : 'failed',
cases: model.policy.cases.map((item) => {
const matches = events.filter(event => ['test:pass', 'test:fail'].includes(event.type) && event.file === item.file && event.name === item.title)
assert.equal(matches.length, 1, `Missing or ambiguous runner result: ${item.id}; evidence ${output}`)
const event = matches[0]
return { id: item.id, definition: item, definitionSha256: sha256(JSON.stringify(item)), status: event.skip || event.todo ? 'skipped' : event.type === 'test:pass' ? 'passed' : 'failed' }
}),
limitations: 'Selected source/controlled-host assertions only; not full family, package tarball, browser, device, SDK or support-range acceptance',
}
evaluateRun(model, report, candidate.digest)
fs.writeFileSync(path.join(output, 'report.json'), `${JSON.stringify(report, null, 2)}\n`)
fs.mkdirSync(path.join(root, 'refactor/.cache/ci'), { recursive: true })
fs.writeFileSync(path.join(root, 'refactor/.cache/ci/contracts-run.json'), `${JSON.stringify(report, null, 2)}\n`)
fs.writeFileSync(path.join(directory, 'latest.json'), `${JSON.stringify({ output: path.relative(root, output).split(path.sep).join('/') }, null, 2)}\n`)
console.log(`Contract observations: ${report.cases.length} indexed cases; process ${report.outcome}; ${path.relative(root, output)}`)
process.exitCode = executed.status === 0 && report.cases.every(item => item.status === 'passed') ? 0 : 1
+7 -2
View File
@@ -654,7 +654,7 @@
"risk": "M",
"id": "ENG-COVERAGE-01",
"title": "建立公开契约与版本测试证据覆盖索引",
"status": "todo",
"status": "done",
"dependsOn": [
"BASE-08",
"CORE-22",
@@ -662,7 +662,12 @@
],
"deliverable": "契约-包-支持版本-固定测试ID-命令-候选/报告-责任任务索引和文档检查",
"acceptance": "计划、已执行和缺证据状态分开,所有公开契约都有验证归属;无效路径、版本依据或报告对应不明确会被识别",
"evidence": []
"evidence": [
"contract-coverage.md",
"contract-index.md",
"changes/2026-09-12-ENG-COVERAGE-01-index.md",
"baselines/contracts-validation.json"
]
},
{
"id": "ENG-09",
+4 -1
View File
@@ -1,5 +1,8 @@
# 测试与验收
本清单是验收目录,早期未勾选项不代表当前未实施。计划、历史执行与精确候选观察
见tasks/progress及 [契约覆盖索引](contract-coverage.md),不能以总通过数代替契约验证。
核心/chapter 的 Node TS 覆盖率使用 `yarn test:coverage`。源映射防伪验证、关键生命周期门槛、报告位置及资源/性能实施状态见 [覆盖率与性能维护说明](coverage-performance.md)。
## 测试层次
@@ -79,4 +82,4 @@ ENG-02 先交付仓库内可执行的 CI 配置;GitHub required checks 的实
## 本计划不代表已有测试
以上新增命令、测试目录和矩阵都是待实现内容。现有测试事实及评估范围见 [progress.md](progress.md)。
以上目录源自初始计划,部分命令、测试和矩阵已实施;当前事实及范围见 [progress.md](progress.md)。