@microagi/alchemy-gcp 0.11.4 → 0.11.6

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 (147) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +11 -0
  3. package/lib/ArtifactRegistry/Operations.d.ts +2 -1
  4. package/lib/ArtifactRegistry/Operations.d.ts.map +1 -1
  5. package/lib/ArtifactRegistry/Operations.js +9 -2
  6. package/lib/ArtifactRegistry/Operations.js.map +1 -1
  7. package/lib/ArtifactRegistry/Repository.d.ts.map +1 -1
  8. package/lib/ArtifactRegistry/Repository.js +6 -1
  9. package/lib/ArtifactRegistry/Repository.js.map +1 -1
  10. package/lib/ArtifactRegistry/RepositoryIamMember.d.ts.map +1 -1
  11. package/lib/ArtifactRegistry/RepositoryIamMember.js +6 -1
  12. package/lib/ArtifactRegistry/RepositoryIamMember.js.map +1 -1
  13. package/lib/CloudResourceManager/Project.d.ts.map +1 -1
  14. package/lib/CloudResourceManager/Project.js +10 -2
  15. package/lib/CloudResourceManager/Project.js.map +1 -1
  16. package/lib/Compute/Address.d.ts.map +1 -1
  17. package/lib/Compute/Address.js +5 -2
  18. package/lib/Compute/Address.js.map +1 -1
  19. package/lib/Compute/Firewall.d.ts.map +1 -1
  20. package/lib/Compute/Firewall.js +5 -2
  21. package/lib/Compute/Firewall.js.map +1 -1
  22. package/lib/Compute/ForwardingRule.d.ts.map +1 -1
  23. package/lib/Compute/ForwardingRule.js +5 -2
  24. package/lib/Compute/ForwardingRule.js.map +1 -1
  25. package/lib/Compute/GlobalAddress.d.ts.map +1 -1
  26. package/lib/Compute/GlobalAddress.js +5 -2
  27. package/lib/Compute/GlobalAddress.js.map +1 -1
  28. package/lib/Compute/Network.d.ts.map +1 -1
  29. package/lib/Compute/Network.js +5 -2
  30. package/lib/Compute/Network.js.map +1 -1
  31. package/lib/Compute/Operations.d.ts.map +1 -1
  32. package/lib/Compute/Operations.js +10 -5
  33. package/lib/Compute/Operations.js.map +1 -1
  34. package/lib/Compute/ProjectDefaultNetworkTier.d.ts.map +1 -1
  35. package/lib/Compute/ProjectDefaultNetworkTier.js +2 -0
  36. package/lib/Compute/ProjectDefaultNetworkTier.js.map +1 -1
  37. package/lib/Compute/SharedVpcHost.d.ts.map +1 -1
  38. package/lib/Compute/SharedVpcHost.js +7 -2
  39. package/lib/Compute/SharedVpcHost.js.map +1 -1
  40. package/lib/Compute/SharedVpcServiceProject.d.ts.map +1 -1
  41. package/lib/Compute/SharedVpcServiceProject.js +5 -2
  42. package/lib/Compute/SharedVpcServiceProject.js.map +1 -1
  43. package/lib/Compute/Subnetwork.d.ts.map +1 -1
  44. package/lib/Compute/Subnetwork.js +9 -3
  45. package/lib/Compute/Subnetwork.js.map +1 -1
  46. package/lib/Container/Cluster.d.ts.map +1 -1
  47. package/lib/Container/Cluster.js +6 -2
  48. package/lib/Container/Cluster.js.map +1 -1
  49. package/lib/Container/NodePool.d.ts +13 -3
  50. package/lib/Container/NodePool.d.ts.map +1 -1
  51. package/lib/Container/NodePool.js +11 -6
  52. package/lib/Container/NodePool.js.map +1 -1
  53. package/lib/Container/Operations.d.ts +2 -2
  54. package/lib/Container/Operations.d.ts.map +1 -1
  55. package/lib/Container/Operations.js +9 -3
  56. package/lib/Container/Operations.js.map +1 -1
  57. package/lib/Iam/ServiceAccount.d.ts.map +1 -1
  58. package/lib/Iam/ServiceAccount.js +6 -1
  59. package/lib/Iam/ServiceAccount.js.map +1 -1
  60. package/lib/Iam/ServiceAccountKey.d.ts.map +1 -1
  61. package/lib/Iam/ServiceAccountKey.js +2 -0
  62. package/lib/Iam/ServiceAccountKey.js.map +1 -1
  63. package/lib/Kubernetes/Helm.d.ts.map +1 -1
  64. package/lib/Kubernetes/Helm.js +2 -0
  65. package/lib/Kubernetes/Helm.js.map +1 -1
  66. package/lib/Kubernetes/KubernetesManifest.d.ts.map +1 -1
  67. package/lib/Kubernetes/KubernetesManifest.js +2 -0
  68. package/lib/Kubernetes/KubernetesManifest.js.map +1 -1
  69. package/lib/Kubernetes/Secret.d.ts.map +1 -1
  70. package/lib/Kubernetes/Secret.js +2 -0
  71. package/lib/Kubernetes/Secret.js.map +1 -1
  72. package/lib/ManagedLustre/Instance.d.ts.map +1 -1
  73. package/lib/ManagedLustre/Instance.js +11 -4
  74. package/lib/ManagedLustre/Instance.js.map +1 -1
  75. package/lib/Run/IamSync.d.ts.map +1 -1
  76. package/lib/Run/IamSync.js +4 -1
  77. package/lib/Run/IamSync.js.map +1 -1
  78. package/lib/Run/Job.d.ts.map +1 -1
  79. package/lib/Run/Job.js +6 -1
  80. package/lib/Run/Job.js.map +1 -1
  81. package/lib/Run/Operations.d.ts +2 -2
  82. package/lib/Run/Operations.d.ts.map +1 -1
  83. package/lib/Run/Operations.js +9 -3
  84. package/lib/Run/Operations.js.map +1 -1
  85. package/lib/Run/Service.d.ts.map +1 -1
  86. package/lib/Run/Service.js +6 -1
  87. package/lib/Run/Service.js.map +1 -1
  88. package/lib/ServiceNetworking/PsaConnection.d.ts.map +1 -1
  89. package/lib/ServiceNetworking/PsaConnection.js +11 -4
  90. package/lib/ServiceNetworking/PsaConnection.js.map +1 -1
  91. package/lib/ServiceUsage/ApiEnable.d.ts.map +1 -1
  92. package/lib/ServiceUsage/ApiEnable.js +9 -1
  93. package/lib/ServiceUsage/ApiEnable.js.map +1 -1
  94. package/lib/Sqladmin/Database.d.ts.map +1 -1
  95. package/lib/Sqladmin/Database.js +2 -0
  96. package/lib/Sqladmin/Database.js.map +1 -1
  97. package/lib/Sqladmin/Instance.d.ts.map +1 -1
  98. package/lib/Sqladmin/Instance.js +6 -1
  99. package/lib/Sqladmin/Instance.js.map +1 -1
  100. package/lib/Sqladmin/Operations.d.ts.map +1 -1
  101. package/lib/Sqladmin/Operations.js +7 -1
  102. package/lib/Sqladmin/Operations.js.map +1 -1
  103. package/lib/Sqladmin/User.d.ts.map +1 -1
  104. package/lib/Sqladmin/User.js +2 -0
  105. package/lib/Sqladmin/User.js.map +1 -1
  106. package/lib/Storage/Bucket.d.ts.map +1 -1
  107. package/lib/Storage/Bucket.js +6 -1
  108. package/lib/Storage/Bucket.js.map +1 -1
  109. package/lib/Storage/StorageBucketIamMember.d.ts.map +1 -1
  110. package/lib/Storage/StorageBucketIamMember.js +6 -1
  111. package/lib/Storage/StorageBucketIamMember.js.map +1 -1
  112. package/package.json +10 -10
  113. package/src/ArtifactRegistry/Operations.ts +10 -5
  114. package/src/ArtifactRegistry/Repository.ts +7 -3
  115. package/src/ArtifactRegistry/RepositoryIamMember.ts +6 -3
  116. package/src/CloudResourceManager/Project.ts +11 -6
  117. package/src/Compute/Address.ts +5 -1
  118. package/src/Compute/Firewall.ts +5 -1
  119. package/src/Compute/ForwardingRule.ts +5 -1
  120. package/src/Compute/GlobalAddress.ts +5 -1
  121. package/src/Compute/Network.ts +5 -1
  122. package/src/Compute/Operations.ts +8 -4
  123. package/src/Compute/ProjectDefaultNetworkTier.ts +2 -0
  124. package/src/Compute/SharedVpcHost.ts +7 -1
  125. package/src/Compute/SharedVpcServiceProject.ts +5 -1
  126. package/src/Compute/Subnetwork.ts +9 -4
  127. package/src/Container/Cluster.ts +7 -3
  128. package/src/Container/NodePool.ts +31 -8
  129. package/src/Container/Operations.ts +10 -6
  130. package/src/Iam/ServiceAccount.ts +6 -3
  131. package/src/Iam/ServiceAccountKey.ts +2 -0
  132. package/src/Kubernetes/Helm.ts +2 -0
  133. package/src/Kubernetes/KubernetesManifest.ts +2 -0
  134. package/src/Kubernetes/Secret.ts +2 -0
  135. package/src/ManagedLustre/Instance.ts +10 -4
  136. package/src/Run/IamSync.ts +4 -3
  137. package/src/Run/Job.ts +7 -3
  138. package/src/Run/Operations.ts +10 -6
  139. package/src/Run/Service.ts +7 -3
  140. package/src/ServiceNetworking/PsaConnection.ts +10 -4
  141. package/src/ServiceUsage/ApiEnable.ts +10 -4
  142. package/src/Sqladmin/Database.ts +2 -0
  143. package/src/Sqladmin/Instance.ts +7 -3
  144. package/src/Sqladmin/Operations.ts +8 -4
  145. package/src/Sqladmin/User.ts +2 -0
  146. package/src/Storage/Bucket.ts +6 -3
  147. package/src/Storage/StorageBucketIamMember.ts +6 -3
