@patronage/software-factory 1.0.0-alpha.10 → 1.0.0-alpha.13
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/index.d.ts +8 -8
- package/dist/index.js +76 -42
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -743,8 +743,8 @@ declare const mergeFreezeStateSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
743
743
|
generationId: z.ZodNumber;
|
|
744
744
|
headSha: z.ZodString;
|
|
745
745
|
outcome: z.ZodEnum<{
|
|
746
|
-
stale: "stale";
|
|
747
746
|
active: "active";
|
|
747
|
+
stale: "stale";
|
|
748
748
|
}>;
|
|
749
749
|
reason: z.ZodString;
|
|
750
750
|
recordedAt: z.ZodISODateTime;
|
|
@@ -1243,8 +1243,8 @@ declare const managedReadinessLedgerSchema: z.ZodObject<{
|
|
|
1243
1243
|
status: z.ZodEnum<{
|
|
1244
1244
|
blocked: "blocked";
|
|
1245
1245
|
"not-required": "not-required";
|
|
1246
|
-
current: "current";
|
|
1247
1246
|
stale: "stale";
|
|
1247
|
+
current: "current";
|
|
1248
1248
|
missing: "missing";
|
|
1249
1249
|
}>;
|
|
1250
1250
|
}, z.core.$strip>;
|
|
@@ -1255,8 +1255,8 @@ declare const managedReadinessLedgerSchema: z.ZodObject<{
|
|
|
1255
1255
|
status: z.ZodEnum<{
|
|
1256
1256
|
blocked: "blocked";
|
|
1257
1257
|
"not-required": "not-required";
|
|
1258
|
-
current: "current";
|
|
1259
1258
|
stale: "stale";
|
|
1259
|
+
current: "current";
|
|
1260
1260
|
missing: "missing";
|
|
1261
1261
|
}>;
|
|
1262
1262
|
}, z.core.$strip>>;
|
|
@@ -1267,8 +1267,8 @@ declare const managedReadinessLedgerSchema: z.ZodObject<{
|
|
|
1267
1267
|
docsOnlyDeltaAccepted: z.ZodOptional<z.ZodBoolean>;
|
|
1268
1268
|
docsOnlyVerifiedHeadSha: z.ZodOptional<z.ZodString>;
|
|
1269
1269
|
prVerify: z.ZodEnum<{
|
|
1270
|
-
passed: "passed";
|
|
1271
1270
|
stale: "stale";
|
|
1271
|
+
passed: "passed";
|
|
1272
1272
|
missing: "missing";
|
|
1273
1273
|
}>;
|
|
1274
1274
|
trivialDeltaAccepted: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -1772,8 +1772,8 @@ declare const REVIEW_STATUS_VALUES: readonly ["not-required", "current", "stale"
|
|
|
1772
1772
|
declare const reviewStatusSchema: z.ZodEnum<{
|
|
1773
1773
|
blocked: "blocked";
|
|
1774
1774
|
"not-required": "not-required";
|
|
1775
|
-
current: "current";
|
|
1776
1775
|
stale: "stale";
|
|
1776
|
+
current: "current";
|
|
1777
1777
|
missing: "missing";
|
|
1778
1778
|
}>;
|
|
1779
1779
|
type ReviewStatus = z.infer<typeof reviewStatusSchema>;
|
|
@@ -4074,13 +4074,13 @@ declare const evaluateReadiness: (input: EvaluationInput) => {
|
|
|
4074
4074
|
reviews: {
|
|
4075
4075
|
correctness: {
|
|
4076
4076
|
required: boolean;
|
|
4077
|
-
status: "blocked" | "not-required" | "
|
|
4077
|
+
status: "blocked" | "not-required" | "stale" | "current" | "missing";
|
|
4078
4078
|
reviewedHeadSha?: string | undefined;
|
|
4079
4079
|
reviewedPatchId?: string | undefined;
|
|
4080
4080
|
};
|
|
4081
4081
|
security?: {
|
|
4082
4082
|
required: boolean;
|
|
4083
|
-
status: "blocked" | "not-required" | "
|
|
4083
|
+
status: "blocked" | "not-required" | "stale" | "current" | "missing";
|
|
4084
4084
|
reviewedHeadSha?: string | undefined;
|
|
4085
4085
|
reviewedPatchId?: string | undefined;
|
|
4086
4086
|
} | undefined;
|
|
@@ -4088,7 +4088,7 @@ declare const evaluateReadiness: (input: EvaluationInput) => {
|
|
|
4088
4088
|
schemaVersion: 1;
|
|
4089
4089
|
verification: {
|
|
4090
4090
|
command: "patronage-factory pr:verify";
|
|
4091
|
-
prVerify: "
|
|
4091
|
+
prVerify: "stale" | "passed" | "missing";
|
|
4092
4092
|
docsOnlyDeltaAccepted?: boolean | undefined;
|
|
4093
4093
|
docsOnlyVerifiedHeadSha?: string | undefined;
|
|
4094
4094
|
trivialDeltaAccepted?: boolean | undefined;
|
package/dist/index.js
CHANGED
|
@@ -17,7 +17,7 @@ import { parse } from "yaml";
|
|
|
17
17
|
import { promisify } from "node:util";
|
|
18
18
|
import { FACTORY_PROOF_GATE_STEP_NAME, GitHubApiError, mintInstallationToken, productionImpactTargetOutput, readVitestProfileDocument } from "@patronage/factory-ci";
|
|
19
19
|
//#region package.json
|
|
20
|
-
var version = "1.0.0-alpha.
|
|
20
|
+
var version = "1.0.0-alpha.13";
|
|
21
21
|
//#endregion
|
|
22
22
|
//#region src/review-rungs.ts
|
|
23
23
|
const EVIDENCE_REVIEW_RUNGS$1 = [
|
|
@@ -268,8 +268,9 @@ const pullRequestTopologyReasons = ({ manifest, pullRequest }) => {
|
|
|
268
268
|
};
|
|
269
269
|
/**
|
|
270
270
|
* Validate the complete declared member set at boundary closeout. Callers
|
|
271
|
-
* supply only issue-linked and
|
|
272
|
-
* those authoritative members' roles but never discover
|
|
271
|
+
* supply only issue-linked PRs and unlinked manifest-fallback PRs; branch
|
|
272
|
+
* names classify those authoritative members' roles but never discover
|
|
273
|
+
* membership.
|
|
273
274
|
*/
|
|
274
275
|
const boundaryTopologyReasons = ({ manifest, pullRequests }) => {
|
|
275
276
|
const integrationBranch = eachToEpicBranch(manifest);
|
|
@@ -5319,6 +5320,24 @@ z.object({
|
|
|
5319
5320
|
//#endregion
|
|
5320
5321
|
//#region src/boundary-check.ts
|
|
5321
5322
|
const DEFAULT_BOUNDARY_CHECK_PROOF_PATH = ".factory-memory/boundary-check.json";
|
|
5323
|
+
const liveMembershipLabel = (entry) => entry.issue === void 0 ? `PR #${entry.pr} (wave ${entry.wave})` : `PR #${entry.pr} membership for issue #${entry.issue} (wave ${entry.wave})`;
|
|
5324
|
+
const removedMembershipMessage = (reviewed) => reviewed.issue === void 0 ? `PR #${reviewed.pr} was covered by the proof but is no longer in the delivery boundary's live covered set` : `PR #${reviewed.pr} membership for issue #${reviewed.issue} was covered by the proof but is no longer in the delivery boundary's live covered set`;
|
|
5325
|
+
const coveredEntriesByPr = (source) => {
|
|
5326
|
+
const byPr = /* @__PURE__ */ new Map();
|
|
5327
|
+
for (const entry of source) {
|
|
5328
|
+
const entries = byPr.get(entry.pr);
|
|
5329
|
+
if (entries) entries.push(entry);
|
|
5330
|
+
else byPr.set(entry.pr, [entry]);
|
|
5331
|
+
}
|
|
5332
|
+
return byPr;
|
|
5333
|
+
};
|
|
5334
|
+
const contributionDivergences = (entry, reviewed) => {
|
|
5335
|
+
const label = liveMembershipLabel(entry);
|
|
5336
|
+
if (reviewed.state !== entry.state) return [`${label} changed state since the proof (reviewed ${reviewed.state}, now ${entry.state})`];
|
|
5337
|
+
const reviewedSha = reviewed.mergedSha ?? reviewed.headSha;
|
|
5338
|
+
if (!reviewedSha || !sameHeadSha(reviewedSha, entry.sha)) return [entry.state === "open" ? `${label} open head moved since the proof (reviewed ${reviewedSha ?? "unknown"}, now ${entry.sha})` : `${label} merged SHA does not match the proof (reviewed ${reviewedSha ?? "unknown"}, now ${entry.sha})`];
|
|
5339
|
+
return [];
|
|
5340
|
+
};
|
|
5322
5341
|
const CLOSING_PRS_QUERY = `query($owner: String!, $repo: String!, $issue: Int!) {
|
|
5323
5342
|
repository(owner: $owner, name: $repo) {
|
|
5324
5343
|
issue(number: $issue) {
|
|
@@ -5420,49 +5439,65 @@ const coveredEntryFromRef = (ref, wave, issue, notices) => {
|
|
|
5420
5439
|
};
|
|
5421
5440
|
const computeCoveredSet = (manifest, repo, github) => {
|
|
5422
5441
|
const notices = [];
|
|
5423
|
-
const
|
|
5424
|
-
const
|
|
5442
|
+
const entries = [];
|
|
5443
|
+
const linkedPrs = /* @__PURE__ */ new Set();
|
|
5444
|
+
const pullRequests = [];
|
|
5425
5445
|
for (const wave of manifest.waves) for (const issue of wave.issues) {
|
|
5426
5446
|
const refs = github.fetchClosingPullRequests(repo, issue);
|
|
5427
5447
|
if (refs.length === 0) notices.push(`issue #${issue} (wave ${wave.name}) has no linked closing PR yet`);
|
|
5428
5448
|
for (const ref of refs) {
|
|
5429
|
-
|
|
5449
|
+
pullRequests.push(ref);
|
|
5450
|
+
linkedPrs.add(ref.number);
|
|
5430
5451
|
const entry = coveredEntryFromRef(ref, wave.name, issue, notices);
|
|
5431
|
-
if (entry
|
|
5452
|
+
if (entry) entries.push(entry);
|
|
5432
5453
|
}
|
|
5433
5454
|
}
|
|
5434
5455
|
for (const [prKey, waveName] of Object.entries(manifest.prs ?? {})) {
|
|
5435
5456
|
const prNumber = Number(prKey);
|
|
5436
|
-
if (
|
|
5457
|
+
if (linkedPrs.has(prNumber)) continue;
|
|
5437
5458
|
const ref = github.fetchPullRequest(repo, prNumber);
|
|
5438
|
-
pullRequests.
|
|
5459
|
+
pullRequests.push(ref);
|
|
5439
5460
|
const entry = coveredEntryFromRef(ref, waveName, void 0, notices);
|
|
5440
|
-
if (entry)
|
|
5461
|
+
if (entry) entries.push(entry);
|
|
5441
5462
|
}
|
|
5442
5463
|
return {
|
|
5443
|
-
entries:
|
|
5464
|
+
entries: entries.toSorted((a, b) => a.pr - b.pr || (a.issue ?? 0) - (b.issue ?? 0)),
|
|
5444
5465
|
notices,
|
|
5445
|
-
pullRequests:
|
|
5466
|
+
pullRequests: pullRequests.toSorted((a, b) => a.number - b.number)
|
|
5446
5467
|
};
|
|
5447
5468
|
};
|
|
5448
5469
|
const coveredSetDivergences = (computed, proof) => {
|
|
5449
5470
|
const divergences = [];
|
|
5450
|
-
const
|
|
5451
|
-
|
|
5452
|
-
|
|
5453
|
-
|
|
5454
|
-
|
|
5471
|
+
const computedByPr = coveredEntriesByPr(computed);
|
|
5472
|
+
const proofByPr = coveredEntriesByPr(proof.coveredSet);
|
|
5473
|
+
for (const [pr, entries] of computedByPr) {
|
|
5474
|
+
const reviewedEntries = proofByPr.get(pr) ?? [];
|
|
5475
|
+
if (reviewedEntries.length === 0) {
|
|
5476
|
+
divergences.push(...entries.map((entry) => `${liveMembershipLabel(entry)} joined the delivery boundary after the proof was minted`));
|
|
5455
5477
|
continue;
|
|
5456
5478
|
}
|
|
5457
|
-
|
|
5458
|
-
|
|
5479
|
+
const [firstEntry] = entries;
|
|
5480
|
+
const [firstReviewed] = reviewedEntries;
|
|
5481
|
+
if (!(entries.length > 1 || reviewedEntries.length > 1 || firstEntry?.issue !== firstReviewed?.issue)) {
|
|
5482
|
+
if (firstEntry && firstReviewed) divergences.push(...contributionDivergences(firstEntry, firstReviewed));
|
|
5459
5483
|
continue;
|
|
5460
5484
|
}
|
|
5461
|
-
const
|
|
5462
|
-
|
|
5485
|
+
const unmatchedReviewed = [...reviewedEntries];
|
|
5486
|
+
for (const entry of entries) {
|
|
5487
|
+
const reviewedIndex = unmatchedReviewed.findIndex((candidate) => candidate.issue === entry.issue);
|
|
5488
|
+
if (reviewedIndex === -1) {
|
|
5489
|
+
divergences.push(`${liveMembershipLabel(entry)} joined the delivery boundary after the proof was minted`);
|
|
5490
|
+
continue;
|
|
5491
|
+
}
|
|
5492
|
+
const [reviewed] = unmatchedReviewed.splice(reviewedIndex, 1);
|
|
5493
|
+
if (reviewed) divergences.push(...contributionDivergences(entry, reviewed));
|
|
5494
|
+
}
|
|
5495
|
+
for (const reviewed of unmatchedReviewed) divergences.push(removedMembershipMessage(reviewed));
|
|
5496
|
+
}
|
|
5497
|
+
for (const [pr, reviewedEntries] of proofByPr) {
|
|
5498
|
+
if (computedByPr.has(pr)) continue;
|
|
5499
|
+
divergences.push(...reviewedEntries.map(removedMembershipMessage));
|
|
5463
5500
|
}
|
|
5464
|
-
const computedPrs = new Set(computed.map((entry) => entry.pr));
|
|
5465
|
-
for (const reviewed of proof.coveredSet) if (!computedPrs.has(reviewed.pr)) divergences.push(`PR #${reviewed.pr} was covered by the proof but is no longer in the boundary's live covered set`);
|
|
5466
5501
|
return divergences;
|
|
5467
5502
|
};
|
|
5468
5503
|
const defaultResolveRepo = (cwd) => runCapture("gh", [
|
|
@@ -12087,24 +12122,12 @@ const resolveCandidateDemands = ({ changedFiles, profile }) => ({
|
|
|
12087
12122
|
});
|
|
12088
12123
|
/**
|
|
12089
12124
|
* The wave demand that binds one PR under a boundary manifest — moved from the
|
|
12090
|
-
* private resolution inside `pr:merge-check` (#351).
|
|
12091
|
-
*
|
|
12092
|
-
*
|
|
12125
|
+
* private resolution inside `pr:merge-check` (#351). Closing issue links are
|
|
12126
|
+
* authoritative: the PR must close exactly one declared issue in exactly one
|
|
12127
|
+
* wave. A manifest `prs` entry is consulted only when no declared closing link
|
|
12128
|
+
* exists. Every refusal is a named reason, fail closed.
|
|
12093
12129
|
*/
|
|
12094
12130
|
const waveDemandForPullRequest = ({ fetchClosingPullRequests, manifest, owner, pr, repo }) => {
|
|
12095
|
-
const override = manifest.prs?.[String(pr)];
|
|
12096
|
-
if (override) {
|
|
12097
|
-
const wave = manifest.waves.find((candidate) => candidate.name === override);
|
|
12098
|
-
if (!wave) return { reasons: [`PR #${pr} override names unknown wave "${override}".`] };
|
|
12099
|
-
return {
|
|
12100
|
-
demand: {
|
|
12101
|
-
autoMerge: wave.autoMerge === true,
|
|
12102
|
-
review: wave.review,
|
|
12103
|
-
wave: wave.name
|
|
12104
|
-
},
|
|
12105
|
-
reasons: []
|
|
12106
|
-
};
|
|
12107
|
-
}
|
|
12108
12131
|
const matches = manifest.waves.flatMap((wave) => wave.issues.filter((issue) => fetchClosingPullRequests({
|
|
12109
12132
|
issue,
|
|
12110
12133
|
owner,
|
|
@@ -12113,10 +12136,9 @@ const waveDemandForPullRequest = ({ fetchClosingPullRequests, manifest, owner, p
|
|
|
12113
12136
|
issue,
|
|
12114
12137
|
wave
|
|
12115
12138
|
})));
|
|
12116
|
-
if (matches.length
|
|
12139
|
+
if (matches.length > 1) return { reasons: [`PR #${pr} must close exactly one issue in exactly one boundary wave; found ${matches.length} matching issue${matches.length === 1 ? "" : "s"}.`] };
|
|
12117
12140
|
const [match] = matches;
|
|
12118
|
-
if (
|
|
12119
|
-
return {
|
|
12141
|
+
if (match) return {
|
|
12120
12142
|
demand: {
|
|
12121
12143
|
autoMerge: match.wave.autoMerge === true,
|
|
12122
12144
|
review: match.wave.review,
|
|
@@ -12124,6 +12146,18 @@ const waveDemandForPullRequest = ({ fetchClosingPullRequests, manifest, owner, p
|
|
|
12124
12146
|
},
|
|
12125
12147
|
reasons: []
|
|
12126
12148
|
};
|
|
12149
|
+
const fallback = manifest.prs?.[String(pr)];
|
|
12150
|
+
if (!fallback) return { reasons: [`PR #${pr} must close exactly one issue in exactly one boundary wave; found 0 matching issues.`] };
|
|
12151
|
+
const wave = manifest.waves.find((candidate) => candidate.name === fallback);
|
|
12152
|
+
if (!wave) return { reasons: [`PR #${pr} fallback names unknown wave "${fallback}".`] };
|
|
12153
|
+
return {
|
|
12154
|
+
demand: {
|
|
12155
|
+
autoMerge: wave.autoMerge === true,
|
|
12156
|
+
review: wave.review,
|
|
12157
|
+
wave: wave.name
|
|
12158
|
+
},
|
|
12159
|
+
reasons: []
|
|
12160
|
+
};
|
|
12127
12161
|
};
|
|
12128
12162
|
const closingPullRequestsQuery = `query($owner: String!, $repo: String!, $issue: Int!) {
|
|
12129
12163
|
repository(owner: $owner, name: $repo) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@patronage/software-factory",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.13",
|
|
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.
|
|
40
|
+
"@patronage/factory-ci": "1.0.0-alpha.13"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@types/node": "24.13.3",
|