@patronage/software-factory 1.0.0-alpha.37 → 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/README.md CHANGED
@@ -76,6 +76,6 @@ npx skills add https://github.com/patronage/software-factory -g --all
76
76
 
77
77
  They provide procedure and link back to the documentation site; they are not a second command reference.
78
78
 
79
- The package also exports an Oxlint plugin at `@patronage/software-factory/oxlint-jev`. It asks Jev questions that a project writes about its own code, through HQ, from a dedicated Oxlint config. It records by default and sends only with `PATRONAGE_JEV_MODE=live`. `psf jev:calibrate` scores those questions against the project's labeled cases. Both are session diagnostics, never gates. See [Ask Jev about code](https://factory.patronage.com/how-to/ask-jev-about-code).
79
+ The package also exports an Oxlint plugin at `@patronage/software-factory/oxlint-jev`. It asks Jev questions that a project writes about its own code, through HQ, from a dedicated Oxlint config. It records by default and sends only with `PATRONAGE_JEV_MODE=live`. `psf jev:calibrate` scores those questions against the project's labeled cases. Both are session diagnostics, never gates. See [Ask Jev about your code](https://factory.patronage.com/tutorials/ask-jev-about-code).
80
80
 
81
81
  `assembleReviewPrompt` puts a repository-root `CODING_STANDARDS.md` into the correctness prompt when the file exists and is not blank. It reads the file at the candidate head, not from the working tree, and emits one `coding-standards` section that names the head SHA. A repository without the file gets an unchanged prompt. `pr:review --assemble` writes that factory-owned prompt under `.factory-memory/pr-review-prompt.json`. `pr:review --findings` copies the recorded sections onto the proof when the artifact's patch-id matches.
package/dist/index.d.ts CHANGED
@@ -4,6 +4,109 @@ 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>;
@@ -1250,58 +1393,6 @@ declare const followUpFromArgv: (argv: string[]) => FollowUpAction;
1250
1393
  /** Canonical zod schema for the optional follow-up field on JSON outputs. */
1251
1394
  declare const FollowUpActionSchema: z.ZodType<FollowUpAction>;
1252
1395
  //#endregion
1253
- //#region src/user-config.d.ts
1254
- declare const githubAppConfigSchema: z.ZodObject<{
1255
- appId: z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>;
1256
- installationId: z.ZodOptional<z.ZodNumber>;
1257
- privateKeyPath: z.ZodString;
1258
- }, z.core.$strip>;
1259
- type GithubAppConfig = z.infer<typeof githubAppConfigSchema>;
1260
- declare const factoryUserConfigSchema: z.ZodObject<{
1261
- githubApp: z.ZodOptional<z.ZodObject<{
1262
- appId: z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>;
1263
- installationId: z.ZodOptional<z.ZodNumber>;
1264
- privateKeyPath: z.ZodString;
1265
- }, z.core.$strip>>;
1266
- hqAllowedOrigins: z.ZodOptional<z.ZodArray<z.ZodString>>;
1267
- hqIngestCredentials: z.ZodOptional<z.ZodObject<{
1268
- clientIdRef: z.ZodString;
1269
- clientSecretRef: z.ZodString;
1270
- }, z.core.$strip>>;
1271
- schemaVersion: z.ZodLiteral<2>;
1272
- }, z.core.$strip>;
1273
- type FactoryUserConfig = z.infer<typeof factoryUserConfigSchema>;
1274
- interface LoadUserConfigResult {
1275
- path: string;
1276
- config: FactoryUserConfig;
1277
- ignoredKeys?: string[];
1278
- }
1279
- //#endregion
1280
- //#region src/github-app-access.d.ts
1281
- interface FactoryAppAccessDependencies {
1282
- env?: NodeJS.ProcessEnv;
1283
- existsSync?: (path: string) => boolean;
1284
- exchangeBroker?: (input: {
1285
- fetch?: typeof fetch;
1286
- oidc: string;
1287
- owner: string;
1288
- repo: string;
1289
- timeoutMs?: number;
1290
- url?: string;
1291
- }) => Promise<string>;
1292
- fetch?: typeof fetch;
1293
- githubApp?: GithubAppConfig;
1294
- mintCursorOidc?: (input: {
1295
- audience?: string;
1296
- socketPath?: string;
1297
- timeoutMs?: number;
1298
- }) => Promise<string>;
1299
- now?: () => number;
1300
- timeoutMs?: number;
1301
- token?: string;
1302
- transportAttempts?: number;
1303
- }
1304
- //#endregion
1305
1396
  //#region src/pr-verify-mode.d.ts
