@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,41 @@
1
+ /**
2
+ * idl/types.ts — re-export the SDK's IDL types for framework-internal use.
3
+ *
4
+ * Single source of truth lives in `@aperant/driver-sdk/idl.ts`. Framework
5
+ * code imports from here so future moves (e.g. an internal-only IDL
6
+ * extension) only touch this re-export, not every call site.
7
+ */
8
+
9
+ export type {
10
+ AssertNetworkArgs,
11
+ AssertOutputArgs,
12
+ AssertTextArgs,
13
+ AssertVisibleArgs,
14
+ AwaitSignalArgs,
15
+ CaptureLogsArgs,
16
+ ClickArgs,
17
+ DomDumpArgs,
18
+ DragDropArgs,
19
+ DriverModule,
20
+ DumpStateArgs,
21
+ ExecuteJsArgs,
22
+ Handle,
23
+ IdlVerbName,
24
+ KeyArgs,
25
+ LaunchArgs,
26
+ NavigateArgs,
27
+ RecordVideoArgs,
28
+ ScreenshotArgs,
29
+ SelectArgs,
30
+ SwipeArgs,
31
+ SwitchContextArgs,
32
+ Target,
33
+ TargetKind,
34
+ TeardownArgs,
35
+ TriggerArgs,
36
+ TypeArgs,
37
+ UploadFileArgs,
38
+ VerbResult,
39
+ WaitForEventArgs,
40
+ WaitForIdleArgs,
41
+ } from '@aperant/driver-sdk'
@@ -0,0 +1,211 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://aperant.dev/schemas/driver-manifest-v1.json",
4
+ "title": "Aperant Driver Manifest v1",
5
+ "description": "Structural contract for a verify-proof driver manifest. Validates at publish-time (apt-tools driver doctor) and at load-time (resolver). The transport oneOf rejects free-form shell strings — local_command MUST be a structured {binary, args[]} object per ID-03.",
6
+ "type": "object",
7
+ "required": [
8
+ "driverId",
9
+ "version",
10
+ "targets",
11
+ "capabilities",
12
+ "frameworkApiRange",
13
+ "priority",
14
+ "stability",
15
+ "trust",
16
+ "security",
17
+ "transport"
18
+ ],
19
+ "properties": {
20
+ "driverId": {
21
+ "type": "string",
22
+ "pattern": "^[a-z][a-z0-9_-]*$"
23
+ },
24
+ "version": {
25
+ "type": "string"
26
+ },
27
+ "targets": {
28
+ "type": "array",
29
+ "items": { "type": "string" }
30
+ },
31
+ "capabilities": {
32
+ "type": "array",
33
+ "items": { "type": "string" }
34
+ },
35
+ "evidence_capabilities": {
36
+ "type": "array",
37
+ "items": { "type": "string" }
38
+ },
39
+ "frameworkApiRange": {
40
+ "type": "string"
41
+ },
42
+ "requires": {
43
+ "type": "object",
44
+ "properties": {
45
+ "allOf": { "type": "array", "items": { "type": "string" } },
46
+ "anyOf": { "type": "array", "items": { "type": "string" } }
47
+ }
48
+ },
49
+ "os": {
50
+ "type": "array",
51
+ "items": { "type": "string" }
52
+ },
53
+ "arch": {
54
+ "type": "array",
55
+ "items": { "type": "string" }
56
+ },
57
+ "container_support": { "type": "boolean" },
58
+ "conflicts_with": {
59
+ "type": "array",
60
+ "items": { "type": "string" }
61
+ },
62
+ "priority": { "type": "number" },
63
+ "stability": {
64
+ "type": "string",
65
+ "enum": ["experimental", "beta", "ga"]
66
+ },
67
+ "trust": {
68
+ "type": "object",
69
+ "required": ["tier"],
70
+ "properties": {
71
+ "tier": {
72
+ "type": "string",
73
+ "enum": [
74
+ "first_party_signed",
75
+ "community_verified",
76
+ "community_unverified",
77
+ "local_unverified"
78
+ ]
79
+ },
80
+ "publisher": {
81
+ "type": "object",
82
+ "required": ["name"],
83
+ "properties": {
84
+ "name": { "type": "string" },
85
+ "npm_scope": { "type": "string" },
86
+ "github_org": { "type": "string" },
87
+ "verified": { "type": "boolean" }
88
+ }
89
+ },
90
+ "provenance": {
91
+ "type": "object",
92
+ "properties": {
93
+ "scheme": {
94
+ "type": "string",
95
+ "enum": ["sigstore", "npm-provenance", "gpg", "none"]
96
+ },
97
+ "identity": { "type": "string" },
98
+ "signature": { "type": "string" },
99
+ "certificate": { "type": "string" }
100
+ }
101
+ },
102
+ "integrity": {
103
+ "type": "object",
104
+ "properties": {
105
+ "manifest_sha256": { "type": "string" },
106
+ "artifact_digest": { "type": "string" }
107
+ }
108
+ }
109
+ }
110
+ },
111
+ "security": {
112
+ "type": "object",
113
+ "required": ["sandbox_profile", "network"],
114
+ "properties": {
115
+ "sandbox_profile": {
116
+ "type": "string",
117
+ "enum": ["read_only", "workspace_write", "system_write"]
118
+ },
119
+ "network": {
120
+ "type": "string",
121
+ "enum": ["deny", "allowlist", "allow"]
122
+ },
123
+ "allowed_executables": {
124
+ "type": "array",
125
+ "items": { "type": "string" }
126
+ },
127
+ "allow_shell": { "type": "boolean" },
128
+ "requires_user_approval": { "type": "boolean" }
129
+ }
130
+ },
131
+ "audit": {
132
+ "type": "object",
133
+ "required": ["log_invocations", "log_manifest_hash"],
134
+ "properties": {
135
+ "log_invocations": { "type": "boolean" },
136
+ "log_manifest_hash": { "type": "boolean" }
137
+ }
138
+ },
139
+ "preflight": {
140
+ "type": "array",
141
+ "items": {
142
+ "type": "object",
143
+ "required": ["name", "required"],
144
+ "properties": {
145
+ "name": { "type": "string" },
146
+ "command": {
147
+ "type": "object",
148
+ "required": ["binary", "args"],
149
+ "properties": {
150
+ "binary": { "type": "string" },
151
+ "args": {
152
+ "type": "array",
153
+ "items": { "type": "string" }
154
+ }
155
+ }
156
+ },
157
+ "required": { "type": "boolean" }
158
+ }
159
+ }
160
+ },
161
+ "transport": {
162
+ "oneOf": [
163
+ {
164
+ "type": "object",
165
+ "required": ["mcp_server_id"],
166
+ "properties": {
167
+ "mcp_server_id": { "type": "string" }
168
+ }
169
+ },
170
+ {
171
+ "type": "object",
172
+ "required": ["local_command"],
173
+ "properties": {
174
+ "local_command": {
175
+ "type": "object",
176
+ "required": ["binary", "args"],
177
+ "properties": {
178
+ "binary": { "type": "string" },
179
+ "args": {
180
+ "type": "array",
181
+ "items": { "type": "string" }
182
+ }
183
+ }
184
+ }
185
+ }
186
+ },
187
+ {
188
+ "type": "object",
189
+ "required": ["http_endpoint"],
190
+ "properties": {
191
+ "http_endpoint": { "type": "string" }
192
+ }
193
+ },
194
+ {
195
+ "type": "object",
196
+ "required": ["npm_package"],
197
+ "properties": {
198
+ "npm_package": { "type": "string" }
199
+ }
200
+ },
201
+ {
202
+ "type": "object",
203
+ "required": ["builtin"],
204
+ "properties": {
205
+ "builtin": { "const": true }
206
+ }
207
+ }
208
+ ]
209
+ }
210
+ }
211
+ }
@@ -0,0 +1,184 @@
1
+ /**
2
+ * manifest-validator.mjs — hand-rolled JSON-Schema subset validator.
3
+ *
4
+ * Validates driver manifests against manifest-schema.json. Implements
5
+ * exactly the JSON-Schema keywords we need:
6
+ *
7
+ * type / required / properties / items / enum / const / pattern / oneOf
8
+ *
9
+ * Returns { valid: true } or { valid: false, errors: [{ path, message }] }
10
+ * where `path` is a JSON-pointer (`/transport/local_command/binary`).
11
+ *
12
+ * No external deps — keeps the validator installable as `dist/` with the
13
+ * framework package and avoids dragging ajv (or any v5+ JSON-Schema
14
+ * validator) into the runtime surface for ~80 lines of validator code per
15
+ * AC15. We don't need allOf / anyOf / definitions — manifest-schema.json
16
+ * is intentionally a thin contract.
17
+ */
18
+
19
+ import { readFileSync } from 'node:fs'
20
+ import { dirname, join } from 'node:path'
21
+ import { fileURLToPath } from 'node:url'
22
+
23
+ const __dirname = dirname(fileURLToPath(import.meta.url))
24
+
25
+ /** Lazy-loaded JSON-Schema (read once on first call). */
26
+ let cachedSchema = null
27
+
28
+ /** Read the bundled manifest-schema.json. */
29
+ export function loadManifestSchema() {
30
+ if (cachedSchema !== null) return cachedSchema
31
+ const schemaPath = join(__dirname, 'manifest-schema.json')
32
+ cachedSchema = JSON.parse(readFileSync(schemaPath, 'utf-8'))
33
+ return cachedSchema
34
+ }
35
+
36
+ /**
37
+ * Validate a manifest object against the bundled schema.
38
+ *
39
+ * @param {unknown} manifest — usually JSON.parse(manifest.json contents).
40
+ * @param {object} [schema] — override the bundled schema (used by tests).
41
+ * @returns {{valid: true} | {valid: false, errors: Array<{path: string, message: string}>}}
42
+ */
43
+ export function validateManifest(manifest, schema) {
44
+ const root = schema ?? loadManifestSchema()
45
+ const errors = []
46
+ validateNode(manifest, root, '', errors)
47
+ if (errors.length === 0) return { valid: true }
48
+ return { valid: false, errors }
49
+ }
50
+
51
+ /** Type check — JSON-Schema's union of "string", "number", "object", "array", "boolean", "null". */
52
+ function checkType(value, type) {
53
+ if (type === 'string') return typeof value === 'string'
54
+ if (type === 'number') return typeof value === 'number' && !Number.isNaN(value)
55
+ if (type === 'integer') return typeof value === 'number' && Number.isInteger(value)
56
+ if (type === 'boolean') return typeof value === 'boolean'
57
+ if (type === 'null') return value === null
58
+ if (type === 'array') return Array.isArray(value)
59
+ if (type === 'object') {
60
+ return value !== null && typeof value === 'object' && !Array.isArray(value)
61
+ }
62
+ return false
63
+ }
64
+
65
+ /**
66
+ * Human-readable name for the actual shape of a value. Distinguishes
67
+ * `null`, `array`, and plain `object` — JS `typeof` reports all three as
68
+ * `'object'`, which makes error messages like "expected type object, got
69
+ * object" misleading.
70
+ *
71
+ * @param {unknown} value
72
+ * @returns {string}
73
+ */
74
+ function describeType(value) {
75
+ if (value === null) return 'null'
76
+ if (Array.isArray(value)) return 'array'
77
+ return typeof value
78
+ }
79
+
80
+ /**
81
+ * Recursive validator. `errors` is mutated.
82
+ *
83
+ * @param {unknown} value
84
+ * @param {object} schema
85
+ * @param {string} path — JSON-pointer
86
+ * @param {Array<{path: string, message: string}>} errors
87
+ */
88
+ function validateNode(value, schema, path, errors) {
89
+ // const
90
+ if ('const' in schema) {
91
+ if (value !== schema.const) {
92
+ errors.push({
93
+ path: path || '/',
94
+ message: `expected const ${JSON.stringify(schema.const)}, got ${JSON.stringify(value)}`,
95
+ })
96
+ return
97
+ }
98
+ }
99
+
100
+ // type
101
+ if (schema.type !== undefined) {
102
+ const types = Array.isArray(schema.type) ? schema.type : [schema.type]
103
+ if (!types.some((t) => checkType(value, t))) {
104
+ errors.push({
105
+ path: path || '/',
106
+ message: `expected type ${types.join('|')}, got ${describeType(value)}`,
107
+ })
108
+ return
109
+ }
110
+ }
111
+
112
+ // enum
113
+ if (Array.isArray(schema.enum)) {
114
+ if (!schema.enum.includes(value)) {
115
+ errors.push({
116
+ path: path || '/',
117
+ message: `value ${JSON.stringify(value)} not in enum [${schema.enum.join(', ')}]`,
118
+ })
119
+ return
120
+ }
121
+ }
122
+
123
+ // pattern
124
+ if (typeof schema.pattern === 'string' && typeof value === 'string') {
125
+ const re = new RegExp(schema.pattern)
126
+ if (!re.test(value)) {
127
+ errors.push({
128
+ path: path || '/',
129
+ message: `value ${JSON.stringify(value)} does not match pattern ${schema.pattern}`,
130
+ })
131
+ return
132
+ }
133
+ }
134
+
135
+ // oneOf
136
+ if (Array.isArray(schema.oneOf)) {
137
+ let matchCount = 0
138
+ const branchErrors = []
139
+ for (let i = 0; i < schema.oneOf.length; i++) {
140
+ const sub = schema.oneOf[i]
141
+ const subErrors = []
142
+ validateNode(value, sub, path, subErrors)
143
+ if (subErrors.length === 0) matchCount++
144
+ else branchErrors.push({ branch: i, errors: subErrors })
145
+ }
146
+ if (matchCount !== 1) {
147
+ errors.push({
148
+ path: path || '/',
149
+ message:
150
+ matchCount === 0
151
+ ? `oneOf: value matched no branch (${branchErrors.length} branches tried)`
152
+ : `oneOf: value matched ${matchCount} branches (must match exactly 1)`,
153
+ })
154
+ return
155
+ }
156
+ }
157
+
158
+ // object validation
159
+ if (checkType(value, 'object')) {
160
+ const obj = /** @type {Record<string, unknown>} */ (value)
161
+ if (Array.isArray(schema.required)) {
162
+ for (const key of schema.required) {
163
+ if (!(key in obj)) {
164
+ errors.push({ path: `${path}/${key}`, message: `required property "${key}" missing` })
165
+ }
166
+ }
167
+ }
168
+ if (schema.properties && typeof schema.properties === 'object') {
169
+ for (const [key, subSchema] of Object.entries(schema.properties)) {
170
+ if (key in obj) {
171
+ validateNode(obj[key], /** @type {object} */ (subSchema), `${path}/${key}`, errors)
172
+ }
173
+ }
174
+ }
175
+ }
176
+
177
+ // array validation
178
+ if (checkType(value, 'array') && schema.items) {
179
+ const arr = /** @type {unknown[]} */ (value)
180
+ for (let i = 0; i < arr.length; i++) {
181
+ validateNode(arr[i], schema.items, `${path}/${i}`, errors)
182
+ }
183
+ }
184
+ }
@@ -0,0 +1,163 @@
1
+ /**
2
+ * resolver.mjs — driver selection + fallback chain.
3
+ *
4
+ * Given a project's runtime capabilities, the discovered drivers, and the
5
+ * test's required IDL capabilities, return the best driver — or throw
6
+ * UnsatisfiedRequiredCapabilityError if no driver in the chain satisfies
7
+ * a REQUIRED capability (ID-02 fail-closed).
8
+ *
9
+ * Scoring (ID-02, ordered high → low):
10
+ * 1. capability_coverage — how many required capabilities the driver has
11
+ * 2. compatibility — does the runtime match the manifest.targets?
12
+ * 3. stability — ga > beta > experimental
13
+ * 4. priority — higher wins (manifest.priority)
14
+ * 5. locality — bundled (packages/framework/drivers/) > user-installed
15
+ * (.aperant/drivers/) > registry (.aperant/registry-cache/)
16
+ *
17
+ * Ties are broken by the next dimension; if every dimension is equal the
18
+ * driver with the lexicographically smaller driverId wins (deterministic).
19
+ */
20
+
21
+ import { UnsatisfiedRequiredCapabilityError } from '@aperant/driver-sdk'
22
+
23
+ /**
24
+ * @typedef {import('@aperant/driver-sdk').DriverManifest} DriverManifest
25
+ * @typedef {import('./runtime-detect.mjs').RuntimeCapabilities} RuntimeCapabilities
26
+ */
27
+
28
+ const STABILITY_RANK = { ga: 3, beta: 2, experimental: 1 }
29
+ const LOCALITY_RANK = { bundled: 3, user: 2, registry: 1 }
30
+
31
+ /**
32
+ * Score a single driver against the project's runtime + required capabilities.
33
+ *
34
+ * @param {DriverManifest & {locality?: 'bundled'|'user'|'registry'}} manifest
35
+ * @param {RuntimeCapabilities} runtime
36
+ * @param {string[]} required
37
+ * @returns {{score: number[], reason?: string}}
38
+ */
39
+ export function scoreDriver(manifest, runtime, required) {
40
+ const caps = new Set(manifest.capabilities ?? [])
41
+ const coverage = required.filter((c) => caps.has(c)).length
42
+ const compatibility = matchesRuntime(manifest, runtime) ? 1 : 0
43
+ const stability = STABILITY_RANK[manifest.stability] ?? 0
44
+ const priority = manifest.priority ?? 0
45
+ const locality = LOCALITY_RANK[manifest.locality ?? 'bundled'] ?? 0
46
+ return { score: [coverage, compatibility, stability, priority, locality] }
47
+ }
48
+
49
+ /**
50
+ * Whether a manifest's targets[] mentions a runtime the project has.
51
+ *
52
+ * @param {DriverManifest} manifest
53
+ * @param {RuntimeCapabilities} runtime
54
+ * @returns {boolean}
55
+ */
56
+ function matchesRuntime(manifest, runtime) {
57
+ const targets = manifest.targets ?? []
58
+ for (const t of targets) {
59
+ // Strip any version range suffix (e.g. "electron@>=25" → "electron").
60
+ const stripped = String(t).split('@')[0].toLowerCase()
61
+ if (stripped === 'electron' && runtime.is_electron) return true
62
+ if (stripped === 'tauri' && runtime.is_tauri) return true
63
+ if ((stripped === 'browser' || stripped === 'web') && runtime.is_web_frontend) return true
64
+ if (stripped === 'next' && runtime.is_nextjs) return true
65
+ if (stripped === 'nuxt' && runtime.is_nuxt) return true
66
+ if (stripped === 'expo' && runtime.is_expo) return true
67
+ if (stripped === 'react-native' && runtime.is_react_native) return true
68
+ if (stripped === 'node' && runtime.has_node) return true
69
+ if (stripped === 'cli' && runtime.has_node) return true
70
+ if (stripped === 'api' && runtime.has_node) return true
71
+ if (stripped === 'fake') return true // fixture-only — always matches
72
+ }
73
+ return false
74
+ }
75
+
76
+ /**
77
+ * Compare two driver-score tuples lexicographically.
78
+ * @param {number[]} a
79
+ * @param {number[]} b
80
+ * @returns {number}
81
+ */
82
+ function compareScores(a, b) {
83
+ for (let i = 0; i < Math.min(a.length, b.length); i++) {
84
+ if (a[i] !== b[i]) return b[i] - a[i] // higher wins
85
+ }
86
+ return 0
87
+ }
88
+
89
+ /**
90
+ * Resolve the best driver for a given (runtime, drivers, override, required)
91
+ * tuple.
92
+ *
93
+ * @param {Object} opts
94
+ * @param {DriverManifest[]} opts.drivers — manifests of every discovered driver.
95
+ * @param {RuntimeCapabilities} opts.runtime — project's runtime capabilities.
96
+ * @param {string[]} opts.required_capabilities — IDL verbs the test needs.
97
+ * @param {string[]} [opts.required_stability] — accepted stability values; default ['ga'].
98
+ * @param {string[]} [opts.override] — config.json:verification.runtimes[]: explicit driverId precedence.
99
+ * @returns {{selected: DriverManifest, fallback_attempts: Array<{driver_id: string, reason: string}>}}
100
+ * @throws {UnsatisfiedRequiredCapabilityError} when no driver satisfies a REQUIRED capability.
101
+ */
102
+ export function resolveDriver(opts) {
103
+ const drivers = opts.drivers ?? []
104
+ const runtime = opts.runtime
105
+ const required = opts.required_capabilities ?? []
106
+ const stabilityFilter = new Set(opts.required_stability ?? ['ga'])
107
+ const override = opts.override ?? []
108
+
109
+ const fallback = []
110
+
111
+ // Apply override precedence first (config-listed driverIds come first).
112
+ const overrideOrdered = []
113
+ const overrideSet = new Set(override)
114
+ for (const id of override) {
115
+ const d = drivers.find((m) => m.driverId === id)
116
+ if (d) overrideOrdered.push(d)
117
+ }
118
+ const remaining = drivers.filter((m) => !overrideSet.has(m.driverId))
119
+ const ordered = [...overrideOrdered, ...remaining]
120
+
121
+ // Filter by stability; rejected drivers go into fallback_attempts.
122
+ const stableOnly = []
123
+ for (const d of ordered) {
124
+ if (!stabilityFilter.has(d.stability)) {
125
+ fallback.push({
126
+ driver_id: d.driverId,
127
+ reason: `stability ${d.stability} not in allowed set [${[...stabilityFilter].join(', ')}]`,
128
+ })
129
+ continue
130
+ }
131
+ stableOnly.push(d)
132
+ }
133
+
134
+ // Score every survivor against required capabilities.
135
+ const scored = stableOnly.map((m) => ({ manifest: m, ...scoreDriver(m, runtime, required) }))
136
+
137
+ // Sort highest-first, tie-break on driverId for determinism.
138
+ scored.sort((a, b) => {
139
+ const cmp = compareScores(a.score, b.score)
140
+ if (cmp !== 0) return cmp
141
+ return a.manifest.driverId.localeCompare(b.manifest.driverId)
142
+ })
143
+
144
+ // Pick the top scorer that covers EVERY required capability. Others go into fallback.
145
+ for (const candidate of scored) {
146
+ const caps = new Set(candidate.manifest.capabilities ?? [])
147
+ const missing = required.filter((c) => !caps.has(c))
148
+ if (missing.length === 0) {
149
+ return { selected: candidate.manifest, fallback_attempts: fallback }
150
+ }
151
+ fallback.push({
152
+ driver_id: candidate.manifest.driverId,
153
+ reason: `missing required capabilities: ${missing.join(', ')}`,
154
+ })
155
+ }
156
+
157
+ // Exhausted the chain — fail closed (ID-02).
158
+ throw new UnsatisfiedRequiredCapabilityError(
159
+ `No driver in the chain satisfies every required capability: [${required.join(', ')}]`,
160
+ required[0],
161
+ fallback,
162
+ )
163
+ }