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/net.py ADDED
@@ -0,0 +1,73 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """The one TLS trust context every network call in `tan` uses (tan-cli#304).
3
+
4
+ A PyInstaller `--onefile` freeze bundles its own Python and its own `ssl`, but
5
+ NOT a CA bundle, and `ssl.create_default_context()`'s `set_default_verify_paths`
6
+ falls back to whatever cert path OpenSSL was compiled with -- a path on the
7
+ BUILD machine, not the one the frozen binary later runs on. The result is a
8
+ frozen `tan` with zero trust anchors: every `urllib` HTTPS call fails
9
+ `CERTIFICATE_VERIFY_FAILED`, even though the same host's `curl`/browser/system
10
+ `python3` all verify the same endpoint fine (#304's single-variable proof:
11
+ `SSL_CERT_FILE=/etc/ssl/cert.pem` alone fixed a bare invocation).
12
+
13
+ Two mechanisms, layered:
14
+
15
+ * `truststore` (preferred) -- `SSLContext` that verifies through the OS's own
16
+ trust store (Security.framework / CryptoAPI / OpenSSL-with-system-paths), so
17
+ a genuine corporate CA installed in the machine's keychain keeps working.
18
+ `certifi` alone cannot do that: it is a fixed, hermetic public-CA list, so a
19
+ host behind a real TLS-intercepting proxy would trade one failure for
20
+ another under `certifi`-only.
21
+ * `certifi`'s bundled `cacert.pem` (the floor) -- used only if `truststore`
22
+ itself is unavailable or fails to construct a context, so a run never has
23
+ zero trust anchors just because the preferred mechanism came up short.
24
+ Hermetic: it verifies public CAs even on a host with no usable OS store.
25
+
26
+ Mirrors `crates/tan-cli/src/http.rs`'s "merge, never narrow" intent (widen the
27
+ webpki roots with the OS store rather than replacing them) with the tool the
28
+ Python side actually has: there is no single Python API that merges a bundled
29
+ list with the OS store in one context, so this falls back instead of merging.
30
+ """
31
+ from __future__ import annotations
32
+
33
+ import ssl
34
+
35
+
36
+ def default_ssl_context() -> ssl.SSLContext:
37
+ """An `ssl.SSLContext` that actually has trust anchors, in a frozen build
38
+ or not. Pass as `urllib.request.urlopen(..., context=default_ssl_context())`.
39
+ """
40
+ import certifi
41
+
42
+ try:
43
+ import truststore
44
+
45
+ context = truststore.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
46
+ # tan-cli#354: the floor has to be UNDER truststore, not an
47
+ # ALTERNATIVE to it. `truststore.SSLContext(...)` constructs perfectly
48
+ # well on a host with an EMPTY OS trust store -- it defers to the
49
+ # platform verifier, and that verifier simply has no anchors. The
50
+ # failure then happens at VERIFY time, inside `urlopen`, which no
51
+ # `except` around construction can ever observe. So on any minimal
52
+ # container (`ubuntu:24.04`, `debian:*-slim`, and most CI base images
53
+ # ship no `ca-certificates`) every HTTPS call died
54
+ # `CERTIFICATE_VERIFY_FAILED` while tan's own `certifi` sat unused
55
+ # inside the freeze. Measured in a pristine `ubuntu:24.04`, and
56
+ # reproduced identically on the published `v0.5.0-rc4` asset -- so it
57
+ # predates the `--onedir` change and had shipped in every RC.
58
+ #
59
+ # Loading certifi into the SAME context WIDENS the anchor set instead
60
+ # of replacing it: a populated OS store -- the corporate-CA case #304
61
+ # chose truststore for -- keeps working, and a host with no OS store
62
+ # can still verify public CAs. That is the "merge, never narrow"
63
+ # intent this module's docstring takes from
64
+ # `crates/tan-cli/src/http.rs`, which the original fall-back shape
65
+ # could not actually express.
66
+ context.load_verify_locations(cafile=certifi.where())
67
+ return context
68
+ except Exception:
69
+ # `ImportError` if truststore is absent; anything else is truststore
70
+ # failing outright (an unsupported OS, a store it cannot open) or
71
+ # refusing the extra anchors. Either way certifi alone is a trust
72
+ # anchor set that does not depend on the platform at all.
73
+ return ssl.create_default_context(cafile=certifi.where())
tan/output_format.py ADDED
@@ -0,0 +1,213 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """The one declaration of what `--format` accepts (tan-cli#403).
3
+
4
+ Before this module the answer was written out 31 times. Every command declared
5
+ `output_format: str` with `metavar="FORMAT"` and a prose help sentence, then
6
+ re-validated the value in its own body:
7
+
8
+ if output_format not in ("text", "json"):
9
+ raise typer.BadParameter(
10
+ f"'{output_format}' (choose from 'text', 'json')", param_hint="--format"
11
+ )
12
+
13
+ 29 byte-identical copies of that, a 30th over a wider domain in
14
+ `validate_cmd.py`, a 31st spelled differently in `cli.py`'s root callback
15
+ (`ctx.fail(f"'{value}' is not one of 'text', 'json'")`), and a 32nd, 33rd and
16
+ 34th hardcoded into the bash/zsh/fish scripts `completion_cmd.py` emits. The
17
+ copies had already drifted: `validate` really accepts `diagnostic-v1` and
18
+ `sarif` -- the two documents an IDE integration wants -- while the root
19
+ callback, the help prose and all three completion scripts named only `text`
20
+ and `json`. So `tan --format diagnostic-v1 validate --offline` (the
21
+ pre-subcommand position `alp-sdk-vscode`'s `withSdkRoot` actually uses) was a
22
+ usage error whose message taught that `diagnostic-v1` does not exist, and
23
+ `tan validate --format <TAB>` said the same wrong thing.
24
+
25
+ The oracle never had this problem: clap derives the parser, the `--help`
26
+ choice list and the completions from ONE `ValueEnum`, which is why the frozen
27
+ oracle help in `tests/fixtures/oracle_captures/test_run_oracle_parity.json`
28
+ still prints a `Possible values:` block this port had lost.
29
+
30
+ So: one enum per domain, declared here, annotated on every `--format`
31
+ parameter. Click derives the parse-time value list, the rejection message and
32
+ the `--help` metavar from the annotation, `completion_cmd.py` splices the same
33
+ values into the scripts it emits, and
34
+ `tests/commands/test_output_format.py` asserts all three surfaces agree per
35
+ command. There is no body-level check left to drift.
36
+
37
+ **Why `str` mixin and not `enum.StrEnum`.** A member has to compare equal to
38
+ its own wire value: hundreds of call sites downstream of the parameter say
39
+ `output_format == "json"`, and every one of them keeps working with a `str`
40
+ subclass. `StrEnum` would do that too, but `(str, Enum)` with an explicit
41
+ `__str__` is what also makes `f"{fmt}"` render `json` rather than
42
+ `OutputFormat.JSON` on every Python this project supports -- and an enum
43
+ member that stringifies to its Python name is exactly how a wrong value
44
+ reaches a subprocess argv or a JSON payload unnoticed.
45
+ """
46
+
47
+ from __future__ import annotations
48
+
49
+ from collections.abc import Mapping, Sequence
50
+ from enum import Enum
51
+ from typing import TypeVar
52
+
53
+ import typer
54
+
55
+
56
+ class OutputFormat(str, Enum):
57
+ """What `--format` accepts on every command but `validate`.
58
+
59
+ Verbatim from the oracle's own `ValueEnum` (`crates/tan-cli/src/cli.rs`),
60
+ whose `--help` rendering the frozen capture in
61
+ `tests/fixtures/oracle_captures/test_run_oracle_parity.json` shows:
62
+ `text: Human-readable text (default)` / `json: Machine-readable JSON
63
+ envelope`.
64
+ """
65
+
66
+ TEXT = "text"
67
+ JSON = "json"
68
+
69
+ def __str__(self) -> str:
70
+ return self.value
71
+
72
+
73
+ class ValidateOutputFormat(str, Enum):
74
+ """`validate`'s wider domain -- the same two, plus two IDE-oriented
75
+ documents no other command emits.
76
+
77
+ `diagnostic-v1` is alp-sdk's `metadata/schemas/diagnostic-v1.schema.json`
78
+ (mirroring `scripts/alp_cli/diagnostic_format.py:to_machine_json`'s shape
79
+ -- `schemaVersion`/`tool`/`diagnostics[]`, zero-based LSP ranges), `sarif`
80
+ is its SARIF 2.1.0 sibling (`to_sarif`). Both are additions this port
81
+ makes to the v0.4.1 oracle's two-value surface, not parity gaps: `text`
82
+ and `json` are unchanged by them, so only a caller who asks for one of the
83
+ new two sees anything new.
84
+
85
+ A SEPARATE enum rather than a wider shared one: every other command emits
86
+ an envelope and nothing else, and an accepted-but-unimplemented
87
+ `tan build --format sarif` would exit 0 having written the wrong document
88
+ -- or none.
89
+ """
90
+
91
+ TEXT = "text"
92
+ JSON = "json"
93
+ DIAGNOSTIC_V1 = "diagnostic-v1"
94
+ SARIF = "sarif"
95
+
96
+ def __str__(self) -> str:
97
+ return self.value
98
+
99
+
100
+ #: The one `--format` help string. Click appends the choice list itself (it
101
+ #: becomes the option's metavar), so naming the values in prose here would put
102
+ #: the port straight back to hand-maintaining a second copy that can disagree
103
+ #: with the parser -- which is what `Output format: text or json.` on a
104
+ #: command accepting four values already was. Wording is the oracle's own
105
+ #: (`Output format`), plus this port's trailing full stop.
106
+ FORMAT_HELP = "Output format."
107
+
108
+ #: Commands whose `--format` declares `ValidateOutputFormat`. Consumed by
109
+ #: `completion_cmd.py`, which emits text and so cannot read the live Click
110
+ #: tree the way `cli.py` can (importing `tan.cli` from a command module is a
111
+ #: cycle). `tests/commands/test_output_format.py` pins this tuple to what the
112
+ #: tree really declares, so it cannot silently go stale.
113
+ WIDE_FORMAT_COMMANDS: tuple[str, ...] = ("validate",)
114
+
115
+ _FormatT = TypeVar("_FormatT", bound=Enum)
116
+
117
+
118
+ def format_values(choices: type[Enum]) -> tuple[str, ...]:
119
+ """The wire values of one `--format` enum, in declaration order.
120
+
121
+ Declaration order is deliberate: it is the order `--help` and the shell
122
+ completions present, and `text` is first because it is the default.
123
+ """
124
+ return tuple(str(member.value) for member in choices)
125
+
126
+
127
+ def invalid_format_message(value: str, choices: Sequence[str]) -> str:
128
+ """Click's OWN wording for a rejected choice, reproduced verbatim.
129
+
130
+ `click.types.Choice.get_invalid_choice_message` builds
131
+ `"{value!r} is not one of {choices}."` from `", ".join(map(repr, ...))`.
132
+ Anything that refuses a `--format` value OUTSIDE Click's parser -- i.e.
133
+ `resolve_format` below, for a value inherited across the subcommand
134
+ boundary, and `cli.py`'s root callback, which cannot use a fixed enum at
135
+ all because its domain is whatever the INVOKED subcommand declares -- has
136
+ to say it the same way, or the identical mistake gets two different
137
+ messages depending on where the caller put the flag, which is the defect
138
+ tan-cli#403 was filed about.
139
+
140
+ Takes plain strings, not an enum type, precisely so the root callback can
141
+ pass `declared_formats(...)` straight through.
142
+ """
143
+ quoted = ", ".join(map(repr, choices))
144
+ return f"{value!r} is not one of {quoted}."
145
+
146
+
147
+ def resolve_format(
148
+ # `str`, not the enum type: Click hands the body a member (which IS a str
149
+ # by mixin), while a test calling the command function directly passes the
150
+ # wire value. Both are accepted, and both come back as a member.
151
+ local: str | None,
152
+ ctx_obj: Mapping[str, object] | None,
153
+ *,
154
+ choices: type[_FormatT],
155
+ ) -> _FormatT:
156
+ """`--format` for a command that honours the PRE-subcommand position.
157
+
158
+ Precedence, unchanged from the eleven hand-written copies this replaces:
159
+ the command's own `--format` (declared after the subcommand name) wins;
160
+ otherwise the root callback's value off `ctx.obj` (`cli.py` records what
161
+ preceded the subcommand, since Click hands a group only what comes before
162
+ it); otherwise `text`.
163
+
164
+ `local is not None` rather than a truthiness test: the local value arrives
165
+ already parsed by Click, and the only falsey member an enum could ever
166
+ have would still be an explicit caller choice, not an absent flag.
167
+
168
+ The inherited value is re-validated because it did NOT come through this
169
+ command's parser -- it came through the root callback's, whose domain need
170
+ not be this command's. Passing an out-of-domain value straight through
171
+ would hand the body a format it has no branch for, and the caller would
172
+ get a silent text-mode run with an empty stdout instead of an error.
173
+ """
174
+ if local is not None:
175
+ return choices(local)
176
+ inherited = (ctx_obj or {}).get("format")
177
+ if not inherited:
178
+ return choices("text")
179
+ try:
180
+ return choices(inherited)
181
+ except ValueError:
182
+ raise typer.BadParameter(
183
+ invalid_format_message(str(inherited), format_values(choices)),
184
+ # Quoted INSIDE the string on purpose: Click's own hint comes from
185
+ # `Parameter.get_error_hint`, which is `repr` of the option string,
186
+ # so this renders `Invalid value for '--format': ...` -- byte-identical
187
+ # to what the parser itself would have printed for the same value.
188
+ param_hint="'--format'",
189
+ ) from None
190
+
191
+
192
+ def declared_formats(command: object) -> tuple[str, ...]:
193
+ """The values one live Click command's `--format` accepts, read off the
194
+ command tree rather than any second list.
195
+
196
+ This is what lets `cli.py`'s root callback accept whatever the INVOKED
197
+ subcommand declares instead of a frozen pair -- `tan --format
198
+ diagnostic-v1 validate --offline` has to produce the same document as
199
+ `tan validate --offline --format diagnostic-v1`.
200
+
201
+ Duck-typed on `.opts`/`.type.choices`, never `isinstance`: Typer builds
202
+ its commands out of its own VENDORED click (`typer._click`), so an
203
+ `isinstance(param, click.Option)` test against the top-level `click`
204
+ package matches nothing -- the trap `cli._tokens_consumed_after` already
205
+ documents. An empty result means the command declares no `--format` at
206
+ all, which a caller must treat as "refuse", never as "anything goes".
207
+ """
208
+ for param in getattr(command, "params", ()):
209
+ if "--format" not in getattr(param, "opts", ()):
210
+ continue
211
+ found: Sequence[str] = getattr(param.type, "choices", ()) or ()
212
+ return tuple(str(choice) for choice in found)
213
+ return ()
@@ -0,0 +1,219 @@
1
+ #!/usr/bin/env python3
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ """
4
+ ALP heterogeneous-OS build orchestrator (Phase 2 of the 2026-05-15 design).
5
+
6
+ A board.yaml declares per-core runtimes; this module loads the
7
+ project, resolves topology defaults from the SoM preset, allocates
8
+ IPC carve-outs from the SoM's memory_map, and fans out into one
9
+ build slice per non-`off` core.
10
+
11
+ Public API:
12
+
13
+ load_board_yaml(path) -> BoardProject
14
+ resolve_carve_outs(project) -> list[ResolvedCarveOut]
15
+ emit_system_manifest(project, slices=...) -> str
16
+ emit_dts_reservations(project) -> str
17
+ emit_ipc_contract_h(project) -> str
18
+ emit_build_plan(project, board_yaml=..., build_root=...) -> str
19
+ iter_buildable_slices(project) -> Iterator[Slice]
20
+
21
+ BoardProject, Slice, ResolvedCarveOut, SystemManifest, OrchestratorError,
22
+ SdkRevisionUnsupported, SdkRevisionUnknown, SdkRevisionNotBuildable
23
+
24
+ ADR-0020 Phase 4 (preview) retired the SDK-side executor (`Orchestrator`/
25
+ `fan_out`) -- this module is planner/emit-only; execution is an external
26
+ consumer's job.
27
+
28
+ Reference: docs/superpowers/specs/2026-05-15-heterogeneous-os-orchestration-design.md
29
+ """
30
+
31
+ from __future__ import annotations
32
+
33
+ # The filesystem roots now live in paths.py (the #285 paths seam) -- a leaf both
34
+ # __init__ and topology.py import, so topology no longer lazy-imports BOARD_SCHEMA
35
+ # back through the package. Re-exported here so `from alp_orchestrate import REPO`
36
+ # (and friends) keeps working unchanged.
37
+ from .paths import ( # noqa: E402
38
+ BOARD_PRESET_SCHEMA, # noqa: F401 (re-export for the public surface)
39
+ BOARD_SCHEMA, # noqa: F401 (re-export for the public surface)
40
+ METADATA_ROOT, # noqa: F401 (re-export for the public surface)
41
+ REPO, # noqa: F401 (re-export for the public surface)
42
+ )
43
+
44
+
45
+ # The per-core OS-class taxonomy + topology view now live in topology.py (the
46
+ # #285 topology seam). Re-export the names referenced outside the module:
47
+ # `_default_os_from_core_type` (test_silicon_variant_and_os_inference) + the public
48
+ # emit surface `core_os_topology` / `emit_os_topology` (alp_project.py + the
49
+ # test_emit_os_topology tests). The
50
+ # remaining helpers (CLASS_RUNTIMES, _allowed_os_for_core, _runtime_class) stay
51
+ # topology-private -- nothing outside topology.py references them.
52
+ from .topology import ( # noqa: E402
53
+ _default_os_from_core_type, # noqa: F401 (re-export: test_silicon_variant_and_os_inference)
54
+ core_os_topology, # noqa: F401 (re-export: test_emit_os_topology)
55
+ emit_os_topology, # noqa: F401 (re-export: alp_project.py + tests)
56
+ )
57
+
58
+ # The orchestrator data model now lives in alp_orchestrate_models (the first
59
+ # #285 modularization seam); re-exported here so `from alp_orchestrate import
60
+ # Slice` (and friends) keeps working unchanged for callers + tests.
61
+ from .models import ( # noqa: E402
62
+ BoardProject, # noqa: F401 (re-export: public model surface)
63
+ IpcEntry, # noqa: F401 (re-export: public model surface; consumed by loader.py)
64
+ OrchestratorError, # noqa: F401 (re-export: alp_project's lazy imports + tests)
65
+ ResolvedCarveOut, # noqa: F401 (re-export; consumed by carveout.py now, not __init__)
66
+ ResolvedPartition, # noqa: F401 (re-export; consumed by partition.py + headers.py now)
67
+ SdkRevisionNotBuildable, # noqa: F401 (re-export; alp-sdk maps it to its own exit code)
68
+ SdkRevisionUnknown, # noqa: F401 (re-export: validate_board_yaml maps it to its own exit code)
69
+ SdkRevisionUnsupported, # noqa: F401 (re-export: validate_board_yaml maps it to exit 3)
70
+ Slice, # noqa: F401 (re-export: public model surface)
71
+ StorageEntry, # noqa: F401 (re-export: public model surface; consumed by loader.py)
72
+ SystemManifest, # noqa: F401 (re-export: public model surface)
73
+ )
74
+
75
+
76
+ # ---------------------------------------------------------------------
77
+ # Loader
78
+ # ---------------------------------------------------------------------
79
+ # The board.yaml loader now lives in loader.py (the #285 loader seam).
80
+ # Re-export load_board_yaml -- the public parse entry (cli, west alp-build via
81
+ # the module form, alp_project's --emit dispatch, the tests + fuzzer).
82
+ from .loader import load_board_yaml # noqa: E402,F401 (re-export: cli + west + alp_project + tests)
83
+
84
+
85
+ # ---------------------------------------------------------------------
86
+ # Carve-out resolver
87
+ # ---------------------------------------------------------------------
88
+ # resolve_carve_outs lives in carveout.py (the #285 carve-out seam); re-exported
89
+ # so the public surface (orchestrator + emitters + tests) stays unchanged.
90
+ from .carveout import resolve_carve_outs # noqa: E402,F401 (re-export: tests)
91
+
92
+
93
+ # ---------------------------------------------------------------------
94
+ # Storage partition resolver
95
+ # ---------------------------------------------------------------------
96
+ # The storage-partition resolver now lives in partition.py (the #285 partition
97
+ # seam). Re-export resolve_storage_partitions (emitters + orchestrator + tests)
98
+ # and _known_flash_devices (the loader's eager flash-device cross-check above).
99
+ from .partition import resolve_storage_partitions # noqa: E402,F401 (re-export for tests)
100
+
101
+
102
+ # ---------------------------------------------------------------------
103
+ # Memory-region view (#1365 item 3)
104
+ # ---------------------------------------------------------------------
105
+ # The manifest's `memory[]` pane -- the region table `ipc[]` and `storage[]`
106
+ # refer INTO by name. Re-exported for the manifest emitter + tests.
107
+ from .memory import resolve_memory_regions # noqa: E402,F401 (re-export for tests)
108
+
109
+
110
+ # ---------------------------------------------------------------------
111
+ # Emitters
112
+ # ---------------------------------------------------------------------
113
+
114
+
115
+ # The header / DTS / mount-table emitters now live in headers.py (the #285
116
+ # headers emit seam). Re-exported so cli.py + alp_project.py + the build-plan
117
+ # artefact table + tests keep importing them unchanged.
118
+ from .headers import ( # noqa: E402
119
+ emit_dts_partitions, # noqa: F401 (re-export: cli + tests)
120
+ emit_dts_reservations, # noqa: F401 (re-export: cli + alp_project + tests)
121
+ emit_ipc_contract_h, # noqa: F401 (re-export: cli + alp_project + tests)
122
+ emit_storage_mounts_c, # noqa: F401 (re-export: cli + alp_project + tests; not used in __init__)
123
+ )
124
+
125
+
126
+ # emit_system_manifest now lives in manifest.py (the #285 manifest emit seam);
127
+ # re-exported for cli + alp_project + tests. (_helper_mcus + the shared build-plan
128
+ # helpers are imported directly by orchestrator.py now, not back through here.)
129
+ from .manifest import emit_system_manifest # noqa: E402,F401 (re-export: cli + alp_project + tests)
130
+
131
+
132
+ # ---------------------------------------------------------------------
133
+ # Build-plan emission (the Wave C consumer contract)
134
+ # ---------------------------------------------------------------------
135
+
136
+
137
+ # The build-plan emitter now lives in buildplan.py (the #285 build-plan seam);
138
+ # re-exported for cli + tests. (The shared materialise helpers are imported
139
+ # directly by orchestrator.py now, not back through here.)
140
+ from .buildplan import ( # noqa: E402
141
+ _slice_cmake_args_artefact, # noqa: F401 (re-export: tests)
142
+ _slice_dts_overlay, # noqa: F401 (re-export: alp_project + tests)
143
+ _slice_dts_overlay_artefact, # noqa: F401 (re-export: tests)
144
+ _slice_hw_info_artefact, # noqa: F401 (re-export: tests)
145
+ _slice_hw_info_h, # noqa: F401 (re-export: alp_project + tests)
146
+ _slice_west_libraries, # noqa: F401 (re-export: alp_project + tests)
147
+ _slice_west_libs_artefact, # noqa: F401 (re-export: tests)
148
+ _v1_shaped_project, # noqa: F401 (re-export: alp_project)
149
+ DtsOverlayUnavailable, # noqa: F401 (re-export: tests)
150
+ HwInfoUnavailable, # noqa: F401 (re-export: tests)
151
+ emit_build_plan, # noqa: F401 (re-export: cli + tests)
152
+ )
153
+
154
+
155
+ # ---------------------------------------------------------------------
156
+ # --emit kconfig -- the board-scoped Kconfig symbol menu for the LSP (#893)
157
+ # ---------------------------------------------------------------------
158
+
159
+
160
+ # The board-scoped Kconfig symbol-menu emitter lives in kconfig_symbols.py
161
+ # (deliberately separate from kconfig.py, which is the alp.conf/local.conf
162
+ # string-templater); re-exported for cli + tests.
163
+ from .kconfig_symbols import emit_kconfig # noqa: E402,F401 (re-export: cli + tests)
164
+
165
+
166
+ # ---------------------------------------------------------------------
167
+ # Slice-command resolution (planner-side; the executor was retired)
168
+ # ---------------------------------------------------------------------
169
+ # The slice-command / flash-recipe cluster lives in orchestrator.py.
170
+ # Re-export _slice_command / _slice_post_commands (tests) and
171
+ # iter_buildable_slices (ADR-0020 Phase 1 -- tests + buildplan.py).
172
+ from .orchestrator import ( # noqa: E402,F401
173
+ _slice_command,
174
+ _slice_post_commands, # noqa: F401 (re-export: tests -- see buildplan.py)
175
+ iter_buildable_slices, # noqa: F401 (re-export: ADR-0020 Phase 1 -- tests + buildplan.py)
176
+ )
177
+
178
+
179
+ # ---------------------------------------------------------------------
180
+ # Per-slice config emitters (consumed by both Orchestrator and the
181
+ # new --core <id> --emit zephyr-conf / yocto-conf modes in alp_project.py).
182
+ # ---------------------------------------------------------------------
183
+
184
+
185
+ # The shared slug / peripheral-Kconfig helpers now live in slugs.py (the #285
186
+ # slug leaf). Re-exported for the per-slice config emitters below + the tests.
187
+ from .slugs import ( # noqa: E402
188
+ _slugs_from_helper_firmware, # noqa: F401 (re-export for tests)
189
+ _slugs_from_on_module, # noqa: F401 (re-export for tests)
190
+ )
191
+
192
+
193
+ # The per-slice Kconfig (alp.conf) emitter now lives in kconfig.py (the #285
194
+ # kconfig emit seam); it pulls the slug tables from slugs.py. Re-exported for
195
+ # the build-plan _slice_config_artefact helper (inline) + alp_project + tests.
196
+ from .kconfig import ( # noqa: E402
197
+ _emit_extra_library_profile, # noqa: F401 (re-export for tests)
198
+ _slice_alp_conf, # noqa: F401 (re-export: alp_project + tests)
199
+ _slice_cmake_args, # noqa: F401 (re-export: alp_project + tests)
200
+ _slice_local_conf, # noqa: F401 (re-export: alp_project + tests)
201
+ )
202
+
203
+
204
+ # The sysbuild / TF-M secure-boot config emitters now live in secure.py (the
205
+ # #285 secure emit seam). Re-exported so cli.py + tests + the build-plan artefact
206
+ # table / slice-command helpers (still inline) keep importing them unchanged.
207
+ from .secure import emit_sysbuild_conf, emit_tfm_sysbuild_conf # noqa: E402,F401 (re-export: cli + tests)
208
+
209
+
210
+ # ---------------------------------------------------------------------
211
+ # CLI (thin wrapper for ad-hoc invocation)
212
+ # ---------------------------------------------------------------------
213
+
214
+
215
+ # Re-export the CLI entry: main() lives in __main__ (so `python -m
216
+ # alp_orchestrate` is the invocation), but `from alp_orchestrate import main`
217
+ # stays valid for callers + the test-suite. Placed at module end so __main__'s
218
+ # `from alp_orchestrate import ...` sees a fully-populated package.
219
+ from .cli import main # noqa: F401,E402 (intentional re-export)