@wtfalch/foundry 0.76.0 → 0.77.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 (127) hide show
  1. package/dist/app-canary-worker.d.ts +35 -0
  2. package/dist/app-canary-worker.js +56 -0
  3. package/dist/audit/connector.d.ts +6 -2
  4. package/dist/audit/connector.js +3 -2
  5. package/dist/audit/evidence.d.ts +4 -0
  6. package/dist/audit/evidence.js +14 -2
  7. package/dist/audit/host-store.d.ts +58 -0
  8. package/dist/audit/host-store.js +147 -0
  9. package/dist/audit/host.d.ts +53 -0
  10. package/dist/audit/host.js +294 -0
  11. package/dist/audit/index.d.ts +2 -0
  12. package/dist/audit/index.js +2 -0
  13. package/dist/audit/registry.d.ts +14 -0
  14. package/dist/audit/sealer-role.d.ts +12 -0
  15. package/dist/audit/sealer-role.js +26 -2
  16. package/dist/audit/tick.d.ts +10 -2
  17. package/dist/audit/tick.js +15 -2
  18. package/dist/catalogue/copy-en.js +5 -0
  19. package/dist/catalogue/integrations.js +5 -0
  20. package/dist/entry-rules.d.ts +35 -0
  21. package/dist/entry-rules.js +35 -0
  22. package/dist/hosted-label.js +2 -1
  23. package/dist/index.d.ts +11 -3
  24. package/dist/index.js +6 -1
  25. package/dist/migrations/0072_audit_host.sql +46 -0
  26. package/dist/migrations/0074_platform_app_canary.sql +11 -0
  27. package/dist/modules/ai.js +15 -1
  28. package/dist/modules/assistants.js +3 -0
  29. package/dist/modules/email.d.ts +6 -1
  30. package/dist/modules/email.js +88 -5
  31. package/dist/modules/files.js +79 -2
  32. package/dist/modules/index.js +7 -0
  33. package/dist/modules/jobs.d.ts +5 -1
  34. package/dist/modules/jobs.js +74 -4
  35. package/dist/modules/reporting.d.ts +9 -0
  36. package/dist/modules/reporting.js +29 -0
  37. package/dist/modules/sign-in.js +20 -2
  38. package/dist/modules/types.d.ts +2 -0
  39. package/dist/modules/workspaces.js +3 -0
  40. package/dist/org.d.ts +114 -13
  41. package/dist/org.js +233 -20
  42. package/dist/plan/ai-boule-key-apply.d.ts +65 -0
  43. package/dist/plan/ai-boule-key-apply.js +169 -0
  44. package/dist/plan/ai-boule-key.d.ts +89 -0
  45. package/dist/plan/ai-boule-key.js +195 -0
  46. package/dist/plan/analytics.d.ts +8 -1
  47. package/dist/plan/analytics.js +8 -1
  48. package/dist/plan/app-canary-apply.d.ts +49 -0
  49. package/dist/plan/app-canary-apply.js +232 -0
  50. package/dist/plan/app-canary.d.ts +44 -0
  51. package/dist/plan/app-canary.js +110 -0
  52. package/dist/plan/blue-green-apply.d.ts +6 -2
  53. package/dist/plan/blue-green-apply.js +14 -3
  54. package/dist/plan/broker-entries.js +20 -5
  55. package/dist/plan/company-backups.js +2 -0
  56. package/dist/plan/desktop-auth.d.ts +17 -4
  57. package/dist/plan/desktop-auth.js +22 -5
  58. package/dist/plan/diff.d.ts +22 -0
  59. package/dist/plan/diff.js +231 -18
  60. package/dist/plan/entry-files-key.d.ts +39 -3
  61. package/dist/plan/entry-files-key.js +103 -6
  62. package/dist/plan/entry-jobs-boule-key-apply.d.ts +65 -0
  63. package/dist/plan/entry-jobs-boule-key-apply.js +192 -0
  64. package/dist/plan/entry-jobs-boule-key.d.ts +59 -0
  65. package/dist/plan/entry-jobs-boule-key.js +141 -0
  66. package/dist/plan/entry-jobs.d.ts +2 -0
  67. package/dist/plan/entry-reporting-key.d.ts +36 -0
  68. package/dist/plan/entry-reporting-key.js +136 -0
  69. package/dist/plan/executor.d.ts +108 -1
  70. package/dist/plan/executor.js +676 -24
  71. package/dist/plan/files-boule-key-apply.d.ts +73 -0
  72. package/dist/plan/files-boule-key-apply.js +223 -0
  73. package/dist/plan/files-boule-key.d.ts +92 -0
  74. package/dist/plan/files-boule-key.js +175 -0
  75. package/dist/plan/hostnames.js +4 -0
  76. package/dist/plan/index.d.ts +4 -0
  77. package/dist/plan/index.js +2 -0
  78. package/dist/plan/jobs-scheduler-http.d.ts +13 -0
  79. package/dist/plan/jobs-scheduler-http.js +39 -0
  80. package/dist/plan/live.d.ts +1 -1
  81. package/dist/plan/locked-hostnames.d.ts +1 -1
  82. package/dist/plan/locked-hostnames.js +4 -1
  83. package/dist/plan/login-host.js +2 -2
  84. package/dist/plan/org-names.js +3 -0
  85. package/dist/plan/own-diff.js +3 -2
  86. package/dist/plan/own-jobs-route.d.ts +5 -2
  87. package/dist/plan/own-jobs-route.js +6 -2
  88. package/dist/plan/own-limits.d.ts +6 -0
  89. package/dist/plan/own-limits.js +1 -0
  90. package/dist/plan/plan-apply.d.ts +24 -0
  91. package/dist/plan/plan-apply.js +39 -4
  92. package/dist/plan/platform-diff.d.ts +2 -0
  93. package/dist/plan/platform-diff.js +15 -1
  94. package/dist/plan/platform-providers.d.ts +9 -0
  95. package/dist/plan/platform-providers.js +21 -0
  96. package/dist/plan/providers-readers.js +25 -1
  97. package/dist/plan/registered-app-env.d.ts +31 -0
  98. package/dist/plan/registered-app-env.js +63 -0
  99. package/dist/plan/registered-app.d.ts +121 -0
  100. package/dist/plan/registered-app.js +262 -0
  101. package/dist/plan/release-host.js +2 -1
  102. package/dist/plan/reporting-boule.d.ts +18 -0
  103. package/dist/plan/reporting-boule.js +44 -0
  104. package/dist/plan/reporting.d.ts +90 -0
  105. package/dist/plan/reporting.js +135 -0
  106. package/dist/plan/stage-env.js +1 -0
  107. package/dist/plan/stage-isolation.d.ts +3 -1
  108. package/dist/plan/stage-isolation.js +20 -2
  109. package/dist/plan/stage-services.d.ts +3 -1
  110. package/dist/plan/stage-services.js +8 -1
  111. package/dist/plan/stage-sink.d.ts +2 -2
  112. package/dist/plan/stage-sink.js +2 -2
  113. package/dist/plan/workspace-client.d.ts +1 -1
  114. package/dist/plan/workspace-client.js +2 -1
  115. package/dist/plan/workspace-sign-in.d.ts +1 -1
  116. package/dist/plan/workspace-sign-in.js +39 -7
  117. package/dist/platform.d.ts +25 -0
  118. package/dist/platform.js +20 -0
  119. package/dist/service-labels.d.ts +8 -0
  120. package/dist/service-labels.js +8 -0
  121. package/dist/slug.d.ts +1 -1
  122. package/dist/slug.js +5 -2
  123. package/dist/store/postgres.js +13 -0
  124. package/dist/store/schema.d.ts +485 -2
  125. package/dist/store/schema.js +38 -1
  126. package/dist/worker-app-config.js +2 -2
  127. package/package.json +1 -1
