@skillbuilder/miniapp-simulator 0.2.2 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/docs/assets/audit-auto-stop-config-2026-10-21.jpg +0 -0
  2. package/docs/assets/audit-balanced-panel-2026-10-16.jpg +0 -0
  3. package/docs/assets/audit-lcp-input-2026-10-20.jpg +0 -0
  4. package/docs/assets/audit-paint-game3d-2026-10-16.jpg +0 -0
  5. package/docs/assets/audit-paint-webgl-2026-10-16.jpg +0 -0
  6. package/docs/assets/audit-paint-webgl-apm-excerpt-2026-10-16.json +397 -0
  7. package/docs/assets/audit-poetry-2026-10-16.jpg +0 -0
  8. package/docs/assets/audit-remaining-results-2026-10-16.json +2749 -0
  9. package/docs/assets/miniapp-audit-risk-review-2026-10-13.png +0 -0
  10. package/docs/assets/miniapp-audit-runtime-hung-2026-10-13.png +0 -0
  11. package/docs/audit-data-collection.codex.md +422 -0
  12. package/docs/audit-local-validation.codex.md +165 -0
  13. package/docs/audit-score-design.codex.md +221 -0
  14. package/docs/audit-score-design.md +885 -0
  15. package/docs/audit-score-tech-plan.md +448 -0
  16. package/docs/audit-tech-design.codex.md +468 -0
  17. package/docs/electron-review.md +223 -12
  18. package/docs/quality-evaluation-redoc.xml +163 -0
  19. package/lib/client-BDt3WDu_.mjs +1118 -0
  20. package/lib/{index-D5AYVv8s.mjs → index-7MeYFlk_.mjs} +210 -182
  21. package/lib/index.js +1254 -1115
  22. package/lib/remote-client.js +2 -2
  23. package/lib/types/host/shadow-host.d.ts +10 -0
  24. package/lib/types/injection/registry.d.ts +5 -0
  25. package/lib/types/remote/audit-controller.d.ts +56 -0
  26. package/lib/types/remote/audit-leave-monitor.d.ts +12 -0
  27. package/lib/types/remote/audit-telemetry.d.ts +23 -0
  28. package/lib/types/remote/audit-watchdog.d.ts +14 -0
  29. package/lib/types/remote/client.d.ts +25 -1
  30. package/lib/types/remote/load-timing.d.ts +5 -0
  31. package/lib/types/remote/protocol.d.ts +151 -1
  32. package/lib/types/remote/runtime.d.ts +2 -0
  33. package/lib/types/remote-client.d.ts +2 -2
  34. package/lib/types/sw/service-worker-host.d.ts +15 -0
  35. package/lib/types/tracker/default-tracker.d.ts +8 -0
  36. package/lib/types/types/options.d.ts +24 -0
  37. package/lib/workbench.js +1 -1
  38. package/package.json +1 -1
  39. package/lib/client-_vNHVHSm.mjs +0 -775
