@pegma/support-desk-application 0.1.1 → 0.2.0

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.
Files changed (86) hide show
  1. package/README.md +228 -7
  2. package/dist/assistant-turn-capacity.d.ts +13 -0
  3. package/dist/assistant-turn-capacity.d.ts.map +1 -0
  4. package/dist/assistant-turn-capacity.js +111 -0
  5. package/dist/assistant-turn-capacity.js.map +1 -0
  6. package/dist/assistant-turn-records.d.ts +68 -0
  7. package/dist/assistant-turn-records.d.ts.map +1 -0
  8. package/dist/assistant-turn-records.js +418 -0
  9. package/dist/assistant-turn-records.js.map +1 -0
  10. package/dist/assistant-turn-types.d.ts +133 -0
  11. package/dist/assistant-turn-types.d.ts.map +1 -0
  12. package/dist/assistant-turn-types.js +2 -0
  13. package/dist/assistant-turn-types.js.map +1 -0
  14. package/dist/assistant-turns.d.ts +42 -0
  15. package/dist/assistant-turns.d.ts.map +1 -0
  16. package/dist/assistant-turns.js +640 -0
  17. package/dist/assistant-turns.js.map +1 -0
  18. package/dist/engineering-evidence.d.ts +20 -0
  19. package/dist/engineering-evidence.d.ts.map +1 -0
  20. package/dist/engineering-evidence.js +899 -0
  21. package/dist/engineering-evidence.js.map +1 -0
  22. package/dist/engineering-followup-internal.d.ts +45 -0
  23. package/dist/engineering-followup-internal.d.ts.map +1 -0
  24. package/dist/engineering-followup-internal.js +2 -0
  25. package/dist/engineering-followup-internal.js.map +1 -0
  26. package/dist/engineering-followup-records.d.ts +209 -0
  27. package/dist/engineering-followup-records.d.ts.map +1 -0
  28. package/dist/engineering-followup-records.js +1434 -0
  29. package/dist/engineering-followup-records.js.map +1 -0
  30. package/dist/engineering-followup-source.d.ts +168 -0
  31. package/dist/engineering-followup-source.d.ts.map +1 -0
  32. package/dist/engineering-followup-source.js +1144 -0
  33. package/dist/engineering-followup-source.js.map +1 -0
  34. package/dist/engineering-followup-types.d.ts +364 -0
  35. package/dist/engineering-followup-types.d.ts.map +1 -0
  36. package/dist/engineering-followup-types.js +2 -0
  37. package/dist/engineering-followup-types.js.map +1 -0
  38. package/dist/engineering-followup-work.d.ts +70 -0
  39. package/dist/engineering-followup-work.d.ts.map +1 -0
  40. package/dist/engineering-followup-work.js +1153 -0
  41. package/dist/engineering-followup-work.js.map +1 -0
  42. package/dist/engineering-internal.d.ts +38 -0
  43. package/dist/engineering-internal.d.ts.map +1 -0
  44. package/dist/engineering-internal.js +11 -0
  45. package/dist/engineering-internal.js.map +1 -0
  46. package/dist/engineering-policy.d.ts +47 -0
  47. package/dist/engineering-policy.d.ts.map +1 -0
  48. package/dist/engineering-policy.js +579 -0
  49. package/dist/engineering-policy.js.map +1 -0
  50. package/dist/engineering-reconciliation.d.ts +6 -0
  51. package/dist/engineering-reconciliation.d.ts.map +1 -0
  52. package/dist/engineering-reconciliation.js +464 -0
  53. package/dist/engineering-reconciliation.js.map +1 -0
  54. package/dist/engineering-records.d.ts +227 -0
  55. package/dist/engineering-records.d.ts.map +1 -0
  56. package/dist/engineering-records.js +1272 -0
  57. package/dist/engineering-records.js.map +1 -0
  58. package/dist/engineering-types.d.ts +505 -0
  59. package/dist/engineering-types.d.ts.map +1 -0
  60. package/dist/engineering-types.js +2 -0
  61. package/dist/engineering-types.js.map +1 -0
  62. package/dist/engineering-work.d.ts +32 -0
  63. package/dist/engineering-work.d.ts.map +1 -0
  64. package/dist/engineering-work.js +1757 -0
  65. package/dist/engineering-work.js.map +1 -0
  66. package/dist/index.d.ts +609 -14
  67. package/dist/index.d.ts.map +1 -1
  68. package/dist/index.js +3568 -204
  69. package/dist/index.js.map +1 -1
  70. package/dist/resolution-learning-records.d.ts +154 -0
  71. package/dist/resolution-learning-records.d.ts.map +1 -0
  72. package/dist/resolution-learning-records.js +657 -0
  73. package/dist/resolution-learning-records.js.map +1 -0
  74. package/dist/resolution-learning-types.d.ts +356 -0
  75. package/dist/resolution-learning-types.d.ts.map +1 -0
  76. package/dist/resolution-learning-types.js +2 -0
  77. package/dist/resolution-learning-types.js.map +1 -0
  78. package/dist/resolution-learning-worker.d.ts +21 -0
  79. package/dist/resolution-learning-worker.d.ts.map +1 -0
  80. package/dist/resolution-learning-worker.js +1027 -0
  81. package/dist/resolution-learning-worker.js.map +1 -0
  82. package/dist/resolution-learning.d.ts +38 -0
  83. package/dist/resolution-learning.d.ts.map +1 -0
  84. package/dist/resolution-learning.js +860 -0
  85. package/dist/resolution-learning.js.map +1 -0
  86. package/package.json +6 -6
