@homeflare/alchemy 0.48.1 → 0.50.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 (126) hide show
  1. package/README.md +11 -0
  2. package/dist/cloudflare/cloudflared-tunnel-api.d.ts +87 -0
  3. package/dist/cloudflare/cloudflared-tunnel-api.d.ts.map +1 -0
  4. package/dist/cloudflare/cloudflared-tunnel-form.d.ts +49 -0
  5. package/dist/cloudflare/cloudflared-tunnel-form.d.ts.map +1 -0
  6. package/dist/cloudflare/cloudflared-tunnel-harness.d.ts +45 -0
  7. package/dist/cloudflare/cloudflared-tunnel-harness.d.ts.map +1 -0
  8. package/dist/cloudflare/cloudflared-tunnel-lifecycle.d.ts +21 -0
  9. package/dist/cloudflare/cloudflared-tunnel-lifecycle.d.ts.map +1 -0
  10. package/dist/cloudflare/cloudflared-tunnel.d.ts +39 -0
  11. package/dist/cloudflare/cloudflared-tunnel.d.ts.map +1 -0
  12. package/dist/cloudflare/fake-access.d.ts +40 -0
  13. package/dist/cloudflare/fake-access.d.ts.map +1 -0
  14. package/dist/cloudflare/fake-mesh.d.ts +7 -2
  15. package/dist/cloudflare/fake-mesh.d.ts.map +1 -1
  16. package/dist/cloudflare/fake-tunnel.d.ts +48 -0
  17. package/dist/cloudflare/fake-tunnel.d.ts.map +1 -0
  18. package/dist/cloudflare/index.d.ts +15 -4
  19. package/dist/cloudflare/index.d.ts.map +1 -1
  20. package/dist/cloudflare/index.js +518 -7
  21. package/dist/cloudflare/index.js.map +15 -4
  22. package/dist/cloudflare/providers.d.ts +1 -1
  23. package/dist/cloudflare/providers.d.ts.map +1 -1
  24. package/dist/cloudflare/saas-oidc-api.d.ts +88 -0
  25. package/dist/cloudflare/saas-oidc-api.d.ts.map +1 -0
  26. package/dist/cloudflare/saas-oidc-consumer.d.ts +22 -0
  27. package/dist/cloudflare/saas-oidc-consumer.d.ts.map +1 -0
  28. package/dist/cloudflare/saas-oidc-error.d.ts +22 -0
  29. package/dist/cloudflare/saas-oidc-error.d.ts.map +1 -0
  30. package/dist/cloudflare/saas-oidc-form.d.ts +48 -0
  31. package/dist/cloudflare/saas-oidc-form.d.ts.map +1 -0
  32. package/dist/cloudflare/saas-oidc-harness.d.ts +50 -0
  33. package/dist/cloudflare/saas-oidc-harness.d.ts.map +1 -0
  34. package/dist/cloudflare/saas-oidc-lifecycle.d.ts +14 -0
  35. package/dist/cloudflare/saas-oidc-lifecycle.d.ts.map +1 -0
  36. package/dist/cloudflare/saas-oidc-props.d.ts +139 -0
  37. package/dist/cloudflare/saas-oidc-props.d.ts.map +1 -0
  38. package/dist/cloudflare/saas-oidc-team.d.ts +9 -0
  39. package/dist/cloudflare/saas-oidc-team.d.ts.map +1 -0
  40. package/dist/cloudflare/saas-oidc-wire.d.ts +58 -0
  41. package/dist/cloudflare/saas-oidc-wire.d.ts.map +1 -0
  42. package/dist/cloudflare/saas-oidc.d.ts +36 -0
  43. package/dist/cloudflare/saas-oidc.d.ts.map +1 -0
  44. package/dist/kubernetes/helm-chart-bounded.d.ts +44 -0
  45. package/dist/kubernetes/helm-chart-bounded.d.ts.map +1 -0
  46. package/dist/kubernetes/index.d.ts +12 -0
  47. package/dist/kubernetes/index.d.ts.map +1 -0
  48. package/dist/kubernetes/index.js +585 -0
  49. package/dist/kubernetes/index.js.map +19 -0
  50. package/dist/kubernetes/ready-checks.d.ts +45 -0
  51. package/dist/kubernetes/ready-checks.d.ts.map +1 -0
  52. package/dist/kubernetes/ready-classify.d.ts +23 -0
  53. package/dist/kubernetes/ready-classify.d.ts.map +1 -0
  54. package/dist/kubernetes/ready-connect.d.ts +29 -0
  55. package/dist/kubernetes/ready-connect.d.ts.map +1 -0
  56. package/dist/kubernetes/ready-declare.d.ts +5 -0
  57. package/dist/kubernetes/ready-declare.d.ts.map +1 -0
  58. package/dist/kubernetes/ready-errors.d.ts +80 -0
  59. package/dist/kubernetes/ready-errors.d.ts.map +1 -0
  60. package/dist/kubernetes/ready-poll.d.ts +61 -0
  61. package/dist/kubernetes/ready-poll.d.ts.map +1 -0
  62. package/dist/kubernetes/ready.d.ts +74 -0
  63. package/dist/kubernetes/ready.d.ts.map +1 -0
  64. package/dist/talos/fake-apiserver.d.ts +11 -2
  65. package/dist/talos/fake-apiserver.d.ts.map +1 -1
  66. package/docs/cloudflared-tunnel.md +136 -0
  67. package/docs/credentials.md +1 -1
  68. package/docs/kubernetes-ready.md +146 -0
  69. package/docs/ownership.md +3 -2
  70. package/docs/peers.md +13 -0
  71. package/docs/provider-standard.md +7 -0
  72. package/docs/saas-oidc.md +185 -0
  73. package/docs/upstream-conformance-families.md +1 -1
  74. package/docs/upstream-conformance.md +3 -0
  75. package/package.json +7 -1
  76. package/src/cloudflare/cloudflared-tunnel-api.ts +150 -0
  77. package/src/cloudflare/cloudflared-tunnel-edges.test.ts +179 -0
  78. package/src/cloudflare/cloudflared-tunnel-form.ts +78 -0
  79. package/src/cloudflare/cloudflared-tunnel-harness.ts +126 -0
  80. package/src/cloudflare/cloudflared-tunnel-lifecycle.ts +146 -0
  81. package/src/cloudflare/cloudflared-tunnel-state.test.ts +104 -0
  82. package/src/cloudflare/cloudflared-tunnel.test.ts +164 -0
  83. package/src/cloudflare/cloudflared-tunnel.ts +116 -0
  84. package/src/cloudflare/fake-access.ts +196 -0
  85. package/src/cloudflare/fake-mesh.ts +6 -3
  86. package/src/cloudflare/fake-tunnel.ts +148 -0
  87. package/src/cloudflare/index.ts +27 -4
  88. package/src/cloudflare/providers.ts +16 -3
  89. package/src/cloudflare/saas-oidc-api.ts +224 -0
  90. package/src/cloudflare/saas-oidc-consumer.ts +35 -0
  91. package/src/cloudflare/saas-oidc-error.ts +52 -0
  92. package/src/cloudflare/saas-oidc-form.test.ts +227 -0
  93. package/src/cloudflare/saas-oidc-form.ts +190 -0
  94. package/src/cloudflare/saas-oidc-harness.ts +140 -0
  95. package/src/cloudflare/saas-oidc-lifecycle.ts +150 -0
  96. package/src/cloudflare/saas-oidc-parse-error.test.ts +180 -0
  97. package/src/cloudflare/saas-oidc-props.ts +147 -0
  98. package/src/cloudflare/saas-oidc-read.test.ts +85 -0
  99. package/src/cloudflare/saas-oidc-stable.test.ts +38 -0
  100. package/src/cloudflare/saas-oidc-state.test.ts +128 -0
  101. package/src/cloudflare/saas-oidc-team.test.ts +88 -0
  102. package/src/cloudflare/saas-oidc-team.ts +70 -0
  103. package/src/cloudflare/saas-oidc-wire.ts +116 -0
  104. package/src/cloudflare/saas-oidc.test.ts +201 -0
  105. package/src/cloudflare/saas-oidc.ts +107 -0
  106. package/src/kubernetes/helm-chart-bounded.test.ts +242 -0
  107. package/src/kubernetes/helm-chart-bounded.ts +110 -0
  108. package/src/kubernetes/index.ts +28 -0
  109. package/src/kubernetes/ready-backoff.test.ts +124 -0
  110. package/src/kubernetes/ready-checks.test.ts +106 -0
  111. package/src/kubernetes/ready-checks.ts +165 -0
  112. package/src/kubernetes/ready-classify.ts +76 -0
  113. package/src/kubernetes/ready-connect.ts +52 -0
  114. package/src/kubernetes/ready-declare.test.ts +85 -0
  115. package/src/kubernetes/ready-declare.ts +50 -0
  116. package/src/kubernetes/ready-errors.ts +102 -0
  117. package/src/kubernetes/ready-first-install.test.ts +126 -0
  118. package/src/kubernetes/ready-poll-states.test.ts +92 -0
  119. package/src/kubernetes/ready-poll.test.ts +194 -0
  120. package/src/kubernetes/ready-poll.ts +215 -0
  121. package/src/kubernetes/ready-rollout.test.ts +58 -0
  122. package/src/kubernetes/ready-secrecy.test.ts +131 -0
  123. package/src/kubernetes/ready.fixtures.ts +103 -0
  124. package/src/kubernetes/ready.test.ts +205 -0
  125. package/src/kubernetes/ready.ts +188 -0
  126. package/src/talos/fake-apiserver.ts +21 -3
