@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,1272 @@
1
+ import { dataArray, dataObject, encodedBytes, fields, fingerprint, integer, text, timestamp, } from "./assistant-turn-records.js";
2
+ import { safeLearningSnapshot, validateResolutionProduct, } from "./resolution-learning-records.js";
3
+ import { SupportDeskLimitError } from "./index.js";
4
+ export const engineeringMaximumRecordBytes = 60_000;
5
+ export const engineeringMaximumCandidateBytes = 16_000;
6
+ export const engineeringSourceKey = (ticketId) => ({
7
+ partition: ticketId,
8
+ id: "engineering-source",
9
+ });
10
+ export const engineeringWorkKey = (ticketId, generation) => ({ partition: ticketId, id: `engineering-work:${generation}` });
11
+ export const engineeringCommandKey = (ticketId, commandId) => ({
12
+ partition: ticketId,
13
+ id: `engineering-command:${commandId}`,
14
+ });
15
+ export const safeEngineeringSnapshot = safeLearningSnapshot;
16
+ export const engineeringEncodedBytes = encodedBytes;
17
+ export function advanceEngineeringFollowupRevocationEpoch(source) {
18
+ const next = (source.followupRevocationEpoch ?? 0) + 1;
19
+ integer(next, "followupRevocationEpoch", 0);
20
+ return next;
21
+ }
22
+ function prose(value, name, maximum) {
23
+ if (typeof value !== "string" ||
24
+ value.trim().length === 0 ||
25
+ value.length > maximum ||
26
+ /[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/u.test(value))
27
+ throw new TypeError(`${name} must be bounded text without unsafe controls`);
28
+ return value;
29
+ }
30
+ function strings(value, name, maximum, width = 200, multiline = false) {
31
+ const seen = new Set();
32
+ for (const item of dataArray(value, name, maximum)) {
33
+ const entry = multiline
34
+ ? prose(item, name, width)
35
+ : text(item, name, width);
36
+ if (!multiline && seen.has(entry))
37
+ throw new TypeError(`${name} contains duplicates`);
38
+ seen.add(entry);
39
+ }
40
+ }
41
+ function literals(value, name, choices) {
42
+ if (typeof value !== "string" || !choices.includes(value))
43
+ throw new TypeError(`${name} is invalid`);
44
+ }
45
+ function optionalText(value, keys) {
46
+ for (const key of keys)
47
+ if (value[key] !== undefined)
48
+ text(value[key], key);
49
+ }
50
+ function optionalTime(value, keys) {
51
+ for (const key of keys)
52
+ if (value[key] !== undefined)
53
+ timestamp(value[key], key);
54
+ }
55
+ export function validateEngineeringProduct(value) {
56
+ validateResolutionProduct(value);
57
+ }
58
+ export function validateEngineeringMismatch(value) {
59
+ const mismatch = dataObject(value, "engineering mismatch");
60
+ prose(mismatch.expectedBehavior, "expectedBehavior", 2_000);
61
+ prose(mismatch.actualBehavior, "actualBehavior", 2_000);
62
+ strings(mismatch.reproductionSteps, "reproductionSteps", 16, 1_000, true);
63
+ strings(mismatch.visibleMessageIds, "visibleMessageIds", 16);
64
+ if (mismatch.reproductionSteps.length === 0 ||
65
+ mismatch.visibleMessageIds.length === 0)
66
+ throw new TypeError("Mismatch needs reproduction and visible report references");
67
+ }
68
+ export function validateEngineeringEvidenceInput(value) {
69
+ const raw = dataObject(value, "engineering evidence input");
70
+ const extra = raw.kind === "staff_verified_failure"
71
+ ? "verificationReference"
72
+ : raw.kind === "failed_test"
73
+ ? "receipt"
74
+ : null;
75
+ if (extra === null)
76
+ throw new TypeError("Only verified failure evidence is accepted");
77
+ fields(raw, "engineering evidence input", [
78
+ "kind",
79
+ "expectedBehavior",
80
+ "actualBehavior",
81
+ "reproductionSteps",
82
+ "visibleMessageIds",
83
+ extra,
84
+ ]);
85
+ validateEngineeringMismatch(raw);
86
+ text(raw[extra], extra);
87
+ if (encodedBytes(value) > 16_000)
88
+ throw new TypeError("Engineering evidence exceeds serialized limit");
89
+ }
90
+ export function validateEngineeringEvidence(value) {
91
+ const raw = dataObject(value, "engineering evidence");
92
+ const extra = raw.kind === "staff_verified_failure"
93
+ ? "verificationReference"
94
+ : raw.kind === "failed_test"
95
+ ? "test"
96
+ : null;
97
+ if (extra === null)
98
+ throw new TypeError("Successful or unverified evidence cannot establish a bug");
99
+ fields(raw, "engineering evidence", [
100
+ "kind",
101
+ "actorId",
102
+ "expectedBehavior",
103
+ "actualBehavior",
104
+ "reproductionSteps",
105
+ "visibleMessageIds",
106
+ extra,
107
+ ]);
108
+ text(raw.actorId, "actorId");
109
+ validateEngineeringMismatch(raw);
110
+ if (extra === "verificationReference")
111
+ text(raw.verificationReference, "verificationReference");
112
+ else {
113
+ const test = fields(raw.test, "failed test", [
114
+ "result",
115
+ "executionId",
116
+ "runnerId",
117
+ "testId",
118
+ "productBinding",
119
+ "executedAt",
120
+ "receiptId",
121
+ "verifiedAt",
122
+ "validUntil",
123
+ ]);
124
+ literals(test.result, "test.result", ["failed"]);
125
+ for (const key of [
126
+ "executionId",
127
+ "runnerId",
128
+ "testId",
129
+ "productBinding",
130
+ "receiptId",
131
+ ])
132
+ text(test[key], key);
133
+ timestamp(test.executedAt, "executedAt");
134
+ if (timestamp(test.validUntil, "validUntil") <
135
+ timestamp(test.verifiedAt, "verifiedAt"))
136
+ throw new TypeError("Test validity is inverted");
137
+ }
138
+ if (encodedBytes(value) > 16_000)
139
+ throw new TypeError("Engineering evidence exceeds serialized limit");
140
+ }
141
+ export function validateEngineeringRepository(value) {
142
+ const repo = fields(value, "repository descriptor", [
143
+ "repositoryId",
144
+ "owner",
145
+ "name",
146
+ "visibility",
147
+ "responsibilities",
148
+ "product",
149
+ "component",
150
+ "version",
151
+ "allowedLabels",
152
+ "allowedTemplates",
153
+ "publicationMode",
154
+ "enabled",
155
+ ]);
156
+ for (const key of ["repositoryId", "owner", "name"])
157
+ text(repo[key], key);
158
+ literals(repo.visibility, "visibility", ["public", "private"]);
159
+ literals(repo.publicationMode, "publicationMode", ["approval_required"]);
160
+ text(repo.responsibilities, "responsibilities", 2_000);
161
+ for (const key of ["product", "component", "version"])
162
+ text(repo[key], key, 1_000);
163
+ strings(repo.allowedLabels, "allowedLabels", 32, 100);
164
+ strings(repo.allowedTemplates, "allowedTemplates", 8);
165
+ if (typeof repo.enabled !== "boolean")
166
+ throw new TypeError("Repository enabled must be boolean");
167
+ }
168
+ export function validateEngineeringPolicy(value) {
169
+ const policy = fields(value, "engineering policy", [
170
+ "enabled",
171
+ "repositories",
172
+ ]);
173
+ if (typeof policy.enabled !== "boolean")
174
+ throw new TypeError("Policy enabled must be boolean");
175
+ const seen = new Set();
176
+ for (const item of dataArray(policy.repositories, "repositories", 20)) {
177
+ validateEngineeringRepository(item);
178
+ if (seen.has(item.repositoryId))
179
+ throw new TypeError("Duplicate repository identity");
180
+ seen.add(item.repositoryId);
181
+ }
182
+ }
183
+ export function validateEngineeringPolicySnapshot(value) {
184
+ const policy = fields(value, "policy snapshot", [
185
+ "generation",
186
+ "policyHash",
187
+ "policy",
188
+ ]);
189
+ integer(policy.generation, "policyGeneration", 0);
190
+ text(policy.policyHash, "policyHash");
191
+ validateEngineeringPolicy(policy.policy);
192
+ }
193
+ export function validateEngineeringRepositoryObservation(value) {
194
+ const repo = fields(value, "repository observation", [
195
+ "repositoryId",
196
+ "owner",
197
+ "name",
198
+ "visibility",
199
+ "installationBinding",
200
+ "metadataVersion",
201
+ ]);
202
+ for (const key of [
203
+ "repositoryId",
204
+ "owner",
205
+ "name",
206
+ "installationBinding",
207
+ "metadataVersion",
208
+ ])
209
+ text(repo[key], key);
210
+ literals(repo.visibility, "visibility", ["public", "private"]);
211
+ }
212
+ export function validateEngineeringRoutingDecision(value) {
213
+ const route = dataObject(value, "routing decision");
214
+ literals(route.outcome, "routing.outcome", [
215
+ "selected",
216
+ "ambiguous",
217
+ "out_of_scope",
218
+ ]);
219
+ fields(route, "routing decision", [
220
+ "outcome",
221
+ "alternativeRepositoryIds",
222
+ "rationale",
223
+ ...(route.outcome === "selected" ? ["repositoryId"] : []),
224
+ ]);
225
+ strings(route.alternativeRepositoryIds, "alternativeRepositoryIds", 4);
226
+ prose(route.rationale, "routing.rationale", 2_000);
227
+ if (route.outcome === "selected")
228
+ text(route.repositoryId, "repositoryId");
229
+ }
230
+ export function validateEngineeringCandidate(value) {
231
+ const candidate = fields(value, "engineering candidate", [
232
+ "title",
233
+ "summary",
234
+ "expectedBehavior",
235
+ "actualBehavior",
236
+ "reproductionSteps",
237
+ "applicability",
238
+ "security",
239
+ "routing",
240
+ ]);
241
+ prose(candidate.title, "title", 200);
242
+ prose(candidate.summary, "summary", 4_000);
243
+ prose(candidate.expectedBehavior, "expectedBehavior", 2_000);
244
+ prose(candidate.actualBehavior, "actualBehavior", 2_000);
245
+ strings(candidate.reproductionSteps, "reproductionSteps", 16, 1_000, true);
246
+ const product = fields(candidate.applicability, "candidate applicability", [
247
+ "productId",
248
+ "version",
249
+ "buildId",
250
+ ]);
251
+ for (const key of ["productId", "version", "buildId"])
252
+ text(product[key], key);
253
+ literals(candidate.security, "security", [
254
+ "ordinary",
255
+ "restricted",
256
+ "uncertain",
257
+ ]);
258
+ validateEngineeringRoutingDecision(candidate.routing);
259
+ if (encodedBytes(value) > engineeringMaximumCandidateBytes)
260
+ throw new TypeError("Engineering candidate exceeds serialized limit");
261
+ }
262
+ export function validateEngineeringModelBinding(value) {
263
+ const binding = fields(value, "engineering model binding", [
264
+ "inputManifest",
265
+ "inputFingerprint",
266
+ "inputManifestHash",
267
+ "configVersion",
268
+ "promptVersion",
269
+ "modelVersion",
270
+ "parserVersion",
271
+ "checkPolicyVersion",
272
+ "budgetsHash",
273
+ "usageReservationId",
274
+ "deadline",
275
+ ]);
276
+ for (const key of Object.keys(binding).filter((key) => key !== "inputManifest" && key !== "deadline"))
277
+ text(binding[key], key);
278
+ timestamp(binding.deadline, "deadline");
279
+ const manifest = fields(binding.inputManifest, "engineering manifest", [
280
+ "messages",
281
+ "evidenceFingerprint",
282
+ "product",
283
+ "policyGeneration",
284
+ "policyHash",
285
+ "repositoryIds",
286
+ ]);
287
+ text(manifest.evidenceFingerprint, "evidenceFingerprint");
288
+ text(manifest.policyHash, "policyHash");
289
+ integer(manifest.policyGeneration, "policyGeneration", 0);
290
+ strings(manifest.repositoryIds, "repositoryIds", 20);
291
+ const product = fields(manifest.product, "manifest product", [
292
+ "productId",
293
+ "version",
294
+ "buildId",
295
+ "binding",
296
+ "contextVersion",
297
+ ]);
298
+ for (const key of Object.keys(product))
299
+ text(product[key], key);
300
+ const ids = new Set();
301
+ const ordinals = new Set();
302
+ for (const item of dataArray(manifest.messages, "manifest messages", 100)) {
303
+ const message = fields(item, "manifest message", ["messageId", "ordinal"]);
304
+ const id = text(message.messageId, "messageId");
305
+ const ordinal = integer(message.ordinal, "ordinal", 1);
306
+ if (ids.has(id) || ordinals.has(ordinal))
307
+ throw new TypeError("Duplicate manifest message");
308
+ ids.add(id);
309
+ ordinals.add(ordinal);
310
+ }
311
+ }
312
+ export function validateEngineeringRoutingBinding(value) {
313
+ const binding = fields(value, "routing binding", [
314
+ "sourceBindingHash",
315
+ "evidenceFingerprint",
316
+ "candidateHash",
317
+ "inputFingerprint",
318
+ "routingHash",
319
+ "policyGeneration",
320
+ "policyHash",
321
+ "modelVersion",
322
+ "promptVersion",
323
+ "parserVersion",
324
+ "repositoryId",
325
+ ]);
326
+ integer(binding.policyGeneration, "policyGeneration", 0);
327
+ for (const key of Object.keys(binding).filter((key) => key !== "policyGeneration"))
328
+ text(binding[key], key);
329
+ }
330
+ export function validateEngineeringExportChecksBinding(value) {
331
+ const binding = fields(value, "export checks binding", [
332
+ "sourceBindingHash",
333
+ "evidenceFingerprint",
334
+ "candidateHash",
335
+ "routingBindingHash",
336
+ "requestHash",
337
+ "policyGeneration",
338
+ "policyHash",
339
+ "repositoryMetadataHash",
340
+ ]);
341
+ integer(binding.policyGeneration, "policyGeneration", 0);
342
+ for (const key of Object.keys(binding).filter((key) => key !== "policyGeneration"))
343
+ text(binding[key], key);
344
+ }
345
+ export function validateEngineeringExportRequest(value) {
346
+ const request = dataObject(value, "engineering export request");
347
+ literals(request.operation, "operation", [
348
+ "create_issue",
349
+ "link_existing_issue",
350
+ ]);
351
+ fields(request, "engineering export request", [
352
+ "operation",
353
+ "repository",
354
+ "bundle",
355
+ ...(request.operation === "link_existing_issue"
356
+ ? ["issueId", "issueNumber"]
357
+ : []),
358
+ ]);
359
+ validateEngineeringRepositoryObservation(request.repository);
360
+ const bundle = fields(request.bundle, "sanitized bundle", ["title", "body", "labels", "evidenceReferences", "exportMarker"], ["templateId"]);
361
+ prose(bundle.title, "bundle.title", 200);
362
+ prose(bundle.body, "bundle.body", 12_000);
363
+ strings(bundle.labels, "labels", 32, 100);
364
+ strings(bundle.evidenceReferences, "evidenceReferences", 16);
365
+ text(bundle.exportMarker, "exportMarker");
366
+ if (bundle.templateId !== undefined)
367
+ text(bundle.templateId, "templateId");
368
+ if (request.operation === "link_existing_issue") {
369
+ text(request.issueId, "issueId");
370
+ integer(request.issueNumber, "issueNumber", 1);
371
+ }
372
+ if (encodedBytes(value) > 24_000)
373
+ throw new TypeError("Exact engineering request exceeds serialized limit");
374
+ }
375
+ export function validateEngineeringProcessing(value) {
376
+ const processing = fields(value, "engineering processing", [], [
377
+ "provider",
378
+ "model",
379
+ "requestId",
380
+ "failureCategory",
381
+ "inputTokens",
382
+ "outputTokens",
383
+ "costMicros",
384
+ "currency",
385
+ ]);
386
+ for (const [key, item] of Object.entries(processing))
387
+ if (["inputTokens", "outputTokens", "costMicros"].includes(key))
388
+ integer(item, key, 0);
389
+ else
390
+ text(item, key);
391
+ }
392
+ export function validateEngineeringAcknowledgement(value) {
393
+ const ack = fields(value, "issue acknowledgement", ["repositoryId", "issueId", "issueNumber"], ["url"]);
394
+ text(ack.repositoryId, "repositoryId");
395
+ text(ack.issueId, "issueId");
396
+ integer(ack.issueNumber, "issueNumber", 1);
397
+ if (ack.url !== undefined)
398
+ text(ack.url, "url");
399
+ }
400
+ export function validateEngineeringStepResult(value) {
401
+ const result = fields(value, "engineering step result", ["ticketId", "sourceGeneration", "workGeneration", "status"], [
402
+ "stepId",
403
+ "requestHash",
404
+ "candidateHash",
405
+ "proposalHash",
406
+ "dispatchId",
407
+ "acknowledgement",
408
+ ]);
409
+ text(result.ticketId, "ticketId");
410
+ integer(result.sourceGeneration, "sourceGeneration", 1);
411
+ integer(result.workGeneration, "workGeneration", 1);
412
+ literals(result.status, "status", [
413
+ "pending",
414
+ "claimed",
415
+ "retry",
416
+ "candidate",
417
+ "prepared",
418
+ "approved",
419
+ "dispatch_started",
420
+ "accepted",
421
+ "confirmed_rejected",
422
+ "unknown",
423
+ "held",
424
+ "restricted",
425
+ "superseded",
426
+ ]);
427
+ optionalText(result, [
428
+ "stepId",
429
+ "requestHash",
430
+ "candidateHash",
431
+ "proposalHash",
432
+ "dispatchId",
433
+ ]);
434
+ if ((result.stepId === undefined) !== (result.requestHash === undefined))
435
+ throw new TypeError("Engineering step result must retain its complete step fence");
436
+ if (result.acknowledgement !== undefined) {
437
+ fields(result.acknowledgement, "opaque engineering acknowledgement", [
438
+ "repositoryId",
439
+ "issueId",
440
+ "issueNumber",
441
+ ]);
442
+ validateEngineeringAcknowledgement(result.acknowledgement);
443
+ }
444
+ }
445
+ export function resolveEngineeringOptions(input) {
446
+ const raw = input === undefined
447
+ ? {}
448
+ : fields(input, "engineering options", [], [
449
+ "enabled",
450
+ "productContext",
451
+ "testReceipts",
452
+ "routing",
453
+ "checks",
454
+ "repositoryMetadata",
455
+ "dispatchResults",
456
+ "trustTimeoutMilliseconds",
457
+ "maxGenerations",
458
+ "maxTargets",
459
+ "maxProposals",
460
+ "maxModelAttempts",
461
+ "maxLeaseMilliseconds",
462
+ "maxWorkMilliseconds",
463
+ ]);
464
+ if (raw.enabled !== undefined && typeof raw.enabled !== "boolean")
465
+ throw new TypeError("engineering.enabled must be boolean");
466
+ for (const [name, method] of [
467
+ ["productContext", "resolve"],
468
+ ["testReceipts", "verify"],
469
+ ["routing", "verify"],
470
+ ["checks", "verify"],
471
+ ["repositoryMetadata", "resolve"],
472
+ ["dispatchResults", "verify"],
473
+ ])
474
+ if (raw[name] !== undefined &&
475
+ (raw[name] === null ||
476
+ typeof raw[name][method] !== "function"))
477
+ throw new TypeError(`engineering.${name}.${method} is required`);
478
+ return {
479
+ enabled: raw.enabled === true,
480
+ ...(raw.productContext === undefined
481
+ ? {}
482
+ : {
483
+ productContext: raw.productContext,
484
+ }),
485
+ ...(raw.testReceipts === undefined
486
+ ? {}
487
+ : {
488
+ testReceipts: raw.testReceipts,
489
+ }),
490
+ ...(raw.routing === undefined
491
+ ? {}
492
+ : { routing: raw.routing }),
493
+ ...(raw.checks === undefined
494
+ ? {}
495
+ : { checks: raw.checks }),
496
+ ...(raw.repositoryMetadata === undefined
497
+ ? {}
498
+ : {
499
+ repositoryMetadata: raw.repositoryMetadata,
500
+ }),
501
+ ...(raw.dispatchResults === undefined
502
+ ? {}
503
+ : {
504
+ dispatchResults: raw.dispatchResults,
505
+ }),
506
+ trustTimeoutMilliseconds: integer(raw.trustTimeoutMilliseconds ?? 250, "trustTimeoutMilliseconds", 1, 1_000),
507
+ maxGenerations: integer(raw.maxGenerations ?? 4, "maxGenerations", 1, 8),
508
+ maxTargets: integer(raw.maxTargets ?? 2, "maxTargets", 1, 4),
509
+ maxProposals: integer(raw.maxProposals ?? 4, "maxProposals", 1, 8),
510
+ maxModelAttempts: integer(raw.maxModelAttempts ?? 2, "maxModelAttempts", 1, 4),
511
+ maxLeaseMilliseconds: integer(raw.maxLeaseMilliseconds ?? 60_000, "maxLeaseMilliseconds", 1, 300_000),
512
+ maxWorkMilliseconds: integer(raw.maxWorkMilliseconds ?? 300_000, "maxWorkMilliseconds", 1, 3_600_000),
513
+ };
514
+ }
515
+ export async function createEngineeringSource(ticket) {
516
+ text(ticket.id, "ticketId");
517
+ timestamp(ticket.createdAt, "createdAt");
518
+ timestamp(ticket.updatedAt, "updatedAt");
519
+ const source = {
520
+ kind: "engineering_source",
521
+ partition: ticket.id,
522
+ id: "engineering-source",
523
+ ticketId: ticket.id,
524
+ sourceId: await fingerprint({
525
+ system: "pegma.support-desk.engineering",
526
+ ticketId: ticket.id,
527
+ }),
528
+ sourceGeneration: 0,
529
+ followupRevocationEpoch: 0,
530
+ workGeneration: 1,
531
+ generationCount: 0,
532
+ state: "unverified",
533
+ security: "uncertain",
534
+ createdAt: ticket.createdAt,
535
+ updatedAt: ticket.updatedAt,
536
+ destinations: [],
537
+ reconciliationPending: false,
538
+ };
539
+ checkEngineeringRecordCapacity(source);
540
+ return source;
541
+ }
542
+ export function maximumEngineeringSourceBytes(source) {
543
+ const escaped = "\ud800".repeat(200);
544
+ const future = {
545
+ ...source,
546
+ sourceGeneration: Number.MAX_SAFE_INTEGER,
547
+ followupRevocationEpoch: Number.MAX_SAFE_INTEGER,
548
+ workGeneration: Number.MAX_SAFE_INTEGER,
549
+ state: "restricted",
550
+ security: "restricted",
551
+ updatedAt: "+275760-09-13T00:00:00.000Z",
552
+ heldReason: escaped,
553
+ pendingEvidenceReference: escaped,
554
+ reconciliationPending: false,
555
+ destinations: source.destinations.map((entry) => ({
556
+ ...entry,
557
+ followupRevocationEpoch: Number.MAX_SAFE_INTEGER,
558
+ dispatchId: entry.dispatchId ?? escaped,
559
+ outcome: "confirmed_rejected",
560
+ reconciliation: "confirmed",
561
+ acknowledgement: entry.acknowledgement ?? {
562
+ repositoryId: entry.repositoryId,
563
+ issueId: escaped,
564
+ issueNumber: Number.MAX_SAFE_INTEGER,
565
+ url: escaped,
566
+ },
567
+ lastReconciliation: {
568
+ fingerprint: "f".repeat(64),
569
+ proof: escaped,
570
+ outcome: "confirmed_rejected",
571
+ observedAt: "+275760-09-13T00:00:00.000Z",
572
+ },
573
+ })),
574
+ };
575
+ return Math.max(encodedBytes(source), encodedBytes(future));
576
+ }
577
+ export function checkEngineeringRecordCapacity(value) {
578
+ if (encodedBytes(value) > engineeringMaximumRecordBytes ||
579
+ (value.kind === "engineering_source" &&
580
+ maximumEngineeringSourceBytes(value) > engineeringMaximumRecordBytes))
581
+ throw new SupportDeskLimitError("serialized_record", engineeringMaximumRecordBytes);
582
+ assertEngineeringRecord(value, { partition: value.partition, id: value.id });
583
+ }
584
+ function validateSource(value) {
585
+ fields(value, "engineering source", [
586
+ "kind",
587
+ "partition",
588
+ "id",
589
+ "ticketId",
590
+ "sourceId",
591
+ "sourceGeneration",
592
+ "workGeneration",
593
+ "generationCount",
594
+ "state",
595
+ "security",
596
+ "createdAt",
597
+ "updatedAt",
598
+ "destinations",
599
+ "reconciliationPending",
600
+ ], [
601
+ "heldReason",
602
+ "acceptedTicketRevision",
603
+ "lastVisibleMessageId",
604
+ "proofIdentity",
605
+ "evidenceFingerprint",
606
+ "productContextFingerprint",
607
+ "sourceBindingHash",
608
+ "evidence",
609
+ "product",
610
+ "pendingEvidenceReference",
611
+ "followupRevocationEpoch",
612
+ ]);
613
+ if (value.id !== "engineering-source")
614
+ throw new TypeError("Invalid engineering source key");
615
+ text(value.sourceId, "sourceId");
616
+ integer(value.generationCount, "generationCount", 0, 8);
617
+ timestamp(value.updatedAt, "updatedAt");
618
+ literals(value.state, "source.state", [
619
+ "unverified",
620
+ "eligible",
621
+ "restricted",
622
+ "held",
623
+ "withdrawn",
624
+ ]);
625
+ literals(value.security, "source.security", [
626
+ "ordinary",
627
+ "restricted",
628
+ "uncertain",
629
+ ]);
630
+ optionalText(value, [
631
+ "heldReason",
632
+ "lastVisibleMessageId",
633
+ "proofIdentity",
634
+ "evidenceFingerprint",
635
+ "productContextFingerprint",
636
+ "sourceBindingHash",
637
+ "pendingEvidenceReference",
638
+ ]);
639
+ if (value.acceptedTicketRevision !== undefined)
640
+ integer(value.acceptedTicketRevision, "acceptedTicketRevision", 1);
641
+ if (value.followupRevocationEpoch !== undefined)
642
+ integer(value.followupRevocationEpoch, "followupRevocationEpoch", 0);
643
+ const destinations = dataArray(value.destinations, "destinations", 4);
644
+ if (typeof value.reconciliationPending !== "boolean" ||
645
+ value.reconciliationPending !==
646
+ destinations.some((entry) => dataObject(entry, "destination").reconciliation !== "confirmed"))
647
+ throw new TypeError("Engineering reconciliation marker mismatch");
648
+ const identities = new Set();
649
+ for (const item of destinations) {
650
+ const destination = fields(item, "engineering destination", [
651
+ "repositoryId",
652
+ "sourceGeneration",
653
+ "stepId",
654
+ "requestHash",
655
+ "exportMarker",
656
+ "outcome",
657
+ "reconciliation",
658
+ ], [
659
+ "dispatchId",
660
+ "acknowledgement",
661
+ "lastReconciliation",
662
+ "followupRevocationEpoch",
663
+ ]);
664
+ for (const key of ["repositoryId", "stepId", "requestHash", "exportMarker"])
665
+ text(destination[key], key);
666
+ integer(destination.sourceGeneration, "destination.sourceGeneration", 1);
667
+ if (destination.followupRevocationEpoch !== undefined)
668
+ integer(destination.followupRevocationEpoch, "destination.followupRevocationEpoch", 0);
669
+ optionalText(destination, ["dispatchId"]);
670
+ literals(destination.outcome, "destination.outcome", [
671
+ "prepared",
672
+ "dispatch_started",
673
+ "accepted",
674
+ "confirmed_rejected",
675
+ "unknown",
676
+ ]);
677
+ literals(destination.reconciliation, "destination.reconciliation", [
678
+ "pending",
679
+ "blocked",
680
+ "confirmed",
681
+ ]);
682
+ const identity = `${destination.sourceGeneration}:${destination.stepId}`;
683
+ if (identities.has(identity))
684
+ throw new TypeError("Duplicate engineering destination");
685
+ identities.add(identity);
686
+ if (destination.acknowledgement !== undefined) {
687
+ validateEngineeringAcknowledgement(destination.acknowledgement);
688
+ if (destination.acknowledgement.repositoryId !== destination.repositoryId)
689
+ throw new TypeError("Destination repository mismatch");
690
+ }
691
+ if (destination.lastReconciliation !== undefined) {
692
+ const last = fields(destination.lastReconciliation, "engineering reconciliation receipt", ["fingerprint", "outcome", "observedAt"], ["proof"]);
693
+ text(last.fingerprint, "fingerprint");
694
+ timestamp(last.observedAt, "observedAt");
695
+ optionalText(last, ["proof"]);
696
+ literals(last.outcome, "reconciliation.outcome", [
697
+ "accepted",
698
+ "confirmed_rejected",
699
+ "unknown",
700
+ ]);
701
+ }
702
+ }
703
+ if (value.state === "eligible") {
704
+ if (value.security !== "ordinary" ||
705
+ value.sourceGeneration === 0 ||
706
+ value.generationCount === 0)
707
+ throw new TypeError("Eligible source lacks an ordinary evidence generation");
708
+ for (const key of [
709
+ "evidence",
710
+ "product",
711
+ "proofIdentity",
712
+ "evidenceFingerprint",
713
+ "productContextFingerprint",
714
+ "sourceBindingHash",
715
+ "acceptedTicketRevision",
716
+ "lastVisibleMessageId",
717
+ ])
718
+ if (value[key] === undefined)
719
+ throw new TypeError(`Eligible source lacks ${key}`);
720
+ if (value.heldReason !== undefined ||
721
+ value.pendingEvidenceReference !== undefined)
722
+ throw new TypeError("Eligible source retains a pending evidence hold");
723
+ }
724
+ if (value.state === "withdrawn") {
725
+ for (const key of [
726
+ "evidence",
727
+ "product",
728
+ "proofIdentity",
729
+ "evidenceFingerprint",
730
+ "productContextFingerprint",
731
+ "sourceBindingHash",
732
+ "acceptedTicketRevision",
733
+ "lastVisibleMessageId",
734
+ "pendingEvidenceReference",
735
+ ])
736
+ if (value[key] !== undefined)
737
+ throw new TypeError("Withdrawn source retains private evidence bindings");
738
+ for (const item of destinations)
739
+ if (dataObject(item, "destination").acknowledgement !== undefined &&
740
+ dataObject(dataObject(item, "destination").acknowledgement, "acknowledgement").url !== undefined)
741
+ throw new TypeError("Withdrawn source retains a destination URL");
742
+ }
743
+ }
744
+ function validateWork(value) {
745
+ fields(value, "engineering work", [
746
+ "kind",
747
+ "partition",
748
+ "id",
749
+ "ticketId",
750
+ "sourceGeneration",
751
+ "sourceBindingHash",
752
+ "workGeneration",
753
+ "acceptedTicketRevision",
754
+ "createdAt",
755
+ "deadline",
756
+ "security",
757
+ "status",
758
+ "stage",
759
+ "modelAttempts",
760
+ "claimGeneration",
761
+ "terminalReserveRows",
762
+ "proposalCount",
763
+ ], [
764
+ "evidence",
765
+ "product",
766
+ "claim",
767
+ "leaseUntil",
768
+ "retryAt",
769
+ "stepId",
770
+ "requestHash",
771
+ "candidate",
772
+ "candidateHash",
773
+ "staffSelection",
774
+ "proposal",
775
+ "proposalNeedsRebuild",
776
+ "approval",
777
+ "dispatch",
778
+ "acknowledgement",
779
+ "resultProof",
780
+ "heldReason",
781
+ "withdrawnRequest",
782
+ "usageTombstones",
783
+ "lastCompletion",
784
+ "lastFinish",
785
+ "lastDispatchResult",
786
+ "lastProposalPreparation",
787
+ ]);
788
+ if (value.id !==
789
+ engineeringWorkKey(value.ticketId, value.sourceGeneration).id)
790
+ throw new TypeError("Engineering work key mismatch");
791
+ integer(value.sourceGeneration, "sourceGeneration", 1);
792
+ text(value.sourceBindingHash, "sourceBindingHash");
793
+ integer(value.acceptedTicketRevision, "acceptedTicketRevision", 1);
794
+ timestamp(value.deadline, "deadline");
795
+ integer(value.claimGeneration, "claimGeneration", 0);
796
+ integer(value.proposalCount, "proposalCount", 0, 8);
797
+ if (value.terminalReserveRows !== 0 && value.terminalReserveRows !== 2)
798
+ throw new TypeError("Invalid engineering terminal reserve");
799
+ literals(value.security, "work.security", [
800
+ "ordinary",
801
+ "restricted",
802
+ "uncertain",
803
+ ]);
804
+ literals(value.stage, "work.stage", [
805
+ "model",
806
+ "proposal",
807
+ "dispatch",
808
+ "reconcile",
809
+ ]);
810
+ literals(value.status, "work.status", [
811
+ "pending",
812
+ "claimed",
813
+ "retry",
814
+ "candidate",
815
+ "prepared",
816
+ "approved",
817
+ "dispatch_started",
818
+ "accepted",
819
+ "confirmed_rejected",
820
+ "unknown",
821
+ "held",
822
+ "restricted",
823
+ "superseded",
824
+ ]);
825
+ optionalText(value, [
826
+ "stepId",
827
+ "requestHash",
828
+ "candidateHash",
829
+ "resultProof",
830
+ "heldReason",
831
+ ]);
832
+ optionalTime(value, ["leaseUntil", "retryAt"]);
833
+ if (value.claim !== undefined) {
834
+ const claim = fields(value.claim, "engineering claim", [
835
+ "ticketId",
836
+ "sourceGeneration",
837
+ "workerId",
838
+ "claimId",
839
+ "claimGeneration",
840
+ ]);
841
+ for (const key of ["ticketId", "workerId", "claimId"])
842
+ text(claim[key], key);
843
+ integer(claim.sourceGeneration, "claim.sourceGeneration", 1);
844
+ integer(claim.claimGeneration, "claim.claimGeneration", 1);
845
+ if (claim.ticketId !== value.ticketId ||
846
+ claim.sourceGeneration !== value.sourceGeneration ||
847
+ claim.claimGeneration !== value.claimGeneration ||
848
+ value.leaseUntil === undefined)
849
+ throw new TypeError("Engineering claim envelope mismatch");
850
+ }
851
+ for (const item of dataArray(value.modelAttempts, "modelAttempts", 4)) {
852
+ const attempt = fields(item, "engineering model attempt", [
853
+ "preparedClaimGeneration",
854
+ "stepId",
855
+ "requestHash",
856
+ "binding",
857
+ "accounting",
858
+ ], ["processing"]);
859
+ integer(attempt.preparedClaimGeneration, "preparedClaimGeneration", 1);
860
+ text(attempt.stepId, "stepId");
861
+ text(attempt.requestHash, "requestHash");
862
+ validateEngineeringModelBinding(attempt.binding);
863
+ literals(attempt.accounting, "accounting", [
864
+ "not_called",
865
+ "accounted",
866
+ "unknown",
867
+ ]);
868
+ if (attempt.processing !== undefined)
869
+ validateEngineeringProcessing(attempt.processing);
870
+ }
871
+ if (value.candidate !== undefined)
872
+ validateEngineeringCandidate(value.candidate);
873
+ if (value.staffSelection !== undefined) {
874
+ const selection = fields(value.staffSelection, "staff repository selection", [
875
+ "repositoryId",
876
+ "policyGeneration",
877
+ "policyHash",
878
+ "selectedBy",
879
+ "selectedAt",
880
+ "reason",
881
+ "commandId",
882
+ ]);
883
+ for (const key of [
884
+ "repositoryId",
885
+ "policyHash",
886
+ "selectedBy",
887
+ "reason",
888
+ "commandId",
889
+ ])
890
+ text(selection[key], key);
891
+ integer(selection.policyGeneration, "policyGeneration", 0);
892
+ timestamp(selection.selectedAt, "selectedAt");
893
+ }
894
+ if (value.proposal !== undefined) {
895
+ const proposal = fields(value.proposal, "engineering proposal", [
896
+ "stepId",
897
+ "requestHash",
898
+ "proposalHash",
899
+ "candidateHash",
900
+ "preparedClaimGeneration",
901
+ "policyGeneration",
902
+ "policyHash",
903
+ "repositoryMetadataHash",
904
+ "routingBindingHash",
905
+ "checksBinding",
906
+ "checksReceipt",
907
+ "request",
908
+ ], ["routingBinding", "routingReceipt"]);
909
+ for (const key of [
910
+ "stepId",
911
+ "requestHash",
912
+ "proposalHash",
913
+ "candidateHash",
914
+ "policyHash",
915
+ "repositoryMetadataHash",
916
+ "routingBindingHash",
917
+ "checksReceipt",
918
+ ])
919
+ text(proposal[key], key);
920
+ integer(proposal.preparedClaimGeneration, "preparedClaimGeneration", 1);
921
+ integer(proposal.policyGeneration, "policyGeneration", 0);
922
+ optionalText(proposal, ["routingReceipt"]);
923
+ if (proposal.routingBinding !== undefined)
924
+ validateEngineeringRoutingBinding(proposal.routingBinding);
925
+ validateEngineeringExportChecksBinding(proposal.checksBinding);
926
+ validateEngineeringExportRequest(proposal.request);
927
+ if (value.candidate !== undefined)
928
+ throw new TypeError("Candidate body duplicated beside exact proposal");
929
+ }
930
+ if (value.approval !== undefined) {
931
+ const approval = fields(value.approval, "engineering approval", [
932
+ "proposalHash",
933
+ "requestHash",
934
+ "sourceBindingHash",
935
+ "policyGeneration",
936
+ "policyHash",
937
+ "repositoryMetadataHash",
938
+ "routingBindingHash",
939
+ "checksBindingHash",
940
+ "decision",
941
+ "reviewedBy",
942
+ "reviewedAt",
943
+ ], ["reason"]);
944
+ for (const key of [
945
+ "proposalHash",
946
+ "requestHash",
947
+ "sourceBindingHash",
948
+ "policyHash",
949
+ "repositoryMetadataHash",
950
+ "routingBindingHash",
951
+ "checksBindingHash",
952
+ "reviewedBy",
953
+ ])
954
+ text(approval[key], key);
955
+ integer(approval.policyGeneration, "policyGeneration", 0);
956
+ timestamp(approval.reviewedAt, "reviewedAt");
957
+ optionalText(approval, ["reason"]);
958
+ literals(approval.decision, "review decision", ["approve", "reject"]);
959
+ }
960
+ if (value.dispatch !== undefined) {
961
+ const dispatch = fields(value.dispatch, "engineering dispatch", [
962
+ "dispatchId",
963
+ "startedAt",
964
+ "workerId",
965
+ "claimId",
966
+ "claimGeneration",
967
+ "repositoryId",
968
+ "requestHash",
969
+ "exportMarker",
970
+ ]);
971
+ for (const key of [
972
+ "dispatchId",
973
+ "workerId",
974
+ "claimId",
975
+ "repositoryId",
976
+ "requestHash",
977
+ "exportMarker",
978
+ ])
979
+ text(dispatch[key], key);
980
+ integer(dispatch.claimGeneration, "claimGeneration", 1);
981
+ timestamp(dispatch.startedAt, "startedAt");
982
+ if (value.requestHash !== dispatch.requestHash)
983
+ throw new TypeError("Dispatch request mismatch");
984
+ }
985
+ if (value.acknowledgement !== undefined)
986
+ validateEngineeringAcknowledgement(value.acknowledgement);
987
+ if (value.withdrawnRequest !== undefined) {
988
+ const request = fields(value.withdrawnRequest, "withdrawn engineering request", ["operation", "repositoryId", "repositoryMetadataHash", "exportMarker"], ["issueId", "issueNumber"]);
989
+ literals(request.operation, "withdrawn.operation", [
990
+ "create_issue",
991
+ "link_existing_issue",
992
+ ]);
993
+ for (const key of [
994
+ "repositoryId",
995
+ "repositoryMetadataHash",
996
+ "exportMarker",
997
+ ])
998
+ text(request[key], key);
999
+ optionalText(request, ["issueId"]);
1000
+ if (request.issueNumber !== undefined)
1001
+ integer(request.issueNumber, "issueNumber", 1);
1002
+ if (request.operation === "link_existing_issue" &&
1003
+ (request.issueId === undefined || request.issueNumber === undefined))
1004
+ throw new TypeError("Withdrawn issue link lacks identity");
1005
+ }
1006
+ if (value.usageTombstones !== undefined)
1007
+ for (const item of dataArray(value.usageTombstones, "usageTombstones", 4)) {
1008
+ const usage = fields(item, "engineering usage tombstone", [
1009
+ "stepId",
1010
+ "requestHash",
1011
+ "usageReservationId",
1012
+ "accounting",
1013
+ ]);
1014
+ for (const key of ["stepId", "requestHash", "usageReservationId"])
1015
+ text(usage[key], key);
1016
+ literals(usage.accounting, "accounting", [
1017
+ "not_called",
1018
+ "accounted",
1019
+ "unknown",
1020
+ ]);
1021
+ }
1022
+ for (const key of ["lastCompletion", "lastFinish", "lastDispatchResult"])
1023
+ if (value[key] !== undefined) {
1024
+ const receipt = fields(value[key], key, ["fingerprint"]);
1025
+ text(receipt.fingerprint, "fingerprint");
1026
+ }
1027
+ if (value.proposalNeedsRebuild !== undefined &&
1028
+ typeof value.proposalNeedsRebuild !== "boolean")
1029
+ throw new TypeError("Engineering proposal rebuild marker must be boolean");
1030
+ if (value.lastProposalPreparation !== undefined) {
1031
+ const prepared = fields(value.lastProposalPreparation, "engineering proposal preparation receipt", ["fingerprint", "result"]);
1032
+ text(prepared.fingerprint, "fingerprint");
1033
+ validateEngineeringStepResult(prepared.result);
1034
+ if (prepared.result.ticketId !== value.ticketId ||
1035
+ prepared.result.sourceGeneration !== value.sourceGeneration ||
1036
+ prepared.result.workGeneration > value.workGeneration)
1037
+ throw new TypeError("Engineering proposal preparation receipt differs from its work");
1038
+ }
1039
+ validateWorkRelationships(value);
1040
+ }
1041
+ function validateWorkRelationships(value) {
1042
+ const work = value;
1043
+ const scrubbed = work.usageTombstones !== undefined || work.withdrawnRequest !== undefined;
1044
+ if ((work.claim === undefined) !== (work.leaseUntil === undefined))
1045
+ throw new TypeError("Engineering claim and lease must be paired");
1046
+ if ((work.stepId === undefined) !== (work.requestHash === undefined))
1047
+ throw new TypeError("Engineering step and request hash must be paired");
1048
+ if (work.status === "claimed" && work.claim === undefined)
1049
+ throw new TypeError("Claimed engineering work lacks its claim");
1050
+ const attemptIds = new Set();
1051
+ const owners = new Set();
1052
+ for (const attempt of work.modelAttempts) {
1053
+ if (attempt.preparedClaimGeneration > work.claimGeneration ||
1054
+ attemptIds.has(attempt.stepId) ||
1055
+ owners.has(attempt.preparedClaimGeneration))
1056
+ throw new TypeError("Engineering model attempt ownership is inconsistent");
1057
+ attemptIds.add(attempt.stepId);
1058
+ owners.add(attempt.preparedClaimGeneration);
1059
+ }
1060
+ if (scrubbed) {
1061
+ if (![
1062
+ "privacy_withdrawal",
1063
+ "source_access_revoked",
1064
+ "source_deleted",
1065
+ ].includes(work.heldReason ?? ""))
1066
+ throw new TypeError("Scrubbed engineering work lacks its permanent withdrawal reason");
1067
+ for (const key of [
1068
+ "evidence",
1069
+ "product",
1070
+ "candidate",
1071
+ "candidateHash",
1072
+ "staffSelection",
1073
+ "proposal",
1074
+ "proposalNeedsRebuild",
1075
+ "approval",
1076
+ "lastProposalPreparation",
1077
+ ])
1078
+ if (value[key] !== undefined)
1079
+ throw new TypeError("Withdrawn engineering work retains private derivation data");
1080
+ if (work.modelAttempts.length !== 0 ||
1081
+ work.claim !== undefined ||
1082
+ work.acknowledgement?.url !== undefined)
1083
+ throw new TypeError("Withdrawn engineering work retains private model or URL data");
1084
+ }
1085
+ else {
1086
+ if (work.evidence === undefined || work.product === undefined)
1087
+ throw new TypeError("Engineering work lacks its accepted evidence and product");
1088
+ if (work.candidate !== undefined && work.candidateHash === undefined)
1089
+ throw new TypeError("Engineering candidate lacks its hash");
1090
+ if (work.status === "candidate" &&
1091
+ (work.candidateHash === undefined ||
1092
+ (work.candidate === undefined && work.staffSelection === undefined)))
1093
+ throw new TypeError("Engineering candidate state lacks candidate or staff selection");
1094
+ if (work.stage === "model" &&
1095
+ work.stepId !== undefined &&
1096
+ !work.modelAttempts.some((attempt) => attempt.stepId === work.stepId &&
1097
+ attempt.requestHash === work.requestHash))
1098
+ throw new TypeError("Engineering model step lacks its prepared attempt");
1099
+ if (work.status === "prepared" &&
1100
+ work.stage === "model" &&
1101
+ (work.claim === undefined || work.stepId === undefined))
1102
+ throw new TypeError("Prepared engineering model lacks its live preparation envelope");
1103
+ }
1104
+ const proposal = work.proposal;
1105
+ if (work.proposalNeedsRebuild === true &&
1106
+ (proposal === undefined ||
1107
+ work.staffSelection === undefined ||
1108
+ work.approval !== undefined ||
1109
+ work.dispatch !== undefined ||
1110
+ work.candidate !== undefined ||
1111
+ work.stage !== "proposal"))
1112
+ throw new TypeError("Engineering proposal rebuild lacks its retained prior body or staff target");
1113
+ if (proposal !== undefined) {
1114
+ if (proposal.stepId !== work.stepId ||
1115
+ proposal.requestHash !== work.requestHash ||
1116
+ proposal.candidateHash !== work.candidateHash ||
1117
+ work.proposalCount === 0 ||
1118
+ proposal.preparedClaimGeneration > work.claimGeneration)
1119
+ throw new TypeError("Engineering proposal differs from its work envelope");
1120
+ const checks = proposal.checksBinding;
1121
+ if (checks.sourceBindingHash !== work.sourceBindingHash ||
1122
+ checks.candidateHash !== proposal.candidateHash ||
1123
+ checks.requestHash !== proposal.requestHash ||
1124
+ checks.policyGeneration !== proposal.policyGeneration ||
1125
+ checks.policyHash !== proposal.policyHash ||
1126
+ checks.repositoryMetadataHash !== proposal.repositoryMetadataHash ||
1127
+ checks.routingBindingHash !== proposal.routingBindingHash)
1128
+ throw new TypeError("Engineering proposal check binding differs from its exact request");
1129
+ if ((proposal.routingBinding === undefined) !==
1130
+ (proposal.routingReceipt === undefined))
1131
+ throw new TypeError("Engineering calibrated routing binding and receipt must be paired");
1132
+ if (proposal.routingBinding === undefined &&
1133
+ work.staffSelection === undefined)
1134
+ throw new TypeError("Engineering proposal lacks calibrated routing or staff selection");
1135
+ }
1136
+ const approval = work.approval;
1137
+ if (approval !== undefined &&
1138
+ (work.proposalNeedsRebuild === true ||
1139
+ proposal === undefined ||
1140
+ approval.proposalHash !== proposal.proposalHash ||
1141
+ approval.requestHash !== proposal.requestHash ||
1142
+ approval.sourceBindingHash !== work.sourceBindingHash ||
1143
+ approval.policyGeneration !== proposal.policyGeneration ||
1144
+ approval.policyHash !== proposal.policyHash ||
1145
+ approval.repositoryMetadataHash !== proposal.repositoryMetadataHash ||
1146
+ approval.routingBindingHash !== proposal.routingBindingHash))
1147
+ throw new TypeError("Engineering approval differs from the exact prepared proposal");
1148
+ if (work.status === "approved" &&
1149
+ (proposal === undefined ||
1150
+ approval?.decision !== "approve" ||
1151
+ work.stage !== "dispatch"))
1152
+ throw new TypeError("Approved engineering work lacks exact staff approval");
1153
+ if (!scrubbed &&
1154
+ work.status === "prepared" &&
1155
+ work.stage !== "model" &&
1156
+ proposal === undefined)
1157
+ throw new TypeError("Prepared engineering export lacks its exact proposal");
1158
+ if (work.dispatch === undefined) {
1159
+ if (work.terminalReserveRows !== 0 ||
1160
+ work.stage === "reconcile" ||
1161
+ ["dispatch_started", "accepted", "confirmed_rejected"].includes(work.status) ||
1162
+ work.acknowledgement !== undefined)
1163
+ throw new TypeError("Engineering dispatch state lacks immutable dispatch identity");
1164
+ return;
1165
+ }
1166
+ const dispatch = work.dispatch;
1167
+ if (work.stage !== "reconcile" ||
1168
+ !["dispatch_started", "unknown", "accepted", "confirmed_rejected"].includes(work.status) ||
1169
+ work.stepId === undefined ||
1170
+ dispatch.claimGeneration > work.claimGeneration)
1171
+ throw new TypeError("Engineering dispatch recovery envelope is inconsistent");
1172
+ const request = proposal === undefined
1173
+ ? work.withdrawnRequest
1174
+ : {
1175
+ operation: proposal.request.operation,
1176
+ repositoryId: proposal.request.repository.repositoryId,
1177
+ exportMarker: proposal.request.bundle.exportMarker,
1178
+ ...(proposal.request.operation === "link_existing_issue"
1179
+ ? {
1180
+ issueId: proposal.request.issueId,
1181
+ issueNumber: proposal.request.issueNumber,
1182
+ }
1183
+ : {}),
1184
+ };
1185
+ if (request === undefined ||
1186
+ request.repositoryId !== dispatch.repositoryId ||
1187
+ request.exportMarker !== dispatch.exportMarker)
1188
+ throw new TypeError("Engineering dispatch lacks its original exact request identity");
1189
+ const terminal = work.status === "accepted" || work.status === "confirmed_rejected";
1190
+ if (work.terminalReserveRows !== (terminal ? 0 : 2))
1191
+ throw new TypeError("Engineering dispatch terminal reserve does not match its outcome");
1192
+ if (terminal && !scrubbed && work.resultProof === undefined)
1193
+ throw new TypeError("Engineering terminal outcome lacks verified proof");
1194
+ if (work.status === "accepted") {
1195
+ const acknowledgement = work.acknowledgement;
1196
+ if (acknowledgement === undefined ||
1197
+ acknowledgement.repositoryId !== dispatch.repositoryId ||
1198
+ (request.operation === "link_existing_issue" &&
1199
+ (acknowledgement.issueId !== request.issueId ||
1200
+ acknowledgement.issueNumber !== request.issueNumber)))
1201
+ throw new TypeError("Accepted engineering dispatch lacks its exact acknowledgement");
1202
+ }
1203
+ else if (work.acknowledgement !== undefined)
1204
+ throw new TypeError("Nonaccepted engineering dispatch retains an acknowledgement");
1205
+ }
1206
+ export function assertEngineeringRecord(value, key) {
1207
+ const row = dataObject(value, "engineering record");
1208
+ text(row.partition, "partition");
1209
+ text(row.ticketId ?? row.partition, "ticketId");
1210
+ text(row.id, "record.id", 400);
1211
+ if (row.partition !== key.partition ||
1212
+ row.id !== key.id ||
1213
+ (row.ticketId !== undefined && row.ticketId !== key.partition))
1214
+ throw new TypeError("Engineering record physical envelope mismatch");
1215
+ if (row.kind === "engineering_receipt") {
1216
+ fields(row, "engineering receipt", [
1217
+ "kind",
1218
+ "partition",
1219
+ "id",
1220
+ "fingerprint",
1221
+ "result",
1222
+ ]);
1223
+ if (!row.id.startsWith("engineering-command:"))
1224
+ throw new TypeError("Invalid engineering receipt key");
1225
+ text(row.fingerprint, "fingerprint");
1226
+ const outcome = dataObject(row.result, "engineering receipt result");
1227
+ text(outcome.ticketId, "ticketId");
1228
+ integer(outcome.sourceGeneration, "sourceGeneration", 0);
1229
+ if (outcome.ticketId !== row.partition)
1230
+ throw new TypeError("Receipt ticket mismatch");
1231
+ if (outcome.state !== undefined) {
1232
+ fields(outcome, "engineering source result", ["ticketId", "sourceGeneration", "state", "security"], ["heldReason"]);
1233
+ literals(outcome.state, "state", [
1234
+ "unverified",
1235
+ "eligible",
1236
+ "restricted",
1237
+ "held",
1238
+ "withdrawn",
1239
+ ]);
1240
+ literals(outcome.security, "security", [
1241
+ "ordinary",
1242
+ "restricted",
1243
+ "uncertain",
1244
+ ]);
1245
+ optionalText(outcome, ["heldReason"]);
1246
+ }
1247
+ else
1248
+ validateEngineeringStepResult(outcome);
1249
+ }
1250
+ else {
1251
+ integer(row.sourceGeneration, "sourceGeneration", 0);
1252
+ integer(row.workGeneration, "workGeneration", 1);
1253
+ timestamp(row.createdAt, "createdAt");
1254
+ if (row.kind === "engineering_source")
1255
+ validateSource(row);
1256
+ else if (row.kind === "engineering_work")
1257
+ validateWork(row);
1258
+ else
1259
+ throw new TypeError("Invalid engineering record kind");
1260
+ if (row.evidence !== undefined)
1261
+ validateEngineeringEvidence(row.evidence);
1262
+ if (row.product !== undefined)
1263
+ validateEngineeringProduct(row.product);
1264
+ }
1265
+ if (encodedBytes(value) > engineeringMaximumRecordBytes)
1266
+ throw new TypeError("Engineering record exceeds serialized limit");
1267
+ if (row.kind === "engineering_source" &&
1268
+ maximumEngineeringSourceBytes(value) >
1269
+ engineeringMaximumRecordBytes)
1270
+ throw new TypeError("Engineering source lacks future reconciliation capacity");
1271
+ }
1272
+ //# sourceMappingURL=engineering-records.js.map