@patronage/software-factory 1.0.0-alpha.2 → 1.0.0-alpha.21
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/CONTEXT.md +10 -0
- package/README.md +8 -2
- package/dist/index.d.ts +1238 -1224
- package/dist/index.js +6647 -5621
- package/dist/schemas.d.ts +235 -837
- package/dist/schemas.js +133 -431
- package/package.json +11 -6
package/dist/schemas.js
CHANGED
|
@@ -228,6 +228,16 @@ const waivedDemandSchema = z.object({
|
|
|
228
228
|
session: z.string().trim().min(1).optional(),
|
|
229
229
|
unmetReasons: z.array(z.string().min(1)).min(1)
|
|
230
230
|
});
|
|
231
|
+
//#endregion
|
|
232
|
+
//#region src/catch-up-recognition-record.ts
|
|
233
|
+
const objectShaSchema = z.string().regex(/^[0-9a-f]{40}$/u);
|
|
234
|
+
const catchUpRecognitionSchema = z.object({
|
|
235
|
+
baseRef: z.string().min(1),
|
|
236
|
+
baseTipSha: objectShaSchema,
|
|
237
|
+
mergedParentSha: objectShaSchema,
|
|
238
|
+
mergedTreeSha: objectShaSchema,
|
|
239
|
+
upstreamRef: z.string().min(1)
|
|
240
|
+
}).strict();
|
|
231
241
|
const impactStampTargetSchema = z.object({
|
|
232
242
|
basis: z.string().min(1),
|
|
233
243
|
impact: z.enum(["affected", "not-affected"]),
|
|
@@ -241,15 +251,17 @@ const impactStampTargetSchema = z.object({
|
|
|
241
251
|
*/
|
|
242
252
|
const impactStampSchema = z.object({
|
|
243
253
|
basis: z.enum(["target-scoped", "conservative"]),
|
|
254
|
+
inertPaths: z.array(z.string()).default([]),
|
|
244
255
|
reasons: z.array(z.string()),
|
|
245
|
-
stampVersion: z.literal(
|
|
256
|
+
stampVersion: z.literal(3),
|
|
246
257
|
targets: z.array(impactStampTargetSchema).superRefine((targets, context) => {
|
|
247
258
|
const names = targets.map((target) => target.name);
|
|
248
259
|
if (new Set(names).size !== names.length) context.addIssue({
|
|
249
260
|
code: "custom",
|
|
250
261
|
message: "impact stamp targets must have distinct names."
|
|
251
262
|
});
|
|
252
|
-
})
|
|
263
|
+
}),
|
|
264
|
+
unsubscribedPaths: z.array(z.string())
|
|
253
265
|
});
|
|
254
266
|
/**
|
|
255
267
|
* The surfaces that consume the stamp. One classification, consumed
|
|
@@ -287,8 +299,8 @@ const impactStampScopeDecision = ({ stamp, surface, targetName }) => {
|
|
|
287
299
|
reason: `no trusted identity-bound impact stamp covers this candidate; ${floor}`,
|
|
288
300
|
scoped: false
|
|
289
301
|
};
|
|
290
|
-
if (stamp.stampVersion !==
|
|
291
|
-
reason: `impact stamp version ${String(stamp.stampVersion)} is not this build's version
|
|
302
|
+
if (stamp.stampVersion !== 3) return {
|
|
303
|
+
reason: `impact stamp version ${String(stamp.stampVersion)} is not this build's version 3; ${floor}`,
|
|
292
304
|
scoped: false
|
|
293
305
|
};
|
|
294
306
|
if (stamp.basis !== "target-scoped") return {
|
|
@@ -513,85 +525,135 @@ const assertPrVerifyProofRules = (proof, context) => {
|
|
|
513
525
|
message: "notRequiredCommands is a schemaVersion>=4 field; a v1–v3 pr:verify proof must not carry it.",
|
|
514
526
|
path: ["notRequiredCommands"]
|
|
515
527
|
});
|
|
528
|
+
if (proof.schemaVersion < 4 && proof.catchUpRecognition !== void 0) context.addIssue({
|
|
529
|
+
code: "custom",
|
|
530
|
+
message: "catchUpRecognition is a schemaVersion>=4 field; a v1–v3 pr:verify proof must not carry it.",
|
|
531
|
+
path: ["catchUpRecognition"]
|
|
532
|
+
});
|
|
533
|
+
if (proof.schemaVersion < 4 && proof.notDemandedRecords !== void 0) context.addIssue({
|
|
534
|
+
code: "custom",
|
|
535
|
+
message: "notDemandedRecords is a schemaVersion>=4 field; a v1–v3 pr:verify proof must not carry it.",
|
|
536
|
+
path: ["notDemandedRecords"]
|
|
537
|
+
});
|
|
538
|
+
if (proof.notDemandedRecords !== void 0 && proof.impactStamp === void 0) context.addIssue({
|
|
539
|
+
code: "custom",
|
|
540
|
+
message: "notDemandedRecords requires impactStamp; package-level not-demanded records are bound to the stamp identity.",
|
|
541
|
+
path: ["notDemandedRecords"]
|
|
542
|
+
});
|
|
516
543
|
assertBatteryCompleteness(proof, context);
|
|
517
544
|
assertNotRequiredStampAuthorization(proof, context);
|
|
518
545
|
};
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
const
|
|
546
|
+
//#endregion
|
|
547
|
+
//#region src/pr-verify-proof-contract.ts
|
|
548
|
+
const SUPPORTED_PR_VERIFY_SCHEMA_VERSIONS = [
|
|
549
|
+
1,
|
|
550
|
+
2,
|
|
551
|
+
3,
|
|
552
|
+
4
|
|
553
|
+
];
|
|
554
|
+
const executedCommandSchema = z.object({
|
|
555
|
+
command: z.string().min(1),
|
|
556
|
+
counts: z.object({
|
|
557
|
+
testFiles: z.number().nonnegative().optional(),
|
|
558
|
+
tests: z.number().nonnegative().optional()
|
|
559
|
+
}).optional(),
|
|
560
|
+
durationMs: z.number().nonnegative(),
|
|
561
|
+
exitCode: z.number(),
|
|
562
|
+
name: z.string().min(1),
|
|
563
|
+
scope: z.enum([
|
|
564
|
+
"always",
|
|
565
|
+
"docs-only",
|
|
566
|
+
"trivial",
|
|
567
|
+
"full"
|
|
568
|
+
])
|
|
569
|
+
});
|
|
570
|
+
const notRequiredCommandSchema = z.object({
|
|
571
|
+
basis: z.string().min(1),
|
|
572
|
+
impactTarget: z.string().min(1),
|
|
573
|
+
name: z.string().min(1)
|
|
574
|
+
});
|
|
575
|
+
const notDemandedRecordSchema = z.object({
|
|
576
|
+
basis: z.string().min(1),
|
|
577
|
+
name: z.string().min(1)
|
|
578
|
+
});
|
|
579
|
+
const prVerifySchemaVersionSchema = z.number().refine((value) => SUPPORTED_PR_VERIFY_SCHEMA_VERSIONS.includes(value), { message: `schemaVersion must be one of: ${SUPPORTED_PR_VERIFY_SCHEMA_VERSIONS.join(", ")}` });
|
|
580
|
+
const prVerifyProofSchema = z.object({
|
|
581
|
+
authoringSession: z.string().trim().min(1).optional(),
|
|
582
|
+
base: z.string().min(1),
|
|
583
|
+
baselineFullProofs: z.array(z.object({
|
|
584
|
+
base: z.string().min(1),
|
|
585
|
+
changedFiles: z.array(z.string()),
|
|
586
|
+
headSha: z.string().regex(/^[0-9a-f]{40}$/u),
|
|
587
|
+
profilePath: z.string().min(1),
|
|
588
|
+
projectKey: z.string().min(1),
|
|
589
|
+
repository: z.string().min(1)
|
|
590
|
+
})).optional(),
|
|
591
|
+
catchUpRecognition: catchUpRecognitionSchema.optional(),
|
|
592
|
+
changedFiles: z.array(z.string()),
|
|
593
|
+
classification: z.enum([
|
|
594
|
+
"docs/process-only",
|
|
595
|
+
"trivial",
|
|
596
|
+
"non-trivial"
|
|
597
|
+
]),
|
|
598
|
+
classificationReasons: z.array(z.string()),
|
|
599
|
+
command: z.literal("patronage-factory pr:verify"),
|
|
600
|
+
durationMs: z.number().nonnegative(),
|
|
601
|
+
endedAt: z.iso.datetime(),
|
|
602
|
+
executedCommands: z.array(executedCommandSchema).optional(),
|
|
603
|
+
headSha: z.string().regex(/^[0-9a-f]{40}$/u),
|
|
604
|
+
impactStamp: impactStampSchema.optional(),
|
|
605
|
+
mergeBaseSha: z.string().regex(/^[0-9a-f]{40}$/u).optional(),
|
|
606
|
+
mode: z.enum([
|
|
607
|
+
"docs-only",
|
|
608
|
+
"trivial",
|
|
609
|
+
"full"
|
|
610
|
+
]),
|
|
611
|
+
notDemandedRecords: z.array(notDemandedRecordSchema).min(1).optional(),
|
|
612
|
+
notRequiredCommands: z.array(notRequiredCommandSchema).min(1).optional(),
|
|
613
|
+
outcome: z.enum(["aborted", "passed"]).optional(),
|
|
614
|
+
patchId: z.string().regex(/^[0-9a-f]{40}$/u).optional(),
|
|
615
|
+
profilePath: z.string().min(1),
|
|
616
|
+
projectKey: z.string().min(1),
|
|
617
|
+
repository: z.string().min(1),
|
|
618
|
+
schemaVersion: prVerifySchemaVersionSchema,
|
|
619
|
+
startedAt: z.iso.datetime(),
|
|
620
|
+
verificationCommands: z.array(z.object({
|
|
621
|
+
command: z.string().min(1),
|
|
622
|
+
description: z.string().min(1),
|
|
623
|
+
impactTarget: z.string().min(1).optional(),
|
|
624
|
+
name: z.string().min(1),
|
|
625
|
+
scope: z.enum([
|
|
626
|
+
"always",
|
|
627
|
+
"docs-only",
|
|
628
|
+
"trivial",
|
|
629
|
+
"full"
|
|
630
|
+
])
|
|
631
|
+
}))
|
|
632
|
+
}).superRefine(assertPrVerifyProofRules);
|
|
633
|
+
/** Artifact versions accepted at ingest. */
|
|
634
|
+
const SUPPORTED_CLOSEOUT_SCHEMA_VERSIONS = [5];
|
|
522
635
|
const MAX_CLOSEOUT_ROWS = 1e3;
|
|
523
636
|
const IdentifierSchema = z.string().min(1).max(500);
|
|
524
637
|
const NarrativeSchema = z.string().min(1).max(1e4);
|
|
525
638
|
const RepoSchema = z.string().min(1).max(200);
|
|
526
|
-
const
|
|
527
|
-
const CloseoutLandingSpotSchema = z.enum([
|
|
528
|
-
"CLI",
|
|
529
|
-
"profile",
|
|
530
|
-
"skill",
|
|
531
|
-
"ADR"
|
|
532
|
-
]);
|
|
533
|
-
const CurrentCloseoutSourceSchema = z.object({
|
|
534
|
-
detail: NarrativeSchema,
|
|
535
|
-
kind: z.literal("boundary-thermo-attestation")
|
|
536
|
-
}).strict();
|
|
537
|
-
const LegacyCloseoutSourceSchema = z.object({
|
|
639
|
+
const CloseoutSourceSchema = z.object({
|
|
538
640
|
detail: NarrativeSchema,
|
|
539
|
-
kind: z.
|
|
540
|
-
}).strict();
|
|
541
|
-
const LegacyCloseoutScopingSchema = z.object({
|
|
542
|
-
epic: IdentifierSchema,
|
|
543
|
-
issueFilter: z.number().int().positive().optional(),
|
|
544
|
-
prFilter: z.number().int().positive().optional(),
|
|
545
|
-
repo: RepoSchema,
|
|
546
|
-
scopedByEpicIssueSet: z.boolean(),
|
|
547
|
-
window: z.object({
|
|
548
|
-
from: IsoDateSchema,
|
|
549
|
-
to: IsoDateSchema
|
|
550
|
-
}).strict()
|
|
641
|
+
kind: z.literal("token-harvest")
|
|
551
642
|
}).strict();
|
|
552
643
|
const CloseoutBudgetSchema = z.object({
|
|
553
644
|
note: NarrativeSchema,
|
|
554
645
|
tier: z.literal("overseer")
|
|
555
646
|
}).strict();
|
|
556
|
-
const LegacyCloseoutBudgetSchema = z.object({
|
|
557
|
-
interiorSourcesConsumed: z.number().int().nonnegative(),
|
|
558
|
-
note: NarrativeSchema,
|
|
559
|
-
scoping: LegacyCloseoutScopingSchema.optional(),
|
|
560
|
-
tier: z.literal("overseer")
|
|
561
|
-
}).strict();
|
|
562
|
-
const BoundaryThermoAttestationSchema = z.object({
|
|
563
|
-
owner: z.string().min(1).max(200),
|
|
564
|
-
runs: z.number().int().nonnegative(),
|
|
565
|
-
waived: z.boolean(),
|
|
566
|
-
waiverRationale: NarrativeSchema.optional()
|
|
567
|
-
}).strict();
|
|
568
647
|
const CloseoutMetricRowSchema = z.object({
|
|
569
648
|
label: IdentifierSchema,
|
|
570
649
|
metric: IdentifierSchema,
|
|
571
|
-
source:
|
|
650
|
+
source: CloseoutSourceSchema,
|
|
572
651
|
unit: z.enum([
|
|
573
|
-
"count",
|
|
574
652
|
"boolean",
|
|
575
|
-
"text"
|
|
576
|
-
]),
|
|
577
|
-
value: z.union([
|
|
578
|
-
z.number(),
|
|
579
|
-
NarrativeSchema,
|
|
580
|
-
z.boolean(),
|
|
581
|
-
z.null()
|
|
582
|
-
])
|
|
583
|
-
}).strict();
|
|
584
|
-
const LegacyCloseoutMetricRowSchema = z.object({
|
|
585
|
-
label: IdentifierSchema,
|
|
586
|
-
metric: IdentifierSchema,
|
|
587
|
-
source: LegacyCloseoutSourceSchema,
|
|
588
|
-
unit: z.enum([
|
|
589
|
-
"tokens",
|
|
590
|
-
"usd",
|
|
591
653
|
"count",
|
|
592
|
-
"
|
|
593
|
-
"
|
|
594
|
-
"
|
|
654
|
+
"text",
|
|
655
|
+
"tokens",
|
|
656
|
+
"usd"
|
|
595
657
|
]),
|
|
596
658
|
value: z.union([
|
|
597
659
|
z.number(),
|
|
@@ -600,306 +662,21 @@ const LegacyCloseoutMetricRowSchema = z.object({
|
|
|
600
662
|
z.null()
|
|
601
663
|
])
|
|
602
664
|
}).strict();
|
|
603
|
-
const CloseoutLessonSchema = z.object({
|
|
604
|
-
id: IdentifierSchema,
|
|
605
|
-
landingSpot: CloseoutLandingSpotSchema,
|
|
606
|
-
lesson: NarrativeSchema,
|
|
607
|
-
rationale: NarrativeSchema.optional(),
|
|
608
|
-
source: CurrentCloseoutSourceSchema.optional()
|
|
609
|
-
}).strict();
|
|
610
|
-
const LegacyCloseoutLessonSchema = z.object({
|
|
611
|
-
id: IdentifierSchema,
|
|
612
|
-
landingSpot: CloseoutLandingSpotSchema,
|
|
613
|
-
lesson: NarrativeSchema,
|
|
614
|
-
rationale: NarrativeSchema.optional(),
|
|
615
|
-
source: LegacyCloseoutSourceSchema.optional()
|
|
616
|
-
}).strict();
|
|
617
665
|
const CloseoutBlindspotSchema = z.object({
|
|
618
666
|
id: IdentifierSchema,
|
|
619
667
|
note: NarrativeSchema,
|
|
620
668
|
reason: NarrativeSchema.optional()
|
|
621
669
|
}).strict();
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
generatedAt: z.iso.datetime(),
|
|
626
|
-
key: IdentifierSchema,
|
|
627
|
-
landingSpot: CloseoutLandingSpotSchema.optional(),
|
|
628
|
-
repo: RepoSchema,
|
|
629
|
-
rowType: z.enum([
|
|
630
|
-
"metric",
|
|
631
|
-
"lesson",
|
|
632
|
-
"blindspot"
|
|
633
|
-
]),
|
|
634
|
-
schemaVersion: z.literal(schemaVersion),
|
|
635
|
-
source: NarrativeSchema,
|
|
636
|
-
unit: IdentifierSchema.optional(),
|
|
637
|
-
value: z.union([
|
|
638
|
-
NarrativeSchema,
|
|
639
|
-
z.number(),
|
|
640
|
-
z.null()
|
|
641
|
-
])
|
|
642
|
-
}).strict();
|
|
643
|
-
const CloseoutLedgerRowSchema = closeoutLedgerRowSchema(4);
|
|
644
|
-
const LegacyCloseoutLedgerRowSchema = closeoutLedgerRowSchema(LEGACY_CLOSEOUT_SCHEMA_VERSION);
|
|
645
|
-
const closeoutArtifactShape = (input) => z.object({
|
|
646
|
-
boundaryThermo: BoundaryThermoAttestationSchema,
|
|
647
|
-
budget: input.budget,
|
|
670
|
+
/** Strict, bounded runtime transport contract emitted by factory:closeout. */
|
|
671
|
+
const closeoutArtifactSchema = z.object({
|
|
672
|
+
budget: CloseoutBudgetSchema,
|
|
648
673
|
couldNotSee: z.array(CloseoutBlindspotSchema).min(1).max(MAX_CLOSEOUT_ROWS),
|
|
649
674
|
epic: IdentifierSchema,
|
|
650
675
|
generatedAt: z.iso.datetime(),
|
|
651
|
-
|
|
652
|
-
lessons: z.array(input.lessons).max(MAX_CLOSEOUT_ROWS),
|
|
653
|
-
metrics: z.array(input.metrics).max(MAX_CLOSEOUT_ROWS),
|
|
676
|
+
metrics: z.array(CloseoutMetricRowSchema).max(MAX_CLOSEOUT_ROWS),
|
|
654
677
|
repo: RepoSchema,
|
|
655
|
-
schemaVersion: z.literal(
|
|
678
|
+
schemaVersion: z.literal(5)
|
|
656
679
|
}).strict();
|
|
657
|
-
/** Strict, bounded runtime transport contract emitted by factory:closeout. */
|
|
658
|
-
const closeoutArtifactSchema = closeoutArtifactShape({
|
|
659
|
-
budget: CloseoutBudgetSchema,
|
|
660
|
-
ledgerRows: CloseoutLedgerRowSchema,
|
|
661
|
-
lessons: CloseoutLessonSchema,
|
|
662
|
-
metrics: CloseoutMetricRowSchema,
|
|
663
|
-
schemaVersion: 4
|
|
664
|
-
});
|
|
665
|
-
/** Reader-only compatibility for closeout artifacts written before #554. */
|
|
666
|
-
const legacyCloseoutArtifactSchema = closeoutArtifactShape({
|
|
667
|
-
budget: LegacyCloseoutBudgetSchema,
|
|
668
|
-
ledgerRows: LegacyCloseoutLedgerRowSchema,
|
|
669
|
-
lessons: LegacyCloseoutLessonSchema,
|
|
670
|
-
metrics: LegacyCloseoutMetricRowSchema,
|
|
671
|
-
schemaVersion: LEGACY_CLOSEOUT_SCHEMA_VERSION
|
|
672
|
-
});
|
|
673
|
-
/** Accept current output and retained v3 artifacts when reading or replaying. */
|
|
674
|
-
const closeoutArtifactReadSchema = z.union([closeoutArtifactSchema, legacyCloseoutArtifactSchema]);
|
|
675
|
-
//#endregion
|
|
676
|
-
//#region src/retro-envelope.ts
|
|
677
|
-
/**
|
|
678
|
-
* Versioned retro envelope schema (epic #27 wave 2, issue #34).
|
|
679
|
-
*
|
|
680
|
-
* One envelope per lane, built at `factory:closeout` and delivered through the
|
|
681
|
-
* typed gate-sink as the `retro-envelope` ingest kind. Re-derived in TypeScript
|
|
682
|
-
* from the `spike/telemetry-layer2` S5 scratch schema (reference-only, never
|
|
683
|
-
* merged). This module is the single source of truth for the v1 wire shape
|
|
684
|
-
* and its bounds ({@link RETRO_ENVELOPE_WIRE_BOUNDS}) — producer and consumer
|
|
685
|
-
* alike. HQ imports these exports directly from the Worker-safe
|
|
686
|
-
* `@patronage/software-factory/schemas` subpath
|
|
687
|
-
* (`software-factory-hq/src/contracts/retro-schemas.ts`) instead of
|
|
688
|
-
* maintaining a parallel hand-written copy, so there is exactly one wire
|
|
689
|
-
* contract and no drift-detection machinery is needed (issue #350; formerly
|
|
690
|
-
* a hand-written twin plus a 767-line parity test, #46).
|
|
691
|
-
*
|
|
692
|
-
* DESIGN INVARIANT: cross-family token sums must be UNREPRESENTABLE.
|
|
693
|
-
*
|
|
694
|
-
* The two model families use different tokenizers, prices, and accounting
|
|
695
|
-
* conventions, so any token total that spans Claude and GPT is a lie:
|
|
696
|
-
*
|
|
697
|
-
* 1. There is no combined/total token field anywhere in the envelope.
|
|
698
|
-
* 2. `tokenFamilies` is strict — its only keys are `claude` and `gpt`; data
|
|
699
|
-
* cannot smuggle in a third "all"/"combined" slot.
|
|
700
|
-
* 3. The family BLOCKS are structurally different shapes with DIFFERENT keys
|
|
701
|
-
* (claude is a single flat block keyed on freshInput/cacheReadInput/
|
|
702
|
-
* cacheCreationInput; gpt is `{ roles: [...] }`). The exclusive input-tier
|
|
703
|
-
* COUNTS share no key name across families. The residual names shared
|
|
704
|
-
* between the claude block and a gpt ROLE entry are pinned to exactly
|
|
705
|
-
* {costUsd, model, output} (PR #32 advisory): `costUsd` is deliberate —
|
|
706
|
-
* USD is the one cross-family summable unit (rule 4); `model` is an
|
|
707
|
-
* unsummable label; `output` is the same name at DIFFERENT depths (lane
|
|
708
|
-
* block vs per-role entry), frozen by a tripwire test in
|
|
709
|
-
* `retro-envelope.test.ts` so the overlap cannot grow. Renaming `output`
|
|
710
|
-
* is a schemaVersion-2 wire change, deliberately not spent in v1.
|
|
711
|
-
* 4. Cost is per-family USD and nullable. Combined totals are allowed in USD
|
|
712
|
-
* only, and only as a projection-time sum of per-family USD.
|
|
713
|
-
*
|
|
714
|
-
* Field names also encode the S2/S3 reader lessons: Claude `freshInput` alone
|
|
715
|
-
* is not prompt size (true input context = freshInput + cacheReadInput +
|
|
716
|
-
* cacheCreationInput, requestId-deduped), and codex `inputInclusiveOfCache`
|
|
717
|
-
* already includes `cachedInput`, so `freshInputDerived` (inclusive − cached)
|
|
718
|
-
* is the only value safe to feed a per-token pricer.
|
|
719
|
-
*
|
|
720
|
-
* COMPLETENESS POSTURE: harvest may have no usable native log for a lane, so
|
|
721
|
-
* `tokenFamilies` may legitimately be absent. The closeout build gate demands
|
|
722
|
-
* a valid envelope, not available telemetry. A families-absent envelope keeps
|
|
723
|
-
* its operator-visible data gaps and is a replayable advisory HQ event, so it
|
|
724
|
-
* never substitutes unavailable usage with zero. The wire shape (field names,
|
|
725
|
-
* types, structure) stays byte-parity with HQ v1.
|
|
726
|
-
*/
|
|
727
|
-
const RETRO_ENVELOPE_SCHEMA_VERSION = 1;
|
|
728
|
-
/**
|
|
729
|
-
* v1 wire bounds — the single source the schemas below are built from and the
|
|
730
|
-
* builder's sanitization seam clamps to (`retro-envelope-builder.ts` imports
|
|
731
|
-
* these; it keeps no bound constants of its own). HQ imports this module
|
|
732
|
-
* directly (issue #350), so there is one set of bounds, not a second copy to
|
|
733
|
-
* keep in sync.
|
|
734
|
-
*/
|
|
735
|
-
const RETRO_ENVELOPE_WIRE_BOUNDS = {
|
|
736
|
-
/** archiveRef pointer (key/URL) max characters. */
|
|
737
|
-
archiveRefMaxChars: 2048,
|
|
738
|
-
/** refs.branch max characters (git ref length ceiling). */
|
|
739
|
-
branchMaxChars: 255,
|
|
740
|
-
/** gates[] wire cap — the builder keeps the most recent records. */
|
|
741
|
-
gatesMax: 500,
|
|
742
|
-
/** gpt roles[] cap (delegated roles per lane). */
|
|
743
|
-
gptRolesMax: 20,
|
|
744
|
-
/** Cap for list fields: joinKeys id arrays, phases, dataGaps. */
|
|
745
|
-
listMax: 100,
|
|
746
|
-
/** Bounded name fields: agentRunId, gate, phase name, refs, join-key ids. */
|
|
747
|
-
nameMaxChars: 200,
|
|
748
|
-
/** Short identifier fields: repo name/owner, model, role. */
|
|
749
|
-
shortMaxChars: 100,
|
|
750
|
-
/** Free-text fields: dataGaps entries, outcome.verdict. */
|
|
751
|
-
textMaxChars: 500
|
|
752
|
-
};
|
|
753
|
-
const nonNegInt = z.number().int().nonnegative();
|
|
754
|
-
const isoTimestamp = z.iso.datetime();
|
|
755
|
-
const boundedName = z.string().min(1).max(RETRO_ENVELOPE_WIRE_BOUNDS.nameMaxChars);
|
|
756
|
-
const boundedShortName = z.string().min(1).max(RETRO_ENVELOPE_WIRE_BOUNDS.shortMaxChars);
|
|
757
|
-
const boundedText = z.string().min(1).max(RETRO_ENVELOPE_WIRE_BOUNDS.textMaxChars);
|
|
758
|
-
const RetroRepoSchema = z.object({
|
|
759
|
-
name: boundedShortName,
|
|
760
|
-
owner: boundedShortName
|
|
761
|
-
}).strict();
|
|
762
|
-
const RetroRefsSchema = z.object({
|
|
763
|
-
branch: z.string().min(1).max(RETRO_ENVELOPE_WIRE_BOUNDS.branchMaxChars).optional(),
|
|
764
|
-
epic: boundedName.optional(),
|
|
765
|
-
headSha: z.string().regex(/^[0-9a-f]{7,40}$/u).optional(),
|
|
766
|
-
issue: boundedName.optional(),
|
|
767
|
-
prNumber: z.number().int().positive().optional()
|
|
768
|
-
}).strict();
|
|
769
|
-
/**
|
|
770
|
-
* Per-lane join keys: how the envelope re-joins raw per-session sources.
|
|
771
|
-
* `agentRunId` lives at the envelope root; these carry the per-family session
|
|
772
|
-
* identities (Claude `session.id`, codex `threadId`).
|
|
773
|
-
*/
|
|
774
|
-
const RetroJoinKeysSchema = z.object({
|
|
775
|
-
claudeSessionIds: z.array(boundedName).max(RETRO_ENVELOPE_WIRE_BOUNDS.listMax).default([]),
|
|
776
|
-
codexThreadIds: z.array(boundedName).max(RETRO_ENVELOPE_WIRE_BOUNDS.listMax).default([])
|
|
777
|
-
}).strict();
|
|
778
|
-
const RetroWallClockSchema = z.object({
|
|
779
|
-
endTs: isoTimestamp,
|
|
780
|
-
startTs: isoTimestamp,
|
|
781
|
-
totalSec: z.number().nonnegative()
|
|
782
|
-
}).strict();
|
|
783
|
-
const RetroPhaseMarkSchema = z.object({
|
|
784
|
-
at: isoTimestamp,
|
|
785
|
-
deltaSec: z.number().nonnegative().optional(),
|
|
786
|
-
name: boundedName
|
|
787
|
-
}).strict();
|
|
788
|
-
/** Gate-timing ledger entry: `{gate, startedAt, duration, outcome, cycle}`. */
|
|
789
|
-
const RetroGateLedgerEntrySchema = z.object({
|
|
790
|
-
cycle: nonNegInt,
|
|
791
|
-
duration: z.number().nonnegative(),
|
|
792
|
-
gate: boundedName,
|
|
793
|
-
outcome: z.enum([
|
|
794
|
-
"pass",
|
|
795
|
-
"fail",
|
|
796
|
-
"skip"
|
|
797
|
-
]),
|
|
798
|
-
startedAt: isoTimestamp
|
|
799
|
-
}).strict();
|
|
800
|
-
/**
|
|
801
|
-
* Claude family: Anthropic-style EXCLUSIVE input tiers. True input context is
|
|
802
|
-
* freshInput + cacheReadInput + cacheCreationInput; values are
|
|
803
|
-
* requestId-deduped (S2: naive row sums overcount input ~1.94x).
|
|
804
|
-
*/
|
|
805
|
-
const claudeTokenBlockSchema = z.object({
|
|
806
|
-
cacheCreationInput: nonNegInt,
|
|
807
|
-
cacheReadInput: nonNegInt,
|
|
808
|
-
costUsd: z.number().nonnegative().nullable(),
|
|
809
|
-
family: z.literal("claude"),
|
|
810
|
-
freshInput: nonNegInt,
|
|
811
|
-
model: boundedShortName,
|
|
812
|
-
output: nonNegInt,
|
|
813
|
-
requests: nonNegInt
|
|
814
|
-
}).strict();
|
|
815
|
-
/**
|
|
816
|
-
* GPT/codex family, per delegated role. codex reports `input_tokens`
|
|
817
|
-
* INCLUSIVE of cached tokens; `freshInputDerived` = inclusive − cached is the
|
|
818
|
-
* only value safe for a per-token pricer (S3: skipping this overprices ~3.9x).
|
|
819
|
-
*/
|
|
820
|
-
const gptRoleUsageSchema = z.object({
|
|
821
|
-
cachedInput: nonNegInt,
|
|
822
|
-
costUsd: z.number().nonnegative().nullable(),
|
|
823
|
-
freshInputDerived: nonNegInt,
|
|
824
|
-
inputInclusiveOfCache: nonNegInt,
|
|
825
|
-
model: boundedShortName,
|
|
826
|
-
output: nonNegInt,
|
|
827
|
-
reasoningOutput: nonNegInt,
|
|
828
|
-
role: boundedShortName,
|
|
829
|
-
threadId: boundedName.optional()
|
|
830
|
-
}).strict().refine((usage) => usage.freshInputDerived === usage.inputInclusiveOfCache - usage.cachedInput, { message: "freshInputDerived must equal inputInclusiveOfCache - cachedInput (S3 accounting rule)" });
|
|
831
|
-
const gptTokenBlockSchema = z.object({
|
|
832
|
-
family: z.literal("gpt"),
|
|
833
|
-
roles: z.array(gptRoleUsageSchema).min(1).max(RETRO_ENVELOPE_WIRE_BOUNDS.gptRolesMax)
|
|
834
|
-
}).strict();
|
|
835
|
-
/**
|
|
836
|
-
* Strict: only `claude` and `gpt`, both optional, no third slot.
|
|
837
|
-
*
|
|
838
|
-
* A lane may have no usable native session data. The build gate accepts that
|
|
839
|
-
* state and the advisory sink journals the valid envelope with its recorded
|
|
840
|
-
* gaps, without inventing usage.
|
|
841
|
-
*/
|
|
842
|
-
const retroTokenFamiliesSchema = z.object({
|
|
843
|
-
claude: claudeTokenBlockSchema.optional(),
|
|
844
|
-
gpt: gptTokenBlockSchema.optional()
|
|
845
|
-
}).strict();
|
|
846
|
-
const RetroCycleCountersSchema = z.object({
|
|
847
|
-
gateRunsToFirstGreen: nonNegInt,
|
|
848
|
-
reviewerFixRounds: nonNegInt,
|
|
849
|
-
thermoFixRounds: nonNegInt
|
|
850
|
-
}).strict();
|
|
851
|
-
const RetroOutcomeSchema = z.object({
|
|
852
|
-
status: z.enum([
|
|
853
|
-
"success",
|
|
854
|
-
"blocked",
|
|
855
|
-
"fail",
|
|
856
|
-
"ship-with-followups"
|
|
857
|
-
]),
|
|
858
|
-
verdict: boundedText.optional()
|
|
859
|
-
}).strict();
|
|
860
|
-
const retroEnvelopeV1Schema = z.object({
|
|
861
|
-
agentRunId: boundedName,
|
|
862
|
-
/** Pointer (key/URL) to #8's durable proof archive — never the payload. */
|
|
863
|
-
archiveRef: z.string().min(1).max(RETRO_ENVELOPE_WIRE_BOUNDS.archiveRefMaxChars).optional(),
|
|
864
|
-
cycles: RetroCycleCountersSchema,
|
|
865
|
-
dataGaps: z.array(boundedText).max(RETRO_ENVELOPE_WIRE_BOUNDS.listMax).default([]),
|
|
866
|
-
gates: z.array(RetroGateLedgerEntrySchema).max(RETRO_ENVELOPE_WIRE_BOUNDS.gatesMax),
|
|
867
|
-
generatedAt: isoTimestamp,
|
|
868
|
-
interventions: z.object({ count: nonNegInt }).strict(),
|
|
869
|
-
joinKeys: RetroJoinKeysSchema,
|
|
870
|
-
kind: z.literal("retro-envelope"),
|
|
871
|
-
outcome: RetroOutcomeSchema.optional(),
|
|
872
|
-
phases: z.array(RetroPhaseMarkSchema).max(RETRO_ENVELOPE_WIRE_BOUNDS.listMax),
|
|
873
|
-
refs: RetroRefsSchema,
|
|
874
|
-
repo: RetroRepoSchema,
|
|
875
|
-
schemaVersion: z.literal(1),
|
|
876
|
-
tokenFamilies: retroTokenFamiliesSchema,
|
|
877
|
-
wallClock: RetroWallClockSchema
|
|
878
|
-
}).strict().refine((envelope) => envelope.refs.epic !== void 0 || envelope.refs.issue !== void 0 || envelope.refs.prNumber !== void 0, { message: "refs must anchor to an epic, issue, or PR" });
|
|
879
|
-
/**
|
|
880
|
-
* Version 1 envelopes written before #170 carried an inert `harness` label.
|
|
881
|
-
* Accept that exact historical field and normalize it away. The current strict
|
|
882
|
-
* schema above neither emits nor exposes it, and all other unknown fields still
|
|
883
|
-
* fail validation.
|
|
884
|
-
*/
|
|
885
|
-
const retroEnvelopeV1ReaderSchema = z.preprocess((candidate) => {
|
|
886
|
-
if (candidate === null || typeof candidate !== "object" || Array.isArray(candidate) || !("harness" in candidate)) return candidate;
|
|
887
|
-
const record = candidate;
|
|
888
|
-
if (!boundedShortName.safeParse(record.harness).success) return candidate;
|
|
889
|
-
const { harness: _historicalHarness, ...current } = record;
|
|
890
|
-
return current;
|
|
891
|
-
}, retroEnvelopeV1Schema);
|
|
892
|
-
/**
|
|
893
|
-
* Versioned payload validators, keyed by schema major. Unknown majors never
|
|
894
|
-
* reach these — {@link parseRetroEnvelope} returns them raw and marked
|
|
895
|
-
* degraded, mirroring HQ's ingest skew posture (stored raw, never dropped).
|
|
896
|
-
*/
|
|
897
|
-
const RETRO_ENVELOPE_VALIDATORS = { [1]: retroEnvelopeV1ReaderSchema };
|
|
898
|
-
const SUPPORTED_RETRO_ENVELOPE_SCHEMA_VERSIONS = Object.keys(RETRO_ENVELOPE_VALIDATORS).map(Number);
|
|
899
|
-
/** Epic anchor for an envelope: refs.epic, else refs.issue, else the PR. */
|
|
900
|
-
const retroEpicReference = (refs) => {
|
|
901
|
-
return (refs.epic ?? refs.issue ?? (refs.prNumber === void 0 ? void 0 : `pr#${refs.prNumber}`))?.replace(/^#/u, "");
|
|
902
|
-
};
|
|
903
680
|
//#endregion
|
|
904
681
|
//#region src/schemas.ts
|
|
905
682
|
const EVIDENCE_CHECK_TYPES = ["review", "verify"];
|
|
@@ -970,81 +747,6 @@ const evidenceEnvelopeSchema = z.object({
|
|
|
970
747
|
});
|
|
971
748
|
}
|
|
972
749
|
});
|
|
973
|
-
const SUPPORTED_PR_VERIFY_SCHEMA_VERSIONS = [
|
|
974
|
-
1,
|
|
975
|
-
2,
|
|
976
|
-
3,
|
|
977
|
-
4
|
|
978
|
-
];
|
|
979
|
-
const executedCommandSchema = z.object({
|
|
980
|
-
command: z.string().min(1),
|
|
981
|
-
counts: z.object({
|
|
982
|
-
testFiles: z.number().nonnegative().optional(),
|
|
983
|
-
tests: z.number().nonnegative().optional()
|
|
984
|
-
}).optional(),
|
|
985
|
-
durationMs: z.number().nonnegative(),
|
|
986
|
-
exitCode: z.number(),
|
|
987
|
-
name: z.string().min(1),
|
|
988
|
-
scope: z.enum([
|
|
989
|
-
"always",
|
|
990
|
-
"docs-only",
|
|
991
|
-
"trivial",
|
|
992
|
-
"full"
|
|
993
|
-
])
|
|
994
|
-
});
|
|
995
|
-
const notRequiredCommandSchema = z.object({
|
|
996
|
-
basis: z.string().min(1),
|
|
997
|
-
impactTarget: z.string().min(1),
|
|
998
|
-
name: z.string().min(1)
|
|
999
|
-
});
|
|
1000
|
-
const prVerifySchemaVersionSchema = z.number().refine((value) => SUPPORTED_PR_VERIFY_SCHEMA_VERSIONS.includes(value), { message: `schemaVersion must be one of: ${SUPPORTED_PR_VERIFY_SCHEMA_VERSIONS.join(", ")}` });
|
|
1001
|
-
const prVerifyProofSchema = z.object({
|
|
1002
|
-
authoringSession: z.string().trim().min(1).optional(),
|
|
1003
|
-
base: z.string().min(1),
|
|
1004
|
-
baselineFullProofs: z.array(z.object({
|
|
1005
|
-
base: z.string().min(1),
|
|
1006
|
-
changedFiles: z.array(z.string()),
|
|
1007
|
-
headSha: z.string().regex(/^[0-9a-f]{40}$/u),
|
|
1008
|
-
profilePath: z.string().min(1),
|
|
1009
|
-
projectKey: z.string().min(1),
|
|
1010
|
-
repository: z.string().min(1)
|
|
1011
|
-
})).optional(),
|
|
1012
|
-
changedFiles: z.array(z.string()),
|
|
1013
|
-
classification: z.enum(DIFF_CLASSIFICATIONS),
|
|
1014
|
-
classificationReasons: z.array(z.string()),
|
|
1015
|
-
command: z.literal("patronage-factory pr:verify"),
|
|
1016
|
-
durationMs: z.number().nonnegative(),
|
|
1017
|
-
endedAt: z.iso.datetime(),
|
|
1018
|
-
executedCommands: z.array(executedCommandSchema).optional(),
|
|
1019
|
-
headSha: z.string().regex(/^[0-9a-f]{40}$/u),
|
|
1020
|
-
impactStamp: impactStampSchema.optional(),
|
|
1021
|
-
mergeBaseSha: z.string().regex(/^[0-9a-f]{40}$/u).optional(),
|
|
1022
|
-
mode: z.enum([
|
|
1023
|
-
"docs-only",
|
|
1024
|
-
"trivial",
|
|
1025
|
-
"full"
|
|
1026
|
-
]),
|
|
1027
|
-
notRequiredCommands: z.array(notRequiredCommandSchema).min(1).optional(),
|
|
1028
|
-
outcome: z.enum(["aborted", "passed"]).optional(),
|
|
1029
|
-
patchId: z.string().regex(/^[0-9a-f]{40}$/u).optional(),
|
|
1030
|
-
profilePath: z.string().min(1),
|
|
1031
|
-
projectKey: z.string().min(1),
|
|
1032
|
-
repository: z.string().min(1),
|
|
1033
|
-
schemaVersion: prVerifySchemaVersionSchema,
|
|
1034
|
-
startedAt: z.iso.datetime(),
|
|
1035
|
-
verificationCommands: z.array(z.object({
|
|
1036
|
-
command: z.string().min(1),
|
|
1037
|
-
description: z.string().min(1),
|
|
1038
|
-
impactTarget: z.string().min(1).optional(),
|
|
1039
|
-
name: z.string().min(1),
|
|
1040
|
-
scope: z.enum([
|
|
1041
|
-
"always",
|
|
1042
|
-
"docs-only",
|
|
1043
|
-
"trivial",
|
|
1044
|
-
"full"
|
|
1045
|
-
])
|
|
1046
|
-
}))
|
|
1047
|
-
}).superRefine(assertPrVerifyProofRules);
|
|
1048
750
|
const PR_REVIEW_SCHEMA_VERSION = 2;
|
|
1049
751
|
const PR_REVIEW_FINDING_PROVENANCE_VERSION = 1;
|
|
1050
752
|
const nonBlankString = z.string().refine((value) => value.trim().length > 0, { message: "must not be blank" });
|
|
@@ -1202,7 +904,7 @@ const prReviewProofSchema = z.object({
|
|
|
1202
904
|
patchId: z.string().regex(/^[0-9a-f]{40,64}$/u),
|
|
1203
905
|
reviewCycle: z.number().int().positive().optional(),
|
|
1204
906
|
reviewRequirement: z.object({
|
|
1205
|
-
reason: z.enum(["no-applicable-mode"]),
|
|
907
|
+
reason: z.enum(["no-applicable-mode", "faithful-merge"]),
|
|
1206
908
|
status: z.literal("not-required")
|
|
1207
909
|
}).strict().optional(),
|
|
1208
910
|
reviews: z.array(prReviewResultSchema),
|
|
@@ -1518,4 +1220,4 @@ const prReadyProofSchema = z.object({
|
|
|
1518
1220
|
for (const issue of blockedReasonIssues({ ...proof })) context.addIssue(issue);
|
|
1519
1221
|
});
|
|
1520
1222
|
//#endregion
|
|
1521
|
-
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,
|
|
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 };
|