@aperant/framework 0.8.1 → 0.8.4

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 (248) hide show
  1. package/CHANGELOG.md +287 -1
  2. package/agents/apt-planner.md +5 -4
  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/detect-runtime.d.mts +6 -0
  17. package/dist/cli/commands/detect-runtime.d.mts.map +1 -0
  18. package/dist/cli/commands/detect-runtime.mjs +136 -0
  19. package/dist/cli/commands/detect-runtime.mjs.map +1 -0
  20. package/dist/cli/commands/driver-doctor.d.mts +29 -0
  21. package/dist/cli/commands/driver-doctor.d.mts.map +1 -0
  22. package/dist/cli/commands/driver-doctor.mjs +291 -0
  23. package/dist/cli/commands/driver-doctor.mjs.map +1 -0
  24. package/dist/cli/commands/gate.mjs +14 -0
  25. package/dist/cli/commands/gate.mjs.map +1 -1
  26. package/dist/cli/commands/init.d.mts.map +1 -1
  27. package/dist/cli/commands/init.mjs +9 -6
  28. package/dist/cli/commands/init.mjs.map +1 -1
  29. package/dist/cli/commands/install-from-source.d.mts +2 -0
  30. package/dist/cli/commands/install-from-source.d.mts.map +1 -0
  31. package/dist/cli/commands/install-from-source.mjs +2 -0
  32. package/dist/cli/commands/install-from-source.mjs.map +1 -0
  33. package/dist/cli/commands/modes.mjs +1 -1
  34. package/dist/cli/commands/route.d.mts.map +1 -1
  35. package/dist/cli/commands/route.mjs +38 -1
  36. package/dist/cli/commands/route.mjs.map +1 -1
  37. package/dist/cli/commands/triage.mjs +1 -1
  38. package/dist/cli/config/load.d.mts.map +1 -1
  39. package/dist/cli/config/load.mjs +9 -3
  40. package/dist/cli/config/load.mjs.map +1 -1
  41. package/dist/cli/coordination/auto-emit-artifact-ready.mjs +1 -1
  42. package/dist/cli/coordination/event-schema.d.mts.map +1 -1
  43. package/dist/cli/coordination/event-schema.mjs +18 -0
  44. package/dist/cli/coordination/event-schema.mjs.map +1 -1
  45. package/dist/cli/dispatch.d.mts.map +1 -1
  46. package/dist/cli/dispatch.mjs +18 -1
  47. package/dist/cli/dispatch.mjs.map +1 -1
  48. package/dist/cli/gate/context.d.mts +1 -0
  49. package/dist/cli/gate/context.d.mts.map +1 -1
  50. package/dist/cli/gate/context.mjs +14 -1
  51. package/dist/cli/gate/context.mjs.map +1 -1
  52. package/dist/cli/gate/gates/consistency-check.d.mts +1 -0
  53. package/dist/cli/gate/gates/consistency-check.d.mts.map +1 -1
  54. package/dist/cli/gate/gates/consistency-check.mjs +1 -0
  55. package/dist/cli/gate/gates/consistency-check.mjs.map +1 -1
  56. package/dist/cli/gate/gates/review-clean.d.mts +1 -0
  57. package/dist/cli/gate/gates/review-clean.d.mts.map +1 -1
  58. package/dist/cli/gate/gates/review-clean.mjs +1 -0
  59. package/dist/cli/gate/gates/review-clean.mjs.map +1 -1
  60. package/dist/cli/gate/gates/verify-approved.d.mts +1 -0
  61. package/dist/cli/gate/gates/verify-approved.d.mts.map +1 -1
  62. package/dist/cli/gate/gates/verify-approved.mjs +1 -0
  63. package/dist/cli/gate/gates/verify-approved.mjs.map +1 -1
  64. package/dist/cli/gate/registry.d.mts.map +1 -1
  65. package/dist/cli/gate/registry.mjs +9 -1
  66. package/dist/cli/gate/registry.mjs.map +1 -1
  67. package/dist/cli/install/find-workspace-framework-version.d.mts +16 -0
  68. package/dist/cli/install/find-workspace-framework-version.d.mts.map +1 -0
  69. package/dist/cli/install/find-workspace-framework-version.mjs +85 -0
  70. package/dist/cli/install/find-workspace-framework-version.mjs.map +1 -0
  71. package/dist/cli/install/install-from-source.d.mts +10 -0
  72. package/dist/cli/install/install-from-source.d.mts.map +1 -0
  73. package/dist/cli/install/install-from-source.mjs +271 -0
  74. package/dist/cli/install/install-from-source.mjs.map +1 -0
  75. package/dist/cli/install/install-kernel.d.mts.map +1 -1
  76. package/dist/cli/install/install-kernel.mjs +24 -0
  77. package/dist/cli/install/install-kernel.mjs.map +1 -1
  78. package/dist/cli/install/legacy-paths.d.mts +33 -11
  79. package/dist/cli/install/legacy-paths.d.mts.map +1 -1
  80. package/dist/cli/install/legacy-paths.mjs +161 -39
  81. package/dist/cli/install/legacy-paths.mjs.map +1 -1
  82. package/dist/cli/install/runtime-detect.mjs +1 -1
  83. package/dist/cli/install/runtime-detect.mjs.map +1 -1
  84. package/dist/cli/install/runtime-migrate.d.mts +34 -14
  85. package/dist/cli/install/runtime-migrate.d.mts.map +1 -1
  86. package/dist/cli/install/runtime-migrate.mjs +63 -30
  87. package/dist/cli/install/runtime-migrate.mjs.map +1 -1
  88. package/dist/cli/install/transforms/codex.d.mts.map +1 -1
  89. package/dist/cli/install/transforms/codex.mjs +28 -17
  90. package/dist/cli/install/transforms/codex.mjs.map +1 -1
  91. package/dist/cli/skill-author/contract.mjs +2 -2
  92. package/dist/cli/skill-author/contract.mjs.map +1 -1
  93. package/dist/cli/verify-proof/audit.d.mts +34 -0
  94. package/dist/cli/verify-proof/audit.d.mts.map +1 -0
  95. package/dist/cli/verify-proof/audit.mjs +53 -0
  96. package/dist/cli/verify-proof/audit.mjs.map +1 -0
  97. package/dist/cli/verify-proof/exec.d.mts +20 -0
  98. package/dist/cli/verify-proof/exec.d.mts.map +1 -0
  99. package/dist/cli/verify-proof/exec.mjs +74 -0
  100. package/dist/cli/verify-proof/exec.mjs.map +1 -0
  101. package/dist/cli/verify-proof/idl/index.d.mts +2 -0
  102. package/dist/cli/verify-proof/idl/index.d.mts.map +1 -0
  103. package/dist/cli/verify-proof/idl/index.mjs +14 -0
  104. package/dist/cli/verify-proof/idl/index.mjs.map +1 -0
  105. package/dist/cli/verify-proof/idl/types.d.ts +9 -0
  106. package/dist/cli/verify-proof/idl/types.d.ts.map +1 -0
  107. package/dist/cli/verify-proof/idl/types.js +9 -0
  108. package/dist/cli/verify-proof/idl/types.js.map +1 -0
  109. package/dist/cli/verify-proof/manifest-validator.d.mts +19 -0
  110. package/dist/cli/verify-proof/manifest-validator.d.mts.map +1 -0
  111. package/dist/cli/verify-proof/manifest-validator.mjs +182 -0
  112. package/dist/cli/verify-proof/manifest-validator.mjs.map +1 -0
  113. package/dist/cli/verify-proof/resolver.d.mts +43 -0
  114. package/dist/cli/verify-proof/resolver.d.mts.map +1 -0
  115. package/dist/cli/verify-proof/resolver.mjs +160 -0
  116. package/dist/cli/verify-proof/resolver.mjs.map +1 -0
  117. package/dist/cli/verify-proof/runtime-detect.d.mts +27 -0
  118. package/dist/cli/verify-proof/runtime-detect.d.mts.map +1 -0
  119. package/dist/cli/verify-proof/runtime-detect.mjs +116 -0
  120. package/dist/cli/verify-proof/runtime-detect.mjs.map +1 -0
  121. package/dist/cli/verify-proof/trust.d.mts +70 -0
  122. package/dist/cli/verify-proof/trust.d.mts.map +1 -0
  123. package/dist/cli/verify-proof/trust.mjs +174 -0
  124. package/dist/cli/verify-proof/trust.mjs.map +1 -0
  125. package/dist/plugin/.claude-plugin/plugin.json +3 -1
  126. package/dist/plugin/agents/apt-planner.md +5 -4
  127. package/dist/plugin/skills/apt/SKILL.md +75 -6
  128. package/dist/plugin/skills/apt-caveman/SKILL.md +64 -0
  129. package/dist/plugin/skills/apt-debug/SKILL.md +36 -4
  130. package/dist/plugin/skills/apt-debug/appendices/diagnose-discipline.md +2 -11
  131. package/dist/plugin/skills/apt-discuss/SKILL.md +4 -4
  132. package/dist/plugin/skills/apt-discuss/appendices/grill-discipline.md +2 -11
  133. package/dist/plugin/skills/apt-discuss/appendices/zoom-out-helper.md +0 -8
  134. package/dist/plugin/skills/apt-execute/SKILL.md +27 -4
  135. package/dist/plugin/skills/apt-execute/appendices/tdd-mode.md +2 -10
  136. package/dist/plugin/skills/apt-fan-out/SKILL.md +36 -1
  137. package/dist/plugin/skills/apt-handoff/SKILL.md +228 -0
  138. package/dist/plugin/skills/apt-improve/DEEPENING.md +0 -8
  139. package/dist/plugin/skills/apt-improve/INTERFACE-DESIGN.md +0 -8
  140. package/dist/plugin/skills/apt-improve/LANGUAGE.md +0 -8
  141. package/dist/plugin/skills/apt-improve/SKILL.md +4 -4
  142. package/dist/plugin/skills/apt-plan/SKILL.md +23 -5
  143. package/dist/plugin/skills/apt-plan/adapters/conductor.md +1 -1
  144. package/dist/plugin/skills/apt-prototype/LOGIC.md +0 -8
  145. package/dist/plugin/skills/apt-prototype/SKILL.md +4 -4
  146. package/dist/plugin/skills/apt-prototype/UI.md +0 -8
  147. package/dist/plugin/skills/apt-quick/SKILL.md +31 -1
  148. package/dist/plugin/skills/apt-resume/SKILL.md +54 -31
  149. package/dist/plugin/skills/apt-run/SKILL.md +114 -16
  150. package/dist/plugin/skills/apt-setup/SKILL.md +97 -8
  151. package/dist/plugin/skills/apt-ship/SKILL.md +165 -9
  152. package/dist/plugin/skills/apt-spar/SKILL.md +65 -16
  153. package/dist/plugin/skills/apt-triage/AGENT-BRIEF.md +0 -8
  154. package/dist/plugin/skills/apt-triage/OUT-OF-SCOPE.md +0 -7
  155. package/dist/plugin/skills/apt-triage/SKILL.md +2 -2
  156. package/dist/plugin/skills/apt-update/SKILL.md +74 -17
  157. package/dist/plugin/skills/apt-verify-proof/SKILL.md +48 -1
  158. package/dist/plugin/skills/apt-zoom-out/SKILL.md +3 -3
  159. package/dist/schemas/quick-task.d.ts.map +1 -1
  160. package/dist/schemas/quick-task.js +2 -2
  161. package/dist/schemas/quick-task.js.map +1 -1
  162. package/dist/types/config.d.ts +33 -0
  163. package/dist/types/config.d.ts.map +1 -1
  164. package/dist/types/state.d.ts +1 -1
  165. package/dist/types/state.d.ts.map +1 -1
  166. package/dist/types/task-record.d.ts +1 -1
  167. package/dist/types/task-record.d.ts.map +1 -1
  168. package/package.json +134 -133
  169. package/prompts/planner.md +1 -1
  170. package/prompts/spec_writer.md +1 -1
  171. package/skills/apt/SKILL.md +75 -6
  172. package/skills/apt-caveman/SKILL.md +64 -0
  173. package/skills/apt-debug/SKILL.md +36 -4
  174. package/skills/apt-debug/appendices/diagnose-discipline.md +2 -11
  175. package/skills/apt-discuss/SKILL.md +4 -4
  176. package/skills/apt-discuss/appendices/grill-discipline.md +2 -11
  177. package/skills/apt-discuss/appendices/zoom-out-helper.md +0 -8
  178. package/skills/apt-execute/SKILL.md +27 -4
  179. package/skills/apt-execute/appendices/tdd-mode.md +2 -10
  180. package/skills/apt-fan-out/SKILL.md +36 -1
  181. package/skills/apt-handoff/SKILL.md +228 -0
  182. package/skills/apt-improve/DEEPENING.md +0 -8
  183. package/skills/apt-improve/INTERFACE-DESIGN.md +0 -8
  184. package/skills/apt-improve/LANGUAGE.md +0 -8
  185. package/skills/apt-improve/SKILL.md +4 -4
  186. package/skills/apt-plan/SKILL.md +23 -5
  187. package/skills/apt-plan/adapters/conductor.md +1 -1
  188. package/skills/apt-planner.md +1 -1
  189. package/skills/apt-prototype/LOGIC.md +0 -8
  190. package/skills/apt-prototype/SKILL.md +4 -4
  191. package/skills/apt-prototype/UI.md +0 -8
  192. package/skills/apt-quick/SKILL.md +31 -1
  193. package/skills/apt-resume/SKILL.md +54 -31
  194. package/skills/apt-run/SKILL.md +114 -16
  195. package/skills/apt-setup/SKILL.md +97 -8
  196. package/skills/apt-ship/SKILL.md +165 -9
  197. package/skills/apt-spar/SKILL.md +65 -16
  198. package/skills/apt-triage/AGENT-BRIEF.md +0 -8
  199. package/skills/apt-triage/OUT-OF-SCOPE.md +0 -7
  200. package/skills/apt-triage/SKILL.md +2 -2
  201. package/skills/apt-update/SKILL.md +74 -17
  202. package/skills/apt-verify-proof/SKILL.md +48 -1
  203. package/skills/apt-zoom-out/SKILL.md +3 -3
  204. package/src/cli/commands/adr.mjs +1 -1
  205. package/src/cli/commands/audit-branch-current.mjs +250 -0
  206. package/src/cli/commands/audit.mjs +13 -2
  207. package/src/cli/commands/check-version.mjs +20 -1
  208. package/src/cli/commands/context.mjs +3 -3
  209. package/src/cli/commands/detect-runtime.mjs +134 -0
  210. package/src/cli/commands/driver-doctor.mjs +294 -0
  211. package/src/cli/commands/gate.mjs +15 -0
  212. package/src/cli/commands/init.mjs +9 -6
  213. package/src/cli/commands/install-from-source.mjs +1 -0
  214. package/src/cli/commands/modes.mjs +1 -1
  215. package/src/cli/commands/route.mjs +42 -1
  216. package/src/cli/commands/triage.mjs +1 -1
  217. package/src/cli/config/load.mjs +9 -3
  218. package/src/cli/coordination/auto-emit-artifact-ready.mjs +1 -1
  219. package/src/cli/coordination/event-schema.mjs +19 -0
  220. package/src/cli/dispatch.mjs +17 -1
  221. package/src/cli/gate/context.mjs +20 -1
  222. package/src/cli/gate/gates/consistency-check.mjs +1 -0
  223. package/src/cli/gate/gates/review-clean.mjs +1 -0
  224. package/src/cli/gate/gates/verify-approved.mjs +1 -0
  225. package/src/cli/gate/registry.mjs +11 -1
  226. package/src/cli/install/find-workspace-framework-version.mjs +88 -0
  227. package/src/cli/install/install-from-source.mjs +276 -0
  228. package/src/cli/install/install-kernel.mjs +24 -0
  229. package/src/cli/install/legacy-paths.mjs +162 -39
  230. package/src/cli/install/runtime-detect.mjs +1 -1
  231. package/src/cli/install/runtime-migrate.mjs +63 -30
  232. package/src/cli/install/transforms/codex.mjs +26 -17
  233. package/src/cli/skill-author/contract.mjs +2 -2
  234. package/src/cli/verify-proof/.gitkeep +0 -0
  235. package/src/cli/verify-proof/audit.mjs +55 -0
  236. package/src/cli/verify-proof/exec.mjs +74 -0
  237. package/src/cli/verify-proof/idl/index.mjs +24 -0
  238. package/src/cli/verify-proof/idl/types.ts +41 -0
  239. package/src/cli/verify-proof/manifest-schema.json +211 -0
  240. package/src/cli/verify-proof/manifest-validator.mjs +184 -0
  241. package/src/cli/verify-proof/resolver.mjs +163 -0
  242. package/src/cli/verify-proof/runtime-detect.mjs +122 -0
  243. package/src/cli/verify-proof/trust.mjs +187 -0
  244. package/templates/adr-format.md +0 -8
  245. package/templates/aperant-claude-md-appendix.md +1 -1
  246. package/templates/context-format.md +1 -9
  247. package/templates/proof-verification.md +19 -5
  248. package/workflows/verify-proof.md +56 -14
