@wtfalch/foundry 0.60.0 → 0.62.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 (236) hide show
  1. package/dist/apps/cloudflare.d.ts +173 -0
  2. package/dist/apps/cloudflare.js +17 -0
  3. package/dist/apps/deploy.d.ts +116 -0
  4. package/dist/apps/deploy.js +791 -0
  5. package/dist/apps/github.d.ts +47 -0
  6. package/dist/apps/github.js +201 -0
  7. package/dist/apps/index.d.ts +9 -0
  8. package/dist/apps/index.js +9 -0
  9. package/dist/apps/migrations.d.ts +110 -0
  10. package/dist/apps/migrations.js +458 -0
  11. package/dist/audit/connector.d.ts +43 -0
  12. package/dist/audit/connector.js +22 -0
  13. package/dist/audit/evidence.d.ts +49 -0
  14. package/dist/audit/evidence.js +56 -0
  15. package/dist/audit/index.d.ts +9 -0
  16. package/dist/audit/index.js +9 -0
  17. package/dist/audit/migrations.d.ts +5 -0
  18. package/dist/audit/migrations.js +5 -0
  19. package/dist/audit/registry.d.ts +49 -0
  20. package/dist/audit/registry.js +44 -0
  21. package/dist/audit/seal.d.ts +49 -0
  22. package/dist/audit/seal.js +131 -0
  23. package/dist/audit/sealer-role.d.ts +33 -0
  24. package/dist/audit/sealer-role.js +56 -0
  25. package/dist/audit/signer.d.ts +40 -0
  26. package/dist/audit/signer.js +78 -0
  27. package/dist/audit/tick.d.ts +36 -0
  28. package/dist/audit/tick.js +87 -0
  29. package/dist/catalogue/copy-en.d.ts +1 -0
  30. package/dist/catalogue/copy-en.js +129 -0
  31. package/dist/catalogue/index.d.ts +9 -0
  32. package/dist/catalogue/index.js +21 -0
  33. package/dist/catalogue/integrations.d.ts +4 -0
  34. package/dist/catalogue/integrations.js +104 -0
  35. package/dist/catalogue/systems.d.ts +7 -0
  36. package/dist/catalogue/systems.js +31 -0
  37. package/dist/catalogue/types.d.ts +110 -0
  38. package/dist/catalogue/types.js +1 -0
  39. package/dist/entry-rules.d.ts +53 -0
  40. package/dist/entry-rules.js +71 -0
  41. package/dist/hosted-label.d.ts +13 -0
  42. package/dist/hosted-label.js +81 -0
  43. package/dist/index.d.ts +14 -5
  44. package/dist/index.js +7 -3
  45. package/dist/migrations/0062_hosting_github.sql +6 -0
  46. package/dist/migrations/0063_boule_apps.sql +7 -0
  47. package/dist/migrations/0064_requested_changes_boule_apps.sql +6 -0
  48. package/dist/migrations/0065_native_build_runs.sql +32 -0
  49. package/dist/migrations/0066_org_kosmos.sql +10 -0
  50. package/dist/modules/ai.d.ts +3 -0
  51. package/dist/modules/ai.js +64 -0
  52. package/dist/modules/assistants.d.ts +3 -0
  53. package/dist/modules/assistants.js +72 -0
  54. package/dist/modules/audit-ledger.d.ts +29 -0
  55. package/dist/modules/audit-ledger.js +49 -0
  56. package/dist/modules/audit.d.ts +7 -0
  57. package/dist/modules/audit.js +44 -0
  58. package/dist/modules/database.d.ts +23 -0
  59. package/dist/modules/database.js +189 -0
  60. package/dist/modules/email.d.ts +3 -0
  61. package/dist/modules/email.js +18 -0
  62. package/dist/modules/entry-parts.d.ts +23 -0
  63. package/dist/modules/entry-parts.js +63 -0
  64. package/dist/modules/files.d.ts +3 -0
  65. package/dist/modules/files.js +64 -0
  66. package/dist/modules/index.d.ts +74 -0
  67. package/dist/modules/index.js +774 -0
  68. package/dist/modules/jobs.d.ts +3 -0
  69. package/dist/modules/jobs.js +23 -0
  70. package/dist/modules/organisations.d.ts +3 -0
  71. package/dist/modules/organisations.js +20 -0
  72. package/dist/modules/outputs.d.ts +9 -0
  73. package/dist/modules/outputs.js +38 -0
  74. package/dist/modules/sign-in.d.ts +3 -0
  75. package/dist/modules/sign-in.js +197 -0
  76. package/dist/modules/signed-records.d.ts +3 -0
  77. package/dist/modules/signed-records.js +32 -0
  78. package/dist/modules/types.d.ts +194 -0
  79. package/dist/modules/types.js +1 -0
  80. package/dist/modules/workspaces.d.ts +3 -0
  81. package/dist/modules/workspaces.js +75 -0
  82. package/dist/native-build.d.ts +70 -0
  83. package/dist/native-build.js +92 -0
  84. package/dist/org.d.ts +184 -24
  85. package/dist/org.js +444 -49
  86. package/dist/plan/agora.d.ts +1 -1
  87. package/dist/plan/agora.js +4 -0
  88. package/dist/plan/apps-db-route.d.ts +90 -0
  89. package/dist/plan/apps-db-route.js +168 -0
  90. package/dist/plan/apps-zones.d.ts +56 -0
  91. package/dist/plan/apps-zones.js +118 -0
  92. package/dist/plan/archon-blue-green.js +11 -1
  93. package/dist/plan/blue-green-apply.d.ts +37 -1
  94. package/dist/plan/blue-green-apply.js +55 -15
  95. package/dist/plan/blue-green.d.ts +60 -8
  96. package/dist/plan/blue-green.js +115 -10
  97. package/dist/plan/boule.d.ts +23 -1
  98. package/dist/plan/boule.js +35 -2
  99. package/dist/plan/broker-entries.d.ts +28 -3
  100. package/dist/plan/broker-entries.js +74 -3
  101. package/dist/plan/broker-registrar.d.ts +168 -0
  102. package/dist/plan/broker-registrar.js +440 -0
  103. package/dist/plan/broker-workspaces-database.d.ts +48 -0
  104. package/dist/plan/broker-workspaces-database.js +87 -0
  105. package/dist/plan/deploy-health.js +9 -2
  106. package/dist/plan/desktop-callback.js +15 -7
  107. package/dist/plan/diff.d.ts +8 -2
  108. package/dist/plan/diff.js +552 -104
  109. package/dist/plan/edge-access.d.ts +6 -0
  110. package/dist/plan/edge-access.js +1 -0
  111. package/dist/plan/edge-dns.d.ts +6 -0
  112. package/dist/plan/edge-dns.js +9 -7
  113. package/dist/plan/entry-ai-key.d.ts +1 -1
  114. package/dist/plan/entry-ai-key.js +2 -2
  115. package/dist/plan/entry-audit.d.ts +58 -0
  116. package/dist/plan/entry-audit.js +122 -0
  117. package/dist/plan/entry-customer-grants.d.ts +75 -0
  118. package/dist/plan/entry-customer-grants.js +154 -0
  119. package/dist/plan/entry-edge-key.d.ts +26 -1
  120. package/dist/plan/entry-edge-key.js +42 -3
  121. package/dist/plan/entry-jobs.d.ts +64 -0
  122. package/dist/plan/entry-jobs.js +92 -0
  123. package/dist/plan/entry-repo-key-apply.d.ts +55 -0
  124. package/dist/plan/entry-repo-key-apply.js +185 -0
  125. package/dist/plan/entry-repo-key.d.ts +92 -0
  126. package/dist/plan/entry-repo-key.js +235 -0
  127. package/dist/plan/entry-workspaces.d.ts +55 -0
  128. package/dist/plan/entry-workspaces.js +90 -0
  129. package/dist/plan/executor.d.ts +231 -19
  130. package/dist/plan/executor.js +1648 -129
  131. package/dist/plan/gates.d.ts +1 -1
  132. package/dist/plan/gates.js +62 -9
  133. package/dist/plan/github-deploy-keys.d.ts +78 -0
  134. package/dist/plan/github-deploy-keys.js +305 -0
  135. package/dist/plan/github-source.d.ts +53 -0
  136. package/dist/plan/github-source.js +140 -0
  137. package/dist/plan/held-key-refs.d.ts +12 -0
  138. package/dist/plan/held-key-refs.js +12 -0
  139. package/dist/plan/hostnames.d.ts +9 -0
  140. package/dist/plan/hostnames.js +28 -1
  141. package/dist/plan/index.d.ts +9 -0
  142. package/dist/plan/index.js +6 -0
  143. package/dist/plan/jobs-scheduler-http.js +3 -0
  144. package/dist/plan/keys-registrar-http.d.ts +32 -0
  145. package/dist/plan/keys-registrar-http.js +105 -0
  146. package/dist/plan/keys-registrar.d.ts +67 -0
  147. package/dist/plan/keys-registrar.js +103 -0
  148. package/dist/plan/kosmos.d.ts +56 -0
  149. package/dist/plan/kosmos.js +84 -0
  150. package/dist/plan/live.d.ts +1 -1
  151. package/dist/plan/locked-hostnames.d.ts +2 -2
  152. package/dist/plan/locked-hostnames.js +7 -4
  153. package/dist/plan/login-host.js +11 -5
  154. package/dist/plan/machine-paths.d.ts +36 -0
  155. package/dist/plan/machine-paths.js +137 -0
  156. package/dist/plan/mcp.d.ts +1 -1
  157. package/dist/plan/mcp.js +49 -17
  158. package/dist/plan/org-apps-admin.d.ts +108 -0
  159. package/dist/plan/org-apps-admin.js +434 -0
  160. package/dist/plan/org-names.d.ts +3 -3
  161. package/dist/plan/org-names.js +15 -2
  162. package/dist/plan/org-postgres-migrate.js +1 -0
  163. package/dist/plan/own-diff.d.ts +23 -0
  164. package/dist/plan/own-diff.js +53 -0
  165. package/dist/plan/own-hostnames.d.ts +2 -0
  166. package/dist/plan/own-hostnames.js +1 -0
  167. package/dist/plan/own-jobs-route.d.ts +47 -0
  168. package/dist/plan/own-jobs-route.js +91 -0
  169. package/dist/plan/own-limits.d.ts +6 -0
  170. package/dist/plan/own-limits.js +1 -0
  171. package/dist/plan/own-services.d.ts +4 -0
  172. package/dist/plan/own-services.js +4 -0
  173. package/dist/plan/plan-apply.d.ts +39 -0
  174. package/dist/plan/plan-apply.js +163 -9
  175. package/dist/plan/platform-diff.d.ts +2 -2
  176. package/dist/plan/platform-diff.js +7 -2
  177. package/dist/plan/platform-org.d.ts +30 -0
  178. package/dist/plan/platform-org.js +12 -1
  179. package/dist/plan/platform-providers.d.ts +2 -0
  180. package/dist/plan/platform-providers.js +13 -2
  181. package/dist/plan/post-logout-uris.d.ts +1 -1
  182. package/dist/plan/post-logout-uris.js +12 -3
  183. package/dist/plan/private-pages.d.ts +26 -0
  184. package/dist/plan/private-pages.js +39 -0
  185. package/dist/plan/providers-readers.js +20 -1
  186. package/dist/plan/proxy-labels.d.ts +12 -1
  187. package/dist/plan/proxy-labels.js +32 -2
  188. package/dist/plan/release-host.d.ts +6 -2
  189. package/dist/plan/release-host.js +36 -8
  190. package/dist/plan/stage-env.d.ts +19 -1
  191. package/dist/plan/stage-env.js +25 -1
  192. package/dist/plan/stage-guards.d.ts +20 -0
  193. package/dist/plan/stage-guards.js +24 -0
  194. package/dist/plan/stage-isolation.d.ts +24 -0
  195. package/dist/plan/stage-isolation.js +29 -1
  196. package/dist/plan/worker-app-database.d.ts +46 -42
  197. package/dist/plan/worker-app-database.js +150 -153
  198. package/dist/plan/worker-app.d.ts +5 -65
  199. package/dist/plan/worker-app.js +28 -105
  200. package/dist/plan/worker.d.ts +2 -2
  201. package/dist/plan/worker.js +2 -1
  202. package/dist/plan/workspace-client.d.ts +25 -0
  203. package/dist/plan/workspace-client.js +48 -0
  204. package/dist/plan/workspace-sign-in.d.ts +55 -0
  205. package/dist/plan/workspace-sign-in.js +273 -0
  206. package/dist/plan/workspace-worker.d.ts +4 -0
  207. package/dist/plan/workspace-worker.js +13 -0
  208. package/dist/plan/workspace.d.ts +101 -0
  209. package/dist/plan/workspace.js +305 -0
  210. package/dist/release/decide.d.ts +30 -1
  211. package/dist/release/decide.js +73 -11
  212. package/dist/release/health.d.ts +8 -3
  213. package/dist/release/health.js +58 -5
  214. package/dist/release/migration-compatibility.d.ts +66 -0
  215. package/dist/release/migration-compatibility.js +340 -0
  216. package/dist/release/run.d.ts +2 -5
  217. package/dist/release/run.js +4 -0
  218. package/dist/release/state.d.ts +3 -1
  219. package/dist/release/state.js +6 -0
  220. package/dist/release/types.d.ts +20 -0
  221. package/dist/requested-changes.d.ts +11 -2
  222. package/dist/requested-changes.js +13 -1
  223. package/dist/service-labels.d.ts +4 -0
  224. package/dist/service-labels.js +11 -0
  225. package/dist/slug.d.ts +2 -0
  226. package/dist/slug.js +20 -7
  227. package/dist/store/postgres.js +25 -2
  228. package/dist/store/schema.d.ts +73 -1
  229. package/dist/store/schema.js +10 -0
  230. package/dist/worker-app-config.d.ts +1 -11
  231. package/dist/worker-app-config.js +4 -17
  232. package/dist/worker-app-migrations.d.ts +105 -0
  233. package/dist/worker-app-migrations.js +166 -0
  234. package/dist/worker-app-upload.d.ts +103 -0
  235. package/dist/worker-app-upload.js +128 -0
  236. package/package.json +19 -2
