@patronage/software-factory 1.0.0-alpha.17 → 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 +531 -342
- package/dist/index.js +845 -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()
|
|
@@ -1198,11 +1198,16 @@ const impactConfigSchema = z.object({
|
|
|
1198
1198
|
});
|
|
1199
1199
|
})
|
|
1200
1200
|
}).strict();
|
|
1201
|
+
const lifecycleContractSchema = z.object({
|
|
1202
|
+
command: z.string().min(1).describe("Package-script invocation hosted CI runs for the non-credentialed lifecycle-contract lane (for example `pnpm test:factory`). Never a direct Vitest invocation."),
|
|
1203
|
+
jobName: z.string().min(1).optional().describe("Display name for the generated GitHub Actions job. Defaults to Factory / Alchemy Contracts when omitted.")
|
|
1204
|
+
}).strict().describe("Opt-in hosted lifecycle-contract lane. Presence includes the non-credentialed job in generated Verify; absence omits it. factory-ci does not load this object; generators pass it as typed options (ADR 0021).");
|
|
1201
1205
|
const factoryProjectProfileSchema = z.object({
|
|
1202
1206
|
$schema: z.string().min(1).optional(),
|
|
1203
1207
|
extensions: z.record(z.string(), z.unknown()).optional(),
|
|
1204
1208
|
hq: hqIngestConfigSchema.optional(),
|
|
1205
1209
|
impact: impactConfigSchema.optional(),
|
|
1210
|
+
lifecycleContract: lifecycleContractSchema.optional(),
|
|
1206
1211
|
repository: z.object({
|
|
1207
1212
|
name: z.string().min(1),
|
|
1208
1213
|
owner: z.string().min(1)
|
|
@@ -3699,10 +3704,10 @@ const resolveAuthoringSessionIds = ({ override, recorded }) => {
|
|
|
3699
3704
|
//#region src/demand-waiver.ts
|
|
3700
3705
|
const DEMAND_WAIVER_SCHEMA_VERSION$1 = 1;
|
|
3701
3706
|
const DEFAULT_DEMAND_WAIVER_PATH = ".factory-memory/demand-waivers.json";
|
|
3702
|
-
const shaSchema$
|
|
3707
|
+
const shaSchema$5 = z.string().regex(/^[0-9a-f]{40}$/u);
|
|
3703
3708
|
const demandWaiverSchema = z.object({
|
|
3704
3709
|
candidate: z.object({
|
|
3705
|
-
headSha: shaSchema$
|
|
3710
|
+
headSha: shaSchema$5,
|
|
3706
3711
|
pr: z.number().int().positive()
|
|
3707
3712
|
}),
|
|
3708
3713
|
demand: waiverDemandKeySchema,
|
|
@@ -5984,7 +5989,7 @@ function withGateTiming(spec, action) {
|
|
|
5984
5989
|
};
|
|
5985
5990
|
}
|
|
5986
5991
|
//#endregion
|
|
5987
|
-
//#region src/
|
|
5992
|
+
//#region src/command-options.ts
|
|
5988
5993
|
function positiveInteger$1(name) {
|
|
5989
5994
|
return (value) => {
|
|
5990
5995
|
const parsed = Number(value);
|
|
@@ -8533,7 +8538,7 @@ var CandidateIdentityError = class extends Error {
|
|
|
8533
8538
|
this.name = "CandidateIdentityError";
|
|
8534
8539
|
}
|
|
8535
8540
|
};
|
|
8536
|
-
const OBJECT_ID = /^[0-9a-f]{40}$/u;
|
|
8541
|
+
const OBJECT_ID$1 = /^[0-9a-f]{40}$/u;
|
|
8537
8542
|
/**
|
|
8538
8543
|
* Private patch-id primitive for this proof-identity owner. It stays local so
|
|
8539
8544
|
* the ownership boundary is structural rather than an approximation of which
|
|
@@ -8565,7 +8570,7 @@ function runStablePatchId(cwd, base) {
|
|
|
8565
8570
|
return id;
|
|
8566
8571
|
}
|
|
8567
8572
|
function assertObjectId(field, value) {
|
|
8568
|
-
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.`);
|
|
8569
8574
|
return value;
|
|
8570
8575
|
}
|
|
8571
8576
|
/**
|
|
@@ -8665,16 +8670,16 @@ var external_evidence_exports = /* @__PURE__ */ __exportAll({
|
|
|
8665
8670
|
requiredCheckScopeSchema: () => requiredCheckScopeSchema$1,
|
|
8666
8671
|
resolveAuthoringSessionIds: () => resolveAuthoringSessionIds
|
|
8667
8672
|
});
|
|
8668
|
-
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" });
|
|
8669
8674
|
const evidenceEnvelopeSchema = z.object({
|
|
8670
8675
|
check: z.string().min(1),
|
|
8671
8676
|
checkType: z.enum(EVIDENCE_CHECK_TYPES$1),
|
|
8672
8677
|
findingsPointer: z.string().min(1).optional(),
|
|
8673
|
-
headSha: shaSchema$
|
|
8674
|
-
mergeBaseSha: shaSchema$
|
|
8678
|
+
headSha: shaSchema$4,
|
|
8679
|
+
mergeBaseSha: shaSchema$4,
|
|
8675
8680
|
model: z.string().min(1).optional(),
|
|
8676
8681
|
outcome: z.enum(["pass", "fail"]),
|
|
8677
|
-
patchId: shaSchema$
|
|
8682
|
+
patchId: shaSchema$4,
|
|
8678
8683
|
policyVersion: z.string().min(1).optional(),
|
|
8679
8684
|
producer: z.string().min(1),
|
|
8680
8685
|
requestId: z.string().min(1).optional(),
|
|
@@ -9081,6 +9086,124 @@ function generationOrderMs(value) {
|
|
|
9081
9086
|
return Number.isFinite(parsed) ? parsed : Number.NEGATIVE_INFINITY;
|
|
9082
9087
|
}
|
|
9083
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
|
|
9084
9207
|
//#region src/pr-verify-mode.ts
|
|
9085
9208
|
const RESOLVED_PR_VERIFY_MODES = new Set([
|
|
9086
9209
|
"docs-only",
|
|
@@ -9142,64 +9265,6 @@ const prVerifyCheckPayloadFor = (proof) => {
|
|
|
9142
9265
|
};
|
|
9143
9266
|
const renderPrVerifyCheckPayloadText = (payload) => `\`\`\`json\n${JSON.stringify(payload, null, 2)}\n\`\`\``;
|
|
9144
9267
|
//#endregion
|
|
9145
|
-
//#region src/pr-verify-status.ts
|
|
9146
|
-
const PR_VERIFY_STATUS_CONTEXT = "patronage-factory/pr-verify";
|
|
9147
|
-
const GITHUB_PUBLISH_TIMEOUT_MS = 5e3;
|
|
9148
|
-
/**
|
|
9149
|
-
* Fire a best-effort async operation without blocking the caller and without
|
|
9150
|
-
* ever surfacing an unhandled rejection. Every check-run/commit-status
|
|
9151
|
-
* publisher in this module and github-check-runs.ts is a fire-and-forget
|
|
9152
|
-
* sink whose own failure (including its diagnostic write) must never affect
|
|
9153
|
-
* the gate result; this is the single detach wrapper they share instead of
|
|
9154
|
-
* each redeclaring the same try/catch-and-void shape.
|
|
9155
|
-
*/
|
|
9156
|
-
function runDetachedBestEffort(work) {
|
|
9157
|
-
const safe = async () => {
|
|
9158
|
-
try {
|
|
9159
|
-
await work();
|
|
9160
|
-
} catch {}
|
|
9161
|
-
};
|
|
9162
|
-
safe();
|
|
9163
|
-
}
|
|
9164
|
-
async function runGhStatus(args, cwd) {
|
|
9165
|
-
const { execFile } = await import("node:child_process");
|
|
9166
|
-
await promisify(execFile)("gh", args, {
|
|
9167
|
-
cwd,
|
|
9168
|
-
timeout: GITHUB_PUBLISH_TIMEOUT_MS
|
|
9169
|
-
});
|
|
9170
|
-
}
|
|
9171
|
-
function createGhCommitStatusPoster(output, run = runGhStatus) {
|
|
9172
|
-
return (input) => {
|
|
9173
|
-
const { context = PR_VERIFY_STATUS_CONTEXT, cwd, description, owner, repo, sha, state, suppressFailureDiagnostic = false, targetUrl } = input;
|
|
9174
|
-
const args = [
|
|
9175
|
-
"api",
|
|
9176
|
-
"--method",
|
|
9177
|
-
"POST",
|
|
9178
|
-
`/repos/${owner}/${repo}/statuses/${sha}`,
|
|
9179
|
-
"-f",
|
|
9180
|
-
`state=${state}`,
|
|
9181
|
-
"-f",
|
|
9182
|
-
`context=${context}`,
|
|
9183
|
-
"-f",
|
|
9184
|
-
`description=${description}`,
|
|
9185
|
-
"-f",
|
|
9186
|
-
`target_url=${targetUrl}`
|
|
9187
|
-
];
|
|
9188
|
-
const publish = async () => {
|
|
9189
|
-
await Promise.resolve();
|
|
9190
|
-
try {
|
|
9191
|
-
await run(args, cwd);
|
|
9192
|
-
if (state === "success") output.stdout.write(context === "patronage-factory/pr-verify" ? `pr:verify posted ${PR_VERIFY_STATUS_CONTEXT}=success\n` : `${context} posted success\n`);
|
|
9193
|
-
} catch (error) {
|
|
9194
|
-
if (suppressFailureDiagnostic) return;
|
|
9195
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
9196
|
-
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`);
|
|
9197
|
-
}
|
|
9198
|
-
};
|
|
9199
|
-
runDetachedBestEffort(publish);
|
|
9200
|
-
};
|
|
9201
|
-
}
|
|
9202
|
-
//#endregion
|
|
9203
9268
|
//#region src/github-check-runs.ts
|
|
9204
9269
|
const FACTORY_CHECK_NAMES = {
|
|
9205
9270
|
"pr-ready": "patronage-factory/pr-ready",
|
|
@@ -9293,19 +9358,6 @@ const prePushVerifyBindingNotice = (sha) => `${FACTORY_CHECK_NAMES["pr-verify"]}
|
|
|
9293
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.
|
|
9294
9359
|
Re-verify only if the tree changes: a new commit, an amend, or a rebase.
|
|
9295
9360
|
`;
|
|
9296
|
-
async function githubJson(request, url, init, timeoutMs = GITHUB_PUBLISH_TIMEOUT_MS) {
|
|
9297
|
-
const response = await request(url, {
|
|
9298
|
-
...init,
|
|
9299
|
-
headers: {
|
|
9300
|
-
Accept: "application/vnd.github+json",
|
|
9301
|
-
"X-GitHub-Api-Version": "2022-11-28",
|
|
9302
|
-
...init.headers
|
|
9303
|
-
},
|
|
9304
|
-
signal: init.signal ?? AbortSignal.timeout(timeoutMs)
|
|
9305
|
-
});
|
|
9306
|
-
if (!response.ok) throw new GitHubApiError(response.status, response.statusText);
|
|
9307
|
-
return await response.json();
|
|
9308
|
-
}
|
|
9309
9361
|
/**
|
|
9310
9362
|
* Mint an installation token for the Patronage Factory App.
|
|
9311
9363
|
*
|
|
@@ -9315,15 +9367,7 @@ async function githubJson(request, url, init, timeoutMs = GITHUB_PUBLISH_TIMEOUT
|
|
|
9315
9367
|
* where the credentials come from (`user-config`), and the publish timeout the
|
|
9316
9368
|
* rest of these calls are bound by. Nothing is cached, as before.
|
|
9317
9369
|
*/
|
|
9318
|
-
const installationToken = (repository, config, request, now, timeoutMs = GITHUB_PUBLISH_TIMEOUT_MS) =>
|
|
9319
|
-
credentials: config,
|
|
9320
|
-
owner: repository.owner,
|
|
9321
|
-
repo: repository.repo
|
|
9322
|
-
}, {
|
|
9323
|
-
fetch: request,
|
|
9324
|
-
now: () => now,
|
|
9325
|
-
timeoutMs
|
|
9326
|
-
});
|
|
9370
|
+
const installationToken = (repository, config, request, now, timeoutMs = GITHUB_PUBLISH_TIMEOUT_MS) => mintFactoryInstallationToken(repository, config, request, now, timeoutMs);
|
|
9327
9371
|
function verifyOutput(proof) {
|
|
9328
9372
|
const value = proof;
|
|
9329
9373
|
const commands = value.executedCommands?.length ?? 0;
|
|
@@ -9412,7 +9456,7 @@ async function postFactoryCheckRun({ config, deadlineMs, dependencies, detailsUr
|
|
|
9412
9456
|
const timeoutMs = dependencies.timeoutMs ?? 5e3;
|
|
9413
9457
|
try {
|
|
9414
9458
|
const token = await installationToken(input, config, request, (dependencies.now ?? Date.now)(), timeoutMs);
|
|
9415
|
-
return await
|
|
9459
|
+
return await requestGitHubJson(request, `https://api.github.com/repos/${input.owner}/${input.repo}/check-runs`, {
|
|
9416
9460
|
body: JSON.stringify(checkRunRequestBody(input, detailsUrl)),
|
|
9417
9461
|
headers: {
|
|
9418
9462
|
Authorization: `Bearer ${token}`,
|
|
@@ -9509,7 +9553,7 @@ async function servedFactoryCheckRun(input, config, dependencies) {
|
|
|
9509
9553
|
filter: "all",
|
|
9510
9554
|
per_page: "100"
|
|
9511
9555
|
});
|
|
9512
|
-
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()}`, {
|
|
9513
9557
|
headers: { Authorization: `Bearer ${token}` },
|
|
9514
9558
|
method: "GET"
|
|
9515
9559
|
}, timeoutMs);
|
|
@@ -10183,7 +10227,7 @@ const assertCleanInstallResolution = (repoRoot, dependencies = {}) => {
|
|
|
10183
10227
|
//#region src/pr-verify.ts
|
|
10184
10228
|
const PR_VERIFY_SCHEMA_VERSION = 4;
|
|
10185
10229
|
const DEFAULT_PR_VERIFY_PROOF_PATH = ".factory-memory/pr-verify.json";
|
|
10186
|
-
const PR_VERIFY_PROOF_DESCRIPTOR = {
|
|
10230
|
+
const PR_VERIFY_PROOF_DESCRIPTOR$1 = {
|
|
10187
10231
|
defaultPath: DEFAULT_PR_VERIFY_PROOF_PATH,
|
|
10188
10232
|
label: "pr:verify proof",
|
|
10189
10233
|
parse: validatePrVerifyProof,
|
|
@@ -10225,7 +10269,7 @@ function defaultPrVerifyGit() {
|
|
|
10225
10269
|
};
|
|
10226
10270
|
}
|
|
10227
10271
|
function readPreviousPrVerifyProof(cwd, filePath) {
|
|
10228
|
-
return readOptionalProofFor(PR_VERIFY_PROOF_DESCRIPTOR, cwd, filePath);
|
|
10272
|
+
return readOptionalProofFor(PR_VERIFY_PROOF_DESCRIPTOR$1, cwd, filePath);
|
|
10229
10273
|
}
|
|
10230
10274
|
function collectBaselineFullProofs({ appendPrevious, previousProof, seedFilter }) {
|
|
10231
10275
|
if (!previousProof) return;
|
|
@@ -10569,7 +10613,7 @@ function runPrVerify(args, dependencies = {}) {
|
|
|
10569
10613
|
//#endregion
|
|
10570
10614
|
//#region src/demand-waive.ts
|
|
10571
10615
|
const DEMAND_WAIVER_SCHEMA_VERSION = 1;
|
|
10572
|
-
const shaSchema$
|
|
10616
|
+
const shaSchema$3 = z.string().regex(/^[0-9a-f]{40}$/u);
|
|
10573
10617
|
const demandWaiverStorePath = (cwd, filePath) => path.resolve(cwd, filePath ?? ".factory-memory/demand-waivers.json");
|
|
10574
10618
|
/** Every waiver recorded in this checkout; an absent or unreadable store is
|
|
10575
10619
|
* no waivers, so a damaged file can only ever block, never permit. */
|
|
@@ -10619,7 +10663,7 @@ const requestRefusals = ({ demand, parsedDemand, rationale }) => [...parsedDeman
|
|
|
10619
10663
|
* the authoring session read below describes a different candidate.
|
|
10620
10664
|
*/
|
|
10621
10665
|
const candidateBindingRefusals = ({ headSha, pr, verifyProof }) => {
|
|
10622
|
-
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.`];
|
|
10623
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.`] : [];
|
|
10624
10668
|
};
|
|
10625
10669
|
/**
|
|
@@ -10645,7 +10689,7 @@ const runDemandWaive = (args, dependencies = {}) => {
|
|
|
10645
10689
|
pr: args.pr,
|
|
10646
10690
|
repo: repository.name
|
|
10647
10691
|
});
|
|
10648
|
-
const verifyProof = readOptionalProofFor(PR_VERIFY_PROOF_DESCRIPTOR, cwd);
|
|
10692
|
+
const verifyProof = readOptionalProofFor(PR_VERIFY_PROOF_DESCRIPTOR$1, cwd);
|
|
10649
10693
|
refusals.push(...candidateBindingRefusals({
|
|
10650
10694
|
headSha,
|
|
10651
10695
|
pr: args.pr,
|
|
@@ -13184,13 +13228,13 @@ const HOSTED_VERIFY_CHECK_NAME$1 = "verify";
|
|
|
13184
13228
|
const GITHUB_ACTIONS_APP_ID = 15368;
|
|
13185
13229
|
const GITHUB_ACTIONS_APP_SLUG = "github-actions";
|
|
13186
13230
|
const MERGE_FREEZE_SCHEMA_VERSION = 1;
|
|
13187
|
-
const CHECK_RUNS_PER_PAGE = 100;
|
|
13188
|
-
const CHECK_RUN_PAGE_LIMIT = 10;
|
|
13189
|
-
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);
|
|
13190
13234
|
const activeMergeFreezeStateSchema = z.object({
|
|
13191
13235
|
active: z.literal(true),
|
|
13192
13236
|
generationId: z.number().int().positive(),
|
|
13193
|
-
headSha: shaSchema$
|
|
13237
|
+
headSha: shaSchema$2,
|
|
13194
13238
|
outcome: z.enum(["active", "stale"]),
|
|
13195
13239
|
reason: z.string().min(1),
|
|
13196
13240
|
recordedAt: z.iso.datetime(),
|
|
@@ -13200,7 +13244,7 @@ const inactiveMergeFreezeStateSchema = z.object({
|
|
|
13200
13244
|
active: z.literal(false),
|
|
13201
13245
|
clearRationale: z.string().min(1).optional(),
|
|
13202
13246
|
generationId: z.number().int().positive(),
|
|
13203
|
-
headSha: shaSchema$
|
|
13247
|
+
headSha: shaSchema$2,
|
|
13204
13248
|
outcome: z.literal("inactive"),
|
|
13205
13249
|
reason: z.string().min(1),
|
|
13206
13250
|
recordedAt: z.iso.datetime(),
|
|
@@ -13224,7 +13268,7 @@ const mergeFreezeCheckRunListItemSchema = z.object({
|
|
|
13224
13268
|
"neutral",
|
|
13225
13269
|
"success"
|
|
13226
13270
|
]).nullable().optional(),
|
|
13227
|
-
head_sha: shaSchema$
|
|
13271
|
+
head_sha: shaSchema$2,
|
|
13228
13272
|
id: z.number().int().positive(),
|
|
13229
13273
|
name: z.literal(MERGE_FREEZE_CHECK_NAME),
|
|
13230
13274
|
output: z.object({ text: z.string().nullable().optional() }).optional(),
|
|
@@ -13249,7 +13293,7 @@ const hostedVerifyCheckRunSchema = z.object({
|
|
|
13249
13293
|
id: z.literal(GITHUB_ACTIONS_APP_ID),
|
|
13250
13294
|
slug: z.literal(GITHUB_ACTIONS_APP_SLUG)
|
|
13251
13295
|
}),
|
|
13252
|
-
head_sha: shaSchema$
|
|
13296
|
+
head_sha: shaSchema$2,
|
|
13253
13297
|
id: z.number().int().positive(),
|
|
13254
13298
|
name: z.literal(HOSTED_VERIFY_CHECK_NAME$1),
|
|
13255
13299
|
started_at: z.iso.datetime().nullable(),
|
|
@@ -13260,8 +13304,8 @@ const hostedVerifyCheckRunSchema = z.object({
|
|
|
13260
13304
|
])
|
|
13261
13305
|
});
|
|
13262
13306
|
const commitParentsSchema = z.object({
|
|
13263
|
-
parents: z.array(z.object({ sha: shaSchema$
|
|
13264
|
-
sha: shaSchema$
|
|
13307
|
+
parents: z.array(z.object({ sha: shaSchema$2 })),
|
|
13308
|
+
sha: shaSchema$2
|
|
13265
13309
|
});
|
|
13266
13310
|
function selectListedMergeFreezeGeneration(listed, expectedHeadSha) {
|
|
13267
13311
|
const runs = z.object({ check_runs: z.array(z.unknown()) }).parse(listed).check_runs.map((run) => mergeFreezeCheckRunListItemSchema.parse(run));
|
|
@@ -13395,11 +13439,11 @@ function createGitHubCheckRunMergeFreezeApi(dependencies = {}) {
|
|
|
13395
13439
|
return {
|
|
13396
13440
|
list({ cwd, headSha, name, repository }) {
|
|
13397
13441
|
const checkRuns = [];
|
|
13398
|
-
for (let page = 1; page <= CHECK_RUN_PAGE_LIMIT; page += 1) {
|
|
13399
|
-
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));
|
|
13400
13444
|
checkRuns.push(...response.check_runs);
|
|
13401
|
-
if (response.check_runs.length < CHECK_RUNS_PER_PAGE) break;
|
|
13402
|
-
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.`);
|
|
13403
13447
|
}
|
|
13404
13448
|
return { check_runs: checkRuns };
|
|
13405
13449
|
},
|
|
@@ -13495,7 +13539,7 @@ const selectReviewProof = ({ reviewProof }) => {
|
|
|
13495
13539
|
if (isExplicitReviewFailure(reviewProof) || isUntypedReviewProof(reviewProof)) return {};
|
|
13496
13540
|
return hasReviewProof(reviewProof) ? reviewProof : {};
|
|
13497
13541
|
};
|
|
13498
|
-
const shaSchema = z.string().regex(/^[0-9a-f]{40}$/u);
|
|
13542
|
+
const shaSchema$1 = z.string().regex(/^[0-9a-f]{40}$/u);
|
|
13499
13543
|
const patchIdSchema = z.string().regex(/^[0-9a-f]{40,64}$/u);
|
|
13500
13544
|
const reviewCycleStateSchema = z.object({
|
|
13501
13545
|
autoBlockingFindings: z.number().int().nonnegative(),
|
|
@@ -13550,7 +13594,7 @@ const readinessRepairSchema = z.object({
|
|
|
13550
13594
|
command: z.string().min(1)
|
|
13551
13595
|
});
|
|
13552
13596
|
const managedReadinessLedgerSchema = z.object({
|
|
13553
|
-
baseSha: shaSchema,
|
|
13597
|
+
baseSha: shaSchema$1,
|
|
13554
13598
|
blockingReasons: z.array(z.string()),
|
|
13555
13599
|
classification: z.enum([
|
|
13556
13600
|
"docs/process-only",
|
|
@@ -13590,13 +13634,13 @@ const managedReadinessLedgerSchema = z.object({
|
|
|
13590
13634
|
* branded `patronage-factory/pr-ready` check binds to — GitHub's required
|
|
13591
13635
|
* checks compare a new head's checks against exactly this SHA (#477).
|
|
13592
13636
|
*/
|
|
13593
|
-
headSha: shaSchema,
|
|
13637
|
+
headSha: shaSchema$1,
|
|
13594
13638
|
/**
|
|
13595
13639
|
* The merge base used to evaluate tree-scoped external evidence. Optional
|
|
13596
13640
|
* only for backwards-compatible parsing of readiness ledgers written before
|
|
13597
13641
|
* #732; a committed verify-type demand cannot pass the merge gate without it.
|
|
13598
13642
|
*/
|
|
13599
|
-
mergeBaseSha: shaSchema.optional(),
|
|
13643
|
+
mergeBaseSha: shaSchema$1.optional(),
|
|
13600
13644
|
patchId: patchIdSchema,
|
|
13601
13645
|
pr: z.number().int().positive(),
|
|
13602
13646
|
repairs: z.array(readinessRepairSchema).default([]),
|
|
@@ -14312,6 +14356,605 @@ const evaluateReadiness = (input) => {
|
|
|
14312
14356
|
};
|
|
14313
14357
|
};
|
|
14314
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
|
|
14315
14958
|
//#region src/pr-ready.ts
|
|
14316
14959
|
const CHECK_CONCLUSION_BY_READINESS_STATUS = {
|
|
14317
14960
|
blocked: "failure",
|
|
@@ -14583,6 +15226,20 @@ async function runPrReady(args, dependencies = {}) {
|
|
|
14583
15226
|
sleep: dependencies.sleep
|
|
14584
15227
|
});
|
|
14585
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
|
+
};
|
|
14586
15243
|
try {
|
|
14587
15244
|
const authoritativeBaseSha = authoritativePrBase({
|
|
14588
15245
|
args,
|
|
@@ -14594,7 +15251,9 @@ async function runPrReady(args, dependencies = {}) {
|
|
|
14594
15251
|
cwd,
|
|
14595
15252
|
git
|
|
14596
15253
|
});
|
|
14597
|
-
const { headSha: localHeadSha, patchId } = candidateIdentityForBase(authoritativeBaseSha);
|
|
15254
|
+
const { headSha: localHeadSha, mergeBaseSha: resolvedMergeBaseSha, patchId } = candidateIdentityForBase(authoritativeBaseSha);
|
|
15255
|
+
hudMergeBaseSha = resolvedMergeBaseSha;
|
|
15256
|
+
hudPatchId = patchId;
|
|
14598
15257
|
const files = git.changedFiles(cwd, authoritativeBaseSha, pr.headRefOid);
|
|
14599
15258
|
const committedPolicy = policyProfileAtCommittedHead({
|
|
14600
15259
|
cwd,
|
|
@@ -14778,6 +15437,7 @@ async function runPrReady(args, dependencies = {}) {
|
|
|
14778
15437
|
subject: { prNumber: args.pr }
|
|
14779
15438
|
}, dependencies.hq);
|
|
14780
15439
|
reportProof(proof, output, Boolean(args.json), args.report ?? true);
|
|
15440
|
+
await presentHud();
|
|
14781
15441
|
if (proof.status !== "ready" && (args.throwWhenBlocked ?? true)) throw new Error(`pr:ready blocked: ${proof.blockingReasons.join(" ")}`);
|
|
14782
15442
|
return proof;
|
|
14783
15443
|
} catch (error) {
|
|
@@ -14798,6 +15458,7 @@ async function runPrReady(args, dependencies = {}) {
|
|
|
14798
15458
|
sha: pr.headRefOid
|
|
14799
15459
|
};
|
|
14800
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();
|
|
14801
15462
|
}
|
|
14802
15463
|
throw error;
|
|
14803
15464
|
}
|
|
@@ -15207,7 +15868,7 @@ async function runPrReview(args, dependencies = {}) {
|
|
|
15207
15868
|
git
|
|
15208
15869
|
})(args.base);
|
|
15209
15870
|
const { additional: additionalOutput, canonical: output } = resolveProofOutputPaths(cwd, DEFAULT_PR_REVIEW_PROOF_PATH, args.output);
|
|
15210
|
-
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");
|
|
15211
15872
|
const writeProofCopies = (proof) => {
|
|
15212
15873
|
writeReviewProofCopies(cwd, proof, output, additionalOutput);
|
|
15213
15874
|
enqueueHqIngest({
|
|
@@ -15418,7 +16079,7 @@ function discoverOrCreatePullRequestNumber({ args, branch, cwd, gh }) {
|
|
|
15418
16079
|
if (!created[0]) throw new Error(`gh pr create completed but no PR was found for branch ${branch}.`);
|
|
15419
16080
|
return created[0].number;
|
|
15420
16081
|
}
|
|
15421
|
-
const readOptionalVerifyProof = (cwd, proofPath) => readOptionalProofFor(PR_VERIFY_PROOF_DESCRIPTOR, cwd, proofPath);
|
|
16082
|
+
const readOptionalVerifyProof = (cwd, proofPath) => readOptionalProofFor(PR_VERIFY_PROOF_DESCRIPTOR$1, cwd, proofPath);
|
|
15422
16083
|
const followUpSummaryLine = (outcome) => {
|
|
15423
16084
|
if (outcome.status === "succeeded") return `Follow-up succeeded: ${outcome.action.command} — ${outcome.reason}.`;
|
|
15424
16085
|
if (outcome.status === "skipped") return `Follow-up skipped (${outcome.action.command}): ${outcome.reason}.`;
|
|
@@ -16211,6 +16872,67 @@ function createProductionImpactCommand(output) {
|
|
|
16211
16872
|
}));
|
|
16212
16873
|
}
|
|
16213
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
|
|
16214
16936
|
//#region src/worktree-scratch-files.ts
|
|
16215
16937
|
var worktree_scratch_files_exports = /* @__PURE__ */ __exportAll({
|
|
16216
16938
|
checkWorktreeScratchFiles: () => checkWorktreeScratchFiles,
|
|
@@ -16427,60 +17149,6 @@ const assembleReviewPrompt = ({ base, changedFiles, headSha, issueFocus, kind, p
|
|
|
16427
17149
|
};
|
|
16428
17150
|
};
|
|
16429
17151
|
//#endregion
|
|
16430
|
-
//#region src/preview-lifecycle.ts
|
|
16431
|
-
/**
|
|
16432
|
-
* The declared preview stacks: impact targets that are ALSO declared required
|
|
16433
|
-
* checks — i.e. targets whose proof is produced externally, which is exactly
|
|
16434
|
-
* what a preview producer is.
|
|
16435
|
-
*
|
|
16436
|
-
* Deriving the list from the two existing declarations rather than adding a
|
|
16437
|
-
* third is what makes lifecycle and demand unable to disagree: the stacks this
|
|
16438
|
-
* plan can withhold are precisely the demands wave 2 can release, so a stack
|
|
16439
|
-
* can never be skipped while its proof stays demanded.
|
|
16440
|
-
*/
|
|
16441
|
-
const previewLifecycleTargets = (profile) => {
|
|
16442
|
-
const demanded = new Set((profile.requiredChecks ?? []).map(({ name }) => name));
|
|
16443
|
-
return (profile.impact?.targets ?? []).map(({ name }) => name).filter((name) => demanded.has(name));
|
|
16444
|
-
};
|
|
16445
|
-
/**
|
|
16446
|
-
* Plan the preview lifecycle for one candidate.
|
|
16447
|
-
*
|
|
16448
|
-
* Trust is resolved here through the SAME identity gate demand release uses
|
|
16449
|
-
* (`trustedImpactStamp`): the proof must have passed and must bind both the
|
|
16450
|
-
* candidate's headSha and its patchId. A stale, aborted, absent, or
|
|
16451
|
-
* differently-bound proof yields no stamp, and every declared stack is planned
|
|
16452
|
-
* for deploy. Total over its INPUTS — no proof, stamp, or profile can make it
|
|
16453
|
-
* throw. A malformed CALL is the deliberate exception: omitting
|
|
16454
|
-
* `vetoedTargets` is API misuse, not data doubt, and throws.
|
|
16455
|
-
*/
|
|
16456
|
-
const planPreviewLifecycle = ({ candidate, profile, proof, vetoedTargets }) => {
|
|
16457
|
-
assertVetoedTargets(vetoedTargets, "planPreviewLifecycle");
|
|
16458
|
-
const stamp = trustedImpactStamp({
|
|
16459
|
-
candidate,
|
|
16460
|
-
proof
|
|
16461
|
-
});
|
|
16462
|
-
const vetoed = new Set(vetoedTargets);
|
|
16463
|
-
const targets = previewLifecycleTargets(profile).map((name) => {
|
|
16464
|
-
const decision = impactStampScopeDecision({
|
|
16465
|
-
stamp,
|
|
16466
|
-
surface: "preview-lifecycle",
|
|
16467
|
-
targetName: name
|
|
16468
|
-
});
|
|
16469
|
-
const vetoedHere = decision.scoped && vetoed.has(name);
|
|
16470
|
-
return {
|
|
16471
|
-
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,
|
|
16472
|
-
disposition: decision.scoped && !vetoedHere ? "not-required" : "deploy",
|
|
16473
|
-
name
|
|
16474
|
-
};
|
|
16475
|
-
});
|
|
16476
|
-
return {
|
|
16477
|
-
deploy: targets.filter((target) => target.disposition === "deploy").map((target) => target.name),
|
|
16478
|
-
notRequired: targets.filter((target) => target.disposition === "not-required"),
|
|
16479
|
-
stampTrusted: stamp !== void 0,
|
|
16480
|
-
targets
|
|
16481
|
-
};
|
|
16482
|
-
};
|
|
16483
|
-
//#endregion
|
|
16484
17152
|
//#region src/index.ts
|
|
16485
17153
|
function createProgram(options = {}) {
|
|
16486
17154
|
const factoryCliInvocation = options.factoryCliInvocation ? FactoryCliInvocationSchema.parse(options.factoryCliInvocation) : defaultFactoryCliInvocation(import.meta.filename);
|
|
@@ -16507,6 +17175,7 @@ function createProgram(options = {}) {
|
|
|
16507
17175
|
program.addCommand(createEpicPublishStructureCommand(output));
|
|
16508
17176
|
program.addCommand(createPrReviewCommand(output, options.actions?.prReview));
|
|
16509
17177
|
program.addCommand(createPrReadyCommand(output, options.actions?.prReady));
|
|
17178
|
+
program.addCommand(createPrStatusHudCommand(output, options.actions?.prStatusHud));
|
|
16510
17179
|
program.addCommand(createPrPublishCommand(output, prPublishAction));
|
|
16511
17180
|
program.addCommand(createDemandWaiveCommand(output, options.actions?.demandWaive));
|
|
16512
17181
|
return program;
|
|
@@ -16531,4 +17200,4 @@ if (isDirectCliExecution()) try {
|
|
|
16531
17200
|
process.exit(1);
|
|
16532
17201
|
}
|
|
16533
17202
|
//#endregion
|
|
16534
|
-
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 };
|