@conform-ed/qti-react 0.0.14 → 0.0.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +6 -4
- package/dist/index.js +2492 -408
- package/dist/normalized-item.d.ts +7 -5
- package/dist/pnp.d.ts +115 -0
- package/dist/response-validity.d.ts +28 -0
- package/dist/rp/evaluate.d.ts +6 -1
- package/dist/rp/index.d.ts +2 -2
- package/dist/rp/interpreter.d.ts +7 -1
- package/dist/rp/lookup-table.d.ts +17 -0
- package/dist/rp/template-processing.d.ts +5 -0
- package/dist/rp/types.d.ts +71 -7
- package/dist/runtime.d.ts +95 -0
- package/dist/store.d.ts +47 -0
- package/dist/test/controller.d.ts +22 -0
- package/dist/test/index.d.ts +2 -1
- package/dist/test/results.d.ts +102 -0
- package/dist/test/session-store.d.ts +32 -0
- package/dist/test/types.d.ts +173 -5
- package/dist/types.d.ts +5 -0
- package/package.json +5 -1
- package/src/content-model.ts +44 -4
- package/src/index.ts +43 -1
- package/src/normalized-item.ts +106 -4
- package/src/pci/mount.ts +11 -3
- package/src/pnp.ts +333 -0
- package/src/reference-skin/choice.ts +3 -0
- package/src/response-validity.ts +163 -0
- package/src/rp/evaluate.ts +280 -32
- package/src/rp/index.ts +5 -0
- package/src/rp/interpreter.ts +81 -1
- package/src/rp/lookup-table.ts +46 -0
- package/src/rp/template-processing.ts +41 -0
- package/src/rp/types.ts +75 -7
- package/src/runtime.ts +397 -20
- package/src/store.ts +146 -8
- package/src/test/controller.ts +856 -82
- package/src/test/index.ts +23 -0
- package/src/test/results.ts +378 -0
- package/src/test/session-store.ts +109 -1
- package/src/test/types.ts +172 -5
- package/src/types.ts +1 -0
- package/src/xspattern.d.ts +11 -0
package/src/rp/interpreter.ts
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
evaluateExpression,
|
|
15
15
|
type EvalEnv,
|
|
16
16
|
} from "./evaluate";
|
|
17
|
+
import { hasLookupTable, lookupTableValue } from "./lookup-table";
|
|
17
18
|
import { resolveTemplate } from "./templates";
|
|
18
19
|
import type {
|
|
19
20
|
OutcomeDeclarationView,
|
|
@@ -35,7 +36,13 @@ import {
|
|
|
35
36
|
type MaybeRpValue,
|
|
36
37
|
} from "./values";
|
|
37
38
|
|
|
38
|
-
const supportedRuleKinds = new Set([
|
|
39
|
+
const supportedRuleKinds = new Set([
|
|
40
|
+
"responseCondition",
|
|
41
|
+
"setOutcomeValue",
|
|
42
|
+
"lookupOutcomeValue",
|
|
43
|
+
"responseProcessingFragment",
|
|
44
|
+
"exitResponse",
|
|
45
|
+
]);
|
|
39
46
|
|
|
40
47
|
/**
|
|
41
48
|
* RP additionally supports the random operators: the attempt store always provides a
|
|
@@ -83,6 +90,13 @@ export function executeResponseProcessing(
|
|
|
83
90
|
function initialOutcomes(): Map<string, MaybeRpValue> {
|
|
84
91
|
const outcomes = defaultOutcomes(context.outcomeDeclarations);
|
|
85
92
|
|
|
93
|
+
// The built-in completionStatus is "declared implicitly" and starts at
|
|
94
|
+
// "not_attempted" (§2.2.2.3); seeding it into the map lets setOutcomeValue
|
|
95
|
+
// change it. Explicit declarations (legacy content) keep the declared path.
|
|
96
|
+
if (!outcomes.has("completionStatus")) {
|
|
97
|
+
outcomes.set("completionStatus", rpValue("single", [context.completionStatus ?? "not_attempted"], "identifier"));
|
|
98
|
+
}
|
|
99
|
+
|
|
86
100
|
// Adaptive carry-over: prior outcome values (from earlier attempts in the same
|
|
87
101
|
// item session) replace the declared defaults.
|
|
88
102
|
for (const [identifier, prior] of Object.entries(context.priorOutcomes ?? {})) {
|
|
@@ -110,6 +124,16 @@ export function executeResponseProcessing(
|
|
|
110
124
|
|
|
111
125
|
const env: EvalEnv = {
|
|
112
126
|
lookupVariable: (identifier) => {
|
|
127
|
+
// Built-in session variables (reserved identifiers; items must not declare
|
|
128
|
+
// them): duration in seconds, numAttempts including the current attempt.
|
|
129
|
+
if (identifier === "duration") {
|
|
130
|
+
return context.duration === undefined ? null : rpValue("single", [context.duration], "duration");
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (identifier === "numAttempts") {
|
|
134
|
+
return context.numAttempts === undefined ? null : rpValue("single", [context.numAttempts], "integer");
|
|
135
|
+
}
|
|
136
|
+
|
|
113
137
|
const declaration = declarationsById.get(identifier);
|
|
114
138
|
|
|
115
139
|
if (declaration) {
|
|
@@ -130,6 +154,22 @@ export function executeResponseProcessing(
|
|
|
130
154
|
},
|
|
131
155
|
responseDeclaration: (identifier) => declarationsById.get(identifier),
|
|
132
156
|
responseValue: (identifier) => context.responses[identifier] ?? null,
|
|
157
|
+
variableDefault: (identifier) => {
|
|
158
|
+
const declaration =
|
|
159
|
+
declarationsById.get(identifier) ??
|
|
160
|
+
context.outcomeDeclarations.find((entry) => entry.identifier === identifier) ??
|
|
161
|
+
templateDeclarationsById.get(identifier);
|
|
162
|
+
|
|
163
|
+
if (!declaration?.defaultValue) {
|
|
164
|
+
return null; // "NULL if no default value was declared" (§2.11.1.3)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return rpValue(
|
|
168
|
+
declaration.cardinality,
|
|
169
|
+
declaration.defaultValue.values.map((entry) => coerceScalar(entry.value, declaration.baseType)),
|
|
170
|
+
declaration.baseType,
|
|
171
|
+
);
|
|
172
|
+
},
|
|
133
173
|
normalization: context.normalization,
|
|
134
174
|
random: context.random,
|
|
135
175
|
customOperators: context.customOperators,
|
|
@@ -162,6 +202,28 @@ export function executeResponseProcessing(
|
|
|
162
202
|
continue;
|
|
163
203
|
}
|
|
164
204
|
|
|
205
|
+
// "A simple group of responseRules" (§5.118): executes inline, in order, over
|
|
206
|
+
// the same outcome state; exitResponse propagates through the recursion.
|
|
207
|
+
if (rule.kind === "responseProcessingFragment") {
|
|
208
|
+
executeRules(rule.rules ?? []);
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (rule.kind === "lookupOutcomeValue") {
|
|
213
|
+
if (rule.identifier !== undefined && rule.expression !== undefined) {
|
|
214
|
+
const declaration = context.outcomeDeclarations.find((entry) => entry.identifier === rule.identifier);
|
|
215
|
+
|
|
216
|
+
if (!hasLookupTable(declaration)) {
|
|
217
|
+
// §5.87 presumes "the lookupTable associated with the outcome's
|
|
218
|
+
// declaration" — no table, no spec-defined value: refuse, never guess.
|
|
219
|
+
throw new RpUnsupportedError("lookupOutcomeValue");
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
outcomes.set(rule.identifier, lookupTableValue(declaration, evaluateExpression(rule.expression, env)));
|
|
223
|
+
}
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
|
|
165
227
|
// responseCondition
|
|
166
228
|
if (rule.responseIf && branchTaken(rule.responseIf)) {
|
|
167
229
|
continue;
|
|
@@ -195,6 +257,12 @@ export function executeResponseProcessing(
|
|
|
195
257
|
export interface RpIssueOptions {
|
|
196
258
|
/** `customOperator` classes the consumer has registered implementations for. */
|
|
197
259
|
readonly customOperatorClasses?: ReadonlySet<string>;
|
|
260
|
+
/**
|
|
261
|
+
* The item's outcome declarations; when supplied, a `lookupOutcomeValue` rule whose
|
|
262
|
+
* declaration carries no lookupTable is reported statically (gate parity with the
|
|
263
|
+
* runtime refusal).
|
|
264
|
+
*/
|
|
265
|
+
readonly outcomeDeclarations?: readonly OutcomeDeclarationView[];
|
|
198
266
|
}
|
|
199
267
|
|
|
200
268
|
/** Static coverage walk for `canDeliver`: reports constructs the interpreter lacks without executing. */
|
|
@@ -223,10 +291,22 @@ export function collectRpIssues(
|
|
|
223
291
|
continue;
|
|
224
292
|
}
|
|
225
293
|
|
|
294
|
+
if (rule.kind === "lookupOutcomeValue" && options?.outcomeDeclarations !== undefined) {
|
|
295
|
+
const declaration = options.outcomeDeclarations.find((entry) => entry.identifier === rule.identifier);
|
|
296
|
+
|
|
297
|
+
if (!hasLookupTable(declaration)) {
|
|
298
|
+
report("lookupOutcomeValue", "Outcome declaration has no matchTable/interpolationTable.");
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
226
302
|
if (rule.expression) {
|
|
227
303
|
collectExpressionIssues(rule.expression, rpExpressionKinds, report, options?.customOperatorClasses);
|
|
228
304
|
}
|
|
229
305
|
|
|
306
|
+
if (rule.rules) {
|
|
307
|
+
walkRules(rule.rules); // responseProcessingFragment nesting (§5.118)
|
|
308
|
+
}
|
|
309
|
+
|
|
230
310
|
for (const branch of [rule.responseIf, ...(rule.responseElseIfs ?? [])]) {
|
|
231
311
|
if (branch) {
|
|
232
312
|
collectExpressionIssues(branch.expression, rpExpressionKinds, report, options?.customOperatorClasses);
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lookupTable evaluation for the `lookupOutcomeValue` rule (§5.87): "sets the value
|
|
3
|
+
* of an outcome variable to the value obtained by looking up the value of the
|
|
4
|
+
* associated expression in the lookupTable associated with the outcome's
|
|
5
|
+
* declaration." Shared by the item interpreter and the test controller — the test's
|
|
6
|
+
* own outcome declarations use the same view type.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { OutcomeDeclarationView } from "./types";
|
|
10
|
+
import { coerceScalar, rpValue, singleNumber, type MaybeRpValue } from "./values";
|
|
11
|
+
|
|
12
|
+
export function hasLookupTable(declaration: OutcomeDeclarationView | undefined): declaration is OutcomeDeclarationView {
|
|
13
|
+
return declaration?.matchTable !== undefined || declaration?.interpolationTable !== undefined;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Look `source` up in the declaration's lookupTable. A NULL or non-numeric source is
|
|
18
|
+
* read as "no matching table entry is found" (§5.90.1) — an interpretive call the
|
|
19
|
+
* spec leaves open — so it takes the defaultValue path, never a refusal. "If
|
|
20
|
+
* omitted, the NULL value is used." (§5.90.1/§5.78.1)
|
|
21
|
+
*/
|
|
22
|
+
export function lookupTableValue(declaration: OutcomeDeclarationView, source: MaybeRpValue): MaybeRpValue {
|
|
23
|
+
const value = singleNumber(source);
|
|
24
|
+
const table = declaration.matchTable ?? declaration.interpolationTable;
|
|
25
|
+
const target = value === null ? undefined : matchedTarget(declaration, value);
|
|
26
|
+
const result = target ?? table?.defaultValue;
|
|
27
|
+
|
|
28
|
+
if (result === undefined) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return rpValue("single", [coerceScalar(result, declaration.baseType)], declaration.baseType);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function matchedTarget(declaration: OutcomeDeclarationView, value: number) {
|
|
36
|
+
if (declaration.matchTable) {
|
|
37
|
+
// "the first qti-match-table-entry with an exact match to the source" (§5.90)
|
|
38
|
+
return declaration.matchTable.matchTableEntries.find((entry) => entry.sourceValue === value)?.targetValue;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// "the first interpolationTableEntry with a sourceValue that is less than or equal
|
|
42
|
+
// to (subject to includeBoundary) the source value" (§5.78); document order decides.
|
|
43
|
+
return declaration.interpolationTable?.interpolationTableEntries.find((entry) =>
|
|
44
|
+
(entry.includeBoundary ?? true) ? entry.sourceValue <= value : entry.sourceValue < value,
|
|
45
|
+
)?.targetValue;
|
|
46
|
+
}
|
|
@@ -118,6 +118,19 @@ export function executeTemplateProcessing(
|
|
|
118
118
|
lookupVariable: (identifier) => templateValues.get(identifier) ?? null,
|
|
119
119
|
responseDeclaration: (identifier) => responseDeclarationsById.get(identifier),
|
|
120
120
|
responseValue: () => null, // no candidate responses exist at template-processing time
|
|
121
|
+
variableDefault: (identifier) => {
|
|
122
|
+
const declaration = declarationsById.get(identifier) ?? responseDeclarationsById.get(identifier);
|
|
123
|
+
|
|
124
|
+
if (!declaration?.defaultValue) {
|
|
125
|
+
return null; // "NULL if no default value was declared" (§2.11.1.3)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return rpValue(
|
|
129
|
+
declaration.cardinality,
|
|
130
|
+
declaration.defaultValue.values.map((entry) => coerceScalar(entry.value, declaration.baseType)),
|
|
131
|
+
declaration.baseType,
|
|
132
|
+
);
|
|
133
|
+
},
|
|
121
134
|
random: mulberry32(context.seed),
|
|
122
135
|
customOperators: context.customOperators,
|
|
123
136
|
};
|
|
@@ -229,6 +242,34 @@ export function executeTemplateProcessing(
|
|
|
229
242
|
};
|
|
230
243
|
}
|
|
231
244
|
|
|
245
|
+
/**
|
|
246
|
+
* The effective template declarations for a clone: test-level `templateDefault`
|
|
247
|
+
* values (§5.152) replace the declared defaults. A NULL value clears the default.
|
|
248
|
+
*/
|
|
249
|
+
export function applyTemplateDefaultOverrides(
|
|
250
|
+
declarations: readonly TemplateDeclarationView[],
|
|
251
|
+
overrides: Readonly<Record<string, OutcomeValue>>,
|
|
252
|
+
): readonly TemplateDeclarationView[] {
|
|
253
|
+
return declarations.map((declaration) => {
|
|
254
|
+
if (!(declaration.identifier in overrides)) {
|
|
255
|
+
return declaration;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const value = overrides[declaration.identifier];
|
|
259
|
+
|
|
260
|
+
if (value === null || value === undefined) {
|
|
261
|
+
const { defaultValue: _cleared, ...rest } = declaration;
|
|
262
|
+
|
|
263
|
+
return rest;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return {
|
|
267
|
+
...declaration,
|
|
268
|
+
defaultValue: { values: (Array.isArray(value) ? value : [value]).map((member) => ({ value: member })) },
|
|
269
|
+
};
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
|
|
232
273
|
/** The effective response declarations for a clone: setCorrectResponse overrides applied. */
|
|
233
274
|
export function applyCorrectResponseOverrides(
|
|
234
275
|
declarations: readonly ResponseDeclarationView[],
|
package/src/rp/types.ts
CHANGED
|
@@ -32,11 +32,53 @@ export interface RpValue {
|
|
|
32
32
|
|
|
33
33
|
export type MaybeRpValue = RpValue | null;
|
|
34
34
|
|
|
35
|
+
/** One matchTable row: exact integer source → target (§7.23). */
|
|
36
|
+
export interface MatchTableEntryView {
|
|
37
|
+
readonly sourceValue: number;
|
|
38
|
+
readonly targetValue: RpScalar;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* "A matchTable transforms a source integer by finding the first
|
|
43
|
+
* qti-match-table-entry with an exact match to the source." (§5.90)
|
|
44
|
+
*/
|
|
45
|
+
export interface MatchTableView {
|
|
46
|
+
/** "The default outcome value to be used when no matching table entry is found.
|
|
47
|
+
* If omitted, the NULL value is used." (§5.90.1) */
|
|
48
|
+
readonly defaultValue?: RpScalar;
|
|
49
|
+
readonly matchTableEntries: readonly MatchTableEntryView[];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** One interpolationTable row; sourceValue is "the lower bound … to match this entry" (§7.18.1). */
|
|
53
|
+
export interface InterpolationTableEntryView {
|
|
54
|
+
readonly sourceValue: number;
|
|
55
|
+
readonly targetValue: RpScalar;
|
|
56
|
+
/** "If 'true', the default, then an exact match of the value is considered a match
|
|
57
|
+
* of this entry." (§7.18.2) */
|
|
58
|
+
readonly includeBoundary?: boolean;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* "An interpolationTable transforms a source float (or integer) by finding the first
|
|
63
|
+
* interpolationTableEntry with a sourceValue that is less than or equal to (subject
|
|
64
|
+
* to includeBoundary) the source value." (§5.78)
|
|
65
|
+
*/
|
|
66
|
+
export interface InterpolationTableView {
|
|
67
|
+
readonly defaultValue?: RpScalar;
|
|
68
|
+
readonly interpolationTableEntries: readonly InterpolationTableEntryView[];
|
|
69
|
+
}
|
|
70
|
+
|
|
35
71
|
export interface OutcomeDeclarationView {
|
|
36
72
|
readonly identifier: string;
|
|
37
73
|
readonly cardinality: Cardinality;
|
|
38
74
|
readonly baseType?: string;
|
|
39
75
|
readonly defaultValue?: { readonly values: ReadonlyArray<{ readonly value: RpScalar }> };
|
|
76
|
+
/** The declaration's lookupTable (at most one of the two), read by `lookupOutcomeValue` (§5.87). */
|
|
77
|
+
readonly matchTable?: MatchTableView;
|
|
78
|
+
readonly interpolationTable?: InterpolationTableView;
|
|
79
|
+
/** Declared score bounds, aggregated by `outcomeMaximum`/`outcomeMinimum` (§2.11.2.6-7). */
|
|
80
|
+
readonly normalMaximum?: number;
|
|
81
|
+
readonly normalMinimum?: number;
|
|
40
82
|
}
|
|
41
83
|
|
|
42
84
|
/**
|
|
@@ -49,11 +91,14 @@ export interface RpExpressionView {
|
|
|
49
91
|
readonly baseType?: string;
|
|
50
92
|
readonly value?: RpScalar;
|
|
51
93
|
readonly expressions?: readonly RpExpressionView[];
|
|
52
|
-
/**
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
94
|
+
/**
|
|
95
|
+
* Bounds/step for the random operators and `anyN`; string values are variable
|
|
96
|
+
* references resolved at runtime (§2.11.3.6), bare or brace-enclosed (§7.13).
|
|
97
|
+
*/
|
|
98
|
+
readonly min?: number | string;
|
|
99
|
+
readonly max?: number | string;
|
|
100
|
+
readonly step?: number | string;
|
|
101
|
+
/** `equal` tolerance window; string entries are variable references. */
|
|
57
102
|
readonly toleranceMode?: "exact" | "absolute" | "relative";
|
|
58
103
|
readonly tolerance?: ReadonlyArray<number | string>;
|
|
59
104
|
readonly includeLowerBound?: boolean;
|
|
@@ -67,14 +112,18 @@ export interface RpExpressionView {
|
|
|
67
112
|
readonly figures?: number | string;
|
|
68
113
|
/** Pass count for `repeat`. */
|
|
69
114
|
readonly numberRepeats?: number | string;
|
|
115
|
+
/** XSD-dialect pattern for `patternMatch`; "{ref}" resolves from a variable (§7.13). */
|
|
116
|
+
readonly pattern?: string;
|
|
70
117
|
/** String comparison controls for `stringMatch` and `substring`. */
|
|
71
118
|
readonly caseSensitive?: boolean;
|
|
72
119
|
readonly substring?: boolean;
|
|
73
120
|
/** Area for `inside` (QTI shape + coords string). */
|
|
74
121
|
readonly shape?: string;
|
|
75
122
|
readonly coords?: string;
|
|
76
|
-
/** Test-level subset selection (`testVariables`
|
|
123
|
+
/** Test-level subset selection (`testVariables`, `outcomeMinimum`/`outcomeMaximum`, `number*`). */
|
|
77
124
|
readonly variableIdentifier?: string;
|
|
125
|
+
/** Outcome variable whose declared bounds `outcomeMinimum`/`outcomeMaximum` look up (§7.28.4). */
|
|
126
|
+
readonly outcomeIdentifier?: string;
|
|
78
127
|
readonly weightIdentifier?: string;
|
|
79
128
|
readonly sectionIdentifier?: string;
|
|
80
129
|
readonly includeCategory?: string | readonly string[];
|
|
@@ -103,11 +152,16 @@ export interface RpConditionBranch {
|
|
|
103
152
|
readonly rules: readonly RpRuleView[];
|
|
104
153
|
}
|
|
105
154
|
|
|
106
|
-
/**
|
|
155
|
+
/**
|
|
156
|
+
* One response rule: responseCondition, setOutcomeValue, lookupOutcomeValue,
|
|
157
|
+
* responseProcessingFragment, or exitResponse.
|
|
158
|
+
*/
|
|
107
159
|
export interface RpRuleView {
|
|
108
160
|
readonly kind: string;
|
|
109
161
|
readonly identifier?: string;
|
|
110
162
|
readonly expression?: RpExpressionView;
|
|
163
|
+
/** Nested rules of a `responseProcessingFragment` (§5.118). */
|
|
164
|
+
readonly rules?: readonly RpRuleView[];
|
|
111
165
|
readonly responseIf?: RpConditionBranch;
|
|
112
166
|
readonly responseElseIfs?: readonly RpConditionBranch[];
|
|
113
167
|
readonly responseElse?: { readonly rules: readonly RpRuleView[] };
|
|
@@ -156,6 +210,20 @@ export interface ResponseProcessingContext {
|
|
|
156
210
|
* submission sequence then replays the exact same outcomes (ADR-0004 determinism).
|
|
157
211
|
*/
|
|
158
212
|
readonly random?: (() => number) | undefined;
|
|
213
|
+
/**
|
|
214
|
+
* Built-in session variables (reserved identifiers; items must not declare them).
|
|
215
|
+
* `duration` is the item session's elapsed seconds; `numAttempts` "increases by 1
|
|
216
|
+
* at the start of each attempt", so it includes the attempt being scored.
|
|
217
|
+
*/
|
|
218
|
+
readonly duration?: number | undefined;
|
|
219
|
+
readonly numAttempts?: number | undefined;
|
|
220
|
+
/**
|
|
221
|
+
* The session's current `completionStatus` — the third built-in, "declared
|
|
222
|
+
* implicitly"; it enters the outcome map (defaulting to "not_attempted") so
|
|
223
|
+
* `setOutcomeValue` can change it (§2.2.2.3). An explicit declaration (legacy
|
|
224
|
+
* content) wins over this value.
|
|
225
|
+
*/
|
|
226
|
+
readonly completionStatus?: string | undefined;
|
|
159
227
|
/** Registered vendor `customOperator` implementations by class (opt-in). */
|
|
160
228
|
readonly customOperators?: Readonly<Record<string, CustomOperatorImplementation>> | undefined;
|
|
161
229
|
}
|