@patronage/software-factory 1.0.0-alpha.36 → 1.0.0-alpha.38

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,9 +1,112 @@
1
1
  import { Command } from "commander";
2
- import { z } from "zod";
3
2
  import { existsSync, readFileSync, rmSync } from "node:fs";
3
+ import { z } from "zod";
4
4
  import { PreviewProofRegistration } from "@patronage/factory-ci";
5
5
  import { JsonValue, Questions, SystemOneRequest, choice, noul, score } from "@typesafe-ai/sdk";
6
6
 
7
+ //#region src/factory-app-broker-client.d.ts
8
+ /**
9
+ * The opt-in broker permission set (#1360): `pull_requests: write`,
10
+ * `issues: read`, and `contents: read` on the requesting repository. The
11
+ * read-only contents grant is there because GitHub's pull request create
12
+ * reads the head and base refs (#1327, #1375). The CLI asks for the scope by
13
+ * name and the broker decides its permissions. A request without a scope is
14
+ * the `checks: write` mint, unchanged.
15
+ */
16
+ declare const FACTORY_APP_BROKER_ADMISSION_SCOPE = "admission";
17
+ //#endregion
18
+ //#region src/user-config.d.ts
19
+ declare const githubAppConfigSchema: z.ZodObject<{
20
+ appId: z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>;
21
+ installationId: z.ZodOptional<z.ZodNumber>;
22
+ privateKeyPath: z.ZodString;
23
+ }, z.core.$strip>;
24
+ type GithubAppConfig = z.infer<typeof githubAppConfigSchema>;
25
+ declare const factoryUserConfigSchema: z.ZodObject<{
26
+ githubApp: z.ZodOptional<z.ZodObject<{
27
+ appId: z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>;
28
+ installationId: z.ZodOptional<z.ZodNumber>;
29
+ privateKeyPath: z.ZodString;
30
+ }, z.core.$strip>>;
31
+ hqAllowedOrigins: z.ZodOptional<z.ZodArray<z.ZodString>>;
32
+ hqIngestCredentials: z.ZodOptional<z.ZodObject<{
33
+ clientIdRef: z.ZodString;
34
+ clientSecretRef: z.ZodString;
35
+ }, z.core.$strip>>;
36
+ schemaVersion: z.ZodLiteral<2>;
37
+ }, z.core.$strip>;
38
+ type FactoryUserConfig = z.infer<typeof factoryUserConfigSchema>;
39
+ interface LoadUserConfigResult {
40
+ path: string;
41
+ config: FactoryUserConfig;
42
+ ignoredKeys?: string[];
43
+ }
44
+ //#endregion
45
+ //#region src/github-app-access.d.ts
46
+ interface FactoryAppAccessDependencies {
47
+ env?: NodeJS.ProcessEnv;
48
+ existsSync?: (path: string) => boolean;
49
+ exchangeBroker?: (input: {
50
+ fetch?: typeof fetch;
51
+ oidc: string;
52
+ owner: string;
53
+ repo: string;
54
+ scope?: typeof FACTORY_APP_BROKER_ADMISSION_SCOPE;
55
+ timeoutMs?: number;
56
+ url?: string;
57
+ }) => Promise<string>;
58
+ fetch?: typeof fetch;
59
+ githubApp?: GithubAppConfig;
60
+ mintCursorOidc?: (input: {
61
+ audience?: string;
62
+ signal?: AbortSignal;
63
+ socketPath?: string;
64
+ timeoutMs?: number;
65
+ }) => Promise<string>;
66
+ now?: () => number;
67
+ /**
68
+ * Cancels the Cursor OIDC mint (#1322). Fetch-based calls are cancelled
69
+ * through the `fetch` the caller passes, which joins its own signal.
70
+ */
71
+ signal?: AbortSignal;
72
+ timeoutMs?: number;
73
+ token?: string;
74
+ transportAttempts?: number;
75
+ }
76
+ //#endregion
77
+ //#region src/admission-credential.d.ts
78
+ /**
79
+ * The GitHub calls a Cursor VM's own `gh` credential cannot make (#1327).
80
+ * Cursor mints the VM token without `pull_requests: write` or `issues`, so on
81
+ * a Cursor managed runtime these, and only these, run on the broker's
82
+ * `admission` token (#1360): `pull_requests: write`, `issues: read`, and
83
+ * `contents: read`, which creating a pull request needs because GitHub reads
84
+ * the head and base refs (#1375). Push,
85
+ * marking ready, arming auto-merge, check runs, and every other call keep
86
+ * their own credential.
87
+ */
88
+ type AdmissionOperation = "create-pull-request" | "edit-pull-request-body" | "read-issue" | "read-pull-request-for-status-hud";
89
+ /** The one repository the admission token is minted for. */
90
+ interface AdmissionRepository {
91
+ owner: string;
92
+ repo: string;
93
+ }
94
+ interface AdmissionCredential {
95
+ /**
96
+ * The environment overlay for ONE `gh` subprocess that performs
97
+ * `operation`. `undefined` off a Cursor managed runtime: nothing is minted
98
+ * and the subprocess environment is untouched. On one, `{ GH_TOKEN }` with
99
+ * the admission token. Throws {@link AdmissionCredentialError} when the mint
100
+ * fails.
101
+ */
102
+ ghEnv: (operation: AdmissionOperation, repository: AdmissionRepository) => Promise<Readonly<Record<string, string>> | undefined>;
103
+ /**
104
+ * The same token for one direct REST request, or `undefined` off a Cursor
105
+ * managed runtime.
106
+ */
107
+ token: (operation: AdmissionOperation, repository: AdmissionRepository) => Promise<string | undefined>;
108
+ }
109
+ //#endregion
7
110
  //#region src/review-rungs.d.ts
8
111
  declare const EVIDENCE_REVIEW_RUNGS: readonly ["independent-model", "oracle", "human"];
