@askdkc/kiokuko 0.2.3 → 0.2.4
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.ja.md +25 -5
- package/README.ko.md +24 -5
- package/README.md +29 -5
- package/README.zh-CN.md +21 -4
- package/dist/agent-file/render.d.ts +1 -1
- package/dist/agent-file/render.d.ts.map +1 -1
- package/dist/agent-file/render.js +5 -3
- package/dist/agent-file/render.js.map +1 -1
- package/dist/akinator/agent-task.js +2 -2
- package/dist/akinator/agent-task.js.map +1 -1
- package/dist/akinator/capabilities.d.ts +5 -1
- package/dist/akinator/capabilities.d.ts.map +1 -1
- package/dist/akinator/capabilities.js +20 -3
- package/dist/akinator/capabilities.js.map +1 -1
- package/dist/commands/agent.d.ts.map +1 -1
- package/dist/commands/agent.js +36 -9
- package/dist/commands/agent.js.map +1 -1
- package/dist/commands/doctor.d.ts +1 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +33 -0
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/commands/setup.js +2 -1
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/use.d.ts +1 -0
- package/dist/commands/use.d.ts.map +1 -1
- package/dist/commands/use.js +76 -16
- package/dist/commands/use.js.map +1 -1
- package/dist/enno-oduno/adapters.d.ts +4 -1
- package/dist/enno-oduno/adapters.d.ts.map +1 -1
- package/dist/enno-oduno/adapters.js +63 -14
- package/dist/enno-oduno/adapters.js.map +1 -1
- package/dist/enno-oduno/advisory.d.ts.map +1 -1
- package/dist/enno-oduno/advisory.js +133 -26
- package/dist/enno-oduno/advisory.js.map +1 -1
- package/dist/enno-oduno/confirmation.d.ts.map +1 -1
- package/dist/enno-oduno/confirmation.js +3 -0
- package/dist/enno-oduno/confirmation.js.map +1 -1
- package/dist/enno-oduno/directives.d.ts.map +1 -1
- package/dist/enno-oduno/directives.js +63 -34
- package/dist/enno-oduno/directives.js.map +1 -1
- package/dist/enno-oduno/experts.d.ts +1 -1
- package/dist/enno-oduno/experts.d.ts.map +1 -1
- package/dist/enno-oduno/experts.js +21 -7
- package/dist/enno-oduno/experts.js.map +1 -1
- package/dist/enno-oduno/instructions.d.ts +5 -4
- package/dist/enno-oduno/instructions.d.ts.map +1 -1
- package/dist/enno-oduno/instructions.js +4 -3
- package/dist/enno-oduno/instructions.js.map +1 -1
- package/dist/enno-oduno/plan-recovery.d.ts +15 -0
- package/dist/enno-oduno/plan-recovery.d.ts.map +1 -1
- package/dist/enno-oduno/plan-recovery.js +26 -0
- package/dist/enno-oduno/plan-recovery.js.map +1 -1
- package/dist/enno-oduno/repository-state.d.ts +8 -0
- package/dist/enno-oduno/repository-state.d.ts.map +1 -0
- package/dist/enno-oduno/repository-state.js +139 -0
- package/dist/enno-oduno/repository-state.js.map +1 -0
- package/dist/enno-oduno/role-runner.d.ts.map +1 -1
- package/dist/enno-oduno/role-runner.js +34 -1
- package/dist/enno-oduno/role-runner.js.map +1 -1
- package/dist/enno-oduno/sanitize.d.ts +17 -0
- package/dist/enno-oduno/sanitize.d.ts.map +1 -0
- package/dist/enno-oduno/sanitize.js +88 -0
- package/dist/enno-oduno/sanitize.js.map +1 -0
- package/dist/enno-oduno/schemas.d.ts +185 -36
- package/dist/enno-oduno/schemas.d.ts.map +1 -1
- package/dist/enno-oduno/schemas.js +172 -46
- package/dist/enno-oduno/schemas.js.map +1 -1
- package/dist/enno-oduno/service.d.ts +2 -1
- package/dist/enno-oduno/service.d.ts.map +1 -1
- package/dist/enno-oduno/service.js +310 -137
- package/dist/enno-oduno/service.js.map +1 -1
- package/dist/enno-oduno/store.d.ts +37 -4
- package/dist/enno-oduno/store.d.ts.map +1 -1
- package/dist/enno-oduno/store.js +343 -36
- package/dist/enno-oduno/store.js.map +1 -1
- package/dist/enno-oduno/types.d.ts +55 -2
- package/dist/enno-oduno/types.d.ts.map +1 -1
- package/dist/enno-oduno/types.js +1 -0
- package/dist/enno-oduno/types.js.map +1 -1
- package/dist/enno-oduno/validation-errors.d.ts +92 -0
- package/dist/enno-oduno/validation-errors.d.ts.map +1 -0
- package/dist/enno-oduno/validation-errors.js +151 -0
- package/dist/enno-oduno/validation-errors.js.map +1 -0
- package/dist/enno-oduno/verifier.d.ts +1 -0
- package/dist/enno-oduno/verifier.d.ts.map +1 -1
- package/dist/enno-oduno/verifier.js +70 -12
- package/dist/enno-oduno/verifier.js.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +70 -16
- package/dist/mcp/server.js.map +1 -1
- package/dist/server/routes/agent-capability-gate.js +2 -2
- package/dist/server/routes/agent-capability-gate.js.map +1 -1
- package/dist/setup/render.js +1 -1
- package/dist/setup/render.js.map +1 -1
- package/dist/setup/standard-skills.d.ts +12 -5
- package/dist/setup/standard-skills.d.ts.map +1 -1
- package/dist/setup/standard-skills.js +12 -2
- package/dist/setup/standard-skills.js.map +1 -1
- package/migrations/019_enno_execution_integrity.sql +149 -0
- package/package.json +1 -1
- package/skills/kiokuko-enno-oduno/SKILL.md +19 -8
- package/skills/kiokuko-single-purpose-functions/SKILL.md +18 -10
- package/skills/kiokuko-single-purpose-functions/references/problem-shaping-and-language.md +84 -0
- package/skills/kiokuko-ui-design-soul/SKILL.md +5 -1
- package/skills/memory-reasoning/SKILL.md +52 -0
- package/templates/AGENTS.md +6 -4
|
@@ -12,17 +12,20 @@ import { canonicalContentHash, canonicalJson } from '../serialization/validate.j
|
|
|
12
12
|
import { sanitizeJson } from '../security/sanitize.js';
|
|
13
13
|
import { LedgerStore } from '../ledger/store.js';
|
|
14
14
|
import { directiveForIntake, directiveForRun } from './directives.js';
|
|
15
|
-
import {
|
|
15
|
+
import { advisoryContextForSnapshot, advisoryInputDigest, advisoryPhaseForStatus, normalizeAdvisoryContributions } from './advisory.js';
|
|
16
16
|
import { createAdvisoryRoundInTransaction, ensureAdvisoryRoundConsumedInTransaction, readAdvisoryRound, readSubmittedAdvisoryRound, } from './advisory-store.js';
|
|
17
17
|
import { assertWorkPlanExpertCoverage } from './experts.js';
|
|
18
18
|
import { buildEnnoRequestHandoff } from './handoff.js';
|
|
19
19
|
import { identifyEnnoClientKind } from './harness.js';
|
|
20
|
-
import { assertContractVerifierCwds, parseEnnoAnswer, parseAdviceSubmission, parseFinishRequest, parseIdealSubmission, parseMeditationSubmission,
|
|
20
|
+
import { assertContractVerifierCwds, parseEnnoAnswer, parseAdviceSubmission, parseFinishRequest, parseIdealSubmission, parseMeditationSubmission, parsePlanSubmission, parseVerificationPrepare, parseWorkReport, } from './schemas.js';
|
|
21
21
|
import { completeRequiredSkillList, createSkillSetEntries, orderedUniqueSkillNames, unavailableRequiredSkills, } from './skills.js';
|
|
22
|
-
import { appendEnnoEventInTransaction, completeOperationInTransaction, createEnnoDraft, finishVerifierRunsInTransaction, readEnnoSnapshot, readFreshFinalVerifierResults, readOperationReceipt, replaceWorkUnitsInTransaction, setWorkUnitStatusInTransaction, startOperationInTransaction, startVerifierRunsInTransaction, terminalizeLedgerRunInTransaction, updateContractInTransaction, } from './store.js';
|
|
22
|
+
import { appendEnnoEventInTransaction, assertExecutionLeaseInTransaction, claimExecutionLeaseInTransaction, completeOperationInTransaction, createEnnoDraft, finishVerifierRunsInTransaction, readEnnoSnapshot, readFreshFinalVerifierResults, readOperationReceipt, releaseExecutionLeaseInTransaction, renewExecutionLeaseInTransaction, resolveEnnoIdentity, replaceWorkUnitsInTransaction, setWorkUnitStatusInTransaction, startOperationInTransaction, startVerifierRunsInTransaction, operationLeaseMsForVerifiers, terminalizeLedgerRunInTransaction, updateContractInTransaction, } from './store.js';
|
|
23
23
|
import { ENNO_APPLICABLE_TASK_TYPES, ENNO_MAX_EXTERNAL_SKILLS, ENNO_MAX_TOTAL_SKILL_QUERIES, ENNO_PROVENANCE_KEYS, } from './types.js';
|
|
24
24
|
import { runVerifiers } from './verifier.js';
|
|
25
|
-
import { planStartRecoveryError } from './plan-recovery.js';
|
|
25
|
+
import { planStartRecoveryBlocker, planStartRecoveryError, planStartRecoveryReasonFromBlocker, } from './plan-recovery.js';
|
|
26
|
+
import { ennoValidationError } from './validation-errors.js';
|
|
27
|
+
import { captureRepositoryState } from './repository-state.js';
|
|
28
|
+
import { sanitizeEnnoAnswer, sanitizeFinishRequest, sanitizeIdealSubmission, sanitizeMeditationSubmission, sanitizePlanSubmission, sanitizeWorkReport, } from './sanitize.js';
|
|
26
29
|
import { STANDARD_FUNCTION_SKILL_NAME, STANDARD_SOUL_SKILL_NAME, STANDARD_UI_SKILL_NAME, } from '../setup/standard-skills.js';
|
|
27
30
|
function stateForSnapshot(snapshot) {
|
|
28
31
|
let nextAction;
|
|
@@ -56,11 +59,13 @@ function stateForSnapshot(snapshot) {
|
|
|
56
59
|
identified: snapshot.clientKind !== null,
|
|
57
60
|
},
|
|
58
61
|
contractRevision: snapshot.revision,
|
|
62
|
+
routeEpoch: snapshot.routeEpoch ?? 0,
|
|
59
63
|
ideal: snapshot.ideal,
|
|
60
64
|
meditation: snapshot.meditation,
|
|
61
65
|
currentRole: directive?.role ?? null,
|
|
62
66
|
directive,
|
|
63
67
|
nextAction,
|
|
68
|
+
advisoryPhaseState: snapshot.advisoryPhaseState ?? { state: 'not_started' },
|
|
64
69
|
};
|
|
65
70
|
}
|
|
66
71
|
function intakeEnnoState(input) {
|
|
@@ -76,11 +81,13 @@ function intakeEnnoState(input) {
|
|
|
76
81
|
identified: input.clientKind !== null,
|
|
77
82
|
},
|
|
78
83
|
contractRevision: null,
|
|
84
|
+
routeEpoch: null,
|
|
79
85
|
ideal: null,
|
|
80
86
|
meditation: null,
|
|
81
87
|
currentRole: 'enno-oduno',
|
|
82
88
|
directive,
|
|
83
89
|
nextAction: 'answer_intake',
|
|
90
|
+
advisoryPhaseState: { state: 'not_started' },
|
|
84
91
|
};
|
|
85
92
|
}
|
|
86
93
|
export function inapplicableEnnoState() {
|
|
@@ -90,11 +97,13 @@ export function inapplicableEnnoState() {
|
|
|
90
97
|
orchestrationId: null,
|
|
91
98
|
clientBinding: null,
|
|
92
99
|
contractRevision: null,
|
|
100
|
+
routeEpoch: null,
|
|
93
101
|
ideal: null,
|
|
94
102
|
meditation: null,
|
|
95
103
|
currentRole: null,
|
|
96
104
|
directive: null,
|
|
97
105
|
nextAction: 'complete',
|
|
106
|
+
advisoryPhaseState: { state: 'not_started' },
|
|
98
107
|
};
|
|
99
108
|
}
|
|
100
109
|
export function ennoStateForPreparedTask(database, prepared, client) {
|
|
@@ -143,20 +152,27 @@ export function ennoStateForPreparedTask(database, prepared, client) {
|
|
|
143
152
|
return stateForSnapshot(snapshot);
|
|
144
153
|
}
|
|
145
154
|
function operationIdentity(operation, idempotencyKey, input) {
|
|
155
|
+
const semanticInput = typeof input === 'object' && input !== null && !Array.isArray(input)
|
|
156
|
+
? Object.fromEntries(Object.entries(input)
|
|
157
|
+
.filter(([key]) => !['workspace', 'orchestrationId', 'resumeToken', 'idempotencyKey'].includes(key)))
|
|
158
|
+
: input;
|
|
159
|
+
const requestDigest = canonicalContentHash({ version: 1, operation, input: semanticInput });
|
|
160
|
+
const legacyRequestDigest = canonicalContentHash({ version: 1, operation, input });
|
|
146
161
|
return {
|
|
147
162
|
operation,
|
|
148
163
|
idempotencyKey,
|
|
149
|
-
requestDigest
|
|
164
|
+
requestDigest,
|
|
165
|
+
...(legacyRequestDigest === requestDigest ? {} : { legacyRequestDigests: [legacyRequestDigest] }),
|
|
150
166
|
};
|
|
151
167
|
}
|
|
152
168
|
function advisoryContextForSubmission(snapshot, phase) {
|
|
153
|
-
|
|
154
|
-
|
|
169
|
+
if (advisoryPhaseForStatus(snapshot.status) !== phase
|
|
170
|
+
|| (phase === 'final_review' && !snapshot.finalEvidenceReady)) {
|
|
155
171
|
throw new KiokukoError('CONFLICT', 'Advisory round is not valid for the current Enno phase');
|
|
156
172
|
}
|
|
157
|
-
return
|
|
173
|
+
return advisoryContextForSnapshot(snapshot, phase);
|
|
158
174
|
}
|
|
159
|
-
function assertAdvisoryRoundInput(snapshot, phase, mutationRevision, context, inputDigest) {
|
|
175
|
+
function assertAdvisoryRoundInput(snapshot, phase, mutationRevision, context, inputDigest, validationOperation) {
|
|
160
176
|
if (snapshot.mutationRevision !== mutationRevision) {
|
|
161
177
|
throw new KiokukoError('CONFLICT', 'Enno mutation revision changed');
|
|
162
178
|
}
|
|
@@ -171,11 +187,18 @@ function assertAdvisoryRoundInput(snapshot, phase, mutationRevision, context, in
|
|
|
171
187
|
allowlistedContext: context,
|
|
172
188
|
});
|
|
173
189
|
if (expectedDigest !== inputDigest) {
|
|
190
|
+
if (validationOperation !== undefined) {
|
|
191
|
+
throw ennoValidationError(validationOperation, [{
|
|
192
|
+
path: ['advisoryRoundDigest'],
|
|
193
|
+
reasonCode: 'advisory_digest_stale',
|
|
194
|
+
}], 'refresh_state');
|
|
195
|
+
}
|
|
174
196
|
throw new KiokukoError('CONFLICT', 'Advisory round digest does not match the current contract');
|
|
175
197
|
}
|
|
176
198
|
}
|
|
177
199
|
function requireAdvisoryRound(database, snapshot, phase, mutationRevision, context, inputDigest) {
|
|
178
|
-
|
|
200
|
+
const operation = phase === 'planning' ? 'plan_submit' : phase === 'ideal' ? 'ideal_submit' : 'finish';
|
|
201
|
+
assertAdvisoryRoundInput(snapshot, phase, mutationRevision, context, inputDigest, operation);
|
|
179
202
|
const round = readAdvisoryRound(database, {
|
|
180
203
|
runId: snapshot.runId,
|
|
181
204
|
contractRevision: snapshot.revision,
|
|
@@ -186,21 +209,44 @@ function requireAdvisoryRound(database, snapshot, phase, mutationRevision, conte
|
|
|
186
209
|
if (round === undefined)
|
|
187
210
|
throw new KiokukoError('CONFLICT', 'Required advisory round was not submitted');
|
|
188
211
|
}
|
|
189
|
-
function assertDispositionCoversRound(round, dispositions) {
|
|
212
|
+
function assertDispositionCoversRound(operation, round, dispositions) {
|
|
190
213
|
const expected = new Set(round.contributions.map((contribution) => contribution.slotId));
|
|
191
214
|
const covered = new Set();
|
|
192
|
-
for (const disposition of dispositions) {
|
|
215
|
+
for (const [index, disposition] of dispositions.entries()) {
|
|
193
216
|
if (!expected.has(disposition.slotId)) {
|
|
194
|
-
throw
|
|
217
|
+
throw ennoValidationError(operation, [{
|
|
218
|
+
path: ['advisoryDisposition', index, 'slotId'],
|
|
219
|
+
reasonCode: 'advisory_slot_missing',
|
|
220
|
+
expected: { requiredSlotIds: [...expected] },
|
|
221
|
+
}]);
|
|
195
222
|
}
|
|
196
223
|
if (covered.has(disposition.slotId)) {
|
|
197
|
-
throw
|
|
224
|
+
throw ennoValidationError(operation, [{
|
|
225
|
+
path: ['advisoryDisposition', index, 'slotId'],
|
|
226
|
+
reasonCode: 'advisory_slot_duplicate',
|
|
227
|
+
expected: { requiredSlotIds: [...expected] },
|
|
228
|
+
}]);
|
|
229
|
+
}
|
|
230
|
+
const contribution = round.contributions.find((item) => item.slotId === disposition.slotId);
|
|
231
|
+
const compatible = contribution.outcome === 'completed'
|
|
232
|
+
? disposition.disposition === 'adopted' || disposition.disposition === 'not_adopted'
|
|
233
|
+
: disposition.disposition === 'unavailable';
|
|
234
|
+
if (!compatible) {
|
|
235
|
+
throw ennoValidationError(operation, [{
|
|
236
|
+
path: ['advisoryDisposition', index, 'disposition'],
|
|
237
|
+
reasonCode: 'invalid_enum',
|
|
238
|
+
expected: { allowedValues: contribution.outcome === 'completed' ? ['adopted', 'not_adopted'] : ['unavailable'] },
|
|
239
|
+
}]);
|
|
198
240
|
}
|
|
199
241
|
covered.add(disposition.slotId);
|
|
200
242
|
}
|
|
201
243
|
for (const slotId of expected) {
|
|
202
244
|
if (!covered.has(slotId)) {
|
|
203
|
-
throw
|
|
245
|
+
throw ennoValidationError(operation, [{
|
|
246
|
+
path: ['advisoryDisposition'],
|
|
247
|
+
reasonCode: 'advisory_slot_missing',
|
|
248
|
+
expected: { requiredSlotIds: [...expected] },
|
|
249
|
+
}]);
|
|
204
250
|
}
|
|
205
251
|
}
|
|
206
252
|
}
|
|
@@ -213,18 +259,36 @@ function consumeAdvisoryRoundIfPresent(database, snapshot, phase, mutationRevisi
|
|
|
213
259
|
phase,
|
|
214
260
|
});
|
|
215
261
|
if (submitted !== undefined) {
|
|
216
|
-
throw
|
|
262
|
+
throw ennoValidationError(phase === 'planning' ? 'plan_submit' : phase === 'ideal' ? 'ideal_submit' : 'finish', [{
|
|
263
|
+
path: ['advisoryRoundDigest'],
|
|
264
|
+
reasonCode: 'advisory_consumption_required',
|
|
265
|
+
expected: { requiredSlotIds: submitted.contributions.map((item) => item.slotId) },
|
|
266
|
+
}]);
|
|
217
267
|
}
|
|
218
268
|
return undefined;
|
|
219
269
|
}
|
|
220
270
|
if (context === undefined) {
|
|
221
271
|
throw new KiokukoError('CONFLICT', 'Advisory round is not valid for the current Enno phase');
|
|
222
272
|
}
|
|
223
|
-
|
|
273
|
+
const operation = phase === 'planning' ? 'plan_submit' : phase === 'ideal' ? 'ideal_submit' : 'finish';
|
|
274
|
+
assertAdvisoryRoundInput(snapshot, phase, mutationRevision, context, inputDigest, operation);
|
|
224
275
|
const dispositions = disposition ?? [];
|
|
225
276
|
if (dispositions.length === 0) {
|
|
226
|
-
throw
|
|
277
|
+
throw ennoValidationError(phase === 'planning' ? 'plan_submit' : phase === 'ideal' ? 'ideal_submit' : 'finish', [{
|
|
278
|
+
path: ['advisoryDisposition'],
|
|
279
|
+
reasonCode: 'advisory_digest_requires_disposition',
|
|
280
|
+
}]);
|
|
227
281
|
}
|
|
282
|
+
const pendingRound = readAdvisoryRound(database, {
|
|
283
|
+
runId: snapshot.runId,
|
|
284
|
+
contractRevision: snapshot.revision,
|
|
285
|
+
mutationRevision,
|
|
286
|
+
phase,
|
|
287
|
+
inputDigest,
|
|
288
|
+
});
|
|
289
|
+
if (pendingRound === undefined)
|
|
290
|
+
throw new KiokukoError('CONFLICT', 'Required advisory round was not submitted');
|
|
291
|
+
assertDispositionCoversRound(operation, pendingRound, dispositions);
|
|
228
292
|
const round = ensureAdvisoryRoundConsumedInTransaction(database, {
|
|
229
293
|
runId: snapshot.runId,
|
|
230
294
|
contractRevision: snapshot.revision,
|
|
@@ -232,7 +296,6 @@ function consumeAdvisoryRoundIfPresent(database, snapshot, phase, mutationRevisi
|
|
|
232
296
|
phase,
|
|
233
297
|
inputDigest,
|
|
234
298
|
});
|
|
235
|
-
assertDispositionCoversRound(round, dispositions);
|
|
236
299
|
appendEnnoEventInTransaction(database, snapshot.runId, 'enno.advice_disposition', 'enno-oduno', 'recorded', {
|
|
237
300
|
phase,
|
|
238
301
|
contractRevision: snapshot.revision,
|
|
@@ -243,12 +306,16 @@ function consumeAdvisoryRoundIfPresent(database, snapshot, phase, mutationRevisi
|
|
|
243
306
|
return round;
|
|
244
307
|
}
|
|
245
308
|
export function submitEnnoAdvice(database, rawInput) {
|
|
246
|
-
const
|
|
309
|
+
const parsedInput = parseAdviceSubmission(rawInput);
|
|
310
|
+
const input = {
|
|
311
|
+
...parsedInput,
|
|
312
|
+
contributions: normalizeAdvisoryContributions(parsedInput.phase, parsedInput.contributions),
|
|
313
|
+
};
|
|
314
|
+
const before = readEnnoSnapshot(database, identity(database, input));
|
|
247
315
|
const operation = operationIdentity('advice_submit', input.idempotencyKey, input);
|
|
248
316
|
const replay = readOperationReceipt(database, input.runId, operation);
|
|
249
317
|
if (replay !== undefined)
|
|
250
318
|
return replay;
|
|
251
|
-
const before = readEnnoSnapshot(database, identity(input));
|
|
252
319
|
const expectedPhase = advisoryPhaseForStatus(before.status);
|
|
253
320
|
if (expectedPhase !== input.phase)
|
|
254
321
|
throw new KiokukoError('CONFLICT', 'Advisory phase does not match the current Enno state');
|
|
@@ -263,12 +330,12 @@ export function submitEnnoAdvice(database, rawInput) {
|
|
|
263
330
|
const replayed = readOperationReceipt(database, input.runId, operation);
|
|
264
331
|
if (replayed !== undefined)
|
|
265
332
|
return replayed;
|
|
266
|
-
const current = readEnnoSnapshot(database, identity(input));
|
|
333
|
+
const current = readEnnoSnapshot(database, identity(database, input));
|
|
267
334
|
assertExpected(current, input.expectedRevision, [
|
|
268
335
|
input.phase === 'ideal' ? 'oduno_ideal' : input.phase === 'planning' ? 'zenki_planning' : 'enno_verifying',
|
|
269
336
|
]);
|
|
270
337
|
assertAdvisoryRoundInput(current, input.phase, input.mutationRevision, context, inputDigest);
|
|
271
|
-
startOperationInTransaction(database, input.runId, operation);
|
|
338
|
+
const operationOwner = startOperationInTransaction(database, input.runId, operation);
|
|
272
339
|
const existing = readAdvisoryRound(database, {
|
|
273
340
|
runId: input.runId,
|
|
274
341
|
contractRevision: current.revision,
|
|
@@ -301,17 +368,16 @@ export function submitEnnoAdvice(database, rawInput) {
|
|
|
301
368
|
inputDigest,
|
|
302
369
|
degraded: round.degraded,
|
|
303
370
|
});
|
|
304
|
-
const response = {
|
|
305
|
-
|
|
371
|
+
const response = {
|
|
372
|
+
ennoOduno: stateForSnapshot(readEnnoSnapshot(database, identity(database, input))),
|
|
373
|
+
advisoryRound: round,
|
|
374
|
+
};
|
|
375
|
+
completeOperationInTransaction(database, input.runId, operation, operationOwner, response);
|
|
306
376
|
return response;
|
|
307
377
|
});
|
|
308
378
|
}
|
|
309
|
-
function identity(input) {
|
|
310
|
-
return
|
|
311
|
-
runId: input.runId,
|
|
312
|
-
workspace: input.workspace,
|
|
313
|
-
orchestrationId: input.orchestrationId,
|
|
314
|
-
};
|
|
379
|
+
function identity(database, input) {
|
|
380
|
+
return resolveEnnoIdentity(database, input);
|
|
315
381
|
}
|
|
316
382
|
function assertExpected(snapshot, expectedRevision, statuses) {
|
|
317
383
|
if (snapshot.revision !== expectedRevision)
|
|
@@ -328,7 +394,7 @@ function startFirstReadyUnit(database, snapshot) {
|
|
|
328
394
|
const next = firstReadyUnit(snapshot);
|
|
329
395
|
if (next === undefined) {
|
|
330
396
|
if (snapshot.workUnits.every((unit) => unit.status === 'completed'))
|
|
331
|
-
return;
|
|
397
|
+
return undefined;
|
|
332
398
|
throw new KiokukoError('CONFLICT', 'Enno WorkPlan dependencies cannot advance');
|
|
333
399
|
}
|
|
334
400
|
setWorkUnitStatusInTransaction(database, {
|
|
@@ -342,6 +408,12 @@ function startFirstReadyUnit(database, snapshot) {
|
|
|
342
408
|
workUnitId: next.workUnit.id,
|
|
343
409
|
contractRevision: snapshot.revision,
|
|
344
410
|
});
|
|
411
|
+
if (snapshot.clientKind === null || snapshot.clientSessionId === null)
|
|
412
|
+
return undefined;
|
|
413
|
+
return claimExecutionLeaseInTransaction(database, snapshot, next.workUnit.id, {
|
|
414
|
+
clientKind: snapshot.clientKind,
|
|
415
|
+
sessionId: snapshot.clientSessionId,
|
|
416
|
+
});
|
|
345
417
|
}
|
|
346
418
|
function requiresConfirmation(provenance) {
|
|
347
419
|
return ENNO_PROVENANCE_KEYS.some((key) => provenance[key] !== 'explicit_user');
|
|
@@ -426,7 +498,7 @@ async function discoverZenkiSkills(database, snapshot, plan, dependencies) {
|
|
|
426
498
|
return claimed.summary;
|
|
427
499
|
if (claimed.queryBudget === 0 || claimed.selectionBudget === 0) {
|
|
428
500
|
return completeAgentTaskSkillDiscoveryAttempt(database, attempt, emptyDiscovery(intake.mode), () => {
|
|
429
|
-
assertExpected(readEnnoSnapshot(database, identity(plan)), plan.expectedRevision, ['zenki_planning']);
|
|
501
|
+
assertExpected(readEnnoSnapshot(database, identity(database, plan)), plan.expectedRevision, ['zenki_planning']);
|
|
430
502
|
});
|
|
431
503
|
}
|
|
432
504
|
try {
|
|
@@ -440,10 +512,10 @@ async function discoverZenkiSkills(database, snapshot, plan, dependencies) {
|
|
|
440
512
|
...(dependencies.fetchImpl === undefined ? {} : { fetchImpl: dependencies.fetchImpl }),
|
|
441
513
|
}, {
|
|
442
514
|
...(dependencies.fetchImpl === undefined ? {} : { fetchImpl: dependencies.fetchImpl }),
|
|
443
|
-
assertBeforePersist: () => assertExpected(readEnnoSnapshot(database, identity(plan)), plan.expectedRevision, ['zenki_planning']),
|
|
515
|
+
assertBeforePersist: () => assertExpected(readEnnoSnapshot(database, identity(database, plan)), plan.expectedRevision, ['zenki_planning']),
|
|
444
516
|
});
|
|
445
517
|
return completeAgentTaskSkillDiscoveryAttempt(database, attempt, summary, () => {
|
|
446
|
-
assertExpected(readEnnoSnapshot(database, identity(plan)), plan.expectedRevision, ['zenki_planning']);
|
|
518
|
+
assertExpected(readEnnoSnapshot(database, identity(database, plan)), plan.expectedRevision, ['zenki_planning']);
|
|
447
519
|
});
|
|
448
520
|
}
|
|
449
521
|
catch (error) {
|
|
@@ -451,13 +523,11 @@ async function discoverZenkiSkills(database, snapshot, plan, dependencies) {
|
|
|
451
523
|
}
|
|
452
524
|
}
|
|
453
525
|
function planChangesCode(plan, taskType) {
|
|
454
|
-
return
|
|
455
|
-
|| plan.skillRequirements.some((skill) => skill.purposes.includes('implementation'))
|
|
456
|
-
|| plan.workPlan.units.some((unit) => unit.skillNames.includes('kiokuko-single-purpose-functions'));
|
|
526
|
+
return plan.workPlan.units.some((unit) => unit.routes.includes('code') || unit.routes.includes('ui'))
|
|
527
|
+
|| (taskType !== 'review' && plan.skillRequirements.some((skill) => skill.purposes.includes('implementation')));
|
|
457
528
|
}
|
|
458
529
|
function planHasUi(plan) {
|
|
459
|
-
return plan.
|
|
460
|
-
|| plan.workPlan.units.some((unit) => unit.skillNames.includes('kiokuko-ui-design-soul'));
|
|
530
|
+
return plan.workPlan.units.some((unit) => unit.routes.includes('ui'));
|
|
461
531
|
}
|
|
462
532
|
function assertIdealSkillCoverage(snapshot, ideal) {
|
|
463
533
|
const expected = snapshot.contract.skillSet.intakeDiscovery.selected.map((skill) => skill.name);
|
|
@@ -466,26 +536,43 @@ function assertIdealSkillCoverage(snapshot, ideal) {
|
|
|
466
536
|
}
|
|
467
537
|
const actual = ideal.skillContributions.map((contribution) => contribution.skillName);
|
|
468
538
|
if (actual.length !== expected.length || expected.some((name) => !actual.includes(name))) {
|
|
469
|
-
throw
|
|
539
|
+
throw ennoValidationError('ideal_submit', [{
|
|
540
|
+
path: ['ideal', 'skillContributions'],
|
|
541
|
+
reasonCode: actual.length < expected.length ? 'too_few_items' : 'undeclared_skill',
|
|
542
|
+
...(actual.length < expected.length ? { expected: { minItems: expected.length } } : {}),
|
|
543
|
+
}]);
|
|
470
544
|
}
|
|
471
545
|
}
|
|
472
|
-
function
|
|
473
|
-
if (input.capabilities === undefined)
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
const run = new LedgerStore(database).readRun(input.runId, input.workspace);
|
|
546
|
+
function planCapabilityRecoveryReason(database, input, workspace) {
|
|
547
|
+
if (input.capabilities === undefined)
|
|
548
|
+
return 'environment_information_missing';
|
|
549
|
+
const run = new LedgerStore(database).readRun(input.runId, workspace);
|
|
477
550
|
if (run === undefined)
|
|
478
551
|
throw new KiokukoError('INTEGRITY_ERROR', 'Enno ledger run disappeared during plan validation');
|
|
479
552
|
try {
|
|
480
553
|
assertCapabilityCatalogBinding(run.metadata, input.capabilities);
|
|
554
|
+
return null;
|
|
481
555
|
}
|
|
482
556
|
catch (error) {
|
|
483
557
|
if (error instanceof KiokukoError && error.code === 'CONFLICT') {
|
|
484
|
-
|
|
558
|
+
return 'environment_changed';
|
|
485
559
|
}
|
|
486
560
|
throw error;
|
|
487
561
|
}
|
|
488
562
|
}
|
|
563
|
+
function pausePlanStartRecovery(database, snapshot, reason) {
|
|
564
|
+
withImmediateTransaction(database, () => {
|
|
565
|
+
const updated = database.prepare(`
|
|
566
|
+
UPDATE enno_contracts
|
|
567
|
+
SET blocker = ?, updated_at = ?
|
|
568
|
+
WHERE run_id = ? AND revision = ? AND status = 'zenki_planning'
|
|
569
|
+
RETURNING run_id AS runId
|
|
570
|
+
`).get(planStartRecoveryBlocker(reason), new Date().toISOString(), snapshot.runId, snapshot.revision);
|
|
571
|
+
if (updated?.runId !== snapshot.runId)
|
|
572
|
+
throw new KiokukoError('CONFLICT', 'Enno plan state changed concurrently');
|
|
573
|
+
});
|
|
574
|
+
throw planStartRecoveryError(reason);
|
|
575
|
+
}
|
|
489
576
|
function endedBeforeWorkBecausePlanCatalogWasLost(database, snapshot, capabilities) {
|
|
490
577
|
if (capabilities === undefined
|
|
491
578
|
|| snapshot.status !== 'blocked'
|
|
@@ -513,30 +600,28 @@ function endedBeforeWorkBecausePlanCatalogWasLost(database, snapshot, capabiliti
|
|
|
513
600
|
throw error;
|
|
514
601
|
}
|
|
515
602
|
}
|
|
516
|
-
function sanitizedIdeal(ideal, repositoryRoot) {
|
|
517
|
-
return parseOdunoIdeal(sanitizeJson(ideal, { workspace: repositoryRoot }).value);
|
|
518
|
-
}
|
|
519
603
|
export function submitOdunoIdeal(database, rawInput) {
|
|
520
|
-
const
|
|
604
|
+
const parsedInput = parseIdealSubmission(rawInput);
|
|
605
|
+
const before = readEnnoSnapshot(database, identity(database, parsedInput));
|
|
606
|
+
const input = sanitizeIdealSubmission(parsedInput, before.repositoryRoot);
|
|
521
607
|
const operation = operationIdentity('ideal_submit', input.idempotencyKey, input);
|
|
522
608
|
const replay = readOperationReceipt(database, input.runId, operation);
|
|
523
609
|
if (replay !== undefined)
|
|
524
610
|
return replay;
|
|
525
|
-
const before = readEnnoSnapshot(database, identity(input));
|
|
526
611
|
assertExpected(before, input.expectedRevision, ['oduno_ideal']);
|
|
527
612
|
if (input.advisoryRoundDigest !== undefined) {
|
|
528
613
|
requireAdvisoryRound(database, before, 'ideal', before.mutationRevision, advisoryContextForSubmission(before, 'ideal'), input.advisoryRoundDigest);
|
|
529
614
|
}
|
|
530
|
-
const ideal =
|
|
615
|
+
const ideal = input.ideal;
|
|
531
616
|
assertIdealSkillCoverage(before, ideal);
|
|
532
617
|
return withImmediateTransaction(database, () => {
|
|
533
618
|
const replayed = readOperationReceipt(database, input.runId, operation);
|
|
534
619
|
if (replayed !== undefined)
|
|
535
620
|
return replayed;
|
|
536
|
-
const current = readEnnoSnapshot(database, identity(input));
|
|
621
|
+
const current = readEnnoSnapshot(database, identity(database, input));
|
|
537
622
|
assertExpected(current, input.expectedRevision, ['oduno_ideal']);
|
|
538
623
|
assertIdealSkillCoverage(current, ideal);
|
|
539
|
-
startOperationInTransaction(database, input.runId, operation);
|
|
624
|
+
const operationOwner = startOperationInTransaction(database, input.runId, operation);
|
|
540
625
|
const advisoryRound = consumeAdvisoryRoundIfPresent(database, current, 'ideal', current.mutationRevision, advisoryContextForSubmission(current, 'ideal'), input.advisoryRoundDigest, input.advisoryDisposition);
|
|
541
626
|
updateContractInTransaction(database, current, {
|
|
542
627
|
contract: current.contract,
|
|
@@ -551,16 +636,17 @@ export function submitOdunoIdeal(database, rawInput) {
|
|
|
551
636
|
successSignalCount: ideal.successSignals.length,
|
|
552
637
|
});
|
|
553
638
|
const response = {
|
|
554
|
-
ennoOduno: stateForSnapshot(readEnnoSnapshot(database, identity(input))),
|
|
639
|
+
ennoOduno: stateForSnapshot(readEnnoSnapshot(database, identity(database, input))),
|
|
555
640
|
...(advisoryRound === undefined ? {} : { advisoryRound }),
|
|
556
641
|
};
|
|
557
|
-
completeOperationInTransaction(database, input.runId, operation, response);
|
|
642
|
+
completeOperationInTransaction(database, input.runId, operation, operationOwner, response);
|
|
558
643
|
return response;
|
|
559
644
|
});
|
|
560
645
|
}
|
|
561
646
|
export async function submitEnnoPlan(database, rawInput, dependencies = {}) {
|
|
562
|
-
const
|
|
563
|
-
const current = readEnnoSnapshot(database, identity(
|
|
647
|
+
const parsedInput = parsePlanSubmission(rawInput);
|
|
648
|
+
const current = readEnnoSnapshot(database, identity(database, parsedInput));
|
|
649
|
+
const input = sanitizePlanSubmission(parsedInput, current.repositoryRoot);
|
|
564
650
|
if (endedBeforeWorkBecausePlanCatalogWasLost(database, current, input.capabilities)) {
|
|
565
651
|
throw planStartRecoveryError('previous_attempt_ended');
|
|
566
652
|
}
|
|
@@ -570,13 +656,33 @@ export async function submitEnnoPlan(database, rawInput, dependencies = {}) {
|
|
|
570
656
|
return replay;
|
|
571
657
|
const before = current;
|
|
572
658
|
assertExpected(before, input.expectedRevision, ['zenki_planning']);
|
|
573
|
-
|
|
659
|
+
const pendingRecovery = planStartRecoveryReasonFromBlocker(before.blocker);
|
|
660
|
+
if (pendingRecovery === 'environment_information_missing') {
|
|
661
|
+
if (input.recoveryAction === undefined)
|
|
662
|
+
throw planStartRecoveryError(pendingRecovery);
|
|
663
|
+
}
|
|
664
|
+
else if (pendingRecovery !== null) {
|
|
665
|
+
throw planStartRecoveryError(pendingRecovery);
|
|
666
|
+
}
|
|
667
|
+
else if (input.recoveryAction !== undefined) {
|
|
668
|
+
throw new KiokukoError('CONFLICT', 'Enno plan has no pending same-run recovery choice');
|
|
669
|
+
}
|
|
670
|
+
if (input.maxAttempts <= before.attempts) {
|
|
671
|
+
throw ennoValidationError('plan_submit', [{
|
|
672
|
+
path: ['maxAttempts'],
|
|
673
|
+
reasonCode: 'too_few_items',
|
|
674
|
+
expected: { minItems: before.attempts + 1 },
|
|
675
|
+
}]);
|
|
676
|
+
}
|
|
677
|
+
const recoveryReason = planCapabilityRecoveryReason(database, input, before.workspace);
|
|
678
|
+
if (recoveryReason !== null)
|
|
679
|
+
pausePlanStartRecovery(database, before, recoveryReason);
|
|
574
680
|
if (input.advisoryRoundDigest !== undefined) {
|
|
575
681
|
requireAdvisoryRound(database, before, 'planning', before.mutationRevision, advisoryContextForSubmission(before, 'planning'), input.advisoryRoundDigest);
|
|
576
682
|
}
|
|
577
683
|
const includesCodeChanges = planChangesCode(input, before.taskType);
|
|
578
684
|
const includesUiWork = planHasUi(input);
|
|
579
|
-
assertWorkPlanExpertCoverage(input.workPlan
|
|
685
|
+
assertWorkPlanExpertCoverage(input.workPlan);
|
|
580
686
|
assertContractVerifierCwds(before.repositoryRoot, {
|
|
581
687
|
workPlan: input.workPlan,
|
|
582
688
|
finalVerifiers: input.finalVerifiers,
|
|
@@ -593,11 +699,6 @@ export async function submitEnnoPlan(database, rawInput, dependencies = {}) {
|
|
|
593
699
|
discoveries: [before.contract.skillSet.intakeDiscovery, zenkiDiscovery],
|
|
594
700
|
});
|
|
595
701
|
const unavailable = unavailableRequiredSkills(entries);
|
|
596
|
-
const mandatoryWorkSkills = [
|
|
597
|
-
STANDARD_SOUL_SKILL_NAME,
|
|
598
|
-
...(includesCodeChanges ? [STANDARD_FUNCTION_SKILL_NAME] : []),
|
|
599
|
-
...(includesUiWork ? [STANDARD_UI_SKILL_NAME] : []),
|
|
600
|
-
];
|
|
601
702
|
const nextRevision = input.expectedRevision + 1;
|
|
602
703
|
const contract = {
|
|
603
704
|
revision: nextRevision,
|
|
@@ -610,7 +711,12 @@ export async function submitEnnoPlan(database, rawInput, dependencies = {}) {
|
|
|
610
711
|
...unit,
|
|
611
712
|
scope: [...unit.scope],
|
|
612
713
|
dependencies: [...unit.dependencies],
|
|
613
|
-
|
|
714
|
+
routes: [...unit.routes],
|
|
715
|
+
skillNames: orderedUniqueSkillNames([
|
|
716
|
+
STANDARD_SOUL_SKILL_NAME,
|
|
717
|
+
...(unit.routes.includes('code') || unit.routes.includes('ui') ? [STANDARD_FUNCTION_SKILL_NAME] : []),
|
|
718
|
+
...(unit.routes.includes('ui') ? [STANDARD_UI_SKILL_NAME] : []),
|
|
719
|
+
], unit.skillNames),
|
|
614
720
|
expertRefs: unit.expertRefs.map((reference) => ({ ...reference })),
|
|
615
721
|
acceptanceCriteria: [...unit.acceptanceCriteria],
|
|
616
722
|
focusedVerifiers: unit.focusedVerifiers.map((verifier) => ({ ...verifier, args: [...verifier.args] })),
|
|
@@ -631,9 +737,9 @@ export async function submitEnnoPlan(database, rawInput, dependencies = {}) {
|
|
|
631
737
|
const replayed = readOperationReceipt(database, input.runId, operation);
|
|
632
738
|
if (replayed !== undefined)
|
|
633
739
|
return replayed;
|
|
634
|
-
const current = readEnnoSnapshot(database, identity(input));
|
|
740
|
+
const current = readEnnoSnapshot(database, identity(database, input));
|
|
635
741
|
assertExpected(current, input.expectedRevision, ['zenki_planning']);
|
|
636
|
-
startOperationInTransaction(database, input.runId, operation);
|
|
742
|
+
const operationOwner = startOperationInTransaction(database, input.runId, operation);
|
|
637
743
|
const advisoryRound = consumeAdvisoryRoundIfPresent(database, current, 'planning', current.mutationRevision, advisoryContextForSubmission(current, 'planning'), input.advisoryRoundDigest, input.advisoryDisposition);
|
|
638
744
|
const status = unavailable.length > 0
|
|
639
745
|
? 'blocked'
|
|
@@ -653,7 +759,8 @@ export async function submitEnnoPlan(database, rawInput, dependencies = {}) {
|
|
|
653
759
|
contractRevision: nextRevision,
|
|
654
760
|
workUnitIds: contract.workPlan.units.map((unit) => unit.id),
|
|
655
761
|
});
|
|
656
|
-
let updated = readEnnoSnapshot(database, identity(input));
|
|
762
|
+
let updated = readEnnoSnapshot(database, identity(database, input));
|
|
763
|
+
let executionLease;
|
|
657
764
|
if (status === 'blocked') {
|
|
658
765
|
appendEnnoEventInTransaction(database, input.runId, 'enno.blocked', 'enno-oduno', 'blocked', {
|
|
659
766
|
reason: blocker,
|
|
@@ -665,28 +772,32 @@ export async function submitEnnoPlan(database, rawInput, dependencies = {}) {
|
|
|
665
772
|
appendEnnoEventInTransaction(database, input.runId, 'enno.plan_confirmed', 'enno-oduno', 'not_required', {
|
|
666
773
|
contractRevision: nextRevision,
|
|
667
774
|
});
|
|
668
|
-
startFirstReadyUnit(database, updated);
|
|
669
|
-
updated = readEnnoSnapshot(database, identity(input));
|
|
775
|
+
executionLease = startFirstReadyUnit(database, updated);
|
|
776
|
+
updated = readEnnoSnapshot(database, identity(database, input));
|
|
670
777
|
}
|
|
671
778
|
const response = {
|
|
672
779
|
ennoOduno: stateForSnapshot(updated),
|
|
673
780
|
...(advisoryRound === undefined ? {} : { advisoryRound }),
|
|
781
|
+
...(executionLease === undefined ? {} : { executionLease }),
|
|
674
782
|
};
|
|
675
|
-
completeOperationInTransaction(database, input.runId, operation, response);
|
|
783
|
+
completeOperationInTransaction(database, input.runId, operation, operationOwner, response);
|
|
676
784
|
return response;
|
|
677
785
|
});
|
|
678
786
|
}
|
|
679
787
|
export function answerEnno(database, rawInput) {
|
|
680
|
-
const
|
|
788
|
+
const parsedInput = parseEnnoAnswer(rawInput);
|
|
789
|
+
const before = readEnnoSnapshot(database, identity(database, parsedInput));
|
|
790
|
+
const input = sanitizeEnnoAnswer(parsedInput, before.repositoryRoot);
|
|
681
791
|
const operation = operationIdentity('answer', input.idempotencyKey, input);
|
|
682
792
|
const replay = readOperationReceipt(database, input.runId, operation);
|
|
683
793
|
if (replay !== undefined)
|
|
684
794
|
return replay;
|
|
685
795
|
return withImmediateTransaction(database, () => {
|
|
686
|
-
const current = readEnnoSnapshot(database, identity(input));
|
|
796
|
+
const current = readEnnoSnapshot(database, identity(database, input));
|
|
687
797
|
assertExpected(current, input.expectedRevision, input.action === 'cancel' ? ['needs_confirmation', 'zenki_planning'] : ['needs_confirmation']);
|
|
688
|
-
startOperationInTransaction(database, input.runId, operation);
|
|
798
|
+
const operationOwner = startOperationInTransaction(database, input.runId, operation);
|
|
689
799
|
let next;
|
|
800
|
+
let executionLease;
|
|
690
801
|
if (input.action === 'approve') {
|
|
691
802
|
updateContractInTransaction(database, current, {
|
|
692
803
|
contract: current.contract,
|
|
@@ -696,9 +807,9 @@ export function answerEnno(database, rawInput) {
|
|
|
696
807
|
appendEnnoEventInTransaction(database, input.runId, 'enno.plan_confirmed', 'enno-oduno', 'approved', {
|
|
697
808
|
contractRevision: current.revision,
|
|
698
809
|
});
|
|
699
|
-
next = readEnnoSnapshot(database, identity(input));
|
|
700
|
-
startFirstReadyUnit(database, next);
|
|
701
|
-
next = readEnnoSnapshot(database, identity(input));
|
|
810
|
+
next = readEnnoSnapshot(database, identity(database, input));
|
|
811
|
+
executionLease = startFirstReadyUnit(database, next);
|
|
812
|
+
next = readEnnoSnapshot(database, identity(database, input));
|
|
702
813
|
}
|
|
703
814
|
else if (input.action === 'revise') {
|
|
704
815
|
const revisedContract = {
|
|
@@ -711,7 +822,7 @@ export function answerEnno(database, rawInput) {
|
|
|
711
822
|
confirmationState: 'revision_requested',
|
|
712
823
|
blocker: input.requestedChanges ?? null,
|
|
713
824
|
});
|
|
714
|
-
next = readEnnoSnapshot(database, identity(input));
|
|
825
|
+
next = readEnnoSnapshot(database, identity(database, input));
|
|
715
826
|
}
|
|
716
827
|
else {
|
|
717
828
|
updateContractInTransaction(database, current, {
|
|
@@ -723,10 +834,13 @@ export function answerEnno(database, rawInput) {
|
|
|
723
834
|
contractRevision: current.revision,
|
|
724
835
|
});
|
|
725
836
|
terminalizeLedgerRunInTransaction(database, input.runId, 'cancelled');
|
|
726
|
-
next = readEnnoSnapshot(database, identity(input));
|
|
837
|
+
next = readEnnoSnapshot(database, identity(database, input));
|
|
727
838
|
}
|
|
728
|
-
const response = {
|
|
729
|
-
|
|
839
|
+
const response = {
|
|
840
|
+
ennoOduno: stateForSnapshot(next),
|
|
841
|
+
...(executionLease === undefined ? {} : { executionLease }),
|
|
842
|
+
};
|
|
843
|
+
completeOperationInTransaction(database, input.runId, operation, operationOwner, response);
|
|
730
844
|
return response;
|
|
731
845
|
});
|
|
732
846
|
}
|
|
@@ -772,7 +886,20 @@ function sanitizedReviewSummary(summary, repositoryRoot) {
|
|
|
772
886
|
return sanitizeJson({ summary }, { workspace: repositoryRoot }).value.summary;
|
|
773
887
|
}
|
|
774
888
|
function blockedForAttemptLimit(database, snapshot, operation) {
|
|
775
|
-
startOperationInTransaction(database, snapshot.runId, operation);
|
|
889
|
+
const operationOwner = startOperationInTransaction(database, snapshot.runId, operation);
|
|
890
|
+
const activeUnit = snapshot.workUnits.find((candidate) => candidate.status === 'in_progress');
|
|
891
|
+
if (activeUnit !== undefined) {
|
|
892
|
+
setWorkUnitStatusInTransaction(database, {
|
|
893
|
+
runId: snapshot.runId,
|
|
894
|
+
contractRevision: snapshot.revision,
|
|
895
|
+
workUnitId: activeUnit.workUnit.id,
|
|
896
|
+
from: 'in_progress',
|
|
897
|
+
to: 'blocked',
|
|
898
|
+
attemptCount: activeUnit.attemptCount,
|
|
899
|
+
result: activeUnit.result,
|
|
900
|
+
});
|
|
901
|
+
}
|
|
902
|
+
releaseExecutionLeaseInTransaction(database, snapshot.runId);
|
|
776
903
|
updateContractInTransaction(database, snapshot, {
|
|
777
904
|
contract: snapshot.contract,
|
|
778
905
|
status: 'blocked',
|
|
@@ -789,28 +916,36 @@ function blockedForAttemptLimit(database, snapshot, operation) {
|
|
|
789
916
|
workspace: snapshot.workspace,
|
|
790
917
|
orchestrationId: snapshot.orchestrationId,
|
|
791
918
|
})) };
|
|
792
|
-
completeOperationInTransaction(database, snapshot.runId, operation, response);
|
|
919
|
+
completeOperationInTransaction(database, snapshot.runId, operation, operationOwner, response);
|
|
793
920
|
return response;
|
|
794
921
|
}
|
|
795
922
|
export async function reportEnnoWork(database, rawInput, dependencies = {}) {
|
|
796
|
-
const
|
|
923
|
+
const parsedInput = parseWorkReport(rawInput);
|
|
924
|
+
const initial = readEnnoSnapshot(database, identity(database, parsedInput));
|
|
925
|
+
const input = sanitizeWorkReport(parsedInput, initial.repositoryRoot);
|
|
797
926
|
const operation = operationIdentity('work_report', input.idempotencyKey, input);
|
|
798
927
|
const replay = readOperationReceipt(database, input.runId, operation);
|
|
799
928
|
if (replay !== undefined)
|
|
800
929
|
return replay;
|
|
801
|
-
const before =
|
|
930
|
+
const before = initial;
|
|
802
931
|
assertExpected(before, input.expectedRevision, ['goki_executing']);
|
|
803
932
|
const unit = before.workUnits.find((candidate) => candidate.workUnit.id === input.workUnitId);
|
|
804
933
|
if (unit === undefined || unit.status !== 'in_progress')
|
|
805
934
|
throw new KiokukoError('CONFLICT', 'Reported Enno WorkUnit is not active');
|
|
806
935
|
if (before.attempts >= before.contract.maxAttempts) {
|
|
807
|
-
return withImmediateTransaction(database, () =>
|
|
936
|
+
return withImmediateTransaction(database, () => {
|
|
937
|
+
const current = readEnnoSnapshot(database, identity(database, input));
|
|
938
|
+
assertExpected(current, input.expectedRevision, ['goki_executing']);
|
|
939
|
+
assertExecutionLeaseInTransaction(database, current, input);
|
|
940
|
+
return blockedForAttemptLimit(database, current, operation);
|
|
941
|
+
});
|
|
808
942
|
}
|
|
809
943
|
if (input.result.outcome !== 'completed') {
|
|
810
944
|
return withImmediateTransaction(database, () => {
|
|
811
|
-
const current = readEnnoSnapshot(database, identity(input));
|
|
945
|
+
const current = readEnnoSnapshot(database, identity(database, input));
|
|
812
946
|
assertExpected(current, input.expectedRevision, ['goki_executing']);
|
|
813
|
-
|
|
947
|
+
assertExecutionLeaseInTransaction(database, current, input);
|
|
948
|
+
const operationOwner = startOperationInTransaction(database, input.runId, operation);
|
|
814
949
|
const currentUnit = current.workUnits.find((candidate) => candidate.workUnit.id === input.workUnitId);
|
|
815
950
|
const attempts = current.attempts + 1;
|
|
816
951
|
const mustBlock = input.result.outcome === 'blocked' || attempts >= current.contract.maxAttempts;
|
|
@@ -835,21 +970,25 @@ export async function reportEnnoWork(database, rawInput, dependencies = {}) {
|
|
|
835
970
|
attempt: attempts,
|
|
836
971
|
});
|
|
837
972
|
if (mustBlock) {
|
|
973
|
+
releaseExecutionLeaseInTransaction(database, input.runId);
|
|
838
974
|
appendEnnoEventInTransaction(database, input.runId, 'enno.blocked', 'enno-oduno', 'blocked', {
|
|
839
975
|
reason: input.result.summary,
|
|
840
976
|
});
|
|
841
977
|
terminalizeLedgerRunInTransaction(database, input.runId, 'failed');
|
|
842
978
|
}
|
|
843
|
-
const response = { ennoOduno: stateForSnapshot(readEnnoSnapshot(database, identity(input))) };
|
|
844
|
-
completeOperationInTransaction(database, input.runId, operation, response);
|
|
979
|
+
const response = { ennoOduno: stateForSnapshot(readEnnoSnapshot(database, identity(database, input))) };
|
|
980
|
+
completeOperationInTransaction(database, input.runId, operation, operationOwner, response);
|
|
845
981
|
return response;
|
|
846
982
|
});
|
|
847
983
|
}
|
|
848
984
|
let verifierRunIds = [];
|
|
985
|
+
let operationOwner = '';
|
|
986
|
+
const verifierLeaseMs = operationLeaseMsForVerifiers(unit.workUnit.focusedVerifiers);
|
|
849
987
|
withImmediateTransaction(database, () => {
|
|
850
|
-
const current = readEnnoSnapshot(database, identity(input));
|
|
988
|
+
const current = readEnnoSnapshot(database, identity(database, input));
|
|
851
989
|
assertExpected(current, input.expectedRevision, ['goki_executing']);
|
|
852
|
-
|
|
990
|
+
renewExecutionLeaseInTransaction(database, current, input, verifierLeaseMs);
|
|
991
|
+
operationOwner = startOperationInTransaction(database, input.runId, operation, verifierLeaseMs);
|
|
853
992
|
const currentUnit = current.workUnits.find((candidate) => candidate.workUnit.id === input.workUnitId);
|
|
854
993
|
if (currentUnit === undefined || currentUnit.status !== 'in_progress') {
|
|
855
994
|
throw new KiokukoError('CONFLICT', 'Reported Enno WorkUnit changed concurrently');
|
|
@@ -871,8 +1010,9 @@ export async function reportEnnoWork(database, rawInput, dependencies = {}) {
|
|
|
871
1010
|
}
|
|
872
1011
|
const results = sanitizedVerifierResults(rawResults, before.repositoryRoot);
|
|
873
1012
|
return withImmediateTransaction(database, () => {
|
|
874
|
-
const current = readEnnoSnapshot(database, identity(input));
|
|
1013
|
+
const current = readEnnoSnapshot(database, identity(database, input));
|
|
875
1014
|
assertExpected(current, input.expectedRevision, ['goki_executing']);
|
|
1015
|
+
assertExecutionLeaseInTransaction(database, current, input);
|
|
876
1016
|
finishVerifierRunsInTransaction(database, verifierRunIds, results);
|
|
877
1017
|
const passed = results.every((result) => result.status === 'passed');
|
|
878
1018
|
const unsafe = results.some((result) => result.status === 'spawn_failed');
|
|
@@ -903,7 +1043,9 @@ export async function reportEnnoWork(database, rawInput, dependencies = {}) {
|
|
|
903
1043
|
mutated: input.result.mutated,
|
|
904
1044
|
changedPaths: input.result.changedPaths,
|
|
905
1045
|
});
|
|
906
|
-
|
|
1046
|
+
releaseExecutionLeaseInTransaction(database, input.runId);
|
|
1047
|
+
let next = readEnnoSnapshot(database, identity(database, input));
|
|
1048
|
+
let executionLease;
|
|
907
1049
|
if (mustBlock) {
|
|
908
1050
|
appendEnnoEventInTransaction(database, input.runId, 'enno.blocked', 'enno-oduno', 'blocked', {
|
|
909
1051
|
reason: unsafe ? 'spawn_failed' : 'focused_verification_failed',
|
|
@@ -919,37 +1061,46 @@ export async function reportEnnoWork(database, rawInput, dependencies = {}) {
|
|
|
919
1061
|
});
|
|
920
1062
|
}
|
|
921
1063
|
else {
|
|
922
|
-
startFirstReadyUnit(database, next);
|
|
1064
|
+
executionLease = startFirstReadyUnit(database, next);
|
|
923
1065
|
}
|
|
924
|
-
next = readEnnoSnapshot(database, identity(input));
|
|
1066
|
+
next = readEnnoSnapshot(database, identity(database, input));
|
|
925
1067
|
}
|
|
926
|
-
const response = {
|
|
927
|
-
|
|
1068
|
+
const response = {
|
|
1069
|
+
ennoOduno: stateForSnapshot(next),
|
|
1070
|
+
verifierResults: results,
|
|
1071
|
+
...(executionLease === undefined ? {} : { executionLease }),
|
|
1072
|
+
};
|
|
1073
|
+
completeOperationInTransaction(database, input.runId, operation, operationOwner, response);
|
|
928
1074
|
return response;
|
|
929
1075
|
});
|
|
930
1076
|
}
|
|
931
1077
|
export async function prepareEnnoVerification(database, rawInput, dependencies = {}) {
|
|
932
1078
|
const input = parseVerificationPrepare(rawInput);
|
|
1079
|
+
const before = readEnnoSnapshot(database, identity(database, input));
|
|
933
1080
|
const operation = operationIdentity('verify_prepare', input.idempotencyKey, input);
|
|
934
1081
|
const replay = readOperationReceipt(database, input.runId, operation);
|
|
935
1082
|
if (replay !== undefined)
|
|
936
1083
|
return replay;
|
|
937
|
-
const before = readEnnoSnapshot(database, identity(input));
|
|
938
1084
|
assertExpected(before, input.expectedRevision, ['enno_verifying']);
|
|
1085
|
+
const preRepositoryState = captureRepositoryState(before.repositoryRoot);
|
|
1086
|
+
const verifierSpecDigest = canonicalContentHash(before.contract.finalVerifiers);
|
|
939
1087
|
if (before.attempts >= before.contract.maxAttempts) {
|
|
940
|
-
return withImmediateTransaction(database, () => blockedForAttemptLimit(database, readEnnoSnapshot(database, identity(input)), operation));
|
|
1088
|
+
return withImmediateTransaction(database, () => blockedForAttemptLimit(database, readEnnoSnapshot(database, identity(database, input)), operation));
|
|
941
1089
|
}
|
|
942
1090
|
let verifierRunIds = [];
|
|
1091
|
+
let operationOwner = '';
|
|
943
1092
|
let cachedResults;
|
|
1093
|
+
const verifierLeaseMs = operationLeaseMsForVerifiers(before.contract.finalVerifiers);
|
|
944
1094
|
withImmediateTransaction(database, () => {
|
|
945
|
-
const current = readEnnoSnapshot(database, identity(input));
|
|
1095
|
+
const current = readEnnoSnapshot(database, identity(database, input));
|
|
946
1096
|
assertExpected(current, input.expectedRevision, ['enno_verifying']);
|
|
947
|
-
startOperationInTransaction(database, input.runId, operation);
|
|
1097
|
+
operationOwner = startOperationInTransaction(database, input.runId, operation, verifierLeaseMs);
|
|
948
1098
|
cachedResults = readFreshFinalVerifierResults(database, {
|
|
949
1099
|
runId: input.runId,
|
|
950
1100
|
revision: current.revision,
|
|
951
1101
|
mutationRevision: current.mutationRevision,
|
|
952
1102
|
verifiers: current.contract.finalVerifiers,
|
|
1103
|
+
repositoryDigest: preRepositoryState.digest,
|
|
953
1104
|
});
|
|
954
1105
|
if (cachedResults === undefined) {
|
|
955
1106
|
verifierRunIds = startVerifierRunsInTransaction(database, {
|
|
@@ -958,6 +1109,11 @@ export async function prepareEnnoVerification(database, rawInput, dependencies =
|
|
|
958
1109
|
revision: current.revision,
|
|
959
1110
|
mutationRevision: current.mutationRevision,
|
|
960
1111
|
verifiers: current.contract.finalVerifiers,
|
|
1112
|
+
repositoryEvidence: {
|
|
1113
|
+
policyVersion: preRepositoryState.policyVersion,
|
|
1114
|
+
preDigest: preRepositoryState.digest,
|
|
1115
|
+
verifierSpecDigest,
|
|
1116
|
+
},
|
|
961
1117
|
});
|
|
962
1118
|
}
|
|
963
1119
|
appendEnnoEventInTransaction(database, input.runId, 'enno.verification_started', 'enno-oduno', cachedResults === undefined ? 'started' : 'reused', {
|
|
@@ -978,51 +1134,74 @@ export async function prepareEnnoVerification(database, rawInput, dependencies =
|
|
|
978
1134
|
results = spawnFailedVerifierResults(before.contract.finalVerifiers);
|
|
979
1135
|
}
|
|
980
1136
|
}
|
|
1137
|
+
const postRepositoryState = captureRepositoryState(before.repositoryRoot);
|
|
1138
|
+
const changedDuringVerification = postRepositoryState.digest !== preRepositoryState.digest
|
|
1139
|
+
|| results.some((result) => result.changedDuringVerification === true);
|
|
1140
|
+
results = results.map((result) => ({
|
|
1141
|
+
...result,
|
|
1142
|
+
repositoryStatePolicyVersion: postRepositoryState.policyVersion,
|
|
1143
|
+
repositoryStateDigest: postRepositoryState.digest,
|
|
1144
|
+
changedDuringVerification,
|
|
1145
|
+
}));
|
|
981
1146
|
return withImmediateTransaction(database, () => {
|
|
982
|
-
const current = readEnnoSnapshot(database, identity(input));
|
|
1147
|
+
const current = readEnnoSnapshot(database, identity(database, input));
|
|
983
1148
|
assertExpected(current, input.expectedRevision, ['enno_verifying']);
|
|
984
1149
|
if (verifierRunIds.length > 0)
|
|
985
|
-
finishVerifierRunsInTransaction(database, verifierRunIds, results
|
|
1150
|
+
finishVerifierRunsInTransaction(database, verifierRunIds, results, {
|
|
1151
|
+
postDigest: postRepositoryState.digest,
|
|
1152
|
+
changedDuringVerification,
|
|
1153
|
+
});
|
|
986
1154
|
const response = {
|
|
987
|
-
ennoOduno: stateForSnapshot(readEnnoSnapshot(database, identity(input))),
|
|
1155
|
+
ennoOduno: stateForSnapshot(readEnnoSnapshot(database, identity(database, input))),
|
|
988
1156
|
verifierResults: results,
|
|
989
1157
|
};
|
|
990
|
-
completeOperationInTransaction(database, input.runId, operation, response);
|
|
1158
|
+
completeOperationInTransaction(database, input.runId, operation, operationOwner, response);
|
|
991
1159
|
return response;
|
|
992
1160
|
});
|
|
993
1161
|
}
|
|
994
1162
|
export async function finishEnno(database, rawInput, dependencies = {}) {
|
|
995
|
-
const
|
|
1163
|
+
const parsedInput = parseFinishRequest(rawInput);
|
|
1164
|
+
const initial = readEnnoSnapshot(database, identity(database, parsedInput));
|
|
1165
|
+
const input = sanitizeFinishRequest(parsedInput, initial.repositoryRoot);
|
|
996
1166
|
const operation = operationIdentity('finish', input.idempotencyKey, input);
|
|
997
1167
|
const replay = readOperationReceipt(database, input.runId, operation);
|
|
998
1168
|
if (replay !== undefined)
|
|
999
1169
|
return replay;
|
|
1000
|
-
const before =
|
|
1170
|
+
const before = initial;
|
|
1001
1171
|
assertExpected(before, input.expectedRevision, ['enno_verifying']);
|
|
1002
1172
|
if (input.advisoryRoundDigest !== undefined) {
|
|
1173
|
+
if (!before.finalEvidenceReady) {
|
|
1174
|
+
throw ennoValidationError('finish', [{
|
|
1175
|
+
path: ['advisoryRoundDigest'],
|
|
1176
|
+
reasonCode: 'advisory_digest_stale',
|
|
1177
|
+
}], 'refresh_state');
|
|
1178
|
+
}
|
|
1003
1179
|
requireAdvisoryRound(database, before, 'final_review', before.mutationRevision, advisoryContextForSubmission(before, 'final_review'), input.advisoryRoundDigest);
|
|
1004
1180
|
}
|
|
1005
1181
|
const reviewSummary = sanitizedReviewSummary(input.review.summary, before.repositoryRoot);
|
|
1006
1182
|
if (before.attempts >= before.contract.maxAttempts) {
|
|
1007
|
-
return withImmediateTransaction(database, () => blockedForAttemptLimit(database, readEnnoSnapshot(database, identity(input)), operation));
|
|
1183
|
+
return withImmediateTransaction(database, () => blockedForAttemptLimit(database, readEnnoSnapshot(database, identity(database, input)), operation));
|
|
1008
1184
|
}
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
withImmediateTransaction(database, () => {
|
|
1012
|
-
const current = readEnnoSnapshot(database, identity(input));
|
|
1185
|
+
return withImmediateTransaction(database, () => {
|
|
1186
|
+
const current = readEnnoSnapshot(database, identity(database, input));
|
|
1013
1187
|
assertExpected(current, input.expectedRevision, ['enno_verifying']);
|
|
1014
|
-
|
|
1015
|
-
const
|
|
1188
|
+
const currentRepositoryDigest = captureRepositoryState(current.repositoryRoot).digest;
|
|
1189
|
+
const results = readFreshFinalVerifierResults(database, {
|
|
1016
1190
|
runId: input.runId,
|
|
1017
1191
|
revision: current.revision,
|
|
1018
1192
|
mutationRevision: current.mutationRevision,
|
|
1019
1193
|
verifiers: current.contract.finalVerifiers,
|
|
1194
|
+
repositoryDigest: currentRepositoryDigest,
|
|
1020
1195
|
});
|
|
1021
|
-
if (
|
|
1196
|
+
if (results === undefined) {
|
|
1022
1197
|
throw new KiokukoError('CONFLICT', 'Final verification evidence is not prepared; call enno_verify_prepare first');
|
|
1023
1198
|
}
|
|
1024
|
-
results
|
|
1025
|
-
|
|
1199
|
+
if (results.some((result) => result.repositoryStateDigest !== currentRepositoryDigest
|
|
1200
|
+
|| result.changedDuringVerification !== false)) {
|
|
1201
|
+
throw new KiokukoError('CONFLICT', 'Final verification evidence is stale for the current repository state');
|
|
1202
|
+
}
|
|
1203
|
+
const advisoryRound = consumeAdvisoryRoundIfPresent(database, current, 'final_review', current.mutationRevision, input.advisoryRoundDigest === undefined ? undefined : advisoryContextForSubmission(current, 'final_review'), input.advisoryRoundDigest, input.advisoryDisposition);
|
|
1204
|
+
const operationOwner = startOperationInTransaction(database, input.runId, operation);
|
|
1026
1205
|
appendEnnoEventInTransaction(database, input.runId, 'enno.review_started', 'enno-oduno', 'started', {
|
|
1027
1206
|
contractRevision: current.revision,
|
|
1028
1207
|
mutationRevision: current.mutationRevision,
|
|
@@ -1033,10 +1212,6 @@ export async function finishEnno(database, rawInput, dependencies = {}) {
|
|
|
1033
1212
|
mutationRevision: current.mutationRevision,
|
|
1034
1213
|
verifierIds: current.contract.finalVerifiers.map((verifier) => verifier.id),
|
|
1035
1214
|
});
|
|
1036
|
-
});
|
|
1037
|
-
return withImmediateTransaction(database, () => {
|
|
1038
|
-
const current = readEnnoSnapshot(database, identity(input));
|
|
1039
|
-
assertExpected(current, input.expectedRevision, ['enno_verifying']);
|
|
1040
1215
|
const passed = results.length > 0 && results.every((result) => result.status === 'passed');
|
|
1041
1216
|
const unsafe = results.some((result) => result.status === 'spawn_failed');
|
|
1042
1217
|
const attempts = current.attempts + 1;
|
|
@@ -1068,7 +1243,7 @@ export async function finishEnno(database, rawInput, dependencies = {}) {
|
|
|
1068
1243
|
mutationRevision: current.mutationRevision,
|
|
1069
1244
|
statuses: results.map((result) => ({ verifierId: result.verifier.id, status: result.status })),
|
|
1070
1245
|
});
|
|
1071
|
-
let next = readEnnoSnapshot(database, identity(input));
|
|
1246
|
+
let next = readEnnoSnapshot(database, identity(database, input));
|
|
1072
1247
|
if (accepted) {
|
|
1073
1248
|
appendEnnoEventInTransaction(database, input.runId, 'enno.review_accepted', 'enno-oduno', 'accepted', {
|
|
1074
1249
|
contractRevision: current.revision,
|
|
@@ -1096,40 +1271,38 @@ export async function finishEnno(database, rawInput, dependencies = {}) {
|
|
|
1096
1271
|
toContractRevision: next.revision,
|
|
1097
1272
|
reason: reviewFeedback,
|
|
1098
1273
|
});
|
|
1099
|
-
next = readEnnoSnapshot(database, identity(input));
|
|
1274
|
+
next = readEnnoSnapshot(database, identity(database, input));
|
|
1100
1275
|
}
|
|
1101
1276
|
const response = {
|
|
1102
1277
|
ennoOduno: stateForSnapshot(next),
|
|
1103
1278
|
verifierResults: results,
|
|
1104
1279
|
...(advisoryRound === undefined ? {} : { advisoryRound }),
|
|
1105
1280
|
};
|
|
1106
|
-
completeOperationInTransaction(database, input.runId, operation, response);
|
|
1281
|
+
completeOperationInTransaction(database, input.runId, operation, operationOwner, response);
|
|
1107
1282
|
return response;
|
|
1108
1283
|
});
|
|
1109
1284
|
}
|
|
1110
|
-
function sanitizedMeditation(meditation, repositoryRoot) {
|
|
1111
|
-
return parseOdunoMeditation(sanitizeJson(meditation, { workspace: repositoryRoot }).value);
|
|
1112
|
-
}
|
|
1113
1285
|
export function submitOdunoMeditation(database, rawInput) {
|
|
1114
|
-
const
|
|
1286
|
+
const parsedInput = parseMeditationSubmission(rawInput);
|
|
1287
|
+
const before = readEnnoSnapshot(database, identity(database, parsedInput));
|
|
1288
|
+
const input = sanitizeMeditationSubmission(parsedInput, before.repositoryRoot);
|
|
1115
1289
|
const operation = operationIdentity('meditation_submit', input.idempotencyKey, input);
|
|
1116
1290
|
const replay = readOperationReceipt(database, input.runId, operation);
|
|
1117
1291
|
if (replay !== undefined)
|
|
1118
1292
|
return replay;
|
|
1119
|
-
const before = readEnnoSnapshot(database, identity(input));
|
|
1120
1293
|
assertExpected(before, input.expectedRevision, ['oduno_meditation']);
|
|
1121
1294
|
if (before.ideal === null)
|
|
1122
1295
|
throw new KiokukoError('INTEGRITY_ERROR', 'Oduno meditation requires a persisted ideal');
|
|
1123
|
-
const meditation =
|
|
1296
|
+
const meditation = input.meditation;
|
|
1124
1297
|
return withImmediateTransaction(database, () => {
|
|
1125
1298
|
const replayed = readOperationReceipt(database, input.runId, operation);
|
|
1126
1299
|
if (replayed !== undefined)
|
|
1127
1300
|
return replayed;
|
|
1128
|
-
const current = readEnnoSnapshot(database, identity(input));
|
|
1301
|
+
const current = readEnnoSnapshot(database, identity(database, input));
|
|
1129
1302
|
assertExpected(current, input.expectedRevision, ['oduno_meditation']);
|
|
1130
1303
|
if (current.ideal === null)
|
|
1131
1304
|
throw new KiokukoError('INTEGRITY_ERROR', 'Oduno meditation requires a persisted ideal');
|
|
1132
|
-
startOperationInTransaction(database, input.runId, operation);
|
|
1305
|
+
const operationOwner = startOperationInTransaction(database, input.runId, operation);
|
|
1133
1306
|
updateContractInTransaction(database, current, {
|
|
1134
1307
|
contract: current.contract,
|
|
1135
1308
|
status: 'completed',
|
|
@@ -1147,8 +1320,8 @@ export function submitOdunoMeditation(database, rawInput) {
|
|
|
1147
1320
|
mutationRevision: current.mutationRevision,
|
|
1148
1321
|
});
|
|
1149
1322
|
terminalizeLedgerRunInTransaction(database, input.runId, 'completed');
|
|
1150
|
-
const response = { ennoOduno: stateForSnapshot(readEnnoSnapshot(database, identity(input))) };
|
|
1151
|
-
completeOperationInTransaction(database, input.runId, operation, response);
|
|
1323
|
+
const response = { ennoOduno: stateForSnapshot(readEnnoSnapshot(database, identity(database, input))) };
|
|
1324
|
+
completeOperationInTransaction(database, input.runId, operation, operationOwner, response);
|
|
1152
1325
|
return response;
|
|
1153
1326
|
});
|
|
1154
1327
|
}
|