@patronage/software-factory 1.0.0-alpha.23 → 1.0.0-alpha.25

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.d.ts CHANGED
@@ -2,6 +2,7 @@ import { z } from "zod";
2
2
  //#region src/packages/review-prompt-sections/schema.d.ts
3
3
  declare const reviewPromptSectionSchema: z.ZodObject<{
4
4
  provenance: z.ZodEnum<{
5
+ "coding-standards": "coding-standards";
5
6
  "prior-review-ledger": "prior-review-ledger";
6
7
  "issue-review-focus": "issue-review-focus";
7
8
  }>;
@@ -12,6 +13,7 @@ type ReviewPromptSection = z.infer<typeof reviewPromptSectionSchema>;
12
13
  type ReviewPromptSectionProvenance = ReviewPromptSection["provenance"];
13
14
  declare const reviewPromptSectionsSchema: z.ZodArray<z.ZodObject<{
14
15
  provenance: z.ZodEnum<{
16
+ "coding-standards": "coding-standards";
15
17
  "prior-review-ledger": "prior-review-ledger";
16
18
  "issue-review-focus": "issue-review-focus";
17
19
  }>;
@@ -19,6 +21,23 @@ declare const reviewPromptSectionsSchema: z.ZodArray<z.ZodObject<{
19
21
  text: z.ZodString;
20
22
  }, z.core.$strict>>;
21
23
  //#endregion
24
+ //#region src/policy-resolution.d.ts
25
+ /** How a gate resolved the review policy it evaluated a candidate under. */
26
+ declare const policyResolutionSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
27
+ basePolicyDigest: z.ZodString;
28
+ baseRefName: z.ZodString;
29
+ candidatePolicyDigest: z.ZodString;
30
+ effectiveDigest: z.ZodString;
31
+ policyBaseSha: z.ZodString;
32
+ reviewPolicyChanged: z.ZodBoolean;
33
+ status: z.ZodLiteral<"authoritative">;
34
+ }, z.core.$strict>, z.ZodObject<{
35
+ baseRefName: z.ZodString;
36
+ reason: z.ZodString;
37
+ status: z.ZodLiteral<"unresolved">;
38
+ }, z.core.$strict>], "status">;
39
+ type PolicyResolution = z.infer<typeof policyResolutionSchema>;
40
+ //#endregion
22
41
  //#region src/review-ladder-policy.d.ts
23
42
  type ReviewFindingCategory = "correctness" | "safety" | "coordination" | "maintainability" | "unknown";
24
43
  declare const FINDING_DISPOSITIONS: readonly ["open", "fixed-in-thread", "follow-up-filed", "waived", "stale-repeat", "rerun-noise"];
@@ -75,6 +94,7 @@ interface ReviewLadderCycle {
75
94
  headSha: string;
76
95
  patchId: string;
77
96
  };
97
+ historical?: true;
78
98
  model?: string;
79
99
  correlatedRerun?: boolean;
80
100
  session?: LadderSessionAttribution;