9
112
  type EvidenceReviewRung = (typeof EVIDENCE_REVIEW_RUNGS)[number];
@@ -332,6 +435,32 @@ declare const authorizeDemandWaiver: ({
332
435
  session: string | undefined;
333
436
  }) => AuthorizeDemandWaiverResult;
334
437
  //#endregion
438
+ //#region src/hq-credentials.d.ts
439
+ /**
440
+ * Why a reference did not resolve. Each value names a different remedy, and
441
+ * none of them can be inferred from a value-or-nothing result:
442
+ *
443
+ * - `resolver-missing` — no secret-manager binary on PATH.
444
+ * - `resolver-blocked` — a binary exists but this session may not execute it
445
+ * (a sandbox denying exec). The command belongs outside the sandbox.
446
+ * - `resolver-timeout` — the probe expired: a desktop agent waiting on an
447
+ * approval nobody can give here.
448
+ * - `resolver-refused` — the binary ran and produced no value. Its store is
449
+ * unreachable from this session (a sandbox with no keychain access) or the
450
+ * reference is not readable. These two stay one status on purpose: telling
451
+ * them apart would mean reading resolver output.
452
+ */
453
+ type SecretResolutionFailure = "resolver-blocked" | "resolver-missing" | "resolver-refused" | "resolver-timeout";
454
+ /** A structured resolution outcome. The value travels only when resolved. */
455
+ type SecretResolution = {
456
+ status: "resolved";
457
+ value: string;
458
+ } | {
459
+ status: SecretResolutionFailure;
460
+ };
461
+ /** Resolves a secret reference. Injected so tests stay offline. */
462
+ type SecretReferenceResolver = (reference: string) => SecretResolution;
463
+ //#endregion
335
464
  //#region src/hq-ingest-sink.d.ts
