@probelabs/visor 0.1.129 → 0.1.130

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 (206) hide show
  1. package/README.md +23 -0
  2. package/dist/cli-main.d.ts.map +1 -1
  3. package/dist/config.d.ts +4 -0
  4. package/dist/config.d.ts.map +1 -1
  5. package/dist/docs/author-permissions.md +20 -0
  6. package/dist/docs/enterprise-policy.md +1325 -0
  7. package/dist/docs/index.md +10 -0
  8. package/dist/docs/scheduler-storage.md +433 -0
  9. package/dist/docs/scheduler.md +12 -2
  10. package/dist/enterprise/license/validator.d.ts +39 -0
  11. package/dist/enterprise/license/validator.d.ts.map +1 -0
  12. package/dist/enterprise/loader.d.ts +25 -0
  13. package/dist/enterprise/loader.d.ts.map +1 -0
  14. package/dist/enterprise/policy/opa-compiler.d.ts +37 -0
  15. package/dist/enterprise/policy/opa-compiler.d.ts.map +1 -0
  16. package/dist/enterprise/policy/opa-http-evaluator.d.ts +36 -0
  17. package/dist/enterprise/policy/opa-http-evaluator.d.ts.map +1 -0
  18. package/dist/enterprise/policy/opa-policy-engine.d.ts +48 -0
  19. package/dist/enterprise/policy/opa-policy-engine.d.ts.map +1 -0
  20. package/dist/enterprise/policy/opa-wasm-evaluator.d.ts +34 -0
  21. package/dist/enterprise/policy/opa-wasm-evaluator.d.ts.map +1 -0
  22. package/dist/enterprise/policy/policy-input-builder.d.ts +120 -0
  23. package/dist/enterprise/policy/policy-input-builder.d.ts.map +1 -0
  24. package/dist/enterprise/scheduler/knex-store.d.ts +41 -0
  25. package/dist/enterprise/scheduler/knex-store.d.ts.map +1 -0
  26. package/dist/examples/README.md +23 -0
  27. package/dist/examples/enterprise-policy/README.md +344 -0
  28. package/dist/examples/enterprise-policy/policies/capability_resolve.rego +29 -0
  29. package/dist/examples/enterprise-policy/policies/capability_resolve_test.rego +230 -0
  30. package/dist/examples/enterprise-policy/policies/check_execute.rego +71 -0
  31. package/dist/examples/enterprise-policy/policies/check_execute_test.rego +321 -0
  32. package/dist/examples/enterprise-policy/policies/deploy_production.rego +33 -0
  33. package/dist/examples/enterprise-policy/policies/deploy_production_test.rego +29 -0
  34. package/dist/examples/enterprise-policy/policies/slack_channel_gate.rego +17 -0
  35. package/dist/examples/enterprise-policy/policies/slack_tool_restrict.rego +16 -0
  36. package/dist/examples/enterprise-policy/policies/tool_invoke.rego +24 -0
  37. package/dist/examples/enterprise-policy/policies/tool_invoke_test.rego +227 -0
  38. package/dist/examples/enterprise-policy/visor.yaml +64 -0
  39. package/dist/failure-condition-evaluator.d.ts +18 -0
  40. package/dist/failure-condition-evaluator.d.ts.map +1 -1
  41. package/dist/frontends/slack-frontend.d.ts +1 -0
  42. package/dist/frontends/slack-frontend.d.ts.map +1 -1
  43. package/dist/generated/config-schema.d.ts +139 -0
  44. package/dist/generated/config-schema.d.ts.map +1 -1
  45. package/dist/index.js +12121 -7169
  46. package/dist/liquid-extensions.d.ts.map +1 -1
  47. package/dist/output/traces/{run-2026-02-08T18-16-04-160Z.ndjson → run-2026-02-11T16-20-59-999Z.ndjson} +84 -84
  48. package/dist/{traces/run-2026-02-08T18-16-51-253Z.ndjson → output/traces/run-2026-02-11T16-21-47-711Z.ndjson} +1032 -1032
  49. package/dist/policy/default-engine.d.ts +17 -0
  50. package/dist/policy/default-engine.d.ts.map +1 -0
  51. package/dist/policy/index.d.ts +4 -0
  52. package/dist/policy/index.d.ts.map +1 -0
  53. package/dist/policy/policy-check-command.d.ts +65 -0
  54. package/dist/policy/policy-check-command.d.ts.map +1 -0
  55. package/dist/policy/types.d.ts +81 -0
  56. package/dist/policy/types.d.ts.map +1 -0
  57. package/dist/providers/ai-check-provider.d.ts.map +1 -1
  58. package/dist/providers/check-provider.interface.d.ts +2 -0
  59. package/dist/providers/check-provider.interface.d.ts.map +1 -1
  60. package/dist/providers/claude-code-check-provider.d.ts.map +1 -1
  61. package/dist/providers/mcp-check-provider.d.ts.map +1 -1
  62. package/dist/providers/mcp-custom-sse-server.d.ts.map +1 -1
  63. package/dist/providers/workflow-check-provider.d.ts.map +1 -1
  64. package/dist/scheduler/index.d.ts +2 -0
  65. package/dist/scheduler/index.d.ts.map +1 -1
  66. package/dist/scheduler/schedule-store.d.ts +33 -59
  67. package/dist/scheduler/schedule-store.d.ts.map +1 -1
  68. package/dist/scheduler/schedule-tool.d.ts.map +1 -1
  69. package/dist/scheduler/scheduler.d.ts +24 -3
  70. package/dist/scheduler/scheduler.d.ts.map +1 -1
  71. package/dist/scheduler/store/index.d.ts +7 -0
  72. package/dist/scheduler/store/index.d.ts.map +1 -0
  73. package/dist/scheduler/store/json-migrator.d.ts +10 -0
  74. package/dist/scheduler/store/json-migrator.d.ts.map +1 -0
  75. package/dist/scheduler/store/sqlite-store.d.ts +32 -0
  76. package/dist/scheduler/store/sqlite-store.d.ts.map +1 -0
  77. package/dist/scheduler/store/types.d.ts +127 -0
  78. package/dist/scheduler/store/types.d.ts.map +1 -0
  79. package/dist/sdk/check-provider-registry-M3Y6JMTW.mjs +28 -0
  80. package/dist/sdk/check-provider-registry-PANIXYRB.mjs +28 -0
  81. package/dist/sdk/{chunk-D5KI4YQ4.mjs → chunk-DIND4ZCV.mjs} +2 -2
  82. package/dist/sdk/{chunk-DGZPPGJJ.mjs → chunk-EUUAQBTW.mjs} +1463 -568
  83. package/dist/sdk/chunk-EUUAQBTW.mjs.map +1 -0
  84. package/dist/sdk/{chunk-XDLQ3UNF.mjs → chunk-GEW6LS32.mjs} +2 -2
  85. package/dist/sdk/{chunk-N7HO6KKC.mjs → chunk-HOKQOO3G.mjs} +11 -6
  86. package/dist/sdk/chunk-HOKQOO3G.mjs.map +1 -0
  87. package/dist/sdk/{chunk-XR7XXGL7.mjs → chunk-JL7JXCET.mjs} +2 -2
  88. package/dist/sdk/{chunk-6W75IMDC.mjs → chunk-LG4AUKHB.mjs} +2 -2
  89. package/dist/sdk/{chunk-BDGUM6BA.mjs → chunk-S6CD7GFM.mjs} +1463 -568
  90. package/dist/sdk/chunk-S6CD7GFM.mjs.map +1 -0
  91. package/dist/sdk/{chunk-PO7X5XI7.mjs → chunk-SZXICFQ3.mjs} +2 -2
  92. package/dist/sdk/{chunk-HEX3RL32.mjs → chunk-UCMJJ3IM.mjs} +5 -2
  93. package/dist/sdk/{chunk-HEX3RL32.mjs.map → chunk-UCMJJ3IM.mjs.map} +1 -1
  94. package/dist/sdk/{chunk-7YSOINAQ.mjs → chunk-UCNT3PDT.mjs} +342 -5
  95. package/dist/sdk/chunk-UCNT3PDT.mjs.map +1 -0
  96. package/dist/sdk/{chunk-R5Z7YWPB.mjs → chunk-V2IV3ILA.mjs} +7 -5
  97. package/dist/sdk/chunk-V2IV3ILA.mjs.map +1 -0
  98. package/dist/sdk/{chunk-SGS2VMEL.mjs → chunk-VMLORODQ.mjs} +107 -20
  99. package/dist/sdk/chunk-VMLORODQ.mjs.map +1 -0
  100. package/dist/sdk/{chunk-2KB35MB7.mjs → chunk-VPC3QSPW.mjs} +2 -2
  101. package/dist/sdk/{chunk-J5RGJQ53.mjs → chunk-YJRBN3XS.mjs} +2 -2
  102. package/dist/sdk/{command-executor-DVVXERLR.mjs → command-executor-TOYBBE7S.mjs} +4 -4
  103. package/dist/sdk/{config-7VTT64SQ.mjs → config-OGOS4ZU4.mjs} +4 -4
  104. package/dist/sdk/failure-condition-evaluator-HC3M5377.mjs +17 -0
  105. package/dist/sdk/{github-frontend-3N2NLO66.mjs → github-frontend-E2KJSC3Y.mjs} +7 -7
  106. package/dist/sdk/{host-ONVMEHAA.mjs → host-EE6EJ2FM.mjs} +4 -4
  107. package/dist/sdk/lazy-otel-5NH4ZJJM.mjs +24 -0
  108. package/dist/sdk/{liquid-extensions-5IZLTFSZ.mjs → liquid-extensions-E4EUOCES.mjs} +5 -5
  109. package/dist/sdk/memory-store-AAPL2MTE.mjs +12 -0
  110. package/dist/sdk/{metrics-GXQ2EDXA.mjs → metrics-I6A7IHG4.mjs} +3 -3
  111. package/dist/sdk/{prompt-state-YHGXB2OA.mjs → prompt-state-VAKKC773.mjs} +4 -4
  112. package/dist/sdk/{renderer-schema-CMXOLNIG.mjs → renderer-schema-HXEW6BRJ.mjs} +3 -3
  113. package/dist/sdk/{routing-S3Y7T2X3.mjs → routing-OZQWAGAI.mjs} +9 -8
  114. package/dist/sdk/schedule-tool-handler-B7TMSG6A.mjs +38 -0
  115. package/dist/sdk/schedule-tool-handler-IEB2VS7O.mjs +38 -0
  116. package/dist/sdk/sdk.d.mts +134 -4
  117. package/dist/sdk/sdk.d.ts +134 -4
  118. package/dist/sdk/sdk.js +2509 -1085
  119. package/dist/sdk/sdk.js.map +1 -1
  120. package/dist/sdk/sdk.mjs +14 -14
  121. package/dist/sdk/{slack-frontend-R3M2CACB.mjs → slack-frontend-LAY45IBR.mjs} +119 -29
  122. package/dist/sdk/slack-frontend-LAY45IBR.mjs.map +1 -0
  123. package/dist/sdk/{trace-helpers-YHNPC7MR.mjs → trace-helpers-PP3YHTAM.mjs} +3 -3
  124. package/dist/sdk/{tui-frontend-S546M7A7.mjs → tui-frontend-T56PZB67.mjs} +25 -16
  125. package/dist/sdk/tui-frontend-T56PZB67.mjs.map +1 -0
  126. package/dist/sdk/workflow-check-provider-2ET3SFZH.mjs +28 -0
  127. package/dist/sdk/workflow-check-provider-2ET3SFZH.mjs.map +1 -0
  128. package/dist/sdk/workflow-check-provider-HB4XTD4Z.mjs +28 -0
  129. package/dist/sdk/workflow-check-provider-HB4XTD4Z.mjs.map +1 -0
  130. package/dist/sdk/workflow-registry-AAD37XKZ.mjs +12 -0
  131. package/dist/sdk/workflow-registry-AAD37XKZ.mjs.map +1 -0
  132. package/dist/slack/client.d.ts +12 -0
  133. package/dist/slack/client.d.ts.map +1 -1
  134. package/dist/slack/slack-output-adapter.d.ts.map +1 -1
  135. package/dist/slack/socket-runner.d.ts.map +1 -1
  136. package/dist/state-machine/dispatch/execution-invoker.d.ts.map +1 -1
  137. package/dist/state-machine/dispatch/policy-gate.d.ts +28 -0
  138. package/dist/state-machine/dispatch/policy-gate.d.ts.map +1 -0
  139. package/dist/state-machine/states/level-dispatch.d.ts.map +1 -1
  140. package/dist/state-machine/states/routing.d.ts.map +1 -1
  141. package/dist/state-machine/states/wave-planning.d.ts.map +1 -1
  142. package/dist/state-machine-execution-engine.d.ts.map +1 -1
  143. package/dist/test-runner/core/flow-stage.d.ts.map +1 -1
  144. package/dist/test-runner/validator.d.ts.map +1 -1
  145. package/dist/traces/{run-2026-02-08T18-16-04-160Z.ndjson → run-2026-02-11T16-20-59-999Z.ndjson} +84 -84
  146. package/dist/{output/traces/run-2026-02-08T18-16-51-253Z.ndjson → traces/run-2026-02-11T16-21-47-711Z.ndjson} +1032 -1032
  147. package/dist/tui/chat-runner.d.ts.map +1 -1
  148. package/dist/tui/chat-state.d.ts +1 -0
  149. package/dist/tui/chat-state.d.ts.map +1 -1
  150. package/dist/tui/chat-tui.d.ts +3 -2
  151. package/dist/tui/chat-tui.d.ts.map +1 -1
  152. package/dist/tui/components/chat-box.d.ts +9 -0
  153. package/dist/tui/components/chat-box.d.ts.map +1 -1
  154. package/dist/tui/components/input-bar.d.ts +18 -1
  155. package/dist/tui/components/input-bar.d.ts.map +1 -1
  156. package/dist/tui/components/status-bar.d.ts +5 -2
  157. package/dist/tui/components/status-bar.d.ts.map +1 -1
  158. package/dist/tui/components/trace-viewer.d.ts +1 -0
  159. package/dist/tui/components/trace-viewer.d.ts.map +1 -1
  160. package/dist/tui/tui-frontend.d.ts.map +1 -1
  161. package/dist/types/config.d.ts +107 -3
  162. package/dist/types/config.d.ts.map +1 -1
  163. package/dist/types/engine.d.ts +5 -0
  164. package/dist/types/engine.d.ts.map +1 -1
  165. package/dist/types/execution.d.ts +1 -1
  166. package/dist/types/execution.d.ts.map +1 -1
  167. package/package.json +14 -4
  168. package/dist/sdk/check-provider-registry-ACRGIYOB.mjs +0 -28
  169. package/dist/sdk/check-provider-registry-VYHKFHK2.mjs +0 -28
  170. package/dist/sdk/chunk-7YSOINAQ.mjs.map +0 -1
  171. package/dist/sdk/chunk-BDGUM6BA.mjs.map +0 -1
  172. package/dist/sdk/chunk-DGZPPGJJ.mjs.map +0 -1
  173. package/dist/sdk/chunk-N7HO6KKC.mjs.map +0 -1
  174. package/dist/sdk/chunk-R5Z7YWPB.mjs.map +0 -1
  175. package/dist/sdk/chunk-SGS2VMEL.mjs.map +0 -1
  176. package/dist/sdk/failure-condition-evaluator-4WMDF4Q3.mjs +0 -17
  177. package/dist/sdk/memory-store-3N4AZCYB.mjs +0 -12
  178. package/dist/sdk/slack-frontend-R3M2CACB.mjs.map +0 -1
  179. package/dist/sdk/tui-frontend-S546M7A7.mjs.map +0 -1
  180. package/dist/sdk/workflow-check-provider-4F3432ZP.mjs +0 -28
  181. package/dist/sdk/workflow-check-provider-A44PBPG2.mjs +0 -28
  182. package/dist/sdk/workflow-registry-ZAYYXLEP.mjs +0 -12
  183. /package/dist/sdk/{check-provider-registry-ACRGIYOB.mjs.map → check-provider-registry-M3Y6JMTW.mjs.map} +0 -0
  184. /package/dist/sdk/{check-provider-registry-VYHKFHK2.mjs.map → check-provider-registry-PANIXYRB.mjs.map} +0 -0
  185. /package/dist/sdk/{chunk-D5KI4YQ4.mjs.map → chunk-DIND4ZCV.mjs.map} +0 -0
  186. /package/dist/sdk/{chunk-XDLQ3UNF.mjs.map → chunk-GEW6LS32.mjs.map} +0 -0
  187. /package/dist/sdk/{chunk-XR7XXGL7.mjs.map → chunk-JL7JXCET.mjs.map} +0 -0
  188. /package/dist/sdk/{chunk-6W75IMDC.mjs.map → chunk-LG4AUKHB.mjs.map} +0 -0
  189. /package/dist/sdk/{chunk-PO7X5XI7.mjs.map → chunk-SZXICFQ3.mjs.map} +0 -0
  190. /package/dist/sdk/{chunk-2KB35MB7.mjs.map → chunk-VPC3QSPW.mjs.map} +0 -0
  191. /package/dist/sdk/{chunk-J5RGJQ53.mjs.map → chunk-YJRBN3XS.mjs.map} +0 -0
  192. /package/dist/sdk/{command-executor-DVVXERLR.mjs.map → command-executor-TOYBBE7S.mjs.map} +0 -0
  193. /package/dist/sdk/{config-7VTT64SQ.mjs.map → config-OGOS4ZU4.mjs.map} +0 -0
  194. /package/dist/sdk/{failure-condition-evaluator-4WMDF4Q3.mjs.map → failure-condition-evaluator-HC3M5377.mjs.map} +0 -0
  195. /package/dist/sdk/{github-frontend-3N2NLO66.mjs.map → github-frontend-E2KJSC3Y.mjs.map} +0 -0
  196. /package/dist/sdk/{host-ONVMEHAA.mjs.map → host-EE6EJ2FM.mjs.map} +0 -0
  197. /package/dist/sdk/{liquid-extensions-5IZLTFSZ.mjs.map → lazy-otel-5NH4ZJJM.mjs.map} +0 -0
  198. /package/dist/sdk/{memory-store-3N4AZCYB.mjs.map → liquid-extensions-E4EUOCES.mjs.map} +0 -0
  199. /package/dist/sdk/{metrics-GXQ2EDXA.mjs.map → memory-store-AAPL2MTE.mjs.map} +0 -0
  200. /package/dist/sdk/{prompt-state-YHGXB2OA.mjs.map → metrics-I6A7IHG4.mjs.map} +0 -0
  201. /package/dist/sdk/{routing-S3Y7T2X3.mjs.map → prompt-state-VAKKC773.mjs.map} +0 -0
  202. /package/dist/sdk/{renderer-schema-CMXOLNIG.mjs.map → renderer-schema-HXEW6BRJ.mjs.map} +0 -0
  203. /package/dist/sdk/{trace-helpers-YHNPC7MR.mjs.map → routing-OZQWAGAI.mjs.map} +0 -0
  204. /package/dist/sdk/{workflow-check-provider-4F3432ZP.mjs.map → schedule-tool-handler-B7TMSG6A.mjs.map} +0 -0
  205. /package/dist/sdk/{workflow-check-provider-A44PBPG2.mjs.map → schedule-tool-handler-IEB2VS7O.mjs.map} +0 -0
  206. /package/dist/sdk/{workflow-registry-ZAYYXLEP.mjs.map → trace-helpers-PP3YHTAM.mjs.map} +0 -0