@@ -126,6 +146,13 @@ interface PrReviewProof {
126
146
  changedFiles: string[];
127
147
  cleanedPaths: string[];
128
148
  ladder?: PrReviewLadderState;
149
+ /**
150
+ * The policy identity this review run resolved its demanded kinds under
151
+ * (#922 Slice B). Optional: a run that could not reach the authority records
152
+ * the refusal instead, and `pr:ready` decides what an unresolved or
153
+ * disagreeing identity means for admission.
154
+ */
155
+ policyResolution?: PolicyResolution;
129
156
  reviewRequirement?: {
130
157
  reason: "no-applicable-mode" | "faithful-merge";
131
158
  status: "not-required";
@@ -225,21 +252,21 @@ declare const boundaryReviewProofBaseSchema: z.ZodObject<{
225
252
  }, z.core.$loose>;
226
253
  type BoundaryReviewProof = z.infer<typeof boundaryReviewProofBaseSchema>;
227
254
  //#endregion
228
- //#region src/follow-up.d.ts
229
- interface FollowUpAction {
230
- /**
231
- * Machine-safe argv-style array for the obvious next action, e.g.
232
- * ["gh","pr","merge","513","--squash"]. Same shape and field name that
233
- * `factory:delegate --print` emits, so an orchestrating agent can spawn the
234
- * next command without reconstructing a shell string. argv[0] is the
235
- * executable; the rest are literal, already-split arguments.
236
- */
237
- argv: string[];
255
+ //#region src/catch-up-recognition-record.d.ts
256
+ interface CatchUpRecognition {
257
+ /** The ref the candidate targets, as the recognizing command resolved it. */
258
+ baseRef: string;
259
+ /** The live target tip at recognition time; the head's first parent. */
260
+ baseTipSha: string;
261
+ /** The head's second parent: the admitted base-side content merged in. */
262
+ mergedParentSha: string;
238
263
  /**
239
- * Human-readable rendering of {@link argv} for logs and operators. Additive
240
- * to argv, never a substitute for it — argv is the source of truth.
264
+ * The tree `git merge-tree --write-tree` produced for the two parents. It
265
+ * equals the head's own tree; that equality is the faithfulness proof.
241
266
  */
242
- command: string;
267
+ mergedTreeSha: string;
268
+ /** The ref that proves the second parent is admitted content. */
269
+ upstreamRef: string;
243
270
  }
244
271
  //#endregion
245
272
  //#region src/diff-classification.d.ts
@@ -274,6 +301,23 @@ declare const impactStampSchema: z.ZodObject<{
274
301
  }, z.core.$strip>;
275
302
  type ImpactStamp = z.infer<typeof impactStampSchema>;
276
303
  //#endregion
304
+ //#region src/follow-up.d.ts
305
+ interface FollowUpAction {
306
+ /**
307
+ * Machine-safe argv-style array for the obvious next action, e.g.
308
+ * ["gh","pr","merge","513","--squash"]. Same shape and field name that
309
+ * `factory:delegate --print` emits, so an orchestrating agent can spawn the
310
+ * next command without reconstructing a shell string. argv[0] is the
311
+ * executable; the rest are literal, already-split arguments.
312
+ */
313
+ argv: string[];
314
+ /**
315
+ * Human-readable rendering of {@link argv} for logs and operators. Additive
316
+ * to argv, never a substitute for it — argv is the source of truth.
317
+ */
318
+ command: string;
319
+ }
320
+ //#endregion
277
321
  //#region src/pr-verify-mode.d.ts
278
322
  /**
279
323
  * The verification mode `pr:verify` resolved for a run.
@@ -388,12 +432,12 @@ declare const managedReadinessLedgerSchema: z.ZodObject<{
388
432
  "interior-cap-reached": "interior-cap-reached";
389
433
  }>>;
390
434
  nextAction: z.ZodEnum<{
391
- "run-interior-cycle": "run-interior-cycle";
392
- "advance-to-gate": "advance-to-gate";
393
435
  "run-gate-cycle": "run-gate-cycle";
394
436
  "accept-nonblocking-findings": "accept-nonblocking-findings";
395
437
  "escalate-to-triage": "escalate-to-triage";
396
438
  "ready-for-human": "ready-for-human";
439
+ "advance-to-gate": "advance-to-gate";
440
+ "run-interior-cycle": "run-interior-cycle";
397
441
  }>;
398
442
  stage: z.ZodEnum<{
399
443
  gate: "gate";
@@ -461,23 +505,6 @@ type ReadinessRepair = z.infer<typeof readinessRepairSchema>;
461
505
  type ReadinessStatus = "ready" | "blocked";
462
506
  type ManagedReadinessLedger = z.infer<typeof managedReadinessLedgerSchema>;
463
507
  //#endregion
464
- //#region src/catch-up-recognition-record.d.ts
465
- interface CatchUpRecognition {
466
- /** The ref the candidate targets, as the recognizing command resolved it. */
467
- baseRef: string;
468
- /** The live target tip at recognition time; the head's first parent. */
469
- baseTipSha: string;
470
- /** The head's second parent: the admitted base-side content merged in. */
471
- mergedParentSha: string;
472
- /**
473
- * The tree `git merge-tree --write-tree` produced for the two parents. It
474
- * equals the head's own tree; that equality is the faithfulness proof.
475
- */
476
- mergedTreeSha: string;
477
- /** The ref that proves the second parent is admitted content. */
478
- upstreamRef: string;
479
- }
480
- //#endregion
481
508
  //#region src/pr-verify-proof-contract.d.ts
482
509
  declare const SUPPORTED_PR_VERIFY_SCHEMA_VERSIONS: readonly [1, 2, 3, 4];
483
510
  type PrVerifySchemaVersion = (typeof SUPPORTED_PR_VERIFY_SCHEMA_VERSIONS)[number];
@@ -560,6 +587,16 @@ interface PrVerifyProof {
560
587
  notRequiredCommands?: PrVerifyNotRequiredCommand[];
561
588
  outcome?: "aborted" | "passed";
562
589
  patchId?: string;
590
+ /**
591
+ * The review policy identity this verification run resolved (#922 Slice B).
592
+ *
593
+ * Verification demands no review mode of its own. It records the identity so
594
+ * `pr:ready` can see whether the stage that produced this proof judged the
595
+ * candidate under the same policy readiness resolves. Optional, and normally
596
+ * `unresolved` on the first run of a branch: a candidate is verified before
597
+ * it has a pull request, and there is no authority to read without one.
598
+ */
599
+ policyResolution?: PolicyResolution;
563
600
  profilePath: string;
564
601
  projectKey: string;
565
602
  repository: string;
@@ -644,6 +681,19 @@ declare const prVerifyProofSchema: z.ZodObject<{
644
681
  aborted: "aborted";
645
682
  }>>;
646
683
  patchId: z.ZodOptional<z.ZodString>;
684
+ policyResolution: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
685
+ basePolicyDigest: z.ZodString;
686
+ baseRefName: z.ZodString;
687
+ candidatePolicyDigest: z.ZodString;
688
+ effectiveDigest: z.ZodString;
689
+ policyBaseSha: z.ZodString;
690
+ reviewPolicyChanged: z.ZodBoolean;
691
+ status: z.ZodLiteral<"authoritative">;
692
+ }, z.core.$strict>, z.ZodObject<{
693
+ baseRefName: z.ZodString;
694
+ reason: z.ZodString;
695
+ status: z.ZodLiteral<"unresolved">;
696
+ }, z.core.$strict>], "status">>;
647
697
  profilePath: z.ZodString;
648
698
  projectKey: z.ZodString;
649
699
  repository: z.ZodString;
@@ -713,6 +763,13 @@ interface PrReadyProof {
713
763
  * waived demand reads to a human. Absent when there are none.
714
764
  */
715
765
  notices?: string[];
766
+ /**
767
+ * The policy identity readiness resolved review demand under (#922): the
768
+ * base ref, its live tip, and the digests of the base, candidate, and merged
769
+ * review policies. Optional on the wire and additive — this is a new record
770
+ * on an unchanged contract, so no schema version moves.
771
+ */
772
+ policyResolution?: PolicyResolution;
716
773
  /** Committed profile blob evaluated by readiness. Required for merge. */
717
774
  profileBlobSha?: string;
718
775
  /** Checkout-relative committed profile path evaluated by readiness. */
@@ -1086,20 +1143,18 @@ declare const prReadyProofSchema: z.ZodObject<{
1086
1143
  forcedTransition: z.ZodOptional<z.ZodEnum<{
1087
1144
  "gate-cap-exhausted": "gate-cap-exhausted";
1088
1145
  "interior-cap-reached": "interior-cap-reached";
1089
- "interior-takeover": "interior-takeover";
1090
1146
  }>>;
1091
1147
  nextAction: z.ZodEnum<{
1092
- "run-interior-cycle": "run-interior-cycle";
1093
- "advance-to-gate": "advance-to-gate";
1094
1148
  "run-gate-cycle": "run-gate-cycle";
1095
1149
  "accept-nonblocking-findings": "accept-nonblocking-findings";
1096
1150
  "escalate-to-triage": "escalate-to-triage";
1097
1151
  "ready-for-human": "ready-for-human";
1152
+ "advance-to-gate": "advance-to-gate";
1153
+ "run-interior-cycle": "run-interior-cycle";
1098
1154
  }>;
1099
1155
  stage: z.ZodEnum<{
1100
1156
  gate: "gate";
1101
1157
  interior: "interior";
1102
- "interior-takeover": "interior-takeover";
1103
1158
  "interior-complete": "interior-complete";
1104
1159
  }>;
1105
1160
  }, z.core.$strip>>;
@@ -1166,6 +1221,19 @@ declare const prReadyProofSchema: z.ZodObject<{
1166
1221
  }, z.core.$strict>], "prVerify">;
1167
1222
  }, z.core.$strip>;
