@wtfalch/foundry 0.1.0 → 0.2.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/apply.d.ts +14 -0
- package/dist/index.d.ts +6 -4
- package/dist/index.js +3 -2
- package/dist/migrations/0008_hosting.sql +24 -0
- package/dist/migrations/0009_hosting_git_branch_build_pack.sql +14 -0
- package/dist/migrations/0010_owner.sql +13 -0
- package/dist/migrations/0011_test_org.sql +12 -0
- package/dist/org.d.ts +107 -0
- package/dist/org.js +65 -0
- package/dist/plan/diff.d.ts +20 -5
- package/dist/plan/diff.js +402 -9
- package/dist/plan/executor.d.ts +474 -0
- package/dist/plan/executor.js +507 -0
- package/dist/plan/index.d.ts +6 -4
- package/dist/plan/index.js +3 -2
- package/dist/plan/live.d.ts +8 -4
- package/dist/plan/plan-apply.d.ts +114 -4
- package/dist/plan/plan-apply.js +251 -14
- package/dist/plan/providers-readers.d.ts +48 -13
- package/dist/plan/providers-readers.js +93 -16
- package/dist/state.d.ts +26 -5
- package/dist/store/postgres.d.ts +3 -1
- package/dist/store/postgres.js +65 -2
- package/dist/store/schema.d.ts +190 -0
- package/dist/store/schema.js +18 -0
- package/dist/store.d.ts +10 -0
- package/package.json +9 -11
package/dist/apply.d.ts
CHANGED
|
@@ -24,4 +24,18 @@ export interface PlanApply {
|
|
|
24
24
|
* fresh plan would compute no longer matches this `plan` (#9, #10).
|
|
25
25
|
*/
|
|
26
26
|
apply(plan: Plan, confirmedOrgName: string): Promise<AppliedState>;
|
|
27
|
+
/**
|
|
28
|
+
* Deletes every resource foundry created and recorded for `id`, then the
|
|
29
|
+
* declared org itself.
|
|
30
|
+
*
|
|
31
|
+
* Implementations must refuse unless the stored `DeclaredOrg` has
|
|
32
|
+
* `test: true` (goal line: "only a test org can be deleted") and unless
|
|
33
|
+
* `confirmedOrgName` matches the org's `displayName`, the same
|
|
34
|
+
* confirmation `apply` requires for any destructive step (#9) -- required
|
|
35
|
+
* here unconditionally, since destroying an org is the most destructive
|
|
36
|
+
* step there is. A failure partway through must stop and leave the
|
|
37
|
+
* declared org in place with whatever remains recorded, so a retried
|
|
38
|
+
* `destroy` only tears down what's left.
|
|
39
|
+
*/
|
|
40
|
+
destroy(id: OrgId, confirmedOrgName: string): Promise<void>;
|
|
27
41
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export type { OrgId, MailMode, MailConfig, ChatConfig, CallsConfig, IssuerConfig, SiteConfig, SsoConfig, GuestsConfig, SoloConfig, StorageConfig, AiConfig, BuyerAddress, BuyerIdentity, AdoptedAppForm, AdoptedApp, DeclaredOrg, } from './org.js';
|
|
2
|
-
export { validateBuyerIdentity } from './org.js';
|
|
1
|
+
export type { OrgId, MailMode, MailConfig, ChatConfig, CallsConfig, IssuerConfig, SiteConfig, IssuerAppKind, IssuerApp, HostingConfig, SsoConfig, GuestsConfig, SoloConfig, StorageConfig, AiConfig, BuyerAddress, BuyerIdentity, AdoptedAppForm, AdoptedApp, OwnerIdentity, DeclaredOrg, } from './org.js';
|
|
2
|
+
export { validateBuyerIdentity, validateHosting, validateOwner } from './org.js';
|
|
3
3
|
export type { SlugRejectionReason, SlugRejection } from './slug.js';
|
|
4
4
|
export { validateSlug, RESERVED_SLUGS } from './slug.js';
|
|
5
5
|
export type { Checkpoint, LastApplied, AppliedState } from './state.js';
|
|
@@ -9,8 +9,10 @@ export type { PlanApply } from './apply.js';
|
|
|
9
9
|
export { createPostgresDeclaredOrgStore, DeclaredOrgError } from './store/postgres.js';
|
|
10
10
|
export type { FoundryStoreDb } from './store/postgres.js';
|
|
11
11
|
export { declaredOrgs, foundryAppliedState, type DeclaredOrgRow, type NewDeclaredOrgRow, type AppliedStateRow, type NewAppliedStateRow, } from './store/schema.js';
|
|
12
|
-
export type { LiveResource, ZitadelReader, CoolifyReader, CloudflareReader, FilesReader, AiReader, CmsReader, LiveReaders, StepAction, PlannedStep, PlanDetail, AuditActor, AuditLog, AuthzGate, StepExecutor, FoundryPlanApplyOptions, } from './plan/index.js';
|
|
13
|
-
export { readLiveResources, computePlanDetail, toPlan, FoundryPlanApply, DeclaredOrgNotFoundError, StalePlanError, ConfirmationRequiredError, NotAuthorizedError, } from './plan/index.js';
|
|
12
|
+
export type { LiveResource, ZitadelReader, CoolifyReader, CloudflareReader, FilesReader, AiReader, CmsReader, LiveReaders, StepAction, PlannedStep, PlanDetail, AuditActor, AuditLog, AuthzGate, StepExecutor, StepDestroyer, DestroyTarget, StepExecutionContext, FoundryPlanApplyOptions, } from './plan/index.js';
|
|
13
|
+
export { readLiveResources, computePlanDetail, toPlan, FoundryPlanApply, DeclaredOrgNotFoundError, StalePlanError, ConfirmationRequiredError, NotAuthorizedError, NotATestOrgError, } from './plan/index.js';
|
|
14
|
+
export type { CoolifyPort, CloudflarePort, ZitadelPort, FilesOrgLimits, AiOrgLimits, FilesOrganisationPort, AiOrganisationPort, FoundryStepExecutorPorts, } from './plan/index.js';
|
|
15
|
+
export { FoundryStepExecutor, NotBuiltError, MissingBespokeAppRepoError } from './plan/index.js';
|
|
14
16
|
export type { RunnerDb } from './runner/db.js';
|
|
15
17
|
export { AlreadyRunningError, RunNotFoundError, StepFailedError, StepMismatchError, } from './runner/errors.js';
|
|
16
18
|
export { createJobRunner } from './runner/runner.js';
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
export { validateBuyerIdentity } from './org.js';
|
|
1
|
+
export { validateBuyerIdentity, validateHosting, validateOwner } from './org.js';
|
|
2
2
|
export { validateSlug, RESERVED_SLUGS } from './slug.js';
|
|
3
3
|
export { createPostgresDeclaredOrgStore, DeclaredOrgError } from './store/postgres.js';
|
|
4
4
|
export { declaredOrgs, foundryAppliedState, } from './store/schema.js';
|
|
5
|
-
export { readLiveResources, computePlanDetail, toPlan, FoundryPlanApply, DeclaredOrgNotFoundError, StalePlanError, ConfirmationRequiredError, NotAuthorizedError, } from './plan/index.js';
|
|
5
|
+
export { readLiveResources, computePlanDetail, toPlan, FoundryPlanApply, DeclaredOrgNotFoundError, StalePlanError, ConfirmationRequiredError, NotAuthorizedError, NotATestOrgError, } from './plan/index.js';
|
|
6
|
+
export { FoundryStepExecutor, NotBuiltError, MissingBespokeAppRepoError } from './plan/index.js';
|
|
6
7
|
export { AlreadyRunningError, RunNotFoundError, StepFailedError, StepMismatchError, } from './runner/errors.js';
|
|
7
8
|
export { createJobRunner } from './runner/runner.js';
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
-- @wtfalch/foundry: an org's own hosted app, its database, its Cloudflare
|
|
2
|
+
-- tunnel and its declared ZITADEL issuer apps (#71), declared alongside
|
|
3
|
+
-- everything else in `foundry_declared_orgs` (#0001). Mirrors
|
|
4
|
+
-- `src/store/schema.ts`'s `declaredOrgs` table additions.
|
|
5
|
+
--
|
|
6
|
+
-- HostingConfig (org.ts) is the same `usesDomain`-opts-into-`domain` shape as
|
|
7
|
+
-- mail/chat/calls/issuer/site (#0001's `mail_uses_domain` etc, `0007_site.sql`'s
|
|
8
|
+
-- `site_uses_domain`), plus its own on/off switch: hosting_enabled turns the
|
|
9
|
+
-- org's own app on at all, hosting_database and hosting_tunnel each add a
|
|
10
|
+
-- Postgres database and a Cloudflare tunnel beside it (both require
|
|
11
|
+
-- hosting_enabled -- org.ts's `validateHosting`), and hosting_issuer_apps
|
|
12
|
+
-- carries the org's declared ZITADEL OIDC applications (`IssuerApp[]`).
|
|
13
|
+
--
|
|
14
|
+
-- All four booleans default false and hosting_issuer_apps defaults to an
|
|
15
|
+
-- empty array -- a brand new org has no hosted app, the same "full schema
|
|
16
|
+
-- exists from day one" pattern #0001, #0005 and #0007 established: a new
|
|
17
|
+
-- column with a decided default, never a reshape.
|
|
18
|
+
|
|
19
|
+
alter table foundry_declared_orgs
|
|
20
|
+
add column if not exists hosting_enabled boolean not null default false,
|
|
21
|
+
add column if not exists hosting_uses_domain boolean not null default false,
|
|
22
|
+
add column if not exists hosting_database boolean not null default false,
|
|
23
|
+
add column if not exists hosting_tunnel boolean not null default false,
|
|
24
|
+
add column if not exists hosting_issuer_apps jsonb not null default '[]';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
-- @wtfalch/foundry: the org's own hosted app's git branch and Coolify build
|
|
2
|
+
-- pack (#71, hosting-wiring campaign), declarable on `HostingConfig` where
|
|
3
|
+
-- `executor.ts`'s `coolifyApp` previously hardcoded `'main'`/`'nixpacks'`.
|
|
4
|
+
-- Mirrors `src/store/schema.ts`'s `declaredOrgs` table additions.
|
|
5
|
+
--
|
|
6
|
+
-- Both default to the values `coolify:app` already provisioned with, so an
|
|
7
|
+
-- org that never mentions them keeps getting exactly what shipped before
|
|
8
|
+
-- this migration -- the same "full schema exists from day one, new column
|
|
9
|
+
-- with a decided default" pattern `0008_hosting.sql` and its predecessors
|
|
10
|
+
-- established.
|
|
11
|
+
|
|
12
|
+
alter table foundry_declared_orgs
|
|
13
|
+
add column if not exists hosting_git_branch text not null default 'main',
|
|
14
|
+
add column if not exists hosting_build_pack text not null default 'nixpacks';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
-- @wtfalch/foundry: the org's owner, invited by ZITADEL email. Declared
|
|
2
|
+
-- alongside everything else in `foundry_declared_orgs`. Mirrors
|
|
3
|
+
-- `src/store/schema.ts`'s `declaredOrgs` table additions.
|
|
4
|
+
--
|
|
5
|
+
-- OwnerIdentity (org.ts) is flattened into three nullable columns the same
|
|
6
|
+
-- way BuyerIdentity's own contact fields are (`0006_buyer_identity.sql`):
|
|
7
|
+
-- present only when an operator has named an owner, reassembled as one
|
|
8
|
+
-- object only once all three are non-null.
|
|
9
|
+
|
|
10
|
+
alter table foundry_declared_orgs
|
|
11
|
+
add column if not exists owner_email text,
|
|
12
|
+
add column if not exists owner_given_name text,
|
|
13
|
+
add column if not exists owner_family_name text;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
-- @wtfalch/foundry: the test/real flag that gates whether an org can be
|
|
2
|
+
-- destroyed. Declared alongside everything else in
|
|
3
|
+
-- `foundry_declared_orgs`. Mirrors `src/store/schema.ts`'s `declaredOrgs`
|
|
4
|
+
-- table additions.
|
|
5
|
+
--
|
|
6
|
+
-- `is_test` defaults false -- a brand new org is presumed real, matching
|
|
7
|
+
-- every other "full schema exists from day one" column this table has added
|
|
8
|
+
-- (#0001, #0005, #0007, #0008, #0010): a new column with a decided default,
|
|
9
|
+
-- never a reshape.
|
|
10
|
+
|
|
11
|
+
alter table foundry_declared_orgs
|
|
12
|
+
add column if not exists is_test boolean not null default false;
|
package/dist/org.d.ts
CHANGED
|
@@ -89,6 +89,77 @@ export interface SiteConfig {
|
|
|
89
89
|
enabled: boolean;
|
|
90
90
|
usesDomain: boolean;
|
|
91
91
|
}
|
|
92
|
+
/**
|
|
93
|
+
* A ZITADEL OIDC application kind foundry can provision inside an org's
|
|
94
|
+
* issuer project (#71): `web` (a browser-based confidential/PKCE client),
|
|
95
|
+
* `native` (a public native-app client) or `api` (an API resource with no
|
|
96
|
+
* login flow of its own).
|
|
97
|
+
*/
|
|
98
|
+
export type IssuerAppKind = 'web' | 'native' | 'api';
|
|
99
|
+
/**
|
|
100
|
+
* One ZITADEL OIDC application the org declares (#71), keyed within
|
|
101
|
+
* `HostingConfig.issuerApps` by its own `name` -- `validateHosting` below
|
|
102
|
+
* requires every name non-empty and unique in that list. `kind` decides the
|
|
103
|
+
* ZITADEL `appType` the planner provisions it as (`plan/diff.ts`'s
|
|
104
|
+
* `planIssuerApps`); changing `kind` for an app that already exists live is
|
|
105
|
+
* a destructive step there, since ZITADEL has no in-place app-type change.
|
|
106
|
+
*/
|
|
107
|
+
export interface IssuerApp {
|
|
108
|
+
name: string;
|
|
109
|
+
kind: IssuerAppKind;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* The Coolify build strategy for the org's own hosted app -- the same union
|
|
113
|
+
* `@wtfalch/providers`' `CoolifyClient.createGithubAppApplication` accepts as
|
|
114
|
+
* `build_pack`, reused here rather than redeclared so a value this package
|
|
115
|
+
* validates is always one Coolify itself accepts.
|
|
116
|
+
*/
|
|
117
|
+
export type HostingBuildPack = 'dockerfile' | 'nixpacks' | 'static' | 'dockercompose';
|
|
118
|
+
/**
|
|
119
|
+
* The org's own hosted app and what runs beside it (#71, part of #14's
|
|
120
|
+
* adopted/standard/bespoke map) -- distinct from the cms site (`SiteConfig`
|
|
121
|
+
* above), which is `@wtfalch/cms` content, not the org's own application
|
|
122
|
+
* code. `usesDomain` follows the same opt-in-per-service rule as mail, chat,
|
|
123
|
+
* calls, the issuer and the site (#7 amendment 1): setting `DeclaredOrg.domain`
|
|
124
|
+
* alone never moves the app onto it or starts managing its Cloudflare zone.
|
|
125
|
+
*
|
|
126
|
+
* `database` and `tunnel` are each meaningless without the app itself
|
|
127
|
+
* running, so `validateHosting` below requires `enabled` before either can
|
|
128
|
+
* be true.
|
|
129
|
+
*
|
|
130
|
+
* `gitBranch` and `buildPack` were fixed values `executor.ts` hardcoded
|
|
131
|
+
* (`'main'`/`'nixpacks'`) until this campaign's hosting-wiring work made them
|
|
132
|
+
* declarable; those two strings are their defaults now, kept here so an org
|
|
133
|
+
* that never mentions them still gets exactly what shipped before.
|
|
134
|
+
*/
|
|
135
|
+
export interface HostingConfig {
|
|
136
|
+
/** The org's own app runs on Coolify. */
|
|
137
|
+
enabled: boolean;
|
|
138
|
+
/** Served on `DeclaredOrg.domain` (and its Cloudflare zone managed) instead of `<slug>.oikos.host` (`../slug.ts`). */
|
|
139
|
+
usesDomain: boolean;
|
|
140
|
+
/** A Postgres database beside the app. Requires `enabled`. */
|
|
141
|
+
database: boolean;
|
|
142
|
+
/** The app is reached through a Cloudflare tunnel. Requires `enabled`. */
|
|
143
|
+
tunnel: boolean;
|
|
144
|
+
/** The branch `coolify:app` builds and deploys from. Default: `'main'`. */
|
|
145
|
+
gitBranch: string;
|
|
146
|
+
/** The Coolify build strategy `coolify:app` provisions with. Default: `'nixpacks'`. */
|
|
147
|
+
buildPack: HostingBuildPack;
|
|
148
|
+
/** The org's declared ZITADEL OIDC applications (#71). */
|
|
149
|
+
issuerApps: IssuerApp[];
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* `null` when `hosting` is valid; otherwise the reason it is not (#71), same
|
|
153
|
+
* shape as `validateBuyerIdentity` below.
|
|
154
|
+
*
|
|
155
|
+
* - `database` and `tunnel` each require `enabled` -- neither means anything
|
|
156
|
+
* without the app itself running.
|
|
157
|
+
* - `gitBranch` is non-empty and `buildPack` is one of `HostingBuildPack`'s
|
|
158
|
+
* four values.
|
|
159
|
+
* - Every `issuerApps[]` entry has a non-empty `name`, unique within the
|
|
160
|
+
* list, and a `kind` that is one of `IssuerAppKind`'s three values.
|
|
161
|
+
*/
|
|
162
|
+
export declare function validateHosting(hosting: HostingConfig): string | null;
|
|
92
163
|
/**
|
|
93
164
|
* Per-org SSO. Decided to exist in #14 ("Also in scope") and its resolved
|
|
94
165
|
* research #3 ("per-org SSO code exists unused"); nothing about its
|
|
@@ -196,6 +267,29 @@ export interface BuyerIdentity {
|
|
|
196
267
|
* name before its other details are typed).
|
|
197
268
|
*/
|
|
198
269
|
export declare function validateBuyerIdentity(buyer: BuyerIdentity): string | null;
|
|
270
|
+
/**
|
|
271
|
+
* The org's owner: the one person a real org is created for, invited into
|
|
272
|
+
* its ZITADEL org by email so they can set their own first credential (a
|
|
273
|
+
* passkey) without a password ever passing through foundry. `null` until an
|
|
274
|
+
* operator names one -- an org can exist declared before its owner is known.
|
|
275
|
+
*
|
|
276
|
+
* `apply`'s `zitadel:owner` step (`plan/diff.ts`'s `planOwner`) calls
|
|
277
|
+
* `ZitadelClient.inviteOrgOwner(zitadelOrgId, owner)` once the org's ZITADEL
|
|
278
|
+
* org exists, then never touches this again -- a re-run is idempotent on the
|
|
279
|
+
* recorded invited user id, not on this declared value, so editing `owner`
|
|
280
|
+
* after the invite has gone out does not send a second one.
|
|
281
|
+
*/
|
|
282
|
+
export interface OwnerIdentity {
|
|
283
|
+
email: string;
|
|
284
|
+
givenName: string;
|
|
285
|
+
familyName: string;
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* `null` when `owner` is valid; otherwise the reason it is not, same shape as
|
|
289
|
+
* {@link validateBuyerIdentity} and `../slug.ts`'s `validateSlug`. `owner:
|
|
290
|
+
* null` (no owner named yet) is always valid.
|
|
291
|
+
*/
|
|
292
|
+
export declare function validateOwner(owner: OwnerIdentity | null): string | null;
|
|
199
293
|
/**
|
|
200
294
|
* The form an app foundry does not own takes. Decided in #7's `#15`
|
|
201
295
|
* refinement comment:
|
|
@@ -255,6 +349,8 @@ export interface DeclaredOrg {
|
|
|
255
349
|
issuer: IssuerConfig;
|
|
256
350
|
/** `enabled: false` by default -- a brand new org has no cms site. */
|
|
257
351
|
site: SiteConfig;
|
|
352
|
+
/** `enabled: false`, `issuerApps: []` by default -- a brand new org has no hosted app (#71). */
|
|
353
|
+
hosting: HostingConfig;
|
|
258
354
|
sso: SsoConfig;
|
|
259
355
|
guests: GuestsConfig;
|
|
260
356
|
solo: SoloConfig;
|
|
@@ -273,4 +369,15 @@ export interface DeclaredOrg {
|
|
|
273
369
|
bespokeAppRepo: string | null;
|
|
274
370
|
/** `null` when the org has none. */
|
|
275
371
|
adoptedApp: AdoptedApp | null;
|
|
372
|
+
/** The org's owner, invited by ZITADEL email. `null` until an operator names one. See {@link OwnerIdentity}. */
|
|
373
|
+
owner: OwnerIdentity | null;
|
|
374
|
+
/**
|
|
375
|
+
* Marks this org as a test org rather than a real customer's. Purely
|
|
376
|
+
* declarative -- it changes nothing about what `apply` provisions -- but it
|
|
377
|
+
* is the one gate `FoundryPlanApply.destroy` checks: only a test org
|
|
378
|
+
* (`test: true`) can be destroyed, so deleting every resource foundry made
|
|
379
|
+
* for an org, then the org itself, can never reach a real customer's org by
|
|
380
|
+
* mistake.
|
|
381
|
+
*/
|
|
382
|
+
test: boolean;
|
|
276
383
|
}
|
package/dist/org.js
CHANGED
|
@@ -1,3 +1,49 @@
|
|
|
1
|
+
const ISSUER_APP_KINDS = new Set(['web', 'native', 'api']);
|
|
2
|
+
const HOSTING_BUILD_PACKS = new Set([
|
|
3
|
+
'dockerfile',
|
|
4
|
+
'nixpacks',
|
|
5
|
+
'static',
|
|
6
|
+
'dockercompose',
|
|
7
|
+
]);
|
|
8
|
+
/**
|
|
9
|
+
* `null` when `hosting` is valid; otherwise the reason it is not (#71), same
|
|
10
|
+
* shape as `validateBuyerIdentity` below.
|
|
11
|
+
*
|
|
12
|
+
* - `database` and `tunnel` each require `enabled` -- neither means anything
|
|
13
|
+
* without the app itself running.
|
|
14
|
+
* - `gitBranch` is non-empty and `buildPack` is one of `HostingBuildPack`'s
|
|
15
|
+
* four values.
|
|
16
|
+
* - Every `issuerApps[]` entry has a non-empty `name`, unique within the
|
|
17
|
+
* list, and a `kind` that is one of `IssuerAppKind`'s three values.
|
|
18
|
+
*/
|
|
19
|
+
export function validateHosting(hosting) {
|
|
20
|
+
if (hosting.database && !hosting.enabled) {
|
|
21
|
+
return 'hosting.database requires hosting.enabled';
|
|
22
|
+
}
|
|
23
|
+
if (hosting.tunnel && !hosting.enabled) {
|
|
24
|
+
return 'hosting.tunnel requires hosting.enabled';
|
|
25
|
+
}
|
|
26
|
+
if (!hosting.gitBranch) {
|
|
27
|
+
return 'hosting.gitBranch must be non-empty';
|
|
28
|
+
}
|
|
29
|
+
if (!HOSTING_BUILD_PACKS.has(hosting.buildPack)) {
|
|
30
|
+
return `hosting.buildPack has an unknown value '${hosting.buildPack}'`;
|
|
31
|
+
}
|
|
32
|
+
const seenNames = new Set();
|
|
33
|
+
for (const app of hosting.issuerApps) {
|
|
34
|
+
if (!app.name) {
|
|
35
|
+
return 'every issuerApps entry needs a non-empty name';
|
|
36
|
+
}
|
|
37
|
+
if (seenNames.has(app.name)) {
|
|
38
|
+
return `issuerApps name '${app.name}' is not unique`;
|
|
39
|
+
}
|
|
40
|
+
seenNames.add(app.name);
|
|
41
|
+
if (!ISSUER_APP_KINDS.has(app.kind)) {
|
|
42
|
+
return `issuerApps '${app.name}' has an unknown kind '${app.kind}'`;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
1
47
|
const ORG_NUMBER_PATTERN = /^\d{9}$/;
|
|
2
48
|
/**
|
|
3
49
|
* `null` when `buyer` is valid; otherwise the reason it is not, for a caller
|
|
@@ -27,3 +73,22 @@ export function validateBuyerIdentity(buyer) {
|
|
|
27
73
|
}
|
|
28
74
|
return null;
|
|
29
75
|
}
|
|
76
|
+
/** A deliberately loose email shape check -- foundry never sends mail itself, ZITADEL does; this only catches an obviously malformed address before it reaches the invite call. */
|
|
77
|
+
const OWNER_EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
78
|
+
/**
|
|
79
|
+
* `null` when `owner` is valid; otherwise the reason it is not, same shape as
|
|
80
|
+
* {@link validateBuyerIdentity} and `../slug.ts`'s `validateSlug`. `owner:
|
|
81
|
+
* null` (no owner named yet) is always valid.
|
|
82
|
+
*/
|
|
83
|
+
export function validateOwner(owner) {
|
|
84
|
+
if (owner === null)
|
|
85
|
+
return null;
|
|
86
|
+
if (!OWNER_EMAIL_PATTERN.test(owner.email)) {
|
|
87
|
+
return `owner email '${owner.email}' is not a valid email address`;
|
|
88
|
+
}
|
|
89
|
+
if (!owner.givenName.trim())
|
|
90
|
+
return 'owner.givenName must not be empty';
|
|
91
|
+
if (!owner.familyName.trim())
|
|
92
|
+
return 'owner.familyName must not be empty';
|
|
93
|
+
return null;
|
|
94
|
+
}
|
package/dist/plan/diff.d.ts
CHANGED
|
@@ -41,12 +41,27 @@ export declare function toPlan(detail: PlanDetail): Plan;
|
|
|
41
41
|
* exist for every org — the issuer, mail, and chat (always provisioned at
|
|
42
42
|
* level 1, per #7 "Chat", regardless of `usesDomain`). #27 adds two more,
|
|
43
43
|
* in the same singleton shape: storage's spend limit on Files and AI's
|
|
44
|
-
* budget.
|
|
45
|
-
* (`org.ts`'s `SiteConfig`).
|
|
46
|
-
*
|
|
47
|
-
*
|
|
44
|
+
* budget. T10 adds a sixth, in the same shape: the org's cms site
|
|
45
|
+
* (`org.ts`'s `SiteConfig`). #71 adds four more singleton resources, in the
|
|
46
|
+
* same shape — the org's own hosted app, its database, its Cloudflare
|
|
47
|
+
* tunnel and its Cloudflare zone (`org.ts`'s `HostingConfig`) — plus one
|
|
48
|
+
* list-shaped resource: `planIssuerApps` below emits one step per declared
|
|
49
|
+
* `hosting.issuerApps[]` entry rather than a single singleton, since an org
|
|
50
|
+
* can declare more than one ZITADEL app. #80 adds two more singletons,
|
|
51
|
+
* modeled on {@link planIssuer}: `planFilesOrganisation` and
|
|
52
|
+
* `planAiOrganisation`, each planned before its own system's limit step
|
|
53
|
+
* ({@link planStorage}, {@link planAiBudget}) so an org has somewhere for a
|
|
54
|
+
* limit to land, and AI use is switched on, before either limit is ever set.
|
|
55
|
+
* `planOwner` below adds one more singleton, in a different shape from the
|
|
56
|
+
* rest: the org's owner, invited by ZITADEL email, idempotent on the
|
|
57
|
+
* *recorded* resource id (`recordedIds`, sourced from
|
|
58
|
+
* `AppliedState.serviceIds`) rather than a live resource, since a human user
|
|
59
|
+
* is never something `live` can contain. Everything else in `live` that none
|
|
60
|
+
* of these twelve singletons and however many declared issuer apps claim is
|
|
61
|
+
* unmanaged. The full field-by-field provisioning surface (SSO, guests,
|
|
62
|
+
* calls, adopted/bespoke apps, the actual
|
|
48
63
|
* Zitadel/Coolify/Cloudflare/Files/AI/cms shapes) is #20's "provision
|
|
49
64
|
* Company Factory end to end", which wires real readers and executors
|
|
50
65
|
* against this seam.
|
|
51
66
|
*/
|
|
52
|
-
export declare function computePlanDetail(org: DeclaredOrg, live: LiveResource[]): PlanDetail;
|
|
67
|
+
export declare function computePlanDetail(org: DeclaredOrg, live: LiveResource[], recordedIds?: Readonly<Record<string, string>>): PlanDetail;
|