@@ -0,0 +1,70 @@
1
+ /**
2
+ * `wtfalch/shell`'s `build-app.yml`: the one GitHub Actions workflow that
3
+ * builds and signs a company's native app shell (estate
4
+ * `.plans/2026-10-22-app-shells.md`). Boule now owns the `NativeAppConfig`,
5
+ * the build runs and the company's store keys (estate#262, foundry#951);
6
+ * foundry keeps only two things, both in this file: dispatching the
7
+ * workflow, and the key Boule presents when it asks the host (Archon) to
8
+ * dispatch it.
9
+ *
10
+ * Neither function here is wired into `plan/diff.ts` or `plan/executor.ts`:
11
+ * `dispatchNativeBuild` runs inside Archon's own `/api/native/builds`
12
+ * route, not foundry's apply loop, and `mintNativeBuildKey` is left for the
13
+ * host to call from wherever it writes Boule's env (`executor.ts`'s
14
+ * `coolifyBoule`, the same way it already calls `mintIfAbsent` for
15
+ * `BOULE_OWNER_SEED_SECRET_ENV`) once that wiring has its own review --
16
+ * adding it here would touch every one of this package's Boule-convergence
17
+ * fixtures for one new env var, which is a larger change than this issue
18
+ * asks for.
19
+ */
20
+ export declare const NATIVE_BUILD_REPO = "wtfalch/shell";
21
+ export declare const NATIVE_BUILD_WORKFLOW_FILE = "build-app.yml";
22
+ export declare const NATIVE_BUILD_WORKFLOW_REF = "main";
23
+ /** Every GitHub call this module makes is cut off after this long. */
24
+ export declare const NATIVE_BUILD_DISPATCH_TIMEOUT_MS = 20000;
25
+ /** A `build-app.yml` dispatch that failed. The text is fixed: it never holds the token or a response body. */
26
+ export declare class NativeBuildDispatchError extends Error {
27
+ readonly status: number | null;
28
+ constructor(status: number | null);
29
+ }
30
+ export interface NativeBuildDispatchDeps {
31
+ /** A GitHub token that may dispatch workflows in {@link NATIVE_BUILD_REPO}. */
32
+ token: string;
33
+ /** Global fetch by default; tests give their own. */
34
+ fetchImpl?: typeof fetch;
35
+ timeoutMs?: number;
36
+ }
37
+ /**
38
+ * Dispatches `build-app.yml` with exactly `{ runId, origin }` as its inputs:
39
+ * `runId` is the run the caller (Archon, behind its own `/api/native/builds`
40
+ * route) already made, `origin` is the company's Boule address the job
41
+ * reports its status back to and trades its OIDC token for keys at. Foundry
42
+ * keeps no run store of its own any more -- Boule does -- so this is the one
43
+ * thing left for foundry to do with a run: start it.
44
+ */
45
+ export declare function dispatchNativeBuild(deps: NativeBuildDispatchDeps, run: {
46
+ runId: string;
47
+ origin: string;
48
+ }): Promise<void>;
49
+ /** The env name Boule presents this key under, to Archon's native-build route. */
50
+ export declare const NATIVE_BUILD_KEY_ENV = "NATIVE_BUILD_KEY";
51
+ export declare const NATIVE_BUILD_KEY_BYTES = 32;
52
+ /** The one thing {@link mintNativeBuildKey} needs from Coolify: a live app's env, to check whether it already has a key. */
53
+ export interface NativeBuildKeyEnvPort {
54
+ listEnvs(appId: string): Promise<ReadonlyArray<{
55
+ key: string;
56
+ value: string;
57
+ is_preview: boolean;
58
+ }>>;
59
+ }
60
+ /**
61
+ * Mints the per-company key Boule presents to Archon's native-build route
62
+ * (estate#262), the same way every other per-company key in this codebase is
63
+ * minted (estate#207): a random value, written to Boule's env once and never
64
+ * rotated, kept unchanged on every later apply once it is there. Returns
65
+ * `null` when Boule's env already holds one -- the caller then writes
66
+ * nothing, so the live value stands. `onSecret`, when given, is told the
67
+ * minted value so the caller can keep it out of its own logs and error
68
+ * messages.
69
+ */
70
+ export declare function mintNativeBuildKey(coolify: NativeBuildKeyEnvPort, appId: string, onSecret?: (value: string) => void): Promise<string | null>;
@@ -0,0 +1,92 @@
1
+ import { randomBytes } from 'node:crypto';
2
+ /**
3
+ * `wtfalch/shell`'s `build-app.yml`: the one GitHub Actions workflow that
4
+ * builds and signs a company's native app shell (estate
5
+ * `.plans/2026-10-22-app-shells.md`). Boule now owns the `NativeAppConfig`,
6
+ * the build runs and the company's store keys (estate#262, foundry#951);
7
+ * foundry keeps only two things, both in this file: dispatching the
8
+ * workflow, and the key Boule presents when it asks the host (Archon) to
9
+ * dispatch it.
10
+ *
11
+ * Neither function here is wired into `plan/diff.ts` or `plan/executor.ts`:
12
+ * `dispatchNativeBuild` runs inside Archon's own `/api/native/builds`
13
+ * route, not foundry's apply loop, and `mintNativeBuildKey` is left for the
14
+ * host to call from wherever it writes Boule's env (`executor.ts`'s
15
+ * `coolifyBoule`, the same way it already calls `mintIfAbsent` for
16
+ * `BOULE_OWNER_SEED_SECRET_ENV`) once that wiring has its own review --
17
+ * adding it here would touch every one of this package's Boule-convergence
18
+ * fixtures for one new env var, which is a larger change than this issue
19
+ * asks for.
20
+ */
21
+ export const NATIVE_BUILD_REPO = 'wtfalch/shell';
22
+ export const NATIVE_BUILD_WORKFLOW_FILE = 'build-app.yml';
23
+ export const NATIVE_BUILD_WORKFLOW_REF = 'main';
24
+ /** Every GitHub call this module makes is cut off after this long. */
25
+ export const NATIVE_BUILD_DISPATCH_TIMEOUT_MS = 20_000;
26
+ /** A `build-app.yml` dispatch that failed. The text is fixed: it never holds the token or a response body. */
27
+ export class NativeBuildDispatchError extends Error {
28
+ status;
29
+ constructor(status) {
30
+ super(status === null
31
+ ? `dispatching ${NATIVE_BUILD_WORKFLOW_FILE} did not answer`
32
+ : `dispatching ${NATIVE_BUILD_WORKFLOW_FILE} failed with status ${status}`);
33
+ this.status = status;
34
+ this.name = 'NativeBuildDispatchError';
35
+ }
36
+ }
37
+ /**
38
+ * Dispatches `build-app.yml` with exactly `{ runId, origin }` as its inputs:
39
+ * `runId` is the run the caller (Archon, behind its own `/api/native/builds`
40
+ * route) already made, `origin` is the company's Boule address the job
41
+ * reports its status back to and trades its OIDC token for keys at. Foundry
42
+ * keeps no run store of its own any more -- Boule does -- so this is the one
43
+ * thing left for foundry to do with a run: start it.
44
+ */
45
+ export async function dispatchNativeBuild(deps, run) {
46
+ const doFetch = deps.fetchImpl ?? fetch;
47
+ const timeoutMs = deps.timeoutMs ?? NATIVE_BUILD_DISPATCH_TIMEOUT_MS;
48
+ let response;
49
+ try {
50
+ response = await doFetch(`https://api.github.com/repos/${NATIVE_BUILD_REPO}/actions/workflows/${NATIVE_BUILD_WORKFLOW_FILE}/dispatches`, {
51
+ method: 'POST',
52
+ headers: {
53
+ Authorization: `Bearer ${deps.token}`,
54
+ Accept: 'application/vnd.github+json',
55
+ 'X-GitHub-Api-Version': '2022-11-28',
56
+ 'Content-Type': 'application/json',
57
+ },
58
+ body: JSON.stringify({
59
+ ref: NATIVE_BUILD_WORKFLOW_REF,
60
+ inputs: { runId: run.runId, origin: run.origin },
61
+ }),
62
+ signal: AbortSignal.timeout(timeoutMs),
63
+ });
64
+ }
65
+ catch (error) {
66
+ console.error(`native build: dispatch did not answer (${error instanceof Error ? error.name : 'error'})`);
67
+ throw new NativeBuildDispatchError(null);
68
+ }
69
+ if (!response.ok)
70
+ throw new NativeBuildDispatchError(response.status);
71
+ }
72
+ /** The env name Boule presents this key under, to Archon's native-build route. */
73
+ export const NATIVE_BUILD_KEY_ENV = 'NATIVE_BUILD_KEY';
74
+ export const NATIVE_BUILD_KEY_BYTES = 32;
75
+ /**
76
+ * Mints the per-company key Boule presents to Archon's native-build route
77
+ * (estate#262), the same way every other per-company key in this codebase is
78
+ * minted (estate#207): a random value, written to Boule's env once and never
79
+ * rotated, kept unchanged on every later apply once it is there. Returns
80
+ * `null` when Boule's env already holds one -- the caller then writes
81
+ * nothing, so the live value stands. `onSecret`, when given, is told the
82
+ * minted value so the caller can keep it out of its own logs and error
83
+ * messages.
84
+ */
85
+ export async function mintNativeBuildKey(coolify, appId, onSecret) {
86
+ const envs = await coolify.listEnvs(appId);
87
+ if (envs.some((e) => e.key === NATIVE_BUILD_KEY_ENV && !e.is_preview))
88
+ return null;
89
+ const value = randomBytes(NATIVE_BUILD_KEY_BYTES).toString('base64url');
90
+ onSecret?.(value);
91
+ return value;
92
+ }
package/dist/org.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { type WorkerAppConfig } from './worker-app-config.js';
2
2
  export type { WorkerAppConfig } from './worker-app-config.js';
