trackly-cli 0.16.0 → 0.17.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/trackly +2 -2
- package/contracts/trackly-apply-contract-history.json +9 -0
- package/contracts/trackly-apply-tools.json +16 -3
- package/lib/agent.js +1 -1
- package/lib/formatters.js +106 -2
- package/mcp/apply-tools.js +94 -22
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/server.json +2 -2
- package/skills/trackly-apply/SKILL.md +18 -9
- package/skills/trackly-apply/references/access-probe.md +68 -0
- package/skills/trackly-apply/references/answer-resolution.md +25 -0
- package/skills/trackly-apply/references/application-writing.md +20 -2
- package/skills/trackly-apply/references/browser-upload.md +26 -8
- package/skills/trackly-apply/references/form-integrity.md +22 -0
- package/skills/trackly-apply/references/operational-checkpoints.md +319 -0
- package/skills/trackly-apply/references/performance-telemetry.md +61 -0
- package/skills/trackly-apply/references/review-handoff.md +21 -0
- package/skills/trackly-apply/scripts/validate-phase-checkpoint.js +1068 -0
|
@@ -0,0 +1,1068 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const { createHash } = require('node:crypto');
|
|
5
|
+
const { StringDecoder } = require('node:string_decoder');
|
|
6
|
+
|
|
7
|
+
const ACCESS_STATES = new Set([
|
|
8
|
+
'accessible',
|
|
9
|
+
'authentication_required',
|
|
10
|
+
'account_creation_required',
|
|
11
|
+
'otp_required',
|
|
12
|
+
'captcha_before_form',
|
|
13
|
+
'captcha_at_submit',
|
|
14
|
+
'manual_only',
|
|
15
|
+
'unknown_unobservable',
|
|
16
|
+
]);
|
|
17
|
+
|
|
18
|
+
const CLEANUP_PREFERENCES = new Set([
|
|
19
|
+
'never',
|
|
20
|
+
'submitted_only',
|
|
21
|
+
'submitted_and_probe_blockers',
|
|
22
|
+
]);
|
|
23
|
+
|
|
24
|
+
const TAB_STATES = new Set(['open', 'missing', 'closure_unverified', 'closed_verified']);
|
|
25
|
+
const MAX_RECEIPT_BYTES = 64 * 1024;
|
|
26
|
+
const WORK_MODES = new Set(['accessible_execution', 'fixed_inspection']);
|
|
27
|
+
const COMMON_LINEAGE_FIELDS = ['workMode', 'batchId', 'memberId', 'jobId', 'runId', 'inspectionEpoch'];
|
|
28
|
+
const SHA256_PATTERN = /^[a-f0-9]{64}$/;
|
|
29
|
+
const COMMITTED_CONTROL_FIELDS = [
|
|
30
|
+
'filledExactProfile',
|
|
31
|
+
'filledSafeDerivation',
|
|
32
|
+
'filledSupportedDraft',
|
|
33
|
+
'preservedUserEdit',
|
|
34
|
+
];
|
|
35
|
+
const EXCEPTION_CONTROL_FIELDS = [
|
|
36
|
+
'missingFact',
|
|
37
|
+
'liveConsent',
|
|
38
|
+
'authenticationBlocker',
|
|
39
|
+
'unobservableCommit',
|
|
40
|
+
'unsupportedControl',
|
|
41
|
+
'notApplicable',
|
|
42
|
+
];
|
|
43
|
+
const CONTROL_ACCOUNTING_FIELDS = [
|
|
44
|
+
...COMMITTED_CONTROL_FIELDS,
|
|
45
|
+
...EXCEPTION_CONTROL_FIELDS,
|
|
46
|
+
];
|
|
47
|
+
const ANSWER_SCOPE_FIELDS = ['run', 'question', 'office', 'jurisdiction', 'company', 'provider', 'global'];
|
|
48
|
+
const HISTORY_FIELDS = [
|
|
49
|
+
'canonicalEducationRecordCount',
|
|
50
|
+
'accountedEducationRecordCount',
|
|
51
|
+
'canonicalEmploymentPositionCount',
|
|
52
|
+
'accountedEmploymentPositionCount',
|
|
53
|
+
'educationOrderVerified',
|
|
54
|
+
'employmentOrderVerified',
|
|
55
|
+
'datePrecisionInvented',
|
|
56
|
+
'educationReconciliationFingerprint',
|
|
57
|
+
'employmentReconciliationFingerprint',
|
|
58
|
+
];
|
|
59
|
+
const RESUME_AUDIT_FIELDS = [
|
|
60
|
+
'control',
|
|
61
|
+
'mode',
|
|
62
|
+
'approval',
|
|
63
|
+
'preAttachVerification',
|
|
64
|
+
'attachmentCommit',
|
|
65
|
+
'filenameVerification',
|
|
66
|
+
'parserRecheck',
|
|
67
|
+
'finalSweep',
|
|
68
|
+
];
|
|
69
|
+
const EMPLOYER_APPLICATION_STATES = new Set([
|
|
70
|
+
'not_opened',
|
|
71
|
+
'access_probed',
|
|
72
|
+
'form_reached',
|
|
73
|
+
'partially_filled',
|
|
74
|
+
'needs_answers',
|
|
75
|
+
'review_state_prepared',
|
|
76
|
+
'manually_submitted',
|
|
77
|
+
'ats_success_observed',
|
|
78
|
+
'blocked',
|
|
79
|
+
]);
|
|
80
|
+
const TRACKLY_MEMBER_STATES = new Set([
|
|
81
|
+
'reserved',
|
|
82
|
+
'inspecting',
|
|
83
|
+
'needs_input',
|
|
84
|
+
'review_ready',
|
|
85
|
+
'awaiting_manual_submit',
|
|
86
|
+
'submitted',
|
|
87
|
+
'blocked',
|
|
88
|
+
'parked',
|
|
89
|
+
'stopped',
|
|
90
|
+
]);
|
|
91
|
+
const TRACKLY_JOB_STATES = new Set(['new', 'check_later', 'not_interested', 'applied_confirmed', 'unknown']);
|
|
92
|
+
const BROWSER_HANDOFF_STATES = new Set([
|
|
93
|
+
'no_known_tab',
|
|
94
|
+
'controller_owned',
|
|
95
|
+
'user_inventory',
|
|
96
|
+
'visible',
|
|
97
|
+
'durable_handoff_proven',
|
|
98
|
+
'missing',
|
|
99
|
+
'closure_unverified',
|
|
100
|
+
]);
|
|
101
|
+
|
|
102
|
+
const PHASE_FIELDS = {
|
|
103
|
+
selection: new Set([
|
|
104
|
+
'workMode',
|
|
105
|
+
'executionId',
|
|
106
|
+
'batchId',
|
|
107
|
+
'latestExplicitTarget',
|
|
108
|
+
'approvedJobIds',
|
|
109
|
+
'approvalRecorded',
|
|
110
|
+
'noFormMutationBeforeApproval',
|
|
111
|
+
'queueExhausted',
|
|
112
|
+
]),
|
|
113
|
+
access: new Set([
|
|
114
|
+
'workMode',
|
|
115
|
+
'executionId',
|
|
116
|
+
'batchId',
|
|
117
|
+
'memberId',
|
|
118
|
+
'jobId',
|
|
119
|
+
'runId',
|
|
120
|
+
'inspectionEpoch',
|
|
121
|
+
'classification',
|
|
122
|
+
'exactRequisitionVerified',
|
|
123
|
+
'originPolicyVerified',
|
|
124
|
+
'nonMutatingProbe',
|
|
125
|
+
'privateDataEntered',
|
|
126
|
+
'applicantControlsObserved',
|
|
127
|
+
]),
|
|
128
|
+
fill: new Set([
|
|
129
|
+
'workMode',
|
|
130
|
+
'executionId',
|
|
131
|
+
'batchId',
|
|
132
|
+
'memberId',
|
|
133
|
+
'jobId',
|
|
134
|
+
'runId',
|
|
135
|
+
'inspectionEpoch',
|
|
136
|
+
'profileRevision',
|
|
137
|
+
'visibleControlCount',
|
|
138
|
+
'committedControlCount',
|
|
139
|
+
'typedExceptionCount',
|
|
140
|
+
'controlAccounting',
|
|
141
|
+
'formInventoryFingerprint',
|
|
142
|
+
'knownOmissionCount',
|
|
143
|
+
'knownFieldsFilledBeforeQuestions',
|
|
144
|
+
'answerLookupCompleted',
|
|
145
|
+
'answerLookupScopeCounts',
|
|
146
|
+
'answerLookupFingerprint',
|
|
147
|
+
'parserSensitiveFieldsRechecked',
|
|
148
|
+
'educationAndEmploymentVerified',
|
|
149
|
+
'historyReconciliation',
|
|
150
|
+
'resumeAudit',
|
|
151
|
+
'writingPresent',
|
|
152
|
+
'localWritingGate',
|
|
153
|
+
'humanizerAvailability',
|
|
154
|
+
'humanizerRan',
|
|
155
|
+
'humanizerFallbackUsed',
|
|
156
|
+
'questionPacketTrueGapsOnly',
|
|
157
|
+
]),
|
|
158
|
+
review: new Set([
|
|
159
|
+
'workMode',
|
|
160
|
+
'executionId',
|
|
161
|
+
'batchId',
|
|
162
|
+
'memberId',
|
|
163
|
+
'jobId',
|
|
164
|
+
'runId',
|
|
165
|
+
'inspectionEpoch',
|
|
166
|
+
'profileRevision',
|
|
167
|
+
'formInventoryFingerprint',
|
|
168
|
+
'finalIntegrityPassed',
|
|
169
|
+
'truthConfirmationRecorded',
|
|
170
|
+
'truthCertificationAttestationId',
|
|
171
|
+
'truthCertificationDependencyHash',
|
|
172
|
+
'truthCertificationExpiresAt',
|
|
173
|
+
'truthCertificationMemberRuns',
|
|
174
|
+
'truthCertificationRunSetHash',
|
|
175
|
+
'truthCertificationStatus',
|
|
176
|
+
'submitActivated',
|
|
177
|
+
'reviewTabPreserved',
|
|
178
|
+
'userVisibleHandoffProven',
|
|
179
|
+
'browserBindingHash',
|
|
180
|
+
'handoffEvidenceFingerprint',
|
|
181
|
+
'handoffEvidenceType',
|
|
182
|
+
'checkpointAction',
|
|
183
|
+
'continuationAllowed',
|
|
184
|
+
'resolvedActionCount',
|
|
185
|
+
'resolvedActionIdsFingerprint',
|
|
186
|
+
'checkpointStatus',
|
|
187
|
+
'checkpointMemberVersion',
|
|
188
|
+
'checkpointInspectionEpoch',
|
|
189
|
+
'checkpointLifecycle',
|
|
190
|
+
'checkpointActionCount',
|
|
191
|
+
'checkpointActionIdsFingerprint',
|
|
192
|
+
]),
|
|
193
|
+
handoff: new Set([
|
|
194
|
+
'workMode',
|
|
195
|
+
'executionId',
|
|
196
|
+
'batchId',
|
|
197
|
+
'memberId',
|
|
198
|
+
'jobId',
|
|
199
|
+
'runId',
|
|
200
|
+
'inspectionEpoch',
|
|
201
|
+
'employerApplicationState',
|
|
202
|
+
'tracklyMemberState',
|
|
203
|
+
'tracklyJobState',
|
|
204
|
+
'browserTabState',
|
|
205
|
+
'handoffVisibility',
|
|
206
|
+
'reviewReadyClaimed',
|
|
207
|
+
'browserBindingHash',
|
|
208
|
+
'handoffEvidenceFingerprint',
|
|
209
|
+
'handoffEvidenceType',
|
|
210
|
+
'checkpointStatus',
|
|
211
|
+
'checkpointMemberVersion',
|
|
212
|
+
'checkpointInspectionEpoch',
|
|
213
|
+
'checkpointLifecycle',
|
|
214
|
+
'checkpointAction',
|
|
215
|
+
'checkpointActionCount',
|
|
216
|
+
]),
|
|
217
|
+
reconciliation: new Set([
|
|
218
|
+
'workMode',
|
|
219
|
+
'executionId',
|
|
220
|
+
'batchId',
|
|
221
|
+
'memberId',
|
|
222
|
+
'jobId',
|
|
223
|
+
'runId',
|
|
224
|
+
'inspectionEpoch',
|
|
225
|
+
'positiveSubmissionEvidenceRecorded',
|
|
226
|
+
'memberLifecycle',
|
|
227
|
+
'tracklyJobStatus',
|
|
228
|
+
'cleanupPreference',
|
|
229
|
+
'browserTabStatus',
|
|
230
|
+
'completeTabInventoryRecorded',
|
|
231
|
+
'closeReceiptRecorded',
|
|
232
|
+
'postCloseUnionAbsenceProven',
|
|
233
|
+
]),
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
function requireTrue(errors, receipt, field) {
|
|
237
|
+
if (receipt[field] !== true) errors.push(`${field} must be true`);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function requireFalse(errors, receipt, field) {
|
|
241
|
+
if (receipt[field] !== false) errors.push(`${field} must be false`);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function requireTracklyId(errors, receipt, field) {
|
|
245
|
+
if (!Number.isSafeInteger(receipt[field]) || receipt[field] < 1) {
|
|
246
|
+
errors.push(`${field} must be a positive safe integer`);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function requireCount(errors, receipt, field) {
|
|
251
|
+
if (!Number.isSafeInteger(receipt[field]) || receipt[field] < 0) {
|
|
252
|
+
errors.push(`${field} must be a non-negative safe integer`);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function requireFingerprint(errors, value, field) {
|
|
257
|
+
if (typeof value !== 'string' || !SHA256_PATTERN.test(value)) {
|
|
258
|
+
errors.push(`${field} must be a lowercase SHA-256 fingerprint`);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function requirePositiveDecimalId(errors, value, field) {
|
|
263
|
+
if (typeof value !== 'string' || !/^[1-9][0-9]*$/.test(value)) {
|
|
264
|
+
errors.push(`${field} must be a positive decimal identifier`);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function requireFutureIsoDateTime(errors, value, field, validationTimeMs) {
|
|
269
|
+
const canonicalPattern = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
|
|
270
|
+
const parsedTime = typeof value === 'string' && canonicalPattern.test(value)
|
|
271
|
+
? Date.parse(value)
|
|
272
|
+
: Number.NaN;
|
|
273
|
+
if (Number.isNaN(parsedTime) || new Date(parsedTime).toISOString() !== value) {
|
|
274
|
+
errors.push(`${field} must be an ISO-8601 date-time`);
|
|
275
|
+
} else if (parsedTime <= validationTimeMs) {
|
|
276
|
+
errors.push(`${field} must be later than validation time`);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function canonicalJson(value) {
|
|
281
|
+
if (value === null || typeof value === 'boolean' || typeof value === 'number') {
|
|
282
|
+
return JSON.stringify(value);
|
|
283
|
+
}
|
|
284
|
+
if (typeof value === 'string') return JSON.stringify(value);
|
|
285
|
+
if (Array.isArray(value)) return `[${value.map(canonicalJson).join(',')}]`;
|
|
286
|
+
return `{${Object.entries(value)
|
|
287
|
+
.sort(([left], [right]) => (left < right ? -1 : left > right ? 1 : 0))
|
|
288
|
+
.map(([key, item]) => `${JSON.stringify(key)}:${canonicalJson(item)}`)
|
|
289
|
+
.join(',')}}`;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function validateTruthCertificationMemberRuns(errors, memberRuns, receipt, side) {
|
|
293
|
+
const push = (message) => errors.push(`${side}: ${message}`);
|
|
294
|
+
if (!Array.isArray(memberRuns) || memberRuns.length < 1 || memberRuns.length > 100) {
|
|
295
|
+
push('truthCertificationMemberRuns must contain 1 to 100 member bindings');
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
const allowedFields = ['memberId', 'runId', 'memberVersion', 'inspectionEpoch'];
|
|
299
|
+
const normalized = [];
|
|
300
|
+
for (const member of memberRuns) {
|
|
301
|
+
if (!member || typeof member !== 'object' || Array.isArray(member)
|
|
302
|
+
|| Object.keys(member).length !== allowedFields.length
|
|
303
|
+
|| Object.keys(member).some((field) => !allowedFields.includes(field))) {
|
|
304
|
+
push('truthCertificationMemberRuns entries must contain only memberId, runId, memberVersion, and inspectionEpoch');
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
if (!Number.isSafeInteger(member.memberId) || member.memberId < 1
|
|
308
|
+
|| !Number.isSafeInteger(member.runId) || member.runId < 1
|
|
309
|
+
|| !Number.isSafeInteger(member.memberVersion) || member.memberVersion < 1
|
|
310
|
+
|| !Number.isSafeInteger(member.inspectionEpoch) || member.inspectionEpoch < 0) {
|
|
311
|
+
push('truthCertificationMemberRuns entries contain an invalid identifier, version, or epoch');
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
normalized.push({
|
|
315
|
+
memberId: member.memberId,
|
|
316
|
+
runId: member.runId,
|
|
317
|
+
memberVersion: member.memberVersion,
|
|
318
|
+
inspectionEpoch: member.inspectionEpoch,
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
normalized.sort((left, right) => left.memberId - right.memberId || left.runId - right.runId);
|
|
322
|
+
if (new Set(normalized.map((member) => member.memberId)).size !== normalized.length) {
|
|
323
|
+
push('truthCertificationMemberRuns memberId values must be unique');
|
|
324
|
+
}
|
|
325
|
+
const runSetHash = createHash('sha256').update(canonicalJson(normalized)).digest('hex');
|
|
326
|
+
if (runSetHash !== receipt.truthCertificationRunSetHash) {
|
|
327
|
+
push('truthCertificationMemberRuns must match truthCertificationRunSetHash');
|
|
328
|
+
}
|
|
329
|
+
const currentMemberMatches = normalized.filter((member) => (
|
|
330
|
+
member.memberId === receipt.memberId
|
|
331
|
+
&& member.runId === receipt.runId
|
|
332
|
+
&& member.memberVersion === receipt.checkpointMemberVersion
|
|
333
|
+
&& member.inspectionEpoch === receipt.inspectionEpoch
|
|
334
|
+
));
|
|
335
|
+
if (currentMemberMatches.length !== 1) {
|
|
336
|
+
push('truth certification must cover the current member, run, checkpoint version, and inspection epoch');
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function requireExactObject(errors, value, field, allowedFields) {
|
|
341
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
342
|
+
errors.push(`${field} must be an object`);
|
|
343
|
+
return false;
|
|
344
|
+
}
|
|
345
|
+
for (const key of Object.keys(value)) {
|
|
346
|
+
if (!allowedFields.includes(key)) errors.push(`${field} contains an unexpected field`);
|
|
347
|
+
}
|
|
348
|
+
return true;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function validateCurrentLineage(
|
|
352
|
+
errors,
|
|
353
|
+
receipt,
|
|
354
|
+
expectedContext,
|
|
355
|
+
jobSetField = 'approvedJobIds',
|
|
356
|
+
additionalExpectedFields = [],
|
|
357
|
+
) {
|
|
358
|
+
if (!WORK_MODES.has(receipt.workMode)) errors.push('workMode must be accessible_execution or fixed_inspection');
|
|
359
|
+
for (const field of ['batchId', 'memberId', 'jobId', 'runId']) requireTracklyId(errors, receipt, field);
|
|
360
|
+
if (!Number.isSafeInteger(receipt.inspectionEpoch) || receipt.inspectionEpoch < 0) {
|
|
361
|
+
errors.push('inspectionEpoch must be a non-negative safe integer');
|
|
362
|
+
}
|
|
363
|
+
if (receipt.workMode === 'accessible_execution') {
|
|
364
|
+
requireTracklyId(errors, receipt, 'executionId');
|
|
365
|
+
} else if (receipt.workMode === 'fixed_inspection' && Object.hasOwn(receipt, 'executionId')) {
|
|
366
|
+
errors.push('executionId must be omitted for fixed_inspection');
|
|
367
|
+
}
|
|
368
|
+
if (!expectedContext || typeof expectedContext !== 'object' || Array.isArray(expectedContext)) {
|
|
369
|
+
errors.push('expectedContext is required');
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
const expectedFields = new Set([...COMMON_LINEAGE_FIELDS, jobSetField, ...additionalExpectedFields]);
|
|
373
|
+
if (receipt.workMode === 'accessible_execution') expectedFields.add('executionId');
|
|
374
|
+
for (const field of Object.keys(expectedContext)) {
|
|
375
|
+
if (!expectedFields.has(field)) errors.push('expectedContext contains an unexpected field');
|
|
376
|
+
}
|
|
377
|
+
for (const field of COMMON_LINEAGE_FIELDS) {
|
|
378
|
+
if (receipt[field] !== expectedContext[field]) errors.push(`${field} must match expectedContext`);
|
|
379
|
+
}
|
|
380
|
+
if (receipt.workMode === 'accessible_execution'
|
|
381
|
+
&& receipt.executionId !== expectedContext.executionId) {
|
|
382
|
+
errors.push('executionId must match expectedContext');
|
|
383
|
+
}
|
|
384
|
+
if (receipt.workMode === 'fixed_inspection' && Object.hasOwn(expectedContext, 'executionId')) {
|
|
385
|
+
errors.push('expectedContext.executionId must be omitted for fixed_inspection');
|
|
386
|
+
}
|
|
387
|
+
if (!Array.isArray(expectedContext[jobSetField])
|
|
388
|
+
|| expectedContext[jobSetField].some((id) => !Number.isSafeInteger(id) || id < 1)) {
|
|
389
|
+
errors.push(`expectedContext.${jobSetField} must contain positive safe integers`);
|
|
390
|
+
} else if (!expectedContext[jobSetField].includes(receipt.jobId)) {
|
|
391
|
+
errors.push(`jobId must belong to expectedContext.${jobSetField}`);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function validateSelection(receipt, expectedContext) {
|
|
396
|
+
const errors = [];
|
|
397
|
+
if (!WORK_MODES.has(receipt.workMode)) {
|
|
398
|
+
errors.push('workMode must be accessible_execution or fixed_inspection');
|
|
399
|
+
}
|
|
400
|
+
requireTracklyId(errors, receipt, 'batchId');
|
|
401
|
+
if (receipt.workMode === 'accessible_execution') {
|
|
402
|
+
requireTracklyId(errors, receipt, 'executionId');
|
|
403
|
+
} else if (receipt.workMode === 'fixed_inspection' && Object.hasOwn(receipt, 'executionId')) {
|
|
404
|
+
errors.push('executionId must be omitted for fixed_inspection');
|
|
405
|
+
}
|
|
406
|
+
const maximumTarget = receipt.workMode === 'fixed_inspection' ? 100 : 20;
|
|
407
|
+
if (!Number.isInteger(receipt.latestExplicitTarget)
|
|
408
|
+
|| receipt.latestExplicitTarget < 1
|
|
409
|
+
|| receipt.latestExplicitTarget > maximumTarget) {
|
|
410
|
+
errors.push(`latestExplicitTarget must be an integer from 1 to ${maximumTarget} for the selected work mode`);
|
|
411
|
+
}
|
|
412
|
+
if (!Array.isArray(receipt.approvedJobIds)) {
|
|
413
|
+
errors.push('approvedJobIds must be an array');
|
|
414
|
+
} else {
|
|
415
|
+
if (Number.isInteger(receipt.latestExplicitTarget)
|
|
416
|
+
&& receipt.approvedJobIds.length > receipt.latestExplicitTarget) {
|
|
417
|
+
errors.push('approvedJobIds must not exceed latestExplicitTarget');
|
|
418
|
+
}
|
|
419
|
+
const normalized = receipt.approvedJobIds.filter((id) => Number.isSafeInteger(id) && id > 0);
|
|
420
|
+
if (normalized.length !== receipt.approvedJobIds.length) errors.push('approvedJobIds must contain positive safe integers');
|
|
421
|
+
if (new Set(normalized).size !== normalized.length) errors.push('approvedJobIds must be unique');
|
|
422
|
+
if (receipt.approvedJobIds.length === 0 && receipt.queueExhausted !== true) {
|
|
423
|
+
errors.push('approvedJobIds may be empty only when queueExhausted is true');
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
requireTrue(errors, receipt, 'approvalRecorded');
|
|
427
|
+
requireTrue(errors, receipt, 'noFormMutationBeforeApproval');
|
|
428
|
+
if (typeof receipt.queueExhausted !== 'boolean') errors.push('queueExhausted must be a boolean');
|
|
429
|
+
if (!expectedContext || typeof expectedContext !== 'object' || Array.isArray(expectedContext)) {
|
|
430
|
+
errors.push('expectedContext is required');
|
|
431
|
+
return errors;
|
|
432
|
+
}
|
|
433
|
+
const expectedFields = new Set(['workMode', 'batchId', 'latestExplicitTarget', 'selectableJobIds', 'queueExhausted']);
|
|
434
|
+
if (receipt.workMode === 'accessible_execution') expectedFields.add('executionId');
|
|
435
|
+
for (const field of Object.keys(expectedContext)) {
|
|
436
|
+
if (!expectedFields.has(field)) errors.push('expectedContext contains an unexpected field');
|
|
437
|
+
}
|
|
438
|
+
if (receipt.workMode !== expectedContext.workMode) errors.push('workMode must match expectedContext');
|
|
439
|
+
if (receipt.batchId !== expectedContext.batchId) errors.push('batchId must match expectedContext');
|
|
440
|
+
if (receipt.workMode === 'accessible_execution'
|
|
441
|
+
&& receipt.executionId !== expectedContext.executionId) {
|
|
442
|
+
errors.push('executionId must match expectedContext');
|
|
443
|
+
}
|
|
444
|
+
if (receipt.workMode === 'fixed_inspection' && Object.hasOwn(expectedContext, 'executionId')) {
|
|
445
|
+
errors.push('expectedContext.executionId must be omitted for fixed_inspection');
|
|
446
|
+
}
|
|
447
|
+
if (receipt.latestExplicitTarget !== expectedContext.latestExplicitTarget) {
|
|
448
|
+
errors.push('latestExplicitTarget must match expectedContext');
|
|
449
|
+
}
|
|
450
|
+
if (receipt.queueExhausted !== expectedContext.queueExhausted) {
|
|
451
|
+
errors.push('queueExhausted must match expectedContext');
|
|
452
|
+
}
|
|
453
|
+
if (!Array.isArray(expectedContext.selectableJobIds)
|
|
454
|
+
|| expectedContext.selectableJobIds.some((id) => !Number.isSafeInteger(id) || id < 1)
|
|
455
|
+
|| new Set(expectedContext.selectableJobIds).size !== expectedContext.selectableJobIds.length) {
|
|
456
|
+
errors.push('expectedContext.selectableJobIds must contain unique positive safe integers');
|
|
457
|
+
} else if (Array.isArray(receipt.approvedJobIds)
|
|
458
|
+
&& receipt.approvedJobIds.some((id) => !expectedContext.selectableJobIds.includes(id))) {
|
|
459
|
+
errors.push('approvedJobIds must belong to expectedContext.selectableJobIds');
|
|
460
|
+
}
|
|
461
|
+
return errors;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function validateAccess(receipt, expectedContext) {
|
|
465
|
+
const errors = [];
|
|
466
|
+
validateCurrentLineage(errors, receipt, expectedContext, 'waveJobIds');
|
|
467
|
+
if (!ACCESS_STATES.has(receipt.classification)) errors.push('classification must be a terminal access state');
|
|
468
|
+
requireTrue(errors, receipt, 'exactRequisitionVerified');
|
|
469
|
+
requireTrue(errors, receipt, 'originPolicyVerified');
|
|
470
|
+
requireTrue(errors, receipt, 'nonMutatingProbe');
|
|
471
|
+
requireFalse(errors, receipt, 'privateDataEntered');
|
|
472
|
+
if (['accessible', 'captcha_at_submit'].includes(receipt.classification)) {
|
|
473
|
+
requireTrue(errors, receipt, 'applicantControlsObserved');
|
|
474
|
+
} else {
|
|
475
|
+
requireFalse(errors, receipt, 'applicantControlsObserved');
|
|
476
|
+
}
|
|
477
|
+
return errors;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
function validateFill(receipt, expectedContext) {
|
|
481
|
+
const errors = [];
|
|
482
|
+
validateCurrentLineage(errors, receipt, expectedContext, 'approvedJobIds', [
|
|
483
|
+
'profileRevision',
|
|
484
|
+
'canonicalEducationRecordCount',
|
|
485
|
+
'canonicalEmploymentPositionCount',
|
|
486
|
+
'formInventoryFingerprint',
|
|
487
|
+
'resumeControl',
|
|
488
|
+
]);
|
|
489
|
+
if (expectedContext && typeof expectedContext === 'object' && !Array.isArray(expectedContext)) {
|
|
490
|
+
if (receipt.workMode === 'accessible_execution') {
|
|
491
|
+
requireTracklyId(errors, receipt, 'profileRevision');
|
|
492
|
+
requireTracklyId(errors, expectedContext, 'profileRevision');
|
|
493
|
+
} else {
|
|
494
|
+
requireCount(errors, receipt, 'profileRevision');
|
|
495
|
+
requireCount(errors, expectedContext, 'profileRevision');
|
|
496
|
+
}
|
|
497
|
+
if (receipt.profileRevision !== expectedContext.profileRevision) {
|
|
498
|
+
errors.push('profileRevision must match expectedContext');
|
|
499
|
+
}
|
|
500
|
+
requireCount(errors, expectedContext, 'canonicalEducationRecordCount');
|
|
501
|
+
requireCount(errors, expectedContext, 'canonicalEmploymentPositionCount');
|
|
502
|
+
requireFingerprint(errors, expectedContext.formInventoryFingerprint, 'expectedContext.formInventoryFingerprint');
|
|
503
|
+
if (!['required', 'optional', 'absent'].includes(expectedContext.resumeControl)) {
|
|
504
|
+
errors.push('expectedContext.resumeControl must be required, optional, or absent');
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
for (const field of ['visibleControlCount', 'committedControlCount', 'typedExceptionCount', 'knownOmissionCount']) {
|
|
508
|
+
requireCount(errors, receipt, field);
|
|
509
|
+
}
|
|
510
|
+
if (receipt.visibleControlCount === 0) errors.push('visibleControlCount must be at least 1 for a fill checkpoint');
|
|
511
|
+
if (Number.isSafeInteger(receipt.visibleControlCount)
|
|
512
|
+
&& Number.isSafeInteger(receipt.committedControlCount)
|
|
513
|
+
&& Number.isSafeInteger(receipt.typedExceptionCount)
|
|
514
|
+
&& receipt.committedControlCount + receipt.typedExceptionCount !== receipt.visibleControlCount) {
|
|
515
|
+
errors.push('committedControlCount plus typedExceptionCount must equal visibleControlCount');
|
|
516
|
+
}
|
|
517
|
+
if (requireExactObject(errors, receipt.controlAccounting, 'controlAccounting', CONTROL_ACCOUNTING_FIELDS)) {
|
|
518
|
+
for (const field of CONTROL_ACCOUNTING_FIELDS) requireCount(errors, receipt.controlAccounting, field);
|
|
519
|
+
const accountingCount = CONTROL_ACCOUNTING_FIELDS
|
|
520
|
+
.reduce((total, field) => total + (Number.isSafeInteger(receipt.controlAccounting[field]) ? receipt.controlAccounting[field] : 0), 0);
|
|
521
|
+
const committedCount = COMMITTED_CONTROL_FIELDS
|
|
522
|
+
.reduce((total, field) => total + (Number.isSafeInteger(receipt.controlAccounting[field]) ? receipt.controlAccounting[field] : 0), 0);
|
|
523
|
+
const exceptionCount = EXCEPTION_CONTROL_FIELDS
|
|
524
|
+
.reduce((total, field) => total + (Number.isSafeInteger(receipt.controlAccounting[field]) ? receipt.controlAccounting[field] : 0), 0);
|
|
525
|
+
if (Number.isSafeInteger(receipt.visibleControlCount) && accountingCount !== receipt.visibleControlCount) {
|
|
526
|
+
errors.push('controlAccounting counts must equal visibleControlCount');
|
|
527
|
+
}
|
|
528
|
+
if (Number.isSafeInteger(receipt.committedControlCount) && committedCount !== receipt.committedControlCount) {
|
|
529
|
+
errors.push('committed control accounting must equal committedControlCount');
|
|
530
|
+
}
|
|
531
|
+
if (Number.isSafeInteger(receipt.typedExceptionCount) && exceptionCount !== receipt.typedExceptionCount) {
|
|
532
|
+
errors.push('typed exception accounting must equal typedExceptionCount');
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
requireFingerprint(errors, receipt.formInventoryFingerprint, 'formInventoryFingerprint');
|
|
536
|
+
if (receipt.formInventoryFingerprint !== expectedContext?.formInventoryFingerprint) {
|
|
537
|
+
errors.push('formInventoryFingerprint must match expectedContext');
|
|
538
|
+
}
|
|
539
|
+
if (receipt.knownOmissionCount !== 0) errors.push('knownOmissionCount must be 0');
|
|
540
|
+
requireTrue(errors, receipt, 'knownFieldsFilledBeforeQuestions');
|
|
541
|
+
requireTrue(errors, receipt, 'answerLookupCompleted');
|
|
542
|
+
if (requireExactObject(errors, receipt.answerLookupScopeCounts, 'answerLookupScopeCounts', ANSWER_SCOPE_FIELDS)) {
|
|
543
|
+
for (const field of ANSWER_SCOPE_FIELDS) requireCount(errors, receipt.answerLookupScopeCounts, field);
|
|
544
|
+
}
|
|
545
|
+
requireFingerprint(errors, receipt.answerLookupFingerprint, 'answerLookupFingerprint');
|
|
546
|
+
requireTrue(errors, receipt, 'parserSensitiveFieldsRechecked');
|
|
547
|
+
requireTrue(errors, receipt, 'educationAndEmploymentVerified');
|
|
548
|
+
if (requireExactObject(errors, receipt.historyReconciliation, 'historyReconciliation', HISTORY_FIELDS)) {
|
|
549
|
+
for (const field of [
|
|
550
|
+
'canonicalEducationRecordCount',
|
|
551
|
+
'accountedEducationRecordCount',
|
|
552
|
+
'canonicalEmploymentPositionCount',
|
|
553
|
+
'accountedEmploymentPositionCount',
|
|
554
|
+
]) requireCount(errors, receipt.historyReconciliation, field);
|
|
555
|
+
if (receipt.historyReconciliation.canonicalEducationRecordCount
|
|
556
|
+
!== receipt.historyReconciliation.accountedEducationRecordCount) {
|
|
557
|
+
errors.push('education record counts must match');
|
|
558
|
+
}
|
|
559
|
+
if (receipt.historyReconciliation.canonicalEmploymentPositionCount
|
|
560
|
+
!== receipt.historyReconciliation.accountedEmploymentPositionCount) {
|
|
561
|
+
errors.push('employment position counts must match');
|
|
562
|
+
}
|
|
563
|
+
if (receipt.historyReconciliation.canonicalEducationRecordCount
|
|
564
|
+
!== expectedContext?.canonicalEducationRecordCount) {
|
|
565
|
+
errors.push('canonical education record count must match expectedContext');
|
|
566
|
+
}
|
|
567
|
+
if (receipt.historyReconciliation.canonicalEmploymentPositionCount
|
|
568
|
+
!== expectedContext?.canonicalEmploymentPositionCount) {
|
|
569
|
+
errors.push('canonical employment position count must match expectedContext');
|
|
570
|
+
}
|
|
571
|
+
requireTrue(errors, receipt.historyReconciliation, 'educationOrderVerified');
|
|
572
|
+
requireTrue(errors, receipt.historyReconciliation, 'employmentOrderVerified');
|
|
573
|
+
requireFalse(errors, receipt.historyReconciliation, 'datePrecisionInvented');
|
|
574
|
+
requireFingerprint(errors, receipt.historyReconciliation.educationReconciliationFingerprint, 'historyReconciliation.educationReconciliationFingerprint');
|
|
575
|
+
requireFingerprint(errors, receipt.historyReconciliation.employmentReconciliationFingerprint, 'historyReconciliation.employmentReconciliationFingerprint');
|
|
576
|
+
}
|
|
577
|
+
if (requireExactObject(errors, receipt.resumeAudit, 'resumeAudit', RESUME_AUDIT_FIELDS)) {
|
|
578
|
+
if (!['required', 'optional', 'absent'].includes(receipt.resumeAudit.control)) {
|
|
579
|
+
errors.push('resumeAudit.control must be required, optional, or absent');
|
|
580
|
+
}
|
|
581
|
+
if (receipt.resumeAudit.control !== expectedContext?.resumeControl) {
|
|
582
|
+
errors.push('resumeAudit.control must match expectedContext');
|
|
583
|
+
}
|
|
584
|
+
const attachmentExpected = ['required', 'optional'].includes(receipt.resumeAudit.control);
|
|
585
|
+
const allowedModes = attachmentExpected
|
|
586
|
+
? ['automated_verified', 'manual_unbound']
|
|
587
|
+
: ['not_applicable'];
|
|
588
|
+
if (!allowedModes.includes(receipt.resumeAudit.mode)) {
|
|
589
|
+
errors.push('resumeAudit.mode must match the declared control mode');
|
|
590
|
+
}
|
|
591
|
+
const expectedOutcomes = attachmentExpected
|
|
592
|
+
? {
|
|
593
|
+
approval: 'passed',
|
|
594
|
+
preAttachVerification: receipt.resumeAudit.mode === 'manual_unbound' ? 'not_applicable' : 'passed',
|
|
595
|
+
attachmentCommit: receipt.resumeAudit.mode === 'manual_unbound' ? 'user_confirmed' : 'passed',
|
|
596
|
+
filenameVerification: 'passed',
|
|
597
|
+
parserRecheck: 'passed',
|
|
598
|
+
finalSweep: 'passed',
|
|
599
|
+
}
|
|
600
|
+
: Object.fromEntries(RESUME_AUDIT_FIELDS.slice(2).map((field) => [field, 'not_applicable']));
|
|
601
|
+
for (const [field, expected] of Object.entries(expectedOutcomes)) {
|
|
602
|
+
if (receipt.resumeAudit[field] !== expected) {
|
|
603
|
+
errors.push(`resumeAudit.${field} must match the declared control mode`);
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
if (typeof receipt.writingPresent !== 'boolean') errors.push('writingPresent must be a boolean');
|
|
608
|
+
if (!['passed', 'not_applicable'].includes(receipt.localWritingGate)) {
|
|
609
|
+
errors.push('localWritingGate must be passed or not_applicable');
|
|
610
|
+
}
|
|
611
|
+
if (!['available', 'unavailable', 'not_applicable'].includes(receipt.humanizerAvailability)) {
|
|
612
|
+
errors.push('humanizerAvailability is invalid');
|
|
613
|
+
}
|
|
614
|
+
if (typeof receipt.humanizerRan !== 'boolean') errors.push('humanizerRan must be a boolean');
|
|
615
|
+
if (typeof receipt.humanizerFallbackUsed !== 'boolean') errors.push('humanizerFallbackUsed must be a boolean');
|
|
616
|
+
if (receipt.writingPresent === true) {
|
|
617
|
+
if (receipt.localWritingGate !== 'passed') errors.push('localWritingGate must pass when writing is present');
|
|
618
|
+
if (receipt.humanizerAvailability === 'available') {
|
|
619
|
+
requireTrue(errors, receipt, 'humanizerRan');
|
|
620
|
+
requireFalse(errors, receipt, 'humanizerFallbackUsed');
|
|
621
|
+
} else if (receipt.humanizerAvailability === 'unavailable') {
|
|
622
|
+
requireFalse(errors, receipt, 'humanizerRan');
|
|
623
|
+
requireTrue(errors, receipt, 'humanizerFallbackUsed');
|
|
624
|
+
} else {
|
|
625
|
+
errors.push('humanizerAvailability must be available or unavailable when writing is present');
|
|
626
|
+
}
|
|
627
|
+
} else if (receipt.writingPresent === false) {
|
|
628
|
+
if (receipt.localWritingGate !== 'not_applicable') errors.push('localWritingGate must be not_applicable when writing is absent');
|
|
629
|
+
if (receipt.humanizerAvailability !== 'not_applicable') errors.push('humanizerAvailability must be not_applicable when writing is absent');
|
|
630
|
+
requireFalse(errors, receipt, 'humanizerRan');
|
|
631
|
+
requireFalse(errors, receipt, 'humanizerFallbackUsed');
|
|
632
|
+
}
|
|
633
|
+
requireTrue(errors, receipt, 'questionPacketTrueGapsOnly');
|
|
634
|
+
return errors;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
function validateReview(receipt, expectedContext, priorFill, validationTimeMs) {
|
|
638
|
+
const errors = [];
|
|
639
|
+
const fillBaselineFields = [
|
|
640
|
+
'profileRevision',
|
|
641
|
+
'formInventoryFingerprint',
|
|
642
|
+
];
|
|
643
|
+
const resolutionFields = [
|
|
644
|
+
'resolvedActionCount',
|
|
645
|
+
'resolvedActionIdsFingerprint',
|
|
646
|
+
];
|
|
647
|
+
const checkpointFields = [
|
|
648
|
+
'checkpointAction',
|
|
649
|
+
'continuationAllowed',
|
|
650
|
+
'checkpointStatus',
|
|
651
|
+
'checkpointMemberVersion',
|
|
652
|
+
'checkpointInspectionEpoch',
|
|
653
|
+
'checkpointLifecycle',
|
|
654
|
+
'checkpointActionCount',
|
|
655
|
+
'checkpointActionIdsFingerprint',
|
|
656
|
+
];
|
|
657
|
+
const truthCertificationFields = [
|
|
658
|
+
'truthCertificationAttestationId',
|
|
659
|
+
'truthCertificationDependencyHash',
|
|
660
|
+
'truthCertificationExpiresAt',
|
|
661
|
+
'truthCertificationMemberRuns',
|
|
662
|
+
'truthCertificationRunSetHash',
|
|
663
|
+
'truthCertificationStatus',
|
|
664
|
+
];
|
|
665
|
+
const visibilityFields = [
|
|
666
|
+
'browserBindingHash',
|
|
667
|
+
'handoffEvidenceFingerprint',
|
|
668
|
+
'handoffEvidenceType',
|
|
669
|
+
];
|
|
670
|
+
validateCurrentLineage(
|
|
671
|
+
errors,
|
|
672
|
+
receipt,
|
|
673
|
+
expectedContext,
|
|
674
|
+
'approvedJobIds',
|
|
675
|
+
[
|
|
676
|
+
...fillBaselineFields,
|
|
677
|
+
...resolutionFields,
|
|
678
|
+
...checkpointFields,
|
|
679
|
+
...truthCertificationFields,
|
|
680
|
+
...visibilityFields,
|
|
681
|
+
],
|
|
682
|
+
);
|
|
683
|
+
if (receipt.workMode === 'accessible_execution') {
|
|
684
|
+
requireTracklyId(errors, receipt, 'profileRevision');
|
|
685
|
+
} else {
|
|
686
|
+
requireCount(errors, receipt, 'profileRevision');
|
|
687
|
+
}
|
|
688
|
+
requireFingerprint(errors, receipt.formInventoryFingerprint, 'formInventoryFingerprint');
|
|
689
|
+
if (!priorFill || typeof priorFill !== 'object' || Array.isArray(priorFill)) {
|
|
690
|
+
errors.push('priorFill is required for review');
|
|
691
|
+
} else {
|
|
692
|
+
const priorFillFields = Object.keys(priorFill);
|
|
693
|
+
if (priorFillFields.length !== 2
|
|
694
|
+
|| !priorFillFields.includes('receipt')
|
|
695
|
+
|| !priorFillFields.includes('expectedContext')) {
|
|
696
|
+
errors.push('priorFill must contain only receipt and expectedContext');
|
|
697
|
+
} else {
|
|
698
|
+
const priorFillErrors = validateCheckpoint(
|
|
699
|
+
'fill',
|
|
700
|
+
priorFill.receipt,
|
|
701
|
+
priorFill.expectedContext,
|
|
702
|
+
);
|
|
703
|
+
errors.push(...priorFillErrors.map((error) => `priorFill: ${error}`));
|
|
704
|
+
if (priorFill.receipt && typeof priorFill.receipt === 'object' && !Array.isArray(priorFill.receipt)) {
|
|
705
|
+
for (const field of [
|
|
706
|
+
'missingFact',
|
|
707
|
+
'liveConsent',
|
|
708
|
+
'authenticationBlocker',
|
|
709
|
+
'unobservableCommit',
|
|
710
|
+
'unsupportedControl',
|
|
711
|
+
]) {
|
|
712
|
+
if (priorFill.receipt.controlAccounting?.[field] !== 0) {
|
|
713
|
+
errors.push(`priorFill.controlAccounting.${field} must be 0 before review`);
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
const lineageFields = [...COMMON_LINEAGE_FIELDS];
|
|
717
|
+
if (receipt.workMode === 'accessible_execution') lineageFields.push('executionId');
|
|
718
|
+
for (const field of [...lineageFields, ...fillBaselineFields]) {
|
|
719
|
+
if (receipt[field] !== priorFill.receipt[field]) {
|
|
720
|
+
errors.push(`${field} must match the validated priorFill receipt`);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
requireTrue(errors, receipt, 'finalIntegrityPassed');
|
|
727
|
+
requireTrue(errors, receipt, 'truthConfirmationRecorded');
|
|
728
|
+
requirePositiveDecimalId(
|
|
729
|
+
errors,
|
|
730
|
+
receipt.truthCertificationAttestationId,
|
|
731
|
+
'truthCertificationAttestationId',
|
|
732
|
+
);
|
|
733
|
+
requireFingerprint(
|
|
734
|
+
errors,
|
|
735
|
+
receipt.truthCertificationDependencyHash,
|
|
736
|
+
'truthCertificationDependencyHash',
|
|
737
|
+
);
|
|
738
|
+
requireFutureIsoDateTime(
|
|
739
|
+
errors,
|
|
740
|
+
receipt.truthCertificationExpiresAt,
|
|
741
|
+
'truthCertificationExpiresAt',
|
|
742
|
+
validationTimeMs,
|
|
743
|
+
);
|
|
744
|
+
requireFingerprint(
|
|
745
|
+
errors,
|
|
746
|
+
receipt.truthCertificationRunSetHash,
|
|
747
|
+
'truthCertificationRunSetHash',
|
|
748
|
+
);
|
|
749
|
+
validateTruthCertificationMemberRuns(errors, receipt.truthCertificationMemberRuns, receipt, 'receipt');
|
|
750
|
+
validateTruthCertificationMemberRuns(
|
|
751
|
+
errors,
|
|
752
|
+
expectedContext?.truthCertificationMemberRuns,
|
|
753
|
+
expectedContext || {},
|
|
754
|
+
'expectedContext',
|
|
755
|
+
);
|
|
756
|
+
if (Array.isArray(receipt.truthCertificationMemberRuns)
|
|
757
|
+
&& Array.isArray(expectedContext?.truthCertificationMemberRuns)
|
|
758
|
+
&& canonicalJson(receipt.truthCertificationMemberRuns)
|
|
759
|
+
!== canonicalJson(expectedContext.truthCertificationMemberRuns)) {
|
|
760
|
+
errors.push('truthCertificationMemberRuns must match the authoritative expectedContext');
|
|
761
|
+
}
|
|
762
|
+
if (!['recorded', 'replayed'].includes(receipt.truthCertificationStatus)) {
|
|
763
|
+
errors.push('truthCertificationStatus must be recorded or replayed');
|
|
764
|
+
}
|
|
765
|
+
requireFalse(errors, receipt, 'submitActivated');
|
|
766
|
+
requireTrue(errors, receipt, 'reviewTabPreserved');
|
|
767
|
+
requireTrue(errors, receipt, 'userVisibleHandoffProven');
|
|
768
|
+
requireFingerprint(errors, receipt.browserBindingHash, 'browserBindingHash');
|
|
769
|
+
requireFingerprint(errors, receipt.handoffEvidenceFingerprint, 'handoffEvidenceFingerprint');
|
|
770
|
+
if (!['visible_presentation_receipt', 'user_visible_handoff_receipt'].includes(receipt.handoffEvidenceType)) {
|
|
771
|
+
errors.push('handoffEvidenceType must identify visible presentation or an exact tab-bound user-visible handoff receipt');
|
|
772
|
+
}
|
|
773
|
+
if (receipt.checkpointAction !== 'review/manual_submit') {
|
|
774
|
+
errors.push('checkpointAction must be review/manual_submit');
|
|
775
|
+
}
|
|
776
|
+
if (receipt.continuationAllowed !== false) {
|
|
777
|
+
errors.push('continuationAllowed must be false for review/manual_submit');
|
|
778
|
+
}
|
|
779
|
+
requireCount(errors, receipt, 'resolvedActionCount');
|
|
780
|
+
requireFingerprint(errors, receipt.resolvedActionIdsFingerprint, 'resolvedActionIdsFingerprint');
|
|
781
|
+
if (!['recorded', 'replayed'].includes(receipt.checkpointStatus)) {
|
|
782
|
+
errors.push('checkpointStatus must be recorded or replayed');
|
|
783
|
+
}
|
|
784
|
+
requireTracklyId(errors, receipt, 'checkpointMemberVersion');
|
|
785
|
+
if (!Number.isSafeInteger(receipt.checkpointInspectionEpoch) || receipt.checkpointInspectionEpoch < 0) {
|
|
786
|
+
errors.push('checkpointInspectionEpoch must be a non-negative safe integer');
|
|
787
|
+
}
|
|
788
|
+
if (receipt.checkpointInspectionEpoch !== receipt.inspectionEpoch) {
|
|
789
|
+
errors.push('checkpointInspectionEpoch must equal inspectionEpoch');
|
|
790
|
+
}
|
|
791
|
+
if (receipt.checkpointLifecycle !== 'review_ready') {
|
|
792
|
+
errors.push('checkpointLifecycle must be review_ready');
|
|
793
|
+
}
|
|
794
|
+
requireCount(errors, receipt, 'checkpointActionCount');
|
|
795
|
+
if (receipt.checkpointActionCount !== 1) {
|
|
796
|
+
errors.push('review/manual_submit checkpoint must contain exactly one action');
|
|
797
|
+
}
|
|
798
|
+
requireFingerprint(errors, receipt.checkpointActionIdsFingerprint, 'checkpointActionIdsFingerprint');
|
|
799
|
+
if (expectedContext && typeof expectedContext === 'object' && !Array.isArray(expectedContext)) {
|
|
800
|
+
for (const field of [
|
|
801
|
+
...fillBaselineFields,
|
|
802
|
+
...resolutionFields,
|
|
803
|
+
...checkpointFields,
|
|
804
|
+
...truthCertificationFields.filter((field) => field !== 'truthCertificationMemberRuns'),
|
|
805
|
+
...visibilityFields,
|
|
806
|
+
]) {
|
|
807
|
+
if (receipt[field] !== expectedContext[field]) errors.push(`${field} must match expectedContext`);
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
return errors;
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
function validateHandoff(receipt, expectedContext) {
|
|
814
|
+
const errors = [];
|
|
815
|
+
const evidenceFields = ['browserBindingHash', 'handoffEvidenceFingerprint', 'handoffEvidenceType'];
|
|
816
|
+
const stateAuthorityFields = [
|
|
817
|
+
'employerApplicationState',
|
|
818
|
+
'tracklyMemberState',
|
|
819
|
+
'tracklyJobState',
|
|
820
|
+
];
|
|
821
|
+
const checkpointAuthorityFields = [
|
|
822
|
+
'checkpointStatus',
|
|
823
|
+
'checkpointMemberVersion',
|
|
824
|
+
'checkpointInspectionEpoch',
|
|
825
|
+
'checkpointLifecycle',
|
|
826
|
+
'checkpointAction',
|
|
827
|
+
'checkpointActionCount',
|
|
828
|
+
];
|
|
829
|
+
const reviewAuthorityFields = [...stateAuthorityFields, ...checkpointAuthorityFields];
|
|
830
|
+
validateCurrentLineage(
|
|
831
|
+
errors,
|
|
832
|
+
receipt,
|
|
833
|
+
expectedContext,
|
|
834
|
+
'approvedJobIds',
|
|
835
|
+
[...evidenceFields, ...reviewAuthorityFields],
|
|
836
|
+
);
|
|
837
|
+
if (!EMPLOYER_APPLICATION_STATES.has(receipt.employerApplicationState)) {
|
|
838
|
+
errors.push('employerApplicationState is invalid');
|
|
839
|
+
}
|
|
840
|
+
if (!TRACKLY_MEMBER_STATES.has(receipt.tracklyMemberState)) {
|
|
841
|
+
errors.push('tracklyMemberState is invalid');
|
|
842
|
+
}
|
|
843
|
+
if (!TRACKLY_JOB_STATES.has(receipt.tracklyJobState)) {
|
|
844
|
+
errors.push('tracklyJobState is invalid');
|
|
845
|
+
}
|
|
846
|
+
if (expectedContext && typeof expectedContext === 'object' && !Array.isArray(expectedContext)) {
|
|
847
|
+
for (const field of stateAuthorityFields) {
|
|
848
|
+
if (receipt[field] !== expectedContext[field]) errors.push(`${field} must match expectedContext`);
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
if (receipt.browserTabState === 'closed_verified') {
|
|
852
|
+
errors.push('closed_verified is reserved for reconciliation receipts');
|
|
853
|
+
} else if (!BROWSER_HANDOFF_STATES.has(receipt.browserTabState)) {
|
|
854
|
+
errors.push('browserTabState is invalid');
|
|
855
|
+
}
|
|
856
|
+
if (!['verified', 'unverified'].includes(receipt.handoffVisibility)) {
|
|
857
|
+
errors.push('handoffVisibility must be verified or unverified');
|
|
858
|
+
}
|
|
859
|
+
if (typeof receipt.reviewReadyClaimed !== 'boolean') {
|
|
860
|
+
errors.push('reviewReadyClaimed must be a boolean');
|
|
861
|
+
}
|
|
862
|
+
if (receipt.handoffVisibility === 'unverified' && receipt.reviewReadyClaimed !== false) {
|
|
863
|
+
errors.push('reviewReadyClaimed must be false when handoff visibility is unverified');
|
|
864
|
+
}
|
|
865
|
+
if (receipt.browserTabState === 'durable_handoff_proven'
|
|
866
|
+
&& receipt.handoffVisibility !== 'verified') {
|
|
867
|
+
errors.push('durable_handoff_proven requires verified visibility');
|
|
868
|
+
}
|
|
869
|
+
if (receipt.handoffVisibility === 'verified'
|
|
870
|
+
&& !['visible', 'durable_handoff_proven'].includes(receipt.browserTabState)) {
|
|
871
|
+
errors.push('verified handoff requires a visible or durably handed-off browser tab');
|
|
872
|
+
}
|
|
873
|
+
if (receipt.handoffVisibility === 'verified') {
|
|
874
|
+
requireFingerprint(errors, receipt.browserBindingHash, 'browserBindingHash');
|
|
875
|
+
requireFingerprint(errors, receipt.handoffEvidenceFingerprint, 'handoffEvidenceFingerprint');
|
|
876
|
+
if (![
|
|
877
|
+
'visible_presentation_receipt',
|
|
878
|
+
'user_visible_handoff_receipt',
|
|
879
|
+
'durable_handoff_receipt',
|
|
880
|
+
].includes(receipt.handoffEvidenceType)) {
|
|
881
|
+
errors.push('handoffEvidenceType must identify visible presentation, a user-visible handoff receipt, or a durable handoff receipt');
|
|
882
|
+
}
|
|
883
|
+
if (receipt.browserTabState === 'visible'
|
|
884
|
+
&& !['visible_presentation_receipt', 'user_visible_handoff_receipt'].includes(receipt.handoffEvidenceType)) {
|
|
885
|
+
errors.push('visible browser state requires presentation or exact tab-bound user-visible handoff evidence');
|
|
886
|
+
}
|
|
887
|
+
if (receipt.browserTabState === 'durable_handoff_proven'
|
|
888
|
+
&& receipt.handoffEvidenceType !== 'durable_handoff_receipt') {
|
|
889
|
+
errors.push('durable_handoff_proven requires durable_handoff_receipt evidence');
|
|
890
|
+
}
|
|
891
|
+
if (expectedContext && typeof expectedContext === 'object' && !Array.isArray(expectedContext)) {
|
|
892
|
+
for (const field of evidenceFields) {
|
|
893
|
+
if (receipt[field] !== expectedContext[field]) errors.push(`${field} must match expectedContext`);
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
} else if (evidenceFields.some((field) => Object.hasOwn(receipt, field))) {
|
|
897
|
+
errors.push('handoff evidence fields must be omitted when visibility is unverified');
|
|
898
|
+
}
|
|
899
|
+
if (receipt.reviewReadyClaimed === true
|
|
900
|
+
&& (receipt.employerApplicationState !== 'review_state_prepared'
|
|
901
|
+
|| receipt.tracklyMemberState !== 'awaiting_manual_submit'
|
|
902
|
+
|| receipt.tracklyJobState !== 'check_later'
|
|
903
|
+
|| receipt.handoffVisibility !== 'verified')) {
|
|
904
|
+
errors.push('reviewReadyClaimed requires prepared employer state, awaiting_manual_submit member state, check_later job state, and verified visibility');
|
|
905
|
+
}
|
|
906
|
+
if (receipt.reviewReadyClaimed === true) {
|
|
907
|
+
if (!['recorded', 'replayed'].includes(receipt.checkpointStatus)) {
|
|
908
|
+
errors.push('reviewReadyClaimed requires a recorded or replayed checkpointStatus');
|
|
909
|
+
}
|
|
910
|
+
requireTracklyId(errors, receipt, 'checkpointMemberVersion');
|
|
911
|
+
if (!Number.isSafeInteger(receipt.checkpointInspectionEpoch) || receipt.checkpointInspectionEpoch < 0) {
|
|
912
|
+
errors.push('checkpointInspectionEpoch must be a non-negative safe integer');
|
|
913
|
+
}
|
|
914
|
+
if (receipt.checkpointInspectionEpoch !== receipt.inspectionEpoch) {
|
|
915
|
+
errors.push('checkpointInspectionEpoch must equal inspectionEpoch');
|
|
916
|
+
}
|
|
917
|
+
if (receipt.checkpointLifecycle !== 'review_ready') {
|
|
918
|
+
errors.push('reviewReadyClaimed requires checkpointLifecycle review_ready');
|
|
919
|
+
}
|
|
920
|
+
if (receipt.checkpointAction !== 'review/manual_submit') {
|
|
921
|
+
errors.push('reviewReadyClaimed requires checkpointAction review/manual_submit');
|
|
922
|
+
}
|
|
923
|
+
if (receipt.checkpointActionCount !== 1) {
|
|
924
|
+
errors.push('reviewReadyClaimed requires checkpointActionCount 1');
|
|
925
|
+
}
|
|
926
|
+
if (expectedContext && typeof expectedContext === 'object' && !Array.isArray(expectedContext)) {
|
|
927
|
+
for (const field of checkpointAuthorityFields) {
|
|
928
|
+
if (receipt[field] !== expectedContext[field]) errors.push(`${field} must match expectedContext`);
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
} else {
|
|
932
|
+
if (checkpointAuthorityFields.some((field) => Object.hasOwn(receipt, field))) {
|
|
933
|
+
errors.push('checkpoint authority fields must be omitted when reviewReadyClaimed is false');
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
return errors;
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
function validateReconciliation(receipt, expectedContext) {
|
|
940
|
+
const errors = [];
|
|
941
|
+
const reconciliationAuthorityFields = [
|
|
942
|
+
'positiveSubmissionEvidenceRecorded',
|
|
943
|
+
'memberLifecycle',
|
|
944
|
+
'tracklyJobStatus',
|
|
945
|
+
];
|
|
946
|
+
validateCurrentLineage(
|
|
947
|
+
errors,
|
|
948
|
+
receipt,
|
|
949
|
+
expectedContext,
|
|
950
|
+
'approvedJobIds',
|
|
951
|
+
reconciliationAuthorityFields,
|
|
952
|
+
);
|
|
953
|
+
requireTrue(errors, receipt, 'positiveSubmissionEvidenceRecorded');
|
|
954
|
+
if (receipt.memberLifecycle !== 'submitted') errors.push('memberLifecycle must be submitted');
|
|
955
|
+
if (receipt.tracklyJobStatus !== 'applied_confirmed') errors.push('tracklyJobStatus must be applied_confirmed');
|
|
956
|
+
if (expectedContext && typeof expectedContext === 'object' && !Array.isArray(expectedContext)) {
|
|
957
|
+
for (const field of reconciliationAuthorityFields) {
|
|
958
|
+
if (receipt[field] !== expectedContext[field]) errors.push(`${field} must match expectedContext`);
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
if (!CLEANUP_PREFERENCES.has(receipt.cleanupPreference)) errors.push('cleanupPreference is invalid');
|
|
962
|
+
if (!TAB_STATES.has(receipt.browserTabStatus)) errors.push('browserTabStatus is invalid');
|
|
963
|
+
if (receipt.cleanupPreference === 'never' && receipt.browserTabStatus === 'closed_verified') {
|
|
964
|
+
errors.push('browserTabStatus cannot be closed_verified when cleanupPreference is never');
|
|
965
|
+
}
|
|
966
|
+
if (receipt.browserTabStatus === 'closed_verified') {
|
|
967
|
+
requireTrue(errors, receipt, 'completeTabInventoryRecorded');
|
|
968
|
+
requireTrue(errors, receipt, 'closeReceiptRecorded');
|
|
969
|
+
requireTrue(errors, receipt, 'postCloseUnionAbsenceProven');
|
|
970
|
+
} else {
|
|
971
|
+
for (const field of ['completeTabInventoryRecorded', 'closeReceiptRecorded', 'postCloseUnionAbsenceProven']) {
|
|
972
|
+
if (Object.hasOwn(receipt, field) && receipt[field] !== false) {
|
|
973
|
+
errors.push(`${field} must be false or omitted unless browserTabStatus is closed_verified`);
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
return errors;
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
const VALIDATORS = {
|
|
981
|
+
selection: validateSelection,
|
|
982
|
+
access: validateAccess,
|
|
983
|
+
fill: validateFill,
|
|
984
|
+
review: validateReview,
|
|
985
|
+
handoff: validateHandoff,
|
|
986
|
+
reconciliation: validateReconciliation,
|
|
987
|
+
};
|
|
988
|
+
|
|
989
|
+
function validateCheckpoint(phase, receipt, expectedContext, priorFill, validationTimeMs = Date.now()) {
|
|
990
|
+
if (!Object.hasOwn(VALIDATORS, phase)) return [`unknown phase: ${phase}`];
|
|
991
|
+
if (!receipt || typeof receipt !== 'object' || Array.isArray(receipt)) return ['receipt must be a JSON object'];
|
|
992
|
+
if (phase !== 'review' && priorFill !== undefined) return ['priorFill is permitted only for review'];
|
|
993
|
+
const unexpectedFields = Object.keys(receipt)
|
|
994
|
+
.filter((field) => !PHASE_FIELDS[phase].has(field))
|
|
995
|
+
.map(() => 'receipt contains an unexpected field');
|
|
996
|
+
return [
|
|
997
|
+
...unexpectedFields,
|
|
998
|
+
...VALIDATORS[phase](receipt, expectedContext, priorFill, validationTimeMs),
|
|
999
|
+
];
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
async function readReceiptInput(stream) {
|
|
1003
|
+
const decoder = new StringDecoder('utf8');
|
|
1004
|
+
let input = '';
|
|
1005
|
+
let inputBytes = 0;
|
|
1006
|
+
for await (const chunk of stream) {
|
|
1007
|
+
const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
1008
|
+
inputBytes += bytes.length;
|
|
1009
|
+
if (inputBytes > MAX_RECEIPT_BYTES) {
|
|
1010
|
+
return { input: '', oversized: true };
|
|
1011
|
+
}
|
|
1012
|
+
input += decoder.write(bytes);
|
|
1013
|
+
}
|
|
1014
|
+
input += decoder.end();
|
|
1015
|
+
return { input, oversized: false };
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
async function main() {
|
|
1019
|
+
const phase = process.argv[2];
|
|
1020
|
+
if (process.stdin.isTTY) {
|
|
1021
|
+
process.stderr.write('receipt envelope must be provided on standard input\n');
|
|
1022
|
+
process.exitCode = 1;
|
|
1023
|
+
return;
|
|
1024
|
+
}
|
|
1025
|
+
const { input, oversized } = await readReceiptInput(process.stdin);
|
|
1026
|
+
if (oversized) {
|
|
1027
|
+
process.stderr.write(`receipt must be at most ${MAX_RECEIPT_BYTES} bytes\n`);
|
|
1028
|
+
process.exitCode = 1;
|
|
1029
|
+
return;
|
|
1030
|
+
}
|
|
1031
|
+
let envelope;
|
|
1032
|
+
try {
|
|
1033
|
+
envelope = JSON.parse(input);
|
|
1034
|
+
} catch (_) {
|
|
1035
|
+
process.stderr.write('receipt must be valid JSON\n');
|
|
1036
|
+
process.exitCode = 1;
|
|
1037
|
+
return;
|
|
1038
|
+
}
|
|
1039
|
+
if (!envelope || typeof envelope !== 'object' || Array.isArray(envelope)
|
|
1040
|
+
|| !Object.hasOwn(envelope, 'receipt')) {
|
|
1041
|
+
process.stderr.write('input must be an envelope with receipt, expectedContext, and priorFill for review\n');
|
|
1042
|
+
process.exitCode = 1;
|
|
1043
|
+
return;
|
|
1044
|
+
}
|
|
1045
|
+
const unexpectedEnvelopeFields = Object.keys(envelope)
|
|
1046
|
+
.filter((field) => !['receipt', 'expectedContext', 'priorFill'].includes(field));
|
|
1047
|
+
if (unexpectedEnvelopeFields.length > 0) {
|
|
1048
|
+
process.stderr.write('input envelope contains unsupported fields\n');
|
|
1049
|
+
process.exitCode = 1;
|
|
1050
|
+
return;
|
|
1051
|
+
}
|
|
1052
|
+
const errors = validateCheckpoint(phase, envelope.receipt, envelope.expectedContext, envelope.priorFill);
|
|
1053
|
+
if (errors.length > 0) {
|
|
1054
|
+
process.stderr.write(`${errors.join('\n')}\n`);
|
|
1055
|
+
process.exitCode = 1;
|
|
1056
|
+
return;
|
|
1057
|
+
}
|
|
1058
|
+
process.stdout.write(`${phase} checkpoint valid\n`);
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
if (require.main === module) {
|
|
1062
|
+
main().catch(() => {
|
|
1063
|
+
process.stderr.write('unable to read receipt\n');
|
|
1064
|
+
process.exitCode = 1;
|
|
1065
|
+
});
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
module.exports = { readReceiptInput, validateCheckpoint };
|