From f32136e6a206a9e57d154937375f9fdbd5225ba5 Mon Sep 17 00:00:00 2001 From: Josh Stewart Date: Mon, 21 Sep 2026 12:08:27 +0000 Subject: [PATCH] chore(validation-rules-client): billing cycle anchor for relative dates Regenerates the client from the validation-rules-api spec: RelativeDateValue.anchor now accepts a BillingCycleAnchor (day, interval, optional cycle start, occurrence) whose parts are AnchorScalar values. Bumps the client to 1.7.0-rc.0 and refreshes the SDK v2 types and docs. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_019ZVeCwbb3uJoSYXVAiNpVU --- clients/validation-rules-client/package.json | 2 +- .../validation-rules-client/src/openapi.d.ts | 114 ++++- .../validation-rules-client/src/openapi.json | 70 ++- .../epilot-sdk-v2/docs/validation-rules.md | 458 +++++++++++++++--- .../src/docs/validation-rules.json | 2 +- .../src/types/validation-rules.d.ts | 114 ++++- 6 files changed, 669 insertions(+), 91 deletions(-) diff --git a/clients/validation-rules-client/package.json b/clients/validation-rules-client/package.json index 02690c93c..eca6ddf3b 100644 --- a/clients/validation-rules-client/package.json +++ b/clients/validation-rules-client/package.json @@ -1,6 +1,6 @@ { "name": "@epilot/validation-rules-client", - "version": "1.6.0-rc.1", + "version": "1.7.0-rc.0", "description": "API Client for epilot Validation Rules API", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/clients/validation-rules-client/src/openapi.d.ts b/clients/validation-rules-client/src/openapi.d.ts index 74e342abb..142775f0b 100644 --- a/clients/validation-rules-client/src/openapi.d.ts +++ b/clients/validation-rules-client/src/openapi.d.ts @@ -8,6 +8,42 @@ import type { declare namespace Components { namespace Schemas { + /** + * A part of a derived date - a fixed value or one resolved at evaluation time. Carries no adjustment; shift the result with the relative date's offset instead. + */ + export type AnchorScalar = /* A part of a derived date - a fixed value or one resolved at evaluation time. Carries no adjustment; shift the result with the relative date's offset instead. */ /* A fixed comparison value. */ StaticValue | /** + * A dynamic comparison value resolved from runtime context, e.g. `contract.installment_amount` + * or `previous_reading.value`. The first path segment must match the `name` of a declared + * context requirement. + * + */ + ContextValue | /** + * A comparison value resolved at evaluation time from an organisation environment variable + * (environments-api). The rule stores the key, never the value, so one change to the variable + * reaches every rule that references it and a blueprint install never overwrites the + * organisation's own value. + * + * Only browser-safe variable types are allowed: `Number` for numeric comparisons, `Text` for + * text comparisons and for dates (ISO 8601 string), `Boolean` for `applies_when` values. + * `String` and `SecretString` variables are rejected at write time. Write-time validation also + * checks that the variable exists in the organisation; a variable that exists without a value + * (for example seeded by a blueprint install) is accepted and resolves as unavailable until set. + * + */ + EnvironmentValue | /** + * A comparison value produced at evaluation time by an External Values hook of an installed + * app (component type `EXTERNAL_VALUES`). The rule stores the reference only; the value is + * resolved server-side by the external-values-api, which executes the hook's HTTP call with + * the app's credentials and returns the typed result identified by `result_id`. + * + * Write-time validation checks that the app is installed in the organisation, that the hook + * and result exist, and that the result's type is compatible with the compared kind + * (`number` results for numeric comparisons, `text` for text, `date` for date). External values + * are resolved only for authenticated consumers (epilot 360 users and portal end customers); + * in public journeys the dependent conditions are skipped. + * + */ + ExternalValue; /** * Optional precondition on a condition: the condition only takes part in the * validation when this comparison over context holds. Examples: apply the @@ -53,6 +89,39 @@ declare namespace Components { */ EnvironmentValue; } + /** + * A date derived from a billing cycle: the day of the month billing is due, how often it + * recurs, and optionally the date the cycle started (its phase). Day and interval are ordinary + * scalars, so they can come from a contract (`contract.billing_due_day`, + * `contract.billing_period`), a fixed value, an environment variable or an external value. + * + * Resolution at evaluation time: + * - `day` clamps to the last day of the month (31 in February resolves to the 28th/29th). + * - Without `cycle_start`, non-monthly cycles are calendar aligned (quarters start in + * January, April, July and October; half-years in January and July; yearly in January). + * `weekly` needs a `cycle_start` and resolves as unavailable without one. + * - `occurrence` picks the cycle date: `current` is the one in the period that contains the + * evaluation day, `next` the first on or after it, `previous` the last before it. + * - Any part that cannot be resolved (missing attribute, day outside 1-31, unknown interval) + * makes the whole value unavailable and the condition fails. + * + */ + export interface BillingCycleAnchor { + type: "billing_cycle"; + /** + * Day of the month billing is due (1-31). Numeric strings are accepted at evaluation time. Weekly cycles recur from `cycle_start` and ignore it. + */ + day: /* A part of a derived date - a fixed value or one resolved at evaluation time. Carries no adjustment; shift the result with the relative date's offset instead. */ AnchorScalar; + /** + * How often billing recurs, one of `weekly`, `monthly`, `every_quarter`, `every_6_months`, `yearly`. + */ + interval: /* A part of a derived date - a fixed value or one resolved at evaluation time. Carries no adjustment; shift the result with the relative date's offset instead. */ AnchorScalar; + /** + * The date the billing cycle started, e.g. `contract.start_date`. Sets the phase of non-monthly cycles. + */ + cycle_start?: /* A part of a derived date - a fixed value or one resolved at evaluation time. Carries no adjustment; shift the result with the relative date's offset instead. */ AnchorScalar; + occurrence?: "current" | "next" | "previous"; + } /** * Declarative validation rule (schema version v2). Supports predefined comparison operators * over number, date and text inputs, with static, dynamic (context path), relative-date and @@ -130,7 +199,13 @@ declare namespace Components { * context requirement. * */ - ContextValue | /* A date relative to the evaluation moment, e.g. "today minus 30 days". Only valid for date rules. */ RelativeDateValue | /** + ContextValue | /** + * A date relative to an anchor, e.g. "today minus 30 days" or "4 days before the next billing + * date". The anchor is `today` by default, or a billing cycle date derived from other + * attributes (see `BillingCycleAnchor`). Only valid for date rules. + * + */ + RelativeDateValue | /** * A comparison value resolved at evaluation time from an organisation environment variable * (environments-api). The rule stores the key, never the value, so one change to the variable * reaches every rule that references it and a blueprint install never overwrites the @@ -809,7 +884,10 @@ declare namespace Components { conditions: /* Condition definition for a regex-based validation rule (2 levels deep) */ RegexCondition; } /** - * A date relative to the evaluation moment, e.g. "today minus 30 days". Only valid for date rules. + * A date relative to an anchor, e.g. "today minus 30 days" or "4 days before the next billing + * date". The anchor is `today` by default, or a billing cycle date derived from other + * attributes (see `BillingCycleAnchor`). Only valid for date rules. + * */ export interface RelativeDateValue { source: "relative_date"; @@ -818,7 +896,27 @@ declare namespace Components { */ offset: number; unit: "days" | "months" | "years"; - anchor?: "today"; + /** + * The date the offset is applied to. Defaults to the evaluation day. + */ + anchor?: /* The date the offset is applied to. Defaults to the evaluation day. */ ("today") | /** + * A date derived from a billing cycle: the day of the month billing is due, how often it + * recurs, and optionally the date the cycle started (its phase). Day and interval are ordinary + * scalars, so they can come from a contract (`contract.billing_due_day`, + * `contract.billing_period`), a fixed value, an environment variable or an external value. + * + * Resolution at evaluation time: + * - `day` clamps to the last day of the month (31 in February resolves to the 28th/29th). + * - Without `cycle_start`, non-monthly cycles are calendar aligned (quarters start in + * January, April, July and October; half-years in January and July; yearly in January). + * `weekly` needs a `cycle_start` and resolves as unavailable without one. + * - `occurrence` picks the cycle date: `current` is the one in the period that contains the + * evaluation day, `next` the first on or after it, `previous` the last before it. + * - Any part that cannot be resolved (missing attribute, day outside 1-31, unknown interval) + * makes the whole value unavailable and the condition fails. + * + */ + BillingCycleAnchor; } /** * A single comparison value - static, resolved from context, a relative date, an organisation environment variable, or an external value. @@ -829,7 +927,13 @@ declare namespace Components { * context requirement. * */ - ContextValue | /* A date relative to the evaluation moment, e.g. "today minus 30 days". Only valid for date rules. */ RelativeDateValue | /** + ContextValue | /** + * A date relative to an anchor, e.g. "today minus 30 days" or "4 days before the next billing + * date". The anchor is `today` by default, or a billing cycle date derived from other + * attributes (see `BillingCycleAnchor`). Only valid for date rules. + * + */ + RelativeDateValue | /** * A comparison value resolved at evaluation time from an organisation environment variable * (environments-api). The rule stores the key, never the value, so one change to the variable * reaches every rule that references it and a blueprint install never overwrites the @@ -1627,7 +1731,9 @@ export interface PathsDictionary { export type Client = OpenAPIClient +export type AnchorScalar = Components.Schemas.AnchorScalar; export type AppliesWhen = Components.Schemas.AppliesWhen; +export type BillingCycleAnchor = Components.Schemas.BillingCycleAnchor; export type ComparisonRuleType = Components.Schemas.ComparisonRuleType; export type Condition = Components.Schemas.Condition; export type ConditionValue = Components.Schemas.ConditionValue; diff --git a/clients/validation-rules-client/src/openapi.json b/clients/validation-rules-client/src/openapi.json index 56949f420..0e6fbaa2b 100644 --- a/clients/validation-rules-client/src/openapi.json +++ b/clients/validation-rules-client/src/openapi.json @@ -2319,7 +2319,7 @@ }, "RelativeDateValue": { "type": "object", - "description": "A date relative to the evaluation moment, e.g. \"today minus 30 days\". Only valid for date rules.", + "description": "A date relative to an anchor, e.g. \"today minus 30 days\" or \"4 days before the next billing\ndate\". The anchor is `today` by default, or a billing cycle date derived from other\nattributes (see `BillingCycleAnchor`). Only valid for date rules.\n", "required": [ "source", "offset", @@ -2348,14 +2348,78 @@ ] }, "anchor": { + "description": "The date the offset is applied to. Defaults to the evaluation day.", + "default": "today", + "oneOf": [ + { + "type": "string", + "enum": [ + "today" + ] + }, + { + "$ref": "#/components/schemas/BillingCycleAnchor" + } + ] + } + } + }, + "BillingCycleAnchor": { + "type": "object", + "description": "A date derived from a billing cycle: the day of the month billing is due, how often it\nrecurs, and optionally the date the cycle started (its phase). Day and interval are ordinary\nscalars, so they can come from a contract (`contract.billing_due_day`,\n`contract.billing_period`), a fixed value, an environment variable or an external value.\n\nResolution at evaluation time:\n- `day` clamps to the last day of the month (31 in February resolves to the 28th/29th).\n- Without `cycle_start`, non-monthly cycles are calendar aligned (quarters start in\n January, April, July and October; half-years in January and July; yearly in January).\n `weekly` needs a `cycle_start` and resolves as unavailable without one.\n- `occurrence` picks the cycle date: `current` is the one in the period that contains the\n evaluation day, `next` the first on or after it, `previous` the last before it.\n- Any part that cannot be resolved (missing attribute, day outside 1-31, unknown interval)\n makes the whole value unavailable and the condition fails.\n", + "required": [ + "type", + "day", + "interval" + ], + "additionalProperties": false, + "properties": { + "type": { "type": "string", "enum": [ - "today" + "billing_cycle" + ] + }, + "day": { + "description": "Day of the month billing is due (1-31). Numeric strings are accepted at evaluation time. Weekly cycles recur from `cycle_start` and ignore it.", + "$ref": "#/components/schemas/AnchorScalar" + }, + "interval": { + "description": "How often billing recurs, one of `weekly`, `monthly`, `every_quarter`, `every_6_months`, `yearly`.", + "$ref": "#/components/schemas/AnchorScalar" + }, + "cycle_start": { + "description": "The date the billing cycle started, e.g. `contract.start_date`. Sets the phase of non-monthly cycles.", + "$ref": "#/components/schemas/AnchorScalar" + }, + "occurrence": { + "type": "string", + "enum": [ + "current", + "next", + "previous" ], - "default": "today" + "default": "current" } } }, + "AnchorScalar": { + "description": "A part of a derived date - a fixed value or one resolved at evaluation time. Carries no adjustment; shift the result with the relative date's offset instead.", + "oneOf": [ + { + "$ref": "#/components/schemas/StaticValue" + }, + { + "$ref": "#/components/schemas/ContextValue" + }, + { + "$ref": "#/components/schemas/EnvironmentValue" + }, + { + "$ref": "#/components/schemas/ExternalValue" + } + ] + }, "RangeValue": { "type": "object", "description": "A lower and upper bound for range operators (between, dateBetween, lengthBetween). Bounds are inclusive.", diff --git a/packages/epilot-sdk-v2/docs/validation-rules.md b/packages/epilot-sdk-v2/docs/validation-rules.md index e8b9df7bf..2f57ae478 100644 --- a/packages/epilot-sdk-v2/docs/validation-rules.md +++ b/packages/epilot-sdk-v2/docs/validation-rules.md @@ -63,6 +63,8 @@ const { data } = await validationRulesClient.getValidationRules(...) - [`ContextValue`](#contextvalue) - [`ValueAdjustment`](#valueadjustment) - [`RelativeDateValue`](#relativedatevalue) +- [`BillingCycleAnchor`](#billingcycleanchor) +- [`AnchorScalar`](#anchorscalar) - [`RangeValue`](#rangevalue) - [`NoValue`](#novalue) - [`DocumentRuleType`](#documentruletype) @@ -1777,7 +1779,13 @@ type Condition = { source: "relative_date" offset: number unit: "days" | "months" | "years" - anchor?: "today" + anchor?: "today" | { + type: { ... } + day: { ... } + interval: { ... } + cycle_start?: { ... } + occurrence?: { ... } + } } | { source: "environment" key: string @@ -1852,12 +1860,6 @@ type Condition = { error_message: string applies_when?: { path: string - operator: "equal" | "notEqual" | "greaterThan" | "greaterThanInclusive" | "lessThan" | "lessThanInclusive" | "isEmpty" | "isNotEmpty" - value?: number | string | boolean | { - source: { ... } - key: { ... } - adjust?: { ... } - } // ... } ``` @@ -1902,13 +1904,12 @@ type ConditionValue = { source: "relative_date" offset: number unit: "days" | "months" | "years" - anchor?: "today" -} | { - source: "environment" - key: string - adjust?: { - type: "percent" | "absolute" - value: number | { + anchor?: "today" | { + type: "billing_cycle" + day: { + source: { ... } + data: { ... } + } | { source: { ... } path: { ... } adjust?: { ... } @@ -1916,15 +1917,54 @@ type ConditionValue = { source: { ... } key: { ... } adjust?: { ... } + } | { + source: { ... } + app_id: { ... } + hook_id: { ... } + result_id: { ... } + adjust?: { ... } } - direction: "increase" | "decrease" - rounding?: "up" | "down" + interval: { + source: { ... } + data: { ... } + } | { + source: { ... } + path: { ... } + adjust?: { ... } + } | { + source: { ... } + key: { ... } + adjust?: { ... } + } | { + source: { ... } + app_id: { ... } + hook_id: { ... } + result_id: { ... } + adjust?: { ... } + } + cycle_start?: { + source: { ... } + data: { ... } + } | { + source: { ... } + path: { ... } + adjust?: { ... } + } | { + source: { ... } + key: { ... } + adjust?: { ... } + } | { + source: { ... } + app_id: { ... } + hook_id: { ... } + result_id: { ... } + adjust?: { ... } + } + occurrence?: "current" | "next" | "previous" } } | { - source: "external" - app_id: string - hook_id: string - result_id: string + source: "environment" + key: string adjust?: { type: "percent" | "absolute" value: number | { @@ -1940,44 +1980,6 @@ type ConditionValue = { rounding?: "up" | "down" } } | { - source: "range" - min: { - source: "static" - data: number | string | boolean - } | { - source: "context" - path: string - adjust?: { - type: { ... } - value: { ... } - direction: { ... } - rounding?: { ... } - } - } | { - source: "relative_date" - offset: number - unit: "days" | "months" | "years" - anchor?: "today" - } | { - source: "environment" - key: string - adjust?: { - type: { ... } - value: { ... } - direction: { ... } - rounding?: { ... } - } - } | { - source: "external" - app_id: string - hook_id: string - result_id: string - adjust?: { - type: { ... } - value: { ... } - direction: { ... } - rounding?: { ... } - } // ... } ``` @@ -2011,13 +2013,12 @@ type ScalarValue = { source: "relative_date" offset: number unit: "days" | "months" | "years" - anchor?: "today" -} | { - source: "environment" - key: string - adjust?: { - type: "percent" | "absolute" - value: number | { + anchor?: "today" | { + type: "billing_cycle" + day: { + source: { ... } + data: { ... } + } | { source: { ... } path: { ... } adjust?: { ... } @@ -2025,15 +2026,54 @@ type ScalarValue = { source: { ... } key: { ... } adjust?: { ... } + } | { + source: { ... } + app_id: { ... } + hook_id: { ... } + result_id: { ... } + adjust?: { ... } } - direction: "increase" | "decrease" - rounding?: "up" | "down" + interval: { + source: { ... } + data: { ... } + } | { + source: { ... } + path: { ... } + adjust?: { ... } + } | { + source: { ... } + key: { ... } + adjust?: { ... } + } | { + source: { ... } + app_id: { ... } + hook_id: { ... } + result_id: { ... } + adjust?: { ... } + } + cycle_start?: { + source: { ... } + data: { ... } + } | { + source: { ... } + path: { ... } + adjust?: { ... } + } | { + source: { ... } + key: { ... } + adjust?: { ... } + } | { + source: { ... } + app_id: { ... } + hook_id: { ... } + result_id: { ... } + adjust?: { ... } + } + occurrence?: "current" | "next" | "previous" } } | { - source: "external" - app_id: string - hook_id: string - result_id: string + source: "environment" + key: string adjust?: { type: "percent" | "absolute" value: number | { @@ -2048,6 +2088,8 @@ type ScalarValue = { direction: "increase" | "decrease" rounding?: "up" | "down" } +} | { + // ... } ``` @@ -2185,14 +2227,250 @@ type ValueAdjustment = { ### `RelativeDateValue` -A date relative to the evaluation moment, e.g. "today minus 30 days". Only valid for date rules. +A date relative to an anchor, e.g. "today minus 30 days" or "4 days before the next billing +date". The anchor is `today` by default, or a billing cycle date derived from other +attributes (see `BillingCycleAnchor`). Only valid for date rules. + ```ts type RelativeDateValue = { source: "relative_date" offset: number unit: "days" | "months" | "years" - anchor?: "today" + anchor?: "today" | { + type: "billing_cycle" + day: { + source: { ... } + data: { ... } + } | { + source: { ... } + path: { ... } + adjust?: { ... } + } | { + source: { ... } + key: { ... } + adjust?: { ... } + } | { + source: { ... } + app_id: { ... } + hook_id: { ... } + result_id: { ... } + adjust?: { ... } + } + interval: { + source: { ... } + data: { ... } + } | { + source: { ... } + path: { ... } + adjust?: { ... } + } | { + source: { ... } + key: { ... } + adjust?: { ... } + } | { + source: { ... } + app_id: { ... } + hook_id: { ... } + result_id: { ... } + adjust?: { ... } + } + cycle_start?: { + source: { ... } + data: { ... } + } | { + source: { ... } + path: { ... } + adjust?: { ... } + } | { + source: { ... } + key: { ... } + adjust?: { ... } + } | { + source: { ... } + app_id: { ... } + hook_id: { ... } + result_id: { ... } + adjust?: { ... } + } + occurrence?: "current" | "next" | "previous" + } +} +``` + +### `BillingCycleAnchor` + +A date derived from a billing cycle: the day of the month billing is due, how often it +recurs, and optionally the date the cycle started (its phase). Day and interval are ordinary +scalars, so they can come from a contract (`contract.billing_due_day`, +`contract.billing_period`), a fixed value, an env + +```ts +type BillingCycleAnchor = { + type: "billing_cycle" + day: { + source: "static" + data: number | string | boolean + } | { + source: "context" + path: string + adjust?: { + type: { ... } + value: { ... } + direction: { ... } + rounding?: { ... } + } + } | { + source: "environment" + key: string + adjust?: { + type: { ... } + value: { ... } + direction: { ... } + rounding?: { ... } + } + } | { + source: "external" + app_id: string + hook_id: string + result_id: string + adjust?: { + type: { ... } + value: { ... } + direction: { ... } + rounding?: { ... } + } + } + interval: { + source: "static" + data: number | string | boolean + } | { + source: "context" + path: string + adjust?: { + type: { ... } + value: { ... } + direction: { ... } + rounding?: { ... } + } + } | { + source: "environment" + key: string + adjust?: { + type: { ... } + value: { ... } + direction: { ... } + rounding?: { ... } + } + } | { + source: "external" + app_id: string + hook_id: string + result_id: string + adjust?: { + type: { ... } + value: { ... } + direction: { ... } + rounding?: { ... } + } + } + cycle_start?: { + source: "static" + data: number | string | boolean + } | { + source: "context" + path: string + adjust?: { + type: { ... } + value: { ... } + direction: { ... } + rounding?: { ... } + } + } | { + source: "environment" + key: string + adjust?: { + type: { ... } + value: { ... } + direction: { ... } + rounding?: { ... } + } + } | { + source: "external" + app_id: string + hook_id: string + result_id: string + adjust?: { + type: { ... } + value: { ... } + direction: { ... } + rounding?: { ... } + } + // ... +} +``` + +### `AnchorScalar` + +A part of a derived date - a fixed value or one resolved at evaluation time. Carries no adjustment; shift the result with the relative date's offset instead. + +```ts +type AnchorScalar = { + source: "static" + data: number | string | boolean +} | { + source: "context" + path: string + adjust?: { + type: "percent" | "absolute" + value: number | { + source: { ... } + path: { ... } + adjust?: { ... } + } | { + source: { ... } + key: { ... } + adjust?: { ... } + } + direction: "increase" | "decrease" + rounding?: "up" | "down" + } +} | { + source: "environment" + key: string + adjust?: { + type: "percent" | "absolute" + value: number | { + source: { ... } + path: { ... } + adjust?: { ... } + } | { + source: { ... } + key: { ... } + adjust?: { ... } + } + direction: "increase" | "decrease" + rounding?: "up" | "down" + } +} | { + source: "external" + app_id: string + hook_id: string + result_id: string + adjust?: { + type: "percent" | "absolute" + value: number | { + source: { ... } + path: { ... } + adjust?: { ... } + } | { + source: { ... } + key: { ... } + adjust?: { ... } + } + direction: "increase" | "decrease" + rounding?: "up" | "down" + } } ``` @@ -2219,7 +2497,13 @@ type RangeValue = { source: "relative_date" offset: number unit: "days" | "months" | "years" - anchor?: "today" + anchor?: "today" | { + type: { ... } + day: { ... } + interval: { ... } + cycle_start?: { ... } + occurrence?: { ... } + } } | { source: "environment" key: string @@ -2257,7 +2541,13 @@ type RangeValue = { source: "relative_date" offset: number unit: "days" | "months" | "years" - anchor?: "today" + anchor?: "today" | { + type: { ... } + day: { ... } + interval: { ... } + cycle_start?: { ... } + occurrence?: { ... } + } } | { source: "environment" key: string @@ -2451,7 +2741,13 @@ type DocumentConditionValue = { source: "relative_date" offset: number unit: "days" | "months" | "years" - anchor?: "today" + anchor?: "today" | { + type: { ... } + day: { ... } + interval: { ... } + cycle_start?: { ... } + occurrence?: { ... } + } } | { source: "environment" key: string @@ -2489,7 +2785,13 @@ type DocumentConditionValue = { source: "relative_date" offset: number unit: "days" | "months" | "years" - anchor?: "today" + anchor?: "today" | { + type: { ... } + day: { ... } + interval: { ... } + cycle_start?: { ... } + occurrence?: { ... } + } } | { source: "environment" key: string @@ -2518,7 +2820,7 @@ type DocumentConditionValue = { types: "image" | "pdf" | "document" | "spreadsheet"[] } | { source: "criteria" - text: string + // ... } ``` diff --git a/packages/epilot-sdk-v2/src/docs/validation-rules.json b/packages/epilot-sdk-v2/src/docs/validation-rules.json index 3c0e5a313..25c034765 100644 --- a/packages/epilot-sdk-v2/src/docs/validation-rules.json +++ b/packages/epilot-sdk-v2/src/docs/validation-rules.json @@ -1 +1 @@ -"# Validation Rules API\n\n- **Base URL:** `https://validation-rules.sls.epilot.io`\n- **Full API Docs:** [https://docs.epilot.io/api/validation-rules](https://docs.epilot.io/api/validation-rules)\n\n## Usage\n\n```ts\nimport { epilot } from '@epilot/sdk'\n\nepilot.authorize(() => '')\nconst { data } = await epilot.validationRules.getValidationRules(...)\n```\n\n### Tree-shakeable import\n\n```ts\nimport { getClient, authorize } from '@epilot/sdk/validation-rules'\n\nconst validationRulesClient = getClient()\nauthorize(validationRulesClient, () => '')\nconst { data } = await validationRulesClient.getValidationRules(...)\n```\n\n## Operations\n\n**Validation Rules**\n- [`getValidationRules`](#getvalidationrules)\n- [`createValidationRule`](#createvalidationrule)\n- [`getValidationRuleById`](#getvalidationrulebyid)\n- [`updateValidationRule`](#updatevalidationrule)\n- [`deleteValidationRule`](#deletevalidationrule)\n- [`addUsedByReference`](#addusedbyreference)\n- [`removeUsedByReference`](#removeusedbyreference)\n\n**Schemas**\n- [`GetValidationRulesResponse`](#getvalidationrulesresponse)\n- [`CreateValidationRuleRequest`](#createvalidationrulerequest)\n- [`UpdateValidationRuleRequest`](#updatevalidationrulerequest)\n- [`ValidationRuleBase`](#validationrulebase)\n- [`ValidationRule`](#validationrule)\n- [`UsedBy`](#usedby)\n- [`RegexRuleType`](#regexruletype)\n- [`PatternRuleType`](#patternruletype)\n- [`NumericRuleType`](#numericruletype)\n- [`RegexCondition`](#regexcondition)\n- [`RegexNestedCondition`](#regexnestedcondition)\n- [`RegexFactCondition`](#regexfactcondition)\n- [`PatternCondition`](#patterncondition)\n- [`PatternNestedCondition`](#patternnestedcondition)\n- [`PatternFactCondition`](#patternfactcondition)\n- [`NumericCondition`](#numericcondition)\n- [`NumericNestedCondition`](#numericnestedcondition)\n- [`NumericFactCondition`](#numericfactcondition)\n- [`ComparisonRuleType`](#comparisonruletype)\n- [`Condition`](#condition)\n- [`Operator`](#operator)\n- [`ConditionValue`](#conditionvalue)\n- [`ScalarValue`](#scalarvalue)\n- [`EnvironmentValue`](#environmentvalue)\n- [`ExternalValue`](#externalvalue)\n- [`StaticValue`](#staticvalue)\n- [`ContextValue`](#contextvalue)\n- [`ValueAdjustment`](#valueadjustment)\n- [`RelativeDateValue`](#relativedatevalue)\n- [`RangeValue`](#rangevalue)\n- [`NoValue`](#novalue)\n- [`DocumentRuleType`](#documentruletype)\n- [`DocumentCheck`](#documentcheck)\n- [`DocumentCondition`](#documentcondition)\n- [`DocumentOperator`](#documentoperator)\n- [`DocumentConditionValue`](#documentconditionvalue)\n- [`FileTypesValue`](#filetypesvalue)\n- [`CriteriaValue`](#criteriavalue)\n- [`AppliesWhen`](#applieswhen)\n- [`ContextRequirement`](#contextrequirement)\n\n### `getValidationRules`\n\nReturns all validation rules belonging to the authenticated user's organization.\n\n`GET /v1/validation-rules`\n\n```ts\nconst { data } = await client.getValidationRules()\n```\n\n
\nResponse\n\n```json\n{\n \"results\": [\n {\n \"title\": \"string\",\n \"placeholder\": \"string\",\n \"used_by\": [\n {\n \"type\": \"journey\",\n \"source_id\": \"journey-xyz789\"\n }\n ],\n \"rule\": {\n \"type\": \"regex\",\n \"conditions\": {\n \"all\": [\n {\n \"fact\": \"inputValue\",\n \"operator\": \"regexMatch\",\n \"value\": \"string\",\n \"params\": {\n \"errorMessage\": \"string\"\n }\n },\n {\n \"all\": [\n {\n \"fact\": \"inputValue\",\n \"operator\": \"regexMatch\",\n \"value\": \"string\",\n \"params\": {\n \"errorMessage\": \"string\"\n }\n }\n ]\n }\n ]\n }\n },\n \"contexts\": [\n {\n \"schema\": \"contract\"\n }\n ],\n \"_schema_version\": \"string\",\n \"_id\": \"string\",\n \"_organization_id\": \"string\",\n \"created_at\": \"string\",\n \"updated_at\": \"string\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n }\n ]\n}\n```\n\n
\n\n---\n\n### `createValidationRule`\n\nCreates a new validation rule for the authenticated organization.\n\n`POST /v1/validation-rules`\n\n```ts\nconst { data } = await client.createValidationRule(\n null,\n {\n title: 'string',\n placeholder: 'string',\n used_by: [\n {\n type: 'journey',\n source_id: 'journey-xyz789'\n }\n ],\n rule: {\n type: 'regex',\n conditions: {\n all: [\n {\n fact: 'inputValue',\n operator: 'regexMatch',\n value: 'string',\n params: {\n errorMessage: 'string'\n }\n },\n {\n all: [\n {\n fact: 'inputValue',\n operator: 'regexMatch',\n value: 'string',\n params: {\n errorMessage: 'string'\n }\n }\n ]\n }\n ]\n }\n },\n contexts: [\n {\n schema: 'contract'\n }\n ]\n },\n)\n```\n\n
\nResponse\n\n```json\n{\n \"title\": \"string\",\n \"placeholder\": \"string\",\n \"used_by\": [\n {\n \"type\": \"journey\",\n \"source_id\": \"journey-xyz789\"\n }\n ],\n \"rule\": {\n \"type\": \"regex\",\n \"conditions\": {\n \"all\": [\n {\n \"fact\": \"inputValue\",\n \"operator\": \"regexMatch\",\n \"value\": \"string\",\n \"params\": {\n \"errorMessage\": \"string\"\n }\n },\n {\n \"all\": [\n {\n \"fact\": \"inputValue\",\n \"operator\": \"regexMatch\",\n \"value\": \"string\",\n \"params\": {\n \"errorMessage\": \"string\"\n }\n }\n ]\n }\n ]\n }\n },\n \"contexts\": [\n {\n \"schema\": \"contract\"\n }\n ],\n \"_schema_version\": \"string\",\n \"_id\": \"string\",\n \"_organization_id\": \"string\",\n \"created_at\": \"string\",\n \"updated_at\": \"string\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n}\n```\n\n
\n\n---\n\n### `getValidationRuleById`\n\nRetrieves a specific validation rule by its unique ID.\n\n`GET /v1/validation-rules/{ruleId}`\n\n```ts\nconst { data } = await client.getValidationRuleById({\n ruleId: 'example',\n})\n```\n\n
\nResponse\n\n```json\n{\n \"title\": \"string\",\n \"placeholder\": \"string\",\n \"used_by\": [\n {\n \"type\": \"journey\",\n \"source_id\": \"journey-xyz789\"\n }\n ],\n \"rule\": {\n \"type\": \"regex\",\n \"conditions\": {\n \"all\": [\n {\n \"fact\": \"inputValue\",\n \"operator\": \"regexMatch\",\n \"value\": \"string\",\n \"params\": {\n \"errorMessage\": \"string\"\n }\n },\n {\n \"all\": [\n {\n \"fact\": \"inputValue\",\n \"operator\": \"regexMatch\",\n \"value\": \"string\",\n \"params\": {\n \"errorMessage\": \"string\"\n }\n }\n ]\n }\n ]\n }\n },\n \"contexts\": [\n {\n \"schema\": \"contract\"\n }\n ],\n \"_schema_version\": \"string\",\n \"_id\": \"string\",\n \"_organization_id\": \"string\",\n \"created_at\": \"string\",\n \"updated_at\": \"string\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n}\n```\n\n
\n\n---\n\n### `updateValidationRule`\n\nPartially updates an existing validation rule by ID. Only the fields provided in the request body are updated.\n\n`PATCH /v1/validation-rules/{ruleId}`\n\n```ts\nconst { data } = await client.updateValidationRule(\n {\n ruleId: 'example',\n },\n {\n title: 'string',\n placeholder: 'string',\n used_by: [\n {\n type: 'journey',\n source_id: 'journey-xyz789'\n }\n ],\n rule: {\n type: 'regex',\n conditions: {\n all: [\n {\n fact: 'inputValue',\n operator: 'regexMatch',\n value: 'string',\n params: {\n errorMessage: 'string'\n }\n },\n {\n all: [\n {\n fact: 'inputValue',\n operator: 'regexMatch',\n value: 'string',\n params: {\n errorMessage: 'string'\n }\n }\n ]\n }\n ]\n }\n },\n contexts: [\n {\n schema: 'contract'\n }\n ]\n },\n)\n```\n\n
\nResponse\n\n```json\n{\n \"title\": \"string\",\n \"placeholder\": \"string\",\n \"used_by\": [\n {\n \"type\": \"journey\",\n \"source_id\": \"journey-xyz789\"\n }\n ],\n \"rule\": {\n \"type\": \"regex\",\n \"conditions\": {\n \"all\": [\n {\n \"fact\": \"inputValue\",\n \"operator\": \"regexMatch\",\n \"value\": \"string\",\n \"params\": {\n \"errorMessage\": \"string\"\n }\n },\n {\n \"all\": [\n {\n \"fact\": \"inputValue\",\n \"operator\": \"regexMatch\",\n \"value\": \"string\",\n \"params\": {\n \"errorMessage\": \"string\"\n }\n }\n ]\n }\n ]\n }\n },\n \"contexts\": [\n {\n \"schema\": \"contract\"\n }\n ],\n \"_schema_version\": \"string\",\n \"_id\": \"string\",\n \"_organization_id\": \"string\",\n \"created_at\": \"string\",\n \"updated_at\": \"string\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n}\n```\n\n
\n\n---\n\n### `deleteValidationRule`\n\nPermanently deletes a validation rule by ID. Any journeys or entity attributes referencing this rule should be updated before deletion.\n\n`DELETE /v1/validation-rules/{ruleId}`\n\n```ts\nconst { data } = await client.deleteValidationRule({\n ruleId: 'example',\n})\n```\n\n---\n\n### `addUsedByReference`\n\nAdds a single `used_by` reference to an existing validation rule.\n\n`POST /v1/validation-rules/{ruleId}/used-by`\n\n```ts\nconst { data } = await client.addUsedByReference(\n {\n ruleId: 'example',\n },\n {\n type: 'journey',\n source_id: 'journey-xyz789'\n },\n)\n```\n\n
\nResponse\n\n```json\n{\n \"title\": \"string\",\n \"placeholder\": \"string\",\n \"used_by\": [\n {\n \"type\": \"journey\",\n \"source_id\": \"journey-xyz789\"\n }\n ],\n \"rule\": {\n \"type\": \"regex\",\n \"conditions\": {\n \"all\": [\n {\n \"fact\": \"inputValue\",\n \"operator\": \"regexMatch\",\n \"value\": \"string\",\n \"params\": {\n \"errorMessage\": \"string\"\n }\n },\n {\n \"all\": [\n {\n \"fact\": \"inputValue\",\n \"operator\": \"regexMatch\",\n \"value\": \"string\",\n \"params\": {\n \"errorMessage\": \"string\"\n }\n }\n ]\n }\n ]\n }\n },\n \"contexts\": [\n {\n \"schema\": \"contract\"\n }\n ],\n \"_schema_version\": \"string\",\n \"_id\": \"string\",\n \"_organization_id\": \"string\",\n \"created_at\": \"string\",\n \"updated_at\": \"string\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n}\n```\n\n
\n\n---\n\n### `removeUsedByReference`\n\nRemoves a specific `used_by` reference from an existing validation rule.\n\n`DELETE /v1/validation-rules/{ruleId}/used-by`\n\n```ts\nconst { data } = await client.removeUsedByReference(\n {\n ruleId: 'example',\n },\n {\n type: 'journey',\n source_id: 'journey-xyz789'\n },\n)\n```\n\n
\nResponse\n\n```json\n{\n \"title\": \"string\",\n \"placeholder\": \"string\",\n \"used_by\": [\n {\n \"type\": \"journey\",\n \"source_id\": \"journey-xyz789\"\n }\n ],\n \"rule\": {\n \"type\": \"regex\",\n \"conditions\": {\n \"all\": [\n {\n \"fact\": \"inputValue\",\n \"operator\": \"regexMatch\",\n \"value\": \"string\",\n \"params\": {\n \"errorMessage\": \"string\"\n }\n },\n {\n \"all\": [\n {\n \"fact\": \"inputValue\",\n \"operator\": \"regexMatch\",\n \"value\": \"string\",\n \"params\": {\n \"errorMessage\": \"string\"\n }\n }\n ]\n }\n ]\n }\n },\n \"contexts\": [\n {\n \"schema\": \"contract\"\n }\n ],\n \"_schema_version\": \"string\",\n \"_id\": \"string\",\n \"_organization_id\": \"string\",\n \"created_at\": \"string\",\n \"updated_at\": \"string\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n}\n```\n\n
\n\n---\n\n## Schemas\n\n### `GetValidationRulesResponse`\n\nResponse envelope for listing all validation rules within an organization.\n\n```ts\ntype GetValidationRulesResponse = {\n results?: Array<{\n _schema_version: string\n _id: string\n _organization_id: string\n created_at: string\n updated_at: string\n created_by: string\n updated_by: string\n }>\n}\n```\n\n### `CreateValidationRuleRequest`\n\n```ts\ntype CreateValidationRuleRequest = object\n```\n\n### `UpdateValidationRuleRequest`\n\n```ts\ntype UpdateValidationRuleRequest = {\n title?: string\n placeholder?: string\n used_by?: Array<{\n type: \"journey\" | \"entity\"\n schema_slug?: string\n source_id?: string\n }>\n rule?: {\n type: \"regex\"\n conditions: {\n all: { ... }\n } | {\n any: { ... }\n } | {\n not: { ... }\n }\n } | {\n type: \"pattern\"\n conditions: {\n all: { ... }\n } | {\n any: { ... }\n } | {\n not: { ... }\n }\n } | {\n type: \"numeric\"\n conditions: {\n all: { ... }\n } | {\n any: { ... }\n } | {\n not: { ... }\n }\n } | {\n input_type: \"number\" | \"date\" | \"text\"\n conditions: Array<{\n id: { ... }\n operator: { ... }\n value: { ... }\n error_message: { ... }\n applies_when?: { ... }\n allow_failure?: { ... }\n }>\n } | {\n input_type: \"document\"\n check: {\n level: { ... }\n }\n conditions: Array<{\n id: { ... }\n operator: { ... }\n value: { ... }\n error_message: { ... }\n allow_failure?: { ... }\n }>\n }\n contexts?: Array<{\n schema: string\n }>\n}\n```\n\n### `ValidationRuleBase`\n\n```ts\ntype ValidationRuleBase = {\n title?: string\n placeholder?: string\n used_by?: Array<{\n type: \"journey\" | \"entity\"\n schema_slug?: string\n source_id?: string\n }>\n rule?: {\n type: \"regex\"\n conditions: {\n all: { ... }\n } | {\n any: { ... }\n } | {\n not: { ... }\n }\n } | {\n type: \"pattern\"\n conditions: {\n all: { ... }\n } | {\n any: { ... }\n } | {\n not: { ... }\n }\n } | {\n type: \"numeric\"\n conditions: {\n all: { ... }\n } | {\n any: { ... }\n } | {\n not: { ... }\n }\n } | {\n input_type: \"number\" | \"date\" | \"text\"\n conditions: Array<{\n id: { ... }\n operator: { ... }\n value: { ... }\n error_message: { ... }\n applies_when?: { ... }\n allow_failure?: { ... }\n }>\n } | {\n input_type: \"document\"\n check: {\n level: { ... }\n }\n conditions: Array<{\n id: { ... }\n operator: { ... }\n value: { ... }\n error_message: { ... }\n allow_failure?: { ... }\n }>\n }\n contexts?: Array<{\n schema: string\n }>\n}\n```\n\n### `ValidationRule`\n\nThe Validation rule definition.\n\n```ts\ntype ValidationRule = {\n _schema_version: string\n _id: string\n _organization_id: string\n created_at: string\n updated_at: string\n created_by: string\n updated_by: string\n}\n```\n\n### `UsedBy`\n\nDescribes where and how a validation rule is applied. Used to track associations between rules and the journeys or entity schemas that reference them.\n\n```ts\ntype UsedBy = {\n type: \"journey\" | \"entity\"\n schema_slug?: string\n source_id?: string\n}\n```\n\n### `RegexRuleType`\n\nValidation rule that uses a regular expression to validate input.\n\n```ts\ntype RegexRuleType = {\n type: \"regex\"\n conditions: {\n all: Array<{\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n all: { ... }\n } | {\n any: { ... }\n } | {\n not: { ... }\n }>\n } | {\n any: Array<{\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n all: { ... }\n } | {\n any: { ... }\n } | {\n not: { ... }\n }>\n } | {\n not: {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n all: { ... }\n } | {\n any: { ... }\n } | {\n not: { ... }\n }\n }\n}\n```\n\n### `PatternRuleType`\n\nValidation rule that uses a sequence of patterns to validate input.\n\n```ts\ntype PatternRuleType = {\n type: \"pattern\"\n conditions: {\n all: Array<{\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n all: { ... }\n } | {\n any: { ... }\n } | {\n not: { ... }\n }>\n } | {\n any: Array<{\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n all: { ... }\n } | {\n any: { ... }\n } | {\n not: { ... }\n }>\n } | {\n not: {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n all: { ... }\n } | {\n any: { ... }\n } | {\n not: { ... }\n }\n }\n}\n```\n\n### `NumericRuleType`\n\nValidation rule for numeric values, supporting range and digit count constraints.\n\n```ts\ntype NumericRuleType = {\n type: \"numeric\"\n conditions: {\n all: Array<{\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n all: { ... }\n } | {\n any: { ... }\n } | {\n not: { ... }\n }>\n } | {\n any: Array<{\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n all: { ... }\n } | {\n any: { ... }\n } | {\n not: { ... }\n }>\n } | {\n not: {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n all: { ... }\n } | {\n any: { ... }\n } | {\n not: { ... }\n }\n }\n}\n```\n\n### `RegexCondition`\n\nCondition definition for a regex-based validation rule (2 levels deep)\n\n```ts\ntype RegexCondition = {\n all: Array<{\n fact: \"inputValue\"\n operator: \"regexMatch\"\n value: string\n params?: {\n errorMessage?: { ... }\n }\n } | {\n all: Array<{\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n }>\n } | {\n any: Array<{\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n }>\n } | {\n not: {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n }\n }>\n} | {\n any: Array<{\n fact: \"inputValue\"\n operator: \"regexMatch\"\n value: string\n params?: {\n errorMessage?: { ... }\n }\n } | {\n all: Array<{\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n }>\n } | {\n any: Array<{\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n }>\n } | {\n not: {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n }\n }>\n} | {\n not: {\n fact: \"inputValue\"\n operator: \"regexMatch\"\n value: string\n params?: {\n errorMessage?: { ... }\n }\n } | {\n all: Array<{\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n }>\n } | {\n any: Array<{\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n }>\n } | {\n not: {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n }\n }\n}\n```\n\n### `RegexNestedCondition`\n\nNested condition with logical operators (level 2 only)\n\n```ts\ntype RegexNestedCondition = {\n all: Array<{\n fact: \"inputValue\"\n operator: \"regexMatch\"\n value: string\n params?: {\n errorMessage?: { ... }\n }\n }>\n} | {\n any: Array<{\n fact: \"inputValue\"\n operator: \"regexMatch\"\n value: string\n params?: {\n errorMessage?: { ... }\n }\n }>\n} | {\n not: {\n fact: \"inputValue\"\n operator: \"regexMatch\"\n value: string\n params?: {\n errorMessage?: { ... }\n }\n }\n}\n```\n\n### `RegexFactCondition`\n\nFact-based condition for regex validation\n\n```ts\ntype RegexFactCondition = {\n fact: \"inputValue\"\n operator: \"regexMatch\"\n value: string\n params?: {\n errorMessage?: string\n }\n}\n```\n\n### `PatternCondition`\n\nCondition definition for a pattern-based validation rule (2 levels deep)\n\n```ts\ntype PatternCondition = {\n all: Array<{\n fact: \"total-length\"\n operator: \"equal\" | \"notEqual\" | \"lessThan\" | \"lessThanInclusive\" | \"greaterThan\" | \"greaterThanInclusive\"\n value: number\n params?: {\n errorMessage?: { ... }\n start?: { ... }\n end?: { ... }\n }\n } | {\n fact: \"static-check\" | \"total-length\"\n operator: \"exactlyNDigits\"\n value: number\n params?: {\n errorMessage?: { ... }\n start?: { ... }\n end?: { ... }\n }\n } | {\n fact: \"static-check\"\n operator: \"in\" | \"notIn\" | \"contains\" | \"doesNotContain\"\n value: string[]\n params?: {\n errorMessage?: { ... }\n start?: { ... }\n end?: { ... }\n }\n } | {\n fact: \"static-check\"\n operator: \"equal\" | \"notEqual\"\n value: string\n params?: {\n errorMessage?: { ... }\n start?: { ... }\n end?: { ... }\n }\n } | {\n all: Array<{\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n }>\n } | {\n any: Array<{\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n }>\n } | {\n not: {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n fact: { ... }\n operator: { ... }\n // ...\n}\n```\n\n### `PatternNestedCondition`\n\nNested condition with logical operators (level 2 only)\n\n```ts\ntype PatternNestedCondition = {\n all: Array<{\n fact: \"total-length\"\n operator: \"equal\" | \"notEqual\" | \"lessThan\" | \"lessThanInclusive\" | \"greaterThan\" | \"greaterThanInclusive\"\n value: number\n params?: {\n errorMessage?: { ... }\n start?: { ... }\n end?: { ... }\n }\n } | {\n fact: \"static-check\" | \"total-length\"\n operator: \"exactlyNDigits\"\n value: number\n params?: {\n errorMessage?: { ... }\n start?: { ... }\n end?: { ... }\n }\n } | {\n fact: \"static-check\"\n operator: \"in\" | \"notIn\" | \"contains\" | \"doesNotContain\"\n value: string[]\n params?: {\n errorMessage?: { ... }\n start?: { ... }\n end?: { ... }\n }\n } | {\n fact: \"static-check\"\n operator: \"equal\" | \"notEqual\"\n value: string\n params?: {\n errorMessage?: { ... }\n start?: { ... }\n end?: { ... }\n }\n }>\n} | {\n any: Array<{\n fact: \"total-length\"\n operator: \"equal\" | \"notEqual\" | \"lessThan\" | \"lessThanInclusive\" | \"greaterThan\" | \"greaterThanInclusive\"\n value: number\n params?: {\n errorMessage?: { ... }\n start?: { ... }\n end?: { ... }\n }\n } | {\n fact: \"static-check\" | \"total-length\"\n operator: \"exactlyNDigits\"\n value: number\n params?: {\n errorMessage?: { ... }\n start?: { ... }\n end?: { ... }\n }\n } | {\n fact: \"static-check\"\n operator: \"in\" | \"notIn\" | \"contains\" | \"doesNotContain\"\n value: string[]\n params?: {\n errorMessage?: { ... }\n start?: { ... }\n end?: { ... }\n }\n } | {\n fact: \"static-check\"\n operator: \"equal\" | \"notEqual\"\n value: string\n params?: {\n errorMessage?: { ... }\n start?: { ... }\n end?: { ... }\n }\n }>\n} | {\n not: {\n fact: \"total-length\"\n operator: \"equal\" | \"notEqual\" | \"lessThan\" | \"lessThanInclusive\" | \"greaterThan\" | \"greaterThanInclusive\"\n value: number\n params?: {\n errorMessage?: { ... }\n start?: { ... }\n end?: { ... }\n }\n } | {\n fact: \"static-check\" | \"total-length\"\n operator: \"exactlyNDigits\"\n value: number\n params?: {\n errorMessage?: { ... }\n start?: { ... }\n end?: { ... }\n }\n } | {\n fact: \"static-check\"\n operator: \"in\" | \"notIn\" | \"contains\" | \"doesNotContain\"\n value: string[]\n params?: {\n // ...\n}\n```\n\n### `PatternFactCondition`\n\nFact-based condition for pattern validation\n\n```ts\ntype PatternFactCondition = {\n fact: \"total-length\"\n operator: \"equal\" | \"notEqual\" | \"lessThan\" | \"lessThanInclusive\" | \"greaterThan\" | \"greaterThanInclusive\"\n value: number\n params?: {\n errorMessage?: string\n start?: number\n end?: number\n }\n} | {\n fact: \"static-check\" | \"total-length\"\n operator: \"exactlyNDigits\"\n value: number\n params?: {\n errorMessage?: string\n start?: number\n end?: number\n }\n} | {\n fact: \"static-check\"\n operator: \"in\" | \"notIn\" | \"contains\" | \"doesNotContain\"\n value: string[]\n params?: {\n errorMessage?: string\n start?: number\n end?: number\n }\n} | {\n fact: \"static-check\"\n operator: \"equal\" | \"notEqual\"\n value: string\n params?: {\n errorMessage?: string\n start?: number\n end?: number\n }\n}\n```\n\n### `NumericCondition`\n\nCondition definition for a numeric-based validation rule (2 levels deep)\n\n```ts\ntype NumericCondition = {\n all: Array<{\n fact: \"numeric-value\" | \"total-length\"\n operator: \"equal\" | \"notEqual\" | \"lessThan\" | \"lessThanInclusive\" | \"greaterThan\" | \"greaterThanInclusive\"\n value: number\n params?: {\n errorMessage?: { ... }\n }\n } | {\n fact: \"integer-digits-count\"\n operator: \"equal\" | \"exactlyNDigits\" | \"minIntegerDigits\" | \"maxIntegerDigits\"\n value: number\n params?: {\n errorMessage?: { ... }\n allowLeadingZeroes?: { ... }\n }\n } | {\n fact: \"decimal-digits-count\"\n operator: \"equal\" | \"minDecimalDigits\" | \"maxDecimalDigits\"\n value: number\n params?: {\n errorMessage?: { ... }\n }\n } | {\n fact: \"has-leading-zeroes\"\n operator: \"equal\" | \"notAllowed\"\n value: boolean\n params?: {\n errorMessage?: { ... }\n }\n } | {\n all: Array<{\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n }>\n } | {\n any: Array<{\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n }>\n } | {\n not: {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n }\n }>\n} | {\n any: Array<{\n fact: \"numeric-value\" | \"total-length\"\n // ...\n}\n```\n\n### `NumericNestedCondition`\n\nNested condition with logical operators (level 2 only)\n\n```ts\ntype NumericNestedCondition = {\n all: Array<{\n fact: \"numeric-value\" | \"total-length\"\n operator: \"equal\" | \"notEqual\" | \"lessThan\" | \"lessThanInclusive\" | \"greaterThan\" | \"greaterThanInclusive\"\n value: number\n params?: {\n errorMessage?: { ... }\n }\n } | {\n fact: \"integer-digits-count\"\n operator: \"equal\" | \"exactlyNDigits\" | \"minIntegerDigits\" | \"maxIntegerDigits\"\n value: number\n params?: {\n errorMessage?: { ... }\n allowLeadingZeroes?: { ... }\n }\n } | {\n fact: \"decimal-digits-count\"\n operator: \"equal\" | \"minDecimalDigits\" | \"maxDecimalDigits\"\n value: number\n params?: {\n errorMessage?: { ... }\n }\n } | {\n fact: \"has-leading-zeroes\"\n operator: \"equal\" | \"notAllowed\"\n value: boolean\n params?: {\n errorMessage?: { ... }\n }\n }>\n} | {\n any: Array<{\n fact: \"numeric-value\" | \"total-length\"\n operator: \"equal\" | \"notEqual\" | \"lessThan\" | \"lessThanInclusive\" | \"greaterThan\" | \"greaterThanInclusive\"\n value: number\n params?: {\n errorMessage?: { ... }\n }\n } | {\n fact: \"integer-digits-count\"\n operator: \"equal\" | \"exactlyNDigits\" | \"minIntegerDigits\" | \"maxIntegerDigits\"\n value: number\n params?: {\n errorMessage?: { ... }\n allowLeadingZeroes?: { ... }\n }\n } | {\n fact: \"decimal-digits-count\"\n operator: \"equal\" | \"minDecimalDigits\" | \"maxDecimalDigits\"\n value: number\n params?: {\n errorMessage?: { ... }\n }\n } | {\n fact: \"has-leading-zeroes\"\n operator: \"equal\" | \"notAllowed\"\n value: boolean\n params?: {\n errorMessage?: { ... }\n }\n }>\n} | {\n not: {\n fact: \"numeric-value\" | \"total-length\"\n operator: \"equal\" | \"notEqual\" | \"lessThan\" | \"lessThanInclusive\" | \"greaterThan\" | \"greaterThanInclusive\"\n value: number\n params?: {\n errorMessage?: { ... }\n }\n } | {\n fact: \"integer-digits-count\"\n operator: \"equal\" | \"exactlyNDigits\" | \"minIntegerDigits\" | \"maxIntegerDigits\"\n value: number\n params?: {\n errorMessage?: { ... }\n allowLeadingZeroes?: { ... }\n }\n } | {\n fact: \"decimal-digits-count\"\n operator: \"equal\" | \"minDecimalDigits\" | \"maxDecimalDigits\"\n value: number\n params?: {\n errorMessage?: { ... }\n }\n } | {\n fact: \"has-leading-zeroes\"\n operator: \"equal\" | \"notAllowed\"\n value: boolean\n params?: {\n errorMessage?: { ... }\n }\n }\n}\n```\n\n### `NumericFactCondition`\n\nFact-based condition for numeric validation\n\n```ts\ntype NumericFactCondition = {\n fact: \"numeric-value\" | \"total-length\"\n operator: \"equal\" | \"notEqual\" | \"lessThan\" | \"lessThanInclusive\" | \"greaterThan\" | \"greaterThanInclusive\"\n value: number\n params?: {\n errorMessage?: string\n }\n} | {\n fact: \"integer-digits-count\"\n operator: \"equal\" | \"exactlyNDigits\" | \"minIntegerDigits\" | \"maxIntegerDigits\"\n value: number\n params?: {\n errorMessage?: string\n allowLeadingZeroes?: boolean\n }\n} | {\n fact: \"decimal-digits-count\"\n operator: \"equal\" | \"minDecimalDigits\" | \"maxDecimalDigits\"\n value: number\n params?: {\n errorMessage?: string\n }\n} | {\n fact: \"has-leading-zeroes\"\n operator: \"equal\" | \"notAllowed\"\n value: boolean\n params?: {\n errorMessage?: string\n }\n}\n```\n\n### `ComparisonRuleType`\n\nDeclarative validation rule (schema version v2). Supports predefined comparison operators\nover number, date and text inputs, with static, dynamic (context path), relative-date and\nexternal (app-provided) comparison values.\n\n\n```ts\ntype ComparisonRuleType = {\n input_type: \"number\" | \"date\" | \"text\"\n conditions: Array<{\n id: string\n operator: \"equal\" | \"notEqual\" | \"greaterThan\" | \"greaterThanInclusive\" | \"lessThan\" | \"lessThanInclusive\" | \"between\" | \"dateBefore\" | \"dateOnOrBefore\" | \"dateAfter\" | \"dateOnOrAfter\" | \"dateBetween\" | \"notInFuture\" | \"notInPast\" | \"contains\" | \"doesNotContain\" | \"startsWith\" | \"endsWith\" | \"regexMatch\" | \"lengthBetween\" | \"maxDigits\" | \"maxDecimals\"\n value: {\n source: { ... }\n data: { ... }\n } | {\n source: { ... }\n path: { ... }\n adjust?: { ... }\n } | {\n source: { ... }\n offset: { ... }\n unit: { ... }\n anchor?: { ... }\n } | {\n source: { ... }\n key: { ... }\n adjust?: { ... }\n } | {\n source: { ... }\n app_id: { ... }\n hook_id: { ... }\n result_id: { ... }\n adjust?: { ... }\n } | {\n source: { ... }\n min: { ... }\n max: { ... }\n } | {\n source: { ... }\n }\n error_message: string\n applies_when?: {\n path: { ... }\n operator: { ... }\n value?: { ... }\n }\n allow_failure?: boolean\n }>\n}\n```\n\n### `Condition`\n\nA single comparison the input value must satisfy.\n\n```ts\ntype Condition = {\n id: string\n operator: \"equal\" | \"notEqual\" | \"greaterThan\" | \"greaterThanInclusive\" | \"lessThan\" | \"lessThanInclusive\" | \"between\" | \"dateBefore\" | \"dateOnOrBefore\" | \"dateAfter\" | \"dateOnOrAfter\" | \"dateBetween\" | \"notInFuture\" | \"notInPast\" | \"contains\" | \"doesNotContain\" | \"startsWith\" | \"endsWith\" | \"regexMatch\" | \"lengthBetween\" | \"maxDigits\" | \"maxDecimals\"\n value: {\n source: \"static\"\n data: number | string | boolean\n } | {\n source: \"context\"\n path: string\n adjust?: {\n type: { ... }\n value: { ... }\n direction: { ... }\n rounding?: { ... }\n }\n } | {\n source: \"relative_date\"\n offset: number\n unit: \"days\" | \"months\" | \"years\"\n anchor?: \"today\"\n } | {\n source: \"environment\"\n key: string\n adjust?: {\n type: { ... }\n value: { ... }\n direction: { ... }\n rounding?: { ... }\n }\n } | {\n source: \"external\"\n app_id: string\n hook_id: string\n result_id: string\n adjust?: {\n type: { ... }\n value: { ... }\n direction: { ... }\n rounding?: { ... }\n }\n } | {\n source: \"range\"\n min: {\n source: { ... }\n data: { ... }\n } | {\n source: { ... }\n path: { ... }\n adjust?: { ... }\n } | {\n source: { ... }\n offset: { ... }\n unit: { ... }\n anchor?: { ... }\n } | {\n source: { ... }\n key: { ... }\n adjust?: { ... }\n } | {\n source: { ... }\n app_id: { ... }\n hook_id: { ... }\n result_id: { ... }\n adjust?: { ... }\n }\n max: {\n source: { ... }\n data: { ... }\n } | {\n source: { ... }\n path: { ... }\n adjust?: { ... }\n } | {\n source: { ... }\n offset: { ... }\n unit: { ... }\n anchor?: { ... }\n } | {\n source: { ... }\n key: { ... }\n adjust?: { ... }\n } | {\n source: { ... }\n app_id: { ... }\n hook_id: { ... }\n result_id: { ... }\n adjust?: { ... }\n }\n } | {\n source: \"none\"\n }\n error_message: string\n applies_when?: {\n path: string\n operator: \"equal\" | \"notEqual\" | \"greaterThan\" | \"greaterThanInclusive\" | \"lessThan\" | \"lessThanInclusive\" | \"isEmpty\" | \"isNotEmpty\"\n value?: number | string | boolean | {\n source: { ... }\n key: { ... }\n adjust?: { ... }\n }\n // ...\n}\n```\n\n### `Operator`\n\nPredefined comparison operator. Compatibility (enforced at write time):\n- number: equal, notEqual, greaterThan, greaterThanInclusive, lessThan, lessThanInclusive, between, regexMatch,\n maxDigits, maxDecimals\n- date: dateBefore, dateOnOrBefore, dateAfter, dateOnOrAfter, dateBetween, notInFuture, not\n\n```ts\ntype Operator = \"equal\" | \"notEqual\" | \"greaterThan\" | \"greaterThanInclusive\" | \"lessThan\" | \"lessThanInclusive\" | \"between\" | \"dateBefore\" | \"dateOnOrBefore\" | \"dateAfter\" | \"dateOnOrAfter\" | \"dateBetween\" | \"notInFuture\" | \"notInPast\" | \"contains\" | \"doesNotContain\" | \"startsWith\" | \"endsWith\" | \"regexMatch\" | \"lengthBetween\" | \"maxDigits\" | \"maxDecimals\"\n```\n\n### `ConditionValue`\n\nThe comparison value of a condition - a scalar, a range of scalars, or nothing (unary operators).\n\n```ts\ntype ConditionValue = {\n source: \"static\"\n data: number | string | boolean\n} | {\n source: \"context\"\n path: string\n adjust?: {\n type: \"percent\" | \"absolute\"\n value: number | {\n source: { ... }\n path: { ... }\n adjust?: { ... }\n } | {\n source: { ... }\n key: { ... }\n adjust?: { ... }\n }\n direction: \"increase\" | \"decrease\"\n rounding?: \"up\" | \"down\"\n }\n} | {\n source: \"relative_date\"\n offset: number\n unit: \"days\" | \"months\" | \"years\"\n anchor?: \"today\"\n} | {\n source: \"environment\"\n key: string\n adjust?: {\n type: \"percent\" | \"absolute\"\n value: number | {\n source: { ... }\n path: { ... }\n adjust?: { ... }\n } | {\n source: { ... }\n key: { ... }\n adjust?: { ... }\n }\n direction: \"increase\" | \"decrease\"\n rounding?: \"up\" | \"down\"\n }\n} | {\n source: \"external\"\n app_id: string\n hook_id: string\n result_id: string\n adjust?: {\n type: \"percent\" | \"absolute\"\n value: number | {\n source: { ... }\n path: { ... }\n adjust?: { ... }\n } | {\n source: { ... }\n key: { ... }\n adjust?: { ... }\n }\n direction: \"increase\" | \"decrease\"\n rounding?: \"up\" | \"down\"\n }\n} | {\n source: \"range\"\n min: {\n source: \"static\"\n data: number | string | boolean\n } | {\n source: \"context\"\n path: string\n adjust?: {\n type: { ... }\n value: { ... }\n direction: { ... }\n rounding?: { ... }\n }\n } | {\n source: \"relative_date\"\n offset: number\n unit: \"days\" | \"months\" | \"years\"\n anchor?: \"today\"\n } | {\n source: \"environment\"\n key: string\n adjust?: {\n type: { ... }\n value: { ... }\n direction: { ... }\n rounding?: { ... }\n }\n } | {\n source: \"external\"\n app_id: string\n hook_id: string\n result_id: string\n adjust?: {\n type: { ... }\n value: { ... }\n direction: { ... }\n rounding?: { ... }\n }\n // ...\n}\n```\n\n### `ScalarValue`\n\nA single comparison value - static, resolved from context, a relative date, an organisation environment variable, or an external value.\n\n```ts\ntype ScalarValue = {\n source: \"static\"\n data: number | string | boolean\n} | {\n source: \"context\"\n path: string\n adjust?: {\n type: \"percent\" | \"absolute\"\n value: number | {\n source: { ... }\n path: { ... }\n adjust?: { ... }\n } | {\n source: { ... }\n key: { ... }\n adjust?: { ... }\n }\n direction: \"increase\" | \"decrease\"\n rounding?: \"up\" | \"down\"\n }\n} | {\n source: \"relative_date\"\n offset: number\n unit: \"days\" | \"months\" | \"years\"\n anchor?: \"today\"\n} | {\n source: \"environment\"\n key: string\n adjust?: {\n type: \"percent\" | \"absolute\"\n value: number | {\n source: { ... }\n path: { ... }\n adjust?: { ... }\n } | {\n source: { ... }\n key: { ... }\n adjust?: { ... }\n }\n direction: \"increase\" | \"decrease\"\n rounding?: \"up\" | \"down\"\n }\n} | {\n source: \"external\"\n app_id: string\n hook_id: string\n result_id: string\n adjust?: {\n type: \"percent\" | \"absolute\"\n value: number | {\n source: { ... }\n path: { ... }\n adjust?: { ... }\n } | {\n source: { ... }\n key: { ... }\n adjust?: { ... }\n }\n direction: \"increase\" | \"decrease\"\n rounding?: \"up\" | \"down\"\n }\n}\n```\n\n### `EnvironmentValue`\n\nA comparison value resolved at evaluation time from an organisation environment variable\n(environments-api). The rule stores the key, never the value, so one change to the variable\nreaches every rule that references it and a blueprint install never overwrites the\norganisation's own value.\n\nOnly brow\n\n```ts\ntype EnvironmentValue = {\n source: \"environment\"\n key: string\n adjust?: {\n type: \"percent\" | \"absolute\"\n value: number | {\n source: { ... }\n path: { ... }\n adjust?: { ... }\n } | {\n source: { ... }\n key: { ... }\n adjust?: { ... }\n }\n direction: \"increase\" | \"decrease\"\n rounding?: \"up\" | \"down\"\n }\n}\n```\n\n### `ExternalValue`\n\nA comparison value produced at evaluation time by an External Values hook of an installed\napp (component type `EXTERNAL_VALUES`). The rule stores the reference only; the value is\nresolved server-side by the external-values-api, which executes the hook's HTTP call with\nthe app's credentials and retur\n\n```ts\ntype ExternalValue = {\n source: \"external\"\n app_id: string\n hook_id: string\n result_id: string\n adjust?: {\n type: \"percent\" | \"absolute\"\n value: number | {\n source: { ... }\n path: { ... }\n adjust?: { ... }\n } | {\n source: { ... }\n key: { ... }\n adjust?: { ... }\n }\n direction: \"increase\" | \"decrease\"\n rounding?: \"up\" | \"down\"\n }\n}\n```\n\n### `StaticValue`\n\nA fixed comparison value.\n\n```ts\ntype StaticValue = {\n source: \"static\"\n data: number | string | boolean\n}\n```\n\n### `ContextValue`\n\nA dynamic comparison value resolved from runtime context, e.g. `contract.installment_amount`\nor `previous_reading.value`. The first path segment must match the `name` of a declared\ncontext requirement.\n\n\n```ts\ntype ContextValue = {\n source: \"context\"\n path: string\n adjust?: {\n type: \"percent\" | \"absolute\"\n value: number | {\n source: { ... }\n path: { ... }\n adjust?: { ... }\n } | {\n source: { ... }\n key: { ... }\n adjust?: { ... }\n }\n direction: \"increase\" | \"decrease\"\n rounding?: \"up\" | \"down\"\n }\n}\n```\n\n### `ValueAdjustment`\n\nAdjusts a context-resolved numeric value before comparison, e.g. \"context value plus 10 percent\".\nUsed to express tolerance bands such as \"at most 10% above the current instalment amount\".\n\n\n```ts\ntype ValueAdjustment = {\n type: \"percent\" | \"absolute\"\n value: number | {\n source: \"context\"\n path: string\n adjust?: {\n type: { ... }\n value: { ... }\n direction: { ... }\n rounding?: { ... }\n }\n } | {\n source: \"environment\"\n key: string\n adjust?: {\n type: { ... }\n value: { ... }\n direction: { ... }\n rounding?: { ... }\n }\n }\n direction: \"increase\" | \"decrease\"\n rounding?: \"up\" | \"down\"\n}\n```\n\n### `RelativeDateValue`\n\nA date relative to the evaluation moment, e.g. \"today minus 30 days\". Only valid for date rules.\n\n```ts\ntype RelativeDateValue = {\n source: \"relative_date\"\n offset: number\n unit: \"days\" | \"months\" | \"years\"\n anchor?: \"today\"\n}\n```\n\n### `RangeValue`\n\nA lower and upper bound for range operators (between, dateBetween, lengthBetween). Bounds are inclusive.\n\n```ts\ntype RangeValue = {\n source: \"range\"\n min: {\n source: \"static\"\n data: number | string | boolean\n } | {\n source: \"context\"\n path: string\n adjust?: {\n type: { ... }\n value: { ... }\n direction: { ... }\n rounding?: { ... }\n }\n } | {\n source: \"relative_date\"\n offset: number\n unit: \"days\" | \"months\" | \"years\"\n anchor?: \"today\"\n } | {\n source: \"environment\"\n key: string\n adjust?: {\n type: { ... }\n value: { ... }\n direction: { ... }\n rounding?: { ... }\n }\n } | {\n source: \"external\"\n app_id: string\n hook_id: string\n result_id: string\n adjust?: {\n type: { ... }\n value: { ... }\n direction: { ... }\n rounding?: { ... }\n }\n }\n max: {\n source: \"static\"\n data: number | string | boolean\n } | {\n source: \"context\"\n path: string\n adjust?: {\n type: { ... }\n value: { ... }\n direction: { ... }\n rounding?: { ... }\n }\n } | {\n source: \"relative_date\"\n offset: number\n unit: \"days\" | \"months\" | \"years\"\n anchor?: \"today\"\n } | {\n source: \"environment\"\n key: string\n adjust?: {\n type: { ... }\n value: { ... }\n direction: { ... }\n rounding?: { ... }\n }\n } | {\n source: \"external\"\n app_id: string\n hook_id: string\n result_id: string\n adjust?: {\n type: { ... }\n value: { ... }\n direction: { ... }\n rounding?: { ... }\n }\n }\n}\n```\n\n### `NoValue`\n\nNo comparison value - used by unary operators such as notInFuture / notInPast.\n\n```ts\ntype NoValue = {\n source: \"none\"\n}\n```\n\n### `DocumentRuleType`\n\nDeclarative validation rule (schema version v2) for uploaded files. The rule declares how deep\nthe check goes (`check.level`) and the conditions the file must satisfy. Property conditions\n(file type, size, page count, image resolution, blank and password-protected detection) are\ndeterministic and av\n\n```ts\ntype DocumentRuleType = {\n input_type: \"document\"\n check: {\n level: \"basic\" | \"standard\" | \"advanced\"\n }\n conditions: Array<{\n id: string\n operator: \"fileTypeIn\" | \"sizeBelow\" | \"pageCountBetween\" | \"resolutionAtLeast\" | \"resolutionAtMost\" | \"isNotBlank\" | \"isNotPasswordProtected\" | \"meetsCriteria\"\n value: {\n source: { ... }\n data: { ... }\n } | {\n source: { ... }\n min: { ... }\n max: { ... }\n } | {\n source: { ... }\n } | {\n source: { ... }\n types: { ... }\n } | {\n source: { ... }\n text: { ... }\n }\n error_message: string\n allow_failure?: boolean\n }>\n}\n```\n\n### `DocumentCheck`\n\nHow deep the document check goes. Levels are cumulative.\n\n```ts\ntype DocumentCheck = {\n level: \"basic\" | \"standard\" | \"advanced\"\n}\n```\n\n### `DocumentCondition`\n\nA single check the uploaded file must satisfy.\n\n```ts\ntype DocumentCondition = {\n id: string\n operator: \"fileTypeIn\" | \"sizeBelow\" | \"pageCountBetween\" | \"resolutionAtLeast\" | \"resolutionAtMost\" | \"isNotBlank\" | \"isNotPasswordProtected\" | \"meetsCriteria\"\n value: {\n source: \"static\"\n data: number | string | boolean\n } | {\n source: \"range\"\n min: {\n source: { ... }\n data: { ... }\n } | {\n source: { ... }\n path: { ... }\n adjust?: { ... }\n } | {\n source: { ... }\n offset: { ... }\n unit: { ... }\n anchor?: { ... }\n } | {\n source: { ... }\n key: { ... }\n adjust?: { ... }\n } | {\n source: { ... }\n app_id: { ... }\n hook_id: { ... }\n result_id: { ... }\n adjust?: { ... }\n }\n max: {\n source: { ... }\n data: { ... }\n } | {\n source: { ... }\n path: { ... }\n adjust?: { ... }\n } | {\n source: { ... }\n offset: { ... }\n unit: { ... }\n anchor?: { ... }\n } | {\n source: { ... }\n key: { ... }\n adjust?: { ... }\n } | {\n source: { ... }\n app_id: { ... }\n hook_id: { ... }\n result_id: { ... }\n adjust?: { ... }\n }\n } | {\n source: \"none\"\n } | {\n source: \"file_types\"\n types: \"image\" | \"pdf\" | \"document\" | \"spreadsheet\"[]\n } | {\n source: \"criteria\"\n text: string\n }\n error_message: string\n allow_failure?: boolean\n}\n```\n\n### `DocumentOperator`\n\nPredefined document check. Value compatibility (enforced at write time):\n- fileTypeIn: `file_types` value listing the accepted file categories.\n- sizeBelow: static integer, maximum file size in bytes.\n- pageCountBetween: `range` of static non-negative integers (inclusive).\n- resolutionAtLeast / reso\n\n```ts\ntype DocumentOperator = \"fileTypeIn\" | \"sizeBelow\" | \"pageCountBetween\" | \"resolutionAtLeast\" | \"resolutionAtMost\" | \"isNotBlank\" | \"isNotPasswordProtected\" | \"meetsCriteria\"\n```\n\n### `DocumentConditionValue`\n\nThe value of a document condition - a static scalar, a range, nothing, accepted file categories, or a free-text criterion.\n\n```ts\ntype DocumentConditionValue = {\n source: \"static\"\n data: number | string | boolean\n} | {\n source: \"range\"\n min: {\n source: \"static\"\n data: number | string | boolean\n } | {\n source: \"context\"\n path: string\n adjust?: {\n type: { ... }\n value: { ... }\n direction: { ... }\n rounding?: { ... }\n }\n } | {\n source: \"relative_date\"\n offset: number\n unit: \"days\" | \"months\" | \"years\"\n anchor?: \"today\"\n } | {\n source: \"environment\"\n key: string\n adjust?: {\n type: { ... }\n value: { ... }\n direction: { ... }\n rounding?: { ... }\n }\n } | {\n source: \"external\"\n app_id: string\n hook_id: string\n result_id: string\n adjust?: {\n type: { ... }\n value: { ... }\n direction: { ... }\n rounding?: { ... }\n }\n }\n max: {\n source: \"static\"\n data: number | string | boolean\n } | {\n source: \"context\"\n path: string\n adjust?: {\n type: { ... }\n value: { ... }\n direction: { ... }\n rounding?: { ... }\n }\n } | {\n source: \"relative_date\"\n offset: number\n unit: \"days\" | \"months\" | \"years\"\n anchor?: \"today\"\n } | {\n source: \"environment\"\n key: string\n adjust?: {\n type: { ... }\n value: { ... }\n direction: { ... }\n rounding?: { ... }\n }\n } | {\n source: \"external\"\n app_id: string\n hook_id: string\n result_id: string\n adjust?: {\n type: { ... }\n value: { ... }\n direction: { ... }\n rounding?: { ... }\n }\n }\n} | {\n source: \"none\"\n} | {\n source: \"file_types\"\n types: \"image\" | \"pdf\" | \"document\" | \"spreadsheet\"[]\n} | {\n source: \"criteria\"\n text: string\n}\n```\n\n### `FileTypesValue`\n\nAccepted file categories; each category expands to a mime list at check time.\n\n```ts\ntype FileTypesValue = {\n source: \"file_types\"\n types: \"image\" | \"pdf\" | \"document\" | \"spreadsheet\"[]\n}\n```\n\n### `CriteriaValue`\n\nA free-text requirement judged against the document's extracted content, e.g. \"The photo shows\nan electricity meter with the meter number and the current reading fully legible.\" One requirement\nper condition keeps results specific and lets each requirement be graded separately.\n\n\n```ts\ntype CriteriaValue = {\n source: \"criteria\"\n text: string\n}\n```\n\n### `AppliesWhen`\n\nOptional precondition on a condition: the condition only takes part in the\nvalidation when this comparison over context holds. Examples: apply the\ndual-tariff reference only when `contract.htnt` is not empty, or run a\nplausibility check only when a context value reaches a threshold.\n\n\n```ts\ntype AppliesWhen = {\n path: string\n operator: \"equal\" | \"notEqual\" | \"greaterThan\" | \"greaterThanInclusive\" | \"lessThan\" | \"lessThanInclusive\" | \"isEmpty\" | \"isNotEmpty\"\n value?: number | string | boolean | {\n source: \"environment\"\n key: string\n adjust?: {\n type: { ... }\n value: { ... }\n direction: { ... }\n rounding?: { ... }\n }\n }\n}\n```\n\n### `ContextRequirement`\n\nAn entity context source the rule needs at evaluation time, referenced by `context`\nvalue paths via the schema slug as their first segment (e.g. `contract.installment_amount`).\nHow the source is resolved (which entity instance) is decided by the consuming surface,\nnot by the rule. Meter reading comp\n\n```ts\ntype ContextRequirement = {\n schema: string\n}\n```\n" +"# Validation Rules API\n\n- **Base URL:** `https://validation-rules.sls.epilot.io`\n- **Full API Docs:** [https://docs.epilot.io/api/validation-rules](https://docs.epilot.io/api/validation-rules)\n\n## Usage\n\n```ts\nimport { epilot } from '@epilot/sdk'\n\nepilot.authorize(() => '')\nconst { data } = await epilot.validationRules.getValidationRules(...)\n```\n\n### Tree-shakeable import\n\n```ts\nimport { getClient, authorize } from '@epilot/sdk/validation-rules'\n\nconst validationRulesClient = getClient()\nauthorize(validationRulesClient, () => '')\nconst { data } = await validationRulesClient.getValidationRules(...)\n```\n\n## Operations\n\n**Validation Rules**\n- [`getValidationRules`](#getvalidationrules)\n- [`createValidationRule`](#createvalidationrule)\n- [`getValidationRuleById`](#getvalidationrulebyid)\n- [`updateValidationRule`](#updatevalidationrule)\n- [`deleteValidationRule`](#deletevalidationrule)\n- [`addUsedByReference`](#addusedbyreference)\n- [`removeUsedByReference`](#removeusedbyreference)\n\n**Schemas**\n- [`GetValidationRulesResponse`](#getvalidationrulesresponse)\n- [`CreateValidationRuleRequest`](#createvalidationrulerequest)\n- [`UpdateValidationRuleRequest`](#updatevalidationrulerequest)\n- [`ValidationRuleBase`](#validationrulebase)\n- [`ValidationRule`](#validationrule)\n- [`UsedBy`](#usedby)\n- [`RegexRuleType`](#regexruletype)\n- [`PatternRuleType`](#patternruletype)\n- [`NumericRuleType`](#numericruletype)\n- [`RegexCondition`](#regexcondition)\n- [`RegexNestedCondition`](#regexnestedcondition)\n- [`RegexFactCondition`](#regexfactcondition)\n- [`PatternCondition`](#patterncondition)\n- [`PatternNestedCondition`](#patternnestedcondition)\n- [`PatternFactCondition`](#patternfactcondition)\n- [`NumericCondition`](#numericcondition)\n- [`NumericNestedCondition`](#numericnestedcondition)\n- [`NumericFactCondition`](#numericfactcondition)\n- [`ComparisonRuleType`](#comparisonruletype)\n- [`Condition`](#condition)\n- [`Operator`](#operator)\n- [`ConditionValue`](#conditionvalue)\n- [`ScalarValue`](#scalarvalue)\n- [`EnvironmentValue`](#environmentvalue)\n- [`ExternalValue`](#externalvalue)\n- [`StaticValue`](#staticvalue)\n- [`ContextValue`](#contextvalue)\n- [`ValueAdjustment`](#valueadjustment)\n- [`RelativeDateValue`](#relativedatevalue)\n- [`BillingCycleAnchor`](#billingcycleanchor)\n- [`AnchorScalar`](#anchorscalar)\n- [`RangeValue`](#rangevalue)\n- [`NoValue`](#novalue)\n- [`DocumentRuleType`](#documentruletype)\n- [`DocumentCheck`](#documentcheck)\n- [`DocumentCondition`](#documentcondition)\n- [`DocumentOperator`](#documentoperator)\n- [`DocumentConditionValue`](#documentconditionvalue)\n- [`FileTypesValue`](#filetypesvalue)\n- [`CriteriaValue`](#criteriavalue)\n- [`AppliesWhen`](#applieswhen)\n- [`ContextRequirement`](#contextrequirement)\n\n### `getValidationRules`\n\nReturns all validation rules belonging to the authenticated user's organization.\n\n`GET /v1/validation-rules`\n\n```ts\nconst { data } = await client.getValidationRules()\n```\n\n
\nResponse\n\n```json\n{\n \"results\": [\n {\n \"title\": \"string\",\n \"placeholder\": \"string\",\n \"used_by\": [\n {\n \"type\": \"journey\",\n \"source_id\": \"journey-xyz789\"\n }\n ],\n \"rule\": {\n \"type\": \"regex\",\n \"conditions\": {\n \"all\": [\n {\n \"fact\": \"inputValue\",\n \"operator\": \"regexMatch\",\n \"value\": \"string\",\n \"params\": {\n \"errorMessage\": \"string\"\n }\n },\n {\n \"all\": [\n {\n \"fact\": \"inputValue\",\n \"operator\": \"regexMatch\",\n \"value\": \"string\",\n \"params\": {\n \"errorMessage\": \"string\"\n }\n }\n ]\n }\n ]\n }\n },\n \"contexts\": [\n {\n \"schema\": \"contract\"\n }\n ],\n \"_schema_version\": \"string\",\n \"_id\": \"string\",\n \"_organization_id\": \"string\",\n \"created_at\": \"string\",\n \"updated_at\": \"string\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n }\n ]\n}\n```\n\n
\n\n---\n\n### `createValidationRule`\n\nCreates a new validation rule for the authenticated organization.\n\n`POST /v1/validation-rules`\n\n```ts\nconst { data } = await client.createValidationRule(\n null,\n {\n title: 'string',\n placeholder: 'string',\n used_by: [\n {\n type: 'journey',\n source_id: 'journey-xyz789'\n }\n ],\n rule: {\n type: 'regex',\n conditions: {\n all: [\n {\n fact: 'inputValue',\n operator: 'regexMatch',\n value: 'string',\n params: {\n errorMessage: 'string'\n }\n },\n {\n all: [\n {\n fact: 'inputValue',\n operator: 'regexMatch',\n value: 'string',\n params: {\n errorMessage: 'string'\n }\n }\n ]\n }\n ]\n }\n },\n contexts: [\n {\n schema: 'contract'\n }\n ]\n },\n)\n```\n\n
\nResponse\n\n```json\n{\n \"title\": \"string\",\n \"placeholder\": \"string\",\n \"used_by\": [\n {\n \"type\": \"journey\",\n \"source_id\": \"journey-xyz789\"\n }\n ],\n \"rule\": {\n \"type\": \"regex\",\n \"conditions\": {\n \"all\": [\n {\n \"fact\": \"inputValue\",\n \"operator\": \"regexMatch\",\n \"value\": \"string\",\n \"params\": {\n \"errorMessage\": \"string\"\n }\n },\n {\n \"all\": [\n {\n \"fact\": \"inputValue\",\n \"operator\": \"regexMatch\",\n \"value\": \"string\",\n \"params\": {\n \"errorMessage\": \"string\"\n }\n }\n ]\n }\n ]\n }\n },\n \"contexts\": [\n {\n \"schema\": \"contract\"\n }\n ],\n \"_schema_version\": \"string\",\n \"_id\": \"string\",\n \"_organization_id\": \"string\",\n \"created_at\": \"string\",\n \"updated_at\": \"string\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n}\n```\n\n
\n\n---\n\n### `getValidationRuleById`\n\nRetrieves a specific validation rule by its unique ID.\n\n`GET /v1/validation-rules/{ruleId}`\n\n```ts\nconst { data } = await client.getValidationRuleById({\n ruleId: 'example',\n})\n```\n\n
\nResponse\n\n```json\n{\n \"title\": \"string\",\n \"placeholder\": \"string\",\n \"used_by\": [\n {\n \"type\": \"journey\",\n \"source_id\": \"journey-xyz789\"\n }\n ],\n \"rule\": {\n \"type\": \"regex\",\n \"conditions\": {\n \"all\": [\n {\n \"fact\": \"inputValue\",\n \"operator\": \"regexMatch\",\n \"value\": \"string\",\n \"params\": {\n \"errorMessage\": \"string\"\n }\n },\n {\n \"all\": [\n {\n \"fact\": \"inputValue\",\n \"operator\": \"regexMatch\",\n \"value\": \"string\",\n \"params\": {\n \"errorMessage\": \"string\"\n }\n }\n ]\n }\n ]\n }\n },\n \"contexts\": [\n {\n \"schema\": \"contract\"\n }\n ],\n \"_schema_version\": \"string\",\n \"_id\": \"string\",\n \"_organization_id\": \"string\",\n \"created_at\": \"string\",\n \"updated_at\": \"string\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n}\n```\n\n
\n\n---\n\n### `updateValidationRule`\n\nPartially updates an existing validation rule by ID. Only the fields provided in the request body are updated.\n\n`PATCH /v1/validation-rules/{ruleId}`\n\n```ts\nconst { data } = await client.updateValidationRule(\n {\n ruleId: 'example',\n },\n {\n title: 'string',\n placeholder: 'string',\n used_by: [\n {\n type: 'journey',\n source_id: 'journey-xyz789'\n }\n ],\n rule: {\n type: 'regex',\n conditions: {\n all: [\n {\n fact: 'inputValue',\n operator: 'regexMatch',\n value: 'string',\n params: {\n errorMessage: 'string'\n }\n },\n {\n all: [\n {\n fact: 'inputValue',\n operator: 'regexMatch',\n value: 'string',\n params: {\n errorMessage: 'string'\n }\n }\n ]\n }\n ]\n }\n },\n contexts: [\n {\n schema: 'contract'\n }\n ]\n },\n)\n```\n\n
\nResponse\n\n```json\n{\n \"title\": \"string\",\n \"placeholder\": \"string\",\n \"used_by\": [\n {\n \"type\": \"journey\",\n \"source_id\": \"journey-xyz789\"\n }\n ],\n \"rule\": {\n \"type\": \"regex\",\n \"conditions\": {\n \"all\": [\n {\n \"fact\": \"inputValue\",\n \"operator\": \"regexMatch\",\n \"value\": \"string\",\n \"params\": {\n \"errorMessage\": \"string\"\n }\n },\n {\n \"all\": [\n {\n \"fact\": \"inputValue\",\n \"operator\": \"regexMatch\",\n \"value\": \"string\",\n \"params\": {\n \"errorMessage\": \"string\"\n }\n }\n ]\n }\n ]\n }\n },\n \"contexts\": [\n {\n \"schema\": \"contract\"\n }\n ],\n \"_schema_version\": \"string\",\n \"_id\": \"string\",\n \"_organization_id\": \"string\",\n \"created_at\": \"string\",\n \"updated_at\": \"string\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n}\n```\n\n
\n\n---\n\n### `deleteValidationRule`\n\nPermanently deletes a validation rule by ID. Any journeys or entity attributes referencing this rule should be updated before deletion.\n\n`DELETE /v1/validation-rules/{ruleId}`\n\n```ts\nconst { data } = await client.deleteValidationRule({\n ruleId: 'example',\n})\n```\n\n---\n\n### `addUsedByReference`\n\nAdds a single `used_by` reference to an existing validation rule.\n\n`POST /v1/validation-rules/{ruleId}/used-by`\n\n```ts\nconst { data } = await client.addUsedByReference(\n {\n ruleId: 'example',\n },\n {\n type: 'journey',\n source_id: 'journey-xyz789'\n },\n)\n```\n\n
\nResponse\n\n```json\n{\n \"title\": \"string\",\n \"placeholder\": \"string\",\n \"used_by\": [\n {\n \"type\": \"journey\",\n \"source_id\": \"journey-xyz789\"\n }\n ],\n \"rule\": {\n \"type\": \"regex\",\n \"conditions\": {\n \"all\": [\n {\n \"fact\": \"inputValue\",\n \"operator\": \"regexMatch\",\n \"value\": \"string\",\n \"params\": {\n \"errorMessage\": \"string\"\n }\n },\n {\n \"all\": [\n {\n \"fact\": \"inputValue\",\n \"operator\": \"regexMatch\",\n \"value\": \"string\",\n \"params\": {\n \"errorMessage\": \"string\"\n }\n }\n ]\n }\n ]\n }\n },\n \"contexts\": [\n {\n \"schema\": \"contract\"\n }\n ],\n \"_schema_version\": \"string\",\n \"_id\": \"string\",\n \"_organization_id\": \"string\",\n \"created_at\": \"string\",\n \"updated_at\": \"string\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n}\n```\n\n
\n\n---\n\n### `removeUsedByReference`\n\nRemoves a specific `used_by` reference from an existing validation rule.\n\n`DELETE /v1/validation-rules/{ruleId}/used-by`\n\n```ts\nconst { data } = await client.removeUsedByReference(\n {\n ruleId: 'example',\n },\n {\n type: 'journey',\n source_id: 'journey-xyz789'\n },\n)\n```\n\n
\nResponse\n\n```json\n{\n \"title\": \"string\",\n \"placeholder\": \"string\",\n \"used_by\": [\n {\n \"type\": \"journey\",\n \"source_id\": \"journey-xyz789\"\n }\n ],\n \"rule\": {\n \"type\": \"regex\",\n \"conditions\": {\n \"all\": [\n {\n \"fact\": \"inputValue\",\n \"operator\": \"regexMatch\",\n \"value\": \"string\",\n \"params\": {\n \"errorMessage\": \"string\"\n }\n },\n {\n \"all\": [\n {\n \"fact\": \"inputValue\",\n \"operator\": \"regexMatch\",\n \"value\": \"string\",\n \"params\": {\n \"errorMessage\": \"string\"\n }\n }\n ]\n }\n ]\n }\n },\n \"contexts\": [\n {\n \"schema\": \"contract\"\n }\n ],\n \"_schema_version\": \"string\",\n \"_id\": \"string\",\n \"_organization_id\": \"string\",\n \"created_at\": \"string\",\n \"updated_at\": \"string\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n}\n```\n\n
\n\n---\n\n## Schemas\n\n### `GetValidationRulesResponse`\n\nResponse envelope for listing all validation rules within an organization.\n\n```ts\ntype GetValidationRulesResponse = {\n results?: Array<{\n _schema_version: string\n _id: string\n _organization_id: string\n created_at: string\n updated_at: string\n created_by: string\n updated_by: string\n }>\n}\n```\n\n### `CreateValidationRuleRequest`\n\n```ts\ntype CreateValidationRuleRequest = object\n```\n\n### `UpdateValidationRuleRequest`\n\n```ts\ntype UpdateValidationRuleRequest = {\n title?: string\n placeholder?: string\n used_by?: Array<{\n type: \"journey\" | \"entity\"\n schema_slug?: string\n source_id?: string\n }>\n rule?: {\n type: \"regex\"\n conditions: {\n all: { ... }\n } | {\n any: { ... }\n } | {\n not: { ... }\n }\n } | {\n type: \"pattern\"\n conditions: {\n all: { ... }\n } | {\n any: { ... }\n } | {\n not: { ... }\n }\n } | {\n type: \"numeric\"\n conditions: {\n all: { ... }\n } | {\n any: { ... }\n } | {\n not: { ... }\n }\n } | {\n input_type: \"number\" | \"date\" | \"text\"\n conditions: Array<{\n id: { ... }\n operator: { ... }\n value: { ... }\n error_message: { ... }\n applies_when?: { ... }\n allow_failure?: { ... }\n }>\n } | {\n input_type: \"document\"\n check: {\n level: { ... }\n }\n conditions: Array<{\n id: { ... }\n operator: { ... }\n value: { ... }\n error_message: { ... }\n allow_failure?: { ... }\n }>\n }\n contexts?: Array<{\n schema: string\n }>\n}\n```\n\n### `ValidationRuleBase`\n\n```ts\ntype ValidationRuleBase = {\n title?: string\n placeholder?: string\n used_by?: Array<{\n type: \"journey\" | \"entity\"\n schema_slug?: string\n source_id?: string\n }>\n rule?: {\n type: \"regex\"\n conditions: {\n all: { ... }\n } | {\n any: { ... }\n } | {\n not: { ... }\n }\n } | {\n type: \"pattern\"\n conditions: {\n all: { ... }\n } | {\n any: { ... }\n } | {\n not: { ... }\n }\n } | {\n type: \"numeric\"\n conditions: {\n all: { ... }\n } | {\n any: { ... }\n } | {\n not: { ... }\n }\n } | {\n input_type: \"number\" | \"date\" | \"text\"\n conditions: Array<{\n id: { ... }\n operator: { ... }\n value: { ... }\n error_message: { ... }\n applies_when?: { ... }\n allow_failure?: { ... }\n }>\n } | {\n input_type: \"document\"\n check: {\n level: { ... }\n }\n conditions: Array<{\n id: { ... }\n operator: { ... }\n value: { ... }\n error_message: { ... }\n allow_failure?: { ... }\n }>\n }\n contexts?: Array<{\n schema: string\n }>\n}\n```\n\n### `ValidationRule`\n\nThe Validation rule definition.\n\n```ts\ntype ValidationRule = {\n _schema_version: string\n _id: string\n _organization_id: string\n created_at: string\n updated_at: string\n created_by: string\n updated_by: string\n}\n```\n\n### `UsedBy`\n\nDescribes where and how a validation rule is applied. Used to track associations between rules and the journeys or entity schemas that reference them.\n\n```ts\ntype UsedBy = {\n type: \"journey\" | \"entity\"\n schema_slug?: string\n source_id?: string\n}\n```\n\n### `RegexRuleType`\n\nValidation rule that uses a regular expression to validate input.\n\n```ts\ntype RegexRuleType = {\n type: \"regex\"\n conditions: {\n all: Array<{\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n all: { ... }\n } | {\n any: { ... }\n } | {\n not: { ... }\n }>\n } | {\n any: Array<{\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n all: { ... }\n } | {\n any: { ... }\n } | {\n not: { ... }\n }>\n } | {\n not: {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n all: { ... }\n } | {\n any: { ... }\n } | {\n not: { ... }\n }\n }\n}\n```\n\n### `PatternRuleType`\n\nValidation rule that uses a sequence of patterns to validate input.\n\n```ts\ntype PatternRuleType = {\n type: \"pattern\"\n conditions: {\n all: Array<{\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n all: { ... }\n } | {\n any: { ... }\n } | {\n not: { ... }\n }>\n } | {\n any: Array<{\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n all: { ... }\n } | {\n any: { ... }\n } | {\n not: { ... }\n }>\n } | {\n not: {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n all: { ... }\n } | {\n any: { ... }\n } | {\n not: { ... }\n }\n }\n}\n```\n\n### `NumericRuleType`\n\nValidation rule for numeric values, supporting range and digit count constraints.\n\n```ts\ntype NumericRuleType = {\n type: \"numeric\"\n conditions: {\n all: Array<{\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n all: { ... }\n } | {\n any: { ... }\n } | {\n not: { ... }\n }>\n } | {\n any: Array<{\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n all: { ... }\n } | {\n any: { ... }\n } | {\n not: { ... }\n }>\n } | {\n not: {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n all: { ... }\n } | {\n any: { ... }\n } | {\n not: { ... }\n }\n }\n}\n```\n\n### `RegexCondition`\n\nCondition definition for a regex-based validation rule (2 levels deep)\n\n```ts\ntype RegexCondition = {\n all: Array<{\n fact: \"inputValue\"\n operator: \"regexMatch\"\n value: string\n params?: {\n errorMessage?: { ... }\n }\n } | {\n all: Array<{\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n }>\n } | {\n any: Array<{\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n }>\n } | {\n not: {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n }\n }>\n} | {\n any: Array<{\n fact: \"inputValue\"\n operator: \"regexMatch\"\n value: string\n params?: {\n errorMessage?: { ... }\n }\n } | {\n all: Array<{\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n }>\n } | {\n any: Array<{\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n }>\n } | {\n not: {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n }\n }>\n} | {\n not: {\n fact: \"inputValue\"\n operator: \"regexMatch\"\n value: string\n params?: {\n errorMessage?: { ... }\n }\n } | {\n all: Array<{\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n }>\n } | {\n any: Array<{\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n }>\n } | {\n not: {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n }\n }\n}\n```\n\n### `RegexNestedCondition`\n\nNested condition with logical operators (level 2 only)\n\n```ts\ntype RegexNestedCondition = {\n all: Array<{\n fact: \"inputValue\"\n operator: \"regexMatch\"\n value: string\n params?: {\n errorMessage?: { ... }\n }\n }>\n} | {\n any: Array<{\n fact: \"inputValue\"\n operator: \"regexMatch\"\n value: string\n params?: {\n errorMessage?: { ... }\n }\n }>\n} | {\n not: {\n fact: \"inputValue\"\n operator: \"regexMatch\"\n value: string\n params?: {\n errorMessage?: { ... }\n }\n }\n}\n```\n\n### `RegexFactCondition`\n\nFact-based condition for regex validation\n\n```ts\ntype RegexFactCondition = {\n fact: \"inputValue\"\n operator: \"regexMatch\"\n value: string\n params?: {\n errorMessage?: string\n }\n}\n```\n\n### `PatternCondition`\n\nCondition definition for a pattern-based validation rule (2 levels deep)\n\n```ts\ntype PatternCondition = {\n all: Array<{\n fact: \"total-length\"\n operator: \"equal\" | \"notEqual\" | \"lessThan\" | \"lessThanInclusive\" | \"greaterThan\" | \"greaterThanInclusive\"\n value: number\n params?: {\n errorMessage?: { ... }\n start?: { ... }\n end?: { ... }\n }\n } | {\n fact: \"static-check\" | \"total-length\"\n operator: \"exactlyNDigits\"\n value: number\n params?: {\n errorMessage?: { ... }\n start?: { ... }\n end?: { ... }\n }\n } | {\n fact: \"static-check\"\n operator: \"in\" | \"notIn\" | \"contains\" | \"doesNotContain\"\n value: string[]\n params?: {\n errorMessage?: { ... }\n start?: { ... }\n end?: { ... }\n }\n } | {\n fact: \"static-check\"\n operator: \"equal\" | \"notEqual\"\n value: string\n params?: {\n errorMessage?: { ... }\n start?: { ... }\n end?: { ... }\n }\n } | {\n all: Array<{\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n }>\n } | {\n any: Array<{\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n }>\n } | {\n not: {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n fact: { ... }\n operator: { ... }\n // ...\n}\n```\n\n### `PatternNestedCondition`\n\nNested condition with logical operators (level 2 only)\n\n```ts\ntype PatternNestedCondition = {\n all: Array<{\n fact: \"total-length\"\n operator: \"equal\" | \"notEqual\" | \"lessThan\" | \"lessThanInclusive\" | \"greaterThan\" | \"greaterThanInclusive\"\n value: number\n params?: {\n errorMessage?: { ... }\n start?: { ... }\n end?: { ... }\n }\n } | {\n fact: \"static-check\" | \"total-length\"\n operator: \"exactlyNDigits\"\n value: number\n params?: {\n errorMessage?: { ... }\n start?: { ... }\n end?: { ... }\n }\n } | {\n fact: \"static-check\"\n operator: \"in\" | \"notIn\" | \"contains\" | \"doesNotContain\"\n value: string[]\n params?: {\n errorMessage?: { ... }\n start?: { ... }\n end?: { ... }\n }\n } | {\n fact: \"static-check\"\n operator: \"equal\" | \"notEqual\"\n value: string\n params?: {\n errorMessage?: { ... }\n start?: { ... }\n end?: { ... }\n }\n }>\n} | {\n any: Array<{\n fact: \"total-length\"\n operator: \"equal\" | \"notEqual\" | \"lessThan\" | \"lessThanInclusive\" | \"greaterThan\" | \"greaterThanInclusive\"\n value: number\n params?: {\n errorMessage?: { ... }\n start?: { ... }\n end?: { ... }\n }\n } | {\n fact: \"static-check\" | \"total-length\"\n operator: \"exactlyNDigits\"\n value: number\n params?: {\n errorMessage?: { ... }\n start?: { ... }\n end?: { ... }\n }\n } | {\n fact: \"static-check\"\n operator: \"in\" | \"notIn\" | \"contains\" | \"doesNotContain\"\n value: string[]\n params?: {\n errorMessage?: { ... }\n start?: { ... }\n end?: { ... }\n }\n } | {\n fact: \"static-check\"\n operator: \"equal\" | \"notEqual\"\n value: string\n params?: {\n errorMessage?: { ... }\n start?: { ... }\n end?: { ... }\n }\n }>\n} | {\n not: {\n fact: \"total-length\"\n operator: \"equal\" | \"notEqual\" | \"lessThan\" | \"lessThanInclusive\" | \"greaterThan\" | \"greaterThanInclusive\"\n value: number\n params?: {\n errorMessage?: { ... }\n start?: { ... }\n end?: { ... }\n }\n } | {\n fact: \"static-check\" | \"total-length\"\n operator: \"exactlyNDigits\"\n value: number\n params?: {\n errorMessage?: { ... }\n start?: { ... }\n end?: { ... }\n }\n } | {\n fact: \"static-check\"\n operator: \"in\" | \"notIn\" | \"contains\" | \"doesNotContain\"\n value: string[]\n params?: {\n // ...\n}\n```\n\n### `PatternFactCondition`\n\nFact-based condition for pattern validation\n\n```ts\ntype PatternFactCondition = {\n fact: \"total-length\"\n operator: \"equal\" | \"notEqual\" | \"lessThan\" | \"lessThanInclusive\" | \"greaterThan\" | \"greaterThanInclusive\"\n value: number\n params?: {\n errorMessage?: string\n start?: number\n end?: number\n }\n} | {\n fact: \"static-check\" | \"total-length\"\n operator: \"exactlyNDigits\"\n value: number\n params?: {\n errorMessage?: string\n start?: number\n end?: number\n }\n} | {\n fact: \"static-check\"\n operator: \"in\" | \"notIn\" | \"contains\" | \"doesNotContain\"\n value: string[]\n params?: {\n errorMessage?: string\n start?: number\n end?: number\n }\n} | {\n fact: \"static-check\"\n operator: \"equal\" | \"notEqual\"\n value: string\n params?: {\n errorMessage?: string\n start?: number\n end?: number\n }\n}\n```\n\n### `NumericCondition`\n\nCondition definition for a numeric-based validation rule (2 levels deep)\n\n```ts\ntype NumericCondition = {\n all: Array<{\n fact: \"numeric-value\" | \"total-length\"\n operator: \"equal\" | \"notEqual\" | \"lessThan\" | \"lessThanInclusive\" | \"greaterThan\" | \"greaterThanInclusive\"\n value: number\n params?: {\n errorMessage?: { ... }\n }\n } | {\n fact: \"integer-digits-count\"\n operator: \"equal\" | \"exactlyNDigits\" | \"minIntegerDigits\" | \"maxIntegerDigits\"\n value: number\n params?: {\n errorMessage?: { ... }\n allowLeadingZeroes?: { ... }\n }\n } | {\n fact: \"decimal-digits-count\"\n operator: \"equal\" | \"minDecimalDigits\" | \"maxDecimalDigits\"\n value: number\n params?: {\n errorMessage?: { ... }\n }\n } | {\n fact: \"has-leading-zeroes\"\n operator: \"equal\" | \"notAllowed\"\n value: boolean\n params?: {\n errorMessage?: { ... }\n }\n } | {\n all: Array<{\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n }>\n } | {\n any: Array<{\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n }>\n } | {\n not: {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n } | {\n fact: { ... }\n operator: { ... }\n value: { ... }\n params?: { ... }\n }\n }>\n} | {\n any: Array<{\n fact: \"numeric-value\" | \"total-length\"\n // ...\n}\n```\n\n### `NumericNestedCondition`\n\nNested condition with logical operators (level 2 only)\n\n```ts\ntype NumericNestedCondition = {\n all: Array<{\n fact: \"numeric-value\" | \"total-length\"\n operator: \"equal\" | \"notEqual\" | \"lessThan\" | \"lessThanInclusive\" | \"greaterThan\" | \"greaterThanInclusive\"\n value: number\n params?: {\n errorMessage?: { ... }\n }\n } | {\n fact: \"integer-digits-count\"\n operator: \"equal\" | \"exactlyNDigits\" | \"minIntegerDigits\" | \"maxIntegerDigits\"\n value: number\n params?: {\n errorMessage?: { ... }\n allowLeadingZeroes?: { ... }\n }\n } | {\n fact: \"decimal-digits-count\"\n operator: \"equal\" | \"minDecimalDigits\" | \"maxDecimalDigits\"\n value: number\n params?: {\n errorMessage?: { ... }\n }\n } | {\n fact: \"has-leading-zeroes\"\n operator: \"equal\" | \"notAllowed\"\n value: boolean\n params?: {\n errorMessage?: { ... }\n }\n }>\n} | {\n any: Array<{\n fact: \"numeric-value\" | \"total-length\"\n operator: \"equal\" | \"notEqual\" | \"lessThan\" | \"lessThanInclusive\" | \"greaterThan\" | \"greaterThanInclusive\"\n value: number\n params?: {\n errorMessage?: { ... }\n }\n } | {\n fact: \"integer-digits-count\"\n operator: \"equal\" | \"exactlyNDigits\" | \"minIntegerDigits\" | \"maxIntegerDigits\"\n value: number\n params?: {\n errorMessage?: { ... }\n allowLeadingZeroes?: { ... }\n }\n } | {\n fact: \"decimal-digits-count\"\n operator: \"equal\" | \"minDecimalDigits\" | \"maxDecimalDigits\"\n value: number\n params?: {\n errorMessage?: { ... }\n }\n } | {\n fact: \"has-leading-zeroes\"\n operator: \"equal\" | \"notAllowed\"\n value: boolean\n params?: {\n errorMessage?: { ... }\n }\n }>\n} | {\n not: {\n fact: \"numeric-value\" | \"total-length\"\n operator: \"equal\" | \"notEqual\" | \"lessThan\" | \"lessThanInclusive\" | \"greaterThan\" | \"greaterThanInclusive\"\n value: number\n params?: {\n errorMessage?: { ... }\n }\n } | {\n fact: \"integer-digits-count\"\n operator: \"equal\" | \"exactlyNDigits\" | \"minIntegerDigits\" | \"maxIntegerDigits\"\n value: number\n params?: {\n errorMessage?: { ... }\n allowLeadingZeroes?: { ... }\n }\n } | {\n fact: \"decimal-digits-count\"\n operator: \"equal\" | \"minDecimalDigits\" | \"maxDecimalDigits\"\n value: number\n params?: {\n errorMessage?: { ... }\n }\n } | {\n fact: \"has-leading-zeroes\"\n operator: \"equal\" | \"notAllowed\"\n value: boolean\n params?: {\n errorMessage?: { ... }\n }\n }\n}\n```\n\n### `NumericFactCondition`\n\nFact-based condition for numeric validation\n\n```ts\ntype NumericFactCondition = {\n fact: \"numeric-value\" | \"total-length\"\n operator: \"equal\" | \"notEqual\" | \"lessThan\" | \"lessThanInclusive\" | \"greaterThan\" | \"greaterThanInclusive\"\n value: number\n params?: {\n errorMessage?: string\n }\n} | {\n fact: \"integer-digits-count\"\n operator: \"equal\" | \"exactlyNDigits\" | \"minIntegerDigits\" | \"maxIntegerDigits\"\n value: number\n params?: {\n errorMessage?: string\n allowLeadingZeroes?: boolean\n }\n} | {\n fact: \"decimal-digits-count\"\n operator: \"equal\" | \"minDecimalDigits\" | \"maxDecimalDigits\"\n value: number\n params?: {\n errorMessage?: string\n }\n} | {\n fact: \"has-leading-zeroes\"\n operator: \"equal\" | \"notAllowed\"\n value: boolean\n params?: {\n errorMessage?: string\n }\n}\n```\n\n### `ComparisonRuleType`\n\nDeclarative validation rule (schema version v2). Supports predefined comparison operators\nover number, date and text inputs, with static, dynamic (context path), relative-date and\nexternal (app-provided) comparison values.\n\n\n```ts\ntype ComparisonRuleType = {\n input_type: \"number\" | \"date\" | \"text\"\n conditions: Array<{\n id: string\n operator: \"equal\" | \"notEqual\" | \"greaterThan\" | \"greaterThanInclusive\" | \"lessThan\" | \"lessThanInclusive\" | \"between\" | \"dateBefore\" | \"dateOnOrBefore\" | \"dateAfter\" | \"dateOnOrAfter\" | \"dateBetween\" | \"notInFuture\" | \"notInPast\" | \"contains\" | \"doesNotContain\" | \"startsWith\" | \"endsWith\" | \"regexMatch\" | \"lengthBetween\" | \"maxDigits\" | \"maxDecimals\"\n value: {\n source: { ... }\n data: { ... }\n } | {\n source: { ... }\n path: { ... }\n adjust?: { ... }\n } | {\n source: { ... }\n offset: { ... }\n unit: { ... }\n anchor?: { ... }\n } | {\n source: { ... }\n key: { ... }\n adjust?: { ... }\n } | {\n source: { ... }\n app_id: { ... }\n hook_id: { ... }\n result_id: { ... }\n adjust?: { ... }\n } | {\n source: { ... }\n min: { ... }\n max: { ... }\n } | {\n source: { ... }\n }\n error_message: string\n applies_when?: {\n path: { ... }\n operator: { ... }\n value?: { ... }\n }\n allow_failure?: boolean\n }>\n}\n```\n\n### `Condition`\n\nA single comparison the input value must satisfy.\n\n```ts\ntype Condition = {\n id: string\n operator: \"equal\" | \"notEqual\" | \"greaterThan\" | \"greaterThanInclusive\" | \"lessThan\" | \"lessThanInclusive\" | \"between\" | \"dateBefore\" | \"dateOnOrBefore\" | \"dateAfter\" | \"dateOnOrAfter\" | \"dateBetween\" | \"notInFuture\" | \"notInPast\" | \"contains\" | \"doesNotContain\" | \"startsWith\" | \"endsWith\" | \"regexMatch\" | \"lengthBetween\" | \"maxDigits\" | \"maxDecimals\"\n value: {\n source: \"static\"\n data: number | string | boolean\n } | {\n source: \"context\"\n path: string\n adjust?: {\n type: { ... }\n value: { ... }\n direction: { ... }\n rounding?: { ... }\n }\n } | {\n source: \"relative_date\"\n offset: number\n unit: \"days\" | \"months\" | \"years\"\n anchor?: \"today\" | {\n type: { ... }\n day: { ... }\n interval: { ... }\n cycle_start?: { ... }\n occurrence?: { ... }\n }\n } | {\n source: \"environment\"\n key: string\n adjust?: {\n type: { ... }\n value: { ... }\n direction: { ... }\n rounding?: { ... }\n }\n } | {\n source: \"external\"\n app_id: string\n hook_id: string\n result_id: string\n adjust?: {\n type: { ... }\n value: { ... }\n direction: { ... }\n rounding?: { ... }\n }\n } | {\n source: \"range\"\n min: {\n source: { ... }\n data: { ... }\n } | {\n source: { ... }\n path: { ... }\n adjust?: { ... }\n } | {\n source: { ... }\n offset: { ... }\n unit: { ... }\n anchor?: { ... }\n } | {\n source: { ... }\n key: { ... }\n adjust?: { ... }\n } | {\n source: { ... }\n app_id: { ... }\n hook_id: { ... }\n result_id: { ... }\n adjust?: { ... }\n }\n max: {\n source: { ... }\n data: { ... }\n } | {\n source: { ... }\n path: { ... }\n adjust?: { ... }\n } | {\n source: { ... }\n offset: { ... }\n unit: { ... }\n anchor?: { ... }\n } | {\n source: { ... }\n key: { ... }\n adjust?: { ... }\n } | {\n source: { ... }\n app_id: { ... }\n hook_id: { ... }\n result_id: { ... }\n adjust?: { ... }\n }\n } | {\n source: \"none\"\n }\n error_message: string\n applies_when?: {\n path: string\n // ...\n}\n```\n\n### `Operator`\n\nPredefined comparison operator. Compatibility (enforced at write time):\n- number: equal, notEqual, greaterThan, greaterThanInclusive, lessThan, lessThanInclusive, between, regexMatch,\n maxDigits, maxDecimals\n- date: dateBefore, dateOnOrBefore, dateAfter, dateOnOrAfter, dateBetween, notInFuture, not\n\n```ts\ntype Operator = \"equal\" | \"notEqual\" | \"greaterThan\" | \"greaterThanInclusive\" | \"lessThan\" | \"lessThanInclusive\" | \"between\" | \"dateBefore\" | \"dateOnOrBefore\" | \"dateAfter\" | \"dateOnOrAfter\" | \"dateBetween\" | \"notInFuture\" | \"notInPast\" | \"contains\" | \"doesNotContain\" | \"startsWith\" | \"endsWith\" | \"regexMatch\" | \"lengthBetween\" | \"maxDigits\" | \"maxDecimals\"\n```\n\n### `ConditionValue`\n\nThe comparison value of a condition - a scalar, a range of scalars, or nothing (unary operators).\n\n```ts\ntype ConditionValue = {\n source: \"static\"\n data: number | string | boolean\n} | {\n source: \"context\"\n path: string\n adjust?: {\n type: \"percent\" | \"absolute\"\n value: number | {\n source: { ... }\n path: { ... }\n adjust?: { ... }\n } | {\n source: { ... }\n key: { ... }\n adjust?: { ... }\n }\n direction: \"increase\" | \"decrease\"\n rounding?: \"up\" | \"down\"\n }\n} | {\n source: \"relative_date\"\n offset: number\n unit: \"days\" | \"months\" | \"years\"\n anchor?: \"today\" | {\n type: \"billing_cycle\"\n day: {\n source: { ... }\n data: { ... }\n } | {\n source: { ... }\n path: { ... }\n adjust?: { ... }\n } | {\n source: { ... }\n key: { ... }\n adjust?: { ... }\n } | {\n source: { ... }\n app_id: { ... }\n hook_id: { ... }\n result_id: { ... }\n adjust?: { ... }\n }\n interval: {\n source: { ... }\n data: { ... }\n } | {\n source: { ... }\n path: { ... }\n adjust?: { ... }\n } | {\n source: { ... }\n key: { ... }\n adjust?: { ... }\n } | {\n source: { ... }\n app_id: { ... }\n hook_id: { ... }\n result_id: { ... }\n adjust?: { ... }\n }\n cycle_start?: {\n source: { ... }\n data: { ... }\n } | {\n source: { ... }\n path: { ... }\n adjust?: { ... }\n } | {\n source: { ... }\n key: { ... }\n adjust?: { ... }\n } | {\n source: { ... }\n app_id: { ... }\n hook_id: { ... }\n result_id: { ... }\n adjust?: { ... }\n }\n occurrence?: \"current\" | \"next\" | \"previous\"\n }\n} | {\n source: \"environment\"\n key: string\n adjust?: {\n type: \"percent\" | \"absolute\"\n value: number | {\n source: { ... }\n path: { ... }\n adjust?: { ... }\n } | {\n source: { ... }\n key: { ... }\n adjust?: { ... }\n }\n direction: \"increase\" | \"decrease\"\n rounding?: \"up\" | \"down\"\n }\n} | {\n // ...\n}\n```\n\n### `ScalarValue`\n\nA single comparison value - static, resolved from context, a relative date, an organisation environment variable, or an external value.\n\n```ts\ntype ScalarValue = {\n source: \"static\"\n data: number | string | boolean\n} | {\n source: \"context\"\n path: string\n adjust?: {\n type: \"percent\" | \"absolute\"\n value: number | {\n source: { ... }\n path: { ... }\n adjust?: { ... }\n } | {\n source: { ... }\n key: { ... }\n adjust?: { ... }\n }\n direction: \"increase\" | \"decrease\"\n rounding?: \"up\" | \"down\"\n }\n} | {\n source: \"relative_date\"\n offset: number\n unit: \"days\" | \"months\" | \"years\"\n anchor?: \"today\" | {\n type: \"billing_cycle\"\n day: {\n source: { ... }\n data: { ... }\n } | {\n source: { ... }\n path: { ... }\n adjust?: { ... }\n } | {\n source: { ... }\n key: { ... }\n adjust?: { ... }\n } | {\n source: { ... }\n app_id: { ... }\n hook_id: { ... }\n result_id: { ... }\n adjust?: { ... }\n }\n interval: {\n source: { ... }\n data: { ... }\n } | {\n source: { ... }\n path: { ... }\n adjust?: { ... }\n } | {\n source: { ... }\n key: { ... }\n adjust?: { ... }\n } | {\n source: { ... }\n app_id: { ... }\n hook_id: { ... }\n result_id: { ... }\n adjust?: { ... }\n }\n cycle_start?: {\n source: { ... }\n data: { ... }\n } | {\n source: { ... }\n path: { ... }\n adjust?: { ... }\n } | {\n source: { ... }\n key: { ... }\n adjust?: { ... }\n } | {\n source: { ... }\n app_id: { ... }\n hook_id: { ... }\n result_id: { ... }\n adjust?: { ... }\n }\n occurrence?: \"current\" | \"next\" | \"previous\"\n }\n} | {\n source: \"environment\"\n key: string\n adjust?: {\n type: \"percent\" | \"absolute\"\n value: number | {\n source: { ... }\n path: { ... }\n adjust?: { ... }\n } | {\n source: { ... }\n key: { ... }\n adjust?: { ... }\n }\n direction: \"increase\" | \"decrease\"\n rounding?: \"up\" | \"down\"\n }\n} | {\n // ...\n}\n```\n\n### `EnvironmentValue`\n\nA comparison value resolved at evaluation time from an organisation environment variable\n(environments-api). The rule stores the key, never the value, so one change to the variable\nreaches every rule that references it and a blueprint install never overwrites the\norganisation's own value.\n\nOnly brow\n\n```ts\ntype EnvironmentValue = {\n source: \"environment\"\n key: string\n adjust?: {\n type: \"percent\" | \"absolute\"\n value: number | {\n source: { ... }\n path: { ... }\n adjust?: { ... }\n } | {\n source: { ... }\n key: { ... }\n adjust?: { ... }\n }\n direction: \"increase\" | \"decrease\"\n rounding?: \"up\" | \"down\"\n }\n}\n```\n\n### `ExternalValue`\n\nA comparison value produced at evaluation time by an External Values hook of an installed\napp (component type `EXTERNAL_VALUES`). The rule stores the reference only; the value is\nresolved server-side by the external-values-api, which executes the hook's HTTP call with\nthe app's credentials and retur\n\n```ts\ntype ExternalValue = {\n source: \"external\"\n app_id: string\n hook_id: string\n result_id: string\n adjust?: {\n type: \"percent\" | \"absolute\"\n value: number | {\n source: { ... }\n path: { ... }\n adjust?: { ... }\n } | {\n source: { ... }\n key: { ... }\n adjust?: { ... }\n }\n direction: \"increase\" | \"decrease\"\n rounding?: \"up\" | \"down\"\n }\n}\n```\n\n### `StaticValue`\n\nA fixed comparison value.\n\n```ts\ntype StaticValue = {\n source: \"static\"\n data: number | string | boolean\n}\n```\n\n### `ContextValue`\n\nA dynamic comparison value resolved from runtime context, e.g. `contract.installment_amount`\nor `previous_reading.value`. The first path segment must match the `name` of a declared\ncontext requirement.\n\n\n```ts\ntype ContextValue = {\n source: \"context\"\n path: string\n adjust?: {\n type: \"percent\" | \"absolute\"\n value: number | {\n source: { ... }\n path: { ... }\n adjust?: { ... }\n } | {\n source: { ... }\n key: { ... }\n adjust?: { ... }\n }\n direction: \"increase\" | \"decrease\"\n rounding?: \"up\" | \"down\"\n }\n}\n```\n\n### `ValueAdjustment`\n\nAdjusts a context-resolved numeric value before comparison, e.g. \"context value plus 10 percent\".\nUsed to express tolerance bands such as \"at most 10% above the current instalment amount\".\n\n\n```ts\ntype ValueAdjustment = {\n type: \"percent\" | \"absolute\"\n value: number | {\n source: \"context\"\n path: string\n adjust?: {\n type: { ... }\n value: { ... }\n direction: { ... }\n rounding?: { ... }\n }\n } | {\n source: \"environment\"\n key: string\n adjust?: {\n type: { ... }\n value: { ... }\n direction: { ... }\n rounding?: { ... }\n }\n }\n direction: \"increase\" | \"decrease\"\n rounding?: \"up\" | \"down\"\n}\n```\n\n### `RelativeDateValue`\n\nA date relative to an anchor, e.g. \"today minus 30 days\" or \"4 days before the next billing\ndate\". The anchor is `today` by default, or a billing cycle date derived from other\nattributes (see `BillingCycleAnchor`). Only valid for date rules.\n\n\n```ts\ntype RelativeDateValue = {\n source: \"relative_date\"\n offset: number\n unit: \"days\" | \"months\" | \"years\"\n anchor?: \"today\" | {\n type: \"billing_cycle\"\n day: {\n source: { ... }\n data: { ... }\n } | {\n source: { ... }\n path: { ... }\n adjust?: { ... }\n } | {\n source: { ... }\n key: { ... }\n adjust?: { ... }\n } | {\n source: { ... }\n app_id: { ... }\n hook_id: { ... }\n result_id: { ... }\n adjust?: { ... }\n }\n interval: {\n source: { ... }\n data: { ... }\n } | {\n source: { ... }\n path: { ... }\n adjust?: { ... }\n } | {\n source: { ... }\n key: { ... }\n adjust?: { ... }\n } | {\n source: { ... }\n app_id: { ... }\n hook_id: { ... }\n result_id: { ... }\n adjust?: { ... }\n }\n cycle_start?: {\n source: { ... }\n data: { ... }\n } | {\n source: { ... }\n path: { ... }\n adjust?: { ... }\n } | {\n source: { ... }\n key: { ... }\n adjust?: { ... }\n } | {\n source: { ... }\n app_id: { ... }\n hook_id: { ... }\n result_id: { ... }\n adjust?: { ... }\n }\n occurrence?: \"current\" | \"next\" | \"previous\"\n }\n}\n```\n\n### `BillingCycleAnchor`\n\nA date derived from a billing cycle: the day of the month billing is due, how often it\nrecurs, and optionally the date the cycle started (its phase). Day and interval are ordinary\nscalars, so they can come from a contract (`contract.billing_due_day`,\n`contract.billing_period`), a fixed value, an env\n\n```ts\ntype BillingCycleAnchor = {\n type: \"billing_cycle\"\n day: {\n source: \"static\"\n data: number | string | boolean\n } | {\n source: \"context\"\n path: string\n adjust?: {\n type: { ... }\n value: { ... }\n direction: { ... }\n rounding?: { ... }\n }\n } | {\n source: \"environment\"\n key: string\n adjust?: {\n type: { ... }\n value: { ... }\n direction: { ... }\n rounding?: { ... }\n }\n } | {\n source: \"external\"\n app_id: string\n hook_id: string\n result_id: string\n adjust?: {\n type: { ... }\n value: { ... }\n direction: { ... }\n rounding?: { ... }\n }\n }\n interval: {\n source: \"static\"\n data: number | string | boolean\n } | {\n source: \"context\"\n path: string\n adjust?: {\n type: { ... }\n value: { ... }\n direction: { ... }\n rounding?: { ... }\n }\n } | {\n source: \"environment\"\n key: string\n adjust?: {\n type: { ... }\n value: { ... }\n direction: { ... }\n rounding?: { ... }\n }\n } | {\n source: \"external\"\n app_id: string\n hook_id: string\n result_id: string\n adjust?: {\n type: { ... }\n value: { ... }\n direction: { ... }\n rounding?: { ... }\n }\n }\n cycle_start?: {\n source: \"static\"\n data: number | string | boolean\n } | {\n source: \"context\"\n path: string\n adjust?: {\n type: { ... }\n value: { ... }\n direction: { ... }\n rounding?: { ... }\n }\n } | {\n source: \"environment\"\n key: string\n adjust?: {\n type: { ... }\n value: { ... }\n direction: { ... }\n rounding?: { ... }\n }\n } | {\n source: \"external\"\n app_id: string\n hook_id: string\n result_id: string\n adjust?: {\n type: { ... }\n value: { ... }\n direction: { ... }\n rounding?: { ... }\n }\n // ...\n}\n```\n\n### `AnchorScalar`\n\nA part of a derived date - a fixed value or one resolved at evaluation time. Carries no adjustment; shift the result with the relative date's offset instead.\n\n```ts\ntype AnchorScalar = {\n source: \"static\"\n data: number | string | boolean\n} | {\n source: \"context\"\n path: string\n adjust?: {\n type: \"percent\" | \"absolute\"\n value: number | {\n source: { ... }\n path: { ... }\n adjust?: { ... }\n } | {\n source: { ... }\n key: { ... }\n adjust?: { ... }\n }\n direction: \"increase\" | \"decrease\"\n rounding?: \"up\" | \"down\"\n }\n} | {\n source: \"environment\"\n key: string\n adjust?: {\n type: \"percent\" | \"absolute\"\n value: number | {\n source: { ... }\n path: { ... }\n adjust?: { ... }\n } | {\n source: { ... }\n key: { ... }\n adjust?: { ... }\n }\n direction: \"increase\" | \"decrease\"\n rounding?: \"up\" | \"down\"\n }\n} | {\n source: \"external\"\n app_id: string\n hook_id: string\n result_id: string\n adjust?: {\n type: \"percent\" | \"absolute\"\n value: number | {\n source: { ... }\n path: { ... }\n adjust?: { ... }\n } | {\n source: { ... }\n key: { ... }\n adjust?: { ... }\n }\n direction: \"increase\" | \"decrease\"\n rounding?: \"up\" | \"down\"\n }\n}\n```\n\n### `RangeValue`\n\nA lower and upper bound for range operators (between, dateBetween, lengthBetween). Bounds are inclusive.\n\n```ts\ntype RangeValue = {\n source: \"range\"\n min: {\n source: \"static\"\n data: number | string | boolean\n } | {\n source: \"context\"\n path: string\n adjust?: {\n type: { ... }\n value: { ... }\n direction: { ... }\n rounding?: { ... }\n }\n } | {\n source: \"relative_date\"\n offset: number\n unit: \"days\" | \"months\" | \"years\"\n anchor?: \"today\" | {\n type: { ... }\n day: { ... }\n interval: { ... }\n cycle_start?: { ... }\n occurrence?: { ... }\n }\n } | {\n source: \"environment\"\n key: string\n adjust?: {\n type: { ... }\n value: { ... }\n direction: { ... }\n rounding?: { ... }\n }\n } | {\n source: \"external\"\n app_id: string\n hook_id: string\n result_id: string\n adjust?: {\n type: { ... }\n value: { ... }\n direction: { ... }\n rounding?: { ... }\n }\n }\n max: {\n source: \"static\"\n data: number | string | boolean\n } | {\n source: \"context\"\n path: string\n adjust?: {\n type: { ... }\n value: { ... }\n direction: { ... }\n rounding?: { ... }\n }\n } | {\n source: \"relative_date\"\n offset: number\n unit: \"days\" | \"months\" | \"years\"\n anchor?: \"today\" | {\n type: { ... }\n day: { ... }\n interval: { ... }\n cycle_start?: { ... }\n occurrence?: { ... }\n }\n } | {\n source: \"environment\"\n key: string\n adjust?: {\n type: { ... }\n value: { ... }\n direction: { ... }\n rounding?: { ... }\n }\n } | {\n source: \"external\"\n app_id: string\n hook_id: string\n result_id: string\n adjust?: {\n type: { ... }\n value: { ... }\n direction: { ... }\n rounding?: { ... }\n }\n }\n}\n```\n\n### `NoValue`\n\nNo comparison value - used by unary operators such as notInFuture / notInPast.\n\n```ts\ntype NoValue = {\n source: \"none\"\n}\n```\n\n### `DocumentRuleType`\n\nDeclarative validation rule (schema version v2) for uploaded files. The rule declares how deep\nthe check goes (`check.level`) and the conditions the file must satisfy. Property conditions\n(file type, size, page count, image resolution, blank and password-protected detection) are\ndeterministic and av\n\n```ts\ntype DocumentRuleType = {\n input_type: \"document\"\n check: {\n level: \"basic\" | \"standard\" | \"advanced\"\n }\n conditions: Array<{\n id: string\n operator: \"fileTypeIn\" | \"sizeBelow\" | \"pageCountBetween\" | \"resolutionAtLeast\" | \"resolutionAtMost\" | \"isNotBlank\" | \"isNotPasswordProtected\" | \"meetsCriteria\"\n value: {\n source: { ... }\n data: { ... }\n } | {\n source: { ... }\n min: { ... }\n max: { ... }\n } | {\n source: { ... }\n } | {\n source: { ... }\n types: { ... }\n } | {\n source: { ... }\n text: { ... }\n }\n error_message: string\n allow_failure?: boolean\n }>\n}\n```\n\n### `DocumentCheck`\n\nHow deep the document check goes. Levels are cumulative.\n\n```ts\ntype DocumentCheck = {\n level: \"basic\" | \"standard\" | \"advanced\"\n}\n```\n\n### `DocumentCondition`\n\nA single check the uploaded file must satisfy.\n\n```ts\ntype DocumentCondition = {\n id: string\n operator: \"fileTypeIn\" | \"sizeBelow\" | \"pageCountBetween\" | \"resolutionAtLeast\" | \"resolutionAtMost\" | \"isNotBlank\" | \"isNotPasswordProtected\" | \"meetsCriteria\"\n value: {\n source: \"static\"\n data: number | string | boolean\n } | {\n source: \"range\"\n min: {\n source: { ... }\n data: { ... }\n } | {\n source: { ... }\n path: { ... }\n adjust?: { ... }\n } | {\n source: { ... }\n offset: { ... }\n unit: { ... }\n anchor?: { ... }\n } | {\n source: { ... }\n key: { ... }\n adjust?: { ... }\n } | {\n source: { ... }\n app_id: { ... }\n hook_id: { ... }\n result_id: { ... }\n adjust?: { ... }\n }\n max: {\n source: { ... }\n data: { ... }\n } | {\n source: { ... }\n path: { ... }\n adjust?: { ... }\n } | {\n source: { ... }\n offset: { ... }\n unit: { ... }\n anchor?: { ... }\n } | {\n source: { ... }\n key: { ... }\n adjust?: { ... }\n } | {\n source: { ... }\n app_id: { ... }\n hook_id: { ... }\n result_id: { ... }\n adjust?: { ... }\n }\n } | {\n source: \"none\"\n } | {\n source: \"file_types\"\n types: \"image\" | \"pdf\" | \"document\" | \"spreadsheet\"[]\n } | {\n source: \"criteria\"\n text: string\n }\n error_message: string\n allow_failure?: boolean\n}\n```\n\n### `DocumentOperator`\n\nPredefined document check. Value compatibility (enforced at write time):\n- fileTypeIn: `file_types` value listing the accepted file categories.\n- sizeBelow: static integer, maximum file size in bytes.\n- pageCountBetween: `range` of static non-negative integers (inclusive).\n- resolutionAtLeast / reso\n\n```ts\ntype DocumentOperator = \"fileTypeIn\" | \"sizeBelow\" | \"pageCountBetween\" | \"resolutionAtLeast\" | \"resolutionAtMost\" | \"isNotBlank\" | \"isNotPasswordProtected\" | \"meetsCriteria\"\n```\n\n### `DocumentConditionValue`\n\nThe value of a document condition - a static scalar, a range, nothing, accepted file categories, or a free-text criterion.\n\n```ts\ntype DocumentConditionValue = {\n source: \"static\"\n data: number | string | boolean\n} | {\n source: \"range\"\n min: {\n source: \"static\"\n data: number | string | boolean\n } | {\n source: \"context\"\n path: string\n adjust?: {\n type: { ... }\n value: { ... }\n direction: { ... }\n rounding?: { ... }\n }\n } | {\n source: \"relative_date\"\n offset: number\n unit: \"days\" | \"months\" | \"years\"\n anchor?: \"today\" | {\n type: { ... }\n day: { ... }\n interval: { ... }\n cycle_start?: { ... }\n occurrence?: { ... }\n }\n } | {\n source: \"environment\"\n key: string\n adjust?: {\n type: { ... }\n value: { ... }\n direction: { ... }\n rounding?: { ... }\n }\n } | {\n source: \"external\"\n app_id: string\n hook_id: string\n result_id: string\n adjust?: {\n type: { ... }\n value: { ... }\n direction: { ... }\n rounding?: { ... }\n }\n }\n max: {\n source: \"static\"\n data: number | string | boolean\n } | {\n source: \"context\"\n path: string\n adjust?: {\n type: { ... }\n value: { ... }\n direction: { ... }\n rounding?: { ... }\n }\n } | {\n source: \"relative_date\"\n offset: number\n unit: \"days\" | \"months\" | \"years\"\n anchor?: \"today\" | {\n type: { ... }\n day: { ... }\n interval: { ... }\n cycle_start?: { ... }\n occurrence?: { ... }\n }\n } | {\n source: \"environment\"\n key: string\n adjust?: {\n type: { ... }\n value: { ... }\n direction: { ... }\n rounding?: { ... }\n }\n } | {\n source: \"external\"\n app_id: string\n hook_id: string\n result_id: string\n adjust?: {\n type: { ... }\n value: { ... }\n direction: { ... }\n rounding?: { ... }\n }\n }\n} | {\n source: \"none\"\n} | {\n source: \"file_types\"\n types: \"image\" | \"pdf\" | \"document\" | \"spreadsheet\"[]\n} | {\n source: \"criteria\"\n // ...\n}\n```\n\n### `FileTypesValue`\n\nAccepted file categories; each category expands to a mime list at check time.\n\n```ts\ntype FileTypesValue = {\n source: \"file_types\"\n types: \"image\" | \"pdf\" | \"document\" | \"spreadsheet\"[]\n}\n```\n\n### `CriteriaValue`\n\nA free-text requirement judged against the document's extracted content, e.g. \"The photo shows\nan electricity meter with the meter number and the current reading fully legible.\" One requirement\nper condition keeps results specific and lets each requirement be graded separately.\n\n\n```ts\ntype CriteriaValue = {\n source: \"criteria\"\n text: string\n}\n```\n\n### `AppliesWhen`\n\nOptional precondition on a condition: the condition only takes part in the\nvalidation when this comparison over context holds. Examples: apply the\ndual-tariff reference only when `contract.htnt` is not empty, or run a\nplausibility check only when a context value reaches a threshold.\n\n\n```ts\ntype AppliesWhen = {\n path: string\n operator: \"equal\" | \"notEqual\" | \"greaterThan\" | \"greaterThanInclusive\" | \"lessThan\" | \"lessThanInclusive\" | \"isEmpty\" | \"isNotEmpty\"\n value?: number | string | boolean | {\n source: \"environment\"\n key: string\n adjust?: {\n type: { ... }\n value: { ... }\n direction: { ... }\n rounding?: { ... }\n }\n }\n}\n```\n\n### `ContextRequirement`\n\nAn entity context source the rule needs at evaluation time, referenced by `context`\nvalue paths via the schema slug as their first segment (e.g. `contract.installment_amount`).\nHow the source is resolved (which entity instance) is decided by the consuming surface,\nnot by the rule. Meter reading comp\n\n```ts\ntype ContextRequirement = {\n schema: string\n}\n```\n" diff --git a/packages/epilot-sdk-v2/src/types/validation-rules.d.ts b/packages/epilot-sdk-v2/src/types/validation-rules.d.ts index 219ddfb25..3c6015dc5 100644 --- a/packages/epilot-sdk-v2/src/types/validation-rules.d.ts +++ b/packages/epilot-sdk-v2/src/types/validation-rules.d.ts @@ -9,6 +9,42 @@ import type { export declare namespace Components { namespace Schemas { + /** + * A part of a derived date - a fixed value or one resolved at evaluation time. Carries no adjustment; shift the result with the relative date's offset instead. + */ + export type AnchorScalar = /* A part of a derived date - a fixed value or one resolved at evaluation time. Carries no adjustment; shift the result with the relative date's offset instead. */ /* A fixed comparison value. */ StaticValue | /** + * A dynamic comparison value resolved from runtime context, e.g. `contract.installment_amount` + * or `previous_reading.value`. The first path segment must match the `name` of a declared + * context requirement. + * + */ + ContextValue | /** + * A comparison value resolved at evaluation time from an organisation environment variable + * (environments-api). The rule stores the key, never the value, so one change to the variable + * reaches every rule that references it and a blueprint install never overwrites the + * organisation's own value. + * + * Only browser-safe variable types are allowed: `Number` for numeric comparisons, `Text` for + * text comparisons and for dates (ISO 8601 string), `Boolean` for `applies_when` values. + * `String` and `SecretString` variables are rejected at write time. Write-time validation also + * checks that the variable exists in the organisation; a variable that exists without a value + * (for example seeded by a blueprint install) is accepted and resolves as unavailable until set. + * + */ + EnvironmentValue | /** + * A comparison value produced at evaluation time by an External Values hook of an installed + * app (component type `EXTERNAL_VALUES`). The rule stores the reference only; the value is + * resolved server-side by the external-values-api, which executes the hook's HTTP call with + * the app's credentials and returns the typed result identified by `result_id`. + * + * Write-time validation checks that the app is installed in the organisation, that the hook + * and result exist, and that the result's type is compatible with the compared kind + * (`number` results for numeric comparisons, `text` for text, `date` for date). External values + * are resolved only for authenticated consumers (epilot 360 users and portal end customers); + * in public journeys the dependent conditions are skipped. + * + */ + ExternalValue; /** * Optional precondition on a condition: the condition only takes part in the * validation when this comparison over context holds. Examples: apply the @@ -54,6 +90,39 @@ export declare namespace Components { */ EnvironmentValue; } + /** + * A date derived from a billing cycle: the day of the month billing is due, how often it + * recurs, and optionally the date the cycle started (its phase). Day and interval are ordinary + * scalars, so they can come from a contract (`contract.billing_due_day`, + * `contract.billing_period`), a fixed value, an environment variable or an external value. + * + * Resolution at evaluation time: + * - `day` clamps to the last day of the month (31 in February resolves to the 28th/29th). + * - Without `cycle_start`, non-monthly cycles are calendar aligned (quarters start in + * January, April, July and October; half-years in January and July; yearly in January). + * `weekly` needs a `cycle_start` and resolves as unavailable without one. + * - `occurrence` picks the cycle date: `current` is the one in the period that contains the + * evaluation day, `next` the first on or after it, `previous` the last before it. + * - Any part that cannot be resolved (missing attribute, day outside 1-31, unknown interval) + * makes the whole value unavailable and the condition fails. + * + */ + export interface BillingCycleAnchor { + type: "billing_cycle"; + /** + * Day of the month billing is due (1-31). Numeric strings are accepted at evaluation time. Weekly cycles recur from `cycle_start` and ignore it. + */ + day: /* A part of a derived date - a fixed value or one resolved at evaluation time. Carries no adjustment; shift the result with the relative date's offset instead. */ AnchorScalar; + /** + * How often billing recurs, one of `weekly`, `monthly`, `every_quarter`, `every_6_months`, `yearly`. + */ + interval: /* A part of a derived date - a fixed value or one resolved at evaluation time. Carries no adjustment; shift the result with the relative date's offset instead. */ AnchorScalar; + /** + * The date the billing cycle started, e.g. `contract.start_date`. Sets the phase of non-monthly cycles. + */ + cycle_start?: /* A part of a derived date - a fixed value or one resolved at evaluation time. Carries no adjustment; shift the result with the relative date's offset instead. */ AnchorScalar; + occurrence?: "current" | "next" | "previous"; + } /** * Declarative validation rule (schema version v2). Supports predefined comparison operators * over number, date and text inputs, with static, dynamic (context path), relative-date and @@ -131,7 +200,13 @@ export declare namespace Components { * context requirement. * */ - ContextValue | /* A date relative to the evaluation moment, e.g. "today minus 30 days". Only valid for date rules. */ RelativeDateValue | /** + ContextValue | /** + * A date relative to an anchor, e.g. "today minus 30 days" or "4 days before the next billing + * date". The anchor is `today` by default, or a billing cycle date derived from other + * attributes (see `BillingCycleAnchor`). Only valid for date rules. + * + */ + RelativeDateValue | /** * A comparison value resolved at evaluation time from an organisation environment variable * (environments-api). The rule stores the key, never the value, so one change to the variable * reaches every rule that references it and a blueprint install never overwrites the @@ -810,7 +885,10 @@ export declare namespace Components { conditions: /* Condition definition for a regex-based validation rule (2 levels deep) */ RegexCondition; } /** - * A date relative to the evaluation moment, e.g. "today minus 30 days". Only valid for date rules. + * A date relative to an anchor, e.g. "today minus 30 days" or "4 days before the next billing + * date". The anchor is `today` by default, or a billing cycle date derived from other + * attributes (see `BillingCycleAnchor`). Only valid for date rules. + * */ export interface RelativeDateValue { source: "relative_date"; @@ -819,7 +897,27 @@ export declare namespace Components { */ offset: number; unit: "days" | "months" | "years"; - anchor?: "today"; + /** + * The date the offset is applied to. Defaults to the evaluation day. + */ + anchor?: /* The date the offset is applied to. Defaults to the evaluation day. */ ("today") | /** + * A date derived from a billing cycle: the day of the month billing is due, how often it + * recurs, and optionally the date the cycle started (its phase). Day and interval are ordinary + * scalars, so they can come from a contract (`contract.billing_due_day`, + * `contract.billing_period`), a fixed value, an environment variable or an external value. + * + * Resolution at evaluation time: + * - `day` clamps to the last day of the month (31 in February resolves to the 28th/29th). + * - Without `cycle_start`, non-monthly cycles are calendar aligned (quarters start in + * January, April, July and October; half-years in January and July; yearly in January). + * `weekly` needs a `cycle_start` and resolves as unavailable without one. + * - `occurrence` picks the cycle date: `current` is the one in the period that contains the + * evaluation day, `next` the first on or after it, `previous` the last before it. + * - Any part that cannot be resolved (missing attribute, day outside 1-31, unknown interval) + * makes the whole value unavailable and the condition fails. + * + */ + BillingCycleAnchor; } /** * A single comparison value - static, resolved from context, a relative date, an organisation environment variable, or an external value. @@ -830,7 +928,13 @@ export declare namespace Components { * context requirement. * */ - ContextValue | /* A date relative to the evaluation moment, e.g. "today minus 30 days". Only valid for date rules. */ RelativeDateValue | /** + ContextValue | /** + * A date relative to an anchor, e.g. "today minus 30 days" or "4 days before the next billing + * date". The anchor is `today` by default, or a billing cycle date derived from other + * attributes (see `BillingCycleAnchor`). Only valid for date rules. + * + */ + RelativeDateValue | /** * A comparison value resolved at evaluation time from an organisation environment variable * (environments-api). The rule stores the key, never the value, so one change to the variable * reaches every rule that references it and a blueprint install never overwrites the @@ -1628,7 +1732,9 @@ export interface PathsDictionary { export type Client = OpenAPIClient +export type AnchorScalar = Components.Schemas.AnchorScalar; export type AppliesWhen = Components.Schemas.AppliesWhen; +export type BillingCycleAnchor = Components.Schemas.BillingCycleAnchor; export type ComparisonRuleType = Components.Schemas.ComparisonRuleType; export type Condition = Components.Schemas.Condition; export type ConditionValue = Components.Schemas.ConditionValue;