tirtc-device-builder 0.4.0 → 0.5.0
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.
- package/.codex-plugin/plugin.json +1 -1
- package/CHANGELOG.md +8 -0
- package/README.md +16 -9
- package/package.json +1 -1
- package/skills/tirtc-esp32-builder/SKILL.md +8 -2
- package/skills/tirtc-esp32-builder/USAGE.md +7 -3
- package/skills/tirtc-esp32-builder/assets/developer-intake-prompt.md +7 -4
- package/skills/tirtc-esp32-builder/references/capability-rules.md +11 -7
- package/skills/tirtc-esp32-builder/references/hardware-ir.md +14 -1
- package/skills/tirtc-esp32-builder/references/porting-risks.md +1 -1
- package/skills/tirtc-esp32-builder/references/reporting.md +3 -1
- package/skills/tirtc-esp32-builder/references/workflow.md +10 -2
- package/skills/tirtc-esp32-builder/scripts/hardware_ir.py +186 -67
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
This project follows Semantic Versioning.
|
|
4
4
|
|
|
5
|
+
## 0.5.0
|
|
6
|
+
|
|
7
|
+
- Split Hardware IR assessment into explicit `intake`, `build`, and `hil` phases so implementation- and build-resolvable facts do not require runtime proof before adapter work begins.
|
|
8
|
+
- Add `BUILD_VERIFIED` with exact artifact SHA-256 binding, while keeping L5/L6 runtime evidence exclusive to `HIL_VERIFIED`.
|
|
9
|
+
- Classify unresolved facts by their next evidence source and stop pre-build work only for facts that genuinely require user input, unavailable hardware/SDK, or a public-contract decision.
|
|
10
|
+
- Update the developer intake prompt and reporting guidance so missing serial access produces L2-L7 `SKIP` results without blocking L0/L1.
|
|
11
|
+
- Add phase regression coverage while retaining schema v1 compatibility and independent MJPEG, H.264, and H.265 profile validation.
|
|
12
|
+
|
|
5
13
|
## 0.4.0
|
|
6
14
|
|
|
7
15
|
- Add Hardware IR v2 with selected MJPEG, H.264, or H.265 video profiles while retaining schema v1 compatibility.
|
package/README.md
CHANGED
|
@@ -90,8 +90,8 @@ Skill 在 Codex 会话启动时被发现。安装完成后,关闭当前 Codex
|
|
|
90
90
|
|
|
91
91
|
工程:<输出目录或现有工程的绝对路径>
|
|
92
92
|
|
|
93
|
-
请先运行 Doctor,分析全部资料并生成 Hardware IR v2
|
|
94
|
-
本轮不访问串口、不烧录、不擦除 NVS
|
|
93
|
+
请先运行 Doctor,分析全部资料并生成 Hardware IR v2。把未知项区分为可由资料、实现、构建或 HIL 解决,以及必须由用户补充的阻塞项。READY_TO_PORT 表示资料足以开始设计,不要求最终 ELF;随后生成、适配和编译,以 artifact SHA-256 运行 build 阶段评估,并输出 TIRTC_PORTING_REPORT.md。
|
|
94
|
+
本轮不访问串口、不烧录、不擦除 NVS;缺少串口只让 L2-L7 记为 SKIP,不得阻止 L0/L1。不要把任何凭证写入源码或报告。
|
|
95
95
|
```
|
|
96
96
|
|
|
97
97
|
手头只有型号也可以开始:
|
|
@@ -450,26 +450,33 @@ python3 ~/.codex/skills/tirtc-esp32-builder/scripts/hardware_ir.py init \
|
|
|
450
450
|
python3 ~/.codex/skills/tirtc-esp32-builder/scripts/hardware_ir.py validate \
|
|
451
451
|
/absolute/path/hardware-ir.json
|
|
452
452
|
|
|
453
|
-
python3 ~/.codex/skills/tirtc-esp32-builder/scripts/hardware_ir.py assess
|
|
453
|
+
python3 ~/.codex/skills/tirtc-esp32-builder/scripts/hardware_ir.py assess \
|
|
454
|
+
--phase intake --strict \
|
|
454
455
|
/absolute/path/hardware-ir.json
|
|
455
456
|
```
|
|
456
457
|
|
|
457
|
-
|
|
458
|
+
能力门禁有五种状态:
|
|
458
459
|
|
|
459
460
|
| 状态 | 含义 | 怎么处理 |
|
|
460
461
|
|---|---|---|
|
|
461
|
-
| `NEEDS_CONFIRMATION` |
|
|
462
|
+
| `NEEDS_CONFIRMATION` | 当前阶段的关键事实未知、冲突或证据等级不足 | 按资料、实现、构建、HIL 或用户输入来源继续闭环 |
|
|
462
463
|
| `BLOCKED` | 现有硬件或 SDK 已确认不满足 | 更换硬件,补编码/播放路径,或取得匹配 SDK |
|
|
463
464
|
| `READY_TO_PORT` | 资料足以开始生成和板级实现 | 进入工程生成与编译 |
|
|
465
|
+
| `BUILD_VERIFIED` | 精确 artifact 通过源码、编译和 post-link 门禁 | 记录 BIN/ELF SHA-256;按授权进入实机验收 |
|
|
464
466
|
| `HIL_VERIFIED` | 已完成端到端实机验证 | 固定版本并保存证据 |
|
|
465
467
|
|
|
466
468
|
`assess --strict` 在条件不足时返回非零,这是门禁在阻止过早生成,不代表脚本损坏。
|
|
467
469
|
|
|
468
|
-
|
|
470
|
+
构建后运行 build 阶段;只有运行证据绑定到同一固件 SHA-256 时,hil 阶段才会给出 `HIL_VERIFIED`:
|
|
469
471
|
|
|
470
472
|
```bash
|
|
471
473
|
python3 ~/.codex/skills/tirtc-esp32-builder/scripts/hardware_ir.py assess \
|
|
472
474
|
/absolute/path/hardware-ir.json \
|
|
475
|
+
--phase build --artifact-sha256 <64-character-sha256> --strict
|
|
476
|
+
|
|
477
|
+
python3 ~/.codex/skills/tirtc-esp32-builder/scripts/hardware_ir.py assess \
|
|
478
|
+
/absolute/path/hardware-ir.json \
|
|
479
|
+
--phase hil \
|
|
473
480
|
--artifact-sha256 <64-character-sha256> --strict
|
|
474
481
|
```
|
|
475
482
|
|
|
@@ -750,7 +757,7 @@ WSL 默认不一定能看到 USB 设备。按 [Microsoft WSL USB 连接说明](h
|
|
|
750
757
|
|
|
751
758
|
### Hardware IR 一直是 `NEEDS_CONFIRMATION`
|
|
752
759
|
|
|
753
|
-
|
|
760
|
+
先确认当前运行的是 intake、build 还是 hil 阶段,再标记每个未知项的下一证据来源。资料/固定源码可解决的事实继续检查;adapter 或 ELF 可解决的事实进入对应实现/构建层;运行指标留到 HIL。只有准确 PCB 版本、关键连接、器件身份、产品合同或不可用 SDK 等无法安全推导的事实需要用户补充。不要从相似开发板复制管脚来填补这些信息。
|
|
754
761
|
|
|
755
762
|
### 工程能编译,浏览器没有画面
|
|
756
763
|
|
|
@@ -874,8 +881,8 @@ gh release create kit-esp32s3-v1.0.0 \
|
|
|
874
881
|
|
|
875
882
|
```bash
|
|
876
883
|
npm test
|
|
877
|
-
git tag -a v0.
|
|
878
|
-
git push origin v0.
|
|
884
|
+
git tag -a v0.5.0 -m "v0.5.0"
|
|
885
|
+
git push origin v0.5.0
|
|
879
886
|
```
|
|
880
887
|
|
|
881
888
|
不要重复发布已经存在的 npm 版本。版本变化同步更新 `package.json`、`.codex-plugin/plugin.json` 和发布说明。
|
package/package.json
CHANGED
|
@@ -14,7 +14,9 @@ Turn board evidence into an evidence-backed ESP-IDF project. Treat the Hardware
|
|
|
14
14
|
3. Run the Doctor through the managed environment helper when one exists; otherwise run `python3 <skill-dir>/scripts/doctor.py --expected-idf 5.5 --target esp32s3`. Add `--require-workspace` when generation or repository reference documents are needed; a self-contained generated project can instead resolve its bundled SDK through `--project`. Resolve every required failure before claiming build readiness.
|
|
15
15
|
4. Read [workflow.md](references/workflow.md). Select the registered-board, new-board intake, or existing-project branch. The branch is selected when every supplied artifact has been accounted for and the exact board revision is known or explicitly unresolved.
|
|
16
16
|
5. Read [hardware-ir.md](references/hardware-ir.md) when a Hardware IR must be created or updated. New intake uses schema v2; schema v1 remains readable for existing H.264 projects. Record a source and verification level for every hardware fact that affects a requested feature.
|
|
17
|
-
6. Run `python3 <skill-dir>/scripts/hardware_ir.py validate <hardware-ir.json>` and then `assess --strict`.
|
|
17
|
+
6. Run `python3 <skill-dir>/scripts/hardware_ir.py validate <hardware-ir.json>` and then `assess --phase intake --strict`. `READY_TO_PORT` means the evidence is sufficient to design the adapter without guessing wiring or changing an unapproved public contract; it does not require a final ELF or runtime measurements.
|
|
18
|
+
|
|
19
|
+
Before stopping on `NEEDS_CONFIRMATION`, classify every unresolved fact as `source_resolvable`, `implementation_resolvable`, `build_resolvable`, `hil_resolvable`, or `user_blocked`. Resolve source facts by inspection. For implementation/build facts, record the evidenced design plan at `corroborated`, generate a compile-safe adapter, and verify it at L1. Defer HIL-only measurements to L2-L7 when hardware access is unavailable. Stop before implementation only for `user_blocked` facts such as unknown wiring or identity, a missing product choice, an unavailable SDK, or a required public-contract change.
|
|
18
20
|
|
|
19
21
|
## Build the project
|
|
20
22
|
|
|
@@ -26,11 +28,15 @@ Keep the Skill board-agnostic. The prompt supplies product intent and artifact l
|
|
|
26
28
|
|
|
27
29
|
Run focused tests before ESP-IDF build. Resolve the TiRTC SDK target and `manifest/build-contract.env` against the generated `sdkconfig`; a mismatched precompiled SDK is a blocked build, not a code-generation problem.
|
|
28
30
|
|
|
31
|
+
After a successful build, promote only facts actually established by source, compile, or post-link gates to `build_verified`. Hash the exact BIN/ELF and run `hardware_ir.py assess --phase build --artifact-sha256 <sha256> --strict`. A design plan at `corroborated` can pass intake but cannot pass the build phase.
|
|
32
|
+
|
|
29
33
|
## Flash and verify
|
|
30
34
|
|
|
31
35
|
Flash only when the user requested hardware mutation and the exact serial port and chip have been resolved. When more than one candidate device exists, obtain the target choice before writing. Keep credentials outside generated files and redact device keys, Wi-Fi passwords, MQTT/WHIP tokens, and user media from logs and reports.
|
|
32
36
|
|
|
33
|
-
Read [reporting.md](references/reporting.md) before end-to-end verification. Report every acceptance level as `PASS`, `FAIL`, or `SKIP`, with commands and evidence. Bind HIL observations to the exact firmware SHA-256 with `assess --artifact-sha256`; an older artifact cannot verify a newer build. A build-only result is not H5 or AI completion; missing hardware, browser, account, service, or network evidence remains an explicit `SKIP` or blocker.
|
|
37
|
+
Read [reporting.md](references/reporting.md) before end-to-end verification. Report every acceptance level as `PASS`, `FAIL`, or `SKIP`, with commands and evidence. Bind HIL observations to the exact firmware SHA-256 with `assess --phase hil --artifact-sha256`; an older artifact cannot verify a newer build. A build-only result is not H5 or AI completion; missing hardware, browser, account, service, or network evidence remains an explicit `SKIP` or blocker.
|
|
38
|
+
|
|
39
|
+
For HIL assessment, run `hardware_ir.py assess --phase hil --artifact-sha256 <sha256> --strict`. The HIL phase first requires build-verified paths, then promotes only features whose L5/L6 runtime evidence matches that exact artifact.
|
|
34
40
|
|
|
35
41
|
## Finish
|
|
36
42
|
|
|
@@ -25,7 +25,7 @@ $tirtc-esp32-builder
|
|
|
25
25
|
Wi-Fi 与设备绑定:<指定方案/根据 BSP 和平台合同选择>
|
|
26
26
|
工程:<输出目录或现有工程的绝对路径>
|
|
27
27
|
|
|
28
|
-
先运行 Doctor,再生成 Hardware IR v2
|
|
28
|
+
先运行 Doctor,再生成 Hardware IR v2。READY_TO_PORT 只表示资料足以设计板级适配;随后完成适配和编译,以精确 artifact SHA-256 运行 build 阶段评估并输出 TIRTC_PORTING_REPORT.md。
|
|
29
29
|
本轮不访问串口、不烧录、不擦除 NVS,也不把凭证写入源码或报告。
|
|
30
30
|
```
|
|
31
31
|
|
|
@@ -92,10 +92,14 @@ python3 <skill-dir>/scripts/doctor.py \
|
|
|
92
92
|
```bash
|
|
93
93
|
python3 <skill-dir>/scripts/hardware_ir.py init /tmp/hardware-ir.json
|
|
94
94
|
python3 <skill-dir>/scripts/hardware_ir.py validate /tmp/hardware-ir.json
|
|
95
|
-
python3 <skill-dir>/scripts/hardware_ir.py assess --strict /tmp/hardware-ir.json
|
|
95
|
+
python3 <skill-dir>/scripts/hardware_ir.py assess --phase intake --strict /tmp/hardware-ir.json
|
|
96
|
+
python3 <skill-dir>/scripts/hardware_ir.py assess --phase build \
|
|
97
|
+
--artifact-sha256 <64-character-sha256> --strict /tmp/hardware-ir.json
|
|
98
|
+
python3 <skill-dir>/scripts/hardware_ir.py assess --phase hil \
|
|
99
|
+
--artifact-sha256 <64-character-sha256> --strict /tmp/hardware-ir.json
|
|
96
100
|
```
|
|
97
101
|
|
|
98
|
-
`init` 默认创建 schema v2;v1 仅用于兼容已有 H.264 IR。`BLOCKED` 表示资料已确认硬件/合同/资源或凭证策略不满足;`NEEDS_CONFIRMATION`
|
|
102
|
+
`init` 默认创建 schema v2;v1 仅用于兼容已有 H.264 IR。`BLOCKED` 表示资料已确认硬件/合同/资源或凭证策略不满足;`NEEDS_CONFIRMATION` 表示当前阶段仍有未知项或证据不足;`READY_TO_PORT` 表示可以生成并实现板级适配;`BUILD_VERIFIED` 表示精确 artifact 已通过源码、编译和 post-link 门禁;`HIL_VERIFIED` 还要求同一 SHA-256 的 L5/L6 运行证据。
|
|
99
103
|
|
|
100
104
|
## 当前边界
|
|
101
105
|
|
|
@@ -30,10 +30,12 @@ $tirtc-esp32-builder
|
|
|
30
30
|
|
|
31
31
|
执行要求:
|
|
32
32
|
1. 先运行 Device Kit Doctor,读完全部资料,再生成 Hardware IR v2。没有证据的器件、GPIO 和媒体能力保持未知。
|
|
33
|
-
2.
|
|
34
|
-
3.
|
|
35
|
-
4.
|
|
36
|
-
5.
|
|
33
|
+
2. 把每个未知项标为 source_resolvable、implementation_resolvable、build_resolvable、hil_resolvable 或 user_blocked。先通过资料和固定版本源码解决 source_resolvable;只有 user_blocked 会阻止开始 adapter 开发。
|
|
34
|
+
3. READY_TO_PORT 表示硬件身份、连接、媒体合同和资源设计已经有证据,足以开始实现;它不要求最终 ELF 或实机数据。可通过实现或构建解决的项目必须继续生成 compile-safe adapter、锁定依赖、运行门禁并编译。
|
|
35
|
+
4. 移植前核对媒体合同、板级资源、静态内存预算、配网和绑定状态。构建后只把源代码、锁定依赖、编译或 post-link 门禁实际证明的事实升级为 build_verified。
|
|
36
|
+
5. 记录 BIN/ELF 路径、大小和 SHA-256,运行 build 阶段评估并输出 TIRTC_PORTING_REPORT.md。编译结果必须与 L2-L7 实机验收分开。
|
|
37
|
+
6. 本轮不访问串口、不烧录、不擦除 NVS;因此缺少启动、浏览器、实体声音和运行时资源数据时,把相应 L2-L7 标为 SKIP,不得阻止 L0/L1。
|
|
38
|
+
7. Wi-Fi 密码、设备密钥、token、私钥和用户音视频不能写入工程或报告。
|
|
37
39
|
```
|
|
38
40
|
|
|
39
41
|
## 只有已知时才补充
|
|
@@ -46,5 +48,6 @@ $tirtc-esp32-builder
|
|
|
46
48
|
- 凭证保存与重配方式、已有绑定处理和独立清除入口;
|
|
47
49
|
- 已知资料矛盾、实机 PID、串口日志或已知良好固件 SHA-256;
|
|
48
50
|
- 非默认 ESP-IDF、TiRTC SDK、Device Kit、服务发现地址或 HTTP/HTTPS 分阶段要求。
|
|
51
|
+
- 若用于 clean-room 验证,明确允许的资料根目录,并列出不得读取的历史工程或旧 artifact。
|
|
49
52
|
|
|
50
53
|
直接把它们追加在提示词末尾即可,不必逐项填表。
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Capability rules
|
|
2
2
|
|
|
3
|
-
Run `hardware_ir.py assess --strict` before generation. Hardware IR v2 validates the selected product contract rather than assuming one video codec or provisioning method. Schema v1 remains readable for existing H.264 projects.
|
|
3
|
+
Run `hardware_ir.py assess --phase intake --strict` before generation. Hardware IR v2 validates the selected product contract rather than assuming one video codec or provisioning method. Schema v1 remains readable for existing H.264 projects.
|
|
4
4
|
|
|
5
5
|
## Current starter contract
|
|
6
6
|
|
|
@@ -11,7 +11,9 @@ Run `hardware_ir.py assess --strict` before generation. Hardware IR v2 validates
|
|
|
11
11
|
| `h5_talkback` | G.711 A-law, 8 kHz downlink decode and speaker path for stream 14; audio controller/GPIO ownership and memory budget resolved |
|
|
12
12
|
| `ai_talk` | A-law 8 kHz microphone and speaker paths for AI stream 1, started only after `start_session`; audio ownership/channel mapping and memory budget resolved |
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
Hardware identity, wiring, the selected media contract, and the adapter/resource design must be at least `corroborated` to become `READY_TO_PORT`. At intake, `available=true` means a pinned source path can implement the selected profile; it does not claim that the final adapter or physical media path has run. Implementation composition, final ELF policy, runtime memory margin, browser media, and stability belong to the build or HIL phases.
|
|
15
|
+
|
|
16
|
+
A design fact that is still unknown is `NEEDS_CONFIRMATION`; a confirmed missing or incompatible resource is `BLOCKED`. Facts that can be resolved safely through source inspection, adapter implementation, compilation, or post-link inspection should be resolved in that layer rather than converted into a request for HIL evidence.
|
|
15
17
|
|
|
16
18
|
## Selected video profiles
|
|
17
19
|
|
|
@@ -25,14 +27,16 @@ Hardware IR v2 stores one or more `camera.video_profiles` and exactly one select
|
|
|
25
27
|
|
|
26
28
|
Available but unselected profiles do not satisfy or block the selected contract. Stream IDs and codec support must come from the applicable ThingConnect/H5 contract, not this table alone.
|
|
27
29
|
|
|
28
|
-
##
|
|
30
|
+
## Phased project gates
|
|
29
31
|
|
|
30
|
-
|
|
32
|
+
The intake phase requires:
|
|
31
33
|
|
|
32
|
-
- one evidenced I2C driver
|
|
34
|
+
- one selected and evidenced I2C driver-family plan when I2C is used;
|
|
33
35
|
- a selected Wi-Fi credential method that is available, reprovisionable, and keeps credentials outside source control;
|
|
34
36
|
- one evidenced binding method—verification code, factory-bound identity, development credentials, or documented custom flow—plus stored-binding behavior and reset control;
|
|
35
|
-
- feature-specific I2S/GPIO ownership, channel/TDM mapping, realtime camera policy, and startup/media memory budget.
|
|
37
|
+
- feature-specific I2S/GPIO ownership plans, channel/TDM mapping, realtime camera policy, and a static startup/media memory budget.
|
|
38
|
+
|
|
39
|
+
At intake, `corroborated` on these fields means the design is resolved from authoritative sources and is safe to implement. After compilation, promote a field to `build_verified` only when the generated source, component lock, compile result, or post-link gate establishes it. The build phase requires an exact artifact SHA-256 and returns `BUILD_VERIFIED`. Runtime measurements never need to be invented to pass intake or build.
|
|
36
40
|
|
|
37
41
|
SoftAP is one Wi-Fi option, not a universal requirement. BLE, SmartConfig, factory NVS, development configuration, or a documented custom method can satisfy intake when the selected path is evidenced. Committed plaintext credentials are always `BLOCKED`.
|
|
38
42
|
|
|
@@ -43,7 +47,7 @@ SoftAP is one Wi-Fi option, not a universal requirement. BLE, SmartConfig, facto
|
|
|
43
47
|
- Start AI media only after the successful `start_session` response; stop and flush media before disconnecting.
|
|
44
48
|
- Copy SDK callback payloads into bounded queues before returning. Perform decoding, playback, HTTP, and lifecycle changes outside callbacks.
|
|
45
49
|
- Use monotonic timestamps and session generation to reject stale frames and delayed callbacks.
|
|
46
|
-
- Record runtime evidence with the exact BIN/ELF SHA-256. Use `assess --artifact-sha256 <sha>`; documentation verification alone never becomes v2 `HIL_VERIFIED`.
|
|
50
|
+
- Record runtime evidence with the exact BIN/ELF SHA-256. Use `assess --phase hil --artifact-sha256 <sha>`; documentation verification alone never becomes v2 `HIL_VERIFIED`.
|
|
47
51
|
|
|
48
52
|
## Typical blocked cases
|
|
49
53
|
|
|
@@ -27,6 +27,8 @@ Verification levels are ordered:
|
|
|
27
27
|
4. `hardware_verified`: the local peripheral works on the exact board revision.
|
|
28
28
|
5. `hil_verified`: retained for legacy facts; schema v2 feature HIL additionally requires matching artifact evidence.
|
|
29
29
|
|
|
30
|
+
Use the same fact across phases without overstating it. `corroborated` means authoritative sources establish an implementable design; `build_verified` means the generated source, locked dependencies, compile, or post-link gates establish that implementation; runtime behavior requires matching artifact evidence. For example, a corroborated single-I2C-family field records a dependency plan, while its build-verified form records the final ELF audit.
|
|
31
|
+
|
|
30
32
|
## Schema v2 contracts
|
|
31
33
|
|
|
32
34
|
The IR contains:
|
|
@@ -46,11 +48,22 @@ The selected video profile controls assessment. An unselected H.264 fallback can
|
|
|
46
48
|
|
|
47
49
|
## Artifact-bound HIL
|
|
48
50
|
|
|
51
|
+
Run the phase gates in order:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
python3 <skill-dir>/scripts/hardware_ir.py assess hardware-ir.json \
|
|
55
|
+
--phase intake --strict
|
|
56
|
+
python3 <skill-dir>/scripts/hardware_ir.py assess hardware-ir.json \
|
|
57
|
+
--phase build --artifact-sha256 <64-character-sha256> --strict
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
The intake phase returns `READY_TO_PORT`; the build phase returns `BUILD_VERIFIED`. Missing serial or browser access does not block either phase.
|
|
61
|
+
|
|
49
62
|
Run:
|
|
50
63
|
|
|
51
64
|
```bash
|
|
52
65
|
python3 <skill-dir>/scripts/hardware_ir.py assess hardware-ir.json \
|
|
53
|
-
--artifact-sha256 <64-character-sha256> --strict
|
|
66
|
+
--phase hil --artifact-sha256 <64-character-sha256> --strict
|
|
54
67
|
```
|
|
55
68
|
|
|
56
69
|
H5 features require matching L5 evidence; AI requires matching L6 evidence. Evidence from an older firmware remains provenance but does not verify the current artifact.
|
|
@@ -36,7 +36,7 @@ For every method, keep device credentials outside source control, handle an alre
|
|
|
36
36
|
|
|
37
37
|
Platform service discovery and the TiRTC SDK service endpoint are different settings. Record both. HTTPS requires valid time, DNS, certificate validation, TLS client/TLS 1.2, and enough contiguous internal memory.
|
|
38
38
|
|
|
39
|
-
Before
|
|
39
|
+
Define a conservative static budget before implementation using the locked SDK contract, framebuffer geometry, DMA/queue bounds, task stacks, and an internal-memory reserve. Before claiming runtime margin or tuning TiRTC buffers, measure internal free/largest blocks, PSRAM, frame size distribution, queue watermarks, and send/drop rates on the exact artifact. A larger queue can prevent drops, exhaust startup memory, or add buffer latency. If an authorized HTTP baseline exists, stage transport changes separately from media changes and retain the HTTPS requirements as a pending acceptance item.
|
|
40
40
|
|
|
41
41
|
## Network and media evidence
|
|
42
42
|
|
|
@@ -16,11 +16,13 @@ Use [the report template](../assets/report-template.md) and preserve separate `P
|
|
|
16
16
|
| L6 AI | Token, WHIP, `start_session`, bidirectional audio, stop, and H5 recovery work |
|
|
17
17
|
| L7 Stability | Requested weak-network, repeated-session, resource, and soak criteria pass |
|
|
18
18
|
|
|
19
|
+
Run and record the intake assessment before L0, the build assessment with the exact artifact SHA-256 at L1, and the HIL assessment only when matching runtime evidence exists. Missing serial or browser access is a `SKIP` for the affected L2-L7 levels, not an L0/L1 failure.
|
|
20
|
+
|
|
19
21
|
## Evidence
|
|
20
22
|
|
|
21
23
|
Record exact board revision, selected media, Wi-Fi, and binding profiles, toolchain and SDK versions, source/adapter revisions, commands, return codes, firmware size and SHA-256, serial port/chip, sanitized log paths, browser or platform observations, and every unavailable dependency.
|
|
22
24
|
|
|
23
|
-
Runtime evidence belongs to one artifact. Label superseded artifacts and keep their observations as history; do not promote them to the current BIN/ELF. For HIL assessment, add the full SHA-256 to `runtime_evidence` and run `hardware_ir.py assess --artifact-sha256 <sha> --strict`.
|
|
25
|
+
Runtime evidence belongs to one artifact. Label superseded artifacts and keep their observations as history; do not promote them to the current BIN/ELF. For HIL assessment, add the full SHA-256 to `runtime_evidence` and run `hardware_ir.py assess --phase hil --artifact-sha256 <sha> --strict`.
|
|
24
26
|
|
|
25
27
|
For L3-L7 media runs, capture the signals that distinguish software regressions from environment changes: onboarding/binding state, BSSID/channel/RSSI, reconnect or roaming events, audio/video send/receive/drop/error counters, queue watermarks, camera overflow count, internal heap/largest block, PSRAM, and measured latency when available. Never record credential values.
|
|
26
28
|
|
|
@@ -23,8 +23,8 @@ Use this branch when the user supplies a board model, vendor URL, schematic, BOM
|
|
|
23
23
|
3. Prefer official schematic/BOM and BSP facts. For a PDF schematic, inspect page labels and net names; prefer an exported netlist, pin CSV, or vendor board definition when available.
|
|
24
24
|
4. Cross-check critical pins, clocks, power enables, reset lines, sensor/codec variants, ESP-IDF version, resource ownership, and onboarding behavior across at least two independent artifacts when possible.
|
|
25
25
|
5. Create the Hardware IR v2. Use `null` for unknown facts and retain contradictory values as an explicit issue instead of selecting one silently. Store concrete board values in the IR/adapter rather than Skill files.
|
|
26
|
-
6. Validate and
|
|
27
|
-
7. When
|
|
26
|
+
6. Validate and run the intake assessment. Classify unresolved facts by their next evidence source: source, implementation, build, HIL, or user input. Ask only for `user_blocked` facts that prevent a safe design. SoftAP is optional when another evidenced Wi-Fi credential method is available, keeps credentials outside source, and defines reprovisioning.
|
|
27
|
+
7. When hardware identity, wiring, product contracts, and an evidenced resource plan reach `READY_TO_PORT`, generate the starter and implement the board adapter. Generate a compile-safe adapter by default when remaining uncertainty is implementation-, build-, or HIL-resolvable. Stop at the IR/report only when missing user evidence or an incompatible dependency makes a safe implementation impossible.
|
|
28
28
|
|
|
29
29
|
The branch is complete when every supplied artifact maps to an IR fact, provenance entry, contradiction, or declared irrelevant item.
|
|
30
30
|
|
|
@@ -68,4 +68,12 @@ The stable modules own stream IDs, negotiated/contracted formats, TiRTC callback
|
|
|
68
68
|
|
|
69
69
|
Use a bounded loop per layer: diagnose one failing invariant, make the smallest correction, and rerun that layer before moving forward. Change one high-risk variable per HIL comparison. Turn reusable invariants into tests or post-link gates. Stop and report when the remaining failure requires unavailable hardware, credentials, a new SDK binary, a public protocol change, or a user choice.
|
|
70
70
|
|
|
71
|
+
Run the assessor once per layer:
|
|
72
|
+
|
|
73
|
+
- `--phase intake`: corroborated design evidence; success is `READY_TO_PORT`.
|
|
74
|
+
- `--phase build --artifact-sha256 <sha>`: compile/post-link evidence; success is `BUILD_VERIFIED`.
|
|
75
|
+
- `--phase hil --artifact-sha256 <sha>`: matching L5/L6 runtime evidence; success is `HIL_VERIFIED`.
|
|
76
|
+
|
|
77
|
+
No serial authorization is required for L0/L1. When serial, browser, account, service, or network access is unavailable, complete the safe build work and report the affected L2-L7 levels as `SKIP`.
|
|
78
|
+
|
|
71
79
|
Do not use successful compilation as evidence for camera frames, speaker output, Web rendering, AI audio, or long-run stability. Bind every runtime conclusion to the tested firmware SHA-256.
|
|
@@ -25,7 +25,12 @@ VERIFICATION_LEVELS = {
|
|
|
25
25
|
"hardware_verified": 4,
|
|
26
26
|
"hil_verified": 5,
|
|
27
27
|
}
|
|
28
|
-
|
|
28
|
+
ASSESSMENT_PHASES = {"intake", "build", "hil"}
|
|
29
|
+
PHASE_SUCCESS_STATUSES = {
|
|
30
|
+
"intake": {"READY_TO_PORT", "HIL_VERIFIED"},
|
|
31
|
+
"build": {"BUILD_VERIFIED", "HIL_VERIFIED"},
|
|
32
|
+
"hil": {"HIL_VERIFIED"},
|
|
33
|
+
}
|
|
29
34
|
VIDEO_CONTRACTS = {
|
|
30
35
|
"mjpeg": "jpeg_complete_frames",
|
|
31
36
|
"h264": "h264_annex_b_access_units",
|
|
@@ -482,7 +487,15 @@ def validate_ir(data: dict[str, Any]) -> list[str]:
|
|
|
482
487
|
return errors
|
|
483
488
|
|
|
484
489
|
|
|
485
|
-
def
|
|
490
|
+
def minimum_verification_for_phase(phase: str) -> str:
|
|
491
|
+
if phase == "intake":
|
|
492
|
+
return "corroborated"
|
|
493
|
+
return "build_verified"
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
def codec_requirement(
|
|
497
|
+
media: dict[str, Any], section: str, minimum_verification: str = "corroborated"
|
|
498
|
+
) -> Requirement:
|
|
486
499
|
present = media.get("present")
|
|
487
500
|
if present is None:
|
|
488
501
|
return "NEEDS_CONFIRMATION", f"{section} presence is unknown", 0
|
|
@@ -496,17 +509,20 @@ def codec_requirement(media: dict[str, Any], section: str) -> Requirement:
|
|
|
496
509
|
continue
|
|
497
510
|
verification = codec.get("verification")
|
|
498
511
|
level = VERIFICATION_LEVELS.get(verification, 0)
|
|
499
|
-
if level < VERIFICATION_LEVELS[
|
|
512
|
+
if level < VERIFICATION_LEVELS[minimum_verification]:
|
|
500
513
|
return (
|
|
501
514
|
"NEEDS_CONFIRMATION",
|
|
502
|
-
f"{section} A-law 8 kHz path is only {verification}"
|
|
515
|
+
f"{section} A-law 8 kHz path is only {verification}; "
|
|
516
|
+
f"{minimum_verification} is required",
|
|
503
517
|
level,
|
|
504
518
|
)
|
|
505
519
|
return "SATISFIED", f"{section} provides A-law 8 kHz", level
|
|
506
520
|
return "BLOCKED", f"{section} has no A-law 8 kHz path", 0
|
|
507
521
|
|
|
508
522
|
|
|
509
|
-
def legacy_video_requirement(
|
|
523
|
+
def legacy_video_requirement(
|
|
524
|
+
camera: dict[str, Any], minimum_verification: str = "corroborated"
|
|
525
|
+
) -> Requirement:
|
|
510
526
|
present = camera.get("present")
|
|
511
527
|
if present is None:
|
|
512
528
|
return "NEEDS_CONFIRMATION", "camera presence is unknown", 0
|
|
@@ -530,10 +546,11 @@ def legacy_video_requirement(camera: dict[str, Any]) -> Requirement:
|
|
|
530
546
|
return "BLOCKED", "key-frame requests cannot reach the encoder", 0
|
|
531
547
|
verification = h264.get("verification")
|
|
532
548
|
level = VERIFICATION_LEVELS.get(verification, 0)
|
|
533
|
-
if level < VERIFICATION_LEVELS[
|
|
549
|
+
if level < VERIFICATION_LEVELS[minimum_verification]:
|
|
534
550
|
return (
|
|
535
551
|
"NEEDS_CONFIRMATION",
|
|
536
|
-
f"H.264 Annex-B path is only {verification}"
|
|
552
|
+
f"H.264 Annex-B path is only {verification}; "
|
|
553
|
+
f"{minimum_verification} is required",
|
|
537
554
|
level,
|
|
538
555
|
)
|
|
539
556
|
return "SATISFIED", "camera provides H.264 Annex-B and IDR control", level
|
|
@@ -548,7 +565,9 @@ def selected_item(items: Any, selected_id: Any) -> dict[str, Any] | None:
|
|
|
548
565
|
return None
|
|
549
566
|
|
|
550
567
|
|
|
551
|
-
def video_requirement_v2(
|
|
568
|
+
def video_requirement_v2(
|
|
569
|
+
camera: dict[str, Any], minimum_verification: str = "corroborated"
|
|
570
|
+
) -> Requirement:
|
|
552
571
|
present = camera.get("present")
|
|
553
572
|
if present is None:
|
|
554
573
|
return "NEEDS_CONFIRMATION", "camera presence is unknown", 0
|
|
@@ -585,10 +604,11 @@ def video_requirement_v2(camera: dict[str, Any]) -> Requirement:
|
|
|
585
604
|
return "BLOCKED", "H5 refresh requests cannot reach the media pipeline", 0
|
|
586
605
|
verification = profile.get("verification")
|
|
587
606
|
level = VERIFICATION_LEVELS.get(verification, 0)
|
|
588
|
-
if level < VERIFICATION_LEVELS[
|
|
607
|
+
if level < VERIFICATION_LEVELS[minimum_verification]:
|
|
589
608
|
return (
|
|
590
609
|
"NEEDS_CONFIRMATION",
|
|
591
|
-
f"selected {codec} path is only {verification}"
|
|
610
|
+
f"selected {codec} path is only {verification}; "
|
|
611
|
+
f"{minimum_verification} is required",
|
|
592
612
|
level,
|
|
593
613
|
)
|
|
594
614
|
return (
|
|
@@ -599,7 +619,10 @@ def video_requirement_v2(camera: dict[str, Any]) -> Requirement:
|
|
|
599
619
|
|
|
600
620
|
|
|
601
621
|
def verified_bool_requirement(
|
|
602
|
-
section: dict[str, Any],
|
|
622
|
+
section: dict[str, Any],
|
|
623
|
+
field: str,
|
|
624
|
+
label: str,
|
|
625
|
+
minimum_verification: str = "corroborated",
|
|
603
626
|
) -> Requirement:
|
|
604
627
|
value = section.get(field)
|
|
605
628
|
if value is None:
|
|
@@ -608,12 +631,18 @@ def verified_bool_requirement(
|
|
|
608
631
|
return "BLOCKED", f"{label} is unresolved", 0
|
|
609
632
|
verification = section.get("verification")
|
|
610
633
|
level = VERIFICATION_LEVELS.get(verification, 0)
|
|
611
|
-
if level < VERIFICATION_LEVELS[
|
|
612
|
-
return
|
|
634
|
+
if level < VERIFICATION_LEVELS[minimum_verification]:
|
|
635
|
+
return (
|
|
636
|
+
"NEEDS_CONFIRMATION",
|
|
637
|
+
f"{label} is only {verification}; {minimum_verification} is required",
|
|
638
|
+
level,
|
|
639
|
+
)
|
|
613
640
|
return "SATISFIED", f"{label} is resolved", level
|
|
614
641
|
|
|
615
642
|
|
|
616
|
-
def i2c_requirement(
|
|
643
|
+
def i2c_requirement(
|
|
644
|
+
resources: dict[str, Any], minimum_verification: str = "corroborated"
|
|
645
|
+
) -> Requirement:
|
|
617
646
|
i2c = resources.get("i2c", {})
|
|
618
647
|
used = i2c.get("used")
|
|
619
648
|
if used is None:
|
|
@@ -626,11 +655,16 @@ def i2c_requirement(resources: dict[str, Any]) -> Requirement:
|
|
|
626
655
|
if family == "none":
|
|
627
656
|
return "BLOCKED", "I2C is used but no driver family is selected", 0
|
|
628
657
|
return verified_bool_requirement(
|
|
629
|
-
i2c,
|
|
658
|
+
i2c,
|
|
659
|
+
"single_driver_family",
|
|
660
|
+
f"single {family} I2C driver family plan",
|
|
661
|
+
minimum_verification,
|
|
630
662
|
)
|
|
631
663
|
|
|
632
664
|
|
|
633
|
-
def i2s_requirement(
|
|
665
|
+
def i2s_requirement(
|
|
666
|
+
resources: dict[str, Any], minimum_verification: str = "corroborated"
|
|
667
|
+
) -> Requirement:
|
|
634
668
|
i2s = resources.get("i2s", {})
|
|
635
669
|
used = i2s.get("used")
|
|
636
670
|
if used is None:
|
|
@@ -640,11 +674,14 @@ def i2s_requirement(resources: dict[str, Any]) -> Requirement:
|
|
|
640
674
|
return verified_bool_requirement(
|
|
641
675
|
i2s,
|
|
642
676
|
"controller_and_gpio_ownership_resolved",
|
|
643
|
-
"I2S controller and GPIO ownership",
|
|
677
|
+
"I2S controller and GPIO ownership plan",
|
|
678
|
+
minimum_verification,
|
|
644
679
|
)
|
|
645
680
|
|
|
646
681
|
|
|
647
|
-
def audio_mapping_requirement(
|
|
682
|
+
def audio_mapping_requirement(
|
|
683
|
+
resources: dict[str, Any], minimum_verification: str = "corroborated"
|
|
684
|
+
) -> Requirement:
|
|
648
685
|
channel = resources.get("audio_channel_mapping", {})
|
|
649
686
|
required = channel.get("required")
|
|
650
687
|
if required is None:
|
|
@@ -652,23 +689,32 @@ def audio_mapping_requirement(resources: dict[str, Any]) -> Requirement:
|
|
|
652
689
|
if required is False:
|
|
653
690
|
return "SATISFIED", "audio channel/TDM mapping is not required", 2
|
|
654
691
|
return verified_bool_requirement(
|
|
655
|
-
channel,
|
|
692
|
+
channel,
|
|
693
|
+
"resolved",
|
|
694
|
+
"audio channel/TDM mapping",
|
|
695
|
+
minimum_verification,
|
|
656
696
|
)
|
|
657
697
|
|
|
658
698
|
|
|
659
|
-
def camera_realtime_requirement(
|
|
699
|
+
def camera_realtime_requirement(
|
|
700
|
+
resources: dict[str, Any], minimum_verification: str = "corroborated"
|
|
701
|
+
) -> Requirement:
|
|
660
702
|
return verified_bool_requirement(
|
|
661
703
|
resources.get("camera_realtime", {}),
|
|
662
704
|
"pipeline_safe",
|
|
663
|
-
"camera DMA/task realtime policy",
|
|
705
|
+
"camera DMA/task realtime policy design",
|
|
706
|
+
minimum_verification,
|
|
664
707
|
)
|
|
665
708
|
|
|
666
709
|
|
|
667
|
-
def memory_requirement(
|
|
710
|
+
def memory_requirement(
|
|
711
|
+
resources: dict[str, Any], minimum_verification: str = "corroborated"
|
|
712
|
+
) -> Requirement:
|
|
668
713
|
return verified_bool_requirement(
|
|
669
714
|
resources.get("memory", {}),
|
|
670
715
|
"startup_and_media_budgeted",
|
|
671
|
-
"startup and media memory budget",
|
|
716
|
+
"static startup and media memory budget",
|
|
717
|
+
minimum_verification,
|
|
672
718
|
)
|
|
673
719
|
|
|
674
720
|
|
|
@@ -750,7 +796,9 @@ def binding_requirement(onboarding: dict[str, Any]) -> Requirement:
|
|
|
750
796
|
|
|
751
797
|
|
|
752
798
|
def combine_requirements(
|
|
753
|
-
requirements: list[Requirement],
|
|
799
|
+
requirements: list[Requirement],
|
|
800
|
+
success_status: str = "READY_TO_PORT",
|
|
801
|
+
legacy_hil_from_levels: bool = False,
|
|
754
802
|
) -> dict[str, Any]:
|
|
755
803
|
reasons = [reason for _, reason, _ in requirements]
|
|
756
804
|
states = {state for state, _, _ in requirements}
|
|
@@ -766,7 +814,7 @@ def combine_requirements(
|
|
|
766
814
|
):
|
|
767
815
|
status = "HIL_VERIFIED"
|
|
768
816
|
else:
|
|
769
|
-
status =
|
|
817
|
+
status = success_status
|
|
770
818
|
return {"status": status, "reasons": reasons}
|
|
771
819
|
|
|
772
820
|
|
|
@@ -839,9 +887,32 @@ def matching_runtime_evidence(
|
|
|
839
887
|
return None
|
|
840
888
|
|
|
841
889
|
|
|
890
|
+
def artifact_requirement(artifact_sha256: str | None) -> Requirement:
|
|
891
|
+
if artifact_sha256 is None:
|
|
892
|
+
return "NEEDS_CONFIRMATION", "exact build artifact SHA-256 is missing", 0
|
|
893
|
+
if not SHA256_RE.fullmatch(artifact_sha256):
|
|
894
|
+
return "BLOCKED", "build artifact SHA-256 is invalid", 0
|
|
895
|
+
return (
|
|
896
|
+
"SATISFIED",
|
|
897
|
+
f"build evidence is bound to artifact {artifact_sha256}",
|
|
898
|
+
VERIFICATION_LEVELS["build_verified"],
|
|
899
|
+
)
|
|
900
|
+
|
|
901
|
+
|
|
842
902
|
def assess_ir(
|
|
843
|
-
data: dict[str, Any],
|
|
903
|
+
data: dict[str, Any],
|
|
904
|
+
artifact_sha256: str | None = None,
|
|
905
|
+
phase: str | None = None,
|
|
844
906
|
) -> dict[str, Any]:
|
|
907
|
+
selected_phase = phase or ("hil" if artifact_sha256 else "intake")
|
|
908
|
+
if selected_phase not in ASSESSMENT_PHASES:
|
|
909
|
+
raise ValueError(
|
|
910
|
+
f"assessment phase must be one of {', '.join(sorted(ASSESSMENT_PHASES))}"
|
|
911
|
+
)
|
|
912
|
+
minimum_verification = minimum_verification_for_phase(selected_phase)
|
|
913
|
+
success_status = (
|
|
914
|
+
"READY_TO_PORT" if selected_phase == "intake" else "BUILD_VERIFIED"
|
|
915
|
+
)
|
|
845
916
|
schema_version = data.get("schema_version")
|
|
846
917
|
requested = data["features"]["requested"]
|
|
847
918
|
audio_input = data["audio_input"]
|
|
@@ -858,7 +929,7 @@ def assess_ir(
|
|
|
858
929
|
onboarding = data["onboarding"]
|
|
859
930
|
project.extend(
|
|
860
931
|
[
|
|
861
|
-
i2c_requirement(resources),
|
|
932
|
+
i2c_requirement(resources, minimum_verification),
|
|
862
933
|
wifi_requirement(onboarding),
|
|
863
934
|
binding_requirement(onboarding),
|
|
864
935
|
]
|
|
@@ -875,73 +946,101 @@ def assess_ir(
|
|
|
875
946
|
else:
|
|
876
947
|
resources = {}
|
|
877
948
|
|
|
949
|
+
if selected_phase in {"build", "hil"}:
|
|
950
|
+
project.append(artifact_requirement(artifact_sha256))
|
|
951
|
+
|
|
878
952
|
for feature in requested:
|
|
879
953
|
if schema_version == 1:
|
|
880
954
|
if feature == "h5_live_audio":
|
|
881
|
-
requirements = [
|
|
955
|
+
requirements = [
|
|
956
|
+
codec_requirement(
|
|
957
|
+
audio_input, "audio_input", minimum_verification
|
|
958
|
+
)
|
|
959
|
+
]
|
|
882
960
|
elif feature == "h5_live_video":
|
|
883
|
-
requirements = [
|
|
961
|
+
requirements = [
|
|
962
|
+
legacy_video_requirement(camera, minimum_verification)
|
|
963
|
+
]
|
|
884
964
|
elif feature == "h5_talkback":
|
|
885
|
-
requirements = [
|
|
965
|
+
requirements = [
|
|
966
|
+
codec_requirement(
|
|
967
|
+
audio_output, "audio_output", minimum_verification
|
|
968
|
+
)
|
|
969
|
+
]
|
|
886
970
|
else:
|
|
887
971
|
requirements = [
|
|
888
|
-
codec_requirement(
|
|
889
|
-
|
|
972
|
+
codec_requirement(
|
|
973
|
+
audio_input, "audio_input", minimum_verification
|
|
974
|
+
),
|
|
975
|
+
codec_requirement(
|
|
976
|
+
audio_output, "audio_output", minimum_verification
|
|
977
|
+
),
|
|
890
978
|
]
|
|
891
979
|
result[feature] = combine_requirements(
|
|
892
|
-
requirements,
|
|
980
|
+
requirements,
|
|
981
|
+
success_status=success_status,
|
|
982
|
+
legacy_hil_from_levels=(selected_phase == "intake"),
|
|
893
983
|
)
|
|
894
984
|
continue
|
|
895
985
|
|
|
896
986
|
if feature == "h5_live_audio":
|
|
897
987
|
requirements = [
|
|
898
|
-
codec_requirement(audio_input, "audio_input"),
|
|
899
|
-
i2s_requirement(resources),
|
|
900
|
-
audio_mapping_requirement(resources),
|
|
901
|
-
memory_requirement(resources),
|
|
988
|
+
codec_requirement(audio_input, "audio_input", minimum_verification),
|
|
989
|
+
i2s_requirement(resources, minimum_verification),
|
|
990
|
+
audio_mapping_requirement(resources, minimum_verification),
|
|
991
|
+
memory_requirement(resources, minimum_verification),
|
|
902
992
|
]
|
|
903
993
|
elif feature == "h5_live_video":
|
|
904
994
|
requirements = [
|
|
905
|
-
video_requirement_v2(camera),
|
|
906
|
-
camera_realtime_requirement(resources),
|
|
907
|
-
memory_requirement(resources),
|
|
995
|
+
video_requirement_v2(camera, minimum_verification),
|
|
996
|
+
camera_realtime_requirement(resources, minimum_verification),
|
|
997
|
+
memory_requirement(resources, minimum_verification),
|
|
908
998
|
]
|
|
909
999
|
elif feature == "h5_talkback":
|
|
910
1000
|
requirements = [
|
|
911
|
-
codec_requirement(audio_output, "audio_output"),
|
|
912
|
-
i2s_requirement(resources),
|
|
913
|
-
memory_requirement(resources),
|
|
1001
|
+
codec_requirement(audio_output, "audio_output", minimum_verification),
|
|
1002
|
+
i2s_requirement(resources, minimum_verification),
|
|
1003
|
+
memory_requirement(resources, minimum_verification),
|
|
914
1004
|
]
|
|
915
1005
|
else:
|
|
916
1006
|
requirements = [
|
|
917
|
-
codec_requirement(audio_input, "audio_input"),
|
|
918
|
-
codec_requirement(audio_output, "audio_output"),
|
|
919
|
-
i2s_requirement(resources),
|
|
920
|
-
audio_mapping_requirement(resources),
|
|
921
|
-
memory_requirement(resources),
|
|
1007
|
+
codec_requirement(audio_input, "audio_input", minimum_verification),
|
|
1008
|
+
codec_requirement(audio_output, "audio_output", minimum_verification),
|
|
1009
|
+
i2s_requirement(resources, minimum_verification),
|
|
1010
|
+
audio_mapping_requirement(resources, minimum_verification),
|
|
1011
|
+
memory_requirement(resources, minimum_verification),
|
|
922
1012
|
]
|
|
923
|
-
result[feature] = combine_requirements(
|
|
1013
|
+
result[feature] = combine_requirements(
|
|
1014
|
+
requirements, success_status=success_status
|
|
1015
|
+
)
|
|
924
1016
|
|
|
925
1017
|
project_gate = combine_requirements(
|
|
926
|
-
project,
|
|
1018
|
+
project,
|
|
1019
|
+
success_status=success_status,
|
|
1020
|
+
legacy_hil_from_levels=(schema_version == 1 and selected_phase == "intake"),
|
|
927
1021
|
)
|
|
928
1022
|
evidence = matching_runtime_evidence(data, artifact_sha256)
|
|
929
|
-
if schema_version == 2 and
|
|
930
|
-
evidence_features = set(evidence.get("features", []))
|
|
931
|
-
evidence_levels =
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
1023
|
+
if schema_version == 2 and selected_phase == "hil":
|
|
1024
|
+
evidence_features = set(evidence.get("features", [])) if evidence else set()
|
|
1025
|
+
evidence_levels = (
|
|
1026
|
+
set(evidence.get("acceptance_levels", [])) if evidence else set()
|
|
1027
|
+
)
|
|
1028
|
+
for feature, feature_assessment in result.items():
|
|
1029
|
+
if feature_assessment["status"] != "BUILD_VERIFIED":
|
|
1030
|
+
continue
|
|
1031
|
+
required_level = FEATURE_HIL_LEVEL[feature]
|
|
1032
|
+
if feature in evidence_features and required_level in evidence_levels:
|
|
1033
|
+
feature_assessment["status"] = "HIL_VERIFIED"
|
|
1034
|
+
feature_assessment["reasons"].append(
|
|
1035
|
+
f"artifact {artifact_sha256} passed {required_level}"
|
|
1036
|
+
)
|
|
1037
|
+
else:
|
|
1038
|
+
feature_assessment["reasons"].append(
|
|
1039
|
+
f"artifact {artifact_sha256} has no matching {required_level} runtime evidence"
|
|
941
1040
|
)
|
|
942
1041
|
if result and all(
|
|
943
1042
|
item["status"] == "HIL_VERIFIED" for item in result.values()
|
|
944
|
-
):
|
|
1043
|
+
) and project_gate["status"] == "BUILD_VERIFIED":
|
|
945
1044
|
project_gate["status"] = "HIL_VERIFIED"
|
|
946
1045
|
project_gate["reasons"].append(
|
|
947
1046
|
f"all requested features have matching artifact evidence for {artifact_sha256}"
|
|
@@ -949,6 +1048,7 @@ def assess_ir(
|
|
|
949
1048
|
|
|
950
1049
|
assessment: dict[str, Any] = {
|
|
951
1050
|
"schema_version": schema_version,
|
|
1051
|
+
"phase": selected_phase,
|
|
952
1052
|
"board_id": data["board"]["id"],
|
|
953
1053
|
"hardware_revision": data["board"]["hardware_revision"],
|
|
954
1054
|
"project_gate": project_gate,
|
|
@@ -1005,12 +1105,26 @@ def command_assess(args: argparse.Namespace) -> int:
|
|
|
1005
1105
|
for error in errors:
|
|
1006
1106
|
print(f"error: {error}", file=sys.stderr)
|
|
1007
1107
|
return 2
|
|
1008
|
-
|
|
1108
|
+
if args.artifact_sha256 is not None and not SHA256_RE.fullmatch(
|
|
1109
|
+
args.artifact_sha256
|
|
1110
|
+
):
|
|
1111
|
+
print("error: --artifact-sha256 must be a 64-character SHA-256", file=sys.stderr)
|
|
1112
|
+
return 2
|
|
1113
|
+
try:
|
|
1114
|
+
assessment = assess_ir(
|
|
1115
|
+
data,
|
|
1116
|
+
artifact_sha256=args.artifact_sha256,
|
|
1117
|
+
phase=args.phase,
|
|
1118
|
+
)
|
|
1119
|
+
except ValueError as exc:
|
|
1120
|
+
print(f"error: {exc}", file=sys.stderr)
|
|
1121
|
+
return 2
|
|
1009
1122
|
print(json.dumps(assessment, ensure_ascii=False, indent=2))
|
|
1010
1123
|
if args.strict:
|
|
1011
1124
|
statuses = {item["status"] for item in assessment["features"].values()}
|
|
1012
1125
|
statuses.add(assessment["project_gate"]["status"])
|
|
1013
|
-
|
|
1126
|
+
allowed_statuses = PHASE_SUCCESS_STATUSES[assessment["phase"]]
|
|
1127
|
+
if not statuses.issubset(allowed_statuses):
|
|
1014
1128
|
return 3
|
|
1015
1129
|
return 0
|
|
1016
1130
|
|
|
@@ -1036,11 +1150,16 @@ def parse_args() -> argparse.Namespace:
|
|
|
1036
1150
|
assess_parser.add_argument(
|
|
1037
1151
|
"--strict",
|
|
1038
1152
|
action="store_true",
|
|
1039
|
-
help="return non-zero unless every requested feature
|
|
1153
|
+
help="return non-zero unless every requested feature passes the selected phase",
|
|
1154
|
+
)
|
|
1155
|
+
assess_parser.add_argument(
|
|
1156
|
+
"--phase",
|
|
1157
|
+
choices=sorted(ASSESSMENT_PHASES),
|
|
1158
|
+
help="assessment phase; defaults to intake, or hil when an artifact SHA is supplied",
|
|
1040
1159
|
)
|
|
1041
1160
|
assess_parser.add_argument(
|
|
1042
1161
|
"--artifact-sha256",
|
|
1043
|
-
help="bind HIL
|
|
1162
|
+
help="bind build or HIL assessment to this exact firmware artifact",
|
|
1044
1163
|
)
|
|
1045
1164
|
assess_parser.set_defaults(handler=command_assess)
|
|
1046
1165
|
return parser.parse_args()
|