@typecad/cuttlefish 0.1.0-alpha.2 → 1.0.0-alpha.11

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 (224) hide show
  1. package/README.md +10 -10
  2. package/dist/api/board-types.d.ts +1 -1
  3. package/dist/api/config.d.ts +38 -2
  4. package/dist/api/index.d.ts +1 -1
  5. package/dist/api/schema/types.d.ts +15 -0
  6. package/dist/api/shared/async-runtime-static.d.ts +12 -1
  7. package/dist/api/shared/async-runtime-static.js +69 -24
  8. package/dist/api/shared/async-symbol-detector.d.ts +11 -0
  9. package/dist/api/shared/async-symbol-detector.js +140 -0
  10. package/dist/api/shared/async-types.d.ts +24 -0
  11. package/dist/api/shared/coop-scheduler.d.ts +60 -0
  12. package/dist/api/shared/coop-scheduler.js +149 -0
  13. package/dist/api/shared/display-adapter.d.ts +4 -3
  14. package/dist/api/shared/display-adapter.js +15 -118
  15. package/dist/api/shared/display-adapters/sdl.js +38 -31
  16. package/dist/api/shared/display-profile.d.ts +47 -25
  17. package/dist/api/shared/display-profile.js +31 -164
  18. package/dist/api/shared/framework-manifest-registry.d.ts +9 -0
  19. package/dist/api/shared/framework-manifest-registry.js +27 -0
  20. package/dist/api/shared/framework-manifest.d.ts +595 -0
  21. package/dist/api/shared/framework-manifest.js +193 -0
  22. package/dist/api/shared/glcdfont.d.ts +12 -0
  23. package/dist/api/shared/glcdfont.js +124 -0
  24. package/dist/api/shared/graphics-strategy.d.ts +37 -0
  25. package/dist/api/shared/hal-op-ir.d.ts +764 -1
  26. package/dist/api/shared/hal-op-ir.js +129 -1
  27. package/dist/api/shared/index.d.ts +22 -2
  28. package/dist/api/shared/index.js +26 -1
  29. package/dist/api/shared/ir-core.d.ts +4 -0
  30. package/dist/api/shared/ir-declarations.d.ts +6 -0
  31. package/dist/api/shared/native-display-op-resolver.d.ts +10 -0
  32. package/dist/api/shared/native-display-op-resolver.js +64 -0
  33. package/dist/api/shared/platform-strategy.d.ts +119 -2
  34. package/dist/api/shared/polyfill-helper-registry.js +18 -1
  35. package/dist/api/shared/promise-runtime.d.ts +1 -1
  36. package/dist/api/shared/promise-runtime.js +95 -13
  37. package/dist/api/shared/spdx-licenses.d.ts +139 -0
  38. package/dist/api/shared/spdx-licenses.js +385 -0
  39. package/dist/api/shared/toolchain-types.d.ts +17 -0
  40. package/dist/api/shared/types.d.ts +8 -0
  41. package/dist/api/shared/validate-framework-manifest.d.ts +28 -0
  42. package/dist/api/shared/validate-framework-manifest.js +494 -0
  43. package/dist/api/shared/worker-runtime-polyfill.d.ts +22 -0
  44. package/dist/api/shared/worker-runtime-polyfill.js +34 -0
  45. package/dist/api/shared/worker-runtime.d.ts +69 -0
  46. package/dist/api/shared/worker-runtime.js +163 -0
  47. package/dist/cli-utils.d.ts +1 -0
  48. package/dist/cli-utils.js +3 -1
  49. package/dist/cli.js +249 -32
  50. package/dist/config-loader.d.ts +7 -2
  51. package/dist/config-loader.js +23 -3
  52. package/dist/config-schema.d.ts +112 -70
  53. package/dist/config-schema.js +14 -0
  54. package/dist/contract/board-generator.d.ts +17 -0
  55. package/dist/contract/board-generator.js +57 -0
  56. package/dist/contract/contract-parser.d.ts +217 -0
  57. package/dist/contract/contract-parser.js +224 -0
  58. package/dist/contract/index.d.ts +22 -0
  59. package/dist/contract/index.js +68 -0
  60. package/dist/create/board-codegen.js +4 -4
  61. package/dist/create/board-generators.js +4 -5
  62. package/dist/create/board-spec.d.ts +72 -75
  63. package/dist/create/board-spec.js +0 -1
  64. package/dist/create/framework-catalog.d.ts +64 -0
  65. package/dist/create/framework-catalog.js +136 -0
  66. package/dist/create/index.d.ts +5 -1
  67. package/dist/create/index.js +3 -1
  68. package/dist/create/init-scaffold.d.ts +12 -3
  69. package/dist/create/init-scaffold.js +49 -14
  70. package/dist/create/init-templates.d.ts +4 -0
  71. package/dist/create/init-templates.js +271 -17
  72. package/dist/create/init-wizard.js +41 -27
  73. package/dist/create/install-deps.d.ts +32 -0
  74. package/dist/create/install-deps.js +46 -0
  75. package/dist/debug/preprocessor.js +178 -39
  76. package/dist/debug/types.d.ts +23 -0
  77. package/dist/diagnostics/mermaid-builder.d.ts +1 -1
  78. package/dist/diagnostics/mermaid-builder.js +34 -24
  79. package/dist/emit/compliance/arxml-writer.d.ts +11 -0
  80. package/dist/emit/compliance/arxml-writer.js +34 -0
  81. package/dist/emit/compliance/compliance-context.d.ts +57 -0
  82. package/dist/emit/compliance/compliance-context.js +113 -0
  83. package/dist/emit/compliance/deviation-ledger.d.ts +27 -0
  84. package/dist/emit/compliance/deviation-ledger.js +47 -0
  85. package/dist/emit/compliance/deviation-writer.d.ts +30 -0
  86. package/dist/emit/compliance/deviation-writer.js +37 -0
  87. package/dist/emit/compliance/index.d.ts +7 -0
  88. package/dist/emit/compliance/index.js +6 -0
  89. package/dist/emit/compliance/rule-engine.d.ts +13 -0
  90. package/dist/emit/compliance/rule-engine.js +101 -0
  91. package/dist/emit/compliance/rules.d.ts +16 -0
  92. package/dist/emit/compliance/rules.js +191 -0
  93. package/dist/emit/compliance/types.d.ts +59 -0
  94. package/dist/emit/compliance/types.js +8 -0
  95. package/dist/emit/cpp-emitter.js +4 -3
  96. package/dist/emit/emitters/class-emitter.js +6 -1
  97. package/dist/emit/emitters/emitter-context.d.ts +28 -1
  98. package/dist/emit/emitters/function-emitter-impl.js +153 -53
  99. package/dist/emit/emitters/line-appender.js +19 -0
  100. package/dist/emit/emitters/line-marker.d.ts +38 -0
  101. package/dist/emit/emitters/line-marker.js +39 -0
  102. package/dist/emit/emitters/output-finalizer.d.ts +6 -0
  103. package/dist/emit/emitters/output-finalizer.js +101 -12
  104. package/dist/emit/emitters/setup.d.ts +17 -0
  105. package/dist/emit/emitters/setup.js +372 -30
  106. package/dist/emit/emitters/top-level-prep.js +8 -0
  107. package/dist/emit/emitters/type-decl-emitter.js +28 -3
  108. package/dist/emit/emitters/ui-emitter.js +33 -9
  109. package/dist/emit/expression-renderer.d.ts +1 -1
  110. package/dist/emit/expression-renderer.js +91 -6
  111. package/dist/emit/route-hal-op.js +18 -5
  112. package/dist/emit/snprintf-helpers.js +15 -4
  113. package/dist/emit/statement-renderer.d.ts +10 -0
  114. package/dist/emit/statement-renderer.js +34 -5
  115. package/dist/emit/utils/async-state-machine.js +221 -125
  116. package/dist/emit/utils/hal-op-cpp-type.d.ts +6 -0
  117. package/dist/emit/utils/hal-op-cpp-type.js +40 -0
  118. package/dist/framework-package.js +2 -0
  119. package/dist/framework-registry.d.ts +17 -0
  120. package/dist/install/framework-catalog.d.ts +53 -0
  121. package/dist/install/framework-catalog.js +107 -0
  122. package/dist/install/handle-install.d.ts +35 -0
  123. package/dist/install/handle-install.js +177 -0
  124. package/dist/install/index.d.ts +4 -0
  125. package/dist/install/index.js +3 -0
  126. package/dist/ir/adc-range-validation.js +40 -26
  127. package/dist/ir/build-ir-state.d.ts +1 -0
  128. package/dist/ir/build-ir-state.js +14 -0
  129. package/dist/ir/build-ir.js +9 -5
  130. package/dist/ir/call-graph.js +16 -0
  131. package/dist/ir/expression-to-ir.js +207 -6
  132. package/dist/ir/feature-registry.js +7 -25
  133. package/dist/ir/function-builder.js +22 -0
  134. package/dist/ir/hal/hal-emitter.d.ts +5 -2
  135. package/dist/ir/hal/hal-emitter.js +50 -18
  136. package/dist/ir/hal/hal-parser.d.ts +6 -0
  137. package/dist/ir/hal/hal-parser.js +81 -0
  138. package/dist/ir/hal/hal-plugins.js +733 -1
  139. package/dist/ir/identifier-collector.js +35 -0
  140. package/dist/ir/interrupt-analysis.d.ts +5 -1
  141. package/dist/ir/interrupt-analysis.js +16 -19
  142. package/dist/ir/memory-budget-validation.js +1 -0
  143. package/dist/ir/network-validation.d.ts +4 -0
  144. package/dist/ir/network-validation.js +184 -0
  145. package/dist/ir/ownership-analysis.js +39 -1
  146. package/dist/ir/peripheral-ownership.js +5 -0
  147. package/dist/ir/peripheral-validation.d.ts +1 -1
  148. package/dist/ir/peripheral-validation.js +6 -3
  149. package/dist/ir/pin-alias-conflict.d.ts +1 -1
  150. package/dist/ir/pin-alias-conflict.js +2 -1
  151. package/dist/ir/pin-capability-validation.js +71 -32
  152. package/dist/ir/pin-mode-validation.d.ts +2 -2
  153. package/dist/ir/pin-mode-validation.js +54 -18
  154. package/dist/ir/pin-safety.d.ts +1 -1
  155. package/dist/ir/pin-safety.js +2 -1
  156. package/dist/ir/program-analysis.d.ts +81 -0
  157. package/dist/ir/program-analysis.js +429 -0
  158. package/dist/ir/pulldown-validation.d.ts +1 -1
  159. package/dist/ir/pulldown-validation.js +2 -1
  160. package/dist/ir/pwm-timer-sharing.d.ts +1 -1
  161. package/dist/ir/pwm-timer-sharing.js +2 -1
  162. package/dist/ir/render-expr.js +11 -0
  163. package/dist/ir/resource-analysis.js +2 -0
  164. package/dist/ir/timer0-pwm-timing-conflict.d.ts +1 -1
  165. package/dist/ir/timer0-pwm-timing-conflict.js +2 -1
  166. package/dist/ir/timing-validation.d.ts +6 -1
  167. package/dist/ir/timing-validation.js +61 -13
  168. package/dist/ir/transformers/call-statement.js +105 -0
  169. package/dist/ir/transformers/expressions.js +62 -0
  170. package/dist/ir/transformers/hal-call-resolver.js +21 -0
  171. package/dist/ir/transformers/hal-emit-helpers.js +1 -1
  172. package/dist/ir/transformers/namespace-methods.js +17 -12
  173. package/dist/ir/transformers/ui-reactive.js +2 -2
  174. package/dist/ir/transformers/variables.js +137 -19
  175. package/dist/ir/try-catch-validation.js +2 -0
  176. package/dist/ir/type-resolution.js +20 -2
  177. package/dist/ir/ui-element-auto-wire.js +7 -5
  178. package/dist/ir/unit-suspicion-validation.js +9 -7
  179. package/dist/ir/utils/map-statements.d.ts +4 -0
  180. package/dist/ir/utils/map-statements.js +79 -0
  181. package/dist/ir/validation-orchestrator.js +15 -8
  182. package/dist/ir/worker-analysis.d.ts +10 -0
  183. package/dist/ir/worker-analysis.js +261 -0
  184. package/dist/libdef/c-to-decl.d.ts +27 -0
  185. package/dist/libdef/c-to-decl.js +397 -0
  186. package/dist/libdef/component-decls.d.ts +2 -0
  187. package/dist/libdef/component-decls.js +6 -0
  188. package/dist/libdef/component-discovery.d.ts +43 -0
  189. package/dist/libdef/component-discovery.js +83 -0
  190. package/dist/libdef/cpp-to-decl.d.ts +9 -0
  191. package/dist/libdef/cpp-to-decl.js +72 -0
  192. package/dist/libdef/registry.js +5 -2
  193. package/dist/lint-cache.d.ts +59 -0
  194. package/dist/lint-cache.js +257 -0
  195. package/dist/orchestrator/graph-builder.js +14 -9
  196. package/dist/platform/coop-scheduler-runtime.d.ts +19 -0
  197. package/dist/platform/coop-scheduler-runtime.js +41 -0
  198. package/dist/platform/generic-strategy.d.ts +15 -3
  199. package/dist/platform/generic-strategy.js +49 -4
  200. package/dist/safety/safety-bridge.d.ts +11 -0
  201. package/dist/safety/safety-bridge.js +48 -0
  202. package/dist/safety/sidecar-bridge.d.ts +5 -0
  203. package/dist/safety/sidecar-bridge.js +22 -0
  204. package/dist/safety-hook.d.ts +79 -0
  205. package/dist/safety-hook.js +35 -0
  206. package/dist/testing.d.ts +8 -2
  207. package/dist/testing.js +9 -2
  208. package/dist/transpile.d.ts +3 -0
  209. package/dist/transpile.js +191 -49
  210. package/dist/types.d.ts +22 -3
  211. package/dist/ui-hook.d.ts +17 -3
  212. package/dist/utils/cli.js +123 -2
  213. package/dist/utils/fs.d.ts +13 -0
  214. package/dist/utils/fs.js +50 -0
  215. package/dist/utils/ui.js +3 -1
  216. package/package.json +23 -3
  217. package/dist/api/shared/display-adapters/eink-mono.d.ts +0 -2
  218. package/dist/api/shared/display-adapters/eink-mono.js +0 -53
  219. package/dist/api/shared/display-adapters/ssd1309.d.ts +0 -2
  220. package/dist/api/shared/display-adapters/ssd1309.js +0 -136
  221. package/dist/api/shared/display-adapters/st7796.d.ts +0 -2
  222. package/dist/api/shared/display-adapters/st7796.js +0 -132
  223. package/dist/ir/heap-array-validation.d.ts +0 -24
  224. package/dist/ir/heap-array-validation.js +0 -29
