tan-cli 0.7.0__py3-none-any.whl

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 (330) hide show
  1. tan/__init__.py +0 -0
  2. tan/__main__.py +120 -0
  3. tan/cli.py +939 -0
  4. tan/command_table.py +265 -0
  5. tan/commands/__init__.py +8 -0
  6. tan/commands/bootstrap_cmd.py +4856 -0
  7. tan/commands/bootstrap_patches.py +164 -0
  8. tan/commands/bootstrap_toolchain_fetch.py +155 -0
  9. tan/commands/bootstrap_toolchain_pin.py +36 -0
  10. tan/commands/build/__init__.py +1 -0
  11. tan/commands/build/configure_inputs.py +277 -0
  12. tan/commands/build/execute.py +1852 -0
  13. tan/commands/build/host_python.py +59 -0
  14. tan/commands/build/link_stale.py +78 -0
  15. tan/commands/build/manifest.py +428 -0
  16. tan/commands/build/materialise.py +81 -0
  17. tan/commands/build/token_substitution.py +381 -0
  18. tan/commands/build/toolchain.py +482 -0
  19. tan/commands/build/user_defines_wipe.py +115 -0
  20. tan/commands/build/workspace_patches.py +65 -0
  21. tan/commands/build_cmd.py +2236 -0
  22. tan/commands/build_output.py +331 -0
  23. tan/commands/clean_cmd.py +1183 -0
  24. tan/commands/completion_cmd.py +786 -0
  25. tan/commands/debug_config_cmd.py +2425 -0
  26. tan/commands/diff_cmd.py +940 -0
  27. tan/commands/doctor_cmd.py +4767 -0
  28. tan/commands/examples_cmd.py +644 -0
  29. tan/commands/explain_cmd.py +1319 -0
  30. tan/commands/faultdecode_cmd.py +931 -0
  31. tan/commands/flash_atoc_guard.py +240 -0
  32. tan/commands/flash_cmd.py +5731 -0
  33. tan/commands/flash_mram_guard.py +150 -0
  34. tan/commands/flash_ram.py +578 -0
  35. tan/commands/flash_raw.py +583 -0
  36. tan/commands/generate_cmd.py +1456 -0
  37. tan/commands/image_cmd.py +753 -0
  38. tan/commands/init_cmd.py +1740 -0
  39. tan/commands/inspect_cmd.py +377 -0
  40. tan/commands/kconfig_cmd.py +734 -0
  41. tan/commands/model_cmd.py +1514 -0
  42. tan/commands/model_device_cmd.py +351 -0
  43. tan/commands/model_device_live.py +234 -0
  44. tan/commands/model_host_cmd.py +430 -0
  45. tan/commands/model_zoo_cmd.py +363 -0
  46. tan/commands/monitor_actions.py +125 -0
  47. tan/commands/monitor_cmd.py +626 -0
  48. tan/commands/monitor_session.py +505 -0
  49. tan/commands/new_som_cmd.py +1522 -0
  50. tan/commands/pinmux_cmd.py +568 -0
  51. tan/commands/presets_cmd.py +1123 -0
  52. tan/commands/probe_cmd.py +421 -0
  53. tan/commands/reset_cmd.py +354 -0
  54. tan/commands/reset_confirm.py +168 -0
  55. tan/commands/run_cmd.py +637 -0
  56. tan/commands/scaffold_cmd.py +490 -0
  57. tan/commands/sdk_cmd.py +1723 -0
  58. tan/commands/size_cmd.py +1020 -0
  59. tan/commands/support_bundle_cmd.py +1073 -0
  60. tan/commands/trace_cmd.py +341 -0
  61. tan/commands/validate_cmd.py +2009 -0
  62. tan/commands/west_forward_cmd.py +736 -0
  63. tan/commands/workspace_patch_check.py +296 -0
  64. tan/core/__init__.py +1 -0
  65. tan/core/alp_conf_pregen.py +100 -0
  66. tan/core/artifact_provenance.py +240 -0
  67. tan/core/atoc_guard.py +276 -0
  68. tan/core/atoc_guard_messages.py +232 -0
  69. tan/core/atoc_replacement.py +118 -0
  70. tan/core/atomic_write.py +178 -0
  71. tan/core/board_context.py +351 -0
  72. tan/core/board_diagnostic.py +111 -0
  73. tan/core/board_files.py +221 -0
  74. tan/core/board_validator.py +542 -0
  75. tan/core/board_validator_compat.py +196 -0
  76. tan/core/board_validator_run.py +129 -0
  77. tan/core/board_validator_schema.py +186 -0
  78. tan/core/board_validator_skew.py +46 -0
  79. tan/core/board_yaml_edit.py +295 -0
  80. tan/core/board_yaml_pos.py +118 -0
  81. tan/core/bootstrap.py +2572 -0
  82. tan/core/build_plan.py +625 -0
  83. tan/core/consent.py +104 -0
  84. tan/core/console_filter.py +245 -0
  85. tan/core/debug_launch.py +1803 -0
  86. tan/core/devicetree_lint.py +168 -0
  87. tan/core/dir_removal.py +229 -0
  88. tan/core/doctor_git.py +172 -0
  89. tan/core/doctor_libraries.py +253 -0
  90. tan/core/doctor_render.py +185 -0
  91. tan/core/doctor_scope.py +66 -0
  92. tan/core/doctor_setools.py +237 -0
  93. tan/core/doctor_stale.py +180 -0
  94. tan/core/document_guards.py +723 -0
  95. tan/core/dp_id.py +97 -0
  96. tan/core/elf_load.py +63 -0
  97. tan/core/error_catalog.py +171 -0
  98. tan/core/example_catalog.py +499 -0
  99. tan/core/example_facets.py +151 -0
  100. tan/core/faultdecode.py +521 -0
  101. tan/core/flash_plan.py +2501 -0
  102. tan/core/flow_d_report.py +381 -0
  103. tan/core/flow_d_uptime.py +164 -0
  104. tan/core/fs_confine.py +130 -0
  105. tan/core/global_flags.py +230 -0
  106. tan/core/host_python.py +210 -0
  107. tan/core/image_bundle.py +247 -0
  108. tan/core/inert.py +147 -0
  109. tan/core/jlink_binary.py +128 -0
  110. tan/core/jlink_probe.py +481 -0
  111. tan/core/jsonc_splice.py +404 -0
  112. tan/core/launch_provenance.py +257 -0
  113. tan/core/link_refusal.py +76 -0
  114. tan/core/metadata_schema.py +376 -0
  115. tan/core/model_check.py +227 -0
  116. tan/core/model_device.py +449 -0
  117. tan/core/model_doctor.py +198 -0
  118. tan/core/model_host.py +48 -0
  119. tan/core/model_list.py +112 -0
  120. tan/core/model_zoo.py +353 -0
  121. tan/core/module_template.py +340 -0
  122. tan/core/mram_link.py +87 -0
  123. tan/core/new_som_ospi.py +94 -0
  124. tan/core/os_class.py +159 -0
  125. tan/core/patch_paths.py +87 -0
  126. tan/core/pending.py +40 -0
  127. tan/core/plain_zephyr_plan.py +138 -0
  128. tan/core/plan_exec.py +373 -0
  129. tan/core/plan_tokens.py +600 -0
  130. tan/core/probe.py +155 -0
  131. tan/core/probe_plan.py +237 -0
  132. tan/core/proxy.py +168 -0
  133. tan/core/publish.py +31 -0
  134. tan/core/python_floor.py +178 -0
  135. tan/core/ram_run.py +694 -0
  136. tan/core/ram_watch.py +231 -0
  137. tan/core/raw_write.py +160 -0
  138. tan/core/reset_plan.py +56 -0
  139. tan/core/run.py +161 -0
  140. tan/core/scaffold.py +2203 -0
  141. tan/core/scaffold_selftest_identity.py +225 -0
  142. tan/core/sdk_default_registry.py +565 -0
  143. tan/core/sdk_discovery.py +996 -0
  144. tan/core/sdk_removal.py +465 -0
  145. tan/core/serial_actions.py +175 -0
  146. tan/core/serial_capture.py +185 -0
  147. tan/core/serial_url.py +45 -0
  148. tan/core/setools.py +608 -0
  149. tan/core/setools_scratch.py +243 -0
  150. tan/core/shapes.py +194 -0
  151. tan/core/size.py +610 -0
  152. tan/core/som_buildability.py +204 -0
  153. tan/core/som_schema_version.py +62 -0
  154. tan/core/subprocess_env.py +201 -0
  155. tan/core/sysbuild_image.py +94 -0
  156. tan/core/system_manifest.py +827 -0
  157. tan/core/text_layout.py +73 -0
  158. tan/core/timestamp.py +182 -0
  159. tan/core/tool_lookup.py +383 -0
  160. tan/core/toolchain_pin.py +117 -0
  161. tan/core/toolchain_provision.py +1224 -0
  162. tan/core/trusted_path.py +134 -0
  163. tan/core/uboot_breakin.py +154 -0
  164. tan/core/uri_reference.py +415 -0
  165. tan/core/user_defines.py +152 -0
  166. tan/core/venv.py +332 -0
  167. tan/core/west_patches.py +143 -0
  168. tan/core/west_workspace_refusal.py +159 -0
  169. tan/core/zephyr_env.py +124 -0
  170. tan/env.py +185 -0
  171. tan/envelope.py +647 -0
  172. tan/exit_codes.py +12 -0
  173. tan/lazy_typer.py +185 -0
  174. tan/model/__init__.py +2 -0
  175. tan/model/_gen_fixture.py +132 -0
  176. tan/model/adapters/__init__.py +116 -0
  177. tan/model/adapters/cpu.py +26 -0
  178. tan/model/adapters/deepx.py +94 -0
  179. tan/model/adapters/drpai.py +242 -0
  180. tan/model/adapters/ethos_u.py +951 -0
  181. tan/model/adapters/executorch.py +55 -0
  182. tan/model/analyze.py +694 -0
  183. tan/model/build.py +304 -0
  184. tan/model/check.py +682 -0
  185. tan/model/manifest.py +625 -0
  186. tan/model/measure.py +216 -0
  187. tan/model/npu_ops_validate.py +130 -0
  188. tan/model/package.py +125 -0
  189. tan/model/perf.py +635 -0
  190. tan/model/perf_apply.py +721 -0
  191. tan/model/prep.py +193 -0
  192. tan/model/sram_fit.py +502 -0
  193. tan/model/targets.py +374 -0
  194. tan/model/tensorio.py +287 -0
  195. tan/net.py +73 -0
  196. tan/output_format.py +213 -0
  197. tan/planner/__init__.py +219 -0
  198. tan/planner/aperture.py +202 -0
  199. tan/planner/buildplan.py +1092 -0
  200. tan/planner/camera_owner.py +192 -0
  201. tan/planner/cameras.py +132 -0
  202. tan/planner/carveout.py +755 -0
  203. tan/planner/cli.py +274 -0
  204. tan/planner/headers.py +315 -0
  205. tan/planner/kconfig.py +2435 -0
  206. tan/planner/kconfig_symbols.py +473 -0
  207. tan/planner/libraries.py +677 -0
  208. tan/planner/link_target.py +237 -0
  209. tan/planner/linux_ownership.py +216 -0
  210. tan/planner/loader.py +1525 -0
  211. tan/planner/manifest.py +116 -0
  212. tan/planner/memory.py +161 -0
  213. tan/planner/memregion.py +56 -0
  214. tan/planner/models.py +533 -0
  215. tan/planner/orchestrator.py +874 -0
  216. tan/planner/ownership.py +203 -0
  217. tan/planner/partition.py +1160 -0
  218. tan/planner/paths.py +32 -0
  219. tan/planner/plain_slice.py +139 -0
  220. tan/planner/project_emit/__init__.py +60 -0
  221. tan/planner/project_emit/bom_netlist.py +375 -0
  222. tan/planner/project_emit/dts.py +580 -0
  223. tan/planner/project_emit/hw_info.py +221 -0
  224. tan/planner/project_emit/native_sim.py +136 -0
  225. tan/planner/project_emit/west_libs.py +213 -0
  226. tan/planner/project_loader.py +264 -0
  227. tan/planner/sdk_capability.py +203 -0
  228. tan/planner/sdk_compat.py +395 -0
  229. tan/planner/secure.py +350 -0
  230. tan/planner/slugs.py +394 -0
  231. tan/planner/som_metadata.py +325 -0
  232. tan/planner/strict_loaders.py +131 -0
  233. tan/planner/template.py +1037 -0
  234. tan/planner/template_pins.py +535 -0
  235. tan/planner/template_rewrite.py +714 -0
  236. tan/planner/topology.py +208 -0
  237. tan/planner/validate.py +365 -0
  238. tan/planner/whole_device_alias.py +67 -0
  239. tan/planner/zephyr_board.py +3510 -0
  240. tan/planner_cli.py +97 -0
  241. tan/planner_emit.py +617 -0
  242. tan/planner_root.py +119 -0
  243. tan/soc_ref.py +58 -0
  244. tan/templates/__init__.py +34 -0
  245. tan/templates/vendored/MANIFEST.md +1862 -0
  246. tan/templates/vendored/diagnostics/E1M-AEN801/CMakeLists.txt +8 -0
  247. tan/templates/vendored/diagnostics/E1M-AEN801/README.md +112 -0
  248. tan/templates/vendored/diagnostics/E1M-AEN801/board.yaml +38 -0
  249. tan/templates/vendored/diagnostics/E1M-AEN801/boards/native_sim_native_64.conf +11 -0
  250. tan/templates/vendored/diagnostics/E1M-AEN801/boards/native_sim_native_64.overlay +29 -0
  251. tan/templates/vendored/diagnostics/E1M-AEN801/prj.conf +5 -0
  252. tan/templates/vendored/diagnostics/E1M-AEN801/src/main.c +266 -0
  253. tan/templates/vendored/diagnostics/E1M-AEN801/testcase.yaml +53 -0
  254. tan/templates/vendored/diagnostics/E1M-V2N101/CMakeLists.txt +8 -0
  255. tan/templates/vendored/diagnostics/E1M-V2N101/README.md +112 -0
  256. tan/templates/vendored/diagnostics/E1M-V2N101/board.yaml +38 -0
  257. tan/templates/vendored/diagnostics/E1M-V2N101/boards/native_sim_native_64.conf +11 -0
  258. tan/templates/vendored/diagnostics/E1M-V2N101/boards/native_sim_native_64.overlay +29 -0
  259. tan/templates/vendored/diagnostics/E1M-V2N101/prj.conf +5 -0
  260. tan/templates/vendored/diagnostics/E1M-V2N101/src/main.c +266 -0
  261. tan/templates/vendored/diagnostics/E1M-V2N101/testcase.yaml +53 -0
  262. tan/templates/vendored/edge-ai/E1M-AEN801/CMakeLists.txt +10 -0
  263. tan/templates/vendored/edge-ai/E1M-AEN801/README.md +75 -0
  264. tan/templates/vendored/edge-ai/E1M-AEN801/board.yaml +38 -0
  265. tan/templates/vendored/edge-ai/E1M-AEN801/prj.conf +8 -0
  266. tan/templates/vendored/edge-ai/E1M-AEN801/src/cold_chain.c +371 -0
  267. tan/templates/vendored/edge-ai/E1M-AEN801/src/cold_chain.h +178 -0
  268. tan/templates/vendored/edge-ai/E1M-AEN801/src/main.c +373 -0
  269. tan/templates/vendored/edge-ai/E1M-AEN801/testcase.yaml +62 -0
  270. tan/templates/vendored/edge-ai/E1M-V2N101/CMakeLists.txt +10 -0
  271. tan/templates/vendored/edge-ai/E1M-V2N101/README.md +75 -0
  272. tan/templates/vendored/edge-ai/E1M-V2N101/board.yaml +37 -0
  273. tan/templates/vendored/edge-ai/E1M-V2N101/prj.conf +8 -0
  274. tan/templates/vendored/edge-ai/E1M-V2N101/src/cold_chain.c +371 -0
  275. tan/templates/vendored/edge-ai/E1M-V2N101/src/cold_chain.h +178 -0
  276. tan/templates/vendored/edge-ai/E1M-V2N101/src/main.c +373 -0
  277. tan/templates/vendored/edge-ai/E1M-V2N101/testcase.yaml +62 -0
  278. tan/templates/vendored/iot/E1M-AEN801/CMakeLists.txt +14 -0
  279. tan/templates/vendored/iot/E1M-AEN801/README.md +103 -0
  280. tan/templates/vendored/iot/E1M-AEN801/board.yaml +69 -0
  281. tan/templates/vendored/iot/E1M-AEN801/prj.conf +15 -0
  282. tan/templates/vendored/iot/E1M-AEN801/src/cc3501e_bridge.c +196 -0
  283. tan/templates/vendored/iot/E1M-AEN801/src/cc3501e_bridge.h +173 -0
  284. tan/templates/vendored/iot/E1M-AEN801/src/main.c +262 -0
  285. tan/templates/vendored/iot/E1M-AEN801/testcase.yaml +68 -0
  286. tan/templates/vendored/minimal/E1M-AEN801/CMakeLists.txt +8 -0
  287. tan/templates/vendored/minimal/E1M-AEN801/README.md +108 -0
  288. tan/templates/vendored/minimal/E1M-AEN801/board.yaml +32 -0
  289. tan/templates/vendored/minimal/E1M-AEN801/prj.conf +8 -0
  290. tan/templates/vendored/minimal/E1M-AEN801/src/main.c +111 -0
  291. tan/templates/vendored/minimal/E1M-AEN801/testcase.yaml +49 -0
  292. tan/templates/vendored/minimal/E1M-V2N101/CMakeLists.txt +8 -0
  293. tan/templates/vendored/minimal/E1M-V2N101/README.md +108 -0
  294. tan/templates/vendored/minimal/E1M-V2N101/board.yaml +32 -0
  295. tan/templates/vendored/minimal/E1M-V2N101/prj.conf +8 -0
  296. tan/templates/vendored/minimal/E1M-V2N101/src/main.c +111 -0
  297. tan/templates/vendored/minimal/E1M-V2N101/testcase.yaml +49 -0
  298. tan/templates/vendored/multicore-mailbox/E1M-AEN801/CMakeLists.txt +8 -0
  299. tan/templates/vendored/multicore-mailbox/E1M-AEN801/README.md +120 -0
  300. tan/templates/vendored/multicore-mailbox/E1M-AEN801/board.yaml +57 -0
  301. tan/templates/vendored/multicore-mailbox/E1M-AEN801/boards/native_sim_native_64.overlay +40 -0
  302. tan/templates/vendored/multicore-mailbox/E1M-AEN801/peer/CMakeLists.txt +13 -0
  303. tan/templates/vendored/multicore-mailbox/E1M-AEN801/peer/main.c +171 -0
  304. tan/templates/vendored/multicore-mailbox/E1M-AEN801/peer/prj.conf +17 -0
  305. tan/templates/vendored/multicore-mailbox/E1M-AEN801/peer/testcase.yaml +23 -0
  306. tan/templates/vendored/multicore-mailbox/E1M-AEN801/prj.conf +22 -0
  307. tan/templates/vendored/multicore-mailbox/E1M-AEN801/src/main.c +185 -0
  308. tan/templates/vendored/multicore-mailbox/E1M-AEN801/testcase.yaml +26 -0
  309. tan/templates/vendored/sensor/E1M-AEN801/CMakeLists.txt +8 -0
  310. tan/templates/vendored/sensor/E1M-AEN801/README.md +112 -0
  311. tan/templates/vendored/sensor/E1M-AEN801/board.yaml +57 -0
  312. tan/templates/vendored/sensor/E1M-AEN801/boards/native_sim_native_64.conf +10 -0
  313. tan/templates/vendored/sensor/E1M-AEN801/boards/native_sim_native_64.overlay +28 -0
  314. tan/templates/vendored/sensor/E1M-AEN801/prj.conf +5 -0
  315. tan/templates/vendored/sensor/E1M-AEN801/src/main.c +186 -0
  316. tan/templates/vendored/sensor/E1M-AEN801/testcase.yaml +52 -0
  317. tan/templates/vendored/sensor/E1M-V2N101/CMakeLists.txt +8 -0
  318. tan/templates/vendored/sensor/E1M-V2N101/README.md +112 -0
  319. tan/templates/vendored/sensor/E1M-V2N101/board.yaml +57 -0
  320. tan/templates/vendored/sensor/E1M-V2N101/boards/native_sim_native_64.conf +10 -0
  321. tan/templates/vendored/sensor/E1M-V2N101/boards/native_sim_native_64.overlay +28 -0
  322. tan/templates/vendored/sensor/E1M-V2N101/prj.conf +5 -0
  323. tan/templates/vendored/sensor/E1M-V2N101/src/main.c +186 -0
  324. tan/templates/vendored/sensor/E1M-V2N101/testcase.yaml +52 -0
  325. tan/version.py +102 -0
  326. tan_cli-0.7.0.dist-info/METADATA +125 -0
  327. tan_cli-0.7.0.dist-info/RECORD +330 -0
  328. tan_cli-0.7.0.dist-info/WHEEL +5 -0
  329. tan_cli-0.7.0.dist-info/entry_points.txt +2 -0
  330. tan_cli-0.7.0.dist-info/top_level.txt +1 -0
