@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/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(2),
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 !== 2) return {
291
- reason: `impact stamp version ${String(stamp.stampVersion)} is not this build's version 2; ${floor}`,
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
- const LEGACY_CLOSEOUT_SCHEMA_VERSION = 3;
520
- /** Current writer plus retained reader versions accepted at ingest. */
521
- const SUPPORTED_CLOSEOUT_SCHEMA_VERSIONS = [LEGACY_CLOSEOUT_SCHEMA_VERSION, 4];
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 IsoDateSchema = z.string().regex(/^\d{4}-\d{2}-\d{2}$/u);
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.enum(["interior-telemetry", "boundary-thermo-attestation"])
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: CurrentCloseoutSourceSchema,
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
- "ratio",
593
- "boolean",
594
- "text"
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
- const closeoutLedgerRowSchema = (schemaVersion) => z.object({
623
- detail: NarrativeSchema.optional(),
624
- epic: IdentifierSchema,
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
- ledgerRows: z.array(input.ledgerRows).max(MAX_CLOSEOUT_ROWS),
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(input.schemaVersion)
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, RETRO_ENVELOPE_SCHEMA_VERSION, RETRO_ENVELOPE_VALIDATORS, RETRO_ENVELOPE_WIRE_BOUNDS, SUPPORTED_CLOSEOUT_SCHEMA_VERSIONS, SUPPORTED_PR_READY_SCHEMA_VERSIONS, SUPPORTED_PR_VERIFY_SCHEMA_VERSIONS, SUPPORTED_RETRO_ENVELOPE_SCHEMA_VERSIONS, blockedReasonSchema, blockedReasonsSchema, boundaryCheckProofSchema, boundaryReviewProofSchema, closeoutArtifactReadSchema, closeoutArtifactSchema, evidenceEnvelopeSchema, legacyCloseoutArtifactSchema, prReadyProofSchema, prReviewProofSchema, prVerifyProofSchema, retroEnvelopeV1Schema, retroEpicReference, waivedDemandSchema };
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 };