@wtfalch/foundry 0.19.1 → 0.21.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 (49) hide show
  1. package/dist/bootstrap/declare.d.ts +13 -0
  2. package/dist/bootstrap/declare.js +31 -7
  3. package/dist/bootstrap/versions.d.ts +1 -0
  4. package/dist/index.d.ts +7 -3
  5. package/dist/index.js +4 -2
  6. package/dist/migrations/0046_platform_hc.sql +19 -0
  7. package/dist/plan/cloudflared-connector.d.ts +3 -2
  8. package/dist/plan/cloudflared-connector.js +2 -2
  9. package/dist/plan/deploy-health.js +5 -1
  10. package/dist/plan/diff.js +27 -4
  11. package/dist/plan/edge-access.d.ts +68 -0
  12. package/dist/plan/edge-access.js +123 -0
  13. package/dist/plan/edge-dns.d.ts +89 -0
  14. package/dist/plan/edge-dns.js +227 -0
  15. package/dist/plan/edge-ratelimits.d.ts +54 -0
  16. package/dist/plan/edge-ratelimits.js +79 -0
  17. package/dist/plan/edge-tunnels.d.ts +44 -0
  18. package/dist/plan/edge-tunnels.js +108 -0
  19. package/dist/plan/edge-workers.d.ts +31 -0
  20. package/dist/plan/edge-workers.js +165 -0
  21. package/dist/plan/edge-zones.d.ts +37 -0
  22. package/dist/plan/edge-zones.js +64 -0
  23. package/dist/plan/entry-edge-key.d.ts +10 -0
  24. package/dist/plan/entry-edge-key.js +38 -8
  25. package/dist/plan/executor.d.ts +146 -143
  26. package/dist/plan/executor.js +370 -115
  27. package/dist/plan/health-check-settings.d.ts +47 -0
  28. package/dist/plan/health-check-settings.js +47 -0
  29. package/dist/plan/health-targets.d.ts +86 -0
  30. package/dist/plan/health-targets.js +344 -0
  31. package/dist/plan/index.d.ts +17 -3
  32. package/dist/plan/index.js +9 -2
  33. package/dist/plan/live.d.ts +12 -1
  34. package/dist/plan/plan-apply.d.ts +4 -0
  35. package/dist/plan/plan-apply.js +5 -1
  36. package/dist/plan/platform-diff.d.ts +127 -0
  37. package/dist/plan/platform-diff.js +304 -8
  38. package/dist/plan/platform-providers.d.ts +202 -7
  39. package/dist/plan/platform-providers.js +545 -79
  40. package/dist/plan/providers-readers.d.ts +23 -1
  41. package/dist/plan/providers-readers.js +61 -1
  42. package/dist/plan/rollout.d.ts +9 -8
  43. package/dist/platform.d.ts +14 -1
  44. package/dist/platform.js +1 -0
  45. package/dist/store/postgres.js +8 -0
  46. package/dist/store/schema.d.ts +51 -0
  47. package/dist/store/schema.js +4 -0
  48. package/package.json +2 -1
  49. package/versions.json +2 -1
@@ -3,6 +3,9 @@ import type { DeclaredOrg } from '../org.js';
3
3
  import type { DeclaredPlatform } from '../platform.js';
4
4
  import type { DeclaredOrgStore, PlatformStore } from '../store.js';
5
5
  import type { StepAction } from './diff.js';
6
+ import type { AccessPort } from './edge-access.js';
7
+ import type { DnsPort } from './edge-dns.js';
8
+ import type { TunnelPort } from './edge-tunnels.js';
6
9
  import type { CloudflarePort, CoolifyPort, FoundryStepExecutorPorts, ZitadelPort } from './executor.js';
7
10
  import type { LiveResource, PlatformCloudflareReader, PlatformCoolifyReader } from './live.js';
8
11
  import type { PlatformStepExecutionContext, PlatformStepExecutor } from './plan-apply.js';