3
+ export { AI_ENTRY_PERMISSIONS, FILES_ENTRY_PERMISSIONS, MCP_PATH, ONBOARDING_PATH_MAX_LENGTH, ROOM_STEPS_MAX, ROOM_STEPS_MIN, ROOM_TEXT_MAX, WORKSPACE_ROLE, WORKSPACE_ROLES_MAX, } from './entry-rules.js';
3
4
  /**
4
5
  * Opaque, minted by foundry when an org is created. Keys the declared org,
5
6
  * the applied state, every estate-wide service's id map, and the chat
@@ -218,6 +219,11 @@ export interface IssuerApp {
218
219
  * validates is always one Coolify itself accepts.
219
220
  */
220
221
  export type HostingBuildPack = 'dockerfile' | 'nixpacks' | 'static' | 'dockercompose';
222
+ /** The company's GitHub App installation: the numeric installation id and the account (user or org login) it is installed on. */
223
+ export interface HostingGithub {
224
+ installationId: number;
225
+ account: string;
226
+ }
221
227
  /**
222
228
  * The org's hosted apps and what runs beside them (#71, part of #14's
223
229
  * adopted/standard/bespoke map) -- distinct from the cms site (`SiteConfig`
@@ -233,6 +239,12 @@ export type HostingBuildPack = 'dockerfile' | 'nixpacks' | 'static' | 'dockercom
233
239
  * be true.
234
240
  */
