@gobing-ai/ts-ai-decision 0.5.13

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/README.md ADDED
@@ -0,0 +1,142 @@
1
+ # @gobing-ai/ts-ai-decision
2
+
3
+ Declarative YAML **decision catalogs** over the `DecisionMaker` surface of
4
+ `@gobing-ai/ts-ai-runner`, served through a fallback-guaranteed **`DecisionHub`** (ADR-033). A
5
+ catalog declares named, static decision points whose shape follows TypeSafe AI's Jev question
6
+ spec (`type` / `instructions` / `criteria`), with a required `fallback` so every decision always
7
+ has a concrete answer.
8
+
9
+ The package owns three layers: the strict catalog schema and loader (JSON Schema + zod,
10
+ `${params.*}` template validation/rendering, load-time consistency checks), the
11
+ `DecisionMakerRegistry` (makers picked by name), and the `DecisionHub` (one Jev question per
12
+ `decide`, declared fallback on every backend outcome).
13
+
14
+ ## Catalog format
15
+
16
+ ```yaml
17
+ # decisions/support.yaml
18
+ $schema: "@gobing-ai/ts-ai-decision/schemas/decision-catalog.schema.json"
19
+ version: 1
20
+ decisions:
21
+ category:
22
+ type: choice
23
+ instructions: "Classify this ${params.channel} ticket: ${params.instructions}"
24
+ parameters:
25
+ channel: { type: enum, values: [email, chat], default: email }
26
+ criteria:
27
+ bug_report: Something is broken or producing errors
28
+ billing: Charges, invoices, refunds, subscriptions
29
+ fallback: billing
30
+ ```
31
+
32
+ ```ts
33
+ import { buildState, loadDecisionCatalog, renderQuestion, resolveDecisionInput } from '@gobing-ai/ts-ai-decision';
34
+
35
+ const catalog = await loadDecisionCatalog('decisions/support.yaml');
36
+ const decision = catalog.decisions.category;
37
+
38
+ const params = resolveDecisionInput(decision, { instructions: ticketText, channel: 'chat' });
39
+ const question = renderQuestion(decision, params); // rendered Jev question
40
+ const state = buildState(decision, params); // declared params, minus instructions/nulls
41
+ ```
42
+
43
+ Loading is YAML-only (`.yaml` / `.yml`) and reuses ts-runtime's `parseStructuredConfig`, so the
44
+ `$schema` header is honoured when present and no YAML dependency is added here. Inconsistent
45
+ catalogs (fallback outside the answer vocabulary, `${...}` refs to undeclared params or other
46
+ namespaces, a declared reserved `instructions` parameter, mismatched defaults) throw
47
+ `DecisionCatalogError { source, decisionId?, field? }`; input-contract violations throw
48
+ `DecisionInputError { decisionId, param }`.
49
+
50
+ The reserved `instructions` parameter carries caller text to the model: every decision
51
+ implicitly has one (type `string`, default `""`), it cannot be declared under `parameters`, and
52
+ an authored `instructions` template embeds it with `${params.instructions}` beside other
53
+ parameters. Every string leaf of `instructions` and of the criteria descriptions is substituted
54
+ once per `decide`; criteria labels, `fallback`, `maker` and `model` are never templated, so the
55
+ answer vocabulary stays static. `${env.*}` and any other namespace are rejected at load.
56
+
57
+ ## DecisionHub
58
+
59
+ `DecisionHub` loads catalogs and serves `decide(id, input)` with a guaranteed concrete answer —
60
+ see `examples/decisions.yaml` for a complete catalog:
61
+
62
+ ```ts
63
+ import { DecisionHub } from '@gobing-ai/ts-ai-decision';
64
+
65
+ const hub = new DecisionHub(); // registry with the built-ins, defaultMaker 'typesafe'
66
+ await hub.loadFile('decisions/support.yaml'); // or hub.load(loadedCatalog)
67
+
68
+ hub.list(); // [{ id, type, description, source }] — pure discovery
69
+ hub.describe('category'); // parameters (incl. instructions), criteria, fallback,
70
+ // effective minConfidence, maker and model
71
+ const r = await hub.decide('category', { instructions: ticketText, channel: 'chat' });
72
+ // { id, type, value, confidence, source, reason, maker, durationMs }
73
+ const r2 = await hub.decide('category', { instructions: ticketText }, { maker: 'fm-local' });
74
+ ```
75
+
76
+ `load` is all-or-nothing per catalog: a duplicate decision id across catalogs, or a `maker`
77
+ name (decision or `defaults.maker`) that is not registered, throws `DecisionCatalogError` and
78
+ registers nothing from the offending catalog. `list` and `describe` never construct a maker or
79
+ driver.
80
+
81
+ ### The fallback contract
82
+
83
+ `decide` never rejects for backend outcomes — only caller mistakes throw (`UnknownDecisionError`
84
+ for an unknown id, `DecisionInputError` for input breaking the parameter contract,
85
+ `UnknownDecisionMakerError` for an unregistered per-call maker; the constructor throws for an
86
+ unregistered `defaultMaker`). Every throw happens before any backend call.
87
+
88
+ Exactly one Jev question keyed by the decision id goes through a single
89
+ `maker.ask({ state, questions, model })`; `model` resolves decision → catalog default → omitted.
90
+ Backend outcomes map to the declared fallback with `source: 'default'`:
91
+
92
+ | Outcome | `reason` |
93
+ |---------|----------|
94
+ | Maker construction failed (factory throw, missing config) | `no-backend` |
95
+ | `DecisionTimeoutError` | `timeout` |
96
+ | Any other backend failure or an invalid answer | `error` |
97
+ | Confidence below the effective floor (`decision → defaults → 0.7`) | `low-confidence` |
98
+ | A confident answer | `accepted` (`source: 'model'`) |
99
+
100
+ Values: choice returns a string label, score the (possibly fractional) expected score, and noul
101
+ `p >= 0.5` with `probability` and confidence `max(p, 1 - p)`. Every result names the selected
102
+ `maker`.
103
+
104
+ ## Maker registry
105
+
106
+ `DecisionMakerRegistry` maps plain names to `DecisionMaker`s — the three built-ins (`typesafe`,
107
+ `fm-local`, `laya-local`) pre-registered lazily, plus anything consumers register (ADR-033):
108
+
109
+ ```ts
110
+ import { createDecisionMaker } from '@gobing-ai/ts-ai-runner';
111
+ import { DecisionMakerRegistry } from '@gobing-ai/ts-ai-decision';
112
+
113
+ const registry = new DecisionMakerRegistry(); // pass `builtins: false` to start empty
114
+ registry.register('scripted-judge', () => createDecisionMaker({ driver: myDriver }));
115
+
116
+ registry.has('fm-local'); // true — nothing was constructed yet
117
+ const maker = await registry.resolve('fm-local'); // factory runs here, once, and is memoised
118
+ registry.names(); // ['typesafe', 'fm-local', 'laya-local', 'scripted-judge']
119
+ ```
120
+
121
+ Registering builds nothing; the first `resolve` runs a factory and memoises the maker, and a
122
+ factory that throws is not memoised, so a later `resolve` retries. Invalid or duplicate names
123
+ throw `DecisionRegistryError`; unknown names throw `UnknownDecisionMakerError { name }`; factory
124
+ errors propagate unchanged. Pass `builtins: { makerOptions, driverOptions }` to tune the built-ins
125
+ (env/keys/timeouts, or executor/client injection for the local drivers).
126
+
127
+ The hub picks a maker purely by name — no new driver code per routing:
128
+ per-call `options.maker` → the decision's `maker` → the catalog's `defaults.maker` → the hub's
129
+ `defaultMaker` (default `typesafe`). Catalog names are checked at `load`; a misspelled per-call
130
+ name fails loudly instead of falling back. `createDecisionHub` composes the whole thing in one
131
+ call and never returns a partially built hub:
132
+
133
+ ```ts
134
+ import { createDecisionHub } from '@gobing-ai/ts-ai-decision';
135
+
136
+ const hub = await createDecisionHub({
137
+ catalogs: ['decisions/support.yaml'],
138
+ makers: { 'scripted-judge': () => createDecisionMaker({ driver: myDriver }) },
139
+ });
140
+ ```
141
+
142
+ See `docs/design/ai-decision-catalog.md` for the full field reference.
@@ -0,0 +1,16 @@
1
+ import type { FileSystem } from '@gobing-ai/ts-runtime';
2
+ import type { DecisionCatalog } from './types';
3
+ /**
4
+ * Options for {@link loadDecisionCatalog} and {@link parseDecisionCatalog}.
5
+ */
6
+ export interface CatalogLoadOptions {
7
+ /** Honour the top-level `$schema` when present. Default true. */
8
+ validateSchema?: boolean;
9
+ /** ts-runtime FileSystem used to read the catalog; defaults to `createNodeFileSystem()`. */
10
+ fileSystem?: Pick<FileSystem, 'readFile'>;
11
+ }
12
+ /** Load and parse a YAML catalog file, returning typed decision definitions. */
13
+ export declare function loadDecisionCatalog(path: string, options?: CatalogLoadOptions): Promise<DecisionCatalog>;
14
+ /** Parse a catalog string (YAML) into typed decision definitions. The source must end in .yaml/.yml. */
15
+ export declare function parseDecisionCatalog(content: string, source: string, options?: CatalogLoadOptions): Promise<DecisionCatalog>;
16
+ //# sourceMappingURL=catalog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../src/catalog.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAKxD,OAAO,KAAK,EAER,eAAe,EAMlB,MAAM,SAAS,CAAC;AAYjB;;GAEG;AACH,MAAM,WAAW,kBAAkB;IAC/B,iEAAiE;IACjE,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,4FAA4F;IAC5F,UAAU,CAAC,EAAE,IAAI,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;CAC7C;AAaD,gFAAgF;AAChF,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,kBAAuB,GAAG,OAAO,CAAC,eAAe,CAAC,CAalH;AAED,wGAAwG;AACxG,wBAAsB,oBAAoB,CACtC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,kBAAuB,GACjC,OAAO,CAAC,eAAe,CAAC,CA+B1B"}
@@ -0,0 +1,253 @@
1
+ import { createNodeFileSystem, parseStructuredConfig } from '@gobing-ai/ts-runtime';
2
+ import { DecisionCatalogError } from './errors.js';
3
+ import { DEFAULT_INSTRUCTIONS_TEMPLATE, INSTRUCTIONS_PARAM, implicitInstructionsParam } from './params.js';
4
+ import { DecisionCatalogSchema } from './schema.js';
5
+ /**
6
+ * Catalog loading (task 0089 R5/R6): a thin layer over ts-runtime's
7
+ * `parseStructuredConfig` (YAML parse + `$schema` validation), then the zod
8
+ * schema, then the cross-field checks. Validation is all-or-nothing per
9
+ * catalog — a catalog that fails any check produces no definitions.
10
+ */
11
+ /** Same template regex as `ts-dual-workflow-engine`'s variable resolver. */
12
+ const TEMPLATE_REF = /\$\{([^}]+)\}/g;
13
+ const YAML_SOURCE_PATTERN = /\.(yaml|yml)$/;
14
+ function assertYamlSource(source) {
15
+ if (!YAML_SOURCE_PATTERN.test(source)) {
16
+ throw new DecisionCatalogError(`Decision catalogs are YAML only; source "${source}" does not end in .yaml or .yml`, source);
17
+ }
18
+ }
19
+ /** Load and parse a YAML catalog file, returning typed decision definitions. */
20
+ export async function loadDecisionCatalog(path, options = {}) {
21
+ assertYamlSource(path);
22
+ const fileSystem = options.fileSystem ?? createNodeFileSystem();
23
+ let content;
24
+ try {
25
+ content = await fileSystem.readFile(path);
26
+ }
27
+ catch (error) {
28
+ throw new DecisionCatalogError(`Cannot read decision catalog "${path}": ${error instanceof Error ? error.message : String(error)}`, path);
29
+ }
30
+ return parseDecisionCatalog(content, path, options);
31
+ }
32
+ /** Parse a catalog string (YAML) into typed decision definitions. The source must end in .yaml/.yml. */
33
+ export async function parseDecisionCatalog(content, source, options = {}) {
34
+ assertYamlSource(source);
35
+ let raw;
36
+ try {
37
+ raw = await parseStructuredConfig(content, source, {
38
+ validateSchema: options.validateSchema ?? true,
39
+ fileSystem: options.fileSystem,
40
+ });
41
+ }
42
+ catch (error) {
43
+ // YAML syntax (including duplicate keys) and $schema failures are load failures:
44
+ // wrap so callers always see DecisionCatalogError naming the source.
45
+ throw new DecisionCatalogError(`Decision catalog "${source}" failed to parse or validate: ` +
46
+ `${error instanceof Error ? error.message : String(error)}`, source);
47
+ }
48
+ const parsed = DecisionCatalogSchema.safeParse(raw);
49
+ if (!parsed.success) {
50
+ const issue = parsed.error.issues[0];
51
+ const fieldPath = issue?.path.map(String).join('.') ?? '';
52
+ const decisionId = fieldPath.startsWith('decisions.') ? fieldPath.split('.')[1] : undefined;
53
+ throw new DecisionCatalogError(`Invalid decision catalog "${source}"${fieldPath ? ` at ${fieldPath}` : ''}: ${issue?.message ?? 'schema mismatch'}`, source, decisionId, fieldPath || undefined);
54
+ }
55
+ return buildCatalog(parsed.data, source);
56
+ }
57
+ function buildCatalog(parsed, source) {
58
+ const decisions = {};
59
+ for (const [id, raw] of Object.entries(parsed.decisions)) {
60
+ decisions[id] = checkDecision(id, raw, source);
61
+ }
62
+ const defaults = parsed.defaults ?? {};
63
+ return { source, defaults, decisions };
64
+ }
65
+ function catalogError(source, decisionId, field, message) {
66
+ return new DecisionCatalogError(`Decision "${decisionId}" in "${source}": ${message}`, source, decisionId, field);
67
+ }
68
+ function normalizeParam(name, rawParam) {
69
+ if (typeof rawParam === 'string')
70
+ return { name, type: rawParam, required: true };
71
+ const hasDefault = 'default' in rawParam;
72
+ return {
73
+ name,
74
+ type: rawParam.type,
75
+ required: !hasDefault,
76
+ ...(hasDefault ? { default: rawParam.default } : {}),
77
+ ...(rawParam.values !== undefined ? { values: rawParam.values } : {}),
78
+ ...(rawParam.description !== undefined ? { description: rawParam.description } : {}),
79
+ };
80
+ }
81
+ function checkParam(id, param, source) {
82
+ if (param.type === 'enum') {
83
+ if (param.values === undefined || param.values.length < 1) {
84
+ throw catalogError(source, id, `parameters.${param.name}.values`, 'enum parameter requires at least one value');
85
+ }
86
+ }
87
+ else if (param.values !== undefined) {
88
+ throw catalogError(source, id, `parameters.${param.name}.values`, `only enum parameters may declare values; parameter type is "${param.type}"`);
89
+ }
90
+ if (!param.required && !defaultMatches(param)) {
91
+ throw catalogError(source, id, `parameters.${param.name}.default`, `default ${JSON.stringify(param.default ?? null)} does not match declared type "${param.type}"` +
92
+ (param.values ? ` with values [${param.values.join(', ')}]` : ''));
93
+ }
94
+ }
95
+ function defaultMatches(param) {
96
+ const value = param.default;
97
+ switch (param.type) {
98
+ case 'string':
99
+ return typeof value === 'string';
100
+ case 'number':
101
+ return typeof value === 'number' && Number.isFinite(value);
102
+ case 'boolean':
103
+ return typeof value === 'boolean';
104
+ case 'enum':
105
+ return typeof value === 'string' && param.values?.includes(value) === true;
106
+ case 'json':
107
+ return true; // any JSON value, including null
108
+ }
109
+ }
110
+ function normalizeCriteria(id, raw, source) {
111
+ const criteria = raw.criteria;
112
+ if (raw.type === 'choice') {
113
+ if (criteria === undefined || Array.isArray(criteria) || typeof criteria !== 'object' || criteria === null) {
114
+ throw catalogError(source, id, 'criteria', 'choice decision requires a criteria label map');
115
+ }
116
+ const labels = criteria;
117
+ if (Object.keys(labels).length < 2) {
118
+ throw catalogError(source, id, 'criteria', 'choice criteria requires at least two labels');
119
+ }
120
+ return { kind: 'choice', labels };
121
+ }
122
+ if (raw.type === 'score') {
123
+ if (criteria === undefined) {
124
+ throw catalogError(source, id, 'criteria', 'score decision requires a criteria rubric list');
125
+ }
126
+ if (!Array.isArray(criteria)) {
127
+ throw catalogError(source, id, 'criteria', 'score criteria must be a list of level descriptions');
128
+ }
129
+ if (criteria.length < 2) {
130
+ throw catalogError(source, id, 'criteria', 'score criteria requires at least two levels');
131
+ }
132
+ return { kind: 'score', levels: criteria };
133
+ }
134
+ // noul: optional { true?, false? } (R4). The zod union may have matched the open
135
+ // record branch, so unknown outcome keys are rejected here.
136
+ if (criteria === undefined)
137
+ return { kind: 'noul' };
138
+ if (Array.isArray(criteria) || typeof criteria !== 'object' || criteria === null) {
139
+ throw catalogError(source, id, 'criteria', 'noul criteria must be an object with "true"/"false" keys');
140
+ }
141
+ for (const key of Object.keys(criteria)) {
142
+ if (key !== 'true' && key !== 'false') {
143
+ throw catalogError(source, id, 'criteria', `noul criteria accepts only the keys "true" and "false"; got "${key}"`);
144
+ }
145
+ }
146
+ const outcomes = criteria;
147
+ return {
148
+ kind: 'noul',
149
+ ...(outcomes.true !== undefined || outcomes.false !== undefined
150
+ ? {
151
+ outcomes: {
152
+ ...(outcomes.true !== undefined ? { true: outcomes.true } : {}),
153
+ ...(outcomes.false !== undefined ? { false: outcomes.false } : {}),
154
+ },
155
+ }
156
+ : {}),
157
+ };
158
+ }
159
+ function checkFallback(id, type, criteria, fallback, source) {
160
+ if (type === 'choice') {
161
+ // Own-property check: prototype keys like "constructor" must not pass as labels.
162
+ if (criteria.kind !== 'choice' || typeof fallback !== 'string' || !Object.hasOwn(criteria.labels, fallback)) {
163
+ const labels = criteria.kind === 'choice' ? Object.keys(criteria.labels).join(', ') : '';
164
+ throw catalogError(source, id, 'fallback', `fallback ${JSON.stringify(fallback)} is not a declared criteria label (${labels})`);
165
+ }
166
+ return;
167
+ }
168
+ if (type === 'score') {
169
+ const levelCount = criteria.kind === 'score' ? criteria.levels.length : 0;
170
+ if (criteria.kind !== 'score' ||
171
+ typeof fallback !== 'number' ||
172
+ !Number.isInteger(fallback) ||
173
+ fallback < 0 ||
174
+ fallback >= levelCount) {
175
+ throw catalogError(source, id, 'fallback', `fallback ${JSON.stringify(fallback)} is not an integer level in [0, ${levelCount - 1}]`);
176
+ }
177
+ return;
178
+ }
179
+ if (typeof fallback !== 'boolean') {
180
+ throw catalogError(source, id, 'fallback', `noul fallback must be a boolean; got ${JSON.stringify(fallback)}`);
181
+ }
182
+ }
183
+ /** Collect every `${...}` reference inside string leaves of a JSON value (recursing objects/arrays). */
184
+ function collectTemplateRefs(value, into) {
185
+ if (typeof value === 'string') {
186
+ for (const match of value.matchAll(TEMPLATE_REF))
187
+ into.add(match[1] ?? '');
188
+ return;
189
+ }
190
+ if (Array.isArray(value)) {
191
+ for (const item of value)
192
+ collectTemplateRefs(item, into);
193
+ return;
194
+ }
195
+ if (value !== null && typeof value === 'object') {
196
+ for (const entry of Object.values(value))
197
+ collectTemplateRefs(entry, into);
198
+ }
199
+ }
200
+ function checkTemplateRefs(id, field, entry, declared, source) {
201
+ const refs = new Set();
202
+ collectTemplateRefs(entry, refs);
203
+ for (const ref of refs) {
204
+ if (!ref.startsWith('params.')) {
205
+ throw catalogError(source, id, field, `template ref "\${${ref}}" uses namespace "${ref.split('.')[0] ?? ''}"; only "params" is allowed`);
206
+ }
207
+ const name = ref.slice('params.'.length);
208
+ if (!declared.has(name)) {
209
+ throw catalogError(source, id, field, `template ref "\${${ref}}" names an undeclared parameter`);
210
+ }
211
+ }
212
+ }
213
+ function checkDecision(id, raw, source) {
214
+ const parameters = {};
215
+ const rawParams = raw.parameters ?? {};
216
+ if (INSTRUCTIONS_PARAM in rawParams) {
217
+ throw catalogError(source, id, 'parameters.instructions', 'the "instructions" parameter is reserved and implicit (string, default ""); it cannot be declared');
218
+ }
219
+ for (const [name, rawParam] of Object.entries(rawParams)) {
220
+ const param = normalizeParam(name, rawParam);
221
+ checkParam(id, param, source);
222
+ parameters[name] = param;
223
+ }
224
+ parameters[INSTRUCTIONS_PARAM] = implicitInstructionsParam();
225
+ const criteria = normalizeCriteria(id, raw, source);
226
+ checkFallback(id, raw.type, criteria, raw.fallback, source);
227
+ const instructions = raw.instructions ?? DEFAULT_INSTRUCTIONS_TEMPLATE;
228
+ const declared = new Set(Object.keys(parameters));
229
+ checkTemplateRefs(id, 'instructions', instructions, declared, source);
230
+ const criteriaEntries = criteria.kind === 'choice'
231
+ ? Object.values(criteria.labels)
232
+ : criteria.kind === 'score'
233
+ ? [...criteria.levels]
234
+ : criteria.outcomes
235
+ ? [criteria.outcomes.true, criteria.outcomes.false].filter((e) => e !== undefined)
236
+ : [];
237
+ for (const entry of criteriaEntries) {
238
+ checkTemplateRefs(id, 'criteria', entry, declared, source);
239
+ }
240
+ return {
241
+ id,
242
+ source,
243
+ type: raw.type,
244
+ ...(raw.description !== undefined ? { description: raw.description } : {}),
245
+ instructions,
246
+ parameters,
247
+ criteria,
248
+ fallback: raw.fallback,
249
+ ...(raw.minConfidence !== undefined ? { minConfidence: raw.minConfidence } : {}),
250
+ ...(raw.maker !== undefined ? { maker: raw.maker } : {}),
251
+ ...(raw.model !== undefined ? { model: raw.model } : {}),
252
+ };
253
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * DecisionCatalogError — task 0089 — load-time failure for a decision catalog.
3
+ * Names the source and, when known, the decision id and field, so an
4
+ * inconsistent catalog points straight at the offending key.
5
+ */
6
+ export declare class DecisionCatalogError extends Error {
7
+ readonly source: string;
8
+ readonly decisionId?: string | undefined;
9
+ readonly field?: string | undefined;
10
+ constructor(message: string, source: string, decisionId?: string | undefined, field?: string | undefined);
11
+ }
12
+ /**
13
+ * DecisionRegistryError — task 0091 — invalid DecisionMakerRegistry use: a name
14
+ * that fails `^[a-z][a-z0-9-]*$` or a duplicate registration.
15
+ */
16
+ export declare class DecisionRegistryError extends Error {
17
+ constructor(message: string);
18
+ }
19
+ /**
20
+ * UnknownDecisionMakerError — task 0091 — resolve of a name that is not
21
+ * registered. The `name` property carries the unknown maker name; the class is
22
+ * identified via `instanceof`, because assigning `this.name` would clobber it.
23
+ */
24
+ export declare class UnknownDecisionMakerError extends Error {
25
+ readonly name: string;
26
+ constructor(message: string, name: string);
27
+ }
28
+ /**
29
+ * UnknownDecisionError — task 0090 — decide/describe of a decision id that no
30
+ * loaded catalog declares. A caller mistake: it throws instead of falling
31
+ * back, before any backend call. The `decisionId` property carries the id; the
32
+ * class is identified via `instanceof`, like UnknownDecisionMakerError.
33
+ */
34
+ export declare class UnknownDecisionError extends Error {
35
+ readonly decisionId: string;
36
+ constructor(message: string, decisionId: string);
37
+ }
38
+ /**
39
+ * DecisionInputError — task 0089 R7 — violation of one decision's parameter
40
+ * contract: unknown key, missing required parameter, or type mismatch.
41
+ */
42
+ export declare class DecisionInputError extends Error {
43
+ readonly decisionId: string;
44
+ readonly param: string;
45
+ constructor(message: string, decisionId: string, param: string);
46
+ }
47
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,qBAAa,oBAAqB,SAAQ,KAAK;IAGvC,QAAQ,CAAC,MAAM,EAAE,MAAM;IACvB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM;IAC5B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM;gBAHvB,OAAO,EAAE,MAAM,EACN,MAAM,EAAE,MAAM,EACd,UAAU,CAAC,EAAE,MAAM,YAAA,EACnB,KAAK,CAAC,EAAE,MAAM,YAAA;CAK9B;AAED;;;GAGG;AACH,qBAAa,qBAAsB,SAAQ,KAAK;gBAChC,OAAO,EAAE,MAAM;CAI9B;AAED;;;;GAIG;AACH,qBAAa,yBAA0B,SAAQ,KAAK;aAG1B,IAAI,EAAE,MAAM;gBAD9B,OAAO,EAAE,MAAM,EACG,IAAI,EAAE,MAAM;CAIrC;AAED;;;;;GAKG;AACH,qBAAa,oBAAqB,SAAQ,KAAK;IAGvC,QAAQ,CAAC,UAAU,EAAE,MAAM;gBAD3B,OAAO,EAAE,MAAM,EACN,UAAU,EAAE,MAAM;CAKlC;AAED;;;GAGG;AACH,qBAAa,kBAAmB,SAAQ,KAAK;IAGrC,QAAQ,CAAC,UAAU,EAAE,MAAM;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM;gBAFtB,OAAO,EAAE,MAAM,EACN,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,MAAM;CAK7B"}
package/dist/errors.js ADDED
@@ -0,0 +1,67 @@
1
+ /**
2
+ * DecisionCatalogError — task 0089 — load-time failure for a decision catalog.
3
+ * Names the source and, when known, the decision id and field, so an
4
+ * inconsistent catalog points straight at the offending key.
5
+ */
6
+ export class DecisionCatalogError extends Error {
7
+ source;
8
+ decisionId;
9
+ field;
10
+ constructor(message, source, decisionId, field) {
11
+ super(message);
12
+ this.source = source;
13
+ this.decisionId = decisionId;
14
+ this.field = field;
15
+ this.name = 'DecisionCatalogError';
16
+ }
17
+ }
18
+ /**
19
+ * DecisionRegistryError — task 0091 — invalid DecisionMakerRegistry use: a name
20
+ * that fails `^[a-z][a-z0-9-]*$` or a duplicate registration.
21
+ */
22
+ export class DecisionRegistryError extends Error {
23
+ constructor(message) {
24
+ super(message);
25
+ this.name = 'DecisionRegistryError';
26
+ }
27
+ }
28
+ /**
29
+ * UnknownDecisionMakerError — task 0091 — resolve of a name that is not
30
+ * registered. The `name` property carries the unknown maker name; the class is
31
+ * identified via `instanceof`, because assigning `this.name` would clobber it.
32
+ */
33
+ export class UnknownDecisionMakerError extends Error {
34
+ name;
35
+ constructor(message, name) {
36
+ super(message);
37
+ this.name = name;
38
+ }
39
+ }
40
+ /**
41
+ * UnknownDecisionError — task 0090 — decide/describe of a decision id that no
42
+ * loaded catalog declares. A caller mistake: it throws instead of falling
43
+ * back, before any backend call. The `decisionId` property carries the id; the
44
+ * class is identified via `instanceof`, like UnknownDecisionMakerError.
45
+ */
46
+ export class UnknownDecisionError extends Error {
47
+ decisionId;
48
+ constructor(message, decisionId) {
49
+ super(message);
50
+ this.decisionId = decisionId;
51
+ this.name = 'UnknownDecisionError';
52
+ }
53
+ }
54
+ /**
55
+ * DecisionInputError — task 0089 R7 — violation of one decision's parameter
56
+ * contract: unknown key, missing required parameter, or type mismatch.
57
+ */
58
+ export class DecisionInputError extends Error {
59
+ decisionId;
60
+ param;
61
+ constructor(message, decisionId, param) {
62
+ super(message);
63
+ this.decisionId = decisionId;
64
+ this.param = param;
65
+ this.name = 'DecisionInputError';
66
+ }
67
+ }