1306
1397
  /**
1307
1398
  * The verification mode `pr:verify` resolved for a run.
@@ -1547,6 +1638,7 @@ type PostCommitStatus = (input: PostCommitStatusInput) => void;
1547
1638
  //#region src/github-check-runs.d.ts
1548
1639
  declare const FACTORY_CHECK_NAMES: {
1549
1640
  readonly "pr-ready": "patronage-factory/pr-ready";
1641
+ readonly "pr-review": "patronage-factory/pr-review";
1550
1642
  readonly "pr-verify": "patronage-factory/pr-verify";
1551
1643
  };
1552
1644
  type FactoryCheckGate = keyof typeof FACTORY_CHECK_NAMES;
@@ -1570,11 +1662,12 @@ interface PublishFactoryCheckInput {
1570
1662
  }
1571
1663
  interface CheckRunDependencies extends FactoryAppAccessDependencies {
1572
1664
  postCommitStatus?: PostCommitStatus;
1573
- resolveDetailsUrl?: (input: PublishFactoryCheckInput) => Promise<string> | string;
1665
+ resolveDetailsUrl?: (input: PublishFactoryCheckInput, signal?: AbortSignal) => Promise<string> | string;
1574
1666
  /**
1575
1667
  * Bounded retry for the check-run POST. GitHub answers 422 for a head SHA it
1576
1668
  * has not seen yet, which is the normal state when `pr:verify` runs before
1577
- * 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`).
1578
1671
  */
