@patronage/software-factory 0.25.0 → 0.30.0-beta.1

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
@@ -10,30 +10,37 @@ Object.fromEntries(EVIDENCE_REVIEW_RUNGS$1.map((rung, index) => [rung, index]));
10
10
  //#region src/demand-keys.ts
11
11
  /** The demands that exist at most once per candidate. */
12
12
  const DEMAND_KEYS = {
13
+ /**
14
+ * The boundary wave that authorizes this candidate's merge schedule (#477).
15
+ * In force exactly when GitHub already has auto-merge enabled: a live
16
+ * schedule with no wave demand resolved is an unauthorized merge waiting to
17
+ * happen.
18
+ */
19
+ boundaryWave: "boundary-wave",
13
20
  /** The PR is still a draft. */
14
21
  draft: "draft",
15
- /** The candidate is not the intended final human review point. */
16
- finalReviewPoint: "final-review-point",
17
22
  /** GitHub's own check rollup for the candidate head. */
18
23
  githubChecks: "github-checks",
19
24
  /** Local HEAD and the GitHub PR head must be the same commit. */
20
25
  headIdentity: "head-identity",
21
- /** An unhandled post-readiness human comment or review submission. */
26
+ /** GitHub's live aggregate review decision is CHANGES_REQUESTED. */
22
27
  humanBlocker: "human-blocker",
23
28
  /** The repository-wide merge freeze. */
24
29
  mergeFreeze: "merge-freeze",
25
30
  /** GitHub's mergeability / merge-state rollup. */
26
31
  mergeState: "merge-state",
27
- /** The PR body's required rendered sections. */
28
- prBodySections: "pr-body-sections",
32
+ /**
33
+ * Retired readiness demand, retained solely to parse historical v3 proofs
34
+ * and their HQ payloads. No current command emits it: GitHub approval is
35
+ * neither a review-rung authority nor a scheduling requirement.
36
+ */
37
+ nativeApproval: "native-approval",
29
38
  /** A current, head-bound, passing typed `pr:verify` proof. */
30
39
  prVerify: "pr-verify",
31
40
  /** The profile-resolved review ladder policy. */
32
41
  reviewLadder: "review-ladder",
33
42
  /** Unresolved GitHub review threads. */
34
- reviewThreads: "review-threads",
35
- /** An explicit trivial waiver that the diff does not support. */
36
- trivialWaiver: "trivial-waiver"
43
+ reviewThreads: "review-threads"
37
44
  };
38
45
  /** The families whose instances are named by a resolved value. */