@@ -31,11 +31,28 @@ export interface ToolchainOptions {
31
31
  optimize?: string;
32
32
  extraFlags?: string[];
33
33
  defines?: Record<string, string>;
34
+ /** ESP32 PSRAM type ('opi' | 'quad') when the target board has PSRAM. */
35
+ psram?: 'opi' | 'quad';
34
36
  /**
35
37
  * Framework-specific config from `cuttlefish.config.ts`.
36
38
  * Each framework casts this to its own typed interface.
37
39
  */
38
40
  frameworkConfig?: Record<string, unknown>;
41
+ /**
42
+ * Zephyr-specific config from `cuttlefish.config.ts` (the `zephyr` section).
43
+ */
44
+ zephyrConfig?: Record<string, unknown>;
45
+ /**
46
+ * Display config from `cuttlefish.config.ts` (the `display` section).
47
+ * Frameworks use cs/dc/rst/spiFrequency to generate devicetree wiring.
48
+ */
49
+ display?: Record<string, unknown>;
50
+ /**
51
+ * True when `--debug` is active. Frameworks read this to generate debug
52
+ * artifacts (e.g. VS Code launch configs, debug Kconfig symbols) and to
53
+ * select their debug mode (gdb vs printf) via PlatformDebugStrategy.
54
+ */
55
+ debug?: boolean;
39
56
  }