336
465
  interface HqIngestDependencies {
337
466
  /**
@@ -344,6 +473,12 @@ interface HqIngestDependencies {
344
473
  fetch?: typeof fetch;
345
474
  now?: () => Date;
346
475
  randomUUID?: () => string;
476
+ /**
477
+ * Resolves one `hqIngestCredentials` reference on an operator machine
478
+ * (#1313). Injected so tests stay offline; production asks `op-fast`, then
479
+ * `op`.
480
+ */
481
+ resolveSecret?: SecretReferenceResolver;
347
482
  /**
348
483
  * Ceiling for persisting a failure after the transport leg (default
349
484
  * `HQ_JOURNAL_FLUSH_BUDGET_MS`). Injectable for the same reason as
@@ -458,6 +593,12 @@ interface HqSpoolWorkCount {
458
593
  oldestQueuedAt?: string;
459
594
  /** Spooled events waiting in the locations below. */
460
595
  pending: number;
596
+ /**
597
+ * How many pending events carry each recorded spool reason (#1317). Present
598
+ * only when the caller asked for `reasons`. An event whose file could not be
599
+ * read or parsed within budget is counted in `pending` and absent here.
600
+ */
601
+ reasons?: Record<string, number>;
461
602
  /** Locations that exist and hold spooled work. */
462
603
  spools: string[];
463
604
  /**
@@ -477,7 +618,9 @@ interface HqSpoolWorkCount {
477
618
  * same locations `flushHqSpool` drains are inspected here, read-only: no
478
619
  * directory is created, nothing is secured, and nothing is delivered.
479
620
  */
480
- declare function countHqSpoolWork(input: Pick<HqSpoolFlushInput, "explicitDirectories" | "repository">, dependencies?: {
621
+ declare function countHqSpoolWork(input: Pick<HqSpoolFlushInput, "explicitDirectories" | "repository"> & {
622
+ /** Also read each pending event's recorded reason (#1317). */reasons?: boolean;
623
+ }, dependencies?: {
481
624
  budgetMs?: number;
482
625
  env?: NodeJS.ProcessEnv;
483
626
  }): Promise<HqSpoolWorkCount>;
@@ -948,10 +1091,18 @@ declare const blockedReasonSchema: z.ZodObject<{
948
1091
  type BlockedReason = z.infer<typeof blockedReasonSchema>;
949
1092
  //#endregion
950
1093
  //#region src/arm-auto-merge.d.ts
1094
+ /**
1095
+ * How GitHub merges the candidate. `pr:ready` chooses `merge` for a
1096
+ * recognized faithful catch-up merge (ADR 0028), because a squash drops the
1097
+ * second parent and the target loses the merged base as an ancestor (#829).
1098
+ * Every other candidate is squashed.
1099
+ */
1100
+ type AutoMergeMethod = "merge" | "squash";
951
1101
  interface ArmAutoMergeInput {
952
1102
  cwd: string;
953
1103
  /** The validated PR head this arming is a compare-and-set against. */
954
1104
  headSha: string;
1105
+ mergeMethod: AutoMergeMethod;
955
1106
  owner: string;
956
1107
  pr: number;
957
1108
  repo: string;
@@ -1242,58 +1393,6 @@ declare const followUpFromArgv: (argv: string[]) => FollowUpAction;
1242
1393
  /** Canonical zod schema for the optional follow-up field on JSON outputs. */
1243
1394
  declare const FollowUpActionSchema: z.ZodType<FollowUpAction>;
1244
1395
  //#endregion
1245
- //#region src/user-config.d.ts
1246
- declare const githubAppConfigSchema: z.ZodObject<{
1247
- appId: z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>;
1248
- installationId: z.ZodOptional<z.ZodNumber>;
1249
- privateKeyPath: z.ZodString;
1250
- }, z.core.$strip>;
1251
- type GithubAppConfig = z.infer<typeof githubAppConfigSchema>;
1252
- declare const factoryUserConfigSchema: z.ZodObject<{
1253
- githubApp: z.ZodOptional<z.ZodObject<{
1254
- appId: z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>;
1255
- installationId: z.ZodOptional<z.ZodNumber>;
1256
- privateKeyPath: z.ZodString;
1257
- }, z.core.$strip>>;
1258
- hqAllowedOrigins: z.ZodOptional<z.ZodArray<z.ZodString>>;
1259
- hqIngestCredentials: z.ZodOptional<z.ZodObject<{
1260
- clientIdRef: z.ZodString;
1261
- clientSecretRef: z.ZodString;
1262
- }, z.core.$strip>>;
1263
- schemaVersion: z.ZodLiteral<2>;
1264
- }, z.core.$strip>;
1265
- type FactoryUserConfig = z.infer<typeof factoryUserConfigSchema>;
1266
- interface LoadUserConfigResult {
1267
- path: string;
1268
- config: FactoryUserConfig;
1269
- ignoredKeys?: string[];
1270
- }
1271
- //#endregion
1272
- //#region src/github-app-access.d.ts
1273
- interface FactoryAppAccessDependencies {
1274
- env?: NodeJS.ProcessEnv;
1275
- existsSync?: (path: string) => boolean;
1276
- exchangeBroker?: (input: {
1277
- fetch?: typeof fetch;
1278
- oidc: string;
1279
- owner: string;
1280
- repo: string;
1281
- timeoutMs?: number;
1282
- url?: string;
1283
- }) => Promise<string>;
1284
- fetch?: typeof fetch;
1285
- githubApp?: GithubAppConfig;
1286
- mintCursorOidc?: (input: {
1287
- audience?: string;
1288
- socketPath?: string;
1289
- timeoutMs?: number;
1290
- }) => Promise<string>;
1291
- now?: () => number;
1292
- timeoutMs?: number;
1293
- token?: string;
1294
- transportAttempts?: number;
1295
- }
1296
- //#endregion
1297
1396
  //#region src/pr-verify-mode.d.ts
1298
1397
  /**
1299
1398
  * The verification mode `pr:verify` resolved for a run.
@@ -1539,6 +1638,7 @@ type PostCommitStatus = (input: PostCommitStatusInput) => void;
1539
1638
  //#region src/github-check-runs.d.ts
1540
1639
  declare const FACTORY_CHECK_NAMES: {
1541
1640
  readonly "pr-ready": "patronage-factory/pr-ready";
1641
+ readonly "pr-review": "patronage-factory/pr-review";
1542
1642
  readonly "pr-verify": "patronage-factory/pr-verify";
1543
1643
  };
1544
1644
  type FactoryCheckGate = keyof typeof FACTORY_CHECK_NAMES;
@@ -1562,11 +1662,12 @@ interface PublishFactoryCheckInput {
1562
1662
  }
1563
1663
  interface CheckRunDependencies extends FactoryAppAccessDependencies {
1564
1664
  postCommitStatus?: PostCommitStatus;
1565
- resolveDetailsUrl?: (input: PublishFactoryCheckInput) => Promise<string> | string;
1665
+ resolveDetailsUrl?: (input: PublishFactoryCheckInput, signal?: AbortSignal) => Promise<string> | string;
1566
1666
  /**
1567
1667
  * Bounded retry for the check-run POST. GitHub answers 422 for a head SHA it
1568
1668
  * has not seen yet, which is the normal state when `pr:verify` runs before
1569
- * the branch is pushed, and is also briefly true right after a push.
1669
+ * the branch is pushed, and is also briefly true right after a push. Only
1670
+ * that answer is retried (`isRetryableCheckRunFailure`).
1570
1671
  */
1571
1672
  retry?: {
1572
1673
  attempts: number;
@@ -1651,6 +1752,15 @@ declare function fetchPrVerifyProofBinding({
1651
1752
  sha: string;
1652
1753
  runJson?: GhJsonRunner;
1653
1754
  }): PrVerifyProofBinding | undefined;
1755
+ /**
1756
+ * Where rules 2 and 4 read from: the App id to trust and the `gh api` runner.
1757
+ * Both default to production (the user config's App id else the pinned one,
1758
+ * and `runGhJson`); tests state them.
1759
+ */
1760
+ interface ExactHeadRunSource {
1761
+ appId?: () => string;
1762
+ runJson?: GhJsonRunner;
1763
+ }
1654
1764
  //#endregion
1655
1765
  //#region src/merge-freeze.d.ts
1656
1766
  declare const MERGE_FREEZE_CHECK_NAME = "patronage-factory/merge-freeze";
@@ -1660,8 +1770,8 @@ declare const mergeFreezeStateSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
1660
1770
  generationId: z.ZodNumber;
1661
1771
  headSha: z.ZodString;
1662
1772
  outcome: z.ZodEnum<{
1663
- stale: "stale";
1664
1773
  active: "active";
1774
+ stale: "stale";
1665
1775
  }>;
1666
1776
  reason: z.ZodString;
1667
1777
  recordedAt: z.ZodISODateTime;
@@ -1950,9 +2060,9 @@ declare const managedReadinessLedgerSchema: z.ZodObject<{
1950
2060
  reviewedPatchId: z.ZodOptional<z.ZodString>;
1951
2061
  status: z.ZodEnum<{
1952
2062
  "not-required": "not-required";
2063
+ stale: "stale";
1953
2064
  blocked: "blocked";
1954
2065
  current: "current";
1955
- stale: "stale";
1956
2066
  missing: "missing";
1957
2067
  }>;
1958
2068
  }, z.core.$strip>;
@@ -1962,9 +2072,9 @@ declare const managedReadinessLedgerSchema: z.ZodObject<{
1962
2072
  reviewedPatchId: z.ZodOptional<z.ZodString>;
1963
2073
  status: z.ZodEnum<{
1964
2074
  "not-required": "not-required";
2075
+ stale: "stale";
1965
2076
  blocked: "blocked";
1966
2077
  current: "current";
1967
- stale: "stale";
1968
2078
  missing: "missing";
1969
2079
  }>;
1970
2080
  }, z.core.$strip>>;
@@ -2491,6 +2601,12 @@ interface ReadFactoryPrStatusSourcesInput {
2491
2601
  * a completed unsuccessful check run when inventory has no registration.
2492
2602
  */
2493
2603
  readonly previewTargets?: readonly string[];
2604
+ /**
2605
+ * Token for the `/pulls/:n` identity read only. Defaults to `token`. On a
2606
+ * Cursor VM `token` is the broker's checks token, which cannot read pulls,
2607
+ * so this is the broker's admission token (#1327).
2608
+ */
2609
+ readonly pullToken?: string;
2494
2610
  readonly repo: string;
2495
2611
  /** Factory App installation token, normally minted by the presentation job. */
2496
2612
  readonly token: string;
@@ -2547,8 +2663,19 @@ interface RefreshFactoryPrStatusHudInput {
2547
2663
  readonly repo: string;
2548
2664
  readonly token?: string;
2549
2665
  }
2666
+ /**
2667
+ * Where a local HUD refresh gets its credentials. Defaults to the process
2668
+ * environment, the user config, the real broker, and `fetch`.
2669
+ */
2670
+ interface RefreshFactoryPrStatusHudDependencies {
2671
+ /** The broker's admission token on a Cursor VM, for the pull read (#1327). */
2672
+ readonly admission?: AdmissionCredential;
2673
+ /** The checks-scope broker mint on a Cursor VM, unchanged (#949). */
2674
+ readonly factoryApp?: FactoryAppAccessDependencies;
2675
+ readonly fetch?: typeof fetch;
2676
+ }
2550
2677
  /** Plan from the checkout and present the App-owned HUD. */
2551
- declare function refreshFactoryPrStatusHud(input: RefreshFactoryPrStatusHudInput): Promise<UpsertFactoryPrStatusCommentResult>;
2678
+ declare function refreshFactoryPrStatusHud(input: RefreshFactoryPrStatusHudInput, dependencies?: RefreshFactoryPrStatusHudDependencies): Promise<UpsertFactoryPrStatusCommentResult>;
2552
2679
  /**
2553
2680
  * Presentation must not fail an admission gate. Used after pr:ready publishes
2554
2681
  * its check so the table is not stuck at first-write.
@@ -2611,11 +2738,13 @@ interface PrReadyProof {
2611
2738
  schemaVersion: 3;
2612
2739
  /**
2613
2740
  * What GitHub actually did when this run armed native auto-merge (#477).
2614
- * Present exactly when a ready wave authorizes machine merge and arming ran;
2615
- * read back from the PR rather than inferred from the invocation, because `gh pr merge
2616
- * --auto` arms, merges, or does nothing with the same exit code and the same
2617
- * empty output. `not-armed` means the candidate is admitted but nothing will
2618
- * merge it, and `followUp` carries the re-dispatch.
2741
+ * Present exactly when a ready run called the arming module (#1299): not
2742
+ * for an `autoMerge: false` wave, and not when a notice names why the run
2743
+ * did not arm. Read back from the PR rather than inferred from the
2744
+ * invocation, because `gh pr merge --auto` arms, merges, or does nothing
2745
+ * with the same exit code and the same empty output. `not-armed` means the
2746
+ * candidate is admitted but nothing will merge it, and `followUp` carries
2747
+ * the re-dispatch.
2619
2748
  */
2620
2749
  arming?: {
2621
2750
  detail?: string;
@@ -2687,13 +2816,76 @@ interface PrReadyProof {
2687
2816
  */
2688
2817
  waivedDemands?: WaivedDemand[];
2689
2818
  }
2819
+ /** An issue's sub-issue parent, as the membership rule reads it (#1299). */
2820
+ interface IssueParent {
2821
+ labels: string[];
2822
+ number: number;
2823
+ state: string;
2824
+ }
2825
+ /** What `pr:ready` reads about one pull request before it arms (#1299). */
2826
+ interface PullRequestArmingFacts {
2827
+ /** GitHub's per-repository "Allow auto-merge" setting. */
2828
+ autoMergeAllowed: boolean;
2829
+ /**
2830
+ * GitHub's closing references for the pull request to issues in its own
2831
+ * repository, with their parents.
2832
+ */
2833
+ closingIssues: {
2834
+ number: number;
2835
+ parent: IssueParent | null;
2836
+ }[];
2837
+ /**
2838
+ * GitHub has more closing references than the one page `pr:ready` reads,
2839
+ * so `closingIssues` is not the whole set.
2840
+ */
2841
+ closingIssuesIncomplete: boolean;
2842
+ labels: string[];
2843
+ }
2844
+ /**
2845
+ * The GitHub reads behind the reasons not to arm an admitted candidate
2846
+ * (#1299). A read throws when GitHub cannot answer; `pr:ready` then does not
2847
+ * arm, and its notice says which read failed.
2848
+ */
2849
+ interface ArmingPolicyReads {
2850
+ /**
2851
+ * One issue's sub-issue parent, or `null` when it has none. An issue read,
2852
+ * so on a Cursor VM the real one runs on the admission token (#1327).
2853
+ */
2854
+ readIssueParent: (input: {
2855
+ issue: number;
2856
+ owner: string;
2857
+ repo: string;
2858
+ }) => IssueParent | null | Promise<IssueParent | null>;
2859
+ /**
2860
+ * The repository's auto-merge setting and this pull request's labels and
2861
+ * closing issues. The closing issues are issue data, which the VM's own
2862
+ * `gh` credential reads back as none rather than refusing, so on a Cursor
2863
+ * VM the real one also runs on the admission token (#1327).
2864
+ */
2865
+ readPullRequest: (input: {
2866
+ owner: string;
2867
+ pr: number;
2868
+ repo: string;
2869
+ }) => PullRequestArmingFacts | Promise<PullRequestArmingFacts>;
2870
+ }
2690
2871
  interface PrReadyDependencies {
2691
2872
  /**
2692
- * Arms GitHub native auto-merge only for a passing boundary wave that
2693
- * authorizes machine merge (#477). Defaults to the real `gh pr merge --auto`
2694
- * call plus the read-back that says what actually happened.
2873
+ * The broker's admission token on a Cursor VM (#1327), for issue reads the
2874
+ * VM's own `gh` credential cannot make. Defaults to the process-wide
2875
+ * credential, which mints nothing off a Cursor managed runtime.
2876
+ */
2877
+ admission?: AdmissionCredential;
2878
+ /**
2879
+ * Arms GitHub native auto-merge for an admitted candidate (#477, #1299).
2880
+ * Defaults to the real `gh pr merge --auto` call plus the read-back that
2881
+ * says what actually happened.
2695
2882
  */
2696
2883
  armAutoMerge?: (input: ArmAutoMergeInput) => ArmAutoMergeOutcome;
2884
+ /**
2885
+ * The GitHub reads behind the reasons not to arm (#1299). Defaults to the
2886
+ * real GraphQL reads.
2887
+ */
2888
+ armingPolicy?: ArmingPolicyReads;
2697
2889
  /**
2698
2890
  * Reads the authoritative base review policy (#922): the profile committed at
2699
2891
  * the live tip of the PR's own base ref. Defaults to the real GitHub reads,
@@ -2701,6 +2893,16 @@ interface PrReadyDependencies {
2701
2893
  * policy — unreadable authority is never absent authority.
2702
2894
  */
2703
2895
  basePolicy?: BaseReviewPolicyAuthority;
2896
+ /**
2897
+ * #1323: where the exact-head rules read GitHub's App runs for `pr-verify`
2898
+ * and `pr-review` at one head. `pr:ready` refuses when any App run of
2899
+ * either gate at the PR head is not a completed pass (rule 4); `pr:publish`,
2900
+ * which inherits this, posts a gate's proof only when its head has no run
2901
+ * for that gate (rule 2). Defaults to the user config's App id, else the
2902
+ * pinned factory App id, and the real `gh api` read, since readiness is a
2903
+ * control and an absent dependency must not mean an unread veto.
2904
+ */
2905
+ exactHeadRuns?: ExactHeadRunSource;
2704
2906
  github?: {
2705
2907
  fetchClosingPullRequests?: (input: {
2706
2908
  issue: number;
@@ -2935,9 +3137,9 @@ declare const loadEvidenceEnvelopes: (cwd: string) => LoadedEvidenceEnvelope[];
2935
3137
  declare const REVIEW_STATUS_VALUES: readonly ["not-required", "current", "stale", "missing", "blocked"];
2936
3138
  declare const reviewStatusSchema: z.ZodEnum<{
2937
3139
  "not-required": "not-required";
3140
+ stale: "stale";
2938
3141
  blocked: "blocked";
2939
3142
  current: "current";
2940
- stale: "stale";
2941
3143
  missing: "missing";
2942
3144
  }>;
2943
3145
  type ReviewStatus = z.infer<typeof reviewStatusSchema>;
@@ -3130,6 +3332,11 @@ interface BoundaryCheckArgs {
3130
3332
  repo?: string;
3131
3333
  }
3132
3334
  interface BoundaryCheckDependencies {
3335
+ /**
3336
+ * The broker's admission token on a Cursor VM (#1327), for the issue reads.
3337
+ * Read by {@link runBoundaryCheckWithAdmission} only.
3338
+ */
3339
+ admission?: AdmissionCredential;
3133
3340
  github?: BoundaryCheckGithub;
3134
3341
  hq?: HqIngestDependencies;
3135
3342
  readWaivers?: (cwd: string) => DemandWaiver[];
@@ -3297,6 +3504,12 @@ interface PrReviewIssueBodyInput {
3297
3504
  repo: string;
3298
3505
  }
3299
3506
  interface PrReviewDependencies {
3507
+ /**
3508
+ * The broker's admission token on a Cursor VM (#1327), for the issue reads
3509
+ * `--assemble` makes. Defaults to the process-wide credential, which mints
3510
+ * nothing off a Cursor managed runtime.
3511
+ */
3512
+ admission?: AdmissionCredential;
3300
3513
  /**
3301
3514
  * Test-only seam (#1131): runs after the recording has read the proof
3302
3515
  * state it is applied to and immediately before the compare-and-swap write,
@@ -3345,6 +3558,11 @@ interface PrReviewDependencies {
3345
3558
  * measured against (#1131). Defaults to the GitHub lookup.
3346
3559
  */
3347
3560
  lookupPullRequestBase?: (cwd: string) => LivePullRequestBaseLookup;
3561
+ /**
3562
+ * #1323: the detached `patronage-factory/pr-review` publisher, the same one
3563
+ * `pr:verify` uses. Injected by the CLI; absent, no check run is posted.
3564
+ */
3565
+ publishCheckRun?: (input: PublishFactoryCheckInput) => void;
3348
3566
  }
3349
3567
  declare function runPrReview(args: PrReviewArgs, dependencies?: PrReviewDependencies): Promise<PrReviewProof>;
3350
3568
  //#endregion
@@ -3445,11 +3663,12 @@ interface PrPublishHandoff {
3445
3663
  routeOwnedRepairs: ReadinessRepair[];
3446
3664
  /**
3447
3665
  * Whether the merge is actually on GitHub's schedule (#477, #515 review).
3448
- * An admitted candidate is not a handed-off one: `pr:ready` arms only when a
3449
- * boundary wave authorized it, and the arming can fail to take effect. A
3666
+ * An admitted candidate is not a handed-off one: `pr:ready` does not arm
3667
+ * when the repository does not allow auto-merge, the pull request carries
3668
+ * `factory:hold`, a boundary wave keeps it attended, or it is an epic member
3669
+ * run without `--epic` (#1299), and the arming can fail to take effect. A
3450
3670
  * readiness status of `ready` says the candidate passed, never that anything
3451
- * will merge it — only an auto-merge-authorized boundary wave can schedule
3452
- * one — so publish reads this before claiming nothing is owed.
3671
+ * will merge it, so publish reads this before claiming nothing is owed.
3453
3672
  */
3454
3673
  scheduled: boolean;
3455
3674
  status: PrReadyProof["status"];
@@ -3495,6 +3714,12 @@ interface PrPublishResult {
3495
3714
  * it stays an additive change to the published result contract.
3496
3715
  */
3497
3716
  verificationReuse?: VerificationReuse;
3717
+ /**
3718
+ * Whether the conclusion-only pr:review check run landed for this head
3719
+ * (#1323). Optional so adding it stays an additive change to the published
3720
+ * result contract.
3721
+ */
3722
+ reviewProofBinding?: ReviewProofBindingOutcome;
3498
3723
  /**
3499
3724
  * Whether the durable pr:verify check-run binding landed for this head (#247).
3500
3725
  * Always populated from this version on; optional so adding it stays an
@@ -3509,8 +3734,17 @@ declare class PrPublishFollowUpError extends Error {
3509
3734
  interface PrPublishDependencies extends PrReadyDependencies {
3510
3735
  currentBranch?: (cwd: string) => string | undefined;
3511
3736
  /**
3512
- * Awaited republish of the `pr:verify` check run once the head SHA is on
3513
- * GitHub (#247). Returns whether the App-owned check run landed.
3737
+ * Awaited publication of the conclusion-only `pr:review` check run for the
3738
+ * publish head (#1323). Returns whether the App-owned check run landed.
3739
+ * Injected by the CLI like the verify sink. A review cycle publish records
3740
+ * from `--findings` posts through it too, accepted or blocked.
3741
+ */
3742
+ ensureReviewCheckRun?: typeof ensureFactoryCheckRunPublished;
3743
+ /**
3744
+ * Awaited post of the `pr:verify` check run once the head SHA is on GitHub
3745
+ * (#247), when the head has no run for it yet (#1323). Returns whether the
3746
+ * App-owned check run landed. A `pr:verify` publish runs fresh posts through
3747
+ * it too, pass or failure (#1323).
3514
3748
  */
3515
3749
  ensureVerifyCheckRun?: typeof ensureFactoryCheckRunPublished;
3516
3750
  factoryCliInvocation?: FactoryCliInvocation;
@@ -3525,7 +3759,11 @@ interface PrPublishDependencies extends PrReadyDependencies {
3525
3759
  };
3526
3760
  runFollowUp?: FollowUpRunner;
3527
3761
  runPrReady?: typeof runPrReady;
3528
- runPrReview?: (args: PrReviewArgs) => Promise<PrReviewProof>;
3762
+ /**
3763
+ * The review producer publish runs for `--findings`. Its second argument
3764
+ * carries the publisher for the verdict it records (#1323).
3765
+ */
3766
+ runPrReview?: (args: PrReviewArgs, dependencies?: ProducerPublisher) => Promise<PrReviewProof>;
3529
3767
  /** Settles asynchronously scheduled sink work before the handoff drain. */
3530
3768
  awaitPendingIngest?: () => Promise<void>;
3531
3769
  /**
@@ -3535,7 +3773,11 @@ interface PrPublishDependencies extends PrReadyDependencies {
3535
3773
  flushHqSpool?: (args: {
3536
3774
  cwd: string;
3537
3775
  }) => Promise<HqFlushResult>;
3538
- runPrVerify?: (args: PrVerifyArgs) => Promise<PrVerifyProof>;
3776
+ /**
3777
+ * The verify producer publish runs when no proof applies. Its second
3778
+ * argument carries the publisher for the verdict it produces (#1323).
3779
+ */
3780
+ runPrVerify?: (args: PrVerifyArgs, dependencies?: ProducerPublisher) => Promise<PrVerifyProof>;
3539
3781
  /**
3540
3782
  * Injectable clock for the promotion instant the hosted-run await measures
3541
3783
  * `verify` staleness against (#1045; tests only — production reads the real
@@ -3553,13 +3795,19 @@ interface PrPublishDependencies extends PrReadyDependencies {
3553
3795
  * the publish summary: a binding that silently failed to land is
3554
3796
  * indistinguishable from verification that never happened, and the operator is
3555
3797
  * the only one who can fix the cause (usually an unpushed head).
3798
+ *
3799
+ * #1323 (rule 2): `skipped-existing-run` means the head already has an App run
3800
+ * for the gate, so publish posted nothing; `skipped-unreadable` means GitHub's
3801
+ * runs at the head could not be read whole, so publish posted nothing either.
3556
3802
  */
3557
3803
  type VerifyProofBindingOutcome = {
3558
3804
  status: "not-attempted";
3559
3805
  } | {
3560
3806
  headSha: string;
3561
- status: "failed" | "published" | "skipped-relabelled-head";
3807
+ status: ExactHeadBindingStatus | "skipped-relabelled-head";
3562
3808
  };
3809
+ /** What a gate's post at the publish head came to, once rule 2 has read. */
3810
+ type ExactHeadBindingStatus = "failed" | "published" | "skipped-existing-run" | "skipped-unreadable";
3563
3811
  /**
3564
3812
  * Whether publish reused the on-disk `pr:verify` proof, and the one-line reason
3565
3813
  * (#291). Publish decides reuse silently today, which is what made the
@@ -3570,6 +3818,21 @@ interface VerificationReuse {
3570
3818
  reason: string;
3571
3819
  reused: boolean;
3572
3820
  }
3821
+ /** The publisher a producer takes: `pr:verify`'s and `pr:review`'s own seam. */
3822
+ interface ProducerPublisher {
3823
+ publishCheckRun?: (input: PublishFactoryCheckInput) => void;
3824
+ }
3825
+ /**
3826
+ * What became of the conclusion-only `pr:review` check run for this publish
3827
+ * (#1323). A proof that records no review (`not-required`) has no verdict to
3828
+ * post.
3829
+ */
3830
+ type ReviewProofBindingOutcome = {
3831
+ status: "not-attempted";
3832
+ } | {
3833
+ headSha: string;
3834
+ status: ExactHeadBindingStatus | "skipped-no-review" | "skipped-relabelled-head";
3835
+ };
3573
3836
  /**
3574
3837
  * Whether publish runs the follow-up `pr:ready` recommended, plus the one-line
3575
3838
  * reason either way (#291).
@@ -4669,6 +4932,26 @@ interface HqSpoolCheckDependencies {
4669
4932
  countSpool?: typeof countHqSpoolWork;
4670
4933
  sweepOrphans?: typeof sweepHqSpoolOrphans;
4671
4934
  }
4935
+ /** Test seams. Production never sets these. */
4936
+ interface HqReadinessCheckDependencies {
4937
+ fetch?: typeof fetch;
4938
+ resolveSecret?: SecretReferenceResolver;
4939
+ timeoutMs?: number;
4940
+ }
4941
+ //#endregion
4942
+ //#region src/doctor-machine-checks.d.ts
4943
+ /** Test seams. Production never sets these. */
4944
+ interface MachineCheckDependencies {
4945
+ cpuCount?: () => number;
4946
+ /** Whether an executable file with this name is on the PATH in `env`. */
4947
+ isOnPath?: (name: string, env: NodeJS.ProcessEnv) => boolean;
4948
+ nodeVersion?: string;
4949
+ platform?: NodeJS.Platform;
4950
+ /** A small text file's contents, or undefined when it cannot be read. */
4951
+ readText?: (filePath: string) => string | undefined;
4952
+ /** What `/bin/sh` resolves to, or undefined when it cannot be resolved. */
4953
+ resolveShell?: () => string | undefined;
4954
+ }
4672
4955
  //#endregion
4673
4956
  //#region src/doctor.d.ts
4674
4957
  interface DoctorReport {
@@ -4686,8 +4969,12 @@ interface DoctorProjectProfileInput extends LoadProjectProfileInput {
4686
4969
  /** Diff base for the admission preflight; ignored unless `preflight`. */
4687
4970
  base?: string;
4688
4971
  env?: NodeJS.ProcessEnv;
4972
+ /** Test seam for the HQ readiness rows (#1315); production never sets this. */
4973
+ hqReadinessDependencies?: HqReadinessCheckDependencies;
4689
4974
  /** Test seam for the local HQ spool check (#394); production never sets this. */
4690
4975
  hqSpoolDependencies?: HqSpoolCheckDependencies;
4976
+ /** Test seam for the machine readiness rows (#1315); production never sets this. */
4977
+ machineDependencies?: MachineCheckDependencies;
4691
4978
  /**
4692
4979
  * Append the read-only admission checklist (#292): every requirement this
4693
4980
  * candidate must satisfy before merge, named in one pass. Preflight checks
@@ -4821,14 +5108,132 @@ interface EvaluationInput {
4821
5108
  blockingReasons: string[];
4822
5109
  notices: string[];
4823
5110
  };
5111
+ exactHeadRunRefusals?: {
5112
+ prReview: string[];
5113
+ prVerify: string[];
5114
+ };
4824
5115
  waivers?: DemandWaiver[];
4825
5116
  }
4826
5117
  declare const evaluateReadiness: (input: EvaluationInput) => {
5118
+ humanBlockingReasons: string[];
5119
+ ledger: {
5120
+ baseSha: string;
5121
+ blockingReasons: string[];
5122
+ classification: "docs/process-only" | "trivial" | "non-trivial";
5123
+ github: {
5124
+ draft: boolean;
5125
+ mergeStateStatus: string;
5126
+ mergeable: string;
5127
+ requiredChecks: "unknown" | "pending" | "passed" | "failed" | "none";
5128
+ unresolvedReviewThreads: number;
5129
+ reviewDecision?: string | null | undefined;
5130
+ };
5131
+ headSha: string;
5132
+ patchId: string;
5133
+ pr: number;
5134
+ repairs: {
5135
+ action: string;
5136
+ code: "undraft-pr" | "await-post-undraft-checks";
5137
+ command: string;
5138
+ }[];
5139
+ reviews: {
5140
+ correctness: {
5141
+ required: boolean;
5142
+ status: "not-required" | "stale" | "blocked" | "current" | "missing";
5143
+ reviewedHeadSha?: string | undefined;
5144
+ reviewedPatchId?: string | undefined;
5145
+ };
5146
+ security?: {
5147
+ required: boolean;
5148
+ status: "not-required" | "stale" | "blocked" | "current" | "missing";
5149
+ reviewedHeadSha?: string | undefined;
5150
+ reviewedPatchId?: string | undefined;
5151
+ } | undefined;
5152
+ };
5153
+ schemaVersion: 2;
5154
+ verification: {
5155
+ command: "patronage-factory pr:verify";
5156
+ prVerify: "missing";
5157
+ } | {
5158
+ command: "patronage-factory pr:verify";
5159
+ prVerify: "stale";
5160
+ verifiedHeadSha: string;
5161
+ } | {
5162
+ command: "patronage-factory pr:verify";
5163
+ prVerify: "passed-via-head";
5164
+ verifiedHeadSha: string;
5165
+ } | {
5166
+ command: "patronage-factory pr:verify";
5167
+ docsOnlyVerifiedHeadSha: string;
5168
+ prVerify: "docs-only-delta";
5169
+ verifiedHeadSha?: string | undefined;
5170
+ } | {
5171
+ command: "patronage-factory pr:verify";
5172
+ prVerify: "trivial-delta";
5173
+ trivialVerifiedHeadSha: string;
5174
+ verifiedHeadSha?: string | undefined;
5175
+ };
5176
+ externalChecks?: {
5177
+ checkType: "review" | "verify";
5178
+ name: string;
5179
+ scopeReason: string;
5180
+ status: "satisfied" | "unmet" | "out-of-scope";
5181
+ reason?: string | undefined;
5182
+ scope?: {
5183
+ classifications: ("docs/process-only" | "trivial" | "non-trivial")[];
5184
+ } | undefined;
5185
+ }[] | undefined;
5186
+ mergeBaseSha?: string | undefined;
5187
+ reviewCycleState?: {
5188
+ autoBlockingFindings: number;
5189
+ countsBySeverity: {
5190
+ critical: number;
5191
+ high: number;
5192
+ low: number;
5193
+ medium: number;
5194
+ unknown: number;
5195
+ };
5196
+ nonBlockingFindings: number;
5197
+ openFindings: number;
5198
+ windowExhausted: boolean;
5199
+ highestBlockingSeverity?: "unknown" | "critical" | "high" | "low" | "medium" | undefined;
5200
+ highestOpenSeverity?: "unknown" | "critical" | "high" | "low" | "medium" | undefined;
5201
+ maxReviewCycles?: number | undefined;
5202
+ reviewCycle?: number | undefined;
5203
+ staleRepeatFindings?: number | undefined;
5204
+ } | undefined;
5205
+ reviewLadder?: {
5206
+ cycleCounts: {
5207
+ gate: number;
5208
+ interior: number;
5209
+ };
5210
+ nextAction: "run-interior-cycle" | "advance-to-gate" | "run-gate-cycle" | "accept-nonblocking-findings" | "escalate-to-triage" | "ready-for-human";
5211
+ stage: "interior" | "gate" | "interior-complete";
5212
+ forcedTransition?: "gate-cap-exhausted" | "interior-cap-reached" | undefined;
5213
+ } | undefined;
5214
+ reviewRuns?: PrReviewResult[] | undefined;
5215
+ reviewTerminalState?: "blocked" | "accepted-with-findings" | "clean" | undefined;
5216
+ };
5217
+ /**
5218
+ * Facts a reader needs that are not refusals (#477): the settle-window
5219
+ * arming notice, and every waived demand rendered so it can never read as
5220
+ * a met one.
5221
+ */
5222
+ notices: string[];
5223
+ repairs: {
5224
+ action: string;
5225
+ code: "undraft-pr" | "await-post-undraft-checks";
5226
+ command: string;
5227
+ }[];
5228
+ status: ReadinessStatus; /** Demands that were in force, were NOT met, and the operator waived. */
5229
+ waivedDemands: WaivedDemand[];
5230
+ catchUpRecognition: CatchUpRecognition;
4827
5231
  blockedReasons: {
4828
5232
  code: string;
4829
5233
  detail: string;
4830
5234
  }[];
4831
5235
  blockingReasons: string[];
5236
+ } | {
4832
5237
  humanBlockingReasons: string[];
4833
5238
  ledger: {
4834
5239
  baseSha: string;
@@ -4853,13 +5258,13 @@ declare const evaluateReadiness: (input: EvaluationInput) => {
4853
5258
  reviews: {
4854
5259
  correctness: {
4855
5260
  required: boolean;
4856
- status: "not-required" | "blocked" | "current" | "stale" | "missing";
5261
+ status: "not-required" | "stale" | "blocked" | "current" | "missing";
4857
5262
  reviewedHeadSha?: string | undefined;
4858
5263
  reviewedPatchId?: string | undefined;
4859
5264
  };
4860
5265
  security?: {
4861
5266
  required: boolean;
4862
- status: "not-required" | "blocked" | "current" | "stale" | "missing";
5267
+ status: "not-required" | "stale" | "blocked" | "current" | "missing";
4863
5268
  reviewedHeadSha?: string | undefined;
4864
5269
  reviewedPatchId?: string | undefined;
4865
5270
  } | undefined;
@@ -4941,6 +5346,12 @@ declare const evaluateReadiness: (input: EvaluationInput) => {
4941
5346
  }[];
4942
5347
  status: ReadinessStatus; /** Demands that were in force, were NOT met, and the operator waived. */
4943
5348
  waivedDemands: WaivedDemand[];
5349
+ catchUpRecognition?: undefined;
5350
+ blockedReasons: {
5351
+ code: string;
5352
+ detail: string;
5353
+ }[];
5354
+ blockingReasons: string[];
4944
5355
  };
4945
5356
  //#endregion
4946
5357
  //#region src/evidence-emit.d.ts
@@ -5623,6 +6034,12 @@ declare function createProgram(options?: CreateProgramOptions): Command;
5623
6034
  * runnable bin module, which follow-up commands spawn; the bin entry passes its
5624
6035
  * own path. This function never runs itself — `./cli.js` is the only caller
5625
6036
  * that does (#386).
6037
+ *
6038
+ * On the way out it waits for the command's own check-run publications
6039
+ * (bounded, #1322) and HQ emissions, then drains
6040
+ * the checkout's HQ spool once (#1313). This is the one place that drains on
6041
+ * exit. `pr:ready` then prints one line if events are still spooled (#1317).
6042
+ * None of these steps can throw or change the command's exit code.
5626
6043
  */
5627
6044
  declare function run(argv?: string[], cliEntry?: string): Promise<void>;
5628
6045
  //#endregion