@holoscript/framework 6.0.3

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 (329) hide show
  1. package/ALL-test-results.json +1 -0
  2. package/CHANGELOG.md +8 -0
  3. package/LICENSE +21 -0
  4. package/ROADMAP.md +175 -0
  5. package/dist/AgentManifest-CB4xM-Ma.d.cts +704 -0
  6. package/dist/AgentManifest-CB4xM-Ma.d.ts +704 -0
  7. package/dist/BehaviorTree-BrBFECv5.d.cts +103 -0
  8. package/dist/BehaviorTree-BrBFECv5.d.ts +103 -0
  9. package/dist/InvisibleWallet-BB6tFvRA.d.cts +1732 -0
  10. package/dist/InvisibleWallet-rtRrBOA8.d.ts +1732 -0
  11. package/dist/OrchestratorAgent-BvWgf9uw.d.cts +798 -0
  12. package/dist/OrchestratorAgent-Q_CbVTmO.d.ts +798 -0
  13. package/dist/agents/index.cjs +4790 -0
  14. package/dist/agents/index.d.cts +1788 -0
  15. package/dist/agents/index.d.ts +1788 -0
  16. package/dist/agents/index.js +4695 -0
  17. package/dist/ai/index.cjs +5347 -0
  18. package/dist/ai/index.d.cts +1753 -0
  19. package/dist/ai/index.d.ts +1753 -0
  20. package/dist/ai/index.js +5244 -0
  21. package/dist/behavior.cjs +449 -0
  22. package/dist/behavior.d.cts +130 -0
  23. package/dist/behavior.d.ts +130 -0
  24. package/dist/behavior.js +407 -0
  25. package/dist/economy/index.cjs +3659 -0
  26. package/dist/economy/index.d.cts +747 -0
  27. package/dist/economy/index.d.ts +747 -0
  28. package/dist/economy/index.js +3617 -0
  29. package/dist/implementations-D9T3un9D.d.cts +236 -0
  30. package/dist/implementations-D9T3un9D.d.ts +236 -0
  31. package/dist/index.cjs +24550 -0
  32. package/dist/index.d.cts +1729 -0
  33. package/dist/index.d.ts +1729 -0
  34. package/dist/index.js +24277 -0
  35. package/dist/learning/index.cjs +219 -0
  36. package/dist/learning/index.d.cts +104 -0
  37. package/dist/learning/index.d.ts +104 -0
  38. package/dist/learning/index.js +189 -0
  39. package/dist/negotiation/index.cjs +970 -0
  40. package/dist/negotiation/index.d.cts +610 -0
  41. package/dist/negotiation/index.d.ts +610 -0
  42. package/dist/negotiation/index.js +931 -0
  43. package/dist/skills/index.cjs +1118 -0
  44. package/dist/skills/index.d.cts +289 -0
  45. package/dist/skills/index.d.ts +289 -0
  46. package/dist/skills/index.js +1079 -0
  47. package/dist/swarm/index.cjs +5268 -0
  48. package/dist/swarm/index.d.cts +2433 -0
  49. package/dist/swarm/index.d.ts +2433 -0
  50. package/dist/swarm/index.js +5221 -0
  51. package/dist/training/index.cjs +2745 -0
  52. package/dist/training/index.d.cts +1734 -0
  53. package/dist/training/index.d.ts +1734 -0
  54. package/dist/training/index.js +2687 -0
  55. package/extract-failures.js +10 -0
  56. package/package.json +82 -0
  57. package/src/__tests__/bounty-marketplace.test.ts +374 -0
  58. package/src/__tests__/delegation.test.ts +144 -0
  59. package/src/__tests__/distributed-claimer.test.ts +147 -0
  60. package/src/__tests__/done-log-audit.test.ts +342 -0
  61. package/src/__tests__/framework.test.ts +865 -0
  62. package/src/__tests__/goal-synthesizer.test.ts +236 -0
  63. package/src/__tests__/presence.test.ts +223 -0
  64. package/src/__tests__/protocol-agent.test.ts +254 -0
  65. package/src/__tests__/revenue-splitter.test.ts +114 -0
  66. package/src/__tests__/scenario-driven-todo.test.ts +197 -0
  67. package/src/__tests__/self-improve.test.ts +349 -0
  68. package/src/__tests__/service-lifecycle.test.ts +237 -0
  69. package/src/__tests__/skill-router.test.ts +121 -0
  70. package/src/agents/AgentManifest.ts +493 -0
  71. package/src/agents/AgentRegistry.ts +475 -0
  72. package/src/agents/AgentTypes.ts +585 -0
  73. package/src/agents/AgentWalletRegistry.ts +83 -0
  74. package/src/agents/AuthenticatedCRDT.ts +388 -0
  75. package/src/agents/CapabilityMatcher.ts +453 -0
  76. package/src/agents/CrossRealityHandoff.ts +305 -0
  77. package/src/agents/CulturalMemory.ts +454 -0
  78. package/src/agents/FederatedRegistryAdapter.ts +429 -0
  79. package/src/agents/NormEngine.ts +450 -0
  80. package/src/agents/OrchestratorAgent.ts +414 -0
  81. package/src/agents/SkillWorkflowEngine.ts +472 -0
  82. package/src/agents/TaskDelegationService.ts +551 -0
  83. package/src/agents/__tests__/AgentManifest.prod.test.ts +134 -0
  84. package/src/agents/__tests__/AgentManifest.test.ts +182 -0
  85. package/src/agents/__tests__/AgentModule.test.ts +864 -0
  86. package/src/agents/__tests__/AgentRegistry.prod.test.ts +125 -0
  87. package/src/agents/__tests__/AgentRegistry.test.ts +148 -0
  88. package/src/agents/__tests__/AgentTypes.test.ts +534 -0
  89. package/src/agents/__tests__/AgentWalletRegistry.test.ts +152 -0
  90. package/src/agents/__tests__/AuthenticatedCRDT.test.ts +558 -0
  91. package/src/agents/__tests__/CapabilityMatcher.prod.test.ts +117 -0
  92. package/src/agents/__tests__/CapabilityMatcher.test.ts +178 -0
  93. package/src/agents/__tests__/CrossRealityHandoff.test.ts +402 -0
  94. package/src/agents/__tests__/CulturalMemory.test.ts +200 -0
  95. package/src/agents/__tests__/FederatedRegistryAdapter.test.ts +409 -0
  96. package/src/agents/__tests__/NormEngine.test.ts +276 -0
  97. package/src/agents/__tests__/OrchestratorAgent.test.ts +182 -0
  98. package/src/agents/__tests__/SkillWorkflowEngine.test.ts +357 -0
  99. package/src/agents/__tests__/TaskDelegationService.test.ts +446 -0
  100. package/src/agents/index.ts +107 -0
  101. package/src/agents/spatial-comms/Layer1RealTime.ts +621 -0
  102. package/src/agents/spatial-comms/Layer2A2A.ts +661 -0
  103. package/src/agents/spatial-comms/Layer3MCP.ts +651 -0
  104. package/src/agents/spatial-comms/ProtocolTypes.ts +543 -0
  105. package/src/agents/spatial-comms/SpatialCommClient.ts +483 -0
  106. package/src/agents/spatial-comms/__tests__/performance-benchmark.test.ts +465 -0
  107. package/src/agents/spatial-comms/examples/multi-agent-world-creation.ts +409 -0
  108. package/src/agents/spatial-comms/index.ts +66 -0
  109. package/src/ai/AIAdapter.ts +313 -0
  110. package/src/ai/AICopilot.ts +331 -0
  111. package/src/ai/AIOutputValidator.ts +203 -0
  112. package/src/ai/BTNodes.ts +239 -0
  113. package/src/ai/BehaviorSelector.ts +135 -0
  114. package/src/ai/BehaviorTree.ts +153 -0
  115. package/src/ai/Blackboard.ts +165 -0
  116. package/src/ai/GenerationAnalytics.ts +461 -0
  117. package/src/ai/GenerationCache.ts +265 -0
  118. package/src/ai/GoalPlanner.ts +165 -0
  119. package/src/ai/HoloScriptGenerator.ts +580 -0
  120. package/src/ai/InfluenceMap.ts +180 -0
  121. package/src/ai/NavMesh.ts +168 -0
  122. package/src/ai/PerceptionSystem.ts +178 -0
  123. package/src/ai/PromptTemplates.ts +453 -0
  124. package/src/ai/SemanticSearchService.ts +80 -0
  125. package/src/ai/StateMachine.ts +196 -0
  126. package/src/ai/SteeringBehavior.ts +150 -0
  127. package/src/ai/SteeringBehaviors.ts +244 -0
  128. package/src/ai/TrainingDataGenerator.ts +1082 -0
  129. package/src/ai/UtilityAI.ts +145 -0
  130. package/src/ai/__tests__/AIAdapter.prod.test.ts +259 -0
  131. package/src/ai/__tests__/AIAdapter.test.ts +109 -0
  132. package/src/ai/__tests__/AICopilot.prod.test.ts +341 -0
  133. package/src/ai/__tests__/AICopilot.test.ts +178 -0
  134. package/src/ai/__tests__/AIOutputValidator.prod.test.ts +226 -0
  135. package/src/ai/__tests__/AIOutputValidator.test.ts +138 -0
  136. package/src/ai/__tests__/BTNodes.prod.test.ts +391 -0
  137. package/src/ai/__tests__/BTNodes.test.ts +263 -0
  138. package/src/ai/__tests__/BehaviorSelector.prod.test.ts +129 -0
  139. package/src/ai/__tests__/BehaviorSelector.test.ts +132 -0
  140. package/src/ai/__tests__/BehaviorTree.prod.test.ts +266 -0
  141. package/src/ai/__tests__/BehaviorTree.test.ts +216 -0
  142. package/src/ai/__tests__/Blackboard.prod.test.ts +339 -0
  143. package/src/ai/__tests__/Blackboard.test.ts +183 -0
  144. package/src/ai/__tests__/GenerationAnalytics.prod.test.ts +141 -0
  145. package/src/ai/__tests__/GenerationAnalytics.test.ts +165 -0
  146. package/src/ai/__tests__/GenerationCache.prod.test.ts +144 -0
  147. package/src/ai/__tests__/GenerationCache.test.ts +171 -0
  148. package/src/ai/__tests__/GoalPlanner.prod.test.ts +189 -0
  149. package/src/ai/__tests__/GoalPlanner.test.ts +137 -0
  150. package/src/ai/__tests__/GoalPlannerDepth.prod.test.ts +217 -0
  151. package/src/ai/__tests__/HoloScriptGenerator.test.ts +125 -0
  152. package/src/ai/__tests__/InfluenceMap.prod.test.ts +146 -0
  153. package/src/ai/__tests__/InfluenceMap.test.ts +149 -0
  154. package/src/ai/__tests__/NavMesh.prod.test.ts +141 -0
  155. package/src/ai/__tests__/NavMesh.test.ts +159 -0
  156. package/src/ai/__tests__/PerceptionSystem.prod.test.ts +135 -0
  157. package/src/ai/__tests__/PerceptionSystem.test.ts +250 -0
  158. package/src/ai/__tests__/PromptTemplates.prod.test.ts +313 -0
  159. package/src/ai/__tests__/PromptTemplates.test.ts +146 -0
  160. package/src/ai/__tests__/SemanticSearch.test.ts +37 -0
  161. package/src/ai/__tests__/StateMachine.prod.test.ts +162 -0
  162. package/src/ai/__tests__/StateMachine.test.ts +163 -0
  163. package/src/ai/__tests__/SteeringBehavior.prod.test.ts +251 -0
  164. package/src/ai/__tests__/SteeringBehavior.test.ts +135 -0
  165. package/src/ai/__tests__/SteeringBehaviors.prod.test.ts +133 -0
  166. package/src/ai/__tests__/SteeringBehaviors.test.ts +151 -0
  167. package/src/ai/__tests__/TrainingDataGenerator.prod.test.ts +286 -0
  168. package/src/ai/__tests__/TrainingDataGenerator.test.ts +286 -0
  169. package/src/ai/__tests__/UtilityAI.prod.test.ts +207 -0
  170. package/src/ai/__tests__/UtilityAI.test.ts +155 -0
  171. package/src/ai/__tests__/adapters.prod.test.ts +263 -0
  172. package/src/ai/__tests__/adapters.test.ts +320 -0
  173. package/src/ai/adapters.ts +1585 -0
  174. package/src/ai/index.ts +130 -0
  175. package/src/behavior/BehaviorPresets.ts +140 -0
  176. package/src/behavior/BehaviorTree.ts +236 -0
  177. package/src/behavior/StateMachine.ts +176 -0
  178. package/src/behavior/StateTrait.ts +67 -0
  179. package/src/behavior/index.ts +8 -0
  180. package/src/behavior.ts +8 -0
  181. package/src/board/audit.ts +284 -0
  182. package/src/board/board-ops.ts +336 -0
  183. package/src/board/board-types.ts +302 -0
  184. package/src/board/index.ts +69 -0
  185. package/src/define-agent.ts +46 -0
  186. package/src/define-team.ts +33 -0
  187. package/src/delegation.ts +265 -0
  188. package/src/distributed-claimer.ts +228 -0
  189. package/src/economy/AgentBudgetEnforcer.ts +464 -0
  190. package/src/economy/BountyManager.ts +185 -0
  191. package/src/economy/CreatorRevenueAggregator.ts +460 -0
  192. package/src/economy/InvisibleWallet.ts +82 -0
  193. package/src/economy/KnowledgeMarketplace.ts +193 -0
  194. package/src/economy/PaymentWebhookService.ts +512 -0
  195. package/src/economy/RevenueSplitter.ts +156 -0
  196. package/src/economy/SubscriptionManager.ts +546 -0
  197. package/src/economy/UnifiedBudgetOptimizer.ts +635 -0
  198. package/src/economy/UsageMeter.ts +440 -0
  199. package/src/economy/_core-stubs.ts +219 -0
  200. package/src/economy/index.ts +100 -0
  201. package/src/economy/x402-facilitator.ts +1978 -0
  202. package/src/index.ts +348 -0
  203. package/src/knowledge/__tests__/knowledge-consolidator.test.ts +444 -0
  204. package/src/knowledge/__tests__/knowledge-store-vector.test.ts +291 -0
  205. package/src/knowledge/brain.ts +167 -0
  206. package/src/knowledge/consolidation.ts +581 -0
  207. package/src/knowledge/knowledge-consolidator.ts +510 -0
  208. package/src/knowledge/knowledge-store.ts +616 -0
  209. package/src/learning/MemoryConsolidator.ts +102 -0
  210. package/src/learning/MemoryScorer.ts +69 -0
  211. package/src/learning/ProceduralCompiler.ts +45 -0
  212. package/src/learning/SemanticClusterer.ts +66 -0
  213. package/src/learning/index.ts +8 -0
  214. package/src/llm/llm-adapter.ts +159 -0
  215. package/src/mesh/index.ts +309 -0
  216. package/src/negotiation/NegotiationProtocol.ts +694 -0
  217. package/src/negotiation/NegotiationTypes.ts +473 -0
  218. package/src/negotiation/VotingMechanisms.ts +691 -0
  219. package/src/negotiation/index.ts +49 -0
  220. package/src/protocol/goal-synthesizer.ts +317 -0
  221. package/src/protocol/implementations.ts +474 -0
  222. package/src/protocol/micro-phase-decomposer.ts +299 -0
  223. package/src/protocol/micro-step-decomposer.test.ts +306 -0
  224. package/src/protocol-agent.test.ts +353 -0
  225. package/src/protocol-agent.ts +670 -0
  226. package/src/self-improve/absorb-scanner.ts +252 -0
  227. package/src/self-improve/evolution-engine.ts +149 -0
  228. package/src/self-improve/framework-absorber.ts +214 -0
  229. package/src/self-improve/index.ts +50 -0
  230. package/src/self-improve/prompt-optimizer.ts +212 -0
  231. package/src/self-improve/test-generator.ts +175 -0
  232. package/src/skill-router.ts +186 -0
  233. package/src/skills/index.ts +5 -0
  234. package/src/skills/skill-md-bridge.ts +1699 -0
  235. package/src/swarm/ACOEngine.ts +261 -0
  236. package/src/swarm/CollectiveIntelligence.ts +383 -0
  237. package/src/swarm/ContributionSynthesizer.ts +481 -0
  238. package/src/swarm/LeaderElection.ts +393 -0
  239. package/src/swarm/PSOEngine.ts +206 -0
  240. package/src/swarm/QuorumPolicy.ts +173 -0
  241. package/src/swarm/SwarmCoordinator.ts +335 -0
  242. package/src/swarm/SwarmManager.ts +442 -0
  243. package/src/swarm/SwarmMembership.ts +456 -0
  244. package/src/swarm/VotingRound.ts +255 -0
  245. package/src/swarm/__tests__/ACOEngine.prod.test.ts +164 -0
  246. package/src/swarm/__tests__/ACOEngine.test.ts +117 -0
  247. package/src/swarm/__tests__/CollectiveIntelligence.prod.test.ts +296 -0
  248. package/src/swarm/__tests__/CollectiveIntelligence.test.ts +457 -0
  249. package/src/swarm/__tests__/ContributionSynthesizer.prod.test.ts +269 -0
  250. package/src/swarm/__tests__/ContributionSynthesizer.test.ts +254 -0
  251. package/src/swarm/__tests__/LeaderElection.prod.test.ts +196 -0
  252. package/src/swarm/__tests__/LeaderElection.test.ts +151 -0
  253. package/src/swarm/__tests__/PSOEngine.prod.test.ts +162 -0
  254. package/src/swarm/__tests__/PSOEngine.test.ts +106 -0
  255. package/src/swarm/__tests__/QuorumPolicy.prod.test.ts +216 -0
  256. package/src/swarm/__tests__/QuorumPolicy.test.ts +177 -0
  257. package/src/swarm/__tests__/SwarmCoordinator.prod.test.ts +186 -0
  258. package/src/swarm/__tests__/SwarmCoordinator.test.ts +167 -0
  259. package/src/swarm/__tests__/SwarmManager.prod.test.ts +308 -0
  260. package/src/swarm/__tests__/SwarmManager.test.ts +373 -0
  261. package/src/swarm/__tests__/SwarmMembership.prod.test.ts +273 -0
  262. package/src/swarm/__tests__/SwarmMembership.test.ts +264 -0
  263. package/src/swarm/__tests__/VotingRound.prod.test.ts +233 -0
  264. package/src/swarm/__tests__/VotingRound.test.ts +174 -0
  265. package/src/swarm/analytics/SwarmInspector.ts +476 -0
  266. package/src/swarm/analytics/SwarmMetrics.ts +449 -0
  267. package/src/swarm/analytics/__tests__/SwarmInspector.prod.test.ts +366 -0
  268. package/src/swarm/analytics/__tests__/SwarmInspector.test.ts +454 -0
  269. package/src/swarm/analytics/__tests__/SwarmMetrics.prod.test.ts +254 -0
  270. package/src/swarm/analytics/__tests__/SwarmMetrics.test.ts +370 -0
  271. package/src/swarm/analytics/index.ts +7 -0
  272. package/src/swarm/index.ts +69 -0
  273. package/src/swarm/messaging/BroadcastChannel.ts +509 -0
  274. package/src/swarm/messaging/GossipProtocol.ts +565 -0
  275. package/src/swarm/messaging/SwarmEventBus.ts +443 -0
  276. package/src/swarm/messaging/__tests__/BroadcastChannel.prod.test.ts +331 -0
  277. package/src/swarm/messaging/__tests__/BroadcastChannel.test.ts +333 -0
  278. package/src/swarm/messaging/__tests__/GossipProtocol.prod.test.ts +356 -0
  279. package/src/swarm/messaging/__tests__/GossipProtocol.test.ts +437 -0
  280. package/src/swarm/messaging/__tests__/SwarmEventBus.prod.test.ts +191 -0
  281. package/src/swarm/messaging/__tests__/SwarmEventBus.test.ts +247 -0
  282. package/src/swarm/messaging/index.ts +8 -0
  283. package/src/swarm/spatial/FlockingBehavior.ts +462 -0
  284. package/src/swarm/spatial/FormationController.ts +500 -0
  285. package/src/swarm/spatial/Vector3.ts +170 -0
  286. package/src/swarm/spatial/ZoneClaiming.ts +509 -0
  287. package/src/swarm/spatial/__tests__/FlockingBehavior.prod.test.ts +239 -0
  288. package/src/swarm/spatial/__tests__/FlockingBehavior.test.ts +298 -0
  289. package/src/swarm/spatial/__tests__/FormationController.prod.test.ts +240 -0
  290. package/src/swarm/spatial/__tests__/FormationController.test.ts +297 -0
  291. package/src/swarm/spatial/__tests__/Vector3.prod.test.ts +283 -0
  292. package/src/swarm/spatial/__tests__/Vector3.test.ts +224 -0
  293. package/src/swarm/spatial/__tests__/ZoneClaiming.prod.test.ts +246 -0
  294. package/src/swarm/spatial/__tests__/ZoneClaiming.test.ts +374 -0
  295. package/src/swarm/spatial/index.ts +28 -0
  296. package/src/team.ts +1245 -0
  297. package/src/training/LRScheduler.ts +377 -0
  298. package/src/training/QualityScoringPipeline.ts +139 -0
  299. package/src/training/SoftDedup.ts +461 -0
  300. package/src/training/SparsityMonitor.ts +685 -0
  301. package/src/training/SparsityMonitorTypes.ts +209 -0
  302. package/src/training/SpatialTrainingDataGenerator.ts +1526 -0
  303. package/src/training/SpatialTrainingDataTypes.ts +216 -0
  304. package/src/training/TrainingPipelineConfig.ts +215 -0
  305. package/src/training/constants.ts +94 -0
  306. package/src/training/index.ts +138 -0
  307. package/src/training/schema.ts +147 -0
  308. package/src/training/scripts/generate-novel-use-cases-dataset.ts +272 -0
  309. package/src/training/scripts/generate-spatial-dataset.ts +521 -0
  310. package/src/training/training/data/novel-use-cases.jsonl +153 -0
  311. package/src/training/training/data/spatial-reasoning-10k.jsonl +9354 -0
  312. package/src/training/trainingmonkey/TrainingMonkeyIntegration.ts +477 -0
  313. package/src/training/trainingmonkey/TrainingMonkeyTypes.ts +230 -0
  314. package/src/training/trainingmonkey/index.ts +26 -0
  315. package/src/training/trait-mappings.ts +157 -0
  316. package/src/types/core-stubs.d.ts +113 -0
  317. package/src/types.ts +304 -0
  318. package/test-output.txt +0 -0
  319. package/test-result.json +1 -0
  320. package/tsc-errors.txt +4 -0
  321. package/tsc_output.txt +0 -0
  322. package/tsconfig.json +14 -0
  323. package/tsup-learning-esm.config.ts +12 -0
  324. package/tsup.config.ts +21 -0
  325. package/typescript-errors-2.txt +0 -0
  326. package/typescript-errors.txt +22 -0
  327. package/vitest-log-utf8.txt +268 -0
  328. package/vitest-log.txt +0 -0
  329. package/vitest.config.ts +8 -0
