@defra/forms-model 3.0.703 → 3.0.704

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/dist/module/form/form-audit/index.js +12 -0
  2. package/dist/module/form/form-audit/index.js.map +1 -1
  3. package/dist/module/form/form-definition/helpers.js +20 -0
  4. package/dist/module/form/form-definition/helpers.js.map +1 -1
  5. package/dist/module/form/form-definition/index.js +29 -17
  6. package/dist/module/form/form-definition/index.js.map +1 -1
  7. package/dist/module/form/form-definition/types.js.map +1 -1
  8. package/dist/module/form/form-manager/errors.js +6 -1
  9. package/dist/module/form/form-manager/errors.js.map +1 -1
  10. package/dist/module/form/form-manager/types.js +13 -0
  11. package/dist/module/form/form-manager/types.js.map +1 -1
  12. package/dist/module/form/form-submission/enums.js +25 -0
  13. package/dist/module/form/form-submission/enums.js.map +1 -1
  14. package/dist/module/form/form-submission/index.js +21 -1
  15. package/dist/module/form/form-submission/index.js.map +1 -1
  16. package/dist/module/form/form-submission/types.js.map +1 -1
  17. package/dist/types/form/form-audit/index.d.ts +7 -0
  18. package/dist/types/form/form-audit/index.d.ts.map +1 -1
  19. package/dist/types/form/form-definition/helpers.d.ts +15 -1
  20. package/dist/types/form/form-definition/helpers.d.ts.map +1 -1
  21. package/dist/types/form/form-definition/index.d.ts.map +1 -1
  22. package/dist/types/form/form-definition/types.d.ts +5 -0
  23. package/dist/types/form/form-definition/types.d.ts.map +1 -1
  24. package/dist/types/form/form-manager/errors.d.ts.map +1 -1
  25. package/dist/types/form/form-manager/types.d.ts +2 -0
  26. package/dist/types/form/form-manager/types.d.ts.map +1 -1
  27. package/dist/types/form/form-submission/enums.d.ts +23 -0
  28. package/dist/types/form/form-submission/enums.d.ts.map +1 -1
  29. package/dist/types/form/form-submission/index.d.ts +11 -1
  30. package/dist/types/form/form-submission/index.d.ts.map +1 -1
  31. package/dist/types/form/form-submission/types.d.ts +46 -1
  32. package/dist/types/form/form-submission/types.d.ts.map +1 -1
  33. package/package.json +2 -2
  34. package/schemas/form-definition-schema.json +2 -0
  35. package/schemas/form-definition-v2-schema.json +63 -36
  36. package/src/form/form-audit/index.ts +32 -0
  37. package/src/form/form-definition/helpers.ts +32 -1
  38. package/src/form/form-definition/index.ts +66 -21
  39. package/src/form/form-definition/types.ts +5 -0
  40. package/src/form/form-manager/errors.ts +7 -1
  41. package/src/form/form-manager/types.ts +13 -0
  42. package/src/form/form-submission/enums.ts +26 -0
  43. package/src/form/form-submission/index.ts +49 -0
  44. package/src/form/form-submission/types.ts +54 -0
@@ -143,42 +143,6 @@
143
143
  "additionalProperties": false,
144
144
  "title": "Output"
145
145
  },
146
- "outputs": {
147
- "type": "array",
148
- "description": "One or more email targets/types for submission emails",
149
- "items": {
150
- "type": "object",
151
- "properties": {
152
- "emailAddress": {
153
- "type": "string",
154
- "description": "Email address where form submissions are sent",
155
- "format": "email",
156
- "title": "Email Address"
157
- },
158
- "audience": {
159
- "type": "string",
160
- "description": "Target audience for the output (human readable or machine processable)",
161
- "enum": [
162
- "human",
163
- "machine"
164
- ],
165
- "title": "Audience"
166
- },
167
- "version": {
168
- "type": "string",
169
- "description": "Version identifier for the output format",
170
- "title": "Version"
171
- }
172
- },
173
- "required": [
174
- "audience",
175
- "version"
176
- ],
177
- "additionalProperties": false,
178
- "title": "Outputs Item"
179
- },
180
- "title": "Outputs"
181
- },
182
146
  "schema": {
183
147
  "description": "Form schema version to use (2)",
184
148
  "const": 2,
@@ -2441,9 +2405,72 @@
2441
2405
  "title": "Sections Item"
2442
2406
  },