@@ -0,0 +1,448 @@
1
+ # 小工具体验评分 —— 技术改造方案
2
+
3
+ 配套文档:`audit-score-design.md`(做什么、为什么)。本文只讲**怎么改代码**。
4
+
5
+ ## 1. 改造边界
6
+
7
+ | 维度 | 范围 |
8
+ | --- | --- |
9
+ | 链路 | 仅远程模拟器(`createRemoteMiniAppSimulator` → runtime iframe) |
10
+ | 业务线 | 仅 `BusinessLine.mars`(审核),其余业务线行为零变化 |
11
+ | 不涉及 | Electron 审核模拟器、创作者自查、端上真机探针 |
12
+ | 协议 | 远程协议 v8 → v9 |
13
+
14
+ **零接入原则**:消费方升级 npm 包即可,不新增 option、不新增挂载点、不新增配置。
15
+
16
+ ## 2. 改造总览
17
+
18
+ ```text
19
+ ┌─ apps/miniapp-simulator-server ─────────────────────────────┐
20
+ │ 改 config/apollo.ts + AUDIT_POLICY key │
21
+ │ 新 domain/simulator/audit-policy.ts │
22
+ │ 新 domain/simulator/plugin-catalog.ts │
23
+ │ 改 services/simulator-release.service.ts + 订阅 + 聚合 │
24
+ │ 改 controller/simulator.controller.ts + plugin-manifest │
25
+ └──────────────────────────────────────────────────────────────┘
26
+ │ runtime-config.audit / plugin-manifest
27
+ ▼
28
+ ┌─ packages/miniapp-simulator(core,无 Vue 依赖) ────────────┐
29
+ │ 改 injection/registry.ts → 开放注册口(已具备能力) │
30
+ │ 改 core/simulator.ts + registerInjection / audit 控制器│
31
+ │ 改 host/shadow-host.ts + flex 分栏(panelSlot) │
32
+ │ 改 remote/protocol.ts v9 + audit.* method + 事件 │
33
+ │ 改 remote/runtime.ts + 插件装载 + audit.* 分发 │
34
+ │ 新 core/audit-controller.ts 生命周期编排(不含规则、不含 UI) │
35
+ └──────────────────────────────────────────────────────────────┘
36
+ │ 动态 import(pluginUrl)
37
+ ▼
38
+ ┌─ packages/miniapp-audit-plugin(新包,带 Vue) ──────────────┐
39
+ │ guest/ 探针(安全 hook,构建为字符串) │
40
+ │ runtime/ 时序聚合 + 打标 + 调规则引擎 + 上报 │
41
+ │ panel/ Vue SFC → defineCustomElement │
42
+ └──────────────────────────────────────────────────────────────┘
43
+ │ 依赖
44
+ ▼
45
+ ┌─ packages/miniapp-audit-rules(新包,纯函数零依赖) ─────────┐
46
+ │ score(timeline, policy) → AuditReport │
47
+ │ 同一份代码在 runtime 内在线算分 / 在 Node 里离线重算 │
48
+ └──────────────────────────────────────────────────────────────┘
49
+ ```
50
+
51
+ ### 2.1 文件级清单
52
+
53
+ | 类型 | 路径 | 说明 |
54
+ | --- | --- | --- |
55
+ | 改 | `apps/miniapp-simulator-server/src/config/apollo.ts` | 新增 key 常量 |
56
+ | 改 | `.../src/config/index.ts` | 导出 |
57
+ | 新 | `.../src/app/domain/simulator/audit-policy.ts` | 照 `package-limits.ts` 形状 |
58
+ | 新 | `.../src/app/domain/simulator/plugin-catalog.ts` | 照 `runtime-manifest-catalog.ts` 形状 |
59
+ | 改 | `.../src/app/services/simulator-release.service.ts` | 订阅 + resolve + 聚合进 runtime-config |
60
+ | 改 | `.../src/app/controller/simulator.controller.ts` | 新增 `GET /simulator/plugin-manifest` |
61
+ | 改 | `packages/miniapp-simulator/src/core/simulator.ts` | 注册口 + audit 控制器接入 |
62
+ | 新 | `packages/miniapp-simulator/src/core/audit-controller.ts` | 编排 |
63
+ | 改 | `packages/miniapp-simulator/src/host/shadow-host.ts` | flex 分栏 |
64
+ | 改 | `packages/miniapp-simulator/src/types/options.ts` | `MiniAppSimulator.audit` |
65
+ | 改 | `packages/miniapp-simulator/src/remote/protocol.ts` + `protocol-version.json` | v9 |
66
+ | 改 | `packages/miniapp-simulator/src/remote/runtime.ts` | 装载 + 分发 |
67
+ | 改 | `packages/miniapp-simulator/src/remote/client.ts` | 透传 audit.* |
68
+ | 新包 | `packages/miniapp-audit-rules` | 规则引擎 |
69
+ | 新包 | `packages/miniapp-audit-plugin` | 探针 + 面板 |
70
+
71
+ `src/injection/registry.ts` 本身**不需要改**:`register()` 已支持按 id 覆盖,`unregister()` / `list()` 已具备,只是外部拿不到实例。
72
+
73
+ ## 3. 服务端改造
74
+
75
+ ### 3.1 Apollo key
76
+
77
+ `src/config/apollo.ts` 现有六个 key 均为 `miniappSimulator` + 驼峰,scoped policy 类用复数 `Policies`。新增两个:
78
+
79
+ ```ts
80
+ export const SIMULATOR_AUDIT_POLICY_CONFIG_KEY = "miniappSimulatorAuditPolicies";
81
+ export const SIMULATOR_PLUGIN_CATALOG_CONFIG_KEY = "miniappSimulatorPluginCatalog";
82
+ ```
83
+
84
+ ### 3.2 `domain/simulator/audit-policy.ts`
85
+
86
+ 复用 `policy-scope.ts` 的 scope 匹配,Server 不感知评分语义,只校验外层 JSON、scope 和 priority 结构。每条 `strategy` 是完整策略,命中多条时选择 priority 最大的一整份:
87
+
88
+ ```ts
89
+ export function parseSimulatorAuditPolicies(input: unknown): SimulatorPrioritizedScopedConfigPolicy[] {
90
+ return parsePrioritizedScopedConfigPolicies(input, "audit policies");
91
+ }
92
+
93
+ export function resolveSimulatorAuditPolicy(
94
+ policies: SimulatorPrioritizedScopedConfigPolicy[],
95
+ context: SimulatorPolicyContext,
96
+ ): Record<string, unknown> {
97
+ return resolvePrioritizedScopedConfigStrategy(policies, context);
98
+ }
99
+ ```
100
+
101
+ **不做策略字段校验**(与 `package-limits.ts` 的 `positiveInteger` 不同):评分项会频繁增删,逐字段校验会变成每次改指标都要发版本。Runtime 收到 audit 对象后整份采用,不与本地默认拼接;服务端只保证外层结构是合法 JSON 且无原型污染(`cloneJsonRecord` 已处理)。
102
+
103
+ ### 3.3 `domain/simulator/plugin-catalog.ts`
104
+
105
+ 照 `runtime-manifest-catalog.ts` 写,含 `originalHttpUrl` 式的 URL 校验与 `protocolVersion` 交叉校验:
106
+
107
+ ```ts
108
+ export interface SimulatorPluginCatalog {
109
+ activePluginsByBusinessLine: Record<string, string[]>; // { "mars": ["audit@3"] }
110
+ releases: Record<string, {
111
+ buildId: string;
112
+ protocolVersion: number;
113
+ pluginUrl: string; // 必须与 Runtime 同源
114
+ integrity?: string; // SRI
115
+ }>;
116
+ }
117
+ ```
118
+
119
+ 校验要点(对齐 `parseRuntimeManifestCatalog`):`releases` 非空、`activePluginsByBusinessLine` 引用的 buildId 必须存在、`release.protocolVersion` 必须与请求协议一致、URL 必须 http(s) 且不含 credentials。
120
+
121
+ ### 3.4 `simulator-release.service.ts`
122
+
123
+ 三处改动,每处**独立 try/catch 并保持 last-known-good 语义**(与现有五个 key 一致):
124
+
125
+ ```ts
126
+ private auditPolicies: SimulatorScopedConfigPolicy[] = [];
127
+ private pluginCatalog: SimulatorPluginCatalog = createDefaultPluginCatalog();
128
+
129
+ // applyConfiguration 内新增两段
130
+ try {
131
+ const raw = appConfig[SIMULATOR_AUDIT_POLICY_CONFIG_KEY];
132
+ this.auditPolicies = raw === undefined ? [] : parseSimulatorAuditPolicies(raw);
133
+ console.info(`[SimulatorAuditPolicy] ${source} applied: policies=${this.auditPolicies.length}`);
134
+ } catch (error) { /* keeping last-known-good */ }
135
+
136
+ // getRuntimeConfig 返回值新增 audit
137
+ getRuntimeConfig(context) {
138
+ return {
139
+ csp: ..., performance: ..., jsapi: ..., packageLimits: ...,
140
+ audit: resolveSimulatorAuditPolicy(this.auditPolicies, context),
141
+ };
142
+ }
143
+ ```
144
+
145
+ `getRuntimeConfig` 是既有聚合接口(`POST /simulator/runtime-config`,注释明写「一次获取全部动态配置」),**新增字段不新增请求**。
146
+
147
+ ### 3.5 `GET /simulator/plugin-manifest`
148
+
149
+ ```ts
150
+ @Get("simulator/plugin-manifest")
151
+ pluginManifest(@Query() query: { businessLine?: string; protocolVersion?: string }): void
152
+ // Cache-Control: no-store(与 runtime-manifest 一致)
153
+ // 协议不匹配返回 426,对齐 runtime-manifest 的行为
154
+ ```
155
+
156
+ ### 3.6 APM 评测事件流
157
+
158
+ 评测数据复用现有 tracker,不新增服务端上报接口。measurement_name 保持
159
+ `miniapp_simulator_event`,通过 `event_name` 区分 session、sample、paint、incident、package 和
160
+ result,均由模拟器内部采集和上报。
161
+
162
+ 1 Hz timeline 按采样点拆成扁平的 `miniapp_audit_sample`,不作为数组塞入单条 `measurement_data`。
163
+ Runtime 通过 MessageChannel 把 telemetry 交给宿主 tracker;宿主按 10 条或 10 秒批量发送,并在终态、
164
+ 页面隐藏和销毁时立即 flush。具体字段见
165
+ [audit-data-collection.codex.md](./audit-data-collection.codex.md)。
166
+
167
+ ## 4. 模拟器核心改造
168
+
169
+ ### 4.1 开放注入注册口
170
+
171
+ `core/simulator.ts` 构造函数里写死注册 8 个内置模块。新增两个公开方法,插件由 runtime 侧调用:
172
+
173
+ ```ts
174
+ registerInjection(module: InjectionModule): void { this.injection.register(module); }
175
+ unregisterInjection(id: string): boolean { return this.injection.unregister(id); }
176
+ ```
177
+
178
+ **顺序约束**:评测模块必须排在 `securityInjection` 之前(seal IIFE 最后 `applyNavigatorTamperProof()` 会冻结 `navigator`),且尽量靠前。`InjectionRegistry.register()` 按调用序 push,插件在 `load()` 之前注册即可,但需在内置模块**之后**注册会排到末尾——因此需要在 `register` 上补一个可选的插入位置参数,或由插件复用 `consoleForward` 之后的固定锚点。
179
+
180
+ > 实现建议:给 `InjectionRegistry` 加 `registerBefore(id, module)`,避免引入优先级字段。
181
+
182
+ 注入内容为字符串,经 `assembleHeadInjection` 拼进带 nonce 的单个 `<script>`,**guest CSP 无需改动**。
183
+
184
+ `sw/worker/serve.ts:105` 的 `injectHead()` 有三级兜底(有 `<head>` / 有 `<html>` / 都没有),三种情况下注入片段都是文档第一个执行的脚本,hook 安装早于任何业务代码。
185
+
186
+ ### 4.2 `shadow-host.ts` 分栏
187
+
188
+ 现结构:`host(absolute inset:0) → shadowRoot → bounds(absolute inset:0) → stage`。
189
+
190
+ 面板**不能进 `bounds`**——`bounds` 是 stageFit 的居中与缩放参照系。新增一层 flex:
191
+
192
+ ```text
193
+ shadowRoot
194
+ └ div.dms-layout display:flex; position:absolute; inset:0
195
+ ├ div.dms-bounds flex:1 1 auto; position:relative; overflow:hidden; min-width:0
196
+ │ └ div.dms-stage
197
+ └ div.dms-panel-slot flex:none; width:0(默认折叠)
198
+ ```
199
+
200
+ `BASE_STYLE` 中 `.dms-bounds` 从 `position:absolute; inset:0` 改为上述 flex 子项。`stage` 的 `left/top:50% + translate(-50%,-50%)` 解析基准仍是 `bounds` 的尺寸,**居中逻辑一行不用改**。
201
+
202
+ `core/options.ts:210-214` 明确 stageFit 默认开启且为「100% 自适应撑满 mount 区域」,因此面板展开后手机自动缩放适配。
203
+
204
+ `MiniAppShadowHost` 接口新增 `panelSlot: HTMLElement`。
205
+
206
+ 面板不存在时 flex 容器只有 `bounds` 一个子项,布局效果与现状 `inset:0` 等价,不维护两套分支。
207
+
208
+ #### PC guard 必须从 `host` 下移到 `bounds`
209
+
210
+ `utils/simulator-pc-guard.ts` 的注释写明其语义是「**模拟器范围内**禁用 PC 端特有交互,贴近真机体感」——指的是手机区域,不包含工具面板。但它当前挂在 `host` 上:
211
+
212
+ ```ts
213
+ applySimulatorPcGuardStyles(host); // 内联 user-select: none,向下继承
214
+ attachSimulatorPcGuard(host); // selectstart / dragstart → preventDefault
215
+ ```
216
+
217
+ 分栏后 `host` 覆盖面板,会导致 **审核员无法选中、复制报告内的任何文字**(项名、实测值、上限值),而「把超限数值贴进拒绝理由」是审核高频操作。
218
+
219
+ 因此改为:
220
+
221
+ ```text
222
+ host ← 不再挂 guard
223
+ └ .dms-layout
224
+ ├ .dms-bounds ← guard 挂此处
225
+ │ └ .dms-stage
226
+ └ .dms-panel-slot ← 文字可选可复制
227
+ ```
228
+
229
+ 该调整对现有行为**零差异**:当前 `host` 下仅有 `bounds` 一个子节点,guard 挂 `host` 与挂 `bounds` 效果等同。
230
+
231
+ ### 4.3 `MiniAppSimulator.audit` 控制器
232
+
233
+ 对齐现有 `inspector` / `notePreview` / `widgetCover` 的控制器范式(`types/options.ts:767`):
234
+
235
+ ```ts
236
+ export interface MiniAppAuditController {
237
+ start(options?: { autoPilot?: boolean; durationMs?: number }): Promise<void>;
238
+ stop(): Promise<AuditReport | null>;
239
+ getReport(): AuditReport | null;
240
+ exportRaw(): AuditRawTimeline | null;
241
+ readonly isRunning: boolean;
242
+ }
243
+ ```
244
+
245
+ `core/audit-controller.ts` 只做**生命周期编排**:驱动 reload、维护状态机、转发事件、挂载面板元素到 `panelSlot`。**不含规则、不含采集逻辑、不含 UI 实现**——这三者都在插件里。核心侧对插件的依赖是一个接口,不是具体实现:
246
+
247
+ ```ts
248
+ export interface MiniAppAuditPlugin {
249
+ readonly id: string;
250
+ createInjection(ctx: { policy: unknown; tags: MiniAppFeatureTags }): InjectionModule;
251
+ createCollector(ctx: { policy: unknown }): AuditCollector;
252
+ createPanelElement?(): HTMLElement; // 插件未提供时不展开面板
253
+ }
254
+ ```
255
+
256
+ 这样 core 保持零 Vue 依赖、零规则依赖。
257
+
258
+ ### 4.4 协议 v9
259
+
260
+ `remote/protocol-version.json` 8 → 9。`remote/protocol.ts`:
261
+
262
+ ```ts
263
+ REMOTE_SIMULATOR_CAPABILITIES += "audit"
264
+
265
+ // RemoteSimulatorMethod
266
+ | "audit.start" | "audit.stop" | "audit.getReport" | "audit.exportRaw"
267
+
268
+ // RemoteMethodArgumentMap
269
+ "audit.start": [{ autoPilot?: boolean; durationMs?: number }?];
270
+ "audit.stop": [];
271
+ "audit.getReport": [];
272
+ "audit.exportRaw": [];
273
+ ```
274
+
275
+ 事件经既有 `sessionHooks.emitEvent`(`remote/runtime.ts:317`)下发:`auditProgress` / `auditReport`。
276
+
277
+ `RemoteSimulatorSerializableOptions` **不改**——插件选择由 connect 消息的 `businessLine` + 服务端 catalog 决定,不经宿主传函数。
278
+
279
+ ### 4.5 `remote/runtime.ts`
280
+
281
+ 三处改动:
282
+
283
+ **(a) `fetchRuntimeConfig`(:367)** 增加 audit 字段:
284
+
285
+ ```ts
286
+ value: {
287
+ cspDirectives: ..., runtimeLimits: ..., jsapi: ..., packageLimits: ...,
288
+ auditPolicy: payload.audit, // 完整策略直接采用;缺失时规则包使用本地默认
289
+ }
290
+ ```
291
+
292
+ **(b) 插件装载** —— 不在 connect 阶段,在首次 `audit.start` 时:
293
+
294
+ ```ts
295
+ // 同源 ESM 动态导入,不用 eval,无需放开 script-src unsafe-eval
296
+ const mod = await import(/* @vite-ignore */ pluginUrl);
297
+ ```
298
+
299
+ 装载前校验 `businessLine === mars` 且 catalog 有匹配 `protocolVersion` 的 release。
300
+
301
+ **(c) `handleRequest` switch(:488)** 新增四个 case,与 `inspector.*` 同构:
302
+
303
+ ```ts
304
+ case "audit.start": return simulator.audit.start(first as never);
305
+ case "audit.stop": return simulator.audit.stop();
306
+ case "audit.getReport": return simulator.audit.getReport();
307
+ case "audit.exportRaw": return simulator.audit.exportRaw();
308
+ ```
309
+
310
+ ## 5. 新包:`miniapp-audit-rules`
311
+
312
+ 纯函数、零运行时依赖、可在浏览器与 Node 同时运行。
313
+
314
+ ```ts
315
+ export function score(timeline: AuditRawTimeline, policy: AuditPolicy): AuditReport;
316
+ export const RULES_VERSION: string;
317
+ ```
318
+
319
+ 设计约束:
320
+
321
+ - **无副作用、无 IO、无时间依赖**,同输入必定同输出,保证审核可复现。
322
+ - 报告必须同时携带 `policyVersion`(来自 Apollo)与 `rulesVersion`(来自本包),否则线上调整阈值后历史报告无法解释,申诉无从对账。
323
+ - 计分按 §6.1:`总分 = Σ(适用项得分×权重) / Σ(适用项权重)`,**不适用项既不给分也不计入分母**;`覆盖度 < minCoverage` 时返回 `verdict: "uneval"` 而非分数。
324
+ - 硬卡口独立于打分,输出 `blocking: { passed, reasons[] }`。
325
+
326
+ 离线定标:从 APM 导出同一 `audit_session_id + attempt_no` 的 sample,按 `sample_seq` 重建 timeline,
327
+ 在 Node 里用本包的**不同版本**重算,不需要重跑样本。
328
+
329
+ ## 6. 新包:`miniapp-audit-plugin`
330
+
331
+ ### 6.1 guest 探针
332
+
333
+ 按「常驻成本」而非能力标签划分(详见设计文档 §4.2):
334
+
335
+ | 类型 | 注入策略 |
336
+ | --- | --- |
337
+ | 被动 hook(`texImage2D` / `bufferData` / `getContext` / `new Worker` / `WebAssembly.Memory.grow` / `Image.load`) | **全量注入**,不调用即零成本 |
338
+ | 主动轮询(heap 采样、rAF FPS、`PerformanceObserver`、DOM 扫描) | 按 policy / 标签开关 |
339
+
340
+ 统一 `safeHook`,硬性要求:原样透传 this/参数/返回值/**异常**;保留 `name`/`length`/`toString`;幂等 guard;**统计逻辑 O(1) 且零分配**(`texImage2D` 在 3D 游戏中每帧上千次调用,逐次记录会让探针自身成为 OOM 元凶)。
341
+
342
+ 内存口径(设计文档 §6.4):在 **SW mount 完成后、guest 加载前**读一次 `performance.memory` 作为会话基线。底噪非常数——`sw/service-worker-host.ts:146-164` 的 `mount()` 会 `cloneMountInput()` 深拷贝(`bytes.slice(0)`)一份完整解压包常驻 `mountSnapshots`(防 SW 进程被回收),runtime 侧长期持有一份包体。峰值取 **P95**,不取单次 max(GC 锯齿会导致分数漂移)。
343
+
344
+ ### 6.2 打标器
345
+
346
+ 消费 `RawParseResult`(`sw/parse-raw.ts`)的 `rawFiles` 做字符串特征扫描,输出 `MiniAppFeatureTags`。一期 `source: "scanned"`,未来上游下发即 `"declared"`,**下游不改**。
347
+
348
+ 实采数据可反推能力并自动修正覆盖度 + 标记 `tagMismatch`,因此扫描准确性不是系统生命线。
349
+
350
+ ### 6.3 体积分析
351
+
352
+ 直接消费 `RawParseResult.files: Array<{ path, size }>`(解压后字节),**零额外解包**。
353
+
354
+ 与 `parse-raw.ts` 的 `DEFAULT_MINIAPP_PACKAGE_LIMITS` 严格分离:后者是安全阀(50MB 压缩包 / 15MB 单文件,超限 `throw PACKAGE_LIMIT_EXCEEDED` 包加载失败),评分线是质量度量(4MB 解压后即开始扣分,分档线性)。**评分阈值绝不能写进 package-limits**,否则超限时审核员连报告都看不到。
355
+
356
+ ### 6.4 Vue 面板
357
+
358
+ ```
359
+ panel/
360
+ ├── AuditPanel.ce.vue
361
+ ├── components/ScoreRing.ce.vue / CategorySection.ce.vue / CheckItem.ce.vue
362
+ └── index.ts defineCustomElement + 幂等 define
363
+ ```
364
+
365
+ 构建:`@vitejs/plugin-vue` + `features: { customElement: true }`,**runtime-only 预编译**(运行时模板编译依赖 `new Function`,会撞 CSP `unsafe-eval`)。
366
+
367
+ Vue 只进插件产物。core 的 `vite.config.ts` 不改——`remote` 构建是 `inlineDynamicImports` 单文件,Vue 一旦进 core 就会被所有会话下载(含 creator)。
368
+
369
+ 数据经 **property** 传入(`el.report = obj`),attribute 只能传字符串。
370
+
371
+ ## 7. 数据流
372
+
373
+ ```text
374
+ 审核员点「开始评测」
375
+ │
376
+ ├─ client.audit.start() ──MessageChannel──▶ runtime.handleRequest("audit.start")
377
+ │
378
+ ├─ runtime 拉 plugin-manifest → import(pluginUrl)
379
+ ├─ plugin.createInjection() → simulator.registerInjection(module)
380
+ ├─ panelSlot 挂载 <miniapp-audit-panel>,宽度 0 → 380 过渡
381
+ │
382
+ ├─ simulator.reload()
383
+ │ ├─ parseRawZip → RawParseResult.files → 打标 + 体积分析
384
+ │ ├─ SW mount 完成 → 读 performance.memory = 会话基线
385
+ │ └─ guest 加载(探针已在 head 首个 script)
386
+ │
387
+ ├─ 采集期:每 sampleIntervalMs 一个采样点
388
+ │ └─ emitEvent("auditProgress") → 面板 live 数字 + 宿主可选消费
389
+ │
390
+ └─ 「结束评测」
391
+ ├─ rules.score(timeline, policy) → AuditReport
392
+ ├─ 面板渲染报告
393
+ ├─ APM 上报 package / result
394
+ ├─ 宿主 flush 已接收的 sample / incident
395
+ └─ emitEvent("auditReport")
396
+ ```
397
+
398
+ ## 8. 改造顺序
399
+
400
+ ```text
401
+ ① 服务端 Apollo:key + audit-policy.ts + service 订阅 + runtime-config 聚合
402
+ └ 独立可交付,配置生效即可验证(看服务日志 policies=N)
403
+ ② APM 事件流:session / sample / incident / package / result
404
+ └ 采集底座,必须早于出分
405
+ ③ miniapp-audit-rules 包(纯函数,可单测)
406
+ ④ core:registerInjection + audit 控制器 + 协议 v9 + runtime 装载与分发
407
+ ⑤ miniapp-audit-plugin:guest 探针 + 打标 + 体积分析(先不做面板)
408
+ └ 到此可跑通「采集 → APM → 离线算分」,无 UI
409
+ ⑥ shadow-host.ts 分栏改造(独立 PR + 独立回归)
410
+ ⑦ Vue 面板
411
+ ⑧ enforce=false 上线,累积两周数据
412
+ ⑨ 拉分布定阈值 → 改 Apollo → enforce=true(不发版)
413
+ ```
414
+
415
+ ①②③ 相互独立可并行。④ 依赖 ③ 的类型定义。⑤ 依赖 ④。⑦ 依赖 ⑥。
416
+
417
+ ⑤ 完成即可产出设计文档 §2.2 所要求的采样分布表,**不必等 UI**。
418
+
419
+ ## 9. 兼容性与风险
420
+
421
+ | 项 | 风险 | 处置 |
422
+ | --- | --- | --- |
423
+ | `shadow-host.ts` 分栏 | 核心布局文件,改动影响全部消费方的布局路径 | 面板不存在时 flex 容器只有 bounds 一个子项,效果等价于现状 `inset:0`,不维护两套分支;PC guard 下移到 bounds(§4.2);单独 PR + 回归四个接入方 |
424
+ | 协议 v8 → v9 | 线上旧 client 与新 runtime 共存 | `runtime-manifest` 按 `protocolVersion` 精确映射(`activeRuntimeByProtocol`),旧 client 继续拿 v8 runtime;`audit` 走 capability 协商,旧 client 不声明即不可用 |
425
+ | 插件版本矩阵 | runtime 版本 × 插件版本 | `plugin-catalog` 强制 `protocolVersion` 交叉校验,不匹配返回 426 |
426
+ | Apollo 外层规则结构错误 | 无 last-known-good 时服务端不返回 audit | 每个 key 独立 try/catch;规则包使用本地默认;上线前看日志 `[SimulatorAuditPolicy] ... policies=N` 确认 |
427
+ | 探针自身开销 | 高频 hook 逐次记录会成为 OOM 元凶 | `safeHook` 强制 O(1) 零分配;主动轮询类按 policy 门控 |
428
+ | Vue 体积 | 34KB gzip | 只进插件产物、只在 mars 点评测时加载;creator 一个字节不下载 |
429
+
430
+ `policy-scope.ts` 的 `matchesPolicyScope` 是 **OR** 语义(三维度任一命中),无法表达「mars 且某 app」。需要 app 级策略时必须再配置一份完整策略,并用更大的 priority 让它整份生效。
431
+
432
+ ## 10. 回归范围
433
+
434
+ | 触发改动 | 回归对象 |
435
+ | --- | --- |
436
+ | `shadow-host.ts` | `apps/mini-app-review-simulator`(React,本地模式)、`apps/miniapp-simulator-server-playground`、creator IDE、`testsuits/miniapp-simulator-sdk-smoke` |
437
+ | `core/simulator.ts` 注册口 | 现有 8 个内置注入模块的顺序与 head 拼接结果 |
438
+ | 协议 v9 | 线上 v8 client 连新 runtime;新 client 连 v8 runtime(应 426) |
439
+ | `runtime-config` 新增字段 | 既有 csp / performance / jsapi / packageLimits 解析不受影响 |
440
+
441
+ ## 11. 明确不做
442
+
443
+ - 不改 `package-limits` 及其 Apollo key(安全阀与质量线是两把尺子)
444
+ - 不把完整 timeline 数组塞进单条 APM 数据;按 1 Hz 扁平 sample 事件上报
445
+ - 不把打分公式做成 Apollo 表达式字符串(参数可调、逻辑不可调)
446
+ - 不在探针内做 `if (hasWebGL)` 式能力分支
447
+ - 不迁移 `src/native-ui/` 到 Vue(认可方向,独立立项,见设计文档 §11.6)
448
+ - 不做面板宽度拖拽、不做挂载点配置项