@rigour-labs/core 6.7.10 → 6.8.1-rc.1

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 (346) hide show
  1. package/dist/brief/briefing.d.ts +59 -0
  2. package/dist/brief/briefing.js +126 -0
  3. package/dist/index.d.ts +2 -0
  4. package/dist/index.js +2 -0
  5. package/dist/inference/cloud-provider.js +12 -2
  6. package/dist/review/backtest-init.d.ts +8 -5
  7. package/dist/review/backtest-init.js +27 -9
  8. package/dist/review/backtest-last.js +3 -1
  9. package/dist/review/reviewer/adapters.d.ts +4 -0
  10. package/dist/review/reviewer/adapters.js +23 -0
  11. package/dist/review/reviewer/inputs.d.ts +4 -1
  12. package/dist/review/reviewer/inputs.js +36 -2
  13. package/dist/review/reviewer/record.d.ts +5 -0
  14. package/dist/review/reviewer/record.js +3 -3
  15. package/dist/review/reviewer/verdict.d.ts +14 -0
  16. package/dist/review/reviewer/verdict.js +46 -6
  17. package/dist/review/reviewer.d.ts +2 -0
  18. package/dist/review/reviewer.js +24 -5
  19. package/dist/review-learning/repo-rules.d.ts +14 -2
  20. package/dist/review-learning/repo-rules.js +59 -9
  21. package/dist/task/thread.d.ts +47 -0
  22. package/dist/task/thread.js +234 -0
  23. package/dist/templates/universal-config.js +4 -0
  24. package/dist/types/index.d.ts +21 -0
  25. package/dist/types/index.js +7 -0
  26. package/package.json +11 -8
  27. package/dist/context/automatic-index-cache.test.d.ts +0 -1
  28. package/dist/context/automatic-index-cache.test.js +0 -30
  29. package/dist/context/automatic-index.test.d.ts +0 -1
  30. package/dist/context/automatic-index.test.js +0 -45
  31. package/dist/context/cache-engine.test.d.ts +0 -1
  32. package/dist/context/cache-engine.test.js +0 -99
  33. package/dist/context/dependency-graph.test.d.ts +0 -1
  34. package/dist/context/dependency-graph.test.js +0 -13
  35. package/dist/context/index-status.test.d.ts +0 -1
  36. package/dist/context/index-status.test.js +0 -26
  37. package/dist/context.test.d.ts +0 -1
  38. package/dist/context.test.js +0 -228
  39. package/dist/deep/agent-review.test.d.ts +0 -1
  40. package/dist/deep/agent-review.test.js +0 -53
  41. package/dist/deep/code-context.test.d.ts +0 -1
  42. package/dist/deep/code-context.test.js +0 -48
  43. package/dist/deep/code-pass.test.d.ts +0 -1
  44. package/dist/deep/code-pass.test.js +0 -112
  45. package/dist/deep/code-review-prompt.test.d.ts +0 -1
  46. package/dist/deep/code-review-prompt.test.js +0 -20
  47. package/dist/deep/code-verifier.test.d.ts +0 -1
  48. package/dist/deep/code-verifier.test.js +0 -54
  49. package/dist/deep/diff-tests/calls.test.d.ts +0 -1
  50. package/dist/deep/diff-tests/calls.test.js +0 -33
  51. package/dist/deep/diff-tests/run.test.d.ts +0 -1
  52. package/dist/deep/diff-tests/run.test.js +0 -58
  53. package/dist/deep/fact-extractor.test.d.ts +0 -1
  54. package/dist/deep/fact-extractor.test.js +0 -581
  55. package/dist/deep/parse-findings.test.d.ts +0 -1
  56. package/dist/deep/parse-findings.test.js +0 -25
  57. package/dist/deep/pr-review.test.d.ts +0 -1
  58. package/dist/deep/pr-review.test.js +0 -80
  59. package/dist/deep/prompts.test.d.ts +0 -1
  60. package/dist/deep/prompts.test.js +0 -235
  61. package/dist/deep/reference-pack.test.d.ts +0 -1
  62. package/dist/deep/reference-pack.test.js +0 -46
  63. package/dist/deep/related-changes.test.d.ts +0 -1
  64. package/dist/deep/related-changes.test.js +0 -33
  65. package/dist/deep/review-context-export.test.d.ts +0 -1
  66. package/dist/deep/review-context-export.test.js +0 -32
  67. package/dist/deep/review-tools.test.d.ts +0 -1
  68. package/dist/deep/review-tools.test.js +0 -39
  69. package/dist/deep/risk.test.d.ts +0 -1
  70. package/dist/deep/risk.test.js +0 -101
  71. package/dist/deep/verifier.test.d.ts +0 -1
  72. package/dist/deep/verifier.test.js +0 -635
  73. package/dist/discovery.test.d.ts +0 -1
  74. package/dist/discovery.test.js +0 -93
  75. package/dist/environment.test.d.ts +0 -1
  76. package/dist/environment.test.js +0 -94
  77. package/dist/firewall/firewall.test.d.ts +0 -1
  78. package/dist/firewall/firewall.test.js +0 -117
  79. package/dist/firewall/trust-boundaries.test.d.ts +0 -1
  80. package/dist/firewall/trust-boundaries.test.js +0 -64
  81. package/dist/firewall/trusted-control.test.d.ts +0 -1
  82. package/dist/firewall/trusted-control.test.js +0 -188
  83. package/dist/gates/agent-team.test.d.ts +0 -1
  84. package/dist/gates/agent-team.test.js +0 -113
  85. package/dist/gates/ast.test.d.ts +0 -1
  86. package/dist/gates/ast.test.js +0 -112
  87. package/dist/gates/checkpoint.test.d.ts +0 -1
  88. package/dist/gates/checkpoint.test.js +0 -105
  89. package/dist/gates/content.test.d.ts +0 -1
  90. package/dist/gates/content.test.js +0 -73
  91. package/dist/gates/coverage.test.d.ts +0 -1
  92. package/dist/gates/coverage.test.js +0 -53
  93. package/dist/gates/dedupe-failures.test.d.ts +0 -1
  94. package/dist/gates/dedupe-failures.test.js +0 -12
  95. package/dist/gates/deep-analysis.test.d.ts +0 -1
  96. package/dist/gates/deep-analysis.test.js +0 -86
  97. package/dist/gates/deep-intent.test.d.ts +0 -1
  98. package/dist/gates/deep-intent.test.js +0 -51
  99. package/dist/gates/deep-timeout.test.d.ts +0 -1
  100. package/dist/gates/deep-timeout.test.js +0 -10
  101. package/dist/gates/deprecated-apis.test.d.ts +0 -1
  102. package/dist/gates/deprecated-apis.test.js +0 -318
  103. package/dist/gates/deprecated-dependencies.test.d.ts +0 -1
  104. package/dist/gates/deprecated-dependencies.test.js +0 -55
  105. package/dist/gates/frontend-secret-exposure.test.d.ts +0 -1
  106. package/dist/gates/frontend-secret-exposure.test.js +0 -148
  107. package/dist/gates/hallucinated-imports/framework-modules-nuxt.test.d.ts +0 -1
  108. package/dist/gates/hallucinated-imports/framework-modules-nuxt.test.js +0 -27
  109. package/dist/gates/hallucinated-imports/js-resolver-types.test.d.ts +0 -1
  110. package/dist/gates/hallucinated-imports/js-resolver-types.test.js +0 -14
  111. package/dist/gates/hallucinated-imports-sveltekit.test.d.ts +0 -1
  112. package/dist/gates/hallucinated-imports-sveltekit.test.js +0 -132
  113. package/dist/gates/hallucinated-imports.test.d.ts +0 -1
  114. package/dist/gates/hallucinated-imports.test.js +0 -1206
  115. package/dist/gates/js-style-context.test.d.ts +0 -1
  116. package/dist/gates/js-style-context.test.js +0 -35
  117. package/dist/gates/logic-drift.test.d.ts +0 -1
  118. package/dist/gates/logic-drift.test.js +0 -52
  119. package/dist/gates/phantom-apis.test.d.ts +0 -1
  120. package/dist/gates/phantom-apis.test.js +0 -396
  121. package/dist/gates/promise-safety.test.d.ts +0 -1
  122. package/dist/gates/promise-safety.test.js +0 -34
  123. package/dist/gates/runner.test.d.ts +0 -1
  124. package/dist/gates/runner.test.js +0 -77
  125. package/dist/gates/scoped-gates.test.d.ts +0 -1
  126. package/dist/gates/scoped-gates.test.js +0 -52
  127. package/dist/gates/security-patterns-owasp.test.d.ts +0 -1
  128. package/dist/gates/security-patterns-owasp.test.js +0 -186
  129. package/dist/gates/security-patterns.test.d.ts +0 -1
  130. package/dist/gates/security-patterns.test.js +0 -194
  131. package/dist/gates/semantic-bugs.test.d.ts +0 -1
  132. package/dist/gates/semantic-bugs.test.js +0 -76
  133. package/dist/gates/side-effect-analysis.test.d.ts +0 -1
  134. package/dist/gates/side-effect-analysis.test.js +0 -162
  135. package/dist/gates/style-drift.test.d.ts +0 -1
  136. package/dist/gates/style-drift.test.js +0 -26
  137. package/dist/gates/test-quality.test.d.ts +0 -1
  138. package/dist/gates/test-quality.test.js +0 -325
  139. package/dist/gates/trusted-reviews.test.d.ts +0 -1
  140. package/dist/gates/trusted-reviews.test.js +0 -16
  141. package/dist/gates/unindexed-reads/queries.test.d.ts +0 -1
  142. package/dist/gates/unindexed-reads/queries.test.js +0 -54
  143. package/dist/gates/unindexed-reads/schema.test.d.ts +0 -1
  144. package/dist/gates/unindexed-reads/schema.test.js +0 -81
  145. package/dist/gates/unindexed-reads/unindexed-reads.test.d.ts +0 -1
  146. package/dist/gates/unindexed-reads/unindexed-reads.test.js +0 -90
  147. package/dist/hooks/checker.test.d.ts +0 -1
  148. package/dist/hooks/checker.test.js +0 -159
  149. package/dist/hooks/dlp-confidence.test.d.ts +0 -1
  150. package/dist/hooks/dlp-confidence.test.js +0 -51
  151. package/dist/hooks/dlp-feedback.test.d.ts +0 -1
  152. package/dist/hooks/dlp-feedback.test.js +0 -131
  153. package/dist/hooks/input-validator.test.d.ts +0 -1
  154. package/dist/hooks/input-validator.test.js +0 -329
  155. package/dist/hooks/templates.test.d.ts +0 -1
  156. package/dist/hooks/templates.test.js +0 -27
  157. package/dist/inference/brain-placeholder.test.d.ts +0 -1
  158. package/dist/inference/brain-placeholder.test.js +0 -28
  159. package/dist/inference/cloud-provider.test.d.ts +0 -1
  160. package/dist/inference/cloud-provider.test.js +0 -139
  161. package/dist/inference/executable.test.d.ts +0 -1
  162. package/dist/inference/executable.test.js +0 -41
  163. package/dist/inference/http-download.test.d.ts +0 -1
  164. package/dist/inference/http-download.test.js +0 -109
  165. package/dist/inference/llama-engine-checksum.test.d.ts +0 -1
  166. package/dist/inference/llama-engine-checksum.test.js +0 -27
  167. package/dist/inference/llama-engine.test.d.ts +0 -1
  168. package/dist/inference/llama-engine.test.js +0 -51
  169. package/dist/inference/llama-process.test.d.ts +0 -1
  170. package/dist/inference/llama-process.test.js +0 -61
  171. package/dist/inference/local-model.test.d.ts +0 -1
  172. package/dist/inference/local-model.test.js +0 -23
  173. package/dist/inference/model-download.test.d.ts +0 -1
  174. package/dist/inference/model-download.test.js +0 -125
  175. package/dist/inference/model-manager.test.d.ts +0 -1
  176. package/dist/inference/model-manager.test.js +0 -24
  177. package/dist/inference/types.test.d.ts +0 -1
  178. package/dist/inference/types.test.js +0 -19
  179. package/dist/memory/recall.test.d.ts +0 -1
  180. package/dist/memory/recall.test.js +0 -36
  181. package/dist/pattern-index/indexer.test.d.ts +0 -6
  182. package/dist/pattern-index/indexer.test.js +0 -197
  183. package/dist/pattern-index/matcher.test.d.ts +0 -6
  184. package/dist/pattern-index/matcher.test.js +0 -238
  185. package/dist/pattern-index/pattern-reuse.test.d.ts +0 -1
  186. package/dist/pattern-index/pattern-reuse.test.js +0 -76
  187. package/dist/pattern-index/semantic-runtime.test.d.ts +0 -1
  188. package/dist/pattern-index/semantic-runtime.test.js +0 -31
  189. package/dist/pattern-index/staleness.test.d.ts +0 -6
  190. package/dist/pattern-index/staleness.test.js +0 -211
  191. package/dist/review/agent-fixes.test.d.ts +0 -1
  192. package/dist/review/agent-fixes.test.js +0 -32
  193. package/dist/review/backtest-init.test.d.ts +0 -1
  194. package/dist/review/backtest-init.test.js +0 -98
  195. package/dist/review/backtest-judges.test.d.ts +0 -1
  196. package/dist/review/backtest-judges.test.js +0 -32
  197. package/dist/review/backtest-last.test.d.ts +0 -1
  198. package/dist/review/backtest-last.test.js +0 -109
  199. package/dist/review/backtest.test.d.ts +0 -1
  200. package/dist/review/backtest.test.js +0 -183
  201. package/dist/review/baseline.test.d.ts +0 -1
  202. package/dist/review/baseline.test.js +0 -22
  203. package/dist/review/branch-checks.test.d.ts +0 -1
  204. package/dist/review/branch-checks.test.js +0 -48
  205. package/dist/review/check-outcomes.test.d.ts +0 -1
  206. package/dist/review/check-outcomes.test.js +0 -56
  207. package/dist/review/code-patterns.test.d.ts +0 -1
  208. package/dist/review/code-patterns.test.js +0 -142
  209. package/dist/review/dead-code.test.d.ts +0 -1
  210. package/dist/review/dead-code.test.js +0 -154
  211. package/dist/review/deep-runs.test.d.ts +0 -1
  212. package/dist/review/deep-runs.test.js +0 -23
  213. package/dist/review/effectiveness.test.d.ts +0 -1
  214. package/dist/review/effectiveness.test.js +0 -42
  215. package/dist/review/fix-scope.test.d.ts +0 -1
  216. package/dist/review/fix-scope.test.js +0 -104
  217. package/dist/review/generated-files.test.d.ts +0 -1
  218. package/dist/review/generated-files.test.js +0 -25
  219. package/dist/review/migration-order.test.d.ts +0 -1
  220. package/dist/review/migration-order.test.js +0 -62
  221. package/dist/review/quiet.test.d.ts +0 -1
  222. package/dist/review/quiet.test.js +0 -38
  223. package/dist/review/receipt.test.d.ts +0 -1
  224. package/dist/review/receipt.test.js +0 -60
  225. package/dist/review/review-task.test.d.ts +0 -1
  226. package/dist/review/review-task.test.js +0 -89
  227. package/dist/review/review.test.d.ts +0 -1
  228. package/dist/review/review.test.js +0 -203
  229. package/dist/review/reviewer/adapters.test.d.ts +0 -1
  230. package/dist/review/reviewer/adapters.test.js +0 -60
  231. package/dist/review/reviewer/api-judge.test.d.ts +0 -1
  232. package/dist/review/reviewer/api-judge.test.js +0 -108
  233. package/dist/review/reviewer/background.test.d.ts +0 -1
  234. package/dist/review/reviewer/background.test.js +0 -118
  235. package/dist/review/reviewer/context.test.d.ts +0 -1
  236. package/dist/review/reviewer/context.test.js +0 -45
  237. package/dist/review/reviewer/exec.test.d.ts +0 -1
  238. package/dist/review/reviewer/exec.test.js +0 -59
  239. package/dist/review/reviewer/inputs.test.d.ts +0 -1
  240. package/dist/review/reviewer/inputs.test.js +0 -26
  241. package/dist/review/reviewer/panel.test.d.ts +0 -1
  242. package/dist/review/reviewer/panel.test.js +0 -110
  243. package/dist/review/reviewer/record.test.d.ts +0 -1
  244. package/dist/review/reviewer/record.test.js +0 -32
  245. package/dist/review/reviewer/rule-writer.test.d.ts +0 -1
  246. package/dist/review/reviewer/rule-writer.test.js +0 -52
  247. package/dist/review/reviewer/settings.test.d.ts +0 -1
  248. package/dist/review/reviewer/settings.test.js +0 -57
  249. package/dist/review/reviewer/usage.test.d.ts +0 -1
  250. package/dist/review/reviewer/usage.test.js +0 -14
  251. package/dist/review/reviewer.test.d.ts +0 -1
  252. package/dist/review/reviewer.test.js +0 -705
  253. package/dist/review/stories.test.d.ts +0 -1
  254. package/dist/review/stories.test.js +0 -58
  255. package/dist/review/toolchain.test.d.ts +0 -1
  256. package/dist/review/toolchain.test.js +0 -108
  257. package/dist/review/typed/redundancy.test.d.ts +0 -1
  258. package/dist/review/typed/redundancy.test.js +0 -315
  259. package/dist/review/typed/schema-nullability.test.d.ts +0 -1
  260. package/dist/review/typed/schema-nullability.test.js +0 -63
  261. package/dist/review-learning/human-edits.test.d.ts +0 -1
  262. package/dist/review-learning/human-edits.test.js +0 -47
  263. package/dist/review-learning/repo-rules.test.d.ts +0 -1
  264. package/dist/review-learning/repo-rules.test.js +0 -55
  265. package/dist/review-learning/review-learning.test.d.ts +0 -1
  266. package/dist/review-learning/review-learning.test.js +0 -249
  267. package/dist/safety.test.d.ts +0 -1
  268. package/dist/safety.test.js +0 -42
  269. package/dist/semantic/benchmark.test.d.ts +0 -1
  270. package/dist/semantic/benchmark.test.js +0 -22
  271. package/dist/semantic/intent/intent.test.d.ts +0 -1
  272. package/dist/semantic/intent/intent.test.js +0 -46
  273. package/dist/semantic/learn/learn.test.d.ts +0 -1
  274. package/dist/semantic/learn/learn.test.js +0 -67
  275. package/dist/semantic/origins.test.d.ts +0 -1
  276. package/dist/semantic/origins.test.js +0 -77
  277. package/dist/semantic/project-facts.test.d.ts +0 -1
  278. package/dist/semantic/project-facts.test.js +0 -45
  279. package/dist/semantic/sites/call-sites.test.d.ts +0 -1
  280. package/dist/semantic/sites/call-sites.test.js +0 -46
  281. package/dist/services/adaptive-thresholds.test.d.ts +0 -1
  282. package/dist/services/adaptive-thresholds.test.js +0 -53
  283. package/dist/services/agent-history.test.d.ts +0 -1
  284. package/dist/services/agent-history.test.js +0 -69
  285. package/dist/services/context-scope-summary.test.d.ts +0 -1
  286. package/dist/services/context-scope-summary.test.js +0 -17
  287. package/dist/services/context-telemetry-service.test.d.ts +0 -1
  288. package/dist/services/context-telemetry-service.test.js +0 -182
  289. package/dist/services/cursor-usage-sync.test.d.ts +0 -1
  290. package/dist/services/cursor-usage-sync.test.js +0 -173
  291. package/dist/services/engineering-knowledge-graph.test.d.ts +0 -1
  292. package/dist/services/engineering-knowledge-graph.test.js +0 -76
  293. package/dist/services/model-pricing.test.d.ts +0 -1
  294. package/dist/services/model-pricing.test.js +0 -44
  295. package/dist/services/observed-savings.test.d.ts +0 -1
  296. package/dist/services/observed-savings.test.js +0 -37
  297. package/dist/services/score-history.test.d.ts +0 -1
  298. package/dist/services/score-history.test.js +0 -61
  299. package/dist/smoke.test.d.ts +0 -1
  300. package/dist/smoke.test.js +0 -17
  301. package/dist/storage/cache-cleanup.test.d.ts +0 -1
  302. package/dist/storage/cache-cleanup.test.js +0 -54
  303. package/dist/storage/context-telemetry.test.d.ts +0 -1
  304. package/dist/storage/context-telemetry.test.js +0 -80
  305. package/dist/storage/db.test.d.ts +0 -1
  306. package/dist/storage/db.test.js +0 -46
  307. package/dist/storage/fix-lessons.test.d.ts +0 -1
  308. package/dist/storage/fix-lessons.test.js +0 -61
  309. package/dist/storage/lessons.test.d.ts +0 -1
  310. package/dist/storage/lessons.test.js +0 -81
  311. package/dist/storage/local-encryption.test.d.ts +0 -1
  312. package/dist/storage/local-encryption.test.js +0 -34
  313. package/dist/storage/local-memory.test.d.ts +0 -1
  314. package/dist/storage/local-memory.test.js +0 -55
  315. package/dist/storage/share-memory.test.d.ts +0 -1
  316. package/dist/storage/share-memory.test.js +0 -34
  317. package/dist/storage/team-diagnostics.test.d.ts +0 -1
  318. package/dist/storage/team-diagnostics.test.js +0 -51
  319. package/dist/storage/team-scope.test.d.ts +0 -1
  320. package/dist/storage/team-scope.test.js +0 -22
  321. package/dist/storage/team-store.test.d.ts +0 -1
  322. package/dist/storage/team-store.test.js +0 -11
  323. package/dist/storage/team-sync-scope.test.d.ts +0 -1
  324. package/dist/storage/team-sync-scope.test.js +0 -100
  325. package/dist/storage/team-vector-store.test.d.ts +0 -1
  326. package/dist/storage/team-vector-store.test.js +0 -56
  327. package/dist/storage/telemetry-scope.test.d.ts +0 -1
  328. package/dist/storage/telemetry-scope.test.js +0 -38
  329. package/dist/telemetry/telemetry.test.d.ts +0 -1
  330. package/dist/telemetry/telemetry.test.js +0 -64
  331. package/dist/types/index.test.d.ts +0 -1
  332. package/dist/types/index.test.js +0 -33
  333. package/dist/utils/command-line.test.d.ts +0 -1
  334. package/dist/utils/command-line.test.js +0 -8
  335. package/dist/utils/diff-removed.test.d.ts +0 -1
  336. package/dist/utils/diff-removed.test.js +0 -29
  337. package/dist/utils/diff.test.d.ts +0 -1
  338. package/dist/utils/diff.test.js +0 -38
  339. package/dist/utils/glob-paths.test.d.ts +0 -1
  340. package/dist/utils/glob-paths.test.js +0 -40
  341. package/dist/utils/profile.test.d.ts +0 -1
  342. package/dist/utils/profile.test.js +0 -68
  343. package/dist/utils/scanner.test.d.ts +0 -1
  344. package/dist/utils/scanner.test.js +0 -48
  345. package/dist/utils/scope.test.d.ts +0 -1
  346. package/dist/utils/scope.test.js +0 -38
