@pegma/support-desk-application 0.1.0 → 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 +8 -8
@@ -0,0 +1,1434 @@
1
+ import { dataArray, dataObject, fields, text, integer, timestamp, encodedBytes, fingerprint, } from "./assistant-turn-records.js";
2
+ import { safeLearningSnapshot } from "./resolution-learning-records.js";
3
+ import { validateEngineeringProcessing } from "./engineering-records.js";
4
+ import { SupportDeskLimitError } from "./index.js";
5
+ export const engineeringFollowupMaximumRecordBytes = 60_000;
6
+ export const engineeringFollowupMaximumCandidateBytes = 16_000;
7
+ export const engineeringFollowupHeadKey = (ticketId) => ({
8
+ partition: ticketId,
9
+ id: "engineering-followup-head",
10
+ });
11
+ export const engineeringFollowupWorkKey = (ticketId, workId) => ({ partition: ticketId, id: `engineering-followup:${workId}` });
12
+ export const engineeringFollowupCommandKey = (ticketId, commandId) => ({
13
+ partition: ticketId,
14
+ id: `engineering-followup-command:${commandId}`,
15
+ });
16
+ export const safeEngineeringFollowupSnapshot = safeLearningSnapshot;
17
+ export function engineeringFollowupRecoveryHash(value, name = "recoveryIntentHash") {
18
+ if (typeof value !== "string" || !/^[a-f0-9]{64}$/.test(value))
19
+ throw new TypeError(`${name} must be a lowercase SHA-256 fingerprint`);
20
+ return value;
21
+ }
22
+ /** The actual Support codec stores a JSON string inside this physical envelope. */
23
+ export function engineeringFollowupEncodedBytes(value) {
24
+ return encodedBytes({
25
+ partition: value.partition,
26
+ id: value.id,
27
+ payload: JSON.stringify(value),
28
+ });
29
+ }
30
+ function choice(value, name, choices) {
31
+ if (typeof value !== "string" || !choices.includes(value))
32
+ throw new TypeError(`${name} is invalid`);
33
+ }
34
+ function prose(value, name, maximum) {
35
+ if (typeof value !== "string" ||
36
+ value.trim().length === 0 ||
37
+ value.length > maximum ||
38
+ /[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/u.test(value))
39
+ throw new TypeError(`${name} must be bounded text without unsafe controls`);
40
+ }
41
+ function texts(value, name, maximum, width = 200, multiline = false) {
42
+ const seen = new Set();
43
+ for (const item of dataArray(value, name, maximum)) {
44
+ if (multiline)
45
+ prose(item, name, width);
46
+ else
47
+ text(item, name, width);
48
+ if (!multiline && seen.has(item))
49
+ throw new TypeError(`${name} contains duplicates`);
50
+ seen.add(item);
51
+ }
52
+ }
53
+ function optionalTexts(row, names) {
54
+ for (const name of names)
55
+ if (row[name] !== undefined)
56
+ text(row[name], name);
57
+ }
58
+ function optionalTimes(row, names) {
59
+ for (const name of names)
60
+ if (row[name] !== undefined)
61
+ timestamp(row[name], name);
62
+ }
63
+ function bodyLimit(value, name) {
64
+ if (encodedBytes(value) > engineeringFollowupMaximumCandidateBytes)
65
+ throw new SupportDeskLimitError("serialized_record", engineeringFollowupMaximumCandidateBytes);
66
+ }
67
+ export function validateEngineeringFollowupAssociation(value) {
68
+ const row = fields(value, "follow-up association", [
69
+ "ticketId",
70
+ "repositoryId",
71
+ "issueId",
72
+ "issueNumber",
73
+ "dispatchId",
74
+ "requestHash",
75
+ "engineeringSourceGeneration",
76
+ "followupRevocationEpoch",
77
+ ]);
78
+ for (const key of [
79
+ "ticketId",
80
+ "repositoryId",
81
+ "issueId",
82
+ "dispatchId",
83
+ "requestHash",
84
+ ])
85
+ text(row[key], key);
86
+ integer(row.issueNumber, "issueNumber", 1);
87
+ integer(row.engineeringSourceGeneration, "engineeringSourceGeneration", 1);
88
+ integer(row.followupRevocationEpoch, "followupRevocationEpoch", 0);
89
+ }
90
+ export function validateEngineeringFollowupIssueHead(value) {
91
+ const row = fields(value, "follow-up issue head", [
92
+ "repositoryId",
93
+ "issueId",
94
+ "issueNumber",
95
+ "generation",
96
+ "hash",
97
+ "state",
98
+ ]);
99
+ for (const key of ["repositoryId", "issueId", "hash"])
100
+ text(row[key], key);
101
+ integer(row.issueNumber, "issueNumber", 1);
102
+ integer(row.generation, "issue generation", 0);
103
+ choice(row.state, "issue state", ["open", "closed"]);
104
+ }
105
+ export function validateEngineeringFollowupDeploymentHead(value) {
106
+ const row = fields(value, "follow-up deployment head", [
107
+ "contextRef",
108
+ "generation",
109
+ "hash",
110
+ "state",
111
+ "deploymentEpoch",
112
+ ]);
113
+ for (const key of ["contextRef", "hash", "deploymentEpoch"])
114
+ text(row[key], key);
115
+ integer(row.generation, "deployment generation", 0);
116
+ choice(row.state, "deployment state", ["unknown", "deployed", "rolled_back"]);
117
+ }
118
+ export function validateEngineeringFollowupContext(value) {
119
+ const row = fields(value, "follow-up context", ["issueHead", "deploymentHead"], ["mapping"]);
120
+ validateEngineeringFollowupIssueHead(row.issueHead);
121
+ validateEngineeringFollowupDeploymentHead(row.deploymentHead);
122
+ if (row.mapping !== undefined) {
123
+ const mapping = fields(row.mapping, "confirmed follow-up mapping", [
124
+ "semanticMappingId",
125
+ "mappingId",
126
+ "mappingVersion",
127
+ "affectedProductBinding",
128
+ "fixedProduct",
129
+ "releaseRef",
130
+ "deploymentId",
131
+ "scopeRef",
132
+ "issueHeadHash",
133
+ "deploymentHeadHash",
134
+ "verification",
135
+ ]);
136
+ for (const key of [
137
+ "semanticMappingId",
138
+ "mappingId",
139
+ "mappingVersion",
140
+ "affectedProductBinding",
141
+ "releaseRef",
142
+ "deploymentId",
143
+ "scopeRef",
144
+ "issueHeadHash",
145
+ "deploymentHeadHash",
146
+ ])
147
+ text(mapping[key], key);
148
+ const product = fields(mapping.fixedProduct, "fixed product", [
149
+ "productId",
150
+ "version",
151
+ "buildId",
152
+ ]);
153
+ for (const key of Object.keys(product))
154
+ text(product[key], key);
155
+ const proof = dataObject(mapping.verification, "mapping verification");
156
+ choice(proof.kind, "mapping verifier", ["host_release", "staff_verified"]);
157
+ fields(proof, "mapping verification", [
158
+ "kind",
159
+ "reference",
160
+ ...(proof.kind === "staff_verified" ? ["actorId"] : []),
161
+ ]);
162
+ text(proof.reference, "verification.reference");
163
+ if (proof.kind === "staff_verified")
164
+ text(proof.actorId, "verification.actorId");
165
+ if (mapping.issueHeadHash !== row.issueHead.hash ||
166
+ mapping.deploymentHeadHash !== row.deploymentHead.hash)
167
+ throw new TypeError("Follow-up mapping is not bound to its issue and deployment heads");
168
+ }
169
+ }
170
+ export function validateEngineeringFollowupAttestation(value) {
171
+ const row = dataObject(value, "follow-up attestation");
172
+ text(row.verifierReceiptId, "verifierReceiptId");
173
+ text(row.verifierVersion, "verifierVersion");
174
+ if (timestamp(row.validUntil, "validUntil") <=
175
+ timestamp(row.verifiedAt, "verifiedAt"))
176
+ throw new TypeError("Follow-up attestation validity is inverted");
177
+ }
178
+ export function validateEngineeringFollowupPublicFacts(value) {
179
+ const seen = new Set();
180
+ for (const item of dataArray(value, "public release facts", 8)) {
181
+ const fact = fields(item, "public release fact", ["reference", "text"]);
182
+ const id = text(fact.reference, "fact.reference");
183
+ prose(fact.text, "fact.text", 1_000);
184
+ if (seen.has(id))
185
+ throw new TypeError("Duplicate public release fact reference");
186
+ seen.add(id);
187
+ }
188
+ }
189
+ export function validateVerifiedEngineeringFollowupContext(value) {
190
+ const row = fields(value, "verified follow-up context", [
191
+ "context",
192
+ "publicFacts",
193
+ "attestation",
194
+ ]);
195
+ validateEngineeringFollowupContext(row.context);
196
+ validateEngineeringFollowupPublicFacts(row.publicFacts);
197
+ fields(row.attestation, "follow-up attestation", [
198
+ "verifierReceiptId",
199
+ "verifierVersion",
200
+ "verifiedAt",
201
+ "validUntil",
202
+ ]);
203
+ validateEngineeringFollowupAttestation(row.attestation);
204
+ }
205
+ function validateAdvisory(value, name) {
206
+ const row = dataObject(value, name);
207
+ texts(row.steps, `${name}.steps`, 16, 1_000, true);
208
+ if (row.steps.length === 0)
209
+ throw new TypeError("Retest requests need advisory steps");
210
+ prose(row.expectedBehavior, `${name}.expectedBehavior`, 2_000);
211
+ }
212
+ function validateKbAdvice(value) {
213
+ prose(value.summary, "KB proposal summary", 2_000);
214
+ texts(value.changes, "KB proposed changes", 16, 1_000, true);
215
+ if (value.changes.length === 0)
216
+ throw new TypeError("A KB proposal needs a proposed change");
217
+ texts(value.advisoryValidationRequirements, "advisory validation requirements", 16, 1_000, true);
218
+ }
219
+ export function validateEngineeringFollowupCandidate(value) {
220
+ const row = fields(value, "follow-up candidate", [
221
+ "customerDraft",
222
+ "kbProposal",
223
+ "retestRequests",
224
+ ]);
225
+ const customer = fields(row.customerDraft, "customer draft", ["body"]);
226
+ prose(customer.body, "customerDraft.body", 4_000);
227
+ if (row.kbProposal !== null) {
228
+ const kb = fields(row.kbProposal, "KB change proposal", [
229
+ "summary",
230
+ "changes",
231
+ "advisoryValidationRequirements",
232
+ ]);
233
+ validateKbAdvice(kb);
234
+ }
235
+ for (const item of dataArray(row.retestRequests, "advisory retest requests", 4)) {
236
+ fields(item, "advisory retest request", ["steps", "expectedBehavior"]);
237
+ validateAdvisory(item, "retest request");
238
+ }
239
+ bodyLimit(value, "candidate");
240
+ }
241
+ export function validateEngineeringFollowupKbTarget(value) {
242
+ const row = dataObject(value, "follow-up KB target");
243
+ choice(row.kind, "KB target kind", ["none", "article"]);
244
+ fields(row, "follow-up KB target", row.kind === "none"
245
+ ? ["kind"]
246
+ : [
247
+ "kind",
248
+ "articleId",
249
+ "collectionId",
250
+ "articleRevision",
251
+ "contentHash",
252
+ "validationRequirementsHash",
253
+ "validationRequirementIds",
254
+ ]);
255
+ if (row.kind === "article") {
256
+ for (const key of [
257
+ "articleId",
258
+ "collectionId",
259
+ "contentHash",
260
+ "validationRequirementsHash",
261
+ ])
262
+ text(row[key], key);
263
+ integer(row.articleRevision, "articleRevision", 1);
264
+ texts(row.validationRequirementIds, "validationRequirementIds", 32);
265
+ }
266
+ }
267
+ export function validateEngineeringFollowupRetestRequest(value) {
268
+ const row = fields(value, "bound retest request", [
269
+ "requestId",
270
+ "steps",
271
+ "expectedBehavior",
272
+ "productBinding",
273
+ "validationRequirementIds",
274
+ ]);
275
+ text(row.requestId, "requestId");
276
+ text(row.productBinding, "productBinding");
277
+ texts(row.validationRequirementIds, "validationRequirementIds", 32);
278
+ validateAdvisory(row, "retest request");
279
+ }
280
+ export function validateEngineeringFollowupBundle(value) {
281
+ const row = fields(value, "follow-up exact bundle", [
282
+ "customerDraft",
283
+ "kbProposal",
284
+ "retestRequests",
285
+ ]);
286
+ const customer = fields(row.customerDraft, "customer draft", ["body"]);
287
+ prose(customer.body, "customerDraft.body", 4_000);
288
+ if (row.kbProposal !== null) {
289
+ const kb = fields(row.kbProposal, "bound KB change proposal", [
290
+ "target",
291
+ "summary",
292
+ "changes",
293
+ "advisoryValidationRequirements",
294
+ ]);
295
+ validateEngineeringFollowupKbTarget(kb.target);
296
+ validateKbAdvice(kb);
297
+ }
298
+ const ids = new Set();
299
+ for (const item of dataArray(row.retestRequests, "bound retest requests", 4)) {
300
+ validateEngineeringFollowupRetestRequest(item);
301
+ if (ids.has(item.requestId))
302
+ throw new TypeError("Duplicate retest request ID");
303
+ ids.add(item.requestId);
304
+ }
305
+ bodyLimit(value, "bundle");
306
+ }
307
+ export function validateEngineeringFollowupChecksBinding(value) {
308
+ const row = fields(value, "follow-up checks binding", [
309
+ "workBindingHash",
310
+ "candidateHash",
311
+ "bundleHash",
312
+ "checkPolicyVersion",
313
+ "followupRevocationEpoch",
314
+ "issueHeadHash",
315
+ "deploymentHeadHash",
316
+ ]);
317
+ for (const key of Object.keys(row).filter((key) => key !== "followupRevocationEpoch"))
318
+ text(row[key], key);
319
+ integer(row.followupRevocationEpoch, "followupRevocationEpoch", 0);
320
+ }
321
+ export function validateEngineeringFollowupModelBinding(value) {
322
+ const row = fields(value, "follow-up model binding", [
323
+ "inputManifest",
324
+ "inputFingerprint",
325
+ "inputManifestHash",
326
+ "configVersion",
327
+ "promptVersion",
328
+ "modelVersion",
329
+ "parserVersion",
330
+ "checkPolicyVersion",
331
+ "budgetsHash",
332
+ "usageReservationId",
333
+ "deadline",
334
+ ]);
335
+ for (const key of Object.keys(row).filter((key) => !["inputManifest", "deadline"].includes(key)))
336
+ text(row[key], key);
337
+ timestamp(row.deadline, "deadline");
338
+ const manifest = fields(row.inputManifest, "follow-up input manifest", [
339
+ "messages",
340
+ "workBindingHash",
341
+ "contextHash",
342
+ "publicFactsHash",
343
+ ]);
344
+ for (const key of ["workBindingHash", "contextHash", "publicFactsHash"])
345
+ text(manifest[key], key);
346
+ const ids = new Set();
347
+ const ordinals = new Set();
348
+ for (const item of dataArray(manifest.messages, "manifest messages", 100)) {
349
+ const ref = fields(item, "manifest message", ["messageId", "ordinal"]);
350
+ const id = text(ref.messageId, "messageId");
351
+ const ordinal = integer(ref.ordinal, "ordinal", 1);
352
+ if (ids.has(id) || ordinals.has(ordinal))
353
+ throw new TypeError("Duplicate follow-up manifest reference");
354
+ ids.add(id);
355
+ ordinals.add(ordinal);
356
+ }
357
+ }
358
+ export function validateEngineeringFollowupResult(value) {
359
+ const row = fields(value, "follow-up result", ["ticketId", "workId", "workGeneration", "status"], [
360
+ "stepId",
361
+ "requestHash",
362
+ "candidateHash",
363
+ "bundleHash",
364
+ "messageId",
365
+ "ticketRevision",
366
+ ]);
367
+ text(row.ticketId, "ticketId");
368
+ text(row.workId, "workId");
369
+ integer(row.workGeneration, "workGeneration", 1);
370
+ choice(row.status, "status", [
371
+ "pending",
372
+ "claimed",
373
+ "model_prepared",
374
+ "retry",
375
+ "candidate",
376
+ "bundle_prepared",
377
+ "held",
378
+ "restricted",
379
+ "superseded",
380
+ "delivered",
381
+ ]);
382
+ optionalTexts(row, [
383
+ "stepId",
384
+ "requestHash",
385
+ "candidateHash",
386
+ "bundleHash",
387
+ "messageId",
388
+ ]);
389
+ if (row.ticketRevision !== undefined)
390
+ integer(row.ticketRevision, "ticketRevision", 1);
391
+ if ((row.stepId === undefined) !== (row.requestHash === undefined))
392
+ throw new TypeError("Follow-up result needs its complete step fence");
393
+ }
394
+ export function validateEngineeringFollowupRetestResult(value) {
395
+ const row = fields(value, "verified follow-up retest", [
396
+ "binding",
397
+ "result",
398
+ "executionId",
399
+ "runnerId",
400
+ "verifierReceiptId",
401
+ "verifierVersion",
402
+ "verifiedAt",
403
+ "validUntil",
404
+ ]);
405
+ validateEngineeringFollowupAttestation(row);
406
+ choice(row.result, "retest result", ["passed", "failed"]);
407
+ text(row.executionId, "executionId");
408
+ text(row.runnerId, "runnerId");
409
+ const binding = fields(row.binding, "follow-up retest binding", [
410
+ "ticketId",
411
+ "workId",
412
+ "workBindingHash",
413
+ "requestId",
414
+ "requestHash",
415
+ "bundleHash",
416
+ "productBinding",
417
+ ]);
418
+ for (const key of Object.keys(binding))
419
+ text(binding[key], key);
420
+ }
421
+ export function validateEngineeringFollowupRetestSummary(value) {
422
+ const row = fields(value, "follow-up retest summary", [
423
+ "requestId",
424
+ "requestHash",
425
+ "workBindingHash",
426
+ "bundleHash",
427
+ "productBinding",
428
+ "result",
429
+ "executionHash",
430
+ "verificationHash",
431
+ "verifiedAt",
432
+ "receiptReference",
433
+ "verifierReceiptId",
434
+ ]);
435
+ for (const key of ["requestId", "receiptReference", "verifierReceiptId"])
436
+ text(row[key], key);
437
+ for (const key of [
438
+ "requestHash",
439
+ "workBindingHash",
440
+ "bundleHash",
441
+ "productBinding",
442
+ "executionHash",
443
+ "verificationHash",
444
+ ])
445
+ engineeringFollowupRecoveryHash(row[key], key);
446
+ choice(row.result, "retest result", ["passed", "failed"]);
447
+ timestamp(row.verifiedAt, "verifiedAt");
448
+ }
449
+ export async function engineeringFollowupRetestSummary(proof, receiptReference) {
450
+ validateEngineeringFollowupRetestResult(proof);
451
+ text(receiptReference, "receiptReference");
452
+ const summary = {
453
+ requestId: proof.binding.requestId,
454
+ requestHash: proof.binding.requestHash,
455
+ workBindingHash: proof.binding.workBindingHash,
456
+ bundleHash: proof.binding.bundleHash,
457
+ productBinding: proof.binding.productBinding,
458
+ result: proof.result,
459
+ executionHash: await fingerprint({
460
+ executionId: proof.executionId,
461
+ runnerId: proof.runnerId,
462
+ }),
463
+ verificationHash: await fingerprint(proof),
464
+ verifiedAt: proof.verifiedAt,
465
+ receiptReference,
466
+ verifierReceiptId: proof.verifierReceiptId,
467
+ };
468
+ validateEngineeringFollowupRetestSummary(summary);
469
+ return summary;
470
+ }
471
+ export function engineeringFollowupModelAttemptCount(work) {
472
+ const identities = new Set();
473
+ const requests = new Map();
474
+ for (const entry of [
475
+ ...work.modelAttempts,
476
+ ...(work.usageTombstones ?? []),
477
+ ]) {
478
+ const previous = requests.get(entry.stepId);
479
+ if (previous !== undefined && previous !== entry.requestHash)
480
+ throw new TypeError("Follow-up attempt step has contradictory request hashes");
481
+ requests.set(entry.stepId, entry.requestHash);
482
+ identities.add(JSON.stringify([entry.stepId, entry.requestHash]));
483
+ }
484
+ return identities.size;
485
+ }
486
+ export function resolveEngineeringFollowupOptions(value) {
487
+ const raw = value === undefined
488
+ ? {}
489
+ : fields(value, "follow-up options", [], [
490
+ "enabled",
491
+ "releaseEvidence",
492
+ "checks",
493
+ "retestReceipts",
494
+ "maxWorksets",
495
+ "maxModelAttempts",
496
+ "maxBundleRevisions",
497
+ "trustTimeoutMilliseconds",
498
+ "maxLeaseMilliseconds",
499
+ "maxWorkMilliseconds",
500
+ ]);
501
+ if (raw.enabled !== undefined && typeof raw.enabled !== "boolean")
502
+ throw new TypeError("follow-up enabled must be boolean");
503
+ for (const [name, methods] of [
504
+ ["releaseEvidence", ["verifyInput", "confirm"]],
505
+ ["checks", ["verify"]],
506
+ ["retestReceipts", ["verify"]],
507
+ ]) {
508
+ const port = raw[name];
509
+ if (raw.enabled === true && port === undefined)
510
+ throw new TypeError(`Enabled follow-up requires ${name}`);
511
+ if (port !== undefined)
512
+ for (const method of methods)
513
+ if (port === null ||
514
+ typeof port[method] !== "function")
515
+ throw new TypeError(`follow-up ${name}.${method} is required`);
516
+ }
517
+ return {
518
+ enabled: raw.enabled === true,
519
+ ...(raw.releaseEvidence === undefined
520
+ ? {}
521
+ : {
522
+ releaseEvidence: raw.releaseEvidence,
523
+ }),
524
+ ...(raw.checks === undefined
525
+ ? {}
526
+ : {
527
+ checks: raw.checks,
528
+ }),
529
+ ...(raw.retestReceipts === undefined
530
+ ? {}
531
+ : {
532
+ retestReceipts: raw.retestReceipts,
533
+ }),
534
+ maxWorksets: integer(raw.maxWorksets ?? 4, "maxWorksets", 1, 8),
535
+ maxModelAttempts: integer(raw.maxModelAttempts ?? 2, "maxModelAttempts", 1, 4),
536
+ maxBundleRevisions: integer(raw.maxBundleRevisions ?? 4, "maxBundleRevisions", 1, 8),
537
+ trustTimeoutMilliseconds: integer(raw.trustTimeoutMilliseconds ?? 1_000, "trustTimeoutMilliseconds", 1, 5_000),
538
+ maxLeaseMilliseconds: integer(raw.maxLeaseMilliseconds ?? 60_000, "maxLeaseMilliseconds", 1, 300_000),
539
+ maxWorkMilliseconds: integer(raw.maxWorkMilliseconds ?? 300_000, "maxWorkMilliseconds", 1, 3_600_000),
540
+ };
541
+ }
542
+ export async function engineeringFollowupModelManifestHash(manifest) {
543
+ validateEngineeringFollowupModelBinding({
544
+ inputManifest: manifest,
545
+ inputFingerprint: "manifest",
546
+ inputManifestHash: "manifest",
547
+ configVersion: "manifest",
548
+ promptVersion: "manifest",
549
+ modelVersion: "manifest",
550
+ parserVersion: "manifest",
551
+ checkPolicyVersion: "manifest",
552
+ budgetsHash: "manifest",
553
+ usageReservationId: "manifest",
554
+ deadline: "2000-01-01T00:00:00.000Z",
555
+ });
556
+ return fingerprint(manifest);
557
+ }
558
+ export async function engineeringFollowupContextHash(context) {
559
+ validateEngineeringFollowupContext(context);
560
+ return fingerprint(context);
561
+ }
562
+ export async function engineeringFollowupPublicFactsHash(facts) {
563
+ validateEngineeringFollowupPublicFacts(facts);
564
+ return fingerprint(facts);
565
+ }
566
+ export async function engineeringFollowupChecksBinding(input) {
567
+ validateEngineeringFollowupBundle(input.exactBundle);
568
+ validateEngineeringFollowupContext(input.context);
569
+ const binding = {
570
+ workBindingHash: input.workBindingHash,
571
+ candidateHash: input.candidateHash,
572
+ bundleHash: await fingerprint(input.exactBundle),
573
+ checkPolicyVersion: input.checkPolicyVersion,
574
+ followupRevocationEpoch: input.followupRevocationEpoch,
575
+ issueHeadHash: input.context.issueHead.hash,
576
+ deploymentHeadHash: input.context.deploymentHead.hash,
577
+ };
578
+ validateEngineeringFollowupChecksBinding(binding);
579
+ return binding;
580
+ }
581
+ export async function engineeringFollowupRetestRequestId(input) {
582
+ text(input.workId, "workId");
583
+ integer(input.index, "retest index", 0, 3);
584
+ text(input.productBinding, "productBinding");
585
+ texts(input.validationRequirementIds, "validationRequirementIds", 32);
586
+ validateAdvisory(input, "retest request");
587
+ return fingerprint(input);
588
+ }
589
+ export async function engineeringFollowupRetestProductBinding(context) {
590
+ validateEngineeringFollowupContext(context);
591
+ const mapping = context.mapping;
592
+ if (mapping === undefined)
593
+ throw new TypeError("Retest product binding requires confirmed deployment mapping");
594
+ return fingerprint({
595
+ fixedProduct: mapping.fixedProduct,
596
+ deploymentId: mapping.deploymentId,
597
+ scopeRef: mapping.scopeRef,
598
+ deploymentEpoch: context.deploymentHead.deploymentEpoch,
599
+ });
600
+ }
601
+ export async function engineeringFollowupRetestRequestHash(request) {
602
+ validateEngineeringFollowupRetestRequest(request);
603
+ return fingerprint(request);
604
+ }
605
+ function validateObservationResult(value) {
606
+ const row = fields(value, "follow-up observation result", ["ticketId", "headGeneration", "outcome"], ["workId", "reason"]);
607
+ text(row.ticketId, "ticketId");
608
+ integer(row.headGeneration, "headGeneration", 1);
609
+ choice(row.outcome, "observation outcome", [
610
+ "queued",
611
+ "unchanged",
612
+ "held",
613
+ "stale",
614
+ ]);
615
+ optionalTexts(row, ["workId", "reason"]);
616
+ }
617
+ function validateCompactResult(value) {
618
+ const row = dataObject(value, "follow-up command result");
619
+ if (row.status !== undefined)
620
+ validateEngineeringFollowupResult(row);
621
+ else if (row.headGeneration !== undefined)
622
+ validateObservationResult(row);
623
+ else if (row.messageId !== undefined) {
624
+ fields(row, "follow-up post result", [
625
+ "ticketId",
626
+ "workId",
627
+ "messageId",
628
+ "ticketRevision",
629
+ "bundleHash",
630
+ ]);
631
+ for (const key of ["ticketId", "workId", "messageId", "bundleHash"])
632
+ text(row[key], key);
633
+ integer(row.ticketRevision, "ticketRevision", 1);
634
+ }
635
+ else {
636
+ fields(row, "follow-up retest result", [
637
+ "ticketId",
638
+ "workId",
639
+ "outcome",
640
+ "result",
641
+ ]);
642
+ text(row.ticketId, "ticketId");
643
+ text(row.workId, "workId");
644
+ choice(row.outcome, "retest recording outcome", [
645
+ "recorded",
646
+ "unchanged",
647
+ "held",
648
+ ]);
649
+ choice(row.result, "retest result", ["passed", "failed"]);
650
+ }
651
+ }
652
+ function validateHead(row) {
653
+ fields(row, "follow-up head", [
654
+ "kind",
655
+ "partition",
656
+ "id",
657
+ "ticketId",
658
+ "headGeneration",
659
+ "createdAt",
660
+ "updatedAt",
661
+ "workCount",
662
+ "associationHeads",
663
+ "deploymentContexts",
664
+ ], ["heldReason", "withdrawn", "lastObservation"]);
665
+ if (row.id !== "engineering-followup-head")
666
+ throw new TypeError("Follow-up head key mismatch");
667
+ integer(row.headGeneration, "headGeneration", 1);
668
+ integer(row.workCount, "workCount", 0, 8);
669
+ timestamp(row.updatedAt, "updatedAt");
670
+ optionalTexts(row, ["heldReason"]);
671
+ if (row.withdrawn !== undefined && typeof row.withdrawn !== "boolean")
672
+ throw new TypeError("Follow-up withdrawal marker must be boolean");
673
+ const associations = dataArray(row.associationHeads, "association heads", 4);
674
+ const associationKeys = new Set();
675
+ for (const item of associations) {
676
+ const entry = fields(item, "association head", [
677
+ "associationKey",
678
+ "issueHead",
679
+ ]);
680
+ const id = text(entry.associationKey, "associationKey");
681
+ if (associationKeys.has(id))
682
+ throw new TypeError("Duplicate follow-up association head");
683
+ associationKeys.add(id);
684
+ validateEngineeringFollowupIssueHead(entry.issueHead);
685
+ }
686
+ const contexts = dataArray(row.deploymentContexts, "deployment contexts", 8);
687
+ const contextKeys = new Set();
688
+ for (const item of contexts) {
689
+ const entry = fields(item, "deployment context head", ["associationKey", "deploymentHead"], ["mappingHash", "heldReason"]);
690
+ const id = text(entry.associationKey, "associationKey");
691
+ validateEngineeringFollowupDeploymentHead(entry.deploymentHead);
692
+ optionalTexts(entry, ["mappingHash", "heldReason"]);
693
+ const contextKey = JSON.stringify([id, entry.deploymentHead.contextRef]);
694
+ if (!associationKeys.has(id) || contextKeys.has(contextKey))
695
+ throw new TypeError("Follow-up deployment context is unregistered or duplicated");
696
+ contextKeys.add(contextKey);
697
+ }
698
+ if (row.lastObservation !== undefined) {
699
+ const last = fields(row.lastObservation, "last follow-up observation", [
700
+ "fingerprint",
701
+ "result",
702
+ ]);
703
+ text(last.fingerprint, "fingerprint");
704
+ validateObservationResult(last.result);
705
+ if (last.result.ticketId !== row.ticketId ||
706
+ last.result.headGeneration > row.headGeneration)
707
+ throw new TypeError("Follow-up observation receipt differs from its head");
708
+ }
709
+ if (row.withdrawn === true &&
710
+ (associations.length !== 0 ||
711
+ contexts.length !== 0 ||
712
+ row.lastObservation !== undefined))
713
+ throw new TypeError("Withdrawn follow-up head retains private context");
714
+ }
715
+ function validateWork(row) {
716
+ fields(row, "follow-up work", [
717
+ "kind",
718
+ "partition",
719
+ "id",
720
+ "ticketId",
721
+ "workId",
722
+ "semanticIdentity",
723
+ "association",
724
+ "parentSourceGeneration",
725
+ "acceptedTicketRevision",
726
+ "lastVisibleMessageId",
727
+ "workBindingHash",
728
+ "workGeneration",
729
+ "security",
730
+ "createdAt",
731
+ "deadline",
732
+ "status",
733
+ "stage",
734
+ "modelAttempts",
735
+ "claimGeneration",
736
+ "bundleRevision",
737
+ "completionRows",
738
+ "messageSlots",
739
+ "retestResults",
740
+ ], [
741
+ "context",
742
+ "claim",
743
+ "leaseUntil",
744
+ "retryAt",
745
+ "stepId",
746
+ "requestHash",
747
+ "candidate",
748
+ "candidateHash",
749
+ "preparedBundle",
750
+ "heldReason",
751
+ "withdrawn",
752
+ "delivered",
753
+ "handoff",
754
+ "usageTombstones",
755
+ "lastCompletion",
756
+ "lastFinish",
757
+ ]);
758
+ text(row.workId, "workId");
759
+ if (row.id !==
760
+ engineeringFollowupWorkKey(row.ticketId, row.workId).id)
761
+ throw new TypeError("Follow-up work physical key mismatch");
762
+ const semantic = fields(row.semanticIdentity, "follow-up semantic identity", [
763
+ "repositoryId",
764
+ "issueId",
765
+ "semanticMappingId",
766
+ "deploymentEpoch",
767
+ ]);
768
+ for (const key of Object.keys(semantic))
769
+ text(semantic[key], key);
770
+ validateEngineeringFollowupAssociation(row.association);
771
+ if (row.association.ticketId !== row.ticketId ||
772
+ row.association.repositoryId !== semantic.repositoryId ||
773
+ row.association.issueId !== semantic.issueId)
774
+ throw new TypeError("Follow-up association differs from its semantic identity");
775
+ integer(row.parentSourceGeneration, "parentSourceGeneration", 1);
776
+ integer(row.acceptedTicketRevision, "acceptedTicketRevision", 1);
777
+ text(row.lastVisibleMessageId, "lastVisibleMessageId");
778
+ text(row.workBindingHash, "workBindingHash");
779
+ integer(row.workGeneration, "workGeneration", 1);
780
+ choice(row.security, "security", ["ordinary", "restricted", "uncertain"]);
781
+ timestamp(row.deadline, "deadline");
782
+ choice(row.status, "work status", [
783
+ "pending",
784
+ "claimed",
785
+ "model_prepared",
786
+ "retry",
787
+ "candidate",
788
+ "bundle_prepared",
789
+ "held",
790
+ "restricted",
791
+ "superseded",
792
+ "delivered",
793
+ ]);
794
+ choice(row.stage, "work stage", ["model", "bundle", "delivered"]);
795
+ integer(row.claimGeneration, "claimGeneration", 0);
796
+ integer(row.bundleRevision, "bundleRevision", 0, 8);
797
+ optionalTexts(row, ["stepId", "requestHash", "candidateHash", "heldReason"]);
798
+ optionalTimes(row, ["leaseUntil", "retryAt"]);
799
+ if (![0, 3, 5].includes(row.completionRows) ||
800
+ ![0, 1].includes(row.messageSlots) ||
801
+ (row.completionRows === 0) !== (row.messageSlots === 0))
802
+ throw new TypeError("Follow-up completion and message reservations disagree");
803
+ if (row.withdrawn !== undefined && typeof row.withdrawn !== "boolean")
804
+ throw new TypeError("Follow-up withdrawal marker must be boolean");
805
+ if (row.context !== undefined) {
806
+ validateEngineeringFollowupContext(row.context);
807
+ const mapping = row.context.mapping;
808
+ if (row.context.issueHead.repositoryId !== semantic.repositoryId ||
809
+ row.context.issueHead.issueId !== semantic.issueId ||
810
+ row.context.deploymentHead.deploymentEpoch !== semantic.deploymentEpoch ||
811
+ (mapping !== undefined &&
812
+ mapping.semanticMappingId !== semantic.semanticMappingId))
813
+ throw new TypeError("Follow-up context differs from its semantic work identity");
814
+ }
815
+ if ((row.claim === undefined) !== (row.leaseUntil === undefined) ||
816
+ (row.stepId === undefined) !== (row.requestHash === undefined))
817
+ throw new TypeError("Follow-up claim and step envelopes must be complete");
818
+ if (row.claim !== undefined) {
819
+ const claim = fields(row.claim, "follow-up claim", [
820
+ "ticketId",
821
+ "workId",
822
+ "workerId",
823
+ "claimId",
824
+ "claimGeneration",
825
+ ]);
826
+ for (const key of ["ticketId", "workId", "workerId", "claimId"])
827
+ text(claim[key], key);
828
+ integer(claim.claimGeneration, "claimGeneration", 1);
829
+ if (claim.ticketId !== row.ticketId ||
830
+ claim.workId !== row.workId ||
831
+ claim.claimGeneration !== row.claimGeneration)
832
+ throw new TypeError("Follow-up claim differs from its work envelope");
833
+ }
834
+ const steps = new Set();
835
+ const owners = new Set();
836
+ const attempts = dataArray(row.modelAttempts, "follow-up model attempts", 4);
837
+ for (const item of attempts) {
838
+ const attempt = fields(item, "follow-up model attempt", [
839
+ "preparedClaimGeneration",
840
+ "stepId",
841
+ "requestHash",
842
+ "binding",
843
+ "accounting",
844
+ ], ["processing"]);
845
+ const owner = integer(attempt.preparedClaimGeneration, "preparedClaimGeneration", 1);
846
+ const step = text(attempt.stepId, "stepId");
847
+ text(attempt.requestHash, "requestHash");
848
+ validateEngineeringFollowupModelBinding(attempt.binding);
849
+ choice(attempt.accounting, "accounting", [
850
+ "not_called",
851
+ "accounted",
852
+ "unknown",
853
+ ]);
854
+ if (attempt.processing !== undefined)
855
+ validateEngineeringProcessing(attempt.processing);
856
+ if (owner > row.claimGeneration ||
857
+ owners.has(owner) ||
858
+ steps.has(step))
859
+ throw new TypeError("Follow-up model attempt ownership is inconsistent");
860
+ owners.add(owner);
861
+ steps.add(step);
862
+ }
863
+ if (row.candidate !== undefined) {
864
+ validateEngineeringFollowupCandidate(row.candidate);
865
+ if (row.candidateHash === undefined)
866
+ throw new TypeError("Follow-up candidate lacks its hash");
867
+ }
868
+ if (row.preparedBundle !== undefined) {
869
+ const prepared = fields(row.preparedBundle, "prepared follow-up bundle", [
870
+ "bundle",
871
+ "bundleHash",
872
+ "candidateHash",
873
+ "binding",
874
+ "checksReceipt",
875
+ ]);
876
+ validateEngineeringFollowupBundle(prepared.bundle);
877
+ for (const key of ["bundleHash", "candidateHash", "checksReceipt"])
878
+ text(prepared[key], key);
879
+ validateEngineeringFollowupChecksBinding(prepared.binding);
880
+ if (row.candidate !== undefined ||
881
+ prepared.candidateHash !== row.candidateHash ||
882
+ prepared.binding.candidateHash !== prepared.candidateHash ||
883
+ prepared.binding.bundleHash !== prepared.bundleHash ||
884
+ prepared.binding.workBindingHash !== row.workBindingHash ||
885
+ prepared.binding.followupRevocationEpoch !==
886
+ row.association.followupRevocationEpoch ||
887
+ row.bundleRevision === 0)
888
+ throw new TypeError("Prepared follow-up bundle differs from its work/check envelope");
889
+ }
890
+ if (row.delivered !== undefined) {
891
+ const delivered = fields(row.delivered, "follow-up delivered summary", [
892
+ "messageId",
893
+ "ticketRevision",
894
+ "bodyHash",
895
+ "bundleHash",
896
+ "commandId",
897
+ "postedBy",
898
+ "postedAt",
899
+ ]);
900
+ for (const key of [
901
+ "messageId",
902
+ "bodyHash",
903
+ "bundleHash",
904
+ "commandId",
905
+ "postedBy",
906
+ ])
907
+ text(delivered[key], key);
908
+ integer(delivered.ticketRevision, "ticketRevision", 1);
909
+ timestamp(delivered.postedAt, "postedAt");
910
+ }
911
+ if (row.handoff !== undefined) {
912
+ const handoff = fields(row.handoff, "private follow-up handoff", [
913
+ "kbProposal",
914
+ "retestRequests",
915
+ ]);
916
+ if (handoff.kbProposal !== null) {
917
+ const kb = fields(handoff.kbProposal, "private KB handoff", [
918
+ "target",
919
+ "summary",
920
+ "changes",
921
+ "advisoryValidationRequirements",
922
+ ]);
923
+ validateEngineeringFollowupKbTarget(kb.target);
924
+ validateKbAdvice(kb);
925
+ }
926
+ for (const item of dataArray(handoff.retestRequests, "private retest handoffs", 4))
927
+ validateEngineeringFollowupRetestRequest(item);
928
+ bodyLimit(handoff, "handoff");
929
+ }
930
+ const retests = dataArray(row.retestResults, "follow-up retest results", 4);
931
+ const requests = new Set();
932
+ for (const item of retests) {
933
+ validateEngineeringFollowupRetestSummary(item);
934
+ if (requests.has(item.requestId))
935
+ throw new TypeError("Follow-up retest result duplicates a request");
936
+ requests.add(item.requestId);
937
+ }
938
+ if (row.usageTombstones !== undefined)
939
+ for (const item of dataArray(row.usageTombstones, "follow-up usage tombstones", 4)) {
940
+ const usage = fields(item, "follow-up usage tombstone", [
941
+ "stepId",
942
+ "requestHash",
943
+ "usageReservationId",
944
+ "accounting",
945
+ ]);
946
+ for (const key of ["stepId", "requestHash", "usageReservationId"])
947
+ text(usage[key], key);
948
+ choice(usage.accounting, "accounting", [
949
+ "not_called",
950
+ "accounted",
951
+ "unknown",
952
+ ]);
953
+ }
954
+ if (engineeringFollowupModelAttemptCount(row) > 4)
955
+ throw new TypeError("Follow-up model history exceeds its lifetime attempt bound");
956
+ for (const key of ["lastCompletion", "lastFinish"])
957
+ if (row[key] !== undefined) {
958
+ const receipt = fields(row[key], key, ["fingerprint", "result"]);
959
+ text(receipt.fingerprint, "fingerprint");
960
+ validateEngineeringFollowupResult(receipt.result);
961
+ if (receipt.result.ticketId !== row.ticketId ||
962
+ receipt.result.workId !== row.workId ||
963
+ receipt.result.workGeneration > row.workGeneration)
964
+ throw new TypeError("Follow-up inline receipt differs from its work");
965
+ }
966
+ if (row.withdrawn === true) {
967
+ for (const key of [
968
+ "context",
969
+ "claim",
970
+ "leaseUntil",
971
+ "retryAt",
972
+ "candidate",
973
+ "candidateHash",
974
+ "preparedBundle",
975
+ "handoff",
976
+ ])
977
+ if (row[key] !== undefined)
978
+ throw new TypeError("Withdrawn follow-up retains private derivation data");
979
+ if (attempts.length !== 0 ||
980
+ retests.length !== 0 ||
981
+ row.completionRows !== 0)
982
+ throw new TypeError("Withdrawn follow-up retains private model/test data or reservations");
983
+ }
984
+ else if (row.context === undefined)
985
+ throw new TypeError("Live follow-up work lacks its current proof context");
986
+ if (row.status === "claimed" && row.claim === undefined)
987
+ throw new TypeError("Claimed follow-up work lacks its claim");
988
+ if (row.status === "model_prepared" &&
989
+ (row.stage !== "model" ||
990
+ row.claim === undefined ||
991
+ row.stepId === undefined))
992
+ throw new TypeError("Prepared follow-up model lacks its live envelope");
993
+ if (row.stage === "model" &&
994
+ row.stepId !== undefined &&
995
+ row.withdrawn !== true &&
996
+ !attempts.some((item) => {
997
+ const attempt = item;
998
+ return (attempt.stepId === row.stepId && attempt.requestHash === row.requestHash);
999
+ }))
1000
+ throw new TypeError("Follow-up model step has no prepared attempt");
1001
+ if (row.status === "candidate" &&
1002
+ row.candidate === undefined &&
1003
+ row.preparedBundle === undefined)
1004
+ throw new TypeError("Follow-up candidate state has no retained candidate or bundle");
1005
+ if (row.status === "bundle_prepared" &&
1006
+ (row.stage !== "bundle" ||
1007
+ row.preparedBundle === undefined ||
1008
+ row.completionRows !== 3))
1009
+ throw new TypeError("Prepared follow-up bundle lacks its checked content and posting reserve");
1010
+ if (row.status === "delivered" || row.stage === "delivered") {
1011
+ if (row.status !== "delivered" ||
1012
+ row.stage !== "delivered" ||
1013
+ row.delivered === undefined ||
1014
+ row.candidate !== undefined ||
1015
+ row.preparedBundle !== undefined ||
1016
+ row.completionRows !== 0 ||
1017
+ row.claim !== undefined)
1018
+ throw new TypeError("Delivered follow-up state lacks its exact committed summary");
1019
+ }
1020
+ else if (row.delivered !== undefined || row.handoff !== undefined)
1021
+ throw new TypeError("Undelivered follow-up contains a delivered summary or handoff");
1022
+ }
1023
+ export function assertEngineeringFollowupRecord(value, key) {
1024
+ const row = dataObject(value, "follow-up record");
1025
+ text(row.partition, "partition");
1026
+ text(row.ticketId ?? row.partition, "ticketId");
1027
+ text(row.id, "record.id", 400);
1028
+ if (row.partition !== key.partition ||
1029
+ row.id !== key.id ||
1030
+ (row.ticketId !== undefined && row.ticketId !== key.partition))
1031
+ throw new TypeError("Follow-up physical record envelope mismatch");
1032
+ if (row.kind === "engineering_followup_head") {
1033
+ timestamp(row.createdAt, "createdAt");
1034
+ validateHead(row);
1035
+ }
1036
+ else if (row.kind === "engineering_followup_work") {
1037
+ timestamp(row.createdAt, "createdAt");
1038
+ validateWork(row);
1039
+ }
1040
+ else if (row.kind === "engineering_followup_receipt") {
1041
+ fields(row, "follow-up command receipt", ["kind", "partition", "id", "fingerprint", "result"], ["recoveryFingerprint"]);
1042
+ if (!row.id.startsWith("engineering-followup-command:"))
1043
+ throw new TypeError("Follow-up receipt physical prefix mismatch");
1044
+ text(row.id.slice("engineering-followup-command:".length), "commandId");
1045
+ text(row.fingerprint, "fingerprint");
1046
+ validateCompactResult(row.result);
1047
+ if (row.result.ticketId !== row.partition)
1048
+ throw new TypeError("Follow-up command receipt ticket mismatch");
1049
+ if (row.recoveryFingerprint !== undefined) {
1050
+ engineeringFollowupRecoveryHash(row.recoveryFingerprint, "recoveryFingerprint");
1051
+ const result = dataObject(row.result, "recoverable preparation result");
1052
+ if (result.status !== "bundle_prepared" ||
1053
+ result.candidateHash === undefined ||
1054
+ result.bundleHash === undefined)
1055
+ throw new TypeError("Only an exact checked preparation acknowledgement may retain recovery metadata");
1056
+ }
1057
+ }
1058
+ else
1059
+ throw new TypeError("Unknown follow-up record kind");
1060
+ if (engineeringFollowupEncodedBytes(row) > engineeringFollowupMaximumRecordBytes)
1061
+ throw new TypeError("Follow-up physical record exceeds 60000 bytes");
1062
+ }
1063
+ export function maximumEngineeringFollowupHeadBytes(head) {
1064
+ const wide = "\ud800".repeat(200);
1065
+ const at = "+275760-09-13T00:00:00.000Z";
1066
+ // New proof identities and generations pass admission again through
1067
+ // observation or reassessment. Retained work can grow only control metadata.
1068
+ const future = {
1069
+ ...head,
1070
+ headGeneration: Number.MAX_SAFE_INTEGER,
1071
+ workCount: 8,
1072
+ updatedAt: at,
1073
+ heldReason: wide,
1074
+ deploymentContexts: head.deploymentContexts.map((entry) => ({
1075
+ ...entry,
1076
+ heldReason: wide,
1077
+ })),
1078
+ lastObservation: {
1079
+ fingerprint: "f".repeat(64),
1080
+ result: {
1081
+ ticketId: head.ticketId,
1082
+ headGeneration: Number.MAX_SAFE_INTEGER,
1083
+ workId: "f".repeat(64),
1084
+ outcome: "unchanged",
1085
+ reason: wide,
1086
+ },
1087
+ },
1088
+ };
1089
+ const { lastObservation: _observation, ...retained } = future;
1090
+ const scrubbed = {
1091
+ ...retained,
1092
+ withdrawn: true,
1093
+ associationHeads: [],
1094
+ deploymentContexts: [],
1095
+ };
1096
+ return Math.max(engineeringFollowupEncodedBytes(head), engineeringFollowupEncodedBytes(future), engineeringFollowupEncodedBytes(scrubbed));
1097
+ }
1098
+ export function checkEngineeringFollowupRecordCapacity(row) {
1099
+ if (engineeringFollowupEncodedBytes(row) >
1100
+ engineeringFollowupMaximumRecordBytes ||
1101
+ (row.kind === "engineering_followup_head" &&
1102
+ maximumEngineeringFollowupHeadBytes(row) >
1103
+ engineeringFollowupMaximumRecordBytes))
1104
+ throw new SupportDeskLimitError("serialized_record", engineeringFollowupMaximumRecordBytes);
1105
+ assertEngineeringFollowupRecord(row, {
1106
+ partition: row.partition,
1107
+ id: row.id,
1108
+ });
1109
+ }
1110
+ /**
1111
+ * Each newly admitted stage reserves its own continuations. A future exact
1112
+ * bundle has a separate admission boundary; it is not an unknown paid result.
1113
+ */
1114
+ export function reserveEngineeringFollowupReachable(work) {
1115
+ checkEngineeringFollowupRecordCapacity(work);
1116
+ const wide = "\ud800".repeat(200);
1117
+ const at = "+275760-09-13T00:00:00.000Z";
1118
+ const hash = "f".repeat(64);
1119
+ const maximum = Number.MAX_SAFE_INTEGER;
1120
+ const { claim: _claim, leaseUntil: _lease, retryAt: _retry, ...unclaimed } = work;
1121
+ const step = work.stepId === undefined
1122
+ ? {}
1123
+ : {
1124
+ stepId: work.stepId,
1125
+ requestHash: work.requestHash,
1126
+ };
1127
+ const result = (status, candidateHash = work.candidateHash, bundleHash = work.preparedBundle?.bundleHash) => ({
1128
+ ticketId: work.ticketId,
1129
+ workId: work.workId,
1130
+ workGeneration: maximum,
1131
+ status,
1132
+ ...step,
1133
+ ...(candidateHash === undefined ? {} : { candidateHash }),
1134
+ ...(bundleHash === undefined ? {} : { bundleHash }),
1135
+ });
1136
+ const finish = {
1137
+ fingerprint: hash,
1138
+ result: result("superseded"),
1139
+ };
1140
+ const base = {
1141
+ ...unclaimed,
1142
+ workGeneration: maximum,
1143
+ claimGeneration: maximum,
1144
+ };
1145
+ const claim = {
1146
+ ticketId: work.ticketId,
1147
+ workId: work.workId,
1148
+ workerId: wide,
1149
+ claimId: wide,
1150
+ claimGeneration: maximum,
1151
+ };
1152
+ const histories = new Map();
1153
+ for (const entry of work.usageTombstones ?? [])
1154
+ histories.set(JSON.stringify([entry.stepId, entry.requestHash]), entry);
1155
+ for (const [index, entry] of work.modelAttempts.entries())
1156
+ histories.set(JSON.stringify([entry.stepId, entry.requestHash]), {
1157
+ stepId: entry.stepId,
1158
+ requestHash: entry.requestHash,
1159
+ usageReservationId: entry.binding.usageReservationId,
1160
+ accounting: work.stage === "model" && index === work.modelAttempts.length - 1
1161
+ ? "accounted"
1162
+ : entry.accounting,
1163
+ });
1164
+ const successUsage = [...histories.values()];
1165
+ const currentModelStep = work.stage === "model" ? work.modelAttempts.at(-1)?.stepId : undefined;
1166
+ const unresolvedUsage = successUsage.map((entry) => entry.stepId === currentModelStep
1167
+ ? { ...entry, accounting: "not_called" }
1168
+ : entry);
1169
+ const { context: _context, candidate: _candidate, candidateHash: _candidateHash, preparedBundle: _prepared, handoff: _handoff, modelAttempts: _attempts, retestResults: _results, ...minimal } = base;
1170
+ const scrubbed = {
1171
+ ...minimal,
1172
+ withdrawn: true,
1173
+ heldReason: wide,
1174
+ status: work.status === "delivered" ? "delivered" : "held",
1175
+ modelAttempts: [],
1176
+ retestResults: [],
1177
+ completionRows: 0,
1178
+ messageSlots: 0,
1179
+ usageTombstones: unresolvedUsage,
1180
+ };
1181
+ const held = {
1182
+ ...base,
1183
+ status: "restricted",
1184
+ security: "restricted",
1185
+ heldReason: wide,
1186
+ completionRows: 0,
1187
+ messageSlots: 0,
1188
+ lastFinish: finish,
1189
+ };
1190
+ const futures = [
1191
+ work,
1192
+ scrubbed,
1193
+ { ...scrubbed, lastFinish: finish },
1194
+ ];
1195
+ const measure = () => {
1196
+ for (const future of futures)
1197
+ if (engineeringFollowupEncodedBytes(future) > engineeringFollowupMaximumRecordBytes)
1198
+ throw new SupportDeskLimitError("serialized_record", engineeringFollowupMaximumRecordBytes);
1199
+ };
1200
+ if (work.withdrawn === true)
1201
+ return;
1202
+ // Observation is unpaid. The exact manifest is admitted by model preparation.
1203
+ if (work.stage === "model" && work.modelAttempts.length === 0) {
1204
+ futures.push(held, {
1205
+ ...base,
1206
+ status: "claimed",
1207
+ claim,
1208
+ leaseUntil: at,
1209
+ retryAt: at,
1210
+ });
1211
+ measure();
1212
+ return;
1213
+ }
1214
+ if (work.stage === "model") {
1215
+ const processing = {
1216
+ provider: wide,
1217
+ model: wide,
1218
+ requestId: wide,
1219
+ failureCategory: wide,
1220
+ currency: wide,
1221
+ inputTokens: maximum,
1222
+ outputTokens: maximum,
1223
+ costMicros: maximum,
1224
+ };
1225
+ const finishingAttempts = work.modelAttempts.map((entry, index) => index === work.modelAttempts.length - 1
1226
+ ? { ...entry, accounting: "not_called", processing }
1227
+ : entry);
1228
+ // While a provider outcome is unresolved its one exact manifest survives.
1229
+ const prepared = {
1230
+ ...base,
1231
+ modelAttempts: finishingAttempts,
1232
+ heldReason: wide,
1233
+ lastFinish: finish,
1234
+ };
1235
+ futures.push({ ...prepared, status: "superseded", retryAt: at }, { ...prepared, status: "claimed", claim, leaseUntil: at, retryAt: at });
1236
+ // Successful completion compacts that manifest/processing into usage
1237
+ // tombstones before storing the candidate and the next bundle claim.
1238
+ const candidateHash = hash;
1239
+ const completion = {
1240
+ fingerprint: hash,
1241
+ result: result("candidate", candidateHash),
1242
+ };
1243
+ const candidate = {
1244
+ ...base,
1245
+ stage: "bundle",
1246
+ status: "candidate",
1247
+ modelAttempts: [],
1248
+ usageTombstones: successUsage,
1249
+ candidate: "\\".repeat(Math.ceil(engineeringFollowupMaximumCandidateBytes / 4)),
1250
+ candidateHash,
1251
+ lastCompletion: completion,
1252
+ };
1253
+ futures.push(candidate, {
1254
+ ...candidate,
1255
+ status: "claimed",
1256
+ claim,
1257
+ leaseUntil: at,
1258
+ retryAt: at,
1259
+ heldReason: wide,
1260
+ lastFinish: {
1261
+ fingerprint: hash,
1262
+ result: result("retry", candidateHash),
1263
+ },
1264
+ }, {
1265
+ ...candidate,
1266
+ status: "restricted",
1267
+ security: "restricted",
1268
+ heldReason: wide,
1269
+ completionRows: 0,
1270
+ messageSlots: 0,
1271
+ lastFinish: {
1272
+ fingerprint: hash,
1273
+ result: result("restricted", candidateHash),
1274
+ },
1275
+ });
1276
+ const { candidate: _futureCandidate, candidateHash: _futureHash, ...completedMinimal } = candidate;
1277
+ futures.push({
1278
+ ...completedMinimal,
1279
+ context: undefined,
1280
+ withdrawn: true,
1281
+ heldReason: wide,
1282
+ status: "held",
1283
+ completionRows: 0,
1284
+ messageSlots: 0,
1285
+ retestResults: [],
1286
+ });
1287
+ measure();
1288
+ return;
1289
+ }
1290
+ if (work.preparedBundle === undefined && work.status !== "delivered") {
1291
+ // Retained candidates cannot call a model again. Preparing an actual
1292
+ // checked bundle is a new admission using its immutable rendered content.
1293
+ futures.push(held, {
1294
+ ...base,
1295
+ status: "claimed",
1296
+ claim,
1297
+ leaseUntil: at,
1298
+ retryAt: at,
1299
+ heldReason: wide,
1300
+ lastFinish: { fingerprint: hash, result: result("retry") },
1301
+ });
1302
+ measure();
1303
+ return;
1304
+ }
1305
+ const requests = work.preparedBundle?.bundle.retestRequests ??
1306
+ work.handoff?.retestRequests ??
1307
+ [];
1308
+ const summaries = new Map(work.retestResults.map((entry) => [entry.requestId, entry]));
1309
+ for (const request of requests)
1310
+ summaries.set(request.requestId, {
1311
+ requestId: request.requestId,
1312
+ requestHash: hash,
1313
+ workBindingHash: work.workBindingHash,
1314
+ bundleHash: work.preparedBundle?.bundleHash ?? work.delivered.bundleHash,
1315
+ productBinding: request.productBinding,
1316
+ result: "failed",
1317
+ executionHash: hash,
1318
+ verificationHash: hash,
1319
+ verifiedAt: at,
1320
+ receiptReference: wide,
1321
+ verifierReceiptId: wide,
1322
+ });
1323
+ if (summaries.size > 4)
1324
+ throw new TypeError("Follow-up retest history cannot admit this request scope");
1325
+ const retestResults = [...summaries.values()];
1326
+ const compact = {
1327
+ ...base,
1328
+ modelAttempts: [],
1329
+ usageTombstones: successUsage,
1330
+ retestResults,
1331
+ };
1332
+ if (work.status === "delivered") {
1333
+ // Delivered identities and handoff bodies are immutable. Only bounded
1334
+ // verified retest results and eventual private-data scrubbing can grow.
1335
+ const lateFinish = {
1336
+ fingerprint: hash,
1337
+ result: {
1338
+ ticketId: work.ticketId,
1339
+ workId: work.workId,
1340
+ workGeneration: maximum,
1341
+ status: "delivered",
1342
+ ...step,
1343
+ messageId: work.delivered.messageId,
1344
+ ticketRevision: work.delivered.ticketRevision,
1345
+ },
1346
+ };
1347
+ futures.push(compact, {
1348
+ ...compact,
1349
+ security: "restricted",
1350
+ heldReason: wide,
1351
+ lastFinish: lateFinish,
1352
+ }, { ...scrubbed, lastFinish: lateFinish });
1353
+ measure();
1354
+ return;
1355
+ }
1356
+ const prepared = work.preparedBundle;
1357
+ const { candidate: _postedCandidate, candidateHash: _postedHash, preparedBundle: _postedBundle, handoff: _oldHandoff, ...postedBase } = base;
1358
+ const delivered = {
1359
+ ...postedBase,
1360
+ stage: "delivered",
1361
+ status: "delivered",
1362
+ modelAttempts: [],
1363
+ usageTombstones: successUsage,
1364
+ completionRows: 0,
1365
+ messageSlots: 0,
1366
+ delivered: {
1367
+ messageId: wide,
1368
+ ticketRevision: maximum,
1369
+ bodyHash: hash,
1370
+ bundleHash: prepared.bundleHash,
1371
+ commandId: wide,
1372
+ postedBy: wide,
1373
+ postedAt: at,
1374
+ },
1375
+ handoff: {
1376
+ kbProposal: prepared.bundle.kbProposal,
1377
+ retestRequests: prepared.bundle.retestRequests,
1378
+ },
1379
+ };
1380
+ const lateFinish = {
1381
+ fingerprint: hash,
1382
+ result: {
1383
+ ticketId: work.ticketId,
1384
+ workId: work.workId,
1385
+ workGeneration: maximum,
1386
+ status: "delivered",
1387
+ ...step,
1388
+ messageId: wide,
1389
+ ticketRevision: maximum,
1390
+ },
1391
+ };
1392
+ futures.push(held, compact, {
1393
+ ...compact,
1394
+ status: "restricted",
1395
+ security: "restricted",
1396
+ heldReason: wide,
1397
+ completionRows: 0,
1398
+ messageSlots: 0,
1399
+ lastFinish: finish,
1400
+ }, delivered, { ...delivered, retestResults }, {
1401
+ ...delivered,
1402
+ retestResults,
1403
+ security: "restricted",
1404
+ heldReason: wide,
1405
+ lastFinish: lateFinish,
1406
+ }, {
1407
+ ...scrubbed,
1408
+ stage: "delivered",
1409
+ status: "delivered",
1410
+ delivered: delivered.delivered,
1411
+ lastFinish: lateFinish,
1412
+ }, {
1413
+ kind: "engineering_followup_receipt",
1414
+ partition: work.ticketId,
1415
+ id: engineeringFollowupCommandKey(work.ticketId, wide).id,
1416
+ fingerprint: hash,
1417
+ recoveryFingerprint: hash,
1418
+ result: result("bundle_prepared", prepared.candidateHash, prepared.bundleHash),
1419
+ }, {
1420
+ kind: "engineering_followup_receipt",
1421
+ partition: work.ticketId,
1422
+ id: engineeringFollowupCommandKey(work.ticketId, wide).id,
1423
+ fingerprint: hash,
1424
+ result: {
1425
+ ticketId: work.ticketId,
1426
+ workId: work.workId,
1427
+ messageId: wide,
1428
+ ticketRevision: maximum,
1429
+ bundleHash: prepared.bundleHash,
1430
+ },
1431
+ });
1432
+ measure();
1433
+ }
1434
+ //# sourceMappingURL=engineering-followup-records.js.map