@@ -34,11 +34,19 @@ export type NodePoolProps = {
34
34
  * without a positive node count, so when omitted we default to
35
35
  * `autoscaling.minNodeCount ?? 1` on the create path.
36
36
  *
37
- * Subsequent size changes go via `setSize` when `autoscaling` is
38
- * disabled, or are GKE-managed when `autoscaling.enabled` is true
39
- * (the size sync is skipped to avoid fighting the autoscaler).
37
+ * Subsequent size changes go via `setSize` unless autoscaling or
38
+ * `externallyManagedSize` delegates steady-state size ownership.
40
39
  */
41
40
  initialNodeCount?: number;
41
+ /**
42
+ * Treat the pool's live size as externally owned after creation.
43
+ *
44
+ * `initialNodeCount` is still used to bootstrap a missing pool, but
45
+ * subsequent reconciles do not call GKE `setSize`. This is intended for
46
+ * imperative controllers that resize a pool while Alchemy continues to own
47
+ * its machine shape, labels, taints, management, and upgrade settings.
48
+ */
49
+ externallyManagedSize?: boolean;
42
50
  /**
43
51
  * Per-pool zones. Override the cluster default. Mutable via
44
52
  * `update` — sent in a standalone update body to avoid the SDK's
@@ -235,6 +243,19 @@ export type NodePool = Resource<
235
243
  >;
236
244
  export const NodePool = Resource<NodePool>("GCP.NodePool");
237
245
 
246
+ /** Whether a reconcile should bring a node pool back to its declared size. */
247
+ export const nodePoolSizeNeedsSync = (
248
+ observedCount: number | undefined,
249
+ news: Pick<
250
+ NodePoolProps,
251
+ "initialNodeCount" | "autoscaling" | "externallyManagedSize"
252
+ >,
253
+ ): boolean =>
254
+ news.externallyManagedSize !== true &&
255
+ news.autoscaling?.enabled !== true &&
256
+ news.initialNodeCount !== undefined &&
257
+ observedCount !== news.initialNodeCount;
258
+
238
259
  /**
239
260
  * Translate a public `accelerators` spec into the SDK's
240
261
  * `AcceleratorConfig`. Same shape on both create and update paths.
@@ -449,14 +470,14 @@ export const NodePoolProvider = () =>
449
470
  news: NodePoolProps;
450
471
  session: ScopedPlanStatusSession;
451
472
  }) {
452
- // Skip when the autoscaler is enabled; fighting it creates churn.
453
- if (args.news.autoscaling?.enabled) return;
454
- if (args.news.initialNodeCount === undefined) return;
455
473
  const observedCount = args.observed.initialNodeCount;
456
- if (observedCount === args.news.initialNodeCount) return;
474
+ // Skip when either GKE autoscaling or an external controller owns
475
+ // size; fighting either creates churn. Externally-managed pools still
476
+ // use initialNodeCount on the create path above.
477
+ if (!nodePoolSizeNeedsSync(observedCount, args.news)) return;
457
478
  const op = yield* setSize({
458
479
  name: args.fqName,
459
- body: { nodeCount: args.news.initialNodeCount },
480
+ body: { nodeCount: args.news.initialNodeCount! },
460
481
  });
461
482
  if (op.name) yield* awaitOperation(qualifyOp(args.fqName, op.name), args.session);
462
483
  });
@@ -525,6 +546,8 @@ export const NodePoolProvider = () =>
525
546
  });
526
547
 
527
548
  return {
549
+ nuke: { skip: true },
550
+ list: () => Effect.succeed([]),
528
551
  stables: ["name", "selfLink", "project", "location", "clusterName"],
529
552
  diff: Effect.fn(function* ({ news, olds = {} }) {
530
553
  if (!isResolved(news)) return undefined;
@@ -46,8 +46,8 @@ type GetOperations = Effect.Success<typeof cont.getProjectsLocationsOperations>;
46
46
  * The doneness predicate compares `.status === "DONE"` accordingly.
47
47
  *
48
48
  * Schedule: exponential 2s → 1.5× growth, capped per-poll at 15s via
49
- * `Schedule.either`, then a hard count cap of 80 retries via
50
- * `Schedule.both(Schedule.recurs(80))` → ~20 min wall ceiling, well
49
+ * `Schedule.min`, then a hard count cap of 80 retries via
50
+ * `Schedule.max` → ~20 min wall ceiling, well
51
51
  * above the worst observed GKE op time (cluster create runs 5–10 min).
52
52
  *
53
53
  * The helper is a factory — the caller resolves
@@ -68,10 +68,14 @@ export const makeAwaitOperation = (getOperations: GetOperations) =>
68
68
  ),
69
69
  Effect.retry({
70
70
  while: (e: { _tag?: string }) => e?._tag === "OperationPending",
71
- schedule: Schedule.exponential(Duration.seconds(2), 1.5).pipe(
72
- Schedule.either(Schedule.spaced(Duration.seconds(15))),
73
- Schedule.both(Schedule.recurs(80)),
74
- Schedule.tapOutput(() =>
71
+ schedule: Schedule.max([
72
+ Schedule.min([
73
+ Schedule.exponential(Duration.seconds(2), 1.5),
74
+ Schedule.spaced(Duration.seconds(15)),
75
+ ]),
76
+ Schedule.recurs(80),
77
+ ]).pipe(
78
+ Schedule.tap(() =>
75
79
  session.note(`Waiting for GKE operation ${operationName}…`),
76
80
  ),
77
81
  ),
@@ -330,9 +330,10 @@ export const ServiceAccountProvider = () =>
330
330
  });
331
331
  }).pipe(
332
332
  Effect.retry({
333
- schedule: Schedule.exponential(Duration.seconds(2)).pipe(
334
- Schedule.both(Schedule.recurs(8)),
335
- ),
333
+ schedule: Schedule.max([
334
+ Schedule.exponential(Duration.seconds(2)),
335
+ Schedule.recurs(8),
336
+ ]),
336
337
  }),
337
338
  );
338
339
 
@@ -340,6 +341,8 @@ export const ServiceAccountProvider = () =>
340
341
  // Stable identity: accountId + project; both immutable; either
341
342
  // change → replace. The server-assigned uniqueId is also stable
342
343
  // for the lifetime of the GSA (re-create gets a different one).
344
+ nuke: { skip: true },
345
+ list: () => Effect.succeed([]),
343
346
  stables: ["accountId", "projectId", "uniqueId", "name", "email"],
344
347
  diff: Effect.fn(function* ({ id, news, olds = {}, output }) {
345
348
  if (!isResolved(news)) return undefined;
@@ -153,6 +153,8 @@ export const ServiceAccountKeyProvider = () =>
153
153
  // privateKeyData is stable: it's set on create and never
154
154
  // changes. The engine preserves it across reconciles and
155
155
  // does not use it for drift detection.
156
+ nuke: { skip: true },
157
+ list: () => Effect.succeed([]),
156
158
  stables: ["name", "privateKeyData", "keyAlgorithm", "keyType"],
157
159
  diff: Effect.fn(function* ({ news, olds = {} }) {
158
160
  if (!isResolved(news)) return undefined;
@@ -329,6 +329,8 @@ export const HelmReleaseProvider = () =>
329
329
  // Identity (name/namespace/chart) and endpoint are unchanged by an
330
330
  // in-place upgrade. revision increments on every upgrade — it is NOT
331
331
  // stable (an update must persist the new revision).
332
+ nuke: { skip: true },
333
+ list: () => Effect.succeed([]),
332
334
  stables: ["name", "namespace", "chart", "endpoint"],
333
335
  diff: Effect.fn(function* ({ news, olds = {} }) {
334
336
  if (!isResolved(news)) return undefined;
@@ -179,6 +179,8 @@ export const KubernetesManifestProvider = () =>
179
179
  // Identity (apiVersion/kind/name/namespace) and the server-assigned uid
180
180
  // are unchanged by an in-place apply. endpoint is a replace trigger so
181
181
  // it never changes on update; caCertificate is excluded (can rotate).
182
+ nuke: { skip: true },
183
+ list: () => Effect.succeed([]),
182
184
  stables: [
183
185
  "apiVersion",
184
186
  "kind",
@@ -195,6 +195,8 @@ export const KubernetesSecretProvider = () =>
195
195
  // uid, and endpoint — which is a replace trigger, so it never changes
196
196
  // on update. caCertificate is intentionally excluded: it can rotate on
197
197
  // the same cluster.
198
+ nuke: { skip: true },
199
+ list: () => Effect.succeed([]),
198
200
  stables: ["name", "namespace", "endpoint", "uid"],
199
201
  diff: Effect.fn(function* ({ id, news, olds = {} }) {
200
202
  if (!isResolved(news)) return undefined;
@@ -247,12 +247,16 @@ export const ManagedLustreInstanceProvider = () =>
247
247
  ),
248
248
  Effect.retry({
249
249
  while: (e: { _tag?: string }) => e?._tag === "OperationPending",
250
- schedule: Schedule.exponential(Duration.seconds(5), 1.5).pipe(
251
- Schedule.either(Schedule.spaced(Duration.seconds(30))),
250
+ schedule: Schedule.max([
251
+ Schedule.min([
252
+ Schedule.exponential(Duration.seconds(5), 1.5),
253
+ Schedule.spaced(Duration.seconds(30)),
254
+ ]),
252
255
  // Lustre creates run 10–25 min for multi-TiB instances.
253
256
  // 200 × 30s ≈ 100 min wall ceiling.
254
- Schedule.both(Schedule.recurs(200)),
255
- Schedule.tapOutput(() =>
257
+ Schedule.recurs(200),
258
+ ]).pipe(
259
+ Schedule.tap(() =>
256
260
  session.note(`Waiting for Managed Lustre operation ${operationName}…`),
257
261
  ),
258
262
  ),
@@ -310,6 +314,8 @@ export const ManagedLustreInstanceProvider = () =>
310
314
  });
311
315
 
312
316
  return {
317
+ nuke: { skip: true },
318
+ list: () => Effect.succeed([]),
313
319
  stables: ["instanceId", "project", "location", "name"],
314
320
  diff: Effect.fn(function* ({ news, olds = {} }) {
315
321
  if (!isResolved(news)) return undefined;
@@ -127,8 +127,9 @@ export const makeSyncIam =
127
127
  });
128
128
  }).pipe(
129
129
  Effect.retry({
130
- schedule: Schedule.exponential(Duration.seconds(2)).pipe(
131
- Schedule.both(Schedule.recurs(8)),
132
- ),
130
+ schedule: Schedule.max([
131
+ Schedule.exponential(Duration.seconds(2)),
132
+ Schedule.recurs(8),
133
+ ]),
133
134
  }),
134
135
  );
package/src/Run/Job.ts CHANGED
@@ -336,6 +336,8 @@ export const JobProvider = () =>
336
336
  });
337
337
 
338
338
  return {
339
+ nuke: { skip: true },
340
+ list: () => Effect.succeed([]),
339
341
  stables: ["name", "uid", "resourceName", "project", "location"],
340
342
  diff: Effect.fn(function* ({ news, olds = {} }) {
341
343
  if (!isResolved(news)) return undefined;
@@ -384,9 +386,11 @@ export const JobProvider = () =>
384
386
  /enabled this API recently|has not been used/i.test(
385
387
  e.message ?? "",
386
388
  ),
387
- schedule: Schedule.spaced(Duration.seconds(15)).pipe(
388
- Schedule.both(Schedule.recurs(20)),
389
- Schedule.tapOutput(() =>
389
+ schedule: Schedule.max([
390
+ Schedule.spaced(Duration.seconds(15)),
391
+ Schedule.recurs(20),
392
+ ]).pipe(
393
+ Schedule.tap(() =>
390
394
  session.note(
391
395
  "Waiting for Cloud Run API enablement to propagate…",
392
396
  ),
@@ -33,8 +33,8 @@ type GetOperations = Effect.Success<typeof run.getProjectsLocationsOperations>;
33
33
  * dance needed (unlike Container, which returns bare ids).
34
34
  *
35
35
  * Schedule: exponential 1s → 1.5× growth, capped per-poll at 10s via
36
- * `Schedule.either`, then a hard count cap of 60 retries via
37
- * `Schedule.both(Schedule.recurs(60))` → ~10 min wall ceiling. Cloud
36
+ * `Schedule.min`, then a hard count cap of 60 retries via `Schedule.max`
37
+ * → ~10 min wall ceiling. Cloud
38
38
  * Run create/patch typically completes in seconds to a couple minutes,
39
39
  * with the long pole being container image pulls.
40
40
  *
@@ -57,10 +57,14 @@ export const makeAwaitOperation = (getOperations: GetOperations) =>
57
57
  ),
58
58
  Effect.retry({
59
59
  while: (e: { _tag?: string }) => e?._tag === "OperationPending",
60
- schedule: Schedule.exponential(Duration.seconds(1), 1.5).pipe(
61
- Schedule.either(Schedule.spaced(Duration.seconds(10))),
62
- Schedule.both(Schedule.recurs(60)),
63
- Schedule.tapOutput(() =>
60
+ schedule: Schedule.max([
61
+ Schedule.min([
62
+ Schedule.exponential(Duration.seconds(1), 1.5),
63
+ Schedule.spaced(Duration.seconds(10)),
64
+ ]),
65
+ Schedule.recurs(60),
66
+ ]).pipe(
67
+ Schedule.tap(() =>
64
68
  session.note(`Waiting for Cloud Run operation ${operationName}…`),
65
69
  ),
66
70
  ),
@@ -399,6 +399,8 @@ export const ServiceProvider = () =>
399
399
  });
400
400
 
401
401
  return {
402
+ nuke: { skip: true },
403
+ list: () => Effect.succeed([]),
402
404
  stables: ["name", "uid", "resourceName", "project", "location"],
403
405
  diff: Effect.fn(function* ({ news, olds = {} }) {
404
406
  if (!isResolved(news)) return undefined;
@@ -458,9 +460,11 @@ export const ServiceProvider = () =>
458
460
  /enabled this API recently|has not been used/i.test(
459
461
  e.message ?? "",
460
462
  ),
461
- schedule: Schedule.spaced(Duration.seconds(15)).pipe(
462
- Schedule.both(Schedule.recurs(20)),
463
- Schedule.tapOutput(() =>
463
+ schedule: Schedule.max([
464
+ Schedule.spaced(Duration.seconds(15)),
465
+ Schedule.recurs(20),
466
+ ]).pipe(
467
+ Schedule.tap(() =>
464
468
  session.note(
465
469
  "Waiting for Cloud Run API enablement to propagate…",
466
470
  ),
@@ -124,12 +124,16 @@ export const PsaConnectionProvider = () =>
124
124
  ),
125
125
  Effect.retry({
126
126
  while: (e: { _tag?: string }) => e?._tag === "OperationPending",
127
- schedule: Schedule.exponential(Duration.seconds(2), 1.5).pipe(
128
- Schedule.either(Schedule.spaced(Duration.seconds(15))),
127
+ schedule: Schedule.max([
128
+ Schedule.min([
129
+ Schedule.exponential(Duration.seconds(2), 1.5),
130
+ Schedule.spaced(Duration.seconds(15)),
131
+ ]),
129
132
  // PSA peering creates can take 5–10 min while the
130
133
  // service producer provisions its side. 80 × 15s ≈ 20 min.
131
- Schedule.both(Schedule.recurs(80)),
132
- Schedule.tapOutput(() =>
134
+ Schedule.recurs(80),
135
+ ]).pipe(
136
+ Schedule.tap(() =>
133
137
  session.note(`Waiting for ServiceNetworking operation ${operationName}…`),
134
138
  ),
135
139
  ),
@@ -162,6 +166,8 @@ export const PsaConnectionProvider = () =>
162
166
  );
163
167
 
164
168
  return {
169
+ nuke: { skip: true },
170
+ list: () => Effect.succeed([]),
165
171
  stables: ["network", "serviceName", "peering"],
166
172
  diff: Effect.fn(function* ({ news, olds = {} }) {
167
173
  if (!isResolved(news)) return undefined;
@@ -121,10 +121,14 @@ export const ApiEnableProvider = () =>
121
121
  ),
122
122
  Effect.retry({
123
123
  while: (e: { _tag?: string }) => e?._tag === "OperationPending",
124
- schedule: Schedule.exponential(Duration.seconds(1), 1.5).pipe(
125
- Schedule.either(Schedule.spaced(Duration.seconds(15))),
126
- Schedule.both(Schedule.recurs(60)),
127
- Schedule.tapOutput(() =>
124
+ schedule: Schedule.max([
125
+ Schedule.min([
126
+ Schedule.exponential(Duration.seconds(1), 1.5),
127
+ Schedule.spaced(Duration.seconds(15)),
128
+ ]),
129
+ Schedule.recurs(60),
130
+ ]).pipe(
131
+ Schedule.tap(() =>
128
132
  session.note(`Waiting for ServiceUsage operation ${operationName}…`),
129
133
  ),
130
134
  ),
@@ -143,6 +147,8 @@ export const ApiEnableProvider = () =>
143
147
  return {
144
148
  // No physical-name generation: identity is `(project, service)`.
145
149
  // Both are static strings on `news`/`olds`/`output`.
150
+ nuke: { skip: true },
151
+ list: () => Effect.succeed([]),
146
152
  stables: ["project", "service", "name"],
147
153
  diff: Effect.fn(function* ({ news, olds = {} }) {
148
154
  if (!isResolved(news)) return undefined;
@@ -126,6 +126,8 @@ export const SqlDatabaseProvider = () =>
126
126
  );
127
127
 
128
128
  return {
129
+ nuke: { skip: true },
130
+ list: () => Effect.succeed([]),
129
131
  stables: ["name", "instance", "project", "selfLink"],
130
132
  diff: Effect.fn(function* ({ news, olds = {} }) {
131
133
  if (!isResolved(news)) return undefined;
@@ -485,6 +485,8 @@ export const SqlInstanceProvider = () =>
485
485
  });
486
486
 
487
487
  return {
488
+ nuke: { skip: true },
489
+ list: () => Effect.succeed([]),
488
490
  stables: ["name", "selfLink", "project", "region", "connectionName"],
489
491
  diff: Effect.fn(function* ({ news, olds = {} }) {
490
492
  if (!isResolved(news)) return undefined;
@@ -539,9 +541,11 @@ export const SqlInstanceProvider = () =>
539
541
  /enabled this API recently|has not been used/i.test(
540
542
  e.message ?? "",
541
543
  ),
542
- schedule: Schedule.spaced(Duration.seconds(15)).pipe(
543
- Schedule.both(Schedule.recurs(20)),
544
- Schedule.tapOutput(() =>
544
+ schedule: Schedule.max([
545
+ Schedule.spaced(Duration.seconds(15)),
546
+ Schedule.recurs(20),
547
+ ]).pipe(
548
+ Schedule.tap(() =>
545
549
  session.note(
546
550
  "Waiting for Cloud SQL Admin API enablement to propagate…",
547
551
  ),
@@ -52,10 +52,14 @@ export const makeAwaitOperation = (getOperations: GetOperations) =>
52
52
  ),
53
53
  Effect.retry({
54
54
  while: (e: { _tag?: string }) => e?._tag === "OperationPending",
55
- schedule: Schedule.exponential(Duration.seconds(1), 1.5).pipe(
56
- Schedule.either(Schedule.spaced(Duration.seconds(10))),
57
- Schedule.both(Schedule.recurs(60)),
58
- Schedule.tapOutput(() =>
55
+ schedule: Schedule.max([
56
+ Schedule.min([
57
+ Schedule.exponential(Duration.seconds(1), 1.5),
58
+ Schedule.spaced(Duration.seconds(10)),
59
+ ]),
60
+ Schedule.recurs(60),
61
+ ]).pipe(
62
+ Schedule.tap(() =>
59
63
  session.note(`Waiting for Cloud SQL operation ${operationName}…`),
60
64
  ),
61
65
  ),
@@ -183,6 +183,8 @@ export const SqlUserProvider = () =>
183
183
  );
184
184
 
185
185
  return {
186
+ nuke: { skip: true },
187
+ list: () => Effect.succeed([]),
186
188
  stables: ["name", "instance", "project", "host", "type"],
187
189
  diff: Effect.fn(function* ({ news, olds = {} }) {
188
190
  if (!isResolved(news)) return undefined;
@@ -303,13 +303,16 @@ export const StorageBucketProvider = () =>
303
303
  });
304
304
  }).pipe(
305
305
  Effect.retry({
306
- schedule: Schedule.exponential(Duration.seconds(2)).pipe(
307
- Schedule.both(Schedule.recurs(8)),
308
- ),
306
+ schedule: Schedule.max([
307
+ Schedule.exponential(Duration.seconds(2)),
308
+ Schedule.recurs(8),
309
+ ]),
309
310
  }),
310
311
  );
311
312
 
312
313
  return {
314
+ nuke: { skip: true },
315
+ list: () => Effect.succeed([]),
313
316
  stables: ["name", "id", "selfLink", "location", "timeCreated"],
314
317
  diff: Effect.fn(function* ({ news, olds = {}, output }) {
315
318
  if (!isResolved(news)) return undefined;
@@ -150,9 +150,10 @@ export const StorageBucketIamMemberProvider = () =>
150
150
  eff.pipe(
151
151
  Effect.retry({
152
152
  while: (e) => (e as { _tag?: string })?._tag !== "ConfigError",
153
- schedule: Schedule.exponential(Duration.seconds(2)).pipe(
154
- Schedule.both(Schedule.recurs(8)),
155
- ),
153
+ schedule: Schedule.max([
154
+ Schedule.exponential(Duration.seconds(2)),
155
+ Schedule.recurs(8),
156
+ ]),
156
157
  }),
157
158
  );
158
159
 
@@ -185,6 +186,8 @@ export const StorageBucketIamMemberProvider = () =>
185
186
 
186
187
  return {
187
188
  // Identity IS the tuple; all fields are static values.
189
+ nuke: { skip: true },
190
+ list: () => Effect.succeed([]),
188
191
  stables: ["bucket", "role", "member", "condition"],
189
192
  diff: Effect.fn(function* ({ news, olds = {}, output }) {
190
193
  if (!isResolved(news)) return undefined;