40
57
  /**
41
58
  * Generic compile result. All frameworks return this shape.
@@ -26,6 +26,14 @@ export interface Diagnostic {
26
26
  column?: number;
27
27
  code?: string;
28
28
  source?: string;
29
+ /**
30
+ * Path (or basename) of the source file the diagnostic refers to. When set,
31
+ * it is rendered in the diagnostic header so the location is not just
32
+ * `(line,col)` but `path/to/file.ts (line,col)`. Emitters should prefer the
33
+ * real file path from the originating node's sourceSpan, falling back to a
34
+ * basename when the full path is unavailable.
35
+ */
36
+ filePath?: string;
29
37
  /** Text of the source line for display context. */
30
38
  sourceLine?: string;
31
39
  }
@@ -0,0 +1,28 @@
1
+ import type { PlatformStrategy } from './platform-strategy.js';
2
+ import type { FrameworkToolchain } from '../../framework-registry.js';
3
+ import type { FrameworkManifest } from './framework-manifest.js';
4
+ export interface ManifestValidationContext {
5
+ strategy: PlatformStrategy;
6
+ toolchain?: FrameworkToolchain;
7
+ moduleExports: Record<string, unknown>;
8
+ packageRoot: string;
9
+ repoTestsDir: string;
10
+ }
11
+ export interface ManifestValidationError {
12
+ code: string;
13
+ path: string;
14
+ message: string;
15
+ subject?: string;
16
+ }
17
+ export interface ManifestValidationWarning {
18
+ code: string;
19
+ path: string;
20
+ message: string;
21
+ subject?: string;
22
+ }
23
+ export interface ManifestValidationResult {
24
+ valid: boolean;
25
+ errors: ManifestValidationError[];
26
+ warnings: ManifestValidationWarning[];
27
+ }
28
+ export declare function validateFrameworkManifest(manifest: FrameworkManifest, ctx: ManifestValidationContext): ManifestValidationResult;
@@ -0,0 +1,494 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Framework manifest validator
3
+ //
4
+ // Cross-checks each manifest claim against the loaded PlatformStrategy,
5
+ // Toolchain, and module exports. Errors are plain data (not thrown) so the
6
+ // validator can be called from tests, a future CLI, or agents reading errors
7
+ // programmatically.
8
+ //
9
+ // Categories A-H are documented in docs/framework-manifest-error-codes.md.
10
+ // ---------------------------------------------------------------------------
11
+ import * as fs from 'node:fs';
12
+ import * as path from 'node:path';
13
+ import { POLYFILL_BACKED_OPS } from './framework-manifest.js';
14
+ import { HAL_OPERATION_KINDS } from './hal-op-ir.js';
15
+ import { DISPLAY_OPERATION_KINDS } from './display-op-ir.js';
16
+ class Accumulator {
17
+ constructor() {
18
+ this.errors = [];
19
+ this.warnings = [];
20
+ }
21
+ error(code, path, message, subject) {
22
+ this.errors.push({ code, path, message, subject });
23
+ }
24
+ warning(code, path, message, subject) {
25
+ this.warnings.push({ code, path, message, subject });
26
+ }
27
+ }
28
+ // Map each HAL category to the prefix its op kinds share.
29
+ const CATEGORY_PREFIXES = {
30
+ gpio: ['gpio.'],
31
+ pwm: ['pwm.'],
32
+ rmt: ['rmt.'],
33
+ adc: ['adc.'],
34
+ dac: ['dac.'],
35
+ interrupts: ['interrupt.'],
36
+ tone: ['tone.'],
37
+ timing: ['timing.'],
38
+ power: ['power.'],
39
+ i2c: ['i2c.'],
40
+ spi: ['spi.'],
41
+ uart: ['uart.'],
42
+ pulse: ['pulse.'],
43
+ shift: ['shift.'],
44
+ board: ['board.'],
45
+ wdt: ['wdt.'],
46
+ wifi: ['wifi.'],
47
+ http: ['http.'],
48
+ display: ['display.'],
49
+ preferences: ['preferences.'],
50
+ ble: ['ble.'],
51
+ random: ['random.'],
52
+ fs: ['fs.'],
53
+ mdns: ['mdns.'],
54
+ mqtt: ['mqtt.'],
55
+ ota: ['ota.'],
56
+ temp: ['temp.'],
57
+ hwtimer: ['hwtimer.'],
58
+ capacitive: ['capacitive.'],
59
+ // Unimplemented-but-recognized categories: each framework may declare these
60
+ // as unsupported in its manifest so the coverage matrix records them as a
61
+ // roadmap. The validator probes the resolver and confirms it does NOT lower
62
+ // them (consistent with the 'unsupported' status).
63
+ i2s: ['i2s.'],
64
+ twai: ['twai.'],
65
+ usb: ['usb.'],
66
+ eth: ['eth.'],
67
+ espnow: ['espnow.'],
68
+ crypto: ['crypto.'],
69
+ pcnt: ['pcnt.'],
70
+ mcpwm: ['mcpwm.'],
71
+ // Worker offload + snprintf raw-escape: recognized categories. Frameworks
72
+ // that lower worker.* declare it supported; snprintf.emit is a raw escape
73
+ // hatch (declared unsupported by frameworks that don't special-case it).
74
+ worker: ['worker.'],
75
+ snprintf: ['snprintf.'],
76
+ };
77
+ function opKindsForCategory(category) {
78
+ const prefixes = CATEGORY_PREFIXES[category] ?? [];
79
+ const all = [...HAL_OPERATION_KINDS, ...DISPLAY_OPERATION_KINDS];
80
+ return all.filter((k) => prefixes.some((p) => k.startsWith(p)));
81
+ }
82
+ // Minimal valid payloads for op kinds whose resolver requires more than just
83
+ // the operation discriminator. The default probe (just `{ operation: kind }`)
84
+ // works for most ops; this map fills in the smallest payload that lets the
85
+ // resolver run without throwing on missing-arg validation.
86
+ //
87
+ // Templates must be the MINIMAL payload the resolver needs — enough to prove
88
+ // the framework handles the op, not to produce semantically correct output.
89
+ // Keep entries scoped to fields the resolver actually destructures.
90
+ const OP_PROBE_PAYLOADS = {
91
+ // i2c.write_bytes requires bus + bytes (resolver iterates bytes).
92
+ 'i2c.write_bytes': { bus: 'i2c0', address: 0x50, bytes: [0x00, 0x01] },
93
+ // uart.printf requires format + args (resolver joins args).
94
+ 'uart.printf': { port: 'Serial', format: '%d', args: ['x'] },
95
+ // adc.read / adc.read_voltage require pin.
96
+ 'adc.read': { pin: 0 },
97
+ 'adc.read_voltage': { pin: 0 },
98
+ // dac.write requires pin + value. Use pin 25 (a valid DAC pin on ESP32;
99
+ // pin 0 throws "not a DAC pin" on ESP32 and Arduino maps it to a
100
+ // non-DAC pin too).
101
+ 'dac.write': { pin: 25, value: 128 },
102
+ // display.* require coordinates/color (still inconclusive without driver
103
+ // context — included so future driver-aware probes can build on them).
104
+ 'display.fill_rect': { x: 0, y: 0, w: 10, h: 10, color: 0xffff },
105
+ 'display.draw_text': { x: 0, y: 0, text: 'x', font: '8x16', color: 0xffff },
106
+ 'display.draw_rect': { x: 0, y: 0, w: 10, h: 10, color: 0xffff },
107
+ 'display.flush': { rects: [{ x: 0, y: 0, w: 10, h: 10 }] },
108
+ // fs.* require a path (and content for write_text).
109
+ 'fs.read_text': { path: '/sdcard/x.txt' },
110
+ 'fs.write_text': { path: '/sdcard/x.txt', content: 'hi' },
111
+ 'fs.exists': { path: '/sdcard/x.txt' },
112
+ 'fs.remove': { path: '/sdcard/x.txt' },
113
+ // random.range requires min/max.
114
+ 'random.range': { min: 0, max: 10 },
115
+ // mdns.add_service requires instance/proto/port.
116
+ 'mdns.start': { hostname: 'h' },
117
+ 'mdns.set_hostname': { name: 'h' },
118
+ 'mdns.add_service': { instance: 'i', proto: '_tcp', port: 80 },
119
+ // mqtt.connect requires brokerUri/clientId; mqtt.publish requires topic/data.
120
+ 'mqtt.connect': { brokerUri: 'mqtt://b', clientId: 'c' },
121
+ 'mqtt.on_message': { handler: 'cb' },
122
+ 'mqtt.subscribe': { topic: 't' },
123
+ 'mqtt.publish': { topic: 't', data: 'd' },
124
+ // wifi.on_event requires an event + handler. Probe with 'connect'.
125
+ // Without a payload the probe sends event=undefined and a 'supported'
126
+ // declaration would false-fail as "resolver returned undefined".
127
+ 'wifi.on_event': { event: 'connect', handler: 'cb' },
128
+ // ota.from_url requires url; ota.write requires chunk.
129
+ 'ota.from_url': { url: 'https://x' },
130
+ 'ota.write': { chunk: 'buf' },
131
+ // hwtimer.* require an instance (and hz/handler).
132
+ 'hwtimer.set_frequency': { instance: 0, hz: 1000 },
133
+ 'hwtimer.on_overflow': { instance: 0, handler: 'cb' },
134
+ 'hwtimer.start': { instance: 0 },
135
+ 'hwtimer.stop': { instance: 0 },
136
+ // capacitive.read requires pin.
137
+ 'capacitive.read': { pin: 4 },
138
+ };
139
+ // Builds a HALOpIR probe. Uses OP_PROBE_PAYLOADS when available so resolvers
140
+ // that destructure required fields don't throw on the probe itself.
141
+ function buildHalProbe(opKind) {
142
+ const payload = OP_PROBE_PAYLOADS[opKind];
143
+ return { operation: opKind, ...(payload ?? {}) };
144
+ }
145
+ function buildDisplayProbe(opKind) {
146
+ const payload = OP_PROBE_PAYLOADS[opKind];
147
+ return { operation: opKind, ...(payload ?? {}) };
148
+ }
149
+ function probeResolve(strategy, opKind) {
150
+ const isDisplay = opKind.startsWith('display.');
151
+ try {
152
+ const op = isDisplay ? buildDisplayProbe(opKind) : buildHalProbe(opKind);
153
+ const result = isDisplay
154
+ ? strategy.resolveDisplayOp?.(op)
155
+ : strategy.resolveHALOperation?.(op);
156
+ if (!result)
157
+ return 'undefined';
158
+ if (result.code !== undefined)
159
+ return 'code';
160
+ if (result.expression !== undefined)
161
+ return 'expression';
162
+ return 'undefined';
163
+ }
164
+ catch {
165
+ return 'thrown';
166
+ }
167
+ }
168
+ function statusFromResolution(res) {
169
+ return res === 'undefined' || res === 'thrown' ? 'no-emit' : 'lowers';
170
+ }
171
+ // ---------------------------------------------------------------------------
172
+ // Category A — identity
173
+ // ---------------------------------------------------------------------------
174
+ function validateIdentity(manifest, ctx, acc) {
175
+ const strategyId = ctx.strategy.id;
176
+ const matchesDirect = strategyId === manifest.frameworkId;
177
+ const matchesInherited = manifest.inheritsStrategyId !== undefined &&
178
+ manifest.inheritsStrategyId === strategyId;
179
+ if (!matchesDirect && !matchesInherited) {
180
+ acc.error('identity/id-mismatch', 'frameworkId', `manifest.frameworkId is "${manifest.frameworkId}" but strategy.id is "${strategyId}". ` +
181
+ `Set strategy.id to match, or add "inheritsStrategyId: "${strategyId}"" to the manifest ` +
182
+ `to document intentional id reuse.`, manifest.frameworkId);
183
+ }
184
+ }
185
+ // ---------------------------------------------------------------------------
186
+ // Category B — entrypoint
187
+ // ---------------------------------------------------------------------------
188
+ function validateEntrypoint(manifest, ctx, acc) {
189
+ const ep = manifest.entrypoint;
190
+ const strat = ctx.strategy;
191
+ const checks = [
192
+ ['entrypointFunctionName', ep.entrypointFunctionName, strat.entrypointFunctionName()],
193
+ ['requiresLoopFunction', ep.requiresLoopFunction, strat.requiresLoopFunction()],
194
+ ['sourceExtension', ep.sourceExtension, strat.sourceExtension(true, false)],
195
+ ['generateHeaderFile', ep.generateHeaderFile, strat.generateHeaderFile()],
196
+ ];
197
+ for (const [field, declared, actual] of checks) {
198
+ if (declared !== actual) {
199
+ acc.error(`entrypoint/${field}/mismatch`, `entrypoint.${field}`, `manifest declares ${field}=${JSON.stringify(declared)} but strategy returns ${JSON.stringify(actual)}.`, field);
200
+ }
201
+ }
202
+ }
203
+ // ---------------------------------------------------------------------------
204
+ // Category C — HAL coverage
205
+ // ---------------------------------------------------------------------------
206
+ function validateHalCoverage(manifest, ctx, acc) {
207
+ for (const [category, declarationRaw] of Object.entries(manifest.hal)) {
208
+ if (category === 'raw')
209
+ continue;
210
+ const declaration = declarationRaw;
211
+ const knownKinds = opKindsForCategory(category);
212
+ // Check that every known op kind for this category appears in ops.
213
+ for (const kind of knownKinds) {
214
+ if (!(kind in declaration.ops)) {
215
+ acc.error(`hal/${category}/op/${kind}/undeclared`, `hal.${category}.ops.${kind}`, `manifest.hal.${category}.ops is missing "${kind}". Add it with a status of supported/stub/unsupported.`, kind);
216
+ }
217
+ }
218
+ // Probe each declared op and cross-check status vs resolver behavior.
219
+ for (const [kind, status] of Object.entries(declaration.ops)) {
220
+ // 'probe-inconclusive' is an explicit acknowledgment that the minimal
221
+ // probe (just an operation discriminator) cannot determine support —
222
+ // typically because the resolver needs a valid pin/config payload.
223
+ // The validator does not cross-check these; the renderer flags them
224
+ // for manual review. This keeps the matrix honest without penalizing
225
+ // frameworks for the probe's limitations.
226
+ if (status === 'probe-inconclusive')
227
+ continue;
228
+ // 'polyfill' means the op is backed by a runtime polyfill, NOT by
229
+ // resolveHALOperation. Verify the named polyfill exists in
230
+ // POLYFILL_BACKED_OPS and is declared in polyfills.emitted.
231
+ // No HAL probe — the resolver legitimately returns undefined for these.
232
+ if (status === 'polyfill') {
233
+ const expectedPolyfill = POLYFILL_BACKED_OPS[kind];
234
+ if (!expectedPolyfill) {
235
+ acc.error(`hal/${category}/op/${kind}/polyfill-not-recognized`, `hal.${category}.ops.${kind}`, `op "${kind}" declared "polyfill" but is not in POLYFILL_BACKED_OPS. Either add it to packages/cuttlefish/src/api/shared/framework-manifest.ts or use a different status.`, kind);
236
+ continue;
237
+ }
238
+ const emitted = manifest.polyfills.emitted.map((p) => p.id);
239
+ if (!emitted.includes(expectedPolyfill)) {
240
+ acc.error(`hal/${category}/op/${kind}/polyfill-not-declared`, `hal.${category}.ops.${kind}`, `op "${kind}" declared "polyfill" (backed by "${expectedPolyfill}") but "${expectedPolyfill}" is not in polyfills.emitted. Add it, or remove this op from the polyfill status.`, kind);
241
+ }
242
+ continue;
243
+ }
244
+ const res = probeResolve(ctx.strategy, kind);
245
+ const lowers = statusFromResolution(res) === 'lowers';
246
+ if (status === 'supported' && !lowers) {
247
+ acc.error(`hal/${category}/op/${kind}/status-mismatch`, `hal.${category}.ops.${kind}`, `op "${kind}" declared "supported" but resolver returned ${res}. Either implement the lowering or change the op status.`, kind);
248
+ }
249
+ else if (status === 'stub' && !lowers) {
250
+ acc.error(`hal/${category}/op/${kind}/status-mismatch`, `hal.${category}.ops.${kind}`, `op "${kind}" declared "stub" but resolver emitted nothing (${res}). Either emit something or change to "unsupported".`, kind);
251
+ }
252
+ else if (status === 'unsupported' && lowers) {
253
+ acc.error(`hal/${category}/op/${kind}/status-mismatch`, `hal.${category}.ops.${kind}`, `op "${kind}" declared "unsupported" but resolver actually lowers (${res}). Either mark it supported or remove the lowering.`, kind);
254
+ }
255
+ }
256
+ // Category-level summary check.
257
+ if (declaration.supported) {
258
+ // If every op is probe-inconclusive, we can't verify the supported
259
+ // claim at all — skip rather than false-positive.
260
+ const hasVerifiable = knownKinds.some((k) => declaration.ops[k] !== 'probe-inconclusive');
261
+ if (hasVerifiable) {
262
+ const anyLowersOrInconclusive = knownKinds.some((k) => {
263
+ if (declaration.ops[k] === 'probe-inconclusive')
264
+ return true;
265
+ if (declaration.ops[k] === 'polyfill')
266
+ return true;
267
+ return statusFromResolution(probeResolve(ctx.strategy, k)) === 'lowers';
268
+ });
269
+ if (!anyLowersOrInconclusive) {
270
+ acc.error(`hal/${category}/declared-supported-but-undefined`, `hal.${category}`, `manifest.hal.${category}.supported is true but resolver returns undefined for every verifiable op kind. Change supported to false with unsupportedReason, or implement the lowering.`, category);
271
+ }
272
+ }
273
+ }
274
+ else {
275
+ // supported: false. Ignore probe-inconclusive and polyfill ops when
276
+ // checking "does it actually lower?" — polyfill ops legitimately
277
+ // return undefined from the HAL resolver.
278
+ const anyLowers = knownKinds.some((k) => {
279
+ if (declaration.ops[k] === 'probe-inconclusive')
280
+ return false;
281
+ if (declaration.ops[k] === 'polyfill')
282
+ return false;
283
+ return statusFromResolution(probeResolve(ctx.strategy, k)) === 'lowers';
284
+ });
285
+ if (anyLowers) {
286
+ acc.error(`hal/${category}/declared-unsupported-but-actually-lowers`, `hal.${category}`, `manifest.hal.${category}.supported is false but resolver lowers at least one verifiable op. Either mark supported: true or override the resolver to throw/return undefined.`, category);
287
+ }
288
+ }
289
+ }
290
+ }
291
+ // ---------------------------------------------------------------------------
292
+ // Category C2 — HAL completeness
293
+ // ---------------------------------------------------------------------------
294
+ /**
295
+ * Verify every known HAL category appears in manifest.hal. validateHalCoverage
296
+ * only audits categories the manifest declares; a category omitted entirely is
297
+ * invisible to it (and to the per-op check). This step closes that hole: each
298
+ * category in CATEGORY_PREFIXES must be accounted for, even as unsupported.
299
+ * `raw` is excluded — it is the schema-level escape hatch, not a real category,
300
+ * and is not in CATEGORY_PREFIXES.
301
+ */
302
+ function validateHalCompleteness(manifest, _ctx, acc) {
303
+ const declared = new Set(Object.keys(manifest.hal));
304
+ for (const category of Object.keys(CATEGORY_PREFIXES)) {
305
+ if (!declared.has(category)) {
306
+ acc.error(`hal/${category}/category-undeclared`, `manifest.hal.${category}`, `manifest.hal is missing the "${category}" category. Add a block declaring it (supported: true/false with ops, or unsupported with unsupportedReason).`, category);
307
+ }
308
+ }
309
+ }
310
+ // ---------------------------------------------------------------------------
311
+ // Category D — polyfills
312
+ // ---------------------------------------------------------------------------
313
+ function validatePolyfills(manifest, ctx, acc) {
314
+ const strat = ctx.strategy;
315
+ // A polyfill is "produced" if either generateNativePolyfills OR
316
+ // nativePolyfills mentions it. generateNativePolyfills filters by program
317
+ // analysis (may omit polyfills not needed for the empty probe program);
318
+ // nativePolyfills is the unconditional set the strategy handles natively.
319
+ // Unioning both gives the true picture of what the strategy claims to emit.
320
+ const produced = new Set();
321
+ try {
322
+ const irs = strat.generateNativePolyfills?.({ kind: 'program', modules: [], classes: [], functions: [] }, undefined) ?? [];
323
+ for (const ir of irs)
324
+ produced.add(ir.id);
325
+ }
326
+ catch {
327
+ // generateNativePolyfills may throw on synthetic input; that's fine.
328
+ }
329
+ try {
330
+ const ids = strat.nativePolyfills?.() ?? new Set();
331
+ for (const id of ids)
332
+ produced.add(id);
333
+ }
334
+ catch {
335
+ // nativePolyfills is optional.
336
+ }
337
+ for (const declared of manifest.polyfills.emitted) {
338
+ if (!produced.has(declared.id)) {
339
+ acc.error(`polyfill/${declared.id}/declared-but-not-emitted`, `polyfills.emitted.${declared.id}`, `polyfill "${declared.id}" declared emitted but generateNativePolyfills/nativePolyfills did not produce it.`, declared.id);
340
+ }
341
+ }
342
+ for (const suppressed of manifest.polyfills.suppressed) {
343
+ if (produced.has(suppressed.id)) {
344
+ acc.error(`polyfill/${suppressed.id}/declared-suppressed-but-emitted`, `polyfills.suppressed.${suppressed.id}`, `polyfill "${suppressed.id}" declared suppressed but the strategy actually emits it.`, suppressed.id);
345
+ }
346
+ }
347
+ }
348
+ // ---------------------------------------------------------------------------
349
+ // Category E — toolchain
350
+ // ---------------------------------------------------------------------------
351
+ function validateToolchain(manifest, ctx, acc) {
352
+ const ops = manifest.toolchain.operations;
353
+ if (!ops.compile) {
354
+ acc.error('toolchain/compile/required', 'toolchain.operations.compile', 'toolchain.operations.compile must be true (LoadedFramework contract requires a compile implementation).', 'compile');
355
+ }
356
+ const tc = ctx.toolchain;
357
+ if (tc) {
358
+ const checks = [
359
+ ['prepare', ops.prepare],
360
+ ['compile', ops.compile],
361
+ ['upload', ops.upload],
362
+ ['monitor', ops.monitor],
363
+ ];
364
+ for (const [op, declared] of checks) {
365
+ if (declared && typeof tc[op] !== 'function') {
366
+ acc.error(`toolchain/${op}/declared-but-missing`, `toolchain.operations.${op}`, `operation "${op}" declared true but is not a function on the loaded Toolchain.`, op);
367
+ }
368
+ }
369
+ }
370
+ }
371
+ // ---------------------------------------------------------------------------
372
+ // Category F — library resolution
373
+ // ---------------------------------------------------------------------------
374
+ function validateLibraryResolution(manifest, ctx, acc) {
375
+ if (!manifest.libraryResolution)
376
+ return;
377
+ const lr = manifest.libraryResolution;
378
+ const checks = [
379
+ ['isFrameworkLibraryImport', lr.isFrameworkLibraryImport],
380
+ ['getFrameworkLibraryHeaderName', lr.getFrameworkLibraryHeaderName],
381
+ ['buildClassNameMap', lr.buildClassNameMap],
382
+ ['tryGenerateLibDecl', lr.tryGenerateLibDecl],
383
+ ];
384
+ for (const [name, declared] of checks) {
385
+ if (declared && typeof ctx.moduleExports[name] !== 'function') {
386
+ acc.error(`library-resolution/${name}/declared-but-not-exported`, `libraryResolution.${name}`, `"${name}" declared true but not exported from the framework package index.`, name);
387
+ }
388
+ }
389
+ }
390
+ // ---------------------------------------------------------------------------
391
+ // Category G — type emission
392
+ // ---------------------------------------------------------------------------
393
+ function validateTypeEmission(manifest, ctx, acc) {
394
+ const te = manifest.typeEmission;
395
+ const strat = ctx.strategy;
396
+ const fieldChecks = [
397
+ ['mathHeader', te.mathHeader, strat.mathHeader()],
398
+ ['needsStdString', te.needsStdString, strat.needsStdString()],
399
+ ['needsStdVector', te.needsStdVector, strat.needsStdVector()],
400
+ ['needsIostream', te.needsIostream, strat.needsIostream()],
401
+ ['needsStdFunction', te.needsStdFunction, strat.needsStdFunction()],
402
+ ];
403
+ for (const [field, declared, actual] of fieldChecks) {
404
+ if (declared !== actual) {
405
+ acc.error(`type-emission/${field}/mismatch`, `typeEmission.${field}`, `manifest declares ${field}=${JSON.stringify(declared)} but strategy returns ${JSON.stringify(actual)}.`, field);
406
+ }
407
+ }
408
+ const actualStdlib = strat.getStdLibSupport();
409
+ const declaredStdlib = te.stdlibSupport;
410
+ const stdlibEqual = actualStdlib.hasVector === declaredStdlib.hasVector &&
411
+ actualStdlib.hasString === declaredStdlib.hasString &&
412
+ actualStdlib.hasIostream === declaredStdlib.hasIostream &&
413
+ actualStdlib.hasExceptions === declaredStdlib.hasExceptions &&
414
+ actualStdlib.hasRTTI === declaredStdlib.hasRTTI &&
415
+ actualStdlib.recommendedArrayImpl === declaredStdlib.recommendedArrayImpl &&
416
+ actualStdlib.recommendedStringImpl === declaredStdlib.recommendedStringImpl;
417
+ if (!stdlibEqual) {
418
+ acc.error('type-emission/stdlib-support-mismatch', 'typeEmission.stdlibSupport', `manifest stdlibSupport does not match strategy.getStdLibSupport() return.`);
419
+ }
420
+ // Ambient types
421
+ const emittedAmbient = new Set();
422
+ try {
423
+ const decls = strat.ambientTypeDeclarations?.() ?? [];
424
+ for (const d of decls) {
425
+ if (typeof d === 'string') {
426
+ // Match all ambient declarations in the string: interface/type/class NAME
427
+ // and const NAME: (Arduino/AVR/ESP32 emit `const Timing: {...}` etc.).
428
+ const matches = [...d.matchAll(/\b(?:interface|type|class)\s+([A-Za-z_$][\w$]*)/g)];
429
+ for (const m of matches)
430
+ emittedAmbient.add(m[1]);
431
+ const constMatches = [...d.matchAll(/\bconst\s+([A-Za-z_$][\w$]*)\s*:/g)];
432
+ for (const m of constMatches)
433
+ emittedAmbient.add(m[1]);
434
+ }
435
+ else if (d && typeof d === 'object' && 'name' in d) {
436
+ emittedAmbient.add(String(d.name));
437
+ }
438
+ }
439
+ }
440
+ catch {
441
+ // ambientTypeDeclarations is optional and may require a context.
442
+ }
443
+ for (const declared of manifest.ambientTypes) {
444
+ if (!emittedAmbient.has(declared)) {
445
+ acc.error(`ambient-types/${declared}/declared-but-not-emitted`, `ambientTypes.${declared}`, `ambient type "${declared}" declared but not found in ambientTypeDeclarations() output.`, declared);
446
+ }
447
+ }
448
+ for (const emitted of emittedAmbient) {
449
+ if (!manifest.ambientTypes.includes(emitted)) {
450
+ acc.warning(`ambient-types/${emitted}/emitted-but-undeclared`, 'ambientTypes', `ambient type "${emitted}" emitted by strategy but not listed in manifest. Consider adding it.`, emitted);
451
+ }
452
+ }
453
+ }
454
+ // ---------------------------------------------------------------------------
455
+ // Category H — conformance
456
+ // ---------------------------------------------------------------------------
457
+ function validateConformance(manifest, ctx, acc) {
458
+ for (const group of manifest.conformance.hardwareTestGroups) {
459
+ const filePath = path.join(ctx.packageRoot, 'tests', `${group}.test.ts`);
460
+ if (!fs.existsSync(filePath)) {
461
+ acc.error(`conformance/hardware/${group}/file-not-found`, `conformance.hardwareTestGroups.${group}`, `hardware test group "${group}" listed but ${filePath} does not exist.`, group);
462
+ }
463
+ }
464
+ for (const name of manifest.conformance.halResolutionTests) {
465
+ // Test dir convention is tests/packages/<package-dir-name>/hal-resolution/.
466
+ // Package dir name is the last segment of packageName (e.g.
467
+ // "@typecad/framework-esp32" -> "framework-esp32"), not frameworkId.
468
+ const packageDirName = manifest.packageName.split('/').pop() ?? manifest.frameworkId;
469
+ const filePath = path.join(ctx.repoTestsDir, 'packages', packageDirName, 'hal-resolution', `${name}.test.ts`);
470
+ if (!fs.existsSync(filePath)) {
471
+ acc.error(`conformance/hal/${name}/file-not-found`, `conformance.halResolutionTests.${name}`, `HAL resolution test "${name}" listed but ${filePath} does not exist.`, name);
472
+ }
473
+ }
474
+ }
475
+ // ---------------------------------------------------------------------------
476
+ // Entry point
477
+ // ---------------------------------------------------------------------------
478
+ export function validateFrameworkManifest(manifest, ctx) {
479
+ const acc = new Accumulator();
480
+ validateIdentity(manifest, ctx, acc);
481
+ validateEntrypoint(manifest, ctx, acc);
482
+ validateHalCoverage(manifest, ctx, acc);
483
+ validateHalCompleteness(manifest, ctx, acc);
484
+ validatePolyfills(manifest, ctx, acc);
485
+ validateToolchain(manifest, ctx, acc);
486
+ validateLibraryResolution(manifest, ctx, acc);
487
+ validateTypeEmission(manifest, ctx, acc);
488
+ validateConformance(manifest, ctx, acc);
489
+ return {
490
+ valid: acc.errors.length === 0,
491
+ errors: acc.errors,
492
+ warnings: acc.warnings,
493
+ };
494
+ }
@@ -0,0 +1,22 @@
1
+ import type { ProgramIR } from "../index.js";
2
+ import type { RuntimePolyfillIR } from "./index.js";
3
+ import type { PlatformStrategy } from "./platform-strategy.js";
4
+ import type { WorkerBacking } from "./worker-runtime.js";
5
+ /**
6
+ * Build a RuntimePolyfillIR for the worker-offload runtime, when the framework
7
+ * supplies worker backing via its PlatformAsyncStrategy hooks
8
+ * (workerSpawnLines / workerSignalDoneExpr / workerIsDoneExpr).
9
+ *
10
+ * The polyfill carries only the slot table + __tc_worker_submit/done contract
11
+ * with the per-framework primitives injected from the strategy hooks. STL-free
12
+ * so it links on minimal-libc targets (Zephyr). Returns null if the strategy
13
+ * supplies no backing (worker offload unsupported there) — in which case
14
+ * worker.* ops resolve to unsupported.
15
+ *
16
+ * Callers gate this on the program actually using worker.* ops
17
+ * (ProgramAnalysisResult.usesWorker); the producer does not re-scan.
18
+ */
19
+ export declare function buildWorkerRuntimePolyfill(_program: ProgramIR, strategy: PlatformStrategy, backing: WorkerBacking, options?: {
20
+ poolSize?: number;
21
+ stackSize?: number;
22
+ }): RuntimePolyfillIR | null;
@@ -0,0 +1,34 @@
1
+ import { generateWorkerRuntime } from "./worker-runtime.js";
2
+ /**
3
+ * Build a RuntimePolyfillIR for the worker-offload runtime, when the framework
4
+ * supplies worker backing via its PlatformAsyncStrategy hooks
5
+ * (workerSpawnLines / workerSignalDoneExpr / workerIsDoneExpr).
6
+ *
7
+ * The polyfill carries only the slot table + __tc_worker_submit/done contract
8
+ * with the per-framework primitives injected from the strategy hooks. STL-free
9
+ * so it links on minimal-libc targets (Zephyr). Returns null if the strategy
10
+ * supplies no backing (worker offload unsupported there) — in which case
11
+ * worker.* ops resolve to unsupported.
12
+ *
13
+ * Callers gate this on the program actually using worker.* ops
14
+ * (ProgramAnalysisResult.usesWorker); the producer does not re-scan.
15
+ */
16
+ export function buildWorkerRuntimePolyfill(_program, strategy, backing, options = {}) {
17
+ // The backing is authoritative: it is constructed by the framework from its
18
+ // own hooks. If a framework supplies no backing (worker offload unsupported),
19
+ // it should not call this producer.
20
+ const runtime = generateWorkerRuntime(backing, options);
21
+ if (!runtime)
22
+ return null;
23
+ return {
24
+ kind: "polyfill",
25
+ id: "worker_runtime",
26
+ domain: "embedded",
27
+ requiredIncludes: backing.requiredIncludes,
28
+ forwardDeclarations: backing.declarations ?? [],
29
+ helperStructs: [runtime],
30
+ helperFunctions: [],
31
+ shimMacros: [],
32
+ dependencies: [],
33
+ };
34
+ }