1579
1672
  retry?: {
1580
1673
  attempts: number;
@@ -1659,6 +1752,15 @@ declare function fetchPrVerifyProofBinding({
1659
1752
  sha: string;
1660
1753
  runJson?: GhJsonRunner;
1661
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
+ }
1662
1764
  //#endregion
1663
1765
  //#region src/merge-freeze.d.ts
1664
1766
  declare const MERGE_FREEZE_CHECK_NAME = "patronage-factory/merge-freeze";
@@ -2499,6 +2601,12 @@ interface ReadFactoryPrStatusSourcesInput {
2499
2601
  * a completed unsuccessful check run when inventory has no registration.
2500
2602
  */
2501
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;
2502
2610
  readonly repo: string;
2503
2611
  /** Factory App installation token, normally minted by the presentation job. */
2504
2612
  readonly token: string;
@@ -2555,8 +2663,19 @@ interface RefreshFactoryPrStatusHudInput {
2555
2663
  readonly repo: string;
2556
2664
  readonly token?: string;
2557
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
+ }
2558
2677
  /** Plan from the checkout and present the App-owned HUD. */
2559
- declare function refreshFactoryPrStatusHud(input: RefreshFactoryPrStatusHudInput): Promise<UpsertFactoryPrStatusCommentResult>;
2678
+ declare function refreshFactoryPrStatusHud(input: RefreshFactoryPrStatusHudInput, dependencies?: RefreshFactoryPrStatusHudDependencies): Promise<UpsertFactoryPrStatusCommentResult>;
2560
2679
  /**
2561
2680
  * Presentation must not fail an admission gate. Used after pr:ready publishes
2562
2681
  * its check so the table is not stuck at first-write.
@@ -2728,19 +2847,34 @@ interface PullRequestArmingFacts {
2728
2847
  * arm, and its notice says which read failed.
2729
2848
  */
2730
2849
  interface ArmingPolicyReads {
2731
- /** One issue's sub-issue parent, or `null` when it has none. */
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
+ */
2732
2854
  readIssueParent: (input: {
2733
2855
  issue: number;
2734
2856
  owner: string;
2735
2857
  repo: string;
2736
- }) => IssueParent | null;
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
+ */
2737
2865
  readPullRequest: (input: {
2738
2866
  owner: string;
2739
2867
  pr: number;
2740
2868
  repo: string;
2741
- }) => PullRequestArmingFacts;
2869
+ }) => PullRequestArmingFacts | Promise<PullRequestArmingFacts>;
2742
2870
  }
2743
2871
  interface PrReadyDependencies {
2872
+ /**
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;
2744
2878
  /**
2745
2879
  * Arms GitHub native auto-merge for an admitted candidate (#477, #1299).
2746
2880
  * Defaults to the real `gh pr merge --auto` call plus the read-back that
@@ -2759,6 +2893,16 @@ interface PrReadyDependencies {
2759
2893
  * policy — unreadable authority is never absent authority.
2760
2894
  */
2761
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;
2762
2906
  github?: {
2763
2907
  fetchClosingPullRequests?: (input: {
2764
2908
  issue: number;
@@ -3188,6 +3332,11 @@ interface BoundaryCheckArgs {
3188
3332
  repo?: string;
3189
3333
  }
3190
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;
3191
3340
  github?: BoundaryCheckGithub;
3192
3341
  hq?: HqIngestDependencies;
3193
3342
  readWaivers?: (cwd: string) => DemandWaiver[];
@@ -3355,6 +3504,12 @@ interface PrReviewIssueBodyInput {
3355
3504
  repo: string;
3356
3505
  }
3357
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;
3358
3513
  /**
3359
3514
  * Test-only seam (#1131): runs after the recording has read the proof
3360
3515
  * state it is applied to and immediately before the compare-and-swap write,
@@ -3403,6 +3558,11 @@ interface PrReviewDependencies {
3403
3558
  * measured against (#1131). Defaults to the GitHub lookup.
3404
3559
  */
3405
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;
3406
3566
  }
3407
3567
  declare function runPrReview(args: PrReviewArgs, dependencies?: PrReviewDependencies): Promise<PrReviewProof>;
3408
3568
  //#endregion
@@ -3554,6 +3714,12 @@ interface PrPublishResult {
3554
3714
  * it stays an additive change to the published result contract.
3555
3715
  */
3556
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;
3557
3723
  /**
3558
3724
  * Whether the durable pr:verify check-run binding landed for this head (#247).
3559
3725
  * Always populated from this version on; optional so adding it stays an
@@ -3568,8 +3734,17 @@ declare class PrPublishFollowUpError extends Error {
3568
3734
  interface PrPublishDependencies extends PrReadyDependencies {
3569
3735
  currentBranch?: (cwd: string) => string | undefined;
3570
3736
  /**
3571
- * Awaited republish of the `pr:verify` check run once the head SHA is on
3572
- * 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).
3573
3748
  */
3574
3749
  ensureVerifyCheckRun?: typeof ensureFactoryCheckRunPublished;
3575
3750
  factoryCliInvocation?: FactoryCliInvocation;
@@ -3584,7 +3759,11 @@ interface PrPublishDependencies extends PrReadyDependencies {
3584
3759
  };
3585
3760
  runFollowUp?: FollowUpRunner;
3586
3761
  runPrReady?: typeof runPrReady;
3587
- 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>;
3588
3767
  /** Settles asynchronously scheduled sink work before the handoff drain. */
3589
3768
  awaitPendingIngest?: () => Promise<void>;
3590
3769
  /**
@@ -3594,7 +3773,11 @@ interface PrPublishDependencies extends PrReadyDependencies {
3594
3773
  flushHqSpool?: (args: {
3595
3774
  cwd: string;
3596
3775
  }) => Promise<HqFlushResult>;
3597
- 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>;
3598
3781
  /**
3599
3782
  * Injectable clock for the promotion instant the hosted-run await measures
3600
3783
  * `verify` staleness against (#1045; tests only — production reads the real
@@ -3612,13 +3795,19 @@ interface PrPublishDependencies extends PrReadyDependencies {
3612
3795
  * the publish summary: a binding that silently failed to land is
3613
3796
  * indistinguishable from verification that never happened, and the operator is
3614
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.
3615
3802
  */
3616
3803
  type VerifyProofBindingOutcome = {
3617
3804
  status: "not-attempted";
3618
3805
  } | {
3619
3806
  headSha: string;
3620
- status: "failed" | "published" | "skipped-relabelled-head";
3807
+ status: ExactHeadBindingStatus | "skipped-relabelled-head";
3621
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";
3622
3811
  /**
3623
3812
  * Whether publish reused the on-disk `pr:verify` proof, and the one-line reason
3624
3813
  * (#291). Publish decides reuse silently today, which is what made the
@@ -3629,6 +3818,21 @@ interface VerificationReuse {
3629
3818
  reason: string;
3630
3819
  reused: boolean;
3631
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
+ };
3632
3836
  /**
3633
3837
  * Whether publish runs the follow-up `pr:ready` recommended, plus the one-line
3634
3838
  * reason either way (#291).
@@ -4728,6 +4932,26 @@ interface HqSpoolCheckDependencies {
4728
4932
  countSpool?: typeof countHqSpoolWork;
4729
4933
  sweepOrphans?: typeof sweepHqSpoolOrphans;
4730
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
+ }
4731
4955
  //#endregion
4732
4956
  //#region src/doctor.d.ts
4733
4957
  interface DoctorReport {
@@ -4745,8 +4969,12 @@ interface DoctorProjectProfileInput extends LoadProjectProfileInput {
4745
4969
  /** Diff base for the admission preflight; ignored unless `preflight`. */
4746
4970
  base?: string;
4747
4971
  env?: NodeJS.ProcessEnv;
4972
+ /** Test seam for the HQ readiness rows (#1315); production never sets this. */
4973
+ hqReadinessDependencies?: HqReadinessCheckDependencies;
4748
4974
  /** Test seam for the local HQ spool check (#394); production never sets this. */
4749
4975
  hqSpoolDependencies?: HqSpoolCheckDependencies;
4976
+ /** Test seam for the machine readiness rows (#1315); production never sets this. */
4977
+ machineDependencies?: MachineCheckDependencies;
4750
4978
  /**
4751
4979
  * Append the read-only admission checklist (#292): every requirement this
4752
4980
  * candidate must satisfy before merge, named in one pass. Preflight checks
@@ -4880,6 +5108,10 @@ interface EvaluationInput {
4880
5108
  blockingReasons: string[];
4881
5109
  notices: string[];
4882
5110
  };
5111
+ exactHeadRunRefusals?: {
5112
+ prReview: string[];
5113
+ prVerify: string[];
5114
+ };
4883
5115
  waivers?: DemandWaiver[];
4884
5116
  }
4885
5117
  declare const evaluateReadiness: (input: EvaluationInput) => {
@@ -5802,6 +6034,12 @@ declare function createProgram(options?: CreateProgramOptions): Command;
5802
6034
  * runnable bin module, which follow-up commands spawn; the bin entry passes its
5803
6035
  * own path. This function never runs itself — `./cli.js` is the only caller
5804
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.
5805
6043
  */
5806
6044
  declare function run(argv?: string[], cliEntry?: string): Promise<void>;
5807
6045
  //#endregion