@pegma/support-desk-application 0.2.6 → 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.
@@ -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 command = snapshot(fields(input, "prepare follow-up bundle", [
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
- "candidateHash",
662
- "exactBundle",
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, "bundle preparation identity");
855
+ text(entry, "reassessment identity");
683
856
  integer(command.expectedWorkGeneration, "expectedWorkGeneration", 1);
684
- validateEngineeringFollowupBundle(command.exactBundle);
685
- if (command.recoveryIntentHash !== undefined &&
686
- (!staff || !/^[a-f0-9]{64}$/.test(command.recoveryIntentHash)))
687
- throw new TypeError("Invalid preparation recovery intent");
688
- const hash = await fingerprint({ actorId, command });
689
- const recoveryFingerprint = command.recoveryIntentHash === undefined
690
- ? undefined
691
- : await fingerprint({
692
- actorId,
693
- intentHash: command.recoveryIntentHash,
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 previous = await replay(command, hash);
869
+ const accepted = await recover();
870
+ if (accepted !== null)
871
+ return accepted;
872
+ mutation?.ensure();
698
873
  authorize();
699
- if (previous !== null) {
700
- if (!("status" in previous))
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 (!staff && timestamp(work.deadline, "deadline") <= now())
783
- throw new SupportDeskConflictError("Expired follow-up requires explicit staff reassessment");
784
- if (now() >=
785
- Math.min(checked.deadline, timestamp(proof.attestation.validUntil, "validUntil"), timestamp(next.deadline, "deadline")))
786
- throw new SupportDeskConflictError("Follow-up bundle proof expired");
787
- const outcome = result(next);
788
- if (await save(loaded, stored, next, [
789
- ...cohort.actions,
790
- deps.audit(command, loaded.ticket.value.ticket, actorId, clock.now(), staff
791
- ? supportTicketAuditActions.engineeringFollowupReassessed
792
- : supportTicketAuditActions.engineeringFollowupBundlePrepared),
793
- receipt(command, hash, outcome, recoveryFingerprint),
794
- ], rearm?.nextHead ?? loaded.head.value)) {
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
- return outcome;
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
  }