@patronage/factory-ci 1.0.0-alpha.34 → 1.0.0-alpha.36

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.
@@ -1,8 +1,8 @@
1
- import { i as isLocalPreviewStage, n as executeAlchemyEntry, r as resolveAlchemyChildCwd, s as ALCHEMY_BASELINE } from "../execute-alchemy-entry-BZpk0eOQ.js";
1
+ import { i as isLocalPreviewStage, n as executeAlchemyEntry, r as resolveAlchemyChildCwd, s as ALCHEMY_BASELINE } from "../execute-alchemy-entry-CZ7Knz4q.js";
2
2
  import { createRequire } from "node:module";
3
3
  import path from "node:path";
4
4
  import { createHash } from "node:crypto";
5
- import { readFileSync } from "node:fs";
5
+ import { appendFileSync, closeSync, openSync, readFileSync } from "node:fs";
6
6
  import * as Alchemy from "alchemy";
7
7
  import * as Output from "alchemy/Output";
8
8
  import { tryFindProviderByType } from "alchemy/Provider";
@@ -176,10 +176,11 @@ const assertRetainedIdentities = ({ after, before, markers }) => {
176
176
  * repeat is the account, the permission list, the resource scope, and where
177
177
  * the credential comes from, so every one of those is an input.
178
178
  *
179
- * **No credential value is ever printed.** Errors name environment variables,
180
- * permission groups, and resource identifiers. They never carry a token, a
181
- * policy document, or any part of one. The result carries the token id, which
182
- * Cloudflare's own verification endpoint returns and which is not a secret.
179
+ * **No credential value is ever printed.** Errors name environment variables and
180
+ * missing groups from the caller-owned permission requirements. Untrusted API
181
+ * status, receipt group names, and resource identifiers are never reflected.
182
+ * The result carries the token id, which Cloudflare's own verification endpoint
183
+ * returns and which is not a secret.
183
184
  */
184
185
  const CLOUDFLARE_API = "https://api.cloudflare.com/client/v4";
185
186
  /**
@@ -246,6 +247,7 @@ const permissionGroupsFromPolicies = (policies, resourcesFor) => {
246
247
  const found = [];
247
248
  for (const policy of policies) {
248
249
  if (!isPolicyShaped(policy)) throw new Error("Cloudflare token policy has an invalid shape.");
250
+ if (policy.permission_groups.length === 0) throw new Error("Cloudflare token policy has no permission groups.");
249
251
  if (policy.effect !== "allow") throw new Error("Cloudflare token must carry allow policies only.");
250
252
  if (Object.values(policy.resources).some((selection) => selection !== "*")) throw new Error("Cloudflare token policy has an invalid resource value.");
251
253
  const scope = new Set(Object.keys(policy.resources));
@@ -253,8 +255,8 @@ const permissionGroupsFromPolicies = (policies, resourcesFor) => {
253
255
  if (!permission?.name) throw new Error("Cloudflare token has an unnamed permission group.");
254
256
  found.push(permission.name);
255
257
  const expected = new Set(resourcesFor(permission.name));
256
- if (expected.size === 0) throw new Error(`Cloudflare ${permission.name} policy has no expected resource scope.`);
257
- if (scope.size !== expected.size || [...scope].some((resource) => !expected.has(resource))) throw new Error(`Cloudflare ${permission.name} policy has an unexpected resource scope.`);
258
+ if (expected.size === 0) throw new Error("Cloudflare token policy has no expected resource scope.");
259
+ if (scope.size !== expected.size || [...scope].some((resource) => !expected.has(resource))) throw new Error("Cloudflare token policy has an unexpected resource scope.");
258
260
  }
259
261
  }
260
262
  return found;
@@ -265,7 +267,7 @@ const assertExactPermissionGroups = (found, required) => {
265
267
  const missing = [...expected].filter((name) => !received.has(name));
266
268
  const extra = [...received].filter((name) => !expected.has(name));
267
269
  const duplicates = found.filter((name, index) => found.indexOf(name) !== index);
268
- if (missing.length > 0 || extra.length > 0 || duplicates.length > 0) throw new Error(`Cloudflare permission mismatch; missing: ${missing.join(", ") || "none"}; extra: ${extra.join(", ") || "none"}; duplicates: ${duplicates.join(", ") || "none"}`);
270
+ if (missing.length > 0 || extra.length > 0 || duplicates.length > 0) throw new Error(`Cloudflare permission mismatch; missing: ${missing.join(", ") || "none"}; extra count: ${extra.length}; duplicate count: ${duplicates.length}`);
269
271
  };
270
272
  const assertRequiredPermissionGroups = (required) => {
271
273
  if (required.some((name) => !name.trim())) throw new Error("Credential preflight requires named permission groups; a blank name matches nothing.");
@@ -307,7 +309,8 @@ const resolveResolvedCredential = async (resolveCredential, environment) => {
307
309
  * Cloudflare could not be asked; every other error means the credential is
308
310
  * wrong. Nothing it throws, and nothing it returns, carries a secret value.
309
311
  */
310
- const credentialPreflight = async ({ accountId, environment, fetchImplementation = fetch, requiredPermissionGroups, resolveCredential, resourcesFor, source }) => {
312
+ const credentialPreflight = async ({ accountId, environment, fetchImplementation = fetch, requiredPermissionGroups, resolveCredential, resourcesFor, source, tokenKind = "account" }) => {
313
+ if (tokenKind !== "account" && tokenKind !== "user") throw new Error("Credential preflight has an unknown token kind.");
311
314
  if (!accountId.trim()) throw new Error("Credential preflight requires a Cloudflare account id.");
312
315
  if (requiredPermissionGroups.length === 0) throw new Error("Credential preflight requires at least one permission group; an empty list would admit any token.");
313
316
  assertRequiredPermissionGroups(requiredPermissionGroups);
@@ -317,8 +320,8 @@ const credentialPreflight = async ({ accountId, environment, fetchImplementation
317
320
  const credential = await resolveResolvedCredential(resolveCredential, environment);
318
321
  if (!(credential.token.trim() && credential.policyJson.trim())) throw new Error("Credential preflight resolved an empty credential.");
319
322
  if (source === "hosted" && credential.token.trim() !== environment.CLOUDFLARE_API_TOKEN?.trim()) throw new Error("Hosted credential preflight resolved a token that is not the environment token.");
320
- const verification = await cloudflareJson(`${CLOUDFLARE_API}/accounts/${accountId}/tokens/verify`, credential.token, fetchImplementation);
321
- if (verification.status !== "active") throw new Error(`Cloudflare token is ${verification.status}, not active.`);
323
+ const verification = await cloudflareJson(tokenKind === "user" ? `${CLOUDFLARE_API}/user/tokens/verify` : `${CLOUDFLARE_API}/accounts/${accountId}/tokens/verify`, credential.token, fetchImplementation);
324
+ if (verification.status !== "active") throw new Error("Cloudflare token is not active.");
322
325
  const reviewed = parseReviewedTokenPolicy(credential.policyJson);
323
326
  if (reviewed.tokenId !== verification.id) throw new Error("Cloudflare token policy metadata does not describe the live token.");
324
327
  const found = permissionGroupsFromPolicies(reviewed.policies, scopeFor);
@@ -1211,6 +1214,7 @@ const secretNamePolicy = ({ names, values }) => {
1211
1214
  const LIFECYCLE_FAILURE_MESSAGES = {
1212
1215
  AMBIGUOUS_RESOURCE: "The plan prints one resource id for more than one resource.",
1213
1216
  AMBIGUOUS_STAGE: "The stage table declares the stage both protected and disposable.",
1217
+ CHILD_DIAGNOSTICS_UNWRITABLE: "The requested child diagnostics file could not be written.",
1214
1218
  CHILD_FAILED: "The Alchemy child did not exit successfully.",
1215
1219
  DEPLOY_INCOMPLETE: "The deploy completed without the persisted state its plan described.",
1216
1220
  DESTROY_RESIDUE: "The destroy left persisted state registered for the stage.",
@@ -1236,6 +1240,7 @@ const LIFECYCLE_FAILURE_MESSAGES = {
1236
1240
  };
1237
1241
  /** A sanitized lifecycle failure. See the module comment. */
1238
1242
  var AlchemyLifecycleError = class extends Error {
1243
+ childDiagnosticsPath;
1239
1244
  code;
1240
1245
  mutation;
1241
1246
  operation;
@@ -1248,11 +1253,13 @@ var AlchemyLifecycleError = class extends Error {
1248
1253
  this.mutation = fields.mutation;
1249
1254
  this.operation = fields.operation;
1250
1255
  this.phase = fields.phase;
1256
+ if (fields.childDiagnosticsPath !== void 0) this.childDiagnosticsPath = fields.childDiagnosticsPath;
1251
1257
  if (fields.planHash !== void 0) this.planHash = fields.planHash;
1252
1258
  }
1253
1259
  /** The fields an evidence sink prints. Fixed shape, fixed words. */
1254
1260
  toJSON() {
1255
1261
  return {
1262
+ ...this.childDiagnosticsPath === void 0 ? {} : { childDiagnosticsPath: this.childDiagnosticsPath },
1256
1263
  code: this.code,
1257
1264
  message: this.message,
1258
1265
  mutation: this.mutation,
@@ -1368,6 +1375,81 @@ const deleteStageRows = async (state, target) => {
1368
1375
  }));
1369
1376
  };
1370
1377
  //#endregion
1378
+ //#region src/alchemy/child-diagnostics.ts
1379
+ /**
1380
+ * The optional record of what an Alchemy child printed (#1260).
1381
+ *
1382
+ * `runAlchemyLifecycle` reports a failed child as `CHILD_FAILED` and nothing
1383
+ * else: the child's output is parsed and dropped, so no secret in it can
1384
+ * reach a job log or an evidence file through the operation. That is the
1385
+ * right default and it stays the default. It also leaves an operator with a
1386
+ * failure code and no record of what failed, which twice cost a paitronage
1387
+ * investigation its evidence.
1388
+ *
1389
+ * This is the opt-in other half: a consumer that names a file gets every
1390
+ * child's streams appended to it, each stream passed whole through the
1391
+ * consumer's own redactor, under a header naming the child. The consumer chooses the path
1392
+ * and owns the redactor; this module never picks either, and nothing here
1393
+ * changes what the lifecycle decides.
1394
+ *
1395
+ * The file is opened, and the header written, **before** the child starts,
1396
+ * so an unwritable path is known before anything runs and a parent that dies
1397
+ * mid-child still leaves the marker for the child that was in flight. The
1398
+ * streams themselves arrive after the child exits, because the child is
1399
+ * captured (`spawnSync`), not streamed.
1400
+ *
1401
+ * Nothing here throws into the lifecycle's control flow once the handle is
1402
+ * open. A write failure is retained and reported by the caller under its own
1403
+ * failure code, because a record that could not be written is a different
1404
+ * fact from a child that failed, and reporting it as the second one would
1405
+ * put a false cause in front of the operator this file exists for. When the
1406
+ * caller reports it — up front, or only after the operation has run to the
1407
+ * end — is the caller's rule, not this module's; see `runAlchemyLifecycle`.
1408
+ */
1409
+ const streamSection = (label, text, redact) => {
1410
+ if (text === null || text === "") return `--- ${label}: empty ---\n`;
1411
+ const body = redact(text);
1412
+ return `--- ${label} ---\n${body.endsWith("\n") ? body : `${body}\n`}`;
1413
+ };
1414
+ /**
1415
+ * Open the record for one child and write its header.
1416
+ *
1417
+ * Throws when the file cannot be opened or the header cannot be written.
1418
+ * That throw happens before the child is launched, so a caller that has not
1419
+ * yet mutated anything can refuse the run with the stage still untouched.
1420
+ */
1421
+ const openChildDiagnostics = ({ child, diagnostics, operation, stack, stage }) => {
1422
+ const handle = openSync(diagnostics.path, "a");
1423
+ let retained;
1424
+ const guard = (write) => {
1425
+ try {
1426
+ write();
1427
+ } catch (error) {
1428
+ retained ??= error instanceof Error ? error : new Error(String(error));
1429
+ }
1430
+ };
1431
+ try {
1432
+ appendFileSync(handle, `\n===== ${operation} ${child} child — ${stack}/${stage} — ${(/* @__PURE__ */ new Date()).toISOString()} =====\n`);
1433
+ } catch (error) {
1434
+ closeSync(handle);
1435
+ throw error;
1436
+ }
1437
+ return {
1438
+ append: ({ stderr, stdout }) => {
1439
+ guard(() => {
1440
+ appendFileSync(handle, streamSection("stdout", stdout, diagnostics.redact));
1441
+ appendFileSync(handle, streamSection("stderr", stderr, diagnostics.redact));
1442
+ });
1443
+ },
1444
+ close: () => {
1445
+ guard(() => {
1446
+ closeSync(handle);
1447
+ });
1448
+ },
1449
+ failure: () => retained
1450
+ };
1451
+ };
1452
+ //#endregion
1371
1453
  //#region src/alchemy/lifecycle.ts
1372
1454
  /**
1373
1455
  * The shared Alchemy lifecycle operation (#1077).
@@ -1480,6 +1562,12 @@ var LifecycleRun = class {
1480
1562
  policy;
1481
1563
  state;
1482
1564
  entry;
1565
+ childDiagnostics;
1566
+ /**
1567
+ * A diagnostics write that failed after a mutating child had started, held
1568
+ * until the operation has run to its end. See `noteDiagnosticsFailure`.
1569
+ */
1570
+ diagnosticsUnwritable = false;
1483
1571
  bundledEntry;
1484
1572
  /** What the run has done to the stage so far. Only children advance it. */
1485
1573
  certainty = "not-started";
@@ -1488,9 +1576,13 @@ var LifecycleRun = class {
1488
1576
  this.policy = options.policy;
1489
1577
  this.state = options.state;
1490
1578
  this.entry = options.entry;
1579
+ this.childDiagnostics = options.childDiagnostics;
1491
1580
  }
1492
- fail(code, phase, planHash) {
1581
+ fail(code, phase, extra = {}) {
1582
+ const { namesDiagnostics, planHash } = extra;
1583
+ const diagnosticsPath = namesDiagnostics === true ? this.childDiagnostics?.path : void 0;
1493
1584
  return new AlchemyLifecycleError({
1585
+ ...diagnosticsPath === void 0 ? {} : { childDiagnosticsPath: diagnosticsPath },
1494
1586
  code,
1495
1587
  mutation: this.certainty,
1496
1588
  operation: this.request.operation,
@@ -1661,22 +1753,75 @@ var LifecycleRun = class {
1661
1753
  return ["--stage", this.request.stage];
1662
1754
  }
1663
1755
  /**
1664
- * Run one child under `lease` and return its stdout for parsing. The output
1665
- * is written nowhere else. The child's environment carries the lease, so
1666
- * the adapter in the child adopts it rather than taking its own: there is
1667
- * no child without a lease, and a child that writes outside this lease is
1668
- * refused by the store, not by this module. A `plan` child leaves the
1669
- * certainty where it is unless it is the first child; a mutating child
1670
- * advances it before it starts.
1756
+ * Where a diagnostics write failure is reported.
1757
+ *
1758
+ * Before any mutating child has started, it is reported here and now: the
1759
+ * operator asked for a record, the record cannot be kept, and nothing has
1760
+ * been changed yet, so refusing costs nothing.
1761
+ *
1762
+ * Once a deploy or destroy child has started, it is **retained** and
1763
+ * reported only after the operation has run to its end, the way a failed
1764
+ * lease release is. The apply succeeded; failing here would abandon the
1765
+ * convergence check on a good deploy and invite an operator to destroy and
1766
+ * retry a stage that is fine. A missing record is not a reason to treat a
1767
+ * successful mutation as a failed one, and the run still ends in
1768
+ * `CHILD_DIAGNOSTICS_UNWRITABLE` — after the postconditions have run, with
1769
+ * the mutation certainty the operation actually reached.
1770
+ */
1771
+ noteDiagnosticsFailure(failed) {
1772
+ if (!failed) return;
1773
+ this.diagnosticsUnwritable = true;
1774
+ if (this.certainty !== "mutation-may-have-occurred") throw this.fail("CHILD_DIAGNOSTICS_UNWRITABLE", "execution", { namesDiagnostics: true });
1775
+ }
1776
+ /**
1777
+ * The retained record failure, once the operation itself is complete. The
1778
+ * operation's own failures are reported ahead of it, as they are for a
1779
+ * failed lease release.
1780
+ */
1781
+ assertDiagnosticsWritten() {
1782
+ if (this.diagnosticsUnwritable) throw this.fail("CHILD_DIAGNOSTICS_UNWRITABLE", "execution", { namesDiagnostics: true });
1783
+ }
1784
+ /**
1785
+ * The diagnostics section for one child, opened and headed before the
1786
+ * child starts. A path that cannot be opened refuses the run while nothing
1787
+ * has been mutated; after that it is retained and the child runs without a
1788
+ * record rather than a good apply being abandoned over one.
1789
+ */
1790
+ openDiagnostics(child) {
1791
+ if (this.childDiagnostics === void 0) return;
1792
+ try {
1793
+ return openChildDiagnostics({
1794
+ child,
1795
+ diagnostics: this.childDiagnostics,
1796
+ operation: this.request.operation,
1797
+ stack: this.request.stack,
1798
+ stage: this.request.stage
1799
+ });
1800
+ } catch {
1801
+ this.noteDiagnosticsFailure(true);
1802
+ return;
1803
+ }
1804
+ }
1805
+ /**
1806
+ * Run one child under `lease` and return its stdout for parsing. Its output
1807
+ * goes nowhere else unless the consumer asked for `childDiagnostics`, which
1808
+ * writes a redacted copy and never touches what is returned here. The
1809
+ * child's environment carries the lease, so the adapter in the child adopts
1810
+ * it rather than taking its own: there is no child without a lease, and a
1811
+ * child that writes outside this lease is refused by the store, not by this
1812
+ * module. A `plan` child leaves the certainty where it is unless it is the
1813
+ * first child; a mutating child advances it before it starts.
1671
1814
  */
1672
1815
  async runChild(child, args, lease) {
1673
1816
  if (this.bundledEntry === void 0) throw this.fail("PREPARATION_FAILED", "admission");
1817
+ const record = this.openDiagnostics(child);
1674
1818
  if (child !== "plan") this.certainty = "mutation-may-have-occurred";
1675
1819
  else if (this.certainty === "not-started") this.certainty = "read-only-started";
1676
1820
  const { env, from } = this.entry;
1821
+ let stdout;
1677
1822
  try {
1678
- return (await executeAlchemyEntry({
1679
- args,
1823
+ stdout = (await executeAlchemyEntry({
1824
+ args: [...args, ...this.entry.envFile === void 0 ? [] : ["--env-file", this.entry.envFile]],
1680
1825
  bundledEntry: this.bundledEntry,
1681
1826
  cwd: resolveAlchemyChildCwd(this.entry),
1682
1827
  env: {
@@ -1685,11 +1830,16 @@ var LifecycleRun = class {
1685
1830
  },
1686
1831
  from,
1687
1832
  maxBuffer: 67108864,
1833
+ ...record === void 0 ? {} : { onCaptured: record.append },
1688
1834
  stdio: "pipe"
1689
1835
  })).stdout ?? "";
1690
1836
  } catch {
1691
- throw this.fail("CHILD_FAILED", "execution");
1837
+ stdout = void 0;
1692
1838
  }
1839
+ record?.close();
1840
+ if (stdout === void 0) throw this.fail("CHILD_FAILED", "execution", { namesDiagnostics: true });
1841
+ this.noteDiagnosticsFailure(record?.failure() !== void 0);
1842
+ return stdout;
1693
1843
  }
1694
1844
  effectsOf(output, phase) {
1695
1845
  let effects;
@@ -1782,7 +1932,7 @@ var LifecycleRun = class {
1782
1932
  stage: this.request.stage
1783
1933
  });
1784
1934
  } catch {
1785
- throw this.fail("FIRST_DEPLOY_UNAUTHORIZED", "admission", planHash);
1935
+ throw this.fail("FIRST_DEPLOY_UNAUTHORIZED", "admission", { planHash });
1786
1936
  }
1787
1937
  }
1788
1938
  retainedMarkers() {
@@ -1942,6 +2092,15 @@ var LifecycleRun = class {
1942
2092
  *
1943
2093
  * Every failure is an `AlchemyLifecycleError`. After a deploy or destroy
1944
2094
  * child was started, no failure reports `not-started`.
2095
+ *
2096
+ * `childDiagnostics` is the one opt-in that keeps what the children printed:
2097
+ * both streams of every child, each passed whole through the consumer's own
2098
+ * redactor, are appended to the consumer's own file under a per-child
2099
+ * header, and a `CHILD_FAILED` then names that path so an operator knows
2100
+ * where to look. Nothing about the operation changes otherwise: a record
2101
+ * that cannot be written refuses the run only while nothing has been
2102
+ * mutated, and after that is reported as `CHILD_DIAGNOSTICS_UNWRITABLE` once
2103
+ * the operation has finished, never in place of its postconditions.
1945
2104
  */
1946
2105
  const runAlchemyLifecycle = async (options) => {
1947
2106
  const run = new LifecycleRun(options);
@@ -1949,11 +2108,15 @@ const runAlchemyLifecycle = async (options) => {
1949
2108
  const { isProtected } = run.admitStage();
1950
2109
  const alchemyVersion = run.installedAlchemyVersion();
1951
2110
  await run.prepareEntry();
1952
- switch (options.request.operation) {
1953
- case "plan": return await run.plan(alchemyVersion, isProtected);
1954
- case "deploy": return await run.deploy(alchemyVersion, isProtected);
1955
- default: return await run.destroy(alchemyVersion);
1956
- }
2111
+ const result = await (async () => {
2112
+ switch (options.request.operation) {
2113
+ case "plan": return await run.plan(alchemyVersion, isProtected);
2114
+ case "deploy": return await run.deploy(alchemyVersion, isProtected);
2115
+ default: return await run.destroy(alchemyVersion);
2116
+ }
2117
+ })();
2118
+ run.assertDiagnosticsWritten();
2119
+ return result;
1957
2120
  };
1958
2121
  //#endregion
1959
2122
  //#region src/alchemy/index.ts
@@ -70,6 +70,23 @@ interface ExecuteAlchemyEntryCommon {
70
70
  * pass `EXECUTE_ALCHEMY_ENTRY_MAX_BUFFER` or another explicit value.
71
71
  */
72
72
  readonly maxBuffer?: number;
73
+ /**
74
+ * Called once with the child's captured streams, after the child exits and
75
+ * **before its exit status is judged**, so a caller keeping a diagnostic
76
+ * record still receives the output of a child that failed. Unused when
77
+ * stdio is `"inherit"`.
78
+ *
79
+ * The text handed over is the raw capture: `redact` is not applied to it,
80
+ * because `redact` shapes what this function returns and a record is not a
81
+ * return value. A caller that writes this anywhere owns its own redaction.
82
+ *
83
+ * It must not throw. Its work is a side record, and a throw from it would
84
+ * be indistinguishable here from the child's own failure.
85
+ */
86
+ readonly onCaptured?: (captured: {
87
+ readonly stdout: string | null;
88
+ readonly stderr: string | null;
89
+ }) => void;
73
90
  /**
74
91
  * Applied to captured stdout and stderr before they are returned. Unused
75
92
  * when stdio is `"inherit"`. This package does not supply a secret-name
@@ -106,7 +123,8 @@ interface ExecuteAlchemyEntryResult {
106
123
  * Options carry exactly one entry source: `bundle` to bundle now, or
107
124
  * `bundledEntry` to reuse an already-bundled path. Captured stdout and stderr (`stdio: "pipe"`) can be redacted through
108
125
  * `redact` before they are returned; `stdio: "inherit"` still streams the
109
- * child unredacted.
126
+ * child unredacted. `onCaptured` receives the raw streams of every piped
127
+ * child, including one that then fails.
110
128
  *
111
129
  * Deploy/destroy/plan selection, stage admission, credentials, and every
112
130
  * convergence or destructive-change policy remain the caller's responsibility.
@@ -123,7 +123,8 @@ const captured = (text, redact) => {
123
123
  * Options carry exactly one entry source: `bundle` to bundle now, or
124
124
  * `bundledEntry` to reuse an already-bundled path. Captured stdout and stderr (`stdio: "pipe"`) can be redacted through
125
125
  * `redact` before they are returned; `stdio: "inherit"` still streams the
126
- * child unredacted.
126
+ * child unredacted. `onCaptured` receives the raw streams of every piped
127
+ * child, including one that then fails.
127
128
  *
128
129
  * Deploy/destroy/plan selection, stage admission, credentials, and every
129
130
  * convergence or destructive-change policy remain the caller's responsibility.
@@ -142,6 +143,10 @@ const executeAlchemyEntry = async (options) => {
142
143
  ...options.maxBuffer === void 0 ? {} : { maxBuffer: options.maxBuffer },
143
144
  stdio: options.stdio ?? "inherit"
144
145
  });
146
+ options.onCaptured?.({
147
+ stderr: result.stderr,
148
+ stdout: result.stdout
149
+ });
145
150
  if (result.error) throw new Error("Failed to start the Alchemy CLI.");
146
151
  if (result.signal) throw new Error(`Alchemy CLI terminated by signal ${result.signal}.`);
147
152
  if (result.status !== 0) throw new Error(result.status === null ? "Alchemy CLI exited without reporting a status." : `Alchemy CLI exited with status ${result.status}.`);
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { a as BundleAlchemyEntryOptions, i as executeAlchemyEntry, n as ExecuteAlchemyEntryOptions, o as bundleAlchemyEntry, r as ExecuteAlchemyEntryResult, t as EXECUTE_ALCHEMY_ENTRY_MAX_BUFFER } from "./execute-alchemy-entry-DB8fFZTu.js";
1
+ import { a as BundleAlchemyEntryOptions, i as executeAlchemyEntry, n as ExecuteAlchemyEntryOptions, o as bundleAlchemyEntry, r as ExecuteAlchemyEntryResult, t as EXECUTE_ALCHEMY_ENTRY_MAX_BUFFER } from "./execute-alchemy-entry-B397GSk5.js";
2
2
  import { StdioOptions } from "node:child_process";
3
3
 
4
4
  //#region src/alchemy-baseline.d.ts
@@ -1203,6 +1203,30 @@ declare const previewCleanupDestroyJobId: (stack: string) => string;
1203
1203
  */
1204
1204
  declare const factoryPreviewCleanupTopology: (options: FactoryPreviewCleanupTopologyOptions) => FactoryPreviewCleanupTopology;
1205
1205
  //#endregion
1206
+ //#region src/preview-receipt-workflow.d.ts
1207
+ interface FactoryPreviewReceiptWorkflowOptions {
1208
+ readonly target: string;
1209
+ readonly cli?: string;
1210
+ /** Caller-owned PR number expression, passed through the environment. */
1211
+ readonly pr?: string;
1212
+ }
1213
+ interface FactoryPreviewReceiptWorkflow {
1214
+ /** Place before the selected target's first mutation, under its selection guard. */
1215
+ readonly beginStep: WorkflowStep;
1216
+ /** Append after consumer deploy and required smoke steps. Never rename the bind step. */
1217
+ readonly completionSteps: readonly WorkflowStep[];
1218
+ /** Place before cleanup, including cleanup of a failed/unregistered deployment. */
1219
+ readonly invalidateStep: WorkflowStep;
1220
+ readonly permissions: Readonly<{
1221
+ actions: "read";
1222
+ contents: "read";
1223
+ deployments: "write";
1224
+ "pull-requests": "read";
1225
+ }>;
1226
+ }
1227
+ /** Receipt transport only. The consumer owns selection, lifecycle, smoke, and freshness. */
1228
+ declare const factoryPreviewReceiptWorkflow: (options: FactoryPreviewReceiptWorkflowOptions) => FactoryPreviewReceiptWorkflow;
1229
+ //#endregion
1206
1230
  //#region src/production-impact-workflow.d.ts
1207
1231
  declare const FACTORY_PRODUCTION_IMPACT_STEP_ID = "production_impact";
1208
1232
  declare const FACTORY_PRODUCTION_IMPACT_DECISION_OUTPUT = "decision";
@@ -1645,4 +1669,4 @@ declare const assertWorkflowShellParses: (yaml: string, options: {
1645
1669
  readonly source: string;
1646
1670
  }) => void;
1647
1671
  //#endregion
1648
- export { ALCHEMY_BASELINE, type AlchemyBaselinePackageJson, type AssertAlchemyBaselineInput, type BundleAlchemyEntryOptions, type CheckoutStepOptions, EXECUTE_ALCHEMY_ENTRY_MAX_BUFFER, type ExecuteAlchemyEntryOptions, type ExecuteAlchemyEntryResult, FACTORY_APP_BROKER_ORIGIN, FACTORY_APP_BROKER_PATH, FACTORY_APP_BROKER_TOKEN_PERMISSIONS, 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_CURSOR_OIDC_AUDIENCE, FACTORY_CURSOR_OIDC_ISSUER, FACTORY_CURSOR_OIDC_JWKS_URL, FACTORY_CURSOR_OIDC_MANAGED_RUNTIME, 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_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 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 GithubAppInstallationPermissions, 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, SETUP_STEP_ROLES, type SetupNodeStepOptions, type SetupStepRole, 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, assertAlchemyBaseline, 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, setupStepsForRoles, verifyRunnerPolicy, workflowRunBlocks, workflowShellParseFailures, writeVitestProfile };
1672
+ export { ALCHEMY_BASELINE, type AlchemyBaselinePackageJson, type AssertAlchemyBaselineInput, type BundleAlchemyEntryOptions, type CheckoutStepOptions, EXECUTE_ALCHEMY_ENTRY_MAX_BUFFER, type ExecuteAlchemyEntryOptions, type ExecuteAlchemyEntryResult, FACTORY_APP_BROKER_ORIGIN, FACTORY_APP_BROKER_PATH, FACTORY_APP_BROKER_TOKEN_PERMISSIONS, 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_CURSOR_OIDC_AUDIENCE, FACTORY_CURSOR_OIDC_ISSUER, FACTORY_CURSOR_OIDC_JWKS_URL, FACTORY_CURSOR_OIDC_MANAGED_RUNTIME, 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_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 FactoryPreviewReceiptWorkflow, type FactoryPreviewReceiptWorkflowOptions, type FactoryProductionImpactWorkflow, type FactoryProductionImpactWorkflowOptions, type FactoryProofGateOptions, type FactoryProofGateReason, 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 GithubAppInstallationPermissions, 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, SETUP_STEP_ROLES, type SetupNodeStepOptions, type SetupStepRole, 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, assertAlchemyBaseline, assertProofReuseCoverage, assertVerifyRunnerPolicy, assertWorkflowShellParses, bundleAlchemyEntry, captureVitestProfileEnvironment, executeAlchemyEntry, factoryCandidateImpactWorkflow, factoryCandidateOrPushCondition, factoryLifecycleContractLane, factoryMergeFreezeJob, factoryMergeFreezeScript, factoryPrStatusHudWorkflow, factoryPreviewCleanupTopology, factoryPreviewReceiptWorkflow, factoryProductionImpactWorkflow, factoryProofGateScript, factoryProofGateStep, factoryProofReuseSummaryStep, factoryProofTimingStartStep, factoryPushIdentityConsumer, factoryPushIdentityProducer, factoryVerifyRunner, factoryWorkflow, githubAppJwt, isLocalPreviewStage, localPreviewStage, mintInstallationToken, normalizeVitestProfileSample, parseLocalPreviewStage, previewCleanupDestroyJobId, previewProofInventory, previewProofLifecycle, productionImpactTargetOutput, proofReuseCoverage, proofReuseRequiredCommands, readVitestProfileDocument, resolveProofReuseCommands, runVitestProfile, setupStepsForRoles, verifyRunnerPolicy, workflowRunBlocks, workflowShellParseFailures, writeVitestProfile };
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { t as bundleAlchemyEntry } from "./bundle-alchemy-entry-CT5hCphA.js";
2
- import { a as localPreviewStage, c as assertAlchemyBaseline, i as isLocalPreviewStage, n as executeAlchemyEntry, o as parseLocalPreviewStage, s as ALCHEMY_BASELINE, t as EXECUTE_ALCHEMY_ENTRY_MAX_BUFFER } from "./execute-alchemy-entry-BZpk0eOQ.js";
2
+ import { a as localPreviewStage, c as assertAlchemyBaseline, i as isLocalPreviewStage, n as executeAlchemyEntry, o as parseLocalPreviewStage, s as ALCHEMY_BASELINE, t as EXECUTE_ALCHEMY_ENTRY_MAX_BUFFER } from "./execute-alchemy-entry-CZ7Knz4q.js";
3
3
  import { createRequire } from "node:module";
4
4
  import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
5
5
  import path from "node:path";
@@ -2337,6 +2337,67 @@ const factoryPreviewCleanupTopology = (options) => {
2337
2337
  });
2338
2338
  };
2339
2339
  //#endregion
2340
+ //#region src/preview-receipt-workflow.ts
2341
+ /** Receipt transport only. The consumer owns selection, lifecycle, smoke, and freshness. */
2342
+ const factoryPreviewReceiptWorkflow = (options) => {
2343
+ if (!/^preview:[a-z][a-z0-9-]*$/u.test(options.target)) throw new Error("Preview receipts require a declared preview target name.");
2344
+ const cli = options.cli ?? "pnpm exec psf";
2345
+ assertRunnableCli("cli", cli);
2346
+ const id = `preview_intent_${options.target.slice(8).replaceAll("-", "_")}`;
2347
+ const deploymentId = `\${{ steps.${id}.outputs.deployment_id }}`;
2348
+ const hasIntent = `steps.${id}.outputs.deployment_id != ''`;
2349
+ const env = {
2350
+ FACTORY_PREVIEW_PR: options.pr ?? `\${{ github.event.pull_request.number }}`,
2351
+ GH_TOKEN: `\${{ github.token }}`
2352
+ };
2353
+ const command = `${cli} preview:receipt --pr "$FACTORY_PREVIEW_PR" --target '${options.target}'`;
2354
+ return Object.freeze({
2355
+ beginStep: Object.freeze({
2356
+ env,
2357
+ id,
2358
+ name: `Record preview intent for ${options.target}`,
2359
+ run: `${command} --action begin --github-output "$GITHUB_OUTPUT"`
2360
+ }),
2361
+ completionSteps: Object.freeze([
2362
+ Object.freeze({
2363
+ env: {
2364
+ ...env,
2365
+ FACTORY_DEPLOYMENT_ID: deploymentId
2366
+ },
2367
+ if: `success() && ${hasIntent}`,
2368
+ name: `Complete preview receipt for ${options.target}`,
2369
+ run: `${command} --action complete --deployment-id "$FACTORY_DEPLOYMENT_ID" --outcome success`
2370
+ }),
2371
+ Object.freeze({
2372
+ env: { FACTORY_DEPLOYMENT_ID: deploymentId },
2373
+ if: `success() && ${hasIntent}`,
2374
+ name: `Bind deployment receipt ${deploymentId}`,
2375
+ run: "test -n \"$FACTORY_DEPLOYMENT_ID\""
2376
+ }),
2377
+ Object.freeze({
2378
+ env: {
2379
+ ...env,
2380
+ FACTORY_DEPLOYMENT_ID: deploymentId
2381
+ },
2382
+ if: `failure() && ${hasIntent}`,
2383
+ name: `Fail preview receipt for ${options.target}`,
2384
+ run: `${command} --action complete --deployment-id "$FACTORY_DEPLOYMENT_ID" --outcome failure`
2385
+ })
2386
+ ]),
2387
+ invalidateStep: Object.freeze({
2388
+ env,
2389
+ name: `Invalidate preview receipts for ${options.target}`,
2390
+ run: `${command} --action invalidate`
2391
+ }),
2392
+ permissions: Object.freeze({
2393
+ actions: "read",
2394
+ contents: "read",
2395
+ deployments: "write",
2396
+ "pull-requests": "read"
2397
+ })
2398
+ });
2399
+ };
2400
+ //#endregion
2340
2401
  //#region src/push-identity-workflow.ts
2341
2402
  /** `owner/repo` slice of the catalog's `UPLOAD_ARTIFACT` pin, derived rather
2342
2403
  * than repeated as a literal, so a repository rename here cannot drift from
@@ -3530,4 +3591,4 @@ const assertWorkflowShellParses = (yaml, options) => {
3530
3591
  throw new Error(`${options.source} emits shell bash cannot parse; the runner would treat it as a no-op:\n${detail}`);
3531
3592
  };
3532
3593
  //#endregion
3533
- export { ALCHEMY_BASELINE, EXECUTE_ALCHEMY_ENTRY_MAX_BUFFER, FACTORY_APP_BROKER_ORIGIN, FACTORY_APP_BROKER_PATH, FACTORY_APP_BROKER_TOKEN_PERMISSIONS, 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_CURSOR_OIDC_AUDIENCE, FACTORY_CURSOR_OIDC_ISSUER, FACTORY_CURSOR_OIDC_JWKS_URL, FACTORY_CURSOR_OIDC_MANAGED_RUNTIME, 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_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, GitHubApiError, GitHubAppTokenTransportError, NODE_PNPM_ACTION_FAMILY_NODE24, PREVIEW_PROOF_INVENTORY_CHECK_NAME, PREVIEW_PROOF_INVENTORY_LIST_PERMISSIONS, PreviewProofTransportError, SETUP_STEP_ROLES, UPLOAD_ARTIFACT, VITEST_PROFILE_SCHEMA_VERSION, VITEST_PROFILE_TOOL, VitestProfileError, assertAlchemyBaseline, 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, setupStepsForRoles, verifyRunnerPolicy, workflowRunBlocks, workflowShellParseFailures, writeVitestProfile };
3594
+ export { ALCHEMY_BASELINE, EXECUTE_ALCHEMY_ENTRY_MAX_BUFFER, FACTORY_APP_BROKER_ORIGIN, FACTORY_APP_BROKER_PATH, FACTORY_APP_BROKER_TOKEN_PERMISSIONS, 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_CURSOR_OIDC_AUDIENCE, FACTORY_CURSOR_OIDC_ISSUER, FACTORY_CURSOR_OIDC_JWKS_URL, FACTORY_CURSOR_OIDC_MANAGED_RUNTIME, 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_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, GitHubApiError, GitHubAppTokenTransportError, NODE_PNPM_ACTION_FAMILY_NODE24, PREVIEW_PROOF_INVENTORY_CHECK_NAME, PREVIEW_PROOF_INVENTORY_LIST_PERMISSIONS, PreviewProofTransportError, SETUP_STEP_ROLES, UPLOAD_ARTIFACT, VITEST_PROFILE_SCHEMA_VERSION, VITEST_PROFILE_TOOL, VitestProfileError, assertAlchemyBaseline, assertProofReuseCoverage, assertVerifyRunnerPolicy, assertWorkflowShellParses, bundleAlchemyEntry, captureVitestProfileEnvironment, executeAlchemyEntry, factoryCandidateImpactWorkflow, factoryCandidateOrPushCondition, factoryLifecycleContractLane, factoryMergeFreezeJob, factoryMergeFreezeScript, factoryPrStatusHudWorkflow, factoryPreviewCleanupTopology, factoryPreviewReceiptWorkflow, factoryProductionImpactWorkflow, factoryProofGateScript, factoryProofGateStep, factoryProofReuseSummaryStep, factoryProofTimingStartStep, factoryPushIdentityConsumer, factoryPushIdentityProducer, factoryVerifyRunner, factoryWorkflow, githubAppJwt, isLocalPreviewStage, localPreviewStage, mintInstallationToken, normalizeVitestProfileSample, parseLocalPreviewStage, previewCleanupDestroyJobId, previewProofInventory, previewProofLifecycle, productionImpactTargetOutput, proofReuseCoverage, proofReuseRequiredCommands, readVitestProfileDocument, resolveProofReuseCommands, runVitestProfile, setupStepsForRoles, verifyRunnerPolicy, workflowRunBlocks, workflowShellParseFailures, writeVitestProfile };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@patronage/factory-ci",
3
- "version": "1.0.0-alpha.34",
3
+ "version": "1.0.0-alpha.36",
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",
@@ -49,7 +49,7 @@
49
49
  "typescript": "5.9.3",
50
50
  "ultracite": "7.10.5",
51
51
  "vitest": "4.1.10",
52
- "@patronage/alchemy-d1-state": "0.3.0"
52
+ "@patronage/alchemy-d1-state": "0.4.0"
53
53
  },
54
54
  "peerDependencies": {
55
55
  "alchemy": ">=2.0.0-beta.63 <3",