@@ -14,6 +17,34 @@ import { type CallsSfuPlatformDeps } from './platform-sfu.js';
14
17
  * positions, so every #167-era caller of {@link FoundryPlatformStepExecutor}
15
18
  * (zone only) keeps compiling with this third argument simply omitted.
16
19
  */
20
+ /**
21
+ * F4.dns: builds the {@link DnsPort} a platform step writes its records
22
+ * through, given the address edge answers at on the Coolify network
23
+ * ({@link PLATFORM_EDGE_INTERNAL_URL_SCOPE_KEY}). The host builds it with
24
+ * `createEdgeDns(createEdgeClient({ baseUrl, key }).platform.dns)`, `key`
25
+ * being the platform key held under {@link PLATFORM_EDGE_KEY_REF}.
26
+ */
27
+ export type PlatformDnsFactory = (edgeUrl: string) => DnsPort | Promise<DnsPort>;
28
+ /**
29
+ * F4.tunnels: builds the {@link TunnelPort} a platform step creates, configures
30
+ * and reads the connector token of its tunnel through, given the same address
31
+ * as {@link PlatformDnsFactory}. The host builds it with
32
+ * `createEdgeTunnels(createEdgeClient({ baseUrl, key }).platform.tunnels)`,
33
+ * `key` being the platform key held under {@link PLATFORM_EDGE_KEY_REF}.
34
+ */
35
+ export type PlatformTunnelsFactory = (edgeUrl: string) => PlatformTunnelPort | Promise<PlatformTunnelPort>;
36
+ /** The tunnel calls a platform step makes; it never deletes one. */
37
+ export type PlatformTunnelPort = Pick<TunnelPort, 'findTunnelByName' | 'createTunnel' | 'putTunnelConfig' | 'getTunnelToken'>;
38
+ /**
39
+ * F4.access: builds the {@link AccessPort} a platform step finds, creates and
40
+ * deletes its Access app through, given the same address as
41
+ * {@link PlatformDnsFactory}. The host builds it with
42
+ * `createEdgeAccess(createEdgeClient({ baseUrl, key }).platform.access)`,
43
+ * `key` being the platform key held under {@link PLATFORM_EDGE_KEY_REF}.
44
+ */
45
+ export type PlatformAccessFactory = (edgeUrl: string) => PlatformAccessPort | Promise<PlatformAccessPort>;
46
+ /** The Access calls a platform step makes. */
47
+ export type PlatformAccessPort = Pick<AccessPort, 'listAccessApps' | 'createAccessApp' | 'listAccessPolicies' | 'createAccessPolicy' | 'deleteAccessApp'>;
17
48
  /**
18
49
  * The SMTP call `provisionPlatformAuth` makes on the instance's ZITADEL.
19
50
  * `@wtfalch/providers` gains it in 0.7.2; stated here so foundry compiles
@@ -35,9 +66,11 @@ export interface PlatformZitadelSmtp {
35
66
  }
36
67
  export interface PlatformAuthDeps {
37
68
  coolify: Pick<CoolifyPort, 'findProjectByName' | 'createProject' | 'findOrCreateEnvironment' | 'listServers' | 'findApplicationByName' | 'getApplication' | 'requireGithubApp' | 'createGithubAppApplication' | 'createDockerImageApplication' | 'createDockerfileApplication' | 'createPostgresDatabase' | 'getDatabase' | 'bulkUpsertEnvs' | 'listEnvs' | 'updateApplication' | 'deploy' | 'raw'>;
38
- cloudflare: Pick<CloudflarePort, 'findZoneByApex' | 'findTunnelByName' | 'createTunnel' | 'putTunnelConfig' | 'findDnsRecord' | 'createDnsRecord' | 'updateDnsRecord'
39
- /** #197: fetches the tunnel's connector token for {@link ensureCloudflaredConnector}. */
40
- | 'getTunnelToken'>;
69
+ /** F4.dns: every DNS record call, through edge at its recorded address (see {@link PlatformDnsFactory}). */
70
+ dns: PlatformDnsFactory;
71
+ /** F4.tunnels: every tunnel call, through edge at its recorded address (see {@link PlatformTunnelsFactory}). */
72
+ tunnels: PlatformTunnelsFactory;
73
+ cloudflare: Pick<CloudflarePort, 'findZoneByApex'>;
41
74
  /** Must target the platform's own new instance (its issuer), or `zitadelForInstance` must be set. */
