@patronage/software-factory 1.0.0-alpha.2 → 1.0.0-alpha.20

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
@@ -241,15 +241,17 @@ const impactStampTargetSchema = z.object({
241
241
  */
242
242
  const impactStampSchema = z.object({
243
243
  basis: z.enum(["target-scoped", "conservative"]),
244
+ inertPaths: z.array(z.string()).default([]),
244
245
  reasons: z.array(z.string()),
245
- stampVersion: z.literal(2),
246
+ stampVersion: z.literal(3),
246
247
  targets: z.array(impactStampTargetSchema).superRefine((targets, context) => {
247
248
  const names = targets.map((target) => target.name);
248
249
  if (new Set(names).size !== names.length) context.addIssue({
249
250
  code: "custom",
250
251
  message: "impact stamp targets must have distinct names."
251
252
  });
252
- })
253
+ }),
254
+ unsubscribedPaths: z.array(z.string())
253
255
  });
254
256
  /**
255
257
  * The surfaces that consume the stamp. One classification, consumed
@@ -287,8 +289,8 @@ const impactStampScopeDecision = ({ stamp, surface, targetName }) => {
287
289
  reason: `no trusted identity-bound impact stamp covers this candidate; ${floor}`,
288
290
  scoped: false
289
291
  };
290
- if (stamp.stampVersion !== 2) return {
291
- reason: `impact stamp version ${String(stamp.stampVersion)} is not this build's version 2; ${floor}`,
292
+ if (stamp.stampVersion !== 3) return {
293
+ reason: `impact stamp version ${String(stamp.stampVersion)} is not this build's version 3; ${floor}`,
292
294
  scoped: false
293
295
  };
294
296
  if (stamp.basis !== "target-scoped") return {
@@ -513,85 +515,129 @@ const assertPrVerifyProofRules = (proof, context) => {
513
515
  message: "notRequiredCommands is a schemaVersion>=4 field; a v1–v3 pr:verify proof must not carry it.",
514
516
  path: ["notRequiredCommands"]
515
517
  });
518
+ if (proof.schemaVersion < 4 && proof.notDemandedRecords !== void 0) context.addIssue({
519
+ code: "custom",
520
+ message: "notDemandedRecords is a schemaVersion>=4 field; a v1–v3 pr:verify proof must not carry it.",
521
+ path: ["notDemandedRecords"]
522
+ });
523
+ if (proof.notDemandedRecords !== void 0 && proof.impactStamp === void 0) context.addIssue({
524
+ code: "custom",
525
+ message: "notDemandedRecords requires impactStamp; package-level not-demanded records are bound to the stamp identity.",
526
+ path: ["notDemandedRecords"]
527
+ });
516
528
  assertBatteryCompleteness(proof, context);
517
529
  assertNotRequiredStampAuthorization(proof, context);
518
530
  };
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];
531
+ //#endregion
532
+ //#region src/pr-verify-proof-contract.ts
533
+ const SUPPORTED_PR_VERIFY_SCHEMA_VERSIONS = [
534
+ 1,
535
+ 2,
536
+ 3,
537
+ 4
538
+ ];
539
+ const executedCommandSchema = z.object({
540
+ command: z.string().min(1),
541
+ counts: z.object({
542
+ testFiles: z.number().nonnegative().optional(),
543
+ tests: z.number().nonnegative().optional()
544
+ }).optional(),
545
+ durationMs: z.number().nonnegative(),
546
+ exitCode: z.number(),
547
+ name: z.string().min(1),
548
+ scope: z.enum([
549
+ "always",
550
+ "docs-only",
551
+ "trivial",
552
+ "full"
553
+ ])
554
+ });
555
+ const notRequiredCommandSchema = z.object({
556
+ basis: z.string().min(1),
557
+ impactTarget: z.string().min(1),
558
+ name: z.string().min(1)
559
+ });
560
+ const notDemandedRecordSchema = z.object({
561
+ basis: z.string().min(1),
562
+ name: z.string().min(1)
563
+ });
564
+ 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(", ")}` });
565
+ const prVerifyProofSchema = z.object({
566
+ authoringSession: z.string().trim().min(1).optional(),
567
+ base: z.string().min(1),
568
+ baselineFullProofs: z.array(z.object({
569
+ base: z.string().min(1),
570
+ changedFiles: z.array(z.string()),
571
+ headSha: z.string().regex(/^[0-9a-f]{40}$/u),
572
+ profilePath: z.string().min(1),
573
+ projectKey: z.string().min(1),
574
+ repository: z.string().min(1)
575
+ })).optional(),
576
+ changedFiles: z.array(z.string()),
577
+ classification: z.enum([
578
+ "docs/process-only",
579
+ "trivial",
580
+ "non-trivial"
581
+ ]),
582
+ classificationReasons: z.array(z.string()),
583
+ command: z.literal("patronage-factory pr:verify"),
584
+ durationMs: z.number().nonnegative(),
585
+ endedAt: z.iso.datetime(),
586
+ executedCommands: z.array(executedCommandSchema).optional(),
587
+ headSha: z.string().regex(/^[0-9a-f]{40}$/u),
588
+ impactStamp: impactStampSchema.optional(),
589
+ mergeBaseSha: z.string().regex(/^[0-9a-f]{40}$/u).optional(),
590
+ mode: z.enum([
591
+ "docs-only",
592
+ "trivial",
593
+ "full"
594
+ ]),
595
+ notDemandedRecords: z.array(notDemandedRecordSchema).min(1).optional(),
596
+ notRequiredCommands: z.array(notRequiredCommandSchema).min(1).optional(),
597
+ outcome: z.enum(["aborted", "passed"]).optional(),
598
+ patchId: z.string().regex(/^[0-9a-f]{40}$/u).optional(),
599
+ profilePath: z.string().min(1),
600
+ projectKey: z.string().min(1),
601
+ repository: z.string().min(1),
602
+ schemaVersion: prVerifySchemaVersionSchema,
603
+ startedAt: z.iso.datetime(),
604
+ verificationCommands: z.array(z.object({
605
+ command: z.string().min(1),
606
+ description: z.string().min(1),
607
+ impactTarget: z.string().min(1).optional(),
608
+ name: z.string().min(1),
609
+ scope: z.enum([
610
+ "always",
611
+ "docs-only",
612
+ "trivial",
613
+ "full"
614
+ ])
615
+ }))
616
+ }).superRefine(assertPrVerifyProofRules);
617
+ /** Artifact versions accepted at ingest. */
618
+ const SUPPORTED_CLOSEOUT_SCHEMA_VERSIONS = [5];
522
619
  const MAX_CLOSEOUT_ROWS = 1e3;