@@ -0,0 +1,19 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/kubernetes/helm-chart-bounded.ts", "../src/kubernetes/ready-classify.ts", "../src/kubernetes/ready.ts", "../src/talos/literal-connection.ts", "../src/talos/cluster-adapter-errors.ts", "../src/kubernetes/ready-errors.ts", "../src/kubernetes/ready-checks.ts", "../src/kubernetes/ready-connect.ts", "../src/kubernetes/ready-declare.ts", "../src/kubernetes/ready-poll.ts"],
4
+ "sourcesContent": [
5
+ "/**\n * `boundedHelmChartProvider`: upstream's `Kubernetes.HelmChart` provider with a wall-clock deadline\n * on `reconcile` and `read`. Wrapped, never re-implemented (alchemy-provider-standard: use what\n * Alchemy ships).\n *\n * ⛔ WHY (measured 2026-10-09, alchemy 2.0.0-beta.81): `requestJson` in `Kubernetes/internal/client.ts`\n * sets no timeout and no signal on any request, and retries transport errors only. A hung apiserver\n * therefore blocks a HelmChart reconcile or read forever, holding the 1h platform token across a\n * stuck deploy. The deadline turns that into a typed `KubernetesReconcileTimeout`.\n * ★ A DIRECT `Provider(HelmChart)` layer, not a collection member: the engine checks the direct\n * service before any collection (`Provider.ts` `tryFindProviderRegistrationByType`), so merging\n * this beside `Kubernetes.providers()` makes the wrapper win with no change to upstream's layer.\n * ⚠️ The interrupted fiber ABANDONS its socket (upstream takes no signal). The run then fails typed\n * and the process exits once main completes, so a dangling socket cannot hold anything.\n * `diff`, `delete`, `stables` and `aliases` come from upstream by spread, so state identity and\n * replace semantics are exactly upstream's.\n */\nimport { HelmChart, HelmChartProvider } from 'alchemy/Kubernetes/HelmChart';\nimport * as Provider from 'alchemy/Provider';\nimport type * as Context from 'effect/Context';\nimport * as Data from 'effect/Data';\nimport * as Duration from 'effect/Duration';\nimport * as Effect from 'effect/Effect';\nimport * as Layer from 'effect/Layer';\nimport { scrubbed } from './ready-classify.ts';\nimport { parseGoDuration } from './ready.ts';\n\n/** A render + discovery + ~60 server-side-apply PATCHes fit well inside this. */\nexport const HELM_RECONCILE_TIMEOUT = '5m0s';\n/** `read` is one connect (identity GET): a minute is generous. */\nexport const HELM_READ_TIMEOUT = '1m0s';\n\n/** The deadline passed with no answer from the apiserver. Names the row and seconds only. */\nexport class KubernetesReconcileTimeout extends Data.TaggedError('KubernetesReconcileTimeout')<{\n readonly id: string;\n readonly operation: 'reconcile' | 'read';\n readonly seconds: number;\n}> {\n override get message(): string {\n return `Kubernetes.HelmChart ${this.id}: ${this.operation} did not finish within ${this.seconds}s (the apiserver stopped answering)`;\n }\n}\n\nconst bound = <A, E, R>(\n effect: Effect.Effect<A, E, R>,\n deadline: Duration.Duration,\n id: string,\n operation: 'reconcile' | 'read',\n): Effect.Effect<A, E | KubernetesReconcileTimeout, R> =>\n effect.pipe(\n // ⛔ Upstream's `KubernetesApiError` quotes up to 1000 bytes of the apiserver body\n // (`client.ts:39-41`): scrub it to a body-free error, as `Kubernetes.Ready` does.\n Effect.mapError((error) => scrubbed(error) as E),\n Effect.timeoutOrElse({\n duration: deadline,\n orElse: () =>\n Effect.fail(\n new KubernetesReconcileTimeout({ id, operation, seconds: Duration.toSeconds(deadline) }),\n ),\n }),\n );\n\n// `Provider(type)` is typed as an Effect; at runtime it is the service key the engine looks up.\nconst HelmChartKey = Provider.Provider<HelmChart>(HelmChart.Type) as unknown as Context.Service<\n Provider.Provider<HelmChart>,\n Provider.ProviderService<HelmChart>\n>;\n\nconst BOUNDED = Symbol.for('@homeflare/alchemy/kubernetes/boundedHelmChartProvider');\n\n/** True for a service built by `boundedHelmChartProvider`: lets a stack test its real layer. */\nexport const isBoundedHelmChartProvider = (service: unknown): boolean =>\n typeof service === 'object' &&\n service !== null &&\n (service as Record<symbol, unknown>)[BOUNDED] === true;\n\n/**\n * ⛔ A malformed duration dies at layer build (`Effect.orDie`), never becomes a silent unbounded\n * call: a bad override cannot quietly drop the deadline.\n */\nexport const boundedHelmChartProvider = (\n reconcileTimeout: string = HELM_RECONCILE_TIMEOUT,\n readTimeout: string = HELM_READ_TIMEOUT,\n) =>\n Layer.effect(\n HelmChartKey,\n Effect.gen(function* () {\n const inner = yield* HelmChartKey;\n const reconcileDeadline = yield* parseGoDuration(\n 'reconcileTimeout',\n reconcileTimeout,\n 'Kubernetes.HelmChart',\n );\n const innerRead = inner.read?.bind(inner);\n const readDeadline = yield* parseGoDuration(\n 'readTimeout',\n readTimeout,\n 'Kubernetes.HelmChart',\n );\n const service = {\n ...inner,\n reconcile: (input) =>\n bound(inner.reconcile(input), reconcileDeadline, input.id, 'reconcile'),\n ...(innerRead === undefined\n ? {}\n : { read: (input) => bound(innerRead(input), readDeadline, input.id, 'read') }),\n } satisfies Provider.ProviderService<HelmChart>;\n return Object.defineProperty(service, BOUNDED, { value: true });\n }).pipe(Effect.orDie),\n ).pipe(Layer.provide(HelmChartProvider()));\n",
6
+ "/**\n * Error classification for `HomeFlare.Kubernetes.Ready`, shared by the poll (`ready-poll.ts`) and\n * the connect (`ready-connect.ts`).\n *\n * ⛔ ERROR CLASSES (T10 design v3 §2, round-2 findings 3b and 8, round-1 finding 3):\n * - 404 is PENDING everywhere. Version-proof: `_tag === 'KubernetesNotFound'` OR a\n * `KubernetesApiError` with `statusCode === 404` (beta.81 raises the latter,\n * `Kubernetes/internal/client.ts:32-37`; a later alchemy may raise the former). Matched by\n * tag string, no import, so neither shape needs a kit release.\n * - TRANSIENT, tolerated only inside a poll or its connect: a per-GET timeout, the identity\n * timeout, `KubernetesApiError` 5xx/429, and upstream's transport error, which is the plain\n * `Error` whose message starts `Failed Kubernetes ` (`client.ts:146-148`).\n * - ⚠️ ANY OTHER UNTAGGED `Error` IS A DEFECT AND PROPAGATES. It used to count as transport, which\n * made upstream's path-builder throw for an empty namespace (`objects.ts:189-192`) and a\n * failing `bao` (`credentials.ts` plain `Error`s) look like a slow rollout for ten minutes.\n * - Everything else propagates: 401/403/other 4xx, vault failures (the ClusterHealth rule).\n */\nimport * as Data from 'effect/Data';\nimport type { LastTransient } from './ready-errors.ts';\n\ninterface Tagged {\n readonly _tag?: unknown;\n readonly statusCode?: unknown;\n readonly message?: unknown;\n}\n\nexport const isNotFound = (error: unknown): boolean => {\n const e = error as Tagged | null;\n return (\n e?._tag === 'KubernetesNotFound' || (e?._tag === 'KubernetesApiError' && e.statusCode === 404)\n );\n};\n\n/** Upstream's transport failure wrapper (`client.ts:146-148`); its message names no body. */\nconst UPSTREAM_TRANSPORT = /^Failed Kubernetes /;\n\n/** The tolerable-in-a-poll classes, as a record of tag + status; `undefined` means propagate. */\nexport const transientOf = (error: unknown): LastTransient | undefined => {\n const e = error as Tagged | null;\n if (e?._tag === 'KubernetesReadyGetTimeout') return { tag: 'KubernetesReadyGetTimeout' };\n if (e?._tag === 'TalosClusterIdentityTimeout') return { tag: 'TalosClusterIdentityTimeout' };\n if (e?._tag === 'KubernetesApiError') {\n const status = typeof e.statusCode === 'number' ? e.statusCode : 0;\n return status >= 500 || status === 429 ? { status, tag: 'KubernetesApiError' } : undefined;\n }\n return error instanceof Error && e?._tag === undefined && UPSTREAM_TRANSPORT.test(error.message)\n ? { tag: 'TransportError' }\n : undefined;\n};\n\n/**\n * A propagated apiserver refusal (401/403/other 4xx, or any status in a single pass). ⛔ Upstream's\n * `KubernetesApiError` message quotes up to 1000 bytes of the response body (`client.ts:38-42`);\n * a body is the server's text, not ours to republish into logs and run output, so it is dropped.\n */\nexport class KubernetesReadyApiError extends Data.TaggedError('KubernetesReadyApiError')<{\n readonly method: string;\n readonly path: string;\n readonly statusCode: number;\n}> {\n override get message(): string {\n return `Kubernetes.Ready: ${this.method} ${this.path} responded ${this.statusCode}`;\n }\n}\n\n/** ⛔ Wrap every error that leaves a GET, the identity GET of connect included (round 1, finding 4). */\nexport const scrubbed = (error: unknown): unknown => {\n const e = error as { _tag?: unknown; method?: unknown; path?: unknown; statusCode?: unknown };\n return e?._tag === 'KubernetesApiError'\n ? new KubernetesReadyApiError({\n method: String(e.method),\n path: String(e.path),\n statusCode: Number(e.statusCode),\n })\n : error;\n};\n",
7
+ "/**\n * `HomeFlare.Kubernetes.Ready` — a readiness gate other rows depend on via `after`/Output edges.\n *\n * ★ WHY IT EXISTS. Upstream ships no readiness resource: `Kubernetes.HelmChart` reconcile returns\n * when server-side apply returns, and `Apply` releases the downstream rows at that moment, so a\n * row chained after the Cilium chart would start before one Cilium pod runs (T10 design v3 §2).\n * ★ SEMANTICS are `kubectl rollout status` (`ready-checks.ts`), the polling rules are in\n * `ready-poll.ts`. Reconcile polls to `waitTimeout`; `read`/`diff` take ONE pass, so a plan is\n * live (like `Talos.ClusterHealth`) but never waits.\n * ⛔ `connection` IS A LITERAL `talos-openbao` CONNECTION, refused otherwise at declaration and in\n * `diff` (`literal-connection.ts`), and it is copied onto the attributes so this row is\n * `ClusterLike` (`stables`, as upstream's `HelmChart`). It carries a uid, never a credential.\n * ⛔ NO SECRET REACHES ANYTHING THIS ROW WRITES: errors and attributes name check keys, counts and\n * an error tag + status only, never a response body.\n * ⚠️ `delete` is a no-op and `list` is empty: the row owns nothing in the cluster.\n *\n * Walked against alchemy@2.0.0-beta.81 (`Kubernetes/internal/client.ts` `readObject`,\n * `KubernetesApiError`); only the fake apiserver was used. Not exercised against a live cluster.\n */\nimport { Resource } from 'alchemy';\nimport { isResolved } from 'alchemy/Diff';\nimport type { Input } from 'alchemy/Input';\nimport type { Output } from 'alchemy/Output';\nimport * as Provider from 'alchemy/Provider';\nimport * as Duration from 'effect/Duration';\nimport * as Effect from 'effect/Effect';\nimport type { TalosOpenBaoConnection } from '../talos/cluster-adapter.ts';\nimport { literalConnectionRefusal, withLiteralConnection } from '../talos/literal-connection.ts';\nimport type { TalosRequirements } from '../talos/resource.ts';\nimport { type ReadyCheck, checksCsv } from './ready-checks.ts';\nimport { connectForDiff, connectOnce, connectTolerant } from './ready-connect.ts';\nimport { declaredReady } from './ready-declare.ts';\nimport { KubernetesReadyBadDuration } from './ready-errors.ts';\nimport { pass, poll } from './ready-poll.ts';\n\nexport interface ReadyProps {\n /** Literal `talos-openbao` connection (uid proven at connect). */\n connection: TalosOpenBaoConnection;\n checks: readonly ReadyCheck[];\n /** Reconcile deadline, Go style. Default `10m0s`. */\n waitTimeout?: string;\n /** Pause between passes, Go style. Default `10s`. */\n pollInterval?: string;\n /**\n * Ordering edge: lazy Outputs of the chart, e.g. `[chart.objects]`. ⛔ Not `chart.connection` or\n * any other stable: those resolve at plan time and the row would plan `noop` before the chart\n * updates (refused at declaration, `ready-declare.ts`).\n */\n after?: readonly Output<unknown>[];\n}\n\nexport interface ReadyAttributes {\n connection: TalosOpenBaoConnection;\n ready: boolean;\n /** The check keys, comma separated: a changed list is an `update`. */\n checks: string;\n}\n\nexport interface KubernetesReady extends Resource<\n 'HomeFlare.Kubernetes.Ready',\n ReadyProps,\n ReadyAttributes,\n never,\n TalosRequirements\n> {}\n\n// ⛔ Declaration-time literal guard: a fresh resource skips `diff` (see `withLiteralConnection`).\nexport const KubernetesReady = declaredReady(\n withLiteralConnection(Resource<KubernetesReady>('HomeFlare.Kubernetes.Ready')),\n);\n\nconst HOUR_MS = 3_600_000;\nconst MINUTE_MS = 60_000;\nconst SECOND_MS = 1000;\n\n// ⛔ Anchored with bounded repeats, in h-m-s order: the former unanchored `(\\d+)([hms])` with `g` was\n// CodeQL js/polynomial-redos #18 once `boundedHelmChartProvider` made it reachable from exported\n// parameters. Length is capped first, so no input can make the match slow.\nconst GO_DURATION = /^(?:(\\d{1,4})h)?(?:(\\d{1,4})m)?(?:(\\d{1,6})s)?$/;\nconst MAX_DURATION_LENGTH = 20;\n\n/**\n * `10m0s`, `90s`, `1h30m`: whole-number Go durations in h/m/s. Anything else is typed-refused.\n * `resource` names the row in the refusal (`Kubernetes.Ready` unless the caller is another one).\n */\nexport const parseGoDuration = (field: string, value: string, resource?: string) =>\n Effect.gen(function* () {\n const refuse = () =>\n Effect.fail(\n new KubernetesReadyBadDuration({\n field,\n value,\n ...(resource === undefined ? {} : { resource }),\n }),\n );\n const match = value.length > MAX_DURATION_LENGTH ? null : GO_DURATION.exec(value);\n if (match === null || value === '') return yield* refuse();\n const [, h, m, s] = match;\n const ms = Number(h ?? 0) * HOUR_MS + Number(m ?? 0) * MINUTE_MS + Number(s ?? 0) * SECOND_MS;\n // A zero poll interval would spin a full pass of GETs per scheduler tick against the apiserver,\n // and a zero wait fails before a single pass: both are refused (DeepSeek read of PR 373).\n if (ms <= 0) return yield* refuse();\n return Duration.millis(ms);\n });\n\nconst attributes = (props: ReadyProps, ready: boolean): ReadyAttributes => ({\n checks: checksCsv(props.checks),\n connection: props.connection,\n ready,\n});\n\n/** ⚠️ A row saved before `connection` existed has none at runtime: missing means \"update\". */\nconst sameConnection = (left: TalosOpenBaoConnection, right: TalosOpenBaoConnection | undefined) =>\n right?.auth?.kind === left.auth.kind &&\n right.auth.uid === left.auth.uid &&\n !Object.hasOwn(right.auth, 'cluster');\n\n/** One pass: `ready` only when every check is ready. A terminal failure is not ready either. */\nexport const readReady = (props: ReadyProps) =>\n Effect.gen(function* () {\n const transport = yield* connectOnce(props.connection);\n const result = yield* pass(transport, props.checks, false);\n return attributes(props, result.pending.length === 0 && result.failed.length === 0);\n });\n\n/**\n * ⛔ The `diff` pass: a transient (connect or GET) yields `undefined`, which diff turns into\n * `update`, so a CNI rollout never fails a plan and `reconcile` does the waiting. 401/403, vault\n * failures and a uid mismatch still propagate.\n */\nconst readReadyForDiff = (props: ReadyProps) =>\n Effect.gen(function* () {\n const transport = yield* connectForDiff(props.connection);\n if (transport === undefined) return undefined;\n const result = yield* pass(transport, props.checks, true);\n const settled = result.pending.length === 0 && result.failed.length === 0;\n return settled ? attributes(props, true) : undefined;\n });\n\nexport const reconcileReady = (props: ReadyProps) =>\n Effect.gen(function* () {\n const waitTimeout = yield* parseGoDuration('waitTimeout', props.waitTimeout ?? '10m0s');\n const pollInterval = yield* parseGoDuration('pollInterval', props.pollInterval ?? '10s');\n const connect = connectTolerant(props.connection, pollInterval);\n yield* poll(connect, props.checks, waitTimeout, pollInterval);\n return attributes(props, true);\n });\n\nexport const diffReady = (news: Input<ReadyProps>, output: ReadyAttributes | undefined) =>\n Effect.gen(function* () {\n // ⛔ Refuse an Output connection while it is still an Input (`literal-connection.ts`).\n const refused = literalConnectionRefusal((news as { connection?: unknown }).connection);\n if (refused !== undefined) return yield* Effect.fail(refused);\n if (output === undefined || !isResolved(news)) return undefined;\n // ⛔ `connection` is in `stables`: dependants read it off the saved row, so a changed one must\n // REPLACE (as upstream's HelmChart does, `HelmChart.ts:243-251`) or they would see the old\n // connection for one more deploy. `delete` is a no-op, so a replace touches nothing live.\n // A legacy row with no connection counts as changed.\n if (!sameConnection(news.connection, output.connection)) return { action: 'replace' } as const;\n const live = yield* readReadyForDiff(news);\n return live !== undefined && live.checks === output.checks\n ? ({ action: 'noop' } as const)\n : ({ action: 'update' } as const);\n });\n\nexport const readHandler = ({\n olds,\n output,\n}: {\n olds: ReadyProps;\n output: ReadyAttributes | undefined;\n}) => (output === undefined ? Effect.succeed(undefined) : readReady(olds));\n\nconst handlers = {\n delete: () => Effect.void,\n diff: ({ news, output }: { news: Input<ReadyProps>; output: ReadyAttributes | undefined }) =>\n diffReady(news, output),\n list: () => Effect.succeed([]),\n // ⛔ No prior output means Alchemy's deferred adoption read on a first create: the row owns\n // nothing, so there is nothing to adopt, and a one-pass read would fail the install on the\n // first 5xx before `reconcile` could poll through it (round 1, finding 2).\n read: readHandler,\n reconcile: ({ news }: { news: ReadyProps }) => reconcileReady(news),\n stables: ['connection'] as (keyof ReadyAttributes)[],\n};\n\nexport const KubernetesReadyProvider = () =>\n Provider.effect(KubernetesReady, Effect.succeed(KubernetesReady.Provider.of(handlers)));\n",
8
+ "/**\n * Runtime refusal of a `talos-openbao` connection that is not a plain literal.\n *\n * ⛔ A RESOURCE THAT TAKES A `connection` PROP MUST CALL THIS IN `diff`, WHERE THE PROP IS STILL\n * AN `Input`. By `read`/`reconcile` the engine has resolved an Output to its current string, so\n * the same check there can no longer tell a literal from an Output: the refusal only works\n * before resolution. An Output in the connection is unresolved at plan time, upstream plans an\n * UPDATE for every workload on it, and reconcile replays the old cluster's deletes on the new\n * one (`talosOpenBaoConnection` in cluster-adapter.ts).\n */\nimport { hasUnresolvedInputs } from 'alchemy/Diff';\nimport * as Effect from 'effect/Effect';\nimport { TalosUidNotLiteral } from './cluster-adapter-errors.ts';\n\n/**\n * The typed refusal unless `connection` is `{ auth: { kind, uid: literal } }`, else `undefined`.\n * ★ A value, never a throw: a throw inside `Effect.gen` would become a defect, not a failure.\n */\nexport const literalConnectionRefusal = (connection: unknown): TalosUidNotLiteral | undefined => {\n const auth = (connection as { auth?: { kind?: unknown; uid?: unknown } } | undefined)?.auth;\n // ⛔ A stock `kubeconfig` connection has no uid and would pass the checks below by failing the\n // `uid` test as \"not a string\" — but it must be refused here, at declaration/diff time, because\n // connect would only refuse it later, after the engine has resolved it (and its empty path has\n // already fallen back to `$KUBECONFIG`). Only `talos-openbao` carries a literal uid.\n return hasUnresolvedInputs(connection) ||\n auth?.kind !== 'talos-openbao' ||\n typeof auth?.uid !== 'string' ||\n auth.uid === ''\n ? new TalosUidNotLiteral({})\n : undefined;\n};\n\n/**\n * ⛔ THE `diff` GUARD ALONE IS NOT ENOUGH (round-4 review): alchemy skips `diff` for a FRESH\n * resource whose inputs are unresolved, and reconcile then copies the resolved string downstream\n * — an Output uid slips through on exactly the first deploy. So the same refusal runs when the\n * resource is DECLARED, before any input resolution, by wrapping its constructor.\n * ★ A Proxy over the class (not a new function) keeps `.Provider`, `.Self`, `.ref` and the type.\n * Plain props fail the declaring Effect; a props Effect fails once it yields.\n */\nexport const withLiteralConnection = <C extends (...args: never[]) => unknown>(resource: C): C =>\n new Proxy(resource, {\n apply: (target, thisArg, args: unknown[]) => {\n const refusal = (given: unknown) =>\n literalConnectionRefusal((given as { connection?: unknown } | undefined)?.connection);\n const props = args[1];\n if (Effect.isEffect(props)) {\n const checked = props.pipe(\n Effect.flatMap((resolved) => {\n const refused = refusal(resolved);\n return refused === undefined ? Effect.succeed(resolved) : Effect.fail(refused);\n }),\n );\n return Reflect.apply(target, thisArg, [args[0], checked, ...args.slice(2)]);\n }\n const refused = refusal(props);\n return refused === undefined ? Reflect.apply(target, thisArg, args) : Effect.fail(refused);\n },\n });\n",
9
+ "/**\n * Typed failures of the `talos-openbao` adapter — split out of cluster-adapter.ts to keep each\n * class's own header out of that file's 250-line budget. No message names document bytes.\n */\nimport * as Data from 'effect/Data';\n\n/** The connection's uid matches no adapter entry. Uids are public. */\nexport class TalosOpenBaoUnknownCluster extends Data.TaggedError('TalosOpenBaoUnknownCluster')<{\n readonly uid: string;\n readonly known: readonly string[];\n}> {\n override get message(): string {\n return (\n `talos-openbao adapter has no entry pinned to cluster uid ${this.uid} ` +\n `(configured uids: ${this.known.join(', ') || 'none'}). Refused rather than reading ` +\n \"another cluster's kubeconfig. If the cluster was retired, keep its entry with \" +\n '`retired: true` and its uid until its rows are destroyed.'\n );\n }\n}\n\n/** Two adapter entries pin the same uid, so which vault key to read is ambiguous. */\nexport class TalosOpenBaoAmbiguousUid extends Data.TaggedError('TalosOpenBaoAmbiguousUid')<{\n readonly uid: string;\n readonly aliases: readonly string[];\n}> {\n override get message(): string {\n return (\n `talos-openbao adapter entries ${this.aliases.join(', ')} are all pinned to uid ` +\n `${this.uid}. Refused instead of picking one: a uid names exactly one entry.`\n );\n }\n}\n\n/** The saved auth block still carries the alias an earlier build of this branch persisted. */\nexport class TalosOpenBaoLegacyAuth extends Data.TaggedError('TalosOpenBaoLegacyAuth')<{\n readonly _?: never;\n}> {\n override get message(): string {\n return (\n 'talos-openbao connection carries a legacy `cluster` alias in its auth block. Refused ' +\n 'before any vault read or request, with no silent migration (old and new auth differ, so ' +\n 'upstream would PATCH then DELETE the same object). Remedy: edit the saved state ONCE so ' +\n \"each such row's `connection.auth` is `{ kind: 'talos-openbao', uid }` (drop `cluster`), \" +\n 'then redeploy.'\n );\n }\n}\n\n/** `Talos.ClusterIdentity` read a different uid than the one it published. Never an update. */\nexport class TalosClusterMoved extends Data.TaggedError('TalosClusterMoved')<{\n readonly cluster: string;\n readonly saved: string;\n readonly live: string;\n}> {\n override get message(): string {\n return (\n `Talos.ClusterIdentity '${this.cluster}' published uid ${this.saved} but the vault key now ` +\n `answers as ${this.live}. Refused, not updated: an update cannot become a replace for the ` +\n 'workloads downstream, so the new cluster would be force-applied and the old one orphaned. ' +\n 'A move is explicit: declare a NEW Talos.ClusterIdentity for the new cluster and leave this ' +\n 'one pointing at the old cluster until its workloads are destroyed.'\n );\n }\n}\n\n/** `bao kv get` reported the key unwritten. The message names `mount/key` and no document bytes. */\nexport class TalosVaultKeyMissing extends Data.TaggedError('TalosVaultKeyMissing')<{\n readonly mount: string;\n readonly key: string;\n}> {\n override get message(): string {\n return (\n `${this.mount}/${this.key}: OpenBao has no value at this key. Talos.Kubeconfig writes it ` +\n 'once at bring-up. Connect refused instead of reading a kubeconfig from disk.'\n );\n }\n}\n\n/** Vault bytes exist but are not a kubeconfig for the pinned context. The document is not echoed. */\nexport class TalosKubeconfigUnreadable extends Data.TaggedError('TalosKubeconfigUnreadable')<{\n readonly mount: string;\n readonly key: string;\n readonly context: string;\n}> {\n override get message(): string {\n return (\n `${this.mount}/${this.key}: OpenBao kubeconfig does not parse for context ${this.context}. ` +\n 'The document stayed in the vault. Connect wrote nothing to disk and nothing to state.'\n );\n }\n}\n\n/** The layer was asked to connect a different auth kind. */\nexport class TalosOpenBaoAuthKind extends Data.TaggedError('TalosOpenBaoAuthKind')<{\n readonly kind: string;\n}> {\n override get message(): string {\n return (\n `talos-openbao adapter received auth kind '${this.kind}'. This adapter only connects ` +\n '`talos-openbao`.'\n );\n }\n}\n\n/** The saved auth block carries no `uid`, so nothing says WHICH physical cluster it meant. */\nexport class TalosClusterIdentityMissing extends Data.TaggedError('TalosClusterIdentityMissing')<{\n readonly _?: never;\n}> {\n override get message(): string {\n return (\n 'talos-openbao connection carries no uid, so it cannot say which physical cluster it ' +\n 'targets. Refused before any vault read or request: pin the uid ' +\n 'printed by `Talos.ClusterIdentity` as a literal in `TalosOpenBaoAdapter` and build ' +\n 'workloads from `talosOpenBaoConnection(<that literal>)`.'\n );\n }\n}\n\n/** The cluster that answered is not the one the saved auth block named. Uids are public. */\nexport class TalosClusterIdentityMismatch extends Data.TaggedError('TalosClusterIdentityMismatch')<{\n readonly cluster: string;\n readonly expected: string;\n readonly got: string;\n}> {\n override get message(): string {\n return (\n `talos-openbao cluster '${this.cluster}' answered as kube-system uid ${this.got}, but the ` +\n `saved connection expects ${this.expected}. Refused before any apply or delete: the name ` +\n 'now resolves to a different physical cluster. If the original cluster is gone for good, ' +\n 'remove its rows from state deliberately.'\n );\n }\n}\n\n/** `GET /api/v1/namespaces/kube-system` answered without a usable `metadata.uid`. */\nexport class TalosClusterIdentityUnreadable extends Data.TaggedError(\n 'TalosClusterIdentityUnreadable',\n)<{\n readonly cluster: string;\n}> {\n override get message(): string {\n return (\n `talos-openbao cluster '${this.cluster}': kube-system has no readable metadata.uid. ` +\n 'Refused rather than assuming the cluster is the expected one.'\n );\n }\n}\n\n/** The kube-system GET got no answer in time: a silent apiserver must not hang connect or plan. */\nexport class TalosClusterIdentityTimeout extends Data.TaggedError('TalosClusterIdentityTimeout')<{\n readonly cluster: string;\n readonly seconds: number;\n}> {\n override get message(): string {\n return (\n `talos-openbao cluster '${this.cluster}': no answer to the kube-system uid read within ` +\n `${this.seconds}s (the identity read's own deadline, inside the 10 s connect deadline). ` +\n 'Refused (failed closed) rather than waiting on a silent apiserver.'\n );\n }\n}\n\n/**\n * The whole connect path (vault read, parse, kube-system uid read) did not finish in time. One\n * deadline covers `bao` and the apiserver together: a hung `bao` or a silent apiserver must not\n * hang connect or a plan. The `bao` child is killed when the deadline interrupts the read.\n */\nexport class TalosOpenBaoConnectTimeout extends Data.TaggedError('TalosOpenBaoConnectTimeout')<{\n readonly cluster: string;\n readonly seconds: number;\n}> {\n override get message(): string {\n return (\n `talos-openbao cluster '${this.cluster}': vault read and identity check did not finish ` +\n `within ${this.seconds}s. Refused (failed closed); the bao child was killed.`\n );\n }\n}\n\n/** A connection was built from a uid that is not a plain string known at plan time. */\nexport class TalosUidNotLiteral extends Data.TaggedError('TalosUidNotLiteral')<{\n readonly _?: never;\n}> {\n override get message(): string {\n return (\n 'talos-openbao connection uid must be a literal string taken from the adapter config, not ' +\n 'an Output (for example a Talos.ClusterIdentity attribute). An unresolved uid hides a ' +\n \"cluster change from the plan: upstream would plan an UPDATE and reconcile the old cluster's \" +\n 'objects on the new one. Pin the uid in `TalosOpenBaoAdapter` and take it from there.'\n );\n }\n}\n",
10
+ "/**\n * The typed errors of `HomeFlare.Kubernetes.Ready` that carry no logic, so a stack can `catchTag`\n * them. ⛔ Every message names check keys, counts, durations and statuses only: never a response\n * body, which is the server's text and not ours to republish.\n */\nimport * as Data from 'effect/Data';\n\n/** What the poll recorded when a pass last tolerated a transient error: tag and status, no body. */\nexport interface LastTransient {\n readonly tag: string;\n readonly status?: number;\n}\n\n/**\n * What the last pass saw of one check: the object was absent (404), or the numbers it reported.\n * ★ A 404 that never resolves (a typo'd name, a chart that never creates it) must read differently\n * from a slow rollout, without republishing anything the server said (round 1, finding 3).\n */\nexport type KeyState =\n | { readonly notFound: true }\n | { readonly counts: Readonly<Record<string, number>> };\n\nconst describeState = (state: KeyState | undefined): string => {\n if (state === undefined) return '';\n if ('notFound' in state) return ' (not found)';\n const counts = Object.entries(state.counts).map(([name, value]) => `${name}=${value}`);\n return counts.length === 0 ? '' : ` (${counts.join(' ')})`;\n};\n\n/** The gate never opened within `waitTimeout`. `failing` holds check keys only. */\nexport class KubernetesReadyTimeout extends Data.TaggedError('KubernetesReadyTimeout')<{\n readonly failing: readonly string[];\n readonly seconds: number;\n readonly lastTransient?: LastTransient;\n /** Per failing key, from the last pass that reached it: `notFound`, or the counts seen. */\n readonly states?: Readonly<Record<string, KeyState>>;\n}> {\n override get message(): string {\n const failing = this.failing.map((key) => `${key}${describeState(this.states?.[key])}`);\n const last =\n this.lastTransient === undefined\n ? ''\n : `; last transient error ${this.lastTransient.tag}${\n this.lastTransient.status === undefined ? '' : ` ${this.lastTransient.status}`\n }`;\n return `Kubernetes.Ready: not ready within ${this.seconds}s: ${failing.join(', ')}${last}`;\n }\n}\n\n/**\n * A rollout that will not finish, which is not pending: a Deployment reported\n * `ProgressDeadlineExceeded`, or a DaemonSet's `updateStrategy` is not `RollingUpdate` (kubectl\n * `rollout_status.go:104-106` errors the same way: `OnDelete` pods never roll by themselves).\n */\nexport class KubernetesRolloutFailed extends Data.TaggedError('KubernetesRolloutFailed')<{\n readonly check: string;\n readonly reason?: 'ProgressDeadlineExceeded' | 'UpdateStrategyNotRollingUpdate';\n}> {\n override get message(): string {\n return this.reason === 'UpdateStrategyNotRollingUpdate'\n ? `Kubernetes.Ready: ${this.check} does not use the RollingUpdate update strategy`\n : `Kubernetes.Ready: ${this.check} exceeded its progress deadline (ProgressDeadlineExceeded)`;\n }\n}\n\n/** A `waitTimeout`/`pollInterval` that is not a Go-style duration (`10m0s`, `90s`, `1h30m`). */\nexport class KubernetesReadyBadDuration extends Data.TaggedError('KubernetesReadyBadDuration')<{\n readonly field: string;\n readonly value: string;\n /** The resource whose prop it is; `Kubernetes.Ready` unless the HelmChart bound says otherwise. */\n readonly resource?: string;\n}> {\n override get message(): string {\n // A hostile or typo'd value can be long: the message quotes a bounded prefix only.\n const shown = this.value.length > 40 ? `${this.value.slice(0, 40)}...` : this.value;\n return `${this.resource ?? 'Kubernetes.Ready'}: ${this.field} '${shown}' is not a duration like 10m0s`;\n }\n}\n\n/** A check that can never be satisfied, refused at declaration instead of timing out in 10 m. */\nexport class KubernetesReadyBadCheck extends Data.TaggedError('KubernetesReadyBadCheck')<{\n /** Index into `checks`. */\n readonly index: number;\n readonly problem: string;\n}> {\n override get message(): string {\n return `Kubernetes.Ready: checks[${this.index}] ${this.problem}`;\n }\n}\n\n/**\n * `after` must hold Outputs of the chart (`chart.objects`). ⛔ A plain value, or a stable such as\n * `chart.connection`, resolves at plan time, so the row would plan `noop` before the chart's\n * update and the gate would be skipped.\n */\nexport class KubernetesReadyBadAfter extends Data.TaggedError('KubernetesReadyBadAfter')<{\n readonly index: number;\n}> {\n override get message(): string {\n return `Kubernetes.Ready: after[${this.index}] is not chart.objects (the only shape that waits for the chart's update)`;\n }\n}\n",
11
+ "/**\n * Readiness evaluators for `HomeFlare.Kubernetes.Ready`: pure functions over one object's JSON,\n * plus the typed errors of the gate. No I/O here, so every rule is testable on a literal body.\n *\n * ★ ROLLOUT SEMANTICS, NOT \"Available\". Copied from `kubectl rollout status` (kubectl v0.34.0\n * `pkg/polymorphichelpers/rollout_status.go:75-89` Deployment, `:107-114` DaemonSet), because a\n * gate that only reads `Available`/`numberReady` passes on the OLD ReplicaSet or pods while a\n * new generation is still rolling out (T10 design v3, red-team round 2, finding 1). The first\n * rule of both is `status.observedGeneration >= metadata.generation`: until the controller has\n * seen the latest spec, every count below describes a spec that no longer exists.\n * ⚠️ A body that is missing or not an object is PENDING, never ready: an empty 200 must not open\n * the gate.\n */\nimport { type KeyState, KubernetesReadyBadCheck } from './ready-errors.ts';\n\nexport type ReadyCheck =\n | {\n readonly kind: 'DaemonSet';\n readonly namespace: string;\n readonly name: string;\n readonly minReady?: number;\n }\n | { readonly kind: 'Deployment'; readonly namespace: string; readonly name: string }\n | { readonly kind: 'CustomResourceDefinition'; readonly name: string };\n\n/** `failed` is terminal (kubectl reports it as an error), `pending` is \"look again\". */\nexport type Verdict = 'ready' | 'pending' | 'failed';\n\n/** Check keys only: names are public, no body byte ever reaches a key, an error or an attribute. */\nexport const checkKey = (check: ReadyCheck): string => {\n switch (check.kind) {\n case 'DaemonSet':\n return `DaemonSet/${check.namespace}/${check.name}${\n check.minReady === undefined ? '' : `:min=${check.minReady}`\n }`;\n case 'Deployment':\n return `Deployment/${check.namespace}/${check.name}`;\n case 'CustomResourceDefinition':\n return `CustomResourceDefinition/${check.name}`;\n }\n};\n\nexport const checksCsv = (checks: readonly ReadyCheck[]): string => checks.map(checkKey).join(',');\n\n/**\n * Declaration-time validation (round 1, finding 3): a check that can never be satisfied is a typed\n * refusal, not a 10 minute timeout. ⚠️ Upstream's path builder THROWS for a namespaced kind with an\n * empty namespace (`objects.ts:189-192`), which would otherwise be read as a transport transient.\n */\nexport const validateChecks = (checks: unknown): KubernetesReadyBadCheck | undefined => {\n if (!Array.isArray(checks) || checks.length === 0) {\n return new KubernetesReadyBadCheck({ index: 0, problem: 'must list at least one check' });\n }\n for (const [index, raw] of checks.entries()) {\n const check = obj(raw);\n const bad = (problem: string) => new KubernetesReadyBadCheck({ index, problem });\n const kind = check['kind'];\n if (kind !== 'DaemonSet' && kind !== 'Deployment' && kind !== 'CustomResourceDefinition') {\n return bad('has an unknown kind');\n }\n if (typeof check['name'] !== 'string' || check['name'] === '') {\n return bad('needs a non-empty name');\n }\n if (kind !== 'CustomResourceDefinition') {\n if (typeof check['namespace'] !== 'string' || check['namespace'] === '') {\n return bad(`needs a non-empty namespace (${kind} is namespaced)`);\n }\n }\n const min = check['minReady'];\n if (min !== undefined && (kind !== 'DaemonSet' || !Number.isInteger(min) || Number(min) < 1)) {\n return bad('minReady is an integer >= 1 and only applies to a DaemonSet');\n }\n }\n return undefined;\n};\n\nexport type Json = Record<string, unknown>;\nexport const obj = (value: unknown): Json =>\n typeof value === 'object' && value !== null ? (value as Json) : {};\nexport const num = (value: unknown): number => (typeof value === 'number' ? value : 0);\n\n/** The numbers a pass saw, for `KubernetesReadyTimeout.states`: counts and generations only. */\nexport const countsOf = (check: ReadyCheck, body: unknown): KeyState => {\n const status = obj(obj(body)['status']);\n const names =\n check.kind === 'DaemonSet'\n ? [\n 'observedGeneration',\n 'desiredNumberScheduled',\n 'updatedNumberScheduled',\n 'numberAvailable',\n ]\n : check.kind === 'Deployment'\n ? ['observedGeneration', 'replicas', 'updatedReplicas', 'availableReplicas']\n : [];\n const counts = Object.fromEntries(names.map((name) => [name, num(status[name])]));\n return { counts: { generation: num(obj(obj(body)['metadata'])['generation']), ...counts } };\n};\nconst conditions = (status: Json): Json[] =>\n Array.isArray(status['conditions']) ? status['conditions'].map(obj) : [];\n\n/** kubectl `rollout_status.go:107-114`; `minReady` defaults to `desiredNumberScheduled`. */\nconst daemonSet = (body: Json, minReady: number | undefined): Verdict => {\n const status = obj(body['status']);\n // ⛔ kubectl checks the strategy first (`:104-106`). The apiserver defaults the type, so an\n // absent one is RollingUpdate; `OnDelete` never rolls by itself, so waiting cannot end.\n const strategy = obj(obj(body['spec'])['updateStrategy'])['type'];\n if (strategy !== undefined && strategy !== 'RollingUpdate') return 'failed';\n const generation = num(obj(body['metadata'])['generation']);\n if (num(status['observedGeneration']) < generation) return 'pending';\n const desired = num(status['desiredNumberScheduled']);\n const need = minReady ?? desired;\n // ⚠️ A DaemonSet that wants zero pods (no node matched yet) has rolled out nothing.\n if (need < 1) return 'pending';\n const updated = num(status['updatedNumberScheduled']);\n // ⛔ `numberAvailable` also counts AVAILABLE OLD-TEMPLATE pods, so comparing it to `minReady`\n // alone passes a broken upgrade: minReady 2 of 4 with two crashlooping new pods and two healthy\n // old ones reads as 2 updated and 2 available. At most `desired - updated` of the available\n // pods are old, so the difference is a LOWER BOUND on pods both updated and available (round 1,\n // finding 1; kubectl gets there by demanding updated == desired).\n const oldAtMost = Math.max(desired - updated, 0);\n return updated >= need && num(status['numberAvailable']) - oldAtMost >= need\n ? 'ready'\n : 'pending';\n};\n\n/** kubectl `rollout_status.go:75-89`. */\nconst deployment = (body: Json): Verdict => {\n const status = obj(body['status']);\n const generation = num(obj(body['metadata'])['generation']);\n if (num(status['observedGeneration']) < generation) return 'pending';\n // ★ Checked after the generation rule, as kubectl does: a deadline condition left over from\n // the previous spec must not fail a rollout the controller has not looked at yet.\n const exceeded = conditions(status).some(\n (c) => c['type'] === 'Progressing' && c['reason'] === 'ProgressDeadlineExceeded',\n );\n if (exceeded) return 'failed';\n const desired =\n obj(body['spec'])['replicas'] === undefined ? 1 : num(obj(body['spec'])['replicas']);\n const updated = num(status['updatedReplicas']);\n // kubectl rollout_status.go waits only while updated < desired, so updated >= desired is the\n // same rule (a scale-down can briefly report more updated replicas than spec.replicas).\n return updated >= desired &&\n num(status['replicas']) === updated &&\n num(status['availableReplicas']) >= updated\n ? 'ready'\n : 'pending';\n};\n\nconst established = (body: Json): Verdict =>\n conditions(obj(body['status'])).some((c) => c['type'] === 'Established' && c['status'] === 'True')\n ? 'ready'\n : 'pending';\n\nexport const evaluate = (check: ReadyCheck, body: unknown): Verdict => {\n if (typeof body !== 'object' || body === null) return 'pending';\n switch (check.kind) {\n case 'DaemonSet':\n return daemonSet(body as Json, check.minReady);\n case 'Deployment':\n return deployment(body as Json);\n case 'CustomResourceDefinition':\n return established(body as Json);\n }\n};\n",
12
+ "/**\n * Connect for `HomeFlare.Kubernetes.Ready`. `connectCluster` reads the vault and then makes the\n * cluster-identity GET (`talos/cluster-identity.ts`), so it reaches the apiserver too.\n *\n * ⛔ A SINGLE PASS (`read`/`diff`) connects once; only errors are scrubbed. Upstream's\n * `KubernetesApiError` message quotes up to 1000 bytes of the response body (`client.ts:38-42`),\n * and a 401/403/5xx on the identity GET carries it (round 1, finding 4).\n * ★ A POLL retries the connect on the poll's transients (identity timeout, 5xx, 429, upstream's\n * transport error) every `pollInterval` until the poll's own deadline: on a first install the\n * apiserver is exactly what a CNI rollout is restarting (round 1, finding 2). Vault failures,\n * 401/403, a uid mismatch and any untagged defect still propagate at once.\n */\nimport { connectCluster } from 'alchemy/Kubernetes/internal/client';\nimport type { ClusterTransport } from 'alchemy/Kubernetes/ClusterAdapter';\nimport type * as Duration from 'effect/Duration';\nimport * as Effect from 'effect/Effect';\nimport { scrubbed, transientOf } from './ready-classify.ts';\nimport type { LastTransient } from './ready-errors.ts';\n\ntype Connection = Parameters<typeof connectCluster>[0];\n\n/** Connect once. */\nexport const connectOnce = (connection: Connection) =>\n connectCluster(connection).pipe(Effect.mapError(scrubbed));\n\n/**\n * Connect once for `diff`: a transient is `undefined` (the plan says `update`, `reconcile` waits);\n * everything else fails scrubbed. ⚠️ Classified BEFORE scrubbing: the scrubbed 5xx loses its tag.\n */\nexport const connectForDiff = (connection: Connection) =>\n Effect.gen(function* () {\n const attempt = yield* Effect.result(connectCluster(connection));\n if (attempt._tag === 'Success') return attempt.success as ClusterTransport;\n return transientOf(attempt.failure) === undefined\n ? yield* Effect.fail(scrubbed(attempt.failure))\n : undefined;\n });\n\n/** Connect, retrying transients until the caller's deadline interrupts. */\nexport const connectTolerant =\n (connection: Connection, pollInterval: Duration.Duration) =>\n (note: (transient: LastTransient) => Effect.Effect<void>) =>\n Effect.gen(function* () {\n while (true) {\n const attempt = yield* Effect.result(connectCluster(connection));\n if (attempt._tag === 'Success') return attempt.success as ClusterTransport;\n const transient = transientOf(attempt.failure);\n if (transient === undefined) return yield* Effect.fail(scrubbed(attempt.failure));\n yield* note(transient);\n yield* Effect.sleep(pollInterval);\n }\n });\n",
13
+ "/**\n * Declaration-time guards for `HomeFlare.Kubernetes.Ready` (the `withLiteralConnection` shape): a\n * fresh resource with unresolved inputs skips `diff`, so anything that must be refused has to be\n * refused when the resource is DECLARED. Plain props fail the declaring Effect; a props Effect\n * fails once it yields.\n *\n * ⛔ `checks`: a check that can never be satisfied (empty name, a namespaced kind with no\n * namespace, `minReady` below 1) is a typed `KubernetesReadyBadCheck`, not a 10 minute timeout\n * (`validateChecks`, round 1 finding 3).\n * ⛔ `after`: ONLY `chart.objects` (a `PropExpr` whose identifier is `objects`) orders the row after\n * the chart's UPDATE. Any other property of the chart (`chart.connection`, `chart.releaseName`)\n * is a stable that Alchemy fills from state while the chart updates, so it resolves to a plain\n * value at plan time, Ready plans `noop` and the gate is silently skipped; a bare `chart`\n * resolves to its stables the same way (round 1 finding 5; round 2: a property-expression test,\n * not a plain-object stand-in, proved `chart.connection` slipped through). Every other shape is\n * `KubernetesReadyBadAfter`.\n */\nimport { isPropExpr } from 'alchemy/Output';\nimport * as Effect from 'effect/Effect';\nimport { validateChecks } from './ready-checks.ts';\nimport { KubernetesReadyBadAfter } from './ready-errors.ts';\n\n/** The typed refusal for the given props, or `undefined`. */\nexport const declarationRefusal = (props: unknown) => {\n const { after, checks } = (props ?? {}) as { after?: unknown; checks?: unknown };\n const badCheck = validateChecks(checks);\n if (badCheck !== undefined) return badCheck;\n if (after === undefined) return undefined;\n const list = Array.isArray(after) ? after : [after];\n const index = list.findIndex((item) => !(isPropExpr(item) && item.identifier === 'objects'));\n return index === -1 ? undefined : new KubernetesReadyBadAfter({ index });\n};\n\nexport const declaredReady = <C extends (...args: never[]) => unknown>(resource: C): C =>\n new Proxy(resource, {\n apply: (target, thisArg, args: unknown[]) => {\n const props = args[1];\n if (Effect.isEffect(props)) {\n const checked = props.pipe(\n Effect.flatMap((resolved) => {\n const refused = declarationRefusal(resolved);\n return refused === undefined ? Effect.succeed(resolved) : Effect.fail(refused);\n }),\n );\n return Reflect.apply(target, thisArg, [args[0], checked, ...args.slice(2)]);\n }\n const refused = declarationRefusal(props);\n return refused === undefined ? Reflect.apply(target, thisArg, args) : Effect.fail(refused);\n },\n });\n",
14
+ "/**\n * The poll loop of `HomeFlare.Kubernetes.Ready`: one bounded GET per check, and the deadline.\n * Reads go through alchemy's own `readObject` (TLS, CA and client-cert handling stay upstream's),\n * over the transport `connectCluster` proved against the pinned uid.\n *\n * ⛔ Error classes live in `ready-classify.ts` (404 pending; transients tolerated ONLY in a poll;\n * everything else propagates). A single pass (`tolerant: false`, used by `read`/`diff`)\n * tolerates ONLY 404. The poll covers the CONNECT too (`ready-connect.ts`): the identity GET of\n * a cluster whose apiserver is restarting under a CNI rollout is the same transient.\n * ⚠️ upstream `readObject` takes no signal: a timed-out GET ABANDONS its socket rather than\n * aborting it (`client.ts:71-74`); nothing here can close it, and process exit is NOT a bound\n * for a long apply. So a timeout is rationed: once one GET of a pass times out the rest of that\n * pass is pending without a GET, and the poll doubles its pause per consecutive timeout (cap\n * 30 s), resetting on a clean pass. A hung apiserver then abandons a few dozen sockets in a 10 m\n * wait, not ~120. ★ STANDING WATCH: drop this rationing when upstream's `readObject` takes an\n * AbortSignal (alchemy PR 1948, per-attempt deadlines).\n * ⚠️ upstream also retries a transport error itself, for about 40 s (`Schedule.max([spaced 5 s,\n * recurs(8)])`, `client.ts:158-161`): longer than `GET_TIMEOUT`, so a dead socket surfaces here\n * as the GET timeout, not as the transport error.\n */\nimport type { ClusterTransport } from 'alchemy/Kubernetes/ClusterAdapter';\nimport { readObject } from 'alchemy/Kubernetes/internal/client';\nimport * as Data from 'effect/Data';\nimport * as Duration from 'effect/Duration';\nimport * as Effect from 'effect/Effect';\nimport * as Ref from 'effect/Ref';\nimport { isNotFound, scrubbed, transientOf } from './ready-classify.ts';\nimport { type ReadyCheck, checkKey, countsOf, evaluate } from './ready-checks.ts';\nimport {\n type KeyState,\n KubernetesReadyTimeout,\n KubernetesRolloutFailed,\n type LastTransient,\n} from './ready-errors.ts';\n\nexport { KubernetesReadyApiError, isNotFound, transientOf } from './ready-classify.ts';\n\n/** Each GET is bounded like `readClusterUid` (`talos/cluster-identity.ts`). */\nexport const GET_TIMEOUT = Duration.seconds(5);\n\n/** One GET exceeded `GET_TIMEOUT`. Pending inside a poll, propagated by a single pass. */\nexport class KubernetesReadyGetTimeout extends Data.TaggedError('KubernetesReadyGetTimeout')<{\n readonly check: string;\n readonly seconds: number;\n}> {\n override get message(): string {\n return `Kubernetes.Ready: GET for ${this.check} timed out after ${this.seconds}s`;\n }\n}\n\nconst REFS = {\n CustomResourceDefinition: {\n apiVersion: 'apiextensions.k8s.io/v1',\n kind: 'CustomResourceDefinition',\n },\n DaemonSet: { apiVersion: 'apps/v1', kind: 'DaemonSet' },\n Deployment: { apiVersion: 'apps/v1', kind: 'Deployment' },\n} as const;\n\nconst refOf = (check: ReadyCheck) => ({\n ...REFS[check.kind],\n name: check.name,\n ...('namespace' in check ? { namespace: check.namespace } : {}),\n});\n\n/** Longest pause the poll backs off to after consecutive GET timeouts. */\nexport const BACKOFF_CAP = Duration.seconds(30);\n\nexport interface PassResult {\n /** Keys of checks that are not ready yet. */\n readonly pending: readonly string[];\n /** Keys of checks that are terminally failed (see `KubernetesRolloutFailed`). */\n readonly failed: readonly string[];\n /** What this pass saw per key it reached; a key that hit a transient has none. */\n readonly states: Readonly<Record<string, KeyState>>;\n readonly lastTransient?: LastTransient;\n /** A GET timed out (its socket is abandoned): the poll backs off. */\n readonly timedOut: boolean;\n}\n\nconst getOne = (transport: ClusterTransport, check: ReadyCheck) =>\n readObject({ object: refOf(check), transport }).pipe(\n Effect.timeoutOrElse({\n duration: GET_TIMEOUT,\n orElse: () =>\n Effect.fail(\n new KubernetesReadyGetTimeout({\n check: checkKey(check),\n seconds: Duration.toSeconds(GET_TIMEOUT),\n }),\n ),\n }),\n );\n\n/** One look at every check. `tolerant` selects the poll rules; see the header. */\nexport const pass = (\n transport: ClusterTransport,\n checks: readonly ReadyCheck[],\n tolerant: boolean,\n): Effect.Effect<PassResult, unknown> =>\n Effect.gen(function* () {\n const pending: string[] = [];\n const failed: string[] = [];\n const states: Record<string, KeyState> = {};\n let lastTransient: LastTransient | undefined;\n let timedOut = false;\n for (const check of checks) {\n const key = checkKey(check);\n // ⛔ After a timeout its socket is abandoned: issue no further GET this pass.\n if (timedOut) {\n pending.push(key);\n continue;\n }\n const outcome = yield* getOne(transport, check).pipe(\n Effect.map((body) => {\n states[key] = countsOf(check, body);\n return evaluate(check, body);\n }),\n Effect.catch((error: unknown) => {\n if (isNotFound(error)) {\n states[key] = { notFound: true };\n return Effect.succeed('pending' as const);\n }\n const transient = tolerant ? transientOf(error) : undefined;\n if (transient === undefined) return Effect.fail(scrubbed(error));\n lastTransient = transient;\n timedOut = timedOut || transient.tag === 'KubernetesReadyGetTimeout';\n return Effect.succeed('pending' as const);\n }),\n );\n if (outcome === 'pending') pending.push(key);\n if (outcome === 'failed') failed.push(key);\n }\n return {\n failed,\n pending,\n states,\n timedOut,\n ...(lastTransient === undefined ? {} : { lastTransient }),\n };\n });\n\n/**\n * Poll until every check is ready. A terminal failure ends it at once; otherwise the deadline\n * fails with the check keys that were pending on the LAST pass, what that pass saw of each, and\n * the transient error of the last pass only (⚠️ a transient that cleared is not to be blamed).\n * `connect` runs inside the deadline (it retries its own transients, `ready-connect.ts`).\n */\nexport const poll = <R>(\n connect: (\n note: (transient: LastTransient) => Effect.Effect<void>,\n ) => Effect.Effect<ClusterTransport, unknown, R>,\n checks: readonly ReadyCheck[],\n waitTimeout: Duration.Duration,\n pollInterval: Duration.Duration,\n) =>\n Effect.gen(function* () {\n const pendingNow = checks.map(checkKey);\n const last = yield* Ref.make<{ pending: readonly string[]; states: Record<string, KeyState> }>({\n pending: pendingNow,\n states: {},\n });\n const transient = yield* Ref.make<LastTransient | undefined>(undefined);\n const loop = Effect.gen(function* () {\n const transport = yield* connect((t) => Ref.set(transient, t));\n let pause = pollInterval;\n while (true) {\n const result = yield* pass(transport, checks, true);\n yield* Ref.update(last, (prior) => ({\n pending: result.pending,\n states: { ...prior.states, ...result.states },\n }));\n // ★ Overwritten every pass, so a transient that has since cleared is not blamed.\n yield* Ref.set(transient, result.lastTransient);\n const [firstFailed] = result.failed;\n if (firstFailed !== undefined) {\n return yield* Effect.fail(\n new KubernetesRolloutFailed({\n check: firstFailed,\n reason: firstFailed.startsWith('DaemonSet/')\n ? 'UpdateStrategyNotRollingUpdate'\n : 'ProgressDeadlineExceeded',\n }),\n );\n }\n if (result.pending.length === 0) return;\n // ★ Double per consecutive timeout (cap 30 s, or the interval if longer); a clean pass resets.\n pause = result.timedOut\n ? Duration.min(Duration.times(pause, 2), Duration.max(BACKOFF_CAP, pollInterval))\n : pollInterval;\n yield* Effect.sleep(pause);\n }\n });\n return yield* loop.pipe(\n Effect.timeoutOrElse({\n duration: waitTimeout,\n orElse: () =>\n Effect.gen(function* () {\n const { pending, states } = yield* Ref.get(last);\n const lastTransient = yield* Ref.get(transient);\n const seen = Object.fromEntries(\n pending.flatMap((k) => (states[k] ? [[k, states[k]]] : [])),\n );\n return yield* Effect.fail(\n new KubernetesReadyTimeout({\n failing: pending,\n seconds: Duration.toSeconds(waitTimeout),\n states: seen,\n ...(lastTransient === undefined ? {} : { lastTransient }),\n }),\n );\n }),\n }),\n );\n });\n"
15
+ ],
16
+ "mappings": "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",
17
+ "debugId": "2779EA87BD42448264756E2164756E21",
18
+ "names": []
19
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Readiness evaluators for `HomeFlare.Kubernetes.Ready`: pure functions over one object's JSON,
3
+ * plus the typed errors of the gate. No I/O here, so every rule is testable on a literal body.
4
+ *
5
+ * ★ ROLLOUT SEMANTICS, NOT "Available". Copied from `kubectl rollout status` (kubectl v0.34.0
6
+ * `pkg/polymorphichelpers/rollout_status.go:75-89` Deployment, `:107-114` DaemonSet), because a
7
+ * gate that only reads `Available`/`numberReady` passes on the OLD ReplicaSet or pods while a
8
+ * new generation is still rolling out (T10 design v3, red-team round 2, finding 1). The first
9
+ * rule of both is `status.observedGeneration >= metadata.generation`: until the controller has
10
+ * seen the latest spec, every count below describes a spec that no longer exists.
11
+ * ⚠️ A body that is missing or not an object is PENDING, never ready: an empty 200 must not open
12
+ * the gate.
13
+ */
14
+ import { type KeyState, KubernetesReadyBadCheck } from './ready-errors.ts';
15
+ export type ReadyCheck = {
16
+ readonly kind: 'DaemonSet';
17
+ readonly namespace: string;
18
+ readonly name: string;
19
+ readonly minReady?: number;
20
+ } | {
21
+ readonly kind: 'Deployment';
22
+ readonly namespace: string;
23
+ readonly name: string;
24
+ } | {
25
+ readonly kind: 'CustomResourceDefinition';
26
+ readonly name: string;
27
+ };
28
+ /** `failed` is terminal (kubectl reports it as an error), `pending` is "look again". */
29
+ export type Verdict = 'ready' | 'pending' | 'failed';
30
+ /** Check keys only: names are public, no body byte ever reaches a key, an error or an attribute. */
31
+ export declare const checkKey: (check: ReadyCheck) => string;
32
+ export declare const checksCsv: (checks: readonly ReadyCheck[]) => string;
33
+ /**
34
+ * Declaration-time validation (round 1, finding 3): a check that can never be satisfied is a typed
35
+ * refusal, not a 10 minute timeout. ⚠️ Upstream's path builder THROWS for a namespaced kind with an
36
+ * empty namespace (`objects.ts:189-192`), which would otherwise be read as a transport transient.
37
+ */
38
+ export declare const validateChecks: (checks: unknown) => KubernetesReadyBadCheck | undefined;
39
+ export type Json = Record<string, unknown>;
40
+ export declare const obj: (value: unknown) => Json;
41
+ export declare const num: (value: unknown) => number;
42
+ /** The numbers a pass saw, for `KubernetesReadyTimeout.states`: counts and generations only. */
43
+ export declare const countsOf: (check: ReadyCheck, body: unknown) => KeyState;
44
+ export declare const evaluate: (check: ReadyCheck, body: unknown) => Verdict;
45
+ //# sourceMappingURL=ready-checks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ready-checks.d.ts","sourceRoot":"","sources":["../../src/kubernetes/ready-checks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,KAAK,QAAQ,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AAE3E,MAAM,MAAM,UAAU,GAClB;IACE,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B,GACD;IAAE,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAClF;IAAE,QAAQ,CAAC,IAAI,EAAE,0BAA0B,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAEzE,wFAAwF;AACxF,MAAM,MAAM,OAAO,GAAG,OAAO,GAAG,SAAS,GAAG,QAAQ,CAAC;AAErD,oGAAoG;AACpG,eAAO,MAAM,QAAQ,UAAW,UAAU,KAAG,MAW5C,CAAC;AAEF,eAAO,MAAM,SAAS,WAAY,SAAS,UAAU,EAAE,KAAG,MAAwC,CAAC;AAEnG;;;;GAIG;AACH,eAAO,MAAM,cAAc,WAAY,OAAO,KAAG,uBAAuB,GAAG,SAyB1E,CAAC;AAEF,MAAM,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAC3C,eAAO,MAAM,GAAG,UAAW,OAAO,KAAG,IAC+B,CAAC;AACrE,eAAO,MAAM,GAAG,UAAW,OAAO,KAAG,MAAiD,CAAC;AAEvF,gGAAgG;AAChG,eAAO,MAAM,QAAQ,UAAW,UAAU,QAAQ,OAAO,KAAG,QAe3D,CAAC;AAyDF,eAAO,MAAM,QAAQ,UAAW,UAAU,QAAQ,OAAO,KAAG,OAU3D,CAAC"}
@@ -0,0 +1,23 @@
1
+ import type { LastTransient } from './ready-errors.ts';
2
+ export declare const isNotFound: (error: unknown) => boolean;
3
+ /** The tolerable-in-a-poll classes, as a record of tag + status; `undefined` means propagate. */
4
+ export declare const transientOf: (error: unknown) => LastTransient | undefined;
5
+ declare const KubernetesReadyApiError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
6
+ readonly _tag: "KubernetesReadyApiError";
7
+ } & Readonly<A>;
8
+ /**
9
+ * A propagated apiserver refusal (401/403/other 4xx, or any status in a single pass). ⛔ Upstream's
10
+ * `KubernetesApiError` message quotes up to 1000 bytes of the response body (`client.ts:38-42`);
11
+ * a body is the server's text, not ours to republish into logs and run output, so it is dropped.
12
+ */
13
+ export declare class KubernetesReadyApiError extends KubernetesReadyApiError_base<{
14
+ readonly method: string;
15
+ readonly path: string;
16
+ readonly statusCode: number;
17
+ }> {
18
+ get message(): string;
19
+ }
20
+ /** ⛔ Wrap every error that leaves a GET, the identity GET of connect included (round 1, finding 4). */
21
+ export declare const scrubbed: (error: unknown) => unknown;
22
+ export {};
23
+ //# sourceMappingURL=ready-classify.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ready-classify.d.ts","sourceRoot":"","sources":["../../src/kubernetes/ready-classify.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAQvD,eAAO,MAAM,UAAU,UAAW,OAAO,KAAG,OAK3C,CAAC;AAKF,iGAAiG;AACjG,eAAO,MAAM,WAAW,UAAW,OAAO,KAAG,aAAa,GAAG,SAW5D,CAAC;;;;AAEF;;;;GAIG;AACH,qBAAa,uBAAwB,SAAQ,6BAA4C;IACvF,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B,CAAC;IACA,IAAa,OAAO,IAAI,MAAM,CAE7B;CACF;AAED,uGAAuG;AACvG,eAAO,MAAM,QAAQ,UAAW,OAAO,KAAG,OASzC,CAAC"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Connect for `HomeFlare.Kubernetes.Ready`. `connectCluster` reads the vault and then makes the
3
+ * cluster-identity GET (`talos/cluster-identity.ts`), so it reaches the apiserver too.
4
+ *
5
+ * ⛔ A SINGLE PASS (`read`/`diff`) connects once; only errors are scrubbed. Upstream's
6
+ * `KubernetesApiError` message quotes up to 1000 bytes of the response body (`client.ts:38-42`),
7
+ * and a 401/403/5xx on the identity GET carries it (round 1, finding 4).
8
+ * ★ A POLL retries the connect on the poll's transients (identity timeout, 5xx, 429, upstream's
9
+ * transport error) every `pollInterval` until the poll's own deadline: on a first install the
10
+ * apiserver is exactly what a CNI rollout is restarting (round 1, finding 2). Vault failures,
11
+ * 401/403, a uid mismatch and any untagged defect still propagate at once.
12
+ */
13
+ import { connectCluster } from 'alchemy/Kubernetes/internal/client';
14
+ import type { ClusterTransport } from 'alchemy/Kubernetes/ClusterAdapter';
15
+ import type * as Duration from 'effect/Duration';
16
+ import * as Effect from 'effect/Effect';
17
+ import type { LastTransient } from './ready-errors.ts';
18
+ type Connection = Parameters<typeof connectCluster>[0];
19
+ /** Connect once. */
20
+ export declare const connectOnce: (connection: Connection) => Effect.Effect<ClusterTransport, unknown, never>;
21
+ /**
22
+ * Connect once for `diff`: a transient is `undefined` (the plan says `update`, `reconcile` waits);
23
+ * everything else fails scrubbed. ⚠️ Classified BEFORE scrubbing: the scrubbed 5xx loses its tag.
24
+ */
25
+ export declare const connectForDiff: (connection: Connection) => Effect.Effect<ClusterTransport | undefined, unknown, never>;
26
+ /** Connect, retrying transients until the caller's deadline interrupts. */
27
+ export declare const connectTolerant: (connection: Connection, pollInterval: Duration.Duration) => (note: (transient: LastTransient) => Effect.Effect<void>) => Effect.Effect<ClusterTransport, unknown, never>;
28
+ export {};
29
+ //# sourceMappingURL=ready-connect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ready-connect.d.ts","sourceRoot":"","sources":["../../src/kubernetes/ready-connect.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AACpE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AAC1E,OAAO,KAAK,KAAK,QAAQ,MAAM,iBAAiB,CAAC;AACjD,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AAExC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAEvD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC;AAEvD,oBAAoB;AACpB,eAAO,MAAM,WAAW,eAAgB,UAAU,oDACU,CAAC;AAE7D;;;GAGG;AACH,eAAO,MAAM,cAAc,eAAgB,UAAU,gEAOjD,CAAC;AAEL,2EAA2E;AAC3E,eAAO,MAAM,eAAe,eACb,UAAU,gBAAgB,QAAQ,CAAC,QAAQ,YACjD,CAAC,SAAS,EAAE,aAAa,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,oDAUpD,CAAC"}
@@ -0,0 +1,5 @@
1
+ import { KubernetesReadyBadAfter } from './ready-errors.ts';
2
+ /** The typed refusal for the given props, or `undefined`. */
3
+ export declare const declarationRefusal: (props: unknown) => KubernetesReadyBadAfter | import("./ready-errors.ts").KubernetesReadyBadCheck | undefined;
4
+ export declare const declaredReady: <C extends (...args: never[]) => unknown>(resource: C) => C;
5
+ //# sourceMappingURL=ready-declare.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ready-declare.d.ts","sourceRoot":"","sources":["../../src/kubernetes/ready-declare.ts"],"names":[],"mappings":"AAoBA,OAAO,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AAE5D,6DAA6D;AAC7D,eAAO,MAAM,kBAAkB,UAAW,OAAO,8FAQhD,CAAC;AAEF,eAAO,MAAM,aAAa,GAAI,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,OAAO,YAAY,CAAC,KAAG,CAgBjF,CAAC"}
@@ -0,0 +1,80 @@
1
+ /** What the poll recorded when a pass last tolerated a transient error: tag and status, no body. */
2
+ export interface LastTransient {
3
+ readonly tag: string;
4
+ readonly status?: number;
5
+ }
6
+ /**
7
+ * What the last pass saw of one check: the object was absent (404), or the numbers it reported.
8
+ * ★ A 404 that never resolves (a typo'd name, a chart that never creates it) must read differently
9
+ * from a slow rollout, without republishing anything the server said (round 1, finding 3).
10
+ */
11
+ export type KeyState = {
12
+ readonly notFound: true;
13
+ } | {
14
+ readonly counts: Readonly<Record<string, number>>;
15
+ };
16
+ declare const KubernetesReadyTimeout_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
17
+ readonly _tag: "KubernetesReadyTimeout";
18
+ } & Readonly<A>;
19
+ /** The gate never opened within `waitTimeout`. `failing` holds check keys only. */
20
+ export declare class KubernetesReadyTimeout extends KubernetesReadyTimeout_base<{
21
+ readonly failing: readonly string[];
22
+ readonly seconds: number;
23
+ readonly lastTransient?: LastTransient;
24
+ /** Per failing key, from the last pass that reached it: `notFound`, or the counts seen. */
25
+ readonly states?: Readonly<Record<string, KeyState>>;
26
+ }> {
27
+ get message(): string;
28
+ }
29
+ declare const KubernetesRolloutFailed_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
30
+ readonly _tag: "KubernetesRolloutFailed";
31
+ } & Readonly<A>;
32
+ /**
33
+ * A rollout that will not finish, which is not pending: a Deployment reported
34
+ * `ProgressDeadlineExceeded`, or a DaemonSet's `updateStrategy` is not `RollingUpdate` (kubectl
35
+ * `rollout_status.go:104-106` errors the same way: `OnDelete` pods never roll by themselves).
36
+ */
37
+ export declare class KubernetesRolloutFailed extends KubernetesRolloutFailed_base<{
38
+ readonly check: string;
39
+ readonly reason?: 'ProgressDeadlineExceeded' | 'UpdateStrategyNotRollingUpdate';
40
+ }> {
41
+ get message(): string;
42
+ }
43
+ declare const KubernetesReadyBadDuration_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
44
+ readonly _tag: "KubernetesReadyBadDuration";
45
+ } & Readonly<A>;
46
+ /** A `waitTimeout`/`pollInterval` that is not a Go-style duration (`10m0s`, `90s`, `1h30m`). */
47
+ export declare class KubernetesReadyBadDuration extends KubernetesReadyBadDuration_base<{
48
+ readonly field: string;
49
+ readonly value: string;
50
+ /** The resource whose prop it is; `Kubernetes.Ready` unless the HelmChart bound says otherwise. */
51
+ readonly resource?: string;
52
+ }> {
53
+ get message(): string;
54
+ }
55
+ declare const KubernetesReadyBadCheck_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
56
+ readonly _tag: "KubernetesReadyBadCheck";
57
+ } & Readonly<A>;
58
+ /** A check that can never be satisfied, refused at declaration instead of timing out in 10 m. */
59
+ export declare class KubernetesReadyBadCheck extends KubernetesReadyBadCheck_base<{
60
+ /** Index into `checks`. */
61
+ readonly index: number;
62
+ readonly problem: string;
63
+ }> {
64
+ get message(): string;
65
+ }
66
+ declare const KubernetesReadyBadAfter_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
67
+ readonly _tag: "KubernetesReadyBadAfter";
68
+ } & Readonly<A>;
69
+ /**
70
+ * `after` must hold Outputs of the chart (`chart.objects`). ⛔ A plain value, or a stable such as
71
+ * `chart.connection`, resolves at plan time, so the row would plan `noop` before the chart's
72
+ * update and the gate would be skipped.
73
+ */
74
+ export declare class KubernetesReadyBadAfter extends KubernetesReadyBadAfter_base<{
75
+ readonly index: number;
76
+ }> {
77
+ get message(): string;
78
+ }
79
+ export {};
80
+ //# sourceMappingURL=ready-errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ready-errors.d.ts","sourceRoot":"","sources":["../../src/kubernetes/ready-errors.ts"],"names":[],"mappings":"AAOA,oGAAoG;AACpG,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;;GAIG;AACH,MAAM,MAAM,QAAQ,GAChB;IAAE,QAAQ,CAAC,QAAQ,EAAE,IAAI,CAAA;CAAE,GAC3B;IAAE,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;CAAE,CAAC;;;;AAS1D,mFAAmF;AACnF,qBAAa,sBAAuB,SAAQ,4BAA2C;IACrF,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,aAAa,CAAC,EAAE,aAAa,CAAC;IACvC,2FAA2F;IAC3F,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;CACtD,CAAC;IACA,IAAa,OAAO,IAAI,MAAM,CAS7B;CACF;;;;AAED;;;;GAIG;AACH,qBAAa,uBAAwB,SAAQ,6BAA4C;IACvF,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,CAAC,EAAE,0BAA0B,GAAG,gCAAgC,CAAC;CACjF,CAAC;IACA,IAAa,OAAO,IAAI,MAAM,CAI7B;CACF;;;;AAED,gGAAgG;AAChG,qBAAa,0BAA2B,SAAQ,gCAA+C;IAC7F,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,mGAAmG;IACnG,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B,CAAC;IACA,IAAa,OAAO,IAAI,MAAM,CAI7B;CACF;;;;AAED,iGAAiG;AACjG,qBAAa,uBAAwB,SAAQ,6BAA4C;IACvF,2BAA2B;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B,CAAC;IACA,IAAa,OAAO,IAAI,MAAM,CAE7B;CACF;;;;AAED;;;;GAIG;AACH,qBAAa,uBAAwB,SAAQ,6BAA4C;IACvF,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB,CAAC;IACA,IAAa,OAAO,IAAI,MAAM,CAE7B;CACF"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * The poll loop of `HomeFlare.Kubernetes.Ready`: one bounded GET per check, and the deadline.
3
+ * Reads go through alchemy's own `readObject` (TLS, CA and client-cert handling stay upstream's),
4
+ * over the transport `connectCluster` proved against the pinned uid.
5
+ *
6
+ * ⛔ Error classes live in `ready-classify.ts` (404 pending; transients tolerated ONLY in a poll;
7
+ * everything else propagates). A single pass (`tolerant: false`, used by `read`/`diff`)
8
+ * tolerates ONLY 404. The poll covers the CONNECT too (`ready-connect.ts`): the identity GET of
9
+ * a cluster whose apiserver is restarting under a CNI rollout is the same transient.
10
+ * ⚠️ upstream `readObject` takes no signal: a timed-out GET ABANDONS its socket rather than
11
+ * aborting it (`client.ts:71-74`); nothing here can close it, and process exit is NOT a bound
12
+ * for a long apply. So a timeout is rationed: once one GET of a pass times out the rest of that
13
+ * pass is pending without a GET, and the poll doubles its pause per consecutive timeout (cap
14
+ * 30 s), resetting on a clean pass. A hung apiserver then abandons a few dozen sockets in a 10 m
15
+ * wait, not ~120. ★ STANDING WATCH: drop this rationing when upstream's `readObject` takes an
16
+ * AbortSignal (alchemy PR 1948, per-attempt deadlines).
17
+ * ⚠️ upstream also retries a transport error itself, for about 40 s (`Schedule.max([spaced 5 s,
18
+ * recurs(8)])`, `client.ts:158-161`): longer than `GET_TIMEOUT`, so a dead socket surfaces here
19
+ * as the GET timeout, not as the transport error.
20
+ */
21
+ import type { ClusterTransport } from 'alchemy/Kubernetes/ClusterAdapter';
22
+ import * as Duration from 'effect/Duration';
23
+ import * as Effect from 'effect/Effect';
24
+ import { type ReadyCheck } from './ready-checks.ts';
25
+ import { type KeyState, type LastTransient } from './ready-errors.ts';
26
+ export { KubernetesReadyApiError, isNotFound, transientOf } from './ready-classify.ts';
27
+ /** Each GET is bounded like `readClusterUid` (`talos/cluster-identity.ts`). */
28
+ export declare const GET_TIMEOUT: Duration.Duration;
29
+ declare const KubernetesReadyGetTimeout_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
30
+ readonly _tag: "KubernetesReadyGetTimeout";
31
+ } & Readonly<A>;
32
+ /** One GET exceeded `GET_TIMEOUT`. Pending inside a poll, propagated by a single pass. */
33
+ export declare class KubernetesReadyGetTimeout extends KubernetesReadyGetTimeout_base<{
34
+ readonly check: string;
35
+ readonly seconds: number;
36
+ }> {
37
+ get message(): string;
38
+ }
39
+ /** Longest pause the poll backs off to after consecutive GET timeouts. */
40
+ export declare const BACKOFF_CAP: Duration.Duration;
41
+ export interface PassResult {
42
+ /** Keys of checks that are not ready yet. */
43
+ readonly pending: readonly string[];
44
+ /** Keys of checks that are terminally failed (see `KubernetesRolloutFailed`). */
45
+ readonly failed: readonly string[];
46
+ /** What this pass saw per key it reached; a key that hit a transient has none. */
47
+ readonly states: Readonly<Record<string, KeyState>>;
48
+ readonly lastTransient?: LastTransient;
49
+ /** A GET timed out (its socket is abandoned): the poll backs off. */
50
+ readonly timedOut: boolean;
51
+ }
52
+ /** One look at every check. `tolerant` selects the poll rules; see the header. */
53
+ export declare const pass: (transport: ClusterTransport, checks: readonly ReadyCheck[], tolerant: boolean) => Effect.Effect<PassResult, unknown>;
54
+ /**
55
+ * Poll until every check is ready. A terminal failure ends it at once; otherwise the deadline
56
+ * fails with the check keys that were pending on the LAST pass, what that pass saw of each, and
57
+ * the transient error of the last pass only (⚠️ a transient that cleared is not to be blamed).
58
+ * `connect` runs inside the deadline (it retries its own transients, `ready-connect.ts`).
59
+ */
60
+ export declare const poll: <R>(connect: (note: (transient: LastTransient) => Effect.Effect<void>) => Effect.Effect<ClusterTransport, unknown, R>, checks: readonly ReadyCheck[], waitTimeout: Duration.Duration, pollInterval: Duration.Duration) => Effect.Effect<undefined, unknown, R>;
61
+ //# sourceMappingURL=ready-poll.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ready-poll.d.ts","sourceRoot":"","sources":["../../src/kubernetes/ready-poll.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AAG1E,OAAO,KAAK,QAAQ,MAAM,iBAAiB,CAAC;AAC5C,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AAGxC,OAAO,EAAE,KAAK,UAAU,EAAgC,MAAM,mBAAmB,CAAC;AAClF,OAAO,EACL,KAAK,QAAQ,EAGb,KAAK,aAAa,EACnB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAE,uBAAuB,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAEvF,+EAA+E;AAC/E,eAAO,MAAM,WAAW,mBAAsB,CAAC;;;;AAE/C,0FAA0F;AAC1F,qBAAa,yBAA0B,SAAQ,+BAA8C;IAC3F,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B,CAAC;IACA,IAAa,OAAO,IAAI,MAAM,CAE7B;CACF;AAiBD,0EAA0E;AAC1E,eAAO,MAAM,WAAW,mBAAuB,CAAC;AAEhD,MAAM,WAAW,UAAU;IACzB,6CAA6C;IAC7C,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,iFAAiF;IACjF,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,kFAAkF;IAClF,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;IACpD,QAAQ,CAAC,aAAa,CAAC,EAAE,aAAa,CAAC;IACvC,qEAAqE;IACrE,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;CAC5B;AAgBD,kFAAkF;AAClF,eAAO,MAAM,IAAI,cACJ,gBAAgB,UACnB,SAAS,UAAU,EAAE,YACnB,OAAO,KAChB,MAAM,CAAC,MAAM,CAAC,UAAU,EAAE,OAAO,CAyChC,CAAC;AAEL;;;;;GAKG;AACH,eAAO,MAAM,IAAI,GAAI,CAAC,WACX,CACP,IAAI,EAAE,CAAC,SAAS,EAAE,aAAa,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,KACpD,MAAM,CAAC,MAAM,CAAC,gBAAgB,EAAE,OAAO,EAAE,CAAC,CAAC,UACxC,SAAS,UAAU,EAAE,eAChB,QAAQ,CAAC,QAAQ,gBAChB,QAAQ,CAAC,QAAQ,yCA4D7B,CAAC"}
@@ -0,0 +1,74 @@
1
+ /**
2
+ * `HomeFlare.Kubernetes.Ready` — a readiness gate other rows depend on via `after`/Output edges.
3
+ *
4
+ * ★ WHY IT EXISTS. Upstream ships no readiness resource: `Kubernetes.HelmChart` reconcile returns
5
+ * when server-side apply returns, and `Apply` releases the downstream rows at that moment, so a
6
+ * row chained after the Cilium chart would start before one Cilium pod runs (T10 design v3 §2).
7
+ * ★ SEMANTICS are `kubectl rollout status` (`ready-checks.ts`), the polling rules are in
8
+ * `ready-poll.ts`. Reconcile polls to `waitTimeout`; `read`/`diff` take ONE pass, so a plan is
9
+ * live (like `Talos.ClusterHealth`) but never waits.
10
+ * ⛔ `connection` IS A LITERAL `talos-openbao` CONNECTION, refused otherwise at declaration and in
11
+ * `diff` (`literal-connection.ts`), and it is copied onto the attributes so this row is
12
+ * `ClusterLike` (`stables`, as upstream's `HelmChart`). It carries a uid, never a credential.
13
+ * ⛔ NO SECRET REACHES ANYTHING THIS ROW WRITES: errors and attributes name check keys, counts and
14
+ * an error tag + status only, never a response body.
15
+ * ⚠️ `delete` is a no-op and `list` is empty: the row owns nothing in the cluster.
16
+ *
17
+ * Walked against alchemy@2.0.0-beta.81 (`Kubernetes/internal/client.ts` `readObject`,
18
+ * `KubernetesApiError`); only the fake apiserver was used. Not exercised against a live cluster.
19
+ */
20
+ import { Resource } from 'alchemy';
21
+ import type { Input } from 'alchemy/Input';
22
+ import type { Output } from 'alchemy/Output';
23
+ import * as Provider from 'alchemy/Provider';
24
+ import * as Duration from 'effect/Duration';
25
+ import * as Effect from 'effect/Effect';
26
+ import type { TalosOpenBaoConnection } from '../talos/cluster-adapter.ts';
27
+ import type { TalosRequirements } from '../talos/resource.ts';
28
+ import { type ReadyCheck } from './ready-checks.ts';
29
+ import { KubernetesReadyBadDuration } from './ready-errors.ts';
30
+ export interface ReadyProps {
31
+ /** Literal `talos-openbao` connection (uid proven at connect). */
32
+ connection: TalosOpenBaoConnection;
33
+ checks: readonly ReadyCheck[];
34
+ /** Reconcile deadline, Go style. Default `10m0s`. */
35
+ waitTimeout?: string;
36
+ /** Pause between passes, Go style. Default `10s`. */
37
+ pollInterval?: string;
38
+ /**
39
+ * Ordering edge: lazy Outputs of the chart, e.g. `[chart.objects]`. ⛔ Not `chart.connection` or
40
+ * any other stable: those resolve at plan time and the row would plan `noop` before the chart
41
+ * updates (refused at declaration, `ready-declare.ts`).
42
+ */
43
+ after?: readonly Output<unknown>[];
44
+ }
45
+ export interface ReadyAttributes {
46
+ connection: TalosOpenBaoConnection;
47
+ ready: boolean;
48
+ /** The check keys, comma separated: a changed list is an `update`. */
49
+ checks: string;
50
+ }
51
+ export interface KubernetesReady extends Resource<'HomeFlare.Kubernetes.Ready', ReadyProps, ReadyAttributes, never, TalosRequirements> {
52
+ }
53
+ export declare const KubernetesReady: import("alchemy").ResourceClass<KubernetesReady>;
54
+ /**
55
+ * `10m0s`, `90s`, `1h30m`: whole-number Go durations in h/m/s. Anything else is typed-refused.
56
+ * `resource` names the row in the refusal (`Kubernetes.Ready` unless the caller is another one).
57
+ */
58
+ export declare const parseGoDuration: (field: string, value: string, resource?: string) => Effect.Effect<Duration.Duration, KubernetesReadyBadDuration, never>;
59
+ /** One pass: `ready` only when every check is ready. A terminal failure is not ready either. */
60
+ export declare const readReady: (props: ReadyProps) => Effect.Effect<ReadyAttributes, unknown, never>;
61
+ export declare const reconcileReady: (props: ReadyProps) => Effect.Effect<ReadyAttributes, unknown, never>;
62
+ export declare const diffReady: (news: Input<ReadyProps>, output: ReadyAttributes | undefined) => Effect.Effect<{
63
+ readonly action: 'replace';
64
+ } | {
65
+ readonly action: 'noop';
66
+ } | {
67
+ readonly action: 'update';
68
+ } | undefined, unknown, never>;
69
+ export declare const readHandler: ({ olds, output, }: {
70
+ olds: ReadyProps;
71
+ output: ReadyAttributes | undefined;
72
+ }) => Effect.Effect<undefined, never, never> | Effect.Effect<ReadyAttributes, unknown, never>;
73
+ export declare const KubernetesReadyProvider: () => import("effect/Layer").Layer<Provider.Provider<KubernetesReady>, never, never>;
74
+ //# sourceMappingURL=ready.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ready.d.ts","sourceRoot":"","sources":["../../src/kubernetes/ready.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAEnC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAC7C,OAAO,KAAK,QAAQ,MAAM,iBAAiB,CAAC;AAC5C,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAE1E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,KAAK,UAAU,EAAa,MAAM,mBAAmB,CAAC;AAG/D,OAAO,EAAE,0BAA0B,EAAE,MAAM,mBAAmB,CAAC;AAG/D,MAAM,WAAW,UAAU;IACzB,kEAAkE;IAClE,UAAU,EAAE,sBAAsB,CAAC;IACnC,MAAM,EAAE,SAAS,UAAU,EAAE,CAAC;IAC9B,qDAAqD;IACrD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,qDAAqD;IACrD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,KAAK,CAAC,EAAE,SAAS,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;CACpC;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,sBAAsB,CAAC;IACnC,KAAK,EAAE,OAAO,CAAC;IACf,sEAAsE;IACtE,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,eAAgB,SAAQ,QAAQ,CAC/C,4BAA4B,EAC5B,UAAU,EACV,eAAe,EACf,KAAK,EACL,iBAAiB,CAClB;CAAG;AAGJ,eAAO,MAAM,eAAe,kDAE3B,CAAC;AAYF;;;GAGG;AACH,eAAO,MAAM,eAAe,UAAW,MAAM,SAAS,MAAM,aAAa,MAAM,wEAkB3E,CAAC;AAcL,gGAAgG;AAChG,eAAO,MAAM,SAAS,UAAW,UAAU,mDAKvC,CAAC;AAgBL,eAAO,MAAM,cAAc,UAAW,UAAU,mDAO5C,CAAC;AAEL,eAAO,MAAM,SAAS,SAAU,KAAK,CAAC,UAAU,CAAC,UAAU,eAAe,GAAG,SAAS;qBAUR,SAAS;;qBAGpE,MAAM;;qBACN,QAAQ;8BACvB,CAAC;AAEL,eAAO,MAAM,WAAW,sBAGrB;IACD,IAAI,EAAE,UAAU,CAAC;IACjB,MAAM,EAAE,eAAe,GAAG,SAAS,CAAC;CACrC,4FAAyE,CAAC;AAe3E,eAAO,MAAM,uBAAuB,sFACqD,CAAC"}
@@ -1,8 +1,17 @@
1
1
  /**
2
2
  * `uids` maps an apiserver hostname to the kube-system uid it reports; `hang` lists hostnames that
3
- * accept the request and never answer.
3
+ * accept the request and never answer. `objects` (K1, `HomeFlare.Kubernetes.Ready`) answers a
4
+ * `'GET host /path'` key with its `status` and JSON `body`; `hang: true` accepts and never
5
+ * answers, `error` fails the request like a socket error. The table is read per request, so a
6
+ * test may mutate it between polls.
4
7
  */