tan/planner/models.py ADDED
@@ -0,0 +1,533 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ #
3
+ # Orchestrator data model — the dataclasses the fan-out resolves into and the
4
+ # emitters serialise from. Extracted verbatim from alp_orchestrate.py as the
5
+ # first step of the #285 modularization (one seam, no behaviour change): these
6
+ # are pure data + their own serialisation, with no resolver/emitter logic, so
7
+ # they make a clean leaf module that alp_orchestrate.py (and, later,
8
+ # alp_project.py) re-exports. Public names are unchanged — `from alp_orchestrate
9
+ # import Slice` still works because alp_orchestrate re-exports from here.
10
+ #
11
+ # Relocated into tan-cli (tan/planner/) from alp-sdk's scripts/alp_orchestrate/
12
+ # models.py -- NOT byte-identical to the upstream source; diverges in several
13
+ # places, INCLUDING (not limited to): the import at the bottom of
14
+ # `Slice.to_manifest_entry` is relative (`from .orchestrator import ...`, so
15
+ # the package resolves as `tan.planner`) rather than absolute, and a few
16
+ # exception docstrings (`SdkRevisionUnsupported` and its two siblings) were
17
+ # reworded because the alp-sdk caller they originally named
18
+ # (`scripts/validate_board_yaml.py`) has no tan-cli counterpart. What IS
19
+ # guaranteed byte-identical is the `Slice.to_manifest_entry()` OUTPUT for a
20
+ # given input -- `tests/parity/test_planner_emit_parity.py` diffs
21
+ # `system-manifest` emits against alp-sdk's own front door byte-for-byte; a
22
+ # module-source diff against upstream is not that guarantee and should not be
23
+ # read as one.
24
+ """Dataclasses for the board.yaml orchestrator."""
25
+
26
+ from __future__ import annotations
27
+
28
+ import re
29
+ from dataclasses import dataclass, field
30
+ from pathlib import Path
31
+ from typing import Any, Optional
32
+
33
+
34
+ class OrchestratorError(RuntimeError):
35
+ """Raised when the orchestrator can't resolve / build a project.
36
+
37
+ Carries a human-readable message; the caller (west wrapper / CI)
38
+ prints it and exits non-zero.
39
+ """
40
+
41
+
42
+ class SdkRevisionUnsupported(OrchestratorError):
43
+ """The running SDK is outside a requested hw_rev's declared range.
44
+
45
+ A subclass rather than a flag so a caller COULD map exactly this
46
+ failure to a distinct exit code without string-matching a message. In
47
+ alp-sdk that caller is `scripts/validate_board_yaml.py`, which maps it
48
+ to exit 3 -- but that script does not exist in tan-cli, and no port of
49
+ it is planned; `tan validate`'s own full-validator (spawn) path is
50
+ unported (`tan/commands/validate_cmd.py`). Every command that reaches
51
+ `load_board_yaml` in-process (`build_cmd.py`'s `_emit_plan`,
52
+ `kconfig_cmd.py`'s planner-emit backstop, `generate_cmd.py`'s
53
+ `_emit_one_in_process`) currently catches it only via a broad
54
+ `except Exception`, folding it into the same generic runtime-failure
55
+ issue every other planner error gets -- distinguishable in the message
56
+ text (the exception's class name is included) but not by exit code or
57
+ issue code. So today this is purely a subclass for `except
58
+ OrchestratorError` compatibility (#1019); no tan surface maps it to a
59
+ distinct verdict.
60
+ """
61
+
62
+
63
+ class SdkRevisionUnknown(OrchestratorError):
64
+ """The requested hw_rev is not a key in its resolved `hw_revisions:` table.
65
+
66
+ Distinct from `SdkRevisionUnsupported`: that one names a revision that
67
+ exists but whose declared SDK range excludes the running SDK. This one
68
+ names a revision that doesn't exist at all -- there is no range to
69
+ compare against, so reporting it as out-of-range would name the wrong
70
+ cause. A subclass (not a flag) for the same reason as
71
+ `SdkRevisionUnsupported`, and with the same tan-side caveat: alp-sdk's
72
+ `scripts/validate_board_yaml.py` maps exactly this failure to its own
73
+ exit code, but that script has no tan counterpart, so here the subclass
74
+ buys `except OrchestratorError` compatibility only (alp-sdk #1025, the
75
+ existence-only half).
76
+ """
77
+
78
+
79
+ class SdkRevisionNotBuildable(OrchestratorError):
80
+ """The requested hw_rev exists but its declared `status:` refuses a build.
81
+
82
+ Distinct from `SdkRevisionUnknown`: that one names a revision that
83
+ isn't a key in its table at all. This one names a revision that IS a
84
+ key -- it exists -- but is `status: reserved`, `status: tbd`, or
85
+ carries no `status` key at all (the maintainer's broad-reading decision
86
+ on alp-sdk #1025's status half). In alp-sdk this gets its own exit code
87
+ in `scripts/validate_board_yaml.py`; that script has no tan counterpart
88
+ (see `SdkRevisionUnsupported`'s docstring), so here the subclass buys
89
+ `except OrchestratorError` compatibility only.
90
+ """
91
+
92
+
93
+ _E1M_I2C_BUS_RE = re.compile(r"^e1m(?:_x)?_i2c([0-9]+)$")
94
+
95
+
96
+ def _feature_i2c_bus_id(bus: Any) -> int:
97
+ if not isinstance(bus, str):
98
+ raise OrchestratorError(
99
+ "features.hw_info.eeprom.bus must be an E1M I2C bus slug "
100
+ "such as `e1m_i2c0`.")
101
+ match = _E1M_I2C_BUS_RE.fullmatch(bus)
102
+ if match is None:
103
+ raise OrchestratorError(
104
+ f"features.hw_info.eeprom.bus: {bus!r} is not supported; "
105
+ "use an E1M I2C bus slug such as `e1m_i2c0`.")
106
+ return int(match.group(1), 10)
107
+
108
+
109
+ def _feature_non_negative_int(raw: dict[str, Any], key: str) -> int:
110
+ value = raw.get(key)
111
+ if not isinstance(value, int) or value < 0:
112
+ raise OrchestratorError(
113
+ f"features.hw_info.eeprom.{key} must be a non-negative integer.")
114
+ return value
115
+
116
+
117
+ def _normalise_hw_info_eeprom(raw: dict[str, Any], source: str) -> dict[str, Any]:
118
+ return {
119
+ "source": source,
120
+ "bus": raw.get("bus"),
121
+ "bus_id": _feature_i2c_bus_id(raw.get("bus")),
122
+ "addr_7bit": _feature_non_negative_int(raw, "addr_7bit"),
123
+ "offset": _feature_non_negative_int(raw, "offset"),
124
+ }
125
+
126
+
127
+ # ---------------------------------------------------------------------
128
+ # Dataclasses
129
+ # ---------------------------------------------------------------------
130
+
131
+
132
+ @dataclass
133
+ class Slice:
134
+ """One per-core build slice."""
135
+
136
+ core_id: str
137
+ os: str # zephyr | yocto | baremetal | off
138
+ app: Optional[str] = None
139
+ image: Optional[str] = None # Yocto image recipe name
140
+ # Yocto bitbake recipe that packages this slice's `app:` source dir.
141
+ # Required for an app-only (`app:` set, no `image:`) Yocto slice --
142
+ # `app:` is a filesystem path, never a valid bitbake target itself
143
+ # (issue #597).
144
+ recipe: Optional[str] = None
145
+ machine: Optional[str] = None # Yocto MACHINE
146
+ board: Optional[str] = None # Zephyr board target
147
+ toolchain: Optional[str] = None
148
+ peripherals: list[str] = field(default_factory=list)
149
+ libraries: list[str] = field(default_factory=list)
150
+ # Open-set escape hatch for libraries the SDK doesn't curate. Each
151
+ # entry is a dict with `name:` + (exclusively) `kconfig:` OR
152
+ # `profile:`; loader's _validate_consistency() enforces the
153
+ # exactly-one and uniqueness rules. See docs/board-config-schema.md
154
+ # `extra_libraries:`.
155
+ extra_libraries: list[dict[str, Any]] = field(default_factory=list)
156
+ inference: dict[str, Any] = field(default_factory=dict)
157
+ iot: dict[str, Any] = field(default_factory=dict)
158
+ memory: dict[str, Any] = field(default_factory=dict) # stack_kib, heap_kib, isr_stack_kib
159
+ power: dict[str, Any] = field(default_factory=dict) # sleep_mode, wakeup_sources
160
+ # False when this core has no hardware UART console (headless -- e.g.
161
+ # the RZ/V2N M33 system-manager, whose debug UART the A55 owns). A
162
+ # SoM-topology fact (`topology.<id>.hw_console:` in the SoM preset),
163
+ # NOT customer-overridable; defaults True so every other core keeps
164
+ # its console. Consumed by the console emitter for `diagnostics.
165
+ # sim_console:` (issue #686).
166
+ hw_console: bool = True
167
+ # `diagnostics.link: itcm` (tan-cli#1350): set by the loader on the one
168
+ # slice the knob retargets (the Zephyr M55-HE). Such a slice links at 0x0
169
+ # and must never be flashed to MRAM -- `_slice_flash_recipe` keys off it.
170
+ link_target: Optional[str] = None
171
+ # SEGGER J-Link **part-number flash-device** profile for this slice's
172
+ # SoC variant (soc-spec-v1 `variants[].debug.jlink_flash_device`) --
173
+ # unlocks the built-in Alif MRAM loader (Flow D), distinct from the
174
+ # generic attach/debug `jlink_device` profile. A resolved SoC-variant
175
+ # fact, like `hw_console` above, NOT customer-overridable; None when
176
+ # the variant publishes no such profile -- a published "unknown", not
177
+ # a gap to guess at (see soc-spec-v1.schema.json's `jlink_flash_device`
178
+ # description). Consumed by `_slice_flash_recipe`'s `zephyr` branch to
179
+ # arm the direct-flash path in `flash_args`.
180
+ jlink_flash_device: Optional[str] = None
181
+ # Whether the resolved variant's `debug:` block carried the
182
+ # `jlink_flash_device` KEY AT ALL, independent of what it resolved to
183
+ # (tan-cli#734). `debug.get(...)` collapses a schema-declared
184
+ # `jlink_flash_device: null` -- a deliberate "this variant has no known
185
+ # J-Link flash profile" -- into the same `None` as a genuinely absent
186
+ # key, and the emitter's truthiness test then dropped both. Downstream,
187
+ # `flash_plan.flow_d_available` decides on KEY PRESENCE precisely so a
188
+ # declared-null reaches `plan_alif_mram_jlink`'s loud refusal instead of
189
+ # silently downgrading to Flow A over the SE-UART; that distinction has
190
+ # to survive the loader for it to mean anything.
191
+ jlink_flash_device_declared: bool = False
192
+ # The read-only SW-DP IDR (DPIDR) wrong-board preflight PAIR for this
193
+ # core (soc-spec-v1 `variants[].debug.expect_dpidr` +
194
+ # `variants[].debug.jlink_device[<core_id>]`), resolved together by
195
+ # `loader._resolve_flow_d_preflight` -- both None (preflight not armed,
196
+ # every variant that has not been measured) or both set; never one of
197
+ # the two. `expect_dpidr` is the ID the board's debug port must answer
198
+ # BEFORE any write; `jlink_device` is the LIVE-CORE attach profile that
199
+ # read is performed with -- distinct from `jlink_flash_device` above,
200
+ # which is the part-number flash-algorithm profile. Resolved SoC-variant
201
+ # facts like `jlink_flash_device`, NOT customer-overridable. Consumed by
202
+ # `_slice_flash_recipe`'s `zephyr` branch, which emits them into
203
+ # `flash_args` as an inseparable pair (alp-sdk #1355).
204
+ expect_dpidr: Optional[str] = None
205
+ jlink_device: Optional[str] = None
206
+ # This core's AEN MRAM slot0-XIP load address, `0x`-prefixed hex string
207
+ # (tan-cli#353) -- where Flow D's built-in Alif MRAM loader must write
208
+ # the slot0-linked application blob itself, distinct from
209
+ # `jlink_flash_device` above (which only selects the loader's device
210
+ # PROFILE, not an address). Resolved by
211
+ # `loader._resolve_slot0_load_address` from the SoM preset's
212
+ # `memory_map:` (NOT the SoC JSON -- this is SDK/module build policy,
213
+ # not a silicon fact), so like `jlink_flash_device` it is NOT
214
+ # customer-overridable. None when this core has no AEN slot0-XIP window
215
+ # (every non-AEN slice, and any AEN core whose SoC variant publishes no
216
+ # `jlink_flash_device`) -- a published "unknown", never a value to
217
+ # invent. Consumed by `_slice_flash_recipe`'s `zephyr` branch.
218
+ slot0_load_address: Optional[str] = None
219
+
220
+ # Populated by Orchestrator.fan_out:
221
+ build_dir: Optional[Path] = None
222
+ output_artefact: Optional[str] = None
223
+ status: str = "pending" # pending | ok | failed | skipped
224
+ reason: Optional[str] = None # populated for skipped / failed
225
+ log_path: Optional[Path] = None
226
+ duration_s: float = 0.0
227
+
228
+ def to_manifest_entry(self) -> dict[str, Any]:
229
+ """Project this slice as a dict for system-manifest.yaml.
230
+
231
+ Includes the per-os `flash_method:` + `flash_args:` so
232
+ `west alp-flash` can dispatch each slice without re-deriving
233
+ the backend. The actual backend implementations (driver
234
+ invocations) are the subject of Phase 5 follow-ups; this
235
+ Phase 3 wiring is just the data plumbing.
236
+
237
+ Spec §6.1 byte-stability: the manifest MUST be deterministic
238
+ across rebuilds. `duration_s` is a wall-clock runtime metric
239
+ that varies run-to-run, so it stays on the Slice dataclass
240
+ but never lands in the manifest. Same goes for anything else
241
+ timer / PID-style — keep the manifest content-addressable.
242
+ """
243
+ # Local import: the flash-recipe deriver lives in orchestrator.py; a
244
+ # module-level import here would create a models<->orchestrator cycle.
245
+ # By call time both modules are loaded, so this resolves cleanly.
246
+ # RELOCATED: relative, so the package can be reached as `tan.planner`.
247
+ from .orchestrator import _slice_flash_recipe
248
+
249
+ flash_method, flash_args = _slice_flash_recipe(self)
250
+ entry: dict[str, Any] = {
251
+ "core_id": self.core_id,
252
+ "os": self.os,
253
+ "app": self.app,
254
+ "image": self.image,
255
+ "recipe": self.recipe,
256
+ "machine": self.machine,
257
+ "board": self.board,
258
+ "toolchain": self.toolchain,
259
+ "build_dir": str(self.build_dir) if self.build_dir else None,
260
+ "output_artefact": self.output_artefact,
261
+ "status": self.status,
262
+ "log_path": str(self.log_path) if self.log_path else None,
263
+ "flash_method": flash_method,
264
+ "flash_args": flash_args,
265
+ }
266
+ if self.reason:
267
+ entry["reason"] = self.reason
268
+ # Drop keys with None values to keep the manifest tidy.
269
+ return {k: v for k, v in entry.items() if v is not None}
270
+
271
+
272
+ @dataclass
273
+ class IpcEntry:
274
+ """Raw IPC declaration straight from board.yaml."""
275
+
276
+ name: str
277
+ kind: str
278
+ endpoints: list[str]
279
+ carve_out_kb: int
280
+ cacheable: Optional[bool] = None
281
+ address: Optional[int] = None # explicit base-address override
282
+
283
+
284
+ @dataclass
285
+ class ResolvedCarveOut:
286
+ """An IpcEntry after allocation from the SoM memory_map.
287
+
288
+ `status` is "ok" for a fully resolved carve-out; "blocked" when
289
+ the SoM metadata has TBDs (mailbox controller, memory_map base /
290
+ size, etc.) or the board.yaml entry can't be satisfied. Blocked
291
+ entries land in `system-manifest.yaml` with `status: blocked` +
292
+ `reason: ...` so reviewers see the gap; the actual slice-build
293
+ step (which CI doesn't run) is what fails on a blocked carve-out.
294
+ """
295
+
296
+ name: str
297
+ kind: str
298
+ endpoints: list[str]
299
+ base: int # 0 when blocked
300
+ size: int # in bytes; 0 when blocked
301
+ region: str # source memory-region name; "" when blocked
302
+ cacheable: bool
303
+ src_ept: int # 0 when blocked
304
+ dst_ept: int # 0 when blocked
305
+ mailbox_channel: int # 0 when blocked
306
+ status: str = "ok" # "ok" | "blocked"
307
+ reason: Optional[str] = None # populated when blocked
308
+
309
+ def to_manifest_entry(self) -> dict[str, Any]:
310
+ if self.status == "blocked":
311
+ return {
312
+ "name": self.name,
313
+ "kind": self.kind,
314
+ "endpoints": list(self.endpoints),
315
+ "status": "blocked",
316
+ "reason": self.reason or "",
317
+ }
318
+ return {
319
+ "name": self.name,
320
+ "kind": self.kind,
321
+ "endpoints": list(self.endpoints),
322
+ "carve_out_base": f"0x{self.base:08x}",
323
+ "carve_out_size": f"0x{self.size:08x}",
324
+ "carve_out_region": self.region,
325
+ "cacheable": self.cacheable,
326
+ "rpmsg_endpoint_ids": {
327
+ "src": f"0x{self.src_ept:08x}",
328
+ "dst": f"0x{self.dst_ept:08x}",
329
+ },
330
+ "mailbox_channel": self.mailbox_channel,
331
+ }
332
+
333
+
334
+ @dataclass
335
+ class StorageEntry:
336
+ """Raw storage-partition declaration straight from board.yaml.
337
+
338
+ Mirrors the shape under `storage:` in board.schema.json; the
339
+ orchestrator turns these into ResolvedPartitions in
340
+ `resolve_storage_partitions()`.
341
+ """
342
+
343
+ name: str
344
+ size_kib: int
345
+ fs: str # littlefs | fat | ext4 | raw
346
+ mount: Optional[str] = None
347
+ flash_device: Optional[str] = None
348
+ offset_kib: Optional[int] = None # explicit offset override
349
+
350
+
351
+ @dataclass
352
+ class ResolvedPartition:
353
+ """A StorageEntry after allocation against the SoM flash devices.
354
+
355
+ `status` follows the IPC carve-out convention: "ok" for a fully
356
+ resolved partition; "blocked" when the SoM metadata has TBDs
357
+ (flash device base/size unset) or the entry can't be satisfied
358
+ (unknown flash_device, page-misaligned offset, overlap with a
359
+ sibling partition). Blocked entries land in `system-manifest.yaml`
360
+ with `reason: ...` so reviewers see the gap.
361
+ """
362
+
363
+ name: str
364
+ fs: str
365
+ flash_device: str # original SDK name from board.yaml
366
+ dt_label: str # Zephyr DT label resolved by the loader
367
+ base_kib: int # offset within the flash device, in KiB; 0 when blocked
368
+ size_kib: int
369
+ mount: Optional[str] = None
370
+ status: str = "ok" # "ok" | "blocked"
371
+ reason: Optional[str] = None
372
+
373
+ def to_manifest_entry(self) -> dict[str, Any]:
374
+ if self.status == "blocked":
375
+ return {
376
+ "name": self.name,
377
+ "fs": self.fs,
378
+ "flash_device": self.flash_device,
379
+ "status": "blocked",
380
+ "reason": self.reason or "",
381
+ }
382
+ entry: dict[str, Any] = {
383
+ "name": self.name,
384
+ "fs": self.fs,
385
+ "flash_device": self.flash_device,
386
+ "dt_label": self.dt_label,
387
+ "offset_kib": self.base_kib,
388
+ "size_kib": self.size_kib,
389
+ }
390
+ if self.mount:
391
+ entry["mount"] = self.mount
392
+ return entry
393
+
394
+
395
+ @dataclass
396
+ class BoardProject:
397
+ """Resolved board.yaml project ready for fan-out."""
398
+
399
+ sku: str
400
+ hw_rev: Optional[str]
401
+ board_name: Optional[str]
402
+ board_hw_rev: Optional[str]
403
+ cores: dict[str, Slice] # effective per-core slices
404
+ ipc: list[IpcEntry]
405
+ soc_spec: dict[str, Any]
406
+ som_preset: dict[str, Any]
407
+ board_preset: Optional[dict[str, Any]]
408
+ diagnostics: dict[str, Any] = field(default_factory=dict)
409
+ chips: list[str] = field(default_factory=list)
410
+ # Project-wide curated third-party libraries (ADR 0018). Each name
411
+ # resolves to metadata/libraries/<name>.yaml; the emitters wire its
412
+ # per-OS integration into every slice whose OS the manifest supports.
413
+ libraries: list[str] = field(default_factory=list)
414
+ features: dict[str, Any] = field(default_factory=dict)
415
+ boot: dict[str, Any] = field(default_factory=dict)
416
+ ota: dict[str, Any] = field(default_factory=dict)
417
+ storage: list[StorageEntry] = field(default_factory=list)
418
+ security: dict[str, Any] = field(default_factory=dict)
419
+ # Resolved per-product core ownership ({E1M instance: core}); empty when
420
+ # the SoM family declares no `assignable:` block (ownership.resolve_ownership).
421
+ ownership: dict[str, str] = field(default_factory=dict)
422
+ raw: dict[str, Any] = field(default_factory=dict)
423
+ # The metadata tree this project was RESOLVED against (tan-cli#573).
424
+ # `load_board_yaml(..., metadata_root=...)` records its caller's root
425
+ # here so the downstream resolvers -- `resolve_storage_partitions`,
426
+ # `resolve_carve_outs`, the Kconfig emitters -- read the same tree the
427
+ # loader validated against instead of the module-level bound
428
+ # `paths.METADATA_ROOT`. Without it the loader and the resolvers
429
+ # disagree on an alternate tree: the loader accepts a
430
+ # `storage[].flash_device` the resolver then blocks.
431
+ # `None` means "the bound root" -- see `effective_metadata_root()`.
432
+ metadata_root: Optional[Path] = None
433
+ # Directory of the board.yaml this project was loaded from; anchors a
434
+ # slice's relative `app:` so the emitters can read the app's own
435
+ # `prj.conf` (e.g. to avoid shrinking an app-set RAM console size,
436
+ # tan-cli#1401). `None` for a project built without a file.
437
+ source_dir: Optional[Path] = None
438
+
439
+ def effective_metadata_root(self) -> Path:
440
+ """The metadata tree to read SoM/SoC facts from for THIS project.
441
+
442
+ `paths.METADATA_ROOT` is imported lazily, not at module scope:
443
+ `paths` evaluates `REPO = sdk_root()` on import and raises when no
444
+ SDK root is bound, and `models` is deliberately a bound-root-free
445
+ leaf (`tests/core/test_sdk_revision_gate.py` loads `sdk_compat`
446
+ -- and through it `models` -- with no SDK at all).
447
+ """
448
+ if self.metadata_root is not None:
449
+ return self.metadata_root
450
+ from .paths import METADATA_ROOT
451
+ return METADATA_ROOT
452
+
453
+ def hw_info_eeprom_feature(self) -> Optional[dict[str, Any]]:
454
+ """Return the explicit ``features.hw_info.eeprom`` projection."""
455
+ hw_info = self.features.get("hw_info")
456
+ if not isinstance(hw_info, dict):
457
+ return None
458
+ eeprom = hw_info.get("eeprom")
459
+ if eeprom is None:
460
+ return None
461
+ if not isinstance(eeprom, dict):
462
+ raise OrchestratorError(
463
+ "features.hw_info.eeprom must be a mapping.")
464
+ return _normalise_hw_info_eeprom(eeprom, "features.hw_info.eeprom")
465
+
466
+ def hw_info_eeprom_config(self) -> Optional[dict[str, Any]]:
467
+ """Return the effective EEPROM reader config for Kconfig emit."""
468
+ explicit = self.hw_info_eeprom_feature()
469
+ if explicit is not None:
470
+ return explicit
471
+ if (self.som_preset.get("on_module") or {}).get("eeprom"):
472
+ return {
473
+ "source": "som.on_module.eeprom",
474
+ "bus": "e1m_i2c0",
475
+ "bus_id": 0,
476
+ "addr_7bit": 0x50,
477
+ "offset": 0,
478
+ }
479
+ return None
480
+
481
+
482
+ @dataclass
483
+ class SystemManifest:
484
+ """The artefact written to build/system-manifest.yaml."""
485
+
486
+ project: BoardProject
487
+ slices: list[Slice]
488
+ carve_outs: list[ResolvedCarveOut]
489
+ partitions: list[ResolvedPartition] = field(default_factory=list)
490
+ boot_order: list[dict[str, Any]] = field(default_factory=list)
491
+ helper_mcus: list[dict[str, Any]] = field(default_factory=list)
492
+ memory_regions: list[dict[str, Any]] = field(default_factory=list)
493
+
494
+ def to_dict(self) -> dict[str, Any]:
495
+ hw_info: dict[str, Any] = {
496
+ "sku": self.project.sku,
497
+ "som_hw_rev": self.project.hw_rev,
498
+ "board_name": self.project.board_name,
499
+ "board_hw_rev": self.project.board_hw_rev,
500
+ "silicon": self.project.som_preset.get("silicon"),
501
+ }
502
+ eeprom = self.project.hw_info_eeprom_feature()
503
+ if eeprom is not None:
504
+ hw_info["eeprom"] = {
505
+ "bus": eeprom["bus"],
506
+ "bus_id": eeprom["bus_id"],
507
+ "addr_7bit": eeprom["addr_7bit"],
508
+ "offset": eeprom["offset"],
509
+ }
510
+
511
+ out: dict[str, Any] = {
512
+ "schema_version": 1,
513
+ "generated_by": "scripts/alp_orchestrate.py",
514
+ "hw_info": hw_info,
515
+ "slices": [s.to_manifest_entry() for s in self.slices],
516
+ "ipc": [c.to_manifest_entry() for c in self.carve_outs],
517
+ "helper_mcus": list(self.helper_mcus),
518
+ "boot_order": list(self.boot_order),
519
+ }
520
+ if self.partitions:
521
+ out["storage"] = [p.to_manifest_entry() for p in self.partitions]
522
+ # `memory` is OMITTED, never emitted empty (#1365 item 3): the schema
523
+ # makes an absent pane mean "this producer does not emit it yet",
524
+ # so `memory: []` would tell a consumer a SoM whose silicon_variant
525
+ # is still `TBD` has no memory regions. Rows arrive already shaped
526
+ # by memory.resolve_memory_regions() -- unlike ipc/storage there is
527
+ # no per-row dataclass, matching the helper_mcus precedent.
528
+ if self.memory_regions:
529
+ out["memory"] = list(self.memory_regions)
530
+ # Additive, omitted when the SoM declares no assignable resources.
531
+ if self.project.ownership:
532
+ out["ownership"] = dict(self.project.ownership)
533
+ return out