@wtfalch/foundry 0.23.0 → 0.25.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.
- package/dist/bootstrap/declare.js +6 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1 -1
- package/dist/migrations/0048_hosting_gate_limit.sql +7 -0
- package/dist/migrations/0049_platform_uptime.sql +11 -0
- package/dist/org.d.ts +27 -23
- package/dist/org.js +73 -29
- package/dist/plan/diff.js +36 -28
- package/dist/plan/edge-access.d.ts +48 -1
- package/dist/plan/edge-access.js +51 -2
- package/dist/plan/edge-workers.d.ts +16 -1
- package/dist/plan/edge-workers.js +8 -0
- package/dist/plan/executor.d.ts +50 -17
- package/dist/plan/executor.js +93 -47
- package/dist/plan/gates.d.ts +42 -0
- package/dist/plan/gates.js +126 -0
- package/dist/plan/health-targets.js +3 -4
- package/dist/plan/index.d.ts +2 -1
- package/dist/plan/plan-apply.d.ts +6 -0
- package/dist/plan/plan-apply.js +8 -1
- package/dist/plan/platform-diff.d.ts +102 -0
- package/dist/plan/platform-diff.js +288 -0
- package/dist/plan/platform-org.d.ts +2 -0
- package/dist/plan/platform-org.js +4 -0
- package/dist/plan/platform-providers.d.ts +47 -0
- package/dist/plan/platform-providers.js +271 -14
- package/dist/plan/uptime-worker.d.ts +50 -0
- package/dist/plan/uptime-worker.js +229 -0
- package/dist/platform.d.ts +18 -0
- package/dist/platform.js +14 -0
- package/dist/store/postgres.js +31 -5
- package/dist/store/schema.d.ts +68 -0
- package/dist/store/schema.js +6 -0
- package/package.json +2 -2
|
@@ -694,6 +694,18 @@ export declare const PLATFORM_EDGE_CLOUDFLARE_TOKEN_REF = "cloudflare:edge:apiTo
|
|
|
694
694
|
* reports it and never mints again. Neither key holds the secret itself.
|
|
695
695
|
*/
|
|
696
696
|
export declare const PLATFORM_EDGE_PLATFORM_KEY_UNDELIVERED_SCOPE_KEY = "edge_platform_key_undelivered";
|
|
697
|
+
/**
|
|
698
|
+
* #604: the ZITADEL client Cloudflare Access signs people in through. Its
|
|
699
|
+
* secret exists once, at creation, and goes to edge's `ensureProvider`. The
|
|
700
|
+
* client's app id is recorded here and persisted before the secret leaves;
|
|
701
|
+
* the guard, `ACCESS_PROVIDER_RECORD_KEY` (`gates.ts`), is recorded only
|
|
702
|
+
* after edge returned the provider's id. An undelivered id with no guard
|
|
703
|
+
* means the secret is lost: the planner reports it and never mints again.
|
|
704
|
+
*/
|
|
705
|
+
export declare const PLATFORM_ACCESS_PROVIDER_UNDELIVERED_SCOPE_KEY = "access_provider_client_undelivered";
|
|
706
|
+
/** The sign-in hostname and the Access team address the provider was applied with. */
|
|
707
|
+
export declare const PLATFORM_ACCESS_PROVIDER_APPLIED_HOSTNAME_SCOPE_KEY = "access_provider_applied_hostname";
|
|
708
|
+
export declare const PLATFORM_ACCESS_PROVIDER_APPLIED_AUTH_DOMAIN_SCOPE_KEY = "access_provider_applied_auth_domain";
|
|
697
709
|
export declare const PLATFORM_EDGE_PLATFORM_KEY_SET_SCOPE_KEY = "edge_platform_key_set";
|
|
698
710
|
/**
|
|
699
711
|
* #478: the hostname and `gitRef` the last successful names run applied.
|
|
@@ -810,6 +822,79 @@ export declare function heartbeatCheckUrlRef(name: string): string;
|
|
|
810
822
|
* bootstrapping at all.
|
|
811
823
|
*/
|
|
812
824
|
export declare const PLATFORM_HEARTBEATS_APPLIED_SCOPE_KEY = "heartbeats_applied_desired";
|
|
825
|
+
/**
|
|
826
|
+
* foundry#542: the uptime Worker, Archon's outside prober, deployed from the
|
|
827
|
+
* built script Archon publishes with each release. The step is
|
|
828
|
+
* `cloudflare:worker` keyed {@link PLATFORM_UPTIME_KEY}; `runPlatformSteps`
|
|
829
|
+
* records the script name under `cloudflare:worker:platform:uptime` once it
|
|
830
|
+
* succeeds, which is what stops `planUptime` planning it again.
|
|
831
|
+
*/
|
|
832
|
+
export declare const PLATFORM_UPTIME_KEY = "platform:uptime";
|
|
833
|
+
/** The Worker script's name at Cloudflare. */
|
|
834
|
+
export declare const PLATFORM_UPTIME_SCRIPT_NAME = "platform-uptime";
|
|
835
|
+
/** The Workers KV namespace title behind the Worker's `UPTIME_KV` binding. */
|
|
836
|
+
export declare const PLATFORM_UPTIME_KV_TITLE = "platform-uptime";
|
|
837
|
+
/**
|
|
838
|
+
* foundry#542: the held-key refs of the Worker's secrets. `UPTIME_TOKEN` is
|
|
839
|
+
* minted by the step (the token Archon's `UPTIME_TOKEN` env var also takes);
|
|
840
|
+
* the Access pair is minted through edge by the step, and the other two are held by a person. A missing one is reported as
|
|
841
|
+
* a blocked item, never a throw.
|
|
842
|
+
*/
|
|
843
|
+
export declare const PLATFORM_UPTIME_TOKEN_REF = "uptime:worker:token";
|
|
844
|
+
export declare const PLATFORM_UPTIME_ACCESS_CLIENT_ID_REF = "uptime:worker:access-client-id";
|
|
845
|
+
export declare const PLATFORM_UPTIME_ACCESS_CLIENT_SECRET_REF = "uptime:worker:access-client-secret";
|
|
846
|
+
export declare const PLATFORM_UPTIME_CHAT_TOKEN_REF = "uptime:worker:chat-token";
|
|
847
|
+
export declare const PLATFORM_UPTIME_MAIL_KEY_REF = "uptime:worker:mail-key";
|
|
848
|
+
/**
|
|
849
|
+
* Worker secret name to the held-key ref it is read from, for the secrets a
|
|
850
|
+
* person holds. `UPTIME_TOKEN` and the two `CF_ACCESS_CLIENT_*` secrets are
|
|
851
|
+
* minted by the step and always set, so they are not listed.
|
|
852
|
+
*/
|
|
853
|
+
export declare const PLATFORM_UPTIME_OPTIONAL_SECRETS: Readonly<Record<string, string>>;
|
|
854
|
+
/**
|
|
855
|
+
* foundry#542: scope keys (colon-free), the {@link PLATFORM_EDGE_PLATFORM_KEY_UNDELIVERED_SCOPE_KEY}
|
|
856
|
+
* pattern once. The undelivered id is recorded and persisted the moment the
|
|
857
|
+
* token is generated; the guard is recorded as soon as the value is durably
|
|
858
|
+
* held, before anything is sent to the Worker or Archon. Setting the value on
|
|
859
|
+
* the Worker and on Archon is repeated from the held value on every apply
|
|
860
|
+
* that needs it. Neither key holds the token; the id is the Worker secret's
|
|
861
|
+
* version label.
|
|
862
|
+
*/
|
|
863
|
+
export declare const PLATFORM_UPTIME_TOKEN_UNDELIVERED_SCOPE_KEY = "uptime_token_undelivered";
|
|
864
|
+
export declare const PLATFORM_UPTIME_TOKEN_ID_SCOPE_KEY = "uptime_token_id";
|
|
865
|
+
/** The token id Archon's env was last written with; `planArchon` plans again when it differs from {@link PLATFORM_UPTIME_TOKEN_ID_SCOPE_KEY}. */
|
|
866
|
+
export declare const PLATFORM_ARCHON_UPTIME_TOKEN_SYNCED_SCOPE_KEY = "archon_uptime_token_synced";
|
|
867
|
+
/** The tag, sha256 and Archon hostname the last successful run applied; a change plans the step again and mints nothing. */
|
|
868
|
+
export declare const PLATFORM_UPTIME_APPLIED_TAG_SCOPE_KEY = "uptime_applied_tag";
|
|
869
|
+
export declare const PLATFORM_UPTIME_APPLIED_SHA256_SCOPE_KEY = "uptime_applied_sha256";
|
|
870
|
+
export declare const PLATFORM_UPTIME_APPLIED_HOSTNAME_SCOPE_KEY = "uptime_applied_hostname";
|
|
871
|
+
/** The secret names (names only) the last run set, sorted and comma-joined: a newly held secret plans the step again. */
|
|
872
|
+
export declare const PLATFORM_UPTIME_APPLIED_SECRETS_SCOPE_KEY = "uptime_applied_secrets";
|
|
873
|
+
/** A digest of the plain text variables the last run uploaded (they are not secrets): a changed one plans the step again. */
|
|
874
|
+
export declare const PLATFORM_UPTIME_APPLIED_VARS_SCOPE_KEY = "uptime_applied_vars";
|
|
875
|
+
/** `'1'` once the step removed the Worker because the platform no longer enables it; cleared when it deploys again. */
|
|
876
|
+
export declare const PLATFORM_UPTIME_REMOVED_SCOPE_KEY = "uptime_removed";
|
|
877
|
+
/** The digest of the Worker's plain text variables that are set; empty when none is. */
|
|
878
|
+
export declare function uptimeVarsDigest(vars?: Readonly<Record<string, string | undefined>>): string;
|
|
879
|
+
/** The Worker secret names the step sets: the three it mints, and each held one. */
|
|
880
|
+
export declare function uptimeSecretNames(held: readonly string[]): string;
|
|
881
|
+
/** The Access service token's name at Cloudflare; edge answers `conflict` when a token of this name exists. */
|
|
882
|
+
export declare const PLATFORM_UPTIME_ACCESS_TOKEN_NAME = "platform-uptime";
|
|
883
|
+
/**
|
|
884
|
+
* The Access service token pair, minted through edge once: the same
|
|
885
|
+
* undelivered-then-guard pattern as the Worker token above. The undelivered
|
|
886
|
+
* id is recorded and persisted the moment edge returns the pair; the guard is
|
|
887
|
+
* recorded once both values are held. A pair that may be lost (the undelivered
|
|
888
|
+
* id without its guard, or the guard with nothing held) is repaired by the
|
|
889
|
+
* next apply: it removes that token through edge and mints again. Both are
|
|
890
|
+
* ids, never a value. Archon's step reads the guard to allow the token.
|
|
891
|
+
*/
|
|
892
|
+
export declare const PLATFORM_UPTIME_ACCESS_UNDELIVERED_SCOPE_KEY = "uptime_access_undelivered";
|
|
893
|
+
export declare const PLATFORM_UPTIME_ACCESS_TOKEN_ID_SCOPE_KEY = "uptime_access_token_id";
|
|
894
|
+
/** Why the pair cannot be minted: a token of the name exists and foundry has no id recorded for it. */
|
|
895
|
+
export declare function uptimeAccessTokenConflict(): string;
|
|
896
|
+
/** Whether anything of the uptime Worker is recorded that disabling it must remove. */
|
|
897
|
+
export declare function uptimeRecorded(recordedIds: Readonly<Record<string, string>>): boolean;
|
|
813
898
|
/**
|
|
814
899
|
* Options for {@link planPlatform}, replacing four positional arguments that
|
|
815
900
|
* shifted on every merge that added a platform service (#186, #187, #188) --
|
|
@@ -850,6 +935,8 @@ export interface PlanPlatformOptions {
|
|
|
850
935
|
mail?: string;
|
|
851
936
|
/** #478: why the edge host is blocked, or absent when it is not. */
|
|
852
937
|
edge?: string;
|
|
938
|
+
/** #604: why the Access sign-in provider cannot be made yet (no dependencies, or edge cannot confirm Access is ready), or absent. */
|
|
939
|
+
accessProvider?: string;
|
|
853
940
|
/**
|
|
854
941
|
* #257: true once a service's database exists but the host built the
|
|
855
942
|
* platform executor with no backup dependencies -- every such service's
|
|
@@ -884,6 +971,21 @@ export interface PlanPlatformOptions {
|
|
|
884
971
|
* Not a block.
|
|
885
972
|
*/
|
|
886
973
|
archonRunsInsideApp?: boolean;
|
|
974
|
+
/** foundry#542: why the uptime Worker is blocked, or absent when it is not. */
|
|
975
|
+
uptime?: string;
|
|
976
|
+
/** foundry#542: the optional Worker secret names (`CF_ACCESS_CLIENT_ID`, ...) that are held now; names only. */
|
|
977
|
+
uptimeSecretsHeld?: string[];
|
|
978
|
+
/**
|
|
979
|
+
* foundry#542: what the host reads back for the uptime Worker: whether the
|
|
980
|
+
* Worker token and the Access pair are held, and the digest of its plain
|
|
981
|
+
* text variables ({@link uptimeVarsDigest}). Nothing held plans the step
|
|
982
|
+
* again; it repairs that, it is not a block.
|
|
983
|
+
*/
|
|
984
|
+
uptimeHeld?: {
|
|
985
|
+
token: boolean;
|
|
986
|
+
access: boolean;
|
|
987
|
+
vars: string;
|
|
988
|
+
};
|
|
887
989
|
/** Why Archon's step must wait: the deployment it triggered is still running. Blocks even an applied step. */
|
|
888
990
|
archonDeployRunning?: string;
|
|
889
991
|
};
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
1
2
|
import { VERSIONS } from '../bootstrap/versions.js';
|
|
2
3
|
import { APP_ENTRY_KEY } from '../org.js';
|
|
3
4
|
import { VERSION_CHANGE_KIND, } from '../plan.js';
|
|
5
|
+
import { ACCESS_PROVIDER_RECORD_KEY } from './gates.js';
|
|
4
6
|
import { legacyAppHostname } from './hostnames.js';
|
|
5
7
|
import { withStepSystem } from './step-system.js';
|
|
6
8
|
/**
|
|
@@ -771,6 +773,18 @@ export const PLATFORM_EDGE_CLOUDFLARE_TOKEN_REF = 'cloudflare:edge:apiToken';
|
|
|
771
773
|
* reports it and never mints again. Neither key holds the secret itself.
|
|
772
774
|
*/
|
|
773
775
|
export const PLATFORM_EDGE_PLATFORM_KEY_UNDELIVERED_SCOPE_KEY = 'edge_platform_key_undelivered';
|
|
776
|
+
/**
|
|
777
|
+
* #604: the ZITADEL client Cloudflare Access signs people in through. Its
|
|
778
|
+
* secret exists once, at creation, and goes to edge's `ensureProvider`. The
|
|
779
|
+
* client's app id is recorded here and persisted before the secret leaves;
|
|
780
|
+
* the guard, `ACCESS_PROVIDER_RECORD_KEY` (`gates.ts`), is recorded only
|
|
781
|
+
* after edge returned the provider's id. An undelivered id with no guard
|
|
782
|
+
* means the secret is lost: the planner reports it and never mints again.
|
|
783
|
+
*/
|
|
784
|
+
export const PLATFORM_ACCESS_PROVIDER_UNDELIVERED_SCOPE_KEY = 'access_provider_client_undelivered';
|
|
785
|
+
/** The sign-in hostname and the Access team address the provider was applied with. */
|
|
786
|
+
export const PLATFORM_ACCESS_PROVIDER_APPLIED_HOSTNAME_SCOPE_KEY = 'access_provider_applied_hostname';
|
|
787
|
+
export const PLATFORM_ACCESS_PROVIDER_APPLIED_AUTH_DOMAIN_SCOPE_KEY = 'access_provider_applied_auth_domain';
|
|
774
788
|
export const PLATFORM_EDGE_PLATFORM_KEY_SET_SCOPE_KEY = 'edge_platform_key_set';
|
|
775
789
|
/**
|
|
776
790
|
* #478: the hostname and `gitRef` the last successful names run applied.
|
|
@@ -907,6 +921,100 @@ export function heartbeatCheckUrlRef(name) {
|
|
|
907
921
|
* bootstrapping at all.
|
|
908
922
|
*/
|
|
909
923
|
export const PLATFORM_HEARTBEATS_APPLIED_SCOPE_KEY = 'heartbeats_applied_desired';
|
|
924
|
+
/**
|
|
925
|
+
* foundry#542: the uptime Worker, Archon's outside prober, deployed from the
|
|
926
|
+
* built script Archon publishes with each release. The step is
|
|
927
|
+
* `cloudflare:worker` keyed {@link PLATFORM_UPTIME_KEY}; `runPlatformSteps`
|
|
928
|
+
* records the script name under `cloudflare:worker:platform:uptime` once it
|
|
929
|
+
* succeeds, which is what stops `planUptime` planning it again.
|
|
930
|
+
*/
|
|
931
|
+
export const PLATFORM_UPTIME_KEY = 'platform:uptime';
|
|
932
|
+
/** The Worker script's name at Cloudflare. */
|
|
933
|
+
export const PLATFORM_UPTIME_SCRIPT_NAME = 'platform-uptime';
|
|
934
|
+
/** The Workers KV namespace title behind the Worker's `UPTIME_KV` binding. */
|
|
935
|
+
export const PLATFORM_UPTIME_KV_TITLE = 'platform-uptime';
|
|
936
|
+
/**
|
|
937
|
+
* foundry#542: the held-key refs of the Worker's secrets. `UPTIME_TOKEN` is
|
|
938
|
+
* minted by the step (the token Archon's `UPTIME_TOKEN` env var also takes);
|
|
939
|
+
* the Access pair is minted through edge by the step, and the other two are held by a person. A missing one is reported as
|
|
940
|
+
* a blocked item, never a throw.
|
|
941
|
+
*/
|
|
942
|
+
export const PLATFORM_UPTIME_TOKEN_REF = 'uptime:worker:token';
|
|
943
|
+
export const PLATFORM_UPTIME_ACCESS_CLIENT_ID_REF = 'uptime:worker:access-client-id';
|
|
944
|
+
export const PLATFORM_UPTIME_ACCESS_CLIENT_SECRET_REF = 'uptime:worker:access-client-secret';
|
|
945
|
+
export const PLATFORM_UPTIME_CHAT_TOKEN_REF = 'uptime:worker:chat-token';
|
|
946
|
+
export const PLATFORM_UPTIME_MAIL_KEY_REF = 'uptime:worker:mail-key';
|
|
947
|
+
/**
|
|
948
|
+
* Worker secret name to the held-key ref it is read from, for the secrets a
|
|
949
|
+
* person holds. `UPTIME_TOKEN` and the two `CF_ACCESS_CLIENT_*` secrets are
|
|
950
|
+
* minted by the step and always set, so they are not listed.
|
|
951
|
+
*/
|
|
952
|
+
export const PLATFORM_UPTIME_OPTIONAL_SECRETS = {
|
|
953
|
+
CHAT_ACCESS_TOKEN: PLATFORM_UPTIME_CHAT_TOKEN_REF,
|
|
954
|
+
MAIL_API_KEY: PLATFORM_UPTIME_MAIL_KEY_REF,
|
|
955
|
+
};
|
|
956
|
+
/**
|
|
957
|
+
* foundry#542: scope keys (colon-free), the {@link PLATFORM_EDGE_PLATFORM_KEY_UNDELIVERED_SCOPE_KEY}
|
|
958
|
+
* pattern once. The undelivered id is recorded and persisted the moment the
|
|
959
|
+
* token is generated; the guard is recorded as soon as the value is durably
|
|
960
|
+
* held, before anything is sent to the Worker or Archon. Setting the value on
|
|
961
|
+
* the Worker and on Archon is repeated from the held value on every apply
|
|
962
|
+
* that needs it. Neither key holds the token; the id is the Worker secret's
|
|
963
|
+
* version label.
|
|
964
|
+
*/
|
|
965
|
+
export const PLATFORM_UPTIME_TOKEN_UNDELIVERED_SCOPE_KEY = 'uptime_token_undelivered';
|
|
966
|
+
export const PLATFORM_UPTIME_TOKEN_ID_SCOPE_KEY = 'uptime_token_id';
|
|
967
|
+
/** The token id Archon's env was last written with; `planArchon` plans again when it differs from {@link PLATFORM_UPTIME_TOKEN_ID_SCOPE_KEY}. */
|
|
968
|
+
export const PLATFORM_ARCHON_UPTIME_TOKEN_SYNCED_SCOPE_KEY = 'archon_uptime_token_synced';
|
|
969
|
+
/** The tag, sha256 and Archon hostname the last successful run applied; a change plans the step again and mints nothing. */
|
|
970
|
+
export const PLATFORM_UPTIME_APPLIED_TAG_SCOPE_KEY = 'uptime_applied_tag';
|
|
971
|
+
export const PLATFORM_UPTIME_APPLIED_SHA256_SCOPE_KEY = 'uptime_applied_sha256';
|
|
972
|
+
export const PLATFORM_UPTIME_APPLIED_HOSTNAME_SCOPE_KEY = 'uptime_applied_hostname';
|
|
973
|
+
/** The secret names (names only) the last run set, sorted and comma-joined: a newly held secret plans the step again. */
|
|
974
|
+
export const PLATFORM_UPTIME_APPLIED_SECRETS_SCOPE_KEY = 'uptime_applied_secrets';
|
|
975
|
+
/** A digest of the plain text variables the last run uploaded (they are not secrets): a changed one plans the step again. */
|
|
976
|
+
export const PLATFORM_UPTIME_APPLIED_VARS_SCOPE_KEY = 'uptime_applied_vars';
|
|
977
|
+
/** `'1'` once the step removed the Worker because the platform no longer enables it; cleared when it deploys again. */
|
|
978
|
+
export const PLATFORM_UPTIME_REMOVED_SCOPE_KEY = 'uptime_removed';
|
|
979
|
+
/** The digest of the Worker's plain text variables that are set; empty when none is. */
|
|
980
|
+
export function uptimeVarsDigest(vars = {}) {
|
|
981
|
+
const set = Object.entries(vars)
|
|
982
|
+
.filter(([, value]) => Boolean(value))
|
|
983
|
+
.sort(([a], [b]) => a.localeCompare(b));
|
|
984
|
+
if (set.length === 0)
|
|
985
|
+
return '';
|
|
986
|
+
return createHash('sha256').update(JSON.stringify(set)).digest('hex').slice(0, 16);
|
|
987
|
+
}
|
|
988
|
+
/** The Worker secret names the step sets: the three it mints, and each held one. */
|
|
989
|
+
export function uptimeSecretNames(held) {
|
|
990
|
+
return ['UPTIME_TOKEN', 'CF_ACCESS_CLIENT_ID', 'CF_ACCESS_CLIENT_SECRET', ...held]
|
|
991
|
+
.sort()
|
|
992
|
+
.join(',');
|
|
993
|
+
}
|
|
994
|
+
/** The Access service token's name at Cloudflare; edge answers `conflict` when a token of this name exists. */
|
|
995
|
+
export const PLATFORM_UPTIME_ACCESS_TOKEN_NAME = 'platform-uptime';
|
|
996
|
+
/**
|
|
997
|
+
* The Access service token pair, minted through edge once: the same
|
|
998
|
+
* undelivered-then-guard pattern as the Worker token above. The undelivered
|
|
999
|
+
* id is recorded and persisted the moment edge returns the pair; the guard is
|
|
1000
|
+
* recorded once both values are held. A pair that may be lost (the undelivered
|
|
1001
|
+
* id without its guard, or the guard with nothing held) is repaired by the
|
|
1002
|
+
* next apply: it removes that token through edge and mints again. Both are
|
|
1003
|
+
* ids, never a value. Archon's step reads the guard to allow the token.
|
|
1004
|
+
*/
|
|
1005
|
+
export const PLATFORM_UPTIME_ACCESS_UNDELIVERED_SCOPE_KEY = 'uptime_access_undelivered';
|
|
1006
|
+
export const PLATFORM_UPTIME_ACCESS_TOKEN_ID_SCOPE_KEY = 'uptime_access_token_id';
|
|
1007
|
+
/** Why the pair cannot be minted: a token of the name exists and foundry has no id recorded for it. */
|
|
1008
|
+
export function uptimeAccessTokenConflict() {
|
|
1009
|
+
return `the uptime Worker is blocked, not planned: a Cloudflare Access service token named '${PLATFORM_UPTIME_ACCESS_TOKEN_NAME}' already exists and foundry did not make it; remove that token in Cloudflare Zero Trust (Access, Service Auth), then the next apply mints one through edge`;
|
|
1010
|
+
}
|
|
1011
|
+
/** Whether anything of the uptime Worker is recorded that disabling it must remove. */
|
|
1012
|
+
export function uptimeRecorded(recordedIds) {
|
|
1013
|
+
return ((Boolean(recordedIds[resourceKey('cloudflare', 'worker', PLATFORM_UPTIME_KEY)]) &&
|
|
1014
|
+
!recordedIds[PLATFORM_UPTIME_REMOVED_SCOPE_KEY]) ||
|
|
1015
|
+
Boolean(recordedIds[PLATFORM_UPTIME_ACCESS_TOKEN_ID_SCOPE_KEY]) ||
|
|
1016
|
+
Boolean(recordedIds[PLATFORM_UPTIME_ACCESS_UNDELIVERED_SCOPE_KEY]));
|
|
1017
|
+
}
|
|
910
1018
|
/**
|
|
911
1019
|
* #167: the platform's own diff engine, mirroring `diff.ts`'s
|
|
912
1020
|
* `computePlanDetail` for the one shared {@link DeclaredPlatform} record
|
|
@@ -999,6 +1107,11 @@ export function planPlatform(platform, live, options = {}) {
|
|
|
999
1107
|
const ai = planAi(platform, recordedIds, blocked.ai);
|
|
1000
1108
|
if (ai.step)
|
|
1001
1109
|
plannedSteps.push(ai.step);
|
|
1110
|
+
// #604: after edge and sign-in, whose applied ids it reads; before Archon,
|
|
1111
|
+
// whose redeploy can end the run.
|
|
1112
|
+
const accessProvider = planAccessProvider(platform, recordedIds, blocked.accessProvider);
|
|
1113
|
+
if (accessProvider.step)
|
|
1114
|
+
plannedSteps.push(accessProvider.step);
|
|
1002
1115
|
// #175: last, on purpose. Archon's sign-in client lives in the sign-in
|
|
1003
1116
|
// `planAuth` builds, and its env reads what every other service's step
|
|
1004
1117
|
// records or delivers, so in one apply it runs after all of them.
|
|
@@ -1017,6 +1130,11 @@ export function planPlatform(platform, live, options = {}) {
|
|
|
1017
1130
|
const heartbeats = planHeartbeats(platform, recordedIds, hcReady, blocked.heartbeatsExecutorMissing ?? false, blocked.heartbeatsApiKeyMissing ?? false);
|
|
1018
1131
|
if (heartbeats.step)
|
|
1019
1132
|
plannedSteps.push(heartbeats.step);
|
|
1133
|
+
// foundry#542: after Archon is applied (never merely planned): the Worker
|
|
1134
|
+
// probes Archon, and Archon's env takes the token this step mints.
|
|
1135
|
+
const uptime = planUptime(platform, recordedIds, blocked.uptime, blocked.uptimeSecretsHeld ?? [], Boolean(recordedIds[resourceKey('coolify', 'app', PLATFORM_ARCHON_KEY)]), blocked.uptimeHeld);
|
|
1136
|
+
if (uptime.step)
|
|
1137
|
+
plannedSteps.push(uptime.step);
|
|
1020
1138
|
if (archon.step && blocked.archonRunsInsideApp)
|
|
1021
1139
|
plannedSteps.push(archon.step);
|
|
1022
1140
|
// #251: one line per service whose recorded applied ref no longer matches
|
|
@@ -1081,6 +1199,9 @@ export function planPlatform(platform, live, options = {}) {
|
|
|
1081
1199
|
unmanaged.push({ description: ai.blocked, key: 'ai' });
|
|
1082
1200
|
if (edge.blocked)
|
|
1083
1201
|
unmanaged.push({ description: edge.blocked, key: 'edge' });
|
|
1202
|
+
if (accessProvider.blocked) {
|
|
1203
|
+
unmanaged.push({ description: accessProvider.blocked, key: 'access-provider' });
|
|
1204
|
+
}
|
|
1084
1205
|
if (hc.blocked)
|
|
1085
1206
|
unmanaged.push({ description: hc.blocked, key: 'hc' });
|
|
1086
1207
|
if (archon.blocked)
|
|
@@ -1099,6 +1220,8 @@ export function planPlatform(platform, live, options = {}) {
|
|
|
1099
1220
|
unmanaged.push(...backups.blocked);
|
|
1100
1221
|
if (heartbeats.blocked)
|
|
1101
1222
|
unmanaged.push({ description: heartbeats.blocked, key: 'heartbeats' });
|
|
1223
|
+
for (const description of uptime.blocked)
|
|
1224
|
+
unmanaged.push({ description, key: 'uptime' });
|
|
1102
1225
|
return { plannedSteps: plannedSteps.map(withStepSystem), unmanaged, versionChanges };
|
|
1103
1226
|
}
|
|
1104
1227
|
/**
|
|
@@ -2027,6 +2150,63 @@ function planEdge(platform, recordedIds, executorBlocked) {
|
|
|
2027
2150
|
blocked: null,
|
|
2028
2151
|
};
|
|
2029
2152
|
}
|
|
2153
|
+
/**
|
|
2154
|
+
* #604: the platform's ZITADEL as a sign-in provider in Cloudflare Access: a
|
|
2155
|
+
* ZITADEL client for Cloudflare, then edge's `ensureProvider`. One
|
|
2156
|
+
* `cloudflare:access-provider` step. It needs edge and the platform's sign-in
|
|
2157
|
+
* applied (it reads the Access team address through the one and makes the
|
|
2158
|
+
* client in the other). Done once `ACCESS_PROVIDER_RECORD_KEY` is recorded.
|
|
2159
|
+
*
|
|
2160
|
+
* Blocked, not planned: edge or sign-in not applied, a client minted but never
|
|
2161
|
+
* delivered (its secret cannot be shown again), or `executorBlocked` (no
|
|
2162
|
+
* dependencies, or edge cannot confirm Access is ready).
|
|
2163
|
+
*/
|
|
2164
|
+
function planAccessProvider(platform, recordedIds, executorBlocked) {
|
|
2165
|
+
if (!platform.edge.enabled || !platform.auth.enabled)
|
|
2166
|
+
return { step: null, blocked: null };
|
|
2167
|
+
if (recordedIds[ACCESS_PROVIDER_RECORD_KEY]) {
|
|
2168
|
+
// The secret is not stored, so a changed address cannot be re-sent: a person remakes the client.
|
|
2169
|
+
const appliedHostname = recordedIds[PLATFORM_ACCESS_PROVIDER_APPLIED_HOSTNAME_SCOPE_KEY];
|
|
2170
|
+
if (appliedHostname && appliedHostname !== platform.auth.hostname) {
|
|
2171
|
+
return {
|
|
2172
|
+
step: null,
|
|
2173
|
+
blocked: `the Access sign-in provider is blocked, not planned: it was applied with the sign-in hostname ${appliedHostname} and ${platform.auth.hostname} is declared now; its secret is not stored, so delete the ZITADEL client cloudflare-access, remove the recorded keys ${ACCESS_PROVIDER_RECORD_KEY}, ${PLATFORM_ACCESS_PROVIDER_APPLIED_HOSTNAME_SCOPE_KEY} and ${PLATFORM_ACCESS_PROVIDER_APPLIED_AUTH_DOMAIN_SCOPE_KEY}, then apply again`,
|
|
2174
|
+
};
|
|
2175
|
+
}
|
|
2176
|
+
return { step: null, blocked: null };
|
|
2177
|
+
}
|
|
2178
|
+
const undelivered = recordedIds[PLATFORM_ACCESS_PROVIDER_UNDELIVERED_SCOPE_KEY];
|
|
2179
|
+
if (undelivered) {
|
|
2180
|
+
return {
|
|
2181
|
+
step: null,
|
|
2182
|
+
blocked: `the Access sign-in provider is blocked, not planned: its ZITADEL client (${undelivered}) was made and its secret may or may not have reached edge; it cannot be shown again, so delete that client in ZITADEL and remove the recorded keys ${PLATFORM_ACCESS_PROVIDER_UNDELIVERED_SCOPE_KEY} and ${ACCESS_PROVIDER_RECORD_KEY}, then apply again to make both anew`,
|
|
2183
|
+
};
|
|
2184
|
+
}
|
|
2185
|
+
const applied = Boolean(recordedIds[PLATFORM_EDGE_INTERNAL_URL_SCOPE_KEY]) &&
|
|
2186
|
+
Boolean(recordedIds[resourceKey('coolify', 'app', PLATFORM_AUTH_KEY)]) &&
|
|
2187
|
+
// The sibling ZITADEL steps need the delivered admin key (or the instance it came from).
|
|
2188
|
+
(Boolean(recordedIds[PLATFORM_AUTH_ADMIN_USER_SCOPE_KEY]) ||
|
|
2189
|
+
Boolean(recordedIds[PLATFORM_AUTH_INSTANCE_SCOPE_KEY]));
|
|
2190
|
+
if (!applied) {
|
|
2191
|
+
return {
|
|
2192
|
+
step: null,
|
|
2193
|
+
blocked: 'the Access sign-in provider is blocked, not planned: edge and the platform sign-in are not both applied yet',
|
|
2194
|
+
};
|
|
2195
|
+
}
|
|
2196
|
+
if (executorBlocked)
|
|
2197
|
+
return { step: null, blocked: executorBlocked };
|
|
2198
|
+
return {
|
|
2199
|
+
step: {
|
|
2200
|
+
action: { system: 'cloudflare', kind: 'access-provider', key: 'platform', desired: {} },
|
|
2201
|
+
step: {
|
|
2202
|
+
description: "make the platform's ZITADEL the sign-in provider in Cloudflare Access, for company gates",
|
|
2203
|
+
destroys: false,
|
|
2204
|
+
migrates: false,
|
|
2205
|
+
},
|
|
2206
|
+
},
|
|
2207
|
+
blocked: null,
|
|
2208
|
+
};
|
|
2209
|
+
}
|
|
2030
2210
|
/**
|
|
2031
2211
|
* #540: a self-hosted heartbeat server (Healthchecks) for the platform, for
|
|
2032
2212
|
* now -- a Coolify Postgres plus one Docker-image app
|
|
@@ -2157,6 +2337,106 @@ function planHeartbeats(platform, recordedIds, hcReady, executorMissing, apiKeyM
|
|
|
2157
2337
|
blocked: null,
|
|
2158
2338
|
};
|
|
2159
2339
|
}
|
|
2340
|
+
/**
|
|
2341
|
+
* foundry#542: the uptime Worker, one `cloudflare:worker` step. Blocked, not
|
|
2342
|
+
* planned (rule 2), while no artifact is pinned, Archon is not applied, the
|
|
2343
|
+
* executor lacks its dependencies, a Worker or Access token of the name exists
|
|
2344
|
+
* that foundry did not make. A lost Worker token or Access pair is not a
|
|
2345
|
+
* block: the step is planned and repairs it (rule 4). Planned again when the
|
|
2346
|
+
* pinned tag or sha256, the Archon hostname (`ARCHON_BASE_URL`), the plain
|
|
2347
|
+
* text variables or the set of secrets that can be set changed (rule 3), or
|
|
2348
|
+
* when a half-made deploy never recorded its applied values; it mints nothing
|
|
2349
|
+
* that is already held. When the platform no longer enables the Worker, a
|
|
2350
|
+
* destructive step removes whatever of it is recorded (edge removes its KV
|
|
2351
|
+
* namespace with the script).
|
|
2352
|
+
*
|
|
2353
|
+
* The returned `blocked` lines are reported in `unmanaged`; some accompany a
|
|
2354
|
+
* planned step (a secret whose source does not exist yet), because the step
|
|
2355
|
+
* still delivers everything that exists.
|
|
2356
|
+
*/
|
|
2357
|
+
function planUptime(platform, recordedIds, executorBlocked, heldOptional, archonApplied, uptimeHeld = {
|
|
2358
|
+
token: true,
|
|
2359
|
+
access: true,
|
|
2360
|
+
vars: '',
|
|
2361
|
+
}) {
|
|
2362
|
+
const { uptime } = platform;
|
|
2363
|
+
const deployed = Boolean(recordedIds[resourceKey('cloudflare', 'worker', PLATFORM_UPTIME_KEY)]) &&
|
|
2364
|
+
!recordedIds[PLATFORM_UPTIME_REMOVED_SCOPE_KEY];
|
|
2365
|
+
const action = (desired) => ({
|
|
2366
|
+
system: 'cloudflare',
|
|
2367
|
+
kind: 'worker',
|
|
2368
|
+
key: PLATFORM_UPTIME_KEY,
|
|
2369
|
+
desired,
|
|
2370
|
+
});
|
|
2371
|
+
if (!uptime.enabled) {
|
|
2372
|
+
if (!uptimeRecorded(recordedIds))
|
|
2373
|
+
return { step: null, blocked: [] };
|
|
2374
|
+
if (executorBlocked)
|
|
2375
|
+
return { step: null, blocked: [executorBlocked] };
|
|
2376
|
+
return {
|
|
2377
|
+
step: {
|
|
2378
|
+
action: action({ remove: true }),
|
|
2379
|
+
step: {
|
|
2380
|
+
description: `remove the uptime Worker ${PLATFORM_UPTIME_SCRIPT_NAME} and, with it, its KV namespace, its Access service token and its held token`,
|
|
2381
|
+
destroys: true,
|
|
2382
|
+
migrates: false,
|
|
2383
|
+
},
|
|
2384
|
+
},
|
|
2385
|
+
blocked: [],
|
|
2386
|
+
};
|
|
2387
|
+
}
|
|
2388
|
+
if (!uptime.tag || !uptime.sha256) {
|
|
2389
|
+
return {
|
|
2390
|
+
step: null,
|
|
2391
|
+
blocked: [
|
|
2392
|
+
'the uptime Worker is blocked, not planned: no artifact is pinned; declare uptime.tag (an Archon release tag) and uptime.sha256 (the digest of archon-uptime-worker.js at that tag)',
|
|
2393
|
+
],
|
|
2394
|
+
};
|
|
2395
|
+
}
|
|
2396
|
+
if (!archonApplied) {
|
|
2397
|
+
return {
|
|
2398
|
+
step: null,
|
|
2399
|
+
blocked: [
|
|
2400
|
+
'the uptime Worker is blocked, not planned: platform:archon has not been applied yet',
|
|
2401
|
+
],
|
|
2402
|
+
};
|
|
2403
|
+
}
|
|
2404
|
+
if (executorBlocked)
|
|
2405
|
+
return { step: null, blocked: [executorBlocked] };
|
|
2406
|
+
const missing = [];
|
|
2407
|
+
if (!heldOptional.includes('CHAT_ACCESS_TOKEN')) {
|
|
2408
|
+
missing.push(`the uptime Worker has no chat credential (CHAT_ACCESS_TOKEN): hold one under ${PLATFORM_UPTIME_CHAT_TOKEN_REF} and it is set on the next apply; until then it sends no chat alerts`);
|
|
2409
|
+
}
|
|
2410
|
+
if (!heldOptional.includes('MAIL_API_KEY')) {
|
|
2411
|
+
missing.push(`the uptime Worker has no mail API key (MAIL_API_KEY): hold one under ${PLATFORM_UPTIME_MAIL_KEY_REF} and it is set on the next apply; until then it sends no alert email`);
|
|
2412
|
+
}
|
|
2413
|
+
const hostname = platform.archon.hostname;
|
|
2414
|
+
// Applied only once a run got to its end and recorded these: a Worker that
|
|
2415
|
+
// was uploaded and then failed has its key but none of the applied values.
|
|
2416
|
+
const applied = deployed &&
|
|
2417
|
+
Boolean(recordedIds[PLATFORM_UPTIME_TOKEN_ID_SCOPE_KEY]) &&
|
|
2418
|
+
uptimeHeld.token &&
|
|
2419
|
+
Boolean(recordedIds[PLATFORM_UPTIME_ACCESS_TOKEN_ID_SCOPE_KEY]) &&
|
|
2420
|
+
uptimeHeld.access &&
|
|
2421
|
+
recordedIds[PLATFORM_UPTIME_APPLIED_TAG_SCOPE_KEY] === uptime.tag &&
|
|
2422
|
+
recordedIds[PLATFORM_UPTIME_APPLIED_SHA256_SCOPE_KEY] === uptime.sha256 &&
|
|
2423
|
+
recordedIds[PLATFORM_UPTIME_APPLIED_HOSTNAME_SCOPE_KEY] === hostname &&
|
|
2424
|
+
(recordedIds[PLATFORM_UPTIME_APPLIED_VARS_SCOPE_KEY] ?? '') === uptimeHeld.vars &&
|
|
2425
|
+
recordedIds[PLATFORM_UPTIME_APPLIED_SECRETS_SCOPE_KEY] === uptimeSecretNames(heldOptional);
|
|
2426
|
+
if (applied)
|
|
2427
|
+
return { step: null, blocked: missing };
|
|
2428
|
+
return {
|
|
2429
|
+
step: {
|
|
2430
|
+
action: action({ hostname, gitRef: uptime.tag, sha256: uptime.sha256 }),
|
|
2431
|
+
step: {
|
|
2432
|
+
description: `deploy the uptime Worker ${PLATFORM_UPTIME_SCRIPT_NAME} from Archon release ${uptime.tag}, probing https://${hostname}, with its KV namespace, secrets and cron trigger`,
|
|
2433
|
+
destroys: false,
|
|
2434
|
+
migrates: false,
|
|
2435
|
+
},
|
|
2436
|
+
},
|
|
2437
|
+
blocked: missing,
|
|
2438
|
+
};
|
|
2439
|
+
}
|
|
2160
2440
|
/**
|
|
2161
2441
|
* #172: the platform's AI host (https://github.com/wtfalch/ai) -- a Coolify
|
|
2162
2442
|
* Postgres plus three apps built from that repo's root Dockerfile, one per
|
|
@@ -2304,6 +2584,14 @@ function planArchon(platform, recordedIds, executorBlocked, authReady, drifted,
|
|
|
2304
2584
|
// other drift reason.
|
|
2305
2585
|
recordedIds[PLATFORM_ARCHON_APPLIED_ROLLING_UPDATE_SCOPE_KEY] === '1' &&
|
|
2306
2586
|
!recordedIds[PLATFORM_ARCHON_GATE_CLOSED_SCOPE_KEY] &&
|
|
2587
|
+
// foundry#542: the uptime token minted after Archon's last apply is not
|
|
2588
|
+
// in Archon's env yet.
|
|
2589
|
+
// A disabled Worker leaves the token in Archon's env until the step drops it.
|
|
2590
|
+
(platform.uptime.enabled
|
|
2591
|
+
? !recordedIds[PLATFORM_UPTIME_TOKEN_ID_SCOPE_KEY] ||
|
|
2592
|
+
recordedIds[PLATFORM_UPTIME_TOKEN_ID_SCOPE_KEY] ===
|
|
2593
|
+
recordedIds[PLATFORM_ARCHON_UPTIME_TOKEN_SYNCED_SCOPE_KEY]
|
|
2594
|
+
: !recordedIds[PLATFORM_ARCHON_UPTIME_TOKEN_SYNCED_SCOPE_KEY]) &&
|
|
2307
2595
|
// #398: a rotated integrations key id Archon's env was not yet written
|
|
2308
2596
|
// with (see PLATFORM_ARCHON_INTEGRATIONS_KEY_SYNCED_SCOPE_KEY). Absent
|
|
2309
2597
|
// before the first rotation on a platform applied before #398.
|
|
@@ -87,6 +87,8 @@ export interface OrgPlatformReadiness {
|
|
|
87
87
|
callsReady: boolean;
|
|
88
88
|
/** The platform's own sign-in (#168), which every org's own ZITADEL org lives inside. */
|
|
89
89
|
authReady: boolean;
|
|
90
|
+
/** The Access provider id the platform recorded (`ACCESS_PROVIDER_RECORD_KEY`), which every gate requires. `null` or absent until the platform has recorded it. */
|
|
91
|
+
accessProviderId?: string | null;
|
|
90
92
|
/** The jobs scheduler (#173) is enabled, applied, has recorded its internal address, and its admin token is held. */
|
|
91
93
|
jobsReady: boolean;
|
|
92
94
|
/**
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ACCESS_PROVIDER_RECORD_KEY } from './gates.js';
|
|
1
2
|
import { JOBS_INTERNAL_URL_SCOPE_KEY, JOBS_SCHEDULER_KEY, PLATFORM_AI_INTERNAL_URL_SCOPE_KEY, PLATFORM_AI_KEY, PLATFORM_AUTH_KEY, PLATFORM_EDGE_KEY, PLATFORM_FILES_INTERNAL_URL_SCOPE_KEY, PLATFORM_FILES_KEY, PLATFORM_MAIL_INTERNAL_URL_SCOPE_KEY, PLATFORM_MAIL_KEY, PLATFORM_MAIL_ZITADEL_PROJECT_SCOPE_KEY, platformMailMxHost, } from './platform-diff.js';
|
|
2
3
|
import { FILES_MANAGEMENT_KEY_REF, JOBS_ADMIN_TOKEN_REF, PLATFORM_AI_MANAGEMENT_KEY_REF, } from './platform-providers.js';
|
|
3
4
|
/**
|
|
@@ -129,6 +130,9 @@ edgePortPresent = false) {
|
|
|
129
130
|
aiReady: aiApplied && missingKeys.ai === null,
|
|
130
131
|
aiUrl: aiApplied ? (platformRecordedIds[PLATFORM_AI_INTERNAL_URL_SCOPE_KEY] ?? null) : null,
|
|
131
132
|
callsReady: platform.sfu.enabled,
|
|
133
|
+
...(platformRecordedIds[ACCESS_PROVIDER_RECORD_KEY]
|
|
134
|
+
? { accessProviderId: platformRecordedIds[ACCESS_PROVIDER_RECORD_KEY] }
|
|
135
|
+
: {}),
|
|
132
136
|
authReady: platform.auth.enabled && coolifyAppApplied(platformRecordedIds, PLATFORM_AUTH_KEY),
|
|
133
137
|
jobsReady: jobsApplied && jobsUrl !== undefined && missingKeys.jobs === null,
|
|
134
138
|
...(jobsApplied && jobsUrl === undefined ? { jobsUrlMissing: true } : {}),
|
|
@@ -12,6 +12,7 @@ import type { LiveResource, PlatformCloudflareReader, PlatformCoolifyReader } fr
|
|
|
12
12
|
import type { PlatformStepExecutionContext, PlatformStepExecutor } from './plan-apply.js';
|
|
13
13
|
import { type BrokerDeployRecord } from './platform-diff.js';
|
|
14
14
|
import { type CallsSfuPlatformDeps } from './platform-sfu.js';
|
|
15
|
+
import { type PlatformUptimeDeps } from './uptime-worker.js';
|
|
15
16
|
/**
|
|
16
17
|
* #168: everything {@link provisionPlatformAuth} needs beyond
|
|
17
18
|
* {@link PlatformStepExecutionContext} -- a bundle, not more constructor
|
|
@@ -197,6 +198,18 @@ export interface FoundryPlatformStepExecutorOptions {
|
|
|
197
198
|
* heartbeat checks reported as blocked rather than planned.
|
|
198
199
|
*/
|
|
199
200
|
heartbeats?: PlatformHeartbeatsDeps;
|
|
201
|
+
/**
|
|
202
|
+
* foundry#542: dependencies for the uptime Worker step
|
|
203
|
+
* (`cloudflare:worker` keyed {@link PLATFORM_UPTIME_KEY}) -- optional for
|
|
204
|
+
* the same reason as `auth`; without it the step is reported blocked.
|
|
205
|
+
*/
|
|
206
|
+
uptime?: PlatformUptimeDeps;
|
|
207
|
+
/**
|
|
208
|
+
* #604: dependencies for the `cloudflare:access-provider` step -- optional
|
|
209
|
+
* for the same reason as `auth`; edge and sign-in applied with this unset
|
|
210
|
+
* gets the step reported as blocked.
|
|
211
|
+
*/
|
|
212
|
+
accessProvider?: PlatformAccessProviderDeps;
|
|
200
213
|
}
|
|
201
214
|
/**
|
|
202
215
|
* The real {@link PlatformStepExecutor} for the two steps built so far:
|
|
@@ -279,6 +292,8 @@ export declare class FoundryPlatformStepExecutor implements PlatformStepExecutor
|
|
|
279
292
|
private readonly backup?;
|
|
280
293
|
private readonly hc?;
|
|
281
294
|
private readonly heartbeats?;
|
|
295
|
+
private readonly uptime?;
|
|
296
|
+
private readonly accessProvider?;
|
|
282
297
|
constructor(cloudflare: Pick<CloudflarePort, 'findZoneByApex' | 'createZone'>, declaredOrgStore: Pick<DeclaredOrgStore, 'findOrgIdForZone' | 'list' | 'get'>, options?: FoundryPlatformStepExecutorOptions);
|
|
283
298
|
blockedSteps(platform: DeclaredPlatform, recordedIds?: Readonly<Record<string, string>>): Promise<{
|
|
284
299
|
auth?: string;
|
|
@@ -290,13 +305,29 @@ export declare class FoundryPlatformStepExecutor implements PlatformStepExecutor
|
|
|
290
305
|
edge?: string;
|
|
291
306
|
archon?: string;
|
|
292
307
|
mail?: string;
|
|
308
|
+
accessProvider?: string;
|
|
293
309
|
backupExecutorMissing?: boolean;
|
|
294
310
|
hc?: string;
|
|
295
311
|
heartbeatsExecutorMissing?: boolean;
|
|
296
312
|
heartbeatsApiKeyMissing?: boolean;
|
|
313
|
+
uptime?: string;
|
|
314
|
+
uptimeSecretsHeld?: string[];
|
|
315
|
+
uptimeHeld?: {
|
|
316
|
+
token: boolean;
|
|
317
|
+
access: boolean;
|
|
318
|
+
vars: string;
|
|
319
|
+
};
|
|
297
320
|
archonRunsInsideApp?: boolean;
|
|
298
321
|
archonDeployRunning?: string;
|
|
299
322
|
}>;
|
|
323
|
+
/**
|
|
324
|
+
* foundry#542: why the uptime Worker cannot run yet, which optional secrets
|
|
325
|
+
* are held, and whether the minted values are held. A Worker token or Access
|
|
326
|
+
* pair that is gone is not a block here: the step repairs it. The planner
|
|
327
|
+
* checks the pin and Archon first, so none of the reads here happens before
|
|
328
|
+
* they hold.
|
|
329
|
+
*/
|
|
330
|
+
private uptimeBlocked;
|
|
300
331
|
/**
|
|
301
332
|
* Whether Archon's live Access policy lets through anyone but the declared
|
|
302
333
|
* emails, or (#211 round 4) the broker it last registered with no longer
|
|
@@ -959,6 +990,22 @@ export interface PlatformEdgeDeps {
|
|
|
959
990
|
/** Clock, sleep and fetch for the readiness wait and delivery retries; real ones by default. */
|
|
960
991
|
runtime?: PlatformEdgeRuntime;
|
|
961
992
|
}
|
|
993
|
+
/** The Access calls the provider step makes. */
|
|
994
|
+
export type PlatformAccessProviderPort = Pick<AccessPort, 'readiness' | 'ensureProvider'>;
|
|
995
|
+
/** #604: dependencies for {@link provisionPlatformAccessProvider}. */
|
|
996
|
+
export interface PlatformAccessProviderDeps {
|
|
997
|
+
/** Edge's Access calls, at its recorded address (see {@link PlatformAccessFactory}). */
|
|
998
|
+
access: (edgeUrl: string) => PlatformAccessProviderPort | Promise<PlatformAccessProviderPort>;
|
|
999
|
+
/** Must target the platform's own ZITADEL instance, or `zitadelForInstance` must be set. */
|
|
1000
|
+
zitadel: Pick<ZitadelPort, 'findOrCreateOrg' | 'findOrCreateProject' | 'listApps' | 'deleteApp'> & Required<Pick<ZitadelPort, 'createOidcAppWithSecret'>>;
|
|
1001
|
+
/** {@link PlatformArchonDeps.zitadelForInstance}: the ZITADEL client for the sign-in at `issuerUrl`. */
|
|
1002
|
+
zitadelForInstance?: (issuerUrl: string) => PlatformAccessProviderDeps['zitadel'];
|
|
1003
|
+
/** Sleep and delivery tries for the one call that carries the secret; real ones by default. */
|
|
1004
|
+
runtime?: {
|
|
1005
|
+
sleep?: (ms: number) => Promise<void>;
|
|
1006
|
+
deliverAttempts?: number;
|
|
1007
|
+
};
|
|
1008
|
+
}
|
|
962
1009
|
export interface PlatformHcRuntime {
|
|
963
1010
|
fetch?: typeof fetch;
|
|
964
1011
|
sleep?: (ms: number) => Promise<void>;
|