523
620
  const IdentifierSchema = z.string().min(1).max(500);
524
621
  const NarrativeSchema = z.string().min(1).max(1e4);
525
622
  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({
623
+ const CloseoutSourceSchema = z.object({
534
624
  detail: NarrativeSchema,
535
- kind: z.literal("boundary-thermo-attestation")
536
- }).strict();
537
- const LegacyCloseoutSourceSchema = z.object({
538
- 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()
625
+ kind: z.literal("token-harvest")
551
626
  }).strict();
552
627
  const CloseoutBudgetSchema = z.object({
553
628
  note: NarrativeSchema,
554
629
  tier: z.literal("overseer")
555
630
  }).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
631
  const CloseoutMetricRowSchema = z.object({
569
632
  label: IdentifierSchema,
570
633
  metric: IdentifierSchema,
571
- source: CurrentCloseoutSourceSchema,
634
+ source: CloseoutSourceSchema,
572
635
  unit: z.enum([
573
- "count",
574
636
  "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
637
  "count",
592
- "ratio",
593
- "boolean",
594
- "text"
638
+ "text",
639
+ "tokens",
640
+ "usd"
595
641
  ]),
596
642
  value: z.union([
597
643
  z.number(),
@@ -600,306 +646,21 @@ const LegacyCloseoutMetricRowSchema = z.object({
600
646
  z.null()
601
647
  ])
602
648
  }).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
649
  const CloseoutBlindspotSchema = z.object({
618
650
  id: IdentifierSchema,
619
651
  note: NarrativeSchema,
620
652
  reason: NarrativeSchema.optional()
621
653
  }).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,
654
+ /** Strict, bounded runtime transport contract emitted by factory:closeout. */
655
+ const closeoutArtifactSchema = z.object({
656
+ budget: CloseoutBudgetSchema,
648
657
  couldNotSee: z.array(CloseoutBlindspotSchema).min(1).max(MAX_CLOSEOUT_ROWS),
649
658
  epic: IdentifierSchema,
650
659
  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),
660
+ metrics: z.array(CloseoutMetricRowSchema).max(MAX_CLOSEOUT_ROWS),
654
661
  repo: RepoSchema,
655
- schemaVersion: z.literal(input.schemaVersion)
656
- }).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
662
+ schemaVersion: z.literal(5)
814
663
  }).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
664
  //#endregion
904
665
  //#region src/schemas.ts
905
666
  const EVIDENCE_CHECK_TYPES = ["review", "verify"];
@@ -970,81 +731,6 @@ const evidenceEnvelopeSchema = z.object({
970
731
  });
971
732
  }
