@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.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.18";
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$5 = z.string().regex(/^[0-9a-f]{7,40}$/u, { message: "must be a 7-40 char lowercase hex git SHA" });
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$5.optional(),
296
+ headSha: shaSchema$6.optional(),
297
297
  issue: z.number().int().positive().optional(),
298
- mergedSha: shaSchema$5.optional(),
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$4 = z.string().regex(/^[0-9a-f]{40}$/u);
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$4,
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/commands/options.ts
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$3 = z.string().regex(/^[0-9a-f]{7,40}$/u, { message: "must be a 7-40 char lowercase hex git SHA" });
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$3,
8679
- mergeBaseSha: shaSchema$3,
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$3,
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) => mintInstallationToken({
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 githubJson(request, `https://api.github.com/repos/${input.owner}/${input.repo}/check-runs`, {
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 githubJson(request, `https://api.github.com/repos/${input.owner}/${input.repo}/commits/${input.sha}/check-runs?${query.toString()}`, {
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$2 = z.string().regex(/^[0-9a-f]{40}$/u);
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$2.safeParse(headSha).success) return [`demand:waive could not resolve a pushed head SHA for PR #${pr}; a waiver binds to one candidate.`];
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$1 = z.string().regex(/^[0-9a-f]{40}$/u);
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$1,
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$1,
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$1,
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$1,
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$1 })),
13269
- sha: shaSchema$1
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("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("@", "&#64;").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 };