@@ -0,0 +1,558 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import {
3
+ signOperation,
4
+ verifyOperation,
5
+ LWWRegister,
6
+ GCounter,
7
+ ORSet,
8
+ createAgentState,
9
+ setRegister,
10
+ getRegister,
11
+ incrementCounter,
12
+ getCounter,
13
+ mergeStates,
14
+ type DID,
15
+ type SignedOperation,
16
+ } from '../AuthenticatedCRDT';
17
+
18
+ // =============================================================================
19
+ // HELPERS
20
+ // =============================================================================
21
+
22
+ function makeDID(overrides?: Partial<DID>): DID {
23
+ return {
24
+ id: 'did:key:z6MkTest',
25
+ deviceId: 'device-1',
26
+ scope: ['*'],
27
+ revoked: false,
28
+ ...overrides,
29
+ };
30
+ }
31
+
32
+ function makeScopedDID(scopes: string[]): DID {
33
+ return makeDID({ scope: scopes });
34
+ }
35
+
36
+ function makeRevokedDID(): DID {
37
+ return makeDID({ revoked: true });
38
+ }
39
+
40
+ // =============================================================================
41
+ // SIGN & VERIFY
42
+ // =============================================================================
43
+
44
+ describe('AuthenticatedCRDT — signOperation / verifyOperation', () => {
45
+ it('creates a signed operation with all fields', () => {
46
+ const signer = makeDID();
47
+ const op = signOperation('hello', signer, 'state:name', 1);
48
+ expect(op.payload).toBe('hello');
49
+ expect(op.signer).toBe(signer);
50
+ expect(op.timestamp).toBe(1);
51
+ expect(op.scopeTag).toBe('state:name');
52
+ expect(typeof op.signature).toBe('string');
53
+ expect(op.signature.length).toBeGreaterThan(0);
54
+ });
55
+
56
+ it('verifies a valid signed operation', () => {
57
+ const signer = makeDID();
58
+ const op = signOperation({ x: 1 }, signer, 'state:pos', 10);
59
+ const result = verifyOperation(op);
60
+ expect(result.valid).toBe(true);
61
+ expect(result.reason).toBeUndefined();
62
+ });
63
+
64
+ it('rejects a revoked signer', () => {
65
+ const signer = makeRevokedDID();
66
+ const op = signOperation('data', signer, 'state:x', 1);
67
+ const result = verifyOperation(op);
68
+ expect(result.valid).toBe(false);
69
+ expect(result.reason).toBe('Signer DID revoked');
70
+ });
71
+
72
+ it('rejects out-of-scope operations', () => {
73
+ const signer = makeScopedDID(['state:name']);
74
+ const op = signOperation('val', signer, 'state:secret', 1);
75
+ const result = verifyOperation(op);
76
+ expect(result.valid).toBe(false);
77
+ expect(result.reason).toContain("lacks scope 'state:secret'");
78
+ });
79
+
80
+ it('wildcard scope (*) allows any scope tag', () => {
81
+ const signer = makeDID({ scope: ['*'] });
82
+ const op = signOperation('val', signer, 'anything:here', 1);
83
+ expect(verifyOperation(op).valid).toBe(true);
84
+ });
85
+
86
+ it('rejects tampered signatures', () => {
87
+ const signer = makeDID();
88
+ const op = signOperation('data', signer, 'state:x', 1);
89
+ const tampered: SignedOperation<string> = { ...op, signature: 'deadbeef' };
90
+ const result = verifyOperation(tampered);
91
+ expect(result.valid).toBe(false);
92
+ expect(result.reason).toBe('Signature mismatch');
93
+ });
94
+
95
+ it('signature is deterministic', () => {
96
+ const signer = makeDID();
97
+ const op1 = signOperation('same', signer, 'tag', 5);
98
+ const op2 = signOperation('same', signer, 'tag', 5);
99
+ expect(op1.signature).toBe(op2.signature);
100
+ });
101
+
102
+ it('different payloads produce different signatures', () => {
103
+ const signer = makeDID();
104
+ const op1 = signOperation('alpha', signer, 'tag', 1);
105
+ const op2 = signOperation('bravo', signer, 'tag', 1);
106
+ expect(op1.signature).not.toBe(op2.signature);
107
+ });
108
+ });
109
+
110
+ // =============================================================================
111
+ // LWW-REGISTER
112
+ // =============================================================================
113
+
114
+ describe('AuthenticatedCRDT — LWWRegister', () => {
115
+ it('starts with initial value', () => {
116
+ const reg = new LWWRegister('initial');
117
+ expect(reg.get()).toBe('initial');
118
+ expect(reg.getTimestamp()).toBe(0);
119
+ });
120
+
121
+ it('accepts a valid set operation', () => {
122
+ const reg = new LWWRegister('old');
123
+ const signer = makeDID();
124
+ const op = signOperation('new', signer, 'state:val', 1);
125
+ const result = reg.set(op);
126
+ expect(result.accepted).toBe(true);
127
+ expect(reg.get()).toBe('new');
128
+ expect(reg.getTimestamp()).toBe(1);
129
+ });
130
+
131
+ it('rejects stale timestamps', () => {
132
+ const reg = new LWWRegister('v0');
133
+ const signer = makeDID();
134
+ reg.set(signOperation('v1', signer, 'state:x', 10));
135
+ const result = reg.set(signOperation('v2', signer, 'state:x', 5));
136
+ expect(result.accepted).toBe(false);
137
+ expect(result.reason).toBe('Stale timestamp');
138
+ expect(reg.get()).toBe('v1');
139
+ });
140
+
141
+ it('rejects equal timestamps (not strictly greater)', () => {
142
+ const reg = new LWWRegister('v0');
143
+ const signer = makeDID();
144
+ reg.set(signOperation('v1', signer, 'state:x', 10));
145
+ const result = reg.set(signOperation('v2', signer, 'state:x', 10));
146
+ expect(result.accepted).toBe(false);
147
+ });
148
+
149
+ it('rejects revoked signers', () => {
150
+ const reg = new LWWRegister('v0');
151
+ const signer = makeRevokedDID();
152
+ const op = signOperation('v1', signer, 'state:x', 1);
153
+ const result = reg.set(op);
154
+ expect(result.accepted).toBe(false);
155
+ expect(result.reason).toBe('Signer DID revoked');
156
+ });
157
+
158
+ it('tracks operation history', () => {
159
+ const reg = new LWWRegister(0);
160
+ const signer = makeDID();
161
+ reg.set(signOperation(1, signer, 'state:n', 1));
162
+ reg.set(signOperation(2, signer, 'state:n', 2));
163
+ reg.set(signOperation(3, signer, 'state:n', 3));
164
+ expect(reg.getHistory()).toHaveLength(3);
165
+ });
166
+
167
+ it('merge takes the higher-timestamp value', () => {
168
+ const signer = makeDID();
169
+ const regA = new LWWRegister('A');
170
+ regA.set(signOperation('A-val', signer, 'state:x', 5));
171
+
172
+ const regB = new LWWRegister('B');
173
+ regB.set(signOperation('B-val', signer, 'state:x', 10));
174
+
175
+ regA.merge(regB);
176
+ expect(regA.get()).toBe('B-val');
177
+ });
178
+
179
+ it('merge does not downgrade', () => {
180
+ const signer = makeDID();
181
+ const regA = new LWWRegister('A');
182
+ regA.set(signOperation('A-val', signer, 'state:x', 10));
183
+
184
+ const regB = new LWWRegister('B');
185
+ regB.set(signOperation('B-val', signer, 'state:x', 5));
186
+
187
+ regA.merge(regB);
188
+ expect(regA.get()).toBe('A-val');
189
+ });
190
+ });
191
+
192
+ // =============================================================================
193
+ // G-COUNTER
194
+ // =============================================================================
195
+
196
+ describe('AuthenticatedCRDT — GCounter', () => {
197
+ it('starts at zero', () => {
198
+ const counter = new GCounter();
199
+ expect(counter.value()).toBe(0);
200
+ });
201
+
202
+ it('increments by 1 by default', () => {
203
+ const counter = new GCounter();
204
+ counter.increment('node-a');
205
+ expect(counter.value()).toBe(1);
206
+ expect(counter.nodeValue('node-a')).toBe(1);
207
+ });
208
+
209
+ it('increments by custom amount', () => {
210
+ const counter = new GCounter();
211
+ counter.increment('node-a', 5);
212
+ expect(counter.value()).toBe(5);
213
+ });
214
+
215
+ it('tracks per-node counts independently', () => {
216
+ const counter = new GCounter();
217
+ counter.increment('node-a', 3);
218
+ counter.increment('node-b', 7);
219
+ expect(counter.nodeValue('node-a')).toBe(3);
220
+ expect(counter.nodeValue('node-b')).toBe(7);
221
+ expect(counter.value()).toBe(10);
222
+ });
223
+
224
+ it('returns 0 for unknown nodes', () => {
225
+ const counter = new GCounter();
226
+ expect(counter.nodeValue('ghost')).toBe(0);
227
+ });
228
+
229
+ it('merge takes max per node', () => {
230
+ const a = new GCounter();
231
+ a.increment('x', 5);
232
+ a.increment('y', 3);
233
+
234
+ const b = new GCounter();
235
+ b.increment('x', 3);
236
+ b.increment('y', 7);
237
+ b.increment('z', 2);
238
+
239
+ a.merge(b);
240
+ expect(a.nodeValue('x')).toBe(5); // max(5,3)
241
+ expect(a.nodeValue('y')).toBe(7); // max(3,7)
242
+ expect(a.nodeValue('z')).toBe(2); // new from b
243
+ expect(a.value()).toBe(14);
244
+ });
245
+
246
+ it('merge is idempotent', () => {
247
+ const a = new GCounter();
248
+ a.increment('x', 5);
249
+ const b = new GCounter();
250
+ b.increment('x', 3);
251
+
252
+ a.merge(b);
253
+ const v1 = a.value();
254
+ a.merge(b);
255
+ expect(a.value()).toBe(v1);
256
+ });
257
+
258
+ it('toJSON / fromJSON round-trips', () => {
259
+ const original = new GCounter();
260
+ original.increment('a', 10);
261
+ original.increment('b', 20);
262
+
263
+ const json = original.toJSON();
264
+ expect(json).toEqual({ a: 10, b: 20 });
265
+
266
+ const restored = GCounter.fromJSON(json);
267
+ expect(restored.value()).toBe(30);
268
+ expect(restored.nodeValue('a')).toBe(10);
269
+ expect(restored.nodeValue('b')).toBe(20);
270
+ });
271
+ });
272
+
273
+ // =============================================================================
274
+ // OR-SET
275
+ // =============================================================================
276
+
277
+ describe('AuthenticatedCRDT — ORSet', () => {
278
+ it('starts empty', () => {
279
+ const set = new ORSet<string>();
280
+ expect(set.size).toBe(0);
281
+ expect(set.values()).toEqual([]);
282
+ });
283
+
284
+ it('add inserts an element', () => {
285
+ const set = new ORSet<string>();
286
+ const signer = makeDID();
287
+ const tag = set.add('hello', signer, 1);
288
+ expect(tag).toBeTruthy();
289
+ expect(set.has('hello')).toBe(true);
290
+ expect(set.size).toBe(1);
291
+ });
292
+
293
+ it('add from revoked signer is rejected', () => {
294
+ const set = new ORSet<string>();
295
+ const signer = makeRevokedDID();
296
+ const tag = set.add('secret', signer, 1);
297
+ expect(tag).toBe('');
298
+ expect(set.has('secret')).toBe(false);
299
+ });
300
+
301
+ it('remove removes all copies of a value', () => {
302
+ const set = new ORSet<string>();
303
+ const signer = makeDID();
304
+ set.add('x', signer, 1);
305
+ set.add('x', signer, 2); // duplicate add
306
+ expect(set.has('x')).toBe(true);
307
+
308
+ const removed = set.remove('x', signer);
309
+ expect(removed).toBe(2);
310
+ expect(set.has('x')).toBe(false);
311
+ });
312
+
313
+ it('remove returns 0 for non-existent values', () => {
314
+ const set = new ORSet<string>();
315
+ const signer = makeDID();
316
+ expect(set.remove('ghost', signer)).toBe(0);
317
+ });
318
+
319
+ it('values are deduplicated', () => {
320
+ const set = new ORSet<number>();
321
+ const signer = makeDID();
322
+ set.add(42, signer, 1);
323
+ set.add(42, signer, 2);
324
+ set.add(42, signer, 3);
325
+ // Internal elements are 3, but unique values is 1
326
+ expect(set.values()).toEqual([42]);
327
+ expect(set.size).toBe(1);
328
+ });
329
+
330
+ it('works with object values', () => {
331
+ const set = new ORSet<{ id: number; name: string }>();
332
+ const signer = makeDID();
333
+ set.add({ id: 1, name: 'Alice' }, signer, 1);
334
+ set.add({ id: 2, name: 'Bob' }, signer, 2);
335
+ expect(set.size).toBe(2);
336
+ expect(set.has({ id: 1, name: 'Alice' })).toBe(true);
337
+ expect(set.has({ id: 3, name: 'Charlie' })).toBe(false);
338
+ });
339
+
340
+ it('merge: adds from other win over concurrent state', () => {
341
+ const signerA = makeDID({ id: 'did:key:A', deviceId: 'A' });
342
+ const signerB = makeDID({ id: 'did:key:B', deviceId: 'B' });
343
+
344
+ const setA = new ORSet<string>();
345
+ setA.add('shared', signerA, 1);
346
+
347
+ const setB = new ORSet<string>();
348
+ setB.add('only-b', signerB, 1);
349
+
350
+ setA.merge(setB);
351
+ expect(setA.has('shared')).toBe(true);
352
+ expect(setA.has('only-b')).toBe(true);
353
+ });
354
+
355
+ it('merge: tombstones from other are applied when tags collide', () => {
356
+ const signer = makeDID();
357
+
358
+ const setA = new ORSet<string>();
359
+ setA.add('doomed', signer, 1);
360
+
361
+ // setB has same element added with same signer/timestamp, generating the same tag
362
+ const setB = new ORSet<string>();
363
+ setB.add('doomed', signer, 1);
364
+ setB.remove('doomed', signer);
365
+
366
+ // After merge, tombstone from B deletes A's element because tags collide
367
+ // (same signer.id + same timestamp + same tagCounter start = identical tag)
368
+ setA.merge(setB);
369
+ expect(setA.has('doomed')).toBe(false);
370
+ });
371
+
372
+ it('merge: concurrent adds with different signers survive removes', () => {
373
+ const signerA = makeDID({ id: 'did:key:A', deviceId: 'A' });
374
+ const signerB = makeDID({ id: 'did:key:B', deviceId: 'B' });
375
+
376
+ const setA = new ORSet<string>();
377
+ setA.add('item', signerA, 1); // tag: did:key:A_1_0
378
+
379
+ const setB = new ORSet<string>();
380
+ setB.add('item', signerB, 1); // tag: did:key:B_1_0 (different!)
381
+ setB.remove('item', signerB);
382
+
383
+ // A's element survives because its tag differs from B's tombstone
384
+ setA.merge(setB);
385
+ expect(setA.has('item')).toBe(true);
386
+ });
387
+
388
+ it('merge: revoked signers are filtered out', () => {
389
+ const revokedSigner = makeRevokedDID();
390
+ const goodSigner = makeDID({ id: 'did:key:good' });
391
+
392
+ const setA = new ORSet<string>();
393
+ const setB = new ORSet<string>();
394
+ setB.add('good', goodSigner, 1);
395
+ // Can't add via revoked signer (returns '')
396
+ setB.add('bad', revokedSigner, 2);
397
+
398
+ setA.merge(setB);
399
+ expect(setA.has('good')).toBe(true);
400
+ expect(setA.has('bad')).toBe(false);
401
+ });
402
+ });
403
+
404
+ // =============================================================================
405
+ // AUTHENTICATED AGENT STATE
406
+ // =============================================================================
407
+
408
+ describe('AuthenticatedCRDT — Agent State helpers', () => {
409
+ it('createAgentState initializes empty state', () => {
410
+ const state = createAgentState('did:key:z6MkAgent');
411
+ expect(state.agentDID).toBe('did:key:z6MkAgent');
412
+ expect(state.registers.size).toBe(0);
413
+ expect(state.counters.size).toBe(0);
414
+ expect(state.sets.size).toBe(0);
415
+ expect(state.lastSync).toBe(0);
416
+ });
417
+
418
+ it('setRegister / getRegister round-trip', () => {
419
+ const state = createAgentState('agent-1');
420
+ const signer = makeDID({ scope: ['state:name'] });
421
+ const result = setRegister(state, 'name', 'HoloBot', signer, 1);
422
+ expect(result.accepted).toBe(true);
423
+ expect(getRegister<string>(state, 'name')).toBe('HoloBot');
424
+ });
425
+
426
+ it('setRegister creates register on first use', () => {
427
+ const state = createAgentState('agent-1');
428
+ const signer = makeDID();
429
+ expect(state.registers.has('new-key')).toBe(false);
430
+ setRegister(state, 'new-key', 42, signer, 1);
431
+ expect(state.registers.has('new-key')).toBe(true);
432
+ expect(getRegister<number>(state, 'new-key')).toBe(42);
433
+ });
434
+
435
+ it('setRegister rejects stale updates', () => {
436
+ const state = createAgentState('agent-1');
437
+ const signer = makeDID();
438
+ setRegister(state, 'x', 'first', signer, 10);
439
+ const result = setRegister(state, 'x', 'second', signer, 5);
440
+ expect(result.accepted).toBe(false);
441
+ expect(getRegister(state, 'x')).toBe('first');
442
+ });
443
+
444
+ it('getRegister returns undefined for missing keys', () => {
445
+ const state = createAgentState('agent-1');
446
+ expect(getRegister(state, 'missing')).toBeUndefined();
447
+ });
448
+
449
+ it('incrementCounter / getCounter', () => {
450
+ const state = createAgentState('agent-1');
451
+ incrementCounter(state, 'actions', 'node-a', 3);
452
+ incrementCounter(state, 'actions', 'node-b', 7);
453
+ expect(getCounter(state, 'actions')).toBe(10);
454
+ });
455
+
456
+ it('getCounter returns 0 for missing counters', () => {
457
+ const state = createAgentState('agent-1');
458
+ expect(getCounter(state, 'nonexistent')).toBe(0);
459
+ });
460
+
461
+ it('incrementCounter creates counter on first use', () => {
462
+ const state = createAgentState('agent-1');
463
+ expect(state.counters.has('new-counter')).toBe(false);
464
+ incrementCounter(state, 'new-counter', 'n', 1);
465
+ expect(state.counters.has('new-counter')).toBe(true);
466
+ expect(getCounter(state, 'new-counter')).toBe(1);
467
+ });
468
+ });
469
+
470
+ // =============================================================================
471
+ // STATE MERGING
472
+ // =============================================================================
473
+
474
+ describe('AuthenticatedCRDT — mergeStates', () => {
475
+ it('merges registers from remote into local', () => {
476
+ const signer = makeDID();
477
+ const local = createAgentState('local');
478
+ const remote = createAgentState('remote');
479
+
480
+ setRegister(local, 'x', 'local-val', signer, 5);
481
+ setRegister(remote, 'x', 'remote-val', signer, 10);
482
+ setRegister(remote, 'y', 'only-remote', signer, 1);
483
+
484
+ mergeStates(local, remote);
485
+ expect(getRegister(local, 'x')).toBe('remote-val'); // remote has higher ts
486
+ expect(getRegister(local, 'y')).toBe('only-remote'); // new from remote
487
+ });
488
+
489
+ it('local register wins when it has higher timestamp', () => {
490
+ const signer = makeDID();
491
+ const local = createAgentState('local');
492
+ const remote = createAgentState('remote');
493
+
494
+ setRegister(local, 'x', 'local-wins', signer, 20);
495
+ setRegister(remote, 'x', 'remote-loses', signer, 10);
496
+
497
+ mergeStates(local, remote);
498
+ expect(getRegister(local, 'x')).toBe('local-wins');
499
+ });
500
+
501
+ it('merges counters from remote into local', () => {
502
+ const local = createAgentState('local');
503
+ const remote = createAgentState('remote');
504
+
505
+ incrementCounter(local, 'ops', 'a', 5);
506
+ incrementCounter(remote, 'ops', 'a', 3);
507
+ incrementCounter(remote, 'ops', 'b', 7);
508
+
509
+ mergeStates(local, remote);
510
+ expect(getCounter(local, 'ops')).toBe(12); // max(5,3) + 7
511
+ });
512
+
513
+ it('merges sets from remote into local', () => {
514
+ const signer = makeDID();
515
+ const local = createAgentState('local');
516
+ const remote = createAgentState('remote');
517
+
518
+ // Manually add sets
519
+ local.sets.set('tags', new ORSet<string>());
520
+ local.sets.get('tags')!.add('alpha', signer, 1);
521
+
522
+ remote.sets.set('tags', new ORSet<string>());
523
+ remote.sets.get('tags')!.add('beta', signer, 2);
524
+
525
+ mergeStates(local, remote);
526
+ const tags = local.sets.get('tags')!;
527
+ expect(tags.has('alpha')).toBe(true);
528
+ expect(tags.has('beta')).toBe(true);
529
+ });
530
+
531
+ it('updates lastSync to the max of both', () => {
532
+ const local = createAgentState('local');
533
+ local.lastSync = 100;
534
+ const remote = createAgentState('remote');
535
+ remote.lastSync = 200;
536
+
537
+ mergeStates(local, remote);
538
+ expect(local.lastSync).toBe(200);
539
+ });
540
+
541
+ it('merge is commutative for counters', () => {
542
+ const a = createAgentState('a');
543
+ const b = createAgentState('b');
544
+
545
+ incrementCounter(a, 'c', 'x', 5);
546
+ incrementCounter(b, 'c', 'y', 3);
547
+
548
+ const a2 = createAgentState('a2');
549
+ incrementCounter(a2, 'c', 'x', 5);
550
+ const b2 = createAgentState('b2');
551
+ incrementCounter(b2, 'c', 'y', 3);
552
+
553
+ mergeStates(a, b);
554
+ mergeStates(b2, a2);
555
+
556
+ expect(getCounter(a, 'c')).toBe(getCounter(b2, 'c'));
557
+ });
558
+ });
@@ -0,0 +1,117 @@
1
+ /**
2
+ * CapabilityMatcher — Production Test Suite
3
+ *
4
+ * Covers: matchCapability, matchAgent, findMatches, findBest, sortMatches.
5
+ */
6
+ import { describe, it, expect } from 'vitest';
7
+ import { CapabilityMatcher } from '../CapabilityMatcher';
8
+ import type { AgentManifest, AgentCapability } from '../AgentManifest';
9
+
10
+ function mkManifest(id: string, caps: Partial<AgentCapability>[]): AgentManifest {
11
+ return {
12
+ id,
13
+ name: `Agent ${id}`,
14
+ version: '1.0.0',
15
+ capabilities: caps.map((c) => ({ type: 'compute', domain: 'physics', ...c })),
16
+ endpoints: [{ protocol: 'http' as const, address: 'localhost' }],
17
+ trustLevel: 'local',
18
+ status: 'online',
19
+ } as AgentManifest;
20
+ }
21
+
22
+ describe('CapabilityMatcher — Production', () => {
23
+ const matcher = new CapabilityMatcher();
24
+
25
+ // ─── matchCapability ─────────────────────────────────────────────
26
+ it('matches capability by type', () => {
27
+ const cap: AgentCapability = { type: 'compute', domain: 'physics' };
28
+ const match = matcher.matchCapability(cap, { type: 'compute' });
29
+ expect(match).not.toBeNull();
30
+ expect(match!.score).toBeGreaterThan(0);
31
+ });
32
+
33
+ it('returns null for non-matching type', () => {
34
+ const cap: AgentCapability = { type: 'compute', domain: 'physics' };
35
+ expect(matcher.matchCapability(cap, { type: 'render' })).toBeNull();
36
+ });
37
+
38
+ it('matches capability by domain', () => {
39
+ const cap: AgentCapability = { type: 'compute', domain: 'physics' };
40
+ const match = matcher.matchCapability(cap, { domain: 'physics' });
41
+ expect(match).not.toBeNull();
42
+ });
43
+
44
+ // ─── matchAgent ──────────────────────────────────────────────────
45
+ it('matchAgent returns match for matching agent', () => {
46
+ const m = mkManifest('a1', [{ type: 'compute', domain: 'physics' }]);
47
+ const match = matcher.matchAgent(m, { type: 'compute' });
48
+ expect(match).not.toBeNull();
49
+ expect(match!.manifest.id).toBe('a1');
50
+ });
51
+
52
+ it('matchAgent returns null for non-matching agent', () => {
53
+ const m = mkManifest('a1', [{ type: 'render', domain: 'graphics' }]);
54
+ expect(matcher.matchAgent(m, { type: 'compute' })).toBeNull();
55
+ });
56
+
57
+ // ─── findMatches ─────────────────────────────────────────────────
58
+ it('findMatches filters and scores agents', () => {
59
+ const agents = [
60
+ mkManifest('a1', [{ type: 'compute', domain: 'physics' }]),
61
+ mkManifest('a2', [{ type: 'render', domain: 'graphics' }]),
62
+ mkManifest('a3', [{ type: 'compute', domain: 'ai' }]),
63
+ ];
64
+ const matches = matcher.findMatches(agents, { type: 'compute' });
65
+ expect(matches.length).toBe(2);
66
+ expect(matches.every((m) => m.manifest.id !== 'a2')).toBe(true);
67
+ });
68
+
69
+ it('findMatches respects limit', () => {
70
+ const agents = [
71
+ mkManifest('a1', [{ type: 'compute' }]),
72
+ mkManifest('a2', [{ type: 'compute' }]),
73
+ mkManifest('a3', [{ type: 'compute' }]),
74
+ ];
75
+ const matches = matcher.findMatches(agents, { type: 'compute', limit: 1 });
76
+ expect(matches.length).toBe(1);
77
+ });
78
+
79
+ // ─── findBest ────────────────────────────────────────────────────
80
+ it('findBest returns top match', () => {
81
+ const agents = [
82
+ mkManifest('a1', [{ type: 'compute', domain: 'physics' }]),
83
+ mkManifest('a2', [{ type: 'compute', domain: 'physics', priority: 10 }]),
84
+ ];
85
+ const best = matcher.findBest(agents, { type: 'compute' });
86
+ expect(best).not.toBeNull();
87
+ });
88
+
89
+ it('findBest returns null for no matches', () => {
90
+ expect(matcher.findBest([], { type: 'compute' })).toBeNull();
91
+ });
92
+
93
+ // ─── sortMatches ─────────────────────────────────────────────────
94
+ it('sortMatches by score desc', () => {
95
+ const agents = [
96
+ mkManifest('low', [{ type: 'compute' }]),
97
+ mkManifest('high', [{ type: 'compute', domain: 'physics' }]),
98
+ ];
99
+ const matches = matcher.findMatches(agents, { type: 'compute', domain: 'physics' });
100
+ matcher.sortMatches(matches, 'score', 'desc');
101
+ if (matches.length >= 2) {
102
+ expect(matches[0].score).toBeGreaterThanOrEqual(matches[1].score);
103
+ }
104
+ });
105
+
106
+ it('sortMatches by name asc', () => {
107
+ const agents = [
108
+ mkManifest('z-agent', [{ type: 'compute' }]),
109
+ mkManifest('a-agent', [{ type: 'compute' }]),
110
+ ];
111
+ const matches = matcher.findMatches(agents, { type: 'compute' });
112
+ matcher.sortMatches(matches, 'name', 'asc');
113
+ if (matches.length >= 2) {
114
+ expect(matches[0].manifest.name <= matches[1].manifest.name).toBe(true);
115
+ }
116
+ });
117
+ });