@pegma/support-desk-application 0.2.7 → 0.2.8
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 +8 -6
- package/dist/engineering-followup-internal.d.ts +9 -2
- package/dist/engineering-followup-internal.d.ts.map +1 -1
- package/dist/engineering-followup-source.d.ts +10 -7
- package/dist/engineering-followup-source.d.ts.map +1 -1
- package/dist/engineering-followup-source.js +97 -7
- package/dist/engineering-followup-source.js.map +1 -1
- package/dist/engineering-followup-types.d.ts +31 -7
- package/dist/engineering-followup-types.d.ts.map +1 -1
- package/dist/engineering-followup-work.d.ts +5 -8
- package/dist/engineering-followup-work.d.ts.map +1 -1
- package/dist/engineering-followup-work.js +320 -135
- package/dist/engineering-followup-work.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -2
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { engineeringMutationBoundary, } from "./engineering-mutation-guard.js";
|
|
1
2
|
import { SupportDeskConflictError, SupportDeskNotFoundError, supportPermissions, supportTicketAuditActions, } from "./index.js";
|
|
2
3
|
import { canonical, fields, fingerprint, integer, text, timestamp, } from "./assistant-turn-records.js";
|
|
3
4
|
import { safeEngineeringFollowupSnapshot as snapshot, checkEngineeringFollowupRecordCapacity, reserveEngineeringFollowupReachable, engineeringFollowupWorkKey, engineeringFollowupCommandKey, validateEngineeringFollowupCandidate, validateEngineeringFollowupBundle, validateEngineeringFollowupModelBinding, engineeringFollowupModelManifestHash, engineeringFollowupContextHash, engineeringFollowupPublicFactsHash, engineeringFollowupChecksBinding, engineeringFollowupRetestProductBinding, engineeringFollowupRetestRequestHash, engineeringFollowupRetestRequestId, engineeringFollowupRetestSummary, engineeringFollowupModelAttemptCount, } from "./engineering-followup-records.js";
|
|
4
|
-
import { createEngineeringFollowupSourceServices, engineeringFollowupPermission as permission, engineeringFollowupNow, engineeringFollowupTrusted, engineeringFollowupResult as result, engineeringFollowupWithoutLease as withoutLease, engineeringFollowupCurrent, engineeringFollowupVersionsCurrent, holdEngineeringFollowupWork, engineeringFollowupAssociation, engineeringFollowupProofContext, engineeringFollowupValidateProof, planEngineeringFollowupContextChange, } from "./engineering-followup-source.js";
|
|
5
|
+
import { createEngineeringFollowupSourceServices, engineeringFollowupPermission as permission, engineeringFollowupObservation, engineeringFollowupNow, engineeringFollowupTrusted, engineeringFollowupResult as result, engineeringFollowupWithoutLease as withoutLease, engineeringFollowupCurrent, engineeringFollowupVersionsCurrent, holdEngineeringFollowupWork, engineeringFollowupAssociation, engineeringFollowupProofContext, engineeringFollowupValidateProof, planEngineeringFollowupContextChange, } from "./engineering-followup-source.js";
|
|
6
|
+
import { engineeringSourceKey } from "./engineering-records.js";
|
|
5
7
|
function validateFence(command) {
|
|
6
8
|
for (const value of [
|
|
7
9
|
command.ticketId,
|
|
@@ -71,11 +73,17 @@ export function createEngineeringFollowupWorkServices(deps, load) {
|
|
|
71
73
|
throw new SupportDeskNotFoundError();
|
|
72
74
|
return { value: work.value, version: work.version };
|
|
73
75
|
}
|
|
74
|
-
async function save(loaded, work, next, extra = [], nextHead = loaded.head?.value) {
|
|
76
|
+
async function save(loaded, work, next, extra = [], nextHead = loaded.head?.value, mutation, beforeCommit) {
|
|
75
77
|
if (loaded.head === null || nextHead === undefined)
|
|
76
78
|
throw new SupportDeskConflictError("Follow-up head disappeared");
|
|
77
79
|
checkEngineeringFollowupRecordCapacity(next);
|
|
78
80
|
checkEngineeringFollowupRecordCapacity(nextHead);
|
|
81
|
+
if (mutation !== undefined) {
|
|
82
|
+
const assertHostCurrent = await mutation.verify();
|
|
83
|
+
assertHostCurrent();
|
|
84
|
+
beforeCommit?.();
|
|
85
|
+
mutation.ensure();
|
|
86
|
+
}
|
|
79
87
|
return (await records.transact(next.ticketId, [
|
|
80
88
|
{
|
|
81
89
|
action: "putIfUnchanged",
|
|
@@ -188,6 +196,177 @@ export function createEngineeringFollowupWorkServices(deps, load) {
|
|
|
188
196
|
text(checked.verifierVersion, "verifierVersion");
|
|
189
197
|
return { binding, deadline: timestamp(checked.validUntil, "validUntil") };
|
|
190
198
|
}
|
|
199
|
+
class ReassessmentRetry extends Error {
|
|
200
|
+
}
|
|
201
|
+
async function prepareBundle(access, input, guard, control, initial) {
|
|
202
|
+
const actorId = access.principalId;
|
|
203
|
+
const command = snapshot(fields(input, "prepare follow-up bundle", [
|
|
204
|
+
"ticketId",
|
|
205
|
+
"workId",
|
|
206
|
+
"commandId",
|
|
207
|
+
"correlationId",
|
|
208
|
+
"mode",
|
|
209
|
+
"expectedWorkGeneration",
|
|
210
|
+
"candidateHash",
|
|
211
|
+
"exactBundle",
|
|
212
|
+
"checksReceipt",
|
|
213
|
+
"checkPolicyVersion",
|
|
214
|
+
], ["recoveryIntentHash"]));
|
|
215
|
+
if (command.mode !== "prepare" && command.mode !== "staff_reassessment")
|
|
216
|
+
throw new TypeError("Invalid follow-up preparation mode");
|
|
217
|
+
const staff = command.mode === "staff_reassessment";
|
|
218
|
+
const authorize = () => {
|
|
219
|
+
initial?.authorize();
|
|
220
|
+
permission(access, staff
|
|
221
|
+
? supportPermissions.engineeringFollowupReview
|
|
222
|
+
: supportPermissions.engineeringFollowupProcess, actorId, staff);
|
|
223
|
+
};
|
|
224
|
+
authorize();
|
|
225
|
+
const mutation = initial?.mutation ?? engineeringMutationBoundary(control);
|
|
226
|
+
for (const entry of [
|
|
227
|
+
command.ticketId,
|
|
228
|
+
command.workId,
|
|
229
|
+
command.commandId,
|
|
230
|
+
command.correlationId,
|
|
231
|
+
command.candidateHash,
|
|
232
|
+
command.checksReceipt,
|
|
233
|
+
command.checkPolicyVersion,
|
|
234
|
+
])
|
|
235
|
+
text(entry, "bundle preparation identity");
|
|
236
|
+
integer(command.expectedWorkGeneration, "expectedWorkGeneration", 1);
|
|
237
|
+
validateEngineeringFollowupBundle(command.exactBundle);
|
|
238
|
+
if (command.recoveryIntentHash !== undefined &&
|
|
239
|
+
(!staff || !/^[a-f0-9]{64}$/.test(command.recoveryIntentHash)))
|
|
240
|
+
throw new TypeError("Invalid preparation recovery intent");
|
|
241
|
+
const hash = await fingerprint({ actorId, command });
|
|
242
|
+
const recoveryFingerprint = command.recoveryIntentHash === undefined
|
|
243
|
+
? undefined
|
|
244
|
+
: await fingerprint({
|
|
245
|
+
actorId,
|
|
246
|
+
intentHash: command.recoveryIntentHash,
|
|
247
|
+
});
|
|
248
|
+
const now = engineeringFollowupNow(deps);
|
|
249
|
+
for (let attempt = 0; attempt < (initial === undefined ? deps.maxConflictAttempts : 1); attempt += 1) {
|
|
250
|
+
const previous = await replay(command, hash);
|
|
251
|
+
authorize();
|
|
252
|
+
if (previous !== null) {
|
|
253
|
+
if (!("status" in previous))
|
|
254
|
+
throw new SupportDeskConflictError("Wrong follow-up receipt kind");
|
|
255
|
+
return previous;
|
|
256
|
+
}
|
|
257
|
+
if (typeof guard !== "function")
|
|
258
|
+
throw new TypeError("Follow-up bundle preparation needs a current read-only guard");
|
|
259
|
+
mutation?.ensure();
|
|
260
|
+
const loaded = initial?.current.loaded ?? (await load(command.ticketId));
|
|
261
|
+
const stored = initial?.current.work ?? (await workAt(command));
|
|
262
|
+
const work = stored.value;
|
|
263
|
+
if (work.workGeneration !== command.expectedWorkGeneration ||
|
|
264
|
+
work.candidateHash !== command.candidateHash ||
|
|
265
|
+
work.withdrawn ||
|
|
266
|
+
work.status === "delivered" ||
|
|
267
|
+
work.security !== "ordinary")
|
|
268
|
+
throw new SupportDeskConflictError("Follow-up bundle source, candidate or generation changed");
|
|
269
|
+
if (!staff && timestamp(work.deadline, "deadline") <= now())
|
|
270
|
+
throw new SupportDeskConflictError("Expired follow-up requires explicit staff reassessment");
|
|
271
|
+
if (work.bundleRevision >= options.maxBundleRevisions)
|
|
272
|
+
throw new SupportDeskConflictError("Follow-up bundle preparation bound reached");
|
|
273
|
+
const rearm = staff
|
|
274
|
+
? await sourceServices.internal.reassessment(command, now, "bundle", initial?.current, initial === undefined ? undefined : () => initial.authorize())
|
|
275
|
+
: null;
|
|
276
|
+
const current = staff
|
|
277
|
+
? null
|
|
278
|
+
: await engineeringFollowupCurrent(deps, loaded, stored, now);
|
|
279
|
+
if (!staff &&
|
|
280
|
+
(work.stage !== "bundle" ||
|
|
281
|
+
work.candidate === undefined ||
|
|
282
|
+
work.completionRows !== 5 ||
|
|
283
|
+
work.messageSlots !== 1))
|
|
284
|
+
throw new SupportDeskConflictError("Follow-up bundle preparation needs its current model candidate");
|
|
285
|
+
if (rearm !== null &&
|
|
286
|
+
(rearm.work.version !== stored.version ||
|
|
287
|
+
rearm.loaded.ticket.version !== loaded.ticket.version ||
|
|
288
|
+
rearm.loaded.source.version !== loaded.source.version ||
|
|
289
|
+
rearm.loaded.head?.version !== loaded.head?.version)) {
|
|
290
|
+
if (initial !== undefined)
|
|
291
|
+
throw new ReassessmentRetry();
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
const base = rearm === null
|
|
295
|
+
? work
|
|
296
|
+
: {
|
|
297
|
+
...work,
|
|
298
|
+
...rearm.prospective,
|
|
299
|
+
workBindingHash: rearm.prospectiveWorkBindingHash,
|
|
300
|
+
};
|
|
301
|
+
const checked = await verifyBundle(base, command.exactBundle, command.candidateHash, command.checkPolicyVersion, command.checksReceipt, now);
|
|
302
|
+
const { candidate: _candidate, ...retained } = withoutLease(base);
|
|
303
|
+
const next = {
|
|
304
|
+
...retained,
|
|
305
|
+
stage: "bundle",
|
|
306
|
+
status: "bundle_prepared",
|
|
307
|
+
completionRows: 3,
|
|
308
|
+
messageSlots: 1,
|
|
309
|
+
deadline: staff
|
|
310
|
+
? new Date(now() + options.maxWorkMilliseconds).toISOString()
|
|
311
|
+
: work.deadline,
|
|
312
|
+
workGeneration: work.workGeneration + 1,
|
|
313
|
+
bundleRevision: work.bundleRevision + 1,
|
|
314
|
+
preparedBundle: {
|
|
315
|
+
bundle: command.exactBundle,
|
|
316
|
+
bundleHash: checked.binding.bundleHash,
|
|
317
|
+
candidateHash: command.candidateHash,
|
|
318
|
+
binding: checked.binding,
|
|
319
|
+
checksReceipt: command.checksReceipt,
|
|
320
|
+
},
|
|
321
|
+
};
|
|
322
|
+
reserveEngineeringFollowupReachable(next);
|
|
323
|
+
const cohort = rearm === null
|
|
324
|
+
? { actions: [], nextWorkRows: [] }
|
|
325
|
+
: await planEngineeringFollowupContextChange(records, {
|
|
326
|
+
rows: loaded.rows,
|
|
327
|
+
context: rearm.proof.context,
|
|
328
|
+
previousHead: loaded.head.value,
|
|
329
|
+
nextHead: rearm.nextHead,
|
|
330
|
+
excludedWorkId: work.workId,
|
|
331
|
+
});
|
|
332
|
+
deps.admission(loaded.ticket.value.ticket, loaded.rows, [next, ...cohort.nextWorkRows], 2);
|
|
333
|
+
await guard();
|
|
334
|
+
const proof = rearm?.proof ?? current.proof;
|
|
335
|
+
const associationWork = rearm?.engineeringWork ?? current.engineeringWork;
|
|
336
|
+
if (!(await engineeringFollowupVersionsCurrent(deps, loaded, stored, associationWork))) {
|
|
337
|
+
if (initial !== undefined)
|
|
338
|
+
throw new ReassessmentRetry();
|
|
339
|
+
continue;
|
|
340
|
+
}
|
|
341
|
+
authorize();
|
|
342
|
+
if (!staff && timestamp(work.deadline, "deadline") <= now())
|
|
343
|
+
throw new SupportDeskConflictError("Expired follow-up requires explicit staff reassessment");
|
|
344
|
+
if (now() >=
|
|
345
|
+
Math.min(checked.deadline, timestamp(proof.attestation.validUntil, "validUntil"), timestamp(next.deadline, "deadline")))
|
|
346
|
+
throw new SupportDeskConflictError("Follow-up bundle proof expired");
|
|
347
|
+
const outcome = result(next);
|
|
348
|
+
if (await save(loaded, stored, next, [
|
|
349
|
+
...cohort.actions,
|
|
350
|
+
deps.audit(command, loaded.ticket.value.ticket, actorId, clock.now(), staff
|
|
351
|
+
? supportTicketAuditActions.engineeringFollowupReassessed
|
|
352
|
+
: supportTicketAuditActions.engineeringFollowupBundlePrepared),
|
|
353
|
+
receipt(command, hash, outcome, recoveryFingerprint),
|
|
354
|
+
], rearm?.nextHead ?? loaded.head.value, mutation, () => {
|
|
355
|
+
authorize();
|
|
356
|
+
if (!staff && timestamp(work.deadline, "deadline") <= now())
|
|
357
|
+
throw new SupportDeskConflictError("Expired follow-up requires explicit staff reassessment");
|
|
358
|
+
if (now() >=
|
|
359
|
+
Math.min(checked.deadline, timestamp(proof.attestation.validUntil, "validUntil"), timestamp(next.deadline, "deadline")))
|
|
360
|
+
throw new SupportDeskConflictError("Follow-up bundle proof expired");
|
|
361
|
+
})) {
|
|
362
|
+
authorize();
|
|
363
|
+
return outcome;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
if (initial !== undefined)
|
|
367
|
+
throw new ReassessmentRetry();
|
|
368
|
+
throw new SupportDeskConflictError();
|
|
369
|
+
}
|
|
191
370
|
const application = {
|
|
192
371
|
async claimEngineeringFollowupWork(access, input) {
|
|
193
372
|
const actorId = access.principalId;
|
|
@@ -649,159 +828,155 @@ export function createEngineeringFollowupWorkServices(deps, load) {
|
|
|
649
828
|
throw new SupportDeskConflictError("Follow-up recovery identity does not match accepted preparation");
|
|
650
829
|
return snapshot(prior.result);
|
|
651
830
|
},
|
|
652
|
-
async prepareEngineeringFollowupBundle(access, input, guard) {
|
|
831
|
+
async prepareEngineeringFollowupBundle(access, input, guard, control) {
|
|
832
|
+
return prepareBundle(access, input, guard, control);
|
|
833
|
+
},
|
|
834
|
+
async reassessEngineeringFollowupBundle(access, input, planner, control) {
|
|
653
835
|
const actorId = access.principalId;
|
|
654
|
-
const
|
|
836
|
+
const authorize = (reader = access) => {
|
|
837
|
+
permission(reader, supportPermissions.engineeringFollowupReview, actorId, true);
|
|
838
|
+
permission(reader, supportPermissions.engineeringFollowupRead, actorId, true);
|
|
839
|
+
};
|
|
840
|
+
permission(access, supportPermissions.engineeringFollowupReview, actorId, true);
|
|
841
|
+
const command = snapshot(fields(input, "reassess follow-up bundle", [
|
|
655
842
|
"ticketId",
|
|
656
843
|
"workId",
|
|
657
844
|
"commandId",
|
|
658
845
|
"correlationId",
|
|
659
|
-
"mode",
|
|
660
846
|
"expectedWorkGeneration",
|
|
661
|
-
"
|
|
662
|
-
|
|
663
|
-
"checksReceipt",
|
|
664
|
-
"checkPolicyVersion",
|
|
665
|
-
], ["recoveryIntentHash"]));
|
|
666
|
-
if (command.mode !== "prepare" && command.mode !== "staff_reassessment")
|
|
667
|
-
throw new TypeError("Invalid follow-up preparation mode");
|
|
668
|
-
const staff = command.mode === "staff_reassessment";
|
|
669
|
-
const authorize = () => permission(access, staff
|
|
670
|
-
? supportPermissions.engineeringFollowupReview
|
|
671
|
-
: supportPermissions.engineeringFollowupProcess, actorId, staff);
|
|
672
|
-
authorize();
|
|
847
|
+
"recoveryIntentHash",
|
|
848
|
+
]));
|
|
673
849
|
for (const entry of [
|
|
674
850
|
command.ticketId,
|
|
675
851
|
command.workId,
|
|
676
852
|
command.commandId,
|
|
677
853
|
command.correlationId,
|
|
678
|
-
command.candidateHash,
|
|
679
|
-
command.checksReceipt,
|
|
680
|
-
command.checkPolicyVersion,
|
|
681
854
|
])
|
|
682
|
-
text(entry, "
|
|
855
|
+
text(entry, "reassessment identity");
|
|
683
856
|
integer(command.expectedWorkGeneration, "expectedWorkGeneration", 1);
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
});
|
|
857
|
+
if (!/^[a-f0-9]{64}$/.test(command.recoveryIntentHash))
|
|
858
|
+
throw new TypeError("Invalid reassessment recovery intent");
|
|
859
|
+
const recovery = {
|
|
860
|
+
ticketId: command.ticketId,
|
|
861
|
+
workId: command.workId,
|
|
862
|
+
commandId: command.commandId,
|
|
863
|
+
recoveryIntentHash: command.recoveryIntentHash,
|
|
864
|
+
};
|
|
865
|
+
const recover = () => application.recoverEngineeringFollowupPreparation(access, recovery);
|
|
866
|
+
const mutation = engineeringMutationBoundary(control);
|
|
695
867
|
const now = engineeringFollowupNow(deps);
|
|
696
868
|
for (let attempt = 0; attempt < deps.maxConflictAttempts; attempt += 1) {
|
|
697
|
-
const
|
|
869
|
+
const accepted = await recover();
|
|
870
|
+
if (accepted !== null)
|
|
871
|
+
return accepted;
|
|
872
|
+
mutation?.ensure();
|
|
698
873
|
authorize();
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
throw new SupportDeskConflictError("Wrong follow-up receipt kind");
|
|
702
|
-
return previous;
|
|
703
|
-
}
|
|
704
|
-
if (typeof guard !== "function")
|
|
705
|
-
throw new TypeError("Follow-up bundle preparation needs a current read-only guard");
|
|
706
|
-
const loaded = await load(command.ticketId);
|
|
707
|
-
const stored = await workAt(command);
|
|
708
|
-
const work = stored.value;
|
|
709
|
-
if (work.workGeneration !== command.expectedWorkGeneration ||
|
|
710
|
-
work.candidateHash !== command.candidateHash ||
|
|
711
|
-
work.withdrawn ||
|
|
712
|
-
work.status === "delivered" ||
|
|
713
|
-
work.security !== "ordinary")
|
|
714
|
-
throw new SupportDeskConflictError("Follow-up bundle source, candidate or generation changed");
|
|
715
|
-
if (!staff && timestamp(work.deadline, "deadline") <= now())
|
|
716
|
-
throw new SupportDeskConflictError("Expired follow-up requires explicit staff reassessment");
|
|
717
|
-
if (work.bundleRevision >= options.maxBundleRevisions)
|
|
718
|
-
throw new SupportDeskConflictError("Follow-up bundle preparation bound reached");
|
|
719
|
-
const rearm = staff
|
|
720
|
-
? await sourceServices.internal.reassessment(command, now)
|
|
721
|
-
: null;
|
|
722
|
-
const current = staff
|
|
723
|
-
? null
|
|
724
|
-
: await engineeringFollowupCurrent(deps, loaded, stored, now);
|
|
725
|
-
if (!staff &&
|
|
726
|
-
(work.stage !== "bundle" ||
|
|
727
|
-
work.candidate === undefined ||
|
|
728
|
-
work.completionRows !== 5 ||
|
|
729
|
-
work.messageSlots !== 1))
|
|
730
|
-
throw new SupportDeskConflictError("Follow-up bundle preparation needs its current model candidate");
|
|
731
|
-
if (rearm !== null &&
|
|
732
|
-
(rearm.work.version !== stored.version ||
|
|
733
|
-
rearm.loaded.ticket.version !== loaded.ticket.version ||
|
|
734
|
-
rearm.loaded.source.version !== loaded.source.version ||
|
|
735
|
-
rearm.loaded.head?.version !== loaded.head?.version))
|
|
736
|
-
continue;
|
|
737
|
-
const base = rearm === null
|
|
738
|
-
? work
|
|
739
|
-
: {
|
|
740
|
-
...work,
|
|
741
|
-
...rearm.prospective,
|
|
742
|
-
workBindingHash: rearm.prospectiveWorkBindingHash,
|
|
743
|
-
};
|
|
744
|
-
const checked = await verifyBundle(base, command.exactBundle, command.candidateHash, command.checkPolicyVersion, command.checksReceipt, now);
|
|
745
|
-
const { candidate: _candidate, ...retained } = withoutLease(base);
|
|
746
|
-
const next = {
|
|
747
|
-
...retained,
|
|
748
|
-
stage: "bundle",
|
|
749
|
-
status: "bundle_prepared",
|
|
750
|
-
completionRows: 3,
|
|
751
|
-
messageSlots: 1,
|
|
752
|
-
deadline: staff
|
|
753
|
-
? new Date(now() + options.maxWorkMilliseconds).toISOString()
|
|
754
|
-
: work.deadline,
|
|
755
|
-
workGeneration: work.workGeneration + 1,
|
|
756
|
-
bundleRevision: work.bundleRevision + 1,
|
|
757
|
-
preparedBundle: {
|
|
758
|
-
bundle: command.exactBundle,
|
|
759
|
-
bundleHash: checked.binding.bundleHash,
|
|
760
|
-
candidateHash: command.candidateHash,
|
|
761
|
-
binding: checked.binding,
|
|
762
|
-
checksReceipt: command.checksReceipt,
|
|
763
|
-
},
|
|
764
|
-
};
|
|
765
|
-
reserveEngineeringFollowupReachable(next);
|
|
766
|
-
const cohort = rearm === null
|
|
767
|
-
? { actions: [], nextWorkRows: [] }
|
|
768
|
-
: await planEngineeringFollowupContextChange(records, {
|
|
769
|
-
rows: loaded.rows,
|
|
770
|
-
context: rearm.proof.context,
|
|
771
|
-
previousHead: loaded.head.value,
|
|
772
|
-
nextHead: rearm.nextHead,
|
|
773
|
-
excludedWorkId: work.workId,
|
|
774
|
-
});
|
|
775
|
-
deps.admission(loaded.ticket.value.ticket, loaded.rows, [next, ...cohort.nextWorkRows], 2);
|
|
776
|
-
await guard();
|
|
777
|
-
const proof = rearm?.proof ?? current.proof;
|
|
778
|
-
const associationWork = rearm?.engineeringWork ?? current.engineeringWork;
|
|
779
|
-
if (!(await engineeringFollowupVersionsCurrent(deps, loaded, stored, associationWork)))
|
|
780
|
-
continue;
|
|
874
|
+
// Eligibility and private Source authority precede any protected hydration.
|
|
875
|
+
const source = await records.getVersioned(engineeringSourceKey(command.ticketId));
|
|
781
876
|
authorize();
|
|
782
|
-
if (
|
|
783
|
-
throw new
|
|
784
|
-
if (
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
877
|
+
if (source?.value.kind !== "engineering_source")
|
|
878
|
+
throw new SupportDeskNotFoundError();
|
|
879
|
+
if (source.value.security !== "ordinary")
|
|
880
|
+
permission(access, supportPermissions.engineeringRestrictedRead, actorId, true);
|
|
881
|
+
if (source.value.security !== "ordinary" ||
|
|
882
|
+
source.value.state === "withdrawn")
|
|
883
|
+
throw new SupportDeskConflictError("Follow-up Source is ineligible");
|
|
884
|
+
try {
|
|
885
|
+
const current = await sourceServices.internal.reassessment(command, now, "bundle", undefined, (source) => {
|
|
886
|
+
authorize();
|
|
887
|
+
if (source !== undefined) {
|
|
888
|
+
if (source.security !== "ordinary")
|
|
889
|
+
permission(access, supportPermissions.engineeringRestrictedRead, actorId, true);
|
|
890
|
+
if (source.security !== "ordinary" ||
|
|
891
|
+
source.state === "withdrawn")
|
|
892
|
+
throw new SupportDeskConflictError("Follow-up Source is ineligible");
|
|
893
|
+
}
|
|
894
|
+
}, { value: source.value, version: source.version });
|
|
895
|
+
// The initial external release proof cannot make the original Source current.
|
|
896
|
+
const confirmedSource = await records.getVersioned(engineeringSourceKey(command.ticketId));
|
|
795
897
|
authorize();
|
|
796
|
-
|
|
898
|
+
if (confirmedSource?.value.kind !== "engineering_source")
|
|
899
|
+
throw new SupportDeskNotFoundError();
|
|
900
|
+
if (confirmedSource.value.security !== "ordinary")
|
|
901
|
+
permission(access, supportPermissions.engineeringRestrictedRead, actorId, true);
|
|
902
|
+
if (confirmedSource.version !== source.version)
|
|
903
|
+
throw new SupportDeskConflictError("Follow-up Source changed during release verification");
|
|
904
|
+
if (current.work.value.workGeneration !== command.expectedWorkGeneration)
|
|
905
|
+
throw new SupportDeskConflictError("Follow-up reassessment generation changed");
|
|
906
|
+
const scope = engineeringFollowupObservation(deps, current, actorId, authorize);
|
|
907
|
+
const observation = Object.freeze({
|
|
908
|
+
read(reader) {
|
|
909
|
+
scope.check(reader);
|
|
910
|
+
if (now() >=
|
|
911
|
+
timestamp(current.proof.attestation.validUntil, "validUntil"))
|
|
912
|
+
throw new SupportDeskConflictError("Follow-up reassessment expired");
|
|
913
|
+
return snapshot({
|
|
914
|
+
work: current.work.value,
|
|
915
|
+
proofContext: current.proofContext,
|
|
916
|
+
proof: current.proof,
|
|
917
|
+
prospectiveWorkBindingHash: current.prospectiveWorkBindingHash,
|
|
918
|
+
workGeneration: current.work.value.workGeneration,
|
|
919
|
+
});
|
|
920
|
+
},
|
|
921
|
+
confirm: scope.confirm,
|
|
922
|
+
});
|
|
923
|
+
try {
|
|
924
|
+
if (typeof planner !== "function")
|
|
925
|
+
throw new TypeError("Follow-up reassessment needs a read-only planner");
|
|
926
|
+
const planned = await planner(observation);
|
|
927
|
+
authorize();
|
|
928
|
+
const checkedPlan = fields(planned, "follow-up reassessment plan", [
|
|
929
|
+
"candidateHash",
|
|
930
|
+
"exactBundle",
|
|
931
|
+
"checksReceipt",
|
|
932
|
+
"checkPolicyVersion",
|
|
933
|
+
"guard",
|
|
934
|
+
]);
|
|
935
|
+
const guard = checkedPlan.guard;
|
|
936
|
+
if (typeof guard !== "function")
|
|
937
|
+
throw new TypeError("Follow-up reassessment guard must be callable");
|
|
938
|
+
const plan = snapshot({
|
|
939
|
+
candidateHash: checkedPlan.candidateHash,
|
|
940
|
+
exactBundle: checkedPlan.exactBundle,
|
|
941
|
+
checksReceipt: checkedPlan.checksReceipt,
|
|
942
|
+
checkPolicyVersion: checkedPlan.checkPolicyVersion,
|
|
943
|
+
});
|
|
944
|
+
// Functions never enter the immutable command or an accepted receipt.
|
|
945
|
+
return await prepareBundle(access, {
|
|
946
|
+
...command,
|
|
947
|
+
mode: "staff_reassessment",
|
|
948
|
+
candidateHash: plan.candidateHash,
|
|
949
|
+
exactBundle: plan.exactBundle,
|
|
950
|
+
checksReceipt: plan.checksReceipt,
|
|
951
|
+
checkPolicyVersion: plan.checkPolicyVersion,
|
|
952
|
+
}, async () => {
|
|
953
|
+
try {
|
|
954
|
+
return await guard();
|
|
955
|
+
}
|
|
956
|
+
finally {
|
|
957
|
+
scope.close();
|
|
958
|
+
}
|
|
959
|
+
}, undefined, { current, mutation, authorize });
|
|
960
|
+
}
|
|
961
|
+
finally {
|
|
962
|
+
scope.close();
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
catch (error) {
|
|
966
|
+
const accepted = await recover();
|
|
967
|
+
if (accepted !== null)
|
|
968
|
+
return accepted;
|
|
969
|
+
if (!(error instanceof ReassessmentRetry))
|
|
970
|
+
throw error;
|
|
797
971
|
}
|
|
798
972
|
}
|
|
799
973
|
throw new SupportDeskConflictError();
|
|
800
974
|
},
|
|
801
|
-
async retryEngineeringFollowupWork(access, input) {
|
|
975
|
+
async retryEngineeringFollowupWork(access, input, control) {
|
|
802
976
|
const actorId = access.principalId;
|
|
803
977
|
const authorize = () => permission(access, supportPermissions.engineeringFollowupReview, actorId, true);
|
|
804
978
|
authorize();
|
|
979
|
+
const mutation = engineeringMutationBoundary(control);
|
|
805
980
|
const now = engineeringFollowupNow(deps);
|
|
806
981
|
const command = snapshot(fields(input, "retry follow-up work", [
|
|
807
982
|
"ticketId",
|
|
@@ -827,6 +1002,7 @@ export function createEngineeringFollowupWorkServices(deps, load) {
|
|
|
827
1002
|
throw new SupportDeskConflictError("Wrong follow-up receipt kind");
|
|
828
1003
|
return previous;
|
|
829
1004
|
}
|
|
1005
|
+
mutation?.ensure();
|
|
830
1006
|
const current = await sourceServices.internal.reassessment(command, now, "model_retry");
|
|
831
1007
|
const { loaded, work: stored } = current;
|
|
832
1008
|
const work = stored.value;
|
|
@@ -865,17 +1041,23 @@ export function createEngineeringFollowupWorkServices(deps, load) {
|
|
|
865
1041
|
...cohort.actions,
|
|
866
1042
|
deps.audit(command, loaded.ticket.value.ticket, actorId, clock.now(), supportTicketAuditActions.engineeringFollowupRetried),
|
|
867
1043
|
receipt(command, hash, outcome),
|
|
868
|
-
], current.nextHead)
|
|
1044
|
+
], current.nextHead, mutation, () => {
|
|
1045
|
+
authorize();
|
|
1046
|
+
if (now() >=
|
|
1047
|
+
Math.min(timestamp(current.proof.attestation.validUntil, "validUntil"), timestamp(next.deadline, "deadline")))
|
|
1048
|
+
throw new SupportDeskConflictError("Follow-up recovery proof expired");
|
|
1049
|
+
})) {
|
|
869
1050
|
authorize();
|
|
870
1051
|
return outcome;
|
|
871
1052
|
}
|
|
872
1053
|
}
|
|
873
1054
|
throw new SupportDeskConflictError();
|
|
874
1055
|
},
|
|
875
|
-
async abandonEngineeringFollowupWork(access, input) {
|
|
1056
|
+
async abandonEngineeringFollowupWork(access, input, control) {
|
|
876
1057
|
const actorId = access.principalId;
|
|
877
1058
|
const authorize = () => permission(access, supportPermissions.engineeringFollowupReview, actorId, true);
|
|
878
1059
|
authorize();
|
|
1060
|
+
const mutation = engineeringMutationBoundary(control);
|
|
879
1061
|
const command = snapshot(fields(input, "abandon follow-up work", [
|
|
880
1062
|
"ticketId",
|
|
881
1063
|
"workId",
|
|
@@ -902,6 +1084,7 @@ export function createEngineeringFollowupWorkServices(deps, load) {
|
|
|
902
1084
|
throw new SupportDeskConflictError("Wrong follow-up receipt kind");
|
|
903
1085
|
return previous;
|
|
904
1086
|
}
|
|
1087
|
+
mutation?.ensure();
|
|
905
1088
|
const loaded = await load(command.ticketId);
|
|
906
1089
|
const stored = await workAt(command);
|
|
907
1090
|
const work = stored.value;
|
|
@@ -916,17 +1099,18 @@ export function createEngineeringFollowupWorkServices(deps, load) {
|
|
|
916
1099
|
if (await save(loaded, stored, next, [
|
|
917
1100
|
deps.audit(command, loaded.ticket.value.ticket, actorId, clock.now(), supportTicketAuditActions.engineeringFollowupAbandoned),
|
|
918
1101
|
receipt(command, hash, outcome),
|
|
919
|
-
])) {
|
|
1102
|
+
], loaded.head?.value, mutation, authorize)) {
|
|
920
1103
|
authorize();
|
|
921
1104
|
return outcome;
|
|
922
1105
|
}
|
|
923
1106
|
}
|
|
924
1107
|
throw new SupportDeskConflictError();
|
|
925
1108
|
},
|
|
926
|
-
async rejectEngineeringFollowupBundle(access, input) {
|
|
1109
|
+
async rejectEngineeringFollowupBundle(access, input, control) {
|
|
927
1110
|
const actorId = access.principalId;
|
|
928
1111
|
const authorize = () => permission(access, supportPermissions.engineeringFollowupReview, actorId, true);
|
|
929
1112
|
authorize();
|
|
1113
|
+
const mutation = engineeringMutationBoundary(control);
|
|
930
1114
|
const command = snapshot(fields(input, "reject follow-up bundle", [
|
|
931
1115
|
"ticketId",
|
|
932
1116
|
"workId",
|
|
@@ -955,6 +1139,7 @@ export function createEngineeringFollowupWorkServices(deps, load) {
|
|
|
955
1139
|
throw new SupportDeskConflictError("Wrong follow-up receipt kind");
|
|
956
1140
|
return previous;
|
|
957
1141
|
}
|
|
1142
|
+
mutation?.ensure();
|
|
958
1143
|
const loaded = await load(command.ticketId);
|
|
959
1144
|
const stored = await workAt(command);
|
|
960
1145
|
const work = stored.value;
|
|
@@ -970,7 +1155,7 @@ export function createEngineeringFollowupWorkServices(deps, load) {
|
|
|
970
1155
|
if (await save(loaded, stored, next, [
|
|
971
1156
|
deps.audit(command, loaded.ticket.value.ticket, actorId, clock.now(), supportTicketAuditActions.engineeringFollowupRejected),
|
|
972
1157
|
receipt(command, hash, outcome),
|
|
973
|
-
])) {
|
|
1158
|
+
], loaded.head?.value, mutation, authorize)) {
|
|
974
1159
|
authorize();
|
|
975
1160
|
return outcome;
|
|
976
1161
|
}
|