oxe-cc 1.2.1 → 1.4.0

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 (281) hide show
  1. package/.cursor/commands/oxe-ask.md +2 -2
  2. package/.cursor/commands/oxe-capabilities.md +2 -2
  3. package/.cursor/commands/oxe-checkpoint.md +2 -2
  4. package/.cursor/commands/oxe-compact.md +2 -2
  5. package/.cursor/commands/oxe-dashboard.md +2 -2
  6. package/.cursor/commands/oxe-debug.md +2 -2
  7. package/.cursor/commands/oxe-discuss.md +2 -2
  8. package/.cursor/commands/oxe-execute.md +5 -2
  9. package/.cursor/commands/oxe-forensics.md +2 -2
  10. package/.cursor/commands/oxe-help.md +2 -2
  11. package/.cursor/commands/oxe-loop.md +2 -2
  12. package/.cursor/commands/oxe-milestone.md +2 -2
  13. package/.cursor/commands/oxe-next.md +2 -2
  14. package/.cursor/commands/oxe-obs.md +2 -2
  15. package/.cursor/commands/oxe-plan-agent.md +2 -2
  16. package/.cursor/commands/oxe-plan.md +2 -2
  17. package/.cursor/commands/oxe-project.md +2 -2
  18. package/.cursor/commands/oxe-quick.md +2 -2
  19. package/.cursor/commands/oxe-research.md +2 -2
  20. package/.cursor/commands/oxe-retro.md +2 -2
  21. package/.cursor/commands/oxe-review-pr.md +2 -2
  22. package/.cursor/commands/oxe-route.md +2 -2
  23. package/.cursor/commands/oxe-scan.md +2 -2
  24. package/.cursor/commands/oxe-security.md +2 -2
  25. package/.cursor/commands/oxe-session.md +2 -2
  26. package/.cursor/commands/oxe-ship.md +2 -2
  27. package/.cursor/commands/oxe-skill.md +2 -2
  28. package/.cursor/commands/oxe-spec.md +2 -2
  29. package/.cursor/commands/oxe-ui-review.md +2 -2
  30. package/.cursor/commands/oxe-ui-spec.md +2 -2
  31. package/.cursor/commands/oxe-update.md +2 -2
  32. package/.cursor/commands/oxe-validate-gaps.md +2 -2
  33. package/.cursor/commands/oxe-verify.md +5 -2
  34. package/.cursor/commands/oxe-workstream.md +2 -2
  35. package/.cursor/commands/oxe.md +2 -2
  36. package/.github/copilot-instructions.md +13 -13
  37. package/.github/prompts/oxe-ask.prompt.md +2 -2
  38. package/.github/prompts/oxe-capabilities.prompt.md +2 -2
  39. package/.github/prompts/oxe-checkpoint.prompt.md +2 -2
  40. package/.github/prompts/oxe-compact.prompt.md +2 -2
  41. package/.github/prompts/oxe-dashboard.prompt.md +2 -2
  42. package/.github/prompts/oxe-debug.prompt.md +2 -2
  43. package/.github/prompts/oxe-discuss.prompt.md +2 -2
  44. package/.github/prompts/oxe-execute.prompt.md +5 -2
  45. package/.github/prompts/oxe-forensics.prompt.md +2 -2
  46. package/.github/prompts/oxe-help.prompt.md +2 -2
  47. package/.github/prompts/oxe-loop.prompt.md +2 -2
  48. package/.github/prompts/oxe-milestone.prompt.md +2 -2
  49. package/.github/prompts/oxe-next.prompt.md +2 -2
  50. package/.github/prompts/oxe-obs.prompt.md +2 -2
  51. package/.github/prompts/oxe-plan-agent.prompt.md +2 -2
  52. package/.github/prompts/oxe-plan.prompt.md +2 -2
  53. package/.github/prompts/oxe-project.prompt.md +2 -2
  54. package/.github/prompts/oxe-quick.prompt.md +2 -2
  55. package/.github/prompts/oxe-research.prompt.md +2 -2
  56. package/.github/prompts/oxe-retro.prompt.md +2 -2
  57. package/.github/prompts/oxe-review-pr.prompt.md +2 -2
  58. package/.github/prompts/oxe-route.prompt.md +2 -2
  59. package/.github/prompts/oxe-scan.prompt.md +2 -2
  60. package/.github/prompts/oxe-security.prompt.md +2 -2
  61. package/.github/prompts/oxe-session.prompt.md +2 -2
  62. package/.github/prompts/oxe-ship.prompt.md +2 -2
  63. package/.github/prompts/oxe-skill.prompt.md +2 -2
  64. package/.github/prompts/oxe-spec.prompt.md +2 -2
  65. package/.github/prompts/oxe-ui-review.prompt.md +2 -2
  66. package/.github/prompts/oxe-ui-spec.prompt.md +2 -2
  67. package/.github/prompts/oxe-update.prompt.md +2 -2
  68. package/.github/prompts/oxe-validate-gaps.prompt.md +2 -2
  69. package/.github/prompts/oxe-verify.prompt.md +5 -2
  70. package/.github/prompts/oxe-workstream.prompt.md +2 -2
  71. package/.github/prompts/oxe.prompt.md +2 -2
  72. package/AGENTS.md +5 -3
  73. package/CHANGELOG.md +72 -10
  74. package/LICENSE +21 -674
  75. package/README.md +631 -535
  76. package/bin/banner.txt +6 -6
  77. package/bin/lib/oxe-agent-install.cjs +69 -69
  78. package/bin/lib/oxe-azure.cjs +1445 -1445
  79. package/bin/lib/oxe-context-engine.cjs +867 -867
  80. package/bin/lib/oxe-dashboard.cjs +76 -28
  81. package/bin/lib/oxe-operational.cjs +2144 -1340
  82. package/bin/lib/oxe-project-health.cjs +483 -1
  83. package/bin/lib/oxe-runtime-semantics.cjs +12 -0
  84. package/bin/oxe-cc.js +554 -152
  85. package/commands/oxe/ask.md +2 -2
  86. package/commands/oxe/capabilities.md +2 -2
  87. package/commands/oxe/checkpoint.md +2 -2
  88. package/commands/oxe/compact.md +2 -2
  89. package/commands/oxe/dashboard.md +2 -2
  90. package/commands/oxe/debug.md +2 -2
  91. package/commands/oxe/discuss.md +2 -2
  92. package/commands/oxe/execute.md +5 -2
  93. package/commands/oxe/forensics.md +2 -2
  94. package/commands/oxe/help.md +2 -2
  95. package/commands/oxe/loop.md +2 -2
  96. package/commands/oxe/milestone.md +2 -2
  97. package/commands/oxe/next.md +2 -2
  98. package/commands/oxe/obs.md +2 -2
  99. package/commands/oxe/oxe.md +2 -2
  100. package/commands/oxe/plan-agent.md +2 -2
  101. package/commands/oxe/plan.md +2 -2
  102. package/commands/oxe/project.md +2 -2
  103. package/commands/oxe/quick.md +2 -2
  104. package/commands/oxe/research.md +2 -2
  105. package/commands/oxe/retro.md +2 -2
  106. package/commands/oxe/review-pr.md +2 -2
  107. package/commands/oxe/route.md +2 -2
  108. package/commands/oxe/scan.md +2 -2
  109. package/commands/oxe/security.md +2 -2
  110. package/commands/oxe/session.md +2 -2
  111. package/commands/oxe/ship.md +2 -2
  112. package/commands/oxe/skill.md +2 -2
  113. package/commands/oxe/spec.md +2 -2
  114. package/commands/oxe/ui-review.md +2 -2
  115. package/commands/oxe/ui-spec.md +2 -2
  116. package/commands/oxe/update.md +2 -2
  117. package/commands/oxe/validate-gaps.md +2 -2
  118. package/commands/oxe/verify.md +5 -2
  119. package/commands/oxe/workstream.md +2 -2
  120. package/lib/runtime/delivery/branch-manager.d.ts +1 -0
  121. package/lib/runtime/delivery/branch-manager.js +7 -0
  122. package/lib/runtime/delivery/ci-checks.js +34 -1
  123. package/lib/runtime/delivery/delivery-records.d.ts +34 -0
  124. package/lib/runtime/delivery/delivery-records.js +48 -0
  125. package/lib/runtime/delivery/index.d.ts +1 -0
  126. package/lib/runtime/delivery/index.js +1 -0
  127. package/lib/runtime/delivery/promotion-pipeline.d.ts +26 -2
  128. package/lib/runtime/delivery/promotion-pipeline.js +111 -14
  129. package/lib/runtime/gate/gate-manager.d.ts +41 -0
  130. package/lib/runtime/gate/gate-manager.js +108 -1
  131. package/lib/runtime/index.d.ts +2 -2
  132. package/lib/runtime/index.js +3 -1
  133. package/lib/runtime/models/gate-decision.d.ts +4 -1
  134. package/lib/runtime/models/workspace.d.ts +3 -0
  135. package/lib/runtime/plugins/capability-adapter.d.ts +12 -0
  136. package/lib/runtime/plugins/capability-adapter.js +204 -0
  137. package/lib/runtime/plugins/capability-matrix.d.ts +5 -0
  138. package/lib/runtime/plugins/capability-matrix.js +48 -17
  139. package/lib/runtime/plugins/index.d.ts +1 -0
  140. package/lib/runtime/plugins/index.js +1 -0
  141. package/lib/runtime/plugins/plugin-abi.d.ts +2 -0
  142. package/lib/runtime/plugins/plugin-manifest.d.ts +1 -1
  143. package/lib/runtime/plugins/plugin-manifest.js +6 -2
  144. package/lib/runtime/plugins/plugin-registry.d.ts +46 -0
  145. package/lib/runtime/plugins/plugin-registry.js +79 -2
  146. package/lib/runtime/policy/policy-engine.d.ts +19 -0
  147. package/lib/runtime/policy/policy-engine.js +76 -4
  148. package/lib/runtime/projection/projection-engine.d.ts +9 -1
  149. package/lib/runtime/projection/projection-engine.js +73 -3
  150. package/lib/runtime/scheduler/multi-agent-coordinator.d.ts +43 -1
  151. package/lib/runtime/scheduler/multi-agent-coordinator.js +151 -39
  152. package/lib/runtime/scheduler/run-journal.d.ts +1 -1
  153. package/lib/runtime/scheduler/scheduler.d.ts +19 -1
  154. package/lib/runtime/scheduler/scheduler.js +258 -13
  155. package/lib/runtime/verification/verification-compiler.d.ts +43 -0
  156. package/lib/runtime/verification/verification-compiler.js +137 -0
  157. package/lib/runtime/verification/verification-manifest.d.ts +9 -0
  158. package/lib/runtime/verification/verification-manifest.js +56 -6
  159. package/lib/runtime/workspace/strategies/ephemeral-container.d.ts +1 -0
  160. package/lib/runtime/workspace/strategies/ephemeral-container.js +4 -0
  161. package/lib/runtime/workspace/strategies/git-worktree.d.ts +1 -0
  162. package/lib/runtime/workspace/strategies/git-worktree.js +2 -0
  163. package/lib/runtime/workspace/strategies/inplace.d.ts +1 -0
  164. package/lib/runtime/workspace/strategies/inplace.js +2 -0
  165. package/lib/runtime/workspace/workspace-manager.d.ts +2 -1
  166. package/lib/sdk/README.md +20 -8
  167. package/lib/sdk/index.cjs +33 -24
  168. package/lib/sdk/index.d.ts +149 -14
  169. package/oxe/templates/ACTIVE-RUN.template.json +32 -32
  170. package/oxe/templates/CAPABILITIES.template.md +7 -7
  171. package/oxe/templates/CAPABILITY.template.md +45 -45
  172. package/oxe/templates/CHECKPOINTS.template.md +7 -7
  173. package/oxe/templates/EXECUTION-RUNTIME.template.md +68 -68
  174. package/oxe/templates/HYPOTHESES.template.md +33 -33
  175. package/oxe/templates/LESSONS-METRICS.template.json +13 -13
  176. package/oxe/templates/NOTES.template.md +16 -16
  177. package/oxe/templates/PLAN-REVIEW.template.md +31 -31
  178. package/oxe/templates/SESSION.template.md +34 -34
  179. package/oxe/templates/SKILL.template.md +26 -26
  180. package/oxe/templates/STATE.md +55 -55
  181. package/oxe/templates/WORKFLOW_AUTHORING.md +18 -18
  182. package/oxe/workflows/ask.md +96 -96
  183. package/oxe/workflows/capabilities.md +25 -25
  184. package/oxe/workflows/dashboard.md +33 -33
  185. package/oxe/workflows/discuss.md +12 -12
  186. package/oxe/workflows/execute.md +14 -0
  187. package/oxe/workflows/help.md +352 -352
  188. package/oxe/workflows/next.md +22 -22
  189. package/oxe/workflows/oxe.md +6 -6
  190. package/oxe/workflows/plan-agent.md +9 -9
  191. package/oxe/workflows/plan.md +51 -20
  192. package/oxe/workflows/quick.md +10 -10
  193. package/oxe/workflows/references/reasoning-discovery.md +28 -28
  194. package/oxe/workflows/references/reasoning-execution.md +29 -29
  195. package/oxe/workflows/references/reasoning-planning.md +32 -32
  196. package/oxe/workflows/references/reasoning-review.md +29 -29
  197. package/oxe/workflows/references/reasoning-status.md +24 -24
  198. package/oxe/workflows/references/robustness-elevation.md +295 -295
  199. package/oxe/workflows/references/workflow-runtime-contracts.json +952 -930
  200. package/oxe/workflows/route.md +16 -16
  201. package/oxe/workflows/session.md +213 -213
  202. package/oxe/workflows/ship.md +142 -142
  203. package/oxe/workflows/skill.md +44 -44
  204. package/oxe/workflows/ui-review.md +36 -36
  205. package/oxe/workflows/verify-audit.md +73 -73
  206. package/oxe/workflows/verify.md +10 -0
  207. package/package.json +92 -92
  208. package/packages/runtime/package.json +16 -15
  209. package/packages/runtime/src/audit/audit-trail.ts +243 -243
  210. package/packages/runtime/src/audit/index.ts +2 -2
  211. package/packages/runtime/src/audit/policy-pack.ts +62 -62
  212. package/packages/runtime/src/compiler/graph-compiler.ts +245 -245
  213. package/packages/runtime/src/compiler/index.ts +1 -1
  214. package/packages/runtime/src/context/context-pack-builder.ts +259 -259
  215. package/packages/runtime/src/context/context-pack-store.ts +197 -197
  216. package/packages/runtime/src/context/context-profiles.ts +60 -60
  217. package/packages/runtime/src/context/index.ts +3 -3
  218. package/packages/runtime/src/decision/decision-engine.ts +174 -174
  219. package/packages/runtime/src/decision/decision-memo.ts +211 -211
  220. package/packages/runtime/src/decision/index.ts +2 -2
  221. package/packages/runtime/src/delivery/branch-manager.ts +91 -84
  222. package/packages/runtime/src/delivery/ci-checks.ts +285 -252
  223. package/packages/runtime/src/delivery/delivery-records.ts +75 -0
  224. package/packages/runtime/src/delivery/index.ts +5 -4
  225. package/packages/runtime/src/delivery/pr-manager.ts +112 -112
  226. package/packages/runtime/src/delivery/promotion-pipeline.ts +334 -180
  227. package/packages/runtime/src/events/bus.ts +92 -92
  228. package/packages/runtime/src/events/catalog.ts +29 -29
  229. package/packages/runtime/src/events/envelope.ts +14 -14
  230. package/packages/runtime/src/events/index.ts +3 -3
  231. package/packages/runtime/src/evidence/evidence-store.ts +130 -130
  232. package/packages/runtime/src/evidence/index.ts +1 -1
  233. package/packages/runtime/src/gate/gate-manager.ts +289 -137
  234. package/packages/runtime/src/gate/index.ts +1 -1
  235. package/packages/runtime/src/index.ts +41 -37
  236. package/packages/runtime/src/models/attempt.ts +19 -19
  237. package/packages/runtime/src/models/evidence.ts +21 -21
  238. package/packages/runtime/src/models/gate-decision.ts +25 -21
  239. package/packages/runtime/src/models/index.ts +8 -8
  240. package/packages/runtime/src/models/run.ts +24 -24
  241. package/packages/runtime/src/models/session.ts +11 -11
  242. package/packages/runtime/src/models/verification-result.ts +10 -10
  243. package/packages/runtime/src/models/work-item.ts +25 -25
  244. package/packages/runtime/src/models/workspace.ts +31 -28
  245. package/packages/runtime/src/plugins/capability-adapter.ts +206 -0
  246. package/packages/runtime/src/plugins/capability-matrix.ts +126 -83
  247. package/packages/runtime/src/plugins/index.ts +5 -4
  248. package/packages/runtime/src/plugins/plugin-abi.ts +97 -95
  249. package/packages/runtime/src/plugins/plugin-manifest.ts +118 -113
  250. package/packages/runtime/src/plugins/plugin-registry.ts +232 -124
  251. package/packages/runtime/src/policy/index.ts +1 -1
  252. package/packages/runtime/src/policy/policy-engine.ts +330 -244
  253. package/packages/runtime/src/projection/index.ts +1 -1
  254. package/packages/runtime/src/projection/projection-engine.ts +328 -249
  255. package/packages/runtime/src/reducers/debug-reducer.ts +36 -36
  256. package/packages/runtime/src/reducers/index.ts +2 -2
  257. package/packages/runtime/src/reducers/run-state-reducer.ts +269 -269
  258. package/packages/runtime/src/scheduler/agent-registry.ts +132 -132
  259. package/packages/runtime/src/scheduler/agent-roles.ts +109 -109
  260. package/packages/runtime/src/scheduler/index.ts +4 -4
  261. package/packages/runtime/src/scheduler/multi-agent-coordinator.ts +521 -333
  262. package/packages/runtime/src/scheduler/run-journal.ts +62 -62
  263. package/packages/runtime/src/scheduler/scheduler.ts +722 -441
  264. package/packages/runtime/src/verification/index.ts +2 -2
  265. package/packages/runtime/src/verification/verification-compiler.ts +436 -225
  266. package/packages/runtime/src/verification/verification-manifest.ts +252 -192
  267. package/packages/runtime/src/workspace/index.ts +5 -5
  268. package/packages/runtime/src/workspace/strategies/ephemeral-container.ts +126 -121
  269. package/packages/runtime/src/workspace/strategies/git-worktree.ts +79 -77
  270. package/packages/runtime/src/workspace/strategies/inplace.ts +38 -35
  271. package/packages/runtime/src/workspace/workspace-manager.ts +16 -15
  272. package/packages/runtime/tsconfig.json +17 -17
  273. package/vscode-extension/.vscodeignore +7 -7
  274. package/vscode-extension/LICENSE +21 -0
  275. package/vscode-extension/oxe-agents-1.0.0.vsix +0 -0
  276. package/vscode-extension/oxe-agents-1.4.0.vsix +0 -0
  277. package/vscode-extension/package.json +184 -184
  278. package/vscode-extension/src/extension.js +310 -310
  279. package/vscode-extension/src/shared/contextLoader.js +137 -137
  280. package/vscode-extension/src/shared/contractBuilder.js +159 -159
  281. package/vscode-extension/src/shared/stateReader.js +101 -101
