@zitadel/config 1.0.0-alpha.24 → 1.0.0-alpha.25

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 (55) hide show
  1. package/defaults/branding/centered/login.liquid +31 -11
  2. package/defaults/branding/minimal/login.liquid +30 -10
  3. package/dist/branding-contrast.d.mts +28 -21
  4. package/dist/branding-contrast.d.mts.map +1 -1
  5. package/dist/branding-contrast.mjs +1 -1
  6. package/dist/branding-contrast.mjs.map +1 -1
  7. package/dist/branding-url.d.mts +1 -2
  8. package/dist/branding-url.d.mts.map +1 -1
  9. package/dist/branding-url.mjs.map +1 -1
  10. package/dist/css-color.d.mts +5 -10
  11. package/dist/css-color.d.mts.map +1 -1
  12. package/dist/css-color.mjs +1 -1
  13. package/dist/css-color.mjs.map +1 -1
  14. package/dist/{defaults-BnWXllBk.d.mts → defaults-BtKIcWxW.d.mts} +11 -7
  15. package/dist/defaults-BtKIcWxW.d.mts.map +1 -0
  16. package/dist/{defaults-BnRHT7RQ.mjs → defaults-COLBFU3S.mjs} +1 -1
  17. package/dist/defaults-COLBFU3S.mjs.map +1 -0
  18. package/dist/defaults.d.mts +1 -1
  19. package/dist/defaults.mjs +1 -1
  20. package/dist/idp/index.d.mts +2 -0
  21. package/dist/idp/index.mjs +2 -0
  22. package/dist/idp-ByzXQN85.mjs +207 -0
  23. package/dist/idp-ByzXQN85.mjs.map +1 -0
  24. package/dist/idp-connection-CHxGm1Wr.mjs +324 -0
  25. package/dist/idp-connection-CHxGm1Wr.mjs.map +1 -0
  26. package/dist/index-Cuxu-zQD.d.mts +210 -0
  27. package/dist/index-Cuxu-zQD.d.mts.map +1 -0
  28. package/dist/index.d.mts +4 -3
  29. package/dist/index.mjs +5 -4
  30. package/dist/{meta-schemas-K8f7YZYz.mjs → meta-schemas-C03zgFux.mjs} +31 -328
  31. package/dist/meta-schemas-C03zgFux.mjs.map +1 -0
  32. package/dist/meta-schemas.d.mts +5 -6
  33. package/dist/meta-schemas.d.mts.map +1 -1
  34. package/dist/meta-schemas.mjs +1 -1
  35. package/dist/normalize.d.mts +3 -4
  36. package/dist/normalize.d.mts.map +1 -1
  37. package/dist/normalize.mjs.map +1 -1
  38. package/dist/{schemas-7oNU25Qz.mjs → schemas-CUU4Rf8J.mjs} +11 -4
  39. package/dist/schemas-CUU4Rf8J.mjs.map +1 -0
  40. package/dist/schemas.d.mts +361 -9
  41. package/dist/schemas.d.mts.map +1 -1
  42. package/dist/schemas.mjs +2 -2
  43. package/dist/template.d.mts +9 -10
  44. package/dist/template.d.mts.map +1 -1
  45. package/dist/template.mjs.map +1 -1
  46. package/dist/validate.d.mts +15 -13
  47. package/dist/validate.d.mts.map +1 -1
  48. package/dist/validate.mjs +28 -18
  49. package/dist/validate.mjs.map +1 -1
  50. package/meta-schemas/flow-definition.json +81 -6
  51. package/package.json +12 -6
  52. package/dist/defaults-BnRHT7RQ.mjs.map +0 -1
  53. package/dist/defaults-BnWXllBk.d.mts.map +0 -1
  54. package/dist/meta-schemas-K8f7YZYz.mjs.map +0 -1
  55. package/dist/schemas-7oNU25Qz.mjs.map +0 -1