@@ -0,0 +1,122 @@
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
+
13
+ import { existsSync, readFileSync } from 'node:fs'
14
+ import { join, resolve } from 'node:path'
15
+
16
+ /**
17
+ * @typedef {Object} RuntimeCapabilities
18
+ * @property {boolean} is_electron
19
+ * @property {boolean} is_tauri
20
+ * @property {boolean} is_nextjs
21
+ * @property {boolean} is_nuxt
22
+ * @property {boolean} is_web_frontend
23
+ * @property {boolean} is_expo
24
+ * @property {boolean} is_react_native
25
+ * @property {boolean} has_node
26
+ */
27
+
28
+ /** A capability record with every flag defaulted to false. */
29
+ function emptyCapabilities() {
30
+ return {
31
+ is_electron: false,
32
+ is_tauri: false,
33
+ is_nextjs: false,
34
+ is_nuxt: false,
35
+ is_web_frontend: false,
36
+ is_expo: false,
37
+ is_react_native: false,
38
+ has_node: false,
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Detect runtime capabilities from a parsed package.json object.
44
+ *
45
+ * @param {Record<string, unknown> | null} packageJson
46
+ * @returns {RuntimeCapabilities}
47
+ */
48
+ export function detectFromPackageJson(packageJson) {
49
+ const capabilities = emptyCapabilities()
50
+ if (packageJson === null || typeof packageJson !== 'object') return capabilities
51
+
52
+ const allDeps = {
53
+ ...(packageJson.dependencies ?? {}) /** @type {Record<string,string>} */,
54
+ ...(packageJson.devDependencies ?? {}) /** @type {Record<string,string>} */,
55
+ }
56
+ const deps = new Set(Object.keys(allDeps).map((k) => k.toLowerCase()))
57
+
58
+ if (deps.has('electron') || [...deps].some((d) => d.startsWith('@electron'))) {
59
+ capabilities.is_electron = true
60
+ }
61
+ if (deps.has('@tauri-apps/api') || deps.has('tauri')) {
62
+ capabilities.is_tauri = true
63
+ }
64
+ if (deps.has('expo')) capabilities.is_expo = true
65
+ if (deps.has('react-native')) capabilities.is_react_native = true
66
+
67
+ // Web frontends — react/vue/svelte/angular/solid → frontend. BUT: an
68
+ // Electron app commonly bundles React as its renderer; in that case the
69
+ // runtime is "electron", not "web_frontend". Electron + react-native +
70
+ // expo + tauri all win over the web flag.
71
+ const isNativeContainer =
72
+ capabilities.is_electron ||
73
+ capabilities.is_tauri ||
74
+ capabilities.is_react_native ||
75
+ capabilities.is_expo
76
+ const webFrameworks = ['react', 'vue', 'svelte', 'angular', 'solid']
77
+ if (!isNativeContainer && webFrameworks.some((f) => deps.has(f))) {
78
+ capabilities.is_web_frontend = true
79
+ }
80
+
81
+ if (deps.has('next')) {
82
+ capabilities.is_nextjs = true
83
+ capabilities.is_web_frontend = true
84
+ }
85
+ if (deps.has('nuxt')) {
86
+ capabilities.is_nuxt = true
87
+ capabilities.is_web_frontend = true
88
+ }
89
+ if (deps.has('vite') && !isNativeContainer) {
90
+ capabilities.is_web_frontend = true
91
+ }
92
+
93
+ // Node-ish? Anything with a package.json + an entry point counts.
94
+ if (typeof packageJson.main === 'string' || typeof packageJson.module === 'string') {
95
+ capabilities.has_node = true
96
+ }
97
+ if (!capabilities.has_node && packageJson.scripts && typeof packageJson.scripts === 'object') {
98
+ capabilities.has_node = true
99
+ }
100
+
101
+ return capabilities
102
+ }
103
+
104
+ /**
105
+ * Load and detect capabilities from a project directory's package.json.
106
+ * Returns empty capabilities if no package.json exists (malformed or
107
+ * missing — both cases fall through to "nothing detected").
108
+ *
109
+ * @param {string} projectDir
110
+ * @returns {RuntimeCapabilities}
111
+ */
112
+ export function loadRuntimeCapabilities(projectDir) {
113
+ const pkgPath = join(resolve(projectDir), 'package.json')
114
+ if (!existsSync(pkgPath)) return emptyCapabilities()
115
+ let parsed
116
+ try {
117
+ parsed = JSON.parse(readFileSync(pkgPath, 'utf-8'))
118
+ } catch {
119
+ return emptyCapabilities()
120
+ }
121
+ return detectFromPackageJson(parsed)
122
+ }
@@ -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/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": {
@@ -66,8 +76,8 @@ JSON schema reference for `.aperant/proof/{app}/{date}/{run}/verification.json`
66
76
  {
67
77
  "registry_id": "terminals-settings-scrollback-slider",
68
78
  "name": "Scrollback buffer slider",
69
- "reason": "blocked_by_platform",
70
- "detail": "Slider interaction not supported via current MCP tools"
79
+ "reason": "unsatisfied_optional_capability",
80
+ "detail": "Selected driver does not implement the IDL verb required for slider interaction"
71
81
  }
72
82
  ],
73
83
 
@@ -149,7 +159,9 @@ JSON schema reference for `.aperant/proof/{app}/{date}/{run}/verification.json`
149
159
  | `updated` | string | OVERWRITE | ISO timestamp, updated on every write |
150
160
  | `spec` | string | IMMUTABLE | Path to spec or task description |
151
161
  | `verdict` | string | OVERWRITE | Derived from dimension scores after all tests |
152
- | `tools` | array | IMMUTABLE | MCP tools detected as available |
162
+ | `tools` | array | IMMUTABLE | MCP tools / auxiliary capture tools detected as available |
163
+ | `selected_driver` | object | IMMUTABLE | The verify-proof driver chosen by the resolver per ID-02. Fields: `driverId`, `version`, `manifest_sha256`, `trust_tier`, `sandbox_profile`. |
164
+ | `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
165
 
154
166
  ### automated_checks
155
167
  Each check has `status` (pass/fail) and `output` (captured terminal output). These are populated in step 3 before any manual testing begins.
@@ -188,9 +200,11 @@ Every registered sub-feature that does NOT have a test MUST appear here. Silence
188
200
  |-------|------|-------------|
189
201
  | `registry_id` | string | The sub-feature `id` from the feature registry |
190
202
  | `name` | string | Human-readable name of the skipped feature |
191
- | `reason` | string | One of: `time_constraint`, `blocked_by_platform`, `depends_on_other`, `not_applicable` |
203
+ | `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
204
  | `detail` | string | Brief explanation of why this feature was skipped |
193
205
 
206
+ > **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[]`.
207
+
194
208
  ### dimensions
195
209
  Four QA dimensions, each with `score` (1-10) and `rationale` (string). Scores are evidence-based, never assumed.
196
210
 
@@ -53,17 +53,47 @@ mkdir -p "${PROOF_DIR}/screenshots"
53
53
  </step>
54
54
 
55
55
  <step name="pre_check">
56
- ## 2. Pre-Check
56
+ ## 2. Pre-Check + Driver Selection
57
57
 
58
58
  If `implementation_plan.json` exists, verify all subtasks are completed.
59
59
  If any are pending: report and exit.
60
60
 
61
- Check which MCP tools are available:
62
- - Try `mcp__electron__get_electron_window_info` — if responds, Electron is available
63
- - Try `mcp__puppeteer__puppeteer_navigate` — if responds, browser is available
64
- - Check `apt-proof-video --version` — if available, CDP video capture is enabled
61
+ ### 2a. Resolve the verify-proof driver
65
62
 
66
- Record available tools in verification.json `meta.tools` array.
63
+ The workflow is **runtime-agnostic** it refers to IDL verbs
64
+ (`driver.click(target)`, `driver.screenshot(path)`), never to concrete
65
+ MCP tool names. The driver layer (`packages/framework/drivers/*/`)
66
+ maps verbs to transports. Auto-detect the runtime and select a driver:
67
+
68
+ ```bash
69
+ # Detect the project's runtime capabilities + select a driver
70
+ node .aperant/deps/node_modules/@aperant/framework/bin/apt-tools.mjs detect-runtime .
71
+ # → {status:"ok", runtime:{is_electron, is_tauri, is_nextjs, ...}, selected_driver_id:"..."}
72
+ ```
73
+
74
+ The resolver picks the highest-scoring driver per ID-02 (capability_coverage
75
+ > compatibility > stability > priority > locality). Adopters override
76
+ auto-detect via `.aperant/config.json:verification.runtimes[]`:
77
+
78
+ ```json
79
+ { "verification": { "runtimes": ["electron", "cli"] } }
80
+ ```
81
+
82
+ If no driver satisfies a REQUIRED capability, the resolver throws
83
+ `unsatisfied_required_capability` — fail-closed, NOT a silent
84
+ `blocked_by_platform` skip (per ID-02).
85
+
86
+ ### 2b. Probe optional evidence-capture tools
87
+
88
+ ```bash
89
+ # CDP video capture (optional)
90
+ apt-proof-video --version # if available, CDP video is on
91
+ ```
92
+
93
+ Record the selected driver + auxiliary tools in verification.json:
94
+ `meta.selected_driver: {driverId, version, manifest_sha256, trust_tier,
95
+ sandbox_profile}` and `meta.fallback_attempts: [{driver_id, reason}]`
96
+ (see ST11's schema extension in `templates/proof-verification.md`).
67
97
  </step>
68
98
 
69
99
  <step name="automated_checks">
@@ -165,10 +195,14 @@ coverage = tested_count / registered_count
165
195
  **Every untested registered feature MUST go in the `skipped` array** with a reason.
166
196
  Valid skip reasons:
167
197
  - `time_constraint` — would take too long relative to value
168
- - `blocked_by_platform` — MCP tools cannot interact with this element
198
+ - `unsatisfied_optional_capability` — the selected driver lacks an OPTIONAL IDL verb needed by this test (e.g. `assert_network` not implemented). Loud, deliberate skip.
169
199
  - `depends_on_other` — requires a feature not yet implemented
170
200
  - `not_applicable` — feature is deprecated or not relevant to this verification
171
201
 
202
+ > **Legacy alias.** `blocked_by_platform` (pre-driver-architecture) 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.
203
+
204
+ > **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). This aborts the run — it does NOT silently land in the `skipped` array. Coverage loss is loud, deliberate, and audited.
205
+
172
206
  Silence about untested features is NOT acceptable. Every registered sub-feature must appear
173
207
  either as a test (with `registry_id`) or in the `skipped` array.
174
208
  </step>
@@ -232,11 +266,17 @@ If CDP is NOT available, note it and proceed without video.
232
266
  For each test, in order:
233
267
 
234
268
  ### 7a. Perform the Action
235
- Actually do what the test says:
236
- - If it says "type X" → use `mcp__electron__send_command_to_electron` with `fill_input` or `send_keyboard_shortcut`
237
- - If it says "click X" → use the appropriate click tool
238
- - If it says "navigate to X" → navigate there
239
- - If it says "run command X" → run it
269
+ Actually do what the test says — invoke the IDL verb on the selected driver:
270
+ - If it says "type X" → `driver.type({ target, text: "X" })`
271
+ - If it says "click X" → `driver.click({ target })`
272
+ - If it says "navigate to X" → `driver.navigate({ url: "X" })`
273
+ - If it says "press key X" → `driver.key({ key: "X" })`
274
+ - If it says "run command X" → `driver.execute_js({ script: "X" })`
275
+
276
+ The driver layer translates IDL verbs to its transport (an MCP server,
277
+ a local binary, or a built-in HTTP request). Workflow prose names ONLY
278
+ IDL verbs per ID-01 — concrete tool names live in
279
+ `packages/framework/drivers/<driver>/driver.mjs`.
240
280
 
241
281
  ### 7b. Wait for Result
242
282
  Wait for the action to complete. If it involves loading, wait for the load.
@@ -245,8 +285,10 @@ Add a short delay (1-2 seconds) so the CDP capture also captures this state.
245
285
  ### 7c. Capture Evidence
246
286
  Take a screenshot AFTER the action completes:
247
287
  ```
248
- Tool: mcp__electron__take_screenshot
249
- Args: {"outputPath": "${PROOF_DIR}/screenshots/{feature-id}-test-{n}.png"}
288
+ driver.screenshot({
289
+ target: { kind: "css", value: "body" },
290
+ output_path: "${PROOF_DIR}/screenshots/{feature-id}-test-{n}.png"
291
+ })
250
292
  ```
251
293
 
252
294
  ### 7d. Analyze Evidence