@patronage/software-factory 0.20.0 → 0.25.0

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 CHANGED
@@ -1,10 +1,10 @@
1
1
  import { existsSync, readFileSync, rmSync } from "node:fs";
2
2
  import { Command } from "commander";
3
3
  import { z } from "zod";
4
+ import { readFile } from "node:fs/promises";
4
5
 
5
6
  //#region src/github-issue-comments.d.ts
6
7
  interface GithubIssueCommentApi {
7
- author_association?: string;
8
8
  body: string;
9
9
  created_at: string;
10
10
  html_url: string;
@@ -18,7 +18,6 @@ interface GithubIssueComment {
18
18
  author: {
19
19
  login?: string;
20
20
  };
21
- authorAssociation?: string;
22
21
  body: string;
23
22
  createdAt: string;
24
23
  id: string;
@@ -168,6 +167,299 @@ interface HqIngestDependencies {
168
167
  timeoutMs?: number;
169
168
  transportTimeoutMs?: number;
170
169
  }
170
+ /** The repository whose undelivered evidence a spool holds. */
171
+ interface HqSpoolRepository {
172
+ owner: string;
173
+ repo: string;
174
+ }
175
+ /**
176
+ * What became of one spooled event. `delivered` and `duplicate` both mean HQ
177
+ * holds it (dedup is by content-addressed eventId), so the entry is removed;
178
+ * `rejected` and `unreachable` leave it spooled.
179
+ */
180
+ interface HqSpoolEntryOutcome {
181
+ detail?: string;
182
+ eventId: string;
183
+ kind: string;
184
+ spool: string;
185
+ /**
186
+ * `migrated` belongs to the legacy JSONL journal only: the row was moved
187
+ * into the current spool without being delivered. The spool pass that runs
188
+ * after it in the same drain supersedes that line with a real outcome when
189
+ * it gets to the row; a `migrated` line that survives the run means the row
190
+ * is still waiting.
191
+ *
192
+ * `undeliverable` is the one terminal verdict (#445). Every other status
193
+ * describes a moment: HQ was unreachable, HQ refused this content today, the
194
+ * row moved. Retrying is meaningful for all of them. A wrong-origin entry is
195
+ * different in kind — it is refused here, from the entry's own bytes, with no
196
+ * request made, and the same bytes produce the same verdict on every future
197
+ * run. Leaving it spooled asks the operator to retry something that provably
198
+ * cannot succeed, and the count it inflates is the one doctor goes red on.
199
+ */
200
+ status: "delivered" | "duplicate" | "migrated" | "rejected" | "undeliverable" | "unreachable";
201
+ }
202
+ interface HqSpoolFlushInput {
203
+ clientId: string;
204
+ clientSecret: string;
205
+ /** Repository root whose legacy `.factory-memory` spool is also drained. */
206
+ cwd: string;
207
+ /** The profile's HQ origin; entries recorded against another are refused. */
208
+ endpoint: string;
209
+ /** Operator-named spool directories; replaces the default two locations. */
210
+ explicitDirectories?: string[];
211
+ repository: HqSpoolRepository;
212
+ }
213
+ interface HqSpoolFlushSummary {
214
+ delivered: number;
215
+ duplicate: number;
216
+ /**
217
+ * The drain did not finish: the budget elapsed, or events beyond the
218
+ * rejected ones are still spooled. Never report an incomplete pass as a
219
+ * clean drain — a recovery run reads this to know whether to run again.
220
+ */
221
+ incomplete: boolean;
222
+ outcomes: HqSpoolEntryOutcome[];
223
+ rejected: number;
224
+ /** Events still in the drained spools when the pass ended. */
225
+ remaining: number;
226
+ /** Locations that were read; a missing one is simply absent from the list. */
227
+ spools: string[];
228
+ /**
229
+ * Events dispositioned as permanently undeliverable this pass (#445). They
230
+ * are gone from `remaining` — that is the point — so this is the only place
231
+ * the run says they existed.
232
+ */
233
+ undeliverable: number;
234
+ unreachable: number;
235
+ /** Files retained by a transport failure, counted per file. */
236
+ unreachableFiles: number;
237
+ }
238
+ interface HqSpoolWorkCount {
239
+ /**
240
+ * The earliest moment learned across pending spool files (their own write
241
+ * time) and legacy journal rows (their own `failedAt`). Absent only when
242
+ * `pending` is `0`, or when every timestamp source was unreadable within
243
+ * budget — an estimate for doctor's remediation message (#394), never a
244
+ * precise audit trail.
245
+ */
246
+ oldestQueuedAt?: string;
247
+ /** Spooled events and replayable journals waiting in the locations below. */
248
+ pending: number;
249
+ /** Locations that exist and hold spooled work. */
250
+ spools: string[];
251
+ /**
252
+ * A location existed but could not be listed. The count above saw nothing
253
+ * there, so a caller deciding whether the drain is worth doing must treat a
254
+ * non-zero value as "work may be waiting" — never as an empty spool.
255
+ */
256
+ unlistable: number;
257
+ }
258
+ /**
259
+ * Counts spooled work for a repository without draining it or touching a
260
+ * credential (#414).
261
+ *
262
+ * `hq:flush` used to resolve the HQ Access token before it ever looked at the
263
+ * spool, so a lane with nothing to send still paid a secret-manager round trip
264
+ * — and still failed, opaquely, in a sandbox that has no keychain access. The
265
+ * same locations `flushHqSpool` drains are inspected here, read-only: no
266
+ * directory is created, nothing is secured, and nothing is delivered.
267
+ */
268
+ declare function countHqSpoolWork(input: Pick<HqSpoolFlushInput, "cwd" | "explicitDirectories" | "repository">, dependencies?: {
269
+ budgetMs?: number;
270
+ env?: NodeJS.ProcessEnv;
271
+ }): Promise<HqSpoolWorkCount>;
272
+ /** A spool under an earlier key for this repository, not the current one. */
273
+ interface HqSpoolOrphan {
274
+ /** The `hq-retry-spool` directory itself, ready to pass to `--dir`. */
275
+ directory: string;
276
+ oldestQueuedAt?: string;
277
+ /** Spooled events and journal rows waiting there. */
278
+ pending: number;
279
+ /**
280
+ * The location exists but could not be listed. As everywhere else in this
281
+ * inspection, unknown counts as work: a swept location nobody could read is
282
+ * reported, never quietly dropped as empty.
283
+ */
284
+ unlistable: number;
285
+ }
286
+ interface HqSpoolOrphanSweep {
287
+ /** Only locations holding work; an empty orphan spool is not a finding. */
288
+ orphans: HqSpoolOrphan[];
289
+ root: string;
290
+ }
291
+ /**
292
+ * Reports spooled evidence sitting under an earlier key for *this* repository
293
+ * (#420).
294
+ *
295
+ * The spool is keyed by owner and repo, so a change to the segment encoding
296
+ * itself — which happened during #390's own development — moves the address
297
+ * without moving the evidence. Both readers of the spool resolve exactly one
298
+ * key, so the events under the old one become invisible: the drain reports
299
+ * success, doctor reports empty, and three real proofs sat unread until an
300
+ * attended recovery enumerated the tree by hand.
301
+ *
302
+ * This sweep only reports. Draining another key's events is a decision this
303
+ * does not make — the operator gets the location and the count, and
304
+ * `hq:flush --dir` remains the recovery path.
305
+ *
306
+ * **Why candidate keys and not a walk of the root (#446).** The root is shared
307
+ * by every factory repository on the machine, so enumerating it and calling
308
+ * everything that is not the current key an orphan describes another
309
+ * repository's ordinary, current, correct spool exactly as well as it describes
310
+ * this repository's obsolete one. That made doctor red in one checkout because
311
+ * a different repository had pending work, and told the operator to drain it —
312
+ * confidently prescribing the wrong action. Nothing on disk distinguishes the
313
+ * two cases: an unrecognised key carries no statement about who wrote it.
314
+ *
315
+ * So discovery is scoped to the keys *this* repository could plausibly have
316
+ * produced — the current scheme plus the earlier ones listed in
317
+ * `hqSpoolCandidateKeys` — and a key outside that set is never this
318
+ * repository's business. The #420 incident is inside it: the joined
319
+ * single-segment key is one of the candidates.
320
+ *
321
+ * **And a marked walk beside them (#447).** Derivation's other blind spot is a
322
+ * key whose *encoding* this checkout no longer produces but whose owner and
323
+ * repo are unchanged — the retired non-injective era being the live example.
324
+ * That era cannot be derived safely, because a candidate built from it can
325
+ * equal a different repository's current key, so #446 dropped it rather than
326
+ * risk the cross-repository claim again.
327
+ *
328
+ * `SPOOL_REPOSITORY_MARKER` supplies the proof that derivation could not. Every
329
+ * enqueue stamps its spool with the repository writing it, so the root can be
330
+ * walked again: a directory whose marker names *this* repository is this
331
+ * repository's, whatever key encoding it sits under, and a directory whose
332
+ * marker names another repository is never reported here. That is the
333
+ * discriminator #446 correctly said did not exist — it exists now because
334
+ * something writes it down.
335
+ *
336
+ * The two discoveries are complements, not alternatives. The walk sees only
337
+ * what was stamped; directories written before this shipped have no marker, and
338
+ * candidate keys still find those. An unmarked directory is still never
339
+ * reported, because it still carries no statement about who wrote it.
340
+ *
341
+ * **What this does NOT close, despite being the marker's obvious use: renames
342
+ * and owner changes.** A marker records the identity that was current when the
343
+ * directory was written, so after `patronage/old` becomes `patronage/new` the
344
+ * stranded directory is stamped `patronage/old` — and matching is equality
345
+ * against the checkout's *present* identity, which rejects it. Making that work
346
+ * needs an identifier that survives a rename, which neither the profile nor the
347
+ * marker carries today; accepting a non-matching marker instead would be
348
+ * guessing, which is the #446 defect wearing a new hat. #447 stays open for it.
349
+ *
350
+ * Also outside the sweep, by construction: evidence under a *different state
351
+ * root*, if `XDG_STATE_HOME` moves. No walk of this root can reach another one.
352
+ */
353
+ declare function sweepHqSpoolOrphans(input: {
354
+ repository: HqSpoolRepository;
355
+ }, dependencies?: {
356
+ budgetMs?: number;
357
+ env?: NodeJS.ProcessEnv;
358
+ }): Promise<HqSpoolOrphanSweep>;
359
+ //#endregion
360
+ //#region src/demand-waiver.d.ts
361
+ declare const DEFAULT_DEMAND_WAIVER_PATH = ".factory-memory/demand-waivers.json";
362
+ /** One recorded operator act: this demand, on this candidate, waived. */
363
+ interface DemandWaiver {
364
+ candidate: {
365
+ headSha: string;
366
+ pr: number;
367
+ };
368
+ demand: string;
369
+ /** The authenticated GitHub account that recorded the waiver. */
370
+ operator: string;
371
+ rationale: string;
372
+ recordedAt: string;
373
+ /** The operator session the waiver was recorded in. */
374
+ session: string;
375
+ }
376
+ interface DemandWaiverStore {
377
+ command: "patronage-factory demand:waive";
378
+ schemaVersion: 1;
379
+ waivers: DemandWaiver[];
380
+ }
381
+ declare const validateDemandWaiverStore: (value: unknown) => DemandWaiverStore;
382
+ /**
383
+ * A demand that was in force, was NOT met, and was waived by the operator.
384
+ *
385
+ * `unmetReasons` is required and non-empty: every reason the demand refused is
386
+ * carried through verbatim. There is no field on this record that could say
387
+ * "satisfied", and no code path constructs one without a refusal to carry.
388
+ */
389
+ interface WaivedDemand {
390
+ demand: string;
391
+ operator: string;
392
+ rationale: string;
393
+ recordedAt: string;
394
+ session: string;
395
+ /** The demand's refusals at evaluation time, preserved verbatim. */
396
+ unmetReasons: string[];
397
+ }
398
+ declare const waivedDemandSchema: z.ZodType<WaivedDemand>;
399
+ /** The waivers that bind one candidate: same PR, same head. */
400
+ declare const selectWaiversForCandidate: ({
401
+ headSha,
402
+ pr,
403
+ waivers
404
+ }: {
405
+ headSha: string | undefined;
406
+ pr: number;
407
+ waivers: readonly DemandWaiver[];
408
+ }) => DemandWaiver[];
409
+ interface DemandOutcome {
410
+ blockingReasons: string[];
411
+ waived?: WaivedDemand;
412
+ }
413
+ /**
414
+ * Fold one resolved demand's refusals through the operator's waivers.
415
+ *
416
+ * A satisfied demand (no refusals) stays satisfied and the waiver stays inert
417
+ * — a waiver can only ever move a demand from *unmet-and-blocking* to
418
+ * *unmet-and-waived*, never to met.
419
+ */
420
+ declare const applyDemandWaiver: ({
421
+ demand,
422
+ reasons,
423
+ waivers
424
+ }: {
425
+ demand: string;
426
+ reasons: readonly string[];
427
+ waivers: readonly DemandWaiver[];
428
+ }) => DemandOutcome;
429
+ /** How a waived demand reads to a human. Never the word "satisfied". */
430
+ declare const waivedDemandNotice: (waived: WaivedDemand) => string;
431
+ interface DemandWaiverAuthorization {
432
+ operator: string;
433
+ session: string;
434
+ }
435
+ type AuthorizeDemandWaiverResult = {
436
+ authorization: DemandWaiverAuthorization;
437
+ refusals?: undefined;
438
+ } | {
439
+ authorization?: undefined;
440
+ refusals: string[];
441
+ };
442
+ /**
443
+ * Operator identity, `declaredBy`-style: a named human account, recorded on
444
+ * the waiver and enforced here — plus session distinctness from the candidate
445
+ * the waiver applies to. Every refusal is named; nothing falls open.
446
+ */
447
+ declare const authorizeDemandWaiver: ({
448
+ authenticatedLogin,
449
+ authoringSession,
450
+ session
451
+ }: {
452
+ authenticatedLogin: string | undefined;
453
+ authoringSession: string | undefined;
454
+ session: string | undefined;
455
+ }) => AuthorizeDemandWaiverResult;
456
+ //#endregion
457
+ //#region src/blocked-reasons.d.ts
458
+ declare const blockedReasonSchema: z.ZodObject<{
459
+ code: z.ZodString;
460
+ detail: z.ZodString;
461
+ }, z.core.$strip>;
462
+ type BlockedReason = z.infer<typeof blockedReasonSchema>;
171
463
  //#endregion
172
464
  //#region src/checkout-repository.d.ts
173
465
  interface CheckoutRepository {
@@ -218,281 +510,46 @@ interface GithubPullRequestReview {
218
510
  url: string;
219
511
  }
220
512
  //#endregion
221
- //#region src/pr-readiness/handled-human-comments.d.ts
222
- declare const HANDLED_COMMENTS_PAYLOAD_KIND: "handled-human-comments";
223
- /**
224
- * Authenticated producer of the durable handled set. Recorded on the readiness
225
- * ledger; unverifiable producers are ignored on read (fail closed).
226
- */
227
- type HandledCommentsProducerMode = "app" | "commit-status";
228
- interface HandledCommentsProducer {
229
- /** App id (stringified) or GitHub login, depending on mode. */
230
- identity: string;
231
- mode: HandledCommentsProducerMode;
232
- }
233
- interface HandledCommentsCheckPayload {
234
- clearedAt?: string;
235
- handledCommentUrls: string[];
236
- kind: typeof HANDLED_COMMENTS_PAYLOAD_KIND;
237
- pr: number;
238
- schemaVersion: 1;
239
- sessionId?: string;
240
- }
241
- //#endregion
242
- //#region src/diff-classification.d.ts
243
- declare const DIFF_CLASSIFICATIONS: readonly ["docs/process-only", "trivial", "non-trivial"];
244
- type DiffClassification = (typeof DIFF_CLASSIFICATIONS)[number];
245
- //#endregion
246
- //#region src/profile.d.ts
247
- declare const DEFAULT_FACTORY_REPOSITORY = "unknown/unknown";
248
- /** The one profile shape this build accepts (ADR 0023, #318). */
249
- declare const PROFILE_SCHEMA_VERSION = 3;
250
- declare const factoryProjectProfileSchema: z.ZodObject<{
251
- $schema: z.ZodOptional<z.ZodString>;
252
- env: z.ZodOptional<z.ZodObject<{
253
- required: z.ZodArray<z.ZodString>;
254
- }, z.core.$strict>>;
255
- extensions: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
256
- hq: z.ZodOptional<z.ZodObject<{
257
- enabled: z.ZodBoolean;
258
- endpoint: z.ZodIntersection<z.ZodString, z.ZodString>;
259
- }, z.core.$strict>>;
260
- project: z.ZodObject<{
261
- key: z.ZodString;
262
- }, z.core.$strict>;
263
- proof: z.ZodObject<{
264
- classificationPolicy: z.ZodObject<{
265
- docsOnly: z.ZodDefault<z.ZodArray<z.ZodString>>;
266
- trivial: z.ZodDefault<z.ZodArray<z.ZodString>>;
267
- }, z.core.$strict>;
268
- }, z.core.$strict>;
269
- repository: z.ZodObject<{
270
- defaultBranch: z.ZodString;
271
- name: z.ZodString;
272
- owner: z.ZodString;
273
- }, z.core.$strict>;
274
- requiredChecks: z.ZodOptional<z.ZodArray<z.ZodObject<{
275
- checkType: z.ZodEnum<{
276
- review: "review";
277
- verify: "verify";
278
- }>;
279
- name: z.ZodString;
280
- scope: z.ZodOptional<z.ZodObject<{
281
- classifications: z.ZodOptional<z.ZodArray<z.ZodEnum<{
282
- trivial: "trivial";
283
- "docs/process-only": "docs/process-only";
284
- "non-trivial": "non-trivial";
285
- }>>>;
286
- labels: z.ZodOptional<z.ZodArray<z.ZodString>>;
287
- }, z.core.$strict>>;
288
- }, z.core.$strict>>>;
289
- review: z.ZodObject<{
290
- conditional: z.ZodOptional<z.ZodArray<z.ZodObject<{
291
- modes: z.ZodArray<z.ZodEnum<{
292
- correctness: "correctness";
293
- security: "security";
294
- }>>;
295
- paths: z.ZodArray<z.ZodString>;
296
- }, z.core.$strict>>>;
297
- defaultMaxCycles: z.ZodNumber;
298
- docsOnlyBypass: z.ZodOptional<z.ZodBoolean>;
299
- ladder: z.ZodOptional<z.ZodObject<{
300
- gate: z.ZodOptional<z.ZodObject<{
301
- cap: z.ZodNumber;
302
- }, z.core.$strict>>;
303
- }, z.core.$strict>>;
304
- modes: z.ZodArray<z.ZodEnum<{
305
- correctness: "correctness";
306
- security: "security";
307
- }>>;
308
- standingChecklist: z.ZodOptional<z.ZodArray<z.ZodString>>;
309
- }, z.core.$strict>;
310
- schemaVersion: z.ZodLiteral<3>;
311
- verification: z.ZodObject<{
312
- commands: z.ZodArray<z.ZodObject<{
313
- command: z.ZodString;
314
- description: z.ZodString;
315
- name: z.ZodString;
316
- requiredCheck: z.ZodOptional<z.ZodString>;
317
- scope: z.ZodDefault<z.ZodEnum<{
318
- trivial: "trivial";
319
- "docs-only": "docs-only";
320
- full: "full";
321
- }>>;
322
- }, z.core.$strip>>;
323
- }, z.core.$strip>;
324
- }, z.core.$strict>;
325
- type FactoryProjectProfile = z.infer<typeof factoryProjectProfileSchema>;
326
- declare const resolveFactoryRepository: (profile?: Pick<FactoryProjectProfile, "repository"> | undefined) => string;
327
- /**
328
- * Whether a pure docs/process diff may skip independent review. Opt-in: absent
329
- * config means review is required (#318, ADR 0023).
330
- */
331
- declare const resolveDocsOnlyReviewBypass: (profile: Pick<FactoryProjectProfile, "review">) => boolean;
332
- interface LoadProjectProfileInput {
333
- cwd?: string;
334
- profilePath?: string;
335
- }
336
- interface LoadProjectProfileResult {
337
- path: string;
338
- profile: FactoryProjectProfile;
339
- }
340
- declare function loadProjectProfile(input?: LoadProjectProfileInput): LoadProjectProfileResult;
341
- //#endregion
342
- //#region src/pr-verify-mode.d.ts
343
- /**
344
- * The verification mode `pr:verify` resolved for a run.
345
- *
346
- * Canonically declared here rather than inside `pr-readiness/` so that modules
347
- * on either side of that boundary — the readiness proof shape and the durable
348
- * check-run payload — can name the same union without importing each other.
349
- */
350
- type ResolvedPrVerifyMode = "docs-only" | "trivial" | "full";
351
- //#endregion
352
- //#region src/pr-verify-status.d.ts
353
- type CommitStatusState = "failure" | "pending" | "success";
354
- interface PostCommitStatusInput {
355
- context?: string;
356
- cwd: string;
357
- description: string;
358
- owner: string;
359
- repo: string;
360
- sha: string;
361
- state: CommitStatusState;
362
- /**
363
- * Drop this mirror's own failure diagnostic because the caller has already
364
- * reported the same cause. Set only on the pre-push path, where the status
365
- * POST 422s for exactly the reason the pre-push notice gives and a trailing
366
- * "unable to post ... status" line would put back the false-defect reading
367
- * that notice exists to remove (#316).
368
- *
369
- * Deliberately a field on the request rather than a second, quieter poster:
370
- * an injected `PostCommitStatus` double sees the flag, so the branch is
371
- * assertable instead of collapsing to the same closure under test.
372
- */
373
- suppressFailureDiagnostic?: boolean;
374
- targetUrl: string;
375
- }
376
- type PostCommitStatus = (input: PostCommitStatusInput) => void;
377
- //#endregion
378
- //#region src/user-config.d.ts
379
- declare const githubAppConfigSchema: z.ZodObject<{
380
- appId: z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>;
381
- installationId: z.ZodOptional<z.ZodNumber>;
382
- privateKeyPath: z.ZodString;
383
- }, z.core.$strip>;
384
- type GithubAppConfig = z.infer<typeof githubAppConfigSchema>;
385
- declare const factoryUserConfigSchema: z.ZodObject<{
386
- githubApp: z.ZodOptional<z.ZodObject<{
387
- appId: z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>;
388
- installationId: z.ZodOptional<z.ZodNumber>;
389
- privateKeyPath: z.ZodString;
390
- }, z.core.$strip>>;
391
- hqAllowedOrigins: z.ZodOptional<z.ZodArray<z.ZodString>>;
392
- schemaVersion: z.ZodLiteral<2>;
393
- }, z.core.$strip>;
394
- type FactoryUserConfig = z.infer<typeof factoryUserConfigSchema>;
395
- interface LoadUserConfigResult {
396
- path: string;
397
- config: FactoryUserConfig;
398
- ignoredKeys?: string[];
399
- }
400
- //#endregion
401
- //#region src/github-check-runs.d.ts
402
- declare const FACTORY_CHECK_NAMES: {
403
- readonly boundary: "patronage-factory/boundary";
404
- readonly "pr-ready": "patronage-factory/pr-ready";
405
- readonly "pr-review": "patronage-factory/pr-review";
406
- readonly "pr-verify": "patronage-factory/pr-verify";
407
- };
408
- type FactoryCheckGate = keyof typeof FACTORY_CHECK_NAMES;
409
- interface PublishFactoryCheckInput {
410
- conclusion?: "failure" | "success";
411
- cwd: string;
412
- gate: FactoryCheckGate;
413
- /**
414
- * HQ lane-permalink base (`<origin>/lanes/by-ref`). When present, the check
415
- * run's Details link deep-links to the HQ lane page instead of the PR-ledger
416
- * fallback. Derive it from the repository profile via
417
- * `hqLaneRefBaseUrlFromProfile` — the same source as the HQ gate sink.
418
- */
419
- hqLaneBaseUrl?: string;
420
- /**
421
- * Explicit issue/PR number for the HQ lane ref, used when the check run is not
422
- * PR-scoped (e.g. an epic boundary check keyed by its epic issue). Falls back
423
- * to `pr`, then to the checkout's PR, when omitted.
424
- */
425
- laneRefNumber?: number;
426
- owner: string;
427
- pr?: number;
428
- proof: unknown;
429
- repo: string;
430
- sha: string;
431
- status?: "completed" | "in_progress";
432
- }
433
- interface CheckRunDependencies {
434
- fetch?: typeof fetch;
435
- githubApp?: GithubAppConfig;
436
- now?: () => number;
437
- postCommitStatus?: PostCommitStatus;
438
- resolveDetailsUrl?: (input: PublishFactoryCheckInput) => Promise<string> | string;
439
- /**
440
- * Bounded retry for the check-run POST. GitHub answers 422 for a head SHA it
441
- * has not seen yet, which is the normal state when `pr:verify` runs before
442
- * the branch is pushed, and is also briefly true right after a push.
443
- */
444
- retry?: {
445
- attempts: number;
446
- budgetMs?: number;
447
- delayMs: number;
448
- };
449
- /** Injectable delay for the bounded retry (tests only). */
450
- sleep?: (ms: number) => Promise<void>;
451
- /** Overrides GITHUB_PUBLISH_TIMEOUT_MS for the App fetch calls (tests only). */
452
- timeoutMs?: number;
453
- }
513
+ //#region src/merge-freeze.d.ts
514
+ declare const MERGE_FREEZE_CHECK_NAME = "patronage-factory/merge-freeze";
515
+ declare const MERGE_FREEZE_APP_SLUG = "patronage-factory";
516
+ declare const mergeFreezeStateSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
517
+ active: z.ZodLiteral<true>;
518
+ generationId: z.ZodNumber;
519
+ headSha: z.ZodString;
520
+ outcome: z.ZodEnum<{
521
+ active: "active";
522
+ stale: "stale";
523
+ }>;
524
+ reason: z.ZodString;
525
+ recordedAt: z.ZodISODateTime;
526
+ schemaVersion: z.ZodLiteral<1>;
527
+ }, z.core.$strip>, z.ZodObject<{
528
+ active: z.ZodLiteral<false>;
529
+ clearRationale: z.ZodOptional<z.ZodString>;
530
+ generationId: z.ZodNumber;
531
+ headSha: z.ZodString;
532
+ outcome: z.ZodLiteral<"inactive">;
533
+ reason: z.ZodString;
534
+ recordedAt: z.ZodISODateTime;
535
+ schemaVersion: z.ZodLiteral<1>;
536
+ }, z.core.$strip>], "active">;
537
+ type MergeFreezeState = z.infer<typeof mergeFreezeStateSchema>;
454
538
  /**
455
- * Publish a factory check run and *wait* for it, so a caller that has just made
456
- * the head SHA visible on GitHub (pushed the branch, created the PR) can make
457
- * the proof reliably present for that SHA before it returns (#247).
458
- *
459
- * Never throws and never posts a commit-status fallback: the commit status is a
460
- * human-readable mirror, not a proof surface, so a caller that needs an
461
- * App-verified check run must be told plainly whether it got one. Returns
462
- * `true` only when the App-owned check run landed.
539
+ * The write side of this contract lives in the generated main-push verify
540
+ * workflow (#356, ADR 0016 as amended): it is the ONLY producer of
541
+ * `patronage-factory/merge-freeze` generations. Its emitted `output.text`
542
+ * payload must parse under this exact reader schema, which is what the
543
+ * workflow's own tests assert through this export.
463
544
  */
464
- declare function ensureFactoryCheckRunPublished(input: PublishFactoryCheckInput, dependencies?: CheckRunDependencies & {
465
- onDiagnostic?: (message: string) => void;
466
- }): Promise<boolean>;
467
- interface PublishHandledCommentsCheckInput {
468
- cwd: string;
469
- owner: string;
470
- payload: HandledCommentsCheckPayload;
471
- pr: number;
472
- repo: string;
473
- sha: string;
474
- }
475
- //#endregion
476
- //#region src/merge-freeze.d.ts
477
- interface MergeFreezeGeneration {
478
- headSha: string;
479
- id: number;
480
- startedAt: string;
481
- }
545
+ declare function validateMergeFreezeState(value: unknown): MergeFreezeState;
482
546
  interface MergeFreezeStoreInput {
483
547
  cwd: string;
484
548
  headSha: string;
485
549
  repository: CheckoutRepository;
486
550
  }
487
551
  interface MergeFreezeStore {
488
- complete: (input: MergeFreezeStoreInput & {
489
- clearRationale?: string;
490
- generation: MergeFreezeGeneration;
491
- outcome: "active" | "inactive" | "stale";
492
- reason: string;
493
- }) => Promise<void>;
494
552
  read: (input: MergeFreezeStoreInput) => unknown;
495
- start: (input: MergeFreezeStoreInput) => Promise<MergeFreezeGeneration>;
496
553
  }
497
554
  declare namespace worktree_held_branch_d_exports {
498
555
  export { MergeOperationalNotices, WorktreeHeldBranch, WorktreeHeldBranchCheck, WorktreeListEntry, checkWorktreeHeldBranch, findWorktreeHeldBranch, formatHeldBranchCloseoutSummary, listWorktreesPorcelain, parseGitWorktreeList, resolveMergeOperationalNotices, worktreeHeldBranchNotice };
@@ -670,6 +727,96 @@ declare const resolveMergeGuardIdentity: ({
670
727
  declare const mergeGuardIdentitySchema: z.ZodType<MergeGuardIdentity>;
671
728
  declare const mergeGuardBlockingReasons: (identity: MergeGuardIdentity) => string[];
672
729
  //#endregion
730
+ //#region src/profile.d.ts
731
+ declare const DEFAULT_FACTORY_REPOSITORY = "unknown/unknown";
732
+ /** The one profile shape this build accepts (ADR 0023, #318, #351). */
733
+ declare const PROFILE_SCHEMA_VERSION = 4;
734
+ declare const factoryProjectProfileSchema: z.ZodObject<{
735
+ $schema: z.ZodOptional<z.ZodString>;
736
+ env: z.ZodOptional<z.ZodObject<{
737
+ required: z.ZodArray<z.ZodString>;
738
+ }, z.core.$strict>>;
739
+ extensions: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
740
+ hq: z.ZodOptional<z.ZodObject<{
741
+ enabled: z.ZodBoolean;
742
+ endpoint: z.ZodIntersection<z.ZodString, z.ZodString>;
743
+ }, z.core.$strict>>;
744
+ project: z.ZodObject<{
745
+ key: z.ZodString;
746
+ }, z.core.$strict>;
747
+ proof: z.ZodObject<{
748
+ classificationPolicy: z.ZodObject<{
749
+ docsOnly: z.ZodDefault<z.ZodArray<z.ZodString>>;
750
+ trivial: z.ZodDefault<z.ZodArray<z.ZodString>>;
751
+ }, z.core.$strict>;
752
+ }, z.core.$strict>;
753
+ repository: z.ZodObject<{
754
+ defaultBranch: z.ZodString;
755
+ name: z.ZodString;
756
+ owner: z.ZodString;
757
+ }, z.core.$strict>;
758
+ requiredChecks: z.ZodOptional<z.ZodArray<z.ZodObject<{
759
+ checkType: z.ZodEnum<{
760
+ review: "review";
761
+ verify: "verify";
762
+ }>;
763
+ name: z.ZodString;
764
+ scope: z.ZodOptional<z.ZodObject<{
765
+ classifications: z.ZodOptional<z.ZodArray<z.ZodEnum<{
766
+ trivial: "trivial";
767
+ "docs/process-only": "docs/process-only";
768
+ "non-trivial": "non-trivial";
769
+ }>>>;
770
+ labels: z.ZodOptional<z.ZodArray<z.ZodString>>;
771
+ }, z.core.$strict>>;
772
+ }, z.core.$strict>>>;
773
+ review: z.ZodObject<{
774
+ conditional: z.ZodOptional<z.ZodArray<z.ZodObject<{
775
+ modes: z.ZodArray<z.ZodEnum<{
776
+ correctness: "correctness";
777
+ security: "security";
778
+ }>>;
779
+ paths: z.ZodArray<z.ZodString>;
780
+ }, z.core.$strict>>>;
781
+ defaultMaxCycles: z.ZodNumber;
782
+ ladder: z.ZodOptional<z.ZodObject<{
783
+ gate: z.ZodOptional<z.ZodObject<{
784
+ cap: z.ZodNumber;
785
+ }, z.core.$strict>>;
786
+ }, z.core.$strict>>;
787
+ modes: z.ZodArray<z.ZodEnum<{
788
+ correctness: "correctness";
789
+ security: "security";
790
+ }>>;
791
+ standingChecklist: z.ZodOptional<z.ZodArray<z.ZodString>>;
792
+ }, z.core.$strict>;
793
+ schemaVersion: z.ZodLiteral<4>;
794
+ verification: z.ZodObject<{
795
+ commands: z.ZodArray<z.ZodObject<{
796
+ command: z.ZodString;
797
+ description: z.ZodString;
798
+ name: z.ZodString;
799
+ requiredCheck: z.ZodOptional<z.ZodString>;
800
+ scope: z.ZodDefault<z.ZodEnum<{
801
+ trivial: "trivial";
802
+ "docs-only": "docs-only";
803
+ full: "full";
804
+ }>>;
805
+ }, z.core.$strip>>;
806
+ }, z.core.$strip>;
807
+ }, z.core.$strict>;
808
+ type FactoryProjectProfile = z.infer<typeof factoryProjectProfileSchema>;
809
+ declare const resolveFactoryRepository: (profile?: Pick<FactoryProjectProfile, "repository"> | undefined) => string;
810
+ interface LoadProjectProfileInput {
811
+ cwd?: string;
812
+ profilePath?: string;
813
+ }
814
+ interface LoadProjectProfileResult {
815
+ path: string;
816
+ profile: FactoryProjectProfile;
817
+ }
818
+ declare function loadProjectProfile(input?: LoadProjectProfileInput): LoadProjectProfileResult;
819
+ //#endregion
673
820
  //#region src/review-rungs.d.ts
674
821
  declare const EVIDENCE_REVIEW_RUNGS: readonly ["independent-model", "oracle", "human"];
675
822
  type EvidenceReviewRung = (typeof EVIDENCE_REVIEW_RUNGS)[number];
@@ -715,13 +862,19 @@ interface PrMergeCheckProof {
715
862
  notices?: string[];
716
863
  pr: number;
717
864
  status: "pass" | "fail";
865
+ /**
866
+ * Demands that were in force, were NOT met, and were waived by the operator
867
+ * (#354). Each entry carries the demand's refusals verbatim, so a waived
868
+ * demand can never read as a met one; the merge proceeds on the recorded
869
+ * operator act, not on evidence.
870
+ */
871
+ waivedDemands?: WaivedDemand[];
718
872
  /** Present when the PR head branch is checked out in a local worktree. */
719
873
  worktreeHeldBranch?: WorktreeHeldBranch;
720
874
  /** All merge-relevant branches held by local worktrees (head and default). */
721
875
  worktreeHeldBranches?: WorktreeHeldBranch[];
722
876
  }
723
877
  interface PrMergeCheckGitDependencies {
724
- changedFilesBetween: (cwd: string, baseSha: string, headSha: string) => string[] | undefined;
725
878
  checkoutRepository: (cwd: string) => CheckoutRepository;
726
879
  commitsBetween: (cwd: string, fromSha: string, toSha: string) => PostProofCommit[] | undefined;
727
880
  worktreeListPorcelain: (cwd: string) => string | undefined;
@@ -771,6 +924,169 @@ declare function validatePrMergeCheckProof(value: unknown): PrMergeCheckProof;
771
924
  declare function readPrMergeCheckProof(filePath: string): PrMergeCheckProof;
772
925
  declare function runPrMergeCheck(args: PrMergeCheckArgs, dependencies?: PrMergeCheckDependencies): PrMergeCheckProof;
773
926
  //#endregion
927
+ //#region src/pr-readiness/handled-human-comments.d.ts
928
+ declare const HANDLED_COMMENTS_PAYLOAD_KIND: "handled-human-comments";
929
+ /**
930
+ * Authenticated producer of the durable handled set. Recorded on the readiness
931
+ * ledger; unverifiable producers are ignored on read (fail closed).
932
+ */
933
+ type HandledCommentsProducerMode = "app" | "commit-status";
934
+ interface HandledCommentsProducer {
935
+ /** App id (stringified) or GitHub login, depending on mode. */
936
+ identity: string;
937
+ mode: HandledCommentsProducerMode;
938
+ }
939
+ interface HandledCommentsCheckPayload {
940
+ clearedAt?: string;
941
+ handledCommentUrls: string[];
942
+ kind: typeof HANDLED_COMMENTS_PAYLOAD_KIND;
943
+ pr: number;
944
+ schemaVersion: 1;
945
+ sessionId?: string;
946
+ }
947
+ //#endregion
948
+ //#region src/diff-classification.d.ts
949
+ declare const DIFF_CLASSIFICATIONS: readonly ["docs/process-only", "trivial", "non-trivial"];
950
+ type DiffClassification = (typeof DIFF_CLASSIFICATIONS)[number];
951
+ //#endregion
952
+ //#region src/pr-verify-mode.d.ts
953
+ /**
954
+ * The verification mode `pr:verify` resolved for a run.
955
+ *
956
+ * Canonically declared here rather than inside `pr-readiness/` so that modules
957
+ * on either side of that boundary — the readiness proof shape and the durable
958
+ * check-run payload — can name the same union without importing each other.
959
+ */
960
+ type ResolvedPrVerifyMode = "docs-only" | "trivial" | "full";
961
+ //#endregion
962
+ //#region src/pr-verify-status.d.ts
963
+ type CommitStatusState = "failure" | "pending" | "success";
964
+ interface PostCommitStatusInput {
965
+ context?: string;
966
+ cwd: string;
967
+ description: string;
968
+ owner: string;
969
+ repo: string;
970
+ sha: string;
971
+ state: CommitStatusState;
972
+ /**
973
+ * Drop this mirror's own failure diagnostic because the caller has already
974
+ * reported the same cause. Set only on the pre-push path, where the status
975
+ * POST 422s for exactly the reason the pre-push notice gives and a trailing
976
+ * "unable to post ... status" line would put back the false-defect reading
977
+ * that notice exists to remove (#316).
978
+ *
979
+ * Deliberately a field on the request rather than a second, quieter poster:
980
+ * an injected `PostCommitStatus` double sees the flag, so the branch is
981
+ * assertable instead of collapsing to the same closure under test.
982
+ */
983
+ suppressFailureDiagnostic?: boolean;
984
+ targetUrl: string;
985
+ }
986
+ type PostCommitStatus = (input: PostCommitStatusInput) => void;
987
+ //#endregion
988
+ //#region src/user-config.d.ts
989
+ declare const githubAppConfigSchema: z.ZodObject<{
990
+ appId: z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>;
991
+ installationId: z.ZodOptional<z.ZodNumber>;
992
+ privateKeyPath: z.ZodString;
993
+ }, z.core.$strip>;
994
+ type GithubAppConfig = z.infer<typeof githubAppConfigSchema>;
995
+ declare const factoryUserConfigSchema: z.ZodObject<{
996
+ githubApp: z.ZodOptional<z.ZodObject<{
997
+ appId: z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>;
998
+ installationId: z.ZodOptional<z.ZodNumber>;
999
+ privateKeyPath: z.ZodString;
1000
+ }, z.core.$strip>>;
1001
+ hqAllowedOrigins: z.ZodOptional<z.ZodArray<z.ZodString>>;
1002
+ hqIngestCredentials: z.ZodOptional<z.ZodObject<{
1003
+ clientIdRef: z.ZodString;
1004
+ clientSecretRef: z.ZodString;
1005
+ }, z.core.$strip>>;
1006
+ schemaVersion: z.ZodLiteral<2>;
1007
+ }, z.core.$strip>;
1008
+ type FactoryUserConfig = z.infer<typeof factoryUserConfigSchema>;
1009
+ interface LoadUserConfigResult {
1010
+ path: string;
1011
+ config: FactoryUserConfig;
1012
+ ignoredKeys?: string[];
1013
+ }
1014
+ //#endregion
1015
+ //#region src/github-check-runs.d.ts
1016
+ declare const FACTORY_CHECK_NAMES: {
1017
+ readonly boundary: "patronage-factory/boundary";
1018
+ readonly "pr-ready": "patronage-factory/pr-ready";
1019
+ readonly "pr-review": "patronage-factory/pr-review";
1020
+ readonly "pr-verify": "patronage-factory/pr-verify";
1021
+ };
1022
+ type FactoryCheckGate = keyof typeof FACTORY_CHECK_NAMES;
1023
+ interface PublishFactoryCheckInput {
1024
+ conclusion?: "failure" | "success";
1025
+ cwd: string;
1026
+ gate: FactoryCheckGate;
1027
+ /**
1028
+ * HQ lane-permalink base (`<origin>/lanes/by-ref`). When present, the check
1029
+ * run's Details link deep-links to the HQ lane page instead of the PR-ledger
1030
+ * fallback. Derive it from the repository profile via
1031
+ * `hqLaneRefBaseUrlFromProfile` — the same source as the HQ gate sink.
1032
+ */
1033
+ hqLaneBaseUrl?: string;
1034
+ /**
1035
+ * Explicit issue/PR number for the HQ lane ref, used when the check run is not
1036
+ * PR-scoped (e.g. an epic boundary check keyed by its epic issue). Falls back
1037
+ * to `pr`, then to the checkout's PR, when omitted.
1038
+ */
1039
+ laneRefNumber?: number;
1040
+ owner: string;
1041
+ pr?: number;
1042
+ proof: unknown;
1043
+ repo: string;
1044
+ sha: string;
1045
+ status?: "completed" | "in_progress";
1046
+ }
1047
+ interface CheckRunDependencies {
1048
+ fetch?: typeof fetch;
1049
+ githubApp?: GithubAppConfig;
1050
+ now?: () => number;
1051
+ postCommitStatus?: PostCommitStatus;
1052
+ resolveDetailsUrl?: (input: PublishFactoryCheckInput) => Promise<string> | string;
1053
+ /**
1054
+ * Bounded retry for the check-run POST. GitHub answers 422 for a head SHA it
1055
+ * has not seen yet, which is the normal state when `pr:verify` runs before
1056
+ * the branch is pushed, and is also briefly true right after a push.
1057
+ */
1058
+ retry?: {
1059
+ attempts: number;
1060
+ budgetMs?: number;
1061
+ delayMs: number;
1062
+ };
1063
+ /** Injectable delay for the bounded retry (tests only). */
1064
+ sleep?: (ms: number) => Promise<void>;
1065
+ /** Overrides GITHUB_PUBLISH_TIMEOUT_MS for the App fetch calls (tests only). */
1066
+ timeoutMs?: number;
1067
+ }
1068
+ /**
1069
+ * Publish a factory check run and *wait* for it, so a caller that has just made
1070
+ * the head SHA visible on GitHub (pushed the branch, created the PR) can make
1071
+ * the proof reliably present for that SHA before it returns (#247).
1072
+ *
1073
+ * Never throws and never posts a commit-status fallback: the commit status is a
1074
+ * human-readable mirror, not a proof surface, so a caller that needs an
1075
+ * App-verified check run must be told plainly whether it got one. Returns
1076
+ * `true` only when the App-owned check run landed.
1077
+ */
1078
+ declare function ensureFactoryCheckRunPublished(input: PublishFactoryCheckInput, dependencies?: CheckRunDependencies & {
1079
+ onDiagnostic?: (message: string) => void;
1080
+ }): Promise<boolean>;
1081
+ interface PublishHandledCommentsCheckInput {
1082
+ cwd: string;
1083
+ owner: string;
1084
+ payload: HandledCommentsCheckPayload;
1085
+ pr: number;
1086
+ repo: string;
1087
+ sha: string;
1088
+ }
1089
+ //#endregion
774
1090
  //#region src/pr-proof-io.d.ts
775
1091
  interface ProofDescriptor<T> {
776
1092
  label: string;
@@ -1064,7 +1380,7 @@ interface PrReviewProof {
1064
1380
  cleanedPaths: string[];
1065
1381
  ladder?: PrReviewLadderState;
1066
1382
  reviewRequirement?: {
1067
- reason: "docs-only-profile-bypass";
1383
+ reason: "no-applicable-mode";
1068
1384
  status: "not-required";
1069
1385
  };
1070
1386
  reviews: PrReviewResult[];
@@ -1280,8 +1596,8 @@ declare const managedReadinessLedgerSchema: z.ZodObject<{
1280
1596
  status: z.ZodEnum<{
1281
1597
  blocked: "blocked";
1282
1598
  "not-required": "not-required";
1283
- current: "current";
1284
1599
  stale: "stale";
1600
+ current: "current";
1285
1601
  missing: "missing";
1286
1602
  }>;
1287
1603
  }, z.core.$strip>;
@@ -1293,8 +1609,8 @@ declare const managedReadinessLedgerSchema: z.ZodObject<{
1293
1609
  status: z.ZodEnum<{
1294
1610
  blocked: "blocked";
1295
1611
  "not-required": "not-required";
1296
- current: "current";
1297
1612
  stale: "stale";
1613
+ current: "current";
1298
1614
  missing: "missing";
1299
1615
  }>;
1300
1616
  }, z.core.$strip>>;
@@ -1311,8 +1627,8 @@ declare const managedReadinessLedgerSchema: z.ZodObject<{
1311
1627
  docsOnlyDeltaAccepted: z.ZodOptional<z.ZodBoolean>;
1312
1628
  docsOnlyVerifiedHeadSha: z.ZodOptional<z.ZodString>;
1313
1629
  prVerify: z.ZodEnum<{
1314
- passed: "passed";
1315
1630
  stale: "stale";
1631
+ passed: "passed";
1316
1632
  missing: "missing";
1317
1633
  }>;
1318
1634
  trivialDeltaAccepted: z.ZodOptional<z.ZodBoolean>;
@@ -1349,6 +1665,7 @@ interface PrReadyArgs extends LoadProjectProfileInput {
1349
1665
  authoringSessionIds?: string[];
1350
1666
  base: string;
1351
1667
  bodyForEvaluation?: string;
1668
+ epic?: number;
1352
1669
  handledCommentUrls?: string[];
1353
1670
  json?: boolean;
1354
1671
  output?: string;
@@ -1396,7 +1713,15 @@ interface GitHubPullRequest {
1396
1713
  url: string;
1397
1714
  }
1398
1715
  interface PrReadyProof {
1399
- schemaVersion: 1;
1716
+ schemaVersion: 2;
1717
+ /**
1718
+ * Why this run blocked, one entry per refusing demand (#391): `code` is the
1719
+ * demand key from the resolver's vocabulary, `detail` the one-sentence
1720
+ * refusal. Same refusals as `blockingReasons`, in the same order — the
1721
+ * analyzable projection of a flat string list, so a wall of blocked proofs
1722
+ * on one PR can be counted by cause. Absent when nothing blocked.
1723
+ */
1724
+ blockedReasons?: BlockedReason[];
1400
1725
  blockingReasons: string[];
1401
1726
  humanBlockingReasons: string[];
1402
1727
  command: "patronage-factory pr:ready";
@@ -1427,6 +1752,18 @@ interface PrReadyProof {
1427
1752
  type PublishHandledCommentsResult = HandledCommentsProducer | Promise<HandledCommentsProducer | undefined> | undefined;
1428
1753
  interface PrReadyDependencies {
1429
1754
  github?: {
1755
+ fetchClosingPullRequests?: (input: {
1756
+ issue: number;
1757
+ owner: string;
1758
+ repo: string;
1759
+ }) => {
1760
+ number: number;
1761
+ }[];
1762
+ fetchIssueBody?: (input: {
1763
+ issue: number;
1764
+ owner: string;
1765
+ repo: string;
1766
+ }) => string;
1430
1767
  fetchHandledComments?: (input: {
1431
1768
  owner: string;
1432
1769
  pr: number;
@@ -1484,7 +1821,7 @@ declare const requiredCheckScopeSchema: z.ZodObject<{
1484
1821
  type RequiredCheckScope = z.infer<typeof requiredCheckScopeSchema>;
1485
1822
  interface RequiredCheckScopeContext {
1486
1823
  labels: string[] | undefined;
1487
- classification: DiffClassification;
1824
+ classification: DiffClassification | undefined;
1488
1825
  }
1489
1826
  interface ScopeDecision {
1490
1827
  inScope: boolean;
@@ -1647,8 +1984,8 @@ declare const REVIEW_STATUS_VALUES: readonly ["not-required", "current", "stale"
1647
1984
  declare const reviewStatusSchema: z.ZodEnum<{
1648
1985
  blocked: "blocked";
1649
1986
  "not-required": "not-required";
1650
- current: "current";
1651
1987
  stale: "stale";
1988
+ current: "current";
1652
1989
  missing: "missing";
1653
1990
  }>;
1654
1991
  type ReviewStatus = z.infer<typeof reviewStatusSchema>;
@@ -1913,15 +2250,195 @@ declare const resolveVerifyProofApplicability: ({
1913
2250
  fullVerifiedHeadShas: never[];
1914
2251
  verificationProof: VerificationProofState;
1915
2252
  } | {
1916
- fullVerifiedHeadShas: string[];
1917
- verificationProof: {
1918
- headShas: VerifiedHeadShas;
1919
- kind: "typed";
1920
- proof: PrVerifyProof;
1921
- };
2253
+ fullVerifiedHeadShas: string[];
2254
+ verificationProof: {
2255
+ headShas: VerifiedHeadShas;
2256
+ kind: "typed";
2257
+ proof: PrVerifyProof;
2258
+ };
2259
+ };
2260
+ declare const verificationProofForReadiness: (state: VerificationProofState) => VerificationHeadShas;
2261
+ declare const verificationProofBlockingReason: (state: VerificationProofState) => string | undefined;
2262
+ //#endregion
2263
+ //#region src/retro-envelope.d.ts
2264
+ /**
2265
+ * Versioned retro envelope schema (epic #27 wave 2, issue #34).
2266
+ *
2267
+ * One envelope per lane, built at `factory:closeout` and delivered through the
2268
+ * typed gate-sink as the `retro-envelope` ingest kind. Re-derived in TypeScript
2269
+ * from the `spike/telemetry-layer2` S5 scratch schema (reference-only, never
2270
+ * merged). This module is the single source of truth for the v1 wire shape
2271
+ * and its bounds ({@link RETRO_ENVELOPE_WIRE_BOUNDS}) — producer and consumer
2272
+ * alike. HQ imports these exports directly from the Worker-safe
2273
+ * `@patronage/software-factory/schemas` subpath
2274
+ * (`software-factory-hq/src/contracts/retro-schemas.ts`) instead of
2275
+ * maintaining a parallel hand-written copy, so there is exactly one wire
2276
+ * contract and no drift-detection machinery is needed (issue #350; formerly
2277
+ * a hand-written twin plus a 767-line parity test, #46).
2278
+ *
2279
+ * DESIGN INVARIANT: cross-family token sums must be UNREPRESENTABLE.
2280
+ *
2281
+ * The two model families use different tokenizers, prices, and accounting
2282
+ * conventions, so any token total that spans Claude and GPT is a lie:
2283
+ *
2284
+ * 1. There is no combined/total token field anywhere in the envelope.
2285
+ * 2. `tokenFamilies` is strict — its only keys are `claude` and `gpt`; data
2286
+ * cannot smuggle in a third "all"/"combined" slot.
2287
+ * 3. The family BLOCKS are structurally different shapes with DIFFERENT keys
2288
+ * (claude is a single flat block keyed on freshInput/cacheReadInput/
2289
+ * cacheCreationInput; gpt is `{ roles: [...] }`). The exclusive input-tier
2290
+ * COUNTS share no key name across families. The residual names shared
2291
+ * between the claude block and a gpt ROLE entry are pinned to exactly
2292
+ * {costUsd, model, output} (PR #32 advisory): `costUsd` is deliberate —
2293
+ * USD is the one cross-family summable unit (rule 4); `model` is an
2294
+ * unsummable label; `output` is the same name at DIFFERENT depths (lane
2295
+ * block vs per-role entry), frozen by a tripwire test in
2296
+ * `retro-envelope.test.ts` so the overlap cannot grow. Renaming `output`
2297
+ * is a schemaVersion-2 wire change, deliberately not spent in v1.
2298
+ * 4. Cost is per-family USD and nullable. Combined totals are allowed in USD
2299
+ * only, and only as a projection-time sum of per-family USD.
2300
+ *
2301
+ * Field names also encode the S2/S3 reader lessons: Claude `freshInput` alone
2302
+ * is not prompt size (true input context = freshInput + cacheReadInput +
2303
+ * cacheCreationInput, requestId-deduped), and codex `inputInclusiveOfCache`
2304
+ * already includes `cachedInput`, so `freshInputDerived` (inclusive − cached)
2305
+ * is the only value safe to feed a per-token pricer.
2306
+ *
2307
+ * COMPLETENESS POSTURE: harvest may have no usable native log for a lane, so
2308
+ * `tokenFamilies` may legitimately be absent. The closeout build gate demands
2309
+ * a valid envelope, not available telemetry. A families-absent envelope keeps
2310
+ * its operator-visible data gaps and is a replayable advisory HQ event, so it
2311
+ * never substitutes unavailable usage with zero. The wire shape (field names,
2312
+ * types, structure) stays byte-parity with HQ v1.
2313
+ */
2314
+ declare const RETRO_ENVELOPE_SCHEMA_VERSION = 1;
2315
+ declare const retroEnvelopeV1Schema: z.ZodObject<{
2316
+ agentRunId: z.ZodString;
2317
+ archiveRef: z.ZodOptional<z.ZodString>;
2318
+ cycles: z.ZodObject<{
2319
+ gateRunsToFirstGreen: z.ZodNumber;
2320
+ reviewerFixRounds: z.ZodNumber;
2321
+ thermoFixRounds: z.ZodNumber;
2322
+ }, z.core.$strict>;
2323
+ dataGaps: z.ZodDefault<z.ZodArray<z.ZodString>>;
2324
+ gates: z.ZodArray<z.ZodObject<{
2325
+ cycle: z.ZodNumber;
2326
+ duration: z.ZodNumber;
2327
+ gate: z.ZodString;
2328
+ outcome: z.ZodEnum<{
2329
+ pass: "pass";
2330
+ fail: "fail";
2331
+ skip: "skip";
2332
+ }>;
2333
+ startedAt: z.ZodISODateTime;
2334
+ }, z.core.$strict>>;
2335
+ generatedAt: z.ZodISODateTime;
2336
+ interventions: z.ZodObject<{
2337
+ count: z.ZodNumber;
2338
+ }, z.core.$strict>;
2339
+ joinKeys: z.ZodObject<{
2340
+ claudeSessionIds: z.ZodDefault<z.ZodArray<z.ZodString>>;
2341
+ codexThreadIds: z.ZodDefault<z.ZodArray<z.ZodString>>;
2342
+ }, z.core.$strict>;
2343
+ kind: z.ZodLiteral<"retro-envelope">;
2344
+ outcome: z.ZodOptional<z.ZodObject<{
2345
+ mergeCheck: z.ZodOptional<z.ZodEnum<{
2346
+ pass: "pass";
2347
+ fail: "fail";
2348
+ "not-run": "not-run";
2349
+ }>>;
2350
+ status: z.ZodEnum<{
2351
+ success: "success";
2352
+ fail: "fail";
2353
+ blocked: "blocked";
2354
+ "ship-with-followups": "ship-with-followups";
2355
+ }>;
2356
+ verdict: z.ZodOptional<z.ZodString>;
2357
+ }, z.core.$strict>>;
2358
+ phases: z.ZodArray<z.ZodObject<{
2359
+ at: z.ZodISODateTime;
2360
+ deltaSec: z.ZodOptional<z.ZodNumber>;
2361
+ name: z.ZodString;
2362
+ }, z.core.$strict>>;
2363
+ refs: z.ZodObject<{
2364
+ branch: z.ZodOptional<z.ZodString>;
2365
+ epic: z.ZodOptional<z.ZodString>;
2366
+ headSha: z.ZodOptional<z.ZodString>;
2367
+ issue: z.ZodOptional<z.ZodString>;
2368
+ prNumber: z.ZodOptional<z.ZodNumber>;
2369
+ }, z.core.$strict>;
2370
+ repo: z.ZodObject<{
2371
+ name: z.ZodString;
2372
+ owner: z.ZodString;
2373
+ }, z.core.$strict>;
2374
+ schemaVersion: z.ZodLiteral<1>;
2375
+ tokenFamilies: z.ZodObject<{
2376
+ claude: z.ZodOptional<z.ZodObject<{
2377
+ cacheCreationInput: z.ZodNumber;
2378
+ cacheReadInput: z.ZodNumber;
2379
+ costUsd: z.ZodNullable<z.ZodNumber>;
2380
+ family: z.ZodLiteral<"claude">;
2381
+ freshInput: z.ZodNumber;
2382
+ model: z.ZodString;
2383
+ output: z.ZodNumber;
2384
+ requests: z.ZodNumber;
2385
+ }, z.core.$strict>>;
2386
+ gpt: z.ZodOptional<z.ZodObject<{
2387
+ family: z.ZodLiteral<"gpt">;
2388
+ roles: z.ZodArray<z.ZodObject<{
2389
+ cachedInput: z.ZodNumber;
2390
+ costUsd: z.ZodNullable<z.ZodNumber>;
2391
+ freshInputDerived: z.ZodNumber;
2392
+ inputInclusiveOfCache: z.ZodNumber;
2393
+ model: z.ZodString;
2394
+ output: z.ZodNumber;
2395
+ reasoningOutput: z.ZodNumber;
2396
+ role: z.ZodString;
2397
+ threadId: z.ZodOptional<z.ZodString>;
2398
+ }, z.core.$strict>>;
2399
+ }, z.core.$strict>>;
2400
+ }, z.core.$strict>;
2401
+ wallClock: z.ZodObject<{
2402
+ endTs: z.ZodISODateTime;
2403
+ startTs: z.ZodISODateTime;
2404
+ totalSec: z.ZodNumber;
2405
+ }, z.core.$strict>;
2406
+ }, z.core.$strict>;
2407
+ type RetroEnvelope = z.infer<typeof retroEnvelopeV1Schema>;
2408
+ /**
2409
+ * Versioned payload validators, keyed by schema major. Unknown majors never
2410
+ * reach these — {@link parseRetroEnvelope} returns them raw and marked
2411
+ * degraded, mirroring HQ's ingest skew posture (stored raw, never dropped).
2412
+ */
2413
+ declare const RETRO_ENVELOPE_VALIDATORS: Record<number, z.ZodType<RetroEnvelope, unknown>>;
2414
+ declare const SUPPORTED_RETRO_ENVELOPE_SCHEMA_VERSIONS: readonly number[];
2415
+ /** The `schemaVersion` a candidate declares, or undefined when unreadable. */
2416
+ declare const retroEnvelopeSchemaVersionOf: (candidate: unknown) => number | undefined;
2417
+ type ParsedRetroEnvelope = {
2418
+ disposition: "trusted";
2419
+ envelope: RetroEnvelope;
2420
+ schemaVersion: number;
2421
+ } | {
2422
+ disposition: "degraded";
2423
+ raw: unknown;
2424
+ schemaVersion: number | undefined;
1922
2425
  };
1923
- declare const verificationProofForReadiness: (state: VerificationProofState) => VerificationHeadShas;
1924
- declare const verificationProofBlockingReason: (state: VerificationProofState) => string | undefined;
2426
+ /**
2427
+ * Parses a candidate against the versioned validators. A recognized major is
2428
+ * validated typed-only (throws on an invalid known-version payload); an unknown
2429
+ * major is round-tripped RAW and marked degraded — the retained payload is the
2430
+ * exact input object, never a coerced projection. Mirrors the HQ ingest seam so
2431
+ * the two sides agree on the skew posture.
2432
+ */
2433
+ declare const parseRetroEnvelope: (candidate: unknown) => ParsedRetroEnvelope;
2434
+ /**
2435
+ * Whether harvest recorded at least one token family. This is an operator
2436
+ * summary predicate, not a delivery gate: a families-absent envelope remains
2437
+ * valid and replayable when its data gaps explain why telemetry is unavailable.
2438
+ */
2439
+ declare const isRetroEnvelopeWireComplete: (envelope: Pick<RetroEnvelope, "tokenFamilies">) => boolean;
2440
+ /** Epic anchor for an envelope: refs.epic, else refs.issue, else the PR. */
2441
+ declare const retroEpicReference: (refs: RetroEnvelope["refs"]) => string | undefined;
1925
2442
  //#endregion
1926
2443
  //#region src/schemas.d.ts
1927
2444
  declare const boundaryCheckProofSchema: z.ZodObject<{
@@ -2015,90 +2532,166 @@ interface CliOutput {
2015
2532
  //#region src/commands/boundary-check.d.ts
2016
2533
  type BoundaryCheckAction = (args: BoundaryCheckArgs) => BoundaryCheckProofRecord;
2017
2534
  //#endregion
2018
- //#region src/workspace-install-resolution.d.ts
2019
- interface CleanInstallResolutionDependencies {
2020
- existsSync?: typeof existsSync;
2021
- readFileSync?: typeof readFileSync;
2022
- rmSync?: typeof rmSync;
2023
- runInstall?: (repoRoot: string) => void;
2535
+ //#region src/demand-waive.d.ts
2536
+ /** Every waiver recorded in this checkout; an absent or unreadable store is
2537
+ * no waivers, so a damaged file can only ever block, never permit. */
2538
+ declare const readDemandWaivers: (cwd: string, filePath?: string) => DemandWaiver[];
2539
+ interface DemandWaiveArgs {
2540
+ cwd?: string;
2541
+ demand: string;
2542
+ json?: boolean;
2543
+ output?: string;
2544
+ pr: number;
2545
+ rationale: string;
2546
+ }
2547
+ interface DemandWaiveDependencies {
2548
+ checkoutRepository?: (cwd: string) => CheckoutRepository;
2549
+ env?: NodeJS.ProcessEnv;
2550
+ fetchAuthenticatedLogin?: (cwd: string) => string | undefined;
2551
+ fetchPullRequestHeadSha?: (input: {
2552
+ cwd: string;
2553
+ owner: string;
2554
+ pr: number;
2555
+ repo: string;
2556
+ }) => string | undefined;
2557
+ now?: () => Date;
2558
+ }
2559
+ declare class DemandWaiveRefusalError extends Error {
2560
+ readonly refusals: string[];
2561
+ constructor(refusals: string[]);
2024
2562
  }
2563
+ /**
2564
+ * Record one waiver. One step: the operator names the demand, the candidate,
2565
+ * and why. No confirmation, no second approval, no cooldown — the trust model
2566
+ * puts the bar at operator identity, not at ceremony aimed at the operator
2567
+ * (ADR 0025).
2568
+ */
2569
+ declare const runDemandWaive: (args: DemandWaiveArgs, dependencies?: DemandWaiveDependencies) => DemandWaiver;
2025
2570
  //#endregion
2026
- //#region src/pr-verify.d.ts
2027
- type PrVerifyMode = "auto" | "docs-only" | "trivial" | "full";
2028
- interface VerificationCommandOutcome {
2029
- counts?: {
2030
- testFiles?: number;
2031
- tests?: number;
2032
- };
2033
- durationMs: number;
2034
- exitCode: number;
2571
+ //#region src/commands/demand-waive.d.ts
2572
+ type DemandWaiveAction = (args: DemandWaiveArgs) => DemandWaiver;
2573
+ //#endregion
2574
+ //#region src/commands/pr-merge-check.d.ts
2575
+ type PrMergeCheckAction = (args: PrMergeCheckArgs) => PrMergeCheckProof;
2576
+ //#endregion
2577
+ //#region src/hq-credentials.d.ts
2578
+ /**
2579
+ * Why a reference did not resolve. Each value names a different remedy, and
2580
+ * none of them can be inferred from a value-or-nothing result:
2581
+ *
2582
+ * - `resolver-missing` — no secret-manager binary on PATH.
2583
+ * - `resolver-blocked` — a binary exists but this session may not execute it
2584
+ * (a sandbox denying exec). The command belongs outside the sandbox.
2585
+ * - `resolver-timeout` — the probe expired: a desktop agent waiting on an
2586
+ * approval nobody can give here.
2587
+ * - `resolver-refused` — the binary ran and produced no value. Its store is
2588
+ * unreachable from this session (a sandbox with no keychain access) or the
2589
+ * reference is not readable. These two stay one status on purpose: telling
2590
+ * them apart would mean reading resolver output.
2591
+ */
2592
+ type SecretResolutionFailure = "resolver-blocked" | "resolver-missing" | "resolver-refused" | "resolver-timeout";
2593
+ /** A structured resolution outcome. The value travels only when resolved. */
2594
+ type SecretResolution = {
2595
+ status: "resolved";
2596
+ value: string;
2597
+ } | {
2598
+ status: SecretResolutionFailure;
2599
+ };
2600
+ /** Resolves a secret reference. Injected so tests stay offline. */
2601
+ type SecretReferenceResolver = (reference: string) => SecretResolution;
2602
+ //#endregion
2603
+ //#region src/hq-flush.d.ts
2604
+ /**
2605
+ * Spool locations this run did not drain because they sit under an earlier
2606
+ * key for this repository. Reported, never drained: draining an older key's
2607
+ * events is an operator decision, made with `--dir`.
2608
+ */
2609
+ interface HqFlushOrphans {
2610
+ orphans: HqSpoolOrphan[];
2035
2611
  }
2036
- interface PrVerifyArgs extends LoadProjectProfileInput {
2037
- authoringSession?: string;
2612
+ type HqFlushResult = (HqFlushOrphans & {
2613
+ reason: string;
2614
+ /**
2615
+ * Events still spooled when the command gave up. Non-zero means the
2616
+ * skip retained work: the exit status says so, and no caller may read
2617
+ * the skip as "there was nothing to do".
2618
+ */
2619
+ retained: number;
2620
+ status: "skipped";
2621
+ }) | (HqFlushOrphans & HqSpoolFlushSummary & {
2622
+ endpoint: string;
2623
+ status: "flushed";
2624
+ });
2625
+ //#endregion
2626
+ //#region src/pr-review.d.ts
2627
+ interface PrReviewArgs extends LoadProjectProfileInput {
2038
2628
  base: string;
2039
- mode: PrVerifyMode;
2629
+ cycle: number;
2630
+ dispositions?: string;
2631
+ findings?: string;
2632
+ issue?: number;
2633
+ maxCycles?: number;
2634
+ mode: PrReviewMode;
2040
2635
  output?: string;
2041
- requireKnownAuthoringSession?: boolean;
2636
+ verifyProof?: string;
2042
2637
  }
2043
- interface PrVerifyGitDependencies {
2638
+ interface PrReviewGitDependencies {
2044
2639
  changedFiles: (cwd: string, base: string) => string[];
2045
2640
  currentHeadSha: (cwd: string) => string;
2046
- emptyTreeHash: (cwd: string) => string;
2047
- mergeBaseSha: (cwd: string, base: string) => string;
2048
- runVerificationCommand: (command: string, cwd: string, env: Record<string, string>) => VerificationCommandOutcome;
2049
- statusPorcelain: (cwd: string) => string;
2641
+ isAncestor: (cwd: string, ancestor: string, descendant: string) => boolean;
2050
2642
  stablePatchId: (cwd: string, base: string) => string;
2643
+ statusPorcelain: (cwd: string) => string;
2051
2644
  }
2052
- interface PrVerifyDependencies {
2053
- cleanInstall?: CleanInstallResolutionDependencies | false;
2054
- env?: NodeJS.ProcessEnv;
2055
- git?: PrVerifyGitDependencies;
2645
+ interface PrReviewDependencies {
2646
+ git?: PrReviewGitDependencies;
2056
2647
  hq?: HqIngestDependencies;
2057
- postCommitStatus?: PostCommitStatus;
2058
2648
  publishCheckRun?: (input: PublishFactoryCheckInput) => void;
2059
2649
  }
2060
- declare function runPrVerify(args: PrVerifyArgs, dependencies?: PrVerifyDependencies): PrVerifyProof;
2650
+ declare function runPrReview(args: PrReviewArgs, dependencies?: PrReviewDependencies): Promise<PrReviewProof>;
2061
2651
  //#endregion
2062
- //#region src/canary-verify.d.ts
2063
- interface CanaryVerifyArgs extends LoadProjectProfileInput {
2064
- /** Lift an active merge freeze without running verification. */
2065
- clear?: boolean;
2066
- output?: string;
2067
- /** Required with --clear; recorded on the canary proof. */
2068
- rationale?: string;
2652
+ //#region src/workspace-install-resolution.d.ts
2653
+ interface CleanInstallResolutionDependencies {
2654
+ existsSync?: typeof existsSync;
2655
+ readFileSync?: typeof readFileSync;
2656
+ rmSync?: typeof rmSync;
2657
+ runInstall?: (repoRoot: string) => void;
2069
2658
  }
2070
2659
  //#endregion
2071
- //#region src/commands/canary-verify.d.ts
2072
- type CanaryVerifyAction = (args: CanaryVerifyArgs) => Promise<unknown> | unknown;
2073
- //#endregion
2074
- //#region src/commands/pr-merge-check.d.ts
2075
- type PrMergeCheckAction = (args: PrMergeCheckArgs) => PrMergeCheckProof;
2076
- //#endregion
2077
- //#region src/pr-review.d.ts
2078
- interface PrReviewArgs extends LoadProjectProfileInput {
2660
+ //#region src/pr-verify.d.ts
2661
+ type PrVerifyMode = "auto" | "docs-only" | "trivial" | "full";
2662
+ interface VerificationCommandOutcome {
2663
+ counts?: {
2664
+ testFiles?: number;
2665
+ tests?: number;
2666
+ };
2667
+ durationMs: number;
2668
+ exitCode: number;
2669
+ }
2670
+ interface PrVerifyArgs extends LoadProjectProfileInput {
2671
+ authoringSession?: string;
2079
2672
  base: string;
2080
- cycle: number;
2081
- dispositions?: string;
2082
- findings?: string;
2083
- issue?: number;
2084
- maxCycles?: number;
2085
- mode: PrReviewMode;
2673
+ mode: PrVerifyMode;
2086
2674
  output?: string;
2087
- verifyProof?: string;
2675
+ requireKnownAuthoringSession?: boolean;
2088
2676
  }
2089
- interface PrReviewGitDependencies {
2677
+ interface PrVerifyGitDependencies {
2090
2678
  changedFiles: (cwd: string, base: string) => string[];
2091
2679
  currentHeadSha: (cwd: string) => string;
2092
- isAncestor: (cwd: string, ancestor: string, descendant: string) => boolean;
2093
- stablePatchId: (cwd: string, base: string) => string;
2680
+ emptyTreeHash: (cwd: string) => string;
2681
+ mergeBaseSha: (cwd: string, base: string) => string;
2682
+ runVerificationCommand: (command: string, cwd: string, env: Record<string, string>) => VerificationCommandOutcome;
2094
2683
  statusPorcelain: (cwd: string) => string;
2684
+ stablePatchId: (cwd: string, base: string) => string;
2095
2685
  }
2096
- interface PrReviewDependencies {
2097
- git?: PrReviewGitDependencies;
2686
+ interface PrVerifyDependencies {
2687
+ cleanInstall?: CleanInstallResolutionDependencies | false;
2688
+ env?: NodeJS.ProcessEnv;
2689
+ git?: PrVerifyGitDependencies;
2098
2690
  hq?: HqIngestDependencies;
2691
+ postCommitStatus?: PostCommitStatus;
2099
2692
  publishCheckRun?: (input: PublishFactoryCheckInput) => void;
2100
2693
  }
2101
- declare function runPrReview(args: PrReviewArgs, dependencies?: PrReviewDependencies): Promise<PrReviewProof>;
2694
+ declare function runPrVerify(args: PrVerifyArgs, dependencies?: PrVerifyDependencies): PrVerifyProof;
2102
2695
  //#endregion
2103
2696
  //#region src/pr-publish.d.ts
2104
2697
  type PrPublishArgs = Omit<PrReadyArgs, "pr" | "throwWhenBlocked"> & {
@@ -2175,7 +2768,21 @@ interface PrPublishDependencies extends PrReadyDependencies {
2175
2768
  runFollowUp?: FollowUpRunner;
2176
2769
  runPrReady?: typeof runPrReady;
2177
2770
  runPrReview?: (args: PrReviewArgs) => Promise<PrReviewProof>;
2771
+ /** Settles asynchronously scheduled sink work before the handoff drain. */
2772
+ awaitPendingIngest?: () => Promise<void>;
2773
+ /**
2774
+ * The handoff drain (#390). Advisory everywhere: publish reports what it
2775
+ * found and never changes its verdict or exit status on the result.
2776
+ */
2777
+ flushHqSpool?: (args: {
2778
+ cwd: string;
2779
+ }) => Promise<HqFlushResult>;
2178
2780
  runPrVerify?: (args: PrVerifyArgs) => Promise<PrVerifyProof>;
2781
+ /**
2782
+ * Injectable delay for the bounded hosted-run await (#348; tests only —
2783
+ * production always waits the real interval).
2784
+ */
2785
+ sleep?: (ms: number) => Promise<void>;
2179
2786
  }
2180
2787
  /**
2181
2788
  * What became of the durable `pr:verify` binding for this publish. Reported in
@@ -2392,7 +2999,9 @@ declare const FACTORY_TRACE_EVENT_DEFINITIONS: readonly [DefinedTraceEvent<"revi
2392
2999
  observedAt: z.ZodString;
2393
3000
  outcome: z.ZodLiteral<"not-required">;
2394
3001
  patchId: z.ZodString;
2395
- reason: z.ZodLiteral<"docs-only-profile-bypass">;
3002
+ reason: z.ZodEnum<{
3003
+ "no-applicable-mode": "no-applicable-mode";
3004
+ }>;
2396
3005
  reviewCycle: z.ZodNumber;
2397
3006
  }, z.core.$strict>, readonly [z.ZodObject<{
2398
3007
  createdAt: z.ZodString;
@@ -2412,7 +3021,9 @@ declare const FACTORY_TRACE_EVENT_DEFINITIONS: readonly [DefinedTraceEvent<"revi
2412
3021
  observedAt: z.ZodString;
2413
3022
  outcome: z.ZodLiteral<"not-required">;
2414
3023
  patchId: z.ZodString;
2415
- reason: z.ZodLiteral<"docs-only-profile-bypass">;
3024
+ reason: z.ZodEnum<{
3025
+ "no-applicable-mode": "no-applicable-mode";
3026
+ }>;
2416
3027
  reviewCycle: z.ZodNumber;
2417
3028
  }, z.core.$strict>], {
2418
3029
  readonly 1: z.ZodObject<{
@@ -2423,7 +3034,9 @@ declare const FACTORY_TRACE_EVENT_DEFINITIONS: readonly [DefinedTraceEvent<"revi
2423
3034
  observedAt: z.ZodString;
2424
3035
  outcome: z.ZodLiteral<"not-required">;
2425
3036
  patchId: z.ZodString;
2426
- reason: z.ZodLiteral<"docs-only-profile-bypass">;
3037
+ reason: z.ZodEnum<{
3038
+ "no-applicable-mode": "no-applicable-mode";
3039
+ }>;
2427
3040
  reviewCycle: z.ZodNumber;
2428
3041
  }, z.core.$strict>;
2429
3042
  }, {
@@ -2440,7 +3053,7 @@ declare const FACTORY_TRACE_EVENT_DEFINITIONS: readonly [DefinedTraceEvent<"revi
2440
3053
  observedAt: string;
2441
3054
  outcome: "not-required";
2442
3055
  patchId: string;
2443
- reason: "docs-only-profile-bypass";
3056
+ reason: "no-applicable-mode";
2444
3057
  reviewCycle: number;
2445
3058
  issue?: number | undefined;
2446
3059
  pr?: number | undefined;
@@ -3179,249 +3792,68 @@ interface FactoryTraceDiagnostic {
3179
3792
  workerId?: string;
3180
3793
  };
3181
3794
  file: string;
3182
- line: number;
3183
- message: string;
3184
- }
3185
- interface ScanFactoryTraceOptions {
3186
- diagnosticsOnly?: boolean;
3187
- filters?: FactoryTraceFilters;
3188
- from: string;
3189
- malformed?: "diagnostic" | "throw";
3190
- repoRoot: string;
3191
- to: string;
3192
- }
3193
- interface ScanFactoryTraceResult {
3194
- diagnostics: FactoryTraceDiagnostic[];
3195
- events: ReadableFactoryTraceEvent[];
3196
- }
3197
- interface ScanFactoryTraceDiagnosticsOptions {
3198
- filters?: FactoryTraceFilters;
3199
- from: string;
3200
- repoRoot: string;
3201
- to: string;
3202
- }
3203
- declare const validateFactoryTraceEvent: (event: unknown) => FactoryTraceEvent;
3204
- declare const toFactoryTraceEnvelope: (event: FactoryTraceEvent) => {
3205
- createdAt: string;
3206
- envelope: 1;
3207
- eventId: string;
3208
- payload: unknown;
3209
- repo: string;
3210
- type: string;
3211
- typeVersion: number;
3212
- issue?: number | undefined;
3213
- pr?: number | undefined;
3214
- thread?: string | undefined;
3215
- worker?: string | undefined;
3216
- };
3217
- interface AppendFactoryTraceEventResult {
3218
- event: FactoryTraceEvent;
3219
- filePath: string;
3220
- mirrorDiagnostics: TraceMirrorDiagnostic[];
3221
- }
3222
- declare const scanFactoryTraceEvents: ({
3223
- diagnosticsOnly,
3224
- filters,
3225
- from,
3226
- malformed,
3227
- repoRoot,
3228
- to
3229
- }: ScanFactoryTraceOptions) => ScanFactoryTraceResult;
3230
- /**
3231
- * Diagnostics-only trace read (#707). Scans the shard window purely to surface
3232
- * malformed / unknown-record diagnostics, without materializing (and then
3233
- * discarding) every well-formed event. Malformed shards are always collected as
3234
- * diagnostics, never thrown. Use when a consumer wants shard-corruption signal
3235
- * but no event payloads — e.g. epic closeout after the orchestrator-metrics
3236
- * removal.
3237
- */
3238
- declare const scanFactoryTraceDiagnostics: ({
3239
- filters,
3240
- from,
3241
- repoRoot,
3242
- to
3243
- }: ScanFactoryTraceDiagnosticsOptions) => FactoryTraceDiagnostic[];
3244
- //#endregion
3245
- //#region src/retro-envelope.d.ts
3246
- /**
3247
- * Versioned retro envelope schema (epic #27 wave 2, issue #34).
3248
- *
3249
- * One envelope per lane, built at `factory:closeout` and delivered through the
3250
- * typed gate-sink as the `retro-envelope` ingest kind. Re-derived in TypeScript
3251
- * from the `spike/telemetry-layer2` S5 scratch schema (reference-only, never
3252
- * merged) and kept structurally parity-compatible with HQ's v1 wire validator
3253
- * (`software-factory-hq/src/contracts/retro-schemas.ts`, PR #32): a complete
3254
- * envelope emitted by this package validates verbatim against HQ's payload
3255
- * validator. This module is the producer-side single source of truth for the
3256
- * v1 wire shape and its bounds ({@link RETRO_ENVELOPE_WIRE_BOUNDS}); the
3257
- * exhaustive per-field/bound parity test in
3258
- * `software-factory-hq/src/retro-envelope-parity.test.ts` (#46) walks this
3259
- * schema against HQ's actual validator so ANY drift — field, type, bound,
3260
- * enum, strictness — fails CI, not just drift a fixture happens to exercise.
3261
- *
3262
- * DESIGN INVARIANT: cross-family token sums must be UNREPRESENTABLE.
3263
- *
3264
- * The two model families use different tokenizers, prices, and accounting
3265
- * conventions, so any token total that spans Claude and GPT is a lie:
3266
- *
3267
- * 1. There is no combined/total token field anywhere in the envelope.
3268
- * 2. `tokenFamilies` is strict — its only keys are `claude` and `gpt`; data
3269
- * cannot smuggle in a third "all"/"combined" slot.
3270
- * 3. The family BLOCKS are structurally different shapes with DIFFERENT keys
3271
- * (claude is a single flat block keyed on freshInput/cacheReadInput/
3272
- * cacheCreationInput; gpt is `{ roles: [...] }`). The exclusive input-tier
3273
- * COUNTS share no key name across families. The residual names shared
3274
- * between the claude block and a gpt ROLE entry are pinned to exactly
3275
- * {costUsd, model, output} (PR #32 advisory): `costUsd` is deliberate —
3276
- * USD is the one cross-family summable unit (rule 4); `model` is an
3277
- * unsummable label; `output` is the same name at DIFFERENT depths (lane
3278
- * block vs per-role entry), frozen by a tripwire test in
3279
- * `retro-envelope.test.ts` so the overlap cannot grow. Renaming `output`
3280
- * is a schemaVersion-2 wire change, deliberately not spent in v1.
3281
- * 4. Cost is per-family USD and nullable. Combined totals are allowed in USD
3282
- * only, and only as a projection-time sum of per-family USD.
3283
- *
3284
- * Field names also encode the S2/S3 reader lessons: Claude `freshInput` alone
3285
- * is not prompt size (true input context = freshInput + cacheReadInput +
3286
- * cacheCreationInput, requestId-deduped), and codex `inputInclusiveOfCache`
3287
- * already includes `cachedInput`, so `freshInputDerived` (inclusive − cached)
3288
- * is the only value safe to feed a per-token pricer.
3289
- *
3290
- * COMPLETENESS POSTURE: harvest may have no usable native log for a lane, so
3291
- * `tokenFamilies` may legitimately be absent. The closeout build gate demands
3292
- * a valid envelope, not available telemetry. A families-absent envelope keeps
3293
- * its operator-visible data gaps and is a replayable advisory HQ event, so it
3294
- * never substitutes unavailable usage with zero. The wire shape (field names,
3295
- * types, structure) stays byte-parity with HQ v1.
3296
- */
3297
- declare const RETRO_ENVELOPE_SCHEMA_VERSION = 1;
3298
- declare const retroEnvelopeV1Schema: z.ZodObject<{
3299
- agentRunId: z.ZodString;
3300
- archiveRef: z.ZodOptional<z.ZodString>;
3301
- cycles: z.ZodObject<{
3302
- gateRunsToFirstGreen: z.ZodNumber;
3303
- reviewerFixRounds: z.ZodNumber;
3304
- thermoFixRounds: z.ZodNumber;
3305
- }, z.core.$strict>;
3306
- dataGaps: z.ZodDefault<z.ZodArray<z.ZodString>>;
3307
- gates: z.ZodArray<z.ZodObject<{
3308
- cycle: z.ZodNumber;
3309
- duration: z.ZodNumber;
3310
- gate: z.ZodString;
3311
- outcome: z.ZodEnum<{
3312
- pass: "pass";
3313
- fail: "fail";
3314
- skip: "skip";
3315
- }>;
3316
- startedAt: z.ZodISODateTime;
3317
- }, z.core.$strict>>;
3318
- generatedAt: z.ZodISODateTime;
3319
- interventions: z.ZodObject<{
3320
- count: z.ZodNumber;
3321
- }, z.core.$strict>;
3322
- joinKeys: z.ZodObject<{
3323
- claudeSessionIds: z.ZodDefault<z.ZodArray<z.ZodString>>;
3324
- codexThreadIds: z.ZodDefault<z.ZodArray<z.ZodString>>;
3325
- }, z.core.$strict>;
3326
- kind: z.ZodLiteral<"retro-envelope">;
3327
- outcome: z.ZodOptional<z.ZodObject<{
3328
- mergeCheck: z.ZodOptional<z.ZodEnum<{
3329
- pass: "pass";
3330
- fail: "fail";
3331
- "not-run": "not-run";
3332
- }>>;
3333
- status: z.ZodEnum<{
3334
- success: "success";
3335
- fail: "fail";
3336
- blocked: "blocked";
3337
- "ship-with-followups": "ship-with-followups";
3338
- }>;
3339
- verdict: z.ZodOptional<z.ZodString>;
3340
- }, z.core.$strict>>;
3341
- phases: z.ZodArray<z.ZodObject<{
3342
- at: z.ZodISODateTime;
3343
- deltaSec: z.ZodOptional<z.ZodNumber>;
3344
- name: z.ZodString;
3345
- }, z.core.$strict>>;
3346
- refs: z.ZodObject<{
3347
- branch: z.ZodOptional<z.ZodString>;
3348
- epic: z.ZodOptional<z.ZodString>;
3349
- headSha: z.ZodOptional<z.ZodString>;
3350
- issue: z.ZodOptional<z.ZodString>;
3351
- prNumber: z.ZodOptional<z.ZodNumber>;
3352
- }, z.core.$strict>;
3353
- repo: z.ZodObject<{
3354
- name: z.ZodString;
3355
- owner: z.ZodString;
3356
- }, z.core.$strict>;
3357
- schemaVersion: z.ZodLiteral<1>;
3358
- tokenFamilies: z.ZodObject<{
3359
- claude: z.ZodOptional<z.ZodObject<{
3360
- cacheCreationInput: z.ZodNumber;
3361
- cacheReadInput: z.ZodNumber;
3362
- costUsd: z.ZodNullable<z.ZodNumber>;
3363
- family: z.ZodLiteral<"claude">;
3364
- freshInput: z.ZodNumber;
3365
- model: z.ZodString;
3366
- output: z.ZodNumber;
3367
- requests: z.ZodNumber;
3368
- }, z.core.$strict>>;
3369
- gpt: z.ZodOptional<z.ZodObject<{
3370
- family: z.ZodLiteral<"gpt">;
3371
- roles: z.ZodArray<z.ZodObject<{
3372
- cachedInput: z.ZodNumber;
3373
- costUsd: z.ZodNullable<z.ZodNumber>;
3374
- freshInputDerived: z.ZodNumber;
3375
- inputInclusiveOfCache: z.ZodNumber;
3376
- model: z.ZodString;
3377
- output: z.ZodNumber;
3378
- reasoningOutput: z.ZodNumber;
3379
- role: z.ZodString;
3380
- threadId: z.ZodOptional<z.ZodString>;
3381
- }, z.core.$strict>>;
3382
- }, z.core.$strict>>;
3383
- }, z.core.$strict>;
3384
- wallClock: z.ZodObject<{
3385
- endTs: z.ZodISODateTime;
3386
- startTs: z.ZodISODateTime;
3387
- totalSec: z.ZodNumber;
3388
- }, z.core.$strict>;
3389
- }, z.core.$strict>;
3390
- type RetroEnvelope = z.infer<typeof retroEnvelopeV1Schema>;
3391
- /**
3392
- * Versioned payload validators, keyed by schema major. Unknown majors never
3393
- * reach these — {@link parseRetroEnvelope} returns them raw and marked
3394
- * degraded, mirroring HQ's ingest skew posture (stored raw, never dropped).
3395
- */
3396
- declare const RETRO_ENVELOPE_VALIDATORS: Record<number, z.ZodType<RetroEnvelope, unknown>>;
3397
- declare const SUPPORTED_RETRO_ENVELOPE_SCHEMA_VERSIONS: readonly number[];
3398
- /** The `schemaVersion` a candidate declares, or undefined when unreadable. */
3399
- declare const retroEnvelopeSchemaVersionOf: (candidate: unknown) => number | undefined;
3400
- type ParsedRetroEnvelope = {
3401
- disposition: "trusted";
3402
- envelope: RetroEnvelope;
3403
- schemaVersion: number;
3404
- } | {
3405
- disposition: "degraded";
3406
- raw: unknown;
3407
- schemaVersion: number | undefined;
3795
+ line: number;
3796
+ message: string;
3797
+ }
3798
+ interface ScanFactoryTraceOptions {
3799
+ diagnosticsOnly?: boolean;
3800
+ filters?: FactoryTraceFilters;
3801
+ from: string;
3802
+ malformed?: "diagnostic" | "throw";
3803
+ repoRoot: string;
3804
+ to: string;
3805
+ }
3806
+ interface ScanFactoryTraceResult {
3807
+ diagnostics: FactoryTraceDiagnostic[];
3808
+ events: ReadableFactoryTraceEvent[];
3809
+ }
3810
+ interface ScanFactoryTraceDiagnosticsOptions {
3811
+ filters?: FactoryTraceFilters;
3812
+ from: string;
3813
+ repoRoot: string;
3814
+ to: string;
3815
+ }
3816
+ declare const validateFactoryTraceEvent: (event: unknown) => FactoryTraceEvent;
3817
+ declare const toFactoryTraceEnvelope: (event: FactoryTraceEvent) => {
3818
+ createdAt: string;
3819
+ envelope: 1;
3820
+ eventId: string;
3821
+ payload: unknown;
3822
+ repo: string;
3823
+ type: string;
3824
+ typeVersion: number;
3825
+ issue?: number | undefined;
3826
+ pr?: number | undefined;
3827
+ thread?: string | undefined;
3828
+ worker?: string | undefined;
3408
3829
  };
3830
+ interface AppendFactoryTraceEventResult {
3831
+ event: FactoryTraceEvent;
3832
+ filePath: string;
3833
+ mirrorDiagnostics: TraceMirrorDiagnostic[];
3834
+ }
3835
+ declare const scanFactoryTraceEvents: ({
3836
+ diagnosticsOnly,
3837
+ filters,
3838
+ from,
3839
+ malformed,
3840
+ repoRoot,
3841
+ to
3842
+ }: ScanFactoryTraceOptions) => ScanFactoryTraceResult;
3409
3843
  /**
3410
- * Parses a candidate against the versioned validators. A recognized major is
3411
- * validated typed-only (throws on an invalid known-version payload); an unknown
3412
- * major is round-tripped RAW and marked degraded the retained payload is the
3413
- * exact input object, never a coerced projection. Mirrors the HQ ingest seam so
3414
- * the two sides agree on the skew posture.
3415
- */
3416
- declare const parseRetroEnvelope: (candidate: unknown) => ParsedRetroEnvelope;
3417
- /**
3418
- * Whether harvest recorded at least one token family. This is an operator
3419
- * summary predicate, not a delivery gate: a families-absent envelope remains
3420
- * valid and replayable when its data gaps explain why telemetry is unavailable.
3844
+ * Diagnostics-only trace read (#707). Scans the shard window purely to surface
3845
+ * malformed / unknown-record diagnostics, without materializing (and then
3846
+ * discarding) every well-formed event. Malformed shards are always collected as
3847
+ * diagnostics, never thrown. Use when a consumer wants shard-corruption signal
3848
+ * but no event payloads e.g. epic closeout after the orchestrator-metrics
3849
+ * removal.
3421
3850
  */
3422
- declare const isRetroEnvelopeWireComplete: (envelope: Pick<RetroEnvelope, "tokenFamilies">) => boolean;
3423
- /** Epic anchor for an envelope: refs.epic, else refs.issue, else the PR. */
3424
- declare const retroEpicReference: (refs: RetroEnvelope["refs"]) => string | undefined;
3851
+ declare const scanFactoryTraceDiagnostics: ({
3852
+ filters,
3853
+ from,
3854
+ repoRoot,
3855
+ to
3856
+ }: ScanFactoryTraceDiagnosticsOptions) => FactoryTraceDiagnostic[];
3425
3857
  //#endregion
3426
3858
  //#region src/interior-telemetry/gate-timing.d.ts
3427
3859
  declare const GATE_TIMING_SCHEMA_VERSION = 1;
@@ -3497,13 +3929,39 @@ declare const isBotLogin: (login?: string | undefined) => boolean;
3497
3929
  declare const SHA_MATCH_MIN_LENGTH = 7;
3498
3930
  declare const sameHeadSha: (left: string, right: string) => boolean;
3499
3931
  //#endregion
3500
- //#region src/doctor.d.ts
3932
+ //#region src/doctor-hq-checks.d.ts
3933
+ /**
3934
+ * Two `doctor` checks that make silent HQ delivery failure loud (#394,
3935
+ * epic #389 wave 2).
3936
+ *
3937
+ * Both consume #414's structured credential resolution rather than inventing
3938
+ * a second classification: a sandboxed session that cannot reach the
3939
+ * keychain is reported as "cannot resolve credentials here", never as
3940
+ * "credentials are wrong" or a silent pass. Both name `psf hq:flush` and
3941
+ * `psf pr:publish` as the commands that need a trusted local session.
3942
+ *
3943
+ * Advisory stays advisory: neither check can block anything but doctor's own
3944
+ * exit status (epic #389 design decision 4). Absent credentials make the
3945
+ * remote check a skip with a printed reason, never a silent pass.
3946
+ */
3501
3947
  type DoctorCheckStatus = "error" | "ok" | "warning";
3502
3948
  interface DoctorCheck {
3503
3949
  message: string;
3504
3950
  name: string;
3505
3951
  status: DoctorCheckStatus;
3506
3952
  }
3953
+ interface HqSpoolCheckDependencies {
3954
+ countSpool?: typeof countHqSpoolWork;
3955
+ sweepOrphans?: typeof sweepHqSpoolOrphans;
3956
+ }
3957
+ interface HqRetroReadbackCheckDependencies {
3958
+ fetch?: typeof fetch;
3959
+ readFile?: typeof readFile;
3960
+ resolve?: SecretReferenceResolver;
3961
+ timeoutMs?: number;
3962
+ }
3963
+ //#endregion
3964
+ //#region src/doctor.d.ts
3507
3965
  interface DoctorReport {
3508
3966
  checks: DoctorCheck[];
3509
3967
  ok: boolean;
@@ -3520,6 +3978,10 @@ interface DoctorProjectProfileInput extends LoadProjectProfileInput {
3520
3978
  /** Diff base for the admission preflight; ignored unless `preflight`. */
3521
3979
  base?: string;
3522
3980
  env?: NodeJS.ProcessEnv;
3981
+ /** Test seam for the local HQ spool check (#394); production never sets this. */
3982
+ hqRetroReadbackDependencies?: HqRetroReadbackCheckDependencies;
3983
+ /** Test seam for the remote HQ retro-envelope read-back check (#394). */
3984
+ hqSpoolDependencies?: HqSpoolCheckDependencies;
3523
3985
  /**
3524
3986
  * Append the read-only admission checklist (#292): every requirement this
3525
3987
  * candidate must satisfy before merge, named in one pass. Preflight checks
@@ -3528,7 +3990,7 @@ interface DoctorProjectProfileInput extends LoadProjectProfileInput {
3528
3990
  preflight?: boolean;
3529
3991
  userConfig?: LoadUserConfigResult;
3530
3992
  }
3531
- declare function doctorProjectProfile(input?: DoctorProjectProfileInput): DoctorReport;
3993
+ declare function doctorProjectProfile(input?: DoctorProjectProfileInput): Promise<DoctorReport>;
3532
3994
  //#endregion
3533
3995
  //#region src/pr-review-gate-trace.d.ts
3534
3996
  interface ReviewGateTraceIdentity {
@@ -3716,17 +4178,100 @@ declare const renderPrBodySections: ({
3716
4178
  reviewProof?: PrReviewProof;
3717
4179
  verifyProof?: PrVerifyProof;
3718
4180
  }) => string;
4181
+ declare namespace boundary_manifest_d_exports {
4182
+ export { BOUNDARY_CLOSEOUT_DEFAULT_RUNG, BOUNDARY_MANIFEST_SCHEMA_VERSION, BoundaryManifest, BoundaryManifestExtraction, BoundaryManifestParse, FACTORY_BOUNDARY_FENCE_LANG, boundaryManifestSchema, closeoutRungFor, extractBoundaryManifestBlock, manifestContentHash, parseBoundaryManifest, rungMeetsMinimum, specContentHash };
4183
+ }
4184
+ declare const FACTORY_BOUNDARY_FENCE_LANG = "factory-boundary";
4185
+ declare const BOUNDARY_MANIFEST_SCHEMA_VERSION = 1;
4186
+ declare const BOUNDARY_CLOSEOUT_DEFAULT_RUNG: EvidenceReviewRung;
4187
+ declare const rungMeetsMinimum: (rung: EvidenceReviewRung, minimum: EvidenceReviewRung) => boolean;
4188
+ declare const boundaryManifestBaseSchema: z.ZodObject<{
4189
+ boundary: z.ZodString;
4190
+ closeout: z.ZodOptional<z.ZodObject<{
4191
+ review: z.ZodEnum<{
4192
+ oracle: "oracle";
4193
+ "independent-model": "independent-model";
4194
+ human: "human";
4195
+ }>;
4196
+ }, z.core.$loose>>;
4197
+ declaredBy: z.ZodString;
4198
+ prs: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
4199
+ schemaVersion: z.ZodLiteral<1>;
4200
+ topology: z.ZodEnum<{
4201
+ flagged: "flagged";
4202
+ "each-to-main": "each-to-main";
4203
+ stacked: "stacked";
4204
+ }>;
4205
+ waves: z.ZodArray<z.ZodObject<{
4206
+ autoMerge: z.ZodOptional<z.ZodBoolean>;
4207
+ issues: z.ZodArray<z.ZodNumber>;
4208
+ name: z.ZodString;
4209
+ review: z.ZodEnum<{
4210
+ oracle: "oracle";
4211
+ "independent-model": "independent-model";
4212
+ human: "human";
4213
+ }>;
4214
+ }, z.core.$loose>>;
4215
+ }, z.core.$loose>;
4216
+ declare const boundaryManifestSchema: z.ZodObject<{
4217
+ boundary: z.ZodString;
4218
+ closeout: z.ZodOptional<z.ZodObject<{
4219
+ review: z.ZodEnum<{
4220
+ oracle: "oracle";
4221
+ "independent-model": "independent-model";
4222
+ human: "human";
4223
+ }>;
4224
+ }, z.core.$loose>>;
4225
+ declaredBy: z.ZodString;
4226
+ prs: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
4227
+ schemaVersion: z.ZodLiteral<1>;
4228
+ topology: z.ZodEnum<{
4229
+ flagged: "flagged";
4230
+ "each-to-main": "each-to-main";
4231
+ stacked: "stacked";
4232
+ }>;
4233
+ waves: z.ZodArray<z.ZodObject<{
4234
+ autoMerge: z.ZodOptional<z.ZodBoolean>;
4235
+ issues: z.ZodArray<z.ZodNumber>;
4236
+ name: z.ZodString;
4237
+ review: z.ZodEnum<{
4238
+ oracle: "oracle";
4239
+ "independent-model": "independent-model";
4240
+ human: "human";
4241
+ }>;
4242
+ }, z.core.$loose>>;
4243
+ }, z.core.$loose>;
4244
+ type BoundaryManifest = z.infer<typeof boundaryManifestBaseSchema>;
4245
+ type BoundaryManifestExtraction = {
4246
+ ok: true;
4247
+ blockText: string;
4248
+ } | {
4249
+ ok: false;
4250
+ error: string;
4251
+ };
4252
+ declare const extractBoundaryManifestBlock: (issueBody: string) => BoundaryManifestExtraction;
4253
+ declare const manifestContentHash: (blockText: string) => string;
4254
+ declare const specContentHash: (issueBody: string) => string;
4255
+ type BoundaryManifestParse = {
4256
+ ok: true;
4257
+ manifest: BoundaryManifest;
4258
+ manifestHash: string;
4259
+ } | {
4260
+ ok: false;
4261
+ error: string;
4262
+ };
4263
+ declare const parseBoundaryManifest: (issueBody: string) => BoundaryManifestParse;
4264
+ declare const closeoutRungFor: (manifest: BoundaryManifest) => EvidenceReviewRung;
3719
4265
  //#endregion
3720
- //#region src/pr-readiness/review-requiredness.d.ts
3721
- interface DocsOnlyReviewBypassState {
3722
- applies: boolean;
4266
+ //#region src/demand-resolution.d.ts
4267
+ /** The boundary-manifest wave demand that applies to one pull request. */
4268
+ interface WaveReviewDemand {
4269
+ autoMerge: boolean;
4270
+ review: EvidenceReviewRung;
4271
+ wave: string;
3723
4272
  }
3724
- declare const resolveDocsOnlyReviewBypassState: (input: {
3725
- classification: DiffClassification;
3726
- docsOnlyReviewBypass?: boolean;
3727
- }) => DocsOnlyReviewBypassState;
3728
4273
  declare namespace readiness_evaluation_d_exports {
3729
- export { CORRECTNESS_UNTYPED_REVIEW_BLOCKER_REASON, DRAFT_BLOCKER_REASON, DocsOnlyReviewBypassState, EvaluationInput, ManagedReadinessLedger, PENDING_CHECKS_BLOCKER_REASON_PREFIX, PreviewDeployProof, PreviewSeedStatus, READINESS_REPAIR_CODES, RENDER_PR_BODY_SECTIONS_BLOCKER_REASON, ReadinessRepair, ReadinessStatus, SCHEMA_VERSION, SECURITY_UNTYPED_REVIEW_BLOCKER_REASON, evaluateReadiness, managedReadinessLedgerSchema, readinessExitCode, readinessRepairSchema, resolveDocsOnlyReviewBypassState, validateManagedReadinessLedger };
4274
+ export { CORRECTNESS_UNTYPED_REVIEW_BLOCKER_REASON, DRAFT_BLOCKER_REASON, EvaluationInput, ManagedReadinessLedger, PENDING_CHECKS_BLOCKER_REASON_PREFIX, PreviewDeployProof, PreviewSeedStatus, READINESS_REPAIR_CODES, RENDER_PR_BODY_SECTIONS_BLOCKER_REASON, ReadinessRepair, ReadinessStatus, SCHEMA_VERSION, SECURITY_UNTYPED_REVIEW_BLOCKER_REASON, evaluateReadiness, managedReadinessLedgerSchema, readinessExitCode, readinessRepairSchema, validateManagedReadinessLedger };
3730
4275
  }
3731
4276
  declare const CORRECTNESS_UNTYPED_REVIEW_BLOCKER_REASON = "Correctness review proof is not a head-bound typed findings verdict; re-run pr:review with a valid findings file.";
3732
4277
  declare const SECURITY_UNTYPED_REVIEW_BLOCKER_REASON = "Security review proof is not a head-bound typed findings verdict; re-run pr:review with a valid findings file.";
@@ -3757,7 +4302,6 @@ interface EvaluationInput {
3757
4302
  verificationProof?: VerificationProofState;
3758
4303
  docsOnlyVerifyBaselineHeadShas?: string[];
3759
4304
  docsOnlySinceReviewProof?: boolean;
3760
- docsOnlyReviewBypass?: boolean;
3761
4305
  externalRequiredChecks?: RequiredCheck[];
3762
4306
  evidenceEnvelopes?: LoadedEvidenceEnvelope[];
3763
4307
  mergeBaseSha?: string;
@@ -3772,8 +4316,13 @@ interface EvaluationInput {
3772
4316
  };
3773
4317
  handledCommentSessionId?: string;
3774
4318
  handledCommentClearedAt?: string;
4319
+ waveReviewDemand?: Pick<WaveReviewDemand, "review" | "wave">;
3775
4320
  }
3776
4321
  declare const evaluateReadiness: (input: EvaluationInput) => {
4322
+ blockedReasons: {
4323
+ code: string;
4324
+ detail: string;
4325
+ }[];
3777
4326
  blockingReasons: string[];
3778
4327
  humanBlockingReasons: string[];
3779
4328
  ledger: {
@@ -3801,14 +4350,14 @@ declare const evaluateReadiness: (input: EvaluationInput) => {
3801
4350
  reviews: {
3802
4351
  correctness: {
3803
4352
  required: boolean;
3804
- status: "blocked" | "not-required" | "current" | "stale" | "missing";
4353
+ status: "blocked" | "not-required" | "stale" | "current" | "missing";
3805
4354
  docsOnlyDeltaAccepted?: boolean | undefined;
3806
4355
  reviewedHeadSha?: string | undefined;
3807
4356
  reviewedPatchId?: string | undefined;
3808
4357
  };
3809
4358
  security?: {
3810
4359
  required: boolean;
3811
- status: "blocked" | "not-required" | "current" | "stale" | "missing";
4360
+ status: "blocked" | "not-required" | "stale" | "current" | "missing";
3812
4361
  docsOnlyDeltaAccepted?: boolean | undefined;
3813
4362
  reviewedHeadSha?: string | undefined;
3814
4363
  reviewedPatchId?: string | undefined;
@@ -3818,7 +4367,7 @@ declare const evaluateReadiness: (input: EvaluationInput) => {
3818
4367
  stackRole: "slice" | "single" | "rollup" | "merge-gate prerequisite";
3819
4368
  verification: {
3820
4369
  command: "patronage-factory pr:verify";
3821
- prVerify: "passed" | "stale" | "missing";
4370
+ prVerify: "stale" | "passed" | "missing";
3822
4371
  docsOnlyDeltaAccepted?: boolean | undefined;
3823
4372
  docsOnlyVerifiedHeadSha?: string | undefined;
3824
4373
  trivialDeltaAccepted?: boolean | undefined;
@@ -4104,6 +4653,7 @@ type FetchLike = (input: string, init?: {
4104
4653
  headers?: Record<string, string>;
4105
4654
  method?: string;
4106
4655
  redirect?: "error";
4656
+ signal?: AbortSignal;
4107
4657
  }) => Promise<{
4108
4658
  json: () => Promise<unknown>;
4109
4659
  status: number;
@@ -4118,6 +4668,14 @@ interface PublishEpicStructureArgs {
4118
4668
  event: EpicStructureEvent;
4119
4669
  url: string;
4120
4670
  fetchImpl?: FetchLike;
4671
+ /**
4672
+ * Optional caller-owned abort signal. A caller that bounds this call with
4673
+ * its own deadline (e.g. `factory:closeout`'s advisory re-emission, #392)
4674
+ * can abort the in-flight request itself instead of merely abandoning the
4675
+ * `await` — leaving the outbound socket alive past the caller's own
4676
+ * declared timeout.
4677
+ */
4678
+ signal?: AbortSignal;
4121
4679
  }
4122
4680
  interface PublishEpicStructureResult {
4123
4681
  duplicate: boolean;
@@ -4134,90 +4692,6 @@ declare const isProductionHqUrl: (url: string) => boolean;
4134
4692
  * surfaced with actionable guidance; any non-2xx throws.
4135
4693
  */
4136
4694
  declare const publishEpicStructure: (args: PublishEpicStructureArgs) => Promise<PublishEpicStructureResult>;
4137
- declare namespace boundary_manifest_d_exports {
4138
- export { BOUNDARY_CLOSEOUT_DEFAULT_RUNG, BOUNDARY_MANIFEST_SCHEMA_VERSION, BoundaryManifest, BoundaryManifestExtraction, BoundaryManifestParse, FACTORY_BOUNDARY_FENCE_LANG, boundaryManifestSchema, closeoutRungFor, extractBoundaryManifestBlock, manifestContentHash, parseBoundaryManifest, rungMeetsMinimum, specContentHash };
4139
- }
4140
- declare const FACTORY_BOUNDARY_FENCE_LANG = "factory-boundary";
4141
- declare const BOUNDARY_MANIFEST_SCHEMA_VERSION = 1;
4142
- declare const BOUNDARY_CLOSEOUT_DEFAULT_RUNG: EvidenceReviewRung;
4143
- declare const rungMeetsMinimum: (rung: EvidenceReviewRung, minimum: EvidenceReviewRung) => boolean;
4144
- declare const boundaryManifestBaseSchema: z.ZodObject<{
4145
- boundary: z.ZodString;
4146
- closeout: z.ZodOptional<z.ZodObject<{
4147
- review: z.ZodEnum<{
4148
- oracle: "oracle";
4149
- "independent-model": "independent-model";
4150
- human: "human";
4151
- }>;
4152
- }, z.core.$loose>>;
4153
- declaredBy: z.ZodString;
4154
- prs: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
4155
- schemaVersion: z.ZodLiteral<1>;
4156
- topology: z.ZodEnum<{
4157
- flagged: "flagged";
4158
- "each-to-main": "each-to-main";
4159
- stacked: "stacked";
4160
- }>;
4161
- waves: z.ZodArray<z.ZodObject<{
4162
- autoMerge: z.ZodOptional<z.ZodBoolean>;
4163
- issues: z.ZodArray<z.ZodNumber>;
4164
- name: z.ZodString;
4165
- review: z.ZodEnum<{
4166
- oracle: "oracle";
4167
- "independent-model": "independent-model";
4168
- human: "human";
4169
- }>;
4170
- }, z.core.$loose>>;
4171
- }, z.core.$loose>;
4172
- declare const boundaryManifestSchema: z.ZodObject<{
4173
- boundary: z.ZodString;
4174
- closeout: z.ZodOptional<z.ZodObject<{
4175
- review: z.ZodEnum<{
4176
- oracle: "oracle";
4177
- "independent-model": "independent-model";
4178
- human: "human";
4179
- }>;
4180
- }, z.core.$loose>>;
4181
- declaredBy: z.ZodString;
4182
- prs: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
4183
- schemaVersion: z.ZodLiteral<1>;
4184
- topology: z.ZodEnum<{
4185
- flagged: "flagged";
4186
- "each-to-main": "each-to-main";
4187
- stacked: "stacked";
4188
- }>;
4189
- waves: z.ZodArray<z.ZodObject<{
4190
- autoMerge: z.ZodOptional<z.ZodBoolean>;
4191
- issues: z.ZodArray<z.ZodNumber>;
4192
- name: z.ZodString;
4193
- review: z.ZodEnum<{
4194
- oracle: "oracle";
4195
- "independent-model": "independent-model";
4196
- human: "human";
4197
- }>;
4198
- }, z.core.$loose>>;
4199
- }, z.core.$loose>;
4200
- type BoundaryManifest = z.infer<typeof boundaryManifestBaseSchema>;
4201
- type BoundaryManifestExtraction = {
4202
- ok: true;
4203
- blockText: string;
4204
- } | {
4205
- ok: false;
4206
- error: string;
4207
- };
4208
- declare const extractBoundaryManifestBlock: (issueBody: string) => BoundaryManifestExtraction;
4209
- declare const manifestContentHash: (blockText: string) => string;
4210
- declare const specContentHash: (issueBody: string) => string;
4211
- type BoundaryManifestParse = {
4212
- ok: true;
4213
- manifest: BoundaryManifest;
4214
- manifestHash: string;
4215
- } | {
4216
- ok: false;
4217
- error: string;
4218
- };
4219
- declare const parseBoundaryManifest: (issueBody: string) => BoundaryManifestParse;
4220
- declare const closeoutRungFor: (manifest: BoundaryManifest) => EvidenceReviewRung;
4221
4695
  //#endregion
4222
4696
  //#region src/review-focus.d.ts
4223
4697
  declare const REVIEW_FOCUS_SECTION = "Review focus";
@@ -4323,7 +4797,7 @@ declare function assertWorkerCheckoutAllowed({
4323
4797
  interface CreateProgramOptions {
4324
4798
  actions?: {
4325
4799
  boundaryCheck?: BoundaryCheckAction;
4326
- canaryVerify?: CanaryVerifyAction;
4800
+ demandWaive?: DemandWaiveAction;
4327
4801
  prMergeCheck?: PrMergeCheckAction;
4328
4802
  prPublish?: PrPublishAction;
4329
4803
  prReady?: PrReadyAction;
@@ -4335,4 +4809,4 @@ interface CreateProgramOptions {
4335
4809
  declare function createProgram(options?: CreateProgramOptions): Command;
4336
4810
  declare function run(argv?: string[]): Promise<void>;
4337
4811
  //#endregion
4338
- export { type AppendFactoryTraceEventResult, type AssembledReviewPrompt, type BoundaryCheckArgs, type BoundaryCheckDependencies, type BoundaryCheckProofRecord, type BuildEpicStructureEventInput, type BuildRetroEnvelopeInput, type CloudflareAccessServiceToken, CreateProgramOptions, DEFAULT_FACTORY_REPOSITORY, type DagDocument, EPIC_STRUCTURE_NODE_STATUSES, EPIC_STRUCTURE_SCHEMA_VERSION, type EpicStructureEvent, type EpicStructureGraphPayload, type EpicStructureNodeStatus, EpicStructureValidationError, type EvidenceEmitArgs, type EvidenceEmitDependencies, type EvidenceEmitResult, type FactoryCliInvocation, FactoryCliInvocationSchema, type FactoryProjectProfile, type FactoryTraceDiagnostic, type FactoryTraceEnvelope, type FactoryTraceEvent, type FindingDisposition, type FindingLedgerEntry, type FollowUpAction, FollowUpActionSchema, type IssueReviewFocus, type LadderDispositionDeclaration, type ParsedRetroEnvelope, type PrPublishArgs, type PrPublishDependencies, PrPublishFollowUpError, type PrPublishFollowUpOutcome, type PrPublishHandoff, type PrPublishResult, type PrReadyArgs, type PrReadyProof, type PublishEpicStructureArgs, type PublishEpicStructureResult, type PublishFollowUpPlan, RETRO_ENVELOPE_SCHEMA_VERSION, RETRO_ENVELOPE_VALIDATORS, REVIEW_FOCUS_SECTION, type RetroEnvelope, type ReviewGateNotRequiredProof, type ReviewGateTraceIdentity, type ReviewLadderCycle, type ReviewLadderEvaluation, type ReviewLadderPolicy, type ReviewLadderStageEvent, type ReviewLadderTraceIdentity, type ReviewPromptSection, type ReviewPromptSectionProvenance, SUPPORTED_RETRO_ENVELOPE_SCHEMA_VERSIONS, type ScanFactoryTraceDiagnosticsOptions, type ScanFactoryTraceOptions, type ScanFactoryTraceResult, type TraceMirrorDiagnostic, type TraceSink, type TraceWriteResult, type TraceWriteSinks, type VerificationReuse, WorkerCheckoutGuardError, type WorkerCloseoutLessonsTraceEvent, appendReviewLadderStageTraceEvent, appendWithTraceSinks, assembleReviewPrompt, assertWorkerCheckoutAllowed, blockingLadderFindings, boundary_manifest_d_exports as boundaryManifest, boundary_review_proof_d_exports as boundaryReviewProof, buildEpicStructureEvent, buildEpicStructurePayload, buildEvidenceEnvelope, buildRetroEnvelope, buildReviewGateNotRequiredTraceEvent, buildReviewLadderStageTraceEvent, comment_provenance_d_exports as commentProvenance, createLocalJsonlTraceSink, createProgram, doctorProjectProfile, epicStructureEventId, evaluateReviewLadder, evidenceEnvelopeFilename, findingKey, followUpFromArgv, inferFixedInThreadDispositions, isProductionHqUrl, isRetroEnvelopeWireComplete, loadProjectProfile, normalizeIssueComments, openLadderFindings, parseRetroEnvelope, planPublishFollowUp, worktree_held_branch_d_exports as prMergePreflight, pr_body_metadata_d_exports as prReadinessBodyMetadata, readiness_evaluation_d_exports as prReadinessEvaluation, external_evidence_d_exports as prReadinessExternalEvidence, merge_identity_d_exports as prReadinessMergeIdentity, post_readiness_comments_d_exports as prReadinessPostComments, pr_body_renderer_d_exports as prReadinessPrBodyRenderer, proof_identity_d_exports as prReadinessProofIdentity, review_proof_d_exports as prReadinessReviewProof, status_check_rollup_d_exports as prReadinessStatusChecks, verification_proof_d_exports as prReadinessVerificationProof, publishEpicStructure, readPrMergeCheckProof, readPrReadyProof, readPrReviewProof, resolveDocsOnlyReviewBypass, resolveFactoryRepository, resolveFindingBlocking, resolveReviewFindingCategory, resolveReviewFindingSeverity, resolveReviewLadderPolicy, resolveTraceWriteSinks, retroEnvelopeSchemaVersionOf, retroEnvelopeV1Schema, retroEpicReference, reviewCycleStateFor, reviewFocusFromIssueBody, reviewPromptSectionSchema, reviewPromptSectionsSchema, run, runBoundaryCheck, runEvidenceEmit, runPrMergeCheck, runPrPublish, runPrReady, runPrReview, runPrVerify, scanFactoryTraceDiagnostics, scanFactoryTraceEvents, staleRepeatLadderFindings, toFactoryTraceEnvelope, tryAppendReviewGateNotRequiredTraceEvent, tryAppendReviewLadderStageTraceEvent, validateBoundaryCheckProof, validateDagDocument, validateFactoryTraceEvent, validatePrMergeCheckProof, validatePrReadyProof, validatePrReviewProof, validatePrVerifyProof, worktree_scratch_files_d_exports as worktreeScratchFiles };
4812
+ export { type AppendFactoryTraceEventResult, type AssembledReviewPrompt, type BoundaryCheckArgs, type BoundaryCheckDependencies, type BoundaryCheckProofRecord, type BuildEpicStructureEventInput, type BuildRetroEnvelopeInput, type CloudflareAccessServiceToken, CreateProgramOptions, DEFAULT_DEMAND_WAIVER_PATH, DEFAULT_FACTORY_REPOSITORY, type DagDocument, type DemandWaiveArgs, type DemandWaiveDependencies, DemandWaiveRefusalError, type DemandWaiver, type DemandWaiverStore, EPIC_STRUCTURE_NODE_STATUSES, EPIC_STRUCTURE_SCHEMA_VERSION, type EpicStructureEvent, type EpicStructureGraphPayload, type EpicStructureNodeStatus, EpicStructureValidationError, type EvidenceEmitArgs, type EvidenceEmitDependencies, type EvidenceEmitResult, type FactoryCliInvocation, FactoryCliInvocationSchema, type FactoryProjectProfile, type FactoryTraceDiagnostic, type FactoryTraceEnvelope, type FactoryTraceEvent, type FindingDisposition, type FindingLedgerEntry, type FollowUpAction, FollowUpActionSchema, type IssueReviewFocus, type LadderDispositionDeclaration, MERGE_FREEZE_APP_SLUG, MERGE_FREEZE_CHECK_NAME, type MergeFreezeState, type ParsedRetroEnvelope, type PrPublishArgs, type PrPublishDependencies, PrPublishFollowUpError, type PrPublishFollowUpOutcome, type PrPublishHandoff, type PrPublishResult, type PrReadyArgs, type PrReadyProof, type PublishEpicStructureArgs, type PublishEpicStructureResult, type PublishFollowUpPlan, RETRO_ENVELOPE_SCHEMA_VERSION, RETRO_ENVELOPE_VALIDATORS, REVIEW_FOCUS_SECTION, type RetroEnvelope, type ReviewGateNotRequiredProof, type ReviewGateTraceIdentity, type ReviewLadderCycle, type ReviewLadderEvaluation, type ReviewLadderPolicy, type ReviewLadderStageEvent, type ReviewLadderTraceIdentity, type ReviewPromptSection, type ReviewPromptSectionProvenance, SUPPORTED_RETRO_ENVELOPE_SCHEMA_VERSIONS, type ScanFactoryTraceDiagnosticsOptions, type ScanFactoryTraceOptions, type ScanFactoryTraceResult, type TraceMirrorDiagnostic, type TraceSink, type TraceWriteResult, type TraceWriteSinks, type VerificationReuse, type WaivedDemand, WorkerCheckoutGuardError, type WorkerCloseoutLessonsTraceEvent, appendReviewLadderStageTraceEvent, appendWithTraceSinks, applyDemandWaiver, assembleReviewPrompt, assertWorkerCheckoutAllowed, authorizeDemandWaiver, blockingLadderFindings, boundary_manifest_d_exports as boundaryManifest, boundary_review_proof_d_exports as boundaryReviewProof, buildEpicStructureEvent, buildEpicStructurePayload, buildEvidenceEnvelope, buildRetroEnvelope, buildReviewGateNotRequiredTraceEvent, buildReviewLadderStageTraceEvent, comment_provenance_d_exports as commentProvenance, createLocalJsonlTraceSink, createProgram, doctorProjectProfile, epicStructureEventId, evaluateReviewLadder, evidenceEnvelopeFilename, findingKey, followUpFromArgv, inferFixedInThreadDispositions, isProductionHqUrl, isRetroEnvelopeWireComplete, loadProjectProfile, normalizeIssueComments, openLadderFindings, parseRetroEnvelope, planPublishFollowUp, worktree_held_branch_d_exports as prMergePreflight, pr_body_metadata_d_exports as prReadinessBodyMetadata, readiness_evaluation_d_exports as prReadinessEvaluation, external_evidence_d_exports as prReadinessExternalEvidence, merge_identity_d_exports as prReadinessMergeIdentity, post_readiness_comments_d_exports as prReadinessPostComments, pr_body_renderer_d_exports as prReadinessPrBodyRenderer, proof_identity_d_exports as prReadinessProofIdentity, review_proof_d_exports as prReadinessReviewProof, status_check_rollup_d_exports as prReadinessStatusChecks, verification_proof_d_exports as prReadinessVerificationProof, publishEpicStructure, readDemandWaivers, readPrMergeCheckProof, readPrReadyProof, readPrReviewProof, resolveFactoryRepository, resolveFindingBlocking, resolveReviewFindingCategory, resolveReviewFindingSeverity, resolveReviewLadderPolicy, resolveTraceWriteSinks, retroEnvelopeSchemaVersionOf, retroEnvelopeV1Schema, retroEpicReference, reviewCycleStateFor, reviewFocusFromIssueBody, reviewPromptSectionSchema, reviewPromptSectionsSchema, run, runBoundaryCheck, runDemandWaive, runEvidenceEmit, runPrMergeCheck, runPrPublish, runPrReady, runPrReview, runPrVerify, scanFactoryTraceDiagnostics, scanFactoryTraceEvents, selectWaiversForCandidate, staleRepeatLadderFindings, toFactoryTraceEnvelope, tryAppendReviewGateNotRequiredTraceEvent, tryAppendReviewLadderStageTraceEvent, validateBoundaryCheckProof, validateDagDocument, validateDemandWaiverStore, validateFactoryTraceEvent, validateMergeFreezeState, validatePrMergeCheckProof, validatePrReadyProof, validatePrReviewProof, validatePrVerifyProof, waivedDemandNotice, waivedDemandSchema, worktree_scratch_files_d_exports as worktreeScratchFiles };