972
733
  });
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
734
  const PR_REVIEW_SCHEMA_VERSION = 2;
1049
735
  const PR_REVIEW_FINDING_PROVENANCE_VERSION = 1;
1050
736
  const nonBlankString = z.string().refine((value) => value.trim().length > 0, { message: "must not be blank" });
@@ -1518,4 +1204,4 @@ const prReadyProofSchema = z.object({
1518
1204
  for (const issue of blockedReasonIssues({ ...proof })) context.addIssue(issue);
1519
1205
  });
1520
1206
  //#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 };
1207
+ 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.2",
3
+ "version": "1.0.0-alpha.20",
4
4
  "description": "Shared Patronage software factory CLI and project-profile validation tools",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -29,13 +29,15 @@
29
29
  }
30
30
  },
31
31
  "publishConfig": {
32
- "access": "public"
32
+ "access": "public",
33
+ "tag": "next"
33
34
  },
34
35
  "dependencies": {
35
36
  "commander": "14.0.3",
36
37
  "picomatch": "^4.0.5",
37
38
  "yaml": "^2.9.0",
38
- "zod": "4.4.3"
39
+ "zod": "4.4.3",
40
+ "@patronage/factory-ci": "1.0.0-alpha.20"
39
41
  },
40
42
  "devDependencies": {
41
43
  "@types/node": "24.13.3",
@@ -60,7 +62,8 @@
60
62
  "prebuild": "bash ../scripts/ensure-worktree-bootstrap.sh",
61
63
  "build": "tsdown",
62
64
  "boundaries": "node scripts/package-boundaries.mjs",
63
- "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",
64
67
  "precli": "bash ../scripts/ensure-worktree-bootstrap.sh",
65
68
  "cli": "tsx src/index.ts",
66
69
  "cleanup:check": "knip --files --dependencies",
@@ -75,6 +78,8 @@
75
78
  "profile:schema": "tsx scripts/generate-profile-schema.ts",
76
79
  "pretest": "bash ../scripts/ensure-worktree-bootstrap.sh",
77
80
  "test": "vitest run",
81
+ "pretest:profile": "bash ../scripts/ensure-worktree-bootstrap.sh",
82
+ "test:profile": "tsx scripts/vitest-profile.ts",
78
83
  "pretest:stress": "bash ../scripts/ensure-worktree-bootstrap.sh",
79
84
  "test:stress": "node scripts/stress-tests.mjs",
80
85
  "pretypecheck": "bash ../scripts/ensure-worktree-bootstrap.sh",