@neuroverseos/governance 0.4.0 → 0.4.2
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.
- package/dist/adapters/autoresearch.d.cts +2 -1
- package/dist/adapters/autoresearch.d.ts +2 -1
- package/dist/adapters/autoresearch.js +2 -2
- package/dist/adapters/deep-agents.d.cts +3 -2
- package/dist/adapters/deep-agents.d.ts +3 -2
- package/dist/adapters/deep-agents.js +2 -2
- package/dist/adapters/express.d.cts +2 -1
- package/dist/adapters/express.d.ts +2 -1
- package/dist/adapters/express.js +2 -2
- package/dist/adapters/github.cjs +1697 -0
- package/dist/adapters/github.d.cts +225 -0
- package/dist/adapters/github.d.ts +225 -0
- package/dist/adapters/github.js +27 -0
- package/dist/adapters/index.cjs +66 -1
- package/dist/adapters/index.d.cts +4 -278
- package/dist/adapters/index.d.ts +4 -278
- package/dist/adapters/index.js +18 -18
- package/dist/adapters/langchain.d.cts +3 -2
- package/dist/adapters/langchain.d.ts +3 -2
- package/dist/adapters/langchain.js +2 -2
- package/dist/adapters/mentraos.cjs +2181 -0
- package/dist/adapters/mentraos.d.cts +319 -0
- package/dist/adapters/mentraos.d.ts +319 -0
- package/dist/{mentraos-YFS7FMJH.js → adapters/mentraos.js} +6 -6
- package/dist/adapters/openai.d.cts +3 -2
- package/dist/adapters/openai.d.ts +3 -2
- package/dist/adapters/openai.js +2 -2
- package/dist/adapters/openclaw.d.cts +3 -2
- package/dist/adapters/openclaw.d.ts +3 -2
- package/dist/adapters/openclaw.js +2 -2
- package/dist/{add-LYHDZ5RL.js → add-XSANI3FK.js} +1 -1
- package/dist/bootstrap-contract-DcV6t-8M.d.cts +216 -0
- package/dist/bootstrap-contract-DcV6t-8M.d.ts +216 -0
- package/dist/{build-THUEYMVT.js → build-EGBGZFIJ.js} +5 -5
- package/dist/{chunk-MFKHTE5R.js → chunk-3AYKQHYI.js} +1 -1
- package/dist/chunk-3S5AD4AB.js +421 -0
- package/dist/{chunk-V4FZHJQX.js → chunk-A7SHG75T.js} +1 -1
- package/dist/{chunk-JKGPSFGH.js → chunk-AV7XJJWK.js} +1 -1
- package/dist/{chunk-Y6WXAPKY.js → chunk-DA5MHFRR.js} +1 -1
- package/dist/{chunk-7D7PZLB7.js → chunk-FS2UUJJO.js} +3 -3
- package/dist/{chunk-TD5GKIHP.js → chunk-FVOGUCB6.js} +1 -1
- package/dist/{chunk-APU4OZIP.js → chunk-GTPV2XGO.js} +67 -2
- package/dist/{chunk-BXLTEUS4.js → chunk-I4RTIMLX.js} +2 -2
- package/dist/{chunk-5JUZ4HL7.js → chunk-J2IZBHXJ.js} +3 -3
- package/dist/{chunk-YNYCQECH.js → chunk-QMVQ6KPL.js} +1 -1
- package/dist/{chunk-25XHSTPT.js → chunk-RDA7ISWC.js} +1 -1
- package/dist/{chunk-DWHUZUEY.js → chunk-YJ34R5NB.js} +1 -1
- package/dist/{chunk-UTH7OXTM.js → chunk-ZEIT2QLM.js} +3 -3
- package/dist/cli/neuroverse.cjs +580 -28
- package/dist/cli/neuroverse.js +21 -21
- package/dist/cli/plan.js +2 -2
- package/dist/cli/run.js +2 -2
- package/dist/{demo-66MMJTEH.js → demo-6OQYWRR6.js} +3 -3
- package/dist/{derive-5LOMN7GO.js → derive-7Y7YWVLU.js} +4 -4
- package/dist/{doctor-WIO4FLA3.js → doctor-EC5OYTI3.js} +3 -2
- package/dist/engine/bootstrap-emitter.cjs +241 -0
- package/dist/engine/bootstrap-emitter.d.cts +27 -0
- package/dist/engine/bootstrap-emitter.d.ts +27 -0
- package/dist/{bootstrap-emitter-GIMOJFOC.js → engine/bootstrap-emitter.js} +2 -2
- package/dist/engine/bootstrap-parser.cjs +560 -0
- package/dist/engine/bootstrap-parser.d.cts +96 -0
- package/dist/engine/bootstrap-parser.d.ts +96 -0
- package/dist/{bootstrap-parser-LBLGVEMU.js → engine/bootstrap-parser.js} +2 -2
- package/dist/engine/guard-engine.cjs +1116 -0
- package/dist/engine/guard-engine.d.cts +60 -0
- package/dist/engine/guard-engine.d.ts +60 -0
- package/dist/{guard-engine-N7TUIUU7.js → engine/guard-engine.js} +3 -3
- package/dist/engine/simulate-engine.cjs +390 -0
- package/dist/engine/simulate-engine.d.cts +105 -0
- package/dist/engine/simulate-engine.d.ts +105 -0
- package/dist/engine/simulate-engine.js +9 -0
- package/dist/{equity-penalties-WWC7UDQD.js → equity-penalties-NVBAB5WL.js} +2 -2
- package/dist/{explain-MUSGDT67.js → explain-HDFN4ION.js} +1 -1
- package/dist/{guard-W3BMQPBJ.js → guard-6KSCWT2W.js} +2 -2
- package/dist/{guard-contract-CLBbTGK_.d.ts → guard-contract-ddiIPlOg.d.cts} +2 -369
- package/dist/{guard-contract-CLBbTGK_.d.cts → guard-contract-q6HJAq3Q.d.ts} +2 -369
- package/dist/{improve-PJDAWW4Q.js → improve-2PWGGO5B.js} +3 -3
- package/dist/index.cjs +452 -0
- package/dist/index.d.cts +12 -492
- package/dist/index.d.ts +12 -492
- package/dist/index.js +75 -54
- package/dist/{lens-IP6GIZ2Q.js → lens-MHMUDCMQ.js} +92 -25
- package/dist/{mcp-server-OG3PPVD2.js → mcp-server-TNIWZ7B5.js} +2 -2
- package/dist/{playground-4BK2XQ47.js → playground-3FLDGBET.js} +2 -2
- package/dist/{redteam-BRZALBPP.js → redteam-HV6LMKEH.js} +2 -2
- package/dist/{session-SGRUT2UH.js → session-XZP2754M.js} +2 -2
- package/dist/{shared-CwGpPheR.d.ts → shared-DAzdfWtU.d.ts} +1 -1
- package/dist/{shared-BGzmYP5g.d.cts → shared-PpalGKxc.d.cts} +1 -1
- package/dist/{simulate-FGXKIH7V.js → simulate-VT437EEL.js} +2 -2
- package/dist/spatial/index.cjs +682 -0
- package/dist/spatial/index.d.cts +517 -0
- package/dist/spatial/index.d.ts +517 -0
- package/dist/spatial/index.js +633 -0
- package/dist/{test-PT44BSYG.js → test-4WTX6RKQ.js} +2 -2
- package/dist/types.cjs +18 -0
- package/dist/types.d.cts +370 -0
- package/dist/types.d.ts +370 -0
- package/dist/types.js +0 -0
- package/dist/{validate-Q5O5TGLT.js → validate-M52DX22Y.js} +1 -1
- package/dist/{world-V52ZMH26.js → world-O4HTQPDP.js} +1 -1
- package/dist/{world-loader-C4D3VPP3.js → world-loader-YTYFOP7D.js} +1 -1
- package/dist/worlds/mentraos-spatial.nv-world.md +68 -0
- package/package.json +52 -3
- package/dist/{behavioral-SPWPGYXL.js → behavioral-SLW7ALEK.js} +3 -3
- package/dist/{bootstrap-IP5QMC3Q.js → bootstrap-2OW5ZLBL.js} +3 -3
- package/dist/{chunk-7QIAF377.js → chunk-CYDMUJVZ.js} +0 -0
- package/dist/{chunk-QZ666FCV.js → chunk-FHXXD2TI.js} +6 -6
- package/dist/{configure-ai-5MP5DWTT.js → configure-ai-LL3VAPQW.js} +3 -3
|
@@ -0,0 +1,1697 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/adapters/github.ts
|
|
31
|
+
var github_exports = {};
|
|
32
|
+
__export(github_exports, {
|
|
33
|
+
GitHubGovernanceBlockedError: () => GitHubGovernanceBlockedError,
|
|
34
|
+
GitHubGovernor: () => GitHubGovernor,
|
|
35
|
+
GitHubWebhookHandler: () => GitHubWebhookHandler,
|
|
36
|
+
createGitHubGovernor: () => createGitHubGovernor,
|
|
37
|
+
createGitHubGovernorFromWorld: () => createGitHubGovernorFromWorld,
|
|
38
|
+
createGitHubWebhookHandler: () => createGitHubWebhookHandler,
|
|
39
|
+
createGitHubWebhookHandlerFromWorld: () => createGitHubWebhookHandlerFromWorld,
|
|
40
|
+
formatForActions: () => formatForActions,
|
|
41
|
+
formatPRComment: () => formatPRComment
|
|
42
|
+
});
|
|
43
|
+
module.exports = __toCommonJS(github_exports);
|
|
44
|
+
|
|
45
|
+
// src/engine/text-utils.ts
|
|
46
|
+
function normalizeEventText(event) {
|
|
47
|
+
return [
|
|
48
|
+
event.intent,
|
|
49
|
+
event.tool ?? "",
|
|
50
|
+
event.scope ?? ""
|
|
51
|
+
].join(" ").toLowerCase();
|
|
52
|
+
}
|
|
53
|
+
function extractKeywords(text, minLength = 3) {
|
|
54
|
+
return text.toLowerCase().split(/\s+/).filter((w) => w.length > minLength);
|
|
55
|
+
}
|
|
56
|
+
function matchesAllKeywords(eventText, ruleText) {
|
|
57
|
+
const keywords = extractKeywords(ruleText);
|
|
58
|
+
if (keywords.length === 0) return false;
|
|
59
|
+
return keywords.every((kw) => eventText.includes(kw));
|
|
60
|
+
}
|
|
61
|
+
function matchesKeywordThreshold(eventText, ruleText, threshold = 0.5) {
|
|
62
|
+
const keywords = extractKeywords(ruleText);
|
|
63
|
+
if (keywords.length === 0) return false;
|
|
64
|
+
const matched = keywords.filter((kw) => eventText.includes(kw));
|
|
65
|
+
return matched.length >= Math.ceil(keywords.length * threshold);
|
|
66
|
+
}
|
|
67
|
+
function tokenSimilarity(a, b) {
|
|
68
|
+
const tokensA = new Set(a.toLowerCase().split(/\s+/).filter((w) => w.length > 2));
|
|
69
|
+
const tokensB = new Set(b.toLowerCase().split(/\s+/).filter((w) => w.length > 2));
|
|
70
|
+
if (tokensA.size === 0 || tokensB.size === 0) return 0;
|
|
71
|
+
let intersection = 0;
|
|
72
|
+
for (const t of tokensA) {
|
|
73
|
+
if (tokensB.has(t)) intersection++;
|
|
74
|
+
}
|
|
75
|
+
const union = (/* @__PURE__ */ new Set([...tokensA, ...tokensB])).size;
|
|
76
|
+
return union > 0 ? intersection / union : 0;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// src/engine/plan-engine.ts
|
|
80
|
+
function keywordMatch(eventText, step) {
|
|
81
|
+
const stepText = [
|
|
82
|
+
step.label,
|
|
83
|
+
step.description ?? "",
|
|
84
|
+
...step.tags ?? []
|
|
85
|
+
].join(" ");
|
|
86
|
+
return matchesKeywordThreshold(eventText, stepText, 0.5);
|
|
87
|
+
}
|
|
88
|
+
function tokenSimilarity2(a, b) {
|
|
89
|
+
return tokenSimilarity(a, b);
|
|
90
|
+
}
|
|
91
|
+
function findMatchingStep(eventText, event, steps) {
|
|
92
|
+
const pendingOrActive = steps.filter((s) => s.status === "pending" || s.status === "active");
|
|
93
|
+
if (pendingOrActive.length === 0) {
|
|
94
|
+
return { matched: null, closest: null, closestScore: 0 };
|
|
95
|
+
}
|
|
96
|
+
for (const step of pendingOrActive) {
|
|
97
|
+
if (keywordMatch(eventText, step)) {
|
|
98
|
+
if (step.tools && event.tool && !step.tools.includes(event.tool)) {
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
return { matched: step, closest: step, closestScore: 1 };
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
const intentText = [event.intent, event.tool ?? "", event.scope ?? ""].join(" ");
|
|
105
|
+
let bestStep = null;
|
|
106
|
+
let bestScore = 0;
|
|
107
|
+
for (const step of pendingOrActive) {
|
|
108
|
+
const stepText = [step.label, step.description ?? "", ...step.tags ?? []].join(" ");
|
|
109
|
+
const score = tokenSimilarity2(intentText, stepText);
|
|
110
|
+
if (score > bestScore) {
|
|
111
|
+
bestScore = score;
|
|
112
|
+
bestStep = step;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
const SIMILARITY_THRESHOLD = 0.35;
|
|
116
|
+
if (bestScore >= SIMILARITY_THRESHOLD && bestStep) {
|
|
117
|
+
if (bestStep.tools && event.tool && !bestStep.tools.includes(event.tool)) {
|
|
118
|
+
return { matched: null, closest: bestStep, closestScore: bestScore };
|
|
119
|
+
}
|
|
120
|
+
return { matched: bestStep, closest: bestStep, closestScore: bestScore };
|
|
121
|
+
}
|
|
122
|
+
return { matched: null, closest: bestStep, closestScore: bestScore };
|
|
123
|
+
}
|
|
124
|
+
function isSequenceValid(step, plan) {
|
|
125
|
+
if (!plan.sequential) return true;
|
|
126
|
+
if (!step.requires || step.requires.length === 0) return true;
|
|
127
|
+
return step.requires.every((reqId) => {
|
|
128
|
+
const reqStep = plan.steps.find((s) => s.id === reqId);
|
|
129
|
+
return reqStep?.status === "completed";
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
function checkConstraints(event, eventText, constraints) {
|
|
133
|
+
const checks = [];
|
|
134
|
+
for (const constraint of constraints) {
|
|
135
|
+
if (constraint.type === "approval") {
|
|
136
|
+
if (constraint.trigger && eventText.includes(constraint.trigger.substring(0, 10).toLowerCase())) {
|
|
137
|
+
checks.push({ constraintId: constraint.id, passed: false, reason: constraint.description });
|
|
138
|
+
return { violated: constraint, checks };
|
|
139
|
+
}
|
|
140
|
+
const keywords = constraint.description.toLowerCase().split(/\s+/).filter((w) => w.length > 3);
|
|
141
|
+
const relevant = keywords.some((kw) => eventText.includes(kw));
|
|
142
|
+
if (relevant) {
|
|
143
|
+
checks.push({ constraintId: constraint.id, passed: false, reason: constraint.description });
|
|
144
|
+
return { violated: constraint, checks };
|
|
145
|
+
}
|
|
146
|
+
checks.push({ constraintId: constraint.id, passed: true });
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
if (constraint.type === "scope" && constraint.trigger) {
|
|
150
|
+
const keywords = extractKeywords(constraint.trigger);
|
|
151
|
+
const violated = keywords.length > 0 && keywords.every((kw) => eventText.includes(kw));
|
|
152
|
+
checks.push({
|
|
153
|
+
constraintId: constraint.id,
|
|
154
|
+
passed: !violated,
|
|
155
|
+
reason: violated ? constraint.description : void 0
|
|
156
|
+
});
|
|
157
|
+
if (violated) {
|
|
158
|
+
return { violated: constraint, checks };
|
|
159
|
+
}
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
checks.push({ constraintId: constraint.id, passed: true });
|
|
163
|
+
}
|
|
164
|
+
return { violated: null, checks };
|
|
165
|
+
}
|
|
166
|
+
function getPlanProgress(plan) {
|
|
167
|
+
const completed = plan.steps.filter((s) => s.status === "completed").length;
|
|
168
|
+
const total = plan.steps.length;
|
|
169
|
+
return {
|
|
170
|
+
completed,
|
|
171
|
+
total,
|
|
172
|
+
percentage: total > 0 ? Math.round(completed / total * 100) : 0
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
function advancePlan(plan, stepId, evidence) {
|
|
176
|
+
const step = plan.steps.find((s) => s.id === stepId);
|
|
177
|
+
if (!step) {
|
|
178
|
+
return { success: false, reason: `Step "${stepId}" not found in plan.` };
|
|
179
|
+
}
|
|
180
|
+
if (step.status === "completed") {
|
|
181
|
+
return { success: false, reason: `Step "${stepId}" is already completed.` };
|
|
182
|
+
}
|
|
183
|
+
const mode = plan.completion ?? "trust";
|
|
184
|
+
if (mode === "verified" && step.verify) {
|
|
185
|
+
if (!evidence) {
|
|
186
|
+
return {
|
|
187
|
+
success: false,
|
|
188
|
+
reason: `Step "${step.label}" requires evidence (verify: ${step.verify}). Provide evidence to advance.`
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
if (evidence.type !== step.verify) {
|
|
192
|
+
return {
|
|
193
|
+
success: false,
|
|
194
|
+
reason: `Evidence type "${evidence.type}" does not match required verification "${step.verify}".`
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
const updatedPlan = {
|
|
199
|
+
...plan,
|
|
200
|
+
steps: plan.steps.map(
|
|
201
|
+
(s) => s.id === stepId ? { ...s, status: "completed" } : s
|
|
202
|
+
)
|
|
203
|
+
};
|
|
204
|
+
return {
|
|
205
|
+
success: true,
|
|
206
|
+
plan: updatedPlan,
|
|
207
|
+
evidence: evidence ?? void 0
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
function evaluatePlan(event, plan) {
|
|
211
|
+
const progress = getPlanProgress(plan);
|
|
212
|
+
if (plan.expires_at) {
|
|
213
|
+
const expiresAt = new Date(plan.expires_at).getTime();
|
|
214
|
+
if (Date.now() > expiresAt) {
|
|
215
|
+
return {
|
|
216
|
+
allowed: true,
|
|
217
|
+
status: "PLAN_COMPLETE",
|
|
218
|
+
reason: "Plan has expired.",
|
|
219
|
+
progress
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if (progress.completed === progress.total) {
|
|
224
|
+
return {
|
|
225
|
+
allowed: true,
|
|
226
|
+
status: "PLAN_COMPLETE",
|
|
227
|
+
reason: "All plan steps are completed.",
|
|
228
|
+
progress
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
const eventText = normalizeEventText(event);
|
|
232
|
+
const { matched, closest, closestScore } = findMatchingStep(eventText, event, plan.steps);
|
|
233
|
+
if (!matched) {
|
|
234
|
+
return {
|
|
235
|
+
allowed: false,
|
|
236
|
+
status: "OFF_PLAN",
|
|
237
|
+
reason: "Action does not match any plan step.",
|
|
238
|
+
closestStep: closest?.label,
|
|
239
|
+
similarityScore: closestScore,
|
|
240
|
+
progress
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
if (!isSequenceValid(matched, plan)) {
|
|
244
|
+
const pendingDeps = (matched.requires ?? []).filter((reqId) => plan.steps.find((s) => s.id === reqId)?.status !== "completed").join(", ");
|
|
245
|
+
return {
|
|
246
|
+
allowed: false,
|
|
247
|
+
status: "OFF_PLAN",
|
|
248
|
+
reason: `Step "${matched.label}" requires completion of: ${pendingDeps}`,
|
|
249
|
+
matchedStep: matched.id,
|
|
250
|
+
progress
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
const { violated } = checkConstraints(event, eventText, plan.constraints);
|
|
254
|
+
if (violated) {
|
|
255
|
+
return {
|
|
256
|
+
allowed: false,
|
|
257
|
+
status: "CONSTRAINT_VIOLATED",
|
|
258
|
+
reason: violated.description,
|
|
259
|
+
matchedStep: matched.id,
|
|
260
|
+
progress
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
return {
|
|
264
|
+
allowed: true,
|
|
265
|
+
status: "ON_PLAN",
|
|
266
|
+
reason: `Matches step: ${matched.label}`,
|
|
267
|
+
matchedStep: matched.id,
|
|
268
|
+
progress
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
function buildPlanCheck(event, plan, verdict) {
|
|
272
|
+
const eventText = normalizeEventText(event);
|
|
273
|
+
const { matched, closest, closestScore } = findMatchingStep(eventText, event, plan.steps);
|
|
274
|
+
const { checks: constraintChecks } = checkConstraints(event, eventText, plan.constraints);
|
|
275
|
+
const progress = getPlanProgress(plan);
|
|
276
|
+
return {
|
|
277
|
+
planId: plan.plan_id,
|
|
278
|
+
matched: !!matched,
|
|
279
|
+
matchedStepId: matched?.id,
|
|
280
|
+
matchedStepLabel: matched?.label,
|
|
281
|
+
closestStepId: !matched ? closest?.id : void 0,
|
|
282
|
+
closestStepLabel: !matched ? closest?.label : void 0,
|
|
283
|
+
similarityScore: !matched ? closestScore : void 0,
|
|
284
|
+
sequenceValid: matched ? isSequenceValid(matched, plan) : void 0,
|
|
285
|
+
constraintsChecked: constraintChecks,
|
|
286
|
+
progress: { completed: progress.completed, total: progress.total }
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// src/engine/guard-engine.ts
|
|
291
|
+
var PROMPT_INJECTION_PATTERNS = [
|
|
292
|
+
// Instruction override
|
|
293
|
+
{ pattern: /ignore\s+(previous|all|prior|above)\s+(instructions?|rules?)/i, label: "ignore-instructions" },
|
|
294
|
+
{ pattern: /disregard\s+(your|the)\s+(rules|constraints)/i, label: "disregard-rules" },
|
|
295
|
+
{ pattern: /new\s+instructions?:/i, label: "new-instructions" },
|
|
296
|
+
// Identity manipulation
|
|
297
|
+
{ pattern: /you\s+are\s+now/i, label: "identity-override" },
|
|
298
|
+
{ pattern: /new\s+persona/i, label: "new-persona" },
|
|
299
|
+
{ pattern: /act\s+as\s+if/i, label: "act-as-if" },
|
|
300
|
+
{ pattern: /pretend\s+(you|to\s+be|you\s+are\s+unrestricted)/i, label: "pretend-to-be" },
|
|
301
|
+
// Context reset
|
|
302
|
+
{ pattern: /forget\s+(everything|all|your)/i, label: "forget-context" },
|
|
303
|
+
{ pattern: /system\s*:\s*override/i, label: "system-override" },
|
|
304
|
+
// Constraint bypass
|
|
305
|
+
{ pattern: /override\s+(your|the)\s+(programming|constraints)/i, label: "override-constraints" },
|
|
306
|
+
{ pattern: /bypass\s+(your|the)\s+(filters|constraints|rules)/i, label: "bypass-filters" },
|
|
307
|
+
// Prompt extraction
|
|
308
|
+
{ pattern: /system\s+prompt/i, label: "system-prompt-probe" },
|
|
309
|
+
{ pattern: /reveal\s+your\s+(instructions?|prompt|rules)/i, label: "reveal-instructions" },
|
|
310
|
+
// Known jailbreak terms
|
|
311
|
+
{ pattern: /jailbreak/i, label: "jailbreak" },
|
|
312
|
+
{ pattern: /DAN\s+mode/i, label: "dan-mode" },
|
|
313
|
+
{ pattern: /developer\s+mode/i, label: "developer-mode" }
|
|
314
|
+
];
|
|
315
|
+
var EXECUTION_CLAIM_PATTERNS = [
|
|
316
|
+
{ pattern: /I have (executed|completed|performed|done|made|created|sent|deleted|modified|updated)/i, label: "claim-i-have" },
|
|
317
|
+
{ pattern: /Successfully (created|deleted|modified|updated|sent|executed|performed)/i, label: "claim-successfully" },
|
|
318
|
+
{ pattern: /The file has been/i, label: "claim-file-modified" },
|
|
319
|
+
{ pattern: /I've made the changes/i, label: "claim-made-changes" },
|
|
320
|
+
{ pattern: /I('ve| have) (sent|posted|submitted|uploaded|downloaded)/i, label: "claim-sent" },
|
|
321
|
+
{ pattern: /Your (email|message|file|request) has been (sent|submitted)/i, label: "claim-your-sent" },
|
|
322
|
+
{ pattern: /Transaction complete/i, label: "claim-transaction" },
|
|
323
|
+
{ pattern: /Order placed/i, label: "claim-order" },
|
|
324
|
+
{ pattern: /Payment processed/i, label: "claim-payment" }
|
|
325
|
+
];
|
|
326
|
+
var EXECUTION_INTENT_PATTERNS = [
|
|
327
|
+
{ pattern: /^(execute|run|perform|do this)/i, label: "intent-execute" },
|
|
328
|
+
{ pattern: /^(create|write|delete|modify) (a |the )?(file|folder|document)/i, label: "intent-file-ops" },
|
|
329
|
+
{ pattern: /^(send|post|submit) (a |an |the )?(email|message|tweet|post)/i, label: "intent-send" },
|
|
330
|
+
{ pattern: /^(search|look up|browse) (the )?web/i, label: "intent-web-search" },
|
|
331
|
+
{ pattern: /^(make|call|invoke) (a |an )?(api|http|rest) (call|request)/i, label: "intent-api-call" },
|
|
332
|
+
{ pattern: /^(buy|purchase|order|pay|transfer|send money)/i, label: "intent-financial" },
|
|
333
|
+
{ pattern: /^(book|schedule|reserve)/i, label: "intent-booking" },
|
|
334
|
+
{ pattern: /^(download|upload|save to|export to)/i, label: "intent-transfer" }
|
|
335
|
+
];
|
|
336
|
+
var SCOPE_ESCAPE_PATTERNS = [
|
|
337
|
+
{ pattern: /\.\.\//, label: "parent-traversal" },
|
|
338
|
+
{ pattern: /^\/(?!home|project|workspace)/i, label: "absolute-path-outside-safe" },
|
|
339
|
+
{ pattern: /~\//, label: "home-directory" },
|
|
340
|
+
{ pattern: /\/etc\//i, label: "system-config" },
|
|
341
|
+
{ pattern: /\/usr\//i, label: "system-binaries" },
|
|
342
|
+
{ pattern: /\/var\//i, label: "system-variable-data" }
|
|
343
|
+
];
|
|
344
|
+
var NEUTRAL_MESSAGES = {
|
|
345
|
+
"prompt-injection": "This input contains patterns that could alter agent behavior.",
|
|
346
|
+
"scope-escape": "This action would affect resources outside the declared scope.",
|
|
347
|
+
"execution-claim": "This response claims to have performed an action.",
|
|
348
|
+
"execution-intent": "This input requests execution in a thinking-only environment.",
|
|
349
|
+
"delete": "This action would remove files. Confirmation needed.",
|
|
350
|
+
"write-external": "This action would write outside the project folder.",
|
|
351
|
+
"network-mutate": "This action would send data to an external service.",
|
|
352
|
+
"credential-access": "This action would access stored credentials."
|
|
353
|
+
};
|
|
354
|
+
function levelRequiresConfirmation(level, actionType) {
|
|
355
|
+
if (level === "strict") return true;
|
|
356
|
+
if (level === "standard") {
|
|
357
|
+
return actionType === "delete" || actionType === "credential-access";
|
|
358
|
+
}
|
|
359
|
+
return false;
|
|
360
|
+
}
|
|
361
|
+
function isExternalScope(scope) {
|
|
362
|
+
const internalPatterns = [
|
|
363
|
+
/^\.?\/?src\//i,
|
|
364
|
+
/^\.?\/?lib\//i,
|
|
365
|
+
/^\.?\/?app\//i,
|
|
366
|
+
/^\.?\/?components\//i,
|
|
367
|
+
/^\.?\/?pages\//i,
|
|
368
|
+
/^\.?\/?public\//i,
|
|
369
|
+
/^\.?\/?assets\//i,
|
|
370
|
+
/^\.\//
|
|
371
|
+
];
|
|
372
|
+
return !internalPatterns.some((p) => p.test(scope));
|
|
373
|
+
}
|
|
374
|
+
var MAX_INPUT_LENGTH = 1e5;
|
|
375
|
+
function evaluateGuard(event, world, options = {}) {
|
|
376
|
+
const startTime = performance.now();
|
|
377
|
+
const level = options.level ?? "standard";
|
|
378
|
+
const includeTrace = options.trace ?? false;
|
|
379
|
+
if (!event.intent || typeof event.intent !== "string") {
|
|
380
|
+
return {
|
|
381
|
+
status: "BLOCK",
|
|
382
|
+
reason: "GuardEvent.intent is required and must be a string",
|
|
383
|
+
ruleId: "safety-input-validation",
|
|
384
|
+
evidence: {
|
|
385
|
+
worldId: world.world?.world_id ?? "",
|
|
386
|
+
worldName: world.world?.name ?? "",
|
|
387
|
+
worldVersion: world.world?.version ?? "",
|
|
388
|
+
evaluatedAt: Date.now(),
|
|
389
|
+
invariantsSatisfied: 0,
|
|
390
|
+
invariantsTotal: 0,
|
|
391
|
+
guardsMatched: [],
|
|
392
|
+
rulesMatched: [],
|
|
393
|
+
enforcementLevel: level
|
|
394
|
+
}
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
const inputLength = event.intent.length + (event.tool?.length ?? 0) + (event.scope?.length ?? 0) + (event.payload ? JSON.stringify(event.payload).length : 0);
|
|
398
|
+
if (inputLength > MAX_INPUT_LENGTH) {
|
|
399
|
+
return {
|
|
400
|
+
status: "BLOCK",
|
|
401
|
+
reason: `Input exceeds maximum allowed length (${MAX_INPUT_LENGTH} characters)`,
|
|
402
|
+
ruleId: "safety-input-length",
|
|
403
|
+
evidence: {
|
|
404
|
+
worldId: world.world?.world_id ?? "",
|
|
405
|
+
worldName: world.world?.name ?? "",
|
|
406
|
+
worldVersion: world.world?.version ?? "",
|
|
407
|
+
evaluatedAt: Date.now(),
|
|
408
|
+
invariantsSatisfied: 0,
|
|
409
|
+
invariantsTotal: 0,
|
|
410
|
+
guardsMatched: [],
|
|
411
|
+
rulesMatched: [],
|
|
412
|
+
enforcementLevel: level
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
const eventText = normalizeEventText(event);
|
|
417
|
+
const invariantChecks = [];
|
|
418
|
+
const safetyChecks = [];
|
|
419
|
+
let planCheckResult;
|
|
420
|
+
const roleChecks = [];
|
|
421
|
+
const guardChecks = [];
|
|
422
|
+
const kernelRuleChecks = [];
|
|
423
|
+
const levelChecks = [];
|
|
424
|
+
let decidingLayer = "default-allow";
|
|
425
|
+
let decidingId;
|
|
426
|
+
const guardsMatched = [];
|
|
427
|
+
const rulesMatched = [];
|
|
428
|
+
if (options.emergencyOverride) {
|
|
429
|
+
checkInvariantCoverage(world, invariantChecks);
|
|
430
|
+
return buildVerdict(
|
|
431
|
+
"ALLOW",
|
|
432
|
+
void 0,
|
|
433
|
+
"emergency-override",
|
|
434
|
+
"Emergency override active \u2014 all governance rules suspended. Platform constraints still apply.",
|
|
435
|
+
world,
|
|
436
|
+
level,
|
|
437
|
+
invariantChecks,
|
|
438
|
+
guardsMatched,
|
|
439
|
+
rulesMatched,
|
|
440
|
+
includeTrace ? buildTrace(
|
|
441
|
+
invariantChecks,
|
|
442
|
+
safetyChecks,
|
|
443
|
+
planCheckResult,
|
|
444
|
+
roleChecks,
|
|
445
|
+
guardChecks,
|
|
446
|
+
kernelRuleChecks,
|
|
447
|
+
levelChecks,
|
|
448
|
+
"session-allowlist",
|
|
449
|
+
"emergency-override",
|
|
450
|
+
startTime
|
|
451
|
+
) : void 0,
|
|
452
|
+
event.intent
|
|
453
|
+
);
|
|
454
|
+
}
|
|
455
|
+
checkInvariantCoverage(world, invariantChecks);
|
|
456
|
+
if (event.roleId && options.agentStates) {
|
|
457
|
+
const agentState = options.agentStates.get(event.roleId);
|
|
458
|
+
if (agentState && agentState.cooldownRemaining > 0) {
|
|
459
|
+
decidingLayer = "safety";
|
|
460
|
+
decidingId = `penalize-cooldown-${event.roleId}`;
|
|
461
|
+
const verdict = buildVerdict(
|
|
462
|
+
"PENALIZE",
|
|
463
|
+
`Agent "${event.roleId}" is frozen for ${agentState.cooldownRemaining} more round(s) due to prior penalty.`,
|
|
464
|
+
`penalize-cooldown-${event.roleId}`,
|
|
465
|
+
void 0,
|
|
466
|
+
world,
|
|
467
|
+
level,
|
|
468
|
+
invariantChecks,
|
|
469
|
+
guardsMatched,
|
|
470
|
+
rulesMatched,
|
|
471
|
+
includeTrace ? buildTrace(
|
|
472
|
+
invariantChecks,
|
|
473
|
+
safetyChecks,
|
|
474
|
+
planCheckResult,
|
|
475
|
+
roleChecks,
|
|
476
|
+
guardChecks,
|
|
477
|
+
kernelRuleChecks,
|
|
478
|
+
levelChecks,
|
|
479
|
+
decidingLayer,
|
|
480
|
+
decidingId,
|
|
481
|
+
startTime
|
|
482
|
+
) : void 0
|
|
483
|
+
);
|
|
484
|
+
verdict.intentRecord = {
|
|
485
|
+
originalIntent: event.intent,
|
|
486
|
+
finalAction: "blocked (agent frozen)",
|
|
487
|
+
enforcement: "PENALIZE",
|
|
488
|
+
consequence: { type: "freeze", rounds: agentState.cooldownRemaining, description: "Agent still in cooldown from prior penalty" }
|
|
489
|
+
};
|
|
490
|
+
return verdict;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
if (options.sessionAllowlist) {
|
|
494
|
+
const key = eventToAllowlistKey(event);
|
|
495
|
+
if (options.sessionAllowlist.has(key)) {
|
|
496
|
+
decidingLayer = "session-allowlist";
|
|
497
|
+
decidingId = `allowlist:${key}`;
|
|
498
|
+
return buildVerdict(
|
|
499
|
+
"ALLOW",
|
|
500
|
+
void 0,
|
|
501
|
+
`allowlist:${key}`,
|
|
502
|
+
void 0,
|
|
503
|
+
world,
|
|
504
|
+
level,
|
|
505
|
+
invariantChecks,
|
|
506
|
+
guardsMatched,
|
|
507
|
+
rulesMatched,
|
|
508
|
+
includeTrace ? buildTrace(
|
|
509
|
+
invariantChecks,
|
|
510
|
+
safetyChecks,
|
|
511
|
+
planCheckResult,
|
|
512
|
+
roleChecks,
|
|
513
|
+
guardChecks,
|
|
514
|
+
kernelRuleChecks,
|
|
515
|
+
levelChecks,
|
|
516
|
+
decidingLayer,
|
|
517
|
+
decidingId,
|
|
518
|
+
startTime
|
|
519
|
+
) : void 0,
|
|
520
|
+
event.intent
|
|
521
|
+
);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
const safetyVerdict = checkSafety(event, eventText, safetyChecks);
|
|
525
|
+
if (safetyVerdict) {
|
|
526
|
+
decidingLayer = "safety";
|
|
527
|
+
decidingId = safetyVerdict.ruleId;
|
|
528
|
+
return buildVerdict(
|
|
529
|
+
safetyVerdict.status,
|
|
530
|
+
safetyVerdict.reason,
|
|
531
|
+
safetyVerdict.ruleId,
|
|
532
|
+
void 0,
|
|
533
|
+
world,
|
|
534
|
+
level,
|
|
535
|
+
invariantChecks,
|
|
536
|
+
guardsMatched,
|
|
537
|
+
rulesMatched,
|
|
538
|
+
includeTrace ? buildTrace(
|
|
539
|
+
invariantChecks,
|
|
540
|
+
safetyChecks,
|
|
541
|
+
planCheckResult,
|
|
542
|
+
roleChecks,
|
|
543
|
+
guardChecks,
|
|
544
|
+
kernelRuleChecks,
|
|
545
|
+
levelChecks,
|
|
546
|
+
decidingLayer,
|
|
547
|
+
decidingId,
|
|
548
|
+
startTime
|
|
549
|
+
) : void 0,
|
|
550
|
+
event.intent
|
|
551
|
+
);
|
|
552
|
+
}
|
|
553
|
+
if (options.plan) {
|
|
554
|
+
const planVerdict = evaluatePlan(event, options.plan);
|
|
555
|
+
planCheckResult = buildPlanCheck(event, options.plan, planVerdict);
|
|
556
|
+
if (!planVerdict.allowed && planVerdict.status !== "PLAN_COMPLETE") {
|
|
557
|
+
decidingLayer = "plan-enforcement";
|
|
558
|
+
decidingId = `plan-${options.plan.plan_id}`;
|
|
559
|
+
const planStatus = planVerdict.status === "CONSTRAINT_VIOLATED" ? "PAUSE" : "BLOCK";
|
|
560
|
+
let reason = planVerdict.reason ?? "Action blocked by plan.";
|
|
561
|
+
if (planVerdict.status === "OFF_PLAN" && planVerdict.closestStep) {
|
|
562
|
+
reason += ` Closest step: "${planVerdict.closestStep}" (similarity: ${(planVerdict.similarityScore ?? 0).toFixed(2)})`;
|
|
563
|
+
}
|
|
564
|
+
return buildVerdict(
|
|
565
|
+
planStatus,
|
|
566
|
+
reason,
|
|
567
|
+
`plan-${options.plan.plan_id}`,
|
|
568
|
+
void 0,
|
|
569
|
+
world,
|
|
570
|
+
level,
|
|
571
|
+
invariantChecks,
|
|
572
|
+
guardsMatched,
|
|
573
|
+
rulesMatched,
|
|
574
|
+
includeTrace ? buildTrace(
|
|
575
|
+
invariantChecks,
|
|
576
|
+
safetyChecks,
|
|
577
|
+
planCheckResult,
|
|
578
|
+
roleChecks,
|
|
579
|
+
guardChecks,
|
|
580
|
+
kernelRuleChecks,
|
|
581
|
+
levelChecks,
|
|
582
|
+
decidingLayer,
|
|
583
|
+
decidingId,
|
|
584
|
+
startTime
|
|
585
|
+
) : void 0,
|
|
586
|
+
event.intent
|
|
587
|
+
);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
const roleVerdict = checkRoleRules(event, eventText, world, roleChecks);
|
|
591
|
+
if (roleVerdict) {
|
|
592
|
+
decidingLayer = "role";
|
|
593
|
+
decidingId = roleVerdict.ruleId;
|
|
594
|
+
return buildVerdict(
|
|
595
|
+
roleVerdict.status,
|
|
596
|
+
roleVerdict.reason,
|
|
597
|
+
roleVerdict.ruleId,
|
|
598
|
+
void 0,
|
|
599
|
+
world,
|
|
600
|
+
level,
|
|
601
|
+
invariantChecks,
|
|
602
|
+
guardsMatched,
|
|
603
|
+
rulesMatched,
|
|
604
|
+
includeTrace ? buildTrace(
|
|
605
|
+
invariantChecks,
|
|
606
|
+
safetyChecks,
|
|
607
|
+
planCheckResult,
|
|
608
|
+
roleChecks,
|
|
609
|
+
guardChecks,
|
|
610
|
+
kernelRuleChecks,
|
|
611
|
+
levelChecks,
|
|
612
|
+
decidingLayer,
|
|
613
|
+
decidingId,
|
|
614
|
+
startTime
|
|
615
|
+
) : void 0,
|
|
616
|
+
event.intent
|
|
617
|
+
);
|
|
618
|
+
}
|
|
619
|
+
const guardVerdict = checkGuards(event, eventText, world, guardChecks, guardsMatched);
|
|
620
|
+
if (guardVerdict) {
|
|
621
|
+
if (guardVerdict.status !== "ALLOW") {
|
|
622
|
+
decidingLayer = "guard";
|
|
623
|
+
decidingId = guardVerdict.ruleId;
|
|
624
|
+
const intentRecord = {
|
|
625
|
+
originalIntent: event.intent,
|
|
626
|
+
finalAction: guardVerdict.status === "MODIFY" ? guardVerdict.modifiedTo ?? "modified" : guardVerdict.status === "PENALIZE" ? "blocked + penalized" : guardVerdict.status === "REWARD" ? event.intent : guardVerdict.status === "NEUTRAL" ? event.intent : guardVerdict.status === "BLOCK" ? "blocked" : "paused",
|
|
627
|
+
ruleApplied: guardVerdict.ruleId,
|
|
628
|
+
enforcement: guardVerdict.status,
|
|
629
|
+
modifiedTo: guardVerdict.modifiedTo,
|
|
630
|
+
consequence: guardVerdict.consequence,
|
|
631
|
+
reward: guardVerdict.reward
|
|
632
|
+
};
|
|
633
|
+
const verdict = buildVerdict(
|
|
634
|
+
guardVerdict.status,
|
|
635
|
+
guardVerdict.reason,
|
|
636
|
+
guardVerdict.ruleId,
|
|
637
|
+
void 0,
|
|
638
|
+
world,
|
|
639
|
+
level,
|
|
640
|
+
invariantChecks,
|
|
641
|
+
guardsMatched,
|
|
642
|
+
rulesMatched,
|
|
643
|
+
includeTrace ? buildTrace(
|
|
644
|
+
invariantChecks,
|
|
645
|
+
safetyChecks,
|
|
646
|
+
planCheckResult,
|
|
647
|
+
roleChecks,
|
|
648
|
+
guardChecks,
|
|
649
|
+
kernelRuleChecks,
|
|
650
|
+
levelChecks,
|
|
651
|
+
decidingLayer,
|
|
652
|
+
decidingId,
|
|
653
|
+
startTime
|
|
654
|
+
) : void 0,
|
|
655
|
+
event.intent
|
|
656
|
+
);
|
|
657
|
+
verdict.intentRecord = intentRecord;
|
|
658
|
+
if (guardVerdict.consequence) verdict.consequence = guardVerdict.consequence;
|
|
659
|
+
if (guardVerdict.reward) verdict.reward = guardVerdict.reward;
|
|
660
|
+
return verdict;
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
const kernelVerdict = checkKernelRules(eventText, world, kernelRuleChecks, rulesMatched);
|
|
664
|
+
if (kernelVerdict) {
|
|
665
|
+
decidingLayer = "kernel-rule";
|
|
666
|
+
decidingId = kernelVerdict.ruleId;
|
|
667
|
+
return buildVerdict(
|
|
668
|
+
kernelVerdict.status,
|
|
669
|
+
kernelVerdict.reason,
|
|
670
|
+
kernelVerdict.ruleId,
|
|
671
|
+
void 0,
|
|
672
|
+
world,
|
|
673
|
+
level,
|
|
674
|
+
invariantChecks,
|
|
675
|
+
guardsMatched,
|
|
676
|
+
rulesMatched,
|
|
677
|
+
includeTrace ? buildTrace(
|
|
678
|
+
invariantChecks,
|
|
679
|
+
safetyChecks,
|
|
680
|
+
planCheckResult,
|
|
681
|
+
roleChecks,
|
|
682
|
+
guardChecks,
|
|
683
|
+
kernelRuleChecks,
|
|
684
|
+
levelChecks,
|
|
685
|
+
decidingLayer,
|
|
686
|
+
decidingId,
|
|
687
|
+
startTime
|
|
688
|
+
) : void 0,
|
|
689
|
+
event.intent
|
|
690
|
+
);
|
|
691
|
+
}
|
|
692
|
+
const levelVerdict = checkLevelConstraints(event, level, levelChecks);
|
|
693
|
+
if (levelVerdict) {
|
|
694
|
+
decidingLayer = "level-constraint";
|
|
695
|
+
decidingId = levelVerdict.ruleId;
|
|
696
|
+
return buildVerdict(
|
|
697
|
+
levelVerdict.status,
|
|
698
|
+
levelVerdict.reason,
|
|
699
|
+
levelVerdict.ruleId,
|
|
700
|
+
void 0,
|
|
701
|
+
world,
|
|
702
|
+
level,
|
|
703
|
+
invariantChecks,
|
|
704
|
+
guardsMatched,
|
|
705
|
+
rulesMatched,
|
|
706
|
+
includeTrace ? buildTrace(
|
|
707
|
+
invariantChecks,
|
|
708
|
+
safetyChecks,
|
|
709
|
+
planCheckResult,
|
|
710
|
+
roleChecks,
|
|
711
|
+
guardChecks,
|
|
712
|
+
kernelRuleChecks,
|
|
713
|
+
levelChecks,
|
|
714
|
+
decidingLayer,
|
|
715
|
+
decidingId,
|
|
716
|
+
startTime
|
|
717
|
+
) : void 0,
|
|
718
|
+
event.intent
|
|
719
|
+
);
|
|
720
|
+
}
|
|
721
|
+
const warning = guardVerdict?.warning;
|
|
722
|
+
return buildVerdict(
|
|
723
|
+
"ALLOW",
|
|
724
|
+
void 0,
|
|
725
|
+
void 0,
|
|
726
|
+
warning,
|
|
727
|
+
world,
|
|
728
|
+
level,
|
|
729
|
+
invariantChecks,
|
|
730
|
+
guardsMatched,
|
|
731
|
+
rulesMatched,
|
|
732
|
+
includeTrace ? buildTrace(
|
|
733
|
+
invariantChecks,
|
|
734
|
+
safetyChecks,
|
|
735
|
+
planCheckResult,
|
|
736
|
+
roleChecks,
|
|
737
|
+
guardChecks,
|
|
738
|
+
kernelRuleChecks,
|
|
739
|
+
levelChecks,
|
|
740
|
+
decidingLayer,
|
|
741
|
+
decidingId,
|
|
742
|
+
startTime
|
|
743
|
+
) : void 0,
|
|
744
|
+
event.intent
|
|
745
|
+
);
|
|
746
|
+
}
|
|
747
|
+
function checkInvariantCoverage(world, checks) {
|
|
748
|
+
const invariants = world.invariants ?? [];
|
|
749
|
+
const guards = world.guards?.guards ?? [];
|
|
750
|
+
for (const invariant of invariants) {
|
|
751
|
+
const coveringGuard = guards.find(
|
|
752
|
+
(g) => g.invariant_ref === invariant.id && g.immutable
|
|
753
|
+
);
|
|
754
|
+
checks.push({
|
|
755
|
+
invariantId: invariant.id,
|
|
756
|
+
label: invariant.label,
|
|
757
|
+
hasGuardCoverage: !!coveringGuard,
|
|
758
|
+
coveringGuardId: coveringGuard?.id
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
function checkSafety(event, eventText, checks) {
|
|
763
|
+
const textToCheck = event.intent + (event.payload ? JSON.stringify(event.payload) : "");
|
|
764
|
+
for (const { pattern, label } of PROMPT_INJECTION_PATTERNS) {
|
|
765
|
+
const triggered = pattern.test(textToCheck);
|
|
766
|
+
checks.push({
|
|
767
|
+
checkType: "prompt-injection",
|
|
768
|
+
triggered,
|
|
769
|
+
matchedPattern: triggered ? label : void 0
|
|
770
|
+
});
|
|
771
|
+
if (triggered) {
|
|
772
|
+
for (const remaining of PROMPT_INJECTION_PATTERNS.filter((p) => p.label !== label)) {
|
|
773
|
+
checks.push({
|
|
774
|
+
checkType: "prompt-injection",
|
|
775
|
+
triggered: remaining.pattern.test(textToCheck),
|
|
776
|
+
matchedPattern: remaining.pattern.test(textToCheck) ? remaining.label : void 0
|
|
777
|
+
});
|
|
778
|
+
}
|
|
779
|
+
return {
|
|
780
|
+
status: "PAUSE",
|
|
781
|
+
reason: NEUTRAL_MESSAGES["prompt-injection"],
|
|
782
|
+
ruleId: `safety-injection-${label}`
|
|
783
|
+
};
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
const scopeToCheck = event.scope ?? event.intent;
|
|
787
|
+
for (const { pattern, label } of SCOPE_ESCAPE_PATTERNS) {
|
|
788
|
+
const triggered = pattern.test(scopeToCheck);
|
|
789
|
+
checks.push({
|
|
790
|
+
checkType: "scope-escape",
|
|
791
|
+
triggered,
|
|
792
|
+
matchedPattern: triggered ? label : void 0
|
|
793
|
+
});
|
|
794
|
+
if (triggered) {
|
|
795
|
+
for (const remaining of SCOPE_ESCAPE_PATTERNS.filter((p) => p.label !== label)) {
|
|
796
|
+
checks.push({
|
|
797
|
+
checkType: "scope-escape",
|
|
798
|
+
triggered: remaining.pattern.test(scopeToCheck),
|
|
799
|
+
matchedPattern: remaining.pattern.test(scopeToCheck) ? remaining.label : void 0
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
return {
|
|
803
|
+
status: "PAUSE",
|
|
804
|
+
reason: NEUTRAL_MESSAGES["scope-escape"],
|
|
805
|
+
ruleId: `safety-scope-${label}`
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
if (event.direction === "output") {
|
|
810
|
+
for (const { pattern, label } of EXECUTION_CLAIM_PATTERNS) {
|
|
811
|
+
const triggered = pattern.test(textToCheck);
|
|
812
|
+
checks.push({
|
|
813
|
+
checkType: "execution-claim",
|
|
814
|
+
triggered,
|
|
815
|
+
matchedPattern: triggered ? label : void 0
|
|
816
|
+
});
|
|
817
|
+
if (triggered) {
|
|
818
|
+
for (const remaining of EXECUTION_CLAIM_PATTERNS.filter((p) => p.label !== label)) {
|
|
819
|
+
checks.push({
|
|
820
|
+
checkType: "execution-claim",
|
|
821
|
+
triggered: remaining.pattern.test(textToCheck),
|
|
822
|
+
matchedPattern: remaining.pattern.test(textToCheck) ? remaining.label : void 0
|
|
823
|
+
});
|
|
824
|
+
}
|
|
825
|
+
return {
|
|
826
|
+
status: "PAUSE",
|
|
827
|
+
reason: NEUTRAL_MESSAGES["execution-claim"],
|
|
828
|
+
ruleId: `safety-execution-claim-${label}`
|
|
829
|
+
};
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
if (event.direction === "input") {
|
|
834
|
+
const intentTrimmed = event.intent.trim();
|
|
835
|
+
for (const { pattern, label } of EXECUTION_INTENT_PATTERNS) {
|
|
836
|
+
const triggered = pattern.test(intentTrimmed);
|
|
837
|
+
checks.push({
|
|
838
|
+
checkType: "execution-intent",
|
|
839
|
+
triggered,
|
|
840
|
+
matchedPattern: triggered ? label : void 0
|
|
841
|
+
});
|
|
842
|
+
if (triggered) {
|
|
843
|
+
for (const remaining of EXECUTION_INTENT_PATTERNS.filter((p) => p.label !== label)) {
|
|
844
|
+
checks.push({
|
|
845
|
+
checkType: "execution-intent",
|
|
846
|
+
triggered: remaining.pattern.test(intentTrimmed),
|
|
847
|
+
matchedPattern: remaining.pattern.test(intentTrimmed) ? remaining.label : void 0
|
|
848
|
+
});
|
|
849
|
+
}
|
|
850
|
+
return {
|
|
851
|
+
status: "PAUSE",
|
|
852
|
+
reason: NEUTRAL_MESSAGES["execution-intent"],
|
|
853
|
+
ruleId: `safety-execution-intent-${label}`
|
|
854
|
+
};
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
return null;
|
|
859
|
+
}
|
|
860
|
+
function checkRoleRules(event, eventText, world, checks) {
|
|
861
|
+
if (!event.roleId || !world.roles) return null;
|
|
862
|
+
const role = world.roles.roles.find((r) => r.id === event.roleId);
|
|
863
|
+
if (!role) return null;
|
|
864
|
+
if (role.requiresApproval) {
|
|
865
|
+
checks.push({
|
|
866
|
+
roleId: role.id,
|
|
867
|
+
roleName: role.name,
|
|
868
|
+
rule: "All actions require approval",
|
|
869
|
+
ruleType: "requiresApproval",
|
|
870
|
+
matched: true
|
|
871
|
+
});
|
|
872
|
+
return {
|
|
873
|
+
status: "PAUSE",
|
|
874
|
+
reason: `Role "${role.name}" requires approval for all actions.`,
|
|
875
|
+
ruleId: `role-${role.id}-requires-approval`
|
|
876
|
+
};
|
|
877
|
+
}
|
|
878
|
+
for (const rule of role.cannotDo) {
|
|
879
|
+
const matched = matchesKeywords(eventText, rule);
|
|
880
|
+
checks.push({
|
|
881
|
+
roleId: role.id,
|
|
882
|
+
roleName: role.name,
|
|
883
|
+
rule,
|
|
884
|
+
ruleType: "cannotDo",
|
|
885
|
+
matched
|
|
886
|
+
});
|
|
887
|
+
if (matched) {
|
|
888
|
+
return {
|
|
889
|
+
status: "BLOCK",
|
|
890
|
+
reason: `Role "${role.name}" cannot: ${rule}`,
|
|
891
|
+
ruleId: `role-${role.id}-cannotdo`
|
|
892
|
+
};
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
for (const rule of role.canDo) {
|
|
896
|
+
checks.push({
|
|
897
|
+
roleId: role.id,
|
|
898
|
+
roleName: role.name,
|
|
899
|
+
rule,
|
|
900
|
+
ruleType: "canDo",
|
|
901
|
+
matched: matchesKeywords(eventText, rule)
|
|
902
|
+
});
|
|
903
|
+
}
|
|
904
|
+
return null;
|
|
905
|
+
}
|
|
906
|
+
function checkGuards(event, eventText, world, checks, guardsMatched) {
|
|
907
|
+
if (!world.guards) return null;
|
|
908
|
+
const guardsConfig = world.guards;
|
|
909
|
+
let warnResult = null;
|
|
910
|
+
const compiledPatterns = /* @__PURE__ */ new Map();
|
|
911
|
+
for (const [key, def] of Object.entries(guardsConfig.intent_vocabulary)) {
|
|
912
|
+
try {
|
|
913
|
+
compiledPatterns.set(key, new RegExp(def.pattern, "i"));
|
|
914
|
+
} catch {
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
const eventTool = (event.tool ?? "").toLowerCase();
|
|
918
|
+
for (const guard of guardsConfig.guards) {
|
|
919
|
+
if (guard.appliesTo && guard.appliesTo.length > 0) {
|
|
920
|
+
const normalizedAppliesTo = guard.appliesTo.map((t) => t.toLowerCase());
|
|
921
|
+
if (!normalizedAppliesTo.includes(eventTool)) {
|
|
922
|
+
continue;
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
const enabled = guard.immutable || guard.default_enabled !== false;
|
|
926
|
+
const matchedPatterns = [];
|
|
927
|
+
for (const patternKey of guard.intent_patterns) {
|
|
928
|
+
const regex = compiledPatterns.get(patternKey);
|
|
929
|
+
if (regex?.test(eventText)) {
|
|
930
|
+
matchedPatterns.push(patternKey);
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
const matched = matchedPatterns.length > 0 && enabled;
|
|
934
|
+
let roleGated = false;
|
|
935
|
+
if (matched && guard.required_roles && guard.required_roles.length > 0 && event.roleId && guard.required_roles.includes(event.roleId)) {
|
|
936
|
+
roleGated = true;
|
|
937
|
+
}
|
|
938
|
+
checks.push({
|
|
939
|
+
guardId: guard.id,
|
|
940
|
+
label: guard.label,
|
|
941
|
+
category: guard.category,
|
|
942
|
+
enabled,
|
|
943
|
+
matched: matched && !roleGated,
|
|
944
|
+
enforcement: guard.enforcement,
|
|
945
|
+
matchedPatterns,
|
|
946
|
+
roleGated
|
|
947
|
+
});
|
|
948
|
+
if (!matched || roleGated) continue;
|
|
949
|
+
guardsMatched.push(guard.id);
|
|
950
|
+
const actionMode = guard.player_modes?.action ?? guard.enforcement;
|
|
951
|
+
const reason = guard.redirect ? `${guard.description} \u2014 ${guard.redirect}` : guard.description;
|
|
952
|
+
if (actionMode === "block") {
|
|
953
|
+
return { status: "BLOCK", reason, ruleId: `guard-${guard.id}` };
|
|
954
|
+
}
|
|
955
|
+
if (actionMode === "pause") {
|
|
956
|
+
return { status: "PAUSE", reason, ruleId: `guard-${guard.id}` };
|
|
957
|
+
}
|
|
958
|
+
if (actionMode === "penalize") {
|
|
959
|
+
const consequence = guard.consequence ? { ...guard.consequence } : { type: "freeze", rounds: 1, description: `Penalized for violating: ${guard.label}` };
|
|
960
|
+
return { status: "PENALIZE", reason, ruleId: `guard-${guard.id}`, consequence };
|
|
961
|
+
}
|
|
962
|
+
if (actionMode === "reward") {
|
|
963
|
+
const reward = guard.reward ? { ...guard.reward } : { type: "boost_influence", magnitude: 0.1, description: `Rewarded for: ${guard.label}` };
|
|
964
|
+
return { status: "REWARD", reason, ruleId: `guard-${guard.id}`, reward };
|
|
965
|
+
}
|
|
966
|
+
if (actionMode === "modify") {
|
|
967
|
+
const modifiedTo = guard.modify_to ?? guard.redirect ?? "hold";
|
|
968
|
+
return { status: "MODIFY", reason: `${reason} \u2192 Modified to: ${modifiedTo}`, ruleId: `guard-${guard.id}`, modifiedTo };
|
|
969
|
+
}
|
|
970
|
+
if (actionMode === "neutral") {
|
|
971
|
+
return { status: "NEUTRAL", reason, ruleId: `guard-${guard.id}` };
|
|
972
|
+
}
|
|
973
|
+
if (actionMode === "warn" && !warnResult) {
|
|
974
|
+
warnResult = { status: "ALLOW", warning: reason, ruleId: `guard-${guard.id}` };
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
return warnResult;
|
|
978
|
+
}
|
|
979
|
+
function checkKernelRules(eventText, world, checks, rulesMatched) {
|
|
980
|
+
if (!world.kernel) return null;
|
|
981
|
+
const forbidden = world.kernel.input_boundaries?.forbidden_patterns ?? [];
|
|
982
|
+
const output = world.kernel.output_boundaries?.forbidden_patterns ?? [];
|
|
983
|
+
for (const rule of forbidden) {
|
|
984
|
+
let matched = false;
|
|
985
|
+
let matchMethod = "none";
|
|
986
|
+
if (rule.pattern) {
|
|
987
|
+
try {
|
|
988
|
+
matched = new RegExp(rule.pattern, "i").test(eventText);
|
|
989
|
+
matchMethod = "pattern";
|
|
990
|
+
} catch {
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
if (!matched && rule.reason) {
|
|
994
|
+
matched = matchesKeywords(eventText, rule.reason);
|
|
995
|
+
if (matched) matchMethod = "keyword";
|
|
996
|
+
}
|
|
997
|
+
checks.push({
|
|
998
|
+
ruleId: rule.id,
|
|
999
|
+
text: rule.reason,
|
|
1000
|
+
category: "forbidden",
|
|
1001
|
+
matched,
|
|
1002
|
+
matchMethod
|
|
1003
|
+
});
|
|
1004
|
+
if (matched) {
|
|
1005
|
+
rulesMatched.push(rule.id);
|
|
1006
|
+
if (rule.action === "BLOCK") {
|
|
1007
|
+
return {
|
|
1008
|
+
status: "BLOCK",
|
|
1009
|
+
reason: rule.reason,
|
|
1010
|
+
ruleId: `kernel-${rule.id}`
|
|
1011
|
+
};
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
return null;
|
|
1016
|
+
}
|
|
1017
|
+
function checkLevelConstraints(event, level, checks) {
|
|
1018
|
+
if (level === "basic") return null;
|
|
1019
|
+
const intent = event.intent.toLowerCase();
|
|
1020
|
+
const tool = (event.tool ?? "").toLowerCase();
|
|
1021
|
+
const isDelete = intent.includes("delete") || intent.includes("remove") || intent.includes("rm ") || tool === "delete";
|
|
1022
|
+
const deleteTriggered = isDelete && levelRequiresConfirmation(level, "delete");
|
|
1023
|
+
checks.push({
|
|
1024
|
+
checkType: "delete",
|
|
1025
|
+
level,
|
|
1026
|
+
triggered: deleteTriggered,
|
|
1027
|
+
reason: deleteTriggered ? NEUTRAL_MESSAGES["delete"] : void 0
|
|
1028
|
+
});
|
|
1029
|
+
if (deleteTriggered) {
|
|
1030
|
+
return { status: "PAUSE", reason: NEUTRAL_MESSAGES["delete"], ruleId: "level-delete-check" };
|
|
1031
|
+
}
|
|
1032
|
+
const isExternal = event.scope ? isExternalScope(event.scope) : false;
|
|
1033
|
+
const externalTriggered = isExternal && levelRequiresConfirmation(level, "write-external");
|
|
1034
|
+
checks.push({
|
|
1035
|
+
checkType: "write-external",
|
|
1036
|
+
level,
|
|
1037
|
+
triggered: externalTriggered,
|
|
1038
|
+
reason: externalTriggered ? NEUTRAL_MESSAGES["write-external"] : void 0
|
|
1039
|
+
});
|
|
1040
|
+
if (externalTriggered) {
|
|
1041
|
+
return { status: "PAUSE", reason: NEUTRAL_MESSAGES["write-external"], ruleId: "level-external-write-check" };
|
|
1042
|
+
}
|
|
1043
|
+
const isNetwork = tool === "http" || tool === "fetch" || tool === "request" || intent.includes("post ") || intent.includes("sending");
|
|
1044
|
+
const networkTriggered = isNetwork && levelRequiresConfirmation(level, "network-mutate");
|
|
1045
|
+
checks.push({
|
|
1046
|
+
checkType: "network-mutate",
|
|
1047
|
+
level,
|
|
1048
|
+
triggered: networkTriggered,
|
|
1049
|
+
reason: networkTriggered ? NEUTRAL_MESSAGES["network-mutate"] : void 0
|
|
1050
|
+
});
|
|
1051
|
+
if (networkTriggered) {
|
|
1052
|
+
return { status: "PAUSE", reason: NEUTRAL_MESSAGES["network-mutate"], ruleId: "level-network-mutate-check" };
|
|
1053
|
+
}
|
|
1054
|
+
const isCredential = intent.includes("credential") || intent.includes("password") || intent.includes("secret") || intent.includes("api key") || intent.includes("token");
|
|
1055
|
+
const credentialTriggered = isCredential && levelRequiresConfirmation(level, "credential-access");
|
|
1056
|
+
checks.push({
|
|
1057
|
+
checkType: "credential-access",
|
|
1058
|
+
level,
|
|
1059
|
+
triggered: credentialTriggered,
|
|
1060
|
+
reason: credentialTriggered ? NEUTRAL_MESSAGES["credential-access"] : void 0
|
|
1061
|
+
});
|
|
1062
|
+
if (credentialTriggered) {
|
|
1063
|
+
return { status: "PAUSE", reason: NEUTRAL_MESSAGES["credential-access"], ruleId: "level-credential-check" };
|
|
1064
|
+
}
|
|
1065
|
+
const irreversibleTriggered = !!event.irreversible && level !== "basic";
|
|
1066
|
+
checks.push({
|
|
1067
|
+
checkType: "irreversible",
|
|
1068
|
+
level,
|
|
1069
|
+
triggered: irreversibleTriggered,
|
|
1070
|
+
reason: irreversibleTriggered ? "This action is marked as irreversible." : void 0
|
|
1071
|
+
});
|
|
1072
|
+
if (irreversibleTriggered) {
|
|
1073
|
+
return {
|
|
1074
|
+
status: "PAUSE",
|
|
1075
|
+
reason: "This action is marked as irreversible.",
|
|
1076
|
+
ruleId: "level-irreversible-check"
|
|
1077
|
+
};
|
|
1078
|
+
}
|
|
1079
|
+
return null;
|
|
1080
|
+
}
|
|
1081
|
+
function matchesKeywords(eventText, ruleText) {
|
|
1082
|
+
return matchesAllKeywords(eventText, ruleText);
|
|
1083
|
+
}
|
|
1084
|
+
function eventToAllowlistKey(event) {
|
|
1085
|
+
return `${(event.tool ?? "*").toLowerCase()}::${event.intent.toLowerCase().trim()}`;
|
|
1086
|
+
}
|
|
1087
|
+
function buildTrace(invariantChecks, safetyChecks, planCheck, roleChecks, guardChecks, kernelRuleChecks, levelChecks, decidingLayer, decidingId, startTime) {
|
|
1088
|
+
const trace = {
|
|
1089
|
+
invariantChecks,
|
|
1090
|
+
safetyChecks,
|
|
1091
|
+
roleChecks,
|
|
1092
|
+
guardChecks,
|
|
1093
|
+
kernelRuleChecks,
|
|
1094
|
+
levelChecks,
|
|
1095
|
+
precedenceResolution: {
|
|
1096
|
+
decidingLayer,
|
|
1097
|
+
decidingId,
|
|
1098
|
+
strategy: "first-match-wins",
|
|
1099
|
+
chainOrder: [
|
|
1100
|
+
"invariant-coverage",
|
|
1101
|
+
"session-allowlist",
|
|
1102
|
+
"safety-injection",
|
|
1103
|
+
"safety-scope-escape",
|
|
1104
|
+
"safety-execution-claim",
|
|
1105
|
+
"safety-execution-intent",
|
|
1106
|
+
"plan-enforcement",
|
|
1107
|
+
"role-rules",
|
|
1108
|
+
"declarative-guards",
|
|
1109
|
+
"kernel-rules",
|
|
1110
|
+
"level-constraints",
|
|
1111
|
+
"default-allow"
|
|
1112
|
+
]
|
|
1113
|
+
},
|
|
1114
|
+
durationMs: performance.now() - startTime
|
|
1115
|
+
};
|
|
1116
|
+
if (planCheck) {
|
|
1117
|
+
trace.planCheck = planCheck;
|
|
1118
|
+
}
|
|
1119
|
+
return trace;
|
|
1120
|
+
}
|
|
1121
|
+
function buildVerdict(status, reason, ruleId, warning, world, level, invariantChecks, guardsMatched, rulesMatched, trace, eventIntent) {
|
|
1122
|
+
const evidence = {
|
|
1123
|
+
worldId: world.world.world_id,
|
|
1124
|
+
worldName: world.world.name,
|
|
1125
|
+
worldVersion: world.world.version,
|
|
1126
|
+
evaluatedAt: Date.now(),
|
|
1127
|
+
invariantsSatisfied: invariantChecks.filter((c) => c.hasGuardCoverage).length,
|
|
1128
|
+
invariantsTotal: invariantChecks.length,
|
|
1129
|
+
guardsMatched,
|
|
1130
|
+
rulesMatched,
|
|
1131
|
+
enforcementLevel: level
|
|
1132
|
+
};
|
|
1133
|
+
const verdict = {
|
|
1134
|
+
status,
|
|
1135
|
+
evidence
|
|
1136
|
+
};
|
|
1137
|
+
if (reason) verdict.reason = reason;
|
|
1138
|
+
if (ruleId) verdict.ruleId = ruleId;
|
|
1139
|
+
if (warning) verdict.warning = warning;
|
|
1140
|
+
if (trace) verdict.trace = trace;
|
|
1141
|
+
verdict.event = verdictToEvent(status, eventIntent);
|
|
1142
|
+
return verdict;
|
|
1143
|
+
}
|
|
1144
|
+
function verdictToEvent(status, intent) {
|
|
1145
|
+
const statusEventMap = {
|
|
1146
|
+
ALLOW: "action_allowed",
|
|
1147
|
+
BLOCK: "action_blocked",
|
|
1148
|
+
PAUSE: "action_paused",
|
|
1149
|
+
MODIFY: "action_modified",
|
|
1150
|
+
PENALIZE: "action_penalized",
|
|
1151
|
+
REWARD: "action_rewarded",
|
|
1152
|
+
NEUTRAL: "action_neutral"
|
|
1153
|
+
};
|
|
1154
|
+
return {
|
|
1155
|
+
type: intent || statusEventMap[status] || "unknown_action",
|
|
1156
|
+
actor: "agent",
|
|
1157
|
+
source: "guard",
|
|
1158
|
+
timestamp: Date.now(),
|
|
1159
|
+
guardStatus: status
|
|
1160
|
+
};
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
// src/loader/world-loader.ts
|
|
1164
|
+
async function loadWorldFromDirectory(dirPath) {
|
|
1165
|
+
const { readFile } = await import("fs/promises");
|
|
1166
|
+
const { join } = await import("path");
|
|
1167
|
+
const { readdirSync } = await import("fs");
|
|
1168
|
+
async function readJson(filename) {
|
|
1169
|
+
const filePath = join(dirPath, filename);
|
|
1170
|
+
try {
|
|
1171
|
+
const content = await readFile(filePath, "utf-8");
|
|
1172
|
+
return JSON.parse(content);
|
|
1173
|
+
} catch (err) {
|
|
1174
|
+
if (err instanceof Error && "code" in err && err.code === "ENOENT") {
|
|
1175
|
+
return void 0;
|
|
1176
|
+
}
|
|
1177
|
+
process.stderr.write(
|
|
1178
|
+
`[neuroverse] Warning: Failed to read ${filename}: ${err instanceof Error ? err.message : String(err)}
|
|
1179
|
+
`
|
|
1180
|
+
);
|
|
1181
|
+
return void 0;
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
const worldJson = await readJson("world.json");
|
|
1185
|
+
if (!worldJson) {
|
|
1186
|
+
throw new Error(`Cannot read world.json in ${dirPath}`);
|
|
1187
|
+
}
|
|
1188
|
+
const invariantsJson = await readJson("invariants.json");
|
|
1189
|
+
const assumptionsJson = await readJson("assumptions.json");
|
|
1190
|
+
const stateSchemaJson = await readJson("state-schema.json");
|
|
1191
|
+
const gatesJson = await readJson("gates.json");
|
|
1192
|
+
const outcomesJson = await readJson("outcomes.json");
|
|
1193
|
+
const guardsJson = await readJson("guards.json");
|
|
1194
|
+
const rolesJson = await readJson("roles.json");
|
|
1195
|
+
const kernelJson = await readJson("kernel.json");
|
|
1196
|
+
const metadataJson = await readJson("metadata.json");
|
|
1197
|
+
const rules = [];
|
|
1198
|
+
try {
|
|
1199
|
+
const rulesDir = join(dirPath, "rules");
|
|
1200
|
+
const ruleFiles = readdirSync(rulesDir).filter((f) => f.endsWith(".json")).sort();
|
|
1201
|
+
for (const file of ruleFiles) {
|
|
1202
|
+
try {
|
|
1203
|
+
const content = await readFile(join(rulesDir, file), "utf-8");
|
|
1204
|
+
rules.push(JSON.parse(content));
|
|
1205
|
+
} catch (err) {
|
|
1206
|
+
process.stderr.write(
|
|
1207
|
+
`[neuroverse] Warning: Failed to parse rule ${file}: ${err instanceof Error ? err.message : String(err)}
|
|
1208
|
+
`
|
|
1209
|
+
);
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
} catch (err) {
|
|
1213
|
+
if (!(err instanceof Error && "code" in err && err.code === "ENOENT")) {
|
|
1214
|
+
process.stderr.write(
|
|
1215
|
+
`[neuroverse] Warning: Failed to read rules directory: ${err instanceof Error ? err.message : String(err)}
|
|
1216
|
+
`
|
|
1217
|
+
);
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
return {
|
|
1221
|
+
world: worldJson,
|
|
1222
|
+
invariants: invariantsJson?.invariants ?? [],
|
|
1223
|
+
assumptions: assumptionsJson ?? { profiles: {}, parameter_definitions: {} },
|
|
1224
|
+
stateSchema: stateSchemaJson ?? { variables: {}, presets: {} },
|
|
1225
|
+
rules,
|
|
1226
|
+
gates: gatesJson ?? {
|
|
1227
|
+
viability_classification: [],
|
|
1228
|
+
structural_override: { description: "", enforcement: "mandatory" },
|
|
1229
|
+
sustainability_threshold: 0,
|
|
1230
|
+
collapse_visual: { background: "", text: "", border: "", label: "" }
|
|
1231
|
+
},
|
|
1232
|
+
outcomes: outcomesJson ?? {
|
|
1233
|
+
computed_outcomes: [],
|
|
1234
|
+
comparison_layout: { primary_card: "", status_badge: "", structural_indicators: [] }
|
|
1235
|
+
},
|
|
1236
|
+
guards: guardsJson,
|
|
1237
|
+
roles: rolesJson,
|
|
1238
|
+
kernel: kernelJson,
|
|
1239
|
+
metadata: metadataJson ?? {
|
|
1240
|
+
format_version: "1.0.0",
|
|
1241
|
+
created_at: "",
|
|
1242
|
+
last_modified: "",
|
|
1243
|
+
authoring_method: "manual-authoring"
|
|
1244
|
+
}
|
|
1245
|
+
};
|
|
1246
|
+
}
|
|
1247
|
+
async function loadWorld(worldPath) {
|
|
1248
|
+
const { stat } = await import("fs/promises");
|
|
1249
|
+
const info = await stat(worldPath);
|
|
1250
|
+
if (info.isDirectory()) {
|
|
1251
|
+
return loadWorldFromDirectory(worldPath);
|
|
1252
|
+
}
|
|
1253
|
+
throw new Error(`Cannot load world from: ${worldPath} \u2014 expected a directory`);
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
// src/adapters/shared.ts
|
|
1257
|
+
var GovernanceBlockedError = class extends Error {
|
|
1258
|
+
verdict;
|
|
1259
|
+
constructor(verdict, message) {
|
|
1260
|
+
super(message ?? `[NeuroVerse] BLOCKED: ${verdict.reason ?? verdict.ruleId ?? "governance rule"}`);
|
|
1261
|
+
this.name = "GovernanceBlockedError";
|
|
1262
|
+
this.verdict = verdict;
|
|
1263
|
+
}
|
|
1264
|
+
};
|
|
1265
|
+
function trackPlanProgress(event, state, callbacks) {
|
|
1266
|
+
if (!state.activePlan) return;
|
|
1267
|
+
const planVerdict = evaluatePlan(event, state.activePlan);
|
|
1268
|
+
if (planVerdict.matchedStep) {
|
|
1269
|
+
const advResult = advancePlan(state.activePlan, planVerdict.matchedStep);
|
|
1270
|
+
if (advResult.success && advResult.plan) {
|
|
1271
|
+
state.activePlan = advResult.plan;
|
|
1272
|
+
state.engineOptions.plan = state.activePlan;
|
|
1273
|
+
}
|
|
1274
|
+
const progress = getPlanProgress(state.activePlan);
|
|
1275
|
+
callbacks.onPlanProgress?.(progress);
|
|
1276
|
+
if (progress.completed === progress.total) {
|
|
1277
|
+
callbacks.onPlanComplete?.();
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
function buildEngineOptions(options, plan) {
|
|
1282
|
+
return {
|
|
1283
|
+
trace: options.trace ?? false,
|
|
1284
|
+
level: options.level,
|
|
1285
|
+
plan: plan ?? options.plan
|
|
1286
|
+
};
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
// src/adapters/github.ts
|
|
1290
|
+
var GitHubGovernanceBlockedError = class extends GovernanceBlockedError {
|
|
1291
|
+
action;
|
|
1292
|
+
constructor(verdict, action) {
|
|
1293
|
+
super(verdict, `[NeuroVerse] GitHub action blocked: ${action.action} on ${action.repository}`);
|
|
1294
|
+
this.name = "GitHubGovernanceBlockedError";
|
|
1295
|
+
this.action = action;
|
|
1296
|
+
}
|
|
1297
|
+
};
|
|
1298
|
+
function extractBranch(ref) {
|
|
1299
|
+
if (!ref) return void 0;
|
|
1300
|
+
if (ref.startsWith("refs/heads/")) return ref.slice("refs/heads/".length);
|
|
1301
|
+
if (ref.startsWith("refs/tags/")) return ref.slice("refs/tags/".length);
|
|
1302
|
+
return ref;
|
|
1303
|
+
}
|
|
1304
|
+
function isProtectedBranch(branch, protectedBranches) {
|
|
1305
|
+
if (!branch) return false;
|
|
1306
|
+
return protectedBranches.some(
|
|
1307
|
+
(pb) => branch === pb || branch.startsWith(`${pb}/`)
|
|
1308
|
+
);
|
|
1309
|
+
}
|
|
1310
|
+
function defaultMapAction(action, protectedBranches, restrictedActors) {
|
|
1311
|
+
const branch = action.branch ?? extractBranch(action.ref);
|
|
1312
|
+
const isProtected = isProtectedBranch(branch, protectedBranches);
|
|
1313
|
+
const isRestricted = action.actor ? restrictedActors.some((ra) => action.actor === ra || action.actor?.endsWith("[bot]")) : false;
|
|
1314
|
+
let actionCategory = "other";
|
|
1315
|
+
const act = action.action.toLowerCase();
|
|
1316
|
+
if (act.includes("read") || act.includes("get") || act.includes("list") || act.includes("view")) {
|
|
1317
|
+
actionCategory = "read";
|
|
1318
|
+
} else if (act.includes("delete") || act.includes("remove") || act.includes("close")) {
|
|
1319
|
+
actionCategory = "delete";
|
|
1320
|
+
} else if (act.includes("deploy") || act.includes("run") || act.includes("execute") || act.includes("merge")) {
|
|
1321
|
+
actionCategory = "network";
|
|
1322
|
+
} else if (act.includes("create") || act.includes("push") || act.includes("write") || act.includes("update") || act.includes("edit")) {
|
|
1323
|
+
actionCategory = "write";
|
|
1324
|
+
} else if (act.includes("comment") || act.includes("review") || act.includes("notify")) {
|
|
1325
|
+
actionCategory = "other";
|
|
1326
|
+
}
|
|
1327
|
+
return {
|
|
1328
|
+
intent: action.action,
|
|
1329
|
+
tool: "github",
|
|
1330
|
+
scope: `${action.repository}${branch ? `@${branch}` : ""}`,
|
|
1331
|
+
actionCategory,
|
|
1332
|
+
direction: "input",
|
|
1333
|
+
args: {
|
|
1334
|
+
repository: action.repository,
|
|
1335
|
+
ref: action.ref,
|
|
1336
|
+
branch,
|
|
1337
|
+
actor: action.actor,
|
|
1338
|
+
protected_branch: isProtected,
|
|
1339
|
+
restricted_actor: isRestricted,
|
|
1340
|
+
...action.metadata
|
|
1341
|
+
}
|
|
1342
|
+
};
|
|
1343
|
+
}
|
|
1344
|
+
function defaultMapWebhook(eventType, payload) {
|
|
1345
|
+
const repo = payload.repository;
|
|
1346
|
+
const repoFullName = repo?.full_name ?? "unknown/unknown";
|
|
1347
|
+
const sender = payload.sender;
|
|
1348
|
+
const actor = sender?.login ?? void 0;
|
|
1349
|
+
const webhookAction = payload.action;
|
|
1350
|
+
switch (eventType) {
|
|
1351
|
+
case "push": {
|
|
1352
|
+
const ref = payload.ref;
|
|
1353
|
+
const branch = extractBranch(ref);
|
|
1354
|
+
const forced = payload.forced;
|
|
1355
|
+
return {
|
|
1356
|
+
action: forced ? "force_push" : `push_to_${branch ?? "branch"}`,
|
|
1357
|
+
repository: repoFullName,
|
|
1358
|
+
ref,
|
|
1359
|
+
branch,
|
|
1360
|
+
actor,
|
|
1361
|
+
metadata: {
|
|
1362
|
+
forced: forced ?? false,
|
|
1363
|
+
commits_count: payload.commits?.length ?? 0,
|
|
1364
|
+
head_commit: payload.head_commit?.id
|
|
1365
|
+
}
|
|
1366
|
+
};
|
|
1367
|
+
}
|
|
1368
|
+
case "pull_request": {
|
|
1369
|
+
const pr = payload.pull_request;
|
|
1370
|
+
const base = pr?.base;
|
|
1371
|
+
const baseBranch = base?.ref;
|
|
1372
|
+
const prNumber = pr?.number;
|
|
1373
|
+
const merged = pr?.merged;
|
|
1374
|
+
const labels = pr?.labels?.map((l) => l.name) ?? [];
|
|
1375
|
+
let action = `pull_request_${webhookAction ?? "unknown"}`;
|
|
1376
|
+
if (webhookAction === "closed" && merged) {
|
|
1377
|
+
action = "merge_pull_request";
|
|
1378
|
+
}
|
|
1379
|
+
return {
|
|
1380
|
+
action,
|
|
1381
|
+
repository: repoFullName,
|
|
1382
|
+
branch: baseBranch,
|
|
1383
|
+
actor,
|
|
1384
|
+
metadata: {
|
|
1385
|
+
pr_number: prNumber,
|
|
1386
|
+
labels,
|
|
1387
|
+
merged: merged ?? false,
|
|
1388
|
+
draft: pr?.draft ?? false,
|
|
1389
|
+
webhook_action: webhookAction
|
|
1390
|
+
}
|
|
1391
|
+
};
|
|
1392
|
+
}
|
|
1393
|
+
case "release": {
|
|
1394
|
+
const release = payload.release;
|
|
1395
|
+
return {
|
|
1396
|
+
action: `release_${webhookAction ?? "published"}`,
|
|
1397
|
+
repository: repoFullName,
|
|
1398
|
+
ref: release?.tag_name ? `refs/tags/${release.tag_name}` : void 0,
|
|
1399
|
+
actor,
|
|
1400
|
+
metadata: {
|
|
1401
|
+
tag: release?.tag_name,
|
|
1402
|
+
prerelease: release?.prerelease ?? false,
|
|
1403
|
+
draft: release?.draft ?? false,
|
|
1404
|
+
webhook_action: webhookAction
|
|
1405
|
+
}
|
|
1406
|
+
};
|
|
1407
|
+
}
|
|
1408
|
+
case "deployment":
|
|
1409
|
+
case "deployment_status": {
|
|
1410
|
+
const deployment = payload.deployment ?? payload;
|
|
1411
|
+
return {
|
|
1412
|
+
action: eventType === "deployment" ? "create_deployment" : "deployment_status_update",
|
|
1413
|
+
repository: repoFullName,
|
|
1414
|
+
ref: deployment.ref,
|
|
1415
|
+
actor,
|
|
1416
|
+
metadata: {
|
|
1417
|
+
environment: deployment.environment,
|
|
1418
|
+
status: payload.deployment_status?.state,
|
|
1419
|
+
webhook_action: webhookAction
|
|
1420
|
+
}
|
|
1421
|
+
};
|
|
1422
|
+
}
|
|
1423
|
+
case "workflow_run": {
|
|
1424
|
+
const run = payload.workflow_run;
|
|
1425
|
+
return {
|
|
1426
|
+
action: `workflow_${webhookAction ?? "completed"}`,
|
|
1427
|
+
repository: repoFullName,
|
|
1428
|
+
branch: run?.head_branch,
|
|
1429
|
+
actor,
|
|
1430
|
+
metadata: {
|
|
1431
|
+
workflow_name: run?.name,
|
|
1432
|
+
conclusion: run?.conclusion,
|
|
1433
|
+
status: run?.status,
|
|
1434
|
+
webhook_action: webhookAction
|
|
1435
|
+
}
|
|
1436
|
+
};
|
|
1437
|
+
}
|
|
1438
|
+
case "issues": {
|
|
1439
|
+
const issue = payload.issue;
|
|
1440
|
+
return {
|
|
1441
|
+
action: `issue_${webhookAction ?? "opened"}`,
|
|
1442
|
+
repository: repoFullName,
|
|
1443
|
+
actor,
|
|
1444
|
+
metadata: {
|
|
1445
|
+
issue_number: issue?.number,
|
|
1446
|
+
labels: issue?.labels?.map((l) => l.name) ?? [],
|
|
1447
|
+
webhook_action: webhookAction
|
|
1448
|
+
}
|
|
1449
|
+
};
|
|
1450
|
+
}
|
|
1451
|
+
case "issue_comment": {
|
|
1452
|
+
return {
|
|
1453
|
+
action: `issue_comment_${webhookAction ?? "created"}`,
|
|
1454
|
+
repository: repoFullName,
|
|
1455
|
+
actor,
|
|
1456
|
+
metadata: {
|
|
1457
|
+
issue_number: payload.issue?.number,
|
|
1458
|
+
webhook_action: webhookAction
|
|
1459
|
+
}
|
|
1460
|
+
};
|
|
1461
|
+
}
|
|
1462
|
+
case "delete": {
|
|
1463
|
+
return {
|
|
1464
|
+
action: `delete_${payload.ref_type ?? "ref"}`,
|
|
1465
|
+
repository: repoFullName,
|
|
1466
|
+
ref: payload.ref,
|
|
1467
|
+
actor,
|
|
1468
|
+
metadata: {
|
|
1469
|
+
ref_type: payload.ref_type
|
|
1470
|
+
}
|
|
1471
|
+
};
|
|
1472
|
+
}
|
|
1473
|
+
default: {
|
|
1474
|
+
return {
|
|
1475
|
+
action: webhookAction ? `${eventType}_${webhookAction}` : eventType,
|
|
1476
|
+
repository: repoFullName,
|
|
1477
|
+
actor,
|
|
1478
|
+
metadata: { webhook_action: webhookAction }
|
|
1479
|
+
};
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
var GitHubGovernor = class {
|
|
1484
|
+
world;
|
|
1485
|
+
options;
|
|
1486
|
+
engineOptions;
|
|
1487
|
+
activePlan;
|
|
1488
|
+
protectedBranches;
|
|
1489
|
+
restrictedActors;
|
|
1490
|
+
mapFn;
|
|
1491
|
+
constructor(world, options = {}) {
|
|
1492
|
+
this.world = world;
|
|
1493
|
+
this.options = options;
|
|
1494
|
+
this.activePlan = options.plan;
|
|
1495
|
+
this.engineOptions = buildEngineOptions(options, this.activePlan);
|
|
1496
|
+
this.protectedBranches = options.protectedBranches ?? ["main", "master", "production"];
|
|
1497
|
+
this.restrictedActors = options.restrictedActors ?? [];
|
|
1498
|
+
this.mapFn = options.mapAction ?? ((action) => defaultMapAction(action, this.protectedBranches, this.restrictedActors));
|
|
1499
|
+
}
|
|
1500
|
+
/**
|
|
1501
|
+
* Evaluate a GitHub action against governance rules.
|
|
1502
|
+
* Returns a full result with verdict, event, and the original action.
|
|
1503
|
+
*/
|
|
1504
|
+
evaluate(action) {
|
|
1505
|
+
const event = this.mapFn(action);
|
|
1506
|
+
this.engineOptions.plan = this.activePlan;
|
|
1507
|
+
const verdict = evaluateGuard(event, this.world, this.engineOptions);
|
|
1508
|
+
this.options.onEvaluate?.(verdict, event, action);
|
|
1509
|
+
if (verdict.status === "ALLOW") {
|
|
1510
|
+
trackPlanProgress(event, this, this.options);
|
|
1511
|
+
}
|
|
1512
|
+
return { verdict, event, action };
|
|
1513
|
+
}
|
|
1514
|
+
/**
|
|
1515
|
+
* Evaluate and enforce — throws GitHubGovernanceBlockedError on BLOCK/PAUSE.
|
|
1516
|
+
* Use this as a gate before executing GitHub API calls.
|
|
1517
|
+
*/
|
|
1518
|
+
enforce(action) {
|
|
1519
|
+
const result = this.evaluate(action);
|
|
1520
|
+
if (result.verdict.status === "BLOCK" || result.verdict.status === "PAUSE") {
|
|
1521
|
+
throw new GitHubGovernanceBlockedError(result.verdict, action);
|
|
1522
|
+
}
|
|
1523
|
+
return result;
|
|
1524
|
+
}
|
|
1525
|
+
/**
|
|
1526
|
+
* Check if pushing to a branch is allowed.
|
|
1527
|
+
* Convenience method for the most common governance check.
|
|
1528
|
+
*/
|
|
1529
|
+
canPush(repository, branch, actor) {
|
|
1530
|
+
return this.evaluate({
|
|
1531
|
+
action: `push_to_${branch}`,
|
|
1532
|
+
repository,
|
|
1533
|
+
ref: `refs/heads/${branch}`,
|
|
1534
|
+
branch,
|
|
1535
|
+
actor
|
|
1536
|
+
}).verdict;
|
|
1537
|
+
}
|
|
1538
|
+
/**
|
|
1539
|
+
* Check if merging a PR is allowed.
|
|
1540
|
+
*/
|
|
1541
|
+
canMerge(repository, targetBranch, prNumber, actor, labels) {
|
|
1542
|
+
return this.evaluate({
|
|
1543
|
+
action: "merge_pull_request",
|
|
1544
|
+
repository,
|
|
1545
|
+
branch: targetBranch,
|
|
1546
|
+
actor,
|
|
1547
|
+
metadata: { pr_number: prNumber, labels: labels ?? [] }
|
|
1548
|
+
}).verdict;
|
|
1549
|
+
}
|
|
1550
|
+
/**
|
|
1551
|
+
* Check if creating a release is allowed.
|
|
1552
|
+
*/
|
|
1553
|
+
canRelease(repository, tag, actor, prerelease) {
|
|
1554
|
+
return this.evaluate({
|
|
1555
|
+
action: "release_published",
|
|
1556
|
+
repository,
|
|
1557
|
+
ref: `refs/tags/${tag}`,
|
|
1558
|
+
actor,
|
|
1559
|
+
metadata: { tag, prerelease: prerelease ?? false }
|
|
1560
|
+
}).verdict;
|
|
1561
|
+
}
|
|
1562
|
+
/**
|
|
1563
|
+
* Check if deploying to an environment is allowed.
|
|
1564
|
+
*/
|
|
1565
|
+
canDeploy(repository, environment, ref, actor) {
|
|
1566
|
+
return this.evaluate({
|
|
1567
|
+
action: "create_deployment",
|
|
1568
|
+
repository,
|
|
1569
|
+
ref,
|
|
1570
|
+
actor,
|
|
1571
|
+
metadata: { environment }
|
|
1572
|
+
}).verdict;
|
|
1573
|
+
}
|
|
1574
|
+
};
|
|
1575
|
+
var GitHubWebhookHandler = class {
|
|
1576
|
+
governor;
|
|
1577
|
+
mapWebhookFn;
|
|
1578
|
+
webhookSecret;
|
|
1579
|
+
constructor(world, options = {}) {
|
|
1580
|
+
this.governor = new GitHubGovernor(world, options);
|
|
1581
|
+
this.mapWebhookFn = options.mapWebhook ?? defaultMapWebhook;
|
|
1582
|
+
this.webhookSecret = options.webhookSecret;
|
|
1583
|
+
}
|
|
1584
|
+
/**
|
|
1585
|
+
* Evaluate a webhook payload.
|
|
1586
|
+
*
|
|
1587
|
+
* @param eventType - The X-GitHub-Event header value
|
|
1588
|
+
* @param payload - The parsed webhook body
|
|
1589
|
+
*/
|
|
1590
|
+
evaluate(eventType, payload) {
|
|
1591
|
+
const action = this.mapWebhookFn(eventType, payload);
|
|
1592
|
+
const result = this.governor.evaluate(action);
|
|
1593
|
+
return {
|
|
1594
|
+
verdict: result.verdict,
|
|
1595
|
+
event: result.event,
|
|
1596
|
+
webhookEvent: eventType,
|
|
1597
|
+
webhookAction: payload.action
|
|
1598
|
+
};
|
|
1599
|
+
}
|
|
1600
|
+
/**
|
|
1601
|
+
* Evaluate and enforce — throws on BLOCK/PAUSE.
|
|
1602
|
+
*/
|
|
1603
|
+
enforce(eventType, payload) {
|
|
1604
|
+
const result = this.evaluate(eventType, payload);
|
|
1605
|
+
if (result.verdict.status === "BLOCK" || result.verdict.status === "PAUSE") {
|
|
1606
|
+
const action = this.mapWebhookFn(eventType, payload);
|
|
1607
|
+
throw new GitHubGovernanceBlockedError(result.verdict, action);
|
|
1608
|
+
}
|
|
1609
|
+
return result;
|
|
1610
|
+
}
|
|
1611
|
+
/** Access the underlying governor for direct action evaluation. */
|
|
1612
|
+
getGovernor() {
|
|
1613
|
+
return this.governor;
|
|
1614
|
+
}
|
|
1615
|
+
/** Get the configured webhook secret (for signature verification in your server). */
|
|
1616
|
+
getWebhookSecret() {
|
|
1617
|
+
return this.webhookSecret;
|
|
1618
|
+
}
|
|
1619
|
+
};
|
|
1620
|
+
function formatForActions(verdict) {
|
|
1621
|
+
const status = verdict.status === "ALLOW" ? "allowed" : verdict.status === "BLOCK" ? "blocked" : "paused";
|
|
1622
|
+
const reason = verdict.reason ?? "";
|
|
1623
|
+
const ruleId = verdict.ruleId ?? "";
|
|
1624
|
+
const lines = [
|
|
1625
|
+
`governance_status=${status}`,
|
|
1626
|
+
`verdict_status=${verdict.status}`,
|
|
1627
|
+
`reason=${reason}`,
|
|
1628
|
+
`rule_id=${ruleId}`
|
|
1629
|
+
].join("\n");
|
|
1630
|
+
return {
|
|
1631
|
+
governance_status: status,
|
|
1632
|
+
verdict_status: verdict.status,
|
|
1633
|
+
reason,
|
|
1634
|
+
rule_id: ruleId,
|
|
1635
|
+
outputLines: lines
|
|
1636
|
+
};
|
|
1637
|
+
}
|
|
1638
|
+
function formatPRComment(verdict, action) {
|
|
1639
|
+
const icon = verdict.status === "ALLOW" ? "\u2705" : verdict.status === "BLOCK" ? "\u{1F6AB}" : "\u23F8\uFE0F";
|
|
1640
|
+
const status = verdict.status;
|
|
1641
|
+
let body = `## ${icon} Governance: ${status}
|
|
1642
|
+
|
|
1643
|
+
`;
|
|
1644
|
+
body += `**Action:** \`${action.action}\`
|
|
1645
|
+
`;
|
|
1646
|
+
body += `**Repository:** \`${action.repository}\`
|
|
1647
|
+
`;
|
|
1648
|
+
if (action.branch) {
|
|
1649
|
+
body += `**Branch:** \`${action.branch}\`
|
|
1650
|
+
`;
|
|
1651
|
+
}
|
|
1652
|
+
if (action.actor) {
|
|
1653
|
+
body += `**Actor:** \`${action.actor}\`
|
|
1654
|
+
`;
|
|
1655
|
+
}
|
|
1656
|
+
body += "\n";
|
|
1657
|
+
if (verdict.reason) {
|
|
1658
|
+
body += `**Reason:** ${verdict.reason}
|
|
1659
|
+
`;
|
|
1660
|
+
}
|
|
1661
|
+
if (verdict.ruleId) {
|
|
1662
|
+
body += `**Rule:** \`${verdict.ruleId}\`
|
|
1663
|
+
`;
|
|
1664
|
+
}
|
|
1665
|
+
if (verdict.evidence?.invariantsSatisfied < verdict.evidence?.invariantsTotal) {
|
|
1666
|
+
body += `**Invariants:** ${verdict.evidence.invariantsSatisfied}/${verdict.evidence.invariantsTotal} satisfied
|
|
1667
|
+
`;
|
|
1668
|
+
}
|
|
1669
|
+
body += "\n---\n*Evaluated by [NeuroVerse Governance](https://github.com/NeuroverseOS/neuroverseos-governance)*";
|
|
1670
|
+
return body;
|
|
1671
|
+
}
|
|
1672
|
+
async function createGitHubGovernor(worldPath, options) {
|
|
1673
|
+
const world = await loadWorld(worldPath);
|
|
1674
|
+
return new GitHubGovernor(world, options);
|
|
1675
|
+
}
|
|
1676
|
+
function createGitHubGovernorFromWorld(world, options) {
|
|
1677
|
+
return new GitHubGovernor(world, options);
|
|
1678
|
+
}
|
|
1679
|
+
async function createGitHubWebhookHandler(worldPath, options) {
|
|
1680
|
+
const world = await loadWorld(worldPath);
|
|
1681
|
+
return new GitHubWebhookHandler(world, options);
|
|
1682
|
+
}
|
|
1683
|
+
function createGitHubWebhookHandlerFromWorld(world, options) {
|
|
1684
|
+
return new GitHubWebhookHandler(world, options);
|
|
1685
|
+
}
|
|
1686
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1687
|
+
0 && (module.exports = {
|
|
1688
|
+
GitHubGovernanceBlockedError,
|
|
1689
|
+
GitHubGovernor,
|
|
1690
|
+
GitHubWebhookHandler,
|
|
1691
|
+
createGitHubGovernor,
|
|
1692
|
+
createGitHubGovernorFromWorld,
|
|
1693
|
+
createGitHubWebhookHandler,
|
|
1694
|
+
createGitHubWebhookHandlerFromWorld,
|
|
1695
|
+
formatForActions,
|
|
1696
|
+
formatPRComment
|
|
1697
|
+
});
|