@microagi/alchemy-gcp 0.2.1 → 0.3.0

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.
Files changed (47) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/README.md +54 -2
  3. package/lib/Providers.d.ts.map +1 -1
  4. package/lib/Providers.js +5 -1
  5. package/lib/Providers.js.map +1 -1
  6. package/lib/Run/IamMember.d.ts +70 -0
  7. package/lib/Run/IamMember.d.ts.map +1 -0
  8. package/lib/Run/IamMember.js +65 -0
  9. package/lib/Run/IamMember.js.map +1 -0
  10. package/lib/Run/IamSync.d.ts +80 -0
  11. package/lib/Run/IamSync.d.ts.map +1 -0
  12. package/lib/Run/IamSync.js +73 -0
  13. package/lib/Run/IamSync.js.map +1 -0
  14. package/lib/Run/Job.d.ts +167 -0
  15. package/lib/Run/Job.d.ts.map +1 -0
  16. package/lib/Run/Job.js +199 -0
  17. package/lib/Run/Job.js.map +1 -0
  18. package/lib/Run/Operations.d.ts +47 -0
  19. package/lib/Run/Operations.d.ts.map +1 -0
  20. package/lib/Run/Operations.js +46 -0
  21. package/lib/Run/Operations.js.map +1 -0
  22. package/lib/Run/Service.d.ts +193 -0
  23. package/lib/Run/Service.d.ts.map +1 -0
  24. package/lib/Run/Service.js +247 -0
  25. package/lib/Run/Service.js.map +1 -0
  26. package/lib/Run/Validation.d.ts +54 -0
  27. package/lib/Run/Validation.d.ts.map +1 -0
  28. package/lib/Run/Validation.js +129 -0
  29. package/lib/Run/Validation.js.map +1 -0
  30. package/lib/Run/index.d.ts +51 -0
  31. package/lib/Run/index.d.ts.map +1 -0
  32. package/lib/Run/index.js +4 -0
  33. package/lib/Run/index.js.map +1 -0
  34. package/lib/index.d.ts +1 -0
  35. package/lib/index.d.ts.map +1 -1
  36. package/lib/index.js +1 -0
  37. package/lib/index.js.map +1 -1
  38. package/package.json +5 -4
  39. package/src/Providers.ts +6 -0
  40. package/src/Run/IamMember.ts +79 -0
  41. package/src/Run/IamSync.ts +134 -0
  42. package/src/Run/Job.ts +453 -0
  43. package/src/Run/Operations.ts +77 -0
  44. package/src/Run/Service.ts +541 -0
  45. package/src/Run/Validation.ts +153 -0
  46. package/src/Run/index.ts +52 -0
  47. package/src/index.ts +1 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,65 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@microagi/alchemy-gcp`. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this package
5
+ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## 0.3.0 — 2026-05-11
8
+
9
+ Cloud Run v2 — production polish on the Service + Job + IAM surface.
10
+
11
+ ### Added
12
+
13
+ - **`GCP.Service`** — Cloud Run v2 HTTP service. Traffic routing,
14
+ ingress controls (`INGRESS_TRAFFIC_ALL` / `INTERNAL_ONLY` /
15
+ `INTERNAL_LOAD_BALANCER`), launch-stage gating, server-side
16
+ template diffing, label-gated adoption, and full LRO polling.
17
+ - **`GCP.Job`** — Cloud Run v2 batch workload. Declarative
18
+ definition of the Job (template, parallelism, task count, retries,
19
+ GPU node selectors); execution is imperative via
20
+ `runProjectsLocationsJobs` (documented in the resource JSDoc).
21
+ - **`GCP.serviceIamMember`** / **`GCP.jobIamMember`** — target-side
22
+ IAM binding helpers. Provider unions all bindings per target and
23
+ writes a single `setIamPolicy` per reconcile, preserving foreign
24
+ roles and members. Etag round-trip absorbs CI/human contention.
25
+ - **Clean type re-exports** from the top level — `GCP.RevisionTemplate`,
26
+ `GCP.ExecutionTemplate`, `GCP.TaskTemplate`, `GCP.Container`,
27
+ `GCP.TrafficTarget`, `GCP.VpcAccess`, `GCP.NodeSelector`, etc. —
28
+ so consumers no longer need to import from `@distilled.cloud/gcp`
29
+ directly.
30
+ - **Plan-time validation** for Cloud Run resources — name regex
31
+ (`<50 chars`, RFC 1123 label subset), reserved label/annotation
32
+ namespaces (`run.googleapis.com/`, `cloud.googleapis.com/`,
33
+ `serving.knative.dev/`, `autoscaling.knative.dev/`), and
34
+ template-must-have-containers. Fails fast with a typed
35
+ `ConfigError` before any state mutation.
36
+ - **`BadRequest` passthroughs** on Cloud Run create + patch — wrap
37
+ the underlying GCP message with a remediation hint for billing
38
+ not enabled, image-not-in-approved-registry, and reserved-namespace
39
+ failures.
40
+
41
+ ### Changed
42
+
43
+ - **Optimistic concurrency** on Cloud Run Service + Job — patch and
44
+ delete now pass the observed `etag` so concurrent edits (e.g. via
45
+ `gcloud run services update`) surface as `Conflict` instead of
46
+ silently overwriting.
47
+ - README rewritten with categorised resource sections and Cloud Run
48
+ examples.
49
+
50
+ ### Notes
51
+
52
+ `apps/cluster` and other downstream stacks bumping past `0.2.x` should
53
+ swap `invokerIamDisabled: true` (sledgehammer) for the declarative
54
+ `serviceIamMember(svc, "Public", { role: "roles/run.invoker", member: "allUsers" })`
55
+ binding where possible — IAM history is auditable; the flag is not.
56
+
57
+ ## 0.2.x
58
+
59
+ Pre-0.3 history is in `git log` — see commits up to and including
60
+ `c6bcb9d Add Cloud Run v2 Service resource`. Highlights:
61
+
62
+ - 0.2.0 — initial Cloud Run Service support (no IAM, no Job).
63
+ - 0.1.x — Project, ApiEnable, Cluster, NodePool, Network, Subnetwork,
64
+ PsaConnection, GlobalAddress, SharedVpcHost, SharedVpcServiceProject,
65
+ ManagedLustreInstance.
package/README.md CHANGED
@@ -79,27 +79,79 @@ bun alchemy destroy ./alchemy.run.ts
79
79
 
80
80
  `Alchemy.localState()` writes Alchemy's resource state to `.alchemy/` next to the stack file. `Alchemy.inMemoryState()` is fine for tests but loses state between runs. For team use, swap in `httpStateStore()`.
81
81
 
82
- Long-running operations (`createProjects`, `patchProjects`, GKE cluster ops) are polled internally; reconcilers follow the [Alchemy reconciler doctrine](https://v2.alchemy.run/concepts/resource-lifecycle) (single observe → ensure → sync → return flow that converges from any starting state, including adoption).
82
+ Long-running operations (`createProjects`, `patchProjects`, GKE cluster ops, Cloud Run service deploys) are polled internally; reconcilers follow the [Alchemy reconciler doctrine](https://v2.alchemy.run/concepts/resource-lifecycle) (single observe → ensure → sync → return flow that converges from any starting state, including adoption).
83
83
 
84
84
  ## Resources
85
85
 
86
+ ### Foundation
86
87
  - **`GCP.Project`** — projects under an org or folder, with optional billing-account attach.
87
88
  - **`GCP.ApiEnable`** — project-level GCP service enablement.
89
+
90
+ ### Compute
88
91
  - **`GCP.Cluster`** — Standard GKE cluster.
89
92
  - **`GCP.NodePool`** — node pool attached to a cluster, including accelerator (GPU) configurations.
93
+
94
+ ### Networking
90
95
  - **`GCP.Network`** — VPC network.
91
96
  - **`GCP.Subnetwork`** — VPC subnetwork.
92
97
  - **`GCP.PsaConnection`** — Private Service Access peering for Google managed services.
93
98
  - **`GCP.GlobalAddress`** — global IP address (typically used to reserve a PSA range).
94
99
  - **`GCP.SharedVpcHost`** / **`GCP.SharedVpcServiceProject`** — Shared VPC enable/attach.
95
- - **`GCP.ParallelstoreInstance`** — Parallelstore filesystem instance.
100
+
101
+ ### Serverless (Cloud Run v2)
102
+ - **`GCP.Service`** — Cloud Run HTTP service with traffic routing, ingress controls, and IAM.
103
+ - **`GCP.Job`** — Cloud Run batch workload (declarative definition; trigger execution imperatively via `runProjectsLocationsJobs`).
104
+ - **`GCP.serviceIamMember`** / **`GCP.jobIamMember`** — bind `(role, member)` IAM grants on Cloud Run resources. Provider unions all bindings and writes one `setIamPolicy` per target, preserving foreign roles.
105
+
106
+ ### Storage
107
+ - **`GCP.ManagedLustreInstance`** — Managed Lustre filesystem instance.
96
108
 
97
109
  Each resource's full prop/attribute set is documented as JSDoc on the source.
98
110
 
111
+ ### Cloud Run example
112
+
113
+ ```ts
114
+ const api = yield* GCP.Service("HelloApi", {
115
+ project: project.projectId,
116
+ location: "europe-west4",
117
+ template: {
118
+ containers: [{ image: "gcr.io/cloudrun/hello" }],
119
+ },
120
+ });
121
+
122
+ // Declarative public access — preferred over `invokerIamDisabled: true`,
123
+ // which bypasses IAM entirely and leaves no audit trail.
124
+ yield* GCP.serviceIamMember(api, "PublicInvoker", {
125
+ role: "roles/run.invoker",
126
+ member: "allUsers",
127
+ });
128
+
129
+ // Batch workload — definition only; execution is imperative.
130
+ const batch = yield* GCP.Job("ProcessBatch", {
131
+ project: project.projectId,
132
+ location: "europe-west4",
133
+ template: {
134
+ taskCount: 1,
135
+ template: {
136
+ maxRetries: 3,
137
+ containers: [{ image: "europe-west4-docker.pkg.dev/proj/repo/worker:v1" }],
138
+ },
139
+ },
140
+ });
141
+ ```
142
+
143
+ Clean type aliases are re-exported from the top level so consumers don't need to import from `@distilled.cloud/gcp` directly: `GCP.RevisionTemplate`, `GCP.Container`, `GCP.TrafficTarget`, `GCP.VpcAccess`, `GCP.NodeSelector`, etc.
144
+
99
145
  ## Adoption
100
146
 
101
147
  `read` is gated on the alchemy internal labels `alchemy_app` / `alchemy_stage` / `alchemy_id`. Existing GCP resources lacking those labels are returned `Unowned` — the engine refuses to take them over without explicit `--adopt` (or `adopt(true)` on the resource call).
102
148
 
149
+ For resources that don't carry labels (Network, Subnetwork), the same `(app, stage, id)` triple is encoded as a sentinel inside `description` and matched on `read`.
150
+
151
+ ## Optimistic concurrency
152
+
153
+ Resources with server-side fingerprints (Cloud Run Service/Job, Subnetwork) pass the observed `etag`/`fingerprint` through patch and delete calls. A concurrent edit between observe and patch surfaces as `Conflict` rather than silently overwriting.
154
+
103
155
  ## License
104
156
 
105
157
  Apache-2.0.
@@ -1 +1 @@
1
- {"version":3,"file":"Providers.d.ts","sourceRoot":"","sources":["../src/Providers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAC7C,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;;AAwBtC,qBAAa,SAAU,SAAQ,cAA+C;CAAG;AAEjF,MAAM,MAAM,oBAAoB,GAAG,KAAK,CAAC,QAAQ,CAAC,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC,CAAC;AAEhF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,SAAS,uQAuCnB,CAAC"}
1
+ {"version":3,"file":"Providers.d.ts","sourceRoot":"","sources":["../src/Providers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAC7C,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;;AA0BtC,qBAAa,SAAU,SAAQ,cAA+C;CAAG;AAEjF,MAAM,MAAM,oBAAoB,GAAG,KAAK,CAAC,QAAQ,CAAC,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC,CAAC;AAEhF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,SAAS,uQA2CnB,CAAC"}
package/lib/Providers.js CHANGED
@@ -11,6 +11,8 @@ import { Subnetwork, SubnetworkProvider } from "./Compute/Subnetwork.js";
11
11
  import { Cluster, ClusterProvider } from "./Container/Cluster.js";
12
12
  import { NodePool, NodePoolProvider } from "./Container/NodePool.js";
13
13
  import { ManagedLustreInstance, ManagedLustreInstanceProvider, } from "./ManagedLustre/Instance.js";
14
+ import { Job, JobProvider } from "./Run/Job.js";
15
+ import { Service, ServiceProvider } from "./Run/Service.js";
14
16
  import { PsaConnection, PsaConnectionProvider, } from "./ServiceNetworking/PsaConnection.js";
15
17
  import { ApiEnable, ApiEnableProvider } from "./ServiceUsage/ApiEnable.js";
16
18
  export class Providers extends Provider.ProviderCollection()("GCP") {
@@ -42,7 +44,9 @@ export const providers = () => Layer.effect(Providers, Provider.collection([
42
44
  SharedVpcServiceProject,
43
45
  PsaConnection,
44
46
  ManagedLustreInstance,
45
- ])).pipe(Layer.provide(Layer.mergeAll(ProjectProvider(), ClusterProvider(), NodePoolProvider(), ApiEnableProvider(), NetworkProvider(), SubnetworkProvider(), GlobalAddressProvider(), SharedVpcHostProvider(), SharedVpcServiceProjectProvider(), PsaConnectionProvider(), ManagedLustreInstanceProvider())), Layer.provideMerge(fromAuthProvider()), Layer.provideMerge(GCPAuth),
47
+ Service,
48
+ Job,
49
+ ])).pipe(Layer.provide(Layer.mergeAll(ProjectProvider(), ClusterProvider(), NodePoolProvider(), ApiEnableProvider(), NetworkProvider(), SubnetworkProvider(), GlobalAddressProvider(), SharedVpcHostProvider(), SharedVpcServiceProjectProvider(), PsaConnectionProvider(), ManagedLustreInstanceProvider(), ServiceProvider(), JobProvider())), Layer.provideMerge(fromAuthProvider()), Layer.provideMerge(GCPAuth),
46
50
  // Auth/config failures (missing ADC, malformed profile) become
47
51
  // unrecoverable defects rather than typed errors — mirrors
48
52
  // `AWS/Providers.ts` and `Cloudflare/Providers.ts`. Resource lifecycle
@@ -1 +1 @@
1
- {"version":3,"file":"Providers.js","sourceRoot":"","sources":["../src/Providers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAC7C,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AAC7E,OAAO,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAClF,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAChE,OAAO,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAClF,OAAO,EACL,uBAAuB,EACvB,+BAA+B,GAChC,MAAM,sCAAsC,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AACzE,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAClE,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AACrE,OAAO,EACL,qBAAqB,EACrB,6BAA6B,GAC9B,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,aAAa,EACb,qBAAqB,GACtB,MAAM,sCAAsC,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAE3E,MAAM,OAAO,SAAU,SAAQ,QAAQ,CAAC,kBAAkB,EAAa,CAAC,KAAK,CAAC;CAAG;AAIjF;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,GAAG,EAAE,CAC5B,KAAK,CAAC,MAAM,CACV,SAAS,EACT,QAAQ,CAAC,UAAU,CAAC;IAClB,OAAO;IACP,OAAO;IACP,QAAQ;IACR,SAAS;IACT,OAAO;IACP,UAAU;IACV,aAAa;IACb,aAAa;IACb,uBAAuB;IACvB,aAAa;IACb,qBAAqB;CACtB,CAAC,CACH,CAAC,IAAI,CACJ,KAAK,CAAC,OAAO,CACX,KAAK,CAAC,QAAQ,CACZ,eAAe,EAAE,EACjB,eAAe,EAAE,EACjB,gBAAgB,EAAE,EAClB,iBAAiB,EAAE,EACnB,eAAe,EAAE,EACjB,kBAAkB,EAAE,EACpB,qBAAqB,EAAE,EACvB,qBAAqB,EAAE,EACvB,+BAA+B,EAAE,EACjC,qBAAqB,EAAE,EACvB,6BAA6B,EAAE,CAChC,CACF,EACD,KAAK,CAAC,YAAY,CAAC,gBAAgB,EAAE,CAAC,EACtC,KAAK,CAAC,YAAY,CAAC,OAAO,CAAC;AAC3B,+DAA+D;AAC/D,2DAA2D;AAC3D,uEAAuE;AACvE,0DAA0D;AAC1D,KAAK,CAAC,KAAK,CACZ,CAAC"}
1
+ {"version":3,"file":"Providers.js","sourceRoot":"","sources":["../src/Providers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAC7C,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AAC7E,OAAO,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAClF,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAChE,OAAO,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAClF,OAAO,EACL,uBAAuB,EACvB,+BAA+B,GAChC,MAAM,sCAAsC,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AACzE,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAClE,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AACrE,OAAO,EACL,qBAAqB,EACrB,6BAA6B,GAC9B,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAE,GAAG,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAC5D,OAAO,EACL,aAAa,EACb,qBAAqB,GACtB,MAAM,sCAAsC,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAE3E,MAAM,OAAO,SAAU,SAAQ,QAAQ,CAAC,kBAAkB,EAAa,CAAC,KAAK,CAAC;CAAG;AAIjF;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,GAAG,EAAE,CAC5B,KAAK,CAAC,MAAM,CACV,SAAS,EACT,QAAQ,CAAC,UAAU,CAAC;IAClB,OAAO;IACP,OAAO;IACP,QAAQ;IACR,SAAS;IACT,OAAO;IACP,UAAU;IACV,aAAa;IACb,aAAa;IACb,uBAAuB;IACvB,aAAa;IACb,qBAAqB;IACrB,OAAO;IACP,GAAG;CACJ,CAAC,CACH,CAAC,IAAI,CACJ,KAAK,CAAC,OAAO,CACX,KAAK,CAAC,QAAQ,CACZ,eAAe,EAAE,EACjB,eAAe,EAAE,EACjB,gBAAgB,EAAE,EAClB,iBAAiB,EAAE,EACnB,eAAe,EAAE,EACjB,kBAAkB,EAAE,EACpB,qBAAqB,EAAE,EACvB,qBAAqB,EAAE,EACvB,+BAA+B,EAAE,EACjC,qBAAqB,EAAE,EACvB,6BAA6B,EAAE,EAC/B,eAAe,EAAE,EACjB,WAAW,EAAE,CACd,CACF,EACD,KAAK,CAAC,YAAY,CAAC,gBAAgB,EAAE,CAAC,EACtC,KAAK,CAAC,YAAY,CAAC,OAAO,CAAC;AAC3B,+DAA+D;AAC/D,2DAA2D;AAC3D,uEAAuE;AACvE,0DAA0D;AAC1D,KAAK,CAAC,KAAK,CACZ,CAAC"}
@@ -0,0 +1,70 @@
1
+ import * as Effect from "effect/Effect";
2
+ import type { Job } from "./Job.ts";
3
+ import type { Service } from "./Service.ts";
4
+ /**
5
+ * Bind a single `(role, member)` IAM grant onto a Cloud Run
6
+ * {@link Service}.
7
+ *
8
+ * Target-side binding — see
9
+ * {@link import("../Compute/SubnetworkIamMember.ts").subnetworkIamMember}
10
+ * for the pattern, the SID-collision pitfall, and why `key` exists.
11
+ * Service's `reconcile` merges all bindings into a single
12
+ * `setIamPolicy` call against the service, preserving foreign roles
13
+ * and members on the policy. To remove a binding, drop the call and
14
+ * re-deploy — the provider is additive within the bindings we
15
+ * declared, but does NOT prune.
16
+ *
17
+ * The most common usage:
18
+ *
19
+ * - **Public invoker:** `{ role: "roles/run.invoker", member: "allUsers" }`
20
+ * makes the service publicly reachable. Prefer this over
21
+ * `invokerIamDisabled: true` on the {@link Service} props — declarative
22
+ * IAM is auditable in `setIamPolicy` history; `invokerIamDisabled`
23
+ * is a flag with no audit trail.
24
+ * - **Service-to-service:** a downstream service's runtime SA bound
25
+ * to `roles/run.invoker` on the upstream service.
26
+ *
27
+ * @example Public Cloud Run service
28
+ * ```typescript
29
+ * const api = yield* GCP.Service("PublicApi", { ... });
30
+ * yield* GCP.serviceIamMember(api, "PublicInvoker", {
31
+ * role: "roles/run.invoker",
32
+ * member: "allUsers",
33
+ * });
34
+ * ```
35
+ *
36
+ * @example Service-to-service auth
37
+ * ```typescript
38
+ * const upstream = yield* GCP.Service("Upstream", { ... });
39
+ * yield* GCP.serviceIamMember(upstream, "DownstreamCaller", {
40
+ * role: "roles/run.invoker",
41
+ * member: `serviceAccount:${downstreamSa.email}`,
42
+ * });
43
+ * ```
44
+ */
45
+ export declare const serviceIamMember: (service: Service, key: string, args: {
46
+ role: string;
47
+ member: string;
48
+ }) => Effect.Effect<void>;
49
+ /**
50
+ * Bind a single `(role, member)` IAM grant onto a Cloud Run {@link Job}.
51
+ *
52
+ * Same target-side pattern as {@link serviceIamMember}. The most
53
+ * common Job binding is `roles/run.invoker` on the service account
54
+ * that triggers the job (Cloud Scheduler, Eventarc, Workflows, or a
55
+ * developer's user identity).
56
+ *
57
+ * @example Letting a Cloud Scheduler SA trigger the job
58
+ * ```typescript
59
+ * const nightly = yield* GCP.Job("Nightly", { ... });
60
+ * yield* GCP.jobIamMember(nightly, "SchedulerInvoker", {
61
+ * role: "roles/run.invoker",
62
+ * member: `serviceAccount:${schedulerSa.email}`,
63
+ * });
64
+ * ```
65
+ */
66
+ export declare const jobIamMember: (job: Job, key: string, args: {
67
+ role: string;
68
+ member: string;
69
+ }) => Effect.Effect<void>;
70
+ //# sourceMappingURL=IamMember.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IamMember.d.ts","sourceRoot":"","sources":["../../src/Run/IamMember.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AACpC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAE5C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,eAAO,MAAM,gBAAgB,GAC3B,SAAS,OAAO,EAChB,KAAK,MAAM,EACX,MAAM;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,KACrC,MAAM,CAAC,MAAM,CAAC,IAAI,CAGiB,CAAC;AAEvC;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,YAAY,GACvB,KAAK,GAAG,EACR,KAAK,MAAM,EACX,MAAM;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,KACrC,MAAM,CAAC,MAAM,CAAC,IAAI,CAGiB,CAAC"}
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Bind a single `(role, member)` IAM grant onto a Cloud Run
3
+ * {@link Service}.
4
+ *
5
+ * Target-side binding — see
6
+ * {@link import("../Compute/SubnetworkIamMember.ts").subnetworkIamMember}
7
+ * for the pattern, the SID-collision pitfall, and why `key` exists.
8
+ * Service's `reconcile` merges all bindings into a single
9
+ * `setIamPolicy` call against the service, preserving foreign roles
10
+ * and members on the policy. To remove a binding, drop the call and
11
+ * re-deploy — the provider is additive within the bindings we
12
+ * declared, but does NOT prune.
13
+ *
14
+ * The most common usage:
15
+ *
16
+ * - **Public invoker:** `{ role: "roles/run.invoker", member: "allUsers" }`
17
+ * makes the service publicly reachable. Prefer this over
18
+ * `invokerIamDisabled: true` on the {@link Service} props — declarative
19
+ * IAM is auditable in `setIamPolicy` history; `invokerIamDisabled`
20
+ * is a flag with no audit trail.
21
+ * - **Service-to-service:** a downstream service's runtime SA bound
22
+ * to `roles/run.invoker` on the upstream service.
23
+ *
24
+ * @example Public Cloud Run service
25
+ * ```typescript
26
+ * const api = yield* GCP.Service("PublicApi", { ... });
27
+ * yield* GCP.serviceIamMember(api, "PublicInvoker", {
28
+ * role: "roles/run.invoker",
29
+ * member: "allUsers",
30
+ * });
31
+ * ```
32
+ *
33
+ * @example Service-to-service auth
34
+ * ```typescript
35
+ * const upstream = yield* GCP.Service("Upstream", { ... });
36
+ * yield* GCP.serviceIamMember(upstream, "DownstreamCaller", {
37
+ * role: "roles/run.invoker",
38
+ * member: `serviceAccount:${downstreamSa.email}`,
39
+ * });
40
+ * ```
41
+ */
42
+ export const serviceIamMember = (service, key, args) => service.bind `IamMember(${service}, ${key})`({
43
+ iamBindings: [{ role: args.role, members: [args.member] }],
44
+ });
45
+ /**
46
+ * Bind a single `(role, member)` IAM grant onto a Cloud Run {@link Job}.
47
+ *
48
+ * Same target-side pattern as {@link serviceIamMember}. The most
49
+ * common Job binding is `roles/run.invoker` on the service account
50
+ * that triggers the job (Cloud Scheduler, Eventarc, Workflows, or a
51
+ * developer's user identity).
52
+ *
53
+ * @example Letting a Cloud Scheduler SA trigger the job
54
+ * ```typescript
55
+ * const nightly = yield* GCP.Job("Nightly", { ... });
56
+ * yield* GCP.jobIamMember(nightly, "SchedulerInvoker", {
57
+ * role: "roles/run.invoker",
58
+ * member: `serviceAccount:${schedulerSa.email}`,
59
+ * });
60
+ * ```
61
+ */
62
+ export const jobIamMember = (job, key, args) => job.bind `IamMember(${job}, ${key})`({
63
+ iamBindings: [{ role: args.role, members: [args.member] }],
64
+ });
65
+ //# sourceMappingURL=IamMember.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IamMember.js","sourceRoot":"","sources":["../../src/Run/IamMember.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAC9B,OAAgB,EAChB,GAAW,EACX,IAAsC,EACjB,EAAE,CACvB,OAAO,CAAC,IAAI,CAAA,aAAa,OAAO,KAAK,GAAG,GAAG,CAAC;IAC1C,WAAW,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;CAC3D,CAAmC,CAAC;AAEvC;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAC1B,GAAQ,EACR,GAAW,EACX,IAAsC,EACjB,EAAE,CACvB,GAAG,CAAC,IAAI,CAAA,aAAa,GAAG,KAAK,GAAG,GAAG,CAAC;IAClC,WAAW,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;CAC3D,CAAmC,CAAC"}
@@ -0,0 +1,80 @@
1
+ import type * as run from "@distilled.cloud/gcp/run-v2";
2
+ import type { ResourceBinding } from "alchemy/Resource";
3
+ import * as Effect from "effect/Effect";
4
+ /**
5
+ * Common shape of Cloud Run's `getIamPolicy` / `setIamPolicy` request
6
+ * inputs — Service and Job declare nominally distinct request types,
7
+ * but both reduce to `{ resource, body? }` plus the version query for
8
+ * get. We model the operation surface generically so {@link makeSyncIam}
9
+ * can serve both targets without duplication.
10
+ *
11
+ * @internal
12
+ */
13
+ type GetIamPolicyOp = (input: {
14
+ resource: string;
15
+ "options.requestedPolicyVersion"?: number;
16
+ }) => Effect.Effect<run.GoogleIamV1Policy, unknown, never>;
17
+ type SetIamPolicyOp = (input: {
18
+ resource: string;
19
+ body?: run.GoogleIamV1SetIamPolicyRequest;
20
+ }) => Effect.Effect<run.GoogleIamV1Policy, unknown, never>;
21
+ /**
22
+ * Single `(role, members)` entry on a Cloud Run target's IAM policy.
23
+ * Identical shape between Service and Job — both Cloud Run resources
24
+ * speak the same `iam.v1.Policy`, so the binding contract is shared.
25
+ *
26
+ * @example
27
+ * ```typescript
28
+ * yield* GCP.serviceIamMember(svc, "PublicInvoker", {
29
+ * role: "roles/run.invoker",
30
+ * member: "allUsers",
31
+ * });
32
+ * ```
33
+ */
34
+ export type RunIamBinding = {
35
+ /** IAM role, e.g. `"roles/run.invoker"`. */
36
+ role: string;
37
+ /** Principals, e.g. `["allUsers"]` or `["serviceAccount:foo@bar.iam.gserviceaccount.com"]`. */
38
+ members: ReadonlyArray<string>;
39
+ };
40
+ /**
41
+ * Binding contract for a Cloud Run target — services and jobs both
42
+ * accept a list of `iamBindings` records via the alchemy `.bind`
43
+ * mechanism. The reconciler reads `bindings` from its arguments and
44
+ * routes them through {@link makeSyncIam}.
45
+ */
46
+ export type RunIamBindingContract = {
47
+ iamBindings: ReadonlyArray<RunIamBinding>;
48
+ };
49
+ /**
50
+ * Build a reusable `syncIam` step parametrised by the resolved
51
+ * `getIamPolicy` / `setIamPolicy` callables. Cloud Run's Service and
52
+ * Job IAM endpoints have identical request/response shapes (both
53
+ * `resource: "projects/{p}/locations/{l}/{kind}/{n}"`), so the same
54
+ * helper works for both — the caller just hands in the right pair of
55
+ * callables.
56
+ *
57
+ * Semantics, mirroring `Compute/Subnetwork.ts`:
58
+ *
59
+ * - **Union bindings per role across all `.bind` callers.** Multiple
60
+ * capabilities granting the same role get their members merged into
61
+ * a single binding.
62
+ * - **Preserve foreign roles and members verbatim.** We never displace
63
+ * bindings we didn't author. The provider is additive — to remove a
64
+ * binding, drop the `.bind` call and re-deploy (a future revision
65
+ * could narrow this; for now it matches the existing idiom).
66
+ * - **Etag round-trip.** `getIamPolicy` returns an etag; we pass it
67
+ * through `setIamPolicy` so a concurrent edit racing us surfaces as
68
+ * `Conflict`. We retry exponentially up to ~1.5 min — enough to
69
+ * absorb normal CI/human contention without masking real failures.
70
+ */
71
+ export declare const makeSyncIam: (api: {
72
+ getIamPolicy: GetIamPolicyOp;
73
+ setIamPolicy: SetIamPolicyOp;
74
+ }) => (args: {
75
+ /** Fully-qualified resource name: `projects/{p}/locations/{l}/{kind}/{n}`. */
76
+ resource: string;
77
+ bindings: ReadonlyArray<ResourceBinding<RunIamBindingContract>>;
78
+ }) => Effect.Effect<void, unknown, never>;
79
+ export {};
80
+ //# sourceMappingURL=IamSync.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IamSync.d.ts","sourceRoot":"","sources":["../../src/Run/IamSync.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,GAAG,MAAM,6BAA6B,CAAC;AACxD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAExD,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AAGxC;;;;;;;;GAQG;AACH,KAAK,cAAc,GAAG,CAAC,KAAK,EAAE;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,gCAAgC,CAAC,EAAE,MAAM,CAAC;CAC3C,KAAK,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,iBAAiB,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC;AAE3D,KAAK,cAAc,GAAG,CAAC,KAAK,EAAE;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,GAAG,CAAC,8BAA8B,CAAC;CAC3C,KAAK,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,iBAAiB,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC;AAE3D;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,4CAA4C;IAC5C,IAAI,EAAE,MAAM,CAAC;IACb,+FAA+F;IAC/F,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CAChC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC,WAAW,EAAE,aAAa,CAAC,aAAa,CAAC,CAAC;CAC3C,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,WAAW,GACrB,KAAK;IAAE,YAAY,EAAE,cAAc,CAAC;IAAC,YAAY,EAAE,cAAc,CAAA;CAAE,MACnE,MAAM;IACL,8EAA8E;IAC9E,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,aAAa,CAAC,eAAe,CAAC,qBAAqB,CAAC,CAAC,CAAC;CACjE,wCAkDE,CAAC"}
@@ -0,0 +1,73 @@
1
+ import * as Duration from "effect/Duration";
2
+ import * as Effect from "effect/Effect";
3
+ import * as Schedule from "effect/Schedule";
4
+ /**
5
+ * Build a reusable `syncIam` step parametrised by the resolved
6
+ * `getIamPolicy` / `setIamPolicy` callables. Cloud Run's Service and
7
+ * Job IAM endpoints have identical request/response shapes (both
8
+ * `resource: "projects/{p}/locations/{l}/{kind}/{n}"`), so the same
9
+ * helper works for both — the caller just hands in the right pair of
10
+ * callables.
11
+ *
12
+ * Semantics, mirroring `Compute/Subnetwork.ts`:
13
+ *
14
+ * - **Union bindings per role across all `.bind` callers.** Multiple
15
+ * capabilities granting the same role get their members merged into
16
+ * a single binding.
17
+ * - **Preserve foreign roles and members verbatim.** We never displace
18
+ * bindings we didn't author. The provider is additive — to remove a
19
+ * binding, drop the `.bind` call and re-deploy (a future revision
20
+ * could narrow this; for now it matches the existing idiom).
21
+ * - **Etag round-trip.** `getIamPolicy` returns an etag; we pass it
22
+ * through `setIamPolicy` so a concurrent edit racing us surfaces as
23
+ * `Conflict`. We retry exponentially up to ~1.5 min — enough to
24
+ * absorb normal CI/human contention without masking real failures.
25
+ */
26
+ export const makeSyncIam = (api) => (args) => Effect.gen(function* () {
27
+ const desiredByRole = new Map();
28
+ for (const b of args.bindings) {
29
+ for (const ib of b.data.iamBindings) {
30
+ const set = desiredByRole.get(ib.role) ?? new Set();
31
+ for (const m of ib.members)
32
+ set.add(m);
33
+ desiredByRole.set(ib.role, set);
34
+ }
35
+ }
36
+ if (desiredByRole.size === 0)
37
+ return;
38
+ const current = yield* api.getIamPolicy({
39
+ resource: args.resource,
40
+ "options.requestedPolicyVersion": 3,
41
+ });
42
+ const bindings = (current.bindings ?? []).map((b) => ({
43
+ ...b,
44
+ members: [...(b.members ?? [])],
45
+ }));
46
+ let mutated = false;
47
+ for (const [role, members] of desiredByRole) {
48
+ let existing = bindings.find((b) => b.role === role && !b.condition);
49
+ if (!existing) {
50
+ existing = { role, members: [] };
51
+ bindings.push(existing);
52
+ }
53
+ const merged = new Set([...(existing.members ?? []), ...members]);
54
+ if (merged.size !== (existing.members?.length ?? 0))
55
+ mutated = true;
56
+ existing.members = [...merged];
57
+ }
58
+ if (!mutated)
59
+ return;
60
+ yield* api.setIamPolicy({
61
+ resource: args.resource,
62
+ body: {
63
+ policy: {
64
+ ...current,
65
+ bindings,
66
+ version: 3,
67
+ },
68
+ },
69
+ });
70
+ }).pipe(Effect.retry({
71
+ schedule: Schedule.exponential(Duration.seconds(2)).pipe(Schedule.both(Schedule.recurs(8))),
72
+ }));
73
+ //# sourceMappingURL=IamSync.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IamSync.js","sourceRoot":"","sources":["../../src/Run/IamSync.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,167 @@
1
+ import * as run from "@distilled.cloud/gcp/run-v2";
2
+ import { Resource } from "alchemy";
3
+ import * as Provider from "alchemy/Provider";
4
+ import type * as GCP from "../Providers.ts";
5
+ import { type RunIamBindingContract } from "./IamSync.ts";
6
+ /**
7
+ * A Cloud Run v2 Job — a managed batch workload. A Job holds an
8
+ * `ExecutionTemplate` (parallelism + a `TaskTemplate` containing
9
+ * containers); invoking the Job via `runProjectsLocationsJobs` creates
10
+ * an immutable Execution that runs the configured number of tasks to
11
+ * completion.
12
+ *
13
+ * **Declarative vs imperative.** This resource models the Job
14
+ * *definition* — its template, parallelism, task count, IAM. The act
15
+ * of *running* the Job is event-shaped (does not fit the
16
+ * converge-to-desired-state model) and is left to user-side code:
17
+ *
18
+ * ```typescript
19
+ * import * as run from "@distilled.cloud/gcp/run-v2";
20
+ * const job = yield* GCP.Job("Nightly", { ... });
21
+ * const runJob = yield* run.runProjectsLocationsJobs;
22
+ * yield* runJob({
23
+ * name: `projects/${job.project}/locations/${job.location}/jobs/${job.name}`,
24
+ * });
25
+ * // returns an LRO; poll if you want to wait for completion
26
+ * ```
27
+ *
28
+ * **Lifecycle.** observe → ensure (create, retrying the API-enable
29
+ * race) → sync (patch full body if anything changed; Job's patch has
30
+ * no `updateMask` — server diffs the body itself) → sync IAM bindings
31
+ * → return.
32
+ *
33
+ * **Replace triggers.** Only identity fields (project, location, name)
34
+ * force replacement. Everything else is in-place via `patch`. New
35
+ * Executions are created independently by `runProjectsLocationsJobs`.
36
+ *
37
+ * **Optimistic concurrency.** Delete passes through the observed
38
+ * `etag`; patch passes etag in the body so concurrent edits surface
39
+ * as `Conflict` instead of silently overwriting.
40
+ *
41
+ * **Adoption.** Label-gated, same as {@link import("./Service.ts").Service}.
42
+ *
43
+ * **IAM.** Use {@link import("./IamMember.ts").jobIamMember} to bind
44
+ * `(role, member)` grants — typically `roles/run.invoker` on the
45
+ * service account that triggers the job.
46
+ *
47
+ * @section Creating a Cloud Run Job
48
+ * @example Single-shot batch job
49
+ * ```typescript
50
+ * const job = yield* GCP.Job("ProcessBatch", {
51
+ * project: project.projectId,
52
+ * location: "europe-west4",
53
+ * template: {
54
+ * taskCount: 1,
55
+ * template: {
56
+ * maxRetries: 3,
57
+ * containers: [{
58
+ * image: "europe-west4-docker.pkg.dev/proj/repo/worker:latest",
59
+ * }],
60
+ * },
61
+ * },
62
+ * });
63
+ * ```
64
+ *
65
+ * @example Parallel job with custom service account and GPU
66
+ * ```typescript
67
+ * const train = yield* GCP.Job("Train", {
68
+ * project: project.projectId,
69
+ * location: "europe-west4",
70
+ * launchStage: "BETA",
71
+ * template: {
72
+ * taskCount: 8,
73
+ * parallelism: 8,
74
+ * template: {
75
+ * serviceAccount: sa.email,
76
+ * maxRetries: 0,
77
+ * timeout: "21600s",
78
+ * containers: [{
79
+ * image: "europe-west4-docker.pkg.dev/proj/repo/train:v2",
80
+ * resources: { limits: { cpu: "4", memory: "16Gi", "nvidia.com/gpu": "1" } },
81
+ * }],
82
+ * nodeSelector: { accelerator: "nvidia-l4" },
83
+ * },
84
+ * },
85
+ * });
86
+ * ```
87
+ */
88
+ export type JobProps = {
89
+ /** GCP project ID hosting the Job. Immutable — replace if changed. */
90
+ project: string;
91
+ /** Cloud Run region. Immutable — replace if changed. */
92
+ location: string;
93
+ /**
94
+ * Job name. Defaults to `createPhysicalName({ id, lowercase: true,
95
+ * maxLength: 49 })`. Lowercase letters/digits/hyphens; must begin
96
+ * with a letter and not end with a hyphen; **fewer than 50 characters**.
97
+ * Immutable — replace if changed.
98
+ */
99
+ name?: string;
100
+ /** User-visible description. Mutable via `patch`. */
101
+ description?: string;
102
+ /**
103
+ * Resource labels. Alchemy internal labels are merged on top
104
+ * automatically. Cloud Run rejects reserved namespaces (same as
105
+ * Service). Mutable via `patch`.
106
+ */
107
+ labels?: Record<string, string>;
108
+ /** Free-form annotations. Mutable via `patch`. */
109
+ annotations?: Record<string, string>;
110
+ /**
111
+ * Launch stage — `BETA` (or higher) required for preview features
112
+ * (GPU node selectors, Direct VPC). Mutable via `patch`.
113
+ */
114
+ launchStage?: "ALPHA" | "BETA" | "GA" | "EARLY_ACCESS" | "PRELAUNCH" | "DEPRECATED";
115
+ /** Binary Authorization policy. Mutable via `patch`. */
116
+ binaryAuthorization?: run.GoogleCloudRunV2BinaryAuthorization;
117
+ /**
118
+ * Token-suffix used to compose Execution names when the Job is
119
+ * started via the GCP UI or `gcloud run jobs execute`. Required to
120
+ * keep distinct from Job name + 63 chars. Mutable.
121
+ */
122
+ startExecutionToken?: string;
123
+ /** Same as `startExecutionToken` but used on run completion. Mutable. */
124
+ runExecutionToken?: string;
125
+ /**
126
+ * The Execution template — describes parallelism, task count, and
127
+ * the inner `TaskTemplate` (containers, volumes, retry policy).
128
+ * **Required at create.** Mutable via `patch`.
129
+ */
130
+ template: run.GoogleCloudRunV2ExecutionTemplate;
131
+ };
132
+ export type JobAttributes = {
133
+ /** Job name (bare). */
134
+ name: string;
135
+ /** Server-assigned UID. */
136
+ uid: string;
137
+ /** Fully-qualified resource name. */
138
+ resourceName: string;
139
+ /** GCP project ID. */
140
+ project: string;
141
+ /** Region. */
142
+ location: string;
143
+ /** Monotonically increasing generation, bumped on every patch. */
144
+ generation: string | undefined;
145
+ /** Generation reflected in the latest reconciled state. */
146
+ observedGeneration: string | undefined;
147
+ /** Overall readiness condition. */
148
+ terminalCondition: run.GoogleCloudRunV2Condition | undefined;
149
+ /** Number of Executions created for this Job. */
150
+ executionCount: number | undefined;
151
+ /** Reference to the most recently created Execution, if any. */
152
+ latestCreatedExecution: run.GoogleCloudRunV2ExecutionReference | undefined;
153
+ /** True while Cloud Run is reconciling toward the desired state. */
154
+ reconciling: boolean | undefined;
155
+ /** Optimistic-concurrency etag. */
156
+ etag: string | undefined;
157
+ /** Labels currently set, including internals. */
158
+ labels: Record<string, string>;
159
+ /** Creation time. */
160
+ createTime: string | undefined;
161
+ /** Last-modified time. */
162
+ updateTime: string | undefined;
163
+ };
164
+ export type Job = Resource<"GCP.Job", JobProps, JobAttributes, RunIamBindingContract, GCP.Providers>;
165
+ export declare const Job: import("alchemy").ResourceClass<Job>;
166
+ export declare const JobProvider: () => import("effect/Layer").Layer<Provider.Provider<Job>, never, import("effect/unstable/http/HttpClient").HttpClient | import("alchemy").Stage | import("alchemy").Stack | import("@distilled.cloud/gcp").Credentials>;
167
+ //# sourceMappingURL=Job.d.ts.map