@@ -0,0 +1,1757 @@
1
+ import { hasPermission } from "@pegma/authorization-core";
2
+ import { MAX_SCAN_PAGE_SIZE } from "@pegma/storage-core";
3
+ import { SupportDeskAuthorizationError, SupportDeskConflictError, SupportDeskLimitError, SupportDeskNotFoundError, supportPermissions, } from "./index.js";
4
+ import { canonical, dataArray, encodedBytes, fields, fingerprint, integer, text, timestamp, } from "./assistant-turn-records.js";
5
+ import { safeEngineeringSnapshot as snapshot, checkEngineeringRecordCapacity, advanceEngineeringFollowupRevocationEpoch, engineeringMaximumCandidateBytes, engineeringMaximumRecordBytes, engineeringCommandKey, engineeringSourceKey, engineeringWorkKey, validateEngineeringCandidate, validateEngineeringModelBinding, validateEngineeringExportRequest, } from "./engineering-records.js";
6
+ import { projectEngineeringRows } from "./engineering-internal.js";
7
+ export async function engineeringModelManifestHash(manifest) {
8
+ const value = fields(manifest, "engineering input manifest", [
9
+ "messages",
10
+ "evidenceFingerprint",
11
+ "product",
12
+ "policyGeneration",
13
+ "policyHash",
14
+ "repositoryIds",
15
+ ]);
16
+ const seen = new Set();
17
+ for (const item of dataArray(value.messages, "manifest.messages", 100)) {
18
+ const message = fields(item, "manifest message", ["messageId", "ordinal"]);
19
+ text(message.messageId, "messageId");
20
+ integer(message.ordinal, "ordinal", 1);
21
+ if (seen.has(message.messageId))
22
+ throw new TypeError("Duplicate engineering manifest message");
23
+ seen.add(message.messageId);
24
+ }
25
+ text(value.evidenceFingerprint, "manifest.evidenceFingerprint");
26
+ integer(value.policyGeneration, "manifest.policyGeneration", 0);
27
+ text(value.policyHash, "manifest.policyHash");
28
+ const product = fields(value.product, "manifest.product", [
29
+ "productId",
30
+ "version",
31
+ "buildId",
32
+ "binding",
33
+ "contextVersion",
34
+ ]);
35
+ for (const key of Object.keys(product))
36
+ text(product[key], `manifest.product.${key}`);
37
+ const repositories = dataArray(value.repositoryIds, "manifest.repositoryIds", 20).map((id) => text(id, "manifest.repositoryId"));
38
+ if (new Set(repositories).size !== repositories.length)
39
+ throw new TypeError("Duplicate engineering manifest repository");
40
+ return fingerprint(manifest);
41
+ }
42
+ export async function engineeringRoutingBinding(input) {
43
+ const { routing, ...binding } = input;
44
+ return { ...binding, routingHash: await fingerprint(routing) };
45
+ }
46
+ export async function engineeringRoutingBindingHash(binding) {
47
+ return fingerprint(binding);
48
+ }
49
+ export async function engineeringStaffSelectionHash(selection) {
50
+ return fingerprint({ kind: "staff_selection", selection });
51
+ }
52
+ export async function engineeringExportChecksBinding(input) {
53
+ const { exactRequest, ...binding } = input;
54
+ return {
55
+ ...binding,
56
+ requestHash: await fingerprint(exactRequest),
57
+ repositoryMetadataHash: await fingerprint(exactRequest.repository),
58
+ };
59
+ }
60
+ function permission(access, review = false, expectedActor) {
61
+ text(access.principalId, "principalId");
62
+ const name = review
63
+ ? supportPermissions.engineeringReview
64
+ : supportPermissions.engineeringProcess;
65
+ if (expectedActor !== undefined && access.principalId !== expectedActor)
66
+ throw new SupportDeskAuthorizationError(name);
67
+ if (!hasPermission(access, name))
68
+ throw new SupportDeskAuthorizationError(name);
69
+ if (review && !hasPermission(access, supportPermissions.queueRead))
70
+ throw new SupportDeskAuthorizationError(supportPermissions.queueRead);
71
+ }
72
+ function scanPermission(access, actorId) {
73
+ text(access.principalId, "principalId");
74
+ if (access.principalId !== actorId)
75
+ throw new SupportDeskAuthorizationError(supportPermissions.engineeringProcess);
76
+ const process = hasPermission(access, supportPermissions.engineeringProcess);
77
+ const review = hasPermission(access, supportPermissions.engineeringReview) &&
78
+ hasPermission(access, supportPermissions.queueRead);
79
+ if (!process && !review)
80
+ throw new SupportDeskAuthorizationError(supportPermissions.engineeringProcess);
81
+ return {
82
+ process,
83
+ review,
84
+ restricted: review &&
85
+ hasPermission(access, supportPermissions.engineeringRestrictedRead),
86
+ };
87
+ }
88
+ function fence(command) {
89
+ text(command.ticketId, "ticketId");
90
+ integer(command.sourceGeneration, "sourceGeneration", 1);
91
+ text(command.workerId, "workerId");
92
+ text(command.claimId, "claimId");
93
+ integer(command.claimGeneration, "claimGeneration", 1);
94
+ }
95
+ function sameClaim(work, command) {
96
+ return (work.claim !== undefined &&
97
+ canonical(work.claim) ===
98
+ canonical({
99
+ ticketId: command.ticketId,
100
+ sourceGeneration: command.sourceGeneration,
101
+ workerId: command.workerId,
102
+ claimId: command.claimId,
103
+ claimGeneration: command.claimGeneration,
104
+ }));
105
+ }
106
+ function withoutLease(work) {
107
+ const { claim: _claim, leaseUntil: _lease, retryAt: _retry, ...rest } = work;
108
+ return rest;
109
+ }
110
+ function processing(value) {
111
+ if (value === undefined)
112
+ return;
113
+ const input = fields(value, "engineering processing", [], [
114
+ "provider",
115
+ "model",
116
+ "requestId",
117
+ "failureCategory",
118
+ "inputTokens",
119
+ "outputTokens",
120
+ "costMicros",
121
+ "currency",
122
+ ]);
123
+ for (const [key, entry] of Object.entries(input))
124
+ if (["inputTokens", "outputTokens", "costMicros"].includes(key))
125
+ integer(entry, `processing.${key}`, 0);
126
+ else
127
+ text(entry, `processing.${key}`);
128
+ }
129
+ function outcome(work) {
130
+ return {
131
+ ticketId: work.ticketId,
132
+ sourceGeneration: work.sourceGeneration,
133
+ workGeneration: work.workGeneration,
134
+ status: work.status,
135
+ ...(work.stepId === undefined ? {} : { stepId: work.stepId }),
136
+ ...(work.requestHash === undefined
137
+ ? {}
138
+ : { requestHash: work.requestHash }),
139
+ ...(work.candidateHash === undefined
140
+ ? {}
141
+ : { candidateHash: work.candidateHash }),
142
+ ...(work.proposal === undefined
143
+ ? {}
144
+ : { proposalHash: work.proposal.proposalHash }),
145
+ ...(work.dispatch === undefined
146
+ ? {}
147
+ : { dispatchId: work.dispatch.dispatchId }),
148
+ ...(work.acknowledgement === undefined
149
+ ? {}
150
+ : { acknowledgement: snapshot(work.acknowledgement) }),
151
+ };
152
+ }
153
+ function effectiveTime(deps) {
154
+ const initial = timestamp(deps.clock.now(), "now");
155
+ const start = performance.now();
156
+ return () => Math.max(timestamp(deps.clock.now(), "now"), initial + Math.max(0, performance.now() - start));
157
+ }
158
+ async function trusted(deps, operation) {
159
+ const controller = new AbortController();
160
+ let timer;
161
+ try {
162
+ return await Promise.race([
163
+ Promise.resolve().then(() => operation(controller.signal)),
164
+ new Promise((_resolve, reject) => {
165
+ timer = setTimeout(() => {
166
+ controller.abort();
167
+ reject(new SupportDeskConflictError("Engineering trusted observation timed out"));
168
+ }, deps.options.trustTimeoutMilliseconds);
169
+ }),
170
+ ]);
171
+ }
172
+ finally {
173
+ if (timer !== undefined)
174
+ clearTimeout(timer);
175
+ }
176
+ }
177
+ function current(loaded, work) {
178
+ const source = loaded.source.value;
179
+ return (source.state === "eligible" &&
180
+ source.security === "ordinary" &&
181
+ work.security === "ordinary" &&
182
+ source.sourceGeneration === work.sourceGeneration &&
183
+ source.sourceBindingHash === work.sourceBindingHash &&
184
+ source.evidence !== undefined &&
185
+ source.product !== undefined &&
186
+ work.evidence !== undefined &&
187
+ work.product !== undefined &&
188
+ canonical(source.evidence) === canonical(work.evidence) &&
189
+ canonical(source.product) === canonical(work.product));
190
+ }
191
+ function reserveReachable(work) {
192
+ const escaped = "\ud800".repeat(200);
193
+ const maximumTime = "+275760-09-13T00:00:00.000Z";
194
+ const metadata = {
195
+ provider: escaped,
196
+ model: escaped,
197
+ requestId: escaped,
198
+ failureCategory: escaped,
199
+ currency: escaped,
200
+ inputTokens: Number.MAX_SAFE_INTEGER,
201
+ outputTokens: Number.MAX_SAFE_INTEGER,
202
+ costMicros: Number.MAX_SAFE_INTEGER,
203
+ };
204
+ const maximumFence = {
205
+ ticketId: work.ticketId,
206
+ sourceGeneration: work.sourceGeneration,
207
+ workerId: escaped,
208
+ claimId: escaped,
209
+ claimGeneration: Number.MAX_SAFE_INTEGER,
210
+ };
211
+ const base = {
212
+ ...work,
213
+ security: "restricted",
214
+ workGeneration: Number.MAX_SAFE_INTEGER,
215
+ claimGeneration: Number.MAX_SAFE_INTEGER,
216
+ claim: maximumFence,
217
+ leaseUntil: maximumTime,
218
+ retryAt: maximumTime,
219
+ heldReason: escaped,
220
+ lastFinish: { fingerprint: "f".repeat(64) },
221
+ };
222
+ const futures = [base];
223
+ if (work.stage === "model") {
224
+ for (const accounting of ["not_called", "accounted"]) {
225
+ const modelAttempts = work.modelAttempts.map((entry, index) => index !== work.modelAttempts.length - 1
226
+ ? entry
227
+ : { ...entry, accounting, processing: metadata });
228
+ futures.push({ ...base, modelAttempts, status: "superseded" });
229
+ if (accounting === "accounted")
230
+ futures.push({
231
+ ...base,
232
+ modelAttempts,
233
+ stage: "proposal",
234
+ status: "restricted",
235
+ candidate: "\ud800".repeat(Math.ceil(engineeringMaximumCandidateBytes / 12)),
236
+ candidateHash: "f".repeat(64),
237
+ lastCompletion: { fingerprint: "f".repeat(64) },
238
+ });
239
+ }
240
+ }
241
+ else {
242
+ const acknowledgement = {
243
+ repositoryId: escaped,
244
+ issueId: escaped,
245
+ issueNumber: Number.MAX_SAFE_INTEGER,
246
+ url: escaped,
247
+ };
248
+ const proposal = work.proposal;
249
+ const approval = proposal === undefined
250
+ ? {}
251
+ : {
252
+ approval: {
253
+ proposalHash: proposal.proposalHash,
254
+ requestHash: proposal.requestHash,
255
+ sourceBindingHash: work.sourceBindingHash,
256
+ policyGeneration: proposal.policyGeneration,
257
+ policyHash: proposal.policyHash,
258
+ repositoryMetadataHash: proposal.repositoryMetadataHash,
259
+ routingBindingHash: proposal.routingBindingHash,
260
+ checksBindingHash: "f".repeat(64),
261
+ decision: "approve",
262
+ reviewedBy: escaped,
263
+ reviewedAt: maximumTime,
264
+ reason: escaped,
265
+ },
266
+ };
267
+ const dispatch = proposal === undefined
268
+ ? {}
269
+ : {
270
+ dispatch: {
271
+ dispatchId: escaped,
272
+ startedAt: maximumTime,
273
+ workerId: escaped,
274
+ claimId: escaped,
275
+ claimGeneration: Number.MAX_SAFE_INTEGER,
276
+ repositoryId: proposal.request.repository.repositoryId,
277
+ requestHash: proposal.requestHash,
278
+ exportMarker: proposal.request.bundle.exportMarker,
279
+ },
280
+ };
281
+ futures.push({
282
+ ...base,
283
+ ...approval,
284
+ ...dispatch,
285
+ stage: "reconcile",
286
+ status: "confirmed_rejected",
287
+ terminalReserveRows: 2,
288
+ resultProof: escaped,
289
+ acknowledgement,
290
+ lastDispatchResult: { fingerprint: "f".repeat(64) },
291
+ });
292
+ }
293
+ for (const future of futures)
294
+ if (encodedBytes(future) > engineeringMaximumRecordBytes)
295
+ throw new SupportDeskLimitError("serialized_record", engineeringMaximumRecordBytes);
296
+ checkEngineeringRecordCapacity(work);
297
+ }
298
+ export function createEngineeringWorkerServices(deps, load) {
299
+ const { records, clock, options } = deps;
300
+ async function workAt(key) {
301
+ const stored = await records.getVersioned(engineeringWorkKey(key.ticketId, key.sourceGeneration));
302
+ if (stored?.value.kind !== "engineering_work")
303
+ throw new SupportDeskNotFoundError();
304
+ return { value: stored.value, version: stored.version };
305
+ }
306
+ async function save(loaded, stored, work, source = loaded.source.value, extra = [], beforeCommit) {
307
+ const securityRestriction = (source !== loaded.source.value && source.state === "restricted") ||
308
+ (work.security !== "ordinary" &&
309
+ (stored.value.security !== work.security ||
310
+ (stored.value.status !== "restricted" &&
311
+ work.status === "restricted")));
312
+ const followup = securityRestriction
313
+ ? await deps.planFollowupSourceChange({
314
+ rows: projectEngineeringRows(loaded.rows, [source, work]),
315
+ previousSource: loaded.source.value,
316
+ nextSource: source,
317
+ reason: "security_restriction",
318
+ })
319
+ : { actions: [], nextWorkRows: [] };
320
+ checkEngineeringRecordCapacity(work);
321
+ checkEngineeringRecordCapacity(source);
322
+ beforeCommit?.();
323
+ return (await records.transact(work.ticketId, [
324
+ {
325
+ action: "putIfUnchanged",
326
+ version: loaded.ticket.version,
327
+ value: loaded.ticket.value,
328
+ },
329
+ {
330
+ action: "putIfUnchanged",
331
+ version: loaded.source.version,
332
+ value: source,
333
+ },
334
+ { action: "putIfUnchanged", version: stored.version, value: work },
335
+ ...followup.actions,
336
+ ...extra,
337
+ ])).committed;
338
+ }
339
+ function live(loaded, work, command, now) {
340
+ if (!sameClaim(work, command) ||
341
+ work.leaseUntil === undefined ||
342
+ timestamp(work.leaseUntil, "leaseUntil") <= now ||
343
+ timestamp(work.deadline, "deadline") <= now ||
344
+ !current(loaded, work))
345
+ throw new SupportDeskConflictError("Engineering source, work or claim fence changed");
346
+ }
347
+ function restrictedSource(loaded, work, security = "restricted") {
348
+ const source = loaded.source.value;
349
+ if (source.state === "withdrawn" ||
350
+ source.sourceGeneration !== work.sourceGeneration ||
351
+ source.sourceBindingHash !== work.sourceBindingHash)
352
+ return source;
353
+ return {
354
+ ...source,
355
+ state: "restricted",
356
+ security,
357
+ sourceGeneration: source.sourceGeneration + 1,
358
+ followupRevocationEpoch: advanceEngineeringFollowupRevocationEpoch(source),
359
+ workGeneration: source.workGeneration + 1,
360
+ heldReason: "security_restricted",
361
+ updatedAt: clock.now(),
362
+ };
363
+ }
364
+ async function freshPolicy(access, expected) {
365
+ const policy = snapshot(await deps.policy.resolve(access));
366
+ if (!policy.policy.enabled ||
367
+ (expected !== undefined &&
368
+ (policy.generation !== expected.policyGeneration ||
369
+ policy.policyHash !== expected.policyHash)))
370
+ throw new SupportDeskConflictError("Engineering repository policy changed or disabled");
371
+ return policy;
372
+ }
373
+ function allowed(policy, request) {
374
+ const descriptor = policy.policy.repositories.find((entry) => entry.repositoryId === request.repository.repositoryId);
375
+ if (descriptor === undefined ||
376
+ !descriptor.enabled ||
377
+ descriptor.publicationMode !== "approval_required" ||
378
+ descriptor.owner !== request.repository.owner ||
379
+ descriptor.name !== request.repository.name ||
380
+ descriptor.visibility !== request.repository.visibility ||
381
+ request.bundle.labels.some((label) => !descriptor.allowedLabels.includes(label)) ||
382
+ (request.bundle.templateId !== undefined &&
383
+ !descriptor.allowedTemplates.includes(request.bundle.templateId)))
384
+ throw new SupportDeskConflictError("Engineering request is outside current repository authority");
385
+ }
386
+ async function currentRepository(request) {
387
+ if (options.repositoryMetadata === undefined)
388
+ throw new SupportDeskConflictError("Engineering repository observation unavailable");
389
+ const observed = await trusted(deps, (signal) => options.repositoryMetadata.resolve(request.repository.repositoryId, signal));
390
+ if (observed === null ||
391
+ canonical(observed) !== canonical(request.repository))
392
+ throw new SupportDeskConflictError("Engineering repository metadata or installation authority changed");
393
+ }
394
+ function receipt(command, hash, result) {
395
+ return {
396
+ action: "insert",
397
+ value: {
398
+ kind: "engineering_receipt",
399
+ partition: command.ticketId,
400
+ id: engineeringCommandKey(command.ticketId, command.commandId).id,
401
+ fingerprint: hash,
402
+ result,
403
+ },
404
+ };
405
+ }
406
+ async function replay(command, hash) {
407
+ const prior = await records.get(engineeringCommandKey(command.ticketId, command.commandId));
408
+ if (prior === null)
409
+ return null;
410
+ if (prior.kind !== "engineering_receipt" ||
411
+ prior.fingerprint !== hash ||
412
+ !("status" in prior.result))
413
+ throw new SupportDeskConflictError("Engineering command ID reused");
414
+ return snapshot(prior.result);
415
+ }
416
+ function claimView(loaded, work, policy, recoveredModelPreparation) {
417
+ const ordinals = new Set();
418
+ const messages = loaded.rows
419
+ .flatMap((row) => {
420
+ if (row.kind !== "message" ||
421
+ row.message.visibility !== "customer" ||
422
+ row.ordinal > work.acceptedTicketRevision)
423
+ return [];
424
+ integer(row.ordinal, "message.ordinal", 1);
425
+ if (ordinals.has(row.ordinal))
426
+ throw new SupportDeskConflictError("Duplicate message ordinal");
427
+ ordinals.add(row.ordinal);
428
+ return [
429
+ {
430
+ messageId: row.message.id,
431
+ ordinal: row.ordinal,
432
+ body: row.message.body,
433
+ speaker: row.message.authorKind === "customer"
434
+ ? "customer"
435
+ : row.message.authorKind === "assistant"
436
+ ? "assistant"
437
+ : row.message.authorKind === "system"
438
+ ? "notification"
439
+ : "support",
440
+ },
441
+ ];
442
+ })
443
+ .sort((a, b) => a.ordinal - b.ordinal);
444
+ return {
445
+ fence: snapshot(work.claim),
446
+ sourceFence: {
447
+ ticketId: work.ticketId,
448
+ sourceGeneration: work.sourceGeneration,
449
+ sourceBindingHash: work.sourceBindingHash,
450
+ },
451
+ workGeneration: work.workGeneration,
452
+ leaseUntil: work.leaseUntil,
453
+ work: snapshot(work),
454
+ policy: snapshot(policy),
455
+ messages,
456
+ recoveredModelPreparation,
457
+ reconciliationOnly: work.dispatch !== undefined || work.stage === "reconcile",
458
+ };
459
+ }
460
+ function validity(value, now, name) {
461
+ if (timestamp(value.verifiedAt, `${name}.verifiedAt`) > now ||
462
+ timestamp(value.validUntil, `${name}.validUntil`) <= now)
463
+ throw new SupportDeskConflictError(`${name} is not current`);
464
+ return timestamp(value.validUntil, `${name}.validUntil`);
465
+ }
466
+ async function verifyProposal(access, loaded, work, proposal, runNow) {
467
+ const policy = await freshPolicy(access, proposal);
468
+ allowed(policy, proposal.request);
469
+ if (proposal.requestHash !== (await fingerprint(proposal.request)) ||
470
+ proposal.repositoryMetadataHash !==
471
+ (await fingerprint(proposal.request.repository)) ||
472
+ proposal.candidateHash !== work.candidateHash ||
473
+ canonical(proposal.checksBinding) !==
474
+ canonical(await engineeringExportChecksBinding({
475
+ sourceBindingHash: work.sourceBindingHash,
476
+ evidenceFingerprint: loaded.source.value.evidenceFingerprint,
477
+ candidateHash: proposal.candidateHash,
478
+ routingBindingHash: proposal.routingBindingHash,
479
+ exactRequest: proposal.request,
480
+ policyGeneration: proposal.policyGeneration,
481
+ policyHash: proposal.policyHash,
482
+ })))
483
+ throw new SupportDeskConflictError("Engineering exact proposal binding changed");
484
+ let deadline = Number.POSITIVE_INFINITY;
485
+ if (proposal.routingBinding !== undefined) {
486
+ if (options.routing === undefined ||
487
+ proposal.routingReceipt === undefined ||
488
+ proposal.routingBindingHash !==
489
+ (await engineeringRoutingBindingHash(proposal.routingBinding)))
490
+ throw new SupportDeskConflictError("Engineering calibrated routing unavailable");
491
+ const verified = snapshot(await trusted(deps, () => options.routing.verify(proposal.routingReceipt, proposal.routingBinding)));
492
+ if (verified === null)
493
+ throw new SupportDeskConflictError("Engineering routing receipt unverified");
494
+ fields(verified, "verified routing", [
495
+ "binding",
496
+ "outcome",
497
+ "verifierReceiptId",
498
+ "verifierVersion",
499
+ "verifiedAt",
500
+ "validUntil",
501
+ ]);
502
+ text(verified.verifierReceiptId, "routing.verifierReceiptId");
503
+ text(verified.verifierVersion, "routing.verifierVersion");
504
+ if (verified.outcome !== "accepted" ||
505
+ canonical(verified.binding) !== canonical(proposal.routingBinding))
506
+ throw new SupportDeskConflictError("Engineering routing requires staff selection");
507
+ deadline = Math.min(deadline, validity(verified, runNow(), "engineering routing"));
508
+ }
509
+ else if (work.staffSelection === undefined ||
510
+ work.staffSelection.repositoryId !==
511
+ proposal.request.repository.repositoryId ||
512
+ work.staffSelection.policyGeneration !== proposal.policyGeneration ||
513
+ work.staffSelection.policyHash !== proposal.policyHash ||
514
+ proposal.routingBindingHash !==
515
+ (await engineeringStaffSelectionHash(work.staffSelection)))
516
+ throw new SupportDeskConflictError("Engineering staff repository selection changed");
517
+ if (options.checks === undefined)
518
+ throw new SupportDeskConflictError("Engineering independent export checks unavailable");
519
+ const verified = snapshot(await trusted(deps, () => options.checks.verify(proposal.checksReceipt, proposal.checksBinding)));
520
+ if (verified === null)
521
+ throw new SupportDeskConflictError("Engineering export checks unverified");
522
+ fields(verified, "verified engineering checks", [
523
+ "binding",
524
+ "privacy",
525
+ "factual",
526
+ "security",
527
+ "publicReproduction",
528
+ "verifierReceiptId",
529
+ "verifierVersion",
530
+ "verifiedAt",
531
+ "validUntil",
532
+ ]);
533
+ text(verified.verifierReceiptId, "checks.verifierReceiptId");
534
+ text(verified.verifierVersion, "checks.verifierVersion");
535
+ if (canonical(verified.binding) !== canonical(proposal.checksBinding) ||
536
+ verified.privacy !== "passed" ||
537
+ verified.factual !== "passed" ||
538
+ verified.security !== "clear" ||
539
+ (verified.publicReproduction !== "passed" &&
540
+ verified.publicReproduction !== "not_applicable") ||
541
+ (proposal.request.repository.visibility === "public" &&
542
+ verified.publicReproduction !== "passed"))
543
+ throw new SupportDeskConflictError("Engineering exact privacy, factual, security or public reproduction check failed");
544
+ deadline = Math.min(deadline, validity(verified, runNow(), "engineering export checks"));
545
+ if (runNow() >= deadline)
546
+ throw new SupportDeskConflictError("Engineering proposal verification expired");
547
+ return deadline;
548
+ }
549
+ return {
550
+ async scanEngineeringCandidates(access, input) {
551
+ const actorId = access.principalId;
552
+ const authority = scanPermission(access, actorId);
553
+ const bounds = fields(input, "engineering scan", ["limit"], ["cursor"]);
554
+ integer(bounds.limit, "limit", 1, MAX_SCAN_PAGE_SIZE);
555
+ if (bounds.cursor !== undefined)
556
+ text(bounds.cursor, "cursor", 20_000);
557
+ const page = await records.scan({
558
+ limit: bounds.limit,
559
+ ...(bounds.cursor === undefined
560
+ ? {}
561
+ : { cursor: bounds.cursor }),
562
+ });
563
+ if (page.records.length > bounds.limit)
564
+ throw new TypeError("Engineering scan returned more physical rows than requested");
565
+ scanPermission(access, actorId);
566
+ const now = timestamp(clock.now(), "now");
567
+ const sources = new Map();
568
+ function sourceAt(ticketId) {
569
+ let pending = sources.get(ticketId);
570
+ if (pending === undefined) {
571
+ pending = records
572
+ .get(engineeringSourceKey(ticketId))
573
+ .then((value) => {
574
+ if (value === null)
575
+ return null;
576
+ if (value.kind !== "engineering_source" ||
577
+ value.ticketId !== ticketId ||
578
+ value.partition !== ticketId ||
579
+ value.id !== "engineering-source")
580
+ throw new SupportDeskConflictError("Engineering scan source key identity mismatch");
581
+ return value;
582
+ });
583
+ sources.set(ticketId, pending);
584
+ }
585
+ return pending;
586
+ }
587
+ const candidates = [];
588
+ const reviewCandidates = [];
589
+ const restrictedReviewCandidates = [];
590
+ const reviewSources = new Set();
591
+ const restrictedReviewSources = new Set();
592
+ const reconciliationSources = new Set();
593
+ function sourceAttention(source) {
594
+ if (source === null ||
595
+ !["unverified", "held", "restricted"].includes(source.state))
596
+ return;
597
+ if (source.security !== "ordinary" || source.state === "restricted")
598
+ restrictedReviewSources.add(source.ticketId);
599
+ else
600
+ reviewSources.add(source.ticketId);
601
+ }
602
+ for (const row of page.records) {
603
+ if (row.value.kind === "engineering_source") {
604
+ if (row.key.partition !== row.value.ticketId ||
605
+ row.key.id !== "engineering-source")
606
+ throw new TypeError("Engineering scan source identity mismatch");
607
+ if (authority.process && row.value.reconciliationPending)
608
+ reconciliationSources.add(row.value.ticketId);
609
+ if (authority.review)
610
+ sourceAttention(await sourceAt(row.value.ticketId));
611
+ continue;
612
+ }
613
+ if (row.value.kind !== "engineering_work")
614
+ continue;
615
+ const work = row.value;
616
+ if (row.key.partition !== work.ticketId || row.key.id !== work.id)
617
+ throw new TypeError("Engineering scan work identity mismatch");
618
+ const key = {
619
+ ticketId: work.ticketId,
620
+ sourceGeneration: work.sourceGeneration,
621
+ };
622
+ const restrictedWork = work.security !== "ordinary" ||
623
+ work.status === "restricted" ||
624
+ (work.candidate !== undefined &&
625
+ work.candidate.security !== "ordinary");
626
+ let source;
627
+ if (authority.review) {
628
+ source = await sourceAt(work.ticketId);
629
+ sourceAttention(source);
630
+ if (restrictedWork &&
631
+ source !== null &&
632
+ source.state !== "withdrawn" &&
633
+ (source.state === "restricted" || source.security !== "ordinary") &&
634
+ source.sourceBindingHash === work.sourceBindingHash)
635
+ restrictedReviewCandidates.push(key);
636
+ }
637
+ if (work.dispatch !== undefined) {
638
+ if (authority.process &&
639
+ !["accepted", "confirmed_rejected"].includes(work.status))
640
+ reconciliationSources.add(work.ticketId);
641
+ continue;
642
+ }
643
+ if ([
644
+ "accepted",
645
+ "confirmed_rejected",
646
+ "superseded",
647
+ "restricted",
648
+ ].includes(work.status) ||
649
+ restrictedWork)
650
+ continue;
651
+ if (source === undefined)
652
+ source = await sourceAt(work.ticketId);
653
+ if (source?.kind === "engineering_source" &&
654
+ source.state === "eligible" &&
655
+ source.security === "ordinary" &&
656
+ source.sourceGeneration === work.sourceGeneration &&
657
+ source.sourceBindingHash === work.sourceBindingHash) {
658
+ if (authority.review &&
659
+ ((work.proposal !== undefined &&
660
+ !work.proposalNeedsRebuild &&
661
+ (work.approval === undefined ||
662
+ (work.approval.decision === "approve" &&
663
+ timestamp(work.deadline, "work.deadline") <= now))) ||
664
+ (work.proposal === undefined &&
665
+ work.candidate !== undefined &&
666
+ (work.candidate.routing.outcome !== "selected" ||
667
+ work.status === "held"))))
668
+ reviewCandidates.push(key);
669
+ if (authority.process &&
670
+ timestamp(work.deadline, "work.deadline") > now &&
671
+ (work.retryAt === undefined ||
672
+ timestamp(work.retryAt, "work.retryAt") <= now) &&
673
+ (work.proposal === undefined ||
674
+ work.proposalNeedsRebuild ||
675
+ work.approval?.decision === "approve"))
676
+ candidates.push(key);
677
+ }
678
+ }
679
+ const finalAuthority = scanPermission(access, actorId);
680
+ return {
681
+ candidates: finalAuthority.process ? candidates : [],
682
+ reviewCandidates: finalAuthority.review
683
+ ? [
684
+ ...reviewCandidates,
685
+ ...(finalAuthority.restricted ? restrictedReviewCandidates : []),
686
+ ]
687
+ : [],
688
+ reviewSources: finalAuthority.review
689
+ ? [
690
+ ...new Set([
691
+ ...reviewSources,
692
+ ...(finalAuthority.restricted ? restrictedReviewSources : []),
693
+ ]),
694
+ ]
695
+ : [],
696
+ reconciliationSources: finalAuthority.process
697
+ ? [...reconciliationSources]
698
+ : [],
699
+ physicalRows: page.records.length,
700
+ pages: 1,
701
+ nextCursor: page.nextCursor,
702
+ };
703
+ },
704
+ async claimEngineeringWork(access, input) {
705
+ permission(access);
706
+ const actorId = access.principalId;
707
+ const command = snapshot(fields(input, "engineering claim", [
708
+ "ticketId",
709
+ "sourceGeneration",
710
+ "workerId",
711
+ "claimId",
712
+ "leaseMilliseconds",
713
+ ]));
714
+ text(command.ticketId, "ticketId");
715
+ integer(command.sourceGeneration, "sourceGeneration", 1);
716
+ text(command.workerId, "workerId");
717
+ text(command.claimId, "claimId");
718
+ integer(command.leaseMilliseconds, "leaseMilliseconds", 1, options.maxLeaseMilliseconds);
719
+ if (!options.enabled)
720
+ return null;
721
+ for (let attempt = 0; attempt < deps.maxConflictAttempts; attempt += 1) {
722
+ permission(access, false, actorId);
723
+ const loaded = await load(command.ticketId);
724
+ const stored = await workAt(command);
725
+ const work = stored.value;
726
+ const now = timestamp(clock.now(), "now");
727
+ if (!current(loaded, work) ||
728
+ [
729
+ "accepted",
730
+ "confirmed_rejected",
731
+ "superseded",
732
+ "restricted",
733
+ ].includes(work.status) ||
734
+ timestamp(work.deadline, "deadline") <= now ||
735
+ (work.retryAt !== undefined &&
736
+ timestamp(work.retryAt, "retryAt") > now)) {
737
+ permission(access, false, actorId);
738
+ return null;
739
+ }
740
+ const policy = await freshPolicy(access);
741
+ const recovered = work.stage === "model" &&
742
+ work.stepId !== undefined &&
743
+ ["prepared", "claimed", "unknown"].includes(work.status);
744
+ if (work.claim !== undefined &&
745
+ work.leaseUntil !== undefined &&
746
+ timestamp(work.leaseUntil, "leaseUntil") > now) {
747
+ if (work.claim.workerId !== command.workerId ||
748
+ work.claim.claimId !== command.claimId) {
749
+ permission(access, false, actorId);
750
+ return null;
751
+ }
752
+ permission(access, false, actorId);
753
+ if (await save(loaded, stored, work)) {
754
+ permission(access, false, actorId);
755
+ return claimView(loaded, work, policy, recovered);
756
+ }
757
+ continue;
758
+ }
759
+ const claimGeneration = work.claimGeneration + 1;
760
+ integer(claimGeneration, "claimGeneration", 1);
761
+ const next = {
762
+ ...work,
763
+ claimGeneration,
764
+ claim: {
765
+ ticketId: work.ticketId,
766
+ sourceGeneration: work.sourceGeneration,
767
+ workerId: command.workerId,
768
+ claimId: command.claimId,
769
+ claimGeneration,
770
+ },
771
+ leaseUntil: new Date(Math.min(timestamp(clock.now(), "now") + command.leaseMilliseconds, timestamp(work.deadline, "deadline"))).toISOString(),
772
+ workGeneration: work.workGeneration + 1,
773
+ status: work.dispatch === undefined ? "claimed" : work.status,
774
+ };
775
+ permission(access, false, actorId);
776
+ if (timestamp(work.deadline, "deadline") <= timestamp(clock.now(), "now"))
777
+ return null;
778
+ if (await save(loaded, stored, next)) {
779
+ permission(access, false, actorId);
780
+ return claimView(loaded, next, policy, recovered);
781
+ }
782
+ }
783
+ throw new SupportDeskConflictError();
784
+ },
785
+ async prepareEngineeringModel(access, input) {
786
+ permission(access);
787
+ const actorId = access.principalId;
788
+ const command = snapshot(fields(input, "prepare engineering model", [
789
+ "ticketId",
790
+ "sourceGeneration",
791
+ "workerId",
792
+ "claimId",
793
+ "claimGeneration",
794
+ "binding",
795
+ ]));
796
+ fence(command);
797
+ validateEngineeringModelBinding(command.binding);
798
+ if (!options.enabled)
799
+ throw new SupportDeskConflictError("Engineering processing disabled");
800
+ const runNow = effectiveTime(deps);
801
+ for (let attempt = 0; attempt < deps.maxConflictAttempts; attempt += 1) {
802
+ permission(access, false, actorId);
803
+ const loaded = await load(command.ticketId);
804
+ const stored = await workAt(command);
805
+ const work = stored.value;
806
+ live(loaded, work, command, runNow());
807
+ if (work.stage !== "model" ||
808
+ work.dispatch !== undefined ||
809
+ work.candidate !== undefined)
810
+ throw new SupportDeskConflictError("Engineering model stage changed");
811
+ const binding = command.binding;
812
+ const manifest = binding.inputManifest;
813
+ const policy = await freshPolicy(access, {
814
+ policyGeneration: manifest.policyGeneration,
815
+ policyHash: manifest.policyHash,
816
+ });
817
+ if ((await engineeringModelManifestHash(manifest)) !==
818
+ binding.inputManifestHash ||
819
+ manifest.evidenceFingerprint !==
820
+ loaded.source.value.evidenceFingerprint ||
821
+ canonical(manifest.product) !==
822
+ canonical({
823
+ productId: work.product.productId,
824
+ version: work.product.version,
825
+ buildId: work.product.buildId,
826
+ binding: work.product.binding,
827
+ contextVersion: work.product.contextVersion,
828
+ }) ||
829
+ manifest.repositoryIds.some((id) => !policy.policy.repositories.some((repo) => repo.repositoryId === id && repo.enabled)) ||
830
+ manifest.messages.some((reference) => !loaded.rows.some((row) => row.kind === "message" &&
831
+ row.message.visibility === "customer" &&
832
+ row.message.id === reference.messageId &&
833
+ row.ordinal === reference.ordinal &&
834
+ row.ordinal <= work.acceptedTicketRevision)))
835
+ throw new SupportDeskConflictError("Engineering input manifest differs from authoritative evidence");
836
+ const deadline = timestamp(binding.deadline, "model.deadline");
837
+ if (deadline <= runNow() ||
838
+ deadline > timestamp(work.leaseUntil, "leaseUntil") ||
839
+ deadline > timestamp(work.deadline, "deadline"))
840
+ throw new SupportDeskConflictError("Engineering model deadline exceeds live claim");
841
+ const existing = work.modelAttempts.find((entry) => canonical(entry.binding) === canonical(binding));
842
+ if (existing !== undefined) {
843
+ if (existing.preparedClaimGeneration !== command.claimGeneration ||
844
+ existing.stepId !== work.stepId)
845
+ throw new SupportDeskConflictError("Engineering model preparation belongs to another invocation");
846
+ live(loaded, work, command, runNow());
847
+ permission(access, false, actorId);
848
+ if (await save(loaded, stored, work)) {
849
+ permission(access, false, actorId);
850
+ return {
851
+ stepId: existing.stepId,
852
+ requestHash: existing.requestHash,
853
+ maxCandidateBytes: 16_000,
854
+ };
855
+ }
856
+ continue;
857
+ }
858
+ if (work.modelAttempts.length >= options.maxModelAttempts ||
859
+ work.modelAttempts.some((entry) => entry.preparedClaimGeneration === command.claimGeneration))
860
+ throw new SupportDeskConflictError("Engineering model attempt limit reached");
861
+ const requestHash = await fingerprint(binding);
862
+ const stepId = await fingerprint({
863
+ sourceBindingHash: work.sourceBindingHash,
864
+ claimGeneration: command.claimGeneration,
865
+ binding,
866
+ });
867
+ const next = {
868
+ ...work,
869
+ status: "prepared",
870
+ stepId,
871
+ requestHash,
872
+ modelAttempts: [
873
+ ...work.modelAttempts,
874
+ {
875
+ preparedClaimGeneration: command.claimGeneration,
876
+ stepId,
877
+ requestHash,
878
+ binding,
879
+ accounting: "unknown",
880
+ },
881
+ ],
882
+ workGeneration: work.workGeneration + 1,
883
+ };
884
+ reserveReachable(next);
885
+ await freshPolicy(access, {
886
+ policyGeneration: manifest.policyGeneration,
887
+ policyHash: manifest.policyHash,
888
+ });
889
+ live(loaded, work, command, runNow());
890
+ permission(access, false, actorId);
891
+ if (await save(loaded, stored, next)) {
892
+ permission(access, false, actorId);
893
+ return { stepId, requestHash, maxCandidateBytes: 16_000 };
894
+ }
895
+ }
896
+ throw new SupportDeskConflictError();
897
+ },
898
+ async completeEngineeringModel(access, input, guard) {
899
+ permission(access);
900
+ const actorId = access.principalId;
901
+ if (typeof guard !== "function")
902
+ throw new TypeError("Engineering model completion requires a current read-only guard");
903
+ const command = snapshot(fields(input, "complete engineering model", [
904
+ "ticketId",
905
+ "sourceGeneration",
906
+ "workerId",
907
+ "claimId",
908
+ "claimGeneration",
909
+ "stepId",
910
+ "requestHash",
911
+ "candidate",
912
+ "accounting",
913
+ ], ["processing"]));
914
+ fence(command);
915
+ text(command.stepId, "stepId");
916
+ text(command.requestHash, "requestHash");
917
+ validateEngineeringCandidate(command.candidate);
918
+ processing(command.processing);
919
+ if (!["accounted", "unknown"].includes(command.accounting))
920
+ throw new TypeError("Invalid model accounting");
921
+ const completion = await fingerprint(command);
922
+ const candidateHash = await fingerprint(command.candidate);
923
+ const runNow = effectiveTime(deps);
924
+ for (let attempt = 0; attempt < deps.maxConflictAttempts; attempt += 1) {
925
+ permission(access, false, actorId);
926
+ const loaded = await load(command.ticketId);
927
+ const stored = await workAt(command);
928
+ const work = stored.value;
929
+ if (work.lastCompletion?.fingerprint === completion) {
930
+ permission(access, false, actorId);
931
+ return outcome(work);
932
+ }
933
+ live(loaded, work, command, runNow());
934
+ const model = work.modelAttempts.find((entry) => entry.stepId === command.stepId &&
935
+ entry.requestHash === command.requestHash);
936
+ if (work.stage !== "model" ||
937
+ work.stepId !== command.stepId ||
938
+ work.requestHash !== command.requestHash ||
939
+ model === undefined ||
940
+ model.preparedClaimGeneration !== command.claimGeneration)
941
+ throw new SupportDeskConflictError("Engineering prepared model owner changed");
942
+ if (canonical(command.candidate.applicability) !==
943
+ canonical({
944
+ productId: work.product.productId,
945
+ version: work.product.version,
946
+ buildId: work.product.buildId,
947
+ }))
948
+ throw new SupportDeskConflictError("Engineering candidate applicability changed");
949
+ const next = {
950
+ ...withoutLease(work),
951
+ stage: "proposal",
952
+ status: command.candidate.security === "ordinary"
953
+ ? "candidate"
954
+ : "restricted",
955
+ security: command.candidate.security,
956
+ candidate: command.candidate,
957
+ candidateHash,
958
+ workGeneration: work.workGeneration + 1,
959
+ lastCompletion: { fingerprint: completion },
960
+ modelAttempts: work.modelAttempts.map((entry) => entry !== model
961
+ ? entry
962
+ : {
963
+ ...entry,
964
+ accounting: command.accounting,
965
+ ...(command.processing === undefined
966
+ ? {}
967
+ : { processing: command.processing }),
968
+ }),
969
+ };
970
+ checkEngineeringRecordCapacity(next);
971
+ await guard();
972
+ await freshPolicy(access, {
973
+ policyGeneration: model.binding.inputManifest.policyGeneration,
974
+ policyHash: model.binding.inputManifest.policyHash,
975
+ });
976
+ permission(access, false, actorId);
977
+ live(loaded, work, command, runNow());
978
+ if (timestamp(model.binding.deadline, "model.deadline") <= runNow())
979
+ throw new SupportDeskConflictError("Engineering prepared model deadline expired");
980
+ const nextSource = command.candidate.security === "ordinary"
981
+ ? loaded.source.value
982
+ : restrictedSource(loaded, work, command.candidate.security);
983
+ if (await save(loaded, stored, next, nextSource, [], () => {
984
+ permission(access, false, actorId);
985
+ live(loaded, work, command, runNow());
986
+ if (timestamp(model.binding.deadline, "model.deadline") <= runNow())
987
+ throw new SupportDeskConflictError("Engineering prepared model deadline expired");
988
+ })) {
989
+ permission(access, false, actorId);
990
+ return outcome(next);
991
+ }
992
+ }
993
+ throw new SupportDeskConflictError();
994
+ },
995
+ async finishEngineeringWork(access, input) {
996
+ permission(access);
997
+ const actorId = access.principalId;
998
+ const requestStartedAt = timestamp(clock.now(), "now");
999
+ const runNow = effectiveTime(deps);
1000
+ const command = snapshot(fields(input, "finish engineering work", [
1001
+ "ticketId",
1002
+ "sourceGeneration",
1003
+ "workerId",
1004
+ "claimId",
1005
+ "claimGeneration",
1006
+ "disposition",
1007
+ ], [
1008
+ "stepId",
1009
+ "requestHash",
1010
+ "accounting",
1011
+ "retryAt",
1012
+ "reason",
1013
+ "processing",
1014
+ "classificationCandidateHash",
1015
+ ]));
1016
+ fence(command);
1017
+ processing(command.processing);
1018
+ if ((command.stepId === undefined) !==
1019
+ (command.requestHash === undefined))
1020
+ throw new TypeError("Engineering finish requires exact step and request hash together");
1021
+ if (command.stepId !== undefined) {
1022
+ text(command.stepId, "stepId");
1023
+ text(command.requestHash, "requestHash");
1024
+ }
1025
+ if (!["retry", "held", "restricted", "unknown", "superseded"].includes(command.disposition))
1026
+ throw new TypeError("Invalid engineering disposition");
1027
+ if (command.accounting !== undefined &&
1028
+ !["not_called", "accounted", "unknown"].includes(command.accounting))
1029
+ throw new TypeError("Invalid engineering accounting");
1030
+ if (command.reason !== undefined)
1031
+ text(command.reason, "reason");
1032
+ if (command.classificationCandidateHash !== undefined) {
1033
+ text(command.classificationCandidateHash, "classificationCandidateHash");
1034
+ if (command.disposition !== "restricted" ||
1035
+ command.stepId === undefined ||
1036
+ command.accounting !== undefined ||
1037
+ command.processing !== undefined)
1038
+ throw new TypeError("Historical restriction requires an exact step and cannot change usage accounting");
1039
+ }
1040
+ const hash = await fingerprint(command);
1041
+ for (let attempt = 0; attempt < deps.maxConflictAttempts; attempt += 1) {
1042
+ permission(access, false, actorId);
1043
+ const loaded = await load(command.ticketId);
1044
+ const stored = await workAt(command);
1045
+ const work = stored.value;
1046
+ if (work.lastFinish?.fingerprint === hash) {
1047
+ permission(access, false, actorId);
1048
+ return outcome(work);
1049
+ }
1050
+ if (command.classificationCandidateHash !== undefined) {
1051
+ if (work.candidateHash !== command.classificationCandidateHash ||
1052
+ !(work.modelAttempts.some((entry) => entry.stepId === command.stepId &&
1053
+ entry.requestHash === command.requestHash) ||
1054
+ (work.proposal !== undefined &&
1055
+ work.proposal.stepId === command.stepId &&
1056
+ work.proposal.requestHash === command.requestHash)))
1057
+ throw new SupportDeskConflictError("Historical restriction candidate or prepared step changed");
1058
+ const { approval: _approval, ...classified } = withoutLease(work);
1059
+ const preserved = work.dispatch !== undefined ||
1060
+ [
1061
+ "superseded",
1062
+ "held",
1063
+ "accepted",
1064
+ "confirmed_rejected",
1065
+ "unknown",
1066
+ ].includes(work.status);
1067
+ const next = {
1068
+ ...classified,
1069
+ security: "restricted",
1070
+ status: preserved ? work.status : "restricted",
1071
+ workGeneration: work.workGeneration + 1,
1072
+ heldReason: "security_restricted",
1073
+ lastFinish: { fingerprint: hash },
1074
+ };
1075
+ permission(access, false, actorId);
1076
+ if (await save(loaded, stored, next, restrictedSource(loaded, work), [], () => permission(access, false, actorId))) {
1077
+ permission(access, false, actorId);
1078
+ return outcome(next);
1079
+ }
1080
+ continue;
1081
+ }
1082
+ const now = runNow();
1083
+ if (!sameClaim(work, command) ||
1084
+ work.leaseUntil === undefined ||
1085
+ timestamp(work.leaseUntil, "leaseUntil") <= now)
1086
+ throw new SupportDeskConflictError("Engineering finish claim expired");
1087
+ if ((command.stepId !== undefined && command.stepId !== work.stepId) ||
1088
+ (command.requestHash !== undefined &&
1089
+ command.requestHash !== work.requestHash))
1090
+ throw new SupportDeskConflictError("Engineering finish step changed");
1091
+ if (command.stepId === undefined &&
1092
+ (work.proposal !== undefined ||
1093
+ work.dispatch !== undefined ||
1094
+ work.modelAttempts.some((entry) => entry.preparedClaimGeneration === command.claimGeneration)))
1095
+ throw new SupportDeskConflictError("Prepared engineering work requires its exact step fence");
1096
+ // Retry scheduling uses the request floor; only the live claim must
1097
+ // remain unexpired while storage and child planning await.
1098
+ if (command.retryAt !== undefined &&
1099
+ (timestamp(command.retryAt, "retryAt") < requestStartedAt ||
1100
+ timestamp(command.retryAt, "retryAt") >=
1101
+ timestamp(work.deadline, "deadline")))
1102
+ throw new SupportDeskConflictError("Engineering retry exceeds work deadline");
1103
+ const next = {
1104
+ ...withoutLease(work),
1105
+ security: command.disposition === "restricted" ? "restricted" : work.security,
1106
+ status: work.dispatch !== undefined
1107
+ ? "unknown"
1108
+ : current(loaded, work)
1109
+ ? command.disposition
1110
+ : "held",
1111
+ stage: work.dispatch === undefined ? work.stage : "reconcile",
1112
+ workGeneration: work.workGeneration + 1,
1113
+ lastFinish: { fingerprint: hash },
1114
+ ...(command.retryAt === undefined
1115
+ ? {}
1116
+ : { retryAt: command.retryAt }),
1117
+ ...(command.reason === undefined
1118
+ ? {}
1119
+ : { heldReason: command.reason }),
1120
+ modelAttempts: work.modelAttempts.map((entry) => {
1121
+ if (command.stepId === undefined ||
1122
+ entry.stepId !== command.stepId ||
1123
+ entry.requestHash !== command.requestHash)
1124
+ return entry;
1125
+ if (entry.preparedClaimGeneration !== command.claimGeneration) {
1126
+ if (command.accounting !== undefined &&
1127
+ command.accounting !== "unknown" &&
1128
+ command.accounting !== entry.accounting)
1129
+ throw new SupportDeskConflictError("Cannot rewrite another invocation's engineering accounting");
1130
+ return entry;
1131
+ }
1132
+ if (entry.accounting !== "unknown" &&
1133
+ command.accounting !== undefined &&
1134
+ entry.accounting !== command.accounting)
1135
+ throw new SupportDeskConflictError("Known engineering accounting cannot be replaced");
1136
+ return {
1137
+ ...entry,
1138
+ ...(command.accounting === undefined
1139
+ ? {}
1140
+ : { accounting: command.accounting }),
1141
+ ...(command.processing === undefined
1142
+ ? {}
1143
+ : { processing: command.processing }),
1144
+ };
1145
+ }),
1146
+ };
1147
+ const nextSource = command.disposition !== "restricted"
1148
+ ? loaded.source.value
1149
+ : restrictedSource(loaded, work);
1150
+ permission(access, false, actorId);
1151
+ if (await save(loaded, stored, next, nextSource, [], () => {
1152
+ permission(access, false, actorId);
1153
+ const commitNow = runNow();
1154
+ if (!sameClaim(work, command) ||
1155
+ work.leaseUntil === undefined ||
1156
+ timestamp(work.leaseUntil, "leaseUntil") <= commitNow)
1157
+ throw new SupportDeskConflictError("Engineering finish claim expired");
1158
+ if (command.retryAt !== undefined &&
1159
+ timestamp(command.retryAt, "retryAt") >=
1160
+ timestamp(work.deadline, "deadline"))
1161
+ throw new SupportDeskConflictError("Engineering retry exceeds work deadline");
1162
+ })) {
1163
+ permission(access, false, actorId);
1164
+ return outcome(next);
1165
+ }
1166
+ }
1167
+ throw new SupportDeskConflictError();
1168
+ },
1169
+ async selectEngineeringRepository(access, input) {
1170
+ permission(access, true);
1171
+ const actorId = access.principalId;
1172
+ const command = snapshot(fields(input, "select engineering repository", [
1173
+ "ticketId",
1174
+ "sourceGeneration",
1175
+ "commandId",
1176
+ "correlationId",
1177
+ "expectedWorkGeneration",
1178
+ "repositoryId",
1179
+ "policyGeneration",
1180
+ "policyHash",
1181
+ "reason",
1182
+ ]));
1183
+ text(command.ticketId, "ticketId");
1184
+ integer(command.sourceGeneration, "sourceGeneration", 1);
1185
+ for (const key of [
1186
+ "commandId",
1187
+ "correlationId",
1188
+ "repositoryId",
1189
+ "policyHash",
1190
+ "reason",
1191
+ ])
1192
+ text(command[key], key);
1193
+ integer(command.expectedWorkGeneration, "expectedWorkGeneration", 1);
1194
+ integer(command.policyGeneration, "policyGeneration", 0);
1195
+ const hash = await fingerprint({ command, actorId });
1196
+ for (let attempt = 0; attempt < deps.maxConflictAttempts; attempt += 1) {
1197
+ permission(access, true, actorId);
1198
+ const previous = await replay(command, hash);
1199
+ if (previous !== null) {
1200
+ permission(access, true, actorId);
1201
+ return previous;
1202
+ }
1203
+ const loaded = await load(command.ticketId);
1204
+ const stored = await workAt(command);
1205
+ const work = stored.value;
1206
+ if (!current(loaded, work) ||
1207
+ work.workGeneration !== command.expectedWorkGeneration ||
1208
+ work.dispatch !== undefined ||
1209
+ work.candidateHash === undefined ||
1210
+ work.status === "restricted" ||
1211
+ (work.candidate !== undefined &&
1212
+ work.candidate.security !== "ordinary"))
1213
+ throw new SupportDeskConflictError("Engineering repository selection source or work changed");
1214
+ const policy = await freshPolicy(access, command);
1215
+ if (!policy.policy.repositories.some((entry) => entry.repositoryId === command.repositoryId && entry.enabled))
1216
+ throw new SupportDeskConflictError("Selected engineering repository is not allowed");
1217
+ const { approval: _approval, proposalNeedsRebuild: _rebuild, ...editable } = work;
1218
+ const now = clock.now();
1219
+ const next = {
1220
+ ...withoutLease(editable),
1221
+ stage: "proposal",
1222
+ status: "candidate",
1223
+ deadline: new Date(timestamp(now, "now") + options.maxWorkMilliseconds).toISOString(),
1224
+ ...(work.proposal === undefined
1225
+ ? {}
1226
+ : { proposalNeedsRebuild: true }),
1227
+ workGeneration: work.workGeneration + 1,
1228
+ staffSelection: {
1229
+ repositoryId: command.repositoryId,
1230
+ policyGeneration: command.policyGeneration,
1231
+ policyHash: command.policyHash,
1232
+ selectedBy: actorId,
1233
+ selectedAt: now,
1234
+ reason: command.reason,
1235
+ commandId: command.commandId,
1236
+ },
1237
+ };
1238
+ if (next.proposal === undefined)
1239
+ checkEngineeringRecordCapacity(next);
1240
+ else
1241
+ reserveReachable(next);
1242
+ deps.admission(loaded.ticket.value.ticket, loaded.rows, 2);
1243
+ const result = outcome(next);
1244
+ permission(access, true, actorId);
1245
+ if (await save(loaded, stored, next, loaded.source.value, [
1246
+ deps.audit(command, loaded.ticket.value.ticket, actorId, now, "support.engineering.repository_selected"),
1247
+ receipt(command, hash, result),
1248
+ ])) {
1249
+ permission(access, true, actorId);
1250
+ return result;
1251
+ }
1252
+ }
1253
+ const previous = await replay(command, hash);
1254
+ if (previous !== null) {
1255
+ permission(access, true, actorId);
1256
+ return previous;
1257
+ }
1258
+ throw new SupportDeskConflictError();
1259
+ },
1260
+ async prepareEngineeringProposal(access, input, guard) {
1261
+ permission(access);
1262
+ const actorId = access.principalId;
1263
+ if (typeof guard !== "function")
1264
+ throw new TypeError("Engineering proposal requires a current read-only guard");
1265
+ const command = snapshot(fields(input, "prepare engineering proposal", [
1266
+ "ticketId",
1267
+ "sourceGeneration",
1268
+ "workerId",
1269
+ "claimId",
1270
+ "claimGeneration",
1271
+ "candidateHash",
1272
+ "checksReceipt",
1273
+ "exactRequest",
1274
+ "policyGeneration",
1275
+ "policyHash",
1276
+ ], ["routingReceipt"]));
1277
+ fence(command);
1278
+ text(command.candidateHash, "candidateHash");
1279
+ text(command.checksReceipt, "checksReceipt");
1280
+ text(command.policyHash, "policyHash");
1281
+ integer(command.policyGeneration, "policyGeneration", 0);
1282
+ if (command.routingReceipt !== undefined)
1283
+ text(command.routingReceipt, "routingReceipt");
1284
+ validateEngineeringExportRequest(command.exactRequest);
1285
+ if (!command.exactRequest.bundle.body.includes(command.exactRequest.bundle.exportMarker))
1286
+ throw new SupportDeskConflictError("Engineering exact rendered body must contain its opaque export marker");
1287
+ const preparationFingerprint = await fingerprint({ actorId, command });
1288
+ const runNow = effectiveTime(deps);
1289
+ for (let attempt = 0; attempt < deps.maxConflictAttempts; attempt += 1) {
1290
+ permission(access, false, actorId);
1291
+ const accepted = await replay({ ticketId: command.ticketId, commandId: preparationFingerprint }, preparationFingerprint);
1292
+ if (accepted !== null) {
1293
+ permission(access, false, actorId);
1294
+ return accepted;
1295
+ }
1296
+ const stored = await workAt(command);
1297
+ const work = stored.value;
1298
+ if (work.lastProposalPreparation?.fingerprint === preparationFingerprint) {
1299
+ permission(access, false, actorId);
1300
+ return snapshot(work.lastProposalPreparation.result);
1301
+ }
1302
+ if (!options.enabled)
1303
+ throw new SupportDeskConflictError("Engineering processing disabled");
1304
+ const loaded = await load(command.ticketId);
1305
+ live(loaded, work, command, runNow());
1306
+ if (work.dispatch !== undefined ||
1307
+ work.candidateHash !== command.candidateHash ||
1308
+ work.status === "restricted" ||
1309
+ (work.candidate !== undefined &&
1310
+ work.candidate.security !== "ordinary"))
1311
+ throw new SupportDeskConflictError("Engineering proposal candidate or dispatch changed");
1312
+ const policy = await freshPolicy(access, command);
1313
+ allowed(policy, command.exactRequest);
1314
+ const repositoryId = command.exactRequest.repository.repositoryId;
1315
+ let routingBinding;
1316
+ let routingReceipt;
1317
+ let routingBindingHash;
1318
+ if (work.staffSelection !== undefined) {
1319
+ if (work.staffSelection.repositoryId !== repositoryId ||
1320
+ work.staffSelection.policyGeneration !== policy.generation ||
1321
+ work.staffSelection.policyHash !== policy.policyHash)
1322
+ throw new SupportDeskConflictError("Engineering staff selection differs from current target");
1323
+ routingBindingHash = await engineeringStaffSelectionHash(work.staffSelection);
1324
+ }
1325
+ else {
1326
+ const model = work.modelAttempts.at(-1);
1327
+ if (model === undefined || command.routingReceipt === undefined)
1328
+ throw new SupportDeskConflictError("Engineering routing requires authorized staff selection");
1329
+ if (work.candidate !== undefined) {
1330
+ if (work.candidate.routing.outcome !== "selected" ||
1331
+ work.candidate.routing.repositoryId !== repositoryId)
1332
+ throw new SupportDeskConflictError("Engineering routing requires authorized staff selection");
1333
+ routingBinding = await engineeringRoutingBinding({
1334
+ sourceBindingHash: work.sourceBindingHash,
1335
+ evidenceFingerprint: loaded.source.value.evidenceFingerprint,
1336
+ candidateHash: command.candidateHash,
1337
+ inputFingerprint: model.binding.inputFingerprint,
1338
+ routing: work.candidate.routing,
1339
+ policyGeneration: policy.generation,
1340
+ policyHash: policy.policyHash,
1341
+ modelVersion: model.binding.modelVersion,
1342
+ promptVersion: model.binding.promptVersion,
1343
+ parserVersion: model.binding.parserVersion,
1344
+ repositoryId,
1345
+ });
1346
+ }
1347
+ else {
1348
+ const previous = work.proposal?.routingBinding;
1349
+ if (previous === undefined ||
1350
+ previous.repositoryId !== repositoryId ||
1351
+ previous.sourceBindingHash !== work.sourceBindingHash ||
1352
+ previous.candidateHash !== command.candidateHash ||
1353
+ previous.evidenceFingerprint !==
1354
+ loaded.source.value.evidenceFingerprint)
1355
+ throw new SupportDeskConflictError("Engineering replacement needs current staff target selection");
1356
+ routingBinding = {
1357
+ ...previous,
1358
+ policyGeneration: policy.generation,
1359
+ policyHash: policy.policyHash,
1360
+ };
1361
+ }
1362
+ routingBindingHash =
1363
+ await engineeringRoutingBindingHash(routingBinding);
1364
+ routingReceipt = command.routingReceipt;
1365
+ }
1366
+ const checksBinding = await engineeringExportChecksBinding({
1367
+ sourceBindingHash: work.sourceBindingHash,
1368
+ evidenceFingerprint: loaded.source.value.evidenceFingerprint,
1369
+ candidateHash: command.candidateHash,
1370
+ routingBindingHash,
1371
+ exactRequest: command.exactRequest,
1372
+ policyGeneration: policy.generation,
1373
+ policyHash: policy.policyHash,
1374
+ });
1375
+ const body = {
1376
+ candidateHash: command.candidateHash,
1377
+ policyGeneration: policy.generation,
1378
+ policyHash: policy.policyHash,
1379
+ repositoryMetadataHash: checksBinding.repositoryMetadataHash,
1380
+ routingBindingHash,
1381
+ ...(routingBinding === undefined ? {} : { routingBinding }),
1382
+ ...(routingReceipt === undefined ? {} : { routingReceipt }),
1383
+ checksBinding,
1384
+ checksReceipt: command.checksReceipt,
1385
+ request: command.exactRequest,
1386
+ };
1387
+ const old = work.proposal;
1388
+ const identical = old !== undefined &&
1389
+ !work.proposalNeedsRebuild &&
1390
+ canonical(body) ===
1391
+ canonical({
1392
+ candidateHash: old.candidateHash,
1393
+ policyGeneration: old.policyGeneration,
1394
+ policyHash: old.policyHash,
1395
+ repositoryMetadataHash: old.repositoryMetadataHash,
1396
+ routingBindingHash: old.routingBindingHash,
1397
+ ...(old.routingBinding === undefined
1398
+ ? {}
1399
+ : {
1400
+ routingBinding: old.routingBinding,
1401
+ routingReceipt: old.routingReceipt,
1402
+ }),
1403
+ checksBinding: old.checksBinding,
1404
+ checksReceipt: old.checksReceipt,
1405
+ request: old.request,
1406
+ });
1407
+ if (!identical && work.proposalCount >= options.maxProposals)
1408
+ throw new SupportDeskConflictError("Engineering proposal limit reached");
1409
+ const proposalCount = identical
1410
+ ? work.proposalCount
1411
+ : work.proposalCount + 1;
1412
+ const proposalHash = identical
1413
+ ? old.proposalHash
1414
+ : await fingerprint({
1415
+ sourceBindingHash: work.sourceBindingHash,
1416
+ proposalCount,
1417
+ ...body,
1418
+ });
1419
+ const stepId = identical
1420
+ ? old.stepId
1421
+ : await fingerprint({
1422
+ sourceGeneration: work.sourceGeneration,
1423
+ proposalHash,
1424
+ });
1425
+ const proposal = identical
1426
+ ? old
1427
+ : {
1428
+ ...body,
1429
+ proposalHash,
1430
+ stepId,
1431
+ requestHash: checksBinding.requestHash,
1432
+ preparedClaimGeneration: command.claimGeneration,
1433
+ };
1434
+ const deadline = await verifyProposal(access, loaded, work, proposal, runNow);
1435
+ const { approval: _approval, candidate: _candidate, proposalNeedsRebuild: _needsRebuild, ...unreviewed } = work;
1436
+ const next = identical
1437
+ ? work
1438
+ : {
1439
+ ...withoutLease(unreviewed),
1440
+ stage: "proposal",
1441
+ status: "prepared",
1442
+ stepId,
1443
+ requestHash: proposal.requestHash,
1444
+ proposal,
1445
+ proposalCount,
1446
+ workGeneration: work.workGeneration + 1,
1447
+ };
1448
+ reserveReachable(next);
1449
+ const causal = {
1450
+ ticketId: command.ticketId,
1451
+ commandId: preparationFingerprint,
1452
+ correlationId: stepId,
1453
+ };
1454
+ const hash = preparationFingerprint;
1455
+ const result = outcome(next);
1456
+ const acknowledged = {
1457
+ ...next,
1458
+ lastProposalPreparation: {
1459
+ fingerprint: preparationFingerprint,
1460
+ result,
1461
+ },
1462
+ };
1463
+ reserveReachable(acknowledged);
1464
+ deps.admission(loaded.ticket.value.ticket, loaded.rows, 2);
1465
+ await guard();
1466
+ await currentRepository(proposal.request);
1467
+ await freshPolicy(access, proposal);
1468
+ permission(access, false, actorId);
1469
+ live(loaded, work, command, runNow());
1470
+ if (runNow() >= deadline)
1471
+ throw new SupportDeskConflictError("Engineering exact proposal checks expired");
1472
+ const actions = [
1473
+ deps.audit(causal, loaded.ticket.value.ticket, actorId, clock.now(), "support.engineering.proposal_prepared"),
1474
+ receipt(causal, hash, result),
1475
+ ];
1476
+ if (await save(loaded, stored, acknowledged, loaded.source.value, actions)) {
1477
+ permission(access, false, actorId);
1478
+ return result;
1479
+ }
1480
+ }
1481
+ throw new SupportDeskConflictError();
1482
+ },
1483
+ async reviewEngineeringProposal(access, input, guard) {
1484
+ permission(access, true);
1485
+ const actorId = access.principalId;
1486
+ const command = snapshot(fields(input, "review engineering proposal", [
1487
+ "ticketId",
1488
+ "sourceGeneration",
1489
+ "commandId",
1490
+ "correlationId",
1491
+ "expectedWorkGeneration",
1492
+ "proposalHash",
1493
+ "decision",
1494
+ ], ["reason"]));
1495
+ text(command.ticketId, "ticketId");
1496
+ integer(command.sourceGeneration, "sourceGeneration", 1);
1497
+ for (const key of ["commandId", "correlationId", "proposalHash"])
1498
+ text(command[key], key);
1499
+ integer(command.expectedWorkGeneration, "expectedWorkGeneration", 1);
1500
+ if (command.decision !== "approve" && command.decision !== "reject")
1501
+ throw new TypeError("Invalid engineering review decision");
1502
+ if (command.reason !== undefined)
1503
+ text(command.reason, "reason");
1504
+ const hash = await fingerprint({ command, actorId });
1505
+ const runNow = effectiveTime(deps);
1506
+ for (let attempt = 0; attempt < deps.maxConflictAttempts; attempt += 1) {
1507
+ permission(access, true, actorId);
1508
+ const previous = await replay(command, hash);
1509
+ if (previous !== null) {
1510
+ permission(access, true, actorId);
1511
+ return previous;
1512
+ }
1513
+ if (command.decision === "approve" && typeof guard !== "function")
1514
+ throw new TypeError("Engineering approval requires a current read-only guard");
1515
+ const loaded = await load(command.ticketId);
1516
+ const stored = await workAt(command);
1517
+ const work = stored.value;
1518
+ if (!current(loaded, work) ||
1519
+ work.workGeneration !== command.expectedWorkGeneration ||
1520
+ work.dispatch !== undefined ||
1521
+ work.proposal?.proposalHash !== command.proposalHash ||
1522
+ work.proposalNeedsRebuild === true)
1523
+ throw new SupportDeskConflictError("Engineering exact review source, proposal or generation changed");
1524
+ const proposal = work.proposal;
1525
+ const deadline = command.decision === "reject"
1526
+ ? Number.POSITIVE_INFINITY
1527
+ : await verifyProposal(access, loaded, work, proposal, runNow);
1528
+ let next = {
1529
+ ...withoutLease(work),
1530
+ stage: "dispatch",
1531
+ status: command.decision === "approve" ? "approved" : "held",
1532
+ ...(command.decision === "reject"
1533
+ ? {}
1534
+ : {
1535
+ deadline: new Date(runNow() + options.maxWorkMilliseconds).toISOString(),
1536
+ }),
1537
+ workGeneration: work.workGeneration + 1,
1538
+ approval: {
1539
+ proposalHash: proposal.proposalHash,
1540
+ requestHash: proposal.requestHash,
1541
+ sourceBindingHash: work.sourceBindingHash,
1542
+ policyGeneration: proposal.policyGeneration,
1543
+ policyHash: proposal.policyHash,
1544
+ repositoryMetadataHash: proposal.repositoryMetadataHash,
1545
+ routingBindingHash: proposal.routingBindingHash,
1546
+ checksBindingHash: await fingerprint(proposal.checksBinding),
1547
+ decision: command.decision,
1548
+ reviewedBy: actorId,
1549
+ reviewedAt: clock.now(),
1550
+ ...(command.reason === undefined ? {} : { reason: command.reason }),
1551
+ },
1552
+ };
1553
+ reserveReachable(next);
1554
+ deps.admission(loaded.ticket.value.ticket, loaded.rows, 2);
1555
+ const result = outcome(next);
1556
+ if (command.decision === "approve") {
1557
+ await guard();
1558
+ await currentRepository(proposal.request);
1559
+ await freshPolicy(access, proposal);
1560
+ }
1561
+ permission(access, true, actorId);
1562
+ if (!current(loaded, work) || runNow() >= deadline)
1563
+ throw new SupportDeskConflictError("Engineering review source or checks expired");
1564
+ if (command.decision === "approve") {
1565
+ next = {
1566
+ ...next,
1567
+ deadline: new Date(runNow() + options.maxWorkMilliseconds).toISOString(),
1568
+ };
1569
+ reserveReachable(next);
1570
+ }
1571
+ if (await save(loaded, stored, next, loaded.source.value, [
1572
+ deps.audit(command, loaded.ticket.value.ticket, actorId, clock.now(), "support.engineering.reviewed"),
1573
+ receipt(command, hash, result),
1574
+ ])) {
1575
+ permission(access, true, actorId);
1576
+ return result;
1577
+ }
1578
+ }
1579
+ const previous = await replay(command, hash);
1580
+ if (previous !== null) {
1581
+ permission(access, true, actorId);
1582
+ return previous;
1583
+ }
1584
+ throw new SupportDeskConflictError();
1585
+ },
1586
+ async beginEngineeringDispatch(access, input, guard) {
1587
+ permission(access);
1588
+ const actorId = access.principalId;
1589
+ if (typeof guard !== "function")
1590
+ throw new TypeError("Engineering dispatch requires a current read-only guard");
1591
+ const command = snapshot(fields(input, "begin engineering dispatch", [
1592
+ "ticketId",
1593
+ "sourceGeneration",
1594
+ "workerId",
1595
+ "claimId",
1596
+ "claimGeneration",
1597
+ "stepId",
1598
+ "requestHash",
1599
+ "proposalHash",
1600
+ "dispatchId",
1601
+ ]));
1602
+ fence(command);
1603
+ for (const key of [
1604
+ "stepId",
1605
+ "requestHash",
1606
+ "proposalHash",
1607
+ "dispatchId",
1608
+ ])
1609
+ text(command[key], key);
1610
+ if (!options.enabled)
1611
+ throw new SupportDeskConflictError("Engineering processing disabled");
1612
+ if (options.dispatchResults === undefined)
1613
+ throw new SupportDeskConflictError("Engineering dispatch proof verifier unavailable");
1614
+ const runNow = effectiveTime(deps);
1615
+ for (let attempt = 0; attempt < deps.maxConflictAttempts; attempt += 1) {
1616
+ permission(access, false, actorId);
1617
+ const loaded = await load(command.ticketId);
1618
+ const stored = await workAt(command);
1619
+ const work = stored.value;
1620
+ live(loaded, work, command, runNow());
1621
+ const proposal = work.proposal;
1622
+ const approval = work.approval;
1623
+ if (proposal === undefined ||
1624
+ work.proposalNeedsRebuild === true ||
1625
+ approval?.decision !== "approve" ||
1626
+ proposal.proposalHash !== command.proposalHash ||
1627
+ proposal.stepId !== command.stepId ||
1628
+ proposal.requestHash !== command.requestHash ||
1629
+ work.stepId !== command.stepId ||
1630
+ work.requestHash !== command.requestHash ||
1631
+ approval.proposalHash !== proposal.proposalHash ||
1632
+ approval.requestHash !== proposal.requestHash ||
1633
+ approval.sourceBindingHash !== work.sourceBindingHash ||
1634
+ approval.policyGeneration !== proposal.policyGeneration ||
1635
+ approval.policyHash !== proposal.policyHash ||
1636
+ approval.repositoryMetadataHash !== proposal.repositoryMetadataHash ||
1637
+ approval.routingBindingHash !== proposal.routingBindingHash ||
1638
+ approval.checksBindingHash !==
1639
+ (await fingerprint(proposal.checksBinding)))
1640
+ throw new SupportDeskConflictError("Engineering dispatch exact staff approval changed");
1641
+ if (work.dispatch !== undefined &&
1642
+ (work.status !== "dispatch_started" ||
1643
+ work.dispatch.dispatchId !== command.dispatchId ||
1644
+ work.dispatch.workerId !== command.workerId ||
1645
+ work.dispatch.claimId !== command.claimId ||
1646
+ work.dispatch.claimGeneration !== command.claimGeneration)) {
1647
+ permission(access, false, actorId);
1648
+ return null;
1649
+ }
1650
+ const deadline = await verifyProposal(access, loaded, work, proposal, runNow);
1651
+ await guard();
1652
+ await currentRepository(proposal.request);
1653
+ await freshPolicy(access, proposal);
1654
+ permission(access, false, actorId);
1655
+ live(loaded, work, command, runNow());
1656
+ if (runNow() >= deadline)
1657
+ throw new SupportDeskConflictError("Engineering dispatch verification expired");
1658
+ const replayed = work.dispatch !== undefined;
1659
+ const dispatch = work.dispatch ?? {
1660
+ dispatchId: command.dispatchId,
1661
+ startedAt: clock.now(),
1662
+ workerId: command.workerId,
1663
+ claimId: command.claimId,
1664
+ claimGeneration: command.claimGeneration,
1665
+ repositoryId: proposal.request.repository.repositoryId,
1666
+ requestHash: proposal.requestHash,
1667
+ exportMarker: proposal.request.bundle.exportMarker,
1668
+ };
1669
+ const next = replayed
1670
+ ? work
1671
+ : {
1672
+ ...work,
1673
+ dispatch,
1674
+ terminalReserveRows: 2,
1675
+ stage: "reconcile",
1676
+ status: "dispatch_started",
1677
+ workGeneration: work.workGeneration + 1,
1678
+ };
1679
+ const destinations = loaded.source.value.destinations;
1680
+ if (!replayed &&
1681
+ (destinations.length >= options.maxTargets ||
1682
+ destinations.some((entry) => entry.exportMarker === dispatch.exportMarker)))
1683
+ throw new SupportDeskConflictError("Engineering target capacity or opaque marker uniqueness limit reached");
1684
+ const nextSource = replayed
1685
+ ? loaded.source.value
1686
+ : {
1687
+ ...loaded.source.value,
1688
+ workGeneration: loaded.source.value.workGeneration + 1,
1689
+ reconciliationPending: true,
1690
+ destinations: [
1691
+ ...destinations,
1692
+ {
1693
+ repositoryId: dispatch.repositoryId,
1694
+ sourceGeneration: work.sourceGeneration,
1695
+ ...(loaded.source.value.followupRevocationEpoch === undefined
1696
+ ? {}
1697
+ : {
1698
+ followupRevocationEpoch: loaded.source.value.followupRevocationEpoch,
1699
+ }),
1700
+ stepId: proposal.stepId,
1701
+ requestHash: proposal.requestHash,
1702
+ exportMarker: dispatch.exportMarker,
1703
+ dispatchId: dispatch.dispatchId,
1704
+ outcome: "dispatch_started",
1705
+ reconciliation: "pending",
1706
+ },
1707
+ ],
1708
+ };
1709
+ reserveReachable(next);
1710
+ checkEngineeringRecordCapacity(nextSource);
1711
+ const causal = {
1712
+ ticketId: command.ticketId,
1713
+ commandId: await fingerprint({
1714
+ operation: "dispatch_started",
1715
+ dispatchId: command.dispatchId,
1716
+ sourceGeneration: work.sourceGeneration,
1717
+ requestHash: command.requestHash,
1718
+ }),
1719
+ correlationId: command.dispatchId,
1720
+ };
1721
+ const hash = await fingerprint(command);
1722
+ if (!replayed)
1723
+ deps.admission(loaded.ticket.value.ticket, loaded.rows, 4);
1724
+ if (await save(loaded, stored, next, nextSource, replayed
1725
+ ? []
1726
+ : [
1727
+ deps.audit(causal, loaded.ticket.value.ticket, actorId, clock.now(), "support.engineering.dispatch_started"),
1728
+ receipt(causal, hash, outcome(next)),
1729
+ ])) {
1730
+ await guard();
1731
+ await currentRepository(proposal.request);
1732
+ await freshPolicy(access, proposal);
1733
+ const latest = await load(command.ticketId);
1734
+ const latestWork = await workAt(command);
1735
+ const finalSource = await records.getVersioned(engineeringSourceKey(command.ticketId));
1736
+ permission(access, false, actorId);
1737
+ live(latest, latestWork.value, command, runNow());
1738
+ if (finalSource?.version !== latest.source.version ||
1739
+ latestWork.value.dispatch?.dispatchId !== command.dispatchId ||
1740
+ latestWork.value.status !== "dispatch_started" ||
1741
+ runNow() >= deadline)
1742
+ throw new SupportDeskConflictError("Engineering dispatch changed before physical call authorization");
1743
+ return {
1744
+ ticketId: work.ticketId,
1745
+ sourceGeneration: work.sourceGeneration,
1746
+ dispatchId: dispatch.dispatchId,
1747
+ exportMarker: dispatch.exportMarker,
1748
+ requestHash: proposal.requestHash,
1749
+ request: snapshot(proposal.request),
1750
+ };
1751
+ }
1752
+ }
1753
+ throw new SupportDeskConflictError();
1754
+ },
1755
+ };
1756
+ }
1757
+ //# sourceMappingURL=engineering-work.js.map