235
241
  export interface HostingConfig {
242
+ /**
243
+ * The company's installation of the platform's GitHub App. With it an entry's `repo` may be
244
+ * `<account>/<name>` as well as `wtfalch/<name>`, and the planner makes the company its own Coolify GitHub
245
+ * source (`coolify:github-source`) from the installation id. Absent: only `wtfalch/<name>`.
246
+ */
247
+ github?: HostingGithub;
236
248
  /** The org's apps run on Coolify. */
237
249
  enabled: boolean;
238
250
  /** Served on `DeclaredOrg.domain` (and its Cloudflare zone managed) instead of `<slug>.oikos.host` (`../slug.ts`). */
@@ -289,7 +301,7 @@ export type AccessRule = {
289
301
  /** The key of the entry that is today's one app: the Coolify app named `<slug>`. */
290
302
  export declare const APP_ENTRY_KEY = "app";
291
303
  /** Why a validator refused. `code` is stable and never reworded; `message` is for people. */
292
- export type OrgRefusalCode = 'hosting_tunnel_needs_hosting' | 'issuer_app_name_missing' | 'issuer_app_name_not_unique' | 'issuer_app_kind_unknown' | 'issuer_app_name_reserved_agora' | 'issuer_app_name_reserved_boule' | 'hosting_needs_entry' | 'hosting_main_count' | 'entry_key_not_unique' | 'ai_webhook_path_not_unique' | 'worker_route_not_unique' | 'database_not_unique' | 'access_hostname_not_unique' | 'access_hostname_invalid' | 'access_needs_hosting' | 'access_kind_unknown' | 'access_service_token_name_missing' | 'access_hostname_locked' | 'access_hostname_not_served' | 'gate_limit_invalid' | 'hosting_repo_invalid' | 'gate_needs_domain' | 'gate_key_invalid' | 'gate_key_not_unique' | 'gate_needs_hostname' | 'gate_hostname_not_entry_hostname' | 'gate_hostname_held_twice' | 'gate_hostname_locked' | 'gate_limit_exceeded' | 'wildcard_needs_domain' | 'wildcard_held_twice' | 'hostnames_need_domain' | 'hostname_needed' | 'hostname_invalid' | 'hostname_not_unique' | 'hostname_locked' | 'entry_key_invalid' | 'entry_key_reserved' | 'entry_repo_invalid' | 'entry_folder_invalid' | 'entry_branch_empty' | 'entry_build_pack_unknown' | 'entry_build_pack_unavailable' | 'entry_port_invalid' | 'entry_health_path_invalid' | 'entry_capabilities_invalid' | 'entry_database_needs_hosting' | 'blue_green_needs_hostname' | 'env_name_empty' | 'env_name_reserved' | 'env_source_platform_mail_key' | 'env_source_unknown' | 'env_shared_secret_name_invalid' | 'env_held_secret_name_mismatch' | 'env_held_secret_name_invalid' | 'env_looks_like_secret' | 'builtin_app_invalid' | 'blue_green_not_object' | 'blue_green_steps_length' | 'blue_green_steps_range' | 'blue_green_steps_order' | 'blue_green_steps_end' | 'blue_green_hold_invalid' | 'volumes_not_list' | 'volumes_too_many' | 'volume_name_invalid' | 'volume_name_logs' | 'volume_name_not_unique' | 'volume_mount_path_invalid' | 'volume_mount_path_too_long' | 'volume_mount_path_log_dir' | 'volume_mount_path_forbidden' | 'volume_mount_path_not_unique' | 'auth_needs_hosting' | 'auth_users_invalid' | 'auth_registration_invalid' | 'registration_open_needs_customers' | 'auth_replaces_broker_entry_invalid' | 'auth_replaces_broker_entry_owned' | 'auth_needs_exact_hostname' | 'mcp_needs_open_customers' | 'mcp_path_invalid' | 'mcp_onboarding_path_invalid' | 'auth_sign_in_invalid' | 'email_code_needs_customers' | 'auth_room_invalid' | 'auth_room_needs_customers' | 'auth_room_not_unique' | 'client_name_taken' | 'ai_needs_hosting' | 'ai_offerings_invalid' | 'ai_permissions_invalid' | 'ai_webhook_path_invalid' | 'ai_webhook_needs_exact_hostname' | 'ai_env_clash' | 'files_needs_hosting' | 'files_collections_invalid' | 'files_permissions_invalid' | 'files_share_host_invalid' | 'files_share_host_needs_domain' | 'files_env_clash' | 'worker_needs_hosting' | 'worker_needs_repo' | 'worker_key_too_long' | 'worker_artifact_invalid' | 'worker_route_invalid' | 'worker_route_off_domain' | 'worker_rate_limit_name_missing' | 'worker_rate_limit_namespace_missing' | 'worker_rate_limit_invalid' | 'worker_rate_limit_period_invalid' | 'worker_app_invalid' | 'edge_not_true' | 'edge_needs_hosting' | 'edge_needs_domain' | 'edge_env_clash' | 'customers_invalid' | 'customers_sign_up_needs_enabled' | 'registration_open_needs_sign_up';
304
+ export type OrgRefusalCode = 'hosting_tunnel_needs_hosting' | 'issuer_app_name_missing' | 'issuer_app_name_not_unique' | 'issuer_app_kind_unknown' | 'issuer_app_name_reserved_agora' | 'issuer_app_name_reserved_boule' | 'issuer_app_name_reserved_kosmos' | 'hosting_needs_entry' | 'hosting_main_count' | 'entry_key_not_unique' | 'ai_webhook_path_not_unique' | 'worker_route_not_unique' | 'database_not_unique' | 'access_hostname_not_unique' | 'access_hostname_invalid' | 'access_needs_hosting' | 'access_kind_unknown' | 'access_service_token_name_missing' | 'access_hostname_locked' | 'access_hostname_not_served' | 'gate_limit_invalid' | 'hosting_repo_invalid' | 'hosting_github_invalid' | 'gate_needs_domain' | 'gate_key_invalid' | 'gate_key_not_unique' | 'gate_needs_hostname' | 'gate_hostname_not_entry_hostname' | 'gate_hostname_held_twice' | 'gate_hostname_locked' | 'gate_limit_exceeded' | 'wildcard_needs_domain' | 'wildcard_held_twice' | 'hostnames_need_domain' | 'hostname_needed' | 'hostname_invalid' | 'hostname_not_unique' | 'hostname_locked' | 'hosted_name_invalid' | 'hosted_name_not_unique' | 'hosted_name_worker_app' | 'entry_workspace_invalid' | 'entry_workspace_unsupported' | 'entry_live_needs_workspace' | 'entry_live_commit_invalid' | 'gate_key_taken_by_workspace' | 'gate_key_taken_by_private_page' | 'private_page_invalid' | 'private_page_path_invalid' | 'private_page_duplicate' | 'private_page_limit' | 'private_page_needs_hosting' | 'private_page_roles_invalid' | 'private_page_people_invalid' | 'entry_key_invalid' | 'entry_key_reserved' | 'entry_repo_invalid' | 'entry_folder_invalid' | 'entry_branch_empty' | 'entry_build_pack_unknown' | 'entry_build_pack_unavailable' | 'entry_port_invalid' | 'entry_health_path_invalid' | 'entry_capabilities_invalid' | 'entry_database_needs_hosting' | 'blue_green_needs_hostname' | 'env_name_empty' | 'env_name_reserved' | 'env_source_platform_mail_key' | 'env_source_unknown' | 'env_shared_secret_name_invalid' | 'env_held_secret_name_mismatch' | 'env_held_secret_name_invalid' | 'env_looks_like_secret' | 'builtin_app_invalid' | 'blue_green_not_object' | 'blue_green_steps_length' | 'blue_green_steps_range' | 'blue_green_steps_order' | 'blue_green_steps_end' | 'blue_green_hold_invalid' | 'blue_green_advance_invalid' | 'blue_green_min_requests_invalid' | 'blue_green_error_margin_invalid' | 'blue_green_staff_first_invalid' | 'volumes_not_list' | 'volumes_too_many' | 'volume_name_invalid' | 'volume_name_logs' | 'volume_name_not_unique' | 'volume_mount_path_invalid' | 'volume_mount_path_too_long' | 'volume_mount_path_log_dir' | 'volume_mount_path_forbidden' | 'volume_mount_path_not_unique' | 'auth_needs_hosting' | 'auth_users_invalid' | 'auth_registration_invalid' | 'registration_open_needs_customers' | 'auth_replaces_broker_entry_invalid' | 'auth_replaces_broker_entry_owned' | 'auth_needs_exact_hostname' | 'mcp_needs_open_customers' | 'mcp_path_invalid' | 'mcp_onboarding_path_invalid' | 'auth_sign_in_invalid' | 'email_code_needs_customers' | 'auth_room_invalid' | 'auth_room_needs_customers' | 'auth_room_not_unique' | 'client_name_taken' | 'workspaces_needs_customers' | 'workspaces_needs_server' | 'workspaces_roles_invalid' | 'workspaces_role_reserved' | 'workspaces_invite_path_invalid' | 'workspaces_env_clash' | 'ai_needs_hosting' | 'ai_offerings_invalid' | 'ai_permissions_invalid' | 'ai_webhook_path_invalid' | 'ai_webhook_needs_exact_hostname' | 'ai_env_clash' | 'files_needs_hosting' | 'files_collections_invalid' | 'files_permissions_invalid' | 'files_share_host_invalid' | 'files_share_host_needs_domain' | 'files_env_clash' | 'worker_needs_hosting' | 'worker_needs_repo' | 'worker_needs_own_repo' | 'worker_key_too_long' | 'worker_artifact_invalid' | 'worker_route_invalid' | 'worker_route_off_domain' | 'worker_rate_limit_name_missing' | 'worker_rate_limit_namespace_missing' | 'worker_rate_limit_invalid' | 'worker_rate_limit_period_invalid' | 'worker_app_invalid' | 'edge_not_true' | 'edge_needs_hosting' | 'edge_needs_domain' | 'edge_env_clash' | 'mail_not_true' | 'mail_needs_hosting' | 'mail_env_clash' | 'jobs_not_true' | 'jobs_needs_hosting' | 'jobs_env_clash' | 'customer_grants_not_true' | 'customer_grants_needs_customers' | 'customer_grants_env_clash' | 'repo_use_invalid' | 'repo_use_needs_repo' | 'repo_use_env_clash' | 'audit_not_true' | 'audit_needs_hosting' | 'audit_needs_worker_database' | 'customers_invalid' | 'customers_sign_up_needs_enabled' | 'registration_open_needs_sign_up';
293
305
  /**
294
306
  * One refusal: a stable `code`, the dotted `path` into {@link DeclaredOrg} it is about
295
307
  * (`hosting.entries.terminal.uses.auth.registration`) and the sentence for people.
@@ -322,6 +334,11 @@ export interface HostingEntry {
322
334
  buildPack: HostingBuildPack;
323
335
  /** The port the app listens on. `null` is Coolify's default. */
324
336
  port: number | null;
337
+ /**
338
+ * The app's live address on the platform's apps domain: `<hostedName>.companyincluded.app`. A
339
+ * label {@link hostedLabelRefusal} accepts; unique within the org (the host checks it across orgs).
340
+ */
341
+ hostedName?: string;
325
342
  /**
326
343
  * The org's main app: served at the org's main address (`<slug>.oikos.host`, or the domain's
327
344
  * apex), whatever its key. An entry keyed {@link APP_ENTRY_KEY} with `main` unset is main.
@@ -374,8 +391,8 @@ export interface HostingEntry {
374
391
  * F8: a Cloudflare Worker foundry deploys from this entry's own repo,
375
392
  * beside the app, on the org's own zone. Only our own repos may use this
376
393
  * (#318: outside customers need Workers for Platforms, not built yet), so
377
- * `validateEntryWorker` refuses it unless `repo` is set (every non-null
378
- * `repo` already matches `wtfalch/...`, {@link REPO}) and the route's zone
394
+ * `validateEntryWorker` refuses it unless `repo` is set and under `wtfalch`
395
+ * ({@link REPO}; a company's own GitHub account is refused) and the route's zone
379
396
  * is the org's own domain.
380
397
  */
381
398
  worker?: WorkerConfig;
@@ -390,7 +407,63 @@ export interface HostingEntry {
390
407
  * blue and a green copy behind a load balancer on every server it manages, and the tunnel routes to the balancer.
391
408
  */
392
409
  blueGreen?: BlueGreenConfig;
410
+ /**
411
+ * A private workspace for the app: it runs at `workspaceHost(slug, key)` behind a Cloudflare Access gate that
412
+ * lets in the company's people and `testers`. A server entry's workspace is the blue/green stage machinery
413
+ * (its own empty database, the stage mail sink, the stage guards) without colors; a `workerApp` entry's is a
414
+ * second Worker script `<slug>-<key>-work`. See `plan/workspace.ts`. Not combined with `blueGreen`.
415
+ */
416
+ workspace?: EntryWorkspace;
417
+ /**
418
+ * Whether the app is live. Absent means true. `false` plans no public hostname, DNS record, live app or live
419
+ * Worker: only the workspace exists, so it needs `workspace`. Setting it to true makes the live app from the
420
+ * commit the workspace runs, with its own database; the workspace stays.
421
+ */
422
+ live?: boolean;
423
+ /**
424
+ * The 40 character commit the live app runs, set by a promote. Needs `workspace` and a server (repo) entry.
425
+ * When set, the live app is made from it and later moved to it (`git_commit_sha` patched, then deployed),
426
+ * whatever the workspace runs. Absent: the app is made from the workspace's commit and stays there. It may be
427
+ * a commit the workspace never ran (a rollback).
428
+ */
429
+ liveCommit?: string;
430
+ /**
431
+ * Parts of the app only the company's people may open, each gated by a path-scoped Cloudflare Access
432
+ * application on `<host><path>`, on the live hosts and (with a `workspace`) on the workspace host. See
433
+ * `plan/private-pages.ts`.
434
+ */
435
+ privatePages?: PrivatePage[];
436
+ }
437
+ /** One private page of an entry. */
438
+ export interface PrivatePage {
439
+ /** Starts with `/`; letters, digits and `. _ ~ - /`; no query, no trailing slash, not `/` itself. */
440
+ path: string;
441
+ /** The company's role ids that may open the page. Foundry cannot resolve roles; see `people`. */
442
+ roles: string[];
443
+ /**
444
+ * The company people holding `roles`, as email addresses, resolved by the host that declares the org (as
445
+ * `workspace.testers` is supplied). The gate admits the owners plus these. Absent: the owners only.
446
+ */
447
+ people?: string[];
448
+ }
449
+ export declare const MAX_PRIVATE_PAGES = 20;
450
+ export declare const MAX_PRIVATE_PAGE_ROLES = 50;
451
+ export declare const MAX_PRIVATE_PAGE_PEOPLE = 200;
452
+ export declare const PRIVATE_PAGE_PATH: RegExp;
453
+ /** The keys of the Access gates private pages get: `pp-` (live) or `wp-` (workspace) and 16 hex characters. */
454
+ export declare const PRIVATE_PAGE_GATE_KEY: RegExp;
455
+ /** A full git commit: 40 lowercase hex characters. */
456
+ export declare const FULL_COMMIT_SHA: RegExp;
457
+ /** The people outside the company who may open an entry's workspace. */
458
+ export interface EntryWorkspace {
459
+ /** Email addresses, at most {@link MAX_WORKSPACE_TESTERS}, distinct when lowercased. */
460
+ testers: string[];
393
461
  }
462
+ export declare const MAX_WORKSPACE_TESTERS = 50;
463
+ /** An entry is live unless it says `live: false`. */
464
+ export declare const isLiveEntry: (entry: Pick<HostingEntry, "live">) => boolean;
465
+ /** Trimmed, lowercased and distinct testers, in the order first seen. The planner and hosts read testers through this. */
466
+ export declare function normalizeTesters(testers: readonly string[]): string[];
394
467
  /**
395
468
  * `boule` and `agora` on a company: the built-in apps foundry runs for every company (`<slug>-boule`,
396
469
  * `<slug>-agora`). Only blue/green is declared here; absent means off. Optional so every existing literal and
@@ -399,11 +472,46 @@ export interface HostingEntry {
399
472
  export interface OrgBuiltinApp {
400
473
  blueGreen?: BlueGreenConfig;
401
474
  }
475
+ /**
476
+ * The company's Boule. `apps` (F3, F5, F7) turns on the managed-apps platform keys: Boule installs and runs apps
477
+ * for the company, so foundry gives it an edge key that may deploy the company's own Workers
478
+ * (`plan/entry-edge-key.ts`), a registrar key at the sign-in broker (`plan/broker-registrar.ts`) and a registrar key
479
+ * at the signed-records service (`plan/keys-registrar.ts`). Absent is off. Boule's apps are not in `hosting.entries`,
480
+ * so this switch is how the plan knows the company has any.
481
+ */
482
+ export interface BouleApp extends OrgBuiltinApp {
483
+ apps?: boolean;
484
+ }
485
+ /** True while the company's Boule runs apps (`org.boule.apps`). */
486
+ export declare const appsOn: (org: Pick<DeclaredOrg, "boule">) => boolean;
487
+ /**
488
+ * The company's kosmos: its own creative workspace (canvas, libraries, photos, pages) that drives the
489
+ * frontend of its apps. Unlike Boule and Agora, which every company runs, kosmos is set up only when the
490
+ * company asks (William, 2026-10-22) -- absent, or `enabled` absent or `false`, plans nothing for it.
491
+ * `enabled: true` plans its ZITADEL client, its database and its live and idle copies; turning it back off
492
+ * parks the app but keeps the database, the same rule every other switched-off service follows.
493
+ */
494
+ export interface KosmosApp extends OrgBuiltinApp {
495
+ enabled?: boolean;
496
+ }
497
+ /** True while the company's kosmos is turned on (`org.kosmos.enabled`). */
498
+ export declare const kosmosOn: (org: Pick<DeclaredOrg, "kosmos">) => boolean;
402
499
  /** The traffic steps (percent to the new color) and the hold at each step. Both default in `plan/blue-green.ts`. */
403
500
  export interface BlueGreenConfig {
404
501
  /** Ascending, each 1..100, ending at 100. */
405
502
  steps?: number[];
406
503
  stepHoldMinutes?: number;
504
+ /**
505
+ * How a step is left. `signal` (default): the hold passed and the error rate matches the old color's.
506
+ * `time`: the hold passed and the error rate is not failing. `approval`: a person approves each step.
507
+ */
508
+ advance?: 'time' | 'signal' | 'approval';
509
+ /** Requests the new color needs before its error rate counts (whole number 1..100000). Default 20. */
510
+ minRequests?: number;
511
+ /** The new color matches the old while its error rate is at most the old plus this (0..1). Default 0.02. */
512
+ errorMargin?: number;
513
+ /** A promote deploys the idle color and holds it at weight 0 for staff until an `open` action. Default false. */
514
+ staffFirst?: boolean;
407
515
  }
408
516
  /** One named volume on a hosting entry's app. */
409
517
  export interface EntryVolume {
@@ -498,6 +606,16 @@ export interface EntryAuth {
498
606
  * `users: 'customers'`.
499
607
  */
500
608
  signIn?: 'password' | 'email-code';
609
+ /**
610
+ * The entry's own customers get workspaces in the broker: they make one, invite people by email and give
611
+ * each member one of `roles`. The first role is the owner role. `invitePath` is the page on the entry's
612
+ * host the invitation mail links to (`<host><invitePath>?token=`). Needs `users: 'customers'` and an
613
+ * entry that runs as a server. The entry gets `WORKSPACES_URL` and `WORKSPACES_API_KEY`.
614
+ */
615
+ workspaces?: {
616
+ roles: string[];
617
+ invitePath: string;
618
+ };
501
619
  /**
502
620
  * The left-half text of the customers' sign-in window. Written on the
503
621
  * customers organisation as metadata `wtfalch.room` (JSON, keys in the
@@ -506,6 +624,8 @@ export interface EntryAuth {
506
624
  */
507
625
  room?: EntryAuthRoom;
508
626
  }
627
+ /** The env names `uses.auth.workspaces` writes; `env` may not name them. */
628
+ export declare const WORKSPACES_ENTRY_ENV_NAMES: readonly ["WORKSPACES_URL", "WORKSPACES_API_KEY"];
509
629
  export interface EntryAuthRoom {
510
630
  name: string;
511
631
  headline: string;
@@ -514,20 +634,6 @@ export interface EntryAuthRoom {
514
634
  description?: string;
515
635
  steps?: number;
516
636
  }
517
- /** Longest value of each `uses.auth.room` text field. */
518
- export declare const ROOM_TEXT_MAX: {
519
- readonly name: 40;
520
- readonly headline: 120;
521
- readonly line: 200;
522
- readonly title: 60;
523
- readonly description: 200;
524
- };
525
- export declare const ROOM_STEPS_MIN = 2;
526
- export declare const ROOM_STEPS_MAX = 9;
527
- /** `uses.auth.mcp.onboardingPath`: absolute, no query, no fragment, no dot segments. */
528
- export declare const ONBOARDING_PATH_MAX_LENGTH = 200;
529
- /** The MCP endpoint path: absolute, plain characters, no dot segments, no query. */
530
- export declare const MCP_PATH: RegExp;
531
637
  /**
532
638
  * AI for one entry: a key below the org's own ai key, the `AI_*` env and,
533
639
  * with `webhookPath`, the ai webhook. `offerings` is what the app may call,
@@ -540,8 +646,6 @@ export interface EntryAi {
540
646
  permissions?: string[];
541
647
  webhookPath?: string;
542
648
  }
543
- /** What an entry's ai key may hold: the most the org's own key holds. */
544
- export declare const AI_ENTRY_PERMISSIONS: readonly ["ai.offerings:invoke", "ai.runs:create"];
545
649
  /** The env names `uses.ai` writes; `env` may not name them. */
546
650
  export declare const AI_ENTRY_ENV_NAMES: readonly ["AI_URL", "AI_ORGANISATION_ID", "AI_API_KEY", "AI_WEBHOOK_SECRET", "AI_OFFERINGS_JSON"];
547
651
  /**
@@ -559,8 +663,6 @@ export interface EntryFiles {
559
663
  permissions?: string[];
560
664
  shareHost?: boolean;
561
665
  }
562
- /** What an entry's files key may hold: read and write on its collections, and the most the org's own key holds besides minting. */
563
- export declare const FILES_ENTRY_PERMISSIONS: readonly ["files.collections:read", "files.files:read", "files.files:download", "files.files:upload", "files.files:replace", "files.files:delete"];
564
666
  /** The env names `uses.files` always writes, besides one `FILES_COLLECTION_<NAME>_ID` per collection. */
565
667
  export declare const FILES_ENTRY_ENV_NAMES: readonly ["FILES_URL", "FILES_ORGANISATION_ID", "FILES_API_KEY"];
566
668
  export declare const filesCollectionEnvName: (name: string) => string;
@@ -576,9 +678,49 @@ export interface HostingEntryUses {
576
678
  * org `domain`.
577
679
  */
578
680
  edge?: true;
681
+ /**
682
+ * The entry sends mail from the company's mail domain: it gets the company's
683
+ * Lettermint token and `MAIL_FROM`, as the main app does. In a workspace the
684
+ * stage gets the mail sink's env only (`stageMailEnv`).
685
+ */
686
+ mail?: true;
687
+ /**
688
+ * The entry is a project of its own in the jobs scheduler and gets
689
+ * `JOBS_SCHEDULER_URL` (the scheduler's public origin), `JOBS_SIGNING_SECRET`
690
+ * and `JOBS_SYNC_KEY` (`plan/entry-jobs.ts`). Blocked, not planned, until the
691
+ * scheduler's public route (`plan/own-jobs-route.ts`) is recorded: the owner
692
+ * company's for a company on shared services, the company's own otherwise.
693
+ * A workspace gets none of the three.
694
+ */
695
+ jobs?: true;
696
+ /**
697
+ * The app keeps a tamper-evident audit log (`@wtfalch/audit`). A Worker app with a database: the append-only
698
+ * tables and the two chain functions are derived (`audit/ledger-set.ts`), never listed by hand, and the
699
+ * platform's sealing service signs and anchors the ledger on a schedule with a key the app never holds
700
+ * (`plan/entry-audit.ts`, `audit/`). The erase functions are not granted.
701
+ */
702
+ audit?: true;
703
+ /**
704
+ * The app implements Boule's customer-grants routes under `/api/customer-grants`, called by a machine
705
+ * (Boule), not a person. F11 only lets that path through a gated host (`entryMachinePaths`); F13 builds
706
+ * the rest (the credential, the app env and Boule's entry).
707
+ */
708
+ customerGrants?: true;
709
+ /**
710
+ * F12: the entry gets credentials for its own `repo`: a GitHub deploy key made for it (write only with
711
+ * `access: 'write'`), delivered as `GIT_REPO_URL` (`git@github.com:<owner>/<name>.git`) and `GIT_SSH_KEY`
712
+ * (`plan/entry-repo-key.ts`). Needs `repo`. Removed with the entry or the flag; a workspace copy gets none.
713
+ */
714
+ repo?: {
715
+ access: 'read' | 'write';
716
+ };
579
717
  }
580
718
  /** The env names `uses.edge` writes; `env` may not name them (#317). */
581
719
  export declare const EDGE_ENTRY_ENV_NAMES: readonly ["EDGE_URL", "EDGE_API_KEY"];
720
+ /** The env names `uses.mail` writes; `env` may not name them. */
721
+ export declare const MAIL_ENTRY_ENV_NAMES: readonly ["LETTERMINT_API_TOKEN", "MAIL_FROM"];
722
+ /** The env names `uses.jobs` writes; `env` may not name them. */
723
+ export declare const JOBS_ENTRY_ENV_NAMES: readonly ["JOBS_SCHEDULER_URL", "JOBS_SIGNING_SECRET", "JOBS_SYNC_KEY"];
582
724
  /**
583
725
  * A plain string is pushed as it is, so `validateHosting` refuses one that
584
726
  * looks like a secret. `{ from }` names a value the plan already mints.
@@ -661,10 +803,26 @@ export interface HostingNames {
661
803
  export declare function normalizeHostingRepos(repos: readonly string[] | undefined): string[];
662
804
  /** {@link validateHosting} with the code and path of the first refusal. */
663
805
  export declare function refuseHosting(hosting: HostingConfig, names?: HostingNames): OrgRefusal | null;
806
+ /**
807
+ * The company's GitHub config when `repo` is under its account, else `undefined`: such an entry is built
808
+ * through the company's own Coolify source, any other through the platform's. Read it only through this.
809
+ */
810
+ export declare function companyGithubFor(hosting: Pick<HostingConfig, 'github'>, repo: string | null): HostingGithub | undefined;
664
811
  /** `blueGreen`: steps ascending, each a whole number 1..100, the last 100; a hold of 1..1440 minutes. */
665
812
  export declare function validateBlueGreen(value: unknown, at: string): string | null;
666
- /** {@link refuseBlueGreen} for the `blueGreen` of the company's Boule and Agora; the same codes. */
667
- export declare function refuseBuiltinBlueGreen(org: Pick<DeclaredOrg, 'boule' | 'agora'>): OrgRefusal | null;
813
+ /**
814
+ * {@link refuseBlueGreen} for the `blueGreen` of the company's Boule, Agora and kosmos; the same codes.
815
+ * Also refuses an `issuerApps` entry named `kosmos` while kosmos is on -- the same reservation
816
+ * {@link refuseHosting}'s own loop already gives Agora and Boule, but kosmos is opt-in, so it is checked here
817
+ * instead, the same way {@link validateSite} reserves `cms` only while `site.enabled` is true.
818
+ */
819
+ export declare function refuseBuiltinBlueGreen(org: Pick<DeclaredOrg, 'boule' | 'agora' | 'kosmos' | 'hosting'>): OrgRefusal | null;
820
+ /** The env names `uses.repo` writes; `env` may not name them. */
821
+ export declare const REPO_ENTRY_ENV_NAMES: readonly ["GIT_REPO_URL", "GIT_SSH_KEY"];
822
+ /** The env name `uses.customerGrants` writes; `env` may not name it. */
823
+ export declare const CUSTOMER_GRANTS_ENTRY_ENV_NAMES: readonly ["CUSTOMER_GRANTS_CREDENTIAL"];
824
+ /** The platform zone new app addresses (`<hostedName>.companyincluded.app`) and every workspace live in. */
825
+ export declare const APPS_DOMAIN = "companyincluded.app";
668
826
  export declare function validateDomain(domain: string): string | null;
669
827
  /**
670
828
  * A broker entry name foundry writes itself: Archon's, and any company's Boule.
@@ -1082,9 +1240,11 @@ export interface DeclaredOrg {
1082
1240
  /** Absent means `{ inApp: true }`. Optional so every existing literal and fixture compiles unchanged. */
1083
1241
  signIn?: SignInConfig;
1084
1242
  /** The company's Boule. Absent means no blue/green. */
1085
- boule?: OrgBuiltinApp;
1243
+ boule?: BouleApp;
1086
1244
  /** The company's Agora, a static app: its release moves on hard fails and time only. Absent means no blue/green. */
1087
1245
  agora?: OrgBuiltinApp;
1246
+ /** The company's kosmos. Absent or `enabled: false` means it is not set up (2026-10-22). */
1247
+ kosmos?: KosmosApp;
1088
1248
  /** Absent means `{ enabled: false, signUp: false }`. Optional so every existing literal and fixture compiles unchanged. */
1089
1249
  customers?: CustomersConfig;
1090
1250
  /** Parts of this company held on their own version. Absent or null follows the platform. Ignored for the owner company. */