@aperant/framework 0.8.1 → 0.8.6

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 (308) hide show
  1. package/CHANGELOG.md +443 -1
  2. package/agents/apt-planner.md +39 -7
  3. package/dist/cli/commands/adr.mjs +1 -1
  4. package/dist/cli/commands/audit-branch-current.d.mts +25 -0
  5. package/dist/cli/commands/audit-branch-current.d.mts.map +1 -0
  6. package/dist/cli/commands/audit-branch-current.mjs +252 -0
  7. package/dist/cli/commands/audit-branch-current.mjs.map +1 -0
  8. package/dist/cli/commands/audit.d.mts.map +1 -1
  9. package/dist/cli/commands/audit.mjs +13 -2
  10. package/dist/cli/commands/audit.mjs.map +1 -1
  11. package/dist/cli/commands/check-version.d.mts.map +1 -1
  12. package/dist/cli/commands/check-version.mjs +19 -1
  13. package/dist/cli/commands/check-version.mjs.map +1 -1
  14. package/dist/cli/commands/context.mjs +3 -3
  15. package/dist/cli/commands/context.mjs.map +1 -1
  16. package/dist/cli/commands/coverage-check.d.mts.map +1 -1
  17. package/dist/cli/commands/coverage-check.mjs +74 -7
  18. package/dist/cli/commands/coverage-check.mjs.map +1 -1
  19. package/dist/cli/commands/detect-runtime.d.mts +6 -0
  20. package/dist/cli/commands/detect-runtime.d.mts.map +1 -0
  21. package/dist/cli/commands/detect-runtime.mjs +142 -0
  22. package/dist/cli/commands/detect-runtime.mjs.map +1 -0
  23. package/dist/cli/commands/driver-doctor.d.mts +29 -0
  24. package/dist/cli/commands/driver-doctor.d.mts.map +1 -0
  25. package/dist/cli/commands/driver-doctor.mjs +291 -0
  26. package/dist/cli/commands/driver-doctor.mjs.map +1 -0
  27. package/dist/cli/commands/gate.mjs +14 -0
  28. package/dist/cli/commands/gate.mjs.map +1 -1
  29. package/dist/cli/commands/init.d.mts.map +1 -1
  30. package/dist/cli/commands/init.mjs +9 -6
  31. package/dist/cli/commands/init.mjs.map +1 -1
  32. package/dist/cli/commands/install-from-source.d.mts +2 -0
  33. package/dist/cli/commands/install-from-source.d.mts.map +1 -0
  34. package/dist/cli/commands/install-from-source.mjs +2 -0
  35. package/dist/cli/commands/install-from-source.mjs.map +1 -0
  36. package/dist/cli/commands/modes.mjs +1 -1
  37. package/dist/cli/commands/route.d.mts.map +1 -1
  38. package/dist/cli/commands/route.mjs +38 -1
  39. package/dist/cli/commands/route.mjs.map +1 -1
  40. package/dist/cli/commands/triage.mjs +1 -1
  41. package/dist/cli/config/load.d.mts.map +1 -1
  42. package/dist/cli/config/load.mjs +9 -3
  43. package/dist/cli/config/load.mjs.map +1 -1
  44. package/dist/cli/coordination/auto-emit-artifact-ready.mjs +1 -1
  45. package/dist/cli/coordination/event-schema.d.mts.map +1 -1
  46. package/dist/cli/coordination/event-schema.mjs +18 -0
  47. package/dist/cli/coordination/event-schema.mjs.map +1 -1
  48. package/dist/cli/coverage-check/user-outcomes.d.mts +84 -0
  49. package/dist/cli/coverage-check/user-outcomes.d.mts.map +1 -0
  50. package/dist/cli/coverage-check/user-outcomes.mjs +265 -0
  51. package/dist/cli/coverage-check/user-outcomes.mjs.map +1 -0
  52. package/dist/cli/dispatch.d.mts.map +1 -1
  53. package/dist/cli/dispatch.mjs +18 -1
  54. package/dist/cli/dispatch.mjs.map +1 -1
  55. package/dist/cli/gate/context.d.mts +1 -0
  56. package/dist/cli/gate/context.d.mts.map +1 -1
  57. package/dist/cli/gate/context.mjs +14 -1
  58. package/dist/cli/gate/context.mjs.map +1 -1
  59. package/dist/cli/gate/gates/consistency-check.d.mts +1 -0
  60. package/dist/cli/gate/gates/consistency-check.d.mts.map +1 -1
  61. package/dist/cli/gate/gates/consistency-check.mjs +1 -0
  62. package/dist/cli/gate/gates/consistency-check.mjs.map +1 -1
  63. package/dist/cli/gate/gates/review-clean.d.mts +1 -0
  64. package/dist/cli/gate/gates/review-clean.d.mts.map +1 -1
  65. package/dist/cli/gate/gates/review-clean.mjs +1 -0
  66. package/dist/cli/gate/gates/review-clean.mjs.map +1 -1
  67. package/dist/cli/gate/gates/verify-approved.d.mts +1 -0
  68. package/dist/cli/gate/gates/verify-approved.d.mts.map +1 -1
  69. package/dist/cli/gate/gates/verify-approved.mjs +1 -0
  70. package/dist/cli/gate/gates/verify-approved.mjs.map +1 -1
  71. package/dist/cli/gate/registry.d.mts.map +1 -1
  72. package/dist/cli/gate/registry.mjs +9 -1
  73. package/dist/cli/gate/registry.mjs.map +1 -1
  74. package/dist/cli/host/detect.mjs +1 -1
  75. package/dist/cli/host/detect.mjs.map +1 -1
  76. package/dist/cli/install/find-workspace-framework-version.d.mts +16 -0
  77. package/dist/cli/install/find-workspace-framework-version.d.mts.map +1 -0
  78. package/dist/cli/install/find-workspace-framework-version.mjs +85 -0
  79. package/dist/cli/install/find-workspace-framework-version.mjs.map +1 -0
  80. package/dist/cli/install/install-from-source.d.mts +10 -0
  81. package/dist/cli/install/install-from-source.d.mts.map +1 -0
  82. package/dist/cli/install/install-from-source.mjs +271 -0
  83. package/dist/cli/install/install-from-source.mjs.map +1 -0
  84. package/dist/cli/install/install-kernel.d.mts.map +1 -1
  85. package/dist/cli/install/install-kernel.mjs +24 -0
  86. package/dist/cli/install/install-kernel.mjs.map +1 -1
  87. package/dist/cli/install/legacy-paths.d.mts +33 -11
  88. package/dist/cli/install/legacy-paths.d.mts.map +1 -1
  89. package/dist/cli/install/legacy-paths.mjs +163 -39
  90. package/dist/cli/install/legacy-paths.mjs.map +1 -1
  91. package/dist/cli/install/runtime-detect.mjs +1 -1
  92. package/dist/cli/install/runtime-detect.mjs.map +1 -1
  93. package/dist/cli/install/runtime-migrate.d.mts +34 -14
  94. package/dist/cli/install/runtime-migrate.d.mts.map +1 -1
  95. package/dist/cli/install/runtime-migrate.mjs +63 -30
  96. package/dist/cli/install/runtime-migrate.mjs.map +1 -1
  97. package/dist/cli/install/transforms/codex.d.mts.map +1 -1
  98. package/dist/cli/install/transforms/codex.mjs +28 -17
  99. package/dist/cli/install/transforms/codex.mjs.map +1 -1
  100. package/dist/cli/skill-author/contract.mjs +2 -2
  101. package/dist/cli/skill-author/contract.mjs.map +1 -1
  102. package/dist/cli/verify-proof/audit.d.mts +34 -0
  103. package/dist/cli/verify-proof/audit.d.mts.map +1 -0
  104. package/dist/cli/verify-proof/audit.mjs +53 -0
  105. package/dist/cli/verify-proof/audit.mjs.map +1 -0
  106. package/dist/cli/verify-proof/exec.d.mts +20 -0
  107. package/dist/cli/verify-proof/exec.d.mts.map +1 -0
  108. package/dist/cli/verify-proof/exec.mjs +74 -0
  109. package/dist/cli/verify-proof/exec.mjs.map +1 -0
  110. package/dist/cli/verify-proof/idl/index.d.mts +2 -0
  111. package/dist/cli/verify-proof/idl/index.d.mts.map +1 -0
  112. package/dist/cli/verify-proof/idl/index.mjs +13 -0
  113. package/dist/cli/verify-proof/idl/index.mjs.map +1 -0
  114. package/dist/cli/verify-proof/idl/types.d.ts +10 -0
  115. package/dist/cli/verify-proof/idl/types.d.ts.map +1 -0
  116. package/dist/cli/verify-proof/idl/types.js +10 -0
  117. package/dist/cli/verify-proof/idl/types.js.map +1 -0
  118. package/dist/cli/verify-proof/manifest-validator.d.mts +19 -0
  119. package/dist/cli/verify-proof/manifest-validator.d.mts.map +1 -0
  120. package/dist/cli/verify-proof/manifest-validator.mjs +182 -0
  121. package/dist/cli/verify-proof/manifest-validator.mjs.map +1 -0
  122. package/dist/cli/verify-proof/resolver.d.mts +105 -0
  123. package/dist/cli/verify-proof/resolver.d.mts.map +1 -0
  124. package/dist/cli/verify-proof/resolver.mjs +320 -0
  125. package/dist/cli/verify-proof/resolver.mjs.map +1 -0
  126. package/dist/cli/verify-proof/runtime-detect.d.mts +30 -0
  127. package/dist/cli/verify-proof/runtime-detect.d.mts.map +1 -0
  128. package/dist/cli/verify-proof/runtime-detect.mjs +209 -0
  129. package/dist/cli/verify-proof/runtime-detect.mjs.map +1 -0
  130. package/dist/cli/verify-proof/trust.d.mts +70 -0
  131. package/dist/cli/verify-proof/trust.d.mts.map +1 -0
  132. package/dist/cli/verify-proof/trust.mjs +174 -0
  133. package/dist/cli/verify-proof/trust.mjs.map +1 -0
  134. package/dist/driver-sdk/conformance.d.ts +41 -0
  135. package/dist/driver-sdk/conformance.d.ts.map +1 -0
  136. package/dist/driver-sdk/conformance.js +123 -0
  137. package/dist/driver-sdk/conformance.js.map +1 -0
  138. package/dist/driver-sdk/errors.d.ts +75 -0
  139. package/dist/driver-sdk/errors.d.ts.map +1 -0
  140. package/dist/driver-sdk/errors.js +80 -0
  141. package/dist/driver-sdk/errors.js.map +1 -0
  142. package/dist/driver-sdk/idl.d.ts +221 -0
  143. package/dist/driver-sdk/idl.d.ts.map +1 -0
  144. package/dist/driver-sdk/idl.js +140 -0
  145. package/dist/driver-sdk/idl.js.map +1 -0
  146. package/dist/driver-sdk/index.d.ts +28 -0
  147. package/dist/driver-sdk/index.d.ts.map +1 -0
  148. package/dist/driver-sdk/index.js +28 -0
  149. package/dist/driver-sdk/index.js.map +1 -0
  150. package/dist/driver-sdk/manifest.d.ts +93 -0
  151. package/dist/driver-sdk/manifest.d.ts.map +1 -0
  152. package/dist/driver-sdk/manifest.js +12 -0
  153. package/dist/driver-sdk/manifest.js.map +1 -0
  154. package/dist/driver-sdk/retry.d.ts +33 -0
  155. package/dist/driver-sdk/retry.d.ts.map +1 -0
  156. package/dist/driver-sdk/retry.js +50 -0
  157. package/dist/driver-sdk/retry.js.map +1 -0
  158. package/dist/plugin/.claude-plugin/plugin.json +4 -1
  159. package/dist/plugin/agents/apt-planner.md +39 -7
  160. package/dist/plugin/skills/apt/SKILL.md +75 -6
  161. package/dist/plugin/skills/apt-caveman/SKILL.md +64 -0
  162. package/dist/plugin/skills/apt-debug/SKILL.md +36 -4
  163. package/dist/plugin/skills/apt-debug/appendices/diagnose-discipline.md +2 -11
  164. package/dist/plugin/skills/apt-discuss/SKILL.md +4 -4
  165. package/dist/plugin/skills/apt-discuss/appendices/grill-discipline.md +2 -11
  166. package/dist/plugin/skills/apt-discuss/appendices/zoom-out-helper.md +0 -8
  167. package/dist/plugin/skills/apt-execute/SKILL.md +27 -4
  168. package/dist/plugin/skills/apt-execute/appendices/tdd-mode.md +2 -10
  169. package/dist/plugin/skills/apt-fan-out/SKILL.md +36 -1
  170. package/dist/plugin/skills/apt-handoff/SKILL.md +228 -0
  171. package/dist/plugin/skills/apt-improve/DEEPENING.md +0 -8
  172. package/dist/plugin/skills/apt-improve/INTERFACE-DESIGN.md +0 -8
  173. package/dist/plugin/skills/apt-improve/LANGUAGE.md +0 -8
  174. package/dist/plugin/skills/apt-improve/SKILL.md +4 -4
  175. package/dist/plugin/skills/apt-plan/SKILL.md +73 -5
  176. package/dist/plugin/skills/apt-plan/adapters/conductor.md +1 -1
  177. package/dist/plugin/skills/apt-pr-review/SKILL.md +1 -1
  178. package/dist/plugin/skills/apt-prototype/LOGIC.md +0 -8
  179. package/dist/plugin/skills/apt-prototype/SKILL.md +4 -4
  180. package/dist/plugin/skills/apt-prototype/UI.md +0 -8
  181. package/dist/plugin/skills/apt-quick/SKILL.md +31 -1
  182. package/dist/plugin/skills/apt-research/SKILL.md +526 -0
  183. package/dist/plugin/skills/apt-research/appendices/budget-loop.md +397 -0
  184. package/dist/plugin/skills/apt-research/appendices/claim-graph-schema.md +206 -0
  185. package/dist/plugin/skills/apt-research/appendices/domain-tools.md +236 -0
  186. package/dist/plugin/skills/apt-resume/SKILL.md +54 -31
  187. package/dist/plugin/skills/apt-run/SKILL.md +114 -16
  188. package/dist/plugin/skills/apt-setup/SKILL.md +97 -8
  189. package/dist/plugin/skills/apt-ship/SKILL.md +165 -9
  190. package/dist/plugin/skills/apt-spar/SKILL.md +84 -23
  191. package/dist/plugin/skills/apt-triage/AGENT-BRIEF.md +0 -8
  192. package/dist/plugin/skills/apt-triage/OUT-OF-SCOPE.md +0 -7
  193. package/dist/plugin/skills/apt-triage/SKILL.md +2 -2
  194. package/dist/plugin/skills/apt-update/SKILL.md +74 -17
  195. package/dist/plugin/skills/apt-verify-proof/SKILL.md +87 -1
  196. package/dist/plugin/skills/apt-zoom-out/SKILL.md +3 -3
  197. package/dist/schemas/quick-task.d.ts +17 -17
  198. package/dist/schemas/quick-task.d.ts.map +1 -1
  199. package/dist/schemas/quick-task.js +2 -2
  200. package/dist/schemas/quick-task.js.map +1 -1
  201. package/dist/types/config.d.ts +33 -0
  202. package/dist/types/config.d.ts.map +1 -1
  203. package/dist/types/state.d.ts +1 -1
  204. package/dist/types/state.d.ts.map +1 -1
  205. package/dist/types/task-record.d.ts +1 -1
  206. package/dist/types/task-record.d.ts.map +1 -1
  207. package/drivers/.gitkeep +0 -0
  208. package/drivers/api/README.md +40 -0
  209. package/drivers/api/driver.mjs +59 -0
  210. package/drivers/api/manifest.json +26 -0
  211. package/drivers/browser/README.md +105 -0
  212. package/drivers/browser/driver.mjs +134 -0
  213. package/drivers/browser/manifest.json +35 -0
  214. package/drivers/cli/README.md +44 -0
  215. package/drivers/cli/driver.mjs +62 -0
  216. package/drivers/cli/manifest.json +28 -0
  217. package/drivers/electron/README.md +64 -0
  218. package/drivers/electron/driver.mjs +87 -0
  219. package/drivers/electron/manifest.json +37 -0
  220. package/package.json +138 -133
  221. package/prompts/planner.md +1 -1
  222. package/prompts/spec_writer.md +1 -1
  223. package/skills/apt/SKILL.md +75 -6
  224. package/skills/apt-caveman/SKILL.md +64 -0
  225. package/skills/apt-debug/SKILL.md +36 -4
  226. package/skills/apt-debug/appendices/diagnose-discipline.md +2 -11
  227. package/skills/apt-discuss/SKILL.md +4 -4
  228. package/skills/apt-discuss/appendices/grill-discipline.md +2 -11
  229. package/skills/apt-discuss/appendices/zoom-out-helper.md +0 -8
  230. package/skills/apt-execute/SKILL.md +27 -4
  231. package/skills/apt-execute/appendices/tdd-mode.md +2 -10
  232. package/skills/apt-fan-out/SKILL.md +36 -1
  233. package/skills/apt-handoff/SKILL.md +228 -0
  234. package/skills/apt-improve/DEEPENING.md +0 -8
  235. package/skills/apt-improve/INTERFACE-DESIGN.md +0 -8
  236. package/skills/apt-improve/LANGUAGE.md +0 -8
  237. package/skills/apt-improve/SKILL.md +4 -4
  238. package/skills/apt-plan/SKILL.md +73 -5
  239. package/skills/apt-plan/adapters/conductor.md +1 -1
  240. package/skills/apt-planner.md +17 -1
  241. package/skills/apt-pr-review/SKILL.md +1 -1
  242. package/skills/apt-prototype/LOGIC.md +0 -8
  243. package/skills/apt-prototype/SKILL.md +4 -4
  244. package/skills/apt-prototype/UI.md +0 -8
  245. package/skills/apt-quick/SKILL.md +31 -1
  246. package/skills/apt-research/SKILL.md +526 -0
  247. package/skills/apt-research/appendices/budget-loop.md +397 -0
  248. package/skills/apt-research/appendices/claim-graph-schema.md +206 -0
  249. package/skills/apt-research/appendices/domain-tools.md +236 -0
  250. package/skills/apt-resume/SKILL.md +54 -31
  251. package/skills/apt-run/SKILL.md +114 -16
  252. package/skills/apt-setup/SKILL.md +97 -8
  253. package/skills/apt-ship/SKILL.md +165 -9
  254. package/skills/apt-spar/SKILL.md +84 -23
  255. package/skills/apt-triage/AGENT-BRIEF.md +0 -8
  256. package/skills/apt-triage/OUT-OF-SCOPE.md +0 -7
  257. package/skills/apt-triage/SKILL.md +2 -2
  258. package/skills/apt-update/SKILL.md +74 -17
  259. package/skills/apt-verify-proof/SKILL.md +87 -1
  260. package/skills/apt-zoom-out/SKILL.md +3 -3
  261. package/src/cli/commands/adr.mjs +1 -1
  262. package/src/cli/commands/audit-branch-current.mjs +250 -0
  263. package/src/cli/commands/audit.mjs +13 -2
  264. package/src/cli/commands/check-version.mjs +20 -1
  265. package/src/cli/commands/context.mjs +3 -3
  266. package/src/cli/commands/coverage-check.mjs +126 -44
  267. package/src/cli/commands/detect-runtime.mjs +143 -0
  268. package/src/cli/commands/driver-doctor.mjs +294 -0
  269. package/src/cli/commands/gate.mjs +15 -0
  270. package/src/cli/commands/init.mjs +9 -6
  271. package/src/cli/commands/install-from-source.mjs +1 -0
  272. package/src/cli/commands/modes.mjs +1 -1
  273. package/src/cli/commands/route.mjs +42 -1
  274. package/src/cli/commands/triage.mjs +1 -1
  275. package/src/cli/config/load.mjs +9 -3
  276. package/src/cli/coordination/auto-emit-artifact-ready.mjs +1 -1
  277. package/src/cli/coordination/event-schema.mjs +19 -0
  278. package/src/cli/coverage-check/user-outcomes.mjs +273 -0
  279. package/src/cli/dispatch.mjs +17 -1
  280. package/src/cli/gate/context.mjs +20 -1
  281. package/src/cli/gate/gates/consistency-check.mjs +1 -0
  282. package/src/cli/gate/gates/review-clean.mjs +1 -0
  283. package/src/cli/gate/gates/verify-approved.mjs +1 -0
  284. package/src/cli/gate/registry.mjs +11 -1
  285. package/src/cli/host/detect.mjs +1 -1
  286. package/src/cli/install/find-workspace-framework-version.mjs +88 -0
  287. package/src/cli/install/install-from-source.mjs +276 -0
  288. package/src/cli/install/install-kernel.mjs +24 -0
  289. package/src/cli/install/legacy-paths.mjs +164 -39
  290. package/src/cli/install/runtime-detect.mjs +1 -1
  291. package/src/cli/install/runtime-migrate.mjs +63 -30
  292. package/src/cli/install/transforms/codex.mjs +26 -17
  293. package/src/cli/skill-author/contract.mjs +2 -2
  294. package/src/cli/verify-proof/.gitkeep +0 -0
  295. package/src/cli/verify-proof/audit.mjs +55 -0
  296. package/src/cli/verify-proof/exec.mjs +74 -0
  297. package/src/cli/verify-proof/idl/index.mjs +23 -0
  298. package/src/cli/verify-proof/idl/types.ts +42 -0
  299. package/src/cli/verify-proof/manifest-schema.json +212 -0
  300. package/src/cli/verify-proof/manifest-validator.mjs +184 -0
  301. package/src/cli/verify-proof/resolver.mjs +330 -0
  302. package/src/cli/verify-proof/runtime-detect.mjs +211 -0
  303. package/src/cli/verify-proof/trust.mjs +187 -0
  304. package/templates/adr-format.md +0 -8
  305. package/templates/aperant-claude-md-appendix.md +1 -1
  306. package/templates/context-format.md +1 -9
  307. package/templates/proof-verification.md +51 -8
  308. package/workflows/verify-proof.md +130 -19
