wenxiangchao
4 天以前 559890c482f5973f06aafdfec9550fee5ba3c7f2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>仪器注册与仪器调试 系统架构说明 · V3.0 重梳理版</title>
<script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script>
<style>
    * { box-sizing: border-box; }
    body {
        font-family: -apple-system, "Segoe UI", "Microsoft YaHei", "PingFang SC", sans-serif;
        max-width: 1240px; margin: 0 auto; padding: 32px 24px 80px;
        background: #FAFAFA; color: #212121; line-height: 1.75;
    }
    h1 { font-size: 29px; color: #1565C0; border-bottom: 3px solid #1976D2; padding-bottom: 12px; }
    h2 { font-size: 23px; color: #0D47A1; margin-top: 42px; padding-left: 12px; border-left: 4px solid #FF9800; }
    h3 { font-size: 17px; color: #37474F; margin-top: 26px; }
    h4 { font-size: 15px; color: #455A64; margin-top: 18px; }
    .subtitle { color: #757575; font-size: 14px; margin-bottom: 24px; }
    .card { background: white; border-radius: 8px; padding: 18px 22px; margin: 16px 0; box-shadow: 0 2px 8px rgba(0,0,0,0.06); }
    .highlight { background: #FFF8E1; border-left: 4px solid #FFC107; padding: 12px 16px; margin: 12px 0; border-radius: 4px; }
    .info { background: #E3F2FD; border-left: 4px solid #2196F3; padding: 12px 16px; margin: 12px 0; border-radius: 4px; }
    .success { background: #E8F5E9; border-left: 4px solid #4CAF50; padding: 12px 16px; margin: 12px 0; border-radius: 4px; }
    .warn { background: #FBE9E7; border-left: 4px solid #FF5722; padding: 12px 16px; margin: 12px 0; border-radius: 4px; }
    table { width: 100%; border-collapse: collapse; margin: 16px 0; background: white; border-radius: 6px; overflow: hidden; box-shadow: 0 1px 4px rgba(0,0,0,0.06); font-size: 13.5px; }
    th { background: #1976D2; color: white; padding: 9px 12px; text-align: left; font-weight: 600; }
    td { padding: 8px 12px; border-bottom: 1px solid #EEEEEE; vertical-align: top; }
    tr:nth-child(even) td { background: #FAFAFA; }
    tr:hover td { background: #E3F2FD; }
    code { font-family: "Consolas", "Monaco", monospace; background: #F5F5F5; color: #C62828; padding: 2px 6px; border-radius: 3px; font-size: 13px; }
    pre { background: #263238; color: #ECEFF1; padding: 16px 20px; border-radius: 6px; overflow-x: auto; font-family: "Consolas", monospace; font-size: 13px; line-height: 1.55; }
    pre code { background: transparent; color: inherit; padding: 0; font-size: 13px; }
    .mermaid { background: white; padding: 20px; border-radius: 8px; margin: 20px 0; text-align: center; box-shadow: 0 2px 8px rgba(0,0,0,0.06); overflow-x: auto; }
    ul { padding-left: 22px; } li { margin: 4px 0; }
    .toc { background: #F5F5F5; padding: 16px 24px; border-radius: 6px; margin: 20px 0; column-count: 2; column-gap: 40px; }
    .toc a { color: #1976D2; text-decoration: none; display: block; padding: 3px 0; break-inside: avoid; }
    .toc a:hover { text-decoration: underline; }
    .kwd { color: #FFCC80; } .str { color: #C5E1A5; } .cmt { color: #546E7A; } .fn { color: #82AAFF; } .num { color: #F78C6C; }
    .good { color: #2E7D32; font-weight: 600; } .bad { color: #C62828; font-weight: 600; } .mid { color: #EF6C00; font-weight: 600; }
    footer { margin-top: 60px; padding-top: 20px; border-top: 1px solid #E0E0E0; color: #757575; font-size: 12px; text-align: center; }
    .path { font-family: Consolas, monospace; font-size: 12.5px; background: #ECEFF1; color: #37474F; padding: 1px 6px; border-radius: 3px; }
    .iron { display:inline-block; background:#E8EAF6; color:#283593; border-radius:12px; padding:1px 10px; font-size:12px; font-weight:700; margin-right:6px; }
</style>
</head>
<body>
 
<h1>仪器注册与仪器调试 · 系统架构说明(V3.0 重梳理版)</h1>
<div class="subtitle">基于《Local + DeviceBroker 混合架构详细设计说明书 V3.0》对现有系统的一次完整重构叙述 —— 含 TestStep 仪器使用规范 · 2026-11-03</div>
 
<div class="toc">
    <a href="#s1">一、总定位:一句话读新架构</a>
    <a href="#s2">二、五域分层模型</a>
    <a href="#s3">三、仪器注册(DeviceConfig)的角色重塑</a>
    <a href="#s4">四、仪器调试(DeviceDebug)的角色重塑</a>
    <a href="#s5">五、进程拓扑与责任边界</a>
    <a href="#s6">六、接口契约演化映射(现有代码 → V3.0)</a>
    <a href="#s7">七、三条核心数据流</a>
    <a href="#s8">八、保持不变的部分(架构连续性资产)</a>
    <a href="#s9">九、TestStep 仪器使用规范</a>
    <a href="#s10">十、结论</a>
</div>
 
<!-- ======================================================== -->
<h2 id="s1">一、总定位:一句话读新架构</h2>
 
<div class="highlight">
<strong>配置域生产"不可变真理",仲裁域守护"谁有权碰",宿主域执行"谁来动手",业务域决定"该不该判 FAIL",数据域保证"事后能说清"。</strong>
<br><br>
仪器注册不再只是"增删改查表单",而是配置域的<strong>发布管线</strong>;仪器调试不再只是"连上发命令",而是仲裁域中一个特殊的写权限持有者(<strong>Maintenance Lease</strong>)。
</div>
 
<div class="card">
<strong>与旧架构的根本区别:</strong>旧架构里 DeviceConfig → <code>data.db</code> → DeviceDebug 是"两个模块共享一张表"的隐式契约;新架构里<strong>没有任何模块直接操作别人的库</strong>——注册产物是哈希锁定的快照,调试/生产消费的永远是快照 + 租约句柄,物理 I/O 永远只发生在唯一宿主内。
</div>
 
<!-- ======================================================== -->
<h2 id="s2">二、五域分层模型</h2>
 
<div class="mermaid">
graph TB
    subgraph D1["① 配置域(设计时)"]
        CFG["DeviceConfig 注册页<br/>Draft → Validated → Published<br/>→ Prepared → Effective → Retired"]
        SNAP["config_snapshot(不可变+SHA256)<br/>resource_definition · slot_binding<br/>broker_task_template · effective_config"]
        CFG --> SNAP
    end
    subgraph D2["② 仲裁域(运行时)"]
        LS["LeaseStore<br/>arb.db(Local 域)<br/>broker-bxx.db(Broker 域)"]
        RS["resource_state(generation/phase/epoch)<br/>parent_permit · lease_history"]
        ARB["LeaseArbiter<br/>优先级矩阵 · 让渡 · 抢占拒绝"]
        LS --- RS
    end
    subgraph D3["③ 执行宿主域(物理 I/O)"]
        WKR["Worker ×6(W01–W06)<br/>Local 设备唯一宿主<br/>连接守卫 Mutex 专用线程"]
        BRK["Broker ×3(B01–B03)+B00<br/>SessionActor · TaskScheduler<br/>Journal/Outbox · Watchdog"]
        DF["DriverFactory 分化<br/>宿主驱动(载SDK) vs BrokerProxy(不载SDK)"]
    end
    subgraph D4["④ 业务执行域"]
        TAP["OpenTAP TestPlan / Recipe<br/>120 × DUTContext(异步计时)<br/>逻辑能力绑定:空位+能力<br/>不选 COM / IP / 进程"]
        RUNS["RunService<br/>中断窗口 · 续跑/重测决策"]
    end
    subgraph D5["⑤ 数据域"]
        DB1[("aging_system.db<br/>配置+审计索引")]
        DB2[("arb.db / broker-bxx.db<br/>各守单写者")]
        DB3[("每 Run aging_data.db<br/>采样分片归档")]
    end
    SNAP -->|"冻结 RunSnapshot<br/>五元 Hash 对账"| TAP
    TAP -->|"AcquireGroup"| ARB
    ARB --> RS
    TAP -->|"DeviceOperation<br/>Grants+IdemKey"| WKR & BRK
    WKR & BRK --> DB2
    TAP --> DB3
    CFG --> DB1
</div>
 
<table>
    <tr><th>域</th><th>解决什么</th><th>核心产物</th><th>不变量</th></tr>
    <tr><td>① 配置域</td><td>物理世界的数字孪生 + 生效仪式</td><td>不可变快照 + Hash</td><td>INV-04 启动后冻结 Recipe/Config/Driver/DBC/UDS/映射 Hash</td></tr>
    <tr><td>② 仲裁域</td><td>谁有权在这台仪器上发命令</td><td>LeaseGrant(每资源一代)</td><td>INV-02 预约授权与命令串行化分层</td></tr>
    <tr><td>③ 执行宿主域</td><td>物理句柄独占 + 原子片段</td><td>SessionGate + Task Journal</td><td>INV-01 同一 PhysicalSessionKey 只有一个 I/O 宿主</td></tr>
    <tr><td>④ 业务执行域</td><td>工艺编排与合格判定</td><td>Verdict / DUTContext</td><td>INV-05 写操作在实际宿主入口校验,客户端仅提示</td></tr>
    <tr><td>⑤ 数据域</td><td>事后追溯与容量治理</td><td>四库分离 + Outbox</td><td>INV-06 已发未回记 Unknown,禁止当未执行</td></tr>
</table>
 
<!-- ======================================================== -->
<h2 id="s3">三、仪器注册(DeviceConfig)的角色重塑</h2>
 
<h3>3.1 新旧职责对照</h3>
<table>
    <tr><th>维度</th><th>旧模型(现状代码)</th><th>V3.0 模型</th></tr>
    <tr><td>本质</td><td>设备信息 CRUD 表单</td><td><strong>配置发布管线</strong>(Draft→Validated→Published→Prepared→Effective→Retired)</td></tr>
    <tr><td>存储</td><td><code>DeviceDefinition</code> 可变行,改了立即生效</td><td><code>config_snapshot</code> <strong>不可变</strong>,<code>(config_hash, resource_key)</code> 复合主键,回滚也新建版本记录</td></tr>
    <tr><td>版本管理</td><td><code>SaveDraft/Publish/Apply</code> 改状态列</td><td>发布产物 = JSON 快照 + SHA256;运行时冻结 RunSnapshot(Recipe/Driver/DBC/UDS/映射 五件套)</td></tr>
    <tr><td>设备标识</td><td>Endpoint 字符串直写(<code>CAN_BUS_1:0</code>)</td><td><strong>EndpointRef</strong>(凭据/地址引用分离)+ <strong>PhysicalIdentity</strong>(SN 物理身份,防换机后身份漂移)</td></tr>
    <tr><td>工位绑定</td><td><code>SlotDeviceMapping</code> 一张映射表</td><td><code>slot_binding</code>(随快照版本化)+ 120 条映射由公式生成器产出并离线校验</td></tr>
    <tr><td>冲突检测</td><td><code>DetectConflicts()</code> 跑一次 SQL 分组</td><td>发布门禁:身份去重、驱动位数、能力覆盖、Authority 一致、<strong>同一 PhysicalSessionKey 只允许一个 I/O 宿主(INV-01)</strong>,不过审禁止发布</td></tr>
</table>
 
<h3>3.2 设备注册页需新增的字段(改造清单)</h3>
<pre><code>executionMode       Local | Broker            <span class="cmt">// 按物理拓扑决定,禁止运行时混用</span>
authorityId         broker-b01 / local-ipc01  <span class="cmt">// 所属仲裁域</span>
brokerId            + ownerProcessId          <span class="cmt">// Broker 域归属</span>
physicalSessionKey  psu01/main                <span class="cmt">// 同一物理连接标识(SessionGate 键)</span>
physicalIdentity    serial:TBD-PSU01          <span class="cmt">// SN 物理身份;TBD 禁止过发布门禁</span>
accessPolicy        Exclusive | ExclusiveChannelWithParentPermit
                    | TimeShared | SharedRead <span class="cmt">// 独占/分时的策略声明位置</span>
safeStateProfile    PSU-SAFE-001              <span class="cmt">// 恢复/交接时的安全态模板</span>
leasePolicy         TTL / Renew / Handover    <span class="cmt">// 全局默认 + 域级覆盖</span></code></pre>
 
<h3>3.3 发布流程(替代现在的"应用"按钮)</h3>
<div class="mermaid">
flowchart LR
    A[编辑草稿<br/>Draft] --> B[离线校验<br/>身份冲突/能力覆盖<br/>限值/驱动依赖]
    B --> C[Validated]
    C --> D["维护窗口探测<br/>连接/通道寻址/读回"]
    D --> E["Published<br/>不可变快照+Hash"]
    E --> F["分发 Prepare<br/>各宿主回执同 Hash"]
    F --> G["停机窗口 Apply<br/>停Run→撤写权→安全断连<br/>→各宿主Apply+自检"]
    G --> H{全部成功?}
    H -->|是| I["更新 Effective 指针<br/>Run 创建时复制快照"]
    H -->|部分失败| J["整组 ApplyFailed<br/>阻止 Run 启动<br/>统一回旧版或新版"]
    J -.-> F
</div>
 
<div class="info"><strong>关键语义变化:</strong>"生效"从一次 UPDATE 变成一场需要<strong>所有宿主回执确认</strong>的两阶段仪式。旧稿 <code>ApplyVersion()</code> 那种单点改状态的逻辑不复存在;既有 Run 继续引用旧 Hash,不允许静默切版。</div>
 
<!-- ======================================================== -->
<h2 id="s4">四、仪器调试(DeviceDebug)的角色重塑</h2>
 
<h3>4.1 最重要的拆分:监控 ≠ 写权限</h3>
<div class="mermaid">
graph TB
    OLD["旧模型:选设备 → OpenSession(ReadOnly)<br/>→ [RequestMaintenance → Activate] → 发命令<br/>⚠️ 监控和写权限混在同一个会话状态机里"]
    OLD ==> NEW1["新模型 · 监控通路(常驻,无租约)<br/>宿主状态缓存 / Telemetry Fanout → DeviceMonitorView<br/>数据带 来源+质量+序列号+时间;Gap 必须显式呈现"]
    OLD ==> NEW2["新模型 · 写权限通路(维护会话 = 申请 Maintenance 租约)<br/>必须经过仲裁域:看占用 → Draining → 排空 → 安全确认 → Grant"]
</div>
 
<h3>4.2 维护会话状态机(对现有 DebugSessionState 的扩展)</h3>
<div class="mermaid">
stateDiagram-v2
    [*] --> ViewOnly: 打开设备(仅监控)
    ViewOnly --> Requested: 申请维护(填理由)
    Requested --> Draining: Run协调安全暂停该空位
    Draining --> MaintenanceActive: 排空+安全动作读回+旧句柄关闭\n+Mutex守卫移交+联锁手续确认
    Draining --> RecoveryRequired: 交接超时(30s)
    RecoveryRequired --> ViewOnly: 恢复核对完成\n(不能因"等了30秒"直接给维护者—INV-03)
    MaintenanceActive --> Exiting: 退出维护
    Exiting --> Released: 读回复核+恢复通信配置+报警基线
    Released --> [*]: RunService显式恢复或新建Run
</div>
 
<table>
    <tr><th>现有状态/方法</th><th>V3.0 去向</th></tr>
    <tr><td>ReadOnly / OpenSession</td><td>拆为 ViewOnly(监控)+ Requested(写权申请)两个概念</td></tr>
    <tr><td>RequestMaintenance(内存 Reserve)</td><td>进入仲裁域:显示占用者/Run/DUT/剩余租约/父子影响 → Draining</td></tr>
    <tr><td>ActivateMaintenance</td><td>仅在"排空 + 安全动作读回 + 旧句柄/守卫确认"后签发维护 Grant</td></tr>
    <tr><td>CloseSession(释放内存字典)</td><td>升级为"排空 I/O + 物理读回 + ReleaseAsync"三步,失败进 RecoveryRequired 而非假装成功</td></tr>
    <tr><td>Faulted / RecoveryRequired</td><td>语义保留,但进入路径增加:交接超时、续期失败、释放失败</td></tr>
</table>
 
<h3>4.3 "强制释放"按钮的正确语义</h3>
<div class="warn">
<strong>旧直觉:</strong>Admin 点一下 → UPDATE/DELETE 租约行 → 设备归我。<br>
<strong>V3.0:</strong>Force release <strong>只能触发"隔离 + 恢复流程"</strong>——先在物理层确认旧 I/O 已终止(旧进程句柄关闭、Named Mutex 可获取、设备输出读回),否则宁可保持 RecoveryRequired。多人作业、拔线、接线的许可<strong>不由软件 token 单独判定</strong>(INV-07:软件租约不替代断电与硬件联锁)。
</div>
 
<h3>4.4 审计升级</h3>
<p><code>debug_operation</code> 保留,新增两列:<code>lease_generation</code>(哪一代持有者)、<code>idempotency_key</code>(重试去重,UNIQUE 部分索引)。任何一条历史指令可回答:<strong>谁(OwnerId+Principal)、以哪次授权(Grant 快照)、在哪个配置版本(ConfigHash)下、是否可能被重复执行过</strong>。</p>
 
<!-- ======================================================== -->
<h2 id="s5">五、进程拓扑与责任边界</h2>
 
<div class="mermaid">
graph TB
    HMI["HMI 进程(工程师/操作员)<br/>DeviceConfigView ① · DeviceDebugView ②<br/>DeviceMonitorView ③ (新建)"]
    W["Worker ×6(W01–W06,每20空位)<br/>Local 设备 I/O 宿主(连接守卫 Mutex 专用线程)<br/>DUTExecution 状态机 / 步骤引擎(OpenTAP)<br/>独立续期任务(不占UI;失败立即阻断新操作)"]
    B["Broker ×3(B01–B03)+ 可选 B00(全局共享域)<br/>共享会话设备唯一 I/O 宿主<br/>SessionActor(每真连接单队列,一次一段原子 I/O)<br/>Task执行 · Journal/Outbox单写器 · Recovery检查点"]
    SUP["Supervisor<br/>单实例守卫 · 限频拉起 · epoch 提升"]
    DEV["物理设备<br/>工位专用电源(Local) / 多通道PSU·CAN·温箱·PLC(Broker)"]
    HMI -->|"配置发布/维护申请(申请只到仲裁域)"| W & B
    W -->|"Local直连"| DEV
    B -->|"唯一句柄"| DEV
    SUP -.->|看护| W & B
</div>
 
<table>
    <tr><th>进程</th><th>职责</th><th>明确不做</th></tr>
    <tr><td>HMI</td><td>配置视图、维护写权申请、只读监控</td><td>不持有生产设备的物理连接</td></tr>
    <tr><td>Worker</td><td>Local 设备宿主 + 本空位组步骤引擎 + 异步等待(120 空位 ≠ 120 线程)</td><td>不加载共享设备的 SDK</td></tr>
    <tr><td>Broker</td><td>共享会话设备唯一 I/O 宿主、Task 有界执行、周期任务、订阅遥测</td><td><strong>不做 DUT 合格判定、不装整条 Recipe、不管 MES 事务</strong>(不是第二套测试引擎)</td></tr>
    <tr><td>Supervisor</td><td>拉起、限频防快速重启循环、epoch 管理</td><td>不执行业务命令</td></tr>
</table>
 
<div class="highlight"><strong>边界铁律:</strong>
<ul>
<li>Recipe/步骤引擎<strong>从不</strong>直接选 COM 口/IP/进程——只写"空位 S024 + 能力 Power",由 SnapshotResolver + ResourceRouter 按冻结版本解析出资源集与宿主;</li>
<li>跨域多资源组申请 = 协调式预留(Prepare→Activate,任一失败 Abort),<strong>不承诺跨库原子</strong>;严格全有全无的动作必须落在同一 Broker 域或打包成一个 Task;</li>
<li>SQLite 一律本机磁盘,禁止 SMB/NAS 多机共写;连接显式 <code>foreign_keys/busy_timeout</code>;事务内禁做 I/O。</li>
</ul></div>
 
<!-- ======================================================== -->
<h2 id="s6">六、接口契约演化映射(现有代码 → V3.0)</h2>
 
<table>
    <tr><th>现有实现(本仓库)</th><th>V3.0 去向</th><th>变化性质</th></tr>
    <tr>
        <td><span class="path">Core/ResourceManager.cs</span>(内存 Reserve/Release/ValidateOwner)</td>
        <td>旧单资源方法保留为过渡 Adapter;新增 <code>IResourceCoordinator</code>(AcquireGroup / RenewGroup / RequestHandover)</td>
        <td class="mid">兼容扩展</td>
    </tr>
    <tr>
        <td><span class="path">Contracts/ResourceOwnerToken.cs</span>(单一可变 Generation)</td>
        <td>业务元数据保留;I/O 改用<strong>每资源独立 LeaseGrant</strong>(AuthorityId / Epoch / LeaseId / Generation / ExpiresUtcMs)</td>
        <td class="bad">破坏性:一 owner 多资源不能共用 Generation</td>
    </tr>
    <tr>
        <td><span class="path">Services/AutoDeviceFacade.cs</span> + IAutoDeviceLease(同步 Dispose 阻塞释放)</td>
        <td>门面语义保留;Lease 注入独立续期任务 + <code>LeaseLost</code> 信号 + <code>HandoverRequested</code>;Dispose 改显式异步 Release(失败可查询、进恢复)</td>
        <td class="mid">语义增强</td>
    </tr>
    <tr>
        <td><span class="path">Services/DeviceService.cs</span>(deviceId+command+token 裸命令)</td>
        <td>升级为 <code>IDeviceExecutor</code>:类型化 OpCode + Grants 集 + IdempotencyKey + Deadline;raw command 仅保留受控维护入口</td>
        <td class="bad">生产路径重构</td>
    </tr>
    <tr>
        <td><span class="path">Drivers/DriverFactory.cs</span>(静态 switch)</td>
        <td>区分<strong>宿主驱动</strong>(Local/Broker 内加载 SDK)与 <strong>BrokerProxy</strong>(客户端,不加载 SDK);配合 .opentap 包分发(见姊妹篇)</td>
        <td class="mid">分发解耦</td>
    </tr>
    <tr>
        <td><span class="path">Plugin.DeviceDebug/Model/DebugSessionManager.cs</span></td>
        <td>增 Draining / RecoveryRequired;恢复时<strong>不可重造 owner token</strong>,必须使用持久化 Grant;监控与写权限分离</td>
        <td class="mid">状态扩展</td>
    </tr>
    <tr>
        <td><span class="path">Plugin.DeviceConfig/Model/DeviceConfigModel.cs</span>(DetectConflicts/SaveDevice)</td>
        <td>冲突检测移入发布门禁(离线校验 + 维护窗口探测);可变表升级为不可变快照</td>
        <td class="mid">位置前移</td>
    </tr>
    <tr>
        <td><span class="path">data.db</span>(一库三用)+ debug.db</td>
        <td>拆四库:aging_system.db / arb.db / broker-bxx.db / 每Run aging_data.db</td>
        <td class="bad">最大的一次性工程:存储边界重组</td>
    </tr>
    <tr>
        <td><span class="path">ConnectionUtil/PipeClient.cs</span></td>
        <td>V3.0 §13 认定其"响应不可达"(SendMessage 写后直接 return,读取为死代码)、serverName 未生效——修复后方可作为 IPC 候选基础</td>
        <td class="bad">P0 前置修复项</td>
    </tr>
</table>
 
<div class="info"><strong>建议:</strong>契约(ILeaseScope / LeaseGrant / DeviceOperation / IDeviceExecutor / BrokerTaskRequest 等)抽出为独立类库 <span class="path">Aging.ResourceContracts</span>,避免 Broker / Worker / HMI 三端互带 WPF 依赖。</div>
 
<!-- ======================================================== -->
<h2 id="s7">七、三条核心数据流</h2>
 
<h3>7.1 生产写命令(从步骤到物理 I/O 的完整一跳链)</h3>
<div class="mermaid">
sequenceDiagram
    autonumber
    participant STEP as TestStep<br/>(④业务域)
    participant CO as IResourceCoordinator<br/>(②仲裁域)
    participant EX as IDeviceExecutor<br/>(③宿主域)
    participant SG as SessionGate/Actor<br/>(③宿主域)
    participant DRV as 宿主驱动
    participant DB as 运行库(Journal/Outbox)
 
    STEP->>CO: AcquireGroup(资源集, OwnerKind=Production, ConfigHash)
    CO->>DB: BEGIN IMMEDIATE 事务CAS(逐资源 Free→Active, generation+1)<br/>+ parent_permit 检查 + lease_history
    DB-->>CO: 提交成功
    CO-->>STEP: ILeaseScope(Grants + 续期任务 + LeaseLost)
    STEP->>EX: DeviceOperation(OpCode, Params, IdemKey, Grants, Deadline)
    EX->>EX: 入队校验: grant集/epoch/ConfigHash/deadline/连接身份
    EX->>SG: 出队持门: [选通道→写→读响应] 原子片段
    SG->>DB: MarkRunning 先持久化(I/O 之前)
    SG->>DRV: 物理执行
    DRV-->>SG: 读回值
    SG->>DB: 同事务提交 Task终态+事件+Outbox
    SG-->>STEP: OperationResult(Readback, Quality)
    Note over STEP: 判定 PASS/FAIL 在步骤/引擎(INV-05/T-3)
    STEP->>CO: ReleaseAsync(排空→读回/交接→释放)
</div>
 
<h3>7.2 维护交接(摘要,对应文档 14.2 六步)</h3>
<div class="card">
占用可见化(Run/DUT/剩余租约/父子影响)→ Run 协调安全暂停,资源转 <code>Draining</code>,拒新写、停周期 → 等在途片段结束 + 执行 SafeStateProfile 并读回(超时 → <code>RecoveryRequired</code>,<strong>不等于</strong>自动给维护者)→ 旧 owner 排空释放 + 旧句柄关闭 + Mutex 守卫可获取 → 独立联锁/断电手续确认 → 签发维护 Grant,动作全量审计;退出维护反向复核后才交还生产。
</div>
 
<h3>7.3 Broker 重启恢复</h3>
<div class="card">
取单实例守卫 → <strong>epoch+1 落盘</strong>(旧 Grant/排队任务全部失效)→ 阻断普通写 → 设备身份验证 → 读回输出/通道/协议状态 → 按设备安全模板恢复(<strong>安全态 ≠ 统一"输出全关"</strong>:温箱、冷却、被测件各有要求)→ 恢复检查点与证据持久化(需人工审批则等待)→ Free/Ready → RunService 记录中断窗口与数据 Gap,决定续跑/重测。
</div>
 
<!-- ======================================================== -->
<h2 id="s8">八、保持不变的部分(架构连续性资产)</h2>
 
<div class="success">
<ol>
<li><strong>插件化 UI 骨架</strong>:SimpleMaskControl / PluginLoader / MVVM 全部保留,三个视图仍是热插拔插件(注意现状命名对齐:实际为 <code>Plugin.DeviceConfig.DeviceConfig</code> 等,且 <code>DeviceMonitorView</code> 需新建);</li>
<li><strong>SQLite 技术选型</strong>(本机盘 + WAL + 短事务 + busy_timeout),但数据库边界重划;</li>
<li><strong>调试会话"状态机 + 审计"设计思想</strong>——V3.0 是加状态,不是推翻;</li>
<li><strong>Draft/Published/Effective 版本理念</strong>——升级为不可变快照 + 哈希对账;</li>
<li><strong>全自动门面 B+C 模式</strong>(作用域租约 + 类型化服务,本项目已实现)——正是 V3.0 <code>ILeaseScope + DeviceOperation</code> 的雏形。</li>
</ol>
</table>
</div>
 
<!-- ======================================================== -->
<h2 id="s9">九、TestStep 仪器使用规范(业务执行域使用视图)</h2>
 
<p>前八章定义“系统与职责”,本章定义“使用者怎么写”——Recipe/TestStep 面向测试开发人员的<strong>使用契约</strong>。</p>
 
<h3>9.1 三条铁律(先立规矩)</h3>
<table>
    <tr><th>#</th><th>铁律</th><th>原因</th></tr>
    <tr><td><span class="iron">T-1</span></td>
        <td>TestStep <strong>永不</strong>直接 new 驱动、填 COM 口、IP 地址、进程名</td>
        <td>物理路由属于配置域(冻结快照);写死在步骤里 = 换台架全量改序列,且破坏 INV-01/04</td></tr>
    <tr><td><span class="iron">T-2</span></td>
        <td>TestStep <strong>永不</strong>自己调 ResourceManager.Reserve / 拼 LeaseGrant</td>
        <td>租约生命周期归框架(Scope + 独立续期任务),步骤自己拿租约必然忘续期/忘释放</td></tr>
    <tr><td><span class="iron">T-3</span></td>
        <td>TestStep <strong>永不</strong>把 Broker 读回值直接当合格判定</td>
        <td>“数据从哪来、新不新鲜”是宿主责任;“是否达到工艺条件”是步骤/引擎判定(§20.6 分权)</td></tr>
</table>
 
<h3>9.2 解析链:步骤声明逻辑能力,框架解析物理资源</h3>
<div class="mermaid">
flowchart LR
    A["TestStep 声明<br/>SlotId=S024, Capability=Power"] --> B["SnapshotResolver<br/>(Run启动时已冻结 RunSnapshot)"]
    B --> C["slot_binding<br/>S024+Power → psu/psu03/ch/5<br/>+ 父 Permit psu/psu03"]
    C --> D["ResourceRouter<br/>resource_definition:<br/>executionMode=Broker, authority=B01"]
    D --> E1["LocalExecutor(W0x)"]
    D --> E2["BrokerClient(B01)"]
    style A fill:#E8F5E9
    style E1 fill:#FFF8E1
    style E2 fill:#FFF8E1
</div>
<p>步骤作者面对的只有两个概念:<strong>空位号 + 能力名</strong>。DUTContext 每空位独立保存 SN/步骤/计时/结果,<strong>120 个空位 ≠ 120 条线程</strong>(等待用异步计时)。</p>
 
<h3>9.3 三种使用模式</h3>
 
<h4>模式 B(首选):作用域租约 —— 一个步骤多次操作同一资源</h4>
<pre><code><span class="cmt">// net472 无 await using,显式 ReleaseAsync + try/finally</span>
<span class="kwd">public override async void</span> Run()
{
    <span class="kwd">var</span> req = <span class="kwd">new</span> AcquireRequest {
        OwnerId           = RunContext.StationId,          <span class="cmt">// 如 "W02.Run-20261103-A"</span>
        OwnerKind         = OwnerKind.Production,
        ProcessInstanceId = RunContext.ProcessInstanceId,  <span class="cmt">// boot_id+pid,防串台</span>
        ConfigHash        = RunContext.RunSnapshot.ConfigHash,
        ResourceKeys      = <span class="kwd">new</span>[] { <span class="str">"psu/psu03/ch/5"</span>, <span class="str">"can/can01/ch/2"</span> }, <span class="cmt">// 框架按序展开组申请</span>
        WaitTimeoutMs     = <span class="num">60_000</span>
    };
 
    ILeaseScope scope = <span class="kwd">null</span>;
    <span class="kwd">try</span>
    {
        scope = <span class="kwd">await</span> coordinator.<span class="fn">AcquireGroupAsync</span>(req, ct);   <span class="cmt">// 同域:事务全有全无;跨域:协调式预留</span>
        <span class="cmt">// LeaseLost 信号:续期失败/被隔离 → 立即停止下发</span>
        <span class="kwd">var</span> ioCts = CancellationTokenSource.<span class="fn">CreateLinkedTokenSource</span>(ct, scope.LeaseLost);
 
        <span class="kwd">var</span> psu = executorFactory.<span class="fn">For</span>(scope, <span class="str">"psu/psu03/ch/5"</span>); <span class="cmt">// Local/Broker 路由对步骤透明</span>
        <span class="kwd">await</span> psu.<span class="fn">SetVoltageAsync</span>(<span class="num">12.0</span>, ctx.<span class="fn">NextIdempotencyKey</span>(), ioCts.Token);
        <span class="kwd">await</span> psu.<span class="fn">OutputOn</span>(ioCts.Token);
        <span class="kwd">var</span> v = <span class="kwd">await</span> psu.<span class="fn">ReadVoltage</span>(ioCts.Token);
 
        Upwards.<span class="fn">Find</span>(<span class="num">12.0</span>, v.Readback).Verdict =               <span class="cmt">// ← 判定在引擎侧(T-3)</span>
            Math.<span class="fn">Abs</span>(v.Readback - <span class="num">12.0</span>) &lt;= <span class="num">0.5</span> ? Verdict.Pass : Verdict.Fail;
    }
    <span class="kwd">finally</span>
    {
        <span class="cmt">// 显式异步释放:排空 I/O → 读回/安全交接 → 释放;</span>
        <span class="cmt">// 失败进恢复,绝不假装成功;终态保存用独立有界 token,不受调用方取消影响</span>
        <span class="kwd">if</span> (scope != <span class="kwd">null</span>)
            <span class="kwd">await</span> scope.<span class="fn">ReleaseAsync</span>(CancellationToken.None);
    }
}</code></pre>
 
<h4>模式 C(常用封装):类型化服务 + 双签名</h4>
<pre><code><span class="cmt">// 独立调用(框架内部走完 申请→执行→释放)——适合单命令步骤</span>
<span class="kwd">await</span> canBus.<span class="fn">SendFrameAsync</span>(<span class="str">"CAN-001"</span>, <span class="num">0x100</span>, data, ct);
 
<span class="cmt">// 借用作用域(推荐:与模式B组合,复用已持有租约)</span>
<span class="kwd">await</span> canBus.<span class="fn">SendFrameAsync</span>(scope.<span class="fn">LeaseOf</span>(<span class="str">"can/can01/ch/2"</span>), <span class="num">0x100</span>, data, ct);</code></pre>
 
<h4>模式选择速查</h4>
<table>
    <tr><th>场景</th><th>模式</th></tr>
    <tr><td>单条查询类步骤(如扫一条 UDS DID)</td><td>A 一次性</td></tr>
    <tr><td>多命令原子工艺段(上电→设压→读数→关输出)</td><td class="good">B 作用域(+C 封装)</td></tr>
    <tr><td>跨资源联动(同一 DUT 电源 + CAN)</td><td class="good">B 组申请(同域才承诺全有全无)</td></tr>
    <tr><td>周期性激励 / 长时监控</td><td>B + §9.6 后台任务模板,<strong>不是循环 A</strong></td></tr>
</table>
 
<h3>9.4 幂等键与超时重试规范</h3>
<pre><code><span class="cmt">// 键的生成规则:全局唯一、可重放定位、重试不换内容只增 attempt 序号</span>
<span class="kwd">string</span> idemKey = $<span class="str">"{RunId}/{DutExecId}/{StepExecId}/attempt-{attempt}"</span>;</code></pre>
<table>
    <tr><th>动作类型</th><th>超时后可自动重发?</th><th>依据</th></tr>
    <tr><td>设置型(SetVoltage=12.0)</td><td class="good">✅ 先 Query 对账再重发</td><td>幂等,重复执行结果同</td></tr>
    <tr><td>触发/复位/烧写/计数型</td><td class="bad">❌ 只能按 Unknown 走读回核对</td><td>文档 §11.2:跨执行与提交之间无硬件事务,不承诺 exactly-once</td></tr>
    <tr><td>任意动作,结果未收到</td><td class="mid">先同键 Query/重提,Journal 有终态返回原结果</td><td>§20.4:绝不生成新键再触发一次</td></tr>
</table>
<p>客户端重连必须<strong>保留原 ClientInstanceId 查询</strong>,不生成新键重发。</p>
 
<h3>9.5 交接、续期与 LeaseLost:步骤作者的义务</h3>
<table>
    <tr><th>框架职责</th><th>步骤职责</th></tr>
    <tr><td>后台独立线程续期(TTL/3)</td><td>监听 <code>scope.LeaseLost</code>,联进所有下发的 CTS</td></tr>
    <tr><td>Draining 通知(维护者申请进入)</td><td>只在【步骤边界】让渡,绝不在半条命令间放手</td></tr>
    <tr><td>续期连续失败 2 次 → LeaseLost</td><td>立即停止下发新操作</td></tr>
    <tr><td>Release 失败</td><td>如实上报,等待恢复,不“记日志后继续”</td></tr>
</table>
<pre><code><span class="cmt">// 步骤收尾点检查(框架在 HandoverRequested 时置位)</span>
<span class="kwd">if</span> (scope.HandoverRequested) <span class="kwd">return</span>;  <span class="cmt">// 本步骤正常结束,作用域退出时安全让渡</span>
<span class="cmt">// 注意:长 Delay 应拆成可中断的异步等待片段,保证 30s handover 窗口内能到达边界</span></code></pre>
<div class="info"><strong>取消的三种语义</strong>(框架已处理,作者只需知道):排队中取消 = 确定未执行;执行中取消 = 需驱动安全停止确认;响应等待取消 = 只是不等了,<strong>不能反推设备没动</strong>——结果仍走 Unknown/读回通道。</div>
 
<h3>9.6 长周期任务:交给 Broker,步骤不养线程</h3>
<table>
    <tr><th>需求</th><th>❌ 错误做法</th><th>✅ 正确做法(Task 类型)</th></tr>
    <tr><td>CAN 周期发送 0x100@100ms</td><td>TestStep 内 while+Sleep</td><td><strong>PeriodicTx</strong> 模板驻留 Broker,随 owner 租约存续/到期/失效;禁止重连后补发旧帧</td></tr>
    <tr><td>老化期间监控总线信号</td><td>120 空位轮询线程</td><td><strong>Acquisition</strong> 订阅 + 事件游标增量拉取;数据 Gap 带质量标记 Stale/Disconnected</td></tr>
    <tr><td>UDS 会话保活</td><td>步骤定时器发 TesterPresent</td><td><strong>ProtocolSession</strong>:会话归一调度,TesterPresent 与业务动作同队</td></tr>
    <tr><td>等 30 分钟</td><td>Thread.Sleep 占线程</td><td>异步计时留在执行引擎(Recipe 的长 Delay <strong>不进 Broker</strong>——Broker 只做有界设备动作)</td></tr>
</table>
<pre><code><span class="cmt">// 步骤侧订阅形态:提交-查询-ACK,不驻留</span>
<span class="kwd">var</span> sub = <span class="kwd">await</span> broker.<span class="fn">SubscribeAsync</span>(<span class="kwd">new</span> { resource = <span class="str">"can/can01/ch/2"</span>,
    signal = <span class="str">"Motor.Temp"</span>, templateId = <span class="str">"can.acq.motor-temp"</span> }, ct);
 
<span class="cmt">// 等待信号达标(判定仍在引擎)</span>
<span class="kwd">var</span> sample = <span class="kwd">await</span> sub.<span class="fn">WaitMatchingAsync</span>(s =&gt; s.Value &gt; <span class="num">85.0</span>, TimeSpan.<span class="fn">FromMinutes</span>(<span class="num">30</span>), ct);
<span class="kwd">if</span> (sample == <span class="kwd">null</span>) Upwards.<span class="fn">Find</span>().<span class="fn">Fail</span>(<span class="str">$"超时未达85℃,Gap={sub.GapCount}"</span>);
 
<span class="kwd">await</span> sub.<span class="fn">UnsubscribeAsync</span>(ct);   <span class="cmt">// 取消并确认停采/解绑</span></code></pre>
<p>结果消费遵循 Outbox:<strong>ACK 只在结果落盘成功后发送</strong>,步骤崩溃重启用同 IdempotencyKey 找回未 ACK 结果,不重跑设备动作。</p>
 
<h3>9.7 完整示例:多工位 CAN 老化步骤(B + C + 订阅 拼成真实工艺)</h3>
<pre><code>[Display(<span class="str">"CAN 通信老化"</span>, Group = <span class="str">"Aging"</span>)]
<span class="kwd">public class</span> CanCommAgingStep : TestStep
{
    [ResourceProperty(<span class="str">"CAN"</span>)] <span class="kwd">public string</span> SlotId { <span class="kwd">get</span>; <span class="kwd">set</span>; } = <span class="str">"S001"</span>;
    <span class="kwd">public int</span> Cycles { <span class="kwd">get</span>; <span class="kwd">set</span>; } = <span class="num">100</span>;
 
    <span class="kwd">public override async void</span> Run()
    {
        <span class="kwd">var</span> ctx  = RunContext.Current;                     <span class="cmt">// Run/DUT/Step 关联ID</span>
        <span class="kwd">var</span> auto = AgingRuntime.Coordinator;               <span class="cmt">// IResourceCoordinator</span>
        <span class="kwd">var</span> cans = AgingRuntime.CanBus;                    <span class="cmt">// ICanBusService(类型化)</span>
 
        <span class="kwd">var</span> req  = ctx.<span class="fn">BuildAcquire</span>(<span class="kwd">new</span>[] { <span class="str">"can/"</span> + ctx.<span class="fn">SlotOf</span>(SlotId) + <span class="str">"/ch/2"</span> });
        <span class="kwd">using var</span> cts = Utils.<span class="fn">LinkedWithVerdictCancel</span>(Upwards);
 
        ILeaseScope scope = <span class="kwd">await</span> auto.<span class="fn">AcquireGroupAsync</span>(req, cts.Token);
        <span class="kwd">try</span>
        {
            <span class="kwd">var</span> lease = scope.<span class="fn">LeaseOf</span>(ctx.<span class="fn">ResourceOf</span>(SlotId));
 
            <span class="cmt">// ① 起周期激励(驻留 Broker,随租约失效自动停)</span>
            <span class="kwd">var</span> periodic = <span class="kwd">await</span> lease.<span class="fn">StartTemplateAsync</span>(<span class="str">"can.periodic.keepalive"</span>,
                <span class="kwd">new</span> { frameId = <span class="num">0x3E0</span>, periodMs = <span class="num">500</span> }, cts.Token);
 
            <span class="cmt">// ② 订阅响应信号</span>
            <span class="kwd">var</span> sub = <span class="kwd">await</span> lease.<span class="fn">SubscribeAsync</span>(<span class="str">"can.acq.response-0x3E1"</span>, cts.Token);
 
            <span class="kwd">for</span> (<span class="kwd">int</span> i = <span class="num">0</span>; i &lt; Cycles &amp;&amp; !scope.HandoverRequested; i++)
            {
                <span class="kwd">await</span> cans.<span class="fn">SendFrameAsync</span>(lease, <span class="num">0x100</span> + (uint)i, ctx.<span class="fn">Payload</span>(i),
                    ctx.<span class="fn">NextIdempotencyKey</span>(), cts.Token);
 
                <span class="kwd">var</span> echo = <span class="kwd">await</span> sub.<span class="fn">WaitMatchingAsync</span>(f =&gt; f.<span class="fn">MatchRequest</span>(<span class="num">0x100</span> + i),
                    TimeSpan.<span class="fn">FromSeconds</span>(<span class="num">2</span>), cts.Token);
 
                <span class="kwd">if</span> (echo == <span class="kwd">null</span>) { Verdict = Verdict.Fail; <span class="kwd">break</span>; }  <span class="cmt">// 判定在引擎</span>
                Report.<span class="fn">Sample</span>(i, echo);                                <span class="cmt">// 采样走结果通路</span>
            }
 
            <span class="kwd">await</span> periodic.<span class="fn">StopAsync</span>(cts.Token);   <span class="cmt">// 周期任务显式停止并“确认停止”</span>
            <span class="kwd">await</span> sub.<span class="fn">UnsubscribeAsync</span>(cts.Token);
        }
        <span class="kwd">finally</span>
        {
            <span class="kwd">await</span> scope.<span class="fn">ReleaseAsync</span>(CancellationToken.None);  <span class="cmt">// 异步、可失败进恢复</span>
        }
    }
}</code></pre>
 
<h3>9.8 步骤生命周期时序</h3>
<pre><code>OnPlanLoad    解析 Slot+Capability → 预检配置存在性(不占资源)
Run() 入口     DUTContext 状态检查 → AcquireGroup(排队等待=异步,不阻塞他位)
持租阶段      类型化操作/订阅 → 每命令带 Grant+IdemKey+Deadline → 读回质量核对
退出边界      HandoverRequested? → 让渡或续跑
finally       停后台任务(确认) → ReleaseAsync → 结果 Outbox ACK 后终态保存
异常路径      LeaseLost / Unknown → 停止下发 → 上报 → 交给恢复/RunService 决策(不自动判合否)</code></pre>
 
<h3>9.9 反模式清单(TestStep 侧汇总)</h3>
<table>
    <tr><th>反模式</th><th>后果</th><th>正解</th></tr>
    <tr><td class="bad">步骤里 new CanDriverAdapter(...)</td><td>绕过仲裁与快照,双宿主冲突(INV-01)</td><td>只经 Scope/Executor 路由</td></tr>
    <tr><td class="bad">超时后换新 IdempotencyKey 重发</td><td>触发型动作执行两次</td><td>同键 Query 对账(§20.4)</td></tr>
    <tr><td class="bad">在 await 跨线程处释放守卫 Mutex</td><td>AbandonedMutex / 非法释放</td><td>守卫归宿主专用线程,步骤只操作租约</td></tr>
    <tr><td class="bad">scope.Dispose() 同步阻塞 UI/线程池</td><td>排空等待卡死调度</td><td>显式 ReleaseAsync</td></tr>
    <tr><td class="bad">用 Thread.Sleep 等老化时长</td><td>120 空位 = 120 线程</td><td>异步计时 + Broker Periodic/Acquisition</td></tr>
    <tr><td class="bad">把 Broker 读回直接当 PASS</td><td>掩盖通信故障(数据质量≠工艺判定)</td><td>判定在步骤/引擎,Gap 必须计入</td></tr>
    <tr><td class="bad">捕获 LeaseLost 后继续发命令</td><td>僵尸写入(栅栏只挡坏结果,挡不住你没查)</td><td>LeaseLost 联进所有下发的 CTS</td></tr>
</table>
 
<!-- ======================================================== -->
<h2 id="s10">十、结论</h2>
 
<div class="success">
<p><strong>新架构图的一句话读法:</strong>仪器注册生产<strong>带哈希的配置真理</strong>,仪器调试申请<strong>带栅栏的临时写权</strong>,Worker/Broker 执行<strong>带原子片段的物理 I/O</strong>,Recipe 只做<strong>空位→能力→判定</strong>,四本数据库各守各的单写者——任何一次“碰设备”都要同时回答:<strong>授权是谁(Grant)、配置是哪版(Hash)、物理状态信不信得过(读回)</strong>。</p>
<p>落地顺序遵循 P0~P4 裁剪(PipeClient 修复与硬件拓扑冻结 → 单 Authority 租约内核 → Broker MVP → 恢复编排 → 配置发布体系),详见《多进程仪器资源方案评估》。本文与以下文档配套阅读:</p>
<ul>
    <li><a href="./Local + DeviceBroker 混合架构详细设计说明书 V3.0.html">Local + DeviceBroker 混合架构详细设计说明书 V3.0</a>(权威设计源)</li>
    <li><a href="./MultiProcess_Instrument_Resource_Solution.html">多进程/多线程仪器资源独占与共享方案</a>(已被 V3.0 修正:到期不自动接管等)</li>
    <li><a href="./OpenTAP_Instrument_Package_Analysis.html">OpenTAP Instrument + 插件包机制分析</a>(驱动分发形态)</li>
    <li><a href="./DeviceConfig_DeviceDebug_Architecture.html">仪器注册与硬件调试调用关系</a>(现状实现快照)</li>
</ul>
</div>
 
<footer>
    仪器注册与仪器调试 · 系统架构说明 V3.0 重梳理版(含 TestStep 使用规范)· 2026-11-03<br>
    基准:《Local + DeviceBroker 混合架构详细设计说明书 V3.0》· 本文现状描述部分已对照真实代码核实
</footer>
 
<script>
    mermaid.initialize({ 
        startOnLoad: true, theme: 'default', securityLevel: 'loose',
        fontFamily: 'Segoe UI, Microsoft YaHei, sans-serif',
        flowchart: { curve: 'basis', padding: 16 },
        sequence: { showSequenceNumbers: false, actorMargin: 50 }
    });
</script>
 
</body>
</html>