@@ -0,0 +1,36 @@
1
+ import type { DeclaredOrg } from '../org.js';
2
+ import type { DraftStep } from './diff.js';
3
+ /**
4
+ * `uses.reporting` on a server entry: one site key of its own at the company's reporting backend, minted
5
+ * through `POST /v1/site-keys` with the operator key (site = the entry key, environment `production`; Boule
6
+ * reads an app's errors with site = the app key) and written as `REPORTING_URL` and `REPORTING_SITE_KEY` to
7
+ * the entry's env. The backend shows the secret once, so this follows rule 1 of CLAUDE.md "Platform steps":
8
+ *
9
+ * 1. the key's id is recorded as `reporting_key_undelivered.<entry>` and persisted before the env is written;
10
+ * 2. the env write is tried 3 times with the same secret; the id is the main id `reporting:key:<orgId>:<entry>`
11
+ * and the applied marker only after it worked, and the undelivered marker is cleared then;
12
+ * 3. a marker left behind (every try failed) is reported blocked and never minted again.
13
+ *
14
+ * A Worker app entry is skipped: Boule installs its keys per copy. The entry's stage gets its own key through
15
+ * `plan/stage-services.ts` (service `reporting`, environment `stage`), so the two differ. Removing
16
+ * `uses.reporting` or the entry plans the retire form: revoke the key, take both env names away.
17
+ */
18
+ export declare const entryReportingUndeliveredScopeKey: (entryKey: string) => string;
19
+ /** The address the entry's env was last written with; a change rewrites the address only. */
20
+ export declare const entryReportingAppliedScopeKey: (entryKey: string) => string;
21
+ /** The id of a key that was revoked, so a stale main id is not revoked again. */
22
+ export declare const entryReportingRetiredScopeKey: (entryKey: string) => string;
23
+ export declare const ENTRY_REPORTING_KEY_DELIVERY_ATTEMPTS = 3;
24
+ /** The name the backend knows the key by. The site is sent as well (the entry key), so it never derives from the name. */
25
+ export declare const entryReportingKeyName: (org: Pick<DeclaredOrg, "slug">, entryKey: string) => string;
26
+ /** The stage copy's key name; its site is the entry key too, and its environment `stage`. */
27
+ export declare const stageReportingKeyName: (org: Pick<DeclaredOrg, "slug">, entryKey: string) => string;
28
+ type Blocked = Array<{
29
+ description: string;
30
+ key: string;
31
+ }>;
32
+ export declare function planEntryReportingKeys(org: DeclaredOrg, recordedIds: Readonly<Record<string, string>>, plannedSteps: readonly DraftStep[], reportingPort: boolean): {
33
+ steps: DraftStep[];
34
+ blocked: Blocked;
35
+ };
36
+ export {};
@@ -0,0 +1,136 @@
1
+ import { appStepKey } from './platform-diff.js';
2
+ import { reportingUrlFor } from './reporting.js';
3
+ /**
4
+ * `uses.reporting` on a server entry: one site key of its own at the company's reporting backend, minted
5
+ * through `POST /v1/site-keys` with the operator key (site = the entry key, environment `production`; Boule
6
+ * reads an app's errors with site = the app key) and written as `REPORTING_URL` and `REPORTING_SITE_KEY` to
7
+ * the entry's env. The backend shows the secret once, so this follows rule 1 of CLAUDE.md "Platform steps":
8
+ *
9
+ * 1. the key's id is recorded as `reporting_key_undelivered.<entry>` and persisted before the env is written;
10
+ * 2. the env write is tried 3 times with the same secret; the id is the main id `reporting:key:<orgId>:<entry>`
11
+ * and the applied marker only after it worked, and the undelivered marker is cleared then;
12
+ * 3. a marker left behind (every try failed) is reported blocked and never minted again.
13
+ *
14
+ * A Worker app entry is skipped: Boule installs its keys per copy. The entry's stage gets its own key through
15
+ * `plan/stage-services.ts` (service `reporting`, environment `stage`), so the two differ. Removing
16
+ * `uses.reporting` or the entry plans the retire form: revoke the key, take both env names away.
17
+ */
18
+ export const entryReportingUndeliveredScopeKey = (entryKey) => `reporting_key_undelivered.${entryKey}`;
19
+ /** The address the entry's env was last written with; a change rewrites the address only. */
20
+ export const entryReportingAppliedScopeKey = (entryKey) => `reporting_key_applied.${entryKey}`;
21
+ /** The id of a key that was revoked, so a stale main id is not revoked again. */
22
+ export const entryReportingRetiredScopeKey = (entryKey) => `reporting_key_retired.${entryKey}`;
23
+ export const ENTRY_REPORTING_KEY_DELIVERY_ATTEMPTS = 3;
24
+ /** The name the backend knows the key by. The site is sent as well (the entry key), so it never derives from the name. */
25
+ export const entryReportingKeyName = (org, entryKey) => `${org.slug}-${entryKey}-reporting`;
26
+ /** The stage copy's key name; its site is the entry key too, and its environment `stage`. */
27
+ export const stageReportingKeyName = (org, entryKey) => `${org.slug}-${entryKey}-stage-reporting`;
28
+ const servesKey = (entry) => entry.uses?.reporting === true && !entry.workerApp && entry.live !== false;
29
+ export function planEntryReportingKeys(org, recordedIds, plannedSteps, reportingPort) {
30
+ const steps = [];
31
+ const blocked = [];
32
+ if (!org.hosting.enabled)
33
+ return { steps, blocked };
34
+ const planned = (system, kind, key) => plannedSteps.some((p) => p.action.system === system && p.action.kind === kind && p.action.key === key);
35
+ const wanted = new Map(org.hosting.entries.filter(servesKey).map((e) => [e.key, e]));
36
+ const mainPrefix = `reporting:key:${org.id}:`;
37
+ // Keys whose entry stopped using reporting or is gone: a delivered key (main id) or one minted and never
38
+ // delivered (undelivered marker), so the retire clears the marker and the entry can mint again later.
39
+ const undeliveredPrefix = entryReportingUndeliveredScopeKey('');
40
+ const retiring = new Set();
41
+ for (const [name, id] of Object.entries(recordedIds)) {
42
+ if (!id)
43
+ continue;
44
+ const prefix = name.startsWith(mainPrefix)
45
+ ? mainPrefix
46
+ : name.startsWith(undeliveredPrefix)
47
+ ? undeliveredPrefix
48
+ : null;
49
+ if (!prefix)
50
+ continue;
51
+ const entryKey = name.slice(prefix.length);
52
+ if (wanted.has(entryKey) ||
53
+ retiring.has(entryKey) ||
54
+ recordedIds[entryReportingRetiredScopeKey(entryKey)] === id) {
55
+ continue;
56
+ }
57
+ retiring.add(entryKey);
58
+ const key = `${mainPrefix}${entryKey}`;
59
+ if (!reportingPort) {
60
+ blocked.push({
61
+ description: `${org.slug}'s ${entryKey} reporting key is not revoked, not planned: the host supplies no reporting port`,
62
+ key,
63
+ });
64
+ }
65
+ else if (!recordedIds[`coolify:reporting:${org.id}`]) {
66
+ blocked.push({
67
+ description: `${org.slug}'s ${entryKey} reporting key is not revoked, not planned: the reporting backend is not recorded`,
68
+ key,
69
+ });
70
+ }
71
+ else {
72
+ steps.push({
73
+ action: {
74
+ system: 'reporting',
75
+ kind: 'key',
76
+ key: `${org.id}:${entryKey}`,
77
+ desired: { mode: 'retire' },
78
+ },
79
+ step: {
80
+ description: `revoke ${org.slug}'s ${entryKey} reporting key and take REPORTING_* out of its env: the entry no longer uses reporting`,
81
+ destroys: true,
82
+ migrates: false,
83
+ },
84
+ });
85
+ }
86
+ }
87
+ for (const entry of wanted.values()) {
88
+ const stepKey = appStepKey(org, entry.key);
89
+ const key = `reporting:key:${stepKey}`;
90
+ const at = `${org.slug}'s ${entry.key} reporting key`;
91
+ const block = (why) => blocked.push({ description: `${at} is blocked: ${why}`, key });
92
+ const url = reportingUrlFor(org);
93
+ const undelivered = recordedIds[entryReportingUndeliveredScopeKey(entry.key)];
94
+ if (undelivered) {
95
+ block(`key ${undelivered} was minted but never written to the app, and the backend shows a key once; remove the recorded id ${entryReportingUndeliveredScopeKey(entry.key)} to mint a new key, which replaces that one`);
96
+ continue;
97
+ }
98
+ const live = recordedIds[key] &&
99
+ recordedIds[entryReportingRetiredScopeKey(entry.key)] !== recordedIds[key];
100
+ if (live && recordedIds[entryReportingAppliedScopeKey(entry.key)] === url)
101
+ continue;
102
+ if (!reportingPort) {
103
+ block('the host supplies no reporting port');
104
+ continue;
105
+ }
106
+ if (!recordedIds[`coolify:reporting:${org.id}`] && !planned('coolify', 'reporting', org.id)) {
107
+ block("the company's reporting backend is not recorded or planned yet");
108
+ continue;
109
+ }
110
+ if (!recordedIds[`coolify:app:${stepKey}`] && !planned('coolify', 'app', stepKey)) {
111
+ block("the entry's app is not recorded yet");
112
+ continue;
113
+ }
114
+ steps.push({
115
+ action: {
116
+ system: 'reporting',
117
+ kind: 'key',
118
+ key: stepKey,
119
+ desired: {
120
+ mode: live ? 'url' : 'mint',
121
+ name: entryReportingKeyName(org, entry.key),
122
+ site: entry.key,
123
+ reportingUrl: url,
124
+ },
125
+ },
126
+ step: {
127
+ description: live
128
+ ? `write the reporting address to ${org.slug}'s ${entry.key} env`
129
+ : `mint ${at}, write REPORTING_URL and REPORTING_SITE_KEY to its env, restart the app`,
130
+ destroys: false,
131
+ migrates: false,
132
+ },
133
+ });
134
+ }
135
+ return { steps, blocked };
136
+ }
@@ -22,7 +22,9 @@ import { type OwnServicesPort } from './own-services.js';
22
22
  import type { DestroyTarget, StepDestroyer, StepExecutionContext, StepExecutor } from './plan-apply.js';