2443
2407
  "title": "Sections"
2408
+ },
2409
+ "outputs": {
2410
+ "type": "array",
2411
+ "description": "Outputs schema for V2 forms",
2412
+ "uniqueItems": true,
2413
+ "items": {
2414
+ "type": "object",
2415
+ "description": "Configuration for a single submission email target",
2416
+ "properties": {
2417
+ "emailAddress": {
2418
+ "type": "string",
2419
+ "description": "Email address where form submissions are sent",
2420
+ "format": "email",
2421
+ "title": "Email Address"
2422
+ },
2423
+ "audience": {
2424
+ "type": "string",
2425
+ "description": "Target audience for the output (human readable or machine processable)",
2426
+ "enum": [
2427
+ "human",
2428
+ "machine"
2429
+ ],
2430
+ "title": "Audience"
2431
+ },
2432
+ "version": {
2433
+ "type": "string",
2434
+ "description": "Version identifier for the output format",
2435
+ "title": "Version"
2436
+ },
2437
+ "condition": {
2438
+ "type": [
2439
+ "array",
2440
+ "boolean",
2441
+ "number",
2442
+ "object",
2443
+ "string",
2444
+ "null"
2445
+ ],
2446
+ "description": "Optional condition that determines if submissions are sent to this output",
2447
+ "enum": [
2448
+ "",
2449
+ {
2450
+ "ref": {
2451
+ "path": [
2452
+ "conditions"
2453
+ ],
2454
+ "ancestor": "root",
2455
+ "in": true
2456
+ }
2457
+ }
2458
+ ],
2459
+ "title": "Condition"
2460
+ }
2461
+ },
2462
+ "required": [
2463
+ "audience",
2464
+ "version"
2465
+ ],
2466
+ "additionalProperties": false,
2467
+ "title": "Outputs Item"
2468
+ },
2469
+ "title": "Outputs"
2444
2470
  }
2445
2471
  },
2446
2472
  "required": [
2473
+ "schema",
2447
2474
  "pages",
2448
2475
  "sections"
2449
2476
  ],
@@ -54,6 +54,7 @@ import {
54
54
  type FormUploadedMessageData,
55
55
  type FormsBackupRequestedMessageData
56
56
  } from '~/src/form/form-audit/types.js'
57
+ import { type Output } from '~/src/form/form-definition/types.js'
57
58
  import { emailAddressNoUnicodeSchema } from '~/src/form/form-editor/index.js'
58
59
  import {
59
60
  contactSchema,
@@ -102,6 +103,37 @@ export const formDefinitionS3Meta = Joi.object<FormDefinitionS3Meta>()
102
103
  })
103
104
  .description('Schema for form data S3 object in message')
104
105
 
106
+ /**
107
+ * Stands apart from the definition's own output schema, which resolves a
108
+ * condition against the sibling `conditions` array - a lookup that has no
109
+ * meaning on an audit message carrying the output on its own.
110
+ */
111
+ export const formOutputMessageData = Joi.object<Output>()
112
+ .keys({
113
+ emailAddress: emailAddressNoUnicodeSchema
114
+ .required()
115
+ .description('Email address the submission is sent to'),
116
+ audience: Joi.string()
117
+ .trim()
118
+ .valid('human', 'machine')
119
+ .required()
120
+ .description(
121
+ 'Whether the submission is sent human readable or machine processable'
122
+ ),
123
+ version: Joi.string()
124
+ .trim()
125
+ .required()
126
+ .description('Version identifier of the output format'),
127
+ condition: Joi.string()
128
+ .trim()
129
+ .allow('')
130
+ .optional()
131
+ .description(
132
+ 'Id of the condition determining whether the submission is sent to this address'
133
+ )
134
+ })
135
+ .description('A submission email target as recorded on an audit event')
136
+
105
137
  export const formUpdatedMessageData = formMessageDataBase