@@ -1,244 +1,330 @@
1
- export type PolicyAction = 'allow' | 'deny' | 'require_human_gate';
2
-
3
- export type SideEffectClass =
4
- | 'read_fs'
5
- | 'write_fs'
6
- | 'spawn_process'
7
- | 'network_call'
8
- | 'git_mutation'
9
- | 'db_change'
10
- | 'secret_access'
11
- | 'infra_operation';
12
-
13
- export type AutonomyTier = 'L0' | 'L1' | 'L2' | 'L3';
14
-
15
- export interface PolicyWhenClause {
16
- tool?: string;
17
- env?: string;
18
- kind?: string;
19
- side_effect_class?: SideEffectClass;
20
- autonomy_tier?: AutonomyTier;
21
- }
22
-
23
- export interface PolicyAssertClause {
24
- diff_within_scope?: boolean;
25
- }
26
-
27
- export interface NodePolicyConfig {
28
- max_retries: number;
29
- mutation_budget?: number;
30
- autonomy_tier?: AutonomyTier;
31
- allowed_side_effects?: SideEffectClass[];
32
- }
33
-
34
- export interface EnvironmentGuardrail {
35
- protected_paths: string[];
36
- protected_branches: string[];
37
- require_human_gate_on: SideEffectClass[];
38
- }
39
-
40
- export interface PolicyRule {
41
- id: string;
42
- when: PolicyWhenClause;
43
- assert?: PolicyAssertClause;
44
- action: PolicyAction;
45
- }
46
-
47
- export interface PolicyContext {
48
- tool: string;
49
- env?: string;
50
- kind?: string;
51
- mutation_scope?: string[];
52
- affected_paths?: string[];
53
- side_effect_class?: SideEffectClass;
54
- autonomy_tier?: AutonomyTier;
55
- mutation_count?: number;
56
- node_policy?: NodePolicyConfig;
57
- }
58
-
59
- export interface PolicyDecision {
60
- allowed: boolean;
61
- gate_required: boolean;
62
- reason: string;
63
- rule_id: string | null;
64
- }
65
-
66
- const ALLOW_ALL: PolicyDecision = {
67
- allowed: true,
68
- gate_required: false,
69
- reason: 'no matching policy — default allow',
70
- rule_id: null,
71
- };
72
-
73
- const DEFAULT_GUARDRAIL: EnvironmentGuardrail = {
74
- protected_paths: ['.oxe/config.json', '.env', 'package.json'],
75
- protected_branches: ['main', 'master', 'production', 'release'],
76
- require_human_gate_on: ['infra_operation', 'db_change', 'secret_access'],
77
- };
78
-
79
- // Autonomy tier → max side effect class allowed without a gate
80
- const TIER_SIDE_EFFECT_MAP: Record<AutonomyTier, SideEffectClass[]> = {
81
- L0: ['read_fs'],
82
- L1: ['read_fs', 'write_fs', 'spawn_process'],
83
- L2: ['read_fs', 'write_fs', 'spawn_process', 'network_call', 'git_mutation'],
84
- L3: ['read_fs', 'write_fs', 'spawn_process', 'network_call', 'git_mutation', 'db_change', 'secret_access', 'infra_operation'],
85
- };
86
-
87
- export class PolicyEngine {
88
- constructor(
89
- private readonly rules: PolicyRule[] = [],
90
- private readonly guardrail: EnvironmentGuardrail = DEFAULT_GUARDRAIL
91
- ) {}
92
-
93
- evaluate(ctx: PolicyContext): PolicyDecision {
94
- // Check autonomy tier first — a denial takes priority over guardrail gates
95
- const tierDecision = this.checkAutonomyTier(ctx);
96
- if (tierDecision) return tierDecision;
97
-
98
- // Check environment guardrails (may require gate even when tier permits)
99
- const guardrailDecision = this.checkGuardrails(ctx);
100
- if (guardrailDecision) return guardrailDecision;
101
-
102
- // Check mutation budget
103
- const budgetDecision = this.checkMutationBudget(ctx);
104
- if (budgetDecision) return budgetDecision;
105
-
106
- // Evaluate rules (first match wins)
107
- for (const rule of this.rules) {
108
- if (!this.matches(rule.when, ctx)) continue;
109
-
110
- if (rule.assert) {
111
- const assertFailed = this.checkAssert(rule.assert, ctx);
112
- if (assertFailed) {
113
- return {
114
- allowed: false,
115
- gate_required: false,
116
- reason: `Assert failed for rule ${rule.id}: ${assertFailed}`,
117
- rule_id: rule.id,
118
- };
119
- }
120
- }
121
-
122
- switch (rule.action) {
123
- case 'allow':
124
- return { allowed: true, gate_required: false, reason: `Allowed by rule ${rule.id}`, rule_id: rule.id };
125
- case 'deny':
126
- return { allowed: false, gate_required: false, reason: `Denied by rule ${rule.id}`, rule_id: rule.id };
127
- case 'require_human_gate':
128
- return { allowed: true, gate_required: true, reason: `Gate required by rule ${rule.id}`, rule_id: rule.id };
129
- }
130
- }
131
-
132
- return ALLOW_ALL;
133
- }
134
-
135
- private checkGuardrails(ctx: PolicyContext): PolicyDecision | null {
136
- // Protected path check
137
- const affected = ctx.affected_paths ?? [];
138
- for (const p of affected) {
139
- if (this.guardrail.protected_paths.some((pp) => p === pp || p.startsWith(pp + '/'))) {
140
- return {
141
- allowed: true,
142
- gate_required: true,
143
- reason: `Protected path affected: ${p}`,
144
- rule_id: '__guardrail_path',
145
- };
146
- }
147
- }
148
-
149
- // Side effect class requiring gate
150
- if (ctx.side_effect_class && this.guardrail.require_human_gate_on.includes(ctx.side_effect_class)) {
151
- return {
152
- allowed: true,
153
- gate_required: true,
154
- reason: `Side effect class '${ctx.side_effect_class}' requires human gate`,
155
- rule_id: '__guardrail_side_effect',
156
- };
157
- }
158
-
159
- return null;
160
- }
161
-
162
- private checkAutonomyTier(ctx: PolicyContext): PolicyDecision | null {
163
- if (!ctx.autonomy_tier || !ctx.side_effect_class) return null;
164
- const allowed = TIER_SIDE_EFFECT_MAP[ctx.autonomy_tier] ?? [];
165
- if (!allowed.includes(ctx.side_effect_class)) {
166
- return {
167
- allowed: false,
168
- gate_required: false,
169
- reason: `Autonomy tier ${ctx.autonomy_tier} does not permit side effect '${ctx.side_effect_class}'`,
170
- rule_id: '__autonomy_tier',
171
- };
172
- }
173
- return null;
174
- }
175
-
176
- private checkMutationBudget(ctx: PolicyContext): PolicyDecision | null {
177
- const budget = ctx.node_policy?.mutation_budget;
178
- if (budget === undefined || budget === null) return null;
179
- const count = ctx.mutation_count ?? 0;
180
- if (count >= budget) {
181
- return {
182
- allowed: false,
183
- gate_required: false,
184
- reason: `Mutation budget exhausted: ${count}/${budget}`,
185
- rule_id: '__mutation_budget',
186
- };
187
- }
188
- return null;
189
- }
190
-
191
- private matches(when: PolicyWhenClause, ctx: PolicyContext): boolean {
192
- if (when.tool && when.tool !== ctx.tool) return false;
193
- if (when.env && when.env !== ctx.env) return false;
194
- if (when.kind && when.kind !== ctx.kind) return false;
195
- if (when.side_effect_class && when.side_effect_class !== ctx.side_effect_class) return false;
196
- if (when.autonomy_tier && when.autonomy_tier !== ctx.autonomy_tier) return false;
197
- return true;
198
- }
199
-
200
- private checkAssert(assert: PolicyAssertClause, ctx: PolicyContext): string | null {
201
- if (assert.diff_within_scope === true) {
202
- const scope = ctx.mutation_scope ?? [];
203
- const affected = ctx.affected_paths ?? [];
204
- if (scope.length === 0) return null;
205
- const outsideScope = affected.filter(
206
- (p) => !scope.some((s) => p.startsWith(s) || s.startsWith(p))
207
- );
208
- if (outsideScope.length > 0) {
209
- return `paths outside mutation scope: ${outsideScope.join(', ')}`;
210
- }
211
- }
212
- return null;
213
- }
214
-
215
- withRule(rule: PolicyRule): PolicyEngine {
216
- return new PolicyEngine([...this.rules, rule], this.guardrail);
217
- }
218
-
219
- withGuardrail(guardrail: EnvironmentGuardrail): PolicyEngine {
220
- return new PolicyEngine(this.rules, guardrail);
221
- }
222
-
223
- getGuardrail(): EnvironmentGuardrail {
224
- return this.guardrail;
225
- }
226
-
227
- static fromConfig(config: { policies?: PolicyRule[]; guardrail?: EnvironmentGuardrail }): PolicyEngine {
228
- return new PolicyEngine(config.policies ?? [], config.guardrail ?? DEFAULT_GUARDRAIL);
229
- }
230
-
231
- static fromConfigFile(configPath: string): PolicyEngine {
232
- try {
233
- // eslint-disable-next-line @typescript-eslint/no-var-requires
234
- const cfg = require(configPath) as { policies?: PolicyRule[]; guardrail?: EnvironmentGuardrail };
235
- return PolicyEngine.fromConfig(cfg);
236
- } catch {
237
- return new PolicyEngine();
238
- }
239
- }
240
-
241
- static defaultGuardrail(): EnvironmentGuardrail {
242
- return { ...DEFAULT_GUARDRAIL };
243
- }
244
- }
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+
4
+ export type PolicyAction = 'allow' | 'deny' | 'require_human_gate';
5
+
6
+ export type SideEffectClass =
7
+ | 'read_fs'
8
+ | 'write_fs'
9
+ | 'spawn_process'
10
+ | 'network_call'
11
+ | 'git_mutation'
12
+ | 'db_change'
13
+ | 'secret_access'
14
+ | 'infra_operation';
15
+
16
+ export type AutonomyTier = 'L0' | 'L1' | 'L2' | 'L3';
17
+
18
+ export interface PolicyWhenClause {
19
+ tool?: string;
20
+ env?: string;
21
+ kind?: string;
22
+ side_effect_class?: SideEffectClass;
23
+ autonomy_tier?: AutonomyTier;
24
+ }
25
+
26
+ export interface PolicyAssertClause {
27
+ diff_within_scope?: boolean;
28
+ }
29
+
30
+ export interface NodePolicyConfig {
31
+ max_retries: number;
32
+ mutation_budget?: number;
33
+ autonomy_tier?: AutonomyTier;
34
+ allowed_side_effects?: SideEffectClass[];
35
+ }
36
+
37
+ export interface EnvironmentGuardrail {
38
+ protected_paths: string[];
39
+ protected_branches: string[];
40
+ require_human_gate_on: SideEffectClass[];
41
+ }
42
+
43
+ export interface PolicyRule {
44
+ id: string;
45
+ when: PolicyWhenClause;
46
+ assert?: PolicyAssertClause;
47
+ action: PolicyAction;
48
+ }
49
+
50
+ export interface PolicyContext {
51
+ tool: string;
52
+ env?: string;
53
+ kind?: string;
54
+ mutation_scope?: string[];
55
+ affected_paths?: string[];
56
+ side_effect_class?: SideEffectClass;
57
+ autonomy_tier?: AutonomyTier;
58
+ mutation_count?: number;
59
+ node_policy?: NodePolicyConfig;
60
+ }
61
+
62
+ export interface PolicyDecision {
63
+ decision_id: string;
64
+ allowed: boolean;
65
+ gate_required: boolean;
66
+ reason: string;
67
+ rule_id: string | null;
68
+ timestamp: string;
69
+ }
70
+
71
+ const ALLOW_ALL: PolicyDecision = {
72
+ decision_id: '__default_allow',
73
+ allowed: true,
74
+ gate_required: false,
75
+ reason: 'no matching policy — default allow',
76
+ rule_id: null,
77
+ timestamp: new Date().toISOString(),
78
+ };
79
+
80
+ export interface PersistedPolicyDecision extends PolicyDecision {
81
+ run_id: string;
82
+ work_item_id: string | null;
83
+ action: string;
84
+ actor: string;
85
+ override: boolean;
86
+ rationale: string | null;
87
+ context: PolicyContext;
88
+ }
89
+
90
+ const DEFAULT_GUARDRAIL: EnvironmentGuardrail = {
91
+ protected_paths: ['.oxe/config.json', '.env', 'package.json'],
92
+ protected_branches: ['main', 'master', 'production', 'release'],
93
+ require_human_gate_on: ['infra_operation', 'db_change', 'secret_access'],
94
+ };
95
+
96
+ // Autonomy tier → max side effect class allowed without a gate
97
+ const TIER_SIDE_EFFECT_MAP: Record<AutonomyTier, SideEffectClass[]> = {
98
+ L0: ['read_fs'],
99
+ L1: ['read_fs', 'write_fs', 'spawn_process'],
100
+ L2: ['read_fs', 'write_fs', 'spawn_process', 'network_call', 'git_mutation'],
101
+ L3: ['read_fs', 'write_fs', 'spawn_process', 'network_call', 'git_mutation', 'db_change', 'secret_access', 'infra_operation'],
102
+ };
103
+
104
+ export class PolicyEngine {
105
+ constructor(
106
+ private readonly rules: PolicyRule[] = [],
107
+ private readonly guardrail: EnvironmentGuardrail = DEFAULT_GUARDRAIL
108
+ ) {}
109
+
110
+ evaluate(ctx: PolicyContext): PolicyDecision {
111
+ // Check autonomy tier first — a denial takes priority over guardrail gates
112
+ const tierDecision = this.checkAutonomyTier(ctx);
113
+ if (tierDecision) return tierDecision;
114
+
115
+ // Check environment guardrails (may require gate even when tier permits)
116
+ const guardrailDecision = this.checkGuardrails(ctx);
117
+ if (guardrailDecision) return guardrailDecision;
118
+
119
+ // Check mutation budget
120
+ const budgetDecision = this.checkMutationBudget(ctx);
121
+ if (budgetDecision) return budgetDecision;
122
+
123
+ // Evaluate rules (first match wins)
124
+ for (const rule of this.rules) {
125
+ if (!this.matches(rule.when, ctx)) continue;
126
+
127
+ if (rule.assert) {
128
+ const assertFailed = this.checkAssert(rule.assert, ctx);
129
+ if (assertFailed) {
130
+ return {
131
+ decision_id: rule.id,
132
+ allowed: false,
133
+ gate_required: false,
134
+ reason: `Assert failed for rule ${rule.id}: ${assertFailed}`,
135
+ rule_id: rule.id,
136
+ timestamp: new Date().toISOString(),
137
+ };
138
+ }
139
+ }
140
+
141
+ switch (rule.action) {
142
+ case 'allow':
143
+ return {
144
+ decision_id: rule.id,
145
+ allowed: true,
146
+ gate_required: false,
147
+ reason: `Allowed by rule ${rule.id}`,
148
+ rule_id: rule.id,
149
+ timestamp: new Date().toISOString(),
150
+ };
151
+ case 'deny':
152
+ return {
153
+ decision_id: rule.id,
154
+ allowed: false,
155
+ gate_required: false,
156
+ reason: `Denied by rule ${rule.id}`,
157
+ rule_id: rule.id,
158
+ timestamp: new Date().toISOString(),
159
+ };
160
+ case 'require_human_gate':
161
+ return {
162
+ decision_id: rule.id,
163
+ allowed: true,
164
+ gate_required: true,
165
+ reason: `Gate required by rule ${rule.id}`,
166
+ rule_id: rule.id,
167
+ timestamp: new Date().toISOString(),
168
+ };
169
+ }
170
+ }
171
+
172
+ return { ...ALLOW_ALL, timestamp: new Date().toISOString() };
173
+ }
174
+
175
+ private checkGuardrails(ctx: PolicyContext): PolicyDecision | null {
176
+ // Protected path check
177
+ const affected = ctx.affected_paths ?? [];
178
+ for (const p of affected) {
179
+ if (this.guardrail.protected_paths.some((pp) => p === pp || p.startsWith(pp + '/'))) {
180
+ return {
181
+ decision_id: '__guardrail_path',
182
+ allowed: true,
183
+ gate_required: true,
184
+ reason: `Protected path affected: ${p}`,
185
+ rule_id: '__guardrail_path',
186
+ timestamp: new Date().toISOString(),
187
+ };
188
+ }
189
+ }
190
+
191
+ // Side effect class requiring gate
192
+ if (ctx.side_effect_class && this.guardrail.require_human_gate_on.includes(ctx.side_effect_class)) {
193
+ return {
194
+ decision_id: '__guardrail_side_effect',
195
+ allowed: true,
196
+ gate_required: true,
197
+ reason: `Side effect class '${ctx.side_effect_class}' requires human gate`,
198
+ rule_id: '__guardrail_side_effect',
199
+ timestamp: new Date().toISOString(),
200
+ };
201
+ }
202
+
203
+ return null;
204
+ }
205
+
206
+ private checkAutonomyTier(ctx: PolicyContext): PolicyDecision | null {
207
+ if (!ctx.autonomy_tier || !ctx.side_effect_class) return null;
208
+ const allowed = TIER_SIDE_EFFECT_MAP[ctx.autonomy_tier] ?? [];
209
+ if (!allowed.includes(ctx.side_effect_class)) {
210
+ return {
211
+ decision_id: '__autonomy_tier',
212
+ allowed: false,
213
+ gate_required: false,
214
+ reason: `Autonomy tier ${ctx.autonomy_tier} does not permit side effect '${ctx.side_effect_class}'`,
215
+ rule_id: '__autonomy_tier',
216
+ timestamp: new Date().toISOString(),
217
+ };
218
+ }
219
+ return null;
220
+ }
221
+
222
+ private checkMutationBudget(ctx: PolicyContext): PolicyDecision | null {
223
+ const budget = ctx.node_policy?.mutation_budget;
224
+ if (budget === undefined || budget === null) return null;
225
+ const count = ctx.mutation_count ?? 0;
226
+ if (count >= budget) {
227
+ return {
228
+ decision_id: '__mutation_budget',
229
+ allowed: false,
230
+ gate_required: false,
231
+ reason: `Mutation budget exhausted: ${count}/${budget}`,
232
+ rule_id: '__mutation_budget',
233
+ timestamp: new Date().toISOString(),
234
+ };
235
+ }
236
+ return null;
237
+ }
238
+
239
+ private matches(when: PolicyWhenClause, ctx: PolicyContext): boolean {
240
+ if (when.tool && when.tool !== ctx.tool) return false;
241
+ if (when.env && when.env !== ctx.env) return false;
242
+ if (when.kind && when.kind !== ctx.kind) return false;
243
+ if (when.side_effect_class && when.side_effect_class !== ctx.side_effect_class) return false;
244
+ if (when.autonomy_tier && when.autonomy_tier !== ctx.autonomy_tier) return false;
245
+ return true;
246
+ }
247
+
248
+ private checkAssert(assert: PolicyAssertClause, ctx: PolicyContext): string | null {
249
+ if (assert.diff_within_scope === true) {
250
+ const scope = ctx.mutation_scope ?? [];
251
+ const affected = ctx.affected_paths ?? [];
252
+ if (scope.length === 0) return null;
253
+ const outsideScope = affected.filter(
254
+ (p) => !scope.some((s) => p.startsWith(s) || s.startsWith(p))
255
+ );
256
+ if (outsideScope.length > 0) {
257
+ return `paths outside mutation scope: ${outsideScope.join(', ')}`;
258
+ }
259
+ }
260
+ return null;
261
+ }
262
+
263
+ withRule(rule: PolicyRule): PolicyEngine {
264
+ return new PolicyEngine([...this.rules, rule], this.guardrail);
265
+ }
266
+
267
+ withGuardrail(guardrail: EnvironmentGuardrail): PolicyEngine {
268
+ return new PolicyEngine(this.rules, guardrail);
269
+ }
270
+
271
+ getGuardrail(): EnvironmentGuardrail {
272
+ return this.guardrail;
273
+ }
274
+
275
+ static fromConfig(config: { policies?: PolicyRule[]; guardrail?: EnvironmentGuardrail }): PolicyEngine {
276
+ return new PolicyEngine(config.policies ?? [], config.guardrail ?? DEFAULT_GUARDRAIL);
277
+ }
278
+
279
+ static fromConfigFile(configPath: string): PolicyEngine {
280
+ try {
281
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
282
+ const cfg = require(configPath) as { policies?: PolicyRule[]; guardrail?: EnvironmentGuardrail };
283
+ return PolicyEngine.fromConfig(cfg);
284
+ } catch {
285
+ return new PolicyEngine();
286
+ }
287
+ }
288
+
289
+ static defaultGuardrail(): EnvironmentGuardrail {
290
+ return { ...DEFAULT_GUARDRAIL };
291
+ }
292
+ }
293
+
294
+ function policyDecisionPath(projectRoot: string, runId: string): string {
295
+ return path.join(projectRoot, '.oxe', 'runs', runId, 'policy-decisions.json');
296
+ }
297
+
298
+ export function savePolicyDecision(projectRoot: string, decision: PersistedPolicyDecision): PersistedPolicyDecision {
299
+ const target = policyDecisionPath(projectRoot, decision.run_id);
300
+ fs.mkdirSync(path.dirname(target), { recursive: true });
301
+ const existing = loadPolicyDecisions(projectRoot, decision.run_id);
302
+ const next = [...existing.filter((item) => item.decision_id !== decision.decision_id), decision];
303
+ fs.writeFileSync(target, JSON.stringify(next, null, 2), 'utf8');
304
+ return decision;
305
+ }
306
+
307
+ export function loadPolicyDecisions(projectRoot: string, runId: string): PersistedPolicyDecision[] {
308
+ const target = policyDecisionPath(projectRoot, runId);
309
+ if (!fs.existsSync(target)) return [];
310
+ try {
311
+ const raw = JSON.parse(fs.readFileSync(target, 'utf8'));
312
+ return Array.isArray(raw) ? raw as PersistedPolicyDecision[] : [];
313
+ } catch {
314
+ return [];
315
+ }
316
+ }
317
+
318
+ export function summarizePolicyDecisions(decisions: PersistedPolicyDecision[]): {
319
+ total: number;
320
+ denied: number;
321
+ gated: number;
322
+ overridesWithoutRationale: number;
323
+ } {
324
+ return {
325
+ total: decisions.length,
326
+ denied: decisions.filter((decision) => !decision.allowed).length,
327
+ gated: decisions.filter((decision) => decision.gate_required).length,
328
+ overridesWithoutRationale: decisions.filter((decision) => decision.override && !decision.rationale).length,
329
+ };
330
+ }
@@ -1 +1 @@
1
- export * from './projection-engine';
1
+ export * from './projection-engine';