@patronage/software-factory 1.0.0-alpha.18 → 1.0.0-alpha.19
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 +539 -354
- package/dist/index.js +840 -176
- package/dist/schemas.d.ts +97 -98
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -14,10 +14,10 @@ import { setImmediate as setImmediate$1, setTimeout as setTimeout$1 } from "node
|
|
|
14
14
|
import { Worker } from "node:worker_threads";
|
|
15
15
|
import picomatch from "picomatch";
|
|
16
16
|
import { parse } from "yaml";
|
|
17
|
-
import { FACTORY_PROOF_GATE_STEP_NAME, GitHubApiError, mintInstallationToken, productionImpactTargetOutput, readVitestProfileDocument } from "@patronage/factory-ci";
|
|
17
|
+
import { FACTORY_PROOF_GATE_APP_ID, FACTORY_PROOF_GATE_STEP_NAME, GitHubApiError, mintInstallationToken, previewProofInventory, productionImpactTargetOutput, readVitestProfileDocument } from "@patronage/factory-ci";
|
|
18
18
|
import { promisify } from "node:util";
|
|
19
19
|
//#region package.json
|
|
20
|
-
var version = "1.0.0-alpha.
|
|
20
|
+
var version = "1.0.0-alpha.19";
|
|
21
21
|
//#endregion
|
|
22
22
|
//#region src/review-rungs.ts
|
|
23
23
|
const EVIDENCE_REVIEW_RUNGS$1 = [
|
|
@@ -291,11 +291,11 @@ var boundary_review_proof_exports = /* @__PURE__ */ __exportAll({
|
|
|
291
291
|
undispositionedBlockingFindings: () => undispositionedBlockingFindings
|
|
292
292
|
});
|
|
293
293
|
const BOUNDARY_REVIEW_PROOF_KIND$1 = "boundary-review-proof";
|
|
294
|
-
const shaSchema$
|
|
294
|
+
const shaSchema$6 = z.string().regex(/^[0-9a-f]{7,40}$/u, { message: "must be a 7-40 char lowercase hex git SHA" });
|
|
295
295
|
const coveredSetEntrySchema$1 = z.object({
|
|
296
|
-
headSha: shaSchema$
|
|
296
|
+
headSha: shaSchema$6.optional(),
|
|
297
297
|
issue: z.number().int().positive().optional(),
|
|
298
|
-
mergedSha: shaSchema$
|
|
298
|
+
mergedSha: shaSchema$6.optional(),
|
|
299
299
|
pr: z.number().int().positive(),
|
|
300
300
|
state: z.enum(["merged", "open"]),
|
|
301
301
|
wave: z.string().min(1).optional()
|
|
@@ -3704,10 +3704,10 @@ const resolveAuthoringSessionIds = ({ override, recorded }) => {
|
|
|
3704
3704
|
//#region src/demand-waiver.ts
|
|
3705
3705
|
const DEMAND_WAIVER_SCHEMA_VERSION$1 = 1;
|
|
3706
3706
|
const DEFAULT_DEMAND_WAIVER_PATH = ".factory-memory/demand-waivers.json";
|
|
3707
|
-
const shaSchema$
|
|
3707
|
+
const shaSchema$5 = z.string().regex(/^[0-9a-f]{40}$/u);
|
|
3708
3708
|
const demandWaiverSchema = z.object({
|
|
3709
3709
|
candidate: z.object({
|
|
3710
|
-
headSha: shaSchema$
|
|
3710
|
+
headSha: shaSchema$5,
|
|
3711
3711
|
pr: z.number().int().positive()
|
|
3712
3712
|
}),
|
|
3713
3713
|
demand: waiverDemandKeySchema,
|
|
@@ -5989,7 +5989,7 @@ function withGateTiming(spec, action) {
|
|
|
5989
5989
|
};
|
|
5990
5990
|
}
|
|
5991
5991
|
//#endregion
|
|
5992
|
-
//#region src/
|
|
5992
|
+
//#region src/command-options.ts
|
|
5993
5993
|
function positiveInteger$1(name) {
|
|
5994
5994
|
return (value) => {
|
|
5995
5995
|
const parsed = Number(value);
|
|
@@ -8538,7 +8538,7 @@ var CandidateIdentityError = class extends Error {
|
|
|
8538
8538
|
this.name = "CandidateIdentityError";
|
|
8539
8539
|
}
|
|
8540
8540
|
};
|
|
8541
|
-
const OBJECT_ID = /^[0-9a-f]{40}$/u;
|
|
8541
|
+
const OBJECT_ID$1 = /^[0-9a-f]{40}$/u;
|
|
8542
8542
|
/**
|
|
8543
8543
|
* Private patch-id primitive for this proof-identity owner. It stays local so
|
|
8544
8544
|
* the ownership boundary is structural rather than an approximation of which
|
|
@@ -8570,7 +8570,7 @@ function runStablePatchId(cwd, base) {
|
|
|
8570
8570
|
return id;
|
|
8571
8571
|
}
|
|
8572
8572
|
function assertObjectId(field, value) {
|
|
8573
|
-
if (!OBJECT_ID.test(value)) throw new CandidateIdentityError(`Candidate identity ${field} must be a 40-character object id, got ${JSON.stringify(value)}. Evidence bound to a malformed identity cannot be matched against a candidate, so this fails closed.`);
|
|
8573
|
+
if (!OBJECT_ID$1.test(value)) throw new CandidateIdentityError(`Candidate identity ${field} must be a 40-character object id, got ${JSON.stringify(value)}. Evidence bound to a malformed identity cannot be matched against a candidate, so this fails closed.`);
|
|
8574
8574
|
return value;
|
|
8575
8575
|
}
|
|
8576
8576
|
/**
|
|
@@ -8670,16 +8670,16 @@ var external_evidence_exports = /* @__PURE__ */ __exportAll({
|
|
|
8670
8670
|
requiredCheckScopeSchema: () => requiredCheckScopeSchema$1,
|
|
8671
8671
|
resolveAuthoringSessionIds: () => resolveAuthoringSessionIds
|
|
8672
8672
|
});
|
|
8673
|
-
const shaSchema$
|
|
8673
|
+
const shaSchema$4 = z.string().regex(/^[0-9a-f]{7,40}$/u, { message: "must be a 7-40 char lowercase hex git SHA" });
|
|
8674
8674
|
const evidenceEnvelopeSchema = z.object({
|
|
8675
8675
|
check: z.string().min(1),
|
|
8676
8676
|
checkType: z.enum(EVIDENCE_CHECK_TYPES$1),
|
|
8677
8677
|
findingsPointer: z.string().min(1).optional(),
|
|
8678
|
-
headSha: shaSchema$
|
|
8679
|
-
mergeBaseSha: shaSchema$
|
|
8678
|
+
headSha: shaSchema$4,
|
|
8679
|
+
mergeBaseSha: shaSchema$4,
|
|
8680
8680
|
model: z.string().min(1).optional(),
|
|
8681
8681
|
outcome: z.enum(["pass", "fail"]),
|
|
8682
|
-
patchId: shaSchema$
|
|
8682
|
+
patchId: shaSchema$4,
|
|
8683
8683
|
policyVersion: z.string().min(1).optional(),
|
|
8684
8684
|
producer: z.string().min(1),
|
|
8685
8685
|
requestId: z.string().min(1).optional(),
|
|
@@ -9086,6 +9086,124 @@ function generationOrderMs(value) {
|
|
|
9086
9086
|
return Number.isFinite(parsed) ? parsed : Number.NEGATIVE_INFINITY;
|
|
9087
9087
|
}
|
|
9088
9088
|
//#endregion
|
|
9089
|
+
//#region src/pr-verify-status.ts
|
|
9090
|
+
const PR_VERIFY_STATUS_CONTEXT = "patronage-factory/pr-verify";
|
|
9091
|
+
const GITHUB_PUBLISH_TIMEOUT_MS = 5e3;
|
|
9092
|
+
/**
|
|
9093
|
+
* Fire a best-effort async operation without blocking the caller and without
|
|
9094
|
+
* ever surfacing an unhandled rejection. Every check-run/commit-status
|
|
9095
|
+
* publisher in this module and github-check-runs.ts is a fire-and-forget
|
|
9096
|
+
* sink whose own failure (including its diagnostic write) must never affect
|
|
9097
|
+
* the gate result; this is the single detach wrapper they share instead of
|
|
9098
|
+
* each redeclaring the same try/catch-and-void shape.
|
|
9099
|
+
*/
|
|
9100
|
+
function runDetachedBestEffort(work) {
|
|
9101
|
+
const safe = async () => {
|
|
9102
|
+
try {
|
|
9103
|
+
await work();
|
|
9104
|
+
} catch {}
|
|
9105
|
+
};
|
|
9106
|
+
safe();
|
|
9107
|
+
}
|
|
9108
|
+
async function runGhStatus(args, cwd) {
|
|
9109
|
+
const { execFile } = await import("node:child_process");
|
|
9110
|
+
await promisify(execFile)("gh", args, {
|
|
9111
|
+
cwd,
|
|
9112
|
+
timeout: GITHUB_PUBLISH_TIMEOUT_MS
|
|
9113
|
+
});
|
|
9114
|
+
}
|
|
9115
|
+
function createGhCommitStatusPoster(output, run = runGhStatus) {
|
|
9116
|
+
return (input) => {
|
|
9117
|
+
const { context = PR_VERIFY_STATUS_CONTEXT, cwd, description, owner, repo, sha, state, suppressFailureDiagnostic = false, targetUrl } = input;
|
|
9118
|
+
const args = [
|
|
9119
|
+
"api",
|
|
9120
|
+
"--method",
|
|
9121
|
+
"POST",
|
|
9122
|
+
`/repos/${owner}/${repo}/statuses/${sha}`,
|
|
9123
|
+
"-f",
|
|
9124
|
+
`state=${state}`,
|
|
9125
|
+
"-f",
|
|
9126
|
+
`context=${context}`,
|
|
9127
|
+
"-f",
|
|
9128
|
+
`description=${description}`,
|
|
9129
|
+
"-f",
|
|
9130
|
+
`target_url=${targetUrl}`
|
|
9131
|
+
];
|
|
9132
|
+
const publish = async () => {
|
|
9133
|
+
await Promise.resolve();
|
|
9134
|
+
try {
|
|
9135
|
+
await run(args, cwd);
|
|
9136
|
+
if (state === "success") output.stdout.write(context === "patronage-factory/pr-verify" ? `pr:verify posted ${PR_VERIFY_STATUS_CONTEXT}=success\n` : `${context} posted success\n`);
|
|
9137
|
+
} catch (error) {
|
|
9138
|
+
if (suppressFailureDiagnostic) return;
|
|
9139
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
9140
|
+
output.stderr.write(context === "patronage-factory/pr-verify" ? `pr:verify: unable to post ${PR_VERIFY_STATUS_CONTEXT} status: ${message}\n` : `${context}: unable to post commit status: ${message}\n`);
|
|
9141
|
+
}
|
|
9142
|
+
};
|
|
9143
|
+
runDetachedBestEffort(publish);
|
|
9144
|
+
};
|
|
9145
|
+
}
|
|
9146
|
+
//#endregion
|
|
9147
|
+
//#region src/github-app-client.ts
|
|
9148
|
+
const readExchange = async (request, url, init, timeoutMs) => {
|
|
9149
|
+
const response = await request(url, {
|
|
9150
|
+
...init,
|
|
9151
|
+
headers: {
|
|
9152
|
+
Accept: "application/vnd.github+json",
|
|
9153
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
9154
|
+
...init.headers
|
|
9155
|
+
},
|
|
9156
|
+
signal: init.signal ?? AbortSignal.timeout(timeoutMs)
|
|
9157
|
+
});
|
|
9158
|
+
const etag = response.headers.get("etag") ?? void 0;
|
|
9159
|
+
if (response.status === 204) return {
|
|
9160
|
+
etag,
|
|
9161
|
+
json: void 0,
|
|
9162
|
+
status: 204
|
|
9163
|
+
};
|
|
9164
|
+
if (!response.ok) throw new GitHubApiError(response.status, response.statusText);
|
|
9165
|
+
return {
|
|
9166
|
+
etag,
|
|
9167
|
+
json: await response.json(),
|
|
9168
|
+
status: response.status
|
|
9169
|
+
};
|
|
9170
|
+
};
|
|
9171
|
+
async function requestGitHubJson(request, url, init, timeoutMs = GITHUB_PUBLISH_TIMEOUT_MS) {
|
|
9172
|
+
return (await readExchange(request, url, init, timeoutMs)).json;
|
|
9173
|
+
}
|
|
9174
|
+
/** Mint one repository-scoped installation token for the Patronage Factory App. */
|
|
9175
|
+
const mintFactoryInstallationToken = (repository, config, request, now, timeoutMs = GITHUB_PUBLISH_TIMEOUT_MS) => mintInstallationToken({
|
|
9176
|
+
credentials: config,
|
|
9177
|
+
owner: repository.owner,
|
|
9178
|
+
repo: repository.repo
|
|
9179
|
+
}, {
|
|
9180
|
+
fetch: request,
|
|
9181
|
+
now: () => now,
|
|
9182
|
+
timeoutMs
|
|
9183
|
+
});
|
|
9184
|
+
const resolveFactoryAppToken = async (input, dependencies, request, timeoutMs) => {
|
|
9185
|
+
if (dependencies.token) return dependencies.token;
|
|
9186
|
+
const config = dependencies.githubApp ?? tryUserConfig()?.config.githubApp;
|
|
9187
|
+
if (!config) throw new Error("The Patronage Factory GitHub App is required for App-owned GitHub requests.");
|
|
9188
|
+
return mintFactoryInstallationToken(input, config, request, (dependencies.now ?? Date.now)(), timeoutMs);
|
|
9189
|
+
};
|
|
9190
|
+
const factoryAppInit = (input, token) => ({
|
|
9191
|
+
...input.body === void 0 ? {} : { body: JSON.stringify(input.body) },
|
|
9192
|
+
headers: {
|
|
9193
|
+
Authorization: `Bearer ${token}`,
|
|
9194
|
+
"Content-Type": "application/json",
|
|
9195
|
+
...input.headers
|
|
9196
|
+
},
|
|
9197
|
+
method: input.method
|
|
9198
|
+
});
|
|
9199
|
+
/** Perform one repository request as the Patronage Factory GitHub App. */
|
|
9200
|
+
async function requestFactoryApp(input, dependencies = {}) {
|
|
9201
|
+
const request = dependencies.fetch ?? fetch;
|
|
9202
|
+
const timeoutMs = dependencies.timeoutMs ?? 5e3;
|
|
9203
|
+
const token = await resolveFactoryAppToken(input, dependencies, request, timeoutMs);
|
|
9204
|
+
return readExchange(request, `https://api.github.com/repos/${input.owner}/${input.repo}/${input.path}`, factoryAppInit(input, token), timeoutMs);
|
|
9205
|
+
}
|
|
9206
|
+
//#endregion
|
|
9089
9207
|
//#region src/pr-verify-mode.ts
|
|
9090
9208
|
const RESOLVED_PR_VERIFY_MODES = new Set([
|
|
9091
9209
|
"docs-only",
|
|
@@ -9147,64 +9265,6 @@ const prVerifyCheckPayloadFor = (proof) => {
|
|
|
9147
9265
|
};
|
|
9148
9266
|
const renderPrVerifyCheckPayloadText = (payload) => `\`\`\`json\n${JSON.stringify(payload, null, 2)}\n\`\`\``;
|
|
9149
9267
|
//#endregion
|
|
9150
|
-
//#region src/pr-verify-status.ts
|
|
9151
|
-
const PR_VERIFY_STATUS_CONTEXT = "patronage-factory/pr-verify";
|
|
9152
|
-
const GITHUB_PUBLISH_TIMEOUT_MS = 5e3;
|
|
9153
|
-
/**
|
|
9154
|
-
* Fire a best-effort async operation without blocking the caller and without
|
|
9155
|
-
* ever surfacing an unhandled rejection. Every check-run/commit-status
|
|
9156
|
-
* publisher in this module and github-check-runs.ts is a fire-and-forget
|
|
9157
|
-
* sink whose own failure (including its diagnostic write) must never affect
|
|
9158
|
-
* the gate result; this is the single detach wrapper they share instead of
|
|
9159
|
-
* each redeclaring the same try/catch-and-void shape.
|
|
9160
|
-
*/
|
|
9161
|
-
function runDetachedBestEffort(work) {
|
|
9162
|
-
const safe = async () => {
|
|
9163
|
-
try {
|
|
9164
|
-
await work();
|
|
9165
|
-
} catch {}
|
|
9166
|
-
};
|
|
9167
|
-
safe();
|
|
9168
|
-
}
|
|
9169
|
-
async function runGhStatus(args, cwd) {
|
|
9170
|
-
const { execFile } = await import("node:child_process");
|
|
9171
|
-
await promisify(execFile)("gh", args, {
|
|
9172
|
-
cwd,
|
|
9173
|
-
timeout: GITHUB_PUBLISH_TIMEOUT_MS
|
|
9174
|
-
});
|
|
9175
|
-
}
|
|
9176
|
-
function createGhCommitStatusPoster(output, run = runGhStatus) {
|
|
9177
|
-
return (input) => {
|
|
9178
|
-
const { context = PR_VERIFY_STATUS_CONTEXT, cwd, description, owner, repo, sha, state, suppressFailureDiagnostic = false, targetUrl } = input;
|
|
9179
|
-
const args = [
|
|
9180
|
-
"api",
|
|
9181
|
-
"--method",
|
|
9182
|
-
"POST",
|
|
9183
|
-
`/repos/${owner}/${repo}/statuses/${sha}`,
|
|
9184
|
-
"-f",
|
|
9185
|
-
`state=${state}`,
|
|
9186
|
-
"-f",
|
|
9187
|
-
`context=${context}`,
|
|
9188
|
-
"-f",
|
|
9189
|
-
`description=${description}`,
|
|
9190
|
-
"-f",
|
|
9191
|
-
`target_url=${targetUrl}`
|
|
9192
|
-
];
|
|
9193
|
-
const publish = async () => {
|
|
9194
|
-
await Promise.resolve();
|
|
9195
|
-
try {
|
|
9196
|
-
await run(args, cwd);
|
|
9197
|
-
if (state === "success") output.stdout.write(context === "patronage-factory/pr-verify" ? `pr:verify posted ${PR_VERIFY_STATUS_CONTEXT}=success\n` : `${context} posted success\n`);
|
|
9198
|
-
} catch (error) {
|
|
9199
|
-
if (suppressFailureDiagnostic) return;
|
|
9200
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
9201
|
-
output.stderr.write(context === "patronage-factory/pr-verify" ? `pr:verify: unable to post ${PR_VERIFY_STATUS_CONTEXT} status: ${message}\n` : `${context}: unable to post commit status: ${message}\n`);
|
|
9202
|
-
}
|
|
9203
|
-
};
|
|
9204
|
-
runDetachedBestEffort(publish);
|
|
9205
|
-
};
|
|
9206
|
-
}
|
|
9207
|
-
//#endregion
|
|
9208
9268
|
//#region src/github-check-runs.ts
|
|
9209
9269
|
const FACTORY_CHECK_NAMES = {
|
|
9210
9270
|
"pr-ready": "patronage-factory/pr-ready",
|
|
@@ -9298,19 +9358,6 @@ const prePushVerifyBindingNotice = (sha) => `${FACTORY_CHECK_NAMES["pr-verify"]}
|
|
|
9298
9358
|
The proof is complete and stored. After you push, pr:publish binds this check to the same commit and reuses that proof — no verification command runs again.
|
|
9299
9359
|
Re-verify only if the tree changes: a new commit, an amend, or a rebase.
|
|
9300
9360
|
`;
|
|
9301
|
-
async function githubJson(request, url, init, timeoutMs = GITHUB_PUBLISH_TIMEOUT_MS) {
|
|
9302
|
-
const response = await request(url, {
|
|
9303
|
-
...init,
|
|
9304
|
-
headers: {
|
|
9305
|
-
Accept: "application/vnd.github+json",
|
|
9306
|
-
"X-GitHub-Api-Version": "2022-11-28",
|
|
9307
|
-
...init.headers
|
|
9308
|
-
},
|
|
9309
|
-
signal: init.signal ?? AbortSignal.timeout(timeoutMs)
|
|
9310
|
-
});
|
|
9311
|
-
if (!response.ok) throw new GitHubApiError(response.status, response.statusText);
|
|
9312
|
-
return await response.json();
|
|
9313
|
-
}
|
|
9314
9361
|
/**
|
|
9315
9362
|
* Mint an installation token for the Patronage Factory App.
|
|
9316
9363
|
*
|
|
@@ -9320,15 +9367,7 @@ async function githubJson(request, url, init, timeoutMs = GITHUB_PUBLISH_TIMEOUT
|
|
|
9320
9367
|
* where the credentials come from (`user-config`), and the publish timeout the
|
|
9321
9368
|
* rest of these calls are bound by. Nothing is cached, as before.
|
|
9322
9369
|
*/
|
|
9323
|
-
const installationToken = (repository, config, request, now, timeoutMs = GITHUB_PUBLISH_TIMEOUT_MS) =>
|
|
9324
|
-
credentials: config,
|
|
9325
|
-
owner: repository.owner,
|
|
9326
|
-
repo: repository.repo
|
|
9327
|
-
}, {
|
|
9328
|
-
fetch: request,
|
|
9329
|
-
now: () => now,
|
|
9330
|
-
timeoutMs
|
|
9331
|
-
});
|
|
9370
|
+
const installationToken = (repository, config, request, now, timeoutMs = GITHUB_PUBLISH_TIMEOUT_MS) => mintFactoryInstallationToken(repository, config, request, now, timeoutMs);
|
|
9332
9371
|
function verifyOutput(proof) {
|
|
9333
9372
|
const value = proof;
|
|
9334
9373
|
const commands = value.executedCommands?.length ?? 0;
|
|
@@ -9417,7 +9456,7 @@ async function postFactoryCheckRun({ config, deadlineMs, dependencies, detailsUr
|
|
|
9417
9456
|
const timeoutMs = dependencies.timeoutMs ?? 5e3;
|
|
9418
9457
|
try {
|
|
9419
9458
|
const token = await installationToken(input, config, request, (dependencies.now ?? Date.now)(), timeoutMs);
|
|
9420
|
-
return await
|
|
9459
|
+
return await requestGitHubJson(request, `https://api.github.com/repos/${input.owner}/${input.repo}/check-runs`, {
|
|
9421
9460
|
body: JSON.stringify(checkRunRequestBody(input, detailsUrl)),
|
|
9422
9461
|
headers: {
|
|
9423
9462
|
Authorization: `Bearer ${token}`,
|
|
@@ -9514,7 +9553,7 @@ async function servedFactoryCheckRun(input, config, dependencies) {
|
|
|
9514
9553
|
filter: "all",
|
|
9515
9554
|
per_page: "100"
|
|
9516
9555
|
});
|
|
9517
|
-
const served = await
|
|
9556
|
+
const served = await requestGitHubJson(request, `https://api.github.com/repos/${input.owner}/${input.repo}/commits/${input.sha}/check-runs?${query.toString()}`, {
|
|
9518
9557
|
headers: { Authorization: `Bearer ${token}` },
|
|
9519
9558
|
method: "GET"
|
|
9520
9559
|
}, timeoutMs);
|
|
@@ -10188,7 +10227,7 @@ const assertCleanInstallResolution = (repoRoot, dependencies = {}) => {
|
|
|
10188
10227
|
//#region src/pr-verify.ts
|
|
10189
10228
|
const PR_VERIFY_SCHEMA_VERSION = 4;
|
|
10190
10229
|
const DEFAULT_PR_VERIFY_PROOF_PATH = ".factory-memory/pr-verify.json";
|
|
10191
|
-
const PR_VERIFY_PROOF_DESCRIPTOR = {
|
|
10230
|
+
const PR_VERIFY_PROOF_DESCRIPTOR$1 = {
|
|
10192
10231
|
defaultPath: DEFAULT_PR_VERIFY_PROOF_PATH,
|
|
10193
10232
|
label: "pr:verify proof",
|
|
10194
10233
|
parse: validatePrVerifyProof,
|
|
@@ -10230,7 +10269,7 @@ function defaultPrVerifyGit() {
|
|
|
10230
10269
|
};
|
|
10231
10270
|
}
|
|
10232
10271
|
function readPreviousPrVerifyProof(cwd, filePath) {
|
|
10233
|
-
return readOptionalProofFor(PR_VERIFY_PROOF_DESCRIPTOR, cwd, filePath);
|
|
10272
|
+
return readOptionalProofFor(PR_VERIFY_PROOF_DESCRIPTOR$1, cwd, filePath);
|
|
10234
10273
|
}
|
|
10235
10274
|
function collectBaselineFullProofs({ appendPrevious, previousProof, seedFilter }) {
|
|
10236
10275
|
if (!previousProof) return;
|
|
@@ -10574,7 +10613,7 @@ function runPrVerify(args, dependencies = {}) {
|
|
|
10574
10613
|
//#endregion
|
|
10575
10614
|
//#region src/demand-waive.ts
|
|
10576
10615
|
const DEMAND_WAIVER_SCHEMA_VERSION = 1;
|
|
10577
|
-
const shaSchema$
|
|
10616
|
+
const shaSchema$3 = z.string().regex(/^[0-9a-f]{40}$/u);
|
|
10578
10617
|
const demandWaiverStorePath = (cwd, filePath) => path.resolve(cwd, filePath ?? ".factory-memory/demand-waivers.json");
|
|
10579
10618
|
/** Every waiver recorded in this checkout; an absent or unreadable store is
|
|
10580
10619
|
* no waivers, so a damaged file can only ever block, never permit. */
|
|
@@ -10624,7 +10663,7 @@ const requestRefusals = ({ demand, parsedDemand, rationale }) => [...parsedDeman
|
|
|
10624
10663
|
* the authoring session read below describes a different candidate.
|
|
10625
10664
|
*/
|
|
10626
10665
|
const candidateBindingRefusals = ({ headSha, pr, verifyProof }) => {
|
|
10627
|
-
if (!headSha || !shaSchema$
|
|
10666
|
+
if (!headSha || !shaSchema$3.safeParse(headSha).success) return [`demand:waive could not resolve a pushed head SHA for PR #${pr}; a waiver binds to one candidate.`];
|
|
10628
10667
|
return verifyProof && verifyProof.headSha !== headSha ? [`The pr:verify proof in this checkout records head ${verifyProof.headSha}, not the pushed head ${headSha}; re-run pr:verify on the candidate before waiving one of its demands.`] : [];
|
|
10629
10668
|
};
|
|
10630
10669
|
/**
|
|
@@ -10650,7 +10689,7 @@ const runDemandWaive = (args, dependencies = {}) => {
|
|
|
10650
10689
|
pr: args.pr,
|
|
10651
10690
|
repo: repository.name
|
|
10652
10691
|
});
|
|
10653
|
-
const verifyProof = readOptionalProofFor(PR_VERIFY_PROOF_DESCRIPTOR, cwd);
|
|
10692
|
+
const verifyProof = readOptionalProofFor(PR_VERIFY_PROOF_DESCRIPTOR$1, cwd);
|
|
10654
10693
|
refusals.push(...candidateBindingRefusals({
|
|
10655
10694
|
headSha,
|
|
10656
10695
|
pr: args.pr,
|
|
@@ -13189,13 +13228,13 @@ const HOSTED_VERIFY_CHECK_NAME$1 = "verify";
|
|
|
13189
13228
|
const GITHUB_ACTIONS_APP_ID = 15368;
|
|
13190
13229
|
const GITHUB_ACTIONS_APP_SLUG = "github-actions";
|
|
13191
13230
|
const MERGE_FREEZE_SCHEMA_VERSION = 1;
|
|
13192
|
-
const CHECK_RUNS_PER_PAGE = 100;
|
|
13193
|
-
const CHECK_RUN_PAGE_LIMIT = 10;
|
|
13194
|
-
const shaSchema$
|
|
13231
|
+
const CHECK_RUNS_PER_PAGE$1 = 100;
|
|
13232
|
+
const CHECK_RUN_PAGE_LIMIT$1 = 10;
|
|
13233
|
+
const shaSchema$2 = z.string().regex(/^[0-9a-f]{40}$/u);
|
|
13195
13234
|
const activeMergeFreezeStateSchema = z.object({
|
|
13196
13235
|
active: z.literal(true),
|
|
13197
13236
|
generationId: z.number().int().positive(),
|
|
13198
|
-
headSha: shaSchema$
|
|
13237
|
+
headSha: shaSchema$2,
|
|
13199
13238
|
outcome: z.enum(["active", "stale"]),
|
|
13200
13239
|
reason: z.string().min(1),
|
|
13201
13240
|
recordedAt: z.iso.datetime(),
|
|
@@ -13205,7 +13244,7 @@ const inactiveMergeFreezeStateSchema = z.object({
|
|
|
13205
13244
|
active: z.literal(false),
|
|
13206
13245
|
clearRationale: z.string().min(1).optional(),
|
|
13207
13246
|
generationId: z.number().int().positive(),
|
|
13208
|
-
headSha: shaSchema$
|
|
13247
|
+
headSha: shaSchema$2,
|
|
13209
13248
|
outcome: z.literal("inactive"),
|
|
13210
13249
|
reason: z.string().min(1),
|
|
13211
13250
|
recordedAt: z.iso.datetime(),
|
|
@@ -13229,7 +13268,7 @@ const mergeFreezeCheckRunListItemSchema = z.object({
|
|
|
13229
13268
|
"neutral",
|
|
13230
13269
|
"success"
|
|
13231
13270
|
]).nullable().optional(),
|
|
13232
|
-
head_sha: shaSchema$
|
|
13271
|
+
head_sha: shaSchema$2,
|
|
13233
13272
|
id: z.number().int().positive(),
|
|
13234
13273
|
name: z.literal(MERGE_FREEZE_CHECK_NAME),
|
|
13235
13274
|
output: z.object({ text: z.string().nullable().optional() }).optional(),
|
|
@@ -13254,7 +13293,7 @@ const hostedVerifyCheckRunSchema = z.object({
|
|
|
13254
13293
|
id: z.literal(GITHUB_ACTIONS_APP_ID),
|
|
13255
13294
|
slug: z.literal(GITHUB_ACTIONS_APP_SLUG)
|
|
13256
13295
|
}),
|
|
13257
|
-
head_sha: shaSchema$
|
|
13296
|
+
head_sha: shaSchema$2,
|
|
13258
13297
|
id: z.number().int().positive(),
|
|
13259
13298
|
name: z.literal(HOSTED_VERIFY_CHECK_NAME$1),
|
|
13260
13299
|
started_at: z.iso.datetime().nullable(),
|
|
@@ -13265,8 +13304,8 @@ const hostedVerifyCheckRunSchema = z.object({
|
|
|
13265
13304
|
])
|
|
13266
13305
|
});
|
|
13267
13306
|
const commitParentsSchema = z.object({
|
|
13268
|
-
parents: z.array(z.object({ sha: shaSchema$
|
|
13269
|
-
sha: shaSchema$
|
|
13307
|
+
parents: z.array(z.object({ sha: shaSchema$2 })),
|
|
13308
|
+
sha: shaSchema$2
|
|
13270
13309
|
});
|
|
13271
13310
|
function selectListedMergeFreezeGeneration(listed, expectedHeadSha) {
|
|
13272
13311
|
const runs = z.object({ check_runs: z.array(z.unknown()) }).parse(listed).check_runs.map((run) => mergeFreezeCheckRunListItemSchema.parse(run));
|
|
@@ -13400,11 +13439,11 @@ function createGitHubCheckRunMergeFreezeApi(dependencies = {}) {
|
|
|
13400
13439
|
return {
|
|
13401
13440
|
list({ cwd, headSha, name, repository }) {
|
|
13402
13441
|
const checkRuns = [];
|
|
13403
|
-
for (let page = 1; page <= CHECK_RUN_PAGE_LIMIT; page += 1) {
|
|
13404
|
-
const response = z.object({ check_runs: z.array(z.unknown()) }).parse((dependencies.readJson ?? runGhJsonAt)(["api", `/repos/${repository.owner}/${repository.name}/commits/${headSha}/check-runs?check_name=${encodeURIComponent(name)}&filter=all&per_page=${CHECK_RUNS_PER_PAGE}&page=${page}`], cwd));
|
|
13442
|
+
for (let page = 1; page <= CHECK_RUN_PAGE_LIMIT$1; page += 1) {
|
|
13443
|
+
const response = z.object({ check_runs: z.array(z.unknown()) }).parse((dependencies.readJson ?? runGhJsonAt)(["api", `/repos/${repository.owner}/${repository.name}/commits/${headSha}/check-runs?check_name=${encodeURIComponent(name)}&filter=all&per_page=${CHECK_RUNS_PER_PAGE$1}&page=${page}`], cwd));
|
|
13405
13444
|
checkRuns.push(...response.check_runs);
|
|
13406
|
-
if (response.check_runs.length < CHECK_RUNS_PER_PAGE) break;
|
|
13407
|
-
if (page === CHECK_RUN_PAGE_LIMIT) throw new Error(`${MERGE_FREEZE_CHECK_NAME} check-run pagination limit was exhausted before GitHub returned a final page.`);
|
|
13445
|
+
if (response.check_runs.length < CHECK_RUNS_PER_PAGE$1) break;
|
|
13446
|
+
if (page === CHECK_RUN_PAGE_LIMIT$1) throw new Error(`${MERGE_FREEZE_CHECK_NAME} check-run pagination limit was exhausted before GitHub returned a final page.`);
|
|
13408
13447
|
}
|
|
13409
13448
|
return { check_runs: checkRuns };
|
|
13410
13449
|
},
|
|
@@ -13500,7 +13539,7 @@ const selectReviewProof = ({ reviewProof }) => {
|
|
|
13500
13539
|
if (isExplicitReviewFailure(reviewProof) || isUntypedReviewProof(reviewProof)) return {};
|
|
13501
13540
|
return hasReviewProof(reviewProof) ? reviewProof : {};
|
|
13502
13541
|
};
|
|
13503
|
-
const shaSchema = z.string().regex(/^[0-9a-f]{40}$/u);
|
|
13542
|
+
const shaSchema$1 = z.string().regex(/^[0-9a-f]{40}$/u);
|
|
13504
13543
|
const patchIdSchema = z.string().regex(/^[0-9a-f]{40,64}$/u);
|
|
13505
13544
|
const reviewCycleStateSchema = z.object({
|
|
13506
13545
|
autoBlockingFindings: z.number().int().nonnegative(),
|
|
@@ -13555,7 +13594,7 @@ const readinessRepairSchema = z.object({
|
|
|
13555
13594
|
command: z.string().min(1)
|
|
13556
13595
|
});
|
|
13557
13596
|
const managedReadinessLedgerSchema = z.object({
|
|
13558
|
-
baseSha: shaSchema,
|
|
13597
|
+
baseSha: shaSchema$1,
|
|
13559
13598
|
blockingReasons: z.array(z.string()),
|
|
13560
13599
|
classification: z.enum([
|
|
13561
13600
|
"docs/process-only",
|
|
@@ -13595,13 +13634,13 @@ const managedReadinessLedgerSchema = z.object({
|
|
|
13595
13634
|
* branded `patronage-factory/pr-ready` check binds to — GitHub's required
|
|
13596
13635
|
* checks compare a new head's checks against exactly this SHA (#477).
|
|
13597
13636
|
*/
|
|
13598
|
-
headSha: shaSchema,
|
|
13637
|
+
headSha: shaSchema$1,
|
|
13599
13638
|
/**
|
|
13600
13639
|
* The merge base used to evaluate tree-scoped external evidence. Optional
|
|
13601
13640
|
* only for backwards-compatible parsing of readiness ledgers written before
|
|
13602
13641
|
* #732; a committed verify-type demand cannot pass the merge gate without it.
|
|
13603
13642
|
*/
|
|
13604
|
-
mergeBaseSha: shaSchema.optional(),
|
|
13643
|
+
mergeBaseSha: shaSchema$1.optional(),
|
|
13605
13644
|
patchId: patchIdSchema,
|
|
13606
13645
|
pr: z.number().int().positive(),
|
|
13607
13646
|
repairs: z.array(readinessRepairSchema).default([]),
|
|
@@ -14317,6 +14356,605 @@ const evaluateReadiness = (input) => {
|
|
|
14317
14356
|
};
|
|
14318
14357
|
};
|
|
14319
14358
|
//#endregion
|
|
14359
|
+
//#region src/github-pr-status-comment.ts
|
|
14360
|
+
const FACTORY_PR_STATUS_COMMENT_MARKER = "<!-- patronage-factory-pr-status-hud -->";
|
|
14361
|
+
const OBSERVED_MARKER_PREFIX = "<!-- patronage-factory-pr-status-observed:";
|
|
14362
|
+
const OBSERVED_MARKER_SUFFIX = " -->";
|
|
14363
|
+
const UPSERT_ATTEMPT_LIMIT = 5;
|
|
14364
|
+
const writtenCommentSchema = z.object({
|
|
14365
|
+
html_url: z.string().url(),
|
|
14366
|
+
id: z.number().int().positive(),
|
|
14367
|
+
performed_via_github_app: z.object({ id: z.number().int().positive() }).refine((app) => String(app.id) === FACTORY_PROOF_GATE_APP_ID, "PR status HUD mutation was not performed by the Patronage Factory App.")
|
|
14368
|
+
});
|
|
14369
|
+
const issueCommentSchema = writtenCommentSchema.extend({
|
|
14370
|
+
body: z.string().nullable(),
|
|
14371
|
+
performed_via_github_app: z.object({ id: z.number().int().positive() }).nullable().optional()
|
|
14372
|
+
});
|
|
14373
|
+
const managedBody = (body, observedAt) => {
|
|
14374
|
+
const observed = observedAt === void 0 ? "" : `${OBSERVED_MARKER_PREFIX}${observedAt}${OBSERVED_MARKER_SUFFIX}\n`;
|
|
14375
|
+
const trimmed = body.trim();
|
|
14376
|
+
if (trimmed === "" && observed === "") return FACTORY_PR_STATUS_COMMENT_MARKER;
|
|
14377
|
+
return `${FACTORY_PR_STATUS_COMMENT_MARKER}\n${observed}${trimmed}`;
|
|
14378
|
+
};
|
|
14379
|
+
const readObservedAt = (body) => {
|
|
14380
|
+
if (!body) return;
|
|
14381
|
+
const start = body.indexOf(OBSERVED_MARKER_PREFIX);
|
|
14382
|
+
if (start === -1) return;
|
|
14383
|
+
const from = start + 42;
|
|
14384
|
+
const end = body.indexOf(OBSERVED_MARKER_SUFFIX, from);
|
|
14385
|
+
if (end === -1) return;
|
|
14386
|
+
const value = body.slice(from, end);
|
|
14387
|
+
if (Number.isNaN(new Date(value).valueOf())) return;
|
|
14388
|
+
return value;
|
|
14389
|
+
};
|
|
14390
|
+
const existingIsCurrentOrNewer = (existingBody, incomingObservedAt) => {
|
|
14391
|
+
if (incomingObservedAt === void 0) return false;
|
|
14392
|
+
const existingObservedAt = readObservedAt(existingBody);
|
|
14393
|
+
return existingObservedAt !== void 0 && existingObservedAt >= incomingObservedAt;
|
|
14394
|
+
};
|
|
14395
|
+
const isManagedComment = (comment) => String(comment.performed_via_github_app?.id) === FACTORY_PROOF_GATE_APP_ID && Boolean(comment.body?.includes("<!-- patronage-factory-pr-status-hud -->"));
|
|
14396
|
+
const exchangeFor = (dependencies) => {
|
|
14397
|
+
if (dependencies.request) return dependencies.request;
|
|
14398
|
+
if (dependencies.requestJson) {
|
|
14399
|
+
const { requestJson } = dependencies;
|
|
14400
|
+
return async (input) => {
|
|
14401
|
+
return {
|
|
14402
|
+
json: await requestJson(input),
|
|
14403
|
+
status: 200
|
|
14404
|
+
};
|
|
14405
|
+
};
|
|
14406
|
+
}
|
|
14407
|
+
return requestFactoryApp;
|
|
14408
|
+
};
|
|
14409
|
+
const listManagedComments = async (exchange, input, pageNumber = 1, collected = []) => {
|
|
14410
|
+
const response = await exchange({
|
|
14411
|
+
method: "GET",
|
|
14412
|
+
owner: input.owner,
|
|
14413
|
+
path: `issues/${input.pr}/comments?per_page=100&page=${pageNumber}`,
|
|
14414
|
+
repo: input.repo
|
|
14415
|
+
});
|
|
14416
|
+
const page = z.array(issueCommentSchema).parse(response.json);
|
|
14417
|
+
const managed = [...collected, ...page.filter(isManagedComment)];
|
|
14418
|
+
if (page.length < 100) return managed;
|
|
14419
|
+
return listManagedComments(exchange, input, pageNumber + 1, managed);
|
|
14420
|
+
};
|
|
14421
|
+
const resultFrom = (action, comment) => ({
|
|
14422
|
+
action,
|
|
14423
|
+
commentId: comment.id,
|
|
14424
|
+
url: comment.html_url
|
|
14425
|
+
});
|
|
14426
|
+
const isRetryableMutation = (error) => error instanceof GitHubApiError && error.status === 404;
|
|
14427
|
+
const deleteManagedComment = async (exchange, input, commentId) => {
|
|
14428
|
+
try {
|
|
14429
|
+
await exchange({
|
|
14430
|
+
method: "DELETE",
|
|
14431
|
+
owner: input.owner,
|
|
14432
|
+
path: `issues/comments/${commentId}`,
|
|
14433
|
+
repo: input.repo
|
|
14434
|
+
});
|
|
14435
|
+
} catch (error) {
|
|
14436
|
+
if (!(error instanceof GitHubApiError) || error.status !== 404) throw error;
|
|
14437
|
+
}
|
|
14438
|
+
};
|
|
14439
|
+
const newestBody = (comments, incomingBody, incomingObservedAt) => {
|
|
14440
|
+
let body = incomingBody;
|
|
14441
|
+
let observedAt = incomingObservedAt;
|
|
14442
|
+
for (const comment of comments) {
|
|
14443
|
+
const { body: commentBody } = comment;
|
|
14444
|
+
const observed = readObservedAt(commentBody);
|
|
14445
|
+
if (observed !== void 0 && (observedAt === void 0 || observed > observedAt) && commentBody) {
|
|
14446
|
+
body = commentBody;
|
|
14447
|
+
observedAt = observed;
|
|
14448
|
+
}
|
|
14449
|
+
}
|
|
14450
|
+
return body;
|
|
14451
|
+
};
|
|
14452
|
+
const readComment = async (exchange, input, commentId) => {
|
|
14453
|
+
const response = await exchange({
|
|
14454
|
+
method: "GET",
|
|
14455
|
+
owner: input.owner,
|
|
14456
|
+
path: `issues/comments/${commentId}`,
|
|
14457
|
+
repo: input.repo
|
|
14458
|
+
});
|
|
14459
|
+
return issueCommentSchema.parse(response.json);
|
|
14460
|
+
};
|
|
14461
|
+
const patchManaged = async (exchange, input, commentId, body) => {
|
|
14462
|
+
const response = await exchange({
|
|
14463
|
+
body: { body },
|
|
14464
|
+
method: "PATCH",
|
|
14465
|
+
owner: input.owner,
|
|
14466
|
+
path: `issues/comments/${commentId}`,
|
|
14467
|
+
repo: input.repo
|
|
14468
|
+
});
|
|
14469
|
+
return writtenCommentSchema.parse(response.json);
|
|
14470
|
+
};
|
|
14471
|
+
const reconcileManagedComments = async (exchange, input, comments, incomingBody) => {
|
|
14472
|
+
const [first, ...rest] = comments;
|
|
14473
|
+
if (!first) throw new Error("PR status HUD reconcile requires at least one managed comment.");
|
|
14474
|
+
let keeper = first;
|
|
14475
|
+
for (const comment of rest) if (comment.id < keeper.id) keeper = comment;
|
|
14476
|
+
await Promise.all(comments.filter((comment) => comment.id !== keeper.id).map((comment) => deleteManagedComment(exchange, input, comment.id)));
|
|
14477
|
+
const body = newestBody(comments, incomingBody, input.observedAt);
|
|
14478
|
+
if (keeper.body === body) return keeper;
|
|
14479
|
+
const updated = await patchManaged(exchange, input, keeper.id, body);
|
|
14480
|
+
return {
|
|
14481
|
+
...keeper,
|
|
14482
|
+
...updated,
|
|
14483
|
+
body
|
|
14484
|
+
};
|
|
14485
|
+
};
|
|
14486
|
+
const attemptUpsert = async (exchange, input, writtenBody, attempt) => {
|
|
14487
|
+
if (attempt >= UPSERT_ATTEMPT_LIMIT) throw new Error("PR status HUD upsert exhausted attempts without a single current comment.");
|
|
14488
|
+
const managed = await listManagedComments(exchange, input);
|
|
14489
|
+
if (managed.length > 1) return resultFrom("updated", await reconcileManagedComments(exchange, input, managed, writtenBody));
|
|
14490
|
+
const [existing] = managed;
|
|
14491
|
+
if (existing) try {
|
|
14492
|
+
const fresh = await readComment(exchange, input, existing.id);
|
|
14493
|
+
if (existingIsCurrentOrNewer(fresh.body, input.observedAt)) return resultFrom("updated", fresh);
|
|
14494
|
+
return resultFrom("updated", await patchManaged(exchange, input, existing.id, writtenBody));
|
|
14495
|
+
} catch (error) {
|
|
14496
|
+
if (isRetryableMutation(error)) return attemptUpsert(exchange, input, writtenBody, attempt + 1);
|
|
14497
|
+
throw error;
|
|
14498
|
+
}
|
|
14499
|
+
try {
|
|
14500
|
+
const createdResponse = await exchange({
|
|
14501
|
+
body: { body: writtenBody },
|
|
14502
|
+
method: "POST",
|
|
14503
|
+
owner: input.owner,
|
|
14504
|
+
path: `issues/${input.pr}/comments`,
|
|
14505
|
+
repo: input.repo
|
|
14506
|
+
});
|
|
14507
|
+
const created = writtenCommentSchema.parse(createdResponse.json);
|
|
14508
|
+
const after = await listManagedComments(exchange, input);
|
|
14509
|
+
if (after.length > 1) {
|
|
14510
|
+
const keeper = await reconcileManagedComments(exchange, input, after, writtenBody);
|
|
14511
|
+
return resultFrom(keeper.id === created.id ? "created" : "updated", keeper);
|
|
14512
|
+
}
|
|
14513
|
+
return resultFrom("created", created);
|
|
14514
|
+
} catch (error) {
|
|
14515
|
+
if (isRetryableMutation(error)) return attemptUpsert(exchange, input, writtenBody, attempt + 1);
|
|
14516
|
+
throw error;
|
|
14517
|
+
}
|
|
14518
|
+
};
|
|
14519
|
+
async function upsertFactoryPrStatusComment(input, dependencies = {}) {
|
|
14520
|
+
return attemptUpsert(exchangeFor(dependencies), input, managedBody(input.body, input.observedAt), 0);
|
|
14521
|
+
}
|
|
14522
|
+
//#endregion
|
|
14523
|
+
//#region src/preview-lifecycle.ts
|
|
14524
|
+
const previewTargetPlanSchema = z.object({
|
|
14525
|
+
basis: z.string(),
|
|
14526
|
+
disposition: z.enum(["deploy", "not-required"]),
|
|
14527
|
+
name: z.string().min(1)
|
|
14528
|
+
});
|
|
14529
|
+
const previewLifecyclePlanSchema = z.object({
|
|
14530
|
+
deploy: z.array(z.string()),
|
|
14531
|
+
notRequired: z.array(previewTargetPlanSchema),
|
|
14532
|
+
stampTrusted: z.boolean(),
|
|
14533
|
+
targets: z.array(previewTargetPlanSchema)
|
|
14534
|
+
}).superRefine((plan, context) => {
|
|
14535
|
+
const deploy = plan.targets.filter((target) => target.disposition === "deploy").map((target) => target.name);
|
|
14536
|
+
const notRequired = plan.targets.filter((target) => target.disposition === "not-required");
|
|
14537
|
+
if (JSON.stringify(plan.deploy) !== JSON.stringify(deploy)) context.addIssue({
|
|
14538
|
+
code: "custom",
|
|
14539
|
+
message: "deploy must match the plan's deploy target dispositions",
|
|
14540
|
+
path: ["deploy"]
|
|
14541
|
+
});
|
|
14542
|
+
if (JSON.stringify(plan.notRequired) !== JSON.stringify(notRequired)) context.addIssue({
|
|
14543
|
+
code: "custom",
|
|
14544
|
+
message: "notRequired must match the plan's not-required target dispositions",
|
|
14545
|
+
path: ["notRequired"]
|
|
14546
|
+
});
|
|
14547
|
+
});
|
|
14548
|
+
/** Validate a serialized plan without deriving or changing its dispositions. */
|
|
14549
|
+
const validatePreviewLifecyclePlan = (value) => previewLifecyclePlanSchema.parse(value);
|
|
14550
|
+
/**
|
|
14551
|
+
* The declared preview stacks: impact targets that are ALSO declared required
|
|
14552
|
+
* checks — i.e. targets whose proof is produced externally, which is exactly
|
|
14553
|
+
* what a preview producer is.
|
|
14554
|
+
*
|
|
14555
|
+
* Deriving the list from the two existing declarations rather than adding a
|
|
14556
|
+
* third is what makes lifecycle and demand unable to disagree: the stacks this
|
|
14557
|
+
* plan can withhold are precisely the demands wave 2 can release, so a stack
|
|
14558
|
+
* can never be skipped while its proof stays demanded.
|
|
14559
|
+
*/
|
|
14560
|
+
const previewLifecycleTargets = (profile) => {
|
|
14561
|
+
const demanded = new Set((profile.requiredChecks ?? []).map(({ name }) => name));
|
|
14562
|
+
return (profile.impact?.targets ?? []).map(({ name }) => name).filter((name) => demanded.has(name));
|
|
14563
|
+
};
|
|
14564
|
+
/**
|
|
14565
|
+
* Plan the preview lifecycle for one candidate.
|
|
14566
|
+
*
|
|
14567
|
+
* Trust is resolved here through the SAME identity gate demand release uses
|
|
14568
|
+
* (`trustedImpactStamp`): the proof must have passed and must bind both the
|
|
14569
|
+
* candidate's headSha and its patchId. A stale, aborted, absent, or
|
|
14570
|
+
* differently-bound proof yields no stamp, and every declared stack is planned
|
|
14571
|
+
* for deploy. Total over its INPUTS — no proof, stamp, or profile can make it
|
|
14572
|
+
* throw. A malformed CALL is the deliberate exception: omitting
|
|
14573
|
+
* `vetoedTargets` is API misuse, not data doubt, and throws.
|
|
14574
|
+
*/
|
|
14575
|
+
const planPreviewLifecycle = ({ candidate, profile, proof, vetoedTargets }) => {
|
|
14576
|
+
assertVetoedTargets(vetoedTargets, "planPreviewLifecycle");
|
|
14577
|
+
const stamp = trustedImpactStamp({
|
|
14578
|
+
candidate,
|
|
14579
|
+
proof
|
|
14580
|
+
});
|
|
14581
|
+
const vetoed = new Set(vetoedTargets);
|
|
14582
|
+
const targets = previewLifecycleTargets(profile).map((name) => {
|
|
14583
|
+
const decision = impactStampScopeDecision({
|
|
14584
|
+
stamp,
|
|
14585
|
+
surface: "preview-lifecycle",
|
|
14586
|
+
targetName: name
|
|
14587
|
+
});
|
|
14588
|
+
const vetoedHere = decision.scoped && vetoed.has(name);
|
|
14589
|
+
return {
|
|
14590
|
+
basis: vetoedHere ? `veto: a current envelope bound to this candidate records stack "${name}" as FAILING, so the stamp's release is withheld (${decision.reason})` : decision.reason,
|
|
14591
|
+
disposition: decision.scoped && !vetoedHere ? "not-required" : "deploy",
|
|
14592
|
+
name
|
|
14593
|
+
};
|
|
14594
|
+
});
|
|
14595
|
+
return {
|
|
14596
|
+
deploy: targets.filter((target) => target.disposition === "deploy").map((target) => target.name),
|
|
14597
|
+
notRequired: targets.filter((target) => target.disposition === "not-required"),
|
|
14598
|
+
stampTrusted: stamp !== void 0,
|
|
14599
|
+
targets
|
|
14600
|
+
};
|
|
14601
|
+
};
|
|
14602
|
+
//#endregion
|
|
14603
|
+
//#region src/pr-status-hud.ts
|
|
14604
|
+
const statusLabel = (status) => {
|
|
14605
|
+
return `${{
|
|
14606
|
+
Failed: "❌",
|
|
14607
|
+
"In progress": "⏳",
|
|
14608
|
+
Ready: "✅",
|
|
14609
|
+
Skipped: "➖"
|
|
14610
|
+
}[status]} ${status}`;
|
|
14611
|
+
};
|
|
14612
|
+
const utcTimestamp = (value) => {
|
|
14613
|
+
const instant = new Date(value);
|
|
14614
|
+
if (Number.isNaN(instant.valueOf())) throw new TypeError(`PR status HUD requires an ISO timestamp; received ${value}.`);
|
|
14615
|
+
return `${instant.toISOString().slice(0, 16).replace("T", " ")} UTC`;
|
|
14616
|
+
};
|
|
14617
|
+
const markdownText = (value) => value.split(/\r\n?|\n/u).join(" ").replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("@", "@").replaceAll("\\", "\\\\").replaceAll("`", "\\`").replaceAll("*", "\\*").replaceAll("_", "\\_").replaceAll("[", "\\[").replaceAll("]", "\\]");
|
|
14618
|
+
const markdownCell = (value) => markdownText(value).replaceAll("|", "\\|");
|
|
14619
|
+
const safeMarkdownLink = (label, value) => {
|
|
14620
|
+
if (!value) return "—";
|
|
14621
|
+
try {
|
|
14622
|
+
const url = new URL(value);
|
|
14623
|
+
if (url.protocol !== "https:" && url.protocol !== "http:") return "—";
|
|
14624
|
+
return `[${label}](<${url.href}>)`;
|
|
14625
|
+
} catch {
|
|
14626
|
+
return "—";
|
|
14627
|
+
}
|
|
14628
|
+
};
|
|
14629
|
+
const renderTable = (rows) => [
|
|
14630
|
+
"| Target | Status | Link | Timestamp |",
|
|
14631
|
+
"| --- | --- | --- | --- |",
|
|
14632
|
+
...rows.map((row) => `| ${markdownCell(row.target)} | ${statusLabel(row.status)} | ${row.link} | ${row.timestamp} |`)
|
|
14633
|
+
].join("\n");
|
|
14634
|
+
const checkRow = (target, snapshot, renderedAt) => {
|
|
14635
|
+
if (!snapshot) return {
|
|
14636
|
+
detail: "No completed Factory App snapshot is available at this head.",
|
|
14637
|
+
link: "—",
|
|
14638
|
+
status: "In progress",
|
|
14639
|
+
target,
|
|
14640
|
+
timestamp: utcTimestamp(renderedAt)
|
|
14641
|
+
};
|
|
14642
|
+
return {
|
|
14643
|
+
detail: `Completed snapshot: ${snapshot.conclusion}.`,
|
|
14644
|
+
link: safeMarkdownLink("Details", snapshot.detailsUrl),
|
|
14645
|
+
status: snapshot.conclusion === "success" ? "Ready" : "Failed",
|
|
14646
|
+
target,
|
|
14647
|
+
timestamp: utcTimestamp(snapshot.completedAt)
|
|
14648
|
+
};
|
|
14649
|
+
};
|
|
14650
|
+
const previewRow = (target, registration, failure, renderedAt) => {
|
|
14651
|
+
if (target.disposition === "not-required") return {
|
|
14652
|
+
detail: target.basis,
|
|
14653
|
+
link: "—",
|
|
14654
|
+
status: "Skipped",
|
|
14655
|
+
target: target.name,
|
|
14656
|
+
timestamp: utcTimestamp(renderedAt)
|
|
14657
|
+
};
|
|
14658
|
+
if (registration) return {
|
|
14659
|
+
detail: "Factory-owned preview registration is live at this head.",
|
|
14660
|
+
link: safeMarkdownLink("Preview", registration.url),
|
|
14661
|
+
status: "Ready",
|
|
14662
|
+
target: target.name,
|
|
14663
|
+
timestamp: utcTimestamp(renderedAt)
|
|
14664
|
+
};
|
|
14665
|
+
if (failure) return {
|
|
14666
|
+
detail: `Preview deploy concluded ${failure.conclusion}.`,
|
|
14667
|
+
link: safeMarkdownLink("Details", failure.detailsUrl),
|
|
14668
|
+
status: "Failed",
|
|
14669
|
+
target: target.name,
|
|
14670
|
+
timestamp: utcTimestamp(failure.completedAt)
|
|
14671
|
+
};
|
|
14672
|
+
return {
|
|
14673
|
+
detail: target.basis,
|
|
14674
|
+
link: "—",
|
|
14675
|
+
status: "In progress",
|
|
14676
|
+
target: target.name,
|
|
14677
|
+
timestamp: utcTimestamp(renderedAt)
|
|
14678
|
+
};
|
|
14679
|
+
};
|
|
14680
|
+
function renderFactoryPrStatusHud(input) {
|
|
14681
|
+
if (input.planHeadSha !== input.headSha) throw new Error(`PR status HUD plan targets ${input.planHeadSha}, expected current head ${input.headSha}.`);
|
|
14682
|
+
const checks = new Map(Object.entries(input.checks).flatMap(([name, check]) => {
|
|
14683
|
+
if (!check) return [];
|
|
14684
|
+
const expectedSha = name === "patronage-factory/merge-freeze" ? input.baseSha : input.headSha;
|
|
14685
|
+
return check.headSha === expectedSha ? [[name, check]] : [];
|
|
14686
|
+
}));
|
|
14687
|
+
const registrations = new Map(input.previewRegistrations.filter((row) => row.pr === input.pr && row.headSha === input.headSha && row.cleanupStatus === "pending").map((row) => [row.stack, row]));
|
|
14688
|
+
const rows = [
|
|
14689
|
+
checkRow("Verify", checks.get("patronage-factory/pr-verify"), input.renderedAt),
|
|
14690
|
+
checkRow("Ready", checks.get("patronage-factory/pr-ready"), input.renderedAt),
|
|
14691
|
+
...input.plan.targets.map((target) => previewRow(target, registrations.get(target.name), input.previewFailures?.[target.name], input.renderedAt))
|
|
14692
|
+
];
|
|
14693
|
+
const mergeFreeze = checks.get("patronage-factory/merge-freeze");
|
|
14694
|
+
if (mergeFreeze) rows.push(checkRow("Merge freeze", mergeFreeze, input.renderedAt));
|
|
14695
|
+
rows.push({
|
|
14696
|
+
detail: "Production deployment is not demanded for a pull request.",
|
|
14697
|
+
link: "—",
|
|
14698
|
+
status: "Skipped",
|
|
14699
|
+
target: "Production deploy",
|
|
14700
|
+
timestamp: utcTimestamp(input.renderedAt)
|
|
14701
|
+
});
|
|
14702
|
+
const active = rows.filter((row) => row.status !== "Skipped");
|
|
14703
|
+
const skipped = rows.filter((row) => row.status === "Skipped");
|
|
14704
|
+
const skippedDetails = skipped.map((row) => `- **${markdownText(row.target)}:** ${markdownText(row.detail)}`).join("\n");
|
|
14705
|
+
return [
|
|
14706
|
+
"## Patronage Factory status",
|
|
14707
|
+
"",
|
|
14708
|
+
renderTable(active),
|
|
14709
|
+
"",
|
|
14710
|
+
"<details>",
|
|
14711
|
+
`<summary>Skipped (${skipped.length})</summary>`,
|
|
14712
|
+
"",
|
|
14713
|
+
renderTable(skipped),
|
|
14714
|
+
"",
|
|
14715
|
+
skippedDetails,
|
|
14716
|
+
"",
|
|
14717
|
+
"</details>",
|
|
14718
|
+
"",
|
|
14719
|
+
`_Observed ${utcTimestamp(input.renderedAt)} at \`${input.headSha.slice(0, 7)}\`. Human-readable presentation only; checks and factory evidence remain authoritative._`
|
|
14720
|
+
].join("\n");
|
|
14721
|
+
}
|
|
14722
|
+
async function presentFactoryPrStatusHud(input, dependencies) {
|
|
14723
|
+
const identity = {
|
|
14724
|
+
headSha: input.headSha,
|
|
14725
|
+
owner: input.owner,
|
|
14726
|
+
pr: input.pr,
|
|
14727
|
+
repo: input.repo
|
|
14728
|
+
};
|
|
14729
|
+
const sources = await dependencies.readSources(identity);
|
|
14730
|
+
const renderedAt = input.renderedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
14731
|
+
const body = renderFactoryPrStatusHud({
|
|
14732
|
+
...sources,
|
|
14733
|
+
headSha: input.headSha,
|
|
14734
|
+
plan: input.plan,
|
|
14735
|
+
planHeadSha: input.planHeadSha,
|
|
14736
|
+
pr: input.pr,
|
|
14737
|
+
renderedAt
|
|
14738
|
+
});
|
|
14739
|
+
await dependencies.assertCurrentIdentity({
|
|
14740
|
+
baseSha: sources.baseSha,
|
|
14741
|
+
...identity
|
|
14742
|
+
});
|
|
14743
|
+
return (dependencies.upsert ?? upsertFactoryPrStatusComment)({
|
|
14744
|
+
body,
|
|
14745
|
+
observedAt: renderedAt,
|
|
14746
|
+
owner: input.owner,
|
|
14747
|
+
pr: input.pr,
|
|
14748
|
+
repo: input.repo
|
|
14749
|
+
});
|
|
14750
|
+
}
|
|
14751
|
+
const checkRunSchema = z.object({
|
|
14752
|
+
app: z.object({ id: z.number().int().positive() }).nullable().optional(),
|
|
14753
|
+
completed_at: z.iso.datetime().nullable(),
|
|
14754
|
+
conclusion: z.string().nullable().optional(),
|
|
14755
|
+
details_url: z.string().nullable().optional(),
|
|
14756
|
+
head_sha: z.string().regex(/^[0-9a-f]{40}$/u),
|
|
14757
|
+
name: z.string(),
|
|
14758
|
+
started_at: z.iso.datetime().nullable(),
|
|
14759
|
+
status: z.enum([
|
|
14760
|
+
"completed",
|
|
14761
|
+
"in_progress",
|
|
14762
|
+
"queued"
|
|
14763
|
+
])
|
|
14764
|
+
});
|
|
14765
|
+
const CHECK_RUNS_PER_PAGE = 100;
|
|
14766
|
+
const CHECK_RUN_PAGE_LIMIT = 10;
|
|
14767
|
+
const pullIdentitySchema = z.object({
|
|
14768
|
+
base: z.object({ sha: z.string().regex(/^[0-9a-f]{40}$/u) }),
|
|
14769
|
+
head: z.object({ sha: z.string().regex(/^[0-9a-f]{40}$/u) })
|
|
14770
|
+
});
|
|
14771
|
+
const readFactoryPrIdentity = async (input, dependencies) => pullIdentitySchema.parse(await requestGitHubJson(dependencies.fetch ?? fetch, `https://api.github.com/repos/${input.owner}/${input.repo}/pulls/${input.pr}`, {
|
|
14772
|
+
headers: { Authorization: `Bearer ${input.token}` },
|
|
14773
|
+
method: "GET"
|
|
14774
|
+
}, dependencies.timeoutMs));
|
|
14775
|
+
async function assertFactoryPrStatusIdentity(input, dependencies = {}) {
|
|
14776
|
+
const current = await readFactoryPrIdentity(input, dependencies);
|
|
14777
|
+
if (current.head.sha !== input.headSha || current.base.sha !== input.baseSha) throw new Error(`PR status HUD candidate identity moved to base ${current.base.sha} / head ${current.head.sha}; refusing stale presentation for base ${input.baseSha} / head ${input.headSha}.`);
|
|
14778
|
+
}
|
|
14779
|
+
const listFactoryCheckRuns = async (input, name, sha, dependencies) => {
|
|
14780
|
+
const request = dependencies.fetch ?? fetch;
|
|
14781
|
+
const runs = [];
|
|
14782
|
+
for (let page = 1; page <= CHECK_RUN_PAGE_LIMIT; page += 1) {
|
|
14783
|
+
const response = z.object({ check_runs: z.array(checkRunSchema) }).parse(await requestGitHubJson(request, `https://api.github.com/repos/${input.owner}/${input.repo}/commits/${sha}/check-runs?check_name=${encodeURIComponent(name)}&filter=all&per_page=${CHECK_RUNS_PER_PAGE}&page=${page}`, {
|
|
14784
|
+
headers: { Authorization: `Bearer ${input.token}` },
|
|
14785
|
+
method: "GET"
|
|
14786
|
+
}, dependencies.timeoutMs));
|
|
14787
|
+
runs.push(...response.check_runs);
|
|
14788
|
+
if (response.check_runs.length < CHECK_RUNS_PER_PAGE) return runs;
|
|
14789
|
+
}
|
|
14790
|
+
throw new Error(`${name} pagination limit was exhausted before GitHub returned a final page.`);
|
|
14791
|
+
};
|
|
14792
|
+
const completedSnapshot = (name, sha, runs) => {
|
|
14793
|
+
const candidates = runs.filter((run) => String(run.app?.id ?? "") === FACTORY_PROOF_GATE_APP_ID && run.head_sha === sha && run.name === name);
|
|
14794
|
+
if (candidates.some((run) => !Number.isFinite(generationOrderMs(run.started_at)))) throw new Error(`${name} presentation snapshot has a generation with no usable started_at.`);
|
|
14795
|
+
const selected = selectNewestGeneration(candidates.map((run) => ({
|
|
14796
|
+
orderMs: generationOrderMs(run.started_at),
|
|
14797
|
+
run
|
|
14798
|
+
})));
|
|
14799
|
+
if (selected.kind === "ambiguous") throw new Error(`Newest ${name} presentation snapshot is ambiguous: ${selected.tied.length} check runs share the newest timestamp.`);
|
|
14800
|
+
if (selected.kind === "none") return;
|
|
14801
|
+
const { run } = selected.generation;
|
|
14802
|
+
if (run.status !== "completed" || !run.completed_at || !run.conclusion) return;
|
|
14803
|
+
return {
|
|
14804
|
+
completedAt: run.completed_at,
|
|
14805
|
+
conclusion: run.conclusion,
|
|
14806
|
+
...run.details_url ? { detailsUrl: run.details_url } : {},
|
|
14807
|
+
headSha: run.head_sha,
|
|
14808
|
+
name
|
|
14809
|
+
};
|
|
14810
|
+
};
|
|
14811
|
+
const FAILED_PREVIEW_CONCLUSIONS = new Set([
|
|
14812
|
+
"action_required",
|
|
14813
|
+
"cancelled",
|
|
14814
|
+
"failure",
|
|
14815
|
+
"stale",
|
|
14816
|
+
"startup_failure",
|
|
14817
|
+
"timed_out"
|
|
14818
|
+
]);
|
|
14819
|
+
const completedFailedPreview = (name, sha, runs) => {
|
|
14820
|
+
const candidates = runs.filter((run) => run.head_sha === sha && run.name === name);
|
|
14821
|
+
if (candidates.some((run) => !Number.isFinite(generationOrderMs(run.started_at)))) throw new Error(`${name} preview failure snapshot has a generation with no usable started_at.`);
|
|
14822
|
+
const selected = selectNewestGeneration(candidates.map((run) => ({
|
|
14823
|
+
orderMs: generationOrderMs(run.started_at),
|
|
14824
|
+
run
|
|
14825
|
+
})));
|
|
14826
|
+
if (selected.kind === "ambiguous") throw new Error(`Newest ${name} preview failure snapshot is ambiguous: ${selected.tied.length} check runs share the newest timestamp.`);
|
|
14827
|
+
if (selected.kind === "none") return;
|
|
14828
|
+
const { run } = selected.generation;
|
|
14829
|
+
if (run.status !== "completed" || !run.completed_at || !run.conclusion || !FAILED_PREVIEW_CONCLUSIONS.has(run.conclusion)) return;
|
|
14830
|
+
return {
|
|
14831
|
+
completedAt: run.completed_at,
|
|
14832
|
+
conclusion: run.conclusion,
|
|
14833
|
+
...run.details_url ? { detailsUrl: run.details_url } : {},
|
|
14834
|
+
headSha: run.head_sha,
|
|
14835
|
+
name
|
|
14836
|
+
};
|
|
14837
|
+
};
|
|
14838
|
+
async function readFactoryPrStatusSources(input, dependencies = {}) {
|
|
14839
|
+
const listPreviews = dependencies.listPreviewRegistrations ?? ((identity) => previewProofInventory.list(identity, {
|
|
14840
|
+
fetch: dependencies.fetch,
|
|
14841
|
+
timeoutMs: dependencies.timeoutMs,
|
|
14842
|
+
token: identity.token
|
|
14843
|
+
}));
|
|
14844
|
+
const [previewRegistrations, pullIdentity] = await Promise.all([listPreviews(input), readFactoryPrIdentity(input, dependencies)]);
|
|
14845
|
+
if (pullIdentity.head.sha !== input.headSha) throw new Error(`PR status HUD candidate head moved to ${pullIdentity.head.sha}; refusing stale presentation for ${input.headSha}.`);
|
|
14846
|
+
const baseSha = pullIdentity.base.sha;
|
|
14847
|
+
const checkTargets = [
|
|
14848
|
+
["patronage-factory/pr-verify", input.headSha],
|
|
14849
|
+
["patronage-factory/pr-ready", input.headSha],
|
|
14850
|
+
["patronage-factory/merge-freeze", baseSha]
|
|
14851
|
+
];
|
|
14852
|
+
const listedChecks = await Promise.all(checkTargets.map(([name, sha]) => listFactoryCheckRuns(input, name, sha, dependencies)));
|
|
14853
|
+
const checks = Object.fromEntries(checkTargets.flatMap(([name, sha], index) => {
|
|
14854
|
+
const snapshot = completedSnapshot(name, sha, listedChecks[index] ?? []);
|
|
14855
|
+
return snapshot ? [[name, snapshot]] : [];
|
|
14856
|
+
}));
|
|
14857
|
+
const previewTargets = input.previewTargets ?? [];
|
|
14858
|
+
const listedPreviewChecks = await Promise.all(previewTargets.map((name) => listFactoryCheckRuns(input, name, input.headSha, dependencies)));
|
|
14859
|
+
return {
|
|
14860
|
+
baseSha,
|
|
14861
|
+
checks,
|
|
14862
|
+
previewFailures: Object.fromEntries(previewTargets.flatMap((name, index) => {
|
|
14863
|
+
const snapshot = completedFailedPreview(name, input.headSha, listedPreviewChecks[index] ?? []);
|
|
14864
|
+
return snapshot ? [[name, snapshot]] : [];
|
|
14865
|
+
})),
|
|
14866
|
+
previewRegistrations
|
|
14867
|
+
};
|
|
14868
|
+
}
|
|
14869
|
+
const OBJECT_ID = /^[0-9a-f]{40}$/u;
|
|
14870
|
+
const PR_VERIFY_PROOF_DESCRIPTOR = {
|
|
14871
|
+
defaultPath: ".factory-memory/pr-verify.json",
|
|
14872
|
+
label: "pr:verify proof",
|
|
14873
|
+
parse: validatePrVerifyProof,
|
|
14874
|
+
schemaVersion: 4
|
|
14875
|
+
};
|
|
14876
|
+
const objectIdOrHead = (value, headSha) => value !== void 0 && OBJECT_ID.test(value) ? value : headSha;
|
|
14877
|
+
/**
|
|
14878
|
+
* Consume `planPreviewLifecycle` for the current checkout. No second plan is
|
|
14879
|
+
* invented: dispositions are Ready/Skipped from `deploy` vs `not-required`.
|
|
14880
|
+
* Candidate identity is never copied from the proof. Absent independent
|
|
14881
|
+
* identity or an unbound verify proof is the planner's conservative path.
|
|
14882
|
+
*/
|
|
14883
|
+
function planFactoryPrStatusHud(input) {
|
|
14884
|
+
const { profile } = loadProjectProfile({
|
|
14885
|
+
cwd: input.cwd,
|
|
14886
|
+
profilePath: input.profilePath
|
|
14887
|
+
});
|
|
14888
|
+
const proof = readOptionalProofFor(PR_VERIFY_PROOF_DESCRIPTOR, input.cwd);
|
|
14889
|
+
const independentPatchId = input.patchId !== void 0 && OBJECT_ID.test(input.patchId) ? input.patchId : void 0;
|
|
14890
|
+
const boundProof = independentPatchId !== void 0 && proof !== void 0 && proof.headSha === input.headSha ? proof : void 0;
|
|
14891
|
+
const patchId = independentPatchId ?? input.headSha;
|
|
14892
|
+
const mergeBaseSha = objectIdOrHead(input.mergeBaseSha, input.headSha);
|
|
14893
|
+
return planPreviewLifecycle({
|
|
14894
|
+
candidate: {
|
|
14895
|
+
headSha: input.headSha,
|
|
14896
|
+
patchId
|
|
14897
|
+
},
|
|
14898
|
+
profile,
|
|
14899
|
+
proof: boundProof,
|
|
14900
|
+
vetoedTargets: boundFailingCheckNames({
|
|
14901
|
+
candidate: {
|
|
14902
|
+
headSha: input.headSha,
|
|
14903
|
+
mergeBaseSha,
|
|
14904
|
+
patchId
|
|
14905
|
+
},
|
|
14906
|
+
checkType: "verify",
|
|
14907
|
+
envelopes: loadEvidenceEnvelopes(input.cwd)
|
|
14908
|
+
})
|
|
14909
|
+
});
|
|
14910
|
+
}
|
|
14911
|
+
const resolveHudToken = async (input) => {
|
|
14912
|
+
if (input.token) return input.token;
|
|
14913
|
+
const config = tryUserConfig()?.config.githubApp;
|
|
14914
|
+
if (!config) throw new Error("PR status HUD refresh requires the Patronage Factory GitHub App.");
|
|
14915
|
+
return mintFactoryInstallationToken({
|
|
14916
|
+
owner: input.owner,
|
|
14917
|
+
repo: input.repo
|
|
14918
|
+
}, config, fetch, Date.now());
|
|
14919
|
+
};
|
|
14920
|
+
/** Plan from the checkout and present the App-owned HUD. */
|
|
14921
|
+
async function refreshFactoryPrStatusHud(input) {
|
|
14922
|
+
const plan = planFactoryPrStatusHud(input);
|
|
14923
|
+
const token = await resolveHudToken(input);
|
|
14924
|
+
return presentFactoryPrStatusHud({
|
|
14925
|
+
headSha: input.headSha,
|
|
14926
|
+
owner: input.owner,
|
|
14927
|
+
plan,
|
|
14928
|
+
planHeadSha: input.headSha,
|
|
14929
|
+
pr: input.pr,
|
|
14930
|
+
repo: input.repo
|
|
14931
|
+
}, {
|
|
14932
|
+
assertCurrentIdentity: (identity) => assertFactoryPrStatusIdentity({
|
|
14933
|
+
...identity,
|
|
14934
|
+
token
|
|
14935
|
+
}),
|
|
14936
|
+
readSources: (identity) => readFactoryPrStatusSources({
|
|
14937
|
+
...identity,
|
|
14938
|
+
previewTargets: plan.deploy,
|
|
14939
|
+
token
|
|
14940
|
+
}),
|
|
14941
|
+
upsert: (upsertInput) => upsertFactoryPrStatusComment(upsertInput, { request: (request) => requestFactoryApp(request, { token }) })
|
|
14942
|
+
});
|
|
14943
|
+
}
|
|
14944
|
+
/**
|
|
14945
|
+
* Presentation must not fail an admission gate. Used after pr:ready publishes
|
|
14946
|
+
* its check so the table is not stuck at first-write.
|
|
14947
|
+
*/
|
|
14948
|
+
async function refreshFactoryPrStatusHudSafely(input, onNotice = (message) => {
|
|
14949
|
+
console.warn(message);
|
|
14950
|
+
}) {
|
|
14951
|
+
try {
|
|
14952
|
+
await refreshFactoryPrStatusHud(input);
|
|
14953
|
+
} catch (error) {
|
|
14954
|
+
onNotice(`PR status HUD refresh failed without affecting the gate: ${error instanceof Error ? error.message : String(error)}`);
|
|
14955
|
+
}
|
|
14956
|
+
}
|
|
14957
|
+
//#endregion
|
|
14320
14958
|
//#region src/pr-ready.ts
|
|
14321
14959
|
const CHECK_CONCLUSION_BY_READINESS_STATUS = {
|
|
14322
14960
|
blocked: "failure",
|
|
@@ -14588,6 +15226,20 @@ async function runPrReady(args, dependencies = {}) {
|
|
|
14588
15226
|
sleep: dependencies.sleep
|
|
14589
15227
|
});
|
|
14590
15228
|
let finalCheckPublished = false;
|
|
15229
|
+
let hudMergeBaseSha;
|
|
15230
|
+
let hudPatchId;
|
|
15231
|
+
const presentHud = async () => {
|
|
15232
|
+
await (dependencies.presentStatusHud ?? refreshFactoryPrStatusHudSafely)({
|
|
15233
|
+
cwd,
|
|
15234
|
+
headSha: pr.headRefOid,
|
|
15235
|
+
owner: repository.owner,
|
|
15236
|
+
pr: args.pr,
|
|
15237
|
+
repo: repository.name,
|
|
15238
|
+
...args.profilePath === void 0 ? {} : { profilePath: args.profilePath },
|
|
15239
|
+
...hudMergeBaseSha === void 0 ? {} : { mergeBaseSha: hudMergeBaseSha },
|
|
15240
|
+
...hudPatchId === void 0 ? {} : { patchId: hudPatchId }
|
|
15241
|
+
});
|
|
15242
|
+
};
|
|
14591
15243
|
try {
|
|
14592
15244
|
const authoritativeBaseSha = authoritativePrBase({
|
|
14593
15245
|
args,
|
|
@@ -14599,7 +15251,9 @@ async function runPrReady(args, dependencies = {}) {
|
|
|
14599
15251
|
cwd,
|
|
14600
15252
|
git
|
|
14601
15253
|
});
|
|
14602
|
-
const { headSha: localHeadSha, patchId } = candidateIdentityForBase(authoritativeBaseSha);
|
|
15254
|
+
const { headSha: localHeadSha, mergeBaseSha: resolvedMergeBaseSha, patchId } = candidateIdentityForBase(authoritativeBaseSha);
|
|
15255
|
+
hudMergeBaseSha = resolvedMergeBaseSha;
|
|
15256
|
+
hudPatchId = patchId;
|
|
14603
15257
|
const files = git.changedFiles(cwd, authoritativeBaseSha, pr.headRefOid);
|
|
14604
15258
|
const committedPolicy = policyProfileAtCommittedHead({
|
|
14605
15259
|
cwd,
|
|
@@ -14783,6 +15437,7 @@ async function runPrReady(args, dependencies = {}) {
|
|
|
14783
15437
|
subject: { prNumber: args.pr }
|
|
14784
15438
|
}, dependencies.hq);
|
|
14785
15439
|
reportProof(proof, output, Boolean(args.json), args.report ?? true);
|
|
15440
|
+
await presentHud();
|
|
14786
15441
|
if (proof.status !== "ready" && (args.throwWhenBlocked ?? true)) throw new Error(`pr:ready blocked: ${proof.blockingReasons.join(" ")}`);
|
|
14787
15442
|
return proof;
|
|
14788
15443
|
} catch (error) {
|
|
@@ -14803,6 +15458,7 @@ async function runPrReady(args, dependencies = {}) {
|
|
|
14803
15458
|
sha: pr.headRefOid
|
|
14804
15459
|
};
|
|
14805
15460
|
if (!await (dependencies.publishFinalCheckRun ? dependencies.publishFinalCheckRun(failureCheckInput) : ensureFactoryCheckRunPublished(failureCheckInput, { onDiagnostic: (diagnostic) => process.stderr.write(diagnostic) }))) console.warn(`pr:ready could not publish the failing ${FACTORY_CHECK_NAMES["pr-ready"]} check run for ${pr.headRefOid}; the required check is absent rather than red.`);
|
|
15461
|
+
await presentHud();
|
|
14806
15462
|
}
|
|
14807
15463
|
throw error;
|
|
14808
15464
|
}
|
|
@@ -15212,7 +15868,7 @@ async function runPrReview(args, dependencies = {}) {
|
|
|
15212
15868
|
git
|
|
15213
15869
|
})(args.base);
|
|
15214
15870
|
const { additional: additionalOutput, canonical: output } = resolveProofOutputPaths(cwd, DEFAULT_PR_REVIEW_PROOF_PATH, args.output);
|
|
15215
|
-
const verifyProof = readOptionalProofFor(PR_VERIFY_PROOF_DESCRIPTOR, cwd, args.verifyProof ?? ".factory-memory/pr-verify.json");
|
|
15871
|
+
const verifyProof = readOptionalProofFor(PR_VERIFY_PROOF_DESCRIPTOR$1, cwd, args.verifyProof ?? ".factory-memory/pr-verify.json");
|
|
15216
15872
|
const writeProofCopies = (proof) => {
|
|
15217
15873
|
writeReviewProofCopies(cwd, proof, output, additionalOutput);
|
|
15218
15874
|
enqueueHqIngest({
|
|
@@ -15423,7 +16079,7 @@ function discoverOrCreatePullRequestNumber({ args, branch, cwd, gh }) {
|
|
|
15423
16079
|
if (!created[0]) throw new Error(`gh pr create completed but no PR was found for branch ${branch}.`);
|
|
15424
16080
|
return created[0].number;
|
|
15425
16081
|
}
|
|
15426
|
-
const readOptionalVerifyProof = (cwd, proofPath) => readOptionalProofFor(PR_VERIFY_PROOF_DESCRIPTOR, cwd, proofPath);
|
|
16082
|
+
const readOptionalVerifyProof = (cwd, proofPath) => readOptionalProofFor(PR_VERIFY_PROOF_DESCRIPTOR$1, cwd, proofPath);
|
|
15427
16083
|
const followUpSummaryLine = (outcome) => {
|
|
15428
16084
|
if (outcome.status === "succeeded") return `Follow-up succeeded: ${outcome.action.command} — ${outcome.reason}.`;
|
|
15429
16085
|
if (outcome.status === "skipped") return `Follow-up skipped (${outcome.action.command}): ${outcome.reason}.`;
|
|
@@ -16216,6 +16872,67 @@ function createProductionImpactCommand(output) {
|
|
|
16216
16872
|
}));
|
|
16217
16873
|
}
|
|
16218
16874
|
//#endregion
|
|
16875
|
+
//#region src/pr-status-hud-command.ts
|
|
16876
|
+
const shaSchema = z.string().regex(/^[0-9a-f]{40}$/u);
|
|
16877
|
+
const boundPlanSchema = z.object({
|
|
16878
|
+
headSha: shaSchema,
|
|
16879
|
+
plan: z.unknown()
|
|
16880
|
+
});
|
|
16881
|
+
function resolvePrStatusHudPlan(args) {
|
|
16882
|
+
if (args.planPath !== void 0) {
|
|
16883
|
+
const boundPlan = boundPlanSchema.parse(JSON.parse(readFileSync(path.resolve(args.cwd, args.planPath), "utf-8")));
|
|
16884
|
+
if (boundPlan.headSha !== args.headSha) throw new Error(`PR status HUD plan targets ${boundPlan.headSha}, expected current head ${args.headSha}.`);
|
|
16885
|
+
return {
|
|
16886
|
+
plan: validatePreviewLifecyclePlan(boundPlan.plan),
|
|
16887
|
+
planHeadSha: boundPlan.headSha
|
|
16888
|
+
};
|
|
16889
|
+
}
|
|
16890
|
+
return {
|
|
16891
|
+
plan: planFactoryPrStatusHud({
|
|
16892
|
+
cwd: args.cwd,
|
|
16893
|
+
headSha: args.headSha
|
|
16894
|
+
}),
|
|
16895
|
+
planHeadSha: args.headSha
|
|
16896
|
+
};
|
|
16897
|
+
}
|
|
16898
|
+
async function runPrStatusHud(args) {
|
|
16899
|
+
const { plan, planHeadSha } = resolvePrStatusHudPlan(args);
|
|
16900
|
+
return presentFactoryPrStatusHud({
|
|
16901
|
+
headSha: args.headSha,
|
|
16902
|
+
owner: args.owner,
|
|
16903
|
+
plan,
|
|
16904
|
+
planHeadSha,
|
|
16905
|
+
pr: args.pr,
|
|
16906
|
+
repo: args.repo
|
|
16907
|
+
}, {
|
|
16908
|
+
assertCurrentIdentity: (identity) => assertFactoryPrStatusIdentity({
|
|
16909
|
+
...identity,
|
|
16910
|
+
token: args.token
|
|
16911
|
+
}),
|
|
16912
|
+
readSources: (identity) => readFactoryPrStatusSources({
|
|
16913
|
+
...identity,
|
|
16914
|
+
token: args.token
|
|
16915
|
+
}),
|
|
16916
|
+
upsert: (input) => upsertFactoryPrStatusComment(input, { request: (request) => requestFactoryApp(request, { token: args.token }) })
|
|
16917
|
+
});
|
|
16918
|
+
}
|
|
16919
|
+
function createPrStatusHudCommand(output, action = runPrStatusHud, env = process.env) {
|
|
16920
|
+
return markCwdOptionDefault(new Command("pr:status-hud").description("Present the App-owned PR status HUD from planPreviewLifecycle").requiredOption("--head <sha>", "candidate head commit SHA").requiredOption("--owner <owner>", "GitHub repository owner").requiredOption("--repo <repo>", "GitHub repository name").requiredOption("--pr <number>", "pull request number", positiveInteger$1("--pr")).option("--plan <path>", "optional candidate-bound JSON envelope; when omitted, planPreviewLifecycle is consumed from the checkout").option("--cwd <path>", "repository working directory", collectCwdOption).action(async (options) => {
|
|
16921
|
+
const token = env.FACTORY_GITHUB_APP_TOKEN;
|
|
16922
|
+
if (!token) throw new Error("pr:status-hud requires FACTORY_GITHUB_APP_TOKEN from actions/create-github-app-token; a workflow GITHUB_TOKEN cannot own the Factory App comment.");
|
|
16923
|
+
const result = await action({
|
|
16924
|
+
cwd: resolveCwdOption(options.cwd),
|
|
16925
|
+
headSha: shaSchema.parse(options.head),
|
|
16926
|
+
owner: options.owner,
|
|
16927
|
+
...options.plan === void 0 ? {} : { planPath: options.plan },
|
|
16928
|
+
pr: options.pr,
|
|
16929
|
+
repo: options.repo,
|
|
16930
|
+
token
|
|
16931
|
+
});
|
|
16932
|
+
output.stdout.write(`PR status HUD ${result.action}: ${result.url} (comment ${result.commentId})\n`);
|
|
16933
|
+
}));
|
|
16934
|
+
}
|
|
16935
|
+
//#endregion
|
|
16219
16936
|
//#region src/worktree-scratch-files.ts
|
|
16220
16937
|
var worktree_scratch_files_exports = /* @__PURE__ */ __exportAll({
|
|
16221
16938
|
checkWorktreeScratchFiles: () => checkWorktreeScratchFiles,
|
|
@@ -16432,60 +17149,6 @@ const assembleReviewPrompt = ({ base, changedFiles, headSha, issueFocus, kind, p
|
|
|
16432
17149
|
};
|
|
16433
17150
|
};
|
|
16434
17151
|
//#endregion
|
|
16435
|
-
//#region src/preview-lifecycle.ts
|
|
16436
|
-
/**
|
|
16437
|
-
* The declared preview stacks: impact targets that are ALSO declared required
|
|
16438
|
-
* checks — i.e. targets whose proof is produced externally, which is exactly
|
|
16439
|
-
* what a preview producer is.
|
|
16440
|
-
*
|
|
16441
|
-
* Deriving the list from the two existing declarations rather than adding a
|
|
16442
|
-
* third is what makes lifecycle and demand unable to disagree: the stacks this
|
|
16443
|
-
* plan can withhold are precisely the demands wave 2 can release, so a stack
|
|
16444
|
-
* can never be skipped while its proof stays demanded.
|
|
16445
|
-
*/
|
|
16446
|
-
const previewLifecycleTargets = (profile) => {
|
|
16447
|
-
const demanded = new Set((profile.requiredChecks ?? []).map(({ name }) => name));
|
|
16448
|
-
return (profile.impact?.targets ?? []).map(({ name }) => name).filter((name) => demanded.has(name));
|
|
16449
|
-
};
|
|
16450
|
-
/**
|
|
16451
|
-
* Plan the preview lifecycle for one candidate.
|
|
16452
|
-
*
|
|
16453
|
-
* Trust is resolved here through the SAME identity gate demand release uses
|
|
16454
|
-
* (`trustedImpactStamp`): the proof must have passed and must bind both the
|
|
16455
|
-
* candidate's headSha and its patchId. A stale, aborted, absent, or
|
|
16456
|
-
* differently-bound proof yields no stamp, and every declared stack is planned
|
|
16457
|
-
* for deploy. Total over its INPUTS — no proof, stamp, or profile can make it
|
|
16458
|
-
* throw. A malformed CALL is the deliberate exception: omitting
|
|
16459
|
-
* `vetoedTargets` is API misuse, not data doubt, and throws.
|
|
16460
|
-
*/
|
|
16461
|
-
const planPreviewLifecycle = ({ candidate, profile, proof, vetoedTargets }) => {
|
|
16462
|
-
assertVetoedTargets(vetoedTargets, "planPreviewLifecycle");
|
|
16463
|
-
const stamp = trustedImpactStamp({
|
|
16464
|
-
candidate,
|
|
16465
|
-
proof
|
|
16466
|
-
});
|
|
16467
|
-
const vetoed = new Set(vetoedTargets);
|
|
16468
|
-
const targets = previewLifecycleTargets(profile).map((name) => {
|
|
16469
|
-
const decision = impactStampScopeDecision({
|
|
16470
|
-
stamp,
|
|
16471
|
-
surface: "preview-lifecycle",
|
|
16472
|
-
targetName: name
|
|
16473
|
-
});
|
|
16474
|
-
const vetoedHere = decision.scoped && vetoed.has(name);
|
|
16475
|
-
return {
|
|
16476
|
-
basis: vetoedHere ? `veto: a current envelope bound to this candidate records stack "${name}" as FAILING, so the stamp's release is withheld (${decision.reason})` : decision.reason,
|
|
16477
|
-
disposition: decision.scoped && !vetoedHere ? "not-required" : "deploy",
|
|
16478
|
-
name
|
|
16479
|
-
};
|
|
16480
|
-
});
|
|
16481
|
-
return {
|
|
16482
|
-
deploy: targets.filter((target) => target.disposition === "deploy").map((target) => target.name),
|
|
16483
|
-
notRequired: targets.filter((target) => target.disposition === "not-required"),
|
|
16484
|
-
stampTrusted: stamp !== void 0,
|
|
16485
|
-
targets
|
|
16486
|
-
};
|
|
16487
|
-
};
|
|
16488
|
-
//#endregion
|
|
16489
17152
|
//#region src/index.ts
|
|
16490
17153
|
function createProgram(options = {}) {
|
|
16491
17154
|
const factoryCliInvocation = options.factoryCliInvocation ? FactoryCliInvocationSchema.parse(options.factoryCliInvocation) : defaultFactoryCliInvocation(import.meta.filename);
|
|
@@ -16512,6 +17175,7 @@ function createProgram(options = {}) {
|
|
|
16512
17175
|
program.addCommand(createEpicPublishStructureCommand(output));
|
|
16513
17176
|
program.addCommand(createPrReviewCommand(output, options.actions?.prReview));
|
|
16514
17177
|
program.addCommand(createPrReadyCommand(output, options.actions?.prReady));
|
|
17178
|
+
program.addCommand(createPrStatusHudCommand(output, options.actions?.prStatusHud));
|
|
16515
17179
|
program.addCommand(createPrPublishCommand(output, prPublishAction));
|
|
16516
17180
|
program.addCommand(createDemandWaiveCommand(output, options.actions?.demandWaive));
|
|
16517
17181
|
return program;
|
|
@@ -16536,4 +17200,4 @@ if (isDirectCliExecution()) try {
|
|
|
16536
17200
|
process.exit(1);
|
|
16537
17201
|
}
|
|
16538
17202
|
//#endregion
|
|
16539
|
-
export { DEFAULT_DEMAND_WAIVER_PATH, DEFAULT_FACTORY_REPOSITORY, DEFAULT_ROOT_SHARED_GLOBS, DemandWaiveRefusalError, EMPTY_TREE_OBJECT_HASH, EPIC_STRUCTURE_NODE_STATUSES, EPIC_STRUCTURE_SCHEMA_VERSION, EpicStructureValidationError, FACTORY_BASE_REF_ENV, FACTORY_CHANGED_FILES_ENV, FACTORY_CHANGED_FILES_FILE_ENV, FactoryCliInvocationSchema, FollowUpActionSchema, MERGE_FREEZE_APP_SLUG, MERGE_FREEZE_CHECK_NAME, PrPublishFollowUpError, REVIEW_FOCUS_SECTION, WorkerCheckoutGuardError, appendReviewLadderStageTraceEvent, appendWithTraceSinks, applyDemandWaiver, assembleReviewPrompt, assertWorkerCheckoutAllowed, authorizeDemandWaiver, batteryScopeSummaryLines, blockingLadderFindings, boundary_manifest_exports as boundaryManifest, boundary_review_proof_exports as boundaryReviewProof, buildEpicStructureEvent, buildEpicStructurePayload, buildEvidenceEnvelope, buildReviewGateNotRequiredTraceEvent, buildReviewLadderStageTraceEvent, comment_provenance_exports as commentProvenance, createLocalJsonlTraceSink, createProgram, createVerificationRunContext, doctorProjectProfile, epicStructureEventId, evaluateReviewLadder, evidenceEnvelopeFilename, findingKey, followUpFromArgv, impactStampScopeDecision, inferFixedInThreadDispositions, isProductionHqUrl, loadProjectProfile, normalizeIssueComments, openLadderFindings, planPreviewLifecycle, planPublishFollowUp, planVerificationBattery, readiness_evaluation_exports as prReadinessEvaluation, external_evidence_exports as prReadinessExternalEvidence, pr_body_renderer_exports as prReadinessPrBodyRenderer, proof_identity_exports as prReadinessProofIdentity, review_proof_exports as prReadinessReviewProof, status_check_rollup_exports as prReadinessStatusChecks, verification_proof_exports as prReadinessVerificationProof, previewLifecycleTargets, publishEpicStructure, readDemandWaivers, readPrReadyProof, readPrReviewProof, resolveFactoryRepository, resolveFindingBlocking, resolveReviewFindingCategory, resolveReviewFindingSeverity, resolveReviewLadderPolicy, resolveTraceWriteSinks, reviewCycleStateFor, reviewFocusFromIssueBody, reviewPromptSectionSchema, reviewPromptSectionsSchema, run, runBoundaryCheck, runDemandWaive, runEvidenceEmit, runPrPublish, runPrReady, runPrReview, runPrVerify, scanFactoryTraceDiagnostics, scanFactoryTraceEvents, selectWaiversForCandidate, staleRepeatLadderFindings, toFactoryTraceEnvelope, tryAppendReviewGateNotRequiredTraceEvent, tryAppendReviewLadderStageTraceEvent, validateBoundaryCheckProof, validateDagDocument, validateDemandWaiverStore, validateFactoryTraceEvent, validateMergeFreezeState, validatePrReadyProof, validatePrReviewProof, validatePrVerifyProof, waivedDemandNotice, waivedDemandSchema, worktree_scratch_files_exports as worktreeScratchFiles };
|
|
17203
|
+
export { DEFAULT_DEMAND_WAIVER_PATH, DEFAULT_FACTORY_REPOSITORY, DEFAULT_ROOT_SHARED_GLOBS, DemandWaiveRefusalError, EMPTY_TREE_OBJECT_HASH, EPIC_STRUCTURE_NODE_STATUSES, EPIC_STRUCTURE_SCHEMA_VERSION, EpicStructureValidationError, FACTORY_BASE_REF_ENV, FACTORY_CHANGED_FILES_ENV, FACTORY_CHANGED_FILES_FILE_ENV, FactoryCliInvocationSchema, FollowUpActionSchema, MERGE_FREEZE_APP_SLUG, MERGE_FREEZE_CHECK_NAME, PrPublishFollowUpError, REVIEW_FOCUS_SECTION, WorkerCheckoutGuardError, appendReviewLadderStageTraceEvent, appendWithTraceSinks, applyDemandWaiver, assembleReviewPrompt, assertFactoryPrStatusIdentity, assertWorkerCheckoutAllowed, authorizeDemandWaiver, batteryScopeSummaryLines, blockingLadderFindings, boundary_manifest_exports as boundaryManifest, boundary_review_proof_exports as boundaryReviewProof, buildEpicStructureEvent, buildEpicStructurePayload, buildEvidenceEnvelope, buildReviewGateNotRequiredTraceEvent, buildReviewLadderStageTraceEvent, comment_provenance_exports as commentProvenance, createLocalJsonlTraceSink, createProgram, createVerificationRunContext, doctorProjectProfile, epicStructureEventId, evaluateReviewLadder, evidenceEnvelopeFilename, findingKey, followUpFromArgv, impactStampScopeDecision, inferFixedInThreadDispositions, isProductionHqUrl, loadProjectProfile, normalizeIssueComments, openLadderFindings, planFactoryPrStatusHud, planPreviewLifecycle, planPublishFollowUp, planVerificationBattery, readiness_evaluation_exports as prReadinessEvaluation, external_evidence_exports as prReadinessExternalEvidence, pr_body_renderer_exports as prReadinessPrBodyRenderer, proof_identity_exports as prReadinessProofIdentity, review_proof_exports as prReadinessReviewProof, status_check_rollup_exports as prReadinessStatusChecks, verification_proof_exports as prReadinessVerificationProof, presentFactoryPrStatusHud, previewLifecycleTargets, publishEpicStructure, readDemandWaivers, readFactoryPrStatusSources, readPrReadyProof, readPrReviewProof, refreshFactoryPrStatusHud, refreshFactoryPrStatusHudSafely, renderFactoryPrStatusHud, resolveFactoryRepository, resolveFindingBlocking, resolveReviewFindingCategory, resolveReviewFindingSeverity, resolveReviewLadderPolicy, resolveTraceWriteSinks, reviewCycleStateFor, reviewFocusFromIssueBody, reviewPromptSectionSchema, reviewPromptSectionsSchema, run, runBoundaryCheck, runDemandWaive, runEvidenceEmit, runPrPublish, runPrReady, runPrReview, runPrVerify, scanFactoryTraceDiagnostics, scanFactoryTraceEvents, selectWaiversForCandidate, staleRepeatLadderFindings, toFactoryTraceEnvelope, tryAppendReviewGateNotRequiredTraceEvent, tryAppendReviewLadderStageTraceEvent, validateBoundaryCheckProof, validateDagDocument, validateDemandWaiverStore, validateFactoryTraceEvent, validateMergeFreezeState, validatePrReadyProof, validatePrReviewProof, validatePrVerifyProof, validatePreviewLifecyclePlan, waivedDemandNotice, waivedDemandSchema, worktree_scratch_files_exports as worktreeScratchFiles };
|