@patronage/software-factory 1.0.0-alpha.13 → 1.0.0-alpha.16

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