42
75
  zitadel: Pick<ZitadelPort, 'findOrCreateOrg' | 'mintAdminMachineUser' | 'mintLoginClientMachineUser' | 'listLoginClientPats' | 'inviteOrgOwner'> & PlatformZitadelSmtp;
43
76
  /**
@@ -149,6 +182,18 @@ export interface FoundryPlatformStepExecutorOptions {
149
182
  * planned.
150
183
  */
151
184
  backup?: PlatformBackupDeps;
185
+ /**
186
+ * #540: dependencies for `coolify:app` keyed {@link PLATFORM_HC_KEY} --
187
+ * optional for the same reason as `auth`; a host that declares
188
+ * `platform.hc.enabled` without it gets the step reported as blocked.
189
+ */
190
+ hc?: PlatformHcDeps;
191
+ /**
192
+ * #540: dependencies for the `heartbeats:reconcile` step -- optional for
193
+ * the same reason as `auth`; `hc` applied with this unset gets the
194
+ * heartbeat checks reported as blocked rather than planned.
195
+ */
196
+ heartbeats?: PlatformHeartbeatsDeps;
152
197
  }
153
198
  /**
154
199
  * The real {@link PlatformStepExecutor} for the two steps built so far:
@@ -228,6 +273,8 @@ export declare class FoundryPlatformStepExecutor implements PlatformStepExecutor
228
273
  private readonly edge?;
229
274
  private readonly archon?;
230
275
  private readonly backup?;
276
+ private readonly hc?;
277
+ private readonly heartbeats?;
231
278
  constructor(cloudflare: Pick<CloudflarePort, 'findZoneByApex' | 'createZone'>, declaredOrgStore: Pick<DeclaredOrgStore, 'findOrgIdForZone' | 'list' | 'get'>, options?: FoundryPlatformStepExecutorOptions);
232
279
  blockedSteps(platform: DeclaredPlatform, recordedIds?: Readonly<Record<string, string>>): Promise<{
233
280
  auth?: string;
@@ -240,6 +287,9 @@ export declare class FoundryPlatformStepExecutor implements PlatformStepExecutor
240
287
  archon?: string;
241
288
  mail?: string;
242
289
  backupExecutorMissing?: boolean;
290
+ hc?: string;
291
+ heartbeatsExecutorMissing?: boolean;
292
+ heartbeatsApiKeyMissing?: boolean;
243
293
  }>;
244
294
  /**
245
295
  * Whether Archon's live Access policy lets through anyone but the declared
@@ -377,7 +427,11 @@ export declare function defaultStalwartForInstance(input: {
377
427
  */