23
23
  import type { EdgePortName } from './platform-org.js';
24
24
  import type { RequestHeaderPort } from './platform-providers.js';
25
+ import { type RegistrarApiPort } from './registered-app.js';
25
26
  import { type ReleaseContext, type ReleaseHost } from './release-host.js';
27
+ import { type ReportingPort } from './reporting.js';
26
28
  import { type StageCopyHostConfig } from './stage-copy-host.js';
27
29
  import { type ReleasePreviewMembers, type StageProbePort } from './stage-guards.js';
28
30
  import type { WorkerAppDatabasePort } from './worker-app-database.js';
@@ -1547,6 +1549,15 @@ export interface AiOrganisationPort {
1547
1549
  name: string;
1548
1550
  expiresDays: number;
1549
1551
  permissions: string[];
1552
+ /**
1553
+ * foundry#1122 (ai#190 `MintKey.app`): the app and copy the key's runs belong to, fixed for the key's life.
1554
+ * An entry's key passes its entry key and `live`; Boule's key passes none. A host adapter passes it on to
1555
+ * `POST /v1/keys` as `app`.
1556
+ */
1557
+ app?: {
1558
+ key: string;
1559
+ copy: 'live' | 'work';
1560
+ };
1550
1561
  }): Promise<{
1551
1562
  organisationId: string;
1552
1563
  id: string;
@@ -1709,6 +1720,20 @@ export interface JobsSchedulerPort {
1709
1720
  * stage's own project (`<org>-stage`); a port without it leaves that project, which destroy then reports as blocked.
1710
1721
  */
1711
1722
  deleteProject?(name: string): Promise<void>;
1723
+ /**
1724
+ * foundry#1119: mints a key for one project (`POST /admin/projects/<name>/keys` with `{ principal, scopes }`,
1725
+ * jobs#79), shown once with its id. Optional: a host without it (and without `revokeProjectKey`) leaves
1726
+ * `FoundryPlanApplyOptions.jobsProjectKeys` false, and each entry's Boule key is reported blocked.
1727
+ */
1728
+ mintProjectKey?(project: string, grant: {
1729
+ principal: string;
1730
+ scopes: readonly string[];
1731
+ }): Promise<{
1732
+ id: string;
1733
+ key: string;
1734
+ }>;
1735
+ /** Revokes one project key (`DELETE /admin/projects/<name>/keys/<id>`); an unknown key throws with `status: 404`. */
1736
+ revokeProjectKey?(project: string, keyId: string): Promise<void>;
1712
1737
  }
1713
1738
  /**
1714
1739
  * #177: where the org steps' files, AI and jobs ports come from when the host
@@ -1865,6 +1890,12 @@ export interface FoundryStepExecutorPorts {
1865
1890
  * `FoundryPlanApplyOptions.broker`).
1866
1891
  */
1867
1892
  broker?: BrokerPort;
1893
+ /**
1894
+ * ADR 0003: the company's registrar at the sign-in broker, through which an entry whose customers and team
1895
+ * both sign in is registered (`coolify:registered-app`). Optional: without it (and
1896
+ * `FoundryPlanApplyOptions.registrarApi`) such an entry is reported blocked. `httpRegistrarApi()` is the real one.
1897
+ */
1898
+ registrarApi?: RegistrarApiPort;
1868
1899
  /**
1869
1900
  * #318: deploys and routes an entry's `worker` Worker -- see
1870
1901
  * {@link WorkersPort}'s own doc comment. Optional the same way `broker` is:
@@ -1885,6 +1916,8 @@ export interface FoundryStepExecutorPorts {
1885
1916
  keysRegistrar?: KeysRegistrarPort;
1886
1917
  /** The company's analytics backend operator API: mints and revokes site keys. Without it the plan reports each `uses.analytics` key blocked (the host also sets `FoundryPlanApplyOptions.analyticsPort`). */
1887
1918
  analytics?: AnalyticsPort;
1919
+ /** The company's reporting backend operator API: mints and revokes site keys. Without it the plan reports each `uses.reporting` key blocked (the host also sets `FoundryPlanApplyOptions.reportingPort`). */
1920
+ reporting?: ReportingPort;
1888
1921
  /**
1889
1922
  * C3: mints the credential an org's Boule reaches Archon with (org-scoped; `orgs:request` and
1890
1923
  * `orgs:read` for that org). Archon implements it. Every call rotates the credential (the old secret
@@ -2068,6 +2101,13 @@ export declare const KOSMOS_PORT = 3000;
2068
2101
  export declare const ANALYTICS_REPO = "wtfalch/analytics";
2069
2102
  export declare const ANALYTICS_APP_DOCKERFILE = "/Dockerfile";
2070
2103
  export declare const ANALYTICS_PORT = 3000;
2104
+ /**
2105
+ * The company's reporting backend (https://github.com/wtfalch/reporting, `apps/host`): root Dockerfile, serves
2106
+ * on 3000 and answers `/api/health`, migrates in its own start script, as the analytics host does.
2107
+ */
2108
+ export declare const REPORTING_REPO = "wtfalch/reporting";
2109
+ export declare const REPORTING_APP_DOCKERFILE = "/Dockerfile";
2110
+ export declare const REPORTING_PORT = 3000;
2071
2111
  export declare const LOG_DIR = "/app/logs";
2072
2112
  /** The company's Coolify destination (its Docker network `co-<slug>`), recorded when its first resource is created. */
2073
2113
  export declare const COOLIFY_DESTINATION_SCOPE_KEY = "coolify_destination";
@@ -2606,6 +2646,16 @@ export declare class FoundryStepExecutor implements StepExecutor, StepDestroyer
2606
2646
  * key. It never reads or writes the live entry. Records what it wrote, never a key.
2607
2647
  */
2608
2648
  private coolifyWorkspaceBrokerEntry;
2649
+ /**
2650
+ * `coolify:registered-app` (`plan/registered-app.ts`, ADR 0003): one copy of an entry whose customers and team
2651
+ * both sign in, registered at the sign-in broker through the company's registrar with
2652
+ * `users: ['customers', 'staff']`. The answer (the customers' and the team's clients, both organisations and a
2653
+ * new `AUTH_APP_KEY`) goes to the copy's app (the entry's app, or the workspace's stage), which restarts. The
2654
+ * ids are recorded after the env is written, the want last, so a failure anywhere registers again on the next
2655
+ * apply (the broker keeps the key it replaced valid for a window). The registrar key and the app key are
2656
+ * registered for redaction and never recorded; errors name the copy and a status at most.
2657
+ */
2658
+ private coolifyRegisteredApp;
2609
2659
  /**
2610
2660
  * `coolify:workspaces-database` (`plan/broker-workspaces-database.ts`): the broker's workspaces database, made
2611
2661
  * beside the broker (the platform's project and network, or the company's own for an org that owns its services)
@@ -2807,6 +2857,12 @@ export declare class FoundryStepExecutor implements StepExecutor, StepDestroyer
2807
2857
  * provisioned, where this one is unconditional (#239).
2808
2858
  */
2809
2859
  private pushAppEnv;
2860
+ /**
2861
+ * #1116: takes a module setting's env name (`MAIL_REPLY_TO`, ...) out of the app's env once the setting is
2862
+ * unset or its module is off. Only a name the last applied env wrote (the recorded marker) and the entry's
2863
+ * own `env` does not declare, so a value the company set by hand stays.
2864
+ */
2865
+ private dropUnsetSettingsEnv;
2810
2866
  /** Takes `AUTH_EVENTS_SECRET` out of the app's env once the entry stops declaring events. The held value stays: the held store has no delete. */
2811
2867
  private dropAuthEventsEnv;
2812
2868
  /**
@@ -3516,7 +3572,9 @@ export declare class FoundryStepExecutor implements StepExecutor, StepDestroyer
3516
3572
  *
3517
3573
  * `ANALYTICS_OPERATOR_KEY` is made once here (`mintIfAbsent`) and written only to this app's env. No outside
3518
3574
  * service shows it once, so no undelivered marker is needed: a failed write leaves nothing live to mint twice.
3519
- * Steps that call the backend's operator API read it back from this env.
3575
+ * Steps that call the backend's operator API read it back from this env. `ANALYTICS_PERSON_KEY` (64 hex
3576
+ * characters, analytics#109: the per-site key that counts a signed-in person once) is made the same way and
3577
+ * never rotated: a value already in the env, made here or by hand, is kept.
3520
3578
  *
3521
3579
  * A brand new app is deployed once ({@link ANALYTICS_DEPLOYED_SCOPE_KEY}), as for Boule (#504).
3522
3580
  */
@@ -3536,6 +3594,35 @@ export declare class FoundryStepExecutor implements StepExecutor, StepDestroyer
3536
3594
  * env names. The operator key is read from the backend's env and never recorded.
3537
3595
  */
3538
3596
  private analyticsEntryKey;
3597
+ /**
3598
+ * `plan/diff.ts`'s `planReportingBackend`: the company's reporting backend as one more Coolify application
3599
+ * from https://github.com/wtfalch/reporting's root Dockerfile ({@link REPORTING_REPO}), with a database of its
3600
+ * own, a copy of {@link coolifyAnalytics}. Its start script migrates as the owner role and then serves as the
3601
+ * runtime role, so `pushDatabaseUrl` gives it both URLs. It needs no sign-in client: it reads only
3602
+ * `AUTH_ORGANIZATION_ID` (the tenant id derives from it), `APP_URL` and the operator key.
3603
+ *
3604
+ * `REPORTING_OPERATOR_KEY` is made once here (`mintIfAbsent`) and written only to this app's env. No outside
3605
+ * service shows it once, so no undelivered marker is needed: a failed write leaves nothing live to mint twice.
3606
+ * Steps that call the backend's operator API read it back from this env.
3607
+ *
3608
+ * A brand new app is deployed once ({@link REPORTING_DEPLOYED_SCOPE_KEY}), as for Boule (#504).
3609
+ */
3610
+ private coolifyReporting;
3611
+ /** The backend's operator key, read from its own env. Fixed text when the backend or the key is not there. */
3612
+ private reportingOperatorKey;
3613
+ /**
3614
+ * `coolify:reporting-boule` (`plan/reporting-boule.ts`): the backend's address and operator key into
3615
+ * Boule's env, then Boule restarts. `reporting_boule_applied` is recorded last. Retire takes both names away.
3616
+ */
3617
+ private reportingBoule;
3618
+ /**
3619
+ * `plan/entry-reporting-key.ts`: one server entry's site key (CLAUDE.md "Platform steps" rule 1). Mint: the
3620
+ * key's id is recorded as undelivered and persisted before anything is written; the env write is tried 3
3621
+ * times with the same secret; the applied address and the cleared marker come last; every try failing leaves
3622
+ * the marker, and the error names the id only. `url` rewrites the address. Retire revokes the key and the
3623
+ * env names. The operator key is read from the backend's env and never recorded.
3624
+ */
3625
+ private reportingEntryKey;
3539
3626
  /**
3540
3627
  * `plan/diff.ts`'s `planKosmos`: provisions the org's own kosmos
3541
3628
  * (https://github.com/wtfalch/kosmos) as one more Coolify application, from
@@ -3629,6 +3716,9 @@ export declare class FoundryStepExecutor implements StepExecutor, StepDestroyer
3629
3716
  * Boule's env. Returns whether Boule's token changed, so the caller
3630
3717
  * restarts a running Boule, unless `restarted`: the shared rule then already
3631
3718
  * restarted it (`running`), before it deleted the old project.
3719
+ *
3720
+ * The company's reporting backend (foundry#1128) takes its token the same
3721
+ * way, as `role: 'reporting'`. The token is registered for redaction.
3632
3722
  */
3633
3723
  private ensureBouleMail;
3634
3724
  /**
@@ -3758,6 +3848,12 @@ export declare class FoundryStepExecutor implements StepExecutor, StepDestroyer
3758
3848
  * retried. Errors name the project and the refs, never a credential.
3759
3849
  */
3760
3850
  private jobsProject;
3851
+ /**
3852
+ * foundry#1119, `plan/entry-jobs-boule-key.ts`: Boule's key for one entry's jobs project, minted under rule 1,
3853
+ * held, and written to Boule's `JOBS_PROJECTS`; or, in the retire form, revoked and taken out. See
3854
+ * `entry-jobs-boule-key-apply.ts`.
3855
+ */
3856
+ private jobsBouleKey;
3761
3857
  /** `plan/entry-audit.ts`: makes the platform signer if none exists and registers one entry's ledger for sealing. */
3762
3858
  private auditLedger;
3763
3859
  /** F12, `plan/entry-repo-key.ts`: makes or removes one entry's deploy key. See `entry-repo-key-apply.ts`. */
@@ -4145,6 +4241,11 @@ export declare class FoundryStepExecutor implements StepExecutor, StepDestroyer
4145
4241
  * errors name ids, never the cause, which can echo one.
4146
4242
  */
4147
4243
  private aiEntryKey;
4244
+ /**
4245
+ * foundry#1122, `plan/ai-boule-key.ts`: Boule's ai key, minted below the org's own ai key under rule 1, held,
4246
+ * and written to Boule's `AI_URL`, `AI_ORGANISATION_ID` and `AI_API_KEY`. See `ai-boule-key-apply.ts`.
4247
+ */
4248
+ private aiBouleKey;
4148
4249
  /**
4149
4250
  * REVOKE SEAM (ai#157): revokes through `revokeKey` when the port has one.
4150
4251
  * Without it, or when it fails, the id joins the entry's list of keys left
@@ -4208,6 +4309,12 @@ export declare class FoundryStepExecutor implements StepExecutor, StepDestroyer
4208
4309
  * minting nothing. A secret reaches `bulkUpsertEnvs` and nothing else.
4209
4310
  */
4210
4311
  private filesEntryKey;
4312
+ /**
4313
+ * foundry#1121, `plan/files-boule-key.ts`: Boule's files key, minted below the org's own files key under rule
4314
+ * 1, held, and written to Boule's `FILES_BOULE_KEYS`; or, in the retire form, revoked and taken out. See
4315
+ * `files-boule-key-apply.ts`.
4316
+ */
4317
+ private filesBouleKey;
4211
4318
  /**
4212
4319
  * Revokes one entry key. When the call fails the id joins the entry's list
4213
4320
  * of keys left live, which the plan shows, and the step goes on: the app