@patronage/factory-ci 1.0.0-alpha.31 → 1.0.0-alpha.33
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 +66 -18
- package/dist/.build-fingerprint.json +1 -1
- package/dist/alchemy/index.d.ts +176 -29
- package/dist/alchemy/index.js +533 -186
- package/dist/{execute-alchemy-entry-Dbna-8xq.js → execute-alchemy-entry-BZpk0eOQ.js} +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +4 -3
- package/src/alchemy/index.ts +9 -0
- package/src/alchemy/lifecycle.ts +50 -37
- package/src/alchemy/local-emulation.ts +89 -0
- package/src/alchemy/plan-effects.ts +262 -61
- package/src/alchemy/retained-identity.ts +9 -10
- package/src/alchemy/stage-policy.ts +24 -1
- package/src/alchemy/state-tree.ts +168 -0
- package/src/alchemy-baseline.ts +1 -1
package/README.md
CHANGED
|
@@ -153,11 +153,11 @@ const deployWebsite = job("deploy-website", {
|
|
|
153
153
|
});
|
|
154
154
|
```
|
|
155
155
|
|
|
156
|
-
The step
|
|
156
|
+
The generated decision step runs `psf production:impact --candidate "$FACTORY_CANDIDATE_SHA"` (the `candidate` option, default `github.sha`, plus `--stage` when the consumer passes one). The command resolves each target's demand from that target's last trusted successful deployment receipt to the candidate, never from the push's `before` and `after` commits. The step is `continue-on-error`, and every generated target condition keeps work demanded unless the command succeeded, reported a usable decision, and explicitly withdrew that target. Each deploy job declares `impact.permissions` and appends `impact.receiptSteps(target)` after its deploy and health steps: a success receipt (`--publish-receipt success --target <name> --candidate "$FACTORY_CANDIDATE_SHA"`), a bind step named for the new deployment id, and a failure receipt. Only a bound success receipt becomes the next baseline. The artifact with an empty target list generates no deploy jobs because job creation remains with the consumer. This package does not own target declarations, deploy topology, credentials, commands, health policy, or convergence/no-op proof.
|
|
157
157
|
|
|
158
|
-
The decision checkout must make
|
|
158
|
+
The decision checkout must make the baseline commit reachable. Use `factoryWorkflow({ setup: { checkout: { fetchDepth: 0, ref: "${{ github.sha }}" } } })`; a shallow checkout is safe but deliberately refuses withdrawal because the baseline commit is unreadable.
|
|
159
159
|
|
|
160
|
-
When deployment begins from a successful `workflow_run` instead of the push event itself, carry that push identity through the typed artifact seam
|
|
160
|
+
When deployment begins from a successful `workflow_run` instead of the push event itself, carry that push identity through the typed artifact seam so the decision job checks out the triggering head and passes it as the candidate:
|
|
161
161
|
|
|
162
162
|
```ts
|
|
163
163
|
import {
|
|
@@ -197,8 +197,7 @@ const identity = factoryPushIdentityConsumer({
|
|
|
197
197
|
downloadArtifact: workflowArtifact.actions.downloadArtifact,
|
|
198
198
|
});
|
|
199
199
|
const impact = factoryProductionImpactWorkflow({
|
|
200
|
-
|
|
201
|
-
before: identity.outputs.before,
|
|
200
|
+
candidate: identity.outputs.after,
|
|
202
201
|
targets: profile.impact.targets.map(({ name }) => name),
|
|
203
202
|
});
|
|
204
203
|
const decision = {
|
|
@@ -575,7 +574,7 @@ policy.assertDestructiveStage(stage); // throws on a protected stage
|
|
|
575
574
|
policy.assertStackOwnsStage(stack, stage);
|
|
576
575
|
```
|
|
577
576
|
|
|
578
|
-
Two projects wrote the same three questions — is this stage protected, is it disposable, and may this stack run it — and each answered them with its own stage names compiled into the answer. `stagePolicy` takes the table and returns the decisions: `isProtectedStage`, `isDisposableStage`, `isKnownStage`, `assertKnownStage`, `assertDestructiveStage`, and `assertStackOwnsStage`. **No project
|
|
577
|
+
Two projects wrote the same three questions — is this stage protected, is it disposable, and may this stack run it — and each answered them with its own stage names compiled into the answer. `stagePolicy` takes the table and returns the decisions: `isProtectedStage`, `isDisposableStage`, `isKnownStage`, `assertKnownStage`, `assertDestructiveStage`, and `assertStackOwnsStage`. **No project or stack name appears in this package, and one stage name does — `local`, the local emulator's own name, described below.**
|
|
579
578
|
|
|
580
579
|
A `StageMatcher` is a name list, a `RegExp`, or a predicate — the predicate is how a project expresses a stage set it resolves itself, such as one config file per client. `anyStage(...)` composes matchers, and `LOCAL_PREVIEW_STAGES` is the disposable grammar this package already owns (`isLocalPreviewStage`), offered as a matcher rather than assumed, because a project's disposable set is wider than that one grammar.
|
|
581
580
|
|
|
@@ -585,6 +584,34 @@ Refusals are `StagePolicyError` with a stable `code` — `UNKNOWN_STAGE`, `AMBIG
|
|
|
585
584
|
|
|
586
585
|
What stays with the project: which environment variable authorizes an attended protected operation, what counts as a trusted hosted context, whether an outstanding state-transfer proof blocks a stack, and the shape of its own failure envelope. Those name credentials, issues, and contexts, and none of them is a stage question.
|
|
587
586
|
|
|
587
|
+
#### Local emulation
|
|
588
|
+
|
|
589
|
+
```ts
|
|
590
|
+
import {
|
|
591
|
+
isLocalEmulationStage,
|
|
592
|
+
localEmulationEnvironment,
|
|
593
|
+
} from "@patronage/factory-ci/alchemy";
|
|
594
|
+
|
|
595
|
+
if (isLocalEmulationStage(stage)) {
|
|
596
|
+
child.env = { ...child.env, ...localEmulationEnvironment({ stage }) };
|
|
597
|
+
}
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
`isLocalEmulationStage(stage)` is true for the literal `local` and nothing else. Two projects and HQ each answered this question with their own rule — a refusal list, a constant, and `$(whoami)` — and all three meant that one name, so the answer is a literal, not a pattern and not a table. A disposable preview stage such as `local-pr-1-abcdef0` shares the prefix but runs against a real account, so it is false. The comparison is exact: no trim, no case folding. The question sits outside `stagePolicy` because a project's table classifies the stages of its own estate, and the emulated stage belongs to no estate.
|
|
601
|
+
|
|
602
|
+
`localEmulationEnvironment({ stage })` returns the two Cloudflare variables a local-emulation child is launched with. Alchemy refuses to start when `CLOUDFLARE_ACCOUNT_ID` is missing or malformed, even for a run that calls no Cloudflare API, so projects handed the child real credentials to satisfy a check the run never needed. The helper hands it a sentinel pair instead:
|
|
603
|
+
|
|
604
|
+
| Variable | Value |
|
|
605
|
+
| --- | --- |
|
|
606
|
+
| `CLOUDFLARE_ACCOUNT_ID` | thirty-two zeros — well-formed, and addresses no account |
|
|
607
|
+
| `CLOUDFLARE_API_TOKEN` | `factory-ci-local-emulation-no-cloudflare-token` — spelled as words, so no reader and no scanner mistakes it for a credential |
|
|
608
|
+
|
|
609
|
+
The account id is the shape Alchemy validates (`/^[0-9a-f]{32}$/i`, `validateAccountId`); a placeholder such as `""` or `dummy` fails that check before a run starts. Neither value is a secret.
|
|
610
|
+
|
|
611
|
+
Set `FACTORY_CI_LIVE_CLOUDFLARE_BINDING` to a non-empty value and the helper returns no variables, so whatever credentials the parent carries pass through to the child and the run may bind a live Cloudflare resource. That opt-in is the only way a real credential reaches a local-emulation child. The presence of a real credential is never the signal, and the helper reads no credential: it reads the opt-in and nothing else. The result is an overlay — merge it over the environment the launcher has otherwise assembled. Any stage other than `local` throws, because a non-local stage deploys to a real account.
|
|
612
|
+
|
|
613
|
+
Scrubbing an ambient token out of the child stays with the launcher. This package owns the environment shape only.
|
|
614
|
+
|
|
588
615
|
#### Retained identities
|
|
589
616
|
|
|
590
617
|
```ts
|
|
@@ -612,10 +639,32 @@ assertRetainedIdentities({
|
|
|
612
639
|
|
|
613
640
|
A protected stage's value is the physical resources it already owns. An Alchemy deploy that no longer recognises one of them does not fail — it creates a second one and leaves the first orphaned with the data still in it. `assertRetainedIdentities` reads the recorded state before the operation runs and throws `RetainedIdentityError` when an identity is `missing`, `unexpected` (not the `value` the marker names), or `changed` between the two snapshots. Every violation is reported, not just the first.
|
|
614
641
|
|
|
615
|
-
`parseStateSnapshot` reads
|
|
642
|
+
`parseStateSnapshot` reads the persisted-state document — the bulk document, `{ resources: [{ stack, stage, fqn, state }] }`, not a single record. A document covering the whole estate holds one fqn once per stage; pass the second argument, `{ stack, stage }`, to narrow the document to the stage the markers are about. A scoped document that still records one fqn twice throws rather than letting a marker match whichever record sorted first. State it cannot read throws, because reading a broken state as an empty one turns a failed read into a passing assert. An evaluated stack graph is a different artifact carrying the same identities; a caller holding one projects it onto `StateSnapshot` at the call site.
|
|
616
643
|
|
|
617
644
|
Two inputs are refused outright rather than passing: an empty marker list, and a marker with neither a `value` nor a `before` snapshot to compare against. Both would resolve green having asserted nothing.
|
|
618
645
|
|
|
646
|
+
#### Stage inventory, record read, and stage delete
|
|
647
|
+
|
|
648
|
+
```ts
|
|
649
|
+
import {
|
|
650
|
+
deleteStageRows,
|
|
651
|
+
inventoryStages,
|
|
652
|
+
parseStateSnapshot,
|
|
653
|
+
readStageSnapshot,
|
|
654
|
+
} from "@patronage/factory-ci/alchemy";
|
|
655
|
+
|
|
656
|
+
const stages = await inventoryStages(state);
|
|
657
|
+
const snapshot = parseStateSnapshot(
|
|
658
|
+
await readStageSnapshot(state, { stack: "app", stage: "pr-12" }),
|
|
659
|
+
{ stack: "app", stage: "pr-12" }
|
|
660
|
+
);
|
|
661
|
+
await deleteStageRows(state, { stack: "app", stage: "pr-12" });
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
These three read and write the state layer the consumer passes in — the store it configured, and the only thing they address. They launch no child process, and each answer is the state layer's own. `inventoryStages` returns every `(stack, stage)` pair the store holds, ordered by stack then stage. `readStageSnapshot` returns one stage as the persisted-state document `parseStateSnapshot` reads, built from the same atomic stage view and per-resource records `runAlchemyLifecycle` uses; a listed resource with no readable record fails the read rather than being left out. `deleteStageRows` removes every row of one stage and no row of any other; a stage the store does not hold is refused, not reported as removed, while `readStageSnapshot` reads that same stage as a document with no resource in it. Which stages may be deleted stays with the consumer.
|
|
665
|
+
|
|
666
|
+
A stage is addressed by the path `stack/stage`, and a name that is empty, padded, or carries a separator addresses something wider: an empty stage collapses the path to the stack, so a delete meant for one stage would take every stage of that stack. All three refuse such a name before they build a path. `inventoryStages` also takes an optional `{ stack, stage }` filter, and a pinned value is taken at face value: it skips the matching list call, so a pinned `stage` returns that stage once per stack whether or not the stack holds it. Pass no filter to inventory what the store actually holds.
|
|
667
|
+
|
|
619
668
|
#### Lifecycle
|
|
620
669
|
|
|
621
670
|
```ts
|
|
@@ -642,7 +691,7 @@ The consumer passes typed policy and two capabilities. `state` is the store from
|
|
|
642
691
|
The sequence, per operation:
|
|
643
692
|
|
|
644
693
|
- **`plan`** takes the stage lease, reads the persisted state, runs `deploy --dry-run`, reads the state again, and refuses with `STATE_CHANGED` if any resource record or the stack output differs — all under the one lease, so no other writer can reach the stage while the child plans. That refusal wins over the child's own exit status, because a planning child that wrote is the more serious finding. On an empty protected stage with a `firstDeploy` policy the result carries `planHash`, the value an operator authorizes.
|
|
645
|
-
- **`deploy`** starts with the same leased read-only plan, then admits it under the same lease. An empty protected stage is a first deploy: the plan may only `create
|
|
694
|
+
- **`deploy`** starts with the same leased read-only plan, then admits it under the same lease. An empty protected stage is a first deploy: the plan may only `create` or `adopted` — `adopted` is the create of a resource the provider already finds, and it plans only for a record with no prior state — and its hash must be on `firstDeploy.authorizedHashes`, or the refusal is `FIRST_DEPLOY_UNAUTHORIZED` carrying the hash. A protected stage with state refuses `replace` and `delete` (`EFFECT_NOT_PERMITTED`), and every `retainedIdentities` marker must hold against the recorded values before the apply (`RESOURCE_IDENTITY_MISMATCH`); a protected stage with no markers is `INVALID_POLICY`, because a check with nothing in it must not read as a proof. A disposable stage is constrained by neither. Then the apply child (`--yes`, with `--adopt` when the policy says so), under the same lease. Then, still under it: the persisted state again, which must record every resource the plan created (`DEPLOY_INCOMPLETE`; the result's `created` names their FQNs — a planned create resolves to an FQN that was absent before the apply, or was recorded in flight, `creating` or `deleting`, and is exactly the FQN the create row names — a row carries the FQN, so an interrupted create recovers at any depth; the record must have left the in-flight state) and, on a protected stage, the same identities as before; then a second `deploy --dry-run`, bracketed by the same two-read check as the first so a convergence child that wrote is `STATE_CHANGED`, which must be a no-op (`NOT_CONVERGED`) except an `update` on a persisted FQN named in `nonConvergentResources`: a Worker carrying a write-only secret binding re-plans as `update` on every deploy because the value can never be read back. Only `update`, only on those FQNs. A task row (`run` or `drop`) prints only while the task still has work, so any task row is `NOT_CONVERGED` too.
|
|
646
695
|
- **`destroy`** is refused on a protected stage. Then, under the stage lease, the destroy child, then the atomic stage view must be empty (`DESTROY_RESIDUE`) and the empty output row is removed in the same lease (`cleanup: "removed" | "absent"`). An unreadable store fails with `STATE_STORE_UNAVAILABLE`; it never reads as empty.
|
|
647
696
|
|
|
648
697
|
Ownership follows #1074 and #1107. Each operation runs under **one** stage lease, held from its first read to its last decision, and every child it launches — the dry runs, the apply, the destroy — runs inside that lease: the lifecycle spreads the lease's `childEnvironment()` into the child's environment, and the adapter in the child adopts it. The stage is never unowned while an operation runs, so no other writer can interleave between the admitted plan and the apply, and a second run on the stage is `STAGE_HELD`. Before anything read under the lease is decided on, the lifecycle **renews** the lease: renewal is the adapter's fenced operation, it changes no rows unless this holder's unexpired row is still there, and it fails with `lost` when the lease expired or was taken over — `OWNERSHIP_LOST`, and the decision is not made. Independently of that, every exit from a leased window passes the same fence check, whether the window returned or threw and wherever it threw from: a lease lost anywhere in the window — during the apply child, during a state read, under a child failure — is `OWNERSHIP_LOST` at the phase the run was in, ahead of whatever else was found there. The check sits outside the window's code, so no early return or new failure path inside it can skip acceptance. A fence that holds lets a genuine store or child failure through unchanged. A successful `release` is not that check: the adapter's release is idempotent by design and succeeds when the row is gone or belongs to a later holder, so it proves nothing, and the lifecycle infers nothing from it. A release that _fails_ is a different fact — the store could not be reached and this holder's row may still be there, blocking the stage until it expires — so after a successful operation it is reported as `LEASE_RELEASE_FAILED`; a failure inside the operation is reported ahead of it.
|
|
@@ -651,7 +700,7 @@ The consumer's entry point passes its environment to the adapter: `d1State({ ...
|
|
|
651
700
|
|
|
652
701
|
Every failure is an `AlchemyLifecycleError` with an enumerated `code`, `phase` (`request`, `admission`, `state`, `execution`, `postcondition`), and `mutation` (`not-started`, `read-only-started`, `mutation-may-have-occurred`). The message is the fixed sentence `LIFECYCLE_FAILURE_MESSAGES[code]` and nothing else; no failure carries a `cause`. Nothing a child printed, nothing the store said, and nothing a credential resolver quoted can reach an evidence sink through this error. After a deploy or destroy child was started, no failure reports `not-started`.
|
|
653
702
|
|
|
654
|
-
The plan is read once, by `parsePlanEffects`, from the lines Alchemy's `formatPlanLines` prints: one `Plan:` summary
|
|
703
|
+
The plan is read once, by `parsePlanEffects`, from the lines Alchemy's `formatPlanLines` prints: one `Plan:` summary, one `[fqn] action` row per resource, one `[fqn/name] action` row per binding of that resource, and one `[fqn] run|drop [action]` row per task. Every line arrives wrapped in Effect's pretty log prefix (`[HH:MM:SS.mmm] INFO (#1): `) and, when the child inherits `FORCE_COLOR`, in colour; both are stripped first. The id in a row is the resource's fully qualified name, the identity it is persisted under, so every check that binds a row to a resource resolves it to the persisted FQN it names exactly — a planned create to that FQN recorded after the apply and not before it, a convergence row to that FQN recorded for it — and refuses when it is absent. A plan that prints one FQN twice cannot come from the renderer and is refused (`AMBIGUOUS_RESOURCE`). The summary counts `create`, `update`, `adopted`, `replace`, `delete` and `orphaned` resources, then `N binding changes` and `N tasks`; `noop` rows print but are not counted. A binding row extends its resource's FQN by `/name`, and so does a resource inside a namespace, so when a resource named `Parent` is followed by rows tagged `Parent/...` the renderer gives no evidence which are its bindings and which are namespaced resources; the summary's counts decide, the one reading that adds up is taken, a plan the counts can place in more than one way is refused (`ambiguous-rows`; the counts are global, so two such resources in one plan couple and are refused together), the search is memoised so a plan of many such resources costs rows times counts and never the product of their readings, and a `noop` row under such a resource — placed by no count and read by no decision — is read as a binding. `orphaned` (a retained resource leaving the stack) is refused wherever `delete` is; `adopted` (a resource the provider already finds, planned only for a record with no prior state) is permitted wherever `create` is. The adapter is bound to `PLAN_OUTPUT_ALCHEMY_VERSION`, which is `ALCHEMY_BASELINE.alchemy`, and the lifecycle refuses an installed Alchemy of any other version (`UNSUPPORTED_ALCHEMY_VERSION`) before any child runs, so a renderer change cannot be read as a plan with different effects. The version is read from the manifest of the package that owns the CLI the children run — `alchemy/bin/alchemy.js` is resolved from the consumer and the nearest `package.json` named `alchemy` above it is read from disk — because `alchemy/package.json` is not on the package's `exports` map and cannot be resolved as a specifier. The adapter fails on output it cannot account for — no summary, two summaries, an unknown action, a summary it cannot parse, a count that does not match the rows, rows the counts place in more than one way — and reports only the reason, never the output. An effect it could not account for is never reported as zero.
|
|
655
704
|
|
|
656
705
|
What stays with the consumer: the resource graph, the credentials and environment the child runs with, the stage table, the retained-identity markers, the first-deploy authorization list, the state store's configuration, and health checks after the deploy. The Paitronage stack keys, HQ's frozen identities, and every other product value are inputs. There is no `psf` command for the lifecycle: the capabilities are consumer code, not flags, so the consumer calls the function from its own script.
|
|
657
706
|
|
|
@@ -676,7 +725,7 @@ Four checks that surround an Alchemy run. Paitronage and firedup each built the
|
|
|
676
725
|
|
|
677
726
|
`secretNamePolicy({ names, values })` builds the `redact` function `executeAlchemyEntry` already takes. It is a policy, not a mechanism. The project passes the names; this package ships no secret-name list and infers none from a name pattern. The policy is fail-closed at construction: a declared name with no value, a value under eight characters, or an empty `names` list handed a populated `values` map, throws before there is any output to redact, rather than quietly covering less than the caller believes. Empty names beside empty values is the one pass-through: a project with no secrets in its Alchemy output declares none. Values are trimmed before replacement, so redaction never swallows the line break after a secret, and longer values are replaced first, so a secret containing another secret leaves no fragment.
|
|
678
727
|
|
|
679
|
-
Two things paitronage does are deliberately not here. Stage eligibility is dropped: which stages may take a first deploy is project policy, and `firstDeployAuthorization` answers only whether this exact plan was authorized. The live zone lookup on the hosted path stays with the consumer: which zones an account may deploy into is project policy, and this package makes one Cloudflare call, against the token, not against the account's inventory. Two things that were once left out now live in `runAlchemyLifecycle` above: the plan-read-only before/after state capture, which caught a real write in the shared D1 store (#1074), and the first-deploy rule that a plan may only create, which the plan adapter can now read.
|
|
728
|
+
Two things paitronage does are deliberately not here. Stage eligibility is dropped: which stages may take a first deploy is project policy, and `firstDeployAuthorization` answers only whether this exact plan was authorized. The live zone lookup on the hosted path stays with the consumer: which zones an account may deploy into is project policy, and this package makes one Cloudflare call, against the token, not against the account's inventory. Two things that were once left out now live in `runAlchemyLifecycle` above: the plan-read-only before/after state capture, which caught a real write in the shared D1 store (#1074), and the first-deploy rule that a plan may only create or adopt, which the plan adapter can now read.
|
|
680
729
|
|
|
681
730
|
#### `evaluateStack`
|
|
682
731
|
|
|
@@ -693,7 +742,7 @@ const graph = await evaluateStack({
|
|
|
693
742
|
|
|
694
743
|
`evaluateStack` runs a stack program at one stage and returns the graph Alchemy would plan, with no state store and no network. It wraps Alchemy's `Plan.make`: the program registers its resources under a `Stack` service, then the plan builds under `inMemoryState()` at the requested `Stage`. Every provider answers its `read` and `list` probes with "absent", so the cold-adoption probe `Plan.make` makes for a resource with resolved props and no prior state never leaves the process. A stage the program returns from early (paitronage's `placeholder` stage) evaluates to an empty graph.
|
|
695
744
|
|
|
696
|
-
`program` is the effect the consumer hands `Alchemy.Stack`, not the `Alchemy.Stack(...)` value. The value captures the consumer's real `providers` and `state` layers, and at Alchemy 2.0.0-beta.
|
|
745
|
+
`program` is the effect the consumer hands `Alchemy.Stack`, not the `Alchemy.Stack(...)` value. The value captures the consumer's real `providers` and `state` layers, and at Alchemy 2.0.0-beta.77 `Cloudflare.providers()` resolves credentials when its layer builds (`Credentials.fromAuthProvider` inside `CloudflareApiLive`), so evaluating the value cannot stay credential-free. Export the program next to the stack:
|
|
697
746
|
|
|
698
747
|
```ts
|
|
699
748
|
export const program = Effect.gen(function* () {
|
|
@@ -704,7 +753,7 @@ export default Alchemy.Stack("hq", { providers, state }, program);
|
|
|
704
753
|
|
|
705
754
|
Pass `providers` when the program depends on hand-written provider layers. Every provider that layer registers keeps its identity (`stables`, `aliases`, `diff`) and loses its probes; an unregistered resource type still evaluates. Each graph resource carries the `LogicalId`, `Type`, and raw `Props` of its plan node, so a prop may hold an unresolved Alchemy output. `upstreamByProp` names, per top-level prop, the logical ids that prop's value references, which is the only way to read a cross-resource reference out of raw props: an output is a function, so the prop that holds it says nothing on its own. A prop that references nothing carries no key. The references are kept per prop and never pooled into one list, so a caller asking which resources one prop names is never answered with an id a different prop mentioned. Each binding row comes from Alchemy's Worker binding channel: `worker` is the host's logical id, `name` and `type` are the strings Alchemy emits (`d1`, `kv_namespace`, `secrets_store_secret`, and so on), and `target` is the logical id of the resource the row references, when it references one.
|
|
706
755
|
|
|
707
|
-
The caller's `providers` layer is **built** before its probes are stubbed: `evaluateStack` wraps that layer, and Alchemy's own lookup resolves each provider out of it before `read` and `list` are replaced. So any side effect a layer performs at construction — a credential read, a network call, a file write — is the caller's, and happens. This is why `Cloudflare.providers()` must not be passed: at beta.
|
|
756
|
+
The caller's `providers` layer is **built** before its probes are stubbed: `evaluateStack` wraps that layer, and Alchemy's own lookup resolves each provider out of it before `read` and `list` are replaced. So any side effect a layer performs at construction — a credential read, a network call, a file write — is the caller's, and happens. This is why `Cloudflare.providers()` must not be passed: at beta.77 it resolves credentials when its layer builds (`Credentials.fromAuthProvider` inside `CloudflareApiLive`), before there is anything to stub. The no-network guarantee covers evaluation, not layer construction: pass only layers whose construction is inert.
|
|
708
757
|
|
|
709
758
|
The helper decides nothing about the graph. Invariants over it are separate exports; `assertUrlImpliesAuth` below is the first.
|
|
710
759
|
|
|
@@ -740,7 +789,7 @@ The factory's own security review prompt carries the same rule in prose, so a re
|
|
|
740
789
|
import { ALCHEMY_BASELINE, assertAlchemyBaseline } from "@patronage/factory-ci";
|
|
741
790
|
```
|
|
742
791
|
|
|
743
|
-
`ALCHEMY_BASELINE` is the exact `alchemy` and `effect` pair the fleet moves together on: `{ alchemy: "2.0.0-beta.
|
|
792
|
+
`ALCHEMY_BASELINE` is the exact `alchemy` and `effect` pair the fleet moves together on: `{ alchemy: "2.0.0-beta.77", effect: "4.0.0-rc.112" }`. It is a plain constant on the root entry — it imports neither package — so any consumer can read it without installing the `./alchemy` subpath's peers.
|
|
744
793
|
|
|
745
794
|
`assertAlchemyBaseline({ packageJson })` is a consumer contract helper: it fails when the consumer's own `dependencies` or `devDependencies` pin `alchemy` or `effect` to anything other than the baseline, and it fails when only one of the pair is present. Alchemy peers on Effect, so an unpinned auto-installed Effect can drift outside the baseline while a lone `alchemy` pin would otherwise pass. Both packages remaining absent is not drift — they are optional peers of the subpath, so a consumer that never imports it, such as this package's own CLI, carries neither and passes. The pin is exact, not a range: the fleet is pre-1.0 and moves together, so a range would let one project drift silently ahead of or behind the rest. `assertAlchemyBaseline` reads only the object it is handed; it never walks the filesystem or reads a lockfile. Call it from a consumer's own contract test, passing that consumer's parsed `package.json`.
|
|
746
795
|
|
|
@@ -802,11 +851,10 @@ The right-hand column says what this repository proves about each, because "cove
|
|
|
802
851
|
| 3 | `STAGE_HELD` contention between two runs on one stage | Yes, through `runAlchemyLifecycle` |
|
|
803
852
|
| 4 | A `deploy` into a disposable stage: the admission branch for an unprotected stage | Yes, through `runAlchemyLifecycle` |
|
|
804
853
|
| 5 | `nonConvergentResources` — the tolerated-`update` branch | Yes, through `runAlchemyLifecycle` |
|
|
805
|
-
| 6 | `policy.
|
|
806
|
-
| 7 | `
|
|
807
|
-
| 8 |
|
|
808
|
-
| 9 |
|
|
809
|
-
| 10 | More than one stack, or a stack claiming more than one stage | **Only at the stage table.** `stagePolicy` is tested with multi-stack tables; no test drives `runAlchemyLifecycle` with one |
|
|
854
|
+
| 6 | `policy.ownershipTtlMillis` | Yes — the lease window the database records, at the one acquire site every operation shares. Added by #1079. **Note the adoption behaviour it exposes**: adoption resets `expiresAt` to the database's current time plus the **adopting** process's TTL, without taking the maximum against the stored expiry, so it can shorten a parent's window as easily as lengthen it. A shortened window fails closed — the fenced renewal reports `OWNERSHIP_LOST` rather than proceeding — and exclusivity is unaffected. HQ never notices, because both sides take the ten-minute default. `alchemy-d1-state`'s README carries the full note |
|
|
855
|
+
| 7 | The `bundle` entry mode, and the `alias` map with it | Yes, through `runAlchemyLifecycle`, including the `alias` map: the operation bundles an entry that imports an aliased module, and the test reads the bundle the operation produced and asserts the aliased module's marker is in it. Losing the alias does not fail the build — `bundleAlchemyEntry` defaults an unresolved package to external, so the import survives unresolved and the bundle is still written — it fails that marker assertion. A consumer that hands the operation a `bundledEntry` never bundles |
|
|
856
|
+
| 8 | `LEASE_RELEASE_FAILED` — a store outage at exactly the release | Yes, through `runAlchemyLifecycle` |
|
|
857
|
+
| 9 | More than one stack, or a stack claiming more than one stage | **Only at the stage table.** `stagePolicy` is tested with multi-stack tables; no test drives `runAlchemyLifecycle` with one |
|
|
810
858
|
|
|
811
859
|
## No configuration surface
|
|
812
860
|
|
package/dist/alchemy/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { n as ExecuteAlchemyEntryOptions } from "../execute-alchemy-entry-DB8fFZ
|
|
|
2
2
|
import * as Alchemy from "alchemy";
|
|
3
3
|
import * as Effect from "effect/Effect";
|
|
4
4
|
import * as Layer from "effect/Layer";
|
|
5
|
+
import { StateService } from "alchemy/State";
|
|
5
6
|
import { ProviderServices } from "alchemy/Stack";
|
|
6
7
|
|
|
7
8
|
//#region src/alchemy/evaluate-stack.d.ts
|
|
@@ -87,7 +88,7 @@ interface RetainedIdentityMarker {
|
|
|
87
88
|
readonly value?: string;
|
|
88
89
|
}
|
|
89
90
|
/**
|
|
90
|
-
* One resource record
|
|
91
|
+
* One resource record in the persisted-state document: the `(stack, stage,
|
|
91
92
|
* fqn)` address, and under `state` the persisted record itself.
|
|
92
93
|
*/
|
|
93
94
|
interface StateSnapshotResource {
|
|
@@ -110,7 +111,7 @@ interface StateSnapshotScope {
|
|
|
110
111
|
readonly stage?: string;
|
|
111
112
|
}
|
|
112
113
|
/**
|
|
113
|
-
* A recorded stage state: the whole
|
|
114
|
+
* A recorded stage state: the whole persisted-state document.
|
|
114
115
|
*
|
|
115
116
|
* An evaluated stack graph is a different artifact with the same identities in
|
|
116
117
|
* it. A caller holding one projects it onto this shape at the call site;
|
|
@@ -135,15 +136,14 @@ declare class RetainedIdentityError extends Error {
|
|
|
135
136
|
constructor(violations: readonly RetainedIdentityViolation[]);
|
|
136
137
|
}
|
|
137
138
|
/**
|
|
138
|
-
* Read
|
|
139
|
+
* Read a persisted-state document into a snapshot of one stage.
|
|
139
140
|
*
|
|
140
|
-
*
|
|
141
|
-
* (`{ resources: [{ stack, stage, fqn, state }] }`);
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
* the stack and stage the markers are about.
|
|
141
|
+
* The persisted-state document holds every record in scope
|
|
142
|
+
* (`{ resources: [{ stack, stage, fqn, state }] }`); a single-record document
|
|
143
|
+
* is not this shape. A document covering the whole estate holds one fqn once
|
|
144
|
+
* per stage, so a marker naming it would match whichever record sorted
|
|
145
|
+
* first — a `prod` identity asserted against a `dev` record. `scope` narrows
|
|
146
|
+
* the document to the stack and stage the markers are about.
|
|
147
147
|
*
|
|
148
148
|
* Throws on anything that is not a resource inventory, and on a scoped
|
|
149
149
|
* document that still records one fqn twice. A state this module cannot read
|
|
@@ -178,6 +178,45 @@ declare const assertRetainedIdentities: ({
|
|
|
178
178
|
markers
|
|
179
179
|
}: AssertRetainedIdentitiesOptions) => void;
|
|
180
180
|
//#endregion
|
|
181
|
+
//#region src/alchemy/local-emulation.d.ts
|
|
182
|
+
/**
|
|
183
|
+
* The child environment a local-emulation Alchemy run needs (#1168).
|
|
184
|
+
*
|
|
185
|
+
* Alchemy refuses to start when `CLOUDFLARE_ACCOUNT_ID` is missing or
|
|
186
|
+
* malformed, even for a run that only drives the local emulator and calls no
|
|
187
|
+
* Cloudflare API. Every project worked around that by handing the child a real
|
|
188
|
+
* account id and a real token, so a purely local run carried live credentials.
|
|
189
|
+
* This module hands it a sentinel pair instead: a well-formed account id that
|
|
190
|
+
* addresses no account, and a token that is not a token.
|
|
191
|
+
*
|
|
192
|
+
* The helper never reads a real credential. It reads one variable — the
|
|
193
|
+
* live-binding opt-in — and nothing else. The presence of a real credential in
|
|
194
|
+
* the parent environment is never the signal: a developer who is logged in to
|
|
195
|
+
* Cloudflare still gets the sentinel pair unless the opt-in says otherwise.
|
|
196
|
+
*
|
|
197
|
+
* Scrubbing an ambient token out of the child is the launcher's job, not this
|
|
198
|
+
* module's. This module owns the environment shape only.
|
|
199
|
+
*/
|
|
200
|
+
/**
|
|
201
|
+
* Build the Cloudflare variables a local-emulation child is launched with.
|
|
202
|
+
*
|
|
203
|
+
* Returns exactly two variables for the local-emulation stage, or no variables
|
|
204
|
+
* when the live-binding opt-in is set. The result is an overlay: merge it over
|
|
205
|
+
* the child environment the launcher has otherwise assembled.
|
|
206
|
+
*
|
|
207
|
+
* Throws for any other stage. A non-local stage deploys to a real account, so
|
|
208
|
+
* a sentinel there would break that deploy, and returning nothing instead
|
|
209
|
+
* would let a caller believe a local run was protected when it was not.
|
|
210
|
+
*/
|
|
211
|
+
declare const localEmulationEnvironment: (options: {
|
|
212
|
+
/**
|
|
213
|
+
* Where the opt-in is read from. Defaults to this process's environment. No
|
|
214
|
+
* other variable is read.
|
|
215
|
+
*/
|
|
216
|
+
readonly environment?: Readonly<Record<string, string | undefined>>; /** The Alchemy stage the child runs. */
|
|
217
|
+
readonly stage: string;
|
|
218
|
+
}) => Readonly<Record<string, string>>;
|
|
219
|
+
//#endregion
|
|
181
220
|
//#region src/alchemy/stage-policy.d.ts
|
|
182
221
|
/**
|
|
183
222
|
* Stage and stack pairing, and protected-stage policy, from a project-supplied
|
|
@@ -187,7 +226,12 @@ declare const assertRetainedIdentities: ({
|
|
|
187
226
|
* is it disposable, and may this stack run it — and each answered them with its
|
|
188
227
|
* own stage names hard-coded into the answer. The names differ; the questions
|
|
189
228
|
* do not. This module owns the questions. A project supplies the table and gets
|
|
190
|
-
* decisions back, so no project
|
|
229
|
+
* decisions back, so no project or stack name appears here.
|
|
230
|
+
*
|
|
231
|
+
* One stage name does appear: `local`. It names the local emulator rather than
|
|
232
|
+
* any project's estate, every project spells it the same way, and the question
|
|
233
|
+
* it answers — is this the emulated stage — has one answer for the whole fleet.
|
|
234
|
+
* See {@link isLocalEmulationStage}.
|
|
191
235
|
*
|
|
192
236
|
* The disposable grammar this package already owns (`disposable-stage.ts`) is
|
|
193
237
|
* available as a matcher rather than assumed, because a project's disposable
|
|
@@ -253,6 +297,22 @@ interface StagePolicy {
|
|
|
253
297
|
/** Throw unless the table pairs the stack and the stage. */
|
|
254
298
|
readonly assertStackOwnsStage: (stack: string, stage: string) => void;
|
|
255
299
|
}
|
|
300
|
+
/**
|
|
301
|
+
* Is this the stage that runs against the local emulator?
|
|
302
|
+
*
|
|
303
|
+
* True for the literal `local` and nothing else (#991). Two projects and HQ
|
|
304
|
+
* each answered this with their own rule — a refusal list, a constant, and
|
|
305
|
+
* `$(whoami)` — and all three meant the same single name, so the answer is one
|
|
306
|
+
* literal rather than a pattern or a table. A disposable preview stage such as
|
|
307
|
+
* `local-pr-1-abcdef0` shares the prefix but runs against a real account, so it
|
|
308
|
+
* is false here. The comparison is exact: no trim and no case folding, because
|
|
309
|
+
* a stage name reaches Alchemy exactly as it is spelled.
|
|
310
|
+
*
|
|
311
|
+
* This question is deliberately outside {@link stagePolicy}: a project's table
|
|
312
|
+
* classifies the stages of its own estate, and the emulated stage belongs to
|
|
313
|
+
* no estate.
|
|
314
|
+
*/
|
|
315
|
+
declare const isLocalEmulationStage: (stage: string) => boolean;
|
|
256
316
|
/**
|
|
257
317
|
* Read a project's stage table and answer the stage questions from it.
|
|
258
318
|
*
|
|
@@ -633,34 +693,66 @@ declare const isAlchemyLifecycleError: (error: unknown) => error is AlchemyLifec
|
|
|
633
693
|
*
|
|
634
694
|
* Alchemy has no structured plan output at the fleet baseline, so the plan
|
|
635
695
|
* is read from the lines `formatPlanLines` in `alchemy/src/Cli/LoggingCli.ts`
|
|
636
|
-
* prints: one `Plan:` summary
|
|
637
|
-
*
|
|
638
|
-
*
|
|
639
|
-
*
|
|
640
|
-
*
|
|
696
|
+
* prints: one `Plan:` summary, one `[fqn] action` line per resource, one
|
|
697
|
+
* `[fqn/name] action` line per binding of that resource, and one
|
|
698
|
+
* `[fqn] run|drop [action]` line per task. Paitronage and HQ each wrote this
|
|
699
|
+
* reader; this is the one copy, and it is bound to the Alchemy version whose
|
|
700
|
+
* renderer it reads. A consumer running a different Alchemy is refused by
|
|
701
|
+
* `runAlchemyLifecycle` before any child runs, so a renderer change cannot
|
|
702
|
+
* be read as a plan with different effects.
|
|
703
|
+
*
|
|
704
|
+
* The renderer logs every line through Effect's pretty console logger, so a
|
|
705
|
+
* line arrives as `[HH:MM:SS.mmm] INFO (#1): <line>`, and coloured when the
|
|
706
|
+
* child inherits `FORCE_COLOR`. Both wrappers are stripped before a line is
|
|
707
|
+
* read. The captured output under `tests/fixtures/plan-output/beta-77/` is
|
|
708
|
+
* the evidence for every shape this file reads.
|
|
641
709
|
*
|
|
642
710
|
* The adapter fails on anything it does not recognise: no summary, two
|
|
643
|
-
* summaries, an action outside the known set, a summary it cannot parse,
|
|
644
|
-
*
|
|
645
|
-
* not account for is
|
|
711
|
+
* summaries, an action outside the known set, a summary it cannot parse, a
|
|
712
|
+
* summary whose counts do not equal the lines under it, or rows the summary
|
|
713
|
+
* can place in more than one way. An effect it could not account for is
|
|
714
|
+
* never reported as zero.
|
|
646
715
|
*/
|
|
647
716
|
/** The Alchemy version whose plan renderer this adapter reads. */
|
|
648
717
|
declare const PLAN_OUTPUT_ALCHEMY_VERSION: string;
|
|
649
|
-
|
|
718
|
+
/**
|
|
719
|
+
* Every action a resource row can carry. The first six are the actions the
|
|
720
|
+
* summary counts, in the order the renderer prints them; `noop` rows print
|
|
721
|
+
* under the summary but are never counted in it.
|
|
722
|
+
*/
|
|
723
|
+
declare const PLAN_ACTIONS: readonly ["create", "update", "adopted", "replace", "delete", "orphaned", "noop"];
|
|
650
724
|
type PlanAction = (typeof PLAN_ACTIONS)[number];
|
|
651
725
|
/** One top-level resource row of a plan. */
|
|
652
726
|
interface PlanResourceEffect {
|
|
653
727
|
readonly action: PlanAction;
|
|
654
|
-
/**
|
|
728
|
+
/**
|
|
729
|
+
* The fully qualified name Alchemy printed in the row's tag: the namespace
|
|
730
|
+
* path and the logical id joined by `/`. A top-level resource's FQN is its
|
|
731
|
+
* logical id. This is the identity Alchemy persists the resource under.
|
|
732
|
+
*/
|
|
733
|
+
readonly id: string;
|
|
734
|
+
}
|
|
735
|
+
type PlanTaskAction = "drop" | "run";
|
|
736
|
+
/** One task (`Action`) row of a plan. A task with nothing to do prints no row. */
|
|
737
|
+
interface PlanTaskEffect {
|
|
738
|
+
readonly action: PlanTaskAction;
|
|
739
|
+
/** The task's fully qualified name, as printed in the row's tag. */
|
|
655
740
|
readonly id: string;
|
|
656
741
|
}
|
|
657
742
|
interface PlanEffects {
|
|
658
|
-
/**
|
|
743
|
+
/**
|
|
744
|
+
* Resource count per action. The six summary actions are what the summary
|
|
745
|
+
* line states; `noop` is counted from the rows, as the summary omits it.
|
|
746
|
+
*/
|
|
659
747
|
readonly counts: Readonly<Record<PlanAction, number>>;
|
|
660
|
-
/** Every
|
|
748
|
+
/** Every resource row, in the order printed. */
|
|
661
749
|
readonly resources: readonly PlanResourceEffect[];
|
|
750
|
+
/** Binding rows with a change, as the summary's `N binding changes` states. */
|
|
751
|
+
readonly bindingChanges: number;
|
|
752
|
+
/** Every task row, in the order printed. */
|
|
753
|
+
readonly tasks: readonly PlanTaskEffect[];
|
|
662
754
|
}
|
|
663
|
-
type PlanEffectsRejection = "no-summary" | "multiple-summaries" | "unknown-action" | "summary-unrecognized" | "count-mismatch" | "effects-under-no-changes";
|
|
755
|
+
type PlanEffectsRejection = "no-summary" | "multiple-summaries" | "unknown-action" | "summary-unrecognized" | "count-mismatch" | "ambiguous-rows" | "effects-under-no-changes";
|
|
664
756
|
/** The adapter could not read the output. `reason` is the whole detail. */
|
|
665
757
|
declare class PlanEffectsError extends Error {
|
|
666
758
|
readonly reason: PlanEffectsRejection;
|
|
@@ -786,11 +878,11 @@ interface AlchemyLifecyclePolicy {
|
|
|
786
878
|
* deploy because the value can never be read back; that is a property of
|
|
787
879
|
* the secret, not drift. Only `update` is tolerated, only on these FQNs.
|
|
788
880
|
* A top-level resource's FQN is its logical id; a nested one is
|
|
789
|
-
* `namespace/id`.
|
|
881
|
+
* `namespace/id`. A binding change is never tolerated: the plan adapter
|
|
882
|
+
* attributes no binding row to a resource, so a plan with binding work is
|
|
883
|
+
* not converged whatever this list names.
|
|
790
884
|
*/
|
|
791
885
|
readonly nonConvergentResources?: readonly string[];
|
|
792
|
-
/** The Alchemy auth profile, passed as `--profile`. */
|
|
793
|
-
readonly profile?: string;
|
|
794
886
|
}
|
|
795
887
|
type DistributiveOmit<T, K extends PropertyKey> = T extends unknown ? Omit<T, K> : never;
|
|
796
888
|
/**
|
|
@@ -847,7 +939,8 @@ interface AlchemyLifecycleResult {
|
|
|
847
939
|
* apply child, then the persisted state again: it must record what the plan
|
|
848
940
|
* created and, on a protected stage, the same identities as before. Then a
|
|
849
941
|
* second dry-run plan, which must be a no-op except an `update` on a resource
|
|
850
|
-
* the policy names non-convergent.
|
|
942
|
+
* the policy names non-convergent. A binding change or a task row in that
|
|
943
|
+
* plan is work left behind, and no policy tolerates it.
|
|
851
944
|
*
|
|
852
945
|
* `destroy`: refused on a protected stage. Then, under the stage lease, the
|
|
853
946
|
* destroy child, then the atomic stage view must be empty and the empty
|
|
@@ -864,6 +957,60 @@ interface AlchemyLifecycleResult {
|
|
|
864
957
|
*/
|
|
865
958
|
declare const runAlchemyLifecycle: (options: RunAlchemyLifecycleOptions) => Promise<AlchemyLifecycleResult>;
|
|
866
959
|
//#endregion
|
|
960
|
+
//#region src/alchemy/state-tree.d.ts
|
|
961
|
+
/**
|
|
962
|
+
* The subset of the state layer the record read goes through: the atomic
|
|
963
|
+
* stage view and the per-resource record. `@patronage/alchemy-d1-state`
|
|
964
|
+
* satisfies it structurally, the same way `runAlchemyLifecycle` takes it.
|
|
965
|
+
*/
|
|
966
|
+
type StageRecordStore = Pick<StageStateStore, "get" | "snapshotStage">;
|
|
967
|
+
/**
|
|
968
|
+
* Pins part of the inventory.
|
|
969
|
+
*
|
|
970
|
+
* A pinned `stack` or `stage` is taken at face value and skips the matching
|
|
971
|
+
* list call, because naming a scope is not a claim that it exists. So a
|
|
972
|
+
* pinned `stage` returns that stage once per stack whether or not the stack
|
|
973
|
+
* holds it. Pass no filter to inventory what the store actually holds.
|
|
974
|
+
*/
|
|
975
|
+
interface StageFilter {
|
|
976
|
+
readonly stack?: string;
|
|
977
|
+
readonly stage?: string;
|
|
978
|
+
}
|
|
979
|
+
/**
|
|
980
|
+
* Every `(stack, stage)` pair the state layer holds, or the subset a filter
|
|
981
|
+
* pins. Ordered by stack then stage.
|
|
982
|
+
*
|
|
983
|
+
* The pairs come from the store's own `listStacks` and `listStages`, so an
|
|
984
|
+
* empty result is the store reporting nothing, never a read this module
|
|
985
|
+
* could not make: a state layer that cannot be read fails instead.
|
|
986
|
+
*/
|
|
987
|
+
declare const inventoryStages: (state: StateService, filter?: StageFilter) => Promise<readonly StageTarget[]>;
|
|
988
|
+
/**
|
|
989
|
+
* The persisted-state document for one stage, as `parseStateSnapshot` reads
|
|
990
|
+
* it: `{ resources: [{ stack, stage, fqn, state }] }`, ordered by fqn.
|
|
991
|
+
*
|
|
992
|
+
* The read is the one `runAlchemyLifecycle` already makes — the atomic stage
|
|
993
|
+
* view, then each listed resource record — so a caller holding the document
|
|
994
|
+
* and a caller holding the lifecycle see the same records. A listed resource
|
|
995
|
+
* with no readable record fails the read; it is never left out, because a
|
|
996
|
+
* short document reads as a stage with less in it than it has.
|
|
997
|
+
*
|
|
998
|
+
* A stage the store does not hold reads as a document with no resource in
|
|
999
|
+
* it, the same reading the lifecycle takes for a first deploy. Only a stage
|
|
1000
|
+
* the store cannot read fails. `deleteStageRows` answers an absent stage the
|
|
1001
|
+
* other way, because it addresses a path rather than a stage view.
|
|
1002
|
+
*/
|
|
1003
|
+
declare const readStageSnapshot: (state: StageRecordStore, target: StageTarget) => Promise<string>;
|
|
1004
|
+
/**
|
|
1005
|
+
* Remove every row the state layer holds for one stage. Other stages of the
|
|
1006
|
+
* same stack keep every row.
|
|
1007
|
+
*
|
|
1008
|
+
* A stage the store does not hold is refused by the state layer rather than
|
|
1009
|
+
* reported as removed, so a caller never reads a mistyped stage as a stage
|
|
1010
|
+
* that was already empty.
|
|
1011
|
+
*/
|
|
1012
|
+
declare const deleteStageRows: (state: StateService, target: StageTarget) => Promise<void>;
|
|
1013
|
+
//#endregion
|
|
867
1014
|
//#region src/alchemy/index.d.ts
|
|
868
1015
|
/**
|
|
869
1016
|
* The `@patronage/factory-ci/alchemy` subpath: the only place in this package
|
|
@@ -884,4 +1031,4 @@ declare const runAlchemyLifecycle: (options: RunAlchemyLifecycleOptions) => Prom
|
|
|
884
1031
|
/** The specifier a consumer imports this surface by. */
|
|
885
1032
|
declare const ALCHEMY_SUBPATH = "@patronage/factory-ci/alchemy";
|
|
886
1033
|
//#endregion
|
|
887
|
-
export { ALCHEMY_SUBPATH, type AlchemyLifecycleEntry, AlchemyLifecycleError, type AlchemyLifecycleFailureCode, type AlchemyLifecycleFailureFields, type AlchemyLifecycleOperation, type AlchemyLifecyclePhase, type AlchemyLifecyclePolicy, type AlchemyLifecycleRequest, type AlchemyLifecycleResult, type AssertDestroyLeftNothingOptions, type AssertRetainedIdentitiesOptions, type AssertUrlImpliesAuthOptions, type AuthBindingNameMatcher, type CloudflareCredential, CloudflareCredentialUnavailableError, type CredentialPreflightOptions, type CredentialPreflightResult, type CredentialSource, type EvaluateStackInput, type EvaluatedBinding, type EvaluatedGraph, type EvaluatedResource, type FirstDeployAuthorization, type FirstDeployAuthorizationOptions, type FirstDeployPlanHashOptions, type FirstDeployPolicy, HOSTED_CLOUDFLARE_CREDENTIAL_NAMES, LIFECYCLE_FAILURE_MESSAGES, LOCAL_PREVIEW_STAGES, MINIMUM_REDACTABLE_SECRET_LENGTH, type MutationCertainty, PLAN_ACTIONS, PLAN_OUTPUT_ALCHEMY_VERSION, type PlanAction, type PlanEffects, PlanEffectsError, type PlanEffectsRejection, type PlanResourceEffect, RetainedIdentityError, type RetainedIdentityMarker, type RetainedIdentitySection, type RetainedIdentityViolation, type RetainedIdentityViolationReason, type RunAlchemyLifecycleOptions, SECRET_REDACTION_PLACEHOLDER, type SecretNamePolicyOptions, type StackOutputOccupancy, type StackStageOwnership, type StageCleanupOutcome, type StageLease, type StageMatcher, type StagePolicy, StagePolicyError, type StagePolicyErrorCode, type StagePolicyTable, type StageSnapshotView, type StageStateReader, type StageStateStore, type StageStateTarget, type StageTarget, type StateSnapshot, type StateSnapshotResource, type StateSnapshotScope, type UrlImpliesAuthAllowance, UrlImpliesAuthError, type UrlImpliesAuthViolation, type UrlImpliesAuthViolationReason, anyStage, assertDestroyLeftNothing, assertRetainedIdentities, assertUrlImpliesAuth, credentialPreflight, evaluateStack, firstDeployAuthorization, firstDeployPlanHash, isAlchemyLifecycleError, parsePlanEffects, parseStateSnapshot, runAlchemyLifecycle, secretNamePolicy, stagePolicy };
|
|
1034
|
+
export { ALCHEMY_SUBPATH, type AlchemyLifecycleEntry, AlchemyLifecycleError, type AlchemyLifecycleFailureCode, type AlchemyLifecycleFailureFields, type AlchemyLifecycleOperation, type AlchemyLifecyclePhase, type AlchemyLifecyclePolicy, type AlchemyLifecycleRequest, type AlchemyLifecycleResult, type AssertDestroyLeftNothingOptions, type AssertRetainedIdentitiesOptions, type AssertUrlImpliesAuthOptions, type AuthBindingNameMatcher, type CloudflareCredential, CloudflareCredentialUnavailableError, type CredentialPreflightOptions, type CredentialPreflightResult, type CredentialSource, type EvaluateStackInput, type EvaluatedBinding, type EvaluatedGraph, type EvaluatedResource, type FirstDeployAuthorization, type FirstDeployAuthorizationOptions, type FirstDeployPlanHashOptions, type FirstDeployPolicy, HOSTED_CLOUDFLARE_CREDENTIAL_NAMES, LIFECYCLE_FAILURE_MESSAGES, LOCAL_PREVIEW_STAGES, MINIMUM_REDACTABLE_SECRET_LENGTH, type MutationCertainty, PLAN_ACTIONS, PLAN_OUTPUT_ALCHEMY_VERSION, type PlanAction, type PlanEffects, PlanEffectsError, type PlanEffectsRejection, type PlanResourceEffect, type PlanTaskAction, type PlanTaskEffect, RetainedIdentityError, type RetainedIdentityMarker, type RetainedIdentitySection, type RetainedIdentityViolation, type RetainedIdentityViolationReason, type RunAlchemyLifecycleOptions, SECRET_REDACTION_PLACEHOLDER, type SecretNamePolicyOptions, type StackOutputOccupancy, type StackStageOwnership, type StageCleanupOutcome, type StageLease, type StageMatcher, type StagePolicy, StagePolicyError, type StagePolicyErrorCode, type StagePolicyTable, type StageSnapshotView, type StageStateReader, type StageStateStore, type StageStateTarget, type StageTarget, type StateSnapshot, type StateSnapshotResource, type StateSnapshotScope, type UrlImpliesAuthAllowance, UrlImpliesAuthError, type UrlImpliesAuthViolation, type UrlImpliesAuthViolationReason, anyStage, assertDestroyLeftNothing, assertRetainedIdentities, assertUrlImpliesAuth, credentialPreflight, deleteStageRows, evaluateStack, firstDeployAuthorization, firstDeployPlanHash, inventoryStages, isAlchemyLifecycleError, isLocalEmulationStage, localEmulationEnvironment, parsePlanEffects, parseStateSnapshot, readStageSnapshot, runAlchemyLifecycle, secretNamePolicy, stagePolicy };
|