378
428
  export interface PlatformMailDeps {
379
429
  coolify: Pick<CoolifyPort, 'findProjectByName' | 'createProject' | 'findOrCreateEnvironment' | 'listServers' | 'findApplicationByName' | 'getApplication' | 'createDockerImageApplication' | 'createDockerfileApplication' | 'raw' | 'listStorages' | 'createPersistentStorage' | 'bulkUpsertEnvs' | 'listEnvs' | 'updateApplication' | 'deploy' | 'deployAndWait'>;
380
- cloudflare: Pick<CloudflarePort, 'findZoneByApex' | 'findTunnelByName' | 'createTunnel' | 'putTunnelConfig' | 'getTunnelToken' | 'findDnsRecord' | 'createDnsRecord' | 'updateDnsRecord'>;
430
+ /** F4.dns: every DNS record call, through edge at its recorded address (see {@link PlatformDnsFactory}). */
431
+ dns: PlatformDnsFactory;
432
+ /** F4.tunnels: every tunnel call, through edge at its recorded address (see {@link PlatformTunnelsFactory}). */
433
+ tunnels: PlatformTunnelsFactory;
434
+ cloudflare: Pick<CloudflarePort, 'findZoneByApex'>;
381
435
  /**
382
436
  * Must target the platform's own ZITADEL instance, or `zitadelForInstance`
383
437
  * must be set. `provisionPlatformMail` finds or creates a `mail` project
@@ -421,7 +475,13 @@ export interface PlatformMailRuntime {
421
475
  */
422
476
  export interface PlatformArchonDeps {
423
477
  coolify: Pick<CoolifyPort, 'findProjectByName' | 'createProject' | 'findOrCreateEnvironment' | 'listServers' | 'findApplicationByName' | 'getApplication' | 'requireGithubApp' | 'createGithubAppApplication' | 'createDockerfileApplication' | 'listDatabases' | 'createPostgresDatabase' | 'getDatabase' | 'raw' | 'bulkUpsertEnvs' | 'listEnvs' | 'deleteEnv' | 'updateApplication' | 'deploy' | 'deployAndWait'>;
424
- cloudflare: Pick<CloudflarePort, 'findZoneByApex' | 'findTunnelByName' | 'createTunnel' | 'putTunnelConfig' | 'getTunnelToken' | 'findDnsRecord' | 'createDnsRecord' | 'updateDnsRecord' | 'listAccessApps' | 'createAccessApp' | 'listAccessPolicies' | 'createAccessPolicy' | 'deleteAccessApp'>;
478
+ /** F4.dns: every DNS record call, through edge at its recorded address (see {@link PlatformDnsFactory}). */
479
+ dns: PlatformDnsFactory;
480
+ /** F4.tunnels: every tunnel call, through edge at its recorded address (see {@link PlatformTunnelsFactory}). */
481
+ tunnels: PlatformTunnelsFactory;
482
+ /** F4.access: every Access app call, through edge at its recorded address (see {@link PlatformAccessFactory}). */
483
+ access: PlatformAccessFactory;
484
+ cloudflare: Pick<CloudflarePort, 'findZoneByApex'>;
425
485
  /** Must target the platform's own ZITADEL instance, or `zitadelForInstance` must be set. */
426
486
  zitadel: Pick<ZitadelPort, 'findOrCreateOrg' | 'findOrCreateProject' | 'createOidcApp' | 'listApps' | 'deleteApp'>;
427
487
  /** {@link PlatformAuthDeps.zitadelForInstance}, for Archon's sign-in client. */
@@ -633,6 +693,17 @@ export interface PlatformAiRuntime {
633
693
  migrateIntervalMs?: number;
634
694
  /** Tries at handing a secret to the host (default 3): the management key and the signing key both use this. */
635
695
  deliverAttempts?: number;
696
+ /**
697
+ * #543: injected for tests, against a fake runner host -- the runner's own
698
+ * deploy wait now does a `'ready'` probe of its `/api/health`, the same
699
+ * `DeployHealthRuntime.fetch` field `provisionPlatformMail`/
700
+ * `provisionPlatformArchon` already take. Defaults to the global `fetch`.
701
+ */
702
+ fetch?: typeof fetch;
703
+ /** How long to wait for the runner to answer ready (default 5 minutes). */
704
+ readyTimeoutMs?: number;
705
+ /** Pause between readiness polls (default 5 seconds). */
706
+ readyIntervalMs?: number;
636
707
  }
637
708
  /** The ref the AI management key is delivered under; the host's `AiOrganisationPort` adapter reads it back. */
638
709
  export declare const PLATFORM_AI_MANAGEMENT_KEY_REF = "ai:platform:managementKey";
@@ -806,6 +877,17 @@ export interface PlatformFilesRuntime {
806
877
  migrateIntervalMs?: number;
807
878
  /** Tries at handing each secret to the host (default 3). */
808
879
  deliverAttempts?: number;
880
+ /**
881
+ * #543: injected for tests, against a fake runner host -- the runner's own
882
+ * deploy wait now does a `'ready'` probe of its `/api/health`, same as
883
+ * `provisionPlatformMail`/`provisionPlatformArchon` already take. Defaults
884
+ * to the global `fetch`.
885
+ */
886
+ fetch?: typeof fetch;
887
+ /** How long to wait for the runner to answer ready (default 5 minutes). */
888
+ readyTimeoutMs?: number;
889
+ /** Pause between readiness polls (default 5 seconds). */
890
+ readyIntervalMs?: number;
809
891
  }
810
892
  /**
811
893
  * #171: how the host signs a files management request with the key this step
@@ -834,7 +916,11 @@ export interface PlatformEdgeRuntime {
834
916
  }
835
917
  export interface PlatformEdgeDeps {
836
918
  coolify: Pick<CoolifyPort, 'findProjectByName' | 'createProject' | 'findOrCreateEnvironment' | 'listServers' | 'findApplicationByName' | 'getApplication' | 'requireGithubApp' | 'createGithubAppApplication' | 'createDockerfileApplication' | 'listDatabases' | 'createPostgresDatabase' | 'getDatabase' | 'bulkUpsertEnvs' | 'listEnvs' | 'updateApplication' | 'deploy' | 'deployAndWait' | 'raw'>;
837
- cloudflare: Pick<CloudflarePort, 'findZoneByApex' | 'findTunnelByName' | 'createTunnel' | 'putTunnelConfig' | 'getTunnelToken' | 'findDnsRecord' | 'createDnsRecord' | 'updateDnsRecord'>;
919
+ /** F4.dns: every DNS record call, through edge at its recorded address (see {@link PlatformDnsFactory}). */
920
+ dns: PlatformDnsFactory;
921
+ /** F4.tunnels: every tunnel call, through edge at its recorded address (see {@link PlatformTunnelsFactory}). */
922
+ tunnels: PlatformTunnelsFactory;
923
+ cloudflare: Pick<CloudflarePort, 'findZoneByApex'>;
838
924
  deliverSecret: FoundryStepExecutorPorts['deliverSecret'];
839
925
  /**
840
926
  * Reads the edge service's own Cloudflare API token back, held under
@@ -856,6 +942,30 @@ export interface PlatformEdgeDeps {
856
942
  /** Clock, sleep and fetch for the readiness wait and delivery retries; real ones by default. */
857
943
  runtime?: PlatformEdgeRuntime;
858
944
  }
945
+ export interface PlatformHcRuntime {
946
+ fetch?: typeof fetch;
947
+ sleep?: (ms: number) => Promise<void>;
948
+ now?: () => number;
949
+ readyTimeoutMs?: number;
950
+ readyIntervalMs?: number;
951
+ }
952
+ /**
953
+ * #540: dependencies for `coolify:app` keyed {@link PLATFORM_HC_KEY} -- the
954
+ * {@link PlatformEdgeDeps} bundling shape minus a Cloudflare token of its
955
+ * own: `hc` writes its tunnel and DNS record through edge the same way
956
+ * mail and archon do, and needs no `heldKeys`/`deliverSecret` at all --
957
+ * `SECRET_KEY` (Django's session-signing key) is generated once by
958
+ * {@link provisionPlatformHc} itself and never leaves this Coolify app.
959
+ */
960
+ export interface PlatformHcDeps {
961
+ coolify: Pick<CoolifyPort, 'findProjectByName' | 'createProject' | 'findOrCreateEnvironment' | 'listServers' | 'findApplicationByName' | 'getApplication' | 'createDockerImageApplication' | 'createDockerfileApplication' | 'listDatabases' | 'createPostgresDatabase' | 'getDatabase' | 'bulkUpsertEnvs' | 'listEnvs' | 'updateApplication' | 'deploy' | 'deployAndWait' | 'raw'>;
962
+ /** F4.dns: every DNS record call, through edge at its recorded address (see {@link PlatformDnsFactory}). */
963
+ dns: PlatformDnsFactory;
964
+ /** F4.tunnels: every tunnel call, through edge at its recorded address (see {@link PlatformTunnelsFactory}). */
965
+ tunnels: PlatformTunnelsFactory;
966
+ /** Clock, sleep and fetch for the readiness wait; real ones by default. */
967
+ runtime?: PlatformHcRuntime;
968
+ }
859
969
  /**
860
970
  * #174: everything {@link provisionPlatformIntegrations} needs beyond
861
971
  * {@link PlatformStepExecutionContext} -- a bundle, like
@@ -930,6 +1040,91 @@ export interface PlatformBackupDeps {
930
1040
  /** A cron string or one of Coolify's names (`daily`, `weekly`, ...). Default `'daily'`. */
931
1041
  frequency?: string;
932
1042
  }