package/dist/validate.mjs CHANGED
@@ -20,8 +20,8 @@ const FLOW_VALIDATION_RULES = {
20
20
  const RESERVED_OUTCOMES = [
21
21
  "user_not_found",
22
22
  "user_already_exists",
23
- "identity_unknown",
24
- "callback"
23
+ "sso_user_not_found",
24
+ "sso_authenticated"
25
25
  ];
26
26
  /** Mirrors the `FlowDefinitionPurpose` enum (snake transform) in flow_definition.go. */
27
27
  const FLOW_PURPOSES = [
@@ -34,7 +34,7 @@ const FLOW_PURPOSES = [
34
34
  ];
35
35
  /**
36
36
  * Mirrors `purposeFlipTargets` in flow_definition_validator.go: the
37
- * identifier outcomes only. `identity_unknown` comes only from SSO
37
+ * identifier outcomes only. `sso_user_not_found` comes only from SSO
38
38
  * resolution and is left out here; requiring it on every combined entry
39
39
  * step would reject the shipped default flow. A rule on steps carrying
40
40
  * sso_providers is future work (#1044).
@@ -49,14 +49,18 @@ const FLIP_OUTCOME_IMPACTS = {
49
49
  user_already_exists: "someone who already has an account gets stuck at registration instead of being routed to sign-in"
50
50
  };
51
51
  /** Action kinds a flow author may declare; `back` is engine-injected. */
52
- const DECLARABLE_ACTION_KINDS = new Set([
52
+ const DECLARABLE_ACTION_KINDS = /* @__PURE__ */ new Set([
53
53
  "submit",
54
54
  "passkey",
55
55
  "passkey_register",
56
56
  "navigate"
57
57
  ]);
58
- /** Mirrors `flowBackActionName` in flow_state_machine.go. */
59
- const BACK_ACTION_NAME = "back";
58
+ /**
59
+ * Action names the engine claims, with the reason the validator reports.
60
+ * Mirrors `flowBackActionName` and `FlowActionSSO` in flow_state_machine.go.
61
+ * A Map, not an object, so a name like `constructor` cannot match by accident.
62
+ */
63
+ const RESERVED_ACTION_NAMES = /* @__PURE__ */ new Map([["back", "engine-injected back navigation"], ["sso", "sso submissions"]]);
60
64
  /** Mirrors `authMethodPrefix` in flow_definition.go. */
61
65
  const AUTH_METHOD_PREFIX = "x-auth-methods#";
62
66
  /** Mirrors `ManifestForOnSuccess` in flow_on_success.go. */
@@ -188,14 +192,18 @@ function validateStep(step) {
188
192
  }
189
193
  if (action.kind === "" || action.kind !== "back" && !DECLARABLE_ACTION_KINDS.has(action.kind)) issues.push(error("steps", `step ${q(name)}: action ${q(action.name)} has no kind`, name));
190
194
  else if (action.kind === "back") issues.push(error("steps", `step ${q(name)}: action ${q(action.name)} has kind=back, which is engine-injected and cannot be declared`, name));
191
- if (action.name === BACK_ACTION_NAME) issues.push(error("steps", `step ${q(name)}: action name ${q(action.name)} is reserved for engine-injected back navigation`, name));
195
+ const reserved = RESERVED_ACTION_NAMES.get(action.name);
196
+ if (reserved !== void 0) issues.push(error("steps", `step ${q(name)}: action name ${q(action.name)} is reserved for ${reserved}`, name));
192
197
  actionNames.add(action.name);
193
198
  }
194
- const hasCallback = step.transitions.has("callback");
195
- if (step.fields.length === 0 && step.actions.length === 0 && step.ssoProviderCount === 0 && step.gateCount === 0 && !hasCallback) issues.push(error("steps", `step ${q(name)} is non-terminal but has no fields, actions, sso_providers, gates, or transitions.callback`, name));
196
- if (step.ssoProviderCount > 0 && !hasCallback) issues.push(error("steps", `step ${q(name)}: has sso_providers but is missing transitions.callback`, name));
199
+ const hasSsoAuthenticated = step.transitions.has("sso_authenticated");
200
+ if (step.fields.length === 0 && step.actions.length === 0 && step.ssoProviderCount === 0 && step.gateCount === 0 && !hasSsoAuthenticated) issues.push(error("steps", `step ${q(name)} is non-terminal but has no fields, actions, sso_providers, gates, or transitions.sso_authenticated`, name));
201
+ if (step.ssoProviderCount > 0 && !hasSsoAuthenticated) issues.push(error("steps", `step ${q(name)}: has sso_providers but is missing transitions.sso_authenticated`, name));
197
202
  for (const actionName of actionNames) if (!step.transitions.has(actionName)) issues.push(error("steps", `step ${q(name)}: action ${q(actionName)} has no matching transition`, name));
198
- for (const transitionKey of step.transitions.keys()) if (!actionNames.has(transitionKey) && !RESERVED_OUTCOMES.includes(transitionKey)) issues.push(error("steps", `step ${q(name)}: transition key ${q(transitionKey)} is not an action name or reserved outcome (user_not_found, user_already_exists, identity_unknown, callback)`, name));
203
+ for (const [transitionKey, t] of step.transitions) {
204
+ if (!actionNames.has(transitionKey) && !RESERVED_OUTCOMES.includes(transitionKey)) issues.push(error("steps", `step ${q(name)}: transition key ${q(transitionKey)} is not an action name or reserved outcome (user_not_found, user_already_exists, sso_user_not_found, sso_authenticated)`, name));
205
+ if ((transitionKey === "sso_authenticated" || transitionKey === "sso_user_not_found") && (t.purpose !== null || t.action !== null)) issues.push(error("steps", `step ${q(name)}: transition ${q(transitionKey)} is an sso outcome and cannot declare purpose or action`, name));
206
+ }
199
207
  return issues;
200
208
  }
201
209
  /** A transition with no cross-flow action targets a step in this flow. */
@@ -235,12 +243,14 @@ function bfs(start, adj, visited) {
235
243
  function validateGraph(def, stepNames) {
236
244
  const issues = [];
237
245
  for (const step of def.steps) for (const [key, t] of step.transitions) {
238
- if (t.purpose !== null) if (t.action !== null) issues.push(error("graph", `step ${q(step.name)}: transition ${q(key)} declares both purpose and action; a transition either re-purposes locally or targets another flow`, step.name));
239
- else if (!FLOW_PURPOSES.includes(t.purpose)) issues.push(error("graph", `step ${q(step.name)}: transition ${q(key)} has invalid purpose`, step.name));
240
- else {
241
- const entry = def.purposes.get(t.purpose);
242
- if (entry === void 0) issues.push(error("graph", `step ${q(step.name)}: transition ${q(key)} re-purposes to ${q(t.purpose)}, which this definition does not serve`, step.name));
243
- else if (t.target !== entry) issues.push(error("graph", `step ${q(step.name)}: transition ${q(key)} re-purposes to ${q(t.purpose)} but targets ${q(t.target)}; it must target that purpose's entry step ${q(entry)}`, step.name));
246
+ if (t.purpose !== null) {
247
+ if (t.action !== null) issues.push(error("graph", `step ${q(step.name)}: transition ${q(key)} declares both purpose and action; a transition either re-purposes locally or targets another flow`, step.name));
248
+ else if (!FLOW_PURPOSES.includes(t.purpose)) issues.push(error("graph", `step ${q(step.name)}: transition ${q(key)} has invalid purpose`, step.name));
249
+ else {
250
+ const entry = def.purposes.get(t.purpose);
251
+ if (entry === void 0) issues.push(error("graph", `step ${q(step.name)}: transition ${q(key)} re-purposes to ${q(t.purpose)}, which this definition does not serve`, step.name));
252
+ else if (t.target !== entry) issues.push(error("graph", `step ${q(step.name)}: transition ${q(key)} re-purposes to ${q(t.purpose)} but targets ${q(t.target)}; it must target that purpose's entry step ${q(entry)}`, step.name));
253
+ }
244
254
  }
245
255
  if (isCurrentFlow(t)) {
246
256
  if (!stepNames.has(t.target)) issues.push(error("graph", `step ${q(step.name)}: transition ${q(key)} targets unknown step ${q(t.target)}`, step.name));
@@ -344,7 +354,7 @@ function resolveFieldChallenge(field, properties, schema) {
344
354
  return { challenge: "password" };
345
355
  }
346
356
  const segments = field.split(".");
347
- let property = void 0;
357
+ let property;
348
358
  let level = properties;
349
359
  for (const [i, segment] of segments.entries()) {
350
360
  property = level !== void 0 && Object.hasOwn(level, segment) ? level[segment] : void 0;
@@ -1 +1 @@
1
- {"version":3,"file":"validate.mjs","names":[],"sources":["../src/validate.ts"],"sourcesContent":["/**\n * Plan-time port of the server's flow-definition validator\n * (`internal/domain/flow_definition_validator.go`). The CLI runs it in\n * `buildSyncPlan` so every rule the server enforces on apply speaks up\n * at plan time — with the same words — before anything mutates.\n *\n * Contract with the Go source:\n *\n * - Every rule id in {@link FLOW_VALIDATION_RULES} names the Go function\n * it ports (`goRef`); a drift-audit test asserts those functions still\n * exist and that the shared literals below still match.\n * - Error messages mirror the Go detail strings verbatim, so a user who\n * bypasses plan sees identical text from the server.\n * - Unlike the Go validator (fail-fast), this port collects every issue.\n * Later phases are gated on earlier phases being clean so one defect\n * does not cascade into noise (a BFS over duplicate step names is\n * ill-defined).\n *\n * Scope cuts (server/service-side, not portable): pivot/switch targets\n * must name an active flow definition in the same project (needs the\n * DB); the user schema itself is assumed meta-schema-valid.\n *\n * Lifecycle: this port is a local-first interim, not a second source of\n * truth to grow. Its designed successor is the server-side validate-only\n * bundle endpoint (zitadel/nextgen#449); once plan can ask the server,\n * this file, its drift audit, and the keep-in-sync comment on the Go\n * validator all go away. New rules land in Go first and are ported here\n * only until then.\n */\n\nexport type FlowValidationSeverity = \"error\" | \"warning\";\n\nexport type FlowValidationIssue = {\n severity: FlowValidationSeverity;\n /** Stable rule id from {@link FLOW_VALIDATION_RULES}. */\n rule: FlowValidationRuleId;\n /** Mirrors the Go `ErrFlowDefinitionInvalid` detail string where a Go rule exists. */\n message: string;\n /** Step the issue is anchored to, when applicable. */\n step?: string;\n};\n\n/**\n * Rule registry: rule id → the Go function it ports. `goRef: null` marks\n * CLI-only guidance with no server counterpart.\n */\nexport const FLOW_VALIDATION_RULES = {\n \"step-names\": { goRef: \"stepNamesMap\" },\n definition: { goRef: \"validateDefinition\" },\n steps: { goRef: \"validateSteps\" },\n graph: { goRef: \"validateGraph\" },\n cycles: { goRef: \"validateCycles\" },\n \"flip-table\": { goRef: \"validateFlipTableCoverage\" },\n \"schema/required-fields\": { goRef: \"validateRequiredUserSchemaFields\" },\n \"schema/fields-resolve\": { goRef: \"resolveAllStepFields\" },\n \"schema/passkey-actions\": { goRef: \"validatePasskeyActionsEnabled\" },\n \"schema/on-success-manifest\": { goRef: \"validateOnSuccessManifests\" },\n \"warn/password-without-identifier\": { goRef: null },\n} as const;\n\nexport type FlowValidationRuleId = keyof typeof FLOW_VALIDATION_RULES;\n\n/** Mirrors `reservedOutcomes` in flow_definition_validator.go. */\nexport const RESERVED_OUTCOMES = [\n \"user_not_found\",\n \"user_already_exists\",\n \"identity_unknown\",\n \"callback\",\n] as const;\n\n/** Mirrors the `FlowDefinitionPurpose` enum (snake transform) in flow_definition.go. */\nexport const FLOW_PURPOSES = [\n \"login\",\n \"register\",\n \"recovery\",\n \"profiling\",\n \"reauth\",\n \"link_account\",\n] as const;\n\n/**\n * Mirrors `purposeFlipTargets` in flow_definition_validator.go: the\n * identifier outcomes only. `identity_unknown` comes only from SSO\n * resolution and is left out here; requiring it on every combined entry\n * step would reject the shipped default flow. A rule on steps carrying\n * sso_providers is future work (#1044).\n */\nexport const PURPOSE_FLIP_TARGETS: Readonly<Record<string, Readonly<Record<string, string>>>> = {\n login: { user_not_found: \"register\" },\n register: { user_already_exists: \"login\" },\n};\n\n/** Mirrors `flipOutcomeImpacts` in flow_definition_validator.go (verbatim). */\nconst FLIP_OUTCOME_IMPACTS: Readonly<Record<string, string>> = {\n user_not_found:\n \"someone without an account gets stuck at sign-in instead of being routed to registration\",\n user_already_exists:\n \"someone who already has an account gets stuck at registration instead of being routed to sign-in\",\n};\n\n/** Action kinds a flow author may declare; `back` is engine-injected. */\nconst DECLARABLE_ACTION_KINDS = new Set([\"submit\", \"passkey\", \"passkey_register\", \"navigate\"]);\n\n/** Mirrors `flowBackActionName` in flow_state_machine.go. */\nconst BACK_ACTION_NAME = \"back\";\n\n/** Mirrors `authMethodPrefix` in flow_definition.go. */\nconst AUTH_METHOD_PREFIX = \"x-auth-methods#\";\n\n/** Mirrors `ManifestForOnSuccess` in flow_on_success.go. */\nconst ON_SUCCESS_MANIFESTS: Readonly<Record<string, readonly FieldChallenge[]>> = {\n create_user: [\"identifier\", \"password\"],\n};\n\ntype FieldChallenge = \"identifier\" | \"password\";\n\n/**\n * Validate a flow-definition config body (the `.zitadel/flows/*.json`\n * content) against the rules the server enforces on apply. `schema` is\n * the content of the user schema the flow's `user_schema` pins; when\n * omitted, schema-dependent rules are skipped. Pure — collects all\n * issues, never throws.\n */\nexport function validateFlowDefinition(flow: object, schema?: object): FlowValidationIssue[] {\n const def = readFlow(flow);\n const issues: FlowValidationIssue[] = [];\n\n // Phase 1 — step names (everything else keys off them).\n const stepNames = new Set<string>();\n for (const step of def.steps) {\n if (stepNames.has(step.name)) {\n issues.push(error(\"step-names\", `duplicate step name ${q(step.name)}`, step.name));\n }\n stepNames.add(step.name);\n }\n\n // Phase 2 — purposes.\n if (def.purposes.size === 0) {\n issues.push(error(\"definition\", \"no purposes defined\"));\n }\n for (const [purpose, entryStep] of def.purposes) {\n if (!(FLOW_PURPOSES as readonly string[]).includes(purpose)) {\n issues.push(error(\"definition\", `'${purpose}' is not a valid purpose`));\n continue;\n }\n if (entryStep === \"\") {\n issues.push(error(\"definition\", `initial step for purpose '${purpose}' is empty`));\n continue;\n }\n if (!stepNames.has(entryStep)) {\n issues.push(\n error(\"definition\", `purpose ${q(purpose)} targets unknown entry-point step ${q(entryStep)}`),\n );\n }\n }\n\n // Phase 3 — per-step structure.\n for (const step of def.steps) {\n issues.push(...validateStep(step));\n }\n\n // Graph phases are ill-defined over broken structure — gate on 1–3.\n if (issues.length === 0) {\n issues.push(...validateGraph(def, stepNames));\n if (issues.length === 0) {\n issues.push(...validateCycles(def));\n }\n issues.push(...validateFlipTableCoverage(def));\n }\n\n // Schema-dependent phases: only meaningful over structurally sound\n // steps, and only when the paired schema content is available.\n if (schema !== undefined && issues.length === 0) {\n issues.push(...validateAgainstSchema(def, schema));\n }\n\n return issues;\n}\n\n// ── internal flow reading ────────────────────────────────────────────────────\n\ntype FlowStep = {\n name: string;\n fields: string[];\n actions: { name: string; kind: string }[];\n gateCount: number;\n ssoProviderCount: number;\n transitions: Map<string, { target: string; action: string | null; purpose: string | null }>;\n onSuccess: string | undefined;\n terminal: boolean;\n};\n\ntype FlowDef = {\n purposes: Map<string, string>;\n steps: FlowStep[];\n};\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction str(value: unknown): string {\n return typeof value === \"string\" ? value : \"\";\n}\n\n/**\n * Coerce raw parsed JSON into the internal shape. Callers usually run\n * the Zod config schema first; the defensive reads keep the standalone\n * API from crashing on malformed input (it reports rule violations on\n * whatever is readable instead).\n */\nfunction readFlow(flow: object): FlowDef {\n const raw = flow as Record<string, unknown>;\n\n const purposes = new Map<string, string>();\n if (isPlainObject(raw.purposes)) {\n for (const [purpose, entry] of Object.entries(raw.purposes)) {\n purposes.set(purpose, str(entry));\n }\n }\n\n const steps: FlowStep[] = [];\n if (Array.isArray(raw.steps)) {\n for (const value of raw.steps) {\n if (!isPlainObject(value)) continue;\n const transitions = new Map<\n string,\n { target: string; action: string | null; purpose: string | null }\n >();\n if (isPlainObject(value.transitions)) {\n for (const [key, t] of Object.entries(value.transitions)) {\n const entry = isPlainObject(t) ? t : {};\n transitions.set(key, {\n target: str(entry.target),\n action: typeof entry.action === \"string\" ? entry.action : null,\n purpose: typeof entry.purpose === \"string\" ? entry.purpose : null,\n });\n }\n }\n steps.push({\n name: str(value.name),\n fields: Array.isArray(value.fields) ? value.fields.map(str) : [],\n actions: Array.isArray(value.actions)\n ? value.actions\n .filter(isPlainObject)\n .map((a) => ({ name: str(a.name), kind: str(a.kind) }))\n : [],\n gateCount: isPlainObject(value.gates) ? Object.keys(value.gates).length : 0,\n ssoProviderCount: Array.isArray(value.sso_providers) ? value.sso_providers.length : 0,\n transitions,\n onSuccess: typeof value.on_success === \"string\" ? value.on_success : undefined,\n terminal: value.complete !== undefined && value.complete !== null,\n });\n }\n }\n\n return { purposes, steps };\n}\n\n/** Go `%q` — double-quoted string. */\nfunction q(value: string): string {\n return JSON.stringify(value);\n}\n\nfunction error(rule: FlowValidationRuleId, message: string, step?: string): FlowValidationIssue {\n return step === undefined\n ? { severity: \"error\", rule, message }\n : { severity: \"error\", rule, message, step };\n}\n\n// ── phase 3: per-step structure (validateSteps + uniqueNonEmptyFields) ──────\n\nfunction validateStep(step: FlowStep): FlowValidationIssue[] {\n const issues: FlowValidationIssue[] = [];\n const name = step.name;\n\n if (step.terminal) {\n if (\n step.fields.length > 0 ||\n step.actions.length > 0 ||\n step.transitions.size > 0 ||\n step.gateCount > 0 ||\n step.ssoProviderCount > 0\n ) {\n issues.push(\n error(\n \"steps\",\n `step ${q(name)} is terminal (complete is set) but has fields, actions, transitions, gates, or sso_providers`,\n name,\n ),\n );\n }\n return issues;\n }\n\n const seenFields = new Set<string>();\n for (const field of step.fields) {\n if (field === \"\") {\n issues.push(error(\"steps\", `step ${q(name)}: field entry is empty`, name));\n continue;\n }\n if (seenFields.has(field)) {\n issues.push(error(\"steps\", `step ${q(name)}: duplicate field ${q(field)}`, name));\n }\n seenFields.add(field);\n }\n\n const actionNames = new Set<string>();\n for (const action of step.actions) {\n if (action.name === \"\") {\n issues.push(error(\"steps\", `step ${q(name)}: action has empty name`, name));\n continue;\n }\n if (actionNames.has(action.name)) {\n issues.push(error(\"steps\", `step ${q(name)}: duplicate action ${q(action.name)}`, name));\n continue;\n }\n if (action.kind === \"\" || (action.kind !== \"back\" && !DECLARABLE_ACTION_KINDS.has(action.kind))) {\n issues.push(error(\"steps\", `step ${q(name)}: action ${q(action.name)} has no kind`, name));\n } else if (action.kind === \"back\") {\n issues.push(\n error(\n \"steps\",\n `step ${q(name)}: action ${q(action.name)} has kind=back, which is engine-injected and cannot be declared`,\n name,\n ),\n );\n }\n if (action.name === BACK_ACTION_NAME) {\n issues.push(\n error(\n \"steps\",\n `step ${q(name)}: action name ${q(action.name)} is reserved for engine-injected back navigation`,\n name,\n ),\n );\n }\n actionNames.add(action.name);\n }\n\n const hasCallback = step.transitions.has(\"callback\");\n if (\n step.fields.length === 0 &&\n step.actions.length === 0 &&\n step.ssoProviderCount === 0 &&\n step.gateCount === 0 &&\n !hasCallback\n ) {\n issues.push(\n error(\n \"steps\",\n `step ${q(name)} is non-terminal but has no fields, actions, sso_providers, gates, or transitions.callback`,\n name,\n ),\n );\n }\n\n if (step.ssoProviderCount > 0 && !hasCallback) {\n issues.push(\n error(\"steps\", `step ${q(name)}: has sso_providers but is missing transitions.callback`, name),\n );\n }\n\n for (const actionName of actionNames) {\n if (!step.transitions.has(actionName)) {\n issues.push(\n error(\"steps\", `step ${q(name)}: action ${q(actionName)} has no matching transition`, name),\n );\n }\n }\n\n for (const transitionKey of step.transitions.keys()) {\n if (!actionNames.has(transitionKey) && !(RESERVED_OUTCOMES as readonly string[]).includes(transitionKey)) {\n issues.push(\n error(\n \"steps\",\n `step ${q(name)}: transition key ${q(transitionKey)} is not an action name or reserved outcome (user_not_found, user_already_exists, identity_unknown, callback)`,\n name,\n ),\n );\n }\n }\n\n return issues;\n}\n\n// ── phase 4: graph / cycles / flip table ─────────────────────────────────────\n\n/** A transition with no cross-flow action targets a step in this flow. */\nfunction isCurrentFlow(t: { action: string | null }): boolean {\n return t.action === null;\n}\n\n/** Adjacency over local (current-flow) transitions: step → target steps. */\nfunction localAdjacency(def: FlowDef): Map<string, string[]> {\n const adj = new Map<string, string[]>();\n for (const step of def.steps) {\n for (const t of step.transitions.values()) {\n if (isCurrentFlow(t)) {\n const targets = adj.get(step.name) ?? [];\n targets.push(t.target);\n adj.set(step.name, targets);\n }\n }\n }\n return adj;\n}\n\n/** Reverse adjacency over local transitions: target step → source steps. */\nfunction reverseAdjacency(def: FlowDef): Map<string, string[]> {\n const reverse = new Map<string, string[]>();\n for (const step of def.steps) {\n for (const t of step.transitions.values()) {\n if (isCurrentFlow(t)) {\n const sources = reverse.get(t.target) ?? [];\n sources.push(step.name);\n reverse.set(t.target, sources);\n }\n }\n }\n return reverse;\n}\n\n/** BFS from `start` over `adj`, accumulating into `visited`. */\nfunction bfs(start: string, adj: Map<string, string[]>, visited: Set<string>): void {\n const queue = [start];\n for (let head = 0; head < queue.length; head += 1) {\n const current = queue[head];\n if (current === undefined || visited.has(current)) continue;\n visited.add(current);\n queue.push(...(adj.get(current) ?? []));\n }\n}\n\nfunction validateGraph(def: FlowDef, stepNames: Set<string>): FlowValidationIssue[] {\n const issues: FlowValidationIssue[] = [];\n\n for (const step of def.steps) {\n for (const [key, t] of step.transitions) {\n if (t.purpose !== null) {\n // Local re-purposing: never combined with a cross-flow action,\n // only to a purpose this definition serves, and only to that\n // purpose's entry step. Mirrors the server-side validator.\n if (t.action !== null) {\n issues.push(\n error(\n \"graph\",\n `step ${q(step.name)}: transition ${q(key)} declares both purpose and action; a transition either re-purposes locally or targets another flow`,\n step.name,\n ),\n );\n } else if (!(FLOW_PURPOSES as readonly string[]).includes(t.purpose)) {\n // Verbatim-identical to the Go validator's message (which cannot\n // print the raw value — its parse layer rejects it earlier).\n issues.push(\n error(\n \"graph\",\n `step ${q(step.name)}: transition ${q(key)} has invalid purpose`,\n step.name,\n ),\n );\n } else {\n const entry = def.purposes.get(t.purpose);\n if (entry === undefined) {\n issues.push(\n error(\n \"graph\",\n `step ${q(step.name)}: transition ${q(key)} re-purposes to ${q(t.purpose)}, which this definition does not serve`,\n step.name,\n ),\n );\n } else if (t.target !== entry) {\n issues.push(\n error(\n \"graph\",\n `step ${q(step.name)}: transition ${q(key)} re-purposes to ${q(t.purpose)} but targets ${q(t.target)}; it must target that purpose's entry step ${q(entry)}`,\n step.name,\n ),\n );\n }\n }\n }\n if (isCurrentFlow(t)) {\n if (!stepNames.has(t.target)) {\n issues.push(\n error(\n \"graph\",\n `step ${q(step.name)}: transition ${q(key)} targets unknown step ${q(t.target)}`,\n step.name,\n ),\n );\n }\n } else if (t.action !== \"switch\" && t.action !== \"pivot\") {\n issues.push(\n error(\n \"graph\",\n `step ${q(step.name)}: transition ${q(key)} has invalid action ${q(t.action ?? \"\")}`,\n step.name,\n ),\n );\n }\n }\n }\n\n for (const step of def.steps) {\n if (!step.terminal && step.transitions.size === 0) {\n issues.push(\n error(\"graph\", `step ${q(step.name)} is non-terminal but has no outgoing transitions`, step.name),\n );\n }\n }\n\n const reachable = new Set<string>();\n const adj = localAdjacency(def);\n for (const entryStep of def.purposes.values()) {\n bfs(entryStep, adj, reachable);\n }\n for (const step of def.steps) {\n if (!reachable.has(step.name)) {\n issues.push(error(\"graph\", `step ${q(step.name)} is unreachable from any entry point`, step.name));\n }\n }\n\n return issues;\n}\n\nfunction validateCycles(def: FlowDef): FlowValidationIssue[] {\n const issues: FlowValidationIssue[] = [];\n const reverse = reverseAdjacency(def);\n\n // Seed: escape nodes (terminal, or a switch/pivot out); BFS backwards.\n const safe = new Set<string>();\n const queue: string[] = [];\n for (const step of def.steps) {\n const escapes =\n step.terminal || [...step.transitions.values()].some((t) => !isCurrentFlow(t));\n if (escapes) {\n safe.add(step.name);\n queue.push(step.name);\n }\n }\n for (let head = 0; head < queue.length; head += 1) {\n const current = queue[head];\n if (current === undefined) continue;\n for (const pred of reverse.get(current) ?? []) {\n if (!safe.has(pred)) {\n safe.add(pred);\n queue.push(pred);\n }\n }\n }\n\n for (const step of def.steps) {\n if (!step.terminal && !safe.has(step.name)) {\n issues.push(\n error(\n \"cycles\",\n `step ${q(step.name)} is trapped: no path to a terminal step or another flow`,\n step.name,\n ),\n );\n }\n }\n return issues;\n}\n\nfunction validateFlipTableCoverage(def: FlowDef): FlowValidationIssue[] {\n const issues: FlowValidationIssue[] = [];\n for (const [purpose, entryStepName] of def.purposes) {\n const flipTargets = PURPOSE_FLIP_TARGETS[purpose];\n if (flipTargets === undefined) continue;\n const entry = def.steps.find((s) => s.name === entryStepName);\n if (entry === undefined) continue;\n for (const [outcome, targetPurpose] of Object.entries(flipTargets)) {\n if (!def.purposes.has(targetPurpose)) continue;\n if (!entry.transitions.has(outcome)) {\n issues.push(\n error(\n \"flip-table\",\n `step ${q(entry.name)}: entry step for purpose ${q(purpose)} must wire ${q(outcome)} transition because ${q(targetPurpose)} is also a purpose: without it, ${FLIP_OUTCOME_IMPACTS[outcome]}`,\n entry.name,\n ),\n );\n }\n }\n }\n return issues;\n}\n\n// ── phase 5: schema-dependent rules ──────────────────────────────────────────\n\nfunction schemaProperties(schema: Record<string, unknown>): Record<string, unknown> | undefined {\n return isPlainObject(schema.properties) ? schema.properties : undefined;\n}\n\nfunction schemaRequired(schema: Record<string, unknown>): string[] {\n return Array.isArray(schema.required) ? schema.required.map(str).filter((n) => n !== \"\") : [];\n}\n\n/**\n * Mirrors `schemaReader.RequiredPaths`: every name in `required`,\n * recursed through the nested `required` of each required object. A\n * required object declaring no `required` of its own ends the descent at\n * the object itself.\n *\n * An optional object contributes its own `required` too once it appears\n * in `materialized`. It only exists in the document because something\n * beneath it was collected, and from that point document validation\n * enforces the rest of its `required` list.\n */\nfunction requiredPaths(\n schema: Record<string, unknown>,\n materialized: ReadonlySet<string>,\n prefix = \"\",\n): string[] {\n const properties = schemaProperties(schema);\n const required = schemaRequired(schema);\n // A set, like the Go `map[string]struct{}`, so a name repeated in\n // `required` yields the path once.\n const out = new Set<string>();\n\n for (const name of required) {\n const path = prefix === \"\" ? name : `${prefix}.${name}`;\n const property =\n properties !== undefined && Object.hasOwn(properties, name) ? properties[name] : undefined;\n if (!isPlainObject(property) || schemaRequired(property).length === 0) {\n out.add(path);\n continue;\n }\n for (const nested of requiredPaths(property, materialized, path)) out.add(nested);\n }\n\n // An optional object is invisible to the loop above, so descend into\n // the ones a collected field materializes.\n for (const [name, property] of Object.entries(properties ?? {})) {\n if (required.includes(name) || !isPlainObject(property)) continue;\n const path = prefix === \"\" ? name : `${prefix}.${name}`;\n if (!materialized.has(path)) continue;\n for (const nested of requiredPaths(property, materialized, path)) out.add(nested);\n }\n\n return [...out];\n}\n\n/** Mirrors `xAuthMethodsReader.IsEnabled` in flow_field_resolver_schema.go. */\nfunction authMethodEnabled(schema: Record<string, unknown>, method: string): boolean {\n if (!isPlainObject(schema[\"x-auth-methods\"])) return false;\n const entry = schema[\"x-auth-methods\"][method];\n return isPlainObject(entry) && entry.enabled === true;\n}\n\n/**\n * Resolve one step field to its challenge, mirroring the subset of\n * `SchemaFieldResolver.Resolve` the validator needs. Returns either the\n * challenge (or null for none) or the Go-verbatim resolution error.\n */\nfunction resolveFieldChallenge(\n field: string,\n properties: Record<string, unknown>,\n schema: Record<string, unknown>,\n): { challenge: FieldChallenge | null } | { message: string } {\n if (field.startsWith(AUTH_METHOD_PREFIX)) {\n const method = field.slice(AUTH_METHOD_PREFIX.length);\n // Mirrors deriveAuthMethodType: only password is field-shaped today.\n if (method !== \"password\") {\n return { message: `unknown field type for ${q(field)}` };\n }\n if (!authMethodEnabled(schema, method)) {\n // Surfaced by resolveAllStepFields' enabled-method cross-check.\n return { message: `${q(method)} is not an enabled authentication method` };\n }\n return { challenge: \"password\" };\n }\n\n // Mirrors walkUserProperty: a nested property is addressed by its\n // dotted path, descending one `properties` level per segment.\n const segments = field.split(\".\");\n let property: unknown = undefined;\n let level: Record<string, unknown> | undefined = properties;\n for (const [i, segment] of segments.entries()) {\n // Own properties only: Go indexes a map, where an inherited name like\n // `toString` is simply absent.\n property = level !== undefined && Object.hasOwn(level, segment) ? level[segment] : undefined;\n if (property === undefined) {\n return { message: `flow field: not a property in the user schema: ${q(field)}` };\n }\n const nested = isPlainObject(property) ? schemaProperties(property) : undefined;\n if (i < segments.length - 1 && nested === undefined) {\n return { message: `flow field: not a property in the user schema: ${q(field)}` };\n }\n level = nested;\n }\n\n if (!isPlainObject(property)) {\n return { challenge: null };\n }\n\n // Mirrors schemaReader.JSONType: the nullable idiom `[\"null\", X]`\n // reduces to X; any other multi-entry union is unsupported. Go reduces\n // before it tests for a composite, so the union error wins and the\n // object/array test below reads the reduced type rather than the union.\n let jsonType = property.type;\n if (Array.isArray(jsonType)) {\n const nonNull = jsonType.filter((t) => t !== \"null\");\n if (nonNull.length > 1) {\n return { message: `flow field: unsupported JSON type: [${jsonType.map(str).join(\" \")}]` };\n }\n // Undefined when every entry was \"null\", matching Go's empty-string case.\n jsonType = nonNull[0];\n }\n\n // Mirrors deriveUserPropertyType: an object or array has no\n // field-shaped input. A property carrying `properties` is an object,\n // and one carrying `items` is an array, even when it omits the `type`\n // keyword.\n if (\n jsonType === \"object\" ||\n jsonType === \"array\" ||\n schemaProperties(property) !== undefined ||\n \"items\" in property\n ) {\n return {\n message: `flow field: not a scalar property, name a nested leaf instead: ${q(field)}`,\n };\n }\n\n // Mirrors deriveIdentifierChallenge: the field naming the schema-root\n // `x-identifier` designation is the identifier (ADR 057); other\n // properties — unique or not — carry no identifier challenge.\n const identifier = schema[\"x-identifier\"];\n if (typeof identifier === \"string\" && identifier !== \"\" && field === identifier) {\n return { challenge: \"identifier\" };\n }\n return { challenge: null };\n}\n\nfunction validateAgainstSchema(def: FlowDef, schema: object): FlowValidationIssue[] {\n const issues: FlowValidationIssue[] = [];\n const raw = schema as Record<string, unknown>;\n\n const properties = schemaProperties(raw);\n if (properties === undefined) {\n issues.push(error(\"schema/fields-resolve\", \"user schema has no properties\"));\n return issues;\n }\n\n // Required-fields coverage (validateRequiredUserSchemaFields).\n // Collecting `address.street` covers the leaf and every object above\n // it, since an object materializes once one of its children is\n // collected. The same prefixes are what the schema treats as\n // materialized, so an optional object's own `required` list comes into\n // force here too.\n const covered = new Set<string>();\n for (const step of def.steps) {\n for (const field of step.fields) {\n let path = \"\";\n for (const node of field.split(\".\")) {\n path = path === \"\" ? node : `${path}.${node}`;\n covered.add(path);\n }\n }\n }\n const missing = requiredPaths(raw, covered)\n .filter((field) => !covered.has(field))\n .sort();\n if (missing.length > 0) {\n issues.push(\n error(\n \"schema/required-fields\",\n `required fields [${missing.join(\" \")}] in user schema are missing in the flow definition steps`,\n ),\n );\n }\n\n // Field resolution (resolveAllStepFields), collecting challenges for\n // the manifest and warning rules below.\n const challengesByStep = new Map<string, Set<FieldChallenge>>();\n let resolutionFailed = false;\n for (const step of def.steps) {\n const challenges = new Set<FieldChallenge>();\n for (const field of step.fields) {\n const resolved = resolveFieldChallenge(field, properties, raw);\n if (\"message\" in resolved) {\n issues.push(\n error(\"schema/fields-resolve\", `step ${q(step.name)}: ${resolved.message}`, step.name),\n );\n resolutionFailed = true;\n continue;\n }\n if (resolved.challenge !== null) {\n challenges.add(resolved.challenge);\n }\n }\n challengesByStep.set(step.name, challenges);\n }\n\n // Passkey enablement (validatePasskeyActionsEnabled): passkey is\n // action-shaped, not field-shaped, so the per-field enabled-method\n // check above never sees it.\n if (!authMethodEnabled(raw, \"passkey\")) {\n for (const step of def.steps) {\n for (const action of step.actions) {\n if (action.kind === \"passkey\" || action.kind === \"passkey_register\") {\n issues.push(\n error(\n \"schema/passkey-actions\",\n `step ${q(step.name)}: action ${q(action.name)} offers passkey but \"passkey\" is not an enabled authentication method`,\n step.name,\n ),\n );\n }\n }\n }\n }\n\n if (resolutionFailed) {\n return issues;\n }\n\n // on_success manifests (validateOnSuccessManifests): every kind the\n // mutation needs must be collected on the step or upstream.\n const reverse = reverseAdjacency(def);\n for (const step of def.steps) {\n if (step.onSuccess === undefined) continue;\n const manifest = ON_SUCCESS_MANIFESTS[step.onSuccess];\n if (manifest === undefined) continue;\n const upstream = new Set<string>();\n bfs(step.name, reverse, upstream);\n for (const kind of manifest) {\n const established = [...upstream].some((name) => challengesByStep.get(name)?.has(kind));\n if (!established) {\n issues.push(\n error(\n \"schema/on-success-manifest\",\n `step ${q(step.name)}: on_success ${step.onSuccess} requires ${q(kind)} to be collected upstream`,\n step.name,\n ),\n );\n }\n }\n }\n\n // CLI-only guidance (no Go counterpart): a password collected on the\n // login path with no identifier upstream leaves the engine unable to\n // resolve which user to challenge. Path-sensitive: walk forward from\n // the login entry and stop at any step that collects an identifier\n // (every route through it is covered), so an identifier on a\n // *different* route — e.g. only on the register-purpose chain into a\n // shared step — cannot mask a login route that never collects one.\n const loginEntry = def.purposes.get(\"login\");\n if (loginEntry !== undefined) {\n const adj = localAdjacency(def);\n const identifierFree = new Set<string>();\n const queue = [loginEntry];\n for (let head = 0; head < queue.length; head += 1) {\n const name = queue[head];\n if (name === undefined || identifierFree.has(name)) continue;\n if (challengesByStep.get(name)?.has(\"identifier\")) continue;\n identifierFree.add(name);\n queue.push(...(adj.get(name) ?? []));\n }\n for (const step of def.steps) {\n if (!identifierFree.has(step.name)) continue;\n if (!challengesByStep.get(step.name)?.has(\"password\")) continue;\n issues.push({\n severity: \"warning\",\n rule: \"warn/password-without-identifier\",\n message: `step ${q(step.name)} collects ${AUTH_METHOD_PREFIX}password on the login path but no upstream step collects an identifier field — the engine cannot resolve which user to challenge`,\n step: step.name,\n });\n }\n }\n\n return issues;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"validate.mjs","names":[],"sources":["../src/validate.ts"],"sourcesContent":["/**\n * Plan-time port of the server's flow-definition validator\n * (`internal/domain/flow_definition_validator.go`). The CLI runs it in\n * `buildSyncPlan` so every rule the server enforces on apply speaks up\n * at plan time — with the same words — before anything mutates.\n *\n * Contract with the Go source:\n *\n * - Every rule id in {@link FLOW_VALIDATION_RULES} names the Go function\n * it ports (`goRef`); a drift-audit test asserts those functions still\n * exist and that the shared literals below still match.\n * - Error messages mirror the Go detail strings verbatim, so a user who\n * bypasses plan sees identical text from the server.\n * - Unlike the Go validator (fail-fast), this port collects every issue.\n * Later phases are gated on earlier phases being clean so one defect\n * does not cascade into noise (a BFS over duplicate step names is\n * ill-defined).\n *\n * Scope cuts (server/service-side, not portable): pivot/switch targets\n * must name an active flow definition in the same project (needs the\n * DB); the user schema itself is assumed meta-schema-valid.\n *\n * Lifecycle: this port is a local-first interim, not a second source of\n * truth to grow. Its designed successor is the server-side validate-only\n * bundle endpoint (zitadel/nextgen#449); once plan can ask the server,\n * this file, its drift audit, and the keep-in-sync comment on the Go\n * validator all go away. New rules land in Go first and are ported here\n * only until then.\n */\n\nexport type FlowValidationSeverity = \"error\" | \"warning\";\n\nexport type FlowValidationIssue = {\n severity: FlowValidationSeverity;\n /** Stable rule id from {@link FLOW_VALIDATION_RULES}. */\n rule: FlowValidationRuleId;\n /** Mirrors the Go `ErrFlowDefinitionInvalid` detail string where a Go rule exists. */\n message: string;\n /** Step the issue is anchored to, when applicable. */\n step?: string;\n};\n\n/**\n * Rule registry: rule id → the Go function it ports. `goRef: null` marks\n * CLI-only guidance with no server counterpart.\n */\nexport const FLOW_VALIDATION_RULES = {\n \"step-names\": { goRef: \"stepNamesMap\" },\n definition: { goRef: \"validateDefinition\" },\n steps: { goRef: \"validateSteps\" },\n graph: { goRef: \"validateGraph\" },\n cycles: { goRef: \"validateCycles\" },\n \"flip-table\": { goRef: \"validateFlipTableCoverage\" },\n \"schema/required-fields\": { goRef: \"validateRequiredUserSchemaFields\" },\n \"schema/fields-resolve\": { goRef: \"resolveAllStepFields\" },\n \"schema/passkey-actions\": { goRef: \"validatePasskeyActionsEnabled\" },\n \"schema/on-success-manifest\": { goRef: \"validateOnSuccessManifests\" },\n \"warn/password-without-identifier\": { goRef: null },\n} as const;\n\nexport type FlowValidationRuleId = keyof typeof FLOW_VALIDATION_RULES;\n\n/** Mirrors `reservedOutcomes` in flow_definition_validator.go. */\nexport const RESERVED_OUTCOMES = [\n \"user_not_found\",\n \"user_already_exists\",\n \"sso_user_not_found\",\n \"sso_authenticated\",\n] as const;\n\n/** Mirrors the `FlowDefinitionPurpose` enum (snake transform) in flow_definition.go. */\nexport const FLOW_PURPOSES = [\n \"login\",\n \"register\",\n \"recovery\",\n \"profiling\",\n \"reauth\",\n \"link_account\",\n] as const;\n\n/**\n * Mirrors `purposeFlipTargets` in flow_definition_validator.go: the\n * identifier outcomes only. `sso_user_not_found` comes only from SSO\n * resolution and is left out here; requiring it on every combined entry\n * step would reject the shipped default flow. A rule on steps carrying\n * sso_providers is future work (#1044).\n */\nexport const PURPOSE_FLIP_TARGETS: Readonly<Record<string, Readonly<Record<string, string>>>> = {\n login: { user_not_found: \"register\" },\n register: { user_already_exists: \"login\" },\n};\n\n/** Mirrors `flipOutcomeImpacts` in flow_definition_validator.go (verbatim). */\nconst FLIP_OUTCOME_IMPACTS: Readonly<Record<string, string>> = {\n user_not_found:\n \"someone without an account gets stuck at sign-in instead of being routed to registration\",\n user_already_exists:\n \"someone who already has an account gets stuck at registration instead of being routed to sign-in\",\n};\n\n/** Action kinds a flow author may declare; `back` is engine-injected. */\nconst DECLARABLE_ACTION_KINDS = new Set([\"submit\", \"passkey\", \"passkey_register\", \"navigate\"]);\n\n/**\n * Action names the engine claims, with the reason the validator reports.\n * Mirrors `flowBackActionName` and `FlowActionSSO` in flow_state_machine.go.\n * A Map, not an object, so a name like `constructor` cannot match by accident.\n */\nconst RESERVED_ACTION_NAMES = new Map([\n [\"back\", \"engine-injected back navigation\"],\n [\"sso\", \"sso submissions\"],\n]);\n\n/** Mirrors `authMethodPrefix` in flow_definition.go. */\nconst AUTH_METHOD_PREFIX = \"x-auth-methods#\";\n\n/** Mirrors `ManifestForOnSuccess` in flow_on_success.go. */\nconst ON_SUCCESS_MANIFESTS: Readonly<Record<string, readonly FieldChallenge[]>> = {\n create_user: [\"identifier\", \"password\"],\n};\n\ntype FieldChallenge = \"identifier\" | \"password\";\n\n/**\n * Validate a flow-definition config body (the `.zitadel/flows/*.json`\n * content) against the rules the server enforces on apply. `schema` is\n * the content of the user schema the flow's `user_schema` pins; when\n * omitted, schema-dependent rules are skipped. Pure — collects all\n * issues, never throws.\n */\nexport function validateFlowDefinition(flow: object, schema?: object): FlowValidationIssue[] {\n const def = readFlow(flow);\n const issues: FlowValidationIssue[] = [];\n\n // Phase 1 — step names (everything else keys off them).\n const stepNames = new Set<string>();\n for (const step of def.steps) {\n if (stepNames.has(step.name)) {\n issues.push(error(\"step-names\", `duplicate step name ${q(step.name)}`, step.name));\n }\n stepNames.add(step.name);\n }\n\n // Phase 2 — purposes.\n if (def.purposes.size === 0) {\n issues.push(error(\"definition\", \"no purposes defined\"));\n }\n for (const [purpose, entryStep] of def.purposes) {\n if (!(FLOW_PURPOSES as readonly string[]).includes(purpose)) {\n issues.push(error(\"definition\", `'${purpose}' is not a valid purpose`));\n continue;\n }\n if (entryStep === \"\") {\n issues.push(error(\"definition\", `initial step for purpose '${purpose}' is empty`));\n continue;\n }\n if (!stepNames.has(entryStep)) {\n issues.push(\n error(\n \"definition\",\n `purpose ${q(purpose)} targets unknown entry-point step ${q(entryStep)}`,\n ),\n );\n }\n }\n\n // Phase 3 — per-step structure.\n for (const step of def.steps) {\n issues.push(...validateStep(step));\n }\n\n // Graph phases are ill-defined over broken structure — gate on 1–3.\n if (issues.length === 0) {\n issues.push(...validateGraph(def, stepNames));\n if (issues.length === 0) {\n issues.push(...validateCycles(def));\n }\n issues.push(...validateFlipTableCoverage(def));\n }\n\n // Schema-dependent phases: only meaningful over structurally sound\n // steps, and only when the paired schema content is available.\n if (schema !== undefined && issues.length === 0) {\n issues.push(...validateAgainstSchema(def, schema));\n }\n\n return issues;\n}\n\n// ── internal flow reading ────────────────────────────────────────────────────\n\ntype FlowStep = {\n name: string;\n fields: string[];\n actions: { name: string; kind: string }[];\n gateCount: number;\n ssoProviderCount: number;\n transitions: Map<string, { target: string; action: string | null; purpose: string | null }>;\n onSuccess: string | undefined;\n terminal: boolean;\n};\n\ntype FlowDef = {\n purposes: Map<string, string>;\n steps: FlowStep[];\n};\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction str(value: unknown): string {\n return typeof value === \"string\" ? value : \"\";\n}\n\n/**\n * Coerce raw parsed JSON into the internal shape. Callers usually run\n * the Zod config schema first; the defensive reads keep the standalone\n * API from crashing on malformed input (it reports rule violations on\n * whatever is readable instead).\n */\nfunction readFlow(flow: object): FlowDef {\n const raw = flow as Record<string, unknown>;\n\n const purposes = new Map<string, string>();\n if (isPlainObject(raw.purposes)) {\n for (const [purpose, entry] of Object.entries(raw.purposes)) {\n purposes.set(purpose, str(entry));\n }\n }\n\n const steps: FlowStep[] = [];\n if (Array.isArray(raw.steps)) {\n for (const value of raw.steps) {\n if (!isPlainObject(value)) continue;\n const transitions = new Map<\n string,\n { target: string; action: string | null; purpose: string | null }\n >();\n if (isPlainObject(value.transitions)) {\n for (const [key, t] of Object.entries(value.transitions)) {\n const entry = isPlainObject(t) ? t : {};\n transitions.set(key, {\n target: str(entry.target),\n action: typeof entry.action === \"string\" ? entry.action : null,\n purpose: typeof entry.purpose === \"string\" ? entry.purpose : null,\n });\n }\n }\n steps.push({\n name: str(value.name),\n fields: Array.isArray(value.fields) ? value.fields.map(str) : [],\n actions: Array.isArray(value.actions)\n ? value.actions\n .filter(isPlainObject)\n .map((a) => ({ name: str(a.name), kind: str(a.kind) }))\n : [],\n gateCount: isPlainObject(value.gates) ? Object.keys(value.gates).length : 0,\n ssoProviderCount: Array.isArray(value.sso_providers) ? value.sso_providers.length : 0,\n transitions,\n onSuccess: typeof value.on_success === \"string\" ? value.on_success : undefined,\n terminal: value.complete !== undefined && value.complete !== null,\n });\n }\n }\n\n return { purposes, steps };\n}\n\n/** Go `%q` — double-quoted string. */\nfunction q(value: string): string {\n return JSON.stringify(value);\n}\n\nfunction error(rule: FlowValidationRuleId, message: string, step?: string): FlowValidationIssue {\n return step === undefined\n ? { severity: \"error\", rule, message }\n : { severity: \"error\", rule, message, step };\n}\n\n// ── phase 3: per-step structure (validateSteps + uniqueNonEmptyFields) ──────\n\nfunction validateStep(step: FlowStep): FlowValidationIssue[] {\n const issues: FlowValidationIssue[] = [];\n const name = step.name;\n\n if (step.terminal) {\n if (\n step.fields.length > 0 ||\n step.actions.length > 0 ||\n step.transitions.size > 0 ||\n step.gateCount > 0 ||\n step.ssoProviderCount > 0\n ) {\n issues.push(\n error(\n \"steps\",\n `step ${q(name)} is terminal (complete is set) but has fields, actions, transitions, gates, or sso_providers`,\n name,\n ),\n );\n }\n return issues;\n }\n\n const seenFields = new Set<string>();\n for (const field of step.fields) {\n if (field === \"\") {\n issues.push(error(\"steps\", `step ${q(name)}: field entry is empty`, name));\n continue;\n }\n if (seenFields.has(field)) {\n issues.push(error(\"steps\", `step ${q(name)}: duplicate field ${q(field)}`, name));\n }\n seenFields.add(field);\n }\n\n const actionNames = new Set<string>();\n for (const action of step.actions) {\n if (action.name === \"\") {\n issues.push(error(\"steps\", `step ${q(name)}: action has empty name`, name));\n continue;\n }\n if (actionNames.has(action.name)) {\n issues.push(error(\"steps\", `step ${q(name)}: duplicate action ${q(action.name)}`, name));\n continue;\n }\n if (\n action.kind === \"\" ||\n (action.kind !== \"back\" && !DECLARABLE_ACTION_KINDS.has(action.kind))\n ) {\n issues.push(error(\"steps\", `step ${q(name)}: action ${q(action.name)} has no kind`, name));\n } else if (action.kind === \"back\") {\n issues.push(\n error(\n \"steps\",\n `step ${q(name)}: action ${q(action.name)} has kind=back, which is engine-injected and cannot be declared`,\n name,\n ),\n );\n }\n const reserved = RESERVED_ACTION_NAMES.get(action.name);\n if (reserved !== undefined) {\n issues.push(\n error(\n \"steps\",\n `step ${q(name)}: action name ${q(action.name)} is reserved for ${reserved}`,\n name,\n ),\n );\n }\n actionNames.add(action.name);\n }\n\n const hasSsoAuthenticated = step.transitions.has(\"sso_authenticated\");\n if (\n step.fields.length === 0 &&\n step.actions.length === 0 &&\n step.ssoProviderCount === 0 &&\n step.gateCount === 0 &&\n !hasSsoAuthenticated\n ) {\n issues.push(\n error(\n \"steps\",\n `step ${q(name)} is non-terminal but has no fields, actions, sso_providers, gates, or transitions.sso_authenticated`,\n name,\n ),\n );\n }\n\n if (step.ssoProviderCount > 0 && !hasSsoAuthenticated) {\n issues.push(\n error(\n \"steps\",\n `step ${q(name)}: has sso_providers but is missing transitions.sso_authenticated`,\n name,\n ),\n );\n }\n\n for (const actionName of actionNames) {\n if (!step.transitions.has(actionName)) {\n issues.push(\n error(\"steps\", `step ${q(name)}: action ${q(actionName)} has no matching transition`, name),\n );\n }\n }\n\n for (const [transitionKey, t] of step.transitions) {\n if (\n !actionNames.has(transitionKey) &&\n !(RESERVED_OUTCOMES as readonly string[]).includes(transitionKey)\n ) {\n issues.push(\n error(\n \"steps\",\n `step ${q(name)}: transition key ${q(transitionKey)} is not an action name or reserved outcome (user_not_found, user_already_exists, sso_user_not_found, sso_authenticated)`,\n name,\n ),\n );\n }\n // The sso outcomes carry a bound user or parked claims, which a\n // re-purpose would drop and another flow cannot receive.\n const isSsoOutcome =\n transitionKey === \"sso_authenticated\" || transitionKey === \"sso_user_not_found\";\n if (isSsoOutcome && (t.purpose !== null || t.action !== null)) {\n issues.push(\n error(\n \"steps\",\n `step ${q(name)}: transition ${q(transitionKey)} is an sso outcome and cannot declare purpose or action`,\n name,\n ),\n );\n }\n }\n\n return issues;\n}\n\n// ── phase 4: graph / cycles / flip table ─────────────────────────────────────\n\n/** A transition with no cross-flow action targets a step in this flow. */\nfunction isCurrentFlow(t: { action: string | null }): boolean {\n return t.action === null;\n}\n\n/** Adjacency over local (current-flow) transitions: step → target steps. */\nfunction localAdjacency(def: FlowDef): Map<string, string[]> {\n const adj = new Map<string, string[]>();\n for (const step of def.steps) {\n for (const t of step.transitions.values()) {\n if (isCurrentFlow(t)) {\n const targets = adj.get(step.name) ?? [];\n targets.push(t.target);\n adj.set(step.name, targets);\n }\n }\n }\n return adj;\n}\n\n/** Reverse adjacency over local transitions: target step → source steps. */\nfunction reverseAdjacency(def: FlowDef): Map<string, string[]> {\n const reverse = new Map<string, string[]>();\n for (const step of def.steps) {\n for (const t of step.transitions.values()) {\n if (isCurrentFlow(t)) {\n const sources = reverse.get(t.target) ?? [];\n sources.push(step.name);\n reverse.set(t.target, sources);\n }\n }\n }\n return reverse;\n}\n\n/** BFS from `start` over `adj`, accumulating into `visited`. */\nfunction bfs(start: string, adj: Map<string, string[]>, visited: Set<string>): void {\n const queue = [start];\n for (let head = 0; head < queue.length; head += 1) {\n const current = queue[head];\n if (current === undefined || visited.has(current)) continue;\n visited.add(current);\n queue.push(...(adj.get(current) ?? []));\n }\n}\n\nfunction validateGraph(def: FlowDef, stepNames: Set<string>): FlowValidationIssue[] {\n const issues: FlowValidationIssue[] = [];\n\n for (const step of def.steps) {\n for (const [key, t] of step.transitions) {\n if (t.purpose !== null) {\n // Local re-purposing: never combined with a cross-flow action,\n // only to a purpose this definition serves, and only to that\n // purpose's entry step. Mirrors the server-side validator.\n if (t.action !== null) {\n issues.push(\n error(\n \"graph\",\n `step ${q(step.name)}: transition ${q(key)} declares both purpose and action; a transition either re-purposes locally or targets another flow`,\n step.name,\n ),\n );\n } else if (!(FLOW_PURPOSES as readonly string[]).includes(t.purpose)) {\n // Verbatim-identical to the Go validator's message (which cannot\n // print the raw value — its parse layer rejects it earlier).\n issues.push(\n error(\n \"graph\",\n `step ${q(step.name)}: transition ${q(key)} has invalid purpose`,\n step.name,\n ),\n );\n } else {\n const entry = def.purposes.get(t.purpose);\n if (entry === undefined) {\n issues.push(\n error(\n \"graph\",\n `step ${q(step.name)}: transition ${q(key)} re-purposes to ${q(t.purpose)}, which this definition does not serve`,\n step.name,\n ),\n );\n } else if (t.target !== entry) {\n issues.push(\n error(\n \"graph\",\n `step ${q(step.name)}: transition ${q(key)} re-purposes to ${q(t.purpose)} but targets ${q(t.target)}; it must target that purpose's entry step ${q(entry)}`,\n step.name,\n ),\n );\n }\n }\n }\n if (isCurrentFlow(t)) {\n if (!stepNames.has(t.target)) {\n issues.push(\n error(\n \"graph\",\n `step ${q(step.name)}: transition ${q(key)} targets unknown step ${q(t.target)}`,\n step.name,\n ),\n );\n }\n } else if (t.action !== \"switch\" && t.action !== \"pivot\") {\n issues.push(\n error(\n \"graph\",\n `step ${q(step.name)}: transition ${q(key)} has invalid action ${q(t.action ?? \"\")}`,\n step.name,\n ),\n );\n }\n }\n }\n\n for (const step of def.steps) {\n if (!step.terminal && step.transitions.size === 0) {\n issues.push(\n error(\n \"graph\",\n `step ${q(step.name)} is non-terminal but has no outgoing transitions`,\n step.name,\n ),\n );\n }\n }\n\n const reachable = new Set<string>();\n const adj = localAdjacency(def);\n for (const entryStep of def.purposes.values()) {\n bfs(entryStep, adj, reachable);\n }\n for (const step of def.steps) {\n if (!reachable.has(step.name)) {\n issues.push(\n error(\"graph\", `step ${q(step.name)} is unreachable from any entry point`, step.name),\n );\n }\n }\n\n return issues;\n}\n\nfunction validateCycles(def: FlowDef): FlowValidationIssue[] {\n const issues: FlowValidationIssue[] = [];\n const reverse = reverseAdjacency(def);\n\n // Seed: escape nodes (terminal, or a switch/pivot out); BFS backwards.\n const safe = new Set<string>();\n const queue: string[] = [];\n for (const step of def.steps) {\n const escapes = step.terminal || [...step.transitions.values()].some((t) => !isCurrentFlow(t));\n if (escapes) {\n safe.add(step.name);\n queue.push(step.name);\n }\n }\n for (let head = 0; head < queue.length; head += 1) {\n const current = queue[head];\n if (current === undefined) continue;\n for (const pred of reverse.get(current) ?? []) {\n if (!safe.has(pred)) {\n safe.add(pred);\n queue.push(pred);\n }\n }\n }\n\n for (const step of def.steps) {\n if (!step.terminal && !safe.has(step.name)) {\n issues.push(\n error(\n \"cycles\",\n `step ${q(step.name)} is trapped: no path to a terminal step or another flow`,\n step.name,\n ),\n );\n }\n }\n return issues;\n}\n\nfunction validateFlipTableCoverage(def: FlowDef): FlowValidationIssue[] {\n const issues: FlowValidationIssue[] = [];\n for (const [purpose, entryStepName] of def.purposes) {\n const flipTargets = PURPOSE_FLIP_TARGETS[purpose];\n if (flipTargets === undefined) continue;\n const entry = def.steps.find((s) => s.name === entryStepName);\n if (entry === undefined) continue;\n for (const [outcome, targetPurpose] of Object.entries(flipTargets)) {\n if (!def.purposes.has(targetPurpose)) continue;\n if (!entry.transitions.has(outcome)) {\n issues.push(\n error(\n \"flip-table\",\n `step ${q(entry.name)}: entry step for purpose ${q(purpose)} must wire ${q(outcome)} transition because ${q(targetPurpose)} is also a purpose: without it, ${FLIP_OUTCOME_IMPACTS[outcome]}`,\n entry.name,\n ),\n );\n }\n }\n }\n return issues;\n}\n\n// ── phase 5: schema-dependent rules ──────────────────────────────────────────\n\nfunction schemaProperties(schema: Record<string, unknown>): Record<string, unknown> | undefined {\n return isPlainObject(schema.properties) ? schema.properties : undefined;\n}\n\nfunction schemaRequired(schema: Record<string, unknown>): string[] {\n return Array.isArray(schema.required) ? schema.required.map(str).filter((n) => n !== \"\") : [];\n}\n\n/**\n * Mirrors `schemaReader.RequiredPaths`: every name in `required`,\n * recursed through the nested `required` of each required object. A\n * required object declaring no `required` of its own ends the descent at\n * the object itself.\n *\n * An optional object contributes its own `required` too once it appears\n * in `materialized`. It only exists in the document because something\n * beneath it was collected, and from that point document validation\n * enforces the rest of its `required` list.\n */\nfunction requiredPaths(\n schema: Record<string, unknown>,\n materialized: ReadonlySet<string>,\n prefix = \"\",\n): string[] {\n const properties = schemaProperties(schema);\n const required = schemaRequired(schema);\n // A set, like the Go `map[string]struct{}`, so a name repeated in\n // `required` yields the path once.\n const out = new Set<string>();\n\n for (const name of required) {\n const path = prefix === \"\" ? name : `${prefix}.${name}`;\n const property =\n properties !== undefined && Object.hasOwn(properties, name) ? properties[name] : undefined;\n if (!isPlainObject(property) || schemaRequired(property).length === 0) {\n out.add(path);\n continue;\n }\n for (const nested of requiredPaths(property, materialized, path)) out.add(nested);\n }\n\n // An optional object is invisible to the loop above, so descend into\n // the ones a collected field materializes.\n for (const [name, property] of Object.entries(properties ?? {})) {\n if (required.includes(name) || !isPlainObject(property)) continue;\n const path = prefix === \"\" ? name : `${prefix}.${name}`;\n if (!materialized.has(path)) continue;\n for (const nested of requiredPaths(property, materialized, path)) out.add(nested);\n }\n\n return [...out];\n}\n\n/** Mirrors `xAuthMethodsReader.IsEnabled` in flow_field_resolver_schema.go. */\nfunction authMethodEnabled(schema: Record<string, unknown>, method: string): boolean {\n if (!isPlainObject(schema[\"x-auth-methods\"])) return false;\n const entry = schema[\"x-auth-methods\"][method];\n return isPlainObject(entry) && entry.enabled === true;\n}\n\n/**\n * Resolve one step field to its challenge, mirroring the subset of\n * `SchemaFieldResolver.Resolve` the validator needs. Returns either the\n * challenge (or null for none) or the Go-verbatim resolution error.\n */\nfunction resolveFieldChallenge(\n field: string,\n properties: Record<string, unknown>,\n schema: Record<string, unknown>,\n): { challenge: FieldChallenge | null } | { message: string } {\n if (field.startsWith(AUTH_METHOD_PREFIX)) {\n const method = field.slice(AUTH_METHOD_PREFIX.length);\n // Mirrors deriveAuthMethodType: only password is field-shaped today.\n if (method !== \"password\") {\n return { message: `unknown field type for ${q(field)}` };\n }\n if (!authMethodEnabled(schema, method)) {\n // Surfaced by resolveAllStepFields' enabled-method cross-check.\n return { message: `${q(method)} is not an enabled authentication method` };\n }\n return { challenge: \"password\" };\n }\n\n // Mirrors walkUserProperty: a nested property is addressed by its\n // dotted path, descending one `properties` level per segment.\n const segments = field.split(\".\");\n let property: unknown;\n let level: Record<string, unknown> | undefined = properties;\n for (const [i, segment] of segments.entries()) {\n // Own properties only: Go indexes a map, where an inherited name like\n // `toString` is simply absent.\n property = level !== undefined && Object.hasOwn(level, segment) ? level[segment] : undefined;\n if (property === undefined) {\n return { message: `flow field: not a property in the user schema: ${q(field)}` };\n }\n const nested = isPlainObject(property) ? schemaProperties(property) : undefined;\n if (i < segments.length - 1 && nested === undefined) {\n return { message: `flow field: not a property in the user schema: ${q(field)}` };\n }\n level = nested;\n }\n\n if (!isPlainObject(property)) {\n return { challenge: null };\n }\n\n // Mirrors schemaReader.JSONType: the nullable idiom `[\"null\", X]`\n // reduces to X; any other multi-entry union is unsupported. Go reduces\n // before it tests for a composite, so the union error wins and the\n // object/array test below reads the reduced type rather than the union.\n let jsonType = property.type;\n if (Array.isArray(jsonType)) {\n const nonNull = jsonType.filter((t) => t !== \"null\");\n if (nonNull.length > 1) {\n return { message: `flow field: unsupported JSON type: [${jsonType.map(str).join(\" \")}]` };\n }\n // Undefined when every entry was \"null\", matching Go's empty-string case.\n jsonType = nonNull[0];\n }\n\n // Mirrors deriveUserPropertyType: an object or array has no\n // field-shaped input. A property carrying `properties` is an object,\n // and one carrying `items` is an array, even when it omits the `type`\n // keyword.\n if (\n jsonType === \"object\" ||\n jsonType === \"array\" ||\n schemaProperties(property) !== undefined ||\n \"items\" in property\n ) {\n return {\n message: `flow field: not a scalar property, name a nested leaf instead: ${q(field)}`,\n };\n }\n\n // Mirrors deriveIdentifierChallenge: the field naming the schema-root\n // `x-identifier` designation is the identifier (ADR 057); other\n // properties — unique or not — carry no identifier challenge.\n const identifier = schema[\"x-identifier\"];\n if (typeof identifier === \"string\" && identifier !== \"\" && field === identifier) {\n return { challenge: \"identifier\" };\n }\n return { challenge: null };\n}\n\nfunction validateAgainstSchema(def: FlowDef, schema: object): FlowValidationIssue[] {\n const issues: FlowValidationIssue[] = [];\n const raw = schema as Record<string, unknown>;\n\n const properties = schemaProperties(raw);\n if (properties === undefined) {\n issues.push(error(\"schema/fields-resolve\", \"user schema has no properties\"));\n return issues;\n }\n\n // Required-fields coverage (validateRequiredUserSchemaFields).\n // Collecting `address.street` covers the leaf and every object above\n // it, since an object materializes once one of its children is\n // collected. The same prefixes are what the schema treats as\n // materialized, so an optional object's own `required` list comes into\n // force here too.\n const covered = new Set<string>();\n for (const step of def.steps) {\n for (const field of step.fields) {\n let path = \"\";\n for (const node of field.split(\".\")) {\n path = path === \"\" ? node : `${path}.${node}`;\n covered.add(path);\n }\n }\n }\n const missing = requiredPaths(raw, covered)\n .filter((field) => !covered.has(field))\n .sort();\n if (missing.length > 0) {\n issues.push(\n error(\n \"schema/required-fields\",\n `required fields [${missing.join(\" \")}] in user schema are missing in the flow definition steps`,\n ),\n );\n }\n\n // Field resolution (resolveAllStepFields), collecting challenges for\n // the manifest and warning rules below.\n const challengesByStep = new Map<string, Set<FieldChallenge>>();\n let resolutionFailed = false;\n for (const step of def.steps) {\n const challenges = new Set<FieldChallenge>();\n for (const field of step.fields) {\n const resolved = resolveFieldChallenge(field, properties, raw);\n if (\"message\" in resolved) {\n issues.push(\n error(\"schema/fields-resolve\", `step ${q(step.name)}: ${resolved.message}`, step.name),\n );\n resolutionFailed = true;\n continue;\n }\n if (resolved.challenge !== null) {\n challenges.add(resolved.challenge);\n }\n }\n challengesByStep.set(step.name, challenges);\n }\n\n // Passkey enablement (validatePasskeyActionsEnabled): passkey is\n // action-shaped, not field-shaped, so the per-field enabled-method\n // check above never sees it.\n if (!authMethodEnabled(raw, \"passkey\")) {\n for (const step of def.steps) {\n for (const action of step.actions) {\n if (action.kind === \"passkey\" || action.kind === \"passkey_register\") {\n issues.push(\n error(\n \"schema/passkey-actions\",\n `step ${q(step.name)}: action ${q(action.name)} offers passkey but \"passkey\" is not an enabled authentication method`,\n step.name,\n ),\n );\n }\n }\n }\n }\n\n if (resolutionFailed) {\n return issues;\n }\n\n // on_success manifests (validateOnSuccessManifests): every kind the\n // mutation needs must be collected on the step or upstream.\n const reverse = reverseAdjacency(def);\n for (const step of def.steps) {\n if (step.onSuccess === undefined) continue;\n const manifest = ON_SUCCESS_MANIFESTS[step.onSuccess];\n if (manifest === undefined) continue;\n const upstream = new Set<string>();\n bfs(step.name, reverse, upstream);\n for (const kind of manifest) {\n const established = [...upstream].some((name) => challengesByStep.get(name)?.has(kind));\n if (!established) {\n issues.push(\n error(\n \"schema/on-success-manifest\",\n `step ${q(step.name)}: on_success ${step.onSuccess} requires ${q(kind)} to be collected upstream`,\n step.name,\n ),\n );\n }\n }\n }\n\n // CLI-only guidance (no Go counterpart): a password collected on the\n // login path with no identifier upstream leaves the engine unable to\n // resolve which user to challenge. Path-sensitive: walk forward from\n // the login entry and stop at any step that collects an identifier\n // (every route through it is covered), so an identifier on a\n // *different* route — e.g. only on the register-purpose chain into a\n // shared step — cannot mask a login route that never collects one.\n const loginEntry = def.purposes.get(\"login\");\n if (loginEntry !== undefined) {\n const adj = localAdjacency(def);\n const identifierFree = new Set<string>();\n const queue = [loginEntry];\n for (let head = 0; head < queue.length; head += 1) {\n const name = queue[head];\n if (name === undefined || identifierFree.has(name)) continue;\n if (challengesByStep.get(name)?.has(\"identifier\")) continue;\n identifierFree.add(name);\n queue.push(...(adj.get(name) ?? []));\n }\n for (const step of def.steps) {\n if (!identifierFree.has(step.name)) continue;\n if (!challengesByStep.get(step.name)?.has(\"password\")) continue;\n issues.push({\n severity: \"warning\",\n rule: \"warn/password-without-identifier\",\n message: `step ${q(step.name)} collects ${AUTH_METHOD_PREFIX}password on the login path but no upstream step collects an identifier field — the engine cannot resolve which user to challenge`,\n step: step.name,\n });\n }\n }\n\n return issues;\n}\n"],"mappings":"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"}
@@ -199,7 +199,7 @@
199
199
  "transitions": {
200
200
  "not": {
201
201
  "required": [
202
- "callback"
202
+ "sso_authenticated"
203
203
  ]
204
204
  }
205
205
  }
@@ -286,11 +286,84 @@
286
286
  "transitions": {
287
287
  "not": {
288
288
  "required": [
289
- "callback"
289
+ "sso_authenticated"
290
290
  ]
291
291
  }
292
292
  }
293
293
  }
294
+ },
295
+ {
296
+ "required": [
297
+ "transitions"
298
+ ],
299
+ "properties": {
300
+ "transitions": {
301
+ "anyOf": [
302
+ {
303
+ "required": [
304
+ "sso_authenticated"
305
+ ],
306
+ "properties": {
307
+ "sso_authenticated": {
308
+ "anyOf": [
309
+ {
310
+ "required": [
311
+ "purpose"
312
+ ],
313
+ "properties": {
314
+ "purpose": {
315
+ "type": "string"
316
+ }
317
+ }
318
+ },
319
+ {
320
+ "required": [
321
+ "action"
322
+ ],
323
+ "properties": {
324
+ "action": {
325
+ "type": "string"
326
+ }
327
+ }
328
+ }
329
+ ]
330
+ }
331
+ }
332
+ },
333
+ {
334
+ "required": [
335
+ "sso_user_not_found"
336
+ ],
337
+ "properties": {
338
+ "sso_user_not_found": {
339
+ "anyOf": [
340
+ {
341
+ "required": [
342
+ "purpose"
343
+ ],
344
+ "properties": {
345
+ "purpose": {
346
+ "type": "string"
347
+ }
348
+ }
349
+ },
350
+ {
351
+ "required": [
352
+ "action"
353
+ ],
354
+ "properties": {
355
+ "action": {
356
+ "type": "string"
357
+ }
358
+ }
359
+ }
360
+ ]
361
+ }
362
+ }
363
+ }
364
+ ]
365
+ }
366
+ }
294
367
  }
295
368
  ]
296
369
  },
@@ -358,8 +431,9 @@
358
431
  },
359
432
  "sso_providers": {
360
433
  "type": "array",
361
- "description": "Slugs of the identity provider connections this step offers, in display\norder. Each names the `slug` of a connection under `.zitadel/idps/`; the\nconnection carries the display name and template, so a rename there\nreaches every step without editing the flow. The rendered step the client\nreceives carries the resolved `{id, name, template}` objects instead.\n",
434
+ "description": "Slugs of the identity provider connections this step offers, in display\norder. Each names the `slug` of a connection under `.zitadel/idps/`; the\nconnection carries the display name and template, so a rename there\nreaches every step without editing the flow. The rendered step the client\nreceives carries the resolved `{id, name, template}` objects instead. A\nstep offers at most 20 providers.\n",
362
435
  "uniqueItems": true,
436
+ "maxItems": 20,
363
437
  "items": {
364
438
  "type": "string",
365
439
  "pattern": "^[a-z0-9][a-z0-9_-]*$",
@@ -375,9 +449,10 @@
375
449
  "on_success": {
376
450
  "type": "string",
377
451
  "enum": [
378
- "create_user"
452
+ "create_user",
453
+ "create_user_with_sso"
379
454
  ],
380
- "description": "Server-side mutation to execute when this step completes successfully.\nRuns after field validation passes, before the transition fires.\n\n- create_user: creates the user record (registration flows)\n",
455
+ "description": "Server-side mutation to execute when this step completes successfully.\nRuns after field validation passes, before the transition fires.\n\n- create_user: creates the user record (registration flows)\n- create_user_with_sso: creates the user record from the identity an\n external provider returned, linking it to that identity. Accepted by\n the API so an SSO flow can be authored and stored; the engine handler\n is not wired yet, and a step reaching it fails with a flow integrity\n error rather than creating anything.\n",
381
456
  "examples": [
382
457
  "create_user"
383
458
  ]
@@ -395,7 +470,7 @@
395
470
  },
396
471
  "transitions": {
397
472
  "type": "object",
398
- "description": "Maps action/outcome names to their transition descriptor.\n\nKeys match action names from the `actions` array. Additional keys\ncome from implicit outcomes based on schema annotations\n(e.g. `user_not_found` from `x-unique` fields) and engine\nevents (e.g. `sso`, `callback`).\n",
473
+ "description": "Maps action/outcome names to their transition descriptor.\n\nKeys match action names from the `actions` array. Additional keys\ncome from implicit outcomes based on schema annotations\n(e.g. `user_not_found` from `x-unique` fields) and engine\nevents (e.g. `sso_authenticated`, `sso_user_not_found`).\n",
399
474
  "additionalProperties": {
400
475
  "type": "object",
401
476
  "required": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zitadel/config",
3
- "version": "1.0.0-alpha.24",
3
+ "version": "1.0.0-alpha.25",
4
4
  "description": "Versioned Zitadel local config schemas and defaults",
5
5
  "homepage": "https://github.com/zitadel/nextgen/tree/main/packages/config#readme",
6
6
  "bugs": {
@@ -33,6 +33,11 @@
33
33
  "types": "./dist/defaults.d.mts",
34
34
  "import": "./dist/defaults.mjs"
35
35
  },
36
+ "./idp": {
37
+ "@zitadel/source": "./src/idp/index.ts",
38
+ "types": "./dist/idp/index.d.mts",
39
+ "import": "./dist/idp/index.mjs"
40
+ },
36
41
  "./schemas": {
37
42
  "@zitadel/source": "./src/schemas.ts",
38
43
  "types": "./dist/schemas.d.mts",
@@ -84,13 +89,15 @@
84
89
  "culori": "^4.0.2",
85
90
  "liquidjs": "^10.27.2",
86
91
  "zod": "^4.3.6",
87
- "@zitadel/api": "1.0.0-alpha.24"
92
+ "@zitadel/api": "1.0.0-alpha.25"
88
93
  },
89
94
  "devDependencies": {
90
95
  "@types/culori": "^4.0.1",
96
+ "@types/node": "^25.6.0",
91
97
  "ajv": "^8.20.0",
92
- "tsdown": "^0.21.10",
98
+ "tsdown": "^0.23.0",
93
99
  "tslib": "^2.8.1",
100
+ "typescript": "^6.0.3",
94
101
  "vitest": "^4.1.11"
95
102
  },
96
103
  "scripts": {
@@ -98,10 +105,9 @@
98
105
  "prebuild": "pnpm run sync-schemas",
99
106
  "build": "tsdown",
100
107
  "pretypecheck": "pnpm run sync-schemas",
101
- "typecheck": "tsc --build tsconfig.json",
108
+ "typecheck": "tsc --noEmit -p tsconfig.lib.json && tsc --noEmit -p tsconfig.spec.json",
102
109
  "pretest": "pnpm run sync-schemas",
103
110
  "test": "vitest run",
104
- "test:all": "pnpm run test",
105
- "lint": "oxlint ."
111
+ "test:all": "pnpm run test"
106
112
  }
107
113
  }
@@ -1 +0,0 @@
1
- {"version":3,"file":"defaults-BnRHT7RQ.mjs","names":["defaultHumanUserSchemaTemplate","defaultLoginFlowTemplate","passkeyFirstHumanUserSchemaTemplate","passkeyFirstLoginFlowTemplate"],"sources":["../defaults/default-human-user.json","../defaults/default-login.json","../defaults/presets/passkey-first/human-user.json","../defaults/presets/passkey-first/login.json","../src/readmes.ts","../src/defaults.ts"],"sourcesContent":["","","","","import { readFileSync } from \"node:fs\";\nimport { fileURLToPath } from \"node:url\";\n\n/**\n * README content the CLI copies to `.zitadel/schemas/README.md` during setup.\n * Source of truth is `packages/config/defaults/README-schemas.md`; the Go\n * server-side embed reads the same file (see `defaults.go`).\n */\nexport function schemasReadmeContent(): string {\n return readFileSync(readmeUrl(\"README-schemas.md\"), \"utf8\");\n}\n\n/**\n * README content the CLI copies to `.zitadel/flows/README.md` during setup.\n * Source of truth is `packages/config/defaults/README-flows.md`.\n */\nexport function flowsReadmeContent(): string {\n return readFileSync(readmeUrl(\"README-flows.md\"), \"utf8\");\n}\n\n/**\n * README content the CLI copies to `.zitadel/branding/README.md` when a\n * branding design is ejected. Source of truth is\n * `packages/config/defaults/README-branding.md`.\n */\nexport function brandingReadmeContent(): string {\n return readFileSync(readmeUrl(\"README-branding.md\"), \"utf8\");\n}\n\nfunction readmeUrl(filename: string): string {\n return fileURLToPath(new URL(`../defaults/${filename}`, import.meta.url));\n}\n","import { readFileSync } from \"node:fs\";\nimport { fileURLToPath } from \"node:url\";\n\nimport type {\n CreateFlowDefinitionBodyFlowDefinition,\n CreateSchemaBody,\n} from \"@zitadel/api/generated/model\";\n\nimport defaultHumanUserSchemaTemplate from \"../defaults/default-human-user.json\" with {\n type: \"json\",\n};\nimport defaultLoginFlowTemplate from \"../defaults/default-login.json\" with {\n type: \"json\",\n};\nimport passkeyFirstHumanUserSchemaTemplate from \"../defaults/presets/passkey-first/human-user.json\" with {\n type: \"json\",\n};\nimport passkeyFirstLoginFlowTemplate from \"../defaults/presets/passkey-first/login.json\" with {\n type: \"json\",\n};\n\nexport { brandingReadmeContent, flowsReadmeContent, schemasReadmeContent } from \"./readmes.js\";\n\nexport const DEFAULT_BUILTIN_SCHEMA_BASE = \"https://nextgen.com/api/schemas\";\nexport const DEFAULT_FLOW_SCHEMA_URI = \"https://nextgen.com/flow-definition.json\";\nexport const DEFAULT_SCHEMA_CONFIG_PATH = \".zitadel/schemas/default-human-user.json\";\nexport const DEFAULT_FLOW_CONFIG_PATH = \".zitadel/flows/default-login.json\";\nexport const DEFAULT_BRANDING_CONFIG_PATH = \".zitadel/branding/branding.json\";\nexport const DEFAULT_BRANDING_TEMPLATE_PATH = \".zitadel/branding/login.liquid\";\n\n/**\n * Named schema+flow bundles `zitadel setup` can scaffold (#448: the prompt\n * fires before any `.zitadel/` file is written; each preset maps to a\n * pre-defined bundle the CLI copies on first setup). `password-first` is\n * today's default; `passkey-first` puts a passkey ceremony on the login\n * entry step with an email→password fallback path.\n */\nexport const SETUP_PRESETS = [\"password-first\", \"passkey-first\"] as const;\n\nexport type SetupPreset = (typeof SETUP_PRESETS)[number];\n\nexport const DEFAULT_SETUP_PRESET: SetupPreset = \"password-first\";\n\nconst PRESET_TEMPLATES: Record<SetupPreset, { schema: unknown; flow: unknown }> = {\n \"password-first\": {\n schema: defaultHumanUserSchemaTemplate,\n flow: defaultLoginFlowTemplate,\n },\n \"passkey-first\": {\n schema: passkeyFirstHumanUserSchemaTemplate,\n flow: passkeyFirstLoginFlowTemplate,\n },\n};\n\nfunction presetTemplates(preset: string): { schema: unknown; flow: unknown } {\n // Own-property check: indexing a plain object with an arbitrary string\n // would let \"__proto__\"/\"constructor\" resolve via the prototype chain\n // and bypass the unknown-preset error.\n if (!Object.hasOwn(PRESET_TEMPLATES, preset)) {\n throw new Error(\n `unknown setup preset ${JSON.stringify(preset)} (known presets: ${SETUP_PRESETS.join(\", \")})`,\n );\n }\n return PRESET_TEMPLATES[preset as SetupPreset];\n}\n\n/**\n * Widget-template starting points `zitadel branding eject --design <name>`\n * scaffolds into `.zitadel/branding/` (setup never does, #1039). Only\n * widget structure belongs here: `centered` is the bundled default card and\n * `minimal` the same form without card chrome. Page layouts (the former\n * `split`, `split-right`, `hero`) were page chrome and live in the embedding\n * application instead; revisions already published from them keep rendering\n * (see docs/design/branding/customization-strategy.md). Add a design here\n * only when it changes the widget's structure, not the page around it.\n */\nexport const BRANDING_DESIGNS = [\"centered\", \"minimal\"] as const;\n\nexport type BrandingDesign = (typeof BRANDING_DESIGNS)[number];\n\nexport const DEFAULT_BRANDING_DESIGN: BrandingDesign = \"centered\";\n\n/**\n * Descriptor `layout` each design's `branding.json` carries. Both shipped\n * designs are widget structure and carry `centered`; the wire enum still has\n * `split` for revisions published from the retired page-layout designs\n * (#1039), which no catalog entry produces any more.\n */\nconst DESIGN_LAYOUTS: Record<BrandingDesign, \"centered\"> = {\n centered: \"centered\",\n minimal: \"centered\",\n};\n\nexport type DefaultBrandingConfig = {\n /** The `.zitadel/branding/branding.json` descriptor body (sans `$schema`). */\n branding: {\n layout: \"centered\";\n /** A `$file` reference to the sibling template; the CLI inlines it before publishing. */\n liquid_template: { $file: string };\n };\n /** The `.zitadel/branding/login.liquid` template content. */\n template: string;\n};\n\n/**\n * Renders the scaffold files for a branding design. The `centered` template\n * is a drift-tested copy of the bundled default in `@zitadel/components`;\n * `minimal` is an authored variant of it.\n */\nexport function getDefaultBrandingConfig(\n design: string = DEFAULT_BRANDING_DESIGN,\n): DefaultBrandingConfig {\n if (!Object.hasOwn(DESIGN_LAYOUTS, design)) {\n throw new Error(\n `unknown branding design ${JSON.stringify(design)} (known designs: ${BRANDING_DESIGNS.join(\", \")})`,\n );\n }\n const known = design as BrandingDesign;\n return {\n branding: {\n layout: DESIGN_LAYOUTS[known],\n liquid_template: { $file: \"./login.liquid\" },\n },\n template: readFileSync(packageFilePath(`../defaults/branding/${known}/login.liquid`), \"utf8\"),\n };\n}\n\n/**\n * Resolves a package-relative file to a real filesystem path. Vite-driven\n * runtimes (vitest in dependent packages) rewrite `import.meta.url` to a\n * `/@fs/...` URL, which `fileURLToPath` passes through verbatim — strip the\n * prefix so `readFileSync` gets an actual path.\n */\nfunction packageFilePath(relative: string): string {\n const path = fileURLToPath(new URL(relative, import.meta.url));\n return path.startsWith(\"/@fs/\") ? path.slice(\"/@fs\".length) : path;\n}\n\n/**\n * The second setup axis (#448): *who* signs in, orthogonal to the sign-in\n * preset (*how* they sign in). The sign-in preset owns the flow shape and\n * auth methods; the use case owns which profile fields the schema collects.\n * Composing the two keeps us at one field catalog + one flow-per-preset\n * instead of a bundle per (use case × preset) pair. `minimal` is the\n * default and never blocks non-interactive runs.\n */\nexport const SETUP_USE_CASES = [\"minimal\", \"consumer\", \"business\"] as const;\n\nexport type SetupUseCase = (typeof SETUP_USE_CASES)[number];\n\nexport const DEFAULT_SETUP_USE_CASE: SetupUseCase = \"minimal\";\n\n/**\n * Fields each use case collects, in register/display order. `email` is\n * always first and stays the only required property; the rest are optional\n * profile attributes gathered on the flow's register step. `givenName`/\n * `familyName` are the attributes the backend reads for a user's identity\n * (`internal/domain/user.go`); `companyName` is stored as a plain user\n * attribute today — there is no org/team model behind it yet.\n */\nconst USE_CASE_FIELDS: Record<SetupUseCase, readonly string[]> = {\n minimal: [\"email\"],\n consumer: [\"email\", \"givenName\", \"familyName\"],\n business: [\"email\", \"givenName\", \"familyName\", \"companyName\"],\n};\n\n/**\n * JSON-Schema bodies for the optional profile fields the CLI composes in per\n * use case. The shipped templates are an email-only baseline (shared verbatim\n * with the Go server fallback), so this catalog owns every field beyond\n * `email`. Authored in the templates' style — no `x-claim`, since the backend\n * maps identity by attribute name and the user-property meta-schema defines no\n * claim keyword. `givenName`/`familyName` are the attributes the backend reads\n * for a user's identity (`internal/domain/user.go`).\n */\nconst EXTRA_FIELD_BODIES: Record<string, Record<string, unknown>> = {\n givenName: {\n type: \"string\",\n maxLength: 50,\n description: \"The user's given (first) name.\",\n },\n familyName: {\n type: \"string\",\n maxLength: 50,\n description: \"The user's family (last) name.\",\n },\n companyName: {\n type: \"string\",\n maxLength: 200,\n description: \"The user's company name.\",\n },\n};\n\nfunction useCaseFields(useCase: string): readonly string[] {\n // Own-property check, same rationale as presetTemplates: keep prototype\n // keys (\"__proto__\" etc.) from resolving to a real field list.\n if (!Object.hasOwn(USE_CASE_FIELDS, useCase)) {\n throw new Error(\n `unknown setup use case ${JSON.stringify(useCase)} (known use cases: ${SETUP_USE_CASES.join(\", \")})`,\n );\n }\n return USE_CASE_FIELDS[useCase as SetupUseCase];\n}\n\n/**\n * The body for one use-case field: reuse the template's authored body when it\n * has one (so composed schemas match the shipped defaults for shared fields),\n * else the extra catalog, else a generic editable string so composition stays\n * total for any field a future use case introduces.\n */\nfunction fieldBody(\n field: string,\n templateProps: Record<string, Record<string, unknown>>,\n): Record<string, unknown> {\n if (Object.hasOwn(templateProps, field)) {\n return { ...templateProps[field] };\n }\n if (Object.hasOwn(EXTRA_FIELD_BODIES, field)) {\n return { ...EXTRA_FIELD_BODIES[field] };\n }\n return { type: \"string\", description: `The user's ${field}.` };\n}\n\n/**\n * Narrow a rendered schema to the chosen use case: keep the preset-owned\n * header and `x-auth-methods`, but replace `properties`/`required` with the\n * use case's field set. `email` is the sole required property.\n */\nfunction applyUseCaseToSchema(\n schema: Record<string, unknown>,\n useCase: string,\n): Record<string, unknown> {\n const templateProps = (schema.properties ?? {}) as Record<string, Record<string, unknown>>;\n const properties: Record<string, unknown> = {};\n for (const field of useCaseFields(useCase)) {\n properties[field] = fieldBody(field, templateProps);\n }\n return { ...schema, required: [\"email\"], properties };\n}\n\n/**\n * Derive the flow's register-step fields from the use case rather than\n * copying a hard-coded list into every bundle (#448): the register step\n * collects exactly what the schema defines. Other steps are untouched.\n */\nfunction applyUseCaseToFlow(\n flow: Record<string, unknown>,\n useCase: string,\n): Record<string, unknown> {\n const fields = [...useCaseFields(useCase)];\n const steps = (flow.steps as Array<Record<string, unknown>>).map((step) =>\n step.name === \"register\" ? { ...step, fields } : step,\n );\n return { ...flow, steps };\n}\n\nexport type DefaultConfigRenderOptions = {\n builtinSchemaBase?: string;\n userSchemaUrl?: string;\n /** Which flow/auth bundle to render; defaults to {@link DEFAULT_SETUP_PRESET}. */\n preset?: string;\n /** Which field set to collect; defaults to {@link DEFAULT_SETUP_USE_CASE}. */\n useCase?: string;\n};\n\nexport function defaultHumanUserSchemaUrl(\n builtinSchemaBase = DEFAULT_BUILTIN_SCHEMA_BASE,\n): string {\n return `${trimTrailingSlash(builtinSchemaBase)}/default-human-user.json`;\n}\n\nexport function getDefaultHumanUserSchema(\n options: DefaultConfigRenderOptions = {},\n): CreateSchemaBody {\n const builtinSchemaBase = trimTrailingSlash(\n options.builtinSchemaBase ?? DEFAULT_BUILTIN_SCHEMA_BASE,\n );\n const rendered = renderTemplate(presetTemplates(options.preset ?? DEFAULT_SETUP_PRESET).schema, {\n SERVER_URL: builtinSchemaBase,\n USER_SCHEMA_URL: options.userSchemaUrl ?? defaultHumanUserSchemaUrl(builtinSchemaBase),\n }) as Record<string, unknown>;\n return applyUseCaseToSchema(\n rendered,\n options.useCase ?? DEFAULT_SETUP_USE_CASE,\n ) as CreateSchemaBody;\n}\n\nexport function getDefaultLoginFlow(\n options: DefaultConfigRenderOptions = {},\n): CreateFlowDefinitionBodyFlowDefinition {\n const builtinSchemaBase = trimTrailingSlash(\n options.builtinSchemaBase ?? DEFAULT_BUILTIN_SCHEMA_BASE,\n );\n const rendered = renderTemplate(presetTemplates(options.preset ?? DEFAULT_SETUP_PRESET).flow, {\n SERVER_URL: builtinSchemaBase,\n USER_SCHEMA_URL: options.userSchemaUrl ?? defaultHumanUserSchemaUrl(builtinSchemaBase),\n }) as Record<string, unknown>;\n return applyUseCaseToFlow(\n rendered,\n options.useCase ?? DEFAULT_SETUP_USE_CASE,\n ) as CreateFlowDefinitionBodyFlowDefinition;\n}\n\nfunction renderTemplate(value: unknown, replacements: Record<string, string>): unknown {\n if (Array.isArray(value)) {\n return value.map((item) => renderTemplate(item, replacements));\n }\n if (value !== null && typeof value === \"object\") {\n return Object.fromEntries(\n Object.entries(value).map(([key, item]) => [key, renderTemplate(item, replacements)]),\n );\n }\n if (typeof value === \"string\") {\n return value.replaceAll(/\\$\\{([A-Z_]+)\\}/g, (match, name: string) => {\n return replacements[name] ?? match;\n });\n }\n return value;\n}\n\nfunction trimTrailingSlash(value: string): string {\n let trimmed = value;\n while (trimmed.endsWith(\"/\")) {\n trimmed = trimmed.slice(0, -1);\n }\n return trimmed;\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"defaults-BnWXllBk.d.mts","names":[],"sources":["../src/readmes.ts","../src/defaults.ts"],"mappings":";;;;;;AAQA;;iBAAgB,oBAAA,CAAA;;;AAQhB;;iBAAgB,kBAAA,CAAA;;;AAShB;;;iBAAgB,qBAAA,CAAA;;;cCFH,2BAAA;AAAA,cACA,uBAAA;AAAA,cACA,0BAAA;AAAA,cACA,wBAAA;AAAA,cACA,4BAAA;AAAA,cACA,8BAAA;ADZb;;;;;AASA;;AATA,cCqBa,aAAA;AAAA,KAED,WAAA,WAAsB,aAAA;AAAA,cAErB,oBAAA,EAAsB,WAAA;;;;AAlBnC;;;;;AACA;;cAoDa,gBAAA;AAAA,KAED,cAAA,WAAyB,gBAAA;AAAA,cAExB,uBAAA,EAAyB,cAAA;AAAA,KAa1B,qBAAA;EApE2B,8EAsErC,QAAA;IACE,MAAA,cAvEmC;IAyEnC,eAAA;MAAmB,KAAA;IAAA;EAAA,GAxEc;EA2EnC,QAAA;AAAA;;;;AAzEF;;iBAiFgB,wBAAA,CACd,MAAA,YACC,qBAAA;;;AA1EH;;;;;AAEA;cA2Ga,eAAA;AAAA,KAED,YAAA,WAAuB,eAAA;AAAA,cAEtB,sBAAA,EAAwB,YAAA;AAAA,KA0GzB,0BAAA;EACV,iBAAA;EACA,aAAA;EAEA,MAAA,WA3N+D;EA6N/D,OAAA;AAAA;AAAA,iBAGc,yBAAA,CACd,iBAAA;AAAA,iBAKc,yBAAA,CACd,OAAA,GAAS,0BAAA,GACR,gBAAA;AAAA,iBAca,mBAAA,CACd,OAAA,GAAS,0BAAA,GACR,sCAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"meta-schemas-K8f7YZYz.mjs","names":["flowDefinitionMetaSchema","userSchemaMetaSchema","userPropertyMetaSchema","propertyNameMetaSchema","authMethodsMetaSchema","authMethodMetaSchema","ssoAuthMethodMetaSchema","idpConnectionMetaSchema","brandingMetaSchema"],"sources":["../meta-schemas/auth-method.json","../meta-schemas/auth-methods.json","../meta-schemas/branding.json","../meta-schemas/flow-definition.json","../meta-schemas/idp-connection.json","../meta-schemas/property-name.json","../meta-schemas/sso-auth-method.json","../meta-schemas/user-property.json","../meta-schemas/user-schema.json","../src/meta-schemas.ts"],"sourcesContent":["","","","","","","","","","/**\n * The dialect meta-schemas `zitadel setup` copies into a project's\n * `.zitadel/meta/` so the flow/schema dialect is machine-readable offline:\n * editors validate flow files against `flow-definition.json` via a relative\n * `$schema` pointer, and agents read the dialect files before authoring\n * edits — no docs crawl, no hosted schema required.\n *\n * The files under `meta-schemas/` are generated: `scripts/sync-meta-schemas.mjs`\n * copies them from `api/openapi/endpoints/schemas/*.json` (the files the\n * server embeds) ahead of build, typecheck and test. Those are generated in\n * turn from the OpenAPI YAML by `scripts/generate-meta-schemas.ts` at the repo\n * root, so the YAML is the only thing to edit.\n */\nimport authMethodMetaSchema from \"../meta-schemas/auth-method.json\" with {\n type: \"json\",\n};\nimport authMethodsMetaSchema from \"../meta-schemas/auth-methods.json\" with {\n type: \"json\",\n};\nimport brandingMetaSchema from \"../meta-schemas/branding.json\" with {\n type: \"json\",\n};\nimport flowDefinitionMetaSchema from \"../meta-schemas/flow-definition.json\" with {\n type: \"json\",\n};\nimport idpConnectionMetaSchema from \"../meta-schemas/idp-connection.json\" with {\n type: \"json\",\n};\nimport propertyNameMetaSchema from \"../meta-schemas/property-name.json\" with {\n type: \"json\",\n};\nimport ssoAuthMethodMetaSchema from \"../meta-schemas/sso-auth-method.json\" with {\n type: \"json\",\n};\nimport userPropertyMetaSchema from \"../meta-schemas/user-property.json\" with {\n type: \"json\",\n};\nimport userSchemaMetaSchema from \"../meta-schemas/user-schema.json\" with {\n type: \"json\",\n};\n\n/** Project-relative directory setup writes the meta-schemas to. */\nexport const META_SCHEMA_DIR = \".zitadel/meta\";\n\n/**\n * The `$schema` value scaffolded flow files carry, relative to\n * `.zitadel/flows/` — resolves to `{@link META_SCHEMA_DIR}/flow-definition.json`.\n */\nexport const FLOW_FILE_SCHEMA_REF = \"../meta/flow-definition.json\";\n\n/**\n * The `$schema` value scaffolded branding files carry, relative to\n * `.zitadel/branding/` — resolves to `{@link META_SCHEMA_DIR}/branding.json`.\n */\nexport const BRANDING_FILE_SCHEMA_REF = \"../meta/branding.json\";\n\nexport type MetaSchemaFile = { name: string; body: object };\n\n/**\n * The dialect files to materialize, in write order. `auth-methods.json`,\n * `auth-method.json`, and `sso-auth-method.json` are pulled in by\n * `user-schema.json`'s relative `$ref`s — without them the copied dialect\n * cannot resolve offline.\n */\nexport function metaSchemaFiles(): ReadonlyArray<MetaSchemaFile> {\n return [\n { name: \"flow-definition.json\", body: flowDefinitionMetaSchema as object },\n { name: \"user-schema.json\", body: userSchemaMetaSchema as object },\n { name: \"user-property.json\", body: userPropertyMetaSchema as object },\n { name: \"property-name.json\", body: propertyNameMetaSchema as object },\n { name: \"auth-methods.json\", body: authMethodsMetaSchema as object },\n { name: \"auth-method.json\", body: authMethodMetaSchema as object },\n { name: \"sso-auth-method.json\", body: ssoAuthMethodMetaSchema as object },\n { name: \"idp-connection.json\", body: idpConnectionMetaSchema as object },\n { name: \"branding.json\", body: brandingMetaSchema as object },\n ];\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AS0CA,MAAa,kBAAkB;;;;;AAM/B,MAAa,uBAAuB;;;;;AAMpC,MAAa,2BAA2B;;;;;;;AAUxC,SAAgB,kBAAiD;AAC/D,QAAO;EACL;GAAE,MAAM;GAAwB,MAAMA;GAAoC;EAC1E;GAAE,MAAM;GAAoB,MAAMC;GAAgC;EAClE;GAAE,MAAM;GAAsB,MAAMC;GAAkC;EACtE;GAAE,MAAM;GAAsB,MAAMC;GAAkC;EACtE;GAAE,MAAM;GAAqB,MAAMC;GAAiC;EACpE;GAAE,MAAM;GAAoB,MAAMC;GAAgC;EAClE;GAAE,MAAM;GAAwB,MAAMC;GAAmC;EACzE;GAAE,MAAM;GAAuB,MAAMC;GAAmC;EACxE;GAAE,MAAM;GAAiB,MAAMC;GAA8B;EAC9D"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"schemas-7oNU25Qz.mjs","names":[],"sources":["../src/branding-css.ts","../src/schemas.ts"],"sourcesContent":["/**\n * Mirror of the server's appearance-value gates\n * (`internal/domain/branding_css.go`), so `plan` rejects what `apply` would.\n *\n * The widget writes these values straight into a CSS declaration, so a value\n * able to close its own declaration can emit rules of its own. Both gates are\n * allowlists: a value is accepted only when it matches a form we recognise,\n * never merely because it avoided the characters someone thought to ban.\n */\n\nexport const MAX_BRANDING_COLOR_LENGTH = 128;\nexport const MAX_BRANDING_FONT_FAMILY_LENGTH = 256;\n/** Asset URL cap in UTF-8 bytes, like the server's `MaxBrandingURLBytes`. */\nexport const MAX_BRANDING_URL_BYTES = 2048;\n\nconst HEX_COLOR = /^#([0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/;\n\n/** `url` is absent by construction: it is not a colour function. */\nconst COLOR_FUNCTIONS = new Set([\n \"rgb\",\n \"rgba\",\n \"hsl\",\n \"hsla\",\n \"hwb\",\n \"lab\",\n \"lch\",\n \"oklab\",\n \"oklch\",\n \"color\",\n \"color-mix\",\n]);\n\nconst COLOR_ARGS = /^[0-9a-zA-Z%.,+\\-/ ()#]*$/;\n/**\n * A bare colour name. Which names CSS knows is the browser's business: an\n * unknown one paints nothing, and every name of this shape is inert inside a\n * declaration whatever it spells.\n */\nconst COLOR_NAME = /^[a-zA-Z]{3,24}$/;\nconst FONT_IDENT = /^[A-Za-z0-9_-]+( [A-Za-z0-9_-]+)*$/;\n\nfunction isColorFunction(value: string): boolean {\n const open = value.indexOf(\"(\");\n if (open <= 0 || !value.endsWith(\")\")) return false;\n if (!COLOR_FUNCTIONS.has(value.slice(0, open))) return false;\n\n const args = value.slice(open + 1, -1);\n if (!COLOR_ARGS.test(args)) return false;\n\n // Nesting is legal (`color-mix(in srgb, rgb(1 2 3), red)`), so the\n // parentheses only have to balance, at a bounded depth.\n let depth = 0;\n for (const char of args) {\n if (char === \"(\") {\n depth += 1;\n if (depth > 4) return false;\n } else if (char === \")\") {\n depth -= 1;\n if (depth < 0) return false;\n }\n }\n return depth === 0;\n}\n\n/** Whether a value is hex, a CSS colour name, or a colour function. */\nexport function isBrandingColor(value: string): boolean {\n if (value.length > MAX_BRANDING_COLOR_LENGTH) return false;\n if (value.trim() === \"\") return false;\n // Not trimmed: the value is stored and painted verbatim.\n if (HEX_COLOR.test(value)) return true;\n return COLOR_NAME.test(value) || isColorFunction(value.toLowerCase());\n}\n\n/** Whether a value is a CSS font stack of identifiers and quoted names. */\nexport function isBrandingFontFamily(value: string): boolean {\n if (value.length > MAX_BRANDING_FONT_FAMILY_LENGTH) return false;\n if (value.trim() === \"\") return false;\n return value.split(\",\").every((family) => isFontName(trimSpaces(family)));\n}\n\n/** Spaces around a comma are ordinary; other whitespace is not a separator. */\nfunction trimSpaces(value: string): string {\n return value.replace(/^ +/, \"\").replace(/ +$/, \"\");\n}\n\nfunction isFontName(family: string): boolean {\n if (family === \"\") return false;\n const quote = family[0];\n if (quote === '\"' || quote === \"'\") {\n // A quoted name ends at its matching quote and holds no other, so it\n // cannot leave the string it opened.\n if (family.length < 2 || family.at(-1) !== quote) return false;\n const inner = family.slice(1, -1);\n return !inner.includes(quote) && !/[\\\\;{}]/.test(inner);\n }\n return FONT_IDENT.test(family);\n}\n","import {\n CreateBrandingBody,\n CreateFlowDefinitionBody,\n CreateSchemaBody,\n} from \"@zitadel/api/generated/endpoints/zitadelNextGen.zod\";\nimport { z } from \"zod\";\n\nimport {\n isBrandingColor,\n isBrandingFontFamily,\n MAX_BRANDING_URL_BYTES,\n} from \"./branding-css.js\";\nimport { isCanonicalLoopbackHttpUrl } from \"./branding-url.js\";\n\nexport const schemaConfigSchema = CreateSchemaBody;\nexport const flowConfigSchema = CreateFlowDefinitionBody.shape.flow_definition;\nexport const createFlowDefinitionRequestSchema = CreateFlowDefinitionBody;\n\n/** The wire shape of a branding revision (`POST /branding` request body). */\nexport const brandingWireSchema = CreateBrandingBody;\n\n/**\n * `{ \"$file\": \"<path>\" }`, which a `.zitadel/` file may hold in place of a\n * string field the OpenAPI YAML marks `x-local: file`. The path is relative to\n * the file holding it; the CLI inlines the content before publishing.\n */\nexport const localFileReferenceSchema = z.strictObject({ $file: z.string().min(1) });\n\n/**\n * The local `.zitadel/branding/*.json` dialect: the wire shape, where\n * `liquid_template` may also be a `$file` reference to the sibling `.liquid`\n * template (the meta-schema `branding.json` mirrors this).\n */\nexport const brandingConfigSchema = z\n .strictObject({\n // Strict, unlike the generated wire schema: a typo like `hero_urll`\n // must fail plan, not silently pass, get ignored by the server, and\n // vanish on canonical write-back. The editor meta-schema already\n // rejects unknown keys; the CLI gate has to agree.\n ...CreateBrandingBody.shape,\n // Raw strings instead of the generated uri fields: those trim and\n // normalize before refinement runs, hiding exactly the forms the wire\n // body still carries verbatim and Go's url.Parse rejects.\n logo_url: z.string().optional(),\n hero_url: z.string().optional(),\n $schema: z.string().optional(),\n liquid_template: z\n .union([CreateBrandingBody.shape.liquid_template.unwrap(), localFileReferenceSchema])\n .optional(),\n })\n .superRefine((value, ctx) => {\n // A stylesheet alone names no face to render in, so the server rejects the\n // pair as a pair (validateBrandingFontURL in\n // internal/domain/branding_validator.go). plan has to agree, or apply\n // fails after schemas and flows have already been written.\n validateBrandingAssetUrl(value.typography?.font_url, \"typography.font_url\", ctx, {\n loopback: false,\n });\n if (value.typography?.font_url !== undefined && value.typography.font_family === undefined) {\n ctx.addIssue({\n code: \"custom\",\n message:\n \"typography.font_url needs a typography.font_family to load; a stylesheet alone names no face to render in.\",\n });\n }\n // Mirror the server's asset URL gate (validateBrandingAssetURL in\n // internal/domain/branding_validator.go) so plan rejects what apply would.\n // Apply mutates schemas and flows before branding, so a late 400 here would\n // leave a half-applied run.\n validateBrandingAssetUrl(value.logo_url, \"logo_url\", ctx);\n validateBrandingAssetUrl(value.hero_url, \"hero_url\", ctx);\n validateBrandingAssetUrl(value.theme?.light?.logo_url, \"theme.light.logo_url\", ctx);\n validateBrandingAssetUrl(value.theme?.dark?.logo_url, \"theme.dark.logo_url\", ctx);\n // The appearance values land in a CSS declaration, so the server stores\n // only forms it recognises (ValidateBrandingColor / ValidateBrandingFontFamily\n // in internal/domain/branding_css.go).\n validateBrandingPalette(value.theme?.light?.palette, \"theme.light\", ctx);\n validateBrandingPalette(value.theme?.dark?.palette, \"theme.dark\", ctx);\n if (value.typography?.font_family !== undefined && !isBrandingFontFamily(value.typography.font_family)) {\n ctx.addIssue({\n code: \"custom\",\n message:\n 'typography.font_family must be comma-separated font names, each an identifier (Inter, ui-sans-serif) or a quoted name (\"APK Futural\").',\n });\n }\n });\n\nfunction validateBrandingPalette(\n palette: Record<string, unknown> | undefined,\n side: string,\n ctx: z.RefinementCtx,\n): void {\n if (!palette) {\n return;\n }\n for (const [key, colour] of Object.entries(palette)) {\n // A non-string never reaches here: the generated object schema types every\n // palette value as a string, and Zod skips refinements once the base parse\n // has failed. The check is what narrows `unknown` for the call below.\n if (typeof colour !== \"string\") {\n continue;\n }\n if (isBrandingColor(colour)) {\n continue;\n }\n ctx.addIssue({\n code: \"custom\",\n message: `${side}.palette.${key} must be hex (#4F46E5), a CSS colour name, or a colour function such as rgb(), hsl() or oklch().`,\n });\n }\n}\n\nfunction validateBrandingAssetUrl(\n value: string | undefined,\n field: string,\n ctx: z.RefinementCtx,\n options: { loopback?: boolean } = {},\n): void {\n if (value === undefined || value === \"\") {\n return;\n }\n // The server counts bytes (validateBrandingAssetURL), so a URL with\n // non-ASCII characters can be short in characters and still too long.\n if (new TextEncoder().encode(value).length > MAX_BRANDING_URL_BYTES) {\n ctx.addIssue({\n code: \"custom\",\n message: `${field} must be at most ${MAX_BRANDING_URL_BYTES} bytes.`,\n });\n return;\n }\n // Stricter-or-equal than the Go gate (validateBrandingAssetURL): the WHATWG\n // parser forgives what Go's url.Parse rejects — `https:example.com`,\n // backslashes, and whitespace all get silently normalized. plan must never\n // accept what apply would reject (the reverse is harmless), so reject the\n // raw forms before parsing.\n if (/[\\s\\\\]/.test(value)) {\n ctx.addIssue({ code: \"custom\", message: `${field} is not a valid URL.` });\n return;\n }\n if (!/^https?:\\/\\//i.test(value)) {\n ctx.addIssue({ code: \"custom\", message: `${field} must be an absolute https URL.` });\n return;\n }\n let parsed: URL;\n try {\n parsed = new URL(value);\n } catch {\n ctx.addIssue({ code: \"custom\", message: `${field} is not a valid URL.` });\n return;\n }\n // Fetched by every visitor's browser, so userinfo in it is a credential in\n // every access log — the server refuses it and so must plan.\n if (parsed.username !== \"\" || parsed.password !== \"\") {\n ctx.addIssue({\n code: \"custom\",\n message: `${field} must not carry credentials; the URL is fetched by every visitor's browser.`,\n });\n return;\n }\n // Loopback HTTP is the dev-posture carve-out (assets served from the app's\n // own dev server), and it covers images only: a stylesheet is styling rather\n // than an image, so it stays https everywhere. Check the raw URL so WHATWG\n // normalisation cannot make plan accept a host spelling that the Go save\n // gate rejects.\n if (options.loopback !== false && parsed.protocol === \"http:\" && isCanonicalLoopbackHttpUrl(value)) {\n return;\n }\n if (parsed.protocol !== \"https:\" || parsed.host === \"\") {\n ctx.addIssue({\n code: \"custom\",\n message: `${field} must be an absolute https URL (http is allowed for canonical loopback development hosts only).`,\n });\n }\n}\n"],"mappings":";;;;AAaA,MAAa,yBAAyB;AAEtC,MAAM,YAAY;;AAGlB,MAAM,kBAAkB,IAAI,IAAI;CAC9B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;AAEF,MAAM,aAAa;;;;;;AAMnB,MAAM,aAAa;AACnB,MAAM,aAAa;AAEnB,SAAS,gBAAgB,OAAwB;CAC/C,MAAM,OAAO,MAAM,QAAQ,IAAI;AAC/B,KAAI,QAAQ,KAAK,CAAC,MAAM,SAAS,IAAI,CAAE,QAAO;AAC9C,KAAI,CAAC,gBAAgB,IAAI,MAAM,MAAM,GAAG,KAAK,CAAC,CAAE,QAAO;CAEvD,MAAM,OAAO,MAAM,MAAM,OAAO,GAAG,GAAG;AACtC,KAAI,CAAC,WAAW,KAAK,KAAK,CAAE,QAAO;CAInC,IAAI,QAAQ;AACZ,MAAK,MAAM,QAAQ,KACjB,KAAI,SAAS,KAAK;AAChB,WAAS;AACT,MAAI,QAAQ,EAAG,QAAO;YACb,SAAS,KAAK;AACvB,WAAS;AACT,MAAI,QAAQ,EAAG,QAAO;;AAG1B,QAAO,UAAU;;;AAInB,SAAgB,gBAAgB,OAAwB;AACtD,KAAI,MAAM,SAAA,IAAoC,QAAO;AACrD,KAAI,MAAM,MAAM,KAAK,GAAI,QAAO;AAEhC,KAAI,UAAU,KAAK,MAAM,CAAE,QAAO;AAClC,QAAO,WAAW,KAAK,MAAM,IAAI,gBAAgB,MAAM,aAAa,CAAC;;;AAIvE,SAAgB,qBAAqB,OAAwB;AAC3D,KAAI,MAAM,SAAA,IAA0C,QAAO;AAC3D,KAAI,MAAM,MAAM,KAAK,GAAI,QAAO;AAChC,QAAO,MAAM,MAAM,IAAI,CAAC,OAAO,WAAW,WAAW,WAAW,OAAO,CAAC,CAAC;;;AAI3E,SAAS,WAAW,OAAuB;AACzC,QAAO,MAAM,QAAQ,OAAO,GAAG,CAAC,QAAQ,OAAO,GAAG;;AAGpD,SAAS,WAAW,QAAyB;AAC3C,KAAI,WAAW,GAAI,QAAO;CAC1B,MAAM,QAAQ,OAAO;AACrB,KAAI,UAAU,QAAO,UAAU,KAAK;AAGlC,MAAI,OAAO,SAAS,KAAK,OAAO,GAAG,GAAG,KAAK,MAAO,QAAO;EACzD,MAAM,QAAQ,OAAO,MAAM,GAAG,GAAG;AACjC,SAAO,CAAC,MAAM,SAAS,MAAM,IAAI,CAAC,UAAU,KAAK,MAAM;;AAEzD,QAAO,WAAW,KAAK,OAAO;;;;ACjFhC,MAAa,qBAAqB;AAClC,MAAa,mBAAmB,yBAAyB,MAAM;AAC/D,MAAa,oCAAoC;;AAGjD,MAAa,qBAAqB;;;;;;AAOlC,MAAa,2BAA2B,EAAE,aAAa,EAAE,OAAO,EAAE,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC;;;;;;AAOpF,MAAa,uBAAuB,EACjC,aAAa;CAKZ,GAAG,mBAAmB;CAItB,UAAU,EAAE,QAAQ,CAAC,UAAU;CAC/B,UAAU,EAAE,QAAQ,CAAC,UAAU;CAC/B,SAAS,EAAE,QAAQ,CAAC,UAAU;CAC9B,iBAAiB,EACd,MAAM,CAAC,mBAAmB,MAAM,gBAAgB,QAAQ,EAAE,yBAAyB,CAAC,CACpF,UAAU;CACd,CAAC,CACD,aAAa,OAAO,QAAQ;AAK3B,0BAAyB,MAAM,YAAY,UAAU,uBAAuB,KAAK,EAC/E,UAAU,OACX,CAAC;AACF,KAAI,MAAM,YAAY,aAAa,KAAA,KAAa,MAAM,WAAW,gBAAgB,KAAA,EAC/E,KAAI,SAAS;EACX,MAAM;EACN,SACE;EACH,CAAC;AAMJ,0BAAyB,MAAM,UAAU,YAAY,IAAI;AACzD,0BAAyB,MAAM,UAAU,YAAY,IAAI;AACzD,0BAAyB,MAAM,OAAO,OAAO,UAAU,wBAAwB,IAAI;AACnF,0BAAyB,MAAM,OAAO,MAAM,UAAU,uBAAuB,IAAI;AAIjF,yBAAwB,MAAM,OAAO,OAAO,SAAS,eAAe,IAAI;AACxE,yBAAwB,MAAM,OAAO,MAAM,SAAS,cAAc,IAAI;AACtE,KAAI,MAAM,YAAY,gBAAgB,KAAA,KAAa,CAAC,qBAAqB,MAAM,WAAW,YAAY,CACpG,KAAI,SAAS;EACX,MAAM;EACN,SACE;EACH,CAAC;EAEJ;AAEJ,SAAS,wBACP,SACA,MACA,KACM;AACN,KAAI,CAAC,QACH;AAEF,MAAK,MAAM,CAAC,KAAK,WAAW,OAAO,QAAQ,QAAQ,EAAE;AAInD,MAAI,OAAO,WAAW,SACpB;AAEF,MAAI,gBAAgB,OAAO,CACzB;AAEF,MAAI,SAAS;GACX,MAAM;GACN,SAAS,GAAG,KAAK,WAAW,IAAI;GACjC,CAAC;;;AAIN,SAAS,yBACP,OACA,OACA,KACA,UAAkC,EAAE,EAC9B;AACN,KAAI,UAAU,KAAA,KAAa,UAAU,GACnC;AAIF,KAAI,IAAI,aAAa,CAAC,OAAO,MAAM,CAAC,SAAA,MAAiC;AACnE,MAAI,SAAS;GACX,MAAM;GACN,SAAS,GAAG,MAAM,mBAAmB,uBAAuB;GAC7D,CAAC;AACF;;AAOF,KAAI,SAAS,KAAK,MAAM,EAAE;AACxB,MAAI,SAAS;GAAE,MAAM;GAAU,SAAS,GAAG,MAAM;GAAuB,CAAC;AACzE;;AAEF,KAAI,CAAC,gBAAgB,KAAK,MAAM,EAAE;AAChC,MAAI,SAAS;GAAE,MAAM;GAAU,SAAS,GAAG,MAAM;GAAkC,CAAC;AACpF;;CAEF,IAAI;AACJ,KAAI;AACF,WAAS,IAAI,IAAI,MAAM;SACjB;AACN,MAAI,SAAS;GAAE,MAAM;GAAU,SAAS,GAAG,MAAM;GAAuB,CAAC;AACzE;;AAIF,KAAI,OAAO,aAAa,MAAM,OAAO,aAAa,IAAI;AACpD,MAAI,SAAS;GACX,MAAM;GACN,SAAS,GAAG,MAAM;GACnB,CAAC;AACF;;AAOF,KAAI,QAAQ,aAAa,SAAS,OAAO,aAAa,WAAW,2BAA2B,MAAM,CAChG;AAEF,KAAI,OAAO,aAAa,YAAY,OAAO,SAAS,GAClD,KAAI,SAAS;EACX,MAAM;EACN,SAAS,GAAG,MAAM;EACnB,CAAC"}