106
138
  .append<FormUpdatedMessageData>({
107
139
  requestType: Joi.string()
@@ -1,7 +1,10 @@
1
1
  import { ComponentType } from '~/src/components/enums.js'
2
2
  import { type ComponentDef } from '~/src/components/types.js'
3
3
  import { type ConditionListItemRefValueDataV2 } from '~/src/conditions/types.js'
4
- import { type FormDefinition } from '~/src/form/form-definition/types.js'
4
+ import {
5
+ type FormDefinition,
6
+ type Output
7
+ } from '~/src/form/form-definition/types.js'
5
8
  import { type ControllerType } from '~/src/pages/enums.js'
6
9
  import { hasComponents } from '~/src/pages/helpers.js'
7
10
 
@@ -81,6 +84,34 @@ export function getHiddenFields(definition: FormDefinition) {
81
84
  return totalHiddenFields
82
85
  }
83
86
 
87
+ /**
88
+ * Builds the key identifying a submission email target. Two outputs sharing a
89
+ * key would send the same format of the same submission to the same inbox in
90
+ * the same circumstances, so one of them is redundant. Addresses are compared
91
+ * case-insensitively and a missing condition is treated as an empty one.
92
+ * @param output - submission email target
93
+ */
94
+ export function getOutputKey(output: Partial<Output>): string {
95
+ return [
96
+ output.emailAddress?.trim().toLowerCase() ?? '',
97
+ output.condition?.trim() ?? '',
98
+ output.audience ?? '',
99
+ output.version?.trim() ?? ''
100
+ ].join('|')
101
+ }
102
+
103
+ /**
104
+ * Whether two submission email targets are duplicates of one another
105
+ * @param output - submission email target
106
+ * @param otherOutput - submission email target to compare against
107
+ */
108
+ export function isDuplicateOutput(
109
+ output: Partial<Output>,
110
+ otherOutput: Partial<Output>
111
+ ): boolean {
112
+ return getOutputKey(output) === getOutputKey(otherOutput)
113
+ }
114
+
84
115
  /**
85
116
  * @param definition - form definition
86
117
  * @returns {boolean}
@@ -40,6 +40,7 @@ import {
40
40
  } from '~/src/form/form-definition/constants.js'
41
41
  import {
42
42
  isConditionListItemRefValueData,
43
+ isDuplicateOutput,
43
44
  isFormDefinition
44
45
  } from '~/src/form/form-definition/helpers.js'
45
46
  import {
@@ -53,6 +54,7 @@ import {
53
54
  type Item,
54
55
  type Link,
55
56
  type List,
57
+ type Output,
56
58
  type Page,
57
59
  type PhaseBanner,
58
60
  type Repeat,
@@ -1197,6 +1199,66 @@ const outputSchema = Joi.object<FormDefinition['output']>()
1197
1199
  .description('Version identifier for the output format')
1198
1200
  })
1199
1201
 
1202
+ const outputsItemSchema = Joi.object<Output>()
1203
+ .description('Configuration for a single submission email target')
1204
+ .keys({
1205
+ emailAddress: emailAddressNoUnicodeSchema.description(
1206
+ 'Email address where form submissions are sent'
1207
+ ),
1208
+ audience: Joi.string()
1209
+ .trim()
1210
+ .valid('human', 'machine')
1211
+ .required()
1212
+ .description(
1213
+ 'Target audience for the output (human readable or machine processable)'
1214
+ ),
1215
+ version: Joi.string()
1216
+ .trim()
1217
+ .required()
1218
+ .description('Version identifier for the output format')
1219
+ })
1220
+
1221
+ const outputsItemSchemaV2 = outputsItemSchema.keys({
1222
+ condition: Joi.string()
1223
+ .trim()
1224
+ .allow('')
1225
+ .optional()
1226
+ .when('/conditions', {
1227
+ is: Joi.exist(),
1228
+ then: Joi.valid('', conditionIdRef)
1229
+ })
1230
+ .description(
1231
+ 'Optional condition that determines if submissions are sent to this output'
1232
+ )
1233
+ .error(checkErrors(FormDefinitionError.RefOutputCondition))
1234
+ })
1235
+
1236
+ const outputsSchema = Joi.array<Output>()
1237
+ .items(outputsItemSchema)
1238
+ .unique(isDuplicateOutput)
1239
+ // Without this the raw Joi message names the array index, eg `"outputs[4]"
1240
+ // contains a duplicate value`, which means nothing to a form author
1241
+ .messages({
1242
+ 'array.unique':
1243
+ 'Email address {{#value.emailAddress}} is already receiving the same submissions'
1244
+ })
1245
+ .optional()
1246
+ .description('One or more email targets/types for submission emails')
1247
+ .error(checkErrors(FormDefinitionError.UniqueOutput))
1248
+
1249
+ const outputsSchemaV2 = Joi.array<Output>()
1250
+ .items(outputsItemSchemaV2)
1251
+ .unique(isDuplicateOutput)
1252
+ // Without this the raw Joi message names the array index, eg `"outputs[4]"
1253
+ // contains a duplicate value`, which means nothing to a form author
1254
+ .messages({
1255
+ 'array.unique':
1256
+ 'Email address {{#value.emailAddress}} is already receiving the same submissions'
1257
+ })
1258
+ .optional()
1259
+ .description('Outputs schema for V2 forms')
1260
+ .error(checkErrors(FormDefinitionError.UniqueOutput))
1261
+
1200
1262
  /**
1201
1263
  * Joi schema for `FormDefinition` interface
1202
1264
  * @see {@link FormDefinition}
@@ -1271,31 +1333,12 @@ export const formDefinitionSchema = Joi.object<FormDefinition>()
1271
1333
  .description('Phase banner configuration'),
1272
1334
  options: optionsSchema.optional().description('Options for the form'),
1273
1335
  outputEmail: emailAddressNoUnicodeSchema
1274
- .email({ tlds: { allow: ['uk'] } })
1275
1336
  .optional()
1276
1337
  .description('Email address where form submissions are sent'),
1277
1338
  output: outputSchema
1278
1339
  .optional()
1279
1340
  .description('Configuration for submission output format'),
1280
- outputs: Joi.array()
1281
- .items({
1282
- emailAddress: emailAddressNoUnicodeSchema
1283
- .email({ tlds: { allow: ['uk'] } })
1284
- .description('Email address where form submissions are sent'),
1285
- audience: Joi.string()
1286
- .trim()
1287
- .valid('human', 'machine')
1288
- .required()
1289
- .description(
1290
- 'Target audience for the output (human readable or machine processable)'
1291
- ),
1292
- version: Joi.string()
1293
- .trim()
1294
- .required()
1295
- .description('Version identifier for the output format')
1296
- })
1297
- .optional()
1298
- .description('One or more email targets/types for submission emails')
1341
+ outputs: outputsSchema
1299
1342
  })
1300
1343
 
1301
1344
  export const formDefinitionV2Schema = formDefinitionSchema
@@ -1303,6 +1346,7 @@ export const formDefinitionV2Schema = formDefinitionSchema
1303
1346
  schema: Joi.number()
1304
1347
  .integer()
1305
1348
  .valid(SchemaVersion.V2)
1349
+ .required()
1306
1350
  .description('Form schema version to use (2)'),
1307
1351
  pages: Joi.array<Page>()
1308
1352
  .items(pageSchemaV2)
@@ -1353,7 +1397,8 @@ export const formDefinitionV2Schema = formDefinitionSchema
1353
1397
  FormDefinitionError.UniqueSectionName,
1354
1398
  FormDefinitionError.UniqueSectionTitle
1355
1399
  ])
1356
- )
1400
+ ),
1401
+ outputs: outputsSchemaV2
1357
1402
  })
1358
1403
  .description('Form definition schema for V2')
1359
1404
 
@@ -188,6 +188,11 @@ export interface Output {
188
188
  audience: OutputAudience
189
189
  version: string
190
190
  emailAddress: string
191
+ /**
192
+ * Id of the condition determining whether submissions are sent to this
193
+ * output. V2 only - rejected by the V1 schema.
194
+ */
195
+ condition?: string
191
196
  }
192
197
 
193
198
  export interface FormOptions {
@@ -30,10 +30,12 @@ export const checkErrors = (
30
30
  const keyMatch =
31
31
  typeof err.local.key === 'number' ? err.local.path : err.local.key
32
32
 
33
+ // An empty key matches any unique constraint for schemas whose
34
+ // uniqueness is a composite of several keys rather than a single one
33
35
  if (
34
36
  errorDetails.type === FormDefinitionErrorType.Unique &&
35
37
  err.code === 'array.unique' &&
36
- keyMatch === errorDetails.key
38
+ (errorDetails.key === '' || keyMatch === errorDetails.key)
37
39
  ) {
38
40
  err.local.errorCode = formError
39
41
  err.local.errorType = FormDefinitionErrorType.Unique
@@ -172,6 +174,8 @@ export const formErrorsToMessages: Record<FormDefinitionError, string> = {
172
174
  'Each item in a list must have unique text. Change the item text to one that is not already used.',
173
175
  [FormDefinitionError.UniqueListItemValue]:
174
176
  'Each item in a list must have a unique value. Change the item value to one that is not already used.',
177
+ [FormDefinitionError.UniqueOutput]:
178
+ 'This email address is already receiving the same submissions. Change the address, condition or format, or remove the duplicate.',
175
179
  [FormDefinitionError.RefPageCondition]:
176
180
  'This page is referenced by a condition. Remove the condition before making changes to this page.',
177
181
  [FormDefinitionError.RefConditionComponentId]:
@@ -184,6 +188,8 @@ export const formErrorsToMessages: Record<FormDefinitionError, string> = {
184
188
  'A condition is using another condition. Remove the reference before making changes.',
185
189
  [FormDefinitionError.RefPageComponentList]:
186
190
  'A question on this page is using a list. Remove the reference before making changes to the list.',
191
+ [FormDefinitionError.RefOutputCondition]:
192
+ 'A submission email is using a condition that does not exist in this form. Select an existing condition or remove it.',
187
193
  [FormDefinitionError.IncompatibleConditionComponentType]:
188
194
  'You cannot change to this question type because this question is used in a condition. Remove the condition or select a different question type.',
189
195
  [FormDefinitionError.IncompatibleQuestionRegex]:
@@ -45,12 +45,14 @@ export enum FormDefinitionError {
45
45
  UniqueListItemId = 'unique_list_item_id',
46
46
  UniqueListItemText = 'unique_list_item_text',
47
47
  UniqueListItemValue = 'unique_list_item_value',
48
+ UniqueOutput = 'unique_output',
48
49
  RefPageCondition = 'ref_page_condition',
49
50
  RefConditionComponentId = 'ref_condition_component_id',
50
51
  RefConditionListId = 'ref_condition_list_id',
51
52
  RefConditionItemId = 'ref_condition_item_id',
52
53
  RefConditionConditionId = 'ref_condition_condition_id',
53
54
  RefPageComponentList = 'ref_page_component_list',
55
+ RefOutputCondition = 'ref_output_condition',
54
56
  IncompatibleConditionComponentType = 'incompatible_condition_component_type',
55
57
  IncompatibleQuestionRegex = 'incompatible_question_regex',
56
58
  Other = 'other'
@@ -125,6 +127,13 @@ export const formDefinitionErrors: FormDefinitionErrors = {
125
127
  key: 'value',
126
128
  type: FormDefinitionErrorType.Unique
127
129
  },
130
+ [FormDefinitionError.UniqueOutput]: {
131
+ // Outputs are compared on a composite of their address, condition,
132
+ // audience and version, so there is no single key to match on. An empty
133
+ // key matches any unique constraint on the schema it is attached to.
134
+ key: '',
135
+ type: FormDefinitionErrorType.Unique
136
+ },
128
137
  [FormDefinitionError.RefPageCondition]: {
129
138
  key: 'condition',
130
139
  type: FormDefinitionErrorType.Ref
@@ -149,6 +158,10 @@ export const formDefinitionErrors: FormDefinitionErrors = {
149
158
  key: 'list',
150
159
  type: FormDefinitionErrorType.Ref
151
160
  },
161
+ [FormDefinitionError.RefOutputCondition]: {
162
+ key: 'condition',
163
+ type: FormDefinitionErrorType.Ref
164
+ },
152
165
  [FormDefinitionError.IncompatibleConditionComponentType]: {
153
166
  key: 'componentId',
154
167
  type: FormDefinitionErrorType.Incompatible
@@ -19,3 +19,29 @@ export enum SubmissionEventMessageSource {
19
19
  export enum SubmissionEventMessageType {
20
20
  RUNNER_SAVE_AND_EXIT = 'RUNNER_SAVE_AND_EXIT'
21
21
  }
22
+
23
+ /**
24
+ * The outcome of evaluating a single form condition.
25
+ *
26
+ * `Error` is deliberately distinct from `False`. The runner defaults a failed
27
+ * evaluation to `false` when routing, but for an audit record the two are not
28
+ * the same thing and a consumer must be able to tell them apart.
29
+ */
30
+ export enum ConditionEvaluationOutcome {
31
+ /**
32
+ * The condition evaluated to `true`
33
+ */
34
+ True = 'true',
35
+
36
+ /**
37
+ * The condition evaluated to `false`
38
+ */
39
+ False = 'false',
40
+
41
+ /**
42
+ * Evaluation threw - most commonly because the condition references a
43
+ * component that is absent from the evaluation context, such as a component
44
+ * on a repeater page. The runner treats this as `false`.
45
+ */
46
+ Error = 'error'
47
+ }
@@ -2,6 +2,7 @@ import Joi from 'joi'
2
2
 
3
3
  import { FormStatus } from '~/src/common/enums.js'
4
4
  import {
5
+ ConditionEvaluationOutcome,
5
6
  SecurityQuestionsEnum,
6
7
  SubmissionEventMessageCategory,
7
8
  SubmissionEventMessageSchemaVersion,
@@ -11,6 +12,8 @@ import {
11
12
  import {
12
13
  type SaveAndExitMessage,
13
14
  type SaveAndExitMessageData,
15
+ type SubmitConditionEvaluation,
16
+ type SubmitConditionReference,
14
17
  type SubmitPayload,
15
18
  type SubmitRecord,
16
19
  type SubmitRecordset
@@ -61,6 +64,52 @@ export const formSubmitRecordsetSchema = Joi.object<SubmitRecordset>({
61
64
  .label('FormSubmitRecordset')
62
65
  .description('Collection of repeated field values from a repeatable section')
63
66
 
67
+ /**
68
+ * Joi schema for `SubmitConditionReference` interface
69
+ * @see {@link SubmitConditionReference}
70
+ */
71
+ export const formSubmitConditionReferenceSchema =
72
+ Joi.object<SubmitConditionReference>({
73
+ componentId: Joi.string()
74
+ .required()
75
+ .description('Identifier of the component the condition depends on'),
76
+ componentName: Joi.string()
77
+ .required()
78
+ .description(
79
+ 'Name of the component the condition depends on, matching the submitted record name'
80
+ ),
81
+ answered: Joi.boolean()
82
+ .required()
83
+ .description(
84
+ 'Whether the component held an answer when the condition was evaluated'
85
+ )
86
+ })
87
+ .label('FormSubmitConditionReference')
88
+ .description('A component a condition depends on, and its answered state')
89
+
90
+ /**
91
+ * Joi schema for `SubmitConditionEvaluation` interface
92
+ * @see {@link SubmitConditionEvaluation}
93
+ */
94
+ export const formSubmitConditionEvaluationSchema =
95
+ Joi.object<SubmitConditionEvaluation>({
96
+ conditionId: Joi.string()
97
+ .required()
98
+ .description('Identifier of the condition in the V2 form definition'),
99
+ outcome: Joi.string()
100
+ .valid(...Object.values(ConditionEvaluationOutcome))
101
+ .required()
102
+ .description('Result of evaluating the condition'),
103
+ references: Joi.array<SubmitConditionReference>()
104
+ .items(formSubmitConditionReferenceSchema)
105
+ .required()
106
+ .description(
107
+ 'Components the condition depends on, including those reached through nested condition references'
108
+ )
109
+ })
110
+ .label('FormSubmitConditionEvaluation')
111
+ .description('Recorded outcome of a single condition at submission')
112
+
64
113
  /**
65
114
  * Joi schema for `SubmitPayload` interface
66
115
  * @see {@link SubmitPayload}
@@ -1,5 +1,6 @@
1
1
  import { type FormStatus } from '~/src/common/enums.js'
2
2
  import {
3
+ type ConditionEvaluationOutcome,
3
4
  type SecurityQuestionsEnum,
4
5
  type SubmissionEventMessageCategory,
5
6
  type SubmissionEventMessageSchemaVersion,
@@ -7,6 +8,8 @@ import {
7
8
  type SubmissionEventMessageType
8
9
  } from '~/src/form/form-submission/enums.js'
9
10
  import {
11
+ formSubmitConditionEvaluationSchema,
12
+ formSubmitConditionReferenceSchema,
10
13
  formSubmitPayloadSchema,
11
14
  formSubmitRecordSchema,
12
15
  formSubmitRecordsetSchema
@@ -54,6 +57,57 @@ export interface SubmitRecordset {
54
57
  value: SubmitRecord[][]
55
58
  }
56
59
 
60
+ /**
61
+ * A component referenced by a condition, and whether the user had answered it
62
+ * when the condition was evaluated.
63
+ *
64
+ * An unanswered question is not absent from the runner's evaluation context -
65
+ * it is present as `null` - so conditions still return a boolean for questions
66
+ * that were skipped or never reached. Negative operators ("is not", "is shorter
67
+ * than") return `true` in that case. Without knowing which referenced questions
68
+ * were answered, a consumer cannot tell a real match from a vacuous one.
69
+ * @see {@link formSubmitConditionReferenceSchema}
70
+ */
71
+ export interface SubmitConditionReference {
72
+ /**
73
+ * The id of the referenced component
74
+ */
75
+ componentId: string
76
+
77
+ /**
78
+ * The name of the referenced component, as used in the submitted records
79
+ */
80
+ componentName: string
81
+
82
+ /**
83
+ * Whether the referenced component held an answer at the point of evaluation
84
+ */
85
+ answered: boolean
86
+ }
87
+
88
+ /**
89
+ * The recorded outcome of a single condition at the point of submission.
90
+ * V2 forms only - V1 conditions are not captured.
91
+ * @see {@link formSubmitConditionEvaluationSchema}
92
+ */
93
+ export interface SubmitConditionEvaluation {
94
+ /**
95
+ * The id of the condition, as authored in the V2 form definition
96
+ */
97
+ conditionId: string
98
+
99
+ /**
100
+ * The result of evaluating the condition
101
+ */
102
+ outcome: ConditionEvaluationOutcome
103
+
104
+ /**
105
+ * Every component the condition depends on, including those reached through
106
+ * nested condition references, and whether each was answered
107
+ */
108
+ references: SubmitConditionReference[]
109
+ }
110
+
57
111
  /**
58
112
  * Interface for the submission-api `/submit` payload
59
113
  * @see {@link formSubmitPayloadSchema}