1168
1223
  notices: z.ZodOptional<z.ZodArray<z.ZodString>>;
1224
+ policyResolution: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
1225
+ basePolicyDigest: z.ZodString;
1226
+ baseRefName: z.ZodString;
1227
+ candidatePolicyDigest: z.ZodString;
1228
+ effectiveDigest: z.ZodString;
1229
+ policyBaseSha: z.ZodString;
1230
+ reviewPolicyChanged: z.ZodBoolean;
1231
+ status: z.ZodLiteral<"authoritative">;
1232
+ }, z.core.$strict>, z.ZodObject<{
1233
+ baseRefName: z.ZodString;
1234
+ reason: z.ZodString;
1235
+ status: z.ZodLiteral<"unresolved">;
1236
+ }, z.core.$strict>], "status">>;
1169
1237
  profileBlobSha: z.ZodOptional<z.ZodString>;
1170
1238
  profilePath: z.ZodOptional<z.ZodString>;
1171
1239
  repairs: z.ZodDefault<z.ZodArray<z.ZodObject<{
package/dist/schemas.js CHANGED
@@ -2,12 +2,12 @@ import { z } from "zod";
2
2
  import "yaml";
3
3
  import "picomatch";
4
4
  //#region src/review-rungs.ts
5
- const EVIDENCE_REVIEW_RUNGS$1 = [
5
+ const EVIDENCE_REVIEW_RUNGS = [
6
6
  "independent-model",
7
7
  "oracle",
8
8
  "human"
9
9
  ];
10
- Object.fromEntries(EVIDENCE_REVIEW_RUNGS$1.map((rung, index) => [rung, index]));
10
+ Object.fromEntries(EVIDENCE_REVIEW_RUNGS.map((rung, index) => [rung, index]));
11
11
  //#endregion
12
12
  //#region src/demand-keys.ts
13
13
  /** The demands that exist at most once per candidate. */
@@ -19,6 +19,13 @@ const DEMAND_KEYS = {
19
19
  * happen.
20
20
  */
21
21
  boundaryWave: "boundary-wave",
22
+ /**
23
+ * Closeout pin (#901): a merged interior's declared child issue is still
24
+ * open. The factory does not close issues; the operator closes, or waives
25
+ * this demand for that interior PR. In force at `boundary:check --final`,
26
+ * not at interior `boundary:check` and not at `pr:ready`.
27
+ */
28
+ childIssueOpen: "child-issue-open",
22
29
  /** The PR is still a draft. */
23
30
  draft: "draft",
24
31
  /** GitHub's own check rollup for the candidate head. */
@@ -37,6 +44,13 @@ const DEMAND_KEYS = {
37
44
  * neither a review-rung authority nor a scheduling requirement.
38
45
  */
39
46
  nativeApproval: "native-approval",
47
+ /**
48
+ * The policy identity an earlier stage recorded must be the one readiness
49
+ * resolved (#922 Slice B). In force when a consumed `pr:verify` or
50
+ * `pr:review` proof judged this candidate under a different review policy,
51
+ * or recorded none where the resolved demand includes a security review.
52
+ */
53
+ policyIdentity: "policy-identity",
40
54
  /** A current, head-bound, passing typed `pr:verify` proof. */
41
55
  prVerify: "pr-verify",
42
56
  /** The profile-resolved review ladder policy. */
@@ -116,7 +130,7 @@ const qualifierIsMintable = (family, qualifier) => {
116
130
  if (!QUALIFIER_PATTERN.test(qualifier)) return false;
117
131
  const decoded = decodeQualifier(qualifier);
118
132
  if (decoded === void 0 || demandQualifier(decoded) !== qualifier) return false;
119
- if (family === "review-rung") return EVIDENCE_REVIEW_RUNGS$1.includes(decoded);
133
+ if (family === "review-rung") return EVIDENCE_REVIEW_RUNGS.includes(decoded);
120
134
  if (family === "review-mode") return REVIEW_MODE_DEMAND_VALUES.includes(decoded);
121
135
  return true;
122
136
  };
@@ -234,8 +248,71 @@ const waivedDemandSchema = z.object({
234
248
  unmetReasons: z.array(z.string().min(1)).min(1)
235
249
  });
236
250
  //#endregion
251
+ //#region src/diff-classification.ts
252
+ const DIFF_CLASSIFICATIONS = [
253
+ "docs/process-only",
254
+ "trivial",
255
+ "non-trivial"
256
+ ];
257
+ //#endregion
258
+ //#region src/policy-resolution.ts
259
+ const COMMIT_SHA_PATTERN = /^[0-9a-f]{40}$/u;
260
+ const digestSchema = z.string().regex(/^[0-9a-f]{64}$/u);
261
+ /**
262
+ * The authority resolved: the live tip of the PR's own base ref served a
263
+ * parseable profile, and demand was resolved from the union of that policy and
264
+ * the candidate's.
265
+ *
266
+ * Every identity field is REQUIRED. A cross-stage reader compares these digests
267
+ * to decide whether two stages judged the same candidate under the same policy;
268
+ * an `authoritative` record with a digest missing would read to that reader as
269
+ * "nothing disagrees", which is a silent downgrade of exactly the demand this
270
+ * record exists to protect. `reason` is forbidden here — a resolved authority
271
+ * has nothing to explain.
272
+ */
273
+ const authoritativePolicyResolutionSchema = z.object({
274
+ /** sha256 of the canonical base `review` subtree. */
275
+ basePolicyDigest: digestSchema,
276
+ /** The PR's own base ref, whose live tip is the authority. */
277
+ baseRefName: z.string().min(1),
278
+ /** sha256 of the canonical candidate `review` subtree. */
279
+ candidatePolicyDigest: digestSchema,
280
+ /** sha256 of the merged (base ∪ candidate) protected-path set. */
281
+ effectiveDigest: digestSchema,
282
+ /** The base-ref tip the base policy was read at. */
283
+ policyBaseSha: z.string().regex(COMMIT_SHA_PATTERN),
284
+ /**
285
+ * Whether the candidate's `review` subtree differs from the base's. The
286
+ * digests prove which policies were read; this says whether they agreed,
287
+ * which is the fact that explains a demand the candidate's own profile
288
+ * would not have produced.
289
+ */
290
+ reviewPolicyChanged: z.boolean(),
291
+ status: z.literal("authoritative")
292
+ }).strict();
293
+ /**
294
+ * The authority could not be read, and `reason` says why — required, because an
295
+ * unresolved record with no reason is an unactionable refusal. `pr:ready`
296
+ * refuses before it writes a proof, so a Slice A ready proof never carries one;
297
+ * this member is the recorded form of that refusal for the stages that carry it
298
+ * forward.
299
+ */
300
+ const unresolvedPolicyResolutionSchema = z.object({
301
+ /** The base ref whose policy could not be read. */
302
+ baseRefName: z.string().min(1),
303
+ /** Why the authority is unresolved. */
304
+ reason: z.string().min(1),
305
+ status: z.literal("unresolved")
306
+ }).strict();
307
+ /** How a gate resolved the review policy it evaluated a candidate under. */
308
+ const policyResolutionSchema = z.discriminatedUnion("status", [authoritativePolicyResolutionSchema, unresolvedPolicyResolutionSchema]);
309
+ //#endregion
237
310
  //#region src/packages/review-prompt-sections/schema.ts
238
- const REVIEW_PROMPT_SECTION_PROVENANCES = ["prior-review-ledger", "issue-review-focus"];
311
+ const REVIEW_PROMPT_SECTION_PROVENANCES = [
312
+ "coding-standards",
313
+ "prior-review-ledger",
314
+ "issue-review-focus"
315
+ ];
239
316
  const nonBlankString$1 = z.string().refine((value) => value.trim().length > 0, { message: "must not be blank" });
240
317
  const reviewPromptSectionSchema = z.object({
241
318
  provenance: z.enum(REVIEW_PROMPT_SECTION_PROVENANCES),
@@ -617,6 +694,20 @@ z.array(z.object({
617
694
  }).strict());
618
695
  //#endregion
619
696
  //#region src/review-ladder.ts
697
+ const LADDER_NEXT_ACTION_KINDS = [
698
+ "run-interior-cycle",
699
+ "advance-to-gate",
700
+ "run-gate-cycle",
701
+ "accept-nonblocking-findings",
702
+ "escalate-to-triage",
703
+ "ready-for-human"
704
+ ];
705
+ const LADDER_STAGES = [
706
+ "interior",
707
+ "interior-complete",
708
+ "gate"
709
+ ];
710
+ const LADDER_FORCED_TRANSITION_KINDS = ["gate-cap-exhausted", "interior-cap-reached"];
620
711
  const ladderCycleRefsFor = (cycles) => {
621
712
  let interior = 0;
622
713
  let gate = 0;
@@ -685,7 +776,7 @@ const prReviewResultSchema = z.object({
685
776
  ]),
686
777
  producer: nonBlankString.optional(),
687
778
  promptSections: reviewPromptSectionsSchema.optional(),
688
- rung: z.enum(EVIDENCE_REVIEW_RUNGS$1).optional(),
779
+ rung: z.enum(EVIDENCE_REVIEW_RUNGS).optional(),
689
780
  sessionId: nonBlankString.optional(),
690
781
  startedAt: parseableDateString,
691
782
  summary: z.string().refine((value) => value.trim().length > 0 && value !== "<one sentence>", { message: "summary must not be blank or a placeholder" }),
@@ -731,6 +822,7 @@ const prReviewProofSchema = z.object({
731
822
  ladder: prReviewLadderStateSchema.optional(),
732
823
  maxReviewCycles: z.number().int().positive().optional(),
733
824
  patchId: z.string().regex(/^[0-9a-f]{40,64}$/u),
825
+ policyResolution: policyResolutionSchema.optional(),
734
826
  reviewCycle: z.number().int().positive().optional(),
735
827
  reviewRequirement: z.object({
736
828
  reason: z.enum(["no-applicable-mode", "faithful-merge"]),
@@ -751,6 +843,17 @@ const prReviewProofSchema = z.object({
751
843
  });
752
844
  });
753
845
  //#endregion
846
+ //#region src/required-check-policy.ts
847
+ const EVIDENCE_CHECK_TYPES = ["review", "verify"];
848
+ const requiredCheckScopeSchema = z.object({ classifications: z.array(z.enum(DIFF_CLASSIFICATIONS)).min(1) }).strict();
849
+ const reviewStatusSchema = z.enum([
850
+ "not-required",
851
+ "current",
852
+ "stale",
853
+ "missing",
854
+ "blocked"
855
+ ]);
856
+ //#endregion
754
857
  //#region src/review-state.ts
755
858
  const reviewStateIssue = (message, field) => [{
756
859
  code: "custom",
@@ -1161,6 +1264,7 @@ const prVerifyProofSchema = z.object({
1161
1264
  notRequiredCommands: z.array(notRequiredCommandSchema).min(1).optional(),
1162
1265
  outcome: z.enum(["aborted", "passed"]).optional(),
1163
1266
  patchId: z.string().regex(/^[0-9a-f]{40}$/u).optional(),
1267
+ policyResolution: policyResolutionSchema.optional(),
1164
1268
  profilePath: z.string().min(1),
1165
1269
  projectKey: z.string().min(1),
1166
1270
  repository: z.string().min(1),
@@ -1228,24 +1332,12 @@ const closeoutArtifactSchema = z.object({
1228
1332
  }).strict();
1229
1333
  //#endregion
1230
1334
  //#region src/schemas.ts
1231
- const EVIDENCE_CHECK_TYPES = ["review", "verify"];
1232
- const EVIDENCE_REVIEW_RUNGS = [
1233
- "independent-model",
1234
- "oracle",
1235
- "human"
1236
- ];
1237
- const DIFF_CLASSIFICATIONS = [
1238
- "docs/process-only",
1239
- "trivial",
1240
- "non-trivial"
1241
- ];
1242
- const REVIEW_STATUS_VALUES = [
1243
- "not-required",
1244
- "current",
1245
- "stale",
1246
- "missing",
1247
- "blocked"
1248
- ];
1335
+ const evidenceCheckTypeSchema = z.enum(EVIDENCE_CHECK_TYPES);
1336
+ const evidenceReviewRungSchema = z.enum(EVIDENCE_REVIEW_RUNGS);
1337
+ const diffClassificationSchema = z.enum(DIFF_CLASSIFICATIONS);
1338
+ const ladderStageSchema = z.enum(LADDER_STAGES);
1339
+ const ladderNextActionSchema = z.enum(LADDER_NEXT_ACTION_KINDS);
1340
+ const ladderForcedTransitionSchema = z.enum(LADDER_FORCED_TRANSITION_KINDS);
1249
1341
  const followUpActionSchema = z.object({
1250
1342
  argv: z.array(z.string()),
1251
1343
  command: z.string()
@@ -1254,7 +1346,7 @@ const EVIDENCE_ENVELOPE_SCHEMA_VERSION = 1;
1254
1346
  const evidenceShaSchema = z.string().regex(/^[0-9a-f]{7,40}$/u, { message: "must be a 7-40 char lowercase hex git SHA" });
1255
1347
  const evidenceEnvelopeSchema = z.object({
1256
1348
  check: z.string().min(1),
1257
- checkType: z.enum(EVIDENCE_CHECK_TYPES),
1349
+ checkType: evidenceCheckTypeSchema,
1258
1350
  findingsPointer: z.string().min(1).optional(),
1259
1351
  headSha: evidenceShaSchema,
1260
1352
  mergeBaseSha: evidenceShaSchema,
@@ -1264,7 +1356,7 @@ const evidenceEnvelopeSchema = z.object({
1264
1356
  policyVersion: z.string().min(1).optional(),
1265
1357
  producer: z.string().min(1),
1266
1358
  requestId: z.string().min(1).optional(),
1267
- rung: z.enum(EVIDENCE_REVIEW_RUNGS).optional(),
1359
+ rung: evidenceReviewRungSchema.optional(),
1268
1360
  schemaVersion: z.literal(1),
1269
1361
  sessionId: z.string().trim().min(1).optional(),
1270
1362
  timestamp: z.iso.datetime().optional()
@@ -1327,7 +1419,7 @@ const boundaryReviewProofSchema = z.object({
1327
1419
  }).passthrough(),
1328
1420
  outcome: z.enum(["pass", "fail"]),
1329
1421
  producer: z.string().min(1),
1330
- rung: z.enum(EVIDENCE_REVIEW_RUNGS),
1422
+ rung: evidenceReviewRungSchema,
1331
1423
  schemaVersion: z.literal(1),
1332
1424
  sessionId: z.string().min(1).optional(),
1333
1425
  specHash: z.object({
@@ -1348,7 +1440,7 @@ const boundaryCheckProofSchema = z.object({
1348
1440
  state: z.enum(["merged", "open"]),
1349
1441
  wave: z.string().min(1)
1350
1442
  })),
1351
- demandedRung: z.enum(EVIDENCE_REVIEW_RUNGS).optional(),
1443
+ demandedRung: evidenceReviewRungSchema.optional(),
1352
1444
  epicIssue: z.number().int().positive(),
1353
1445
  manifestHash: z.object({
1354
1446
  algo: z.literal("sha256"),
@@ -1360,7 +1452,7 @@ const boundaryCheckProofSchema = z.object({
1360
1452
  commentId: z.union([z.string(), z.number()]).optional(),
1361
1453
  commentUrl: z.string().optional(),
1362
1454
  producer: z.string().min(1),
1363
- rung: z.enum(EVIDENCE_REVIEW_RUNGS),
1455
+ rung: evidenceReviewRungSchema,
1364
1456
  sessionId: z.string().min(1).optional(),
1365
1457
  specHash: z.object({
1366
1458
  algo: z.literal("sha256"),
@@ -1375,7 +1467,6 @@ const boundaryCheckProofSchema = z.object({
1375
1467
  }).optional(),
1376
1468
  status: z.enum(["ready", "blocked"])
1377
1469
  });
1378
- const requiredCheckScopeSchema = z.object({ classifications: z.array(z.enum(DIFF_CLASSIFICATIONS)).min(1) }).strict();
1379
1470
  const readinessRepairSchema = z.object({
1380
1471
  action: z.string().min(1),
1381
1472
  code: z.enum([
@@ -1389,7 +1480,7 @@ const reviewStateSchema = z.object({
1389
1480
  required: z.boolean(),
1390
1481
  reviewedHeadSha: z.string().optional(),
1391
1482
  reviewedPatchId: z.string().optional(),
1392
- status: z.enum(REVIEW_STATUS_VALUES)
1483
+ status: reviewStatusSchema
1393
1484
  }).strict().superRefine((state, context) => {
1394
1485
  for (const issue of reviewStateIssues(state)) context.addIssue(issue);
1395
1486
  });
@@ -1410,9 +1501,9 @@ const historicalHandledHumanCommentSchema = z.object({
1410
1501
  const managedReadinessLedgerSchema = z.object({
1411
1502
  baseSha: z.string().regex(/^[0-9a-f]{40}$/u),
1412
1503
  blockingReasons: z.array(z.string()),
1413
- classification: z.enum(DIFF_CLASSIFICATIONS),
1504
+ classification: diffClassificationSchema,
1414
1505
  externalChecks: z.array(z.object({
1415
- checkType: z.enum(EVIDENCE_CHECK_TYPES),
1506
+ checkType: evidenceCheckTypeSchema,
1416
1507
  name: z.string().min(1),
1417
1508
  reason: z.string().optional(),
1418
1509
  scope: requiredCheckScopeSchema.optional(),
@@ -1485,25 +1576,9 @@ const managedReadinessLedgerSchema = z.object({
1485
1576
  gate: z.number().int().nonnegative(),
1486
1577
  interior: z.number().int().nonnegative()
1487
1578
  }),
1488
- forcedTransition: z.enum([
1489
- "gate-cap-exhausted",
1490
- "interior-cap-reached",
1491
- "interior-takeover"
1492
- ]).optional(),
1493
- nextAction: z.enum([
1494
- "run-interior-cycle",
1495
- "advance-to-gate",
1496
- "run-gate-cycle",
1497
- "accept-nonblocking-findings",
1498
- "escalate-to-triage",
1499
- "ready-for-human"
1500
- ]),
1501
- stage: z.enum([
1502
- "interior",
1503
- "interior-takeover",
1504
- "interior-complete",
1505
- "gate"
1506
- ])
1579
+ forcedTransition: ladderForcedTransitionSchema.optional(),
1580
+ nextAction: ladderNextActionSchema,
1581
+ stage: ladderStageSchema
1507
1582
  }).optional(),
1508
1583
  reviewRuns: z.array(prReviewResultSchema).optional(),
1509
1584
  reviewTerminalState: z.enum([
@@ -1580,6 +1655,7 @@ const prReadyProofSchema = z.object({
1580
1655
  humanBlockingReasons: z.array(z.string()).default([]),
1581
1656
  ledger: managedReadinessLedgerSchema,
1582
1657
  notices: z.array(z.string().min(1)).optional(),
1658
+ policyResolution: policyResolutionSchema.optional(),
1583
1659
  profileBlobSha: z.string().regex(/^[0-9a-f]{40}$/u).optional(),
1584
1660
  profilePath: z.string().min(1).optional(),
1585
1661
  repairs: z.array(readinessRepairSchema).default([]),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@patronage/software-factory",
3
- "version": "1.0.0-alpha.23",
3
+ "version": "1.0.0-alpha.25",
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.23"
40
+ "@patronage/factory-ci": "1.0.0-alpha.25"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@types/node": "24.13.3",