@@ -0,0 +1,211 @@
1
+ /**
2
+ * runtime-detect.mjs — pure project-shape capability detection.
3
+ *
4
+ * Extracted from packages/core/src/prompts/prompt-loader.ts:572-595 so the
5
+ * framework CLI is self-contained (no @aperant/core dep). Same heuristics:
6
+ * read package.json's dependencies + devDependencies, return a capability
7
+ * record the resolver can score against.
8
+ *
9
+ * Pattern: pure data in → pure data out. No filesystem walking, no exec.
10
+ * Use loadRuntimeCapabilities(projectDir) for the cwd-rooted variant.
11
+ *
12
+ * v0.8.6 — `loadRuntimeCapabilities` now ALSO walks `apps/*` by default
13
+ * (configurable via the second arg `{monorepo_globs}`) and OR-merges
14
+ * each match's `package.json` capabilities with the root. The default
15
+ * `['apps/*']` matches the canonical Aperant monorepo layout; adopters
16
+ * whose monorepo lives elsewhere (e.g. `packages/apps/*`) pass a
17
+ * custom `monorepo_globs`. Setting `monorepo_globs: []` disables the
18
+ * walk entirely (root-only escape hatch).
19
+ */
20
+
21
+ import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs'
22
+ import { join, resolve } from 'node:path'
23
+
24
+ /**
25
+ * @typedef {Object} RuntimeCapabilities
26
+ * @property {boolean} is_electron
27
+ * @property {boolean} is_tauri
28
+ * @property {boolean} is_nextjs
29
+ * @property {boolean} is_nuxt
30
+ * @property {boolean} is_web_frontend
31
+ * @property {boolean} is_expo
32
+ * @property {boolean} is_react_native
33
+ * @property {boolean} has_node
34
+ */
35
+
36
+ /** A capability record with every flag defaulted to false. */
37
+ function emptyCapabilities() {
38
+ return {
39
+ is_electron: false,
40
+ is_tauri: false,
41
+ is_nextjs: false,
42
+ is_nuxt: false,
43
+ is_web_frontend: false,
44
+ is_expo: false,
45
+ is_react_native: false,
46
+ has_node: false,
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Detect runtime capabilities from a parsed package.json object.
52
+ *
53
+ * @param {Record<string, unknown> | null} packageJson
54
+ * @returns {RuntimeCapabilities}
55
+ */
56
+ export function detectFromPackageJson(packageJson) {
57
+ const capabilities = emptyCapabilities()
58
+ if (packageJson === null || typeof packageJson !== 'object') return capabilities
59
+
60
+ const allDeps = {
61
+ ...(packageJson.dependencies ?? {}) /** @type {Record<string,string>} */,
62
+ ...(packageJson.devDependencies ?? {}) /** @type {Record<string,string>} */,
63
+ }
64
+ const deps = new Set(Object.keys(allDeps).map((k) => k.toLowerCase()))
65
+
66
+ if (deps.has('electron') || [...deps].some((d) => d.startsWith('@electron'))) {
67
+ capabilities.is_electron = true
68
+ }
69
+ if (deps.has('@tauri-apps/api') || deps.has('tauri')) {
70
+ capabilities.is_tauri = true
71
+ }
72
+ if (deps.has('expo')) capabilities.is_expo = true
73
+ if (deps.has('react-native')) capabilities.is_react_native = true
74
+
75
+ // Web frontends — react/vue/svelte/angular/solid → frontend. BUT: an
76
+ // Electron app commonly bundles React as its renderer; in that case the
77
+ // runtime is "electron", not "web_frontend". Electron + react-native +
78
+ // expo + tauri all win over the web flag.
79
+ const isNativeContainer =
80
+ capabilities.is_electron ||
81
+ capabilities.is_tauri ||
82
+ capabilities.is_react_native ||
83
+ capabilities.is_expo
84
+ const webFrameworks = ['react', 'vue', 'svelte', 'angular', 'solid']
85
+ if (!isNativeContainer && webFrameworks.some((f) => deps.has(f))) {
86
+ capabilities.is_web_frontend = true
87
+ }
88
+
89
+ if (deps.has('next')) {
90
+ capabilities.is_nextjs = true
91
+ capabilities.is_web_frontend = true
92
+ }
93
+ if (deps.has('nuxt')) {
94
+ capabilities.is_nuxt = true
95
+ capabilities.is_web_frontend = true
96
+ }
97
+ if (deps.has('vite') && !isNativeContainer) {
98
+ capabilities.is_web_frontend = true
99
+ }
100
+
101
+ // Node-ish? Anything with a package.json + an entry point counts.
102
+ if (typeof packageJson.main === 'string' || typeof packageJson.module === 'string') {
103
+ capabilities.has_node = true
104
+ }
105
+ if (!capabilities.has_node && packageJson.scripts && typeof packageJson.scripts === 'object') {
106
+ capabilities.has_node = true
107
+ }
108
+
109
+ return capabilities
110
+ }
111
+
112
+ /**
113
+ * Load and detect capabilities from a project directory's package.json.
114
+ * v0.8.6 — also walks `monorepo_globs` (default `['apps/*']`) and
115
+ * OR-merges each match's capabilities with the root.
116
+ *
117
+ * @param {string} projectDir
118
+ * @param {{monorepo_globs?: string[]}} [opts]
119
+ * @returns {RuntimeCapabilities}
120
+ */
121
+ export function loadRuntimeCapabilities(projectDir, opts) {
122
+ const root = resolve(projectDir)
123
+ const rootPkgPath = join(root, 'package.json')
124
+ let rootCaps = emptyCapabilities()
125
+ if (existsSync(rootPkgPath)) {
126
+ try {
127
+ rootCaps = detectFromPackageJson(JSON.parse(readFileSync(rootPkgPath, 'utf-8')))
128
+ } catch {
129
+ /* fall through with empty caps */
130
+ }
131
+ }
132
+
133
+ // Default to the canonical Aperant monorepo layout. Passing `[]`
134
+ // disables the walk entirely (root-only escape hatch per spec ID-05).
135
+ const globs = opts && Array.isArray(opts.monorepo_globs) ? opts.monorepo_globs : ['apps/*']
136
+ if (globs.length === 0) return rootCaps
137
+
138
+ let merged = rootCaps
139
+ for (const glob of globs) {
140
+ const matches = expandSimpleGlob(root, glob)
141
+ for (const dir of matches) {
142
+ const childPkgPath = join(dir, 'package.json')
143
+ if (!existsSync(childPkgPath)) continue
144
+ try {
145
+ const childCaps = detectFromPackageJson(JSON.parse(readFileSync(childPkgPath, 'utf-8')))
146
+ merged = orMergeCapabilities(merged, childCaps)
147
+ } catch {
148
+ /* skip malformed package.json */
149
+ }
150
+ }
151
+ }
152
+ return merged
153
+ }
154
+
155
+ /**
156
+ * Expand a simple `prefix/*` glob against the root. Supports ONLY the
157
+ * `<dir>/*` shape (matches each direct subdirectory of `<dir>`). Other
158
+ * glob syntax is intentionally rejected — v1 keeps the dep surface
159
+ * small. Fails closed on malformed patterns (returns []).
160
+ *
161
+ * @param {string} root
162
+ * @param {string} glob
163
+ * @returns {string[]}
164
+ */
165
+ function expandSimpleGlob(root, glob) {
166
+ if (typeof glob !== 'string' || !glob.endsWith('/*')) return []
167
+ const prefix = glob.slice(0, -2)
168
+ if (prefix.length === 0 || prefix.includes('*')) return []
169
+ const dir = join(root, prefix)
170
+ if (!existsSync(dir)) return []
171
+ let entries
172
+ try {
173
+ entries = readdirSync(dir)
174
+ } catch {
175
+ return []
176
+ }
177
+ const matches = []
178
+ for (const name of entries) {
179
+ const sub = join(dir, name)
180
+ try {
181
+ if (statSync(sub).isDirectory()) matches.push(sub)
182
+ } catch {
183
+ /* skip unreadable entries */
184
+ }
185
+ }
186
+ return matches
187
+ }
188
+
189
+ /**
190
+ * Boolean OR-merge of two capability records. Any flag true in either
191
+ * input is true in the output. The "Electron wins over web" rule from
192
+ * `detectFromPackageJson` is preserved when this is called repeatedly —
193
+ * each child's flags are honest about that child's own shape, and the
194
+ * OR-merge just unions them.
195
+ *
196
+ * @param {RuntimeCapabilities} a
197
+ * @param {RuntimeCapabilities} b
198
+ * @returns {RuntimeCapabilities}
199
+ */
200
+ function orMergeCapabilities(a, b) {
201
+ return {
202
+ is_electron: a.is_electron || b.is_electron,
203
+ is_tauri: a.is_tauri || b.is_tauri,
204
+ is_nextjs: a.is_nextjs || b.is_nextjs,
205
+ is_nuxt: a.is_nuxt || b.is_nuxt,
206
+ is_web_frontend: a.is_web_frontend || b.is_web_frontend,
207
+ is_expo: a.is_expo || b.is_expo,
208
+ is_react_native: a.is_react_native || b.is_react_native,
209
+ has_node: a.has_node || b.has_node,
210
+ }
211
+ }
@@ -0,0 +1,187 @@
1
+ /**
2
+ * trust.mjs — driver trust enforcement (TOFU lock + policy file + decision).
3
+ *
4
+ * Implements ID-03:
5
+ * - 4 trust tiers: first_party_signed (default-allow), community_verified,
6
+ * community_unverified, local_unverified.
7
+ * - Default policy: only first_party_signed auto-runs.
8
+ * - Non-first-party drivers require one-time user approval; approval is
9
+ * bound to a TOFU lock at .aperant/driver-tofu.json keyed by
10
+ * {driver_id, version, manifest_sha256, artifact_digest}. Any drift
11
+ * re-prompts.
12
+ *
13
+ * The audit log (audit.mjs) writes one line per decision (allow/deny).
14
+ * The execution layer (exec.mjs) enforces structured-only local_command.
15
+ *
16
+ * This module is decision-only: it does NOT prompt the user. The caller
17
+ * (workflow runner / apt-tools driver doctor) decides whether to invoke
18
+ * the prompt UX, then calls recordApproval() to persist the TOFU entry.
19
+ */
20
+
21
+ import { createHash } from 'node:crypto'
22
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
23
+ import { dirname, join, resolve } from 'node:path'
24
+
25
+ /**
26
+ * @typedef {import('@aperant/framework/driver-sdk').DriverManifest} DriverManifest
27
+ */
28
+
29
+ const TOFU_PATH = '.aperant/driver-tofu.json'
30
+ const POLICY_PATH = '.aperant/policy.json'
31
+
32
+ /**
33
+ * Compute a stable manifest hash for TOFU + audit lines.
34
+ * @param {DriverManifest} manifest
35
+ * @returns {string}
36
+ */
37
+ export function manifestSha256(manifest) {
38
+ const stable = JSON.stringify(manifest, Object.keys(manifest).sort())
39
+ return createHash('sha256').update(stable).digest('hex')
40
+ }
41
+
42
+ /**
43
+ * Read the TOFU lock file. Returns an empty object if missing or malformed.
44
+ * @param {string} projectDir
45
+ * @returns {Record<string, {manifest_sha256: string, artifact_digest?: string, approved_at: string, trust_tier: string}>}
46
+ */
47
+ export function readTofuLock(projectDir) {
48
+ const path = join(resolve(projectDir), TOFU_PATH)
49
+ if (!existsSync(path)) return {}
50
+ try {
51
+ return JSON.parse(readFileSync(path, 'utf-8'))
52
+ } catch {
53
+ return {}
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Persist a TOFU lock file (atomic-ish: write + rename not used since we
59
+ * append-only to a tiny JSON; conflicting writes from parallel agents
60
+ * would clobber an entry but `.aperant/driver-tofu.json` is per-project,
61
+ * not per-agent).
62
+ *
63
+ * @param {string} projectDir
64
+ * @param {Record<string, unknown>} tofu
65
+ */
66
+ export function writeTofuLock(projectDir, tofu) {
67
+ const path = join(resolve(projectDir), TOFU_PATH)
68
+ mkdirSync(dirname(path), { recursive: true })
69
+ writeFileSync(path, JSON.stringify(tofu, null, 2) + '\n', 'utf-8')
70
+ }
71
+
72
+ /**
73
+ * Build the canonical TOFU key for a (driver, version) pair. The hash is
74
+ * NOT part of the key — the value's manifest_sha256 + artifact_digest
75
+ * detect drift.
76
+ * @param {string} driverId
77
+ * @param {string} version
78
+ * @returns {string}
79
+ */
80
+ export function tofuKey(driverId, version) {
81
+ return `${driverId}@${version}`
82
+ }
83
+
84
+ /**
85
+ * Read the project's policy file. Returns the default policy if missing.
86
+ * @param {string} projectDir
87
+ * @returns {{mode: 'first_party_only' | 'allowlist', allowed: string[], denied: string[]}}
88
+ */
89
+ export function readPolicy(projectDir) {
90
+ const path = join(resolve(projectDir), POLICY_PATH)
91
+ const defaults = { mode: 'first_party_only', allowed: [], denied: [] }
92
+ if (!existsSync(path)) return defaults
93
+ try {
94
+ const parsed = JSON.parse(readFileSync(path, 'utf-8'))
95
+ return {
96
+ mode: parsed.mode === 'allowlist' ? 'allowlist' : 'first_party_only',
97
+ allowed: Array.isArray(parsed.allowed) ? parsed.allowed : [],
98
+ denied: Array.isArray(parsed.denied) ? parsed.denied : [],
99
+ }
100
+ } catch {
101
+ return defaults
102
+ }
103
+ }
104
+
105
+ /**
106
+ * Decide whether a driver invocation is allowed.
107
+ *
108
+ * @param {DriverManifest} manifest
109
+ * @param {ReturnType<typeof readTofuLock>} tofu
110
+ * @param {ReturnType<typeof readPolicy>} policy
111
+ * @returns {{decision: 'allow' | 'deny' | 'requires_approval', reason: string, expected_manifest_sha256?: string}}
112
+ */
113
+ export function decide(manifest, tofu, policy) {
114
+ const tier = manifest.trust?.tier
115
+ const driverId = manifest.driverId
116
+ const version = manifest.version
117
+
118
+ // Policy denylist always wins.
119
+ if (policy.denied.includes(driverId)) {
120
+ return { decision: 'deny', reason: `policy denies driver ${driverId}` }
121
+ }
122
+
123
+ // first_party_signed: default-allow under either policy mode (unless
124
+ // explicitly denied above).
125
+ if (tier === 'first_party_signed') {
126
+ return { decision: 'allow', reason: 'first_party_signed driver — default allow' }
127
+ }
128
+
129
+ // first_party_only mode blocks every other tier.
130
+ if (policy.mode === 'first_party_only') {
131
+ return {
132
+ decision: 'deny',
133
+ reason: `policy.mode=first_party_only blocks ${tier} driver`,
134
+ }
135
+ }
136
+
137
+ // allowlist mode: require explicit allow entry AND a valid TOFU lock.
138
+ if (!policy.allowed.includes(driverId)) {
139
+ return {
140
+ decision: 'deny',
141
+ reason: `policy.mode=allowlist but ${driverId} not in allowed[]`,
142
+ }
143
+ }
144
+
145
+ // Check TOFU lock.
146
+ const key = tofuKey(driverId, version)
147
+ const entry = tofu[key]
148
+ const currentHash = manifestSha256(manifest)
149
+
150
+ if (!entry) {
151
+ return {
152
+ decision: 'requires_approval',
153
+ reason: `no TOFU entry for ${key} — first run requires approval`,
154
+ expected_manifest_sha256: currentHash,
155
+ }
156
+ }
157
+
158
+ if (entry.manifest_sha256 !== currentHash) {
159
+ return {
160
+ decision: 'requires_approval',
161
+ reason: `TOFU manifest_sha256 drift for ${key} — driver changed since approval, re-prompt required`,
162
+ expected_manifest_sha256: currentHash,
163
+ }
164
+ }
165
+
166
+ return { decision: 'allow', reason: `TOFU entry valid for ${key}` }
167
+ }
168
+
169
+ /**
170
+ * Persist a one-time approval for a (driver, version) pair into the TOFU
171
+ * lock. The caller is responsible for actually prompting the user; this
172
+ * function just writes the entry.
173
+ *
174
+ * @param {string} projectDir
175
+ * @param {DriverManifest} manifest
176
+ */
177
+ export function recordApproval(projectDir, manifest) {
178
+ const tofu = readTofuLock(projectDir)
179
+ const key = tofuKey(manifest.driverId, manifest.version)
180
+ tofu[key] = {
181
+ manifest_sha256: manifestSha256(manifest),
182
+ artifact_digest: manifest.trust?.integrity?.artifact_digest,
183
+ approved_at: new Date().toISOString(),
184
+ trust_tier: manifest.trust?.tier,
185
+ }
186
+ writeTofuLock(projectDir, tofu)
187
+ }
@@ -1,11 +1,3 @@
1
- <!--
2
- Adapted from Matt Pocock's MIT-licensed skill suite:
3
- https://github.com/mattpocock/skills/blob/main/skills/engineering/grill-with-docs/ADR-FORMAT.md
4
- Licensed under MIT. Modifications: ported as the Aperant ADR template
5
- emitted by `apt-tools adr write` with Nygard's 3-gate filter
6
- (hard-to-reverse AND surprising-without-context AND real-trade-off).
7
- -->
8
-
9
1
  # ADR-NNNN: {Title}
10
2
 
11
3
  **Status:** {Proposed | Accepted | Deprecated | Superseded}
@@ -1,6 +1,6 @@
1
1
  This project uses the [Aperant Framework](https://github.com/Mikalsen-AI/aperant-cloud-desktop/tree/main/packages/framework) for AI-assisted development.
2
2
 
3
- ### Fast Path Guarantee (Pocock adoption ID-05)
3
+ ### Fast Path Guarantee
4
4
 
5
5
  QUICK track is **hard-exempt** from PRD-shaped spec.md, HITL/AFK metadata,
6
6
  vertical-slice subtask schema, TDD iron law (`apt:execute --tdd`), triage
@@ -1,15 +1,7 @@
1
- <!--
2
- Adapted from Matt Pocock's MIT-licensed skill suite:
3
- https://github.com/mattpocock/skills/blob/main/skills/engineering/grill-with-docs/CONTEXT-FORMAT.md
4
- Licensed under MIT. Modifications: ported as the Aperant CONTEXT.md
5
- template emitted by `apt-tools context write`. Adds the "Project glossary"
6
- header and example dialogue section.
7
- -->
8
-
9
1
  # Project glossary (CONTEXT.md)
10
2
 
11
3
  This file is the persistent project glossary. Each term-block carries the
12
- following fields per Pocock's CONTEXT.md format:
4
+ following fields per the Aperant CONTEXT.md format:
13
5
 
14
6
  - **{Term}** — definition (one line).
15
7
  - **Aliases to avoid:** comma-separated synonyms or near-synonyms that
@@ -18,7 +18,17 @@ JSON schema reference for `.aperant/proof/{app}/{date}/{run}/verification.json`
18
18
  "updated": "{ISO timestamp}",
19
19
  "spec": "{spec.md path or task description}",
20
20
  "verdict": "pending | approved | approved_with_notes | rejected",
21
- "tools": ["electron", "puppeteer", "cdp_video"]
21
+ "tools": ["electron", "puppeteer", "cdp_video"],
22
+ "selected_driver": {
23
+ "driverId": "electron",
24
+ "version": "0.1.0",
25
+ "manifest_sha256": "abc123...",
26
+ "trust_tier": "first_party_signed",
27
+ "sandbox_profile": "workspace_write"
28
+ },
29
+ "fallback_attempts": [
30
+ { "driver_id": "browser", "reason": "missing required capabilities: dump_state" }
31
+ ]
22
32
  },
23
33
 
24
34
  "automated_checks": {
@@ -43,7 +53,13 @@ JSON schema reference for `.aperant/proof/{app}/{date}/{run}/verification.json`
43
53
  "tested_count": 0,
44
54
  "skipped_count": 0,
45
55
  "percent": 0,
46
- "gate_result": "pass | warn | block"
56
+ "gate_result": "pass | warn | block",
57
+ "by_surface": {
58
+ "electron": { "tested": 0, "total": 0 },
59
+ "web": { "tested": 0, "total": 0 },
60
+ "cli": { "tested": 0, "total": 0 },
61
+ "api": { "tested": 0, "total": 0 }
62
+ }
47
63
  },
48
64
 
49
65
  "tests": [
@@ -51,14 +67,33 @@ JSON schema reference for `.aperant/proof/{app}/{date}/{run}/verification.json`
51
67
  "id": "TEST-01",
52
68
  "registry_id": "terminals-grid-auto-tile",
53
69
  "name": "Three Terminal Grid Auto-Tile",
70
+ "outcome_id": "O3",
71
+ "surface": "electron",
72
+ "transport": "mcp",
54
73
  "precondition": "2 terminals active",
55
74
  "action": "Press Cmd+T",
56
75
  "expected": "Grid reflows to 2+1 layout",
57
76
  "actual": "Grid correctly tiled to 2+1",
58
- "evidence": "screenshots/03-grid.png",
77
+ "evidence": "electron/screenshots/03-grid.png",
59
78
  "analysis": "Screenshot shows three terminal panes. Top row has 2 equal-width panes, bottom row has 1 centered pane. Grid lines are clean, no overlap or gap artifacts. Each pane shows an active shell prompt with correct path.",
60
79
  "result": "pass | fail | blocked | skip",
61
80
  "severity": null
81
+ },
82
+ {
83
+ "id": "TEST-02",
84
+ "registry_id": "settings-theme-picker",
85
+ "name": "Theme picker — cross-surface",
86
+ "outcome_id": "O5",
87
+ "surface": "web",
88
+ "transport": "skill",
89
+ "precondition": "App loaded in web shell",
90
+ "action": "Click 'Dark mode' in the theme picker",
91
+ "expected": "Background flips to dark; persists across reload",
92
+ "actual": "Dark mode applied, persisted across reload",
93
+ "evidence": "web/screenshots/05-dark-mode.png",
94
+ "analysis": "Screenshot shows app body with #111 background, all text legible on dark surface. After reload, dark mode is still active — localStorage entry confirmed.",
95
+ "result": "pass",
96
+ "severity": null
62
97
  }
63
98
  ],
64
99
 
@@ -66,8 +101,8 @@ JSON schema reference for `.aperant/proof/{app}/{date}/{run}/verification.json`
66
101
  {
67
102
  "registry_id": "terminals-settings-scrollback-slider",
68
103
  "name": "Scrollback buffer slider",
69
- "reason": "blocked_by_platform",
70
- "detail": "Slider interaction not supported via current MCP tools"
104
+ "reason": "unsatisfied_optional_capability",
105
+ "detail": "Selected driver does not implement the IDL verb required for slider interaction"
71
106
  }
72
107
  ],
73
108
 
@@ -149,7 +184,9 @@ JSON schema reference for `.aperant/proof/{app}/{date}/{run}/verification.json`
149
184
  | `updated` | string | OVERWRITE | ISO timestamp, updated on every write |
150
185
  | `spec` | string | IMMUTABLE | Path to spec or task description |
151
186
  | `verdict` | string | OVERWRITE | Derived from dimension scores after all tests |
152
- | `tools` | array | IMMUTABLE | MCP tools detected as available |
187
+ | `tools` | array | IMMUTABLE | MCP tools / auxiliary capture tools detected as available |
188
+ | `selected_driver` | object | IMMUTABLE | The verify-proof driver chosen by the resolver per ID-02. Fields: `driverId`, `version`, `manifest_sha256`, `trust_tier`, `sandbox_profile`. |
189
+ | `fallback_attempts` | array | IMMUTABLE | Drivers the resolver REJECTED before selecting. Each entry has `driver_id` + `reason` (missing capabilities, stability filter, hash drift, policy denial). Audit trail for coverage-loss decisions. |
153
190
 
154
191
  ### automated_checks
155
192
  Each check has `status` (pass/fail) and `output` (captured terminal output). These are populated in step 3 before any manual testing begins.
@@ -163,6 +200,7 @@ Each check has `status` (pass/fail) and `output` (captured terminal output). The
163
200
  | `skipped_count` | number | Number of entries in the skipped array |
164
201
  | `percent` | number | `tested_count / registered_count * 100` |
165
202
  | `gate_result` | string | `block` if < 50%, `warn` if < 80%, `pass` if >= 80% |
203
+ | `by_surface` | object | v0.8.6 — per-surface rollup. Each surface key (`electron`, `web`, `cli`, `api`) maps to `{tested, total}` so reviewers can tell which surface fell short. |
166
204
 
167
205
  ### tests
168
206
  Each test entry tracks one sub-feature verification. Fields:
@@ -172,11 +210,14 @@ Each test entry tracks one sub-feature verification. Fields:
172
210
  | `id` | string | IMMUTABLE | Sequential ID: TEST-01, TEST-02, etc. |
173
211
  | `registry_id` | string | IMMUTABLE | Must match a sub-feature `id` from `.aperant/features/{area}.json` |
174
212
  | `name` | string | IMMUTABLE | Human-readable test name |
213
+ | `outcome_id` | string | IMMUTABLE | v0.8.6 — positional ID from `## User Outcomes` (e.g. `O1`, `O2`). Synthesized by `parseUserOutcomes`; passed through verbatim by `resolveDriversForOutcomes`. Optional for legacy specs without `## User Outcomes`. |
214
+ | `surface` | string | IMMUTABLE | v0.8.6 — one of `electron | web | cli | api`. Drives the per-surface screenshot path and the `coverage.by_surface` rollup. Cross-surface outcomes (`[electron+web]`) produce TWO test entries with the same `outcome_id` and different `surface` values. |
215
+ | `transport` | string | OVERWRITE | v0.8.6 — `skill | mcp | binary | http`. Populated by the workflow runner from the dispatcher's directive (per Step 7a of `workflows/verify-proof.md`). For browser-driver web outcomes on Skill-capable hosts the value is `"skill"`; on legacy MCP hosts it falls back to `"mcp"`. |
175
216
  | `precondition` | string | IMMUTABLE | What must be true before testing |
176
217
  | `action` | string | IMMUTABLE | Specific action to perform |
177
218
  | `expected` | string | IMMUTABLE | Specific observable result expected |
178
219
  | `actual` | string | OVERWRITE | What actually happened (filled after testing) |
179
- | `evidence` | string | OVERWRITE | Screenshot filename or video timestamp |
220
+ | `evidence` | string | OVERWRITE | Screenshot filename or video timestamp. v0.8.6 — paths are PER-SURFACE: `{surface}/screenshots/{outcome-id}.png`. |
180
221
  | `analysis` | string | OVERWRITE | Adversarial analysis of evidence — describe what you SEE, not what you expect |
181
222
  | `result` | string | OVERWRITE | `pass`, `fail`, `blocked`, or `skip` |
182
223
  | `severity` | string/null | OVERWRITE | `blocker`, `major`, `minor`, `cosmetic`, or `null` if pass |
@@ -188,9 +229,11 @@ Every registered sub-feature that does NOT have a test MUST appear here. Silence
188
229
  |-------|------|-------------|
189
230
  | `registry_id` | string | The sub-feature `id` from the feature registry |
190
231
  | `name` | string | Human-readable name of the skipped feature |
191
- | `reason` | string | One of: `time_constraint`, `blocked_by_platform`, `depends_on_other`, `not_applicable` |
232
+ | `reason` | string | One of: `time_constraint`, `unsatisfied_optional_capability` (preferred), `depends_on_other`, `not_applicable`. **Legacy alias:** `blocked_by_platform` is read by the schema reader as an alias for `unsatisfied_optional_capability` and emits a deprecation warning. New writes MUST use the new reason. |
192
233
  | `detail` | string | Brief explanation of why this feature was skipped |
193
234
 
235
+ > **Fail-closed, not a skip.** If a REQUIRED IDL capability has no implementing driver in the resolver chain, the resolver throws `unsatisfied_required_capability` (per ID-02) — aborts the run. That entry does NOT land in `skipped[]`; coverage loss is loud, deliberate, and audited via `meta.fallback_attempts[]`.
236
+
194
237
  ### dimensions
195
238
  Four QA dimensions, each with `score` (1-10) and `rationale` (string). Scores are evidence-based, never assumed.
196
239