@patronage/factory-ci 1.0.0-alpha.23 → 1.0.0-alpha.25
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 +49 -16
- package/dist/.build-fingerprint.json +3 -0
- package/dist/index.d.ts +123 -40
- package/dist/index.js +324 -137
- package/package.json +2 -2
- package/src/github-app-token.ts +54 -18
- package/src/github-transport.ts +186 -0
- package/src/index.ts +15 -0
- package/src/preview-proof-inventory.ts +17 -116
- package/src/proof-reuse-gate.ts +80 -52
- package/src/proof-reuse-presentation.ts +10 -7
- package/src/verify-runner-policy.ts +290 -0
package/README.md
CHANGED
|
@@ -61,22 +61,50 @@ workflow({/* caller-owned jobs and topology */}).writeOrLint({
|
|
|
61
61
|
});
|
|
62
62
|
```
|
|
63
63
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
Candidate lifecycle support follows the same boundary. Use `FACTORY_CANDIDATE_PULL_REQUEST_TYPES` for the pull-request trigger matrix and `factoryCandidateOrPushCondition()` on each substantive job. With an optional caller-owned path condition, the helper emits the GitHub expression that runs merge-target pushes unconditionally and runs pull-request work only when GitHub's draft boolean says the pull request is a Candidate:
|
|
64
|
+
`factoryWorkflow()` still does not set `runsOn`. This package does not depend on gagen. Job ids, permissions, topology, and deploy policy stay with the caller. Verify runner kinds are this package's business. Name each job's kind (or an override with a non-empty why), pass `.label` into gagen `job()`, and hand the same table to `assertVerifyRunnerPolicy` next to `assertWorkflowShellParses`:
|
|
67
65
|
|
|
68
66
|
```ts
|
|
67
|
+
import {
|
|
68
|
+
assertVerifyRunnerPolicy,
|
|
69
|
+
assertWorkflowShellParses,
|
|
70
|
+
factoryVerifyRunner,
|
|
71
|
+
type FactoryVerifyRunner,
|
|
72
|
+
} from "@patronage/factory-ci";
|
|
73
|
+
|
|
74
|
+
type VerifyJobId = "changes" | "core";
|
|
75
|
+
|
|
76
|
+
const verifyRunners: Record<VerifyJobId, FactoryVerifyRunner> = {
|
|
77
|
+
changes: factoryVerifyRunner("rounding-tax"),
|
|
78
|
+
core: factoryVerifyRunner("sustained-compute"),
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const runsOnFor = (id: VerifyJobId) => verifyRunners[id].label;
|
|
82
|
+
|
|
69
83
|
const changes = job("changes", {
|
|
70
84
|
if: factoryCandidateOrPushCondition(),
|
|
71
|
-
|
|
85
|
+
runsOn: runsOnFor("changes"),
|
|
72
86
|
});
|
|
73
87
|
|
|
74
88
|
const core = job("core", {
|
|
75
89
|
if: factoryCandidateOrPushCondition("needs.changes.outputs.core == 'true'"),
|
|
76
|
-
|
|
90
|
+
runsOn: runsOnFor("core"),
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const generated = workflow({ jobs: [changes, core] });
|
|
94
|
+
|
|
95
|
+
assertWorkflowShellParses(generated.toYamlString(), {
|
|
96
|
+
source: ".github/workflows/verify.ts",
|
|
97
|
+
});
|
|
98
|
+
assertVerifyRunnerPolicy(generated.toYamlString(), {
|
|
99
|
+
source: ".github/workflows/verify.ts",
|
|
100
|
+
jobs: verifyRunners,
|
|
77
101
|
});
|
|
78
102
|
```
|
|
79
103
|
|
|
104
|
+
`sustained-compute` and `rounding-tax` both resolve to `depot-ubuntu-24.04`. They stay distinct so a short billed-rounding job cannot be confused with a long compute job in the table. `app-token` resolves to `ubuntu-latest`. `deploy-credentials` also resolves to `ubuntu-latest` and is refused inside `assertVerifyRunnerPolicy`, because a Verify workflow has no Cloudflare production secret. HQ deploy keeps a literal `ubuntu-latest` and never calls this assert.
|
|
105
|
+
|
|
106
|
+
Candidate lifecycle support follows the same boundary. Use `FACTORY_CANDIDATE_PULL_REQUEST_TYPES` for the pull-request trigger matrix and `factoryCandidateOrPushCondition()` on each substantive job. With an optional caller-owned path condition, the helper emits the GitHub expression that runs merge-target pushes unconditionally and runs pull-request work only when GitHub's draft boolean says the pull request is a Candidate. The runner label still comes from the kind table above.
|
|
107
|
+
|
|
80
108
|
Migration: upgrade `@patronage/factory-ci`, apply the shared trigger types and job condition in the TypeScript workflow source, then regenerate and commit the emitted YAML. Draft `opened` and `synchronize` events will stop running substantive Verify work. Promotion through `ready_for_review`, later non-draft Candidate events, and merge-target pushes continue to run their applicable battery. A skipped draft run is presentation, not proof.
|
|
81
109
|
|
|
82
110
|
Production impact support follows the same ownership line. A generated deploy workflow declares only its target names and consumes the returned decision step and fail-open `demandedIf(target, decisionJob)` expressions:
|
|
@@ -257,7 +285,10 @@ The audit job is `always()` so a failed destroy still runs the stage-wide check.
|
|
|
257
285
|
### Merge-freeze writer job
|
|
258
286
|
|
|
259
287
|
```ts
|
|
260
|
-
import {
|
|
288
|
+
import {
|
|
289
|
+
factoryMergeFreezeJob,
|
|
290
|
+
factoryVerifyRunner,
|
|
291
|
+
} from "@patronage/factory-ci";
|
|
261
292
|
|
|
262
293
|
const freeze = factoryMergeFreezeJob({
|
|
263
294
|
createGithubAppToken: workflowArtifact.actions.createGithubAppToken,
|
|
@@ -272,7 +303,7 @@ const jobs = [
|
|
|
272
303
|
name: freeze.jobName,
|
|
273
304
|
needs: freeze.needs,
|
|
274
305
|
if: freeze.if,
|
|
275
|
-
runsOn: "
|
|
306
|
+
runsOn: factoryVerifyRunner("app-token").label,
|
|
276
307
|
permissions: freeze.permissions,
|
|
277
308
|
timeoutMinutes: 10,
|
|
278
309
|
steps: freeze.steps,
|
|
@@ -282,7 +313,7 @@ const jobs = [
|
|
|
282
313
|
|
|
283
314
|
A consumer's generated merge-target-push Verify workflow is the only producer of `patronage-factory/merge-freeze` generations (#356, ADR 0016 as amended; #429, #872). A red merge-target Verify completes a generation active, a green one completes it inactive, and `pr:ready` reads the generation on the candidate's own base tip. No command, scheduled job, or second workflow writes that check run; the operator override (`demand:waive --demand merge-freeze`) waives the demand for one candidate and never writes here.
|
|
284
315
|
|
|
285
|
-
`factoryMergeFreezeJob` owns everything a reader trusts: the check name (`FACTORY_MERGE_FREEZE_CHECK_NAME`), the pinned Patronage Factory App identity, the `actions/create-github-app-token` inputs, the `needs.*.result` fold (`FACTORY_MERGE_FREEZE_VERIFY_RESULT_EXPRESSION`), the merge-target condition (`FACTORY_MERGE_FREEZE_IF`, built from `FACTORY_MERGE_TARGET_REF_CONDITION`), and the job's least-privilege `permissions` (an empty block: the App installation token carries the check-run write, the job uses no `GITHUB_TOKEN` scope, and it never checks out the repository). The caller owns the runner, the timeout, and the `needs` list.
|
|
316
|
+
`factoryMergeFreezeJob` owns everything a reader trusts: the check name (`FACTORY_MERGE_FREEZE_CHECK_NAME`), the pinned Patronage Factory App identity, the `actions/create-github-app-token` inputs, the `needs.*.result` fold (`FACTORY_MERGE_FREEZE_VERIFY_RESULT_EXPRESSION`), the merge-target condition (`FACTORY_MERGE_FREEZE_IF`, built from `FACTORY_MERGE_TARGET_REF_CONDITION`), and the job's least-privilege `permissions` (an empty block: the App installation token carries the check-run write, the job uses no `GITHUB_TOKEN` scope, and it never checks out the repository). The caller owns the runner kind (`app-token` for the Factory App token), the timeout, and the `needs` list.
|
|
286
317
|
|
|
287
318
|
That `needs` list is the whole trust input: it must name **every authoritative verification leaf**, because the fold is what decides active or inactive. An empty list is refused — a fold over no needed job always reports success, which would report a merge target nothing verified as green.
|
|
288
319
|
|
|
@@ -332,10 +363,10 @@ const coreCommands = profile.verification.commands.filter(
|
|
|
332
363
|
);
|
|
333
364
|
|
|
334
365
|
const core = job("core", {
|
|
335
|
-
// `
|
|
336
|
-
//
|
|
337
|
-
//
|
|
338
|
-
permissions: { checks: "read", contents: "read"
|
|
366
|
+
// `checks: read` is all the default PR-only gate needs. Add
|
|
367
|
+
// `pull-requests: read` only with `reuse: "pull-request-and-default-branch"`,
|
|
368
|
+
// where the merge fallback (#611) looks up the producing pull request.
|
|
369
|
+
permissions: { checks: "read", contents: "read" },
|
|
339
370
|
steps: [
|
|
340
371
|
step(factoryProofGateStep({ commands: coreCommands, surface: "core" })),
|
|
341
372
|
step(factoryProofTimingStartStep()),
|
|
@@ -347,9 +378,11 @@ const core = job("core", {
|
|
|
347
378
|
|
|
348
379
|
The **proof-reuse gate** decides whether a hosted job may reuse the local verification the factory already published for this exact head (ADR 0022). Refusing runs hosted CI; it never fails the candidate. The App identity, check name, step id, output names, guard, and every trust predicate are fixed here rather than consumer-configurable — three repositories had grown three answers to the same question and had already drifted.
|
|
349
380
|
|
|
350
|
-
The gate runs on pull requests
|
|
381
|
+
**The gate runs on pull requests only by default** (#863). A merge target's guarantee is about the whole tree, not the diff, and a candidate proof binds only the diff that candidate was scoped to. Every default-branch push therefore executes the full hosted suite, and the job needs only `checks: read`. #931 is the defect this default answers: a `main` push reused an impact-scoped candidate proof, executed no command, and left a red package unnoticed for three days.
|
|
382
|
+
|
|
383
|
+
`reuse` chooses which events may reuse proof (#873). It takes two values and nothing else, because the emitted condition is a trust predicate: a free-text override lets a consumer reuse proof on a ref nothing proved. `"pull-request"` is the default described above. `"pull-request-and-default-branch"` is the opt-out: it adds pushes to the default branch, and the consumer accepts that an impact-scoped candidate proof may gate a whole tree. Choose it when merge-target runner time matters more than that guarantee.
|
|
351
384
|
|
|
352
|
-
|
|
385
|
+
Under the opt-out, a push run first reads proof at the pushed head. A squash merge mints a new commit, so when that direct read finds no generation at all, a **merge fallback** looks up the single merged pull request whose merge commit is the pushed head and reuses that PR head's proven check run — only when the merge commit's tree id equals the proven head's tree id, which makes the pushed content byte-identical to what was verified (a clean squash of an unchanged tip). Patch identity was considered and rejected as the comparator: `git patch-id` normalizes whitespace and ignores base motion, so an identical patch can still integrate into a tree nothing verified. No unique producing PR, an unreadable commit, a proof that is anything but proven, or tree drift from a dirty or stale merge all leave the direct refusal standing and run the full suite. The fallback needs `pull-requests: read` and `contents: read` on the job in addition to `checks: read`; a job that grants less loses only the fallback.
|
|
353
386
|
|
|
354
387
|
A proof is reusable only when the complete Checks API result (`filter=all`, every page) establishes one unambiguous newest generation by greatest `started_at`, produced by the pinned App for the exact repository and head, completed successfully with `outcome: passed`, and covering every command identity the guarded surface requires. Coverage is the union of `executedCommands` and commands released by `notRequiredCommands` only after the gate validates each release against that same proof binding's command-to-target map and identity-bound impact stamp. Missing, malformed, duplicated, affected, unknown, or differently bound release data refuses reuse and runs the hosted surface. `mode` is reported as diagnostic metadata, never authorized on: a reduced-mode proof whose executed and stamp-authorized released commands cover a surface is reusable.
|
|
355
388
|
|
|
@@ -450,9 +483,9 @@ The factory publishes its check runs, and paitronage its proof comments, under t
|
|
|
450
483
|
|
|
451
484
|
`mintInstallationToken({ credentials, owner, repo }, options?)` is that mechanism and nothing more. `credentials` is `{ appId, installationId?, privateKeyPath }`: the app id GitHub issued, the installation when a consumer has recorded one, and the path to the private key. The key is a _path_, not key material, so no caller has to hold a secret in memory to make this call — and **where that path comes from stays the consumer's**. Operator config, a secret manager, an environment variable: this package neither reads a config file nor knows a key-path convention. `githubAppJwt(credentials, options?)` is the signing step alone, for a caller that needs the app JWT rather than an installation token.
|
|
452
485
|
|
|
453
|
-
Nothing is cached. The token is returned to the caller, which owns its lifetime — this module keeps no copy of the token or the key, and never writes either to any output.
|
|
486
|
+
Nothing is cached. The token is returned to the caller, which owns its lifetime — this module keeps no copy of the token or the key, and never writes either to any output. Mint requests send `Connection: close` and retry transport failures — fetch rejection, timeout, a body that dies mid-read, or 408/429/5xx — with doubling backoff for three attempts by default. Exhaustion throws `GitHubAppTokenTransportError` with the attempt count and a bounded credential-free reason. Any other 4xx is GitHub's verdict and throws `GitHubApiError` on the first response without retrying. The budget is the caller's: `transportAttempts` sets the total attempts, and a caller whose mint is best-effort and already has a fallback passes `1` so the fallback stays prompt. The backoff is fixed and does not scale with `timeoutMs`.
|
|
454
487
|
|
|
455
|
-
`options` are all injectable seams: `fetch`, `now`, `readPrivateKey`, and a `timeoutMs` per request (five seconds by default). Tests substitute the
|
|
488
|
+
`options` are all injectable seams: `fetch`, `now`, `readPrivateKey`, `transportAttempts`, and a `timeoutMs` per request (five seconds by default). Tests substitute the seams; production passes at most a timeout and a budget.
|
|
456
489
|
|
|
457
490
|
### Vitest suite profiling
|
|
458
491
|
|
package/dist/index.d.ts
CHANGED
|
@@ -471,6 +471,20 @@ interface FactoryWorkflowArtifact<Additional extends Readonly<Record<string, Pin
|
|
|
471
471
|
*/
|
|
472
472
|
declare const factoryWorkflow: <const Additional extends Readonly<Record<string, PinnedAction>> = Record<never, never>>(options: FactoryWorkflowOptions<Additional>) => FactoryWorkflowArtifact<Additional>;
|
|
473
473
|
//#endregion
|
|
474
|
+
//#region src/github-transport.d.ts
|
|
475
|
+
/**
|
|
476
|
+
* Bounded transport retries could not reach the GitHub App endpoint (#923).
|
|
477
|
+
* The reason is credential-free, so it is safe to log. Re-exported from
|
|
478
|
+
* `github-app-token.ts`, which owns the mint surface.
|
|
479
|
+
*/
|
|
480
|
+
declare class GitHubAppTokenTransportError extends Error {
|
|
481
|
+
/** How many transport attempts were made before giving up. */
|
|
482
|
+
readonly attempts: number;
|
|
483
|
+
/** The last transient HTTP status, when the server answered at all. */
|
|
484
|
+
readonly status?: number;
|
|
485
|
+
constructor(reason: string, attempts: number, status?: number);
|
|
486
|
+
}
|
|
487
|
+
//#endregion
|
|
474
488
|
//#region src/github-app-token.d.ts
|
|
475
489
|
/**
|
|
476
490
|
* What a consumer must know to mint: the app id, where the private key is, and
|
|
@@ -500,6 +514,15 @@ interface GithubAppTokenOptions {
|
|
|
500
514
|
readPrivateKey?: (privateKeyPath: string) => Buffer | string;
|
|
501
515
|
/** Per-request timeout; defaults to five seconds. */
|
|
502
516
|
timeoutMs?: number;
|
|
517
|
+
/**
|
|
518
|
+
* Total transport attempts per request, the first one included. Defaults to
|
|
519
|
+
* three: a mint that must survive a stale keep-alive socket (#923). A caller
|
|
520
|
+
* whose mint is best-effort and already has a fallback passes 1, because
|
|
521
|
+
* retrying against a deliberately short timeout only delays that fallback
|
|
522
|
+
* (#938). The backoff between attempts is fixed and does not scale with
|
|
523
|
+
* `timeoutMs`; the two dials are unrelated.
|
|
524
|
+
*/
|
|
525
|
+
transportAttempts?: number;
|
|
503
526
|
}
|
|
504
527
|
/**
|
|
505
528
|
* The signed app JWT GitHub accepts as `Authorization: Bearer` for the App
|
|
@@ -595,10 +618,13 @@ declare const executeAlchemyEntry: (options: ExecuteAlchemyEntryOptions) => Prom
|
|
|
595
618
|
*
|
|
596
619
|
* It answers one question, before checkout and before install: may this job
|
|
597
620
|
* reuse the local verification the factory already published for *this exact
|
|
598
|
-
* head*?
|
|
599
|
-
*
|
|
600
|
-
*
|
|
601
|
-
*
|
|
621
|
+
* head*? By default it asks that only on a pull request, so every
|
|
622
|
+
* default-branch push executes the full hosted suite (#863). A consumer may
|
|
623
|
+
* opt into `reuse: "pull-request-and-default-branch"`, where one fallback
|
|
624
|
+
* extends "this exact head" to "this exact tree": a squash merge whose result
|
|
625
|
+
* tree is byte-identical to the producing pull request's proven head tree
|
|
626
|
+
* reuses that head's proof (#611). Refusing runs hosted CI; it never fails
|
|
627
|
+
* the candidate. Three
|
|
602
628
|
* repositories had independently grown their own answer to that question and
|
|
603
629
|
* had already drifted apart, so the answer lives here once, with the App
|
|
604
630
|
* identity, check name, step identity, output names, guard, and every trust
|
|
@@ -676,18 +702,25 @@ declare const FACTORY_PROOF_GATE_SOURCE_URL_OUTPUT = "source-check-url";
|
|
|
676
702
|
*/
|
|
677
703
|
declare const FACTORY_PROOF_GATE_GUARD = "steps.factory-proof.outputs.reuse-proof != 'true'";
|
|
678
704
|
/**
|
|
679
|
-
* Pull requests, plus pushes to the merge target (#611)
|
|
705
|
+
* Pull requests, plus pushes to the merge target (#611) — the **opt-out**
|
|
706
|
+
* condition a consumer gets with `reuse: "pull-request-and-default-branch"`.
|
|
707
|
+
*
|
|
708
|
+
* It buys runner time on the merge target and pays for it in coverage. No
|
|
709
|
+
* local proof is ever written for a merged SHA, so the direct read finds
|
|
710
|
+
* nothing after a squash merge. The push path therefore exists only together
|
|
711
|
+
* with the merge fallback below: when no generation exists at the merged SHA,
|
|
712
|
+
* the gate may look up the producing pull request and reuse its head proof,
|
|
713
|
+
* but only when the merge result's tree is exactly the proven head's tree.
|
|
714
|
+
* Deploy gates keep waiting for a green required check at the merged SHA; a
|
|
715
|
+
* reused proof turns that check green through the same guarded steps a pull
|
|
716
|
+
* request uses.
|
|
680
717
|
*
|
|
681
|
-
* The
|
|
682
|
-
*
|
|
683
|
-
*
|
|
684
|
-
*
|
|
685
|
-
*
|
|
686
|
-
*
|
|
687
|
-
* head proof, but only when the merge result's tree is exactly the proven
|
|
688
|
-
* head's tree. Deploy gates keep
|
|
689
|
-
* waiting for a green required check at the merged SHA; a reused proof turns
|
|
690
|
-
* that check green through the same guarded steps a pull request uses.
|
|
718
|
+
* The trade this mode accepts: a candidate's proof may be impact-scoped, and
|
|
719
|
+
* an impact-scoped proof is a sound gate for the candidate and an unsound
|
|
720
|
+
* gate for the merge target. #931 is that defect observed — a `main` push
|
|
721
|
+
* reused a candidate proof, executed no command, and left a red package
|
|
722
|
+
* unnoticed for three days. Choose this mode only when the consumer accepts
|
|
723
|
+
* that risk for speed.
|
|
691
724
|
*
|
|
692
725
|
* The push side is deliberately the merge target only — the repository's
|
|
693
726
|
* default branch — not every pushed branch. Which branches trigger a
|
|
@@ -697,16 +730,16 @@ declare const FACTORY_PROOF_GATE_GUARD = "steps.factory-proof.outputs.reuse-proo
|
|
|
697
730
|
*/
|
|
698
731
|
declare const FACTORY_PROOF_GATE_IF = "github.event_name == 'pull_request' || (github.event_name == 'push' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch))";
|
|
699
732
|
/**
|
|
700
|
-
* Pull requests only — the condition
|
|
701
|
-
* `reuse: "pull-request"` (#873).
|
|
733
|
+
* Pull requests only — the **default** condition, and the condition a
|
|
734
|
+
* consumer gets with `reuse: "pull-request"` (#873, default since #863).
|
|
702
735
|
*
|
|
703
|
-
* A
|
|
704
|
-
*
|
|
705
|
-
*
|
|
706
|
-
*
|
|
707
|
-
*
|
|
708
|
-
*
|
|
709
|
-
*
|
|
736
|
+
* A merge target's guarantee is about the whole tree, not the diff. A
|
|
737
|
+
* candidate proof only ever binds the diff the candidate was scoped to, so
|
|
738
|
+
* reusing it on the merged commit proves less than the merge target needs.
|
|
739
|
+
* Every default-branch push therefore executes the full hosted suite,
|
|
740
|
+
* whatever proof exists. The mode also skips the push-event merge fallback,
|
|
741
|
+
* so no push path can reuse proof even if the workflow reaches the step
|
|
742
|
+
* through some other trigger.
|
|
710
743
|
*
|
|
711
744
|
* Which branches trigger the workflow at all stays repository-owned. This
|
|
712
745
|
* condition only keeps the gate from consulting proof outside a pull request.
|
|
@@ -720,10 +753,12 @@ declare const FACTORY_PROOF_GATE_PULL_REQUEST_IF = "github.event_name == 'pull_r
|
|
|
720
753
|
* unproven ref — and the emitted condition is a trust predicate. The consumer
|
|
721
754
|
* chooses the mode; `factory-ci` owns what each mode emits.
|
|
722
755
|
*
|
|
723
|
-
* - `pull-request
|
|
724
|
-
* the
|
|
725
|
-
* - `pull-request` pull requests
|
|
726
|
-
*
|
|
756
|
+
* - `pull-request` (default) pull requests only. Default-branch pushes
|
|
757
|
+
* execute the full hosted suite, and the merge fallback is not emitted.
|
|
758
|
+
* - `pull-request-and-default-branch` the opt-out: pull requests plus pushes
|
|
759
|
+
* to the merge target, with the merge fallback. It is faster on the merge
|
|
760
|
+
* target and it lets an impact-scoped candidate proof gate a whole tree
|
|
761
|
+
* (#931), so a consumer must ask for it.
|
|
727
762
|
*/
|
|
728
763
|
type FactoryProofGateReuse = "pull-request" | "pull-request-and-default-branch";
|
|
729
764
|
/**
|
|
@@ -810,10 +845,10 @@ interface FactoryProofGateOptions {
|
|
|
810
845
|
*/
|
|
811
846
|
readonly commands: readonly ProofReuseCommand[];
|
|
812
847
|
/**
|
|
813
|
-
* Which events may reuse proof. Defaults to
|
|
814
|
-
*
|
|
815
|
-
*
|
|
816
|
-
*
|
|
848
|
+
* Which events may reuse proof. Defaults to `"pull-request"`: every
|
|
849
|
+
* default-branch push executes the full hosted suite (#873, #863).
|
|
850
|
+
* `"pull-request-and-default-branch"` is the opt-out a consumer chooses for
|
|
851
|
+
* speed on the merge target, and it accepts #931's risk.
|
|
817
852
|
*/
|
|
818
853
|
readonly reuse?: FactoryProofGateReuse;
|
|
819
854
|
/** Names the suite in the job summary. Changes no trust decision. */
|
|
@@ -852,13 +887,14 @@ interface FactoryProofGateStep {
|
|
|
852
887
|
* The step itself, structurally accepted by gagen's `step()` without adding a
|
|
853
888
|
* gagen runtime dependency. It belongs first in the job it guards: a few API
|
|
854
889
|
* reads with the default `GITHUB_TOKEN`, no checkout, no install, so a proven
|
|
855
|
-
* head costs a runner nothing beyond job startup.
|
|
856
|
-
* `
|
|
857
|
-
*
|
|
858
|
-
*
|
|
859
|
-
*
|
|
860
|
-
*
|
|
861
|
-
*
|
|
890
|
+
* head costs a runner nothing beyond job startup. The default
|
|
891
|
+
* `reuse: "pull-request"` emits neither the push clause nor the fallback, and
|
|
892
|
+
* needs only `checks: read` (#873, #863). A consumer that opts into
|
|
893
|
+
* `reuse: "pull-request-and-default-branch"` also runs the push-event merge
|
|
894
|
+
* fallback, which reads the producing pull request (`pull-requests: read`)
|
|
895
|
+
* and the two commit objects whose tree ids it compares (`contents: read`).
|
|
896
|
+
* A job that grants less loses only the fallback — the failed read degrades
|
|
897
|
+
* to the full suite.
|
|
862
898
|
*
|
|
863
899
|
* A **step, not a job**, and that is not a style preference. A separate gate
|
|
864
900
|
* job that errored would leave the guarded job `skipped`, and a summary job
|
|
@@ -1541,6 +1577,53 @@ type VitestProfileReadResult = {
|
|
|
1541
1577
|
*/
|
|
1542
1578
|
declare const readVitestProfileDocument: (value: unknown) => VitestProfileReadResult;
|
|
1543
1579
|
//#endregion
|
|
1580
|
+
//#region src/verify-runner-policy.d.ts
|
|
1581
|
+
declare const DEPOT_LABEL = "depot-ubuntu-24.04";
|
|
1582
|
+
declare const HOSTED_LABEL = "ubuntu-latest";
|
|
1583
|
+
/**
|
|
1584
|
+
* Why a job runs where it does. The two Depot kinds share a label. They stay
|
|
1585
|
+
* distinct so billed-rounding jobs and long compute jobs cannot be confused
|
|
1586
|
+
* in the declaration table.
|
|
1587
|
+
*
|
|
1588
|
+
* `deploy-credentials` is legal to construct (HQ deploy).
|
|
1589
|
+
* `assertVerifyRunnerPolicy` refuses it, because a Verify workflow has no
|
|
1590
|
+
* Cloudflare production secret.
|
|
1591
|
+
*/
|
|
1592
|
+
type FactoryRunnerKind = "sustained-compute" | "rounding-tax" | "app-token" | "deploy-credentials";
|
|
1593
|
+
type FactoryRunnerHome = "depot" | "hosted";
|
|
1594
|
+
interface FactoryRunnerOverride {
|
|
1595
|
+
readonly kind: "override";
|
|
1596
|
+
readonly home: FactoryRunnerHome;
|
|
1597
|
+
readonly why: string;
|
|
1598
|
+
}
|
|
1599
|
+
type FactoryRunnerDecision = FactoryRunnerKind | FactoryRunnerOverride;
|
|
1600
|
+
interface FactoryVerifyRunner {
|
|
1601
|
+
readonly decision: FactoryRunnerDecision;
|
|
1602
|
+
readonly home: FactoryRunnerHome;
|
|
1603
|
+
readonly label: typeof DEPOT_LABEL | typeof HOSTED_LABEL;
|
|
1604
|
+
readonly why?: string;
|
|
1605
|
+
}
|
|
1606
|
+
type FactoryVerifyRunnerTable = Readonly<Record<string, FactoryVerifyRunner>>;
|
|
1607
|
+
interface FactoryVerifyRunnerPolicyInput {
|
|
1608
|
+
readonly source: string;
|
|
1609
|
+
readonly jobs: FactoryVerifyRunnerTable;
|
|
1610
|
+
}
|
|
1611
|
+
interface FactoryVerifyRunnerMismatch {
|
|
1612
|
+
readonly jobId: string;
|
|
1613
|
+
readonly expected: string;
|
|
1614
|
+
readonly actual: string;
|
|
1615
|
+
}
|
|
1616
|
+
interface FactoryVerifyRunnerPolicyReport {
|
|
1617
|
+
readonly ok: boolean;
|
|
1618
|
+
readonly undeclared: readonly string[];
|
|
1619
|
+
readonly missing: readonly string[];
|
|
1620
|
+
readonly mismatches: readonly FactoryVerifyRunnerMismatch[];
|
|
1621
|
+
readonly forbiddenKinds: readonly string[];
|
|
1622
|
+
}
|
|
1623
|
+
declare const factoryVerifyRunner: (decision: FactoryRunnerDecision) => FactoryVerifyRunner;
|
|
1624
|
+
declare const verifyRunnerPolicy: (yaml: string, input: FactoryVerifyRunnerPolicyInput) => FactoryVerifyRunnerPolicyReport;
|
|
1625
|
+
declare const assertVerifyRunnerPolicy: (yaml: string, input: FactoryVerifyRunnerPolicyInput) => FactoryVerifyRunnerPolicyReport;
|
|
1626
|
+
//#endregion
|
|
1544
1627
|
//#region src/workflow-shell-lint.d.ts
|
|
1545
1628
|
/**
|
|
1546
1629
|
* Parse-check the shell embedded in generated workflow YAML (#376).
|
|
@@ -1592,4 +1675,4 @@ declare const assertWorkflowShellParses: (yaml: string, options: {
|
|
|
1592
1675
|
readonly source: string;
|
|
1593
1676
|
}) => void;
|
|
1594
1677
|
//#endregion
|
|
1595
|
-
export { type BundleAlchemyEntryOptions, type CheckoutStepOptions, EXECUTE_ALCHEMY_ENTRY_MAX_BUFFER, type ExecuteAlchemyEntryOptions, type ExecuteAlchemyEntryResult, FACTORY_CANDIDATE_IMPACT_BASIS_OUTPUT, FACTORY_CANDIDATE_IMPACT_DECISION_OUTPUT, FACTORY_CANDIDATE_IMPACT_INERT_OUTPUT, FACTORY_CANDIDATE_IMPACT_STEP_ID, FACTORY_CANDIDATE_IMPACT_UNSUBSCRIBED_OUTPUT, FACTORY_CANDIDATE_PULL_REQUEST_TYPES, FACTORY_LIFECYCLE_CONTRACT_JOB_ID, FACTORY_LIFECYCLE_CONTRACT_JOB_NAME, FACTORY_MERGE_FREEZE_APP_TOKEN_STEP_ID, FACTORY_MERGE_FREEZE_CHECK_NAME, FACTORY_MERGE_FREEZE_IF, FACTORY_MERGE_FREEZE_JOB_ID, FACTORY_MERGE_FREEZE_JOB_NAME, FACTORY_MERGE_FREEZE_PERMISSIONS, FACTORY_MERGE_FREEZE_VERIFY_RESULT_EXPRESSION, FACTORY_MERGE_TARGET_REF_CONDITION, FACTORY_PREVIEW_CLEANUP_AUDIT_JOB_ID, FACTORY_PREVIEW_CLEANUP_AUDIT_JOB_NAME, FACTORY_PRODUCTION_IMPACT_BASIS_OUTPUT, FACTORY_PRODUCTION_IMPACT_DECISION_OUTPUT, FACTORY_PRODUCTION_IMPACT_STEP_ID, FACTORY_PRODUCTION_IMPACT_UNSUBSCRIBED_OUTPUT, FACTORY_PROOF_GATE_APP_ID, FACTORY_PROOF_GATE_CHECK_NAME, FACTORY_PROOF_GATE_GUARD, FACTORY_PROOF_GATE_IF, FACTORY_PROOF_GATE_MODE_OUTPUT, FACTORY_PROOF_GATE_OUTPUT, FACTORY_PROOF_GATE_PULL_REQUEST_IF, FACTORY_PROOF_GATE_REASONS, FACTORY_PROOF_GATE_REASON_OUTPUT, FACTORY_PROOF_GATE_SHELL, FACTORY_PROOF_GATE_SOURCE_URL_OUTPUT, FACTORY_PROOF_GATE_STEP_ID, FACTORY_PROOF_GATE_STEP_NAME, FACTORY_PR_STATUS_HUD_APP_TOKEN_STEP_ID, FACTORY_PR_STATUS_HUD_CONCURRENCY, FACTORY_PR_STATUS_HUD_IF, FACTORY_PR_STATUS_HUD_JOB_ID, FACTORY_PR_STATUS_HUD_JOB_NAME, FACTORY_PR_STATUS_HUD_PERMISSIONS, FACTORY_PR_STATUS_HUD_PLAN_PATH, FACTORY_PUSH_IDENTITY_SCHEMA_VERSION, type FactoryCandidateImpactWorkflow, type FactoryCandidateImpactWorkflowOptions, type FactoryLifecycleContractLane, type FactoryLifecycleContractLaneOptions, type FactoryMergeFreezeJob, type FactoryMergeFreezeJobOptions, type FactoryPrStatusHudWorkflow, type FactoryPrStatusHudWorkflowOptions, type FactoryPreviewCleanupAuditJob, type FactoryPreviewCleanupDestroyJob, type FactoryPreviewCleanupTopology, type FactoryPreviewCleanupTopologyOptions, type FactoryProductionImpactWorkflow, type FactoryProductionImpactWorkflowOptions, type FactoryProofGateOptions, type FactoryProofGateReason, type FactoryProofGateReuse, type FactoryProofGateStep, type FactoryProofReusePresentationOptions, type FactoryProofReuseSummaryStep, type FactoryProofTimingStartStep, type FactoryPushIdentityConsumer, type FactoryPushIdentityConsumerOptions, type FactoryPushIdentityDisposition, type FactoryPushIdentityEnvelope, type FactoryPushIdentityProducer, type FactoryPushIdentityProducerOptions, type FactoryWorkflowArtifact, type FactoryWorkflowOptions, type FactoryWorkflowSetupOptions, GitHubApiError, type GithubAppCredentials, type GithubAppTokenOptions, type InstallStepOptions, type LocalPreviewStage, type LocalPreviewStageOptions, NODE_PNPM_ACTION_FAMILY_NODE24, type NodePnpmActionFamily, PREVIEW_PROOF_INVENTORY_CHECK_NAME, PREVIEW_PROOF_INVENTORY_LIST_PERMISSIONS, type ParseLocalPreviewStageExpected, type ParsedLocalPreviewStage, type PinnedAction, type PreviewProofCandidate, type PreviewProofCleanupOutcome, type PreviewProofCleanupStatus, type PreviewProofEnvelopeEvidence, type PreviewProofInventoryAccess, type PreviewProofInventoryCleanupInput, type PreviewProofInventoryListInput, type PreviewProofInventoryPersistInput, type PreviewProofInventoryStore, type PreviewProofInventoryTransport, type PreviewProofLifecycleEvent, type PreviewProofLifecycleState, type PreviewProofRegistration, type PreviewProofResolveQuery, PreviewProofTransportError, type ProofReuseCommand, type ProofReuseCoverageInput, type ProofReuseCoverageReport, type SetupNodeStepOptions, UPLOAD_ARTIFACT, VITEST_PROFILE_SCHEMA_VERSION, VITEST_PROFILE_TOOL, type VitestJsonReport, type VitestProfile, type VitestProfileDependencies, type VitestProfileDurationSummary, type VitestProfileEnvironment, VitestProfileError, type VitestProfileOptions, type VitestProfileReadResult, type VitestProfileSample, type VitestProfileSampleExecution, type VitestTestStatus, type WorkflowShellParseFailure, type WorkflowStep, assertProofReuseCoverage, assertWorkflowShellParses, bundleAlchemyEntry, captureVitestProfileEnvironment, executeAlchemyEntry, factoryCandidateImpactWorkflow, factoryCandidateOrPushCondition, factoryLifecycleContractLane, factoryMergeFreezeJob, factoryMergeFreezeScript, factoryPrStatusHudWorkflow, factoryPreviewCleanupTopology, factoryProductionImpactWorkflow, factoryProofGateScript, factoryProofGateStep, factoryProofReuseSummaryStep, factoryProofTimingStartStep, factoryPushIdentityConsumer, factoryPushIdentityProducer, factoryWorkflow, githubAppJwt, isLocalPreviewStage, localPreviewStage, mintInstallationToken, normalizeVitestProfileSample, parseLocalPreviewStage, previewCleanupDestroyJobId, previewProofInventory, previewProofLifecycle, productionImpactTargetOutput, proofReuseCoverage, proofReuseRequiredCommands, readVitestProfileDocument, resolveProofReuseCommands, runVitestProfile, workflowRunBlocks, workflowShellParseFailures, writeVitestProfile };
|
|
1678
|
+
export { type BundleAlchemyEntryOptions, type CheckoutStepOptions, EXECUTE_ALCHEMY_ENTRY_MAX_BUFFER, type ExecuteAlchemyEntryOptions, type ExecuteAlchemyEntryResult, FACTORY_CANDIDATE_IMPACT_BASIS_OUTPUT, FACTORY_CANDIDATE_IMPACT_DECISION_OUTPUT, FACTORY_CANDIDATE_IMPACT_INERT_OUTPUT, FACTORY_CANDIDATE_IMPACT_STEP_ID, FACTORY_CANDIDATE_IMPACT_UNSUBSCRIBED_OUTPUT, FACTORY_CANDIDATE_PULL_REQUEST_TYPES, FACTORY_LIFECYCLE_CONTRACT_JOB_ID, FACTORY_LIFECYCLE_CONTRACT_JOB_NAME, FACTORY_MERGE_FREEZE_APP_TOKEN_STEP_ID, FACTORY_MERGE_FREEZE_CHECK_NAME, FACTORY_MERGE_FREEZE_IF, FACTORY_MERGE_FREEZE_JOB_ID, FACTORY_MERGE_FREEZE_JOB_NAME, FACTORY_MERGE_FREEZE_PERMISSIONS, FACTORY_MERGE_FREEZE_VERIFY_RESULT_EXPRESSION, FACTORY_MERGE_TARGET_REF_CONDITION, FACTORY_PREVIEW_CLEANUP_AUDIT_JOB_ID, FACTORY_PREVIEW_CLEANUP_AUDIT_JOB_NAME, FACTORY_PRODUCTION_IMPACT_BASIS_OUTPUT, FACTORY_PRODUCTION_IMPACT_DECISION_OUTPUT, FACTORY_PRODUCTION_IMPACT_STEP_ID, FACTORY_PRODUCTION_IMPACT_UNSUBSCRIBED_OUTPUT, FACTORY_PROOF_GATE_APP_ID, FACTORY_PROOF_GATE_CHECK_NAME, FACTORY_PROOF_GATE_GUARD, FACTORY_PROOF_GATE_IF, FACTORY_PROOF_GATE_MODE_OUTPUT, FACTORY_PROOF_GATE_OUTPUT, FACTORY_PROOF_GATE_PULL_REQUEST_IF, FACTORY_PROOF_GATE_REASONS, FACTORY_PROOF_GATE_REASON_OUTPUT, FACTORY_PROOF_GATE_SHELL, FACTORY_PROOF_GATE_SOURCE_URL_OUTPUT, FACTORY_PROOF_GATE_STEP_ID, FACTORY_PROOF_GATE_STEP_NAME, FACTORY_PR_STATUS_HUD_APP_TOKEN_STEP_ID, FACTORY_PR_STATUS_HUD_CONCURRENCY, FACTORY_PR_STATUS_HUD_IF, FACTORY_PR_STATUS_HUD_JOB_ID, FACTORY_PR_STATUS_HUD_JOB_NAME, FACTORY_PR_STATUS_HUD_PERMISSIONS, FACTORY_PR_STATUS_HUD_PLAN_PATH, FACTORY_PUSH_IDENTITY_SCHEMA_VERSION, type FactoryCandidateImpactWorkflow, type FactoryCandidateImpactWorkflowOptions, type FactoryLifecycleContractLane, type FactoryLifecycleContractLaneOptions, type FactoryMergeFreezeJob, type FactoryMergeFreezeJobOptions, type FactoryPrStatusHudWorkflow, type FactoryPrStatusHudWorkflowOptions, type FactoryPreviewCleanupAuditJob, type FactoryPreviewCleanupDestroyJob, type FactoryPreviewCleanupTopology, type FactoryPreviewCleanupTopologyOptions, type FactoryProductionImpactWorkflow, type FactoryProductionImpactWorkflowOptions, type FactoryProofGateOptions, type FactoryProofGateReason, type FactoryProofGateReuse, type FactoryProofGateStep, type FactoryProofReusePresentationOptions, type FactoryProofReuseSummaryStep, type FactoryProofTimingStartStep, type FactoryPushIdentityConsumer, type FactoryPushIdentityConsumerOptions, type FactoryPushIdentityDisposition, type FactoryPushIdentityEnvelope, type FactoryPushIdentityProducer, type FactoryPushIdentityProducerOptions, type FactoryRunnerDecision, type FactoryRunnerHome, type FactoryRunnerKind, type FactoryRunnerOverride, type FactoryVerifyRunner, type FactoryVerifyRunnerMismatch, type FactoryVerifyRunnerPolicyInput, type FactoryVerifyRunnerPolicyReport, type FactoryVerifyRunnerTable, type FactoryWorkflowArtifact, type FactoryWorkflowOptions, type FactoryWorkflowSetupOptions, GitHubApiError, GitHubAppTokenTransportError, type GithubAppCredentials, type GithubAppTokenOptions, type InstallStepOptions, type LocalPreviewStage, type LocalPreviewStageOptions, NODE_PNPM_ACTION_FAMILY_NODE24, type NodePnpmActionFamily, PREVIEW_PROOF_INVENTORY_CHECK_NAME, PREVIEW_PROOF_INVENTORY_LIST_PERMISSIONS, type ParseLocalPreviewStageExpected, type ParsedLocalPreviewStage, type PinnedAction, type PreviewProofCandidate, type PreviewProofCleanupOutcome, type PreviewProofCleanupStatus, type PreviewProofEnvelopeEvidence, type PreviewProofInventoryAccess, type PreviewProofInventoryCleanupInput, type PreviewProofInventoryListInput, type PreviewProofInventoryPersistInput, type PreviewProofInventoryStore, type PreviewProofInventoryTransport, type PreviewProofLifecycleEvent, type PreviewProofLifecycleState, type PreviewProofRegistration, type PreviewProofResolveQuery, PreviewProofTransportError, type ProofReuseCommand, type ProofReuseCoverageInput, type ProofReuseCoverageReport, type SetupNodeStepOptions, UPLOAD_ARTIFACT, VITEST_PROFILE_SCHEMA_VERSION, VITEST_PROFILE_TOOL, type VitestJsonReport, type VitestProfile, type VitestProfileDependencies, type VitestProfileDurationSummary, type VitestProfileEnvironment, VitestProfileError, type VitestProfileOptions, type VitestProfileReadResult, type VitestProfileSample, type VitestProfileSampleExecution, type VitestTestStatus, type WorkflowShellParseFailure, type WorkflowStep, assertProofReuseCoverage, assertVerifyRunnerPolicy, assertWorkflowShellParses, bundleAlchemyEntry, captureVitestProfileEnvironment, executeAlchemyEntry, factoryCandidateImpactWorkflow, factoryCandidateOrPushCondition, factoryLifecycleContractLane, factoryMergeFreezeJob, factoryMergeFreezeScript, factoryPrStatusHudWorkflow, factoryPreviewCleanupTopology, factoryProductionImpactWorkflow, factoryProofGateScript, factoryProofGateStep, factoryProofReuseSummaryStep, factoryProofTimingStartStep, factoryPushIdentityConsumer, factoryPushIdentityProducer, factoryVerifyRunner, factoryWorkflow, githubAppJwt, isLocalPreviewStage, localPreviewStage, mintInstallationToken, normalizeVitestProfileSample, parseLocalPreviewStage, previewCleanupDestroyJobId, previewProofInventory, previewProofLifecycle, productionImpactTargetOutput, proofReuseCoverage, proofReuseRequiredCommands, readVitestProfileDocument, resolveProofReuseCommands, runVitestProfile, verifyRunnerPolicy, workflowRunBlocks, workflowShellParseFailures, writeVitestProfile };
|