5
- export declare const fakeApiServer: (uids: Readonly<Record<string, string>>, hang?: readonly string[]) => {
8
+ export type FakeObject = {
9
+ readonly status?: number;
10
+ readonly body?: unknown;
11
+ readonly hang?: boolean;
12
+ readonly error?: string;
13
+ };
14
+ export declare const fakeApiServer: (uids: Readonly<Record<string, string>>, hang?: readonly string[], objects?: Record<string, FakeObject>) => {
6
15
  restore: () => void | Promise<void>;
7
16
  seen: string[];
8
17
  };
@@ -1 +1 @@
1
- {"version":3,"file":"fake-apiserver.d.ts","sourceRoot":"","sources":["../../src/talos/fake-apiserver.ts"],"names":[],"mappings":"AAkBA;;;GAGG;AACH,eAAO,MAAM,aAAa,SAClB,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,SAChC,SAAS,MAAM,EAAE;;;CA8BxB,CAAC"}
1
+ {"version":3,"file":"fake-apiserver.d.ts","sourceRoot":"","sources":["../../src/talos/fake-apiserver.ts"],"names":[],"mappings":"AAkBA;;;;;;GAMG;AACH,MAAM,MAAM,UAAU,GAAG;IACvB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,eAAO,MAAM,aAAa,SAClB,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,SAChC,SAAS,MAAM,EAAE,YACd,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC;;;CAqCpC,CAAC"}