1043
+ /** One check as self-hosted Healthchecks' own Management API reports it. */
1044
+ export interface HeartbeatCheck {
1045
+ id: string;
1046
+ name: string;
1047
+ pingUrl: string;
1048
+ }
1049
+ /**
1050
+ * #540: the heartbeat server's own Management API -- a project API key in a
1051
+ * header, `POST /api/v3/checks/` with `name`/`unique: ['name']` to upsert a
1052
+ * check idempotently by name, `GET /api/v3/checks/` to list every one,
1053
+ * `DELETE /api/v3/checks/<uuid>` to remove one -- restated here as the port
1054
+ * `provisionPlatformHeartbeats` plans and provisions against, never a real
1055
+ * HTTP client: this package never connects itself (`README.md`), so the
1056
+ * host implements this against its own running `hc` instance and its own
1057
+ * project API key, the same seam every other real I/O in this file crosses.
1058
+ */
1059
+ export interface HeartbeatsPort {
1060
+ listChecks(): Promise<HeartbeatCheck[]>;
1061
+ /** Creates a check named `name`, or returns the existing one of that name -- the Management API's own upsert. */
1062
+ createCheck(name: string): Promise<HeartbeatCheck>;
1063
+ /**
1064
+ * #540 round 2: pauses a check (`POST /api/v3/checks/<uuid>/pause/`) so it
1065
+ * reads "paused" rather than "down" while nothing actually pings it yet --
1066
+ * both the uptime Worker's own check and every backup check (see this
1067
+ * file's own doc comment on `PlatformHeartbeatsDeps` for why). Sending a
1068
+ * check a ping resumes it automatically (Healthchecks' own documented
1069
+ * behaviour), so foundry never has to call the reverse of this: once a
1070
+ * real pinger exists, the check un-pauses itself on its first ping.
1071
+ * `provisionPlatformHeartbeats` calls this exactly once, right after
1072
+ * `createCheck`, and never again on a check it merely finds already live
1073
+ * -- a check already being pinged, or one an operator unpaused by hand,
1074
+ * must never be paused again by a reconcile run.
1075
+ */
1076
+ pauseCheck(id: string): Promise<void>;
1077
+ deleteCheck(id: string): Promise<void>;
1078
+ }
1079
+ /**
1080
+ * #540: dependencies for the `heartbeats:reconcile` step
1081
+ * (`platform-diff.ts`'s `planHeartbeats`).
1082
+ *
1083
+ * #540 round 2: `resolve` is new. It guards only the *delete* half of
1084
+ * {@link provisionPlatformHeartbeats} -- a live check is only ever deleted
1085
+ * when foundry itself holds a ping URL for its exact name
1086
+ * (`heartbeatCheckUrlRef`, `platform-diff.ts`), proof this run or an
1087
+ * earlier one created it. Without that guard, a hand-made check sharing
1088
+ * foundry's naming scheme (an operator's own `uptime-worker`, say, made
1089
+ * before foundry ever ran) could be deleted the moment that name was not
1090
+ * yet desired -- a backup service with no recorded database yet is in
1091
+ * `provisionPlatformHeartbeats`'s own `ownedNames` but not `desiredSet`.
1092
+ * `resolve` is never consulted on the *create* half: a check already live
1093
+ * under a desired name is always treated as foundry's, the way it always
1094
+ * was, and (the round-2 fix for a second bug) its ping address is now
1095
+ * always re-saved rather than only on the create that first makes it live
1096
+ * -- see `put`'s own doc comment below for why that half carries no
1097
+ * adoption risk the way deleting does.
1098
+ *
1099
+ * `put` records a check's own ping URL -- on every create, and (new this
1100
+ * round) on every run that finds a desired check already live, not only
1101
+ * the run that created it. Before this fix, a save that failed once, or a
1102
+ * run that died between create and save, or a host that wired `heldKeys`
1103
+ * only after the original create, lost that address for good: the next run
1104
+ * found the check already live and never saved anything, despite this very
1105
+ * doc comment already claiming the step "can simply ask the live check
1106
+ * again." Required whenever an address actually needs saving -- optional
1107
+ * only so a host without one still compiles, in which case nothing here
1108
+ * can ever be created or re-confirmed, and the step throws rather than
1109
+ * silently drop an address: CLAUDE.md platform-step rule 1 is written for a
1110
+ * minted credential, not a ping URL (a lost one is simply re-asked from the
1111
+ * live check, never re-minted), but the rule's "never silently lose it"
1112
+ * half applies just as well here.
1113
+ *
1114
+ * Both fields are optional only so a host without either still compiles;
1115
+ * while `PlatformHeartbeatsDeps` is wired at all, `FoundryPlatformStepExecutor
1116
+ * .blockedSteps`'s own `heartbeatsApiKeyMissing` check blocks the step
1117
+ * until a project API key is held under {@link PLATFORM_HC_API_KEY_REF}, so
1118
+ * the live Healthchecks calls below are never reached with nothing behind
1119
+ * them.
1120
+ */
1121
+ export interface PlatformHeartbeatsDeps {
1122
+ heartbeats: HeartbeatsPort;
1123
+ heldKeys?: {
1124
+ resolve(ref: string): Promise<string | null>;
1125
+ put?(ref: string, value: string): Promise<void>;
1126
+ };
1127
+ }
933
1128
  /**
934
1129
  * The real {@link PlatformCloudflareReader}: the platform's apex zone as
935
1130
  * `planApexZone` expects to see it, and nothing else. The zone's DNS records
@@ -959,7 +1154,7 @@ export declare class ProvidersPlatformCloudflareReader implements PlatformCloudf
959
1154
  export declare class ProvidersPlatformCoolifyReader implements PlatformCoolifyReader {
960
1155
  private readonly client;
961
1156
  private readonly platformStore;
962
- constructor(client: Pick<CoolifyPort, 'getApplication' | 'getDatabase' | 'listDatabaseBackups'>, platformStore: Pick<PlatformStore, 'getServiceIds'>);
1157
+ constructor(client: Pick<CoolifyPort, 'getApplication' | 'getDatabase' | 'listDatabaseBackups' | 'raw'>, platformStore: Pick<PlatformStore, 'getServiceIds'>);
963
1158
  listResources(): Promise<LiveResource[]>;
964
1159
  }
965
1160
  /** Scope id (no `:`): the share domains the runner's Coolify `domains` was last set to, comma-joined. */