@@ -1,93 +0,0 @@
1
- import fs from 'fs';
2
- import os from 'os';
3
- import path from 'path';
4
- import { afterEach, beforeEach, describe, expect, it } from 'vitest';
5
- import { DiscoveryService } from './discovery.js';
6
- import { containsToken, detectParadigm, stripComments } from './discovery-signals.js';
7
- describe('DiscoveryService', () => {
8
- let cwd;
9
- beforeEach(() => { cwd = fs.mkdtempSync(path.join(os.tmpdir(), 'discovery-')); });
10
- afterEach(() => { fs.rmSync(cwd, { recursive: true, force: true }); });
11
- function write(rel, body) {
12
- fs.mkdirSync(path.dirname(path.join(cwd, rel)), { recursive: true });
13
- fs.writeFileSync(path.join(cwd, rel), body);
14
- }
15
- const discover = () => new DiscoveryService().discover(cwd);
16
- it('detects an API from a declared dependency', async () => {
17
- write('package.json', JSON.stringify({ dependencies: { express: '^4.0.0' } }));
18
- const result = await discover();
19
- expect(result.matches.preset).toEqual({ name: 'api', marker: 'dependency:express' });
20
- });
21
- it('detects a scoped framework package such as @nestjs/core', async () => {
22
- write('package.json', JSON.stringify({ dependencies: { '@nestjs/core': '^10.0.0' } }));
23
- expect((await discover()).matches.preset?.marker).toBe('dependency:nestjs');
24
- });
25
- it('detects a Python API with its ignore patterns', async () => {
26
- write('requirements.txt', 'flask==2.0.0\n');
27
- const result = await discover();
28
- expect(result.matches.preset?.name).toBe('api');
29
- expect(result.config.ignore).toEqual(expect.arrayContaining(['venv/**', '__pycache__/**', '*.pyc']));
30
- });
31
- it('detects a UI project from a config file with its ignore patterns', async () => {
32
- write('next.config.js', 'module.exports = {};\n');
33
- const result = await discover();
34
- expect(result.matches.preset?.name).toBe('ui');
35
- expect(result.config.ignore).toEqual(expect.arrayContaining(['node_modules/**', '.next/**']));
36
- });
37
- it('detects Svelte from its dependencies, not "reactivity" or "public" in comments', async () => {
38
- write('package.json', JSON.stringify({ devDependencies: { '@sveltejs/kit': '^2.0.0', svelte: '^5.0.0' } }));
39
- write('eslint.config.js', '// Svelte 5 rules (state_referenced_locally, a11y, reactivity)\nexport default [];\n');
40
- write('playwright.config.ts', '// rides the public internet\nexport default {};\n');
41
- write('src/lib/format.ts', 'export function format(v: number) { return v.toFixed(2); }\nexport const pad = (s: string) => s.padStart(2);\n');
42
- const result = await discover();
43
- expect(result.matches.preset).toEqual({ name: 'ui', marker: 'dependency:svelte' });
44
- expect(result.matches.paradigm?.name).toBe('functional');
45
- });
46
- it('does not read a framework name in source text as a dependency', async () => {
47
- write('src/middleware.ts', 'export function run(next: () => void) { next(); }\n');
48
- expect((await discover()).matches.preset).toBeUndefined();
49
- });
50
- it('identifies OOP from class declarations in a subfolder', async () => {
51
- write('src/Service.ts', 'export class MyService {}\nexport class OtherService {}\n');
52
- const result = await discover();
53
- expect(result.matches.paradigm).toEqual({ name: 'oop', marker: 'declarations: 2 classes, 0 functions' });
54
- });
55
- it('needs several recurring domain keywords before choosing a domain preset', async () => {
56
- write('src/billing.ts', 'export const ledger = new Map<string, number>();\n');
57
- write('src/report.ts', "import { ledger } from './billing';\nexport const total = () => ledger.size;\n");
58
- expect((await discover()).matches.preset).toBeUndefined();
59
- write('src/pay.ts', 'export function payment(ledger: Map<string, number>) { return ledger; }\n');
60
- write('src/refund.ts', 'export function refund(payment: number) { return -payment; }\n');
61
- expect((await discover()).matches.preset).toEqual({ name: 'fintech', marker: 'content:payment,ledger' });
62
- });
63
- it('needs a file marker to exist, not to be mentioned', async () => {
64
- write('src/serve.ts', "export const page = () => 'index.html';\n");
65
- expect((await discover()).matches.preset).toBeUndefined();
66
- });
67
- it('detects notebooks by file, not by the word ipynb in code', async () => {
68
- write('src/scan.ts', "export const exts = ['.ipynb', '.py'];\n");
69
- expect((await discover()).matches.preset).toBeUndefined();
70
- write('notebooks/explore.ipynb', '{}');
71
- expect((await discover()).matches.preset).toEqual({ name: 'data', marker: 'file:*.ipynb' });
72
- });
73
- it('leaves the paradigm unset for an empty project', async () => {
74
- expect((await discover()).matches.paradigm).toBeUndefined();
75
- });
76
- });
77
- describe('discovery signals', () => {
78
- it('matches whole tokens only', () => {
79
- expect(containsToken('import x from "react"', 'react')).toBe(true);
80
- expect(containsToken('reactivity', 'react')).toBe(false);
81
- expect(containsToken('@vue/compiler', 'vue')).toBe(false);
82
- expect(containsToken('GET /health', 'health')).toBe(false);
83
- });
84
- it('drops comments but keeps URLs in strings', () => {
85
- const text = stripComments('/* public */\nconst u = "https://x.io"; // public\n# public\n');
86
- expect(text).not.toContain('public');
87
- expect(text).toContain('https://x.io');
88
- });
89
- it('declines to guess a paradigm when neither side clearly leads', () => {
90
- expect(detectParadigm(['export class A {}\nexport function f() {}\n'])).toBeNull();
91
- expect(detectParadigm(['export class A {}\nexport function f() {}\nexport function g() {}\n'])?.name).toBe('functional');
92
- });
93
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,94 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
- import { GateRunner } from './gates/runner.js';
3
- import { ConfigSchema } from './types/index.js';
4
- import fs from 'fs-extra';
5
- import path from 'path';
6
- import os from 'os';
7
- describe('Environment Alignment Gate', () => {
8
- const testDir = path.join(os.tmpdir(), 'rigour-temp-test-env-' + process.pid);
9
- beforeEach(async () => {
10
- await fs.ensureDir(testDir);
11
- });
12
- afterEach(async () => {
13
- await fs.remove(testDir);
14
- });
15
- it('should detect tool version mismatch (Explicit)', async () => {
16
- const rawConfig = {
17
- gates: {
18
- environment: {
19
- enabled: true,
20
- enforce_contracts: false,
21
- tools: {
22
- node: ">=99.0.0" // Guaranteed to fail
23
- }
24
- }
25
- }
26
- };
27
- const config = ConfigSchema.parse(rawConfig);
28
- const runner = new GateRunner(config);
29
- const report = await runner.run(testDir);
30
- expect(report.status).toBe('FAIL');
31
- const envFailure = report.failures.find(f => f.id === 'environment-alignment');
32
- expect(envFailure).toBeDefined();
33
- expect(envFailure?.details).toContain('node');
34
- expect(envFailure?.details).toContain('version mismatch');
35
- });
36
- it('should detect missing environment variables', async () => {
37
- const rawConfig = {
38
- gates: {
39
- environment: {
40
- enabled: true,
41
- required_env: ["RIGOUR_TEST_VAR_MISSING"]
42
- }
43
- }
44
- };
45
- const config = ConfigSchema.parse(rawConfig);
46
- const runner = new GateRunner(config);
47
- const report = await runner.run(testDir);
48
- expect(report.status).toBe('FAIL');
49
- expect(report.failures[0].details).toContain('RIGOUR_TEST_VAR_MISSING');
50
- });
51
- it('should discover contracts from pyproject.toml', async () => {
52
- // Create mock pyproject.toml with a version that will surely fail
53
- await fs.writeFile(path.join(testDir, 'pyproject.toml'), `
54
- [tool.ruff]
55
- ruff = ">=99.14.0"
56
- `);
57
- const rawConfig = {
58
- gates: {
59
- environment: {
60
- enabled: true,
61
- enforce_contracts: true,
62
- tools: {} // Should discover ruff from file
63
- }
64
- }
65
- };
66
- const config = ConfigSchema.parse(rawConfig);
67
- const runner = new GateRunner(config);
68
- const report = await runner.run(testDir);
69
- // This might pass or fail depending on the local ruff version,
70
- // but we want to check if the gate attempted to check ruff.
71
- // If ruff is missing, it will fail with "is missing".
72
- const ruffFailure = report.failures.find(f => f.details.includes('ruff'));
73
- expect(ruffFailure).toBeDefined();
74
- });
75
- it('should prioritize environment gate and run it first', async () => {
76
- const rawConfig = {
77
- gates: {
78
- max_file_lines: 1,
79
- environment: {
80
- enabled: true,
81
- required_env: ["MANDATORY_SECRET_MISSING"]
82
- }
83
- }
84
- };
85
- const config = ConfigSchema.parse(rawConfig);
86
- // Create a file that would fail max_file_lines
87
- await fs.writeFile(path.join(testDir, 'large.js'), 'line1\nline2\nline3');
88
- const runner = new GateRunner(config);
89
- const report = await runner.run(testDir);
90
- // In a real priority system, we might want to stop after environment failure.
91
- // Currently GateRunner runs all, but environment-alignment has been unshifted.
92
- expect(Object.keys(report.summary)[0]).toBe('environment-alignment');
93
- });
94
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,117 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { evaluateTypedCommand, parseCommandLine, evaluateWriteScope, evaluateActionDeterministic, runAdversarialCorpus, CapabilityBroker, validateAgentClaimedScope, } from './index.js';
3
- describe('typed command firewall', () => {
4
- it('denies shell metacharacters', () => {
5
- const ev = evaluateTypedCommand('ls; rm -rf /');
6
- expect(ev.decision).toBe('deny');
7
- expect(ev.ruleId).toBe('shell.no-meta');
8
- });
9
- it('treats a Windows shim as the program it starts, and still denies an unknown one', () => {
10
- expect(evaluateTypedCommand('D:\\repo\\node_modules\\.bin\\vitest.cmd run a.test.ts').decision).toBe('allow');
11
- expect(evaluateTypedCommand('D:\\tools\\curl.exe https://evil.example').decision).toBe('deny');
12
- });
13
- it('denies unknown binaries including node/npx', () => {
14
- expect(evaluateTypedCommand('curl https://evil.example').decision).toBe('deny');
15
- expect(evaluateTypedCommand('node -e "1"').decision).toBe('deny');
16
- expect(evaluateTypedCommand('npx cowsay hi').decision).toBe('deny');
17
- });
18
- it('denies git push', () => {
19
- const ev = evaluateTypedCommand('git push origin main');
20
- expect(ev.decision).toBe('deny');
21
- expect(ev.ruleId).toBe('git.destructive');
22
- });
23
- it('allows vitest', () => {
24
- const ev = evaluateTypedCommand('vitest run packages/rigour-core');
25
- expect(ev.decision).toBe('allow');
26
- expect(parseCommandLine('vitest run x')?.bin).toBe('vitest');
27
- });
28
- });
29
- describe('scope enforcement', () => {
30
- it('denies unbound writer when scopes exist', () => {
31
- const ev = evaluateWriteScope('/repo', 'packages/rigour-core/src/x.ts', [
32
- { agentId: 'a1', taskScope: ['packages/rigour-core/**'] },
33
- ]);
34
- expect(ev.decision).toBe('scope-violation');
35
- expect(ev.ruleId).toBe('scope.unbound');
36
- });
37
- it('flags out-of-scope writes for bound agent', () => {
38
- const ev = evaluateWriteScope('/repo', '.github/workflows/x.yml', [
39
- { agentId: 'a1', taskScope: ['packages/rigour-core/**'] },
40
- ], 'a1');
41
- expect(ev.decision).toBe('scope-violation');
42
- });
43
- it('allows in-scope writes for bound agent', () => {
44
- const ev = evaluateWriteScope('/repo', 'packages/rigour-core/src/firewall/types.ts', [
45
- { agentId: 'a1', taskScope: ['packages/rigour-core/**'] },
46
- ], 'a1');
47
- expect(ev.decision).toBe('allow');
48
- });
49
- it('rejects broad self-registration scopes', () => {
50
- expect(validateAgentClaimedScope(['**/*']).ok).toBe(false);
51
- expect(validateAgentClaimedScope(['.github/**']).ok).toBe(false);
52
- expect(validateAgentClaimedScope(['packages/rigour-core/**']).ok).toBe(true);
53
- });
54
- });
55
- describe('capability broker', () => {
56
- it('requires explicit authority in enforce mode and reports shadow decisions in observe mode', () => {
57
- const base = { defaultTtlMs: 1000, toolAllowlist: ['*'], agentScopes: [] };
58
- const enforced = new CapabilityBroker({ ...base, authorizationMode: 'enforce' });
59
- expect(enforced.evaluateProposal({ action: 'mcp.call', resource: 'github__get_issue' })).toMatchObject({
60
- decision: 'deny', ruleId: 'capability.required',
61
- });
62
- const observed = new CapabilityBroker({ ...base, authorizationMode: 'observe' });
63
- expect(observed.evaluateProposal({ action: 'mcp.call', resource: 'github__get_issue' })).toMatchObject({
64
- decision: 'allow', simulatedDecision: 'deny', ruleId: 'capability.observe-required',
65
- });
66
- });
67
- it('denies undeclared MCP tools', () => {
68
- const broker = new CapabilityBroker({
69
- defaultTtlMs: 1000,
70
- toolAllowlist: ['rigour_check'],
71
- agentScopes: [],
72
- });
73
- const ev = broker.evaluateProposal({
74
- action: 'mcp.call',
75
- resource: 'slack.post_message',
76
- });
77
- expect(ev.decision).toBe('deny');
78
- expect(ev.ruleId).toBe('mcp.undeclared');
79
- });
80
- it('issues one-use capabilities', () => {
81
- const broker = new CapabilityBroker({
82
- defaultTtlMs: 60_000,
83
- toolAllowlist: ['*'],
84
- agentScopes: [],
85
- });
86
- const grant = broker.issue('mcp.call', 'rigour_check');
87
- const ok = broker.evaluateProposal({
88
- action: 'mcp.call',
89
- resource: 'rigour_check',
90
- capabilityId: grant.id,
91
- });
92
- expect(ok.decision).toBe('allow');
93
- const again = broker.evaluateProposal({
94
- action: 'mcp.call',
95
- resource: 'rigour_check',
96
- capabilityId: grant.id,
97
- });
98
- expect(again.decision).toBe('deny');
99
- expect(again.ruleId).toBe('capability.reuse');
100
- });
101
- });
102
- describe('adversarial corpus', () => {
103
- it('passes default regression corpus', () => {
104
- const report = runAdversarialCorpus();
105
- expect(report.failed).toBe(0);
106
- expect(report.passed).toBeGreaterThan(0);
107
- });
108
- it('evaluateActionDeterministic matches expected deny', () => {
109
- const ev = evaluateActionDeterministic({
110
- action: 'mcp.call',
111
- resource: 'github.merge',
112
- toolAllowlist: [],
113
- agentScopes: [],
114
- });
115
- expect(ev.decision).toBe('deny');
116
- });
117
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,64 +0,0 @@
1
- import { execFileSync } from 'child_process';
2
- import fs from 'fs';
3
- import os from 'os';
4
- import path from 'path';
5
- import { afterEach, beforeEach, describe, expect, it } from 'vitest';
6
- import { issueArbitrationToken, signArbitrationDecision, verifyArbitrationDecision } from './arbitration-token.js';
7
- import { admitForCi, createAttestation } from './attestation.js';
8
- let repo;
9
- const git = (...args) => execFileSync('git', ['-C', repo, ...args], { encoding: 'utf8' });
10
- beforeEach(() => {
11
- repo = fs.mkdtempSync(path.join(os.tmpdir(), 'trust-'));
12
- git('init', '-q', '-b', 'main');
13
- git('config', 'user.email', 't@example.com');
14
- git('config', 'user.name', 't');
15
- git('config', 'commit.gpgsign', 'false');
16
- fs.writeFileSync(path.join(repo, 'a.ts'), 'export const a = 1;\n');
17
- git('add', '-A');
18
- git('commit', '-qm', 'init');
19
- });
20
- afterEach(() => fs.rmSync(repo, { recursive: true, force: true }));
21
- describe('human arbitration', () => {
22
- it('accepts only a decision signed with the issued token, once', async () => {
23
- const token = await issueArbitrationToken(repo, 'r1');
24
- expect(fs.existsSync(path.join(repo, '.rigour', 'arbitration-tokens.json'))).toBe(false);
25
- // A line any process can append to the event log carries no valid proof.
26
- expect(verifyArbitrationDecision(token, 'r1', 'approve', undefined)).toBe(false);
27
- expect(verifyArbitrationDecision(token, 'r1', 'approve', 'f'.repeat(64))).toBe(false);
28
- const proof = await signArbitrationDecision(repo, 'r1', 'approve');
29
- expect(verifyArbitrationDecision(token, 'r1', 'approve', proof)).toBe(true);
30
- expect(verifyArbitrationDecision(token, 'r1', 'reject', proof)).toBe(false);
31
- expect(await signArbitrationDecision(repo, 'r1', 'approve')).toBeNull();
32
- });
33
- });
34
- describe('CI admission', () => {
35
- const tx = (over = {}) => ({
36
- id: 'tx1', agentId: 'agent', scope: ['**/*'], policyHash: 'policy-a', capabilitiesIssued: [], filesChanged: ['a.ts'], ...over,
37
- });
38
- const pass = { status: 'PASS', failedGates: [] };
39
- it('admits a fresh, bound bundle whose files are unchanged', async () => {
40
- await createAttestation(repo, { transaction: tx(), gateResults: pass });
41
- expect(await admitForCi(repo)).toMatchObject({ admit: true });
42
- });
43
- it('refuses when an attested file differs from what was signed, even at the same commit', async () => {
44
- await createAttestation(repo, { transaction: tx(), gateResults: pass });
45
- fs.writeFileSync(path.join(repo, 'a.ts'), 'export const a = 2;\n'); // HEAD and its tree are unchanged
46
- expect(await admitForCi(repo)).toMatchObject({ admit: false, reason: expect.stringContaining('artifactDigest') });
47
- });
48
- it('refuses a bundle produced under another policy than the one required', async () => {
49
- await createAttestation(repo, { transaction: tx(), gateResults: pass });
50
- expect(await admitForCi(repo, { policyHash: 'policy-b' })).toMatchObject({ admit: false });
51
- expect(await admitForCi(repo, { policyHash: 'policy-a' })).toMatchObject({ admit: true });
52
- });
53
- it('refuses a bundle bound to no commit or tree', async () => {
54
- const noRepo = fs.mkdtempSync(path.join(os.tmpdir(), 'trust-norepo-'));
55
- try {
56
- fs.writeFileSync(path.join(noRepo, 'a.ts'), 'export const a = 1;\n');
57
- await createAttestation(noRepo, { transaction: tx(), gateResults: pass });
58
- expect(await admitForCi(noRepo)).toMatchObject({ admit: false, reason: 'Attestation is bound to no commit or tree' });
59
- }
60
- finally {
61
- fs.rmSync(noRepo, { recursive: true, force: true });
62
- }
63
- });
64
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,188 +0,0 @@
1
- import { afterEach, describe, expect, it } from 'vitest';
2
- import fs from 'fs-extra';
3
- import os from 'os';
4
- import path from 'path';
5
- import { appendExecutionReceipt, consumeTrustedGrant, getTrustedControlDir, issueTrustedGrant, listTrustedGrants, normalizeMcpAction, saveGatewayConfig, verifyExecutionReceiptChain, } from './index.js';
6
- const repositories = [];
7
- const controlRoots = [];
8
- async function repository() {
9
- const cwd = await fs.mkdtemp(path.join(os.tmpdir(), 'rigour-gateway-test-'));
10
- repositories.push(cwd);
11
- return cwd;
12
- }
13
- async function controlRoot() {
14
- const root = await fs.mkdtemp(path.join(os.tmpdir(), 'rigour-control-test-'));
15
- controlRoots.push(root);
16
- return root;
17
- }
18
- afterEach(async () => {
19
- for (const cwd of repositories.splice(0)) {
20
- await fs.remove(cwd);
21
- }
22
- for (const root of controlRoots.splice(0))
23
- await fs.remove(root);
24
- });
25
- describe('trusted gateway control', () => {
26
- it('stores configuration outside the repository', async () => {
27
- const cwd = await repository();
28
- const root = await controlRoot();
29
- const location = await saveGatewayConfig(cwd, {
30
- version: 1,
31
- mode: 'observe',
32
- principalId: 'owner',
33
- agentId: 'agent-1',
34
- taskId: 'task-1',
35
- servers: { github: { command: 'github-mcp', allow: ['get_issue'] } },
36
- }, root);
37
- expect(location.startsWith(cwd)).toBe(false);
38
- expect(await fs.pathExists(location)).toBe(true);
39
- });
40
- it('lists valid grants without failing on malformed control files', async () => {
41
- const cwd = await repository();
42
- const root = await controlRoot();
43
- const grant = await issueTrustedGrant(cwd, {
44
- issuerId: 'owner', subjectId: 'agent-1', taskId: 'task-1',
45
- action: 'mcp.call', resource: 'mcp://files/read', ttlMs: 60_000,
46
- }, root);
47
- const grantsDir = path.join(getTrustedControlDir(cwd, root), 'capabilities');
48
- await fs.writeFile(path.join(grantsDir, 'not-a-grant.json'), '{broken');
49
- await expect(listTrustedGrants(cwd, 10, root)).resolves.toEqual([
50
- expect.objectContaining({ id: grant.id, resource: 'mcp://files/read' }),
51
- ]);
52
- });
53
- it('consumes a capability exactly once', async () => {
54
- const cwd = await repository();
55
- const root = await controlRoot();
56
- const grant = await issueTrustedGrant(cwd, {
57
- issuerId: 'owner', subjectId: 'agent-1', taskId: 'task-1',
58
- action: 'mcp.call', resource: 'mcp://github/create_issue', ttlMs: 60_000,
59
- }, root);
60
- const proposal = {
61
- id: grant.id, subjectId: 'agent-1', taskId: 'task-1',
62
- action: 'mcp.call', resource: 'mcp://github/create_issue',
63
- };
64
- expect((await consumeTrustedGrant(cwd, proposal, root)).ok).toBe(true);
65
- expect((await consumeTrustedGrant(cwd, proposal, root)).ok).toBe(false);
66
- });
67
- it('rejects path traversal in a capability id', async () => {
68
- const cwd = await repository();
69
- const root = await controlRoot();
70
- const result = await consumeTrustedGrant(cwd, {
71
- id: '../../gateway', subjectId: 'agent-1', taskId: 'task-1',
72
- action: 'mcp.call', resource: 'mcp://github/create_issue',
73
- }, root);
74
- expect(result).toMatchObject({ ok: false, reason: 'Invalid capability id' });
75
- });
76
- it('rejects delegated authority that expands its parent', async () => {
77
- const cwd = await repository();
78
- const root = await controlRoot();
79
- const parent = await issueTrustedGrant(cwd, {
80
- issuerId: 'owner', subjectId: 'lead-agent', taskId: 'task-1',
81
- action: 'mcp.call', resource: 'mcp://github/get_issue', ttlMs: 60_000,
82
- }, root);
83
- await expect(issueTrustedGrant(cwd, {
84
- issuerId: 'lead-agent', subjectId: 'worker-agent', taskId: 'task-1',
85
- action: 'mcp.call', resource: 'mcp://github/delete_repo', ttlMs: 30_000,
86
- parentCapabilityId: parent.id,
87
- }, root)).rejects.toThrow('preserve parent action and resource');
88
- });
89
- it('consumes the parent when delegated authority is issued', async () => {
90
- const cwd = await repository();
91
- const root = await controlRoot();
92
- const parent = await issueTrustedGrant(cwd, {
93
- issuerId: 'owner', subjectId: 'lead-agent', taskId: 'task-1',
94
- action: 'mcp.call', resource: 'mcp://github/get_issue', ttlMs: 60_000,
95
- }, root);
96
- const child = await issueTrustedGrant(cwd, {
97
- issuerId: 'lead-agent', subjectId: 'worker-agent', taskId: 'task-1',
98
- action: 'mcp.call', resource: 'mcp://github/get_issue', ttlMs: 30_000,
99
- parentCapabilityId: parent.id,
100
- }, root);
101
- expect(child.parentCapabilityId).toBe(parent.id);
102
- const parentResult = await consumeTrustedGrant(cwd, {
103
- id: parent.id, subjectId: 'lead-agent', taskId: 'task-1',
104
- action: 'mcp.call', resource: 'mcp://github/get_issue',
105
- }, root);
106
- expect(parentResult).toMatchObject({ ok: false, reason: 'Capability already consumed' });
107
- });
108
- it('invalidates grants when the trusted policy changes', async () => {
109
- const cwd = await repository();
110
- const root = await controlRoot();
111
- const base = {
112
- version: 1, principalId: 'owner', agentId: 'agent-1', taskId: 'task-1',
113
- servers: { github: { command: 'github-mcp', allow: ['get_issue'] } },
114
- };
115
- await saveGatewayConfig(cwd, { ...base, mode: 'observe' }, root);
116
- const grant = await issueTrustedGrant(cwd, {
117
- issuerId: 'owner', subjectId: 'agent-1', taskId: 'task-1',
118
- action: 'mcp.call', resource: 'mcp://github/get_issue', ttlMs: 60_000,
119
- }, root);
120
- await saveGatewayConfig(cwd, { ...base, mode: 'enforce' }, root);
121
- const result = await consumeTrustedGrant(cwd, {
122
- id: grant.id, subjectId: 'agent-1', taskId: 'task-1',
123
- action: 'mcp.call', resource: 'mcp://github/get_issue',
124
- }, root);
125
- expect(result).toMatchObject({ ok: false, reason: 'Capability policy is stale' });
126
- });
127
- });
128
- describe('action normalization and receipts', () => {
129
- it('defaults unknown tools to external writes instead of assuming read-only', () => {
130
- const action = normalizeMcpAction({
131
- actorId: 'agent-1', taskId: 'task-1', server: 'custom', tool: 'execute', args: {},
132
- });
133
- expect(action.sideEffect).toBe('external-write');
134
- expect(action.blastRadius).toBe('unknown');
135
- });
136
- it('detects receipt tampering', async () => {
137
- const cwd = await repository();
138
- const root = await controlRoot();
139
- const action = normalizeMcpAction({
140
- actorId: 'agent-1', taskId: 'task-1', server: 'github', tool: 'get_issue', args: {},
141
- });
142
- await appendExecutionReceipt(cwd, {
143
- action, mode: 'observe', decision: 'allow', simulatedDecision: 'deny',
144
- reason: 'Observed', policyHash: 'policy', outcome: 'forwarded',
145
- }, root);
146
- expect(await verifyExecutionReceiptChain(cwd, root)).toMatchObject({ valid: true, count: 1 });
147
- const log = path.join(getTrustedControlDir(cwd, root), 'receipts.jsonl');
148
- const receipt = JSON.parse((await fs.readFile(log, 'utf8')).trim());
149
- receipt.reason = 'tampered';
150
- await fs.writeFile(log, `${JSON.stringify(receipt)}\n`);
151
- expect((await verifyExecutionReceiptChain(cwd, root)).valid).toBe(false);
152
- });
153
- it('serializes concurrent receipt appends into one valid chain', async () => {
154
- const cwd = await repository();
155
- const root = await controlRoot();
156
- const action = normalizeMcpAction({
157
- actorId: 'agent-1', taskId: 'task-1', server: 'github', tool: 'get_issue', args: {},
158
- });
159
- await Promise.all(Array.from({ length: 8 }, () => appendExecutionReceipt(cwd, {
160
- action, mode: 'observe', decision: 'allow', reason: 'Concurrent call',
161
- policyHash: 'policy', outcome: 'forwarded',
162
- }, root)));
163
- expect(await verifyExecutionReceiptChain(cwd, root)).toMatchObject({ valid: true, count: 8 });
164
- });
165
- it('keeps the trusted receipt when the Studio projection is blocked', async () => {
166
- const cwd = await repository();
167
- const root = await controlRoot();
168
- await fs.writeFile(path.join(cwd, '.rigour'), 'sandbox blocks this projection');
169
- const action = normalizeMcpAction({
170
- actorId: 'agent-1', taskId: 'task-1', server: 'github', tool: 'get_issue', args: {},
171
- });
172
- await expect(appendExecutionReceipt(cwd, {
173
- action, mode: 'observe', decision: 'allow', reason: 'Projection optional',
174
- policyHash: 'policy', outcome: 'forwarded',
175
- }, root)).resolves.toMatchObject({ outcome: 'forwarded' });
176
- expect(await verifyExecutionReceiptChain(cwd, root)).toMatchObject({ valid: true, count: 1 });
177
- });
178
- });
179
- describe('receipt lock contention', () => {
180
- it('retries while another append releases the lock, including Windows delete-pending errors', async () => {
181
- const { isLockContention } = await import('./execution-receipt.js');
182
- expect(isLockContention('EEXIST', 'linux')).toBe(true);
183
- expect(isLockContention('EPERM', 'win32')).toBe(true);
184
- expect(isLockContention('EBUSY', 'win32')).toBe(true);
185
- expect(isLockContention('EPERM', 'linux')).toBe(false); // a real permission error elsewhere
186
- expect(isLockContention('ENOENT', 'win32')).toBe(false);
187
- });
188
- });
@@ -1 +0,0 @@
1
- export {};