@wtfalch/foundry 0.4.0 → 0.5.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/bootstrap.d.ts +96 -0
- package/dist/bootstrap/bootstrap.js +285 -0
- package/dist/bootstrap/declare.d.ts +36 -0
- package/dist/bootstrap/declare.js +94 -0
- package/dist/bootstrap/drive.d.ts +40 -0
- package/dist/bootstrap/drive.js +51 -0
- package/dist/bootstrap/index.d.ts +3 -0
- package/dist/bootstrap/index.js +1 -0
- package/dist/calls-sfu-deploy-support.d.ts +9 -6
- package/dist/calls-sfu-deploy-support.js +9 -6
- package/dist/calls-sfu-keys.d.ts +21 -0
- package/dist/calls-sfu-keys.js +27 -0
- package/dist/calls-sfu-ssh-adapter.d.ts +55 -0
- package/dist/calls-sfu-ssh-adapter.js +78 -0
- package/dist/calls-sfu-ssh-client.d.ts +49 -0
- package/dist/calls-sfu-ssh-client.js +82 -0
- package/dist/calls-sfu.d.ts +5 -4
- package/dist/calls-sfu.js +5 -4
- package/dist/index.d.ts +31 -6
- package/dist/index.js +16 -3
- package/dist/migrations/0016_platform.sql +61 -0
- package/dist/migrations/0017_requested_changes.sql +42 -0
- package/dist/migrations/0018_platform_apply.sql +19 -0
- package/dist/org.js +8 -0
- package/dist/plan/agora.d.ts +77 -0
- package/dist/plan/agora.js +127 -0
- package/dist/plan/cloudflared-connector.d.ts +70 -0
- package/dist/plan/cloudflared-connector.js +106 -0
- package/dist/plan/diff.d.ts +20 -3
- package/dist/plan/diff.js +631 -116
- package/dist/plan/executor.d.ts +475 -168
- package/dist/plan/executor.js +543 -175
- package/dist/plan/index.d.ts +22 -5
- package/dist/plan/index.js +12 -2
- package/dist/plan/jobs-scheduler-http.d.ts +29 -0
- package/dist/plan/jobs-scheduler-http.js +50 -0
- package/dist/plan/live.d.ts +51 -2
- package/dist/plan/live.js +8 -0
- package/dist/plan/plan-apply.d.ts +192 -3
- package/dist/plan/plan-apply.js +278 -9
- package/dist/plan/platform-diff.d.ts +312 -0
- package/dist/plan/platform-diff.js +867 -0
- package/dist/plan/platform-org-ports.d.ts +50 -0
- package/dist/plan/platform-org-ports.js +70 -0
- package/dist/plan/platform-org.d.ts +120 -0
- package/dist/plan/platform-org.js +115 -0
- package/dist/plan/platform-providers.d.ts +501 -0
- package/dist/plan/platform-providers.js +3029 -0
- package/dist/plan/platform-sfu.d.ts +93 -0
- package/dist/plan/platform-sfu.js +100 -0
- package/dist/plan/providers-readers.js +22 -1
- package/dist/plan/zitadel-machine-user.d.ts +34 -0
- package/dist/plan/zitadel-machine-user.js +95 -0
- package/dist/plan.d.ts +6 -0
- package/dist/platform.d.ts +55 -0
- package/dist/platform.js +46 -0
- package/dist/requested-changes.d.ts +108 -0
- package/dist/requested-changes.js +95 -0
- package/dist/slug.js +15 -2
- package/dist/store/audit.js +4 -2
- package/dist/store/postgres.d.ts +29 -1
- package/dist/store/postgres.js +172 -2
- package/dist/store/schema.d.ts +683 -0
- package/dist/store/schema.js +57 -0
- package/dist/store.d.ts +46 -0
- package/package.json +1 -1
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { PlatformStore } from '../store.js';
|
|
2
|
+
import type { AiOrganisationPort, FilesOrganisationPort, JobsSchedulerPort } from './executor.js';
|
|
3
|
+
import type { HeldKeyResolver } from './platform-org.js';
|
|
4
|
+
/**
|
|
5
|
+
* #177: what `platform-org.ts`'s readiness could not see between a plan and
|
|
6
|
+
* its apply. The planner blocks these first; this is the executor's guard for
|
|
7
|
+
* a key or address that went away in between. The message names the service
|
|
8
|
+
* and the ref, never a value.
|
|
9
|
+
*/
|
|
10
|
+
export declare class PlatformOrgBlockedError extends Error {
|
|
11
|
+
constructor(service: string, what: string);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* The seam onto the files and AI SDKs, which this package does not depend on.
|
|
15
|
+
* Foundry decides the address and the key; the host only says how to speak the
|
|
16
|
+
* service's protocol with them.
|
|
17
|
+
*/
|
|
18
|
+
export interface PlatformOrgClientFactories {
|
|
19
|
+
files(input: {
|
|
20
|
+
baseUrl: string;
|
|
21
|
+
issuer: string;
|
|
22
|
+
keyId: string;
|
|
23
|
+
/** The Ed25519 management private key, PKCS#8 PEM. */
|
|
24
|
+
privateKeyPem: string;
|
|
25
|
+
}): FilesOrganisationPort;
|
|
26
|
+
ai(input: {
|
|
27
|
+
baseUrl: string;
|
|
28
|
+
managementKey: string;
|
|
29
|
+
}): AiOrganisationPort;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* #177: the org steps' files, AI and jobs ports, built from the platform's
|
|
33
|
+
* recorded addresses and `heldKeys.resolve(<ref>)` instead of being built by
|
|
34
|
+
* the host. `executor.ts`'s `FoundryStepExecutorPorts.platformOrg` takes one;
|
|
35
|
+
* a port set directly there still wins, for tests.
|
|
36
|
+
*/
|
|
37
|
+
export declare class PlatformOrgPorts {
|
|
38
|
+
private readonly options;
|
|
39
|
+
constructor(options: {
|
|
40
|
+
store: Pick<PlatformStore, 'get' | 'getServiceIds'>;
|
|
41
|
+
heldKeys: HeldKeyResolver;
|
|
42
|
+
clients: PlatformOrgClientFactories;
|
|
43
|
+
fetch?: typeof fetch;
|
|
44
|
+
});
|
|
45
|
+
files(): Promise<FilesOrganisationPort>;
|
|
46
|
+
ai(): Promise<AiOrganisationPort>;
|
|
47
|
+
jobs(): Promise<JobsSchedulerPort>;
|
|
48
|
+
private recorded;
|
|
49
|
+
private held;
|
|
50
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { HttpJobsSchedulerClient } from './jobs-scheduler-http.js';
|
|
2
|
+
import { PLATFORM_AI_INTERNAL_URL_SCOPE_KEY, PLATFORM_FILES_INTERNAL_URL_SCOPE_KEY, } from './platform-diff.js';
|
|
3
|
+
import { FILES_MANAGEMENT_ISSUER, FILES_MANAGEMENT_KEY_ID, FILES_MANAGEMENT_KEY_REF, JOBS_ADMIN_TOKEN_REF, PLATFORM_AI_MANAGEMENT_KEY_REF, } from './platform-providers.js';
|
|
4
|
+
/**
|
|
5
|
+
* #177: what `platform-org.ts`'s readiness could not see between a plan and
|
|
6
|
+
* its apply. The planner blocks these first; this is the executor's guard for
|
|
7
|
+
* a key or address that went away in between. The message names the service
|
|
8
|
+
* and the ref, never a value.
|
|
9
|
+
*/
|
|
10
|
+
export class PlatformOrgBlockedError extends Error {
|
|
11
|
+
constructor(service, what) {
|
|
12
|
+
super(`${service} is blocked: ${what}`);
|
|
13
|
+
this.name = 'PlatformOrgBlockedError';
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* #177: the org steps' files, AI and jobs ports, built from the platform's
|
|
18
|
+
* recorded addresses and `heldKeys.resolve(<ref>)` instead of being built by
|
|
19
|
+
* the host. `executor.ts`'s `FoundryStepExecutorPorts.platformOrg` takes one;
|
|
20
|
+
* a port set directly there still wins, for tests.
|
|
21
|
+
*/
|
|
22
|
+
export class PlatformOrgPorts {
|
|
23
|
+
options;
|
|
24
|
+
constructor(options) {
|
|
25
|
+
this.options = options;
|
|
26
|
+
}
|
|
27
|
+
async files() {
|
|
28
|
+
const baseUrl = await this.recorded('files', PLATFORM_FILES_INTERNAL_URL_SCOPE_KEY);
|
|
29
|
+
const privateKeyPem = await this.held('files', FILES_MANAGEMENT_KEY_REF);
|
|
30
|
+
return this.options.clients.files({
|
|
31
|
+
baseUrl,
|
|
32
|
+
issuer: FILES_MANAGEMENT_ISSUER,
|
|
33
|
+
keyId: FILES_MANAGEMENT_KEY_ID,
|
|
34
|
+
privateKeyPem,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
async ai() {
|
|
38
|
+
const baseUrl = await this.recorded('ai', PLATFORM_AI_INTERNAL_URL_SCOPE_KEY);
|
|
39
|
+
const managementKey = await this.held('ai', PLATFORM_AI_MANAGEMENT_KEY_REF);
|
|
40
|
+
return this.options.clients.ai({ baseUrl, managementKey });
|
|
41
|
+
}
|
|
42
|
+
async jobs() {
|
|
43
|
+
const platform = await this.options.store.get();
|
|
44
|
+
if (!platform?.jobs.enabled) {
|
|
45
|
+
throw new PlatformOrgBlockedError('jobs', 'the platform does not declare a jobs scheduler');
|
|
46
|
+
}
|
|
47
|
+
const token = await this.held('jobs', JOBS_ADMIN_TOKEN_REF);
|
|
48
|
+
return new HttpJobsSchedulerClient(`https://${platform.jobs.hostname}`, token, this.options.fetch);
|
|
49
|
+
}
|
|
50
|
+
async recorded(service, scopeKey) {
|
|
51
|
+
const value = (await this.options.store.getServiceIds())[scopeKey];
|
|
52
|
+
if (!value) {
|
|
53
|
+
throw new PlatformOrgBlockedError(service, `the platform has not recorded its ${scopeKey} address yet`);
|
|
54
|
+
}
|
|
55
|
+
return value;
|
|
56
|
+
}
|
|
57
|
+
async held(service, ref) {
|
|
58
|
+
let value;
|
|
59
|
+
try {
|
|
60
|
+
value = await this.options.heldKeys.resolve(ref);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
// The resolver's error could carry provider detail; report the ref only.
|
|
64
|
+
}
|
|
65
|
+
if (!value) {
|
|
66
|
+
throw new PlatformOrgBlockedError(service, `the platform key ${ref} is not held yet`);
|
|
67
|
+
}
|
|
68
|
+
return value;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import type { DeclaredPlatform } from '../platform.js';
|
|
2
|
+
import type { AgoraHostValues } from './agora.js';
|
|
3
|
+
/**
|
|
4
|
+
* https://github.com/wtfalch/foundry/issues/177: what an org step needs to
|
|
5
|
+
* know about the platform it runs on -- whether each shared service it
|
|
6
|
+
* depends on is enabled *and* applied (not just declared), and the one
|
|
7
|
+
* address `coolify:mail`'s own step used to read as a literal
|
|
8
|
+
* (`executor.ts`'s old `MAIL_APEX`/`MAIL_HOST` constants). Built by
|
|
9
|
+
* {@link orgPlatformReadinessFrom} from the very `DeclaredPlatform` + applied
|
|
10
|
+
* service ids `planPlatform` (`platform-diff.ts`) already reasons over -- the
|
|
11
|
+
* same "declared vs applied" distinction every platform step already makes
|
|
12
|
+
* for itself (CLAUDE.md's "Blocked, not failing": a step this package cannot
|
|
13
|
+
* yet make true is reported blocked, never planned to fail).
|
|
14
|
+
*
|
|
15
|
+
* `diff.ts`'s `computePlanDetail` takes this as one optional argument rather
|
|
16
|
+
* than five, so a host wiring a platform in later touches one call site, not
|
|
17
|
+
* every org step (`platform-diff.ts`'s own `PlanPlatformOptions` doc comment
|
|
18
|
+
* names the same "positional arguments that shifted on every merge" problem
|
|
19
|
+
* this avoids).
|
|
20
|
+
*/
|
|
21
|
+
export interface OrgPlatformReadiness {
|
|
22
|
+
/**
|
|
23
|
+
* `null` until the platform's own mail server (#169) is enabled and
|
|
24
|
+
* applied; the address `coolify:mail` then writes into its step instead of
|
|
25
|
+
* a literal, and `org.mail.mode !== 'off'` with this `null` is what makes
|
|
26
|
+
* that org's mail step blocked rather than planned.
|
|
27
|
+
*/
|
|
28
|
+
mailHost: {
|
|
29
|
+
apex: string;
|
|
30
|
+
mailHost: string;
|
|
31
|
+
} | null;
|
|
32
|
+
/** The files host (#171). Files is always on for every org (#80), so `false` always blocks rather than only mattering when a flag is set. */
|
|
33
|
+
filesReady: boolean;
|
|
34
|
+
/** The AI host (#172), the same always-on shape as {@link filesReady}. */
|
|
35
|
+
aiReady: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* The shared calls SFU (#170) an org's own calls Matrix stack depends on.
|
|
38
|
+
* Declared is enough here: unlike the others, `platform-diff.ts`'s
|
|
39
|
+
* `planSfu` never diffs the SFU against a live resource either (its own
|
|
40
|
+
* doc comment: "there is no cheap way to read the SFU VM's own state over
|
|
41
|
+
* SSH during planning"), so "applied" has no separate signal to check.
|
|
42
|
+
*/
|
|
43
|
+
callsReady: boolean;
|
|
44
|
+
/** The platform's own sign-in (#168), which every org's own ZITADEL org lives inside. */
|
|
45
|
+
authReady: boolean;
|
|
46
|
+
/** The jobs scheduler (#173) is enabled, applied, and its admin token is held. */
|
|
47
|
+
jobsReady: boolean;
|
|
48
|
+
/**
|
|
49
|
+
* The ref of a platform key an applied service needs that is not held yet,
|
|
50
|
+
* `null` when it is held or when the service is not applied. A ref only,
|
|
51
|
+
* never a value. {@link orgPlatformReadinessFrom} only checks the refs it is
|
|
52
|
+
* given; `FoundryPlanApply` adds the ref of every service the host supplies
|
|
53
|
+
* its own port for, so those read as held.
|
|
54
|
+
*/
|
|
55
|
+
missingKeys: {
|
|
56
|
+
files: string | null;
|
|
57
|
+
ai: string | null;
|
|
58
|
+
jobs: string | null;
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* True when the host wired no held-keys reader, so no key could be checked.
|
|
62
|
+
* A missing key is then reported as "no reader given" instead of "not held".
|
|
63
|
+
*/
|
|
64
|
+
noKeyReader?: boolean;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* What an org's plan reads when no platform is declared: nothing is ready, so
|
|
68
|
+
* every org step that depends on a platform service is blocked (#177). A host
|
|
69
|
+
* with no platform cannot have applied any shared service, and reading "no
|
|
70
|
+
* platform" as "all ready" planned steps that then failed against nothing.
|
|
71
|
+
* Tests that need a ready platform pass an explicit fixture.
|
|
72
|
+
*/
|
|
73
|
+
export declare const DEFAULT_ORG_PLATFORM_READINESS: OrgPlatformReadiness;
|
|
74
|
+
/** The held-key refs an org step needs from the platform, one per service. */
|
|
75
|
+
export declare const ORG_PLATFORM_KEY_REFS: {
|
|
76
|
+
readonly files: "files:platform:managementKey";
|
|
77
|
+
readonly ai: "ai:platform:managementKey";
|
|
78
|
+
readonly jobs: "jobs:scheduler:adminToken";
|
|
79
|
+
};
|
|
80
|
+
/** The one method of Archon's held keys this package reads. A value is returned to the caller and never logged, recorded or put in a plan. */
|
|
81
|
+
export interface HeldKeyResolver {
|
|
82
|
+
resolve(ref: string): Promise<string | null | undefined>;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Which of {@link ORG_PLATFORM_KEY_REFS} `heldKeys` can resolve right now. A
|
|
86
|
+
* resolver that throws counts as "not held": the plan blocks, and the error
|
|
87
|
+
* (which could carry provider detail) goes nowhere.
|
|
88
|
+
*/
|
|
89
|
+
export declare function heldPlatformKeyRefs(heldKeys: HeldKeyResolver): Promise<Set<string>>;
|
|
90
|
+
/**
|
|
91
|
+
* `FoundryPlanApply.computeFreshPlan`'s own precedent for turning a
|
|
92
|
+
* `PlatformStore`'s `get()`/`getServiceIds()` into what an org's plan needs.
|
|
93
|
+
* Unlike `agora.ts`'s `readAgoraHost`, there is no port to be absent here and
|
|
94
|
+
* nothing to throw: the platform is either declared (a caller checked that
|
|
95
|
+
* already) or this is never called.
|
|
96
|
+
*/
|
|
97
|
+
export declare function orgPlatformReadinessFrom(platform: DeclaredPlatform, platformRecordedIds: Readonly<Record<string, string>>,
|
|
98
|
+
/**
|
|
99
|
+
* The refs `heldKeys` resolves ({@link heldPlatformKeyRefs}), plus the ref
|
|
100
|
+
* of any service the host supplies its own port for. `undefined` skips the
|
|
101
|
+
* key check; `FoundryPlanApply` never passes it, because an absent
|
|
102
|
+
* `heldKeys` with no host port must block, not pass.
|
|
103
|
+
*/
|
|
104
|
+
heldRefs?: ReadonlySet<string>,
|
|
105
|
+
/** True when the host wired no held-keys reader ({@link OrgPlatformReadiness.noKeyReader}). */
|
|
106
|
+
noKeyReader?: boolean): OrgPlatformReadiness;
|
|
107
|
+
/**
|
|
108
|
+
* #177: {@link AgoraHostValues} derived from the same platform state, rather
|
|
109
|
+
* than requiring a bespoke host-supplied `AgoraHostPort` for values the
|
|
110
|
+
* platform already knows -- the `issuer`/`mailUpstream`/`filesUpstream`
|
|
111
|
+
* addresses `agora.ts`'s own doc comment named this issue as the source of.
|
|
112
|
+
* `mailProjectId`/`messagingProjectId` still have no live source (chat has no
|
|
113
|
+
* admin API yet, https://github.com/wtfalch/chat/issues/40, and mail's own
|
|
114
|
+
* OIDC directory does not surface a project id), so a platform-derived Agora
|
|
115
|
+
* stays blocked on those two until something records them -- this only stops
|
|
116
|
+
* pretending the other three are unavailable once the platform actually
|
|
117
|
+
* supplies them. `plan-apply.ts`'s `FoundryPlanApply` calls this only when no
|
|
118
|
+
* host-supplied `agora` port overrides it.
|
|
119
|
+
*/
|
|
120
|
+
export declare function agoraHostValuesFromPlatform(platform: DeclaredPlatform, platformRecordedIds: Readonly<Record<string, string>>): AgoraHostValues;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { JOBS_SCHEDULER_KEY, PLATFORM_AI_KEY, PLATFORM_AUTH_KEY, PLATFORM_FILES_INTERNAL_URL_SCOPE_KEY, PLATFORM_FILES_KEY, PLATFORM_MAIL_INTERNAL_URL_SCOPE_KEY, PLATFORM_MAIL_KEY, platformMailMxHost, } from './platform-diff.js';
|
|
2
|
+
import { FILES_MANAGEMENT_KEY_REF, JOBS_ADMIN_TOKEN_REF, PLATFORM_AI_MANAGEMENT_KEY_REF, } from './platform-providers.js';
|
|
3
|
+
/**
|
|
4
|
+
* What an org's plan reads when no platform is declared: nothing is ready, so
|
|
5
|
+
* every org step that depends on a platform service is blocked (#177). A host
|
|
6
|
+
* with no platform cannot have applied any shared service, and reading "no
|
|
7
|
+
* platform" as "all ready" planned steps that then failed against nothing.
|
|
8
|
+
* Tests that need a ready platform pass an explicit fixture.
|
|
9
|
+
*/
|
|
10
|
+
export const DEFAULT_ORG_PLATFORM_READINESS = {
|
|
11
|
+
mailHost: null,
|
|
12
|
+
filesReady: false,
|
|
13
|
+
aiReady: false,
|
|
14
|
+
callsReady: false,
|
|
15
|
+
authReady: false,
|
|
16
|
+
jobsReady: false,
|
|
17
|
+
missingKeys: { files: null, ai: null, jobs: null },
|
|
18
|
+
};
|
|
19
|
+
/** The held-key refs an org step needs from the platform, one per service. */
|
|
20
|
+
export const ORG_PLATFORM_KEY_REFS = {
|
|
21
|
+
files: FILES_MANAGEMENT_KEY_REF,
|
|
22
|
+
ai: PLATFORM_AI_MANAGEMENT_KEY_REF,
|
|
23
|
+
jobs: JOBS_ADMIN_TOKEN_REF,
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Which of {@link ORG_PLATFORM_KEY_REFS} `heldKeys` can resolve right now. A
|
|
27
|
+
* resolver that throws counts as "not held": the plan blocks, and the error
|
|
28
|
+
* (which could carry provider detail) goes nowhere.
|
|
29
|
+
*/
|
|
30
|
+
export async function heldPlatformKeyRefs(heldKeys) {
|
|
31
|
+
const held = new Set();
|
|
32
|
+
for (const ref of Object.values(ORG_PLATFORM_KEY_REFS)) {
|
|
33
|
+
try {
|
|
34
|
+
if (await heldKeys.resolve(ref))
|
|
35
|
+
held.add(ref);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
// not held
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return held;
|
|
42
|
+
}
|
|
43
|
+
/** `AppliedState.serviceIds`' own `system:kind:key` shape, restated for the platform's own recorded ids (`platform-diff.ts`'s `resourceKey`, private there). */
|
|
44
|
+
function coolifyAppApplied(platformRecordedIds, key) {
|
|
45
|
+
return Boolean(platformRecordedIds[`coolify:app:${key}`]);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* `FoundryPlanApply.computeFreshPlan`'s own precedent for turning a
|
|
49
|
+
* `PlatformStore`'s `get()`/`getServiceIds()` into what an org's plan needs.
|
|
50
|
+
* Unlike `agora.ts`'s `readAgoraHost`, there is no port to be absent here and
|
|
51
|
+
* nothing to throw: the platform is either declared (a caller checked that
|
|
52
|
+
* already) or this is never called.
|
|
53
|
+
*/
|
|
54
|
+
export function orgPlatformReadinessFrom(platform, platformRecordedIds,
|
|
55
|
+
/**
|
|
56
|
+
* The refs `heldKeys` resolves ({@link heldPlatformKeyRefs}), plus the ref
|
|
57
|
+
* of any service the host supplies its own port for. `undefined` skips the
|
|
58
|
+
* key check; `FoundryPlanApply` never passes it, because an absent
|
|
59
|
+
* `heldKeys` with no host port must block, not pass.
|
|
60
|
+
*/
|
|
61
|
+
heldRefs,
|
|
62
|
+
/** True when the host wired no held-keys reader ({@link OrgPlatformReadiness.noKeyReader}). */
|
|
63
|
+
noKeyReader = false) {
|
|
64
|
+
const mailApplied = platform.mail.enabled && coolifyAppApplied(platformRecordedIds, PLATFORM_MAIL_KEY);
|
|
65
|
+
const filesApplied = platform.files.enabled && coolifyAppApplied(platformRecordedIds, PLATFORM_FILES_KEY);
|
|
66
|
+
const aiApplied = platform.ai.enabled && coolifyAppApplied(platformRecordedIds, PLATFORM_AI_KEY);
|
|
67
|
+
const jobsApplied = platform.jobs.enabled && Boolean(platformRecordedIds[`coolify:jobs:${JOBS_SCHEDULER_KEY}`]);
|
|
68
|
+
const missing = (applied, ref) => applied && heldRefs !== undefined && !heldRefs.has(ref) ? ref : null;
|
|
69
|
+
const missingKeys = {
|
|
70
|
+
files: missing(filesApplied, ORG_PLATFORM_KEY_REFS.files),
|
|
71
|
+
ai: missing(aiApplied, ORG_PLATFORM_KEY_REFS.ai),
|
|
72
|
+
jobs: missing(jobsApplied, ORG_PLATFORM_KEY_REFS.jobs),
|
|
73
|
+
};
|
|
74
|
+
return {
|
|
75
|
+
mailHost: mailApplied
|
|
76
|
+
? { apex: platform.apex, mailHost: platformMailMxHost(platform.apex) }
|
|
77
|
+
: null,
|
|
78
|
+
filesReady: filesApplied && missingKeys.files === null,
|
|
79
|
+
aiReady: aiApplied && missingKeys.ai === null,
|
|
80
|
+
callsReady: platform.sfu.enabled,
|
|
81
|
+
authReady: platform.auth.enabled && coolifyAppApplied(platformRecordedIds, PLATFORM_AUTH_KEY),
|
|
82
|
+
jobsReady: jobsApplied && missingKeys.jobs === null,
|
|
83
|
+
missingKeys,
|
|
84
|
+
...(noKeyReader ? { noKeyReader } : {}),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* #177: {@link AgoraHostValues} derived from the same platform state, rather
|
|
89
|
+
* than requiring a bespoke host-supplied `AgoraHostPort` for values the
|
|
90
|
+
* platform already knows -- the `issuer`/`mailUpstream`/`filesUpstream`
|
|
91
|
+
* addresses `agora.ts`'s own doc comment named this issue as the source of.
|
|
92
|
+
* `mailProjectId`/`messagingProjectId` still have no live source (chat has no
|
|
93
|
+
* admin API yet, https://github.com/wtfalch/chat/issues/40, and mail's own
|
|
94
|
+
* OIDC directory does not surface a project id), so a platform-derived Agora
|
|
95
|
+
* stays blocked on those two until something records them -- this only stops
|
|
96
|
+
* pretending the other three are unavailable once the platform actually
|
|
97
|
+
* supplies them. `plan-apply.ts`'s `FoundryPlanApply` calls this only when no
|
|
98
|
+
* host-supplied `agora` port overrides it.
|
|
99
|
+
*/
|
|
100
|
+
export function agoraHostValuesFromPlatform(platform, platformRecordedIds) {
|
|
101
|
+
const authReady = platform.auth.enabled && coolifyAppApplied(platformRecordedIds, PLATFORM_AUTH_KEY);
|
|
102
|
+
const mailReady = platform.mail.enabled && coolifyAppApplied(platformRecordedIds, PLATFORM_MAIL_KEY);
|
|
103
|
+
const filesReady = platform.files.enabled && coolifyAppApplied(platformRecordedIds, PLATFORM_FILES_KEY);
|
|
104
|
+
return {
|
|
105
|
+
issuer: authReady ? `https://${platform.auth.hostname}` : null,
|
|
106
|
+
mailProjectId: null,
|
|
107
|
+
messagingProjectId: null,
|
|
108
|
+
mailUpstream: mailReady
|
|
109
|
+
? (platformRecordedIds[PLATFORM_MAIL_INTERNAL_URL_SCOPE_KEY] ?? null)
|
|
110
|
+
: null,
|
|
111
|
+
filesUpstream: filesReady
|
|
112
|
+
? (platformRecordedIds[PLATFORM_FILES_INTERNAL_URL_SCOPE_KEY] ?? null)
|
|
113
|
+
: null,
|
|
114
|
+
};
|
|
115
|
+
}
|