39
46
  const QUALIFIED_DEMAND_FAMILIES = [
@@ -54,7 +61,7 @@ const percentEncode = (character) => {
54
61
  * demand nobody waived, and make HQ count two causes as one.
55
62
  *
56
63
  * Deterministic in both directions of use: the key a `pr:ready` proof records
57
- * is the key a `pr:merge-check` waiver matches.
64
+ * is the key a `pr:ready` waiver matches.
58
65
  */
59
66
  const demandQualifier = (value) => value.replaceAll(/[^A-Za-z0-9._/-]/gu, percentEncode);
60
67
  const FIXED_DEMAND_KEYS = Object.values(DEMAND_KEYS);
@@ -132,7 +139,7 @@ const demandKeySchema = z.string().refine((value) => {
132
139
  * reason codes must come from. A waiver store is durable operator evidence
133
140
  * written before today's vocabulary existed, so tightening its validation
134
141
  * would make an existing record parse as no waiver and let the next write drop
135
- * it. A waiver naming a key nobody resolves is already inert — `pr:merge-check`
142
+ * it. A waiver naming a key nobody resolves is already inert — `pr:ready`
136
143
  * reports it as an unapplied waiver — so nothing is admitted by accepting it.
137
144
  */
138
145
  const waiverDemandKeySchema = z.string().regex(/^[a-z][a-z0-9-]*(?::[A-Za-z0-9._%/-]+)?$/u, "A demand key is a lowercase family, optionally qualified by its resolved value (e.g. review-rung:human).");
@@ -175,29 +182,18 @@ const blockedReasonIssue = (message) => [{
175
182
  * wire schema so it is one rule rather than two that can drift. It is the
176
183
  * producer's state machine, written down:
177
184
  *
178
- * - `ready` refused nothing, so it carries neither projection, and it is the
179
- * final review point;
180
- * - `slice-ready/not-final` was held back by exactly one demand — being a
181
- * slice — and names it;
182
- * - `blocked` refused something other than being a slice, and says so;
185
+ * - `ready` refused nothing, so it carries neither projection;
186
+ * - `blocked` refused something and says so;
183
187
  * - whichever it is, `blockedReasons` names every refusal listed in
184
188
  * `blockingReasons`, in the same order, as the bounded form of that sentence.
185
189
  *
186
- * Naming the slice demand and the ledger's `finalReviewPoint` are the same
187
- * fact, so a proof that says one and not the other is refused.
188
190
  */
189
191
  const blockedReasonIssues = (proof) => {
190
192
  const named = proof.blockedReasons ?? [];
191
193
  const reasons = proof.blockingReasons ?? [];
192
- const slice = DEMAND_KEYS.finalReviewPoint;
193
- const namesSlice = named.some((reason) => reason.code === slice);
194
194
  if (proof.status === "ready") {
195
195
  if (named.length > 0 || reasons.length > 0) return blockedReasonIssue("a ready pr:ready proof must carry no blocking reasons");
196
- } else if (proof.status === "slice-ready/not-final") {
197
- if (named.length !== 1 || !namesSlice) return blockedReasonIssue(`a slice-ready/not-final pr:ready proof is held back by exactly one demand, ${slice}`);
198
196
  } else if (reasons.length === 0) return blockedReasonIssue("a blocked pr:ready proof must record what blocked it");
199
- else if (named.length > 0 && !named.some((r) => r.code !== slice)) return blockedReasonIssue(`a blocked pr:ready proof must name a demand other than ${slice}`);
200
- if (proof.finalReviewPoint !== void 0 && namesSlice === proof.finalReviewPoint) return blockedReasonIssue(`naming ${slice} and the ledger's finalReviewPoint are the same fact; this proof says both`);
201
197
  if (named.length !== reasons.length) return blockedReasonIssue(`blockedReasons must name every blocking reason: ${reasons.length} reason(s), ${named.length} named`);
202
198
  const drifted = named.findIndex((reason, index) => reason.detail !== blockedReasonDetail(reasons[index]));
203
199
  return drifted === -1 ? [] : blockedReasonIssue(`blockedReasons[${drifted}] does not carry blocking reason ${drifted}; the two projections must tell one story`);
@@ -215,7 +211,7 @@ const demandWaiverSchema = z.object({
215
211
  operator: z.string().trim().min(1),
216
212
  rationale: z.string().trim().min(1),
217
213
  recordedAt: z.iso.datetime(),
218
- session: z.string().trim().min(1)
214
+ session: z.string().trim().min(1).optional()
219
215
  });
220
216
  z.object({
221
217
  command: z.literal("patronage-factory demand:waive"),
@@ -227,7 +223,7 @@ const waivedDemandSchema = z.object({
227
223
  operator: z.string().trim().min(1),
228
224
  rationale: z.string().trim().min(1),
229
225
  recordedAt: z.iso.datetime(),
230
- session: z.string().trim().min(1),
226
+ session: z.string().trim().min(1).optional(),
231
227
  unmetReasons: z.array(z.string().min(1)).min(1)
232
228
  });
233
229
  const MAX_CLOSEOUT_ROWS = 1e3;
@@ -510,11 +506,6 @@ const RetroCycleCountersSchema = z.object({
510
506
  thermoFixRounds: nonNegInt
511
507
  }).strict();
512
508
  const RetroOutcomeSchema = z.object({
513
- mergeCheck: z.enum([
514
- "pass",
515
- "fail",
516
- "not-run"
517
- ]).optional(),
518
509
  status: z.enum([
519
510
  "success",
520
511
  "blocked",
@@ -718,11 +709,7 @@ const PR_REVIEW_SCHEMA_VERSION = 2;
718
709
  const PR_REVIEW_FINDING_PROVENANCE_VERSION = 1;
719
710
  const nonBlankString = z.string().refine((value) => value.trim().length > 0, { message: "must not be blank" });
720
711
  const parseableDateString = z.string().refine((value) => Number.isFinite(Date.parse(value)), { message: "must be a parseable date string" });
721
- const reviewPromptSectionProvenances = [
722
- "prior-review-ledger",
723
- "profile-standing-checklist",
724
- "issue-review-focus"
725
- ];
712
+ const reviewPromptSectionProvenances = ["prior-review-ledger", "issue-review-focus"];
726
713
  const reviewPromptSectionSchema = z.object({
727
714
  provenance: z.enum(reviewPromptSectionProvenances),
728
715
  source: nonBlankString,
@@ -979,67 +966,7 @@ const boundaryCheckProofSchema = z.object({
979
966
  }).optional(),
980
967
  status: z.enum(["ready", "blocked"])
981
968
  });
982
- const mergeGuardIdentitySchema = z.discriminatedUnion("kind", [
983
- z.object({
984
- headSha: z.string().regex(/^[0-9a-f]{40}$/u),
985
- kind: z.literal("match")
986
- }),
987
- z.object({
988
- kind: z.literal("diverged"),
989
- liveHeadSha: z.string().regex(/^[0-9a-f]{40}$/u),
990
- postProofCommits: z.array(z.object({
991
- sha: z.string().min(1),
992
- subject: z.string()
993
- })).optional(),
994
- proofHeadSha: z.string().regex(/^[0-9a-f]{40}$/u)
995
- }),
996
- z.object({
997
- kind: z.literal("live-head-invalid"),
998
- pr: z.number().int().positive(),
999
- received: z.string()
1000
- }),
1001
- z.object({
1002
- errorDetail: z.string().optional(),
1003
- kind: z.literal("ready-proof-missing"),
1004
- readyProofPath: z.string().min(1)
1005
- }),
1006
- z.object({
1007
- kind: z.literal("ready-proof-pr-mismatch"),
1008
- proofPr: z.number().int().positive(),
1009
- requestedPr: z.number().int().positive()
1010
- }),
1011
- z.object({
1012
- blockingReasons: z.array(z.string()),
1013
- kind: z.literal("ready-proof-not-ready"),
1014
- status: z.string().min(1)
1015
- })
1016
- ]);
1017
- const PR_MERGE_CHECK_SCHEMA_VERSION = 1;
1018
- const prMergeCheckProofSchema = z.object({
1019
- blockingReasons: z.array(z.string()),
1020
- command: z.literal("patronage-factory pr:merge-check"),
1021
- followUp: followUpActionSchema.optional(),
1022
- identity: mergeGuardIdentitySchema,
1023
- liveHeadSha: z.string().regex(/^[0-9a-f]{40}$/u).optional(),
1024
- notices: z.array(z.string()).optional(),
1025
- pr: z.number().int().positive(),
1026
- schemaVersion: z.literal(1),
1027
- status: z.enum(["pass", "fail"]),
1028
- waivedDemands: z.array(waivedDemandSchema).optional(),
1029
- worktreeHeldBranch: z.object({
1030
- branch: z.string().min(1),
1031
- worktreePath: z.string().min(1)
1032
- }).optional()
1033
- });
1034
- const requiredCheckScopeSchema = z.object({
1035
- classifications: z.array(z.enum(DIFF_CLASSIFICATIONS)).min(1).optional(),
1036
- labels: z.array(z.string().min(1)).min(1).optional()
1037
- }).strict().superRefine((scope, context) => {
1038
- if (scope.labels === void 0 && scope.classifications === void 0) context.addIssue({
1039
- code: "custom",
1040
- message: "requiredChecks scope must declare at least one condition (labels and/or classifications)."
1041
- });
1042
- });
969
+ const requiredCheckScopeSchema = z.object({ classifications: z.array(z.enum(DIFF_CLASSIFICATIONS)).min(1) }).strict();
1043
970
  const readinessRepairSchema = z.object({
1044
971
  action: z.string().min(1),
1045
972
  code: z.enum([
@@ -1056,6 +983,18 @@ const reviewStateSchema = z.object({
1056
983
  reviewedPatchId: z.string().optional(),
1057
984
  status: z.enum(REVIEW_STATUS_VALUES)
1058
985
  });
986
+ const historicalPostReadinessCommentSchema = z.object({
987
+ author: z.string().min(1),
988
+ createdAt: z.string().min(1),
989
+ summary: z.string().min(1),
990
+ url: z.string().min(1)
991
+ });
992
+ const historicalHandledHumanCommentSchema = z.object({
993
+ clearedAt: z.string().min(1).optional(),
994
+ sessionId: z.string().min(1).optional(),
995
+ source: z.enum(["check-run", "cli"]),
996
+ url: z.string().min(1)
997
+ });
1059
998
  const managedReadinessLedgerSchema = z.object({
1060
999
  baseSha: z.string().regex(/^[0-9a-f]{40}$/u),
1061
1000
  blockingReasons: z.array(z.string()),
@@ -1073,9 +1012,8 @@ const managedReadinessLedgerSchema = z.object({
1073
1012
  "out-of-scope"
1074
1013
  ])
1075
1014
  })).optional(),
1076
- finalReviewPoint: z.boolean(),
1015
+ finalReviewPoint: z.boolean().optional(),
1077
1016
  github: z.object({
1078
- currentWithBase: z.boolean(),
1079
1017
  draft: z.boolean(),
1080
1018
  mergeStateStatus: z.string(),
1081
1019
  mergeable: z.string(),
@@ -1086,47 +1024,20 @@ const managedReadinessLedgerSchema = z.object({
1086
1024
  "none",
1087
1025
  "unknown"
1088
1026
  ]),
1027
+ reviewDecision: z.string().nullable().optional(),
1089
1028
  unresolvedReviewThreads: z.number().int().nonnegative()
1090
1029
  }),
1091
1030
  handledCommentsProducer: z.object({
1092
1031
  identity: z.string().min(1),
1093
1032
  mode: z.enum(["app", "commit-status"])
1094
1033
  }).optional(),
1095
- handledHumanComments: z.array(z.object({
1096
- clearedAt: z.string().min(1).optional(),
1097
- sessionId: z.string().min(1).optional(),
1098
- source: z.enum(["check-run", "cli"]),
1099
- url: z.string().min(1)
1100
- })).optional(),
1034
+ handledHumanComments: z.array(historicalHandledHumanCommentSchema).optional(),
1101
1035
  headSha: z.string().regex(/^[0-9a-f]{40}$/u),
1102
1036
  mergeBaseSha: z.string().regex(/^[0-9a-f]{40}$/u).optional(),
1103
1037
  patchId: z.string().regex(/^[0-9a-f]{40,64}$/u),
1104
- postReadinessHumanComments: z.array(z.object({
1105
- author: z.string().min(1),
1106
- createdAt: z.string().min(1),
1107
- summary: z.string().min(1),
1108
- url: z.string().min(1)
1109
- })).optional(),
1038
+ postReadinessHumanComments: z.array(historicalPostReadinessCommentSchema).optional(),
1039
+ postReadinessHumanReviews: z.array(historicalPostReadinessCommentSchema).optional(),
1110
1040
  pr: z.number().int().positive(),
1111
- previewDeploy: z.object({
1112
- adminUrl: z.string().optional(),
1113
- apiHealthUrl: z.string().optional(),
1114
- headSha: z.string().optional(),
1115
- localPreviewProof: z.unknown().optional(),
1116
- localProofPath: z.string().optional(),
1117
- proofSource: z.enum(["github-actions", "local-self-certified"]).optional(),
1118
- publicUrl: z.string().optional(),
1119
- seedStatus: z.enum([
1120
- "passed",
1121
- "failed",
1122
- "skipped",
1123
- "unknown"
1124
- ]),
1125
- stage: z.string().optional(),
1126
- timingsMs: z.record(z.string(), z.number().optional()).optional(),
1127
- workflowRunUrl: z.string().optional()
1128
- }).optional(),
1129
- previewDeployRequired: z.boolean(),
1130
1041
  repairs: z.array(readinessRepairSchema).default([]),
1131
1042
  reviewCycleState: z.object({
1132
1043
  autoBlockingFindings: z.number().int().nonnegative(),
@@ -1199,7 +1110,7 @@ const managedReadinessLedgerSchema = z.object({
1199
1110
  "slice",
1200
1111
  "rollup",
1201
1112
  "merge-gate prerequisite"
1202
- ]),
1113
+ ]).optional(),
1203
1114
  verification: z.object({
1204
1115
  command: z.literal("patronage-factory pr:verify"),
1205
1116
  docsOnlyDeltaAccepted: z.boolean().optional(),
@@ -1214,21 +1125,36 @@ const managedReadinessLedgerSchema = z.object({
1214
1125
  verifiedHeadSha: z.string().optional()
1215
1126
  })
1216
1127
  });
1217
- const PR_READY_SCHEMA_VERSION = 2;
1128
+ const PR_READY_SCHEMA_VERSION = 3;
1218
1129
  /**
1219
- * The pr:ready proof versions a reader still accepts. `pr:ready` emits v2 only
1220
- * (#391) — one current contract — but v1 events were spooled before the bump
1221
- * and HQ must ingest them without degrading, so the wire schema parses both.
1130
+ * The pr:ready proof versions a reader still accepts. `pr:ready` emits v3 only
1131
+ * (#477) — one current contract — but v1 and v2 events were spooled before the
1132
+ * bumps and HQ must ingest them without degrading, so the wire schema parses
1133
+ * all three.
1222
1134
  */
1223
- const SUPPORTED_PR_READY_SCHEMA_VERSIONS = [1, 2];
1135
+ const SUPPORTED_PR_READY_SCHEMA_VERSIONS = [
1136
+ 1,
1137
+ 2,
1138
+ 3
1139
+ ];
1224
1140
  const prReadySchemaVersionSchema = z.number().refine((value) => SUPPORTED_PR_READY_SCHEMA_VERSIONS.includes(value), { message: `schemaVersion must be one of: ${SUPPORTED_PR_READY_SCHEMA_VERSIONS.join(", ")}` });
1225
1141
  const prReadyProofSchema = z.object({
1142
+ arming: z.object({
1143
+ detail: z.string().min(1).optional(),
1144
+ headSha: z.string().regex(/^[0-9a-f]{40}$/u),
1145
+ outcome: z.enum([
1146
+ "armed",
1147
+ "merged",
1148
+ "not-armed"
1149
+ ])
1150
+ }).optional(),
1226
1151
  blockedReasons: blockedReasonsSchema.optional(),
1227
1152
  blockingReasons: z.array(z.string()),
1228
1153
  command: z.literal("patronage-factory pr:ready"),
1229
1154
  followUp: followUpActionSchema.optional(),
1230
1155
  humanBlockingReasons: z.array(z.string()).default([]),
1231
1156
  ledger: managedReadinessLedgerSchema,
1157
+ notices: z.array(z.string().min(1)).optional(),
1232
1158
  profileBlobSha: z.string().regex(/^[0-9a-f]{40}$/u).optional(),
1233
1159
  profilePath: z.string().min(1).optional(),
1234
1160
  repairs: z.array(readinessRepairSchema).default([]),
@@ -1238,7 +1164,8 @@ const prReadyProofSchema = z.object({
1238
1164
  "ready",
1239
1165
  "blocked",
1240
1166
  "slice-ready/not-final"
1241
- ])
1167
+ ]),
1168
+ waivedDemands: z.array(waivedDemandSchema).optional()
1242
1169
  }).superRefine((proof, context) => {
1243
1170
  if (proof.schemaVersion < 2) {
1244
1171
  if (proof.blockedReasons !== void 0) context.addIssue({
@@ -1248,10 +1175,7 @@ const prReadyProofSchema = z.object({
1248
1175
  });
1249
1176
  return;
1250
1177
  }
1251
- for (const issue of blockedReasonIssues({
1252
- ...proof,
1253
- finalReviewPoint: proof.ledger.finalReviewPoint
1254
- })) context.addIssue(issue);
1178
+ for (const issue of blockedReasonIssues({ ...proof })) context.addIssue(issue);
1255
1179
  });
1256
1180
  //#endregion
1257
- export { BLOCKED_REASONS_MAX, BLOCKED_REASON_DETAIL_MAX, BOUNDARY_CHECK_SCHEMA_VERSION, BOUNDARY_REVIEW_PROOF_KIND, EVIDENCE_ENVELOPE_SCHEMA_VERSION, PR_MERGE_CHECK_SCHEMA_VERSION, PR_READY_SCHEMA_VERSION, PR_REVIEW_SCHEMA_VERSION, RETRO_ENVELOPE_SCHEMA_VERSION, RETRO_ENVELOPE_VALIDATORS, RETRO_ENVELOPE_WIRE_BOUNDS, SUPPORTED_PR_READY_SCHEMA_VERSIONS, SUPPORTED_PR_VERIFY_SCHEMA_VERSIONS, SUPPORTED_RETRO_ENVELOPE_SCHEMA_VERSIONS, blockedReasonSchema, blockedReasonsSchema, boundaryCheckProofSchema, boundaryReviewProofSchema, closeoutArtifactSchema, evidenceEnvelopeSchema, prMergeCheckProofSchema, prReadyProofSchema, prReviewProofSchema, prVerifyProofSchema, retroEnvelopeV1Schema, retroEpicReference, waivedDemandSchema };
1181
+ 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_PR_READY_SCHEMA_VERSIONS, SUPPORTED_PR_VERIFY_SCHEMA_VERSIONS, SUPPORTED_RETRO_ENVELOPE_SCHEMA_VERSIONS, blockedReasonSchema, blockedReasonsSchema, boundaryCheckProofSchema, boundaryReviewProofSchema, closeoutArtifactSchema, evidenceEnvelopeSchema, prReadyProofSchema, prReviewProofSchema, prVerifyProofSchema, retroEnvelopeV1Schema, retroEpicReference, waivedDemandSchema };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@patronage/software-factory",
3
- "version": "0.25.0",
3
+ "version": "0.30.0-beta.1",
4
4
  "description": "Shared Patronage software factory CLI and project-profile validation tools",
5
5
  "license": "MIT",
6
6
  "repository": {