@manifesto-ai/governance 3.1.2 → 3.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -5
- package/dist/chunk-JLXJCLOD.js +1020 -0
- package/dist/chunk-JLXJCLOD.js.map +1 -0
- package/dist/index.d.ts +3 -15
- package/dist/index.js +16 -1018
- package/dist/index.js.map +1 -1
- package/dist/provider.d.ts +15 -0
- package/dist/provider.js +73 -0
- package/dist/provider.js.map +1 -0
- package/dist/runtime-types.d.ts +7 -8
- package/dist/service/governance-service.d.ts +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/with-governance.d.ts +1 -1
- package/package.json +11 -5
|
@@ -0,0 +1,1020 @@
|
|
|
1
|
+
// src/types.ts
|
|
2
|
+
function createProposalId(value) {
|
|
3
|
+
return value ?? `prop-${crypto.randomUUID()}`;
|
|
4
|
+
}
|
|
5
|
+
function createDecisionId(value) {
|
|
6
|
+
return value ?? `dec-${crypto.randomUUID()}`;
|
|
7
|
+
}
|
|
8
|
+
function createExecutionKey(proposalId, attempt = 1) {
|
|
9
|
+
return `${proposalId}:${attempt}`;
|
|
10
|
+
}
|
|
11
|
+
var defaultExecutionKeyPolicy = ({
|
|
12
|
+
proposalId,
|
|
13
|
+
attempt
|
|
14
|
+
}) => createExecutionKey(proposalId, attempt);
|
|
15
|
+
function createNoopGovernanceEventSink() {
|
|
16
|
+
return {
|
|
17
|
+
emit() {
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function toHostIntent(intent) {
|
|
22
|
+
if ("body" in intent) {
|
|
23
|
+
return {
|
|
24
|
+
type: intent.body.type,
|
|
25
|
+
input: intent.body.input,
|
|
26
|
+
intentId: intent.intentId
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
type: intent.type,
|
|
31
|
+
input: intent.input,
|
|
32
|
+
intentId: intent.intentId
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// src/state-machine.ts
|
|
37
|
+
var INGRESS_STATUSES = ["submitted", "evaluating"];
|
|
38
|
+
var EXECUTION_STAGE_STATUSES = ["approved", "executing"];
|
|
39
|
+
var TERMINAL_STATUSES = ["rejected", "completed", "failed", "superseded"];
|
|
40
|
+
var DECISION_TRANSITION_TARGETS = ["approved", "rejected"];
|
|
41
|
+
var VALID_TRANSITIONS = {
|
|
42
|
+
submitted: ["evaluating", "rejected", "superseded"],
|
|
43
|
+
evaluating: ["approved", "rejected", "superseded"],
|
|
44
|
+
approved: ["executing"],
|
|
45
|
+
rejected: [],
|
|
46
|
+
executing: ["completed", "failed"],
|
|
47
|
+
completed: [],
|
|
48
|
+
failed: [],
|
|
49
|
+
superseded: []
|
|
50
|
+
};
|
|
51
|
+
function isIngressStatus(status) {
|
|
52
|
+
return INGRESS_STATUSES.includes(status);
|
|
53
|
+
}
|
|
54
|
+
function isExecutionStageStatus(status) {
|
|
55
|
+
return EXECUTION_STAGE_STATUSES.includes(status);
|
|
56
|
+
}
|
|
57
|
+
function isTerminalStatus(status) {
|
|
58
|
+
return TERMINAL_STATUSES.includes(status);
|
|
59
|
+
}
|
|
60
|
+
function isValidTransition(from, to) {
|
|
61
|
+
return VALID_TRANSITIONS[from].includes(to);
|
|
62
|
+
}
|
|
63
|
+
function getValidTransitions(status) {
|
|
64
|
+
return [...VALID_TRANSITIONS[status]];
|
|
65
|
+
}
|
|
66
|
+
function transitionCreatesDecisionRecord(from, to) {
|
|
67
|
+
return from === "submitted" && to === "rejected" || from === "evaluating" && to === "approved" || from === "evaluating" && to === "rejected";
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// src/store/in-memory-governance-store.ts
|
|
71
|
+
function cloneValue(value) {
|
|
72
|
+
return structuredClone(value);
|
|
73
|
+
}
|
|
74
|
+
var InMemoryGovernanceStore = class {
|
|
75
|
+
proposals = /* @__PURE__ */ new Map();
|
|
76
|
+
decisions = /* @__PURE__ */ new Map();
|
|
77
|
+
actorBindings = /* @__PURE__ */ new Map();
|
|
78
|
+
async putProposal(proposal) {
|
|
79
|
+
this.proposals.set(proposal.proposalId, cloneValue(proposal));
|
|
80
|
+
}
|
|
81
|
+
async getProposal(proposalId) {
|
|
82
|
+
return cloneValue(this.proposals.get(proposalId) ?? null);
|
|
83
|
+
}
|
|
84
|
+
async getProposalsByBranch(branchId) {
|
|
85
|
+
return [...this.proposals.values()].filter((proposal) => proposal.branchId === branchId).sort((left, right) => {
|
|
86
|
+
if (left.submittedAt !== right.submittedAt) {
|
|
87
|
+
return left.submittedAt - right.submittedAt;
|
|
88
|
+
}
|
|
89
|
+
return left.proposalId.localeCompare(right.proposalId);
|
|
90
|
+
}).map((proposal) => cloneValue(proposal));
|
|
91
|
+
}
|
|
92
|
+
async getExecutionStageProposal(branchId) {
|
|
93
|
+
const matches = (await this.getProposalsByBranch(branchId)).filter(
|
|
94
|
+
(proposal) => isExecutionStageStatus(proposal.status)
|
|
95
|
+
);
|
|
96
|
+
if (matches.length > 1) {
|
|
97
|
+
throw new Error(
|
|
98
|
+
`GOV-STORE-4 violation: multiple execution-stage proposals found for branch ${branchId}`
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
return matches[0] ?? null;
|
|
102
|
+
}
|
|
103
|
+
async putDecisionRecord(record) {
|
|
104
|
+
this.decisions.set(record.decisionId, cloneValue(record));
|
|
105
|
+
}
|
|
106
|
+
async getDecisionRecord(decisionId) {
|
|
107
|
+
return cloneValue(this.decisions.get(decisionId) ?? null);
|
|
108
|
+
}
|
|
109
|
+
async putActorBinding(binding) {
|
|
110
|
+
this.actorBindings.set(binding.actorId, cloneValue(binding));
|
|
111
|
+
}
|
|
112
|
+
async getActorBinding(actorId) {
|
|
113
|
+
return cloneValue(this.actorBindings.get(actorId) ?? null);
|
|
114
|
+
}
|
|
115
|
+
async getActorBindings() {
|
|
116
|
+
return [...this.actorBindings.values()].sort((left, right) => left.actorId.localeCompare(right.actorId)).map((binding) => cloneValue(binding));
|
|
117
|
+
}
|
|
118
|
+
snapshotState() {
|
|
119
|
+
return {
|
|
120
|
+
proposals: cloneValue(this.proposals),
|
|
121
|
+
decisions: cloneValue(this.decisions),
|
|
122
|
+
actorBindings: cloneValue(this.actorBindings)
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
restoreState(state) {
|
|
126
|
+
this.proposals.clear();
|
|
127
|
+
for (const [proposalId, proposal] of state.proposals) {
|
|
128
|
+
this.proposals.set(proposalId, cloneValue(proposal));
|
|
129
|
+
}
|
|
130
|
+
this.decisions.clear();
|
|
131
|
+
for (const [decisionId, record] of state.decisions) {
|
|
132
|
+
this.decisions.set(decisionId, cloneValue(record));
|
|
133
|
+
}
|
|
134
|
+
this.actorBindings.clear();
|
|
135
|
+
for (const [actorId, binding] of state.actorBindings) {
|
|
136
|
+
this.actorBindings.set(actorId, cloneValue(binding));
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
function createInMemoryGovernanceStore() {
|
|
141
|
+
return new InMemoryGovernanceStore();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// src/authority/auto.ts
|
|
145
|
+
var AutoApproveHandler = class {
|
|
146
|
+
async evaluate(proposal, binding) {
|
|
147
|
+
if (binding.policy.mode !== "auto_approve") {
|
|
148
|
+
throw new Error(
|
|
149
|
+
`AutoApproveHandler received non-auto_approve policy: ${binding.policy.mode}`
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
return {
|
|
153
|
+
kind: "approved",
|
|
154
|
+
approvedScope: proposal.intent.scopeProposal ?? null
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
function createAutoApproveHandler() {
|
|
159
|
+
return new AutoApproveHandler();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// src/authority/hitl.ts
|
|
163
|
+
var HITLHandler = class {
|
|
164
|
+
pendingDecisions = /* @__PURE__ */ new Map();
|
|
165
|
+
notificationCallbacks = /* @__PURE__ */ new Set();
|
|
166
|
+
onPendingDecision(callback) {
|
|
167
|
+
this.notificationCallbacks.add(callback);
|
|
168
|
+
return () => {
|
|
169
|
+
this.notificationCallbacks.delete(callback);
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
async evaluate(proposal, binding) {
|
|
173
|
+
if (binding.policy.mode !== "hitl") {
|
|
174
|
+
throw new Error(`HITLHandler received non-hitl policy: ${binding.policy.mode}`);
|
|
175
|
+
}
|
|
176
|
+
const policy = binding.policy;
|
|
177
|
+
const proposalId = proposal.proposalId;
|
|
178
|
+
if (this.pendingDecisions.has(proposalId)) {
|
|
179
|
+
throw new Error(`Proposal ${proposalId} already has a pending HITL decision`);
|
|
180
|
+
}
|
|
181
|
+
return new Promise((resolve, reject) => {
|
|
182
|
+
const state = {
|
|
183
|
+
proposalId,
|
|
184
|
+
proposal,
|
|
185
|
+
resolve,
|
|
186
|
+
reject
|
|
187
|
+
};
|
|
188
|
+
if (policy.timeout != null) {
|
|
189
|
+
state.timeoutId = setTimeout(() => {
|
|
190
|
+
this.pendingDecisions.delete(proposalId);
|
|
191
|
+
if (policy.onTimeout === "approve") {
|
|
192
|
+
resolve({
|
|
193
|
+
kind: "approved",
|
|
194
|
+
approvedScope: proposal.intent.scopeProposal ?? null
|
|
195
|
+
});
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
reject(
|
|
199
|
+
new Error(
|
|
200
|
+
`HITL decision timed out after ${policy.timeout}ms for proposal '${proposalId}'`
|
|
201
|
+
)
|
|
202
|
+
);
|
|
203
|
+
}, policy.timeout);
|
|
204
|
+
}
|
|
205
|
+
this.pendingDecisions.set(proposalId, state);
|
|
206
|
+
for (const callback of this.notificationCallbacks) {
|
|
207
|
+
callback(proposalId, proposal, binding);
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
submitDecision(proposalId, decision, reasoning, approvedScope) {
|
|
212
|
+
const state = this.pendingDecisions.get(proposalId);
|
|
213
|
+
if (!state) {
|
|
214
|
+
throw new Error(`No pending HITL decision for proposal ${proposalId}`);
|
|
215
|
+
}
|
|
216
|
+
if (state.timeoutId) {
|
|
217
|
+
clearTimeout(state.timeoutId);
|
|
218
|
+
}
|
|
219
|
+
this.pendingDecisions.delete(proposalId);
|
|
220
|
+
if (decision === "approved") {
|
|
221
|
+
state.resolve({
|
|
222
|
+
kind: "approved",
|
|
223
|
+
approvedScope: approvedScope !== void 0 ? approvedScope : state.proposal?.intent.scopeProposal ?? null
|
|
224
|
+
});
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
state.resolve({
|
|
228
|
+
kind: "rejected",
|
|
229
|
+
reason: reasoning ?? "Human rejected"
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
isPending(proposalId) {
|
|
233
|
+
return this.pendingDecisions.has(proposalId);
|
|
234
|
+
}
|
|
235
|
+
getPendingIds() {
|
|
236
|
+
return [...this.pendingDecisions.keys()];
|
|
237
|
+
}
|
|
238
|
+
clearAllPending() {
|
|
239
|
+
for (const [proposalId, state] of this.pendingDecisions) {
|
|
240
|
+
if (state.timeoutId) {
|
|
241
|
+
clearTimeout(state.timeoutId);
|
|
242
|
+
}
|
|
243
|
+
state.reject(new Error(`HITL handler cleared pending proposal ${proposalId}`));
|
|
244
|
+
}
|
|
245
|
+
this.pendingDecisions.clear();
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
function createHITLHandler() {
|
|
249
|
+
return new HITLHandler();
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// src/authority/policy.ts
|
|
253
|
+
var PolicyRulesHandler = class {
|
|
254
|
+
customEvaluators = /* @__PURE__ */ new Map();
|
|
255
|
+
registerCustomEvaluator(name, evaluator) {
|
|
256
|
+
this.customEvaluators.set(name, evaluator);
|
|
257
|
+
}
|
|
258
|
+
async evaluate(proposal, binding) {
|
|
259
|
+
if (binding.policy.mode !== "policy_rules") {
|
|
260
|
+
throw new Error(
|
|
261
|
+
`PolicyRulesHandler received non-policy_rules policy: ${binding.policy.mode}`
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
const approvedScope = proposal.intent.scopeProposal ?? null;
|
|
265
|
+
for (const rule of binding.policy.rules) {
|
|
266
|
+
if (this.evaluateCondition(rule.condition, proposal, binding)) {
|
|
267
|
+
return this.applyDecision(rule, approvedScope);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return this.applyDecision(
|
|
271
|
+
{
|
|
272
|
+
decision: binding.policy.defaultDecision,
|
|
273
|
+
reason: "Default policy decision"
|
|
274
|
+
},
|
|
275
|
+
approvedScope
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
evaluateCondition(condition, proposal, binding) {
|
|
279
|
+
switch (condition.kind) {
|
|
280
|
+
case "intent_type":
|
|
281
|
+
return condition.types.includes(proposal.intent.type);
|
|
282
|
+
case "scope_pattern":
|
|
283
|
+
return this.matchPattern(proposal.intent.type, condition.pattern);
|
|
284
|
+
case "custom": {
|
|
285
|
+
const evaluator = this.customEvaluators.get(condition.evaluator);
|
|
286
|
+
return evaluator ? evaluator(proposal, binding) : false;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
matchPattern(value, pattern) {
|
|
291
|
+
const regex = new RegExp(
|
|
292
|
+
`^${pattern.replace(/\*/g, ".*").replace(/\?/g, ".")}$`
|
|
293
|
+
);
|
|
294
|
+
return regex.test(value);
|
|
295
|
+
}
|
|
296
|
+
applyDecision(rule, approvedScope) {
|
|
297
|
+
switch (rule.decision) {
|
|
298
|
+
case "approve":
|
|
299
|
+
return { kind: "approved", approvedScope };
|
|
300
|
+
case "reject":
|
|
301
|
+
return { kind: "rejected", reason: rule.reason ?? "Policy rejection" };
|
|
302
|
+
case "escalate":
|
|
303
|
+
return {
|
|
304
|
+
kind: "rejected",
|
|
305
|
+
reason: rule.reason ?? "Policy requires escalation (not implemented)"
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
function createPolicyRulesHandler() {
|
|
311
|
+
return new PolicyRulesHandler();
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// src/authority/tribunal.ts
|
|
315
|
+
var TribunalHandler = class {
|
|
316
|
+
pendingTribunals = /* @__PURE__ */ new Map();
|
|
317
|
+
notificationCallback;
|
|
318
|
+
onPendingTribunal(callback) {
|
|
319
|
+
this.notificationCallback = callback;
|
|
320
|
+
}
|
|
321
|
+
async evaluate(proposal, binding) {
|
|
322
|
+
if (binding.policy.mode !== "tribunal") {
|
|
323
|
+
throw new Error(
|
|
324
|
+
`TribunalHandler received non-tribunal policy: ${binding.policy.mode}`
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
const policy = binding.policy;
|
|
328
|
+
const proposalId = proposal.proposalId;
|
|
329
|
+
if (this.pendingTribunals.has(proposalId)) {
|
|
330
|
+
throw new Error(`Proposal ${proposalId} already has a pending tribunal`);
|
|
331
|
+
}
|
|
332
|
+
return new Promise((resolve, reject) => {
|
|
333
|
+
const state = {
|
|
334
|
+
proposalId,
|
|
335
|
+
proposal,
|
|
336
|
+
binding,
|
|
337
|
+
votes: /* @__PURE__ */ new Map(),
|
|
338
|
+
resolve,
|
|
339
|
+
reject
|
|
340
|
+
};
|
|
341
|
+
if (policy.timeout != null) {
|
|
342
|
+
state.timeoutId = setTimeout(() => {
|
|
343
|
+
this.pendingTribunals.delete(proposalId);
|
|
344
|
+
if (policy.onTimeout === "approve") {
|
|
345
|
+
resolve({
|
|
346
|
+
kind: "approved",
|
|
347
|
+
approvedScope: proposal.intent.scopeProposal ?? null
|
|
348
|
+
});
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
reject(
|
|
352
|
+
new Error(
|
|
353
|
+
`Tribunal decision timed out after ${policy.timeout}ms for proposal '${proposalId}'`
|
|
354
|
+
)
|
|
355
|
+
);
|
|
356
|
+
}, policy.timeout);
|
|
357
|
+
}
|
|
358
|
+
this.pendingTribunals.set(proposalId, state);
|
|
359
|
+
this.notificationCallback?.(
|
|
360
|
+
proposalId,
|
|
361
|
+
proposal,
|
|
362
|
+
policy.members
|
|
363
|
+
);
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
submitVote(proposalId, voter, decision, reasoning) {
|
|
367
|
+
const state = this.pendingTribunals.get(proposalId);
|
|
368
|
+
if (!state) {
|
|
369
|
+
throw new Error(`No pending tribunal for proposal ${proposalId}`);
|
|
370
|
+
}
|
|
371
|
+
if (state.votes.has(voter.actorId)) {
|
|
372
|
+
throw new Error(`Actor ${voter.actorId} already voted on proposal ${proposalId}`);
|
|
373
|
+
}
|
|
374
|
+
const member = state.binding.policy.mode === "tribunal" ? state.binding.policy.members.some(({ actorId }) => actorId === voter.actorId) : false;
|
|
375
|
+
if (!member) {
|
|
376
|
+
throw new Error(`Actor ${voter.actorId} is not a tribunal member for proposal ${proposalId}`);
|
|
377
|
+
}
|
|
378
|
+
state.votes.set(voter.actorId, {
|
|
379
|
+
voter,
|
|
380
|
+
decision,
|
|
381
|
+
reasoning,
|
|
382
|
+
votedAt: Date.now()
|
|
383
|
+
});
|
|
384
|
+
this.checkQuorum(state);
|
|
385
|
+
}
|
|
386
|
+
isPending(proposalId) {
|
|
387
|
+
return this.pendingTribunals.has(proposalId);
|
|
388
|
+
}
|
|
389
|
+
getVotes(proposalId) {
|
|
390
|
+
const state = this.pendingTribunals.get(proposalId);
|
|
391
|
+
return state ? [...state.votes.values()] : [];
|
|
392
|
+
}
|
|
393
|
+
getPendingIds() {
|
|
394
|
+
return [...this.pendingTribunals.keys()];
|
|
395
|
+
}
|
|
396
|
+
clearAllPending() {
|
|
397
|
+
for (const [proposalId, state] of this.pendingTribunals) {
|
|
398
|
+
if (state.timeoutId) {
|
|
399
|
+
clearTimeout(state.timeoutId);
|
|
400
|
+
}
|
|
401
|
+
state.reject(new Error(`Tribunal handler cleared pending proposal ${proposalId}`));
|
|
402
|
+
}
|
|
403
|
+
this.pendingTribunals.clear();
|
|
404
|
+
}
|
|
405
|
+
checkQuorum(state) {
|
|
406
|
+
const policy = state.binding.policy;
|
|
407
|
+
if (policy.mode !== "tribunal") {
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
const memberCount = policy.members.length;
|
|
411
|
+
let approveCount = 0;
|
|
412
|
+
let rejectCount = 0;
|
|
413
|
+
for (const vote of state.votes.values()) {
|
|
414
|
+
if (vote.decision === "approve") {
|
|
415
|
+
approveCount++;
|
|
416
|
+
} else if (vote.decision === "reject") {
|
|
417
|
+
rejectCount++;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
let isComplete = false;
|
|
421
|
+
let isApproved = false;
|
|
422
|
+
switch (policy.quorum.kind) {
|
|
423
|
+
case "unanimous":
|
|
424
|
+
if (approveCount === memberCount) {
|
|
425
|
+
isComplete = true;
|
|
426
|
+
isApproved = true;
|
|
427
|
+
} else if (rejectCount > 0 || state.votes.size === memberCount) {
|
|
428
|
+
isComplete = true;
|
|
429
|
+
}
|
|
430
|
+
break;
|
|
431
|
+
case "majority": {
|
|
432
|
+
const majorityNeeded = Math.floor(memberCount / 2) + 1;
|
|
433
|
+
if (approveCount >= majorityNeeded) {
|
|
434
|
+
isComplete = true;
|
|
435
|
+
isApproved = true;
|
|
436
|
+
} else if (rejectCount >= majorityNeeded) {
|
|
437
|
+
isComplete = true;
|
|
438
|
+
} else if (state.votes.size === memberCount) {
|
|
439
|
+
isComplete = true;
|
|
440
|
+
isApproved = approveCount > rejectCount;
|
|
441
|
+
}
|
|
442
|
+
break;
|
|
443
|
+
}
|
|
444
|
+
case "threshold":
|
|
445
|
+
if (approveCount >= policy.quorum.count) {
|
|
446
|
+
isComplete = true;
|
|
447
|
+
isApproved = true;
|
|
448
|
+
} else if (rejectCount > memberCount - policy.quorum.count) {
|
|
449
|
+
isComplete = true;
|
|
450
|
+
} else if (state.votes.size === memberCount) {
|
|
451
|
+
isComplete = true;
|
|
452
|
+
isApproved = approveCount >= policy.quorum.count;
|
|
453
|
+
}
|
|
454
|
+
break;
|
|
455
|
+
}
|
|
456
|
+
if (!isComplete) {
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
if (state.timeoutId) {
|
|
460
|
+
clearTimeout(state.timeoutId);
|
|
461
|
+
}
|
|
462
|
+
this.pendingTribunals.delete(state.proposalId);
|
|
463
|
+
if (isApproved) {
|
|
464
|
+
state.resolve({
|
|
465
|
+
kind: "approved",
|
|
466
|
+
approvedScope: state.proposal.intent.scopeProposal ?? null
|
|
467
|
+
});
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
state.resolve({
|
|
471
|
+
kind: "rejected",
|
|
472
|
+
reason: `Tribunal rejected (${approveCount}/${memberCount} approved)`
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
};
|
|
476
|
+
function createTribunalHandler() {
|
|
477
|
+
return new TribunalHandler();
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// src/authority/evaluator.ts
|
|
481
|
+
var POLICY_MODE_TO_KIND = {
|
|
482
|
+
auto_approve: "auto",
|
|
483
|
+
hitl: "human",
|
|
484
|
+
policy_rules: "policy",
|
|
485
|
+
tribunal: "tribunal"
|
|
486
|
+
};
|
|
487
|
+
var AuthorityEvaluator = class {
|
|
488
|
+
handlers = /* @__PURE__ */ new Map();
|
|
489
|
+
autoHandler;
|
|
490
|
+
policyHandler;
|
|
491
|
+
hitlHandler;
|
|
492
|
+
tribunalHandler;
|
|
493
|
+
constructor() {
|
|
494
|
+
this.autoHandler = createAutoApproveHandler();
|
|
495
|
+
this.policyHandler = createPolicyRulesHandler();
|
|
496
|
+
this.hitlHandler = createHITLHandler();
|
|
497
|
+
this.tribunalHandler = createTribunalHandler();
|
|
498
|
+
this.handlers.set("auto_approve", this.autoHandler);
|
|
499
|
+
this.handlers.set("hitl", this.hitlHandler);
|
|
500
|
+
this.handlers.set("policy_rules", this.policyHandler);
|
|
501
|
+
this.handlers.set("tribunal", this.tribunalHandler);
|
|
502
|
+
}
|
|
503
|
+
async evaluate(proposal, binding) {
|
|
504
|
+
const handler = this.handlers.get(binding.policy.mode);
|
|
505
|
+
if (!handler) {
|
|
506
|
+
throw new Error(`Unknown policy mode: ${binding.policy.mode}`);
|
|
507
|
+
}
|
|
508
|
+
return handler.evaluate(proposal, binding);
|
|
509
|
+
}
|
|
510
|
+
registerHandler(policyMode, handler) {
|
|
511
|
+
this.handlers.set(policyMode, handler);
|
|
512
|
+
}
|
|
513
|
+
getAutoHandler() {
|
|
514
|
+
return this.autoHandler;
|
|
515
|
+
}
|
|
516
|
+
getPolicyHandler() {
|
|
517
|
+
return this.policyHandler;
|
|
518
|
+
}
|
|
519
|
+
getHITLHandler() {
|
|
520
|
+
return this.hitlHandler;
|
|
521
|
+
}
|
|
522
|
+
getTribunalHandler() {
|
|
523
|
+
return this.tribunalHandler;
|
|
524
|
+
}
|
|
525
|
+
getAuthorityKind(policyMode) {
|
|
526
|
+
return POLICY_MODE_TO_KIND[policyMode] ?? null;
|
|
527
|
+
}
|
|
528
|
+
submitHITLDecision(proposalId, decision, reasoning, approvedScope) {
|
|
529
|
+
this.hitlHandler.submitDecision(proposalId, decision, reasoning, approvedScope);
|
|
530
|
+
}
|
|
531
|
+
submitTribunalVote(proposalId, voter, decision, reasoning) {
|
|
532
|
+
this.tribunalHandler.submitVote(proposalId, voter, decision, reasoning);
|
|
533
|
+
}
|
|
534
|
+
hasPendingHITL() {
|
|
535
|
+
return this.hitlHandler.getPendingIds().length > 0;
|
|
536
|
+
}
|
|
537
|
+
hasPendingTribunal() {
|
|
538
|
+
return this.tribunalHandler.getPendingIds().length > 0;
|
|
539
|
+
}
|
|
540
|
+
getPendingHITLIds() {
|
|
541
|
+
return this.hitlHandler.getPendingIds();
|
|
542
|
+
}
|
|
543
|
+
getPendingTribunalIds() {
|
|
544
|
+
return this.tribunalHandler.getPendingIds();
|
|
545
|
+
}
|
|
546
|
+
clearAllPending() {
|
|
547
|
+
this.hitlHandler.clearAllPending();
|
|
548
|
+
this.tribunalHandler.clearAllPending();
|
|
549
|
+
}
|
|
550
|
+
};
|
|
551
|
+
function createAuthorityEvaluator() {
|
|
552
|
+
return new AuthorityEvaluator();
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// src/event-dispatcher.ts
|
|
556
|
+
function createGovernanceEventDispatcher(options) {
|
|
557
|
+
const sink = options.sink ?? createNoopGovernanceEventSink();
|
|
558
|
+
const now = options.now ?? Date.now;
|
|
559
|
+
return {
|
|
560
|
+
emitSealCompleted(governanceCommit, lineageCommit) {
|
|
561
|
+
const timestamp = now();
|
|
562
|
+
const outcome = governanceCommit.proposal.status === "completed" ? "completed" : "failed";
|
|
563
|
+
sink.emit(
|
|
564
|
+
options.service.createWorldCreatedEvent(
|
|
565
|
+
lineageCommit.world,
|
|
566
|
+
governanceCommit.proposal.proposalId,
|
|
567
|
+
deriveWorldCreatedFrom(governanceCommit, lineageCommit),
|
|
568
|
+
outcome,
|
|
569
|
+
timestamp
|
|
570
|
+
)
|
|
571
|
+
);
|
|
572
|
+
if (isTrueForkCommit(lineageCommit)) {
|
|
573
|
+
sink.emit(
|
|
574
|
+
options.service.createWorldForkedEvent(
|
|
575
|
+
governanceCommit.proposal.branchId,
|
|
576
|
+
lineageCommit.edge.from,
|
|
577
|
+
timestamp
|
|
578
|
+
)
|
|
579
|
+
);
|
|
580
|
+
}
|
|
581
|
+
if (outcome === "completed") {
|
|
582
|
+
sink.emit(options.service.createExecutionCompletedEvent(governanceCommit.proposal, timestamp));
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
sink.emit(
|
|
586
|
+
options.service.createExecutionFailedEvent(
|
|
587
|
+
governanceCommit.proposal,
|
|
588
|
+
deriveExecutionFailure(lineageCommit),
|
|
589
|
+
timestamp
|
|
590
|
+
)
|
|
591
|
+
);
|
|
592
|
+
}
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
function deriveWorldCreatedFrom(governanceCommit, lineageCommit) {
|
|
596
|
+
if (lineageCommit.kind === "next") {
|
|
597
|
+
return lineageCommit.edge.from;
|
|
598
|
+
}
|
|
599
|
+
return lineageCommit.world.parentWorldId ?? governanceCommit.proposal.baseWorld;
|
|
600
|
+
}
|
|
601
|
+
function deriveExecutionFailure(lineageCommit) {
|
|
602
|
+
const currentError = lineageCommit.terminalSnapshot.system.lastError ?? void 0;
|
|
603
|
+
const pendingRequirements = lineageCommit.terminalSnapshot.system.pendingRequirements.map(
|
|
604
|
+
(requirement) => requirement.id
|
|
605
|
+
);
|
|
606
|
+
return {
|
|
607
|
+
summary: summarizeFailure(currentError ? 1 : 0, pendingRequirements.length),
|
|
608
|
+
...currentError ? { currentError } : {},
|
|
609
|
+
...pendingRequirements.length > 0 ? { pendingRequirements } : {}
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
function isTrueForkCommit(lineageCommit) {
|
|
613
|
+
return lineageCommit.kind === "next" && "forkCreated" in lineageCommit && lineageCommit.forkCreated === true;
|
|
614
|
+
}
|
|
615
|
+
function summarizeFailure(errorCount, pendingRequirementCount) {
|
|
616
|
+
if (errorCount > 0 && pendingRequirementCount > 0) {
|
|
617
|
+
return `Execution failed with ${errorCount} error(s) and ${pendingRequirementCount} pending requirement(s)`;
|
|
618
|
+
}
|
|
619
|
+
if (errorCount > 0) {
|
|
620
|
+
return `Execution failed with ${errorCount} error(s)`;
|
|
621
|
+
}
|
|
622
|
+
if (pendingRequirementCount > 0) {
|
|
623
|
+
return `Execution failed with ${pendingRequirementCount} pending requirement(s)`;
|
|
624
|
+
}
|
|
625
|
+
return "Execution failed";
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// src/intent-instance.ts
|
|
629
|
+
import { sha256, toJcs } from "@manifesto-ai/core";
|
|
630
|
+
async function computeIntentKey(schemaHash, body) {
|
|
631
|
+
const input = [
|
|
632
|
+
schemaHash,
|
|
633
|
+
body.type,
|
|
634
|
+
toJcs(body.input ?? null),
|
|
635
|
+
toJcs(body.scopeProposal ?? null)
|
|
636
|
+
].join(":");
|
|
637
|
+
return sha256(input);
|
|
638
|
+
}
|
|
639
|
+
async function createIntentInstance(options) {
|
|
640
|
+
const intentId = options.intentId ?? `intent-${crypto.randomUUID()}`;
|
|
641
|
+
const intentKey = await computeIntentKey(options.schemaHash, options.body);
|
|
642
|
+
return createIntentInstanceSync(
|
|
643
|
+
options.body,
|
|
644
|
+
intentId,
|
|
645
|
+
intentKey,
|
|
646
|
+
{
|
|
647
|
+
projectionId: options.projectionId,
|
|
648
|
+
source: options.source,
|
|
649
|
+
actor: options.actor,
|
|
650
|
+
note: options.note
|
|
651
|
+
}
|
|
652
|
+
);
|
|
653
|
+
}
|
|
654
|
+
function createIntentInstanceSync(body, intentId, intentKey, origin) {
|
|
655
|
+
return deepFreeze({
|
|
656
|
+
body,
|
|
657
|
+
intentId,
|
|
658
|
+
intentKey,
|
|
659
|
+
meta: {
|
|
660
|
+
origin
|
|
661
|
+
}
|
|
662
|
+
});
|
|
663
|
+
}
|
|
664
|
+
function deepFreeze(value) {
|
|
665
|
+
if (value === null || typeof value !== "object") {
|
|
666
|
+
return value;
|
|
667
|
+
}
|
|
668
|
+
for (const key of Object.getOwnPropertyNames(value)) {
|
|
669
|
+
const nested = value[key];
|
|
670
|
+
if (nested !== null && typeof nested === "object") {
|
|
671
|
+
deepFreeze(nested);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
return Object.freeze(value);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
// src/service/governance-service.ts
|
|
678
|
+
function freeze(value) {
|
|
679
|
+
return Object.freeze(value);
|
|
680
|
+
}
|
|
681
|
+
var DefaultGovernanceService = class {
|
|
682
|
+
constructor(store, options = {}) {
|
|
683
|
+
this.store = store;
|
|
684
|
+
this.options = options;
|
|
685
|
+
}
|
|
686
|
+
createProposal(input) {
|
|
687
|
+
return freeze({
|
|
688
|
+
proposalId: input.proposalId ?? createProposalId(),
|
|
689
|
+
baseWorld: input.baseWorld,
|
|
690
|
+
branchId: input.branchId,
|
|
691
|
+
actorId: input.actorId,
|
|
692
|
+
authorityId: input.authorityId,
|
|
693
|
+
intent: freeze({ ...input.intent }),
|
|
694
|
+
status: "submitted",
|
|
695
|
+
executionKey: input.executionKey,
|
|
696
|
+
submittedAt: input.submittedAt,
|
|
697
|
+
epoch: input.epoch
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
beginEvaluating(proposal) {
|
|
701
|
+
return this.transitionProposal(proposal, "evaluating");
|
|
702
|
+
}
|
|
703
|
+
beginExecution(proposal) {
|
|
704
|
+
if (!proposal.decisionId) {
|
|
705
|
+
throw new Error(
|
|
706
|
+
"GOV-EXEC-1 violation: approved proposal requires decisionId before execution can begin"
|
|
707
|
+
);
|
|
708
|
+
}
|
|
709
|
+
return this.transitionProposal(proposal, "executing");
|
|
710
|
+
}
|
|
711
|
+
failExecution(proposal, completedAt, resultWorld) {
|
|
712
|
+
return this.transitionProposal(proposal, "failed", {
|
|
713
|
+
completedAt,
|
|
714
|
+
...resultWorld !== void 0 ? { resultWorld } : {}
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
async prepareAuthorityResult(proposal, response, options) {
|
|
718
|
+
if (proposal.status !== "submitted" && proposal.status !== "evaluating") {
|
|
719
|
+
throw new Error(
|
|
720
|
+
`GOV-TRANS-1 violation: authority result requires ingress proposal, received ${proposal.status}`
|
|
721
|
+
);
|
|
722
|
+
}
|
|
723
|
+
const branchInfo = await this.resolveBranchInfo(proposal.branchId);
|
|
724
|
+
const currentEpoch = options.currentEpoch ?? branchInfo?.epoch ?? proposal.epoch;
|
|
725
|
+
const currentHead = options.currentBranchHead ?? branchInfo?.head ?? proposal.baseWorld;
|
|
726
|
+
if (this.shouldDiscardAuthorityResult(proposal, currentEpoch)) {
|
|
727
|
+
return {
|
|
728
|
+
proposal: this.prepareSupersede(proposal, "head_advance"),
|
|
729
|
+
discarded: true
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
if (response.kind === "approved") {
|
|
733
|
+
await this.assertBranchGateAvailable(proposal);
|
|
734
|
+
if (currentHead !== proposal.baseWorld) {
|
|
735
|
+
return {
|
|
736
|
+
proposal: this.prepareSupersede(proposal, "head_advance"),
|
|
737
|
+
discarded: true
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
const decisionRecord2 = freeze({
|
|
741
|
+
decisionId: options.decisionId ?? createDecisionId(),
|
|
742
|
+
proposalId: proposal.proposalId,
|
|
743
|
+
authorityId: proposal.authorityId,
|
|
744
|
+
decision: freeze({ kind: "approved" }),
|
|
745
|
+
decidedAt: options.decidedAt
|
|
746
|
+
});
|
|
747
|
+
return {
|
|
748
|
+
proposal: this.transitionProposal(proposal, "approved", {
|
|
749
|
+
decisionId: decisionRecord2.decisionId,
|
|
750
|
+
decidedAt: decisionRecord2.decidedAt,
|
|
751
|
+
approvedScope: response.approvedScope
|
|
752
|
+
}),
|
|
753
|
+
decisionRecord: decisionRecord2,
|
|
754
|
+
discarded: false
|
|
755
|
+
};
|
|
756
|
+
}
|
|
757
|
+
const decisionRecord = freeze({
|
|
758
|
+
decisionId: options.decisionId ?? createDecisionId(),
|
|
759
|
+
proposalId: proposal.proposalId,
|
|
760
|
+
authorityId: proposal.authorityId,
|
|
761
|
+
decision: freeze({
|
|
762
|
+
kind: "rejected",
|
|
763
|
+
...response.reason ? { reason: response.reason } : {}
|
|
764
|
+
}),
|
|
765
|
+
decidedAt: options.decidedAt
|
|
766
|
+
});
|
|
767
|
+
return {
|
|
768
|
+
proposal: this.transitionProposal(proposal, "rejected", {
|
|
769
|
+
decisionId: decisionRecord.decisionId,
|
|
770
|
+
decidedAt: decisionRecord.decidedAt
|
|
771
|
+
}),
|
|
772
|
+
decisionRecord,
|
|
773
|
+
discarded: false
|
|
774
|
+
};
|
|
775
|
+
}
|
|
776
|
+
prepareSupersede(proposal, reason) {
|
|
777
|
+
return this.transitionProposal(proposal, "superseded", {
|
|
778
|
+
supersededReason: reason
|
|
779
|
+
});
|
|
780
|
+
}
|
|
781
|
+
async invalidateStaleIngress(branchId, currentEpoch) {
|
|
782
|
+
const branchInfo = await this.resolveBranchInfo(branchId);
|
|
783
|
+
const nextEpoch = currentEpoch ?? branchInfo?.epoch;
|
|
784
|
+
if (nextEpoch == null) {
|
|
785
|
+
throw new Error(`Cannot invalidate stale ingress without branch epoch for ${branchId}`);
|
|
786
|
+
}
|
|
787
|
+
return (await this.store.getProposalsByBranch(branchId)).filter((proposal) => isIngressStatus(proposal.status) && proposal.epoch < nextEpoch).map((proposal) => this.prepareSupersede(proposal, "head_advance"));
|
|
788
|
+
}
|
|
789
|
+
shouldDiscardAuthorityResult(proposal, currentEpoch) {
|
|
790
|
+
return proposal.epoch < currentEpoch;
|
|
791
|
+
}
|
|
792
|
+
deriveOutcome(terminalSnapshot) {
|
|
793
|
+
if (terminalSnapshot.system.lastError != null) {
|
|
794
|
+
return "failed";
|
|
795
|
+
}
|
|
796
|
+
if (terminalSnapshot.system.pendingRequirements.length > 0) {
|
|
797
|
+
return "failed";
|
|
798
|
+
}
|
|
799
|
+
return "completed";
|
|
800
|
+
}
|
|
801
|
+
async finalize(executingProposal, lineageCommit, completedAt) {
|
|
802
|
+
if (executingProposal.status !== "executing") {
|
|
803
|
+
throw new Error(
|
|
804
|
+
`GOV-SEAL-6 violation: finalize() requires executing proposal, received ${executingProposal.status}`
|
|
805
|
+
);
|
|
806
|
+
}
|
|
807
|
+
if (!executingProposal.decisionId) {
|
|
808
|
+
throw new Error("GOV-SEAL-6 violation: executing proposal is missing decisionId");
|
|
809
|
+
}
|
|
810
|
+
const decisionRecord = await this.store.getDecisionRecord(executingProposal.decisionId);
|
|
811
|
+
if (!decisionRecord) {
|
|
812
|
+
throw new Error(
|
|
813
|
+
`GOV-SEAL-6 violation: decision record ${executingProposal.decisionId} not found`
|
|
814
|
+
);
|
|
815
|
+
}
|
|
816
|
+
const derivedOutcome = this.deriveOutcome(lineageCommit.terminalSnapshot);
|
|
817
|
+
if (derivedOutcome !== lineageCommit.terminalStatus) {
|
|
818
|
+
throw new Error(
|
|
819
|
+
`GOV-SEAL-1 violation: deriveOutcome=${derivedOutcome} but lineageCommit.terminalStatus=${lineageCommit.terminalStatus}`
|
|
820
|
+
);
|
|
821
|
+
}
|
|
822
|
+
const proposal = this.transitionProposal(executingProposal, derivedOutcome, {
|
|
823
|
+
resultWorld: lineageCommit.worldId,
|
|
824
|
+
completedAt
|
|
825
|
+
});
|
|
826
|
+
return freeze({
|
|
827
|
+
proposal,
|
|
828
|
+
decisionRecord
|
|
829
|
+
});
|
|
830
|
+
}
|
|
831
|
+
createProposalSubmittedEvent(proposal, timestamp = Date.now()) {
|
|
832
|
+
return freeze({
|
|
833
|
+
type: "proposal:submitted",
|
|
834
|
+
timestamp,
|
|
835
|
+
proposalId: proposal.proposalId,
|
|
836
|
+
actorId: proposal.actorId,
|
|
837
|
+
baseWorld: proposal.baseWorld,
|
|
838
|
+
branchId: proposal.branchId,
|
|
839
|
+
intent: freeze({
|
|
840
|
+
type: proposal.intent.type,
|
|
841
|
+
intentId: proposal.intent.intentId,
|
|
842
|
+
...proposal.intent.input !== void 0 ? { input: proposal.intent.input } : {}
|
|
843
|
+
}),
|
|
844
|
+
executionKey: proposal.executionKey,
|
|
845
|
+
epoch: proposal.epoch
|
|
846
|
+
});
|
|
847
|
+
}
|
|
848
|
+
createProposalEvaluatingEvent(proposal, timestamp = Date.now()) {
|
|
849
|
+
return freeze({
|
|
850
|
+
type: "proposal:evaluating",
|
|
851
|
+
timestamp,
|
|
852
|
+
proposalId: proposal.proposalId
|
|
853
|
+
});
|
|
854
|
+
}
|
|
855
|
+
createProposalDecidedEvent(proposal, decisionRecord, timestamp = Date.now()) {
|
|
856
|
+
return freeze({
|
|
857
|
+
type: "proposal:decided",
|
|
858
|
+
timestamp,
|
|
859
|
+
proposalId: proposal.proposalId,
|
|
860
|
+
decisionId: decisionRecord.decisionId,
|
|
861
|
+
decision: decisionRecord.decision.kind,
|
|
862
|
+
...decisionRecord.decision.kind === "rejected" && decisionRecord.decision.reason ? { reason: decisionRecord.decision.reason } : {}
|
|
863
|
+
});
|
|
864
|
+
}
|
|
865
|
+
createProposalSupersededEvent(proposal, currentEpoch, timestamp = Date.now()) {
|
|
866
|
+
if (proposal.status !== "superseded" || !proposal.supersededReason) {
|
|
867
|
+
throw new Error(
|
|
868
|
+
"GOV-EPOCH-5 violation: superseded event requires proposal.status='superseded' with supersededReason"
|
|
869
|
+
);
|
|
870
|
+
}
|
|
871
|
+
return freeze({
|
|
872
|
+
type: "proposal:superseded",
|
|
873
|
+
timestamp,
|
|
874
|
+
proposalId: proposal.proposalId,
|
|
875
|
+
currentEpoch,
|
|
876
|
+
proposalEpoch: proposal.epoch,
|
|
877
|
+
reason: proposal.supersededReason
|
|
878
|
+
});
|
|
879
|
+
}
|
|
880
|
+
createExecutionCompletedEvent(proposal, timestamp = Date.now()) {
|
|
881
|
+
if (!proposal.resultWorld) {
|
|
882
|
+
throw new Error(
|
|
883
|
+
"GOV-EVT-6 violation: execution:completed requires proposal.resultWorld"
|
|
884
|
+
);
|
|
885
|
+
}
|
|
886
|
+
return freeze({
|
|
887
|
+
type: "execution:completed",
|
|
888
|
+
timestamp,
|
|
889
|
+
proposalId: proposal.proposalId,
|
|
890
|
+
executionKey: proposal.executionKey,
|
|
891
|
+
resultWorld: proposal.resultWorld
|
|
892
|
+
});
|
|
893
|
+
}
|
|
894
|
+
createExecutionFailedEvent(proposal, error, timestamp = Date.now()) {
|
|
895
|
+
if (!proposal.resultWorld) {
|
|
896
|
+
throw new Error(
|
|
897
|
+
"GOV-EVT-7 violation: execution:failed requires proposal.resultWorld"
|
|
898
|
+
);
|
|
899
|
+
}
|
|
900
|
+
return freeze({
|
|
901
|
+
type: "execution:failed",
|
|
902
|
+
timestamp,
|
|
903
|
+
proposalId: proposal.proposalId,
|
|
904
|
+
executionKey: proposal.executionKey,
|
|
905
|
+
resultWorld: proposal.resultWorld,
|
|
906
|
+
error: freeze({
|
|
907
|
+
summary: error.summary,
|
|
908
|
+
...error.currentError !== void 0 ? { currentError: error.currentError } : {},
|
|
909
|
+
...error.pendingRequirements !== void 0 ? { pendingRequirements: error.pendingRequirements } : {}
|
|
910
|
+
})
|
|
911
|
+
});
|
|
912
|
+
}
|
|
913
|
+
createWorldCreatedEvent(world, proposalId, from, outcome, timestamp = Date.now()) {
|
|
914
|
+
return freeze({
|
|
915
|
+
type: "world:created",
|
|
916
|
+
timestamp,
|
|
917
|
+
world,
|
|
918
|
+
from,
|
|
919
|
+
proposalId,
|
|
920
|
+
outcome
|
|
921
|
+
});
|
|
922
|
+
}
|
|
923
|
+
createWorldForkedEvent(branchId, forkPoint, timestamp = Date.now()) {
|
|
924
|
+
return freeze({
|
|
925
|
+
type: "world:forked",
|
|
926
|
+
timestamp,
|
|
927
|
+
branchId,
|
|
928
|
+
forkPoint
|
|
929
|
+
});
|
|
930
|
+
}
|
|
931
|
+
async resolveBranchInfo(branchId) {
|
|
932
|
+
return this.options.lineageService?.getBranch(branchId) ?? null;
|
|
933
|
+
}
|
|
934
|
+
async assertBranchGateAvailable(proposal) {
|
|
935
|
+
const occupant = await this.store.getExecutionStageProposal(proposal.branchId);
|
|
936
|
+
if (occupant && occupant.proposalId !== proposal.proposalId) {
|
|
937
|
+
throw new Error(
|
|
938
|
+
`GOV-BRANCH-GATE-1 violation: branch ${proposal.branchId} already occupied by ${occupant.proposalId}`
|
|
939
|
+
);
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
transitionProposal(proposal, to, updates = {}) {
|
|
943
|
+
if (!isValidTransition(proposal.status, to)) {
|
|
944
|
+
throw new Error(
|
|
945
|
+
`GOV-TRANS-1 violation: invalid transition ${proposal.status} -> ${to}; valid targets are ${getValidTransitions(proposal.status).join(", ")}`
|
|
946
|
+
);
|
|
947
|
+
}
|
|
948
|
+
if (to === "superseded") {
|
|
949
|
+
if (updates.decisionId != null) {
|
|
950
|
+
throw new Error("GOV-TRANS-3 violation: superseded transition must not create DecisionRecord");
|
|
951
|
+
}
|
|
952
|
+
if (!updates.supersededReason) {
|
|
953
|
+
throw new Error("GOV-STAGE-7 violation: superseded proposal must record supersededReason");
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
if (transitionCreatesDecisionRecord(proposal.status, to) && updates.decisionId == null) {
|
|
957
|
+
throw new Error(
|
|
958
|
+
`GOV-TRANS-2 violation: transition ${proposal.status} -> ${to} requires decisionId`
|
|
959
|
+
);
|
|
960
|
+
}
|
|
961
|
+
if (to !== "superseded" && updates.supersededReason != null) {
|
|
962
|
+
throw new Error("GOV-TRANS-4 violation: supersededReason is only valid on superseded proposals");
|
|
963
|
+
}
|
|
964
|
+
if (isExecutionStageStatus(proposal.status) && to === "superseded") {
|
|
965
|
+
throw new Error("GOV-STAGE-4 violation: execution-stage proposals must not be superseded");
|
|
966
|
+
}
|
|
967
|
+
return freeze({
|
|
968
|
+
...proposal,
|
|
969
|
+
status: to,
|
|
970
|
+
...updates.decisionId !== void 0 ? { decisionId: updates.decisionId } : {},
|
|
971
|
+
...updates.decidedAt !== void 0 ? { decidedAt: updates.decidedAt } : {},
|
|
972
|
+
...updates.completedAt !== void 0 ? { completedAt: updates.completedAt } : {},
|
|
973
|
+
...updates.resultWorld !== void 0 ? { resultWorld: updates.resultWorld } : {},
|
|
974
|
+
...updates.approvedScope !== void 0 ? { approvedScope: updates.approvedScope } : {},
|
|
975
|
+
...updates.supersededReason !== void 0 ? { supersededReason: updates.supersededReason } : {},
|
|
976
|
+
...to !== "superseded" ? { supersededReason: void 0 } : {}
|
|
977
|
+
});
|
|
978
|
+
}
|
|
979
|
+
};
|
|
980
|
+
function createGovernanceService(store, options) {
|
|
981
|
+
return new DefaultGovernanceService(store, options);
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
export {
|
|
985
|
+
createProposalId,
|
|
986
|
+
createDecisionId,
|
|
987
|
+
createExecutionKey,
|
|
988
|
+
defaultExecutionKeyPolicy,
|
|
989
|
+
createNoopGovernanceEventSink,
|
|
990
|
+
toHostIntent,
|
|
991
|
+
INGRESS_STATUSES,
|
|
992
|
+
EXECUTION_STAGE_STATUSES,
|
|
993
|
+
TERMINAL_STATUSES,
|
|
994
|
+
DECISION_TRANSITION_TARGETS,
|
|
995
|
+
isIngressStatus,
|
|
996
|
+
isExecutionStageStatus,
|
|
997
|
+
isTerminalStatus,
|
|
998
|
+
isValidTransition,
|
|
999
|
+
getValidTransitions,
|
|
1000
|
+
transitionCreatesDecisionRecord,
|
|
1001
|
+
InMemoryGovernanceStore,
|
|
1002
|
+
createInMemoryGovernanceStore,
|
|
1003
|
+
AutoApproveHandler,
|
|
1004
|
+
createAutoApproveHandler,
|
|
1005
|
+
HITLHandler,
|
|
1006
|
+
createHITLHandler,
|
|
1007
|
+
PolicyRulesHandler,
|
|
1008
|
+
createPolicyRulesHandler,
|
|
1009
|
+
TribunalHandler,
|
|
1010
|
+
createTribunalHandler,
|
|
1011
|
+
AuthorityEvaluator,
|
|
1012
|
+
createAuthorityEvaluator,
|
|
1013
|
+
createGovernanceEventDispatcher,
|
|
1014
|
+
computeIntentKey,
|
|
1015
|
+
createIntentInstance,
|
|
1016
|
+
createIntentInstanceSync,
|
|
1017
|
+
DefaultGovernanceService,
|
|
1018
|
+
createGovernanceService
|
|
1019
|
+
};
|
|
1020
|
+
//# sourceMappingURL=chunk-JLXJCLOD.js.map
|