@patronage/factory-ci 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/README.md +3 -3
- package/dist/.build-fingerprint.json +1 -1
- package/dist/index.d.ts +11 -1
- package/dist/index.js +8 -2
- package/package.json +1 -1
- package/src/preview-proof-lifecycle.ts +26 -7
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ It is a **pure library**. There is no `bin`: anything invocable belongs to `psf`
|
|
|
14
14
|
|
|
15
15
|
Three repositories independently built the same scaffolding and then drifted: three wordings of the same setup block, the same `@v4` action tags pinned to different commits because they were generated months apart, two esbuild pre-bundles, two Alchemy CLI wrappers, and one disposable-stage grammar invented twice.
|
|
16
16
|
|
|
17
|
-
The admission rule is **
|
|
17
|
+
The admission rule is **experiment, prove, fan out** (ADR 0036). A mechanism starts as an experiment in one project. When the experiment proves it and the intent is fleet-wide, it moves into the factory: into this package, or into `psf` when it is invocable. Then it fans out to the other projects. A single consumer is a risk to record, not a gate. The public surface is made of deep modules, not a workflow framework.
|
|
18
18
|
|
|
19
19
|
## Exports
|
|
20
20
|
|
|
@@ -459,7 +459,7 @@ import { previewProofLifecycle } from "@patronage/factory-ci";
|
|
|
459
459
|
|
|
460
460
|
Events:
|
|
461
461
|
|
|
462
|
-
- `begin` — the pre-deploy gate.
|
|
462
|
+
- `begin` — the pre-deploy gate. A mismatched branch name is accepted at the PR head SHA. A dirty tree or a SHA mismatch is refused, as are a draft, a closed PR, and any stage the disposable grammar does not own for this PR head. Does not create a registration.
|
|
463
463
|
- `register` — the same identity gate, plus passed smoke, passed convergence, and a passing three-way-bound evidence envelope at this head. Writes a registration with `cleanup.outcome: "pending"`. Cleanup, smoke, and convergence status live only on their nested objects, so no top-level copy can diverge from them. Machine evidence is that envelope; this module never posts a PR comment.
|
|
464
464
|
- `cleanup` — records an explicit passed or failed outcome onto an existing registration. Cleanup is never inferred from whether the stage still exists. `recover` is the failed-to-resolved re-entry of this event.
|
|
465
465
|
|
|
@@ -684,7 +684,7 @@ const result = await runAlchemyLifecycle({
|
|
|
684
684
|
});
|
|
685
685
|
```
|
|
686
686
|
|
|
687
|
-
`runAlchemyLifecycle` is the one operation over an Alchemy plan, deploy, or destroy. It owns the order; a consumer cannot get the order wrong because it does not call the steps. Paitronage and HQ each reconstructed this sequence from the guards below and got different parts wrong (paitronage#1917), which
|
|
687
|
+
`runAlchemyLifecycle` is the one operation over an Alchemy plan, deploy, or destroy. It owns the order; a consumer cannot get the order wrong because it does not call the steps. Paitronage and HQ each reconstructed this sequence from the guards below and got different parts wrong (paitronage#1917), which was the evidence that admitted it here.
|
|
688
688
|
|
|
689
689
|
The consumer passes typed policy and two capabilities. `state` is the store from `@patronage/alchemy-d1-state`: `snapshotStage`, `get`, and `acquireStageOwnership`, whose lease carries `renew`, `release`, `deleteEmptyStage` and `childEnvironment`, matched structurally, so this package learns no table, account, or SQL. `entry` is what `executeAlchemyEntry` needs minus what the lifecycle decides: the arguments are the lifecycle's, stdio is always piped so the plan can be read, and a `bundle` is built once up front, with every child launched in `cwd`, then the bundle root, then this process's directory — the same chain `executeAlchemyEntry` resolves. A bundle that cannot build is `PREPARATION_FAILED` and nothing else: the bundler's own diagnostics are silenced at the source. There is no `redact` and no echo because there is no sink: each child's captured output is parsed and dropped, written nowhere, so nothing in it can reach a job log or an evidence file through this operation. Every child's environment carries the stage lease; see ownership below.
|
|
690
690
|
|
package/dist/index.d.ts
CHANGED
|
@@ -178,7 +178,10 @@ declare const localPreviewStage: (options: LocalPreviewStageOptions) => LocalPre
|
|
|
178
178
|
*
|
|
179
179
|
* Proof sits entirely on one side of GitHub's draft/ready boundary
|
|
180
180
|
* (paitronage#1089): a draft candidate cannot begin or register. Machine
|
|
181
|
-
* evidence is the envelope at head (#355), never a PR comment.
|
|
181
|
+
* evidence is the envelope at head (#355), never a PR comment. The SHA and
|
|
182
|
+
* repository bind that proof (#1312): a detached HEAD or a branch name that
|
|
183
|
+
* differs from the PR head is the same commit, and the registration records
|
|
184
|
+
* `branch: "(detached)"`.
|
|
182
185
|
*/
|
|
183
186
|
type PreviewProofCleanupStatus = "failed" | "passed" | "pending";
|
|
184
187
|
interface PreviewProofCandidate {
|
|
@@ -206,6 +209,13 @@ interface PreviewProofCleanupOutcome {
|
|
|
206
209
|
readonly runUrl?: string;
|
|
207
210
|
}
|
|
208
211
|
interface PreviewProofRegistration {
|
|
212
|
+
/**
|
|
213
|
+
* Present only when the checkout is detached or its branch name is not the
|
|
214
|
+
* PR head. The `(detached)` sentinel is on the lifecycle registration
|
|
215
|
+
* `transition` returns, and `previewProofInventory` does not persist it.
|
|
216
|
+
* It is not a branch to check out.
|
|
217
|
+
*/
|
|
218
|
+
readonly branch?: "(detached)";
|
|
209
219
|
readonly cleanup: {
|
|
210
220
|
readonly evidence: readonly string[];
|
|
211
221
|
readonly outcome: PreviewProofCleanupStatus;
|
package/dist/index.js
CHANGED
|
@@ -74,6 +74,12 @@ const factoryCandidateOrPushCondition = (candidateCondition) => {
|
|
|
74
74
|
//#endregion
|
|
75
75
|
//#region src/preview-proof-lifecycle.ts
|
|
76
76
|
const EMPTY_STATE = Object.freeze({ registrations: Object.freeze([]) });
|
|
77
|
+
const DETACHED_PROOF_BRANCH = "(detached)";
|
|
78
|
+
/**
|
|
79
|
+
* The branch name adds no guarantee. Record the sentinel unless the checkout
|
|
80
|
+
* is actually on the PR head branch.
|
|
81
|
+
*/
|
|
82
|
+
const proofBranch = (candidate) => candidate.branch.length > 0 && candidate.branch === candidate.prHeadRef ? void 0 : DETACHED_PROOF_BRANCH;
|
|
77
83
|
const registrationKey$1 = (row) => `${row.stack}:${row.stage}`;
|
|
78
84
|
const disposableStage = (stage, candidate) => {
|
|
79
85
|
const parsed = parseLocalPreviewStage(stage, {
|
|
@@ -88,9 +94,7 @@ const assertAdmissibleCandidate = (candidate, stage) => {
|
|
|
88
94
|
if (candidate.prState !== "OPEN") throw new Error("Preview proof requires an open pull request.");
|
|
89
95
|
const errors = [];
|
|
90
96
|
if (candidate.dirty) errors.push("Worktree must be clean.");
|
|
91
|
-
if (!candidate.branch) errors.push("Detached HEAD is not trusted for preview proof.");
|
|
92
97
|
if (candidate.headSha !== candidate.prHeadSha) errors.push("HEAD is stale for the current PR head.");
|
|
93
|
-
if (candidate.branch !== candidate.prHeadRef) errors.push("Current branch does not match the PR head branch.");
|
|
94
98
|
if (candidate.repository !== candidate.prRepository) errors.push("PR head repository does not match the current repository.");
|
|
95
99
|
if (errors.length > 0) throw new Error(errors.join("\n"));
|
|
96
100
|
return disposableStage(stage, candidate);
|
|
@@ -116,7 +120,9 @@ const transition = (state, event) => {
|
|
|
116
120
|
if (event.convergence.status !== "passed") throw new Error("Preview proof requires passed convergence evidence.");
|
|
117
121
|
if (!event.stack || !event.url) throw new Error("Preview proof registration requires a stack and URL.");
|
|
118
122
|
assertPassingEnvelope(event.evidence, event.candidate.headSha);
|
|
123
|
+
const branch = proofBranch(event.candidate);
|
|
119
124
|
return replaceRegistration$1(state, {
|
|
125
|
+
...branch === void 0 ? {} : { branch },
|
|
120
126
|
cleanup: {
|
|
121
127
|
evidence: [],
|
|
122
128
|
outcome: "pending"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@patronage/factory-ci",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.38",
|
|
4
4
|
"description": "Deep CI and deploy building blocks for Patronage factory projects: workflow source artifacts, hosted diff classification, Alchemy entry execution, and disposable-stage semantics",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"alchemy",
|
|
@@ -11,7 +11,10 @@ import type { LocalPreviewStage } from "./disposable-stage.ts";
|
|
|
11
11
|
*
|
|
12
12
|
* Proof sits entirely on one side of GitHub's draft/ready boundary
|
|
13
13
|
* (paitronage#1089): a draft candidate cannot begin or register. Machine
|
|
14
|
-
* evidence is the envelope at head (#355), never a PR comment.
|
|
14
|
+
* evidence is the envelope at head (#355), never a PR comment. The SHA and
|
|
15
|
+
* repository bind that proof (#1312): a detached HEAD or a branch name that
|
|
16
|
+
* differs from the PR head is the same commit, and the registration records
|
|
17
|
+
* `branch: "(detached)"`.
|
|
15
18
|
*/
|
|
16
19
|
|
|
17
20
|
export type PreviewProofCleanupStatus = "failed" | "passed" | "pending";
|
|
@@ -44,6 +47,13 @@ export interface PreviewProofCleanupOutcome {
|
|
|
44
47
|
}
|
|
45
48
|
|
|
46
49
|
export interface PreviewProofRegistration {
|
|
50
|
+
/**
|
|
51
|
+
* Present only when the checkout is detached or its branch name is not the
|
|
52
|
+
* PR head. The `(detached)` sentinel is on the lifecycle registration
|
|
53
|
+
* `transition` returns, and `previewProofInventory` does not persist it.
|
|
54
|
+
* It is not a branch to check out.
|
|
55
|
+
*/
|
|
56
|
+
readonly branch?: "(detached)";
|
|
47
57
|
readonly cleanup: {
|
|
48
58
|
readonly evidence: readonly string[];
|
|
49
59
|
readonly outcome: PreviewProofCleanupStatus;
|
|
@@ -111,6 +121,19 @@ const EMPTY_STATE: PreviewProofLifecycleState = Object.freeze({
|
|
|
111
121
|
registrations: Object.freeze([]),
|
|
112
122
|
});
|
|
113
123
|
|
|
124
|
+
const DETACHED_PROOF_BRANCH = "(detached)" as const;
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* The branch name adds no guarantee. Record the sentinel unless the checkout
|
|
128
|
+
* is actually on the PR head branch.
|
|
129
|
+
*/
|
|
130
|
+
const proofBranch = (
|
|
131
|
+
candidate: PreviewProofCandidate
|
|
132
|
+
): typeof DETACHED_PROOF_BRANCH | undefined =>
|
|
133
|
+
candidate.branch.length > 0 && candidate.branch === candidate.prHeadRef
|
|
134
|
+
? undefined
|
|
135
|
+
: DETACHED_PROOF_BRANCH;
|
|
136
|
+
|
|
114
137
|
const registrationKey = (row: {
|
|
115
138
|
readonly stack: string;
|
|
116
139
|
readonly stage: string;
|
|
@@ -149,15 +172,9 @@ const assertAdmissibleCandidate = (
|
|
|
149
172
|
if (candidate.dirty) {
|
|
150
173
|
errors.push("Worktree must be clean.");
|
|
151
174
|
}
|
|
152
|
-
if (!candidate.branch) {
|
|
153
|
-
errors.push("Detached HEAD is not trusted for preview proof.");
|
|
154
|
-
}
|
|
155
175
|
if (candidate.headSha !== candidate.prHeadSha) {
|
|
156
176
|
errors.push("HEAD is stale for the current PR head.");
|
|
157
177
|
}
|
|
158
|
-
if (candidate.branch !== candidate.prHeadRef) {
|
|
159
|
-
errors.push("Current branch does not match the PR head branch.");
|
|
160
|
-
}
|
|
161
178
|
if (candidate.repository !== candidate.prRepository) {
|
|
162
179
|
errors.push("PR head repository does not match the current repository.");
|
|
163
180
|
}
|
|
@@ -233,8 +250,10 @@ const transition = (
|
|
|
233
250
|
throw new Error("Preview proof registration requires a stack and URL.");
|
|
234
251
|
}
|
|
235
252
|
assertPassingEnvelope(event.evidence, event.candidate.headSha);
|
|
253
|
+
const branch = proofBranch(event.candidate);
|
|
236
254
|
|
|
237
255
|
return replaceRegistration(state, {
|
|
256
|
+
...(branch === undefined ? {} : { branch }),
|
|
238
257
|
cleanup: { evidence: [], outcome: "pending" },
|
|
239
258
|
convergence: {
|
|
240
259
|
detail: event.convergence.detail,
|