@patronage/software-factory 1.0.0-alpha.21 → 1.0.0-alpha.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/index.d.ts +752 -680
- package/dist/index.js +7847 -7730
- package/dist/schemas.d.ts +213 -389
- package/dist/schemas.js +596 -221
- package/package.json +4 -9
package/dist/schemas.js
CHANGED
|
@@ -200,6 +200,11 @@ const blockedReasonIssues = (proof) => {
|
|
|
200
200
|
const drifted = named.findIndex((reason, index) => reason.detail !== blockedReasonDetail(reasons[index]));
|
|
201
201
|
return drifted === -1 ? [] : blockedReasonIssue(`blockedReasons[${drifted}] does not carry blocking reason ${drifted}; the two projections must tell one story`);
|
|
202
202
|
};
|
|
203
|
+
const sameHeadSha = (left, right) => {
|
|
204
|
+
if (!left || !right) return false;
|
|
205
|
+
if (Math.min(left.length, right.length) < 7) return false;
|
|
206
|
+
return left.startsWith(right) || right.startsWith(left);
|
|
207
|
+
};
|
|
203
208
|
//#endregion
|
|
204
209
|
//#region src/demand-waiver.ts
|
|
205
210
|
const DEMAND_WAIVER_SCHEMA_VERSION = 1;
|
|
@@ -229,6 +234,549 @@ const waivedDemandSchema = z.object({
|
|
|
229
234
|
unmetReasons: z.array(z.string().min(1)).min(1)
|
|
230
235
|
});
|
|
231
236
|
//#endregion
|
|
237
|
+
//#region src/packages/review-prompt-sections/schema.ts
|
|
238
|
+
const REVIEW_PROMPT_SECTION_PROVENANCES = ["prior-review-ledger", "issue-review-focus"];
|
|
239
|
+
const nonBlankString$1 = z.string().refine((value) => value.trim().length > 0, { message: "must not be blank" });
|
|
240
|
+
const reviewPromptSectionSchema = z.object({
|
|
241
|
+
provenance: z.enum(REVIEW_PROMPT_SECTION_PROVENANCES),
|
|
242
|
+
source: nonBlankString$1,
|
|
243
|
+
text: nonBlankString$1
|
|
244
|
+
}).strict();
|
|
245
|
+
const promptSectionRank = (section) => REVIEW_PROMPT_SECTION_PROVENANCES.indexOf(section.provenance);
|
|
246
|
+
const hasDuplicateProvenance = (sections) => new Set(sections.map((section) => section.provenance)).size !== sections.length;
|
|
247
|
+
const isOutOfAssemblyOrder = (sections) => sections.some((section, index) => index > 0 && promptSectionRank(section) < promptSectionRank(sections[index - 1]));
|
|
248
|
+
const reviewPromptSectionsSchema = z.array(reviewPromptSectionSchema).min(1).refine((sections) => !hasDuplicateProvenance(sections) && !isOutOfAssemblyOrder(sections), { message: "prompt sections must be in assembly order without duplicates" });
|
|
249
|
+
//#endregion
|
|
250
|
+
//#region src/review-ladder-policy.ts
|
|
251
|
+
const REVIEW_CATEGORIES = [
|
|
252
|
+
"correctness",
|
|
253
|
+
"safety",
|
|
254
|
+
"coordination",
|
|
255
|
+
"maintainability",
|
|
256
|
+
"unknown"
|
|
257
|
+
];
|
|
258
|
+
//#endregion
|
|
259
|
+
//#region src/evidence-freshness.ts
|
|
260
|
+
const patchIdMatches = (recorded, candidate) => Boolean(recorded.patchId) && recorded.patchId === candidate.patchId;
|
|
261
|
+
const headShaMatches = (recorded, candidate) => Boolean(recorded.headSha && candidate.headSha && sameHeadSha(candidate.headSha, recorded.headSha));
|
|
262
|
+
/**
|
|
263
|
+
* The one freshness decision. `true` means the recorded evidence still
|
|
264
|
+
* describes the candidate.
|
|
265
|
+
*
|
|
266
|
+
* Time is not an input and never will be: adding one would contradict the
|
|
267
|
+
* `freshness` definition in root `CONTEXT.md`.
|
|
268
|
+
*/
|
|
269
|
+
function evidenceFresh({ candidate, recorded, rule }) {
|
|
270
|
+
if (patchIdMatches(recorded, candidate)) return true;
|
|
271
|
+
return rule === "patch-id-or-head-sha" && headShaMatches(recorded, candidate);
|
|
272
|
+
}
|
|
273
|
+
//#endregion
|
|
274
|
+
//#region src/sha256-hex.ts
|
|
275
|
+
const ROUND_CONSTANTS = new Uint32Array([
|
|
276
|
+
1116352408,
|
|
277
|
+
1899447441,
|
|
278
|
+
3049323471,
|
|
279
|
+
3921009573,
|
|
280
|
+
961987163,
|
|
281
|
+
1508970993,
|
|
282
|
+
2453635748,
|
|
283
|
+
2870763221,
|
|
284
|
+
3624381080,
|
|
285
|
+
310598401,
|
|
286
|
+
607225278,
|
|
287
|
+
1426881987,
|
|
288
|
+
1925078388,
|
|
289
|
+
2162078206,
|
|
290
|
+
2614888103,
|
|
291
|
+
3248222580,
|
|
292
|
+
3835390401,
|
|
293
|
+
4022224774,
|
|
294
|
+
264347078,
|
|
295
|
+
604807628,
|
|
296
|
+
770255983,
|
|
297
|
+
1249150122,
|
|
298
|
+
1555081692,
|
|
299
|
+
1996064986,
|
|
300
|
+
2554220882,
|
|
301
|
+
2821834349,
|
|
302
|
+
2952996808,
|
|
303
|
+
3210313671,
|
|
304
|
+
3336571891,
|
|
305
|
+
3584528711,
|
|
306
|
+
113926993,
|
|
307
|
+
338241895,
|
|
308
|
+
666307205,
|
|
309
|
+
773529912,
|
|
310
|
+
1294757372,
|
|
311
|
+
1396182291,
|
|
312
|
+
1695183700,
|
|
313
|
+
1986661051,
|
|
314
|
+
2177026350,
|
|
315
|
+
2456956037,
|
|
316
|
+
2730485921,
|
|
317
|
+
2820302411,
|
|
318
|
+
3259730800,
|
|
319
|
+
3345764771,
|
|
320
|
+
3516065817,
|
|
321
|
+
3600352804,
|
|
322
|
+
4094571909,
|
|
323
|
+
275423344,
|
|
324
|
+
430227734,
|
|
325
|
+
506948616,
|
|
326
|
+
659060556,
|
|
327
|
+
883997877,
|
|
328
|
+
958139571,
|
|
329
|
+
1322822218,
|
|
330
|
+
1537002063,
|
|
331
|
+
1747873779,
|
|
332
|
+
1955562222,
|
|
333
|
+
2024104815,
|
|
334
|
+
2227730452,
|
|
335
|
+
2361852424,
|
|
336
|
+
2428436474,
|
|
337
|
+
2756734187,
|
|
338
|
+
3204031479,
|
|
339
|
+
3329325298
|
|
340
|
+
]);
|
|
341
|
+
const INITIAL_STATE = new Uint32Array([
|
|
342
|
+
1779033703,
|
|
343
|
+
3144134277,
|
|
344
|
+
1013904242,
|
|
345
|
+
2773480762,
|
|
346
|
+
1359893119,
|
|
347
|
+
2600822924,
|
|
348
|
+
528734635,
|
|
349
|
+
1541459225
|
|
350
|
+
]);
|
|
351
|
+
const BLOCK_BYTES = 64;
|
|
352
|
+
const LENGTH_FIELD_BYTES = 8;
|
|
353
|
+
const WORD_BITS = 32;
|
|
354
|
+
const rotateRight = (value, bits) => (value >>> bits | value << WORD_BITS - bits) >>> 0;
|
|
355
|
+
/** Message bytes plus the FIPS 180-4 padding and 64-bit length suffix. */
|
|
356
|
+
const padMessage = (message) => {
|
|
357
|
+
const paddedLength = Math.ceil((message.length + 1 + LENGTH_FIELD_BYTES) / BLOCK_BYTES) * BLOCK_BYTES;
|
|
358
|
+
const padded = new Uint8Array(paddedLength);
|
|
359
|
+
padded.set(message);
|
|
360
|
+
padded[message.length] = 128;
|
|
361
|
+
const bitLength = BigInt(message.length) * 8n;
|
|
362
|
+
new DataView(padded.buffer).setBigUint64(paddedLength - LENGTH_FIELD_BYTES, bitLength);
|
|
363
|
+
return padded;
|
|
364
|
+
};
|
|
365
|
+
const ROUNDS = 64;
|
|
366
|
+
const SCHEDULE_SEED_WORDS = 16;
|
|
367
|
+
const STATE_WORDS = 8;
|
|
368
|
+
const wordAt = (words, index) => words[index];
|
|
369
|
+
/** One 64-byte block folded into the running state, in place. */
|
|
370
|
+
const compress = (state, view, offset) => {
|
|
371
|
+
const schedule = new Uint32Array(ROUNDS);
|
|
372
|
+
for (let index = 0; index < SCHEDULE_SEED_WORDS; index += 1) schedule[index] = view.getUint32(offset + index * 4);
|
|
373
|
+
for (let index = SCHEDULE_SEED_WORDS; index < ROUNDS; index += 1) {
|
|
374
|
+
const previous = wordAt(schedule, index - 15);
|
|
375
|
+
const recent = wordAt(schedule, index - 2);
|
|
376
|
+
const sigma0 = rotateRight(previous, 7) ^ rotateRight(previous, 18) ^ previous >>> 3;
|
|
377
|
+
const sigma1 = rotateRight(recent, 17) ^ rotateRight(recent, 19) ^ recent >>> 10;
|
|
378
|
+
schedule[index] = wordAt(schedule, index - 16) + sigma0 + wordAt(schedule, index - 7) + sigma1 >>> 0;
|
|
379
|
+
}
|
|
380
|
+
const working = new Uint32Array(state);
|
|
381
|
+
for (let index = 0; index < ROUNDS; index += 1) {
|
|
382
|
+
const a = wordAt(working, 0);
|
|
383
|
+
const b = wordAt(working, 1);
|
|
384
|
+
const c = wordAt(working, 2);
|
|
385
|
+
const d = wordAt(working, 3);
|
|
386
|
+
const e = wordAt(working, 4);
|
|
387
|
+
const f = wordAt(working, 5);
|
|
388
|
+
const g = wordAt(working, 6);
|
|
389
|
+
const h = wordAt(working, 7);
|
|
390
|
+
const sum1 = rotateRight(e, 6) ^ rotateRight(e, 11) ^ rotateRight(e, 25);
|
|
391
|
+
const choose = e & f ^ ~e & g;
|
|
392
|
+
const temp1 = h + sum1 + choose + wordAt(ROUND_CONSTANTS, index) + wordAt(schedule, index) >>> 0;
|
|
393
|
+
const sum0 = rotateRight(a, 2) ^ rotateRight(a, 13) ^ rotateRight(a, 22);
|
|
394
|
+
const majority = a & b ^ a & c ^ b & c;
|
|
395
|
+
working[7] = g;
|
|
396
|
+
working[6] = f;
|
|
397
|
+
working[5] = e;
|
|
398
|
+
working[4] = d + temp1 >>> 0;
|
|
399
|
+
working[3] = c;
|
|
400
|
+
working[2] = b;
|
|
401
|
+
working[1] = a;
|
|
402
|
+
working[0] = temp1 + (sum0 + majority >>> 0) >>> 0;
|
|
403
|
+
}
|
|
404
|
+
for (let index = 0; index < STATE_WORDS; index += 1) state[index] = wordAt(state, index) + wordAt(working, index) >>> 0;
|
|
405
|
+
};
|
|
406
|
+
const toHex = (state) => {
|
|
407
|
+
let hex = "";
|
|
408
|
+
for (const word of state) hex += word.toString(16).padStart(8, "0");
|
|
409
|
+
return hex;
|
|
410
|
+
};
|
|
411
|
+
/** The lowercase hex SHA-256 digest of a UTF-8 string. */
|
|
412
|
+
const sha256Hex = (input) => {
|
|
413
|
+
const padded = padMessage(new TextEncoder().encode(input));
|
|
414
|
+
const view = new DataView(padded.buffer);
|
|
415
|
+
const state = new Uint32Array(INITIAL_STATE);
|
|
416
|
+
for (let offset = 0; offset < padded.length; offset += BLOCK_BYTES) compress(state, view, offset);
|
|
417
|
+
return toHex(state);
|
|
418
|
+
};
|
|
419
|
+
//#endregion
|
|
420
|
+
//#region src/review-ladder-ledger.ts
|
|
421
|
+
const normalizeCitedSpan = (text) => text.split(/\r?\n/u).map((line) => line.trim().replaceAll(/\s+/gu, " ")).filter((line) => line.length > 0).join("\n");
|
|
422
|
+
const PRESCRIBED_ACTION_STOPWORDS = new Set([
|
|
423
|
+
"a",
|
|
424
|
+
"an",
|
|
425
|
+
"the",
|
|
426
|
+
"to",
|
|
427
|
+
"of",
|
|
428
|
+
"in",
|
|
429
|
+
"on",
|
|
430
|
+
"and",
|
|
431
|
+
"or",
|
|
432
|
+
"is",
|
|
433
|
+
"are",
|
|
434
|
+
"be",
|
|
435
|
+
"this",
|
|
436
|
+
"that",
|
|
437
|
+
"it",
|
|
438
|
+
"for",
|
|
439
|
+
"with",
|
|
440
|
+
"as",
|
|
441
|
+
"by",
|
|
442
|
+
"from",
|
|
443
|
+
"at",
|
|
444
|
+
"into",
|
|
445
|
+
"should",
|
|
446
|
+
"must",
|
|
447
|
+
"will",
|
|
448
|
+
"would",
|
|
449
|
+
"can",
|
|
450
|
+
"could",
|
|
451
|
+
"not",
|
|
452
|
+
"but",
|
|
453
|
+
"so",
|
|
454
|
+
"if",
|
|
455
|
+
"then",
|
|
456
|
+
"than",
|
|
457
|
+
"when",
|
|
458
|
+
"which",
|
|
459
|
+
"who",
|
|
460
|
+
"whom",
|
|
461
|
+
"its",
|
|
462
|
+
"their",
|
|
463
|
+
"there",
|
|
464
|
+
"here",
|
|
465
|
+
"also",
|
|
466
|
+
"use",
|
|
467
|
+
"using",
|
|
468
|
+
"instead",
|
|
469
|
+
"rather",
|
|
470
|
+
"than",
|
|
471
|
+
"over",
|
|
472
|
+
"up",
|
|
473
|
+
"out",
|
|
474
|
+
"have",
|
|
475
|
+
"has",
|
|
476
|
+
"had",
|
|
477
|
+
"been"
|
|
478
|
+
]);
|
|
479
|
+
const tokenizePrescribedAction = (text) => {
|
|
480
|
+
const words = text.toLowerCase().replaceAll(/[^a-z0-9\s]/gu, " ").split(/\s+/u).filter((word) => word.length > 0 && !PRESCRIBED_ACTION_STOPWORDS.has(word));
|
|
481
|
+
return [...new Set(words)].toSorted();
|
|
482
|
+
};
|
|
483
|
+
const normalizeTitle = (title) => title.trim().toLowerCase().replaceAll(/\s+/gu, " ");
|
|
484
|
+
const normalizeFile = (file) => file.trim();
|
|
485
|
+
const findingKey = (finding) => {
|
|
486
|
+
const path = finding.file ?? "";
|
|
487
|
+
const normalizedSpan = normalizeCitedSpan(finding.citedSpan ?? "");
|
|
488
|
+
const actionTokens = tokenizePrescribedAction(finding.prescribedAction ?? "");
|
|
489
|
+
const legacyDiscriminator = normalizedSpan.length > 0 || actionTokens.length > 0 ? "" : normalizeTitle(finding.title ?? "");
|
|
490
|
+
return sha256Hex(`${path}\0${sha256Hex(normalizedSpan)}\0${actionTokens.join(" ")}\0${legacyDiscriminator}`);
|
|
491
|
+
};
|
|
492
|
+
const declarationMatchesEntry = (entry, target) => normalizeTitle(entry.title) === normalizeTitle(target.title) && (target.file === void 0 || entry.file !== void 0 && normalizeFile(entry.file) === normalizeFile(target.file));
|
|
493
|
+
const applyDisposition = (ledger, declaration) => {
|
|
494
|
+
const entry = declaration.findingReport ? resolveEntryForFinding([...ledger.values()], declaration.findingReport) : [...ledger.values()].find((candidate) => declarationMatchesEntry(candidate, declaration.finding));
|
|
495
|
+
if (!entry) return false;
|
|
496
|
+
entry.disposition = declaration.disposition;
|
|
497
|
+
entry.priorDisposition = void 0;
|
|
498
|
+
if (declaration.reference !== void 0) entry.reference = declaration.reference;
|
|
499
|
+
return true;
|
|
500
|
+
};
|
|
501
|
+
const definedAttributes = (finding) => Object.fromEntries([
|
|
502
|
+
"blockingAfterCap",
|
|
503
|
+
"category",
|
|
504
|
+
"citedSpan",
|
|
505
|
+
"file",
|
|
506
|
+
"prescribedAction",
|
|
507
|
+
"priority"
|
|
508
|
+
].flatMap((attribute) => finding[attribute] === void 0 ? [] : [[attribute, finding[attribute]]]));
|
|
509
|
+
const MIN_MEANINGFUL_OVERLAP_LINE_LENGTH = 20;
|
|
510
|
+
const spansOverlap = (a, b) => {
|
|
511
|
+
if (a.length === 0 || b.length === 0) return false;
|
|
512
|
+
if (a === b || a.includes(b) || b.includes(a)) return true;
|
|
513
|
+
const linesA = new Set(a.split("\n").filter((line) => line.length >= MIN_MEANINGFUL_OVERLAP_LINE_LENGTH));
|
|
514
|
+
return b.split("\n").some((line) => line.length >= MIN_MEANINGFUL_OVERLAP_LINE_LENGTH && linesA.has(line));
|
|
515
|
+
};
|
|
516
|
+
const resolveEntryForFinding = (entries, finding) => {
|
|
517
|
+
const primaryKey = findingKey(finding);
|
|
518
|
+
const exact = entries.find((entry) => entry.key === primaryKey);
|
|
519
|
+
if (exact) return exact;
|
|
520
|
+
const { supersedes } = finding;
|
|
521
|
+
if (supersedes) {
|
|
522
|
+
const superseded = entries.find((entry) => declarationMatchesEntry(entry, supersedes));
|
|
523
|
+
if (superseded) return superseded;
|
|
524
|
+
}
|
|
525
|
+
if (finding.file !== void 0 && finding.citedSpan !== void 0) {
|
|
526
|
+
const candidateSpan = normalizeCitedSpan(finding.citedSpan);
|
|
527
|
+
if (candidateSpan.length > 0) {
|
|
528
|
+
const overlapping = entries.find((entry) => entry.disposition === "open" && entry.file === finding.file && entry.citedSpan !== void 0 && spansOverlap(normalizeCitedSpan(entry.citedSpan), candidateSpan));
|
|
529
|
+
if (overlapping) return overlapping;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
};
|
|
533
|
+
const REFLAG_TRANSITIONS = {
|
|
534
|
+
"fixed-in-thread": (entry) => {
|
|
535
|
+
entry.disposition = "open";
|
|
536
|
+
entry.reopenedCount += 1;
|
|
537
|
+
},
|
|
538
|
+
"follow-up-filed": (entry) => {
|
|
539
|
+
entry.priorDisposition = entry.disposition;
|
|
540
|
+
entry.disposition = "stale-repeat";
|
|
541
|
+
entry.staleRepeatCount += 1;
|
|
542
|
+
},
|
|
543
|
+
open: () => {},
|
|
544
|
+
"rerun-noise": (entry) => {
|
|
545
|
+
entry.staleRepeatCount += 1;
|
|
546
|
+
},
|
|
547
|
+
"stale-repeat": (entry) => {
|
|
548
|
+
entry.staleRepeatCount += 1;
|
|
549
|
+
},
|
|
550
|
+
waived: (entry) => {
|
|
551
|
+
entry.priorDisposition = entry.disposition;
|
|
552
|
+
entry.disposition = "stale-repeat";
|
|
553
|
+
entry.staleRepeatCount += 1;
|
|
554
|
+
}
|
|
555
|
+
};
|
|
556
|
+
const applyFlag = (ledger, finding, ref, demoteNew) => {
|
|
557
|
+
const matched = resolveEntryForFinding([...ledger.values()], finding);
|
|
558
|
+
if (!matched) {
|
|
559
|
+
const key = findingKey(finding);
|
|
560
|
+
ledger.set(key, {
|
|
561
|
+
...definedAttributes(finding),
|
|
562
|
+
disposition: demoteNew ? "rerun-noise" : "open",
|
|
563
|
+
firstFlagged: ref,
|
|
564
|
+
flagCount: 1,
|
|
565
|
+
key,
|
|
566
|
+
lastFlagged: ref,
|
|
567
|
+
reopenedCount: 0,
|
|
568
|
+
staleRepeatCount: 0,
|
|
569
|
+
title: finding.title.trim()
|
|
570
|
+
});
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
matched.flagCount += 1;
|
|
574
|
+
matched.lastFlagged = ref;
|
|
575
|
+
Object.assign(matched, definedAttributes(finding));
|
|
576
|
+
REFLAG_TRANSITIONS[matched.disposition](matched);
|
|
577
|
+
};
|
|
578
|
+
const findingKeySet = (findings) => new Set(findings.map((finding) => findingKey(finding)));
|
|
579
|
+
const isSameHeadRerun = (previous, current) => previous?.identity !== void 0 && current.identity !== void 0 && evidenceFresh({
|
|
580
|
+
candidate: current.identity,
|
|
581
|
+
recorded: previous.identity,
|
|
582
|
+
rule: "patch-id"
|
|
583
|
+
});
|
|
584
|
+
const isDisjointSameHeadRerun = (previous, current) => {
|
|
585
|
+
if (current.correlatedRerun) return false;
|
|
586
|
+
if (!isSameHeadRerun(previous, current) || previous?.findings.length === 0 || current.findings.length === 0) return false;
|
|
587
|
+
const previousKeys = findingKeySet(previous?.findings ?? []);
|
|
588
|
+
return current.findings.every((finding) => !previousKeys.has(findingKey(finding)));
|
|
589
|
+
};
|
|
590
|
+
const foldLedger = (cycles, refs) => {
|
|
591
|
+
const ledger = /* @__PURE__ */ new Map();
|
|
592
|
+
const unmatchedDispositions = [];
|
|
593
|
+
for (const [index, entry] of cycles.entries()) {
|
|
594
|
+
const dispositions = entry.dispositions ?? [];
|
|
595
|
+
const severityFloorDispositions = dispositions.filter((declaration) => declaration.findingReport !== void 0);
|
|
596
|
+
for (const declaration of dispositions.filter((candidate) => candidate.findingReport === void 0)) if (!applyDisposition(ledger, declaration)) unmatchedDispositions.push(declaration);
|
|
597
|
+
const demoteNew = isDisjointSameHeadRerun(cycles[index - 1], entry);
|
|
598
|
+
for (const finding of entry.findings) applyFlag(ledger, finding, refs[index], demoteNew);
|
|
599
|
+
for (const declaration of severityFloorDispositions) if (!applyDisposition(ledger, declaration)) unmatchedDispositions.push(declaration);
|
|
600
|
+
}
|
|
601
|
+
return {
|
|
602
|
+
ledger,
|
|
603
|
+
unmatchedDispositions
|
|
604
|
+
};
|
|
605
|
+
};
|
|
606
|
+
z.array(z.object({
|
|
607
|
+
disposition: z.enum([
|
|
608
|
+
"fixed-in-thread",
|
|
609
|
+
"follow-up-filed",
|
|
610
|
+
"waived"
|
|
611
|
+
]),
|
|
612
|
+
finding: z.object({
|
|
613
|
+
file: z.string().min(1).optional(),
|
|
614
|
+
title: z.string().min(1)
|
|
615
|
+
}).strict(),
|
|
616
|
+
reference: z.string().min(1).optional()
|
|
617
|
+
}).strict());
|
|
618
|
+
//#endregion
|
|
619
|
+
//#region src/review-ladder.ts
|
|
620
|
+
const ladderCycleRefsFor = (cycles) => {
|
|
621
|
+
let interior = 0;
|
|
622
|
+
let gate = 0;
|
|
623
|
+
return cycles.map((entry) => {
|
|
624
|
+
if (entry.stage === "interior") {
|
|
625
|
+
if (gate > 0) throw new TypeError("Invalid ladder history: interior cycle recorded after the gate stage began.");
|
|
626
|
+
interior += 1;
|
|
627
|
+
return {
|
|
628
|
+
cycle: interior,
|
|
629
|
+
stage: "interior"
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
gate += 1;
|
|
633
|
+
return {
|
|
634
|
+
cycle: gate,
|
|
635
|
+
stage: "gate"
|
|
636
|
+
};
|
|
637
|
+
});
|
|
638
|
+
};
|
|
639
|
+
//#endregion
|
|
640
|
+
//#region src/pr-readiness/ladder-proof-state.ts
|
|
641
|
+
const prReviewLadderStateSchema = z.object({ cycles: z.array(z.custom()) }).superRefine((ladder, context) => {
|
|
642
|
+
try {
|
|
643
|
+
ladderLedgerFor(ladder);
|
|
644
|
+
} catch {
|
|
645
|
+
context.addIssue({
|
|
646
|
+
code: "custom",
|
|
647
|
+
message: "ladder cycles do not fold into a valid disposition ledger"
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
});
|
|
651
|
+
const ladderLedgerFor = (ladder) => [...foldLedger(ladder.cycles, ladderCycleRefsFor(ladder.cycles)).ledger.values()];
|
|
652
|
+
//#endregion
|
|
653
|
+
//#region src/pr-readiness/review-proof.ts
|
|
654
|
+
const PR_REVIEW_SCHEMA_VERSION = 2;
|
|
655
|
+
const nonBlankString = z.string().refine((value) => value.trim().length > 0, { message: "must not be blank" });
|
|
656
|
+
const parseableDateString = z.string().refine((value) => Number.isFinite(Date.parse(value)), { message: "must be a parseable date string" });
|
|
657
|
+
const findingSupersedesSchema = z.object({
|
|
658
|
+
file: z.string().optional(),
|
|
659
|
+
title: nonBlankString
|
|
660
|
+
});
|
|
661
|
+
const prReviewFindingSchema = z.object({
|
|
662
|
+
blockingAfterCap: z.boolean().optional(),
|
|
663
|
+
body: nonBlankString,
|
|
664
|
+
category: z.enum(REVIEW_CATEGORIES).optional(),
|
|
665
|
+
citedSpan: z.string().optional(),
|
|
666
|
+
file: z.string().optional(),
|
|
667
|
+
line: z.number().int().positive().optional(),
|
|
668
|
+
prescribedAction: z.string().optional(),
|
|
669
|
+
priority: z.string().optional(),
|
|
670
|
+
protocolFinding: z.literal(true).optional(),
|
|
671
|
+
supersedes: findingSupersedesSchema.optional(),
|
|
672
|
+
title: nonBlankString
|
|
673
|
+
});
|
|
674
|
+
const prReviewResultSchema = z.object({
|
|
675
|
+
durationMs: z.number().refine((value) => Number.isFinite(value) && value >= 0, { message: "durationMs must be a finite number >= 0" }),
|
|
676
|
+
endedAt: parseableDateString,
|
|
677
|
+
findings: z.array(prReviewFindingSchema),
|
|
678
|
+
issuesFlagged: z.number().int().nonnegative(),
|
|
679
|
+
kind: z.enum(["correctness", "security"]),
|
|
680
|
+
model: nonBlankString.optional(),
|
|
681
|
+
outcome: z.enum([
|
|
682
|
+
"passed",
|
|
683
|
+
"failed",
|
|
684
|
+
"error"
|
|
685
|
+
]),
|
|
686
|
+
producer: nonBlankString.optional(),
|
|
687
|
+
promptSections: reviewPromptSectionsSchema.optional(),
|
|
688
|
+
rung: z.enum(EVIDENCE_REVIEW_RUNGS$1).optional(),
|
|
689
|
+
sessionId: nonBlankString.optional(),
|
|
690
|
+
startedAt: parseableDateString,
|
|
691
|
+
summary: z.string().refine((value) => value.trim().length > 0 && value !== "<one sentence>", { message: "summary must not be blank or a placeholder" }),
|
|
692
|
+
typedVerdict: z.boolean().optional(),
|
|
693
|
+
verdictSource: z.enum(["footer", "recovered"]).optional()
|
|
694
|
+
}).superRefine((review, context) => {
|
|
695
|
+
if (review.outcome === "passed") {
|
|
696
|
+
if (review.issuesFlagged !== 0) context.addIssue({
|
|
697
|
+
code: "custom",
|
|
698
|
+
message: "passed reviews must report zero issues flagged",
|
|
699
|
+
path: ["issuesFlagged"]
|
|
700
|
+
});
|
|
701
|
+
if (review.findings.length !== 0) context.addIssue({
|
|
702
|
+
code: "custom",
|
|
703
|
+
message: "passed reviews must not include findings",
|
|
704
|
+
path: ["findings"]
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
if (review.outcome !== "passed") {
|
|
708
|
+
if (review.findings.length === 0) context.addIssue({
|
|
709
|
+
code: "custom",
|
|
710
|
+
message: "non-passing reviews must include findings",
|
|
711
|
+
path: ["findings"]
|
|
712
|
+
});
|
|
713
|
+
if (review.findings.length !== review.issuesFlagged) context.addIssue({
|
|
714
|
+
code: "custom",
|
|
715
|
+
message: "issuesFlagged must match findings length",
|
|
716
|
+
path: ["issuesFlagged"]
|
|
717
|
+
});
|
|
718
|
+
}
|
|
719
|
+
if (review.producer === void 0 !== (review.rung === void 0)) context.addIssue({
|
|
720
|
+
code: "custom",
|
|
721
|
+
message: "producer and rung must be recorded together",
|
|
722
|
+
path: [review.producer === void 0 ? "producer" : "rung"]
|
|
723
|
+
});
|
|
724
|
+
});
|
|
725
|
+
const prReviewProofSchema = z.object({
|
|
726
|
+
base: z.string(),
|
|
727
|
+
changedFiles: z.array(z.string()),
|
|
728
|
+
cleanedPaths: z.array(z.string()),
|
|
729
|
+
findingProvenanceVersion: z.literal(1),
|
|
730
|
+
headSha: z.string().regex(/^[0-9a-f]{40}$/u),
|
|
731
|
+
ladder: prReviewLadderStateSchema.optional(),
|
|
732
|
+
maxReviewCycles: z.number().int().positive().optional(),
|
|
733
|
+
patchId: z.string().regex(/^[0-9a-f]{40,64}$/u),
|
|
734
|
+
reviewCycle: z.number().int().positive().optional(),
|
|
735
|
+
reviewRequirement: z.object({
|
|
736
|
+
reason: z.enum(["no-applicable-mode", "faithful-merge"]),
|
|
737
|
+
status: z.literal("not-required")
|
|
738
|
+
}).strict().optional(),
|
|
739
|
+
reviews: z.array(prReviewResultSchema),
|
|
740
|
+
schemaVersion: z.literal(2)
|
|
741
|
+
}).passthrough().superRefine((proof, context) => {
|
|
742
|
+
if (proof.reviewRequirement && proof.reviews.length > 0) context.addIssue({
|
|
743
|
+
code: "custom",
|
|
744
|
+
message: "not-required review proofs must not contain review runs",
|
|
745
|
+
path: ["reviews"]
|
|
746
|
+
});
|
|
747
|
+
if (proof.reviewCycle !== void 0 && proof.maxReviewCycles !== void 0 && proof.reviewCycle > proof.maxReviewCycles) context.addIssue({
|
|
748
|
+
code: "custom",
|
|
749
|
+
message: "reviewCycle must not exceed maxReviewCycles",
|
|
750
|
+
path: ["reviewCycle"]
|
|
751
|
+
});
|
|
752
|
+
});
|
|
753
|
+
//#endregion
|
|
754
|
+
//#region src/review-state.ts
|
|
755
|
+
const reviewStateIssue = (message, field) => [{
|
|
756
|
+
code: "custom",
|
|
757
|
+
message,
|
|
758
|
+
path: [field]
|
|
759
|
+
}];
|
|
760
|
+
/**
|
|
761
|
+
* The invariant both ledger schemas enforce, written once so the emitting
|
|
762
|
+
* schema and the wire schema cannot drift. It is the producer's state machine:
|
|
763
|
+
*
|
|
764
|
+
* - a review that is not required is never `current` — `reviewStatus` returns
|
|
765
|
+
* `not-required` before it ever looks at the recorded proof;
|
|
766
|
+
* - `current` means a proof was compared against the candidate delta, so the
|
|
767
|
+
* reviewed identity must be recorded;
|
|
768
|
+
* - `missing` means no proof was found, so no reviewed identity can exist.
|
|
769
|
+
*/
|
|
770
|
+
const reviewStateIssues = (state) => {
|
|
771
|
+
const hasIdentity = Boolean(state.reviewedHeadSha || state.reviewedPatchId);
|
|
772
|
+
if (state.status === "current") {
|
|
773
|
+
if (!state.required) return reviewStateIssue("a review state that is not required cannot be current", "status");
|
|
774
|
+
if (!hasIdentity) return reviewStateIssue("a current review state must record the reviewed head sha or patch id", "reviewedPatchId");
|
|
775
|
+
}
|
|
776
|
+
if (state.status === "missing" && hasIdentity) return reviewStateIssue("a missing review state must record no reviewed head sha or patch id", "status");
|
|
777
|
+
return [];
|
|
778
|
+
};
|
|
779
|
+
//#endregion
|
|
232
780
|
//#region src/catch-up-recognition-record.ts
|
|
233
781
|
const objectShaSchema = z.string().regex(/^[0-9a-f]{40}$/u);
|
|
234
782
|
const catchUpRecognitionSchema = z.object({
|
|
@@ -241,7 +789,8 @@ const catchUpRecognitionSchema = z.object({
|
|
|
241
789
|
const impactStampTargetSchema = z.object({
|
|
242
790
|
basis: z.string().min(1),
|
|
243
791
|
impact: z.enum(["affected", "not-affected"]),
|
|
244
|
-
name: z.string().min(1)
|
|
792
|
+
name: z.string().min(1),
|
|
793
|
+
subscribedPaths: z.array(z.string())
|
|
245
794
|
});
|
|
246
795
|
/**
|
|
247
796
|
* The recorded stamp. `targets` satisfies the completeness invariant: every
|
|
@@ -253,7 +802,7 @@ const impactStampSchema = z.object({
|
|
|
253
802
|
basis: z.enum(["target-scoped", "conservative"]),
|
|
254
803
|
inertPaths: z.array(z.string()).default([]),
|
|
255
804
|
reasons: z.array(z.string()),
|
|
256
|
-
stampVersion: z.literal(
|
|
805
|
+
stampVersion: z.literal(4),
|
|
257
806
|
targets: z.array(impactStampTargetSchema).superRefine((targets, context) => {
|
|
258
807
|
const names = targets.map((target) => target.name);
|
|
259
808
|
if (new Set(names).size !== names.length) context.addIssue({
|
|
@@ -299,8 +848,8 @@ const impactStampScopeDecision = ({ stamp, surface, targetName }) => {
|
|
|
299
848
|
reason: `no trusted identity-bound impact stamp covers this candidate; ${floor}`,
|
|
300
849
|
scoped: false
|
|
301
850
|
};
|
|
302
|
-
if (stamp.stampVersion !==
|
|
303
|
-
reason: `impact stamp version ${String(stamp.stampVersion)} is not this build's version
|
|
851
|
+
if (stamp.stampVersion !== 4) return {
|
|
852
|
+
reason: `impact stamp version ${String(stamp.stampVersion)} is not this build's version 4; ${floor}`,
|
|
304
853
|
scoped: false
|
|
305
854
|
};
|
|
306
855
|
if (stamp.basis !== "target-scoped") return {
|
|
@@ -690,14 +1239,6 @@ const DIFF_CLASSIFICATIONS = [
|
|
|
690
1239
|
"trivial",
|
|
691
1240
|
"non-trivial"
|
|
692
1241
|
];
|
|
693
|
-
const REVIEW_CATEGORIES = [
|
|
694
|
-
"correctness",
|
|
695
|
-
"safety",
|
|
696
|
-
"coordination",
|
|
697
|
-
"maintainability",
|
|
698
|
-
"unknown"
|
|
699
|
-
];
|
|
700
|
-
const REVIEW_RUNGS = ["independentModel", "oracle"];
|
|
701
1242
|
const REVIEW_STATUS_VALUES = [
|
|
702
1243
|
"not-required",
|
|
703
1244
|
"current",
|
|
@@ -747,180 +1288,6 @@ const evidenceEnvelopeSchema = z.object({
|
|
|
747
1288
|
});
|
|
748
1289
|
}
|
|
749
1290
|
});
|
|
750
|
-
const PR_REVIEW_SCHEMA_VERSION = 2;
|
|
751
|
-
const PR_REVIEW_FINDING_PROVENANCE_VERSION = 1;
|
|
752
|
-
const nonBlankString = z.string().refine((value) => value.trim().length > 0, { message: "must not be blank" });
|
|
753
|
-
const parseableDateString = z.string().refine((value) => Number.isFinite(Date.parse(value)), { message: "must be a parseable date string" });
|
|
754
|
-
const reviewPromptSectionProvenances = ["prior-review-ledger", "issue-review-focus"];
|
|
755
|
-
const reviewPromptSectionSchema = z.object({
|
|
756
|
-
provenance: z.enum(reviewPromptSectionProvenances),
|
|
757
|
-
source: nonBlankString,
|
|
758
|
-
text: nonBlankString
|
|
759
|
-
}).strict();
|
|
760
|
-
const reviewPromptSectionsSchema = z.array(reviewPromptSectionSchema).min(1).refine((sections) => new Set(sections.map((section) => section.provenance)).size === sections.length && !sections.some((section, index) => index > 0 && reviewPromptSectionProvenances.indexOf(section.provenance) < reviewPromptSectionProvenances.indexOf(sections[index - 1].provenance)), { message: "prompt sections must be in assembly order without duplicates" });
|
|
761
|
-
const findingSupersedesSchema = z.object({
|
|
762
|
-
file: z.string().optional(),
|
|
763
|
-
title: nonBlankString
|
|
764
|
-
});
|
|
765
|
-
const prReviewFindingSchema = z.object({
|
|
766
|
-
blockingAfterCap: z.boolean().optional(),
|
|
767
|
-
body: nonBlankString,
|
|
768
|
-
category: z.enum(REVIEW_CATEGORIES).optional(),
|
|
769
|
-
citedSpan: z.string().optional(),
|
|
770
|
-
file: z.string().optional(),
|
|
771
|
-
line: z.number().int().positive().optional(),
|
|
772
|
-
prescribedAction: z.string().optional(),
|
|
773
|
-
priority: z.string().optional(),
|
|
774
|
-
protocolFinding: z.literal(true).optional(),
|
|
775
|
-
supersedes: findingSupersedesSchema.optional(),
|
|
776
|
-
title: nonBlankString
|
|
777
|
-
});
|
|
778
|
-
const prReviewResultSchema = z.object({
|
|
779
|
-
command: z.string().refine((value) => value.trim().length > 0, { message: "command must not be blank" }).optional(),
|
|
780
|
-
durationMs: z.number().refine((value) => Number.isFinite(value) && value >= 0, { message: "durationMs must be a finite number >= 0" }),
|
|
781
|
-
endedAt: parseableDateString,
|
|
782
|
-
exitCode: z.number().nullable().optional(),
|
|
783
|
-
findings: z.array(prReviewFindingSchema),
|
|
784
|
-
issuesFlagged: z.number().int().nonnegative(),
|
|
785
|
-
kind: z.enum(["correctness", "security"]),
|
|
786
|
-
model: nonBlankString.optional(),
|
|
787
|
-
outcome: z.enum([
|
|
788
|
-
"passed",
|
|
789
|
-
"failed",
|
|
790
|
-
"error"
|
|
791
|
-
]),
|
|
792
|
-
producer: z.string().min(1).optional(),
|
|
793
|
-
promptSections: reviewPromptSectionsSchema.optional(),
|
|
794
|
-
rung: z.enum(EVIDENCE_REVIEW_RUNGS).optional(),
|
|
795
|
-
sessionId: nonBlankString.optional(),
|
|
796
|
-
stageResolution: z.object({
|
|
797
|
-
effort: z.enum([
|
|
798
|
-
"low",
|
|
799
|
-
"medium",
|
|
800
|
-
"high",
|
|
801
|
-
"xhigh"
|
|
802
|
-
]).optional(),
|
|
803
|
-
engine: z.string().min(1),
|
|
804
|
-
layerSource: z.enum([
|
|
805
|
-
"cli",
|
|
806
|
-
"operatorSessionGlobal",
|
|
807
|
-
"operatorSessionKind",
|
|
808
|
-
"profilePin",
|
|
809
|
-
"stageConfig",
|
|
810
|
-
"userConfig"
|
|
811
|
-
]),
|
|
812
|
-
model: z.string().min(1),
|
|
813
|
-
rung: z.enum(REVIEW_RUNGS).optional()
|
|
814
|
-
}).optional(),
|
|
815
|
-
startedAt: parseableDateString,
|
|
816
|
-
summary: z.string().refine((value) => value.trim().length > 0 && value !== "<one sentence>", { message: "summary must not be blank or a placeholder" }),
|
|
817
|
-
typedVerdict: z.boolean().optional(),
|
|
818
|
-
verdictSource: z.enum(["footer", "recovered"]).optional()
|
|
819
|
-
}).superRefine((review, context) => {
|
|
820
|
-
if (review.outcome === "passed") {
|
|
821
|
-
if (review.issuesFlagged !== 0) context.addIssue({
|
|
822
|
-
code: "custom",
|
|
823
|
-
message: "passed reviews must report zero issues flagged",
|
|
824
|
-
path: ["issuesFlagged"]
|
|
825
|
-
});
|
|
826
|
-
if (review.findings.length !== 0) context.addIssue({
|
|
827
|
-
code: "custom",
|
|
828
|
-
message: "passed reviews must not include findings",
|
|
829
|
-
path: ["findings"]
|
|
830
|
-
});
|
|
831
|
-
}
|
|
832
|
-
if (review.outcome !== "passed") {
|
|
833
|
-
if (review.findings.length === 0) context.addIssue({
|
|
834
|
-
code: "custom",
|
|
835
|
-
message: "non-passing reviews must include findings",
|
|
836
|
-
path: ["findings"]
|
|
837
|
-
});
|
|
838
|
-
if (review.findings.length !== review.issuesFlagged) context.addIssue({
|
|
839
|
-
code: "custom",
|
|
840
|
-
message: "issuesFlagged must match findings length",
|
|
841
|
-
path: ["issuesFlagged"]
|
|
842
|
-
});
|
|
843
|
-
}
|
|
844
|
-
if (review.outcome === "failed" && review.exitCode !== void 0 && review.exitCode !== 0) context.addIssue({
|
|
845
|
-
code: "custom",
|
|
846
|
-
message: "failed reviews must exit with code 0",
|
|
847
|
-
path: ["exitCode"]
|
|
848
|
-
});
|
|
849
|
-
if (review.producer === void 0 !== (review.rung === void 0)) context.addIssue({
|
|
850
|
-
code: "custom",
|
|
851
|
-
message: "producer and rung must be recorded together",
|
|
852
|
-
path: [review.producer === void 0 ? "producer" : "rung"]
|
|
853
|
-
});
|
|
854
|
-
});
|
|
855
|
-
const ladderFindingSchema = z.object({
|
|
856
|
-
citedSpan: z.string().optional(),
|
|
857
|
-
file: z.string().optional(),
|
|
858
|
-
prescribedAction: z.string().optional(),
|
|
859
|
-
supersedes: z.object({
|
|
860
|
-
file: z.string().optional(),
|
|
861
|
-
title: z.string()
|
|
862
|
-
}).optional(),
|
|
863
|
-
title: z.string()
|
|
864
|
-
}).passthrough();
|
|
865
|
-
const ladderDispositionSchema = z.object({
|
|
866
|
-
disposition: z.enum([
|
|
867
|
-
"fixed-in-thread",
|
|
868
|
-
"follow-up-filed",
|
|
869
|
-
"waived"
|
|
870
|
-
]),
|
|
871
|
-
finding: z.object({
|
|
872
|
-
file: z.string().optional(),
|
|
873
|
-
title: z.string()
|
|
874
|
-
}),
|
|
875
|
-
reference: z.string().optional()
|
|
876
|
-
}).passthrough();
|
|
877
|
-
const ladderCycleSchema = z.object({
|
|
878
|
-
dispositions: z.array(ladderDispositionSchema).optional(),
|
|
879
|
-
findings: z.array(ladderFindingSchema)
|
|
880
|
-
}).passthrough();
|
|
881
|
-
const prReviewProofSchema = z.object({
|
|
882
|
-
base: z.string(),
|
|
883
|
-
changedFiles: z.array(z.string()),
|
|
884
|
-
cleanedPaths: z.array(z.string()),
|
|
885
|
-
findingProvenanceVersion: z.literal(PR_REVIEW_FINDING_PROVENANCE_VERSION),
|
|
886
|
-
headSha: z.string().regex(/^[0-9a-f]{40}$/u),
|
|
887
|
-
ladder: z.object({ cycles: z.array(ladderCycleSchema) }).superRefine((ladder, context) => {
|
|
888
|
-
let gateStarted = false;
|
|
889
|
-
try {
|
|
890
|
-
for (const cycle of ladder.cycles) {
|
|
891
|
-
const { stage } = cycle;
|
|
892
|
-
if (stage === "interior") {
|
|
893
|
-
if (gateStarted) throw new TypeError("interior cycle after gate");
|
|
894
|
-
} else gateStarted = true;
|
|
895
|
-
}
|
|
896
|
-
} catch {
|
|
897
|
-
context.addIssue({
|
|
898
|
-
code: "custom",
|
|
899
|
-
message: "ladder cycles do not fold into a valid disposition ledger"
|
|
900
|
-
});
|
|
901
|
-
}
|
|
902
|
-
}).optional(),
|
|
903
|
-
maxReviewCycles: z.number().int().positive().optional(),
|
|
904
|
-
patchId: z.string().regex(/^[0-9a-f]{40,64}$/u),
|
|
905
|
-
reviewCycle: z.number().int().positive().optional(),
|
|
906
|
-
reviewRequirement: z.object({
|
|
907
|
-
reason: z.enum(["no-applicable-mode", "faithful-merge"]),
|
|
908
|
-
status: z.literal("not-required")
|
|
909
|
-
}).strict().optional(),
|
|
910
|
-
reviews: z.array(prReviewResultSchema),
|
|
911
|
-
schemaVersion: z.literal(2)
|
|
912
|
-
}).passthrough().superRefine((proof, context) => {
|
|
913
|
-
if (proof.reviewRequirement && proof.reviews.length > 0) context.addIssue({
|
|
914
|
-
code: "custom",
|
|
915
|
-
message: "not-required review proofs must not contain review runs",
|
|
916
|
-
path: ["reviews"]
|
|
917
|
-
});
|
|
918
|
-
if (proof.reviewCycle !== void 0 && proof.maxReviewCycles !== void 0 && proof.reviewCycle > proof.maxReviewCycles) context.addIssue({
|
|
919
|
-
code: "custom",
|
|
920
|
-
message: "reviewCycle must not exceed maxReviewCycles",
|
|
921
|
-
path: ["reviewCycle"]
|
|
922
|
-
});
|
|
923
|
-
});
|
|
924
1291
|
const BOUNDARY_REVIEW_PROOF_KIND = "boundary-review-proof";
|
|
925
1292
|
const coveredSetEntrySchema = z.object({
|
|
926
1293
|
headSha: evidenceShaSchema.optional(),
|
|
@@ -1019,12 +1386,15 @@ const readinessRepairSchema = z.object({
|
|
|
1019
1386
|
command: z.string().min(1)
|
|
1020
1387
|
});
|
|
1021
1388
|
const reviewStateSchema = z.object({
|
|
1022
|
-
docsOnlyDeltaAccepted: z.boolean().optional(),
|
|
1023
1389
|
required: z.boolean(),
|
|
1024
1390
|
reviewedHeadSha: z.string().optional(),
|
|
1025
1391
|
reviewedPatchId: z.string().optional(),
|
|
1026
1392
|
status: z.enum(REVIEW_STATUS_VALUES)
|
|
1393
|
+
}).strict().superRefine((state, context) => {
|
|
1394
|
+
for (const issue of reviewStateIssues(state)) context.addIssue(issue);
|
|
1027
1395
|
});
|
|
1396
|
+
const verifyCommandSchema = z.literal("patronage-factory pr:verify");
|
|
1397
|
+
const verifiedShaSchema = z.string().min(1);
|
|
1028
1398
|
const historicalPostReadinessCommentSchema = z.object({
|
|
1029
1399
|
author: z.string().min(1),
|
|
1030
1400
|
createdAt: z.string().min(1),
|
|
@@ -1043,7 +1413,6 @@ const managedReadinessLedgerSchema = z.object({
|
|
|
1043
1413
|
classification: z.enum(DIFF_CLASSIFICATIONS),
|
|
1044
1414
|
externalChecks: z.array(z.object({
|
|
1045
1415
|
checkType: z.enum(EVIDENCE_CHECK_TYPES),
|
|
1046
|
-
inScope: z.boolean(),
|
|
1047
1416
|
name: z.string().min(1),
|
|
1048
1417
|
reason: z.string().optional(),
|
|
1049
1418
|
scope: requiredCheckScopeSchema.optional(),
|
|
@@ -1146,40 +1515,54 @@ const managedReadinessLedgerSchema = z.object({
|
|
|
1146
1515
|
correctness: reviewStateSchema,
|
|
1147
1516
|
security: reviewStateSchema.optional()
|
|
1148
1517
|
}),
|
|
1149
|
-
schemaVersion: z.literal(
|
|
1518
|
+
schemaVersion: z.literal(2),
|
|
1150
1519
|
stackRole: z.enum([
|
|
1151
1520
|
"single",
|
|
1152
1521
|
"slice",
|
|
1153
1522
|
"rollup",
|
|
1154
1523
|
"merge-gate prerequisite"
|
|
1155
1524
|
]).optional(),
|
|
1156
|
-
verification: z.
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
"stale"
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1525
|
+
verification: z.discriminatedUnion("prVerify", [
|
|
1526
|
+
z.object({
|
|
1527
|
+
command: verifyCommandSchema,
|
|
1528
|
+
prVerify: z.literal("missing")
|
|
1529
|
+
}).strict(),
|
|
1530
|
+
z.object({
|
|
1531
|
+
command: verifyCommandSchema,
|
|
1532
|
+
prVerify: z.literal("stale"),
|
|
1533
|
+
verifiedHeadSha: verifiedShaSchema
|
|
1534
|
+
}).strict(),
|
|
1535
|
+
z.object({
|
|
1536
|
+
command: verifyCommandSchema,
|
|
1537
|
+
prVerify: z.literal("passed-via-head"),
|
|
1538
|
+
verifiedHeadSha: verifiedShaSchema
|
|
1539
|
+
}).strict(),
|
|
1540
|
+
z.object({
|
|
1541
|
+
command: verifyCommandSchema,
|
|
1542
|
+
docsOnlyVerifiedHeadSha: verifiedShaSchema,
|
|
1543
|
+
prVerify: z.literal("docs-only-delta"),
|
|
1544
|
+
verifiedHeadSha: verifiedShaSchema.optional()
|
|
1545
|
+
}).strict(),
|
|
1546
|
+
z.object({
|
|
1547
|
+
command: verifyCommandSchema,
|
|
1548
|
+
prVerify: z.literal("trivial-delta"),
|
|
1549
|
+
trivialVerifiedHeadSha: verifiedShaSchema,
|
|
1550
|
+
verifiedHeadSha: verifiedShaSchema.optional()
|
|
1551
|
+
}).strict()
|
|
1552
|
+
])
|
|
1169
1553
|
});
|
|
1170
1554
|
const PR_READY_SCHEMA_VERSION = 3;
|
|
1171
1555
|
/**
|
|
1172
|
-
* The pr:ready proof versions a reader
|
|
1173
|
-
*
|
|
1174
|
-
*
|
|
1175
|
-
*
|
|
1556
|
+
* The pr:ready proof versions a reader accepts: the current one only (#917
|
|
1557
|
+
* item 1). #894 narrowed the nested readiness ledger to `schemaVersion: 2` and
|
|
1558
|
+
* the `prVerify` tagged union, which no v1 or v2 pr:ready proof can satisfy.
|
|
1559
|
+
* Advertising 1 and 2 here therefore promised an acceptance the nested schema
|
|
1560
|
+
* already refused. Pre-1.0 posture applies: one current contract, no compat
|
|
1561
|
+
* reader. An alpha.22 consumer must adopt alpha.23 before HQ ingests its
|
|
1562
|
+
* pr:ready proofs.
|
|
1176
1563
|
*/
|
|
1177
|
-
const SUPPORTED_PR_READY_SCHEMA_VERSIONS = [
|
|
1178
|
-
|
|
1179
|
-
2,
|
|
1180
|
-
3
|
|
1181
|
-
];
|
|
1182
|
-
const prReadySchemaVersionSchema = z.number().refine((value) => SUPPORTED_PR_READY_SCHEMA_VERSIONS.includes(value), { message: `schemaVersion must be one of: ${SUPPORTED_PR_READY_SCHEMA_VERSIONS.join(", ")}` });
|
|
1564
|
+
const SUPPORTED_PR_READY_SCHEMA_VERSIONS = [3];
|
|
1565
|
+
const prReadySchemaVersionSchema = z.literal(3);
|
|
1183
1566
|
const prReadyProofSchema = z.object({
|
|
1184
1567
|
arming: z.object({
|
|
1185
1568
|
detail: z.string().min(1).optional(),
|
|
@@ -1209,15 +1592,7 @@ const prReadyProofSchema = z.object({
|
|
|
1209
1592
|
]),
|
|
1210
1593
|
waivedDemands: z.array(waivedDemandSchema).optional()
|
|
1211
1594
|
}).superRefine((proof, context) => {
|
|
1212
|
-
if (proof.schemaVersion < 2) {
|
|
1213
|
-
if (proof.blockedReasons !== void 0) context.addIssue({
|
|
1214
|
-
code: "custom",
|
|
1215
|
-
message: "blockedReasons is a schemaVersion 2 field; a v1 pr:ready proof must not carry it.",
|
|
1216
|
-
path: ["blockedReasons"]
|
|
1217
|
-
});
|
|
1218
|
-
return;
|
|
1219
|
-
}
|
|
1220
1595
|
for (const issue of blockedReasonIssues({ ...proof })) context.addIssue(issue);
|
|
1221
1596
|
});
|
|
1222
1597
|
//#endregion
|
|
1223
|
-
export { BLOCKED_REASONS_MAX, BLOCKED_REASON_DETAIL_MAX, BOUNDARY_CHECK_SCHEMA_VERSION, BOUNDARY_REVIEW_PROOF_KIND, EVIDENCE_ENVELOPE_SCHEMA_VERSION, PR_READY_SCHEMA_VERSION, PR_REVIEW_SCHEMA_VERSION, SUPPORTED_CLOSEOUT_SCHEMA_VERSIONS, SUPPORTED_PR_READY_SCHEMA_VERSIONS, SUPPORTED_PR_VERIFY_SCHEMA_VERSIONS, blockedReasonSchema, blockedReasonsSchema, boundaryCheckProofSchema, boundaryReviewProofSchema, closeoutArtifactSchema, evidenceEnvelopeSchema, prReadyProofSchema, prReviewProofSchema, prVerifyProofSchema, waivedDemandSchema };
|
|
1598
|
+
export { BLOCKED_REASONS_MAX, BLOCKED_REASON_DETAIL_MAX, BOUNDARY_CHECK_SCHEMA_VERSION, BOUNDARY_REVIEW_PROOF_KIND, EVIDENCE_ENVELOPE_SCHEMA_VERSION, PR_READY_SCHEMA_VERSION, PR_REVIEW_SCHEMA_VERSION, SUPPORTED_CLOSEOUT_SCHEMA_VERSIONS, SUPPORTED_PR_READY_SCHEMA_VERSIONS, SUPPORTED_PR_VERIFY_SCHEMA_VERSIONS, blockedReasonSchema, blockedReasonsSchema, boundaryCheckProofSchema, boundaryReviewProofSchema, closeoutArtifactSchema, evidenceEnvelopeSchema, prReadyProofSchema, prReviewFindingSchema, prReviewProofSchema, prReviewResultSchema, prVerifyProofSchema, reviewPromptSectionSchema, reviewPromptSectionsSchema, waivedDemandSchema };
|