@pegma/support-desk-application 0.2.7 → 0.2.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -20
- package/dist/engineering-followup-internal.d.ts +9 -2
- package/dist/engineering-followup-internal.d.ts.map +1 -1
- package/dist/engineering-followup-records.d.ts +12 -0
- package/dist/engineering-followup-records.d.ts.map +1 -1
- package/dist/engineering-followup-records.js +55 -5
- package/dist/engineering-followup-records.js.map +1 -1
- package/dist/engineering-followup-source.d.ts +18 -7
- package/dist/engineering-followup-source.d.ts.map +1 -1
- package/dist/engineering-followup-source.js +191 -23
- package/dist/engineering-followup-source.js.map +1 -1
- package/dist/engineering-followup-types.d.ts +75 -9
- package/dist/engineering-followup-types.d.ts.map +1 -1
- package/dist/engineering-followup-work.d.ts +9 -12
- package/dist/engineering-followup-work.d.ts.map +1 -1
- package/dist/engineering-followup-work.js +783 -234
- package/dist/engineering-followup-work.js.map +1 -1
- package/dist/engineering-handoff.d.ts +6 -0
- package/dist/engineering-handoff.d.ts.map +1 -0
- package/dist/engineering-handoff.js +31 -0
- package/dist/engineering-handoff.js.map +1 -0
- package/dist/engineering-types.d.ts +28 -0
- package/dist/engineering-types.d.ts.map +1 -1
- package/dist/engineering-work.d.ts +1 -1
- package/dist/engineering-work.d.ts.map +1 -1
- package/dist/engineering-work.js +534 -32
- package/dist/engineering-work.js.map +1 -1
- package/dist/index.d.ts +13 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +65 -4
- package/dist/index.js.map +1 -1
- package/dist/resolution-learning-delivery-handoff.d.ts +5 -0
- package/dist/resolution-learning-delivery-handoff.d.ts.map +1 -0
- package/dist/resolution-learning-delivery-handoff.js +20 -0
- package/dist/resolution-learning-delivery-handoff.js.map +1 -0
- package/dist/resolution-learning-types.d.ts +42 -1
- package/dist/resolution-learning-types.d.ts.map +1 -1
- package/dist/resolution-learning-worker.d.ts +1 -1
- package/dist/resolution-learning-worker.d.ts.map +1 -1
- package/dist/resolution-learning-worker.js +594 -369
- package/dist/resolution-learning-worker.js.map +1 -1
- package/package.json +3 -3
package/dist/engineering-work.js
CHANGED
|
@@ -5,6 +5,7 @@ import { SupportDeskAuthorizationError, SupportDeskConflictError, SupportDeskLim
|
|
|
5
5
|
import { canonical, dataArray, encodedBytes, fields, fingerprint, integer, text, timestamp, } from "./assistant-turn-records.js";
|
|
6
6
|
import { safeEngineeringSnapshot as snapshot, checkEngineeringRecordCapacity, advanceEngineeringFollowupRevocationEpoch, engineeringMaximumCandidateBytes, engineeringMaximumRecordBytes, engineeringCommandKey, engineeringSourceKey, engineeringWorkKey, validateEngineeringCandidate, validateEngineeringModelBinding, validateEngineeringExportRequest, } from "./engineering-records.js";
|
|
7
7
|
import { projectEngineeringRows } from "./engineering-internal.js";
|
|
8
|
+
import { readEngineeringSourceVersion } from "./engineering-evidence.js";
|
|
8
9
|
export async function engineeringModelManifestHash(manifest) {
|
|
9
10
|
const value = fields(manifest, "engineering input manifest", [
|
|
10
11
|
"messages",
|
|
@@ -304,6 +305,259 @@ export function createEngineeringWorkerServices(deps, load) {
|
|
|
304
305
|
throw new SupportDeskNotFoundError();
|
|
305
306
|
return { value: stored.value, version: stored.version };
|
|
306
307
|
}
|
|
308
|
+
function prepareTicketRead(ticketId) {
|
|
309
|
+
const failed = (error) => ({ ok: false, error });
|
|
310
|
+
try {
|
|
311
|
+
return records
|
|
312
|
+
.getVersioned({ partition: ticketId, id: "ticket" })
|
|
313
|
+
.then((value) => ({ ok: true, value }), failed);
|
|
314
|
+
}
|
|
315
|
+
catch (error) {
|
|
316
|
+
return Promise.resolve(failed(error));
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
async function observedModelLoad(access, command, actorId, completion, ticketRead) {
|
|
320
|
+
const preparedTicket = ticketRead === undefined ? undefined : await ticketRead;
|
|
321
|
+
if (preparedTicket !== undefined && !preparedTicket.ok)
|
|
322
|
+
throw preparedTicket.error;
|
|
323
|
+
const ticket = preparedTicket === undefined
|
|
324
|
+
? await records.getVersioned({
|
|
325
|
+
partition: command.ticketId,
|
|
326
|
+
id: "ticket",
|
|
327
|
+
})
|
|
328
|
+
: preparedTicket.value;
|
|
329
|
+
permission(access, false, actorId);
|
|
330
|
+
if (ticket?.value.kind !== "ticket")
|
|
331
|
+
throw new SupportDeskNotFoundError();
|
|
332
|
+
const source = await readEngineeringSourceVersion(records, command.ticketId);
|
|
333
|
+
permission(access, false, actorId);
|
|
334
|
+
if (source === null)
|
|
335
|
+
throw new SupportDeskNotFoundError();
|
|
336
|
+
// Terminal accepted recovery never projects private observations or invokes a guard.
|
|
337
|
+
const eligible = source.value.state === "eligible" &&
|
|
338
|
+
source.value.security === "ordinary" &&
|
|
339
|
+
source.value.sourceGeneration === command.sourceGeneration;
|
|
340
|
+
if (!eligible ||
|
|
341
|
+
!hasPermission(access, supportPermissions.engineeringRead) ||
|
|
342
|
+
!hasPermission(access, supportPermissions.queueRead)) {
|
|
343
|
+
if (completion !== undefined) {
|
|
344
|
+
const stored = await workAt(command);
|
|
345
|
+
permission(access, false, actorId);
|
|
346
|
+
if (stored.value.lastCompletion?.fingerprint === completion)
|
|
347
|
+
return { accepted: outcome(stored.value) };
|
|
348
|
+
}
|
|
349
|
+
modelReadPermission(access, actorId);
|
|
350
|
+
throw new SupportDeskConflictError("Engineering source, work or claim fence changed");
|
|
351
|
+
}
|
|
352
|
+
modelReadPermission(access, actorId);
|
|
353
|
+
// The complete bounded list retains error/capacity priority; Work is never used on its failure.
|
|
354
|
+
const [rowsResult, workResult] = await Promise.allSettled([
|
|
355
|
+
Promise.resolve().then(() => records.list(command.ticketId)),
|
|
356
|
+
Promise.resolve().then(() => workAt(command)),
|
|
357
|
+
]);
|
|
358
|
+
permission(access, false, actorId);
|
|
359
|
+
modelReadPermission(access, actorId);
|
|
360
|
+
if (rowsResult.status === "rejected")
|
|
361
|
+
throw rowsResult.reason;
|
|
362
|
+
const rows = rowsResult.value;
|
|
363
|
+
if (rows.length > deps.limits.maxRecordsPerTicket)
|
|
364
|
+
throw new SupportDeskLimitError("ticket_partition", deps.limits.maxRecordsPerTicket);
|
|
365
|
+
if (workResult.status === "rejected")
|
|
366
|
+
throw workResult.reason;
|
|
367
|
+
return {
|
|
368
|
+
loaded: {
|
|
369
|
+
ticket: { value: ticket.value, version: ticket.version },
|
|
370
|
+
source,
|
|
371
|
+
rows,
|
|
372
|
+
},
|
|
373
|
+
stored: workResult.value,
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
async function observedDispatchLoad(access, command, actorId) {
|
|
377
|
+
const source = await readEngineeringSourceVersion(records, command.ticketId);
|
|
378
|
+
modelReadPermission(access, actorId);
|
|
379
|
+
if (source === null)
|
|
380
|
+
throw new SupportDeskNotFoundError();
|
|
381
|
+
if (source.value.state !== "eligible" ||
|
|
382
|
+
source.value.security !== "ordinary" ||
|
|
383
|
+
source.value.sourceGeneration !== command.sourceGeneration)
|
|
384
|
+
throw new SupportDeskConflictError("Engineering dispatch source changed");
|
|
385
|
+
const [ticketResult, rowsResult, workResult] = await Promise.allSettled([
|
|
386
|
+
Promise.resolve().then(() => records.getVersioned({ partition: command.ticketId, id: "ticket" })),
|
|
387
|
+
Promise.resolve().then(() => records.list(command.ticketId)),
|
|
388
|
+
Promise.resolve().then(() => workAt(command)),
|
|
389
|
+
]);
|
|
390
|
+
modelReadPermission(access, actorId);
|
|
391
|
+
if (ticketResult.status === "rejected")
|
|
392
|
+
throw ticketResult.reason;
|
|
393
|
+
const ticket = ticketResult.value;
|
|
394
|
+
if (ticket?.value.kind !== "ticket")
|
|
395
|
+
throw new SupportDeskNotFoundError();
|
|
396
|
+
if (rowsResult.status === "rejected")
|
|
397
|
+
throw rowsResult.reason;
|
|
398
|
+
if (rowsResult.value.length > deps.limits.maxRecordsPerTicket)
|
|
399
|
+
throw new SupportDeskLimitError("ticket_partition", deps.limits.maxRecordsPerTicket);
|
|
400
|
+
if (workResult.status === "rejected")
|
|
401
|
+
throw workResult.reason;
|
|
402
|
+
return {
|
|
403
|
+
loaded: {
|
|
404
|
+
ticket: { value: ticket.value, version: ticket.version },
|
|
405
|
+
source,
|
|
406
|
+
rows: rowsResult.value,
|
|
407
|
+
},
|
|
408
|
+
stored: workResult.value,
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
function modelReadPermission(reader, actorId) {
|
|
412
|
+
permission(reader, false, actorId);
|
|
413
|
+
for (const name of [
|
|
414
|
+
supportPermissions.engineeringRead,
|
|
415
|
+
supportPermissions.queueRead,
|
|
416
|
+
])
|
|
417
|
+
if (!hasPermission(reader, name))
|
|
418
|
+
throw new SupportDeskAuthorizationError(name);
|
|
419
|
+
}
|
|
420
|
+
function modelObservation(loaded, stored, actorId, confirmTicket = false) {
|
|
421
|
+
let open = true;
|
|
422
|
+
let confirmed = false;
|
|
423
|
+
let confirmations = 0;
|
|
424
|
+
const check = (reader) => {
|
|
425
|
+
modelReadPermission(reader, actorId);
|
|
426
|
+
if (!open)
|
|
427
|
+
throw new SupportDeskConflictError("Engineering model observation expired");
|
|
428
|
+
};
|
|
429
|
+
const observation = Object.freeze({
|
|
430
|
+
read(reader) {
|
|
431
|
+
check(reader);
|
|
432
|
+
if (!confirmed)
|
|
433
|
+
throw new SupportDeskConflictError("Engineering model observation requires confirmation");
|
|
434
|
+
confirmed = false;
|
|
435
|
+
return {
|
|
436
|
+
source: snapshot(loaded.source.value),
|
|
437
|
+
work: snapshot(stored.value),
|
|
438
|
+
};
|
|
439
|
+
},
|
|
440
|
+
async confirm(reader) {
|
|
441
|
+
check(reader);
|
|
442
|
+
confirmed = false;
|
|
443
|
+
const source = await records.getVersioned(engineeringSourceKey(loaded.source.value.ticketId));
|
|
444
|
+
check(reader);
|
|
445
|
+
if (source?.version !== loaded.source.version)
|
|
446
|
+
throw new SupportDeskConflictError("Engineering model source changed");
|
|
447
|
+
const workKey = engineeringWorkKey(stored.value.ticketId, stored.value.sourceGeneration);
|
|
448
|
+
if (confirmTicket) {
|
|
449
|
+
const [ticketResult, workResult] = await Promise.allSettled([
|
|
450
|
+
Promise.resolve().then(() => records.get({ partition: stored.value.ticketId, id: "ticket" })),
|
|
451
|
+
Promise.resolve().then(() => records.getVersioned(workKey)),
|
|
452
|
+
]);
|
|
453
|
+
check(reader);
|
|
454
|
+
if (ticketResult.status === "rejected")
|
|
455
|
+
throw ticketResult.reason;
|
|
456
|
+
if (ticketResult.value?.kind !== "ticket")
|
|
457
|
+
throw new SupportDeskNotFoundError();
|
|
458
|
+
if (workResult.status === "rejected")
|
|
459
|
+
throw workResult.reason;
|
|
460
|
+
if (workResult.value?.version !== stored.version)
|
|
461
|
+
throw new SupportDeskConflictError("Engineering model work changed");
|
|
462
|
+
const finalSource = await records.getVersioned(engineeringSourceKey(loaded.source.value.ticketId));
|
|
463
|
+
check(reader);
|
|
464
|
+
if (finalSource?.version !== loaded.source.version)
|
|
465
|
+
throw new SupportDeskConflictError("Engineering model source changed");
|
|
466
|
+
}
|
|
467
|
+
else {
|
|
468
|
+
const work = await records.getVersioned(workKey);
|
|
469
|
+
check(reader);
|
|
470
|
+
if (work?.version !== stored.version)
|
|
471
|
+
throw new SupportDeskConflictError("Engineering model work changed");
|
|
472
|
+
}
|
|
473
|
+
confirmed = true;
|
|
474
|
+
confirmations += 1;
|
|
475
|
+
},
|
|
476
|
+
});
|
|
477
|
+
return {
|
|
478
|
+
observation,
|
|
479
|
+
assertOpen: check,
|
|
480
|
+
confirmationCount: () => confirmations,
|
|
481
|
+
close() {
|
|
482
|
+
open = false;
|
|
483
|
+
confirmed = false;
|
|
484
|
+
},
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
async function preparedModelCurrent(access, loaded, stored, actorId) {
|
|
488
|
+
const source = await records.getVersioned(engineeringSourceKey(stored.value.ticketId));
|
|
489
|
+
permission(access, false, actorId);
|
|
490
|
+
if (source?.version !== loaded.source.version)
|
|
491
|
+
return false;
|
|
492
|
+
const [ticket, work] = await Promise.allSettled([
|
|
493
|
+
Promise.resolve().then(() => records.getVersioned({
|
|
494
|
+
partition: stored.value.ticketId,
|
|
495
|
+
id: "ticket",
|
|
496
|
+
})),
|
|
497
|
+
Promise.resolve().then(() => records.getVersioned(engineeringWorkKey(stored.value.ticketId, stored.value.sourceGeneration))),
|
|
498
|
+
]);
|
|
499
|
+
permission(access, false, actorId);
|
|
500
|
+
if (ticket.status === "rejected")
|
|
501
|
+
throw ticket.reason;
|
|
502
|
+
if (ticket.value?.value.kind !== "ticket")
|
|
503
|
+
throw new SupportDeskNotFoundError();
|
|
504
|
+
if (work.status === "rejected")
|
|
505
|
+
throw work.reason;
|
|
506
|
+
if (ticket.value.version !== loaded.ticket.version ||
|
|
507
|
+
work.value?.version !== stored.version)
|
|
508
|
+
return false;
|
|
509
|
+
const finalSource = await records.getVersioned(engineeringSourceKey(stored.value.ticketId));
|
|
510
|
+
permission(access, false, actorId);
|
|
511
|
+
return finalSource?.version === loaded.source.version;
|
|
512
|
+
}
|
|
513
|
+
async function modelGuard(guard, loaded, stored, actorId) {
|
|
514
|
+
const scope = modelObservation(loaded, stored, actorId);
|
|
515
|
+
const boundary = engineeringMutationBoundary({
|
|
516
|
+
verifyBeforeCommit: () => guard(scope.observation),
|
|
517
|
+
});
|
|
518
|
+
try {
|
|
519
|
+
return await boundary.verify();
|
|
520
|
+
}
|
|
521
|
+
finally {
|
|
522
|
+
scope.close();
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
async function workerGuard(guard, access, loaded, stored, verifyApplication, actorId, phase) {
|
|
526
|
+
const scope = modelObservation(loaded, stored, actorId, phase === "after_dispatch");
|
|
527
|
+
let verified = false;
|
|
528
|
+
let afterApplication = scope.confirmationCount();
|
|
529
|
+
let applicationAssertion;
|
|
530
|
+
const observation = Object.freeze({
|
|
531
|
+
phase,
|
|
532
|
+
read: scope.observation.read,
|
|
533
|
+
confirm: scope.observation.confirm,
|
|
534
|
+
async verifyApplication() {
|
|
535
|
+
scope.assertOpen(access);
|
|
536
|
+
applicationAssertion = await verifyApplication();
|
|
537
|
+
scope.assertOpen(access);
|
|
538
|
+
verified = true;
|
|
539
|
+
afterApplication = scope.confirmationCount();
|
|
540
|
+
return applicationAssertion;
|
|
541
|
+
},
|
|
542
|
+
});
|
|
543
|
+
const boundary = engineeringMutationBoundary({
|
|
544
|
+
verifyBeforeCommit: () => guard(observation),
|
|
545
|
+
});
|
|
546
|
+
try {
|
|
547
|
+
const assertion = await boundary.verify();
|
|
548
|
+
if (!verified)
|
|
549
|
+
await observation.verifyApplication();
|
|
550
|
+
if (scope.confirmationCount() <= afterApplication)
|
|
551
|
+
await observation.confirm(access);
|
|
552
|
+
return () => {
|
|
553
|
+
applicationAssertion?.();
|
|
554
|
+
assertion();
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
finally {
|
|
558
|
+
scope.close();
|
|
559
|
+
}
|
|
560
|
+
}
|
|
307
561
|
async function save(loaded, stored, work, source = loaded.source.value, extra = [], beforeCommit, mutation) {
|
|
308
562
|
const securityRestriction = (source !== loaded.source.value && source.state === "restricted") ||
|
|
309
563
|
(work.security !== "ordinary" &&
|
|
@@ -347,6 +601,13 @@ export function createEngineeringWorkerServices(deps, load) {
|
|
|
347
601
|
!current(loaded, work))
|
|
348
602
|
throw new SupportDeskConflictError("Engineering source, work or claim fence changed");
|
|
349
603
|
}
|
|
604
|
+
function modelPreparationCurrent(loaded, work, command, now) {
|
|
605
|
+
live(loaded, work, command, now);
|
|
606
|
+
if (work.stage !== "model" ||
|
|
607
|
+
work.dispatch !== undefined ||
|
|
608
|
+
work.candidate !== undefined)
|
|
609
|
+
throw new SupportDeskConflictError("Engineering model stage changed");
|
|
610
|
+
}
|
|
350
611
|
function restrictedSource(loaded, work, security = "restricted") {
|
|
351
612
|
const source = loaded.source.value;
|
|
352
613
|
if (source.state === "withdrawn" ||
|
|
@@ -366,6 +627,9 @@ export function createEngineeringWorkerServices(deps, load) {
|
|
|
366
627
|
}
|
|
367
628
|
async function freshPolicy(access, expected) {
|
|
368
629
|
const policy = snapshot(await deps.policy.resolve(access));
|
|
630
|
+
return checkedPolicy(policy, expected);
|
|
631
|
+
}
|
|
632
|
+
function checkedPolicy(policy, expected) {
|
|
369
633
|
if (!policy.policy.enabled ||
|
|
370
634
|
(expected !== undefined &&
|
|
371
635
|
(policy.generation !== expected.policyGeneration ||
|
|
@@ -549,7 +813,7 @@ export function createEngineeringWorkerServices(deps, load) {
|
|
|
549
813
|
throw new SupportDeskConflictError("Engineering proposal verification expired");
|
|
550
814
|
return deadline;
|
|
551
815
|
}
|
|
552
|
-
|
|
816
|
+
const services = {
|
|
553
817
|
async scanEngineeringCandidates(access, input) {
|
|
554
818
|
const actorId = access.principalId;
|
|
555
819
|
const authority = scanPermission(access, actorId);
|
|
@@ -785,7 +1049,7 @@ export function createEngineeringWorkerServices(deps, load) {
|
|
|
785
1049
|
}
|
|
786
1050
|
throw new SupportDeskConflictError();
|
|
787
1051
|
},
|
|
788
|
-
async prepareEngineeringModel(access, input) {
|
|
1052
|
+
async prepareEngineeringModel(access, input, guard, seed) {
|
|
789
1053
|
permission(access);
|
|
790
1054
|
const actorId = access.principalId;
|
|
791
1055
|
const command = snapshot(fields(input, "prepare engineering model", [
|
|
@@ -800,23 +1064,31 @@ export function createEngineeringWorkerServices(deps, load) {
|
|
|
800
1064
|
validateEngineeringModelBinding(command.binding);
|
|
801
1065
|
if (!options.enabled)
|
|
802
1066
|
throw new SupportDeskConflictError("Engineering processing disabled");
|
|
803
|
-
const runNow = effectiveTime(deps);
|
|
1067
|
+
const runNow = seed?.now ?? effectiveTime(deps);
|
|
804
1068
|
for (let attempt = 0; attempt < deps.maxConflictAttempts; attempt += 1) {
|
|
805
1069
|
permission(access, false, actorId);
|
|
806
|
-
const
|
|
807
|
-
|
|
1070
|
+
const observed = attempt === 0 && seed !== undefined
|
|
1071
|
+
? seed
|
|
1072
|
+
: guard === undefined
|
|
1073
|
+
? undefined
|
|
1074
|
+
: await observedModelLoad(access, command, actorId);
|
|
1075
|
+
if (observed !== undefined && "accepted" in observed)
|
|
1076
|
+
throw new SupportDeskConflictError();
|
|
1077
|
+
const loaded = observed === undefined
|
|
1078
|
+
? await load(command.ticketId)
|
|
1079
|
+
: observed.loaded;
|
|
1080
|
+
const stored = observed === undefined ? await workAt(command) : observed.stored;
|
|
808
1081
|
const work = stored.value;
|
|
809
|
-
|
|
810
|
-
if (work.stage !== "model" ||
|
|
811
|
-
work.dispatch !== undefined ||
|
|
812
|
-
work.candidate !== undefined)
|
|
813
|
-
throw new SupportDeskConflictError("Engineering model stage changed");
|
|
1082
|
+
modelPreparationCurrent(loaded, work, command, runNow());
|
|
814
1083
|
const binding = command.binding;
|
|
815
1084
|
const manifest = binding.inputManifest;
|
|
816
|
-
const
|
|
1085
|
+
const expectedPolicy = {
|
|
817
1086
|
policyGeneration: manifest.policyGeneration,
|
|
818
1087
|
policyHash: manifest.policyHash,
|
|
819
|
-
}
|
|
1088
|
+
};
|
|
1089
|
+
const policy = attempt === 0 && seed !== undefined
|
|
1090
|
+
? checkedPolicy(seed.policy, expectedPolicy)
|
|
1091
|
+
: await freshPolicy(access, expectedPolicy);
|
|
820
1092
|
if ((await engineeringModelManifestHash(manifest)) !==
|
|
821
1093
|
binding.inputManifestHash ||
|
|
822
1094
|
manifest.evidenceFingerprint !==
|
|
@@ -841,14 +1113,40 @@ export function createEngineeringWorkerServices(deps, load) {
|
|
|
841
1113
|
deadline > timestamp(work.leaseUntil, "leaseUntil") ||
|
|
842
1114
|
deadline > timestamp(work.deadline, "deadline"))
|
|
843
1115
|
throw new SupportDeskConflictError("Engineering model deadline exceeds live claim");
|
|
1116
|
+
const assertDeadline = () => {
|
|
1117
|
+
if (deadline <= runNow())
|
|
1118
|
+
throw new SupportDeskConflictError("Engineering prepared model deadline expired");
|
|
1119
|
+
};
|
|
844
1120
|
const existing = work.modelAttempts.find((entry) => canonical(entry.binding) === canonical(binding));
|
|
845
1121
|
if (existing !== undefined) {
|
|
846
1122
|
if (existing.preparedClaimGeneration !== command.claimGeneration ||
|
|
847
1123
|
existing.stepId !== work.stepId)
|
|
848
1124
|
throw new SupportDeskConflictError("Engineering model preparation belongs to another invocation");
|
|
1125
|
+
const assertProof = guard === undefined
|
|
1126
|
+
? undefined
|
|
1127
|
+
: await modelGuard(guard, loaded, stored, actorId);
|
|
1128
|
+
if (guard !== undefined) {
|
|
1129
|
+
if (!(await preparedModelCurrent(access, loaded, stored, actorId)))
|
|
1130
|
+
continue;
|
|
1131
|
+
permission(access, false, actorId);
|
|
1132
|
+
live(loaded, work, command, runNow());
|
|
1133
|
+
assertProof?.();
|
|
1134
|
+
assertDeadline();
|
|
1135
|
+
return {
|
|
1136
|
+
stepId: existing.stepId,
|
|
1137
|
+
requestHash: existing.requestHash,
|
|
1138
|
+
maxCandidateBytes: 16_000,
|
|
1139
|
+
};
|
|
1140
|
+
}
|
|
849
1141
|
live(loaded, work, command, runNow());
|
|
1142
|
+
assertDeadline();
|
|
850
1143
|
permission(access, false, actorId);
|
|
851
|
-
if (await save(loaded, stored, work)
|
|
1144
|
+
if (await save(loaded, stored, work, loaded.source.value, [], () => {
|
|
1145
|
+
permission(access, false, actorId);
|
|
1146
|
+
live(loaded, work, command, runNow());
|
|
1147
|
+
assertProof?.();
|
|
1148
|
+
assertDeadline();
|
|
1149
|
+
})) {
|
|
852
1150
|
permission(access, false, actorId);
|
|
853
1151
|
return {
|
|
854
1152
|
stepId: existing.stepId,
|
|
@@ -885,20 +1183,30 @@ export function createEngineeringWorkerServices(deps, load) {
|
|
|
885
1183
|
workGeneration: work.workGeneration + 1,
|
|
886
1184
|
};
|
|
887
1185
|
reserveReachable(next);
|
|
1186
|
+
const assertProof = guard === undefined
|
|
1187
|
+
? undefined
|
|
1188
|
+
: await modelGuard(guard, loaded, stored, actorId);
|
|
1189
|
+
assertDeadline();
|
|
888
1190
|
await freshPolicy(access, {
|
|
889
1191
|
policyGeneration: manifest.policyGeneration,
|
|
890
1192
|
policyHash: manifest.policyHash,
|
|
891
1193
|
});
|
|
892
1194
|
live(loaded, work, command, runNow());
|
|
1195
|
+
assertDeadline();
|
|
893
1196
|
permission(access, false, actorId);
|
|
894
|
-
if (await save(loaded, stored, next)
|
|
1197
|
+
if (await save(loaded, stored, next, loaded.source.value, [], () => {
|
|
1198
|
+
permission(access, false, actorId);
|
|
1199
|
+
live(loaded, work, command, runNow());
|
|
1200
|
+
assertProof?.();
|
|
1201
|
+
assertDeadline();
|
|
1202
|
+
})) {
|
|
895
1203
|
permission(access, false, actorId);
|
|
896
1204
|
return { stepId, requestHash, maxCandidateBytes: 16_000 };
|
|
897
1205
|
}
|
|
898
1206
|
}
|
|
899
1207
|
throw new SupportDeskConflictError();
|
|
900
1208
|
},
|
|
901
|
-
async completeEngineeringModel(access, input, guard) {
|
|
1209
|
+
async completeEngineeringModel(access, input, guard, observationGuard) {
|
|
902
1210
|
permission(access);
|
|
903
1211
|
const actorId = access.principalId;
|
|
904
1212
|
if (typeof guard !== "function")
|
|
@@ -926,8 +1234,25 @@ export function createEngineeringWorkerServices(deps, load) {
|
|
|
926
1234
|
const runNow = effectiveTime(deps);
|
|
927
1235
|
for (let attempt = 0; attempt < deps.maxConflictAttempts; attempt += 1) {
|
|
928
1236
|
permission(access, false, actorId);
|
|
929
|
-
|
|
930
|
-
|
|
1237
|
+
let ticketRead;
|
|
1238
|
+
if (observationGuard !== undefined) {
|
|
1239
|
+
// Start both bounded reads in one wave. Exact receipt recovery never
|
|
1240
|
+
// awaits or interprets the speculative Ticket, including its failure.
|
|
1241
|
+
ticketRead = prepareTicketRead(command.ticketId);
|
|
1242
|
+
const accepted = await workAt(command);
|
|
1243
|
+
permission(access, false, actorId);
|
|
1244
|
+
if (accepted.value.lastCompletion?.fingerprint === completion)
|
|
1245
|
+
return outcome(accepted.value);
|
|
1246
|
+
}
|
|
1247
|
+
const observed = observationGuard === undefined
|
|
1248
|
+
? undefined
|
|
1249
|
+
: await observedModelLoad(access, command, actorId, completion, ticketRead);
|
|
1250
|
+
if (observed !== undefined && "accepted" in observed)
|
|
1251
|
+
return observed.accepted;
|
|
1252
|
+
const loaded = observed === undefined
|
|
1253
|
+
? await load(command.ticketId)
|
|
1254
|
+
: observed.loaded;
|
|
1255
|
+
const stored = observed === undefined ? await workAt(command) : observed.stored;
|
|
931
1256
|
const work = stored.value;
|
|
932
1257
|
if (work.lastCompletion?.fingerprint === completion) {
|
|
933
1258
|
permission(access, false, actorId);
|
|
@@ -971,7 +1296,11 @@ export function createEngineeringWorkerServices(deps, load) {
|
|
|
971
1296
|
}),
|
|
972
1297
|
};
|
|
973
1298
|
checkEngineeringRecordCapacity(next);
|
|
974
|
-
|
|
1299
|
+
let assertProof;
|
|
1300
|
+
if (observationGuard === undefined)
|
|
1301
|
+
await guard();
|
|
1302
|
+
else
|
|
1303
|
+
assertProof = await modelGuard(observationGuard, loaded, stored, actorId);
|
|
975
1304
|
await freshPolicy(access, {
|
|
976
1305
|
policyGeneration: model.binding.inputManifest.policyGeneration,
|
|
977
1306
|
policyHash: model.binding.inputManifest.policyHash,
|
|
@@ -986,6 +1315,7 @@ export function createEngineeringWorkerServices(deps, load) {
|
|
|
986
1315
|
if (await save(loaded, stored, next, nextSource, [], () => {
|
|
987
1316
|
permission(access, false, actorId);
|
|
988
1317
|
live(loaded, work, command, runNow());
|
|
1318
|
+
assertProof?.();
|
|
989
1319
|
if (timestamp(model.binding.deadline, "model.deadline") <= runNow())
|
|
990
1320
|
throw new SupportDeskConflictError("Engineering prepared model deadline expired");
|
|
991
1321
|
})) {
|
|
@@ -1264,7 +1594,7 @@ export function createEngineeringWorkerServices(deps, load) {
|
|
|
1264
1594
|
}
|
|
1265
1595
|
throw new SupportDeskConflictError();
|
|
1266
1596
|
},
|
|
1267
|
-
async prepareEngineeringProposal(access, input, guard) {
|
|
1597
|
+
async prepareEngineeringProposal(access, input, guard, observationGuard) {
|
|
1268
1598
|
permission(access);
|
|
1269
1599
|
const actorId = access.principalId;
|
|
1270
1600
|
if (typeof guard !== "function")
|
|
@@ -1438,7 +1768,9 @@ export function createEngineeringWorkerServices(deps, load) {
|
|
|
1438
1768
|
requestHash: checksBinding.requestHash,
|
|
1439
1769
|
preparedClaimGeneration: command.claimGeneration,
|
|
1440
1770
|
};
|
|
1441
|
-
|
|
1771
|
+
let deadline = observationGuard === undefined
|
|
1772
|
+
? await verifyProposal(access, loaded, work, proposal, runNow)
|
|
1773
|
+
: Number.POSITIVE_INFINITY;
|
|
1442
1774
|
const { approval: _approval, candidate: _candidate, proposalNeedsRebuild: _needsRebuild, ...unreviewed } = work;
|
|
1443
1775
|
const next = identical
|
|
1444
1776
|
? work
|
|
@@ -1469,9 +1801,26 @@ export function createEngineeringWorkerServices(deps, load) {
|
|
|
1469
1801
|
};
|
|
1470
1802
|
reserveReachable(acknowledged);
|
|
1471
1803
|
deps.admission(loaded.ticket.value.ticket, loaded.rows, 2);
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1804
|
+
let assertProof;
|
|
1805
|
+
if (observationGuard === undefined) {
|
|
1806
|
+
await guard();
|
|
1807
|
+
await currentRepository(proposal.request);
|
|
1808
|
+
await freshPolicy(access, proposal);
|
|
1809
|
+
}
|
|
1810
|
+
else {
|
|
1811
|
+
assertProof = await workerGuard(observationGuard, access, loaded, stored, async () => {
|
|
1812
|
+
deadline = Math.min(deadline, await verifyProposal(access, loaded, work, proposal, runNow));
|
|
1813
|
+
await currentRepository(proposal.request);
|
|
1814
|
+
await freshPolicy(access, proposal);
|
|
1815
|
+
return () => {
|
|
1816
|
+
permission(access, false, actorId);
|
|
1817
|
+
live(loaded, work, command, runNow());
|
|
1818
|
+
if (runNow() >= deadline)
|
|
1819
|
+
throw new SupportDeskConflictError("Engineering exact proposal checks expired");
|
|
1820
|
+
return true;
|
|
1821
|
+
};
|
|
1822
|
+
}, actorId, "proposal");
|
|
1823
|
+
}
|
|
1475
1824
|
permission(access, false, actorId);
|
|
1476
1825
|
live(loaded, work, command, runNow());
|
|
1477
1826
|
if (runNow() >= deadline)
|
|
@@ -1480,7 +1829,15 @@ export function createEngineeringWorkerServices(deps, load) {
|
|
|
1480
1829
|
deps.audit(causal, loaded.ticket.value.ticket, actorId, clock.now(), "support.engineering.proposal_prepared"),
|
|
1481
1830
|
receipt(causal, hash, result),
|
|
1482
1831
|
];
|
|
1483
|
-
if (await save(loaded, stored, acknowledged, loaded.source.value, actions
|
|
1832
|
+
if (await save(loaded, stored, acknowledged, loaded.source.value, actions, observationGuard === undefined
|
|
1833
|
+
? undefined
|
|
1834
|
+
: () => {
|
|
1835
|
+
permission(access, false, actorId);
|
|
1836
|
+
live(loaded, work, command, runNow());
|
|
1837
|
+
if (runNow() >= deadline)
|
|
1838
|
+
throw new SupportDeskConflictError("Engineering exact proposal checks expired");
|
|
1839
|
+
assertProof?.();
|
|
1840
|
+
})) {
|
|
1484
1841
|
permission(access, false, actorId);
|
|
1485
1842
|
return result;
|
|
1486
1843
|
}
|
|
@@ -1658,7 +2015,7 @@ export function createEngineeringWorkerServices(deps, load) {
|
|
|
1658
2015
|
}
|
|
1659
2016
|
throw new SupportDeskConflictError();
|
|
1660
2017
|
},
|
|
1661
|
-
async beginEngineeringDispatch(access, input, guard) {
|
|
2018
|
+
async beginEngineeringDispatch(access, input, guard, observationGuard) {
|
|
1662
2019
|
permission(access);
|
|
1663
2020
|
const actorId = access.principalId;
|
|
1664
2021
|
if (typeof guard !== "function")
|
|
@@ -1689,8 +2046,15 @@ export function createEngineeringWorkerServices(deps, load) {
|
|
|
1689
2046
|
const runNow = effectiveTime(deps);
|
|
1690
2047
|
for (let attempt = 0; attempt < deps.maxConflictAttempts; attempt += 1) {
|
|
1691
2048
|
permission(access, false, actorId);
|
|
1692
|
-
const
|
|
1693
|
-
|
|
2049
|
+
const observed = observationGuard === undefined
|
|
2050
|
+
? undefined
|
|
2051
|
+
: await observedModelLoad(access, command, actorId);
|
|
2052
|
+
if (observed !== undefined && "accepted" in observed)
|
|
2053
|
+
throw new SupportDeskConflictError();
|
|
2054
|
+
const loaded = observed === undefined
|
|
2055
|
+
? await load(command.ticketId)
|
|
2056
|
+
: observed.loaded;
|
|
2057
|
+
const stored = observed === undefined ? await workAt(command) : observed.stored;
|
|
1694
2058
|
const work = stored.value;
|
|
1695
2059
|
live(loaded, work, command, runNow());
|
|
1696
2060
|
const proposal = work.proposal;
|
|
@@ -1722,10 +2086,29 @@ export function createEngineeringWorkerServices(deps, load) {
|
|
|
1722
2086
|
permission(access, false, actorId);
|
|
1723
2087
|
return null;
|
|
1724
2088
|
}
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
2089
|
+
let deadline = observationGuard === undefined
|
|
2090
|
+
? await verifyProposal(access, loaded, work, proposal, runNow)
|
|
2091
|
+
: Number.POSITIVE_INFINITY;
|
|
2092
|
+
let assertProof;
|
|
2093
|
+
if (observationGuard === undefined) {
|
|
2094
|
+
await guard();
|
|
2095
|
+
await currentRepository(proposal.request);
|
|
2096
|
+
await freshPolicy(access, proposal);
|
|
2097
|
+
}
|
|
2098
|
+
else {
|
|
2099
|
+
assertProof = await workerGuard(observationGuard, access, loaded, stored, async () => {
|
|
2100
|
+
deadline = Math.min(deadline, await verifyProposal(access, loaded, work, proposal, runNow));
|
|
2101
|
+
await currentRepository(proposal.request);
|
|
2102
|
+
await freshPolicy(access, proposal);
|
|
2103
|
+
return () => {
|
|
2104
|
+
permission(access, false, actorId);
|
|
2105
|
+
live(loaded, work, command, runNow());
|
|
2106
|
+
if (runNow() >= deadline)
|
|
2107
|
+
throw new SupportDeskConflictError("Engineering dispatch verification expired");
|
|
2108
|
+
return true;
|
|
2109
|
+
};
|
|
2110
|
+
}, actorId, "before_dispatch");
|
|
2111
|
+
}
|
|
1729
2112
|
permission(access, false, actorId);
|
|
1730
2113
|
live(loaded, work, command, runNow());
|
|
1731
2114
|
if (runNow() >= deadline)
|
|
@@ -1801,7 +2184,48 @@ export function createEngineeringWorkerServices(deps, load) {
|
|
|
1801
2184
|
: [
|
|
1802
2185
|
deps.audit(causal, loaded.ticket.value.ticket, actorId, clock.now(), "support.engineering.dispatch_started"),
|
|
1803
2186
|
receipt(causal, hash, outcome(next)),
|
|
1804
|
-
]
|
|
2187
|
+
], observationGuard === undefined
|
|
2188
|
+
? undefined
|
|
2189
|
+
: () => {
|
|
2190
|
+
permission(access, false, actorId);
|
|
2191
|
+
live(loaded, work, command, runNow());
|
|
2192
|
+
if (runNow() >= deadline)
|
|
2193
|
+
throw new SupportDeskConflictError("Engineering dispatch verification expired");
|
|
2194
|
+
assertProof?.();
|
|
2195
|
+
})) {
|
|
2196
|
+
if (observationGuard !== undefined) {
|
|
2197
|
+
// The start transaction changed opaque versions: obtain a new scope from real storage.
|
|
2198
|
+
const latest = await observedDispatchLoad(access, command, actorId);
|
|
2199
|
+
live(latest.loaded, latest.stored.value, command, runNow());
|
|
2200
|
+
if (latest.stored.value.dispatch?.dispatchId !== command.dispatchId ||
|
|
2201
|
+
latest.stored.value.status !== "dispatch_started")
|
|
2202
|
+
throw new SupportDeskConflictError("Engineering dispatch changed before physical call authorization");
|
|
2203
|
+
const assertPostProof = await workerGuard(observationGuard, access, latest.loaded, latest.stored, async () => {
|
|
2204
|
+
deadline = Math.min(deadline, await verifyProposal(access, latest.loaded, latest.stored.value, proposal, runNow));
|
|
2205
|
+
await currentRepository(proposal.request);
|
|
2206
|
+
await freshPolicy(access, proposal);
|
|
2207
|
+
return () => {
|
|
2208
|
+
permission(access, false, actorId);
|
|
2209
|
+
live(latest.loaded, latest.stored.value, command, runNow());
|
|
2210
|
+
if (runNow() >= deadline)
|
|
2211
|
+
throw new SupportDeskConflictError("Engineering dispatch verification expired");
|
|
2212
|
+
return true;
|
|
2213
|
+
};
|
|
2214
|
+
}, actorId, "after_dispatch");
|
|
2215
|
+
permission(access, false, actorId);
|
|
2216
|
+
live(latest.loaded, latest.stored.value, command, runNow());
|
|
2217
|
+
if (runNow() >= deadline)
|
|
2218
|
+
throw new SupportDeskConflictError("Engineering dispatch verification expired");
|
|
2219
|
+
assertPostProof();
|
|
2220
|
+
return {
|
|
2221
|
+
ticketId: work.ticketId,
|
|
2222
|
+
sourceGeneration: work.sourceGeneration,
|
|
2223
|
+
dispatchId: dispatch.dispatchId,
|
|
2224
|
+
exportMarker: dispatch.exportMarker,
|
|
2225
|
+
requestHash: proposal.requestHash,
|
|
2226
|
+
request: snapshot(proposal.request),
|
|
2227
|
+
};
|
|
2228
|
+
}
|
|
1805
2229
|
await guard();
|
|
1806
2230
|
await currentRepository(proposal.request);
|
|
1807
2231
|
await freshPolicy(access, proposal);
|
|
@@ -1828,5 +2252,83 @@ export function createEngineeringWorkerServices(deps, load) {
|
|
|
1828
2252
|
throw new SupportDeskConflictError();
|
|
1829
2253
|
},
|
|
1830
2254
|
};
|
|
2255
|
+
return {
|
|
2256
|
+
...services,
|
|
2257
|
+
// Public legacy wrappers cannot receive the private preloaded-attempt parameter.
|
|
2258
|
+
prepareEngineeringModel(access, command) {
|
|
2259
|
+
return services.prepareEngineeringModel(access, command);
|
|
2260
|
+
},
|
|
2261
|
+
completeEngineeringModel(access, command, guard) {
|
|
2262
|
+
return services.completeEngineeringModel(access, command, guard);
|
|
2263
|
+
},
|
|
2264
|
+
prepareEngineeringProposal(access, command, guard) {
|
|
2265
|
+
return services.prepareEngineeringProposal(access, command, guard);
|
|
2266
|
+
},
|
|
2267
|
+
beginEngineeringDispatch(access, command, guard) {
|
|
2268
|
+
return services.beginEngineeringDispatch(access, command, guard);
|
|
2269
|
+
},
|
|
2270
|
+
prepareEngineeringProposalObserved(access, command, guard) {
|
|
2271
|
+
if (typeof guard !== "function")
|
|
2272
|
+
throw new TypeError("Engineering proposal observation requires a guard");
|
|
2273
|
+
return services.prepareEngineeringProposal(access, command, () => { }, guard);
|
|
2274
|
+
},
|
|
2275
|
+
beginEngineeringDispatchObserved(access, command, guard) {
|
|
2276
|
+
if (typeof guard !== "function")
|
|
2277
|
+
throw new TypeError("Engineering dispatch observation requires a guard");
|
|
2278
|
+
return services.beginEngineeringDispatch(access, command, () => { }, guard);
|
|
2279
|
+
},
|
|
2280
|
+
async prepareEngineeringModelPlanned(access, input, planner) {
|
|
2281
|
+
permission(access);
|
|
2282
|
+
const actorId = access.principalId;
|
|
2283
|
+
if (typeof planner !== "function")
|
|
2284
|
+
throw new TypeError("Engineering model planning requires a callback");
|
|
2285
|
+
const command = snapshot(fields(input, "engineering model planning fence", [
|
|
2286
|
+
"ticketId",
|
|
2287
|
+
"sourceGeneration",
|
|
2288
|
+
"workerId",
|
|
2289
|
+
"claimId",
|
|
2290
|
+
"claimGeneration",
|
|
2291
|
+
]));
|
|
2292
|
+
fence(command);
|
|
2293
|
+
if (!options.enabled)
|
|
2294
|
+
throw new SupportDeskConflictError("Engineering processing disabled");
|
|
2295
|
+
const runNow = effectiveTime(deps);
|
|
2296
|
+
const current = await observedModelLoad(access, command, actorId);
|
|
2297
|
+
if ("accepted" in current)
|
|
2298
|
+
throw new SupportDeskConflictError();
|
|
2299
|
+
modelPreparationCurrent(current.loaded, current.stored.value, command, runNow());
|
|
2300
|
+
const policy = await freshPolicy(access);
|
|
2301
|
+
let open = true;
|
|
2302
|
+
let binding;
|
|
2303
|
+
try {
|
|
2304
|
+
binding = snapshot(await planner(Object.freeze({
|
|
2305
|
+
read(reader) {
|
|
2306
|
+
modelReadPermission(reader, actorId);
|
|
2307
|
+
if (!open)
|
|
2308
|
+
throw new SupportDeskConflictError("Engineering model planning expired");
|
|
2309
|
+
return {
|
|
2310
|
+
source: snapshot(current.loaded.source.value),
|
|
2311
|
+
claim: claimView(current.loaded, current.stored.value, policy, false),
|
|
2312
|
+
};
|
|
2313
|
+
},
|
|
2314
|
+
})));
|
|
2315
|
+
}
|
|
2316
|
+
finally {
|
|
2317
|
+
open = false;
|
|
2318
|
+
}
|
|
2319
|
+
permission(access, false, actorId);
|
|
2320
|
+
return services.prepareEngineeringModel(access, { ...command, binding }, () => { }, { ...current, policy, now: runNow });
|
|
2321
|
+
},
|
|
2322
|
+
prepareEngineeringModelObserved(access, command, guard) {
|
|
2323
|
+
if (typeof guard !== "function")
|
|
2324
|
+
throw new TypeError("Engineering model observation requires a guard");
|
|
2325
|
+
return services.prepareEngineeringModel(access, command, guard);
|
|
2326
|
+
},
|
|
2327
|
+
completeEngineeringModelObserved(access, command, guard) {
|
|
2328
|
+
if (typeof guard !== "function")
|
|
2329
|
+
throw new TypeError("Engineering model observation requires a guard");
|
|
2330
|
+
return services.completeEngineeringModel(access, command, () => { }, guard);
|
|
2331
|
+
},
|
|
2332
|
+
};
|
|
1831
2333
|
}
|
|
1832
2334
|
//# sourceMappingURL=engineering-work.js.map
|