@@ -0,0 +1,230 @@
1
+ # Tests for AI capability restriction policy
2
+ # Run with: opa test examples/enterprise-policy/policies/
3
+
4
+ package visor.capability.resolve
5
+
6
+ # ---------------------------------------------------------------------------
7
+ # External contributors – allowBash=false and allowEdit=false
8
+ # ---------------------------------------------------------------------------
9
+
10
+ test_external_gets_allowBash_false {
11
+ capabilities["allowBash"] == false with input as {
12
+ "scope": "capability.resolve",
13
+ "check": {"id": "ai-review", "type": "ai"},
14
+ "capability": {"allowEdit": true, "allowBash": true},
15
+ "actor": {"roles": ["external"], "isLocalMode": false}
16
+ }
17
+ }
18
+
19
+ test_external_gets_allowEdit_false {
20
+ capabilities["allowEdit"] == false with input as {
21
+ "scope": "capability.resolve",
22
+ "check": {"id": "ai-review", "type": "ai"},
23
+ "capability": {"allowEdit": true, "allowBash": true},
24
+ "actor": {"roles": ["external"], "isLocalMode": false}
25
+ }
26
+ }
27
+
28
+ test_external_both_restricted {
29
+ caps := capabilities with input as {
30
+ "scope": "capability.resolve",
31
+ "check": {"id": "ai-review", "type": "ai"},
32
+ "capability": {"allowEdit": true, "allowBash": true},
33
+ "actor": {"roles": ["external"], "isLocalMode": false}
34
+ }
35
+ caps["allowBash"] == false
36
+ caps["allowEdit"] == false
37
+ }
38
+
39
+ # ---------------------------------------------------------------------------
40
+ # Reviewer – not developer/admin so allowEdit=false, not external so no bash restriction
41
+ # ---------------------------------------------------------------------------
42
+
43
+ test_reviewer_gets_allowEdit_false {
44
+ capabilities["allowEdit"] == false with input as {
45
+ "scope": "capability.resolve",
46
+ "check": {"id": "ai-review", "type": "ai"},
47
+ "capability": {"allowEdit": true, "allowBash": true},
48
+ "actor": {"roles": ["reviewer"], "isLocalMode": false}
49
+ }
50
+ }
51
+
52
+ test_reviewer_no_allowBash_restriction {
53
+ not capabilities["allowBash"] with input as {
54
+ "scope": "capability.resolve",
55
+ "check": {"id": "ai-review", "type": "ai"},
56
+ "capability": {"allowEdit": true, "allowBash": true},
57
+ "actor": {"roles": ["reviewer"], "isLocalMode": false}
58
+ }
59
+ }
60
+
61
+ # ---------------------------------------------------------------------------
62
+ # Developer – keeps allowEdit (is_developer), no bash restriction
63
+ # ---------------------------------------------------------------------------
64
+
65
+ test_developer_no_allowEdit_restriction {
66
+ not capabilities["allowEdit"] with input as {
67
+ "scope": "capability.resolve",
68
+ "check": {"id": "ai-review", "type": "ai"},
69
+ "capability": {"allowEdit": true, "allowBash": true},
70
+ "actor": {"roles": ["developer"], "isLocalMode": false}
71
+ }
72
+ }
73
+
74
+ test_developer_no_allowBash_restriction {
75
+ not capabilities["allowBash"] with input as {
76
+ "scope": "capability.resolve",
77
+ "check": {"id": "ai-review", "type": "ai"},
78
+ "capability": {"allowEdit": true, "allowBash": true},
79
+ "actor": {"roles": ["developer"], "isLocalMode": false}
80
+ }
81
+ }
82
+
83
+ test_developer_capabilities_empty {
84
+ # Developer should produce no capability restrictions at all
85
+ count(capabilities) == 0 with input as {
86
+ "scope": "capability.resolve",
87
+ "check": {"id": "ai-review", "type": "ai"},
88
+ "capability": {"allowEdit": true, "allowBash": true},
89
+ "actor": {"roles": ["developer"], "isLocalMode": false}
90
+ }
91
+ }
92
+
93
+ # ---------------------------------------------------------------------------
94
+ # Admin – keeps everything (is_admin bypasses allowEdit restriction)
95
+ # ---------------------------------------------------------------------------
96
+
97
+ test_admin_no_allowEdit_restriction {
98
+ not capabilities["allowEdit"] with input as {
99
+ "scope": "capability.resolve",
100
+ "check": {"id": "ai-review", "type": "ai"},
101
+ "capability": {"allowEdit": true, "allowBash": true},
102
+ "actor": {"roles": ["admin"], "isLocalMode": false}
103
+ }
104
+ }
105
+
106
+ test_admin_no_allowBash_restriction {
107
+ not capabilities["allowBash"] with input as {
108
+ "scope": "capability.resolve",
109
+ "check": {"id": "ai-review", "type": "ai"},
110
+ "capability": {"allowEdit": true, "allowBash": true},
111
+ "actor": {"roles": ["admin"], "isLocalMode": false}
112
+ }
113
+ }
114
+
115
+ test_admin_capabilities_empty {
116
+ count(capabilities) == 0 with input as {
117
+ "scope": "capability.resolve",
118
+ "check": {"id": "ai-review", "type": "ai"},
119
+ "capability": {"allowEdit": true, "allowBash": true},
120
+ "actor": {"roles": ["admin"], "isLocalMode": false}
121
+ }
122
+ }
123
+
124
+ # ---------------------------------------------------------------------------
125
+ # Empty roles – not developer, not admin => allowEdit=false; not external => no bash
126
+ # ---------------------------------------------------------------------------
127
+
128
+ test_empty_roles_gets_allowEdit_false {
129
+ capabilities["allowEdit"] == false with input as {
130
+ "scope": "capability.resolve",
131
+ "check": {"id": "ai-review", "type": "ai"},
132
+ "capability": {"allowEdit": true, "allowBash": true},
133
+ "actor": {"roles": [], "isLocalMode": false}
134
+ }
135
+ }
136
+
137
+ test_empty_roles_no_allowBash_restriction {
138
+ not capabilities["allowBash"] with input as {
139
+ "scope": "capability.resolve",
140
+ "check": {"id": "ai-review", "type": "ai"},
141
+ "capability": {"allowEdit": true, "allowBash": true},
142
+ "actor": {"roles": [], "isLocalMode": false}
143
+ }
144
+ }
145
+
146
+ # ---------------------------------------------------------------------------
147
+ # Helper rules
148
+ # ---------------------------------------------------------------------------
149
+
150
+ test_is_developer_true {
151
+ is_developer with input as {
152
+ "actor": {"roles": ["developer"], "isLocalMode": false}
153
+ }
154
+ }
155
+
156
+ test_is_developer_false_for_reviewer {
157
+ not is_developer with input as {
158
+ "actor": {"roles": ["reviewer"], "isLocalMode": false}
159
+ }
160
+ }
161
+
162
+ test_is_admin_true {
163
+ is_admin with input as {
164
+ "actor": {"roles": ["admin"], "isLocalMode": false}
165
+ }
166
+ }
167
+
168
+ test_is_admin_false_for_developer {
169
+ not is_admin with input as {
170
+ "actor": {"roles": ["developer"], "isLocalMode": false}
171
+ }
172
+ }
173
+
174
+ test_is_admin_false_for_empty_roles {
175
+ not is_admin with input as {
176
+ "actor": {"roles": [], "isLocalMode": false}
177
+ }
178
+ }
179
+
180
+ # ---------------------------------------------------------------------------
181
+ # Multi-role actors
182
+ # ---------------------------------------------------------------------------
183
+
184
+ test_developer_and_external_keeps_edit_but_loses_bash {
185
+ # developer satisfies is_developer so allowEdit is not restricted
186
+ # external triggers allowBash=false
187
+ caps := capabilities with input as {
188
+ "scope": "capability.resolve",
189
+ "check": {"id": "ai-review", "type": "ai"},
190
+ "capability": {"allowEdit": true, "allowBash": true},
191
+ "actor": {"roles": ["developer", "external"], "isLocalMode": false}
192
+ }
193
+ not caps["allowEdit"]
194
+ caps["allowBash"] == false
195
+ }
196
+
197
+ test_admin_and_external_keeps_edit_but_loses_bash {
198
+ # admin satisfies is_admin so allowEdit is not restricted
199
+ # external role still triggers allowBash=false
200
+ caps := capabilities with input as {
201
+ "scope": "capability.resolve",
202
+ "check": {"id": "ai-review", "type": "ai"},
203
+ "capability": {"allowEdit": true, "allowBash": true},
204
+ "actor": {"roles": ["admin", "external"], "isLocalMode": false}
205
+ }
206
+ not caps["allowEdit"]
207
+ caps["allowBash"] == false
208
+ }
209
+
210
+ # ---------------------------------------------------------------------------
211
+ # Unknown role – not developer/admin => allowEdit=false, not external => no bash
212
+ # ---------------------------------------------------------------------------
213
+
214
+ test_unknown_role_gets_allowEdit_false {
215
+ capabilities["allowEdit"] == false with input as {
216
+ "scope": "capability.resolve",
217
+ "check": {"id": "ai-review", "type": "ai"},
218
+ "capability": {"allowEdit": true, "allowBash": true},
219
+ "actor": {"roles": ["guest"], "isLocalMode": false}
220
+ }
221
+ }
222
+
223
+ test_unknown_role_no_allowBash_restriction {
224
+ not capabilities["allowBash"] with input as {
225
+ "scope": "capability.resolve",
226
+ "check": {"id": "ai-review", "type": "ai"},
227
+ "capability": {"allowEdit": true, "allowBash": true},
228
+ "actor": {"roles": ["guest"], "isLocalMode": false}
229
+ }
230
+ }
@@ -0,0 +1,71 @@
1
+ # Check execution gating policy (Visor Enterprise Edition)
2
+ # Controls which roles can run each check.
3
+ # Contact hello@probelabs.com for licensing.
4
+
5
+ package visor.check.execute
6
+
7
+ default allowed = false
8
+
9
+ # Explicit deny list from YAML policy.deny — if any of the actor's roles
10
+ # appear in the deny list, the check is unconditionally blocked.
11
+ # This is WASM-safe: uses explicit iteration with `some` instead of
12
+ # `not collection[_] == value`.
13
+ denied {
14
+ input.check.policy
15
+ some i, j
16
+ input.check.policy.deny[i] == input.actor.roles[j]
17
+ }
18
+
19
+ # Admin can run anything (unless explicitly denied)
20
+ allowed {
21
+ not denied
22
+ input.actor.roles[_] == "admin"
23
+ }
24
+
25
+ # Developers can run non-production checks (unless explicitly denied)
26
+ allowed {
27
+ not denied
28
+ input.actor.roles[_] == "developer"
29
+ not startswith(input.check.id, "deploy-production")
30
+ }
31
+
32
+ # Reviewers can run read-only checks (info/policy criticality)
33
+ allowed {
34
+ not denied
35
+ input.actor.roles[_] == "reviewer"
36
+ input.check.criticality == "info"
37
+ }
38
+
39
+ allowed {
40
+ not denied
41
+ input.actor.roles[_] == "reviewer"
42
+ input.check.criticality == "policy"
43
+ }
44
+
45
+ # Per-step role requirement (from YAML policy.require)
46
+ allowed {
47
+ not denied
48
+ required := input.check.policy.require
49
+ is_string(required)
50
+ input.actor.roles[_] == required
51
+ }
52
+
53
+ allowed {
54
+ not denied
55
+ required := input.check.policy.require
56
+ is_array(required)
57
+ required[_] == input.actor.roles[_]
58
+ }
59
+
60
+ # Local mode bypasses policy for checks without explicit requirements.
61
+ # Checks that declare `policy.require` in YAML still enforce roles even when
62
+ # running locally, so sensitive steps (e.g. deploy-production) stay protected.
63
+ # Explicit deny lists are still enforced in local mode.
64
+ allowed {
65
+ not denied
66
+ input.actor.isLocalMode == true
67
+ not input.check.policy
68
+ }
69
+
70
+ reason = "role is in the deny list for this check" { denied }
71
+ reason = "insufficient role for this check" { not denied; not allowed }
@@ -0,0 +1,321 @@
1
+ # Tests for check execution gating policy
2
+ # Run with: opa test examples/enterprise-policy/policies/
3
+
4
+ package visor.check.execute
5
+
6
+ # ---------------------------------------------------------------------------
7
+ # Admin bypass – admin can run anything regardless of check id or type
8
+ # ---------------------------------------------------------------------------
9
+
10
+ test_admin_allowed_for_production_deploy {
11
+ allowed with input as {
12
+ "scope": "check.execute",
13
+ "check": {"id": "deploy-production", "type": "command"},
14
+ "actor": {"roles": ["admin"], "isLocalMode": false}
15
+ }
16
+ }
17
+
18
+ test_admin_allowed_for_arbitrary_check {
19
+ allowed with input as {
20
+ "scope": "check.execute",
21
+ "check": {"id": "lint-code", "type": "ai", "criticality": "high"},
22
+ "actor": {"roles": ["admin"], "isLocalMode": false}
23
+ }
24
+ }
25
+
26
+ test_admin_allowed_even_with_require_other_role {
27
+ allowed with input as {
28
+ "scope": "check.execute",
29
+ "check": {"id": "deploy-production", "type": "command", "policy": {"require": "superadmin"}},
30
+ "actor": {"roles": ["admin"], "isLocalMode": false}
31
+ }
32
+ }
33
+
34
+ # ---------------------------------------------------------------------------
35
+ # Developer restrictions – allowed for non-production, denied for production
36
+ # ---------------------------------------------------------------------------
37
+
38
+ test_developer_allowed_for_non_production_check {
39
+ allowed with input as {
40
+ "scope": "check.execute",
41
+ "check": {"id": "lint-code", "type": "ai"},
42
+ "actor": {"roles": ["developer"], "isLocalMode": false}
43
+ }
44
+ }
45
+
46
+ test_developer_allowed_for_test_suite {
47
+ allowed with input as {
48
+ "scope": "check.execute",
49
+ "check": {"id": "run-tests", "type": "command"},
50
+ "actor": {"roles": ["developer"], "isLocalMode": false}
51
+ }
52
+ }
53
+
54
+ test_developer_denied_for_deploy_production {
55
+ not allowed with input as {
56
+ "scope": "check.execute",
57
+ "check": {"id": "deploy-production", "type": "command"},
58
+ "actor": {"roles": ["developer"], "isLocalMode": false}
59
+ }
60
+ }
61
+
62
+ test_developer_denied_for_deploy_production_with_suffix {
63
+ not allowed with input as {
64
+ "scope": "check.execute",
65
+ "check": {"id": "deploy-production-canary", "type": "command"},
66
+ "actor": {"roles": ["developer"], "isLocalMode": false}
67
+ }
68
+ }
69
+
70
+ # ---------------------------------------------------------------------------
71
+ # Reviewer – allowed only for info and policy criticality
72
+ # ---------------------------------------------------------------------------
73
+
74
+ test_reviewer_allowed_for_info_criticality {
75
+ allowed with input as {
76
+ "scope": "check.execute",
77
+ "check": {"id": "info-check", "type": "ai", "criticality": "info"},
78
+ "actor": {"roles": ["reviewer"], "isLocalMode": false}
79
+ }
80
+ }
81
+
82
+ test_reviewer_allowed_for_policy_criticality {
83
+ allowed with input as {
84
+ "scope": "check.execute",
85
+ "check": {"id": "policy-check", "type": "ai", "criticality": "policy"},
86
+ "actor": {"roles": ["reviewer"], "isLocalMode": false}
87
+ }
88
+ }
89
+
90
+ test_reviewer_denied_for_high_criticality {
91
+ not allowed with input as {
92
+ "scope": "check.execute",
93
+ "check": {"id": "high-check", "type": "ai", "criticality": "high"},
94
+ "actor": {"roles": ["reviewer"], "isLocalMode": false}
95
+ }
96
+ }
97
+
98
+ test_reviewer_denied_when_no_criticality {
99
+ not allowed with input as {
100
+ "scope": "check.execute",
101
+ "check": {"id": "some-check", "type": "command"},
102
+ "actor": {"roles": ["reviewer"], "isLocalMode": false}
103
+ }
104
+ }
105
+
106
+ test_reviewer_denied_for_critical_criticality {
107
+ not allowed with input as {
108
+ "scope": "check.execute",
109
+ "check": {"id": "critical-check", "type": "command", "criticality": "critical"},
110
+ "actor": {"roles": ["reviewer"], "isLocalMode": false}
111
+ }
112
+ }
113
+
114
+ # ---------------------------------------------------------------------------
115
+ # Per-step policy.require – string match
116
+ # ---------------------------------------------------------------------------
117
+
118
+ test_require_string_match {
119
+ allowed with input as {
120
+ "scope": "check.execute",
121
+ "check": {"id": "custom-step", "type": "command", "policy": {"require": "developer"}},
122
+ "actor": {"roles": ["developer"], "isLocalMode": false}
123
+ }
124
+ }
125
+
126
+ test_require_string_no_match {
127
+ not allowed with input as {
128
+ "scope": "check.execute",
129
+ "check": {"id": "custom-step", "type": "command", "policy": {"require": "admin"}},
130
+ "actor": {"roles": ["external"], "isLocalMode": false}
131
+ }
132
+ }
133
+
134
+ test_require_string_exact_role {
135
+ allowed with input as {
136
+ "scope": "check.execute",
137
+ "check": {"id": "ops-step", "type": "command", "policy": {"require": "ops"}},
138
+ "actor": {"roles": ["ops"], "isLocalMode": false}
139
+ }
140
+ }
141
+
142
+ # ---------------------------------------------------------------------------
143
+ # Per-step policy.require – array match
144
+ # ---------------------------------------------------------------------------
145
+
146
+ test_require_array_match_first_element {
147
+ allowed with input as {
148
+ "scope": "check.execute",
149
+ "check": {"id": "multi-role-step", "type": "command", "policy": {"require": ["admin", "developer"]}},
150
+ "actor": {"roles": ["admin"], "isLocalMode": false}
151
+ }
152
+ }
153
+
154
+ test_require_array_match_second_element {
155
+ allowed with input as {
156
+ "scope": "check.execute",
157
+ "check": {"id": "multi-role-step", "type": "command", "policy": {"require": ["admin", "developer"]}},
158
+ "actor": {"roles": ["developer"], "isLocalMode": false}
159
+ }
160
+ }
161
+
162
+ test_require_array_no_match {
163
+ not allowed with input as {
164
+ "scope": "check.execute",
165
+ "check": {"id": "multi-role-step", "type": "command", "policy": {"require": ["admin", "developer"]}},
166
+ "actor": {"roles": ["external"], "isLocalMode": false}
167
+ }
168
+ }
169
+
170
+ # ---------------------------------------------------------------------------
171
+ # Local mode – CLI gets broader access regardless of roles
172
+ # ---------------------------------------------------------------------------
173
+
174
+ test_local_mode_allows_everything {
175
+ allowed with input as {
176
+ "scope": "check.execute",
177
+ "check": {"id": "deploy-production", "type": "command"},
178
+ "actor": {"roles": [], "isLocalMode": true}
179
+ }
180
+ }
181
+
182
+ test_local_mode_allows_with_no_roles {
183
+ allowed with input as {
184
+ "scope": "check.execute",
185
+ "check": {"id": "any-check", "type": "ai"},
186
+ "actor": {"roles": [], "isLocalMode": true}
187
+ }
188
+ }
189
+
190
+ test_local_mode_false_does_not_grant_access {
191
+ not allowed with input as {
192
+ "scope": "check.execute",
193
+ "check": {"id": "deploy-production", "type": "command"},
194
+ "actor": {"roles": [], "isLocalMode": false}
195
+ }
196
+ }
197
+
198
+ # ---------------------------------------------------------------------------
199
+ # Deny cases – no matching role and not local mode
200
+ # ---------------------------------------------------------------------------
201
+
202
+ test_external_denied_for_production {
203
+ not allowed with input as {
204
+ "scope": "check.execute",
205
+ "check": {"id": "deploy-production", "type": "command"},
206
+ "actor": {"roles": ["external"], "isLocalMode": false}
207
+ }
208
+ }
209
+
210
+ test_empty_roles_denied {
211
+ not allowed with input as {
212
+ "scope": "check.execute",
213
+ "check": {"id": "lint-code", "type": "ai"},
214
+ "actor": {"roles": [], "isLocalMode": false}
215
+ }
216
+ }
217
+
218
+ test_unknown_role_denied {
219
+ not allowed with input as {
220
+ "scope": "check.execute",
221
+ "check": {"id": "lint-code", "type": "ai"},
222
+ "actor": {"roles": ["guest"], "isLocalMode": false}
223
+ }
224
+ }
225
+
226
+ # ---------------------------------------------------------------------------
227
+ # Reason message
228
+ # ---------------------------------------------------------------------------
229
+
230
+ test_reason_present_when_denied {
231
+ reason == "insufficient role for this check" with input as {
232
+ "scope": "check.execute",
233
+ "check": {"id": "deploy-production", "type": "command"},
234
+ "actor": {"roles": ["external"], "isLocalMode": false}
235
+ }
236
+ }
237
+
238
+ test_reason_not_defined_when_allowed {
239
+ not reason with input as {
240
+ "scope": "check.execute",
241
+ "check": {"id": "deploy-production", "type": "command"},
242
+ "actor": {"roles": ["admin"], "isLocalMode": false}
243
+ }
244
+ }
245
+
246
+ # ---------------------------------------------------------------------------
247
+ # Multi-role actor – at least one matching role grants access
248
+ # ---------------------------------------------------------------------------
249
+
250
+ test_multi_role_actor_allowed_via_admin {
251
+ allowed with input as {
252
+ "scope": "check.execute",
253
+ "check": {"id": "deploy-production", "type": "command"},
254
+ "actor": {"roles": ["reviewer", "admin"], "isLocalMode": false}
255
+ }
256
+ }
257
+
258
+ test_multi_role_actor_developer_plus_reviewer_non_production {
259
+ allowed with input as {
260
+ "scope": "check.execute",
261
+ "check": {"id": "lint-code", "type": "ai"},
262
+ "actor": {"roles": ["developer", "reviewer"], "isLocalMode": false}
263
+ }
264
+ }
265
+
266
+ # ---------------------------------------------------------------------------
267
+ # Deny list tests
268
+ # ---------------------------------------------------------------------------
269
+
270
+ test_denied_blocks_admin_when_in_deny_list {
271
+ not allowed with input as {
272
+ "actor": {"roles": ["admin"], "isLocalMode": false},
273
+ "check": {"id": "sensitive-check", "type": "ai", "policy": {"deny": ["admin"]}}
274
+ }
275
+ }
276
+
277
+ test_denied_blocks_developer_when_in_deny_list {
278
+ not allowed with input as {
279
+ "actor": {"roles": ["developer"], "isLocalMode": false},
280
+ "check": {"id": "some-check", "type": "ai", "policy": {"deny": ["developer"]}}
281
+ }
282
+ }
283
+
284
+ test_denied_does_not_block_when_role_not_in_deny_list {
285
+ allowed with input as {
286
+ "actor": {"roles": ["admin"], "isLocalMode": false},
287
+ "check": {"id": "some-check", "type": "ai", "policy": {"deny": ["external"]}}
288
+ }
289
+ }
290
+
291
+ test_denied_with_empty_deny_list {
292
+ allowed with input as {
293
+ "actor": {"roles": ["admin"], "isLocalMode": false},
294
+ "check": {"id": "some-check", "type": "ai", "policy": {"deny": []}}
295
+ }
296
+ }
297
+
298
+ test_denied_reason_message {
299
+ reason == "role is in the deny list for this check" with input as {
300
+ "actor": {"roles": ["developer"], "isLocalMode": false},
301
+ "check": {"id": "some-check", "type": "ai", "policy": {"deny": ["developer"]}}
302
+ }
303
+ }
304
+
305
+ # ---------------------------------------------------------------------------
306
+ # Local mode + policy.require
307
+ # ---------------------------------------------------------------------------
308
+
309
+ test_local_mode_with_policy_require_enforces_roles {
310
+ not allowed with input as {
311
+ "actor": {"roles": [], "isLocalMode": true},
312
+ "check": {"id": "secure-deploy", "type": "command", "policy": {"require": "admin"}}
313
+ }
314
+ }
315
+
316
+ test_local_mode_with_policy_require_admin_allowed {
317
+ allowed with input as {
318
+ "actor": {"roles": ["admin"], "isLocalMode": true},
319
+ "check": {"id": "secure-deploy", "type": "command", "policy": {"require": "admin"}}
320
+ }
321
+ }
@@ -0,0 +1,33 @@
1
+ # Production deployment policy (Visor Enterprise Edition)
2
+ # Custom rule path example: routes `deploy-production` check to this
3
+ # dedicated package instead of the default `visor.check.execute`.
4
+ #
5
+ # Usage in .visor.yaml:
6
+ # steps:
7
+ # deploy-production:
8
+ # type: command
9
+ # exec: ./deploy.sh production
10
+ # policy:
11
+ # require: admin
12
+ # rule: visor/deploy/production
13
+ #
14
+ # The YAML `rule` field uses slashes; the Rego package uses dots.
15
+ # visor/deploy/production --> package visor.deploy.production
16
+ #
17
+ # Contact hello@probelabs.com for licensing.
18
+
19
+ package visor.deploy.production
20
+
21
+ default allowed = false
22
+
23
+ # Helper: check if actor is an admin (WASM-safe pattern —
24
+ # avoids `not input.actor.roles[_] == "admin"` which is unsafe for WASM)
25
+ is_admin { input.actor.roles[_] == "admin" }
26
+
27
+ # Only admins can deploy to production, and only when targeting main
28
+ allowed {
29
+ is_admin
30
+ input.repository.baseBranch == "main"
31
+ }
32
+
33
+ reason = "only admins can deploy to production" { not allowed }
@@ -0,0 +1,29 @@
1
+ package visor.deploy.production
2
+
3
+ test_admin_allowed_with_main_branch {
4
+ allowed with input as {
5
+ "actor": {"roles": ["admin"], "isLocalMode": false},
6
+ "repository": {"baseBranch": "main"}
7
+ }
8
+ }
9
+
10
+ test_admin_denied_without_main_branch {
11
+ not allowed with input as {
12
+ "actor": {"roles": ["admin"], "isLocalMode": false},
13
+ "repository": {"baseBranch": "develop"}
14
+ }
15
+ }
16
+
17
+ test_non_admin_denied {
18
+ not allowed with input as {
19
+ "actor": {"roles": ["developer"], "isLocalMode": false},
20
+ "repository": {"baseBranch": "main"}
21
+ }
22
+ }
23
+
24
+ test_reason_when_denied {
25
+ reason == "only admins can deploy to production" with input as {
26
+ "actor": {"roles": ["developer"], "isLocalMode": false},
27
+ "repository": {"baseBranch": "main"}
28
+ }
29
+ }