@contractspec/lib.contracts 1.49.0 → 1.51.0
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/app-config/contracts.d.ts +51 -51
- package/dist/app-config/events.d.ts +27 -27
- package/dist/app-config/lifecycle-contracts.d.ts +55 -55
- package/dist/app-config/runtime.d.ts +1 -1
- package/dist/app-config/spec.d.ts +1 -1
- package/dist/capabilities/capabilities.d.ts +64 -5
- package/dist/capabilities/capabilities.js +125 -0
- package/dist/capabilities/context.d.ts +88 -0
- package/dist/capabilities/context.js +87 -0
- package/dist/capabilities/docs/capabilities.docblock.js +191 -2
- package/dist/capabilities/guards.d.ts +110 -0
- package/dist/capabilities/guards.js +146 -0
- package/dist/capabilities/index.d.ts +4 -1
- package/dist/capabilities/index.js +4 -1
- package/dist/capabilities/validation.d.ts +76 -0
- package/dist/capabilities/validation.js +141 -0
- package/dist/client/react/feature-render.d.ts +2 -2
- package/dist/data-views/runtime.d.ts +1 -1
- package/dist/events.d.ts +79 -13
- package/dist/events.js +33 -3
- package/dist/examples/schema.d.ts +10 -10
- package/dist/experiments/spec.d.ts +7 -4
- package/dist/features/install.d.ts +4 -4
- package/dist/features/types.d.ts +28 -32
- package/dist/index.d.ts +21 -12
- package/dist/index.js +12 -3
- package/dist/install.d.ts +1 -1
- package/dist/integrations/openbanking/contracts/accounts.d.ts +67 -67
- package/dist/integrations/openbanking/contracts/balances.d.ts +35 -35
- package/dist/integrations/openbanking/contracts/transactions.d.ts +49 -49
- package/dist/integrations/openbanking/models.d.ts +55 -55
- package/dist/integrations/operations.d.ts +103 -103
- package/dist/integrations/spec.d.ts +1 -1
- package/dist/knowledge/operations.d.ts +67 -67
- package/dist/llm/exporters.d.ts +2 -2
- package/dist/markdown.d.ts +1 -1
- package/dist/onboarding-base.d.ts +29 -29
- package/dist/operations/operation.d.ts +6 -0
- package/dist/ownership.d.ts +133 -8
- package/dist/ownership.js +25 -0
- package/dist/policy/context.d.ts +237 -0
- package/dist/policy/context.js +227 -0
- package/dist/policy/guards.d.ts +145 -0
- package/dist/policy/guards.js +254 -0
- package/dist/policy/index.d.ts +12 -1
- package/dist/policy/index.js +11 -1
- package/dist/policy/spec.d.ts +7 -4
- package/dist/policy/validation.d.ts +67 -0
- package/dist/policy/validation.js +307 -0
- package/dist/presentations/presentations.d.ts +6 -0
- package/dist/tests/spec.d.ts +17 -12
- package/dist/themes.d.ts +7 -4
- package/dist/translations/index.d.ts +6 -0
- package/dist/translations/index.js +5 -0
- package/dist/translations/registry.d.ts +144 -0
- package/dist/translations/registry.js +223 -0
- package/dist/translations/spec.d.ts +126 -0
- package/dist/translations/spec.js +31 -0
- package/dist/translations/validation.d.ts +85 -0
- package/dist/translations/validation.js +328 -0
- package/dist/types.d.ts +140 -14
- package/dist/versioning/index.d.ts +2 -1
- package/dist/versioning/index.js +2 -1
- package/dist/versioning/refs.d.ts +179 -0
- package/dist/versioning/refs.js +161 -0
- package/dist/workflow/context.d.ts +191 -0
- package/dist/workflow/context.js +227 -0
- package/dist/workflow/index.d.ts +4 -2
- package/dist/workflow/index.js +4 -2
- package/dist/workflow/spec.d.ts +1 -1
- package/dist/workflow/validation.d.ts +64 -2
- package/dist/workflow/validation.js +194 -1
- package/package.json +19 -6
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
//#region src/translations/validation.ts
|
|
2
|
+
/**
|
|
3
|
+
* Validate a translation spec for internal consistency.
|
|
4
|
+
*
|
|
5
|
+
* @param spec - Translation spec to validate
|
|
6
|
+
* @returns Validation result with any issues found
|
|
7
|
+
*/
|
|
8
|
+
function validateTranslationSpec(spec) {
|
|
9
|
+
const issues = [];
|
|
10
|
+
validateMeta(spec, issues);
|
|
11
|
+
validateLocale(spec, issues);
|
|
12
|
+
validateMessages(spec, issues);
|
|
13
|
+
validatePluralRules(spec, issues);
|
|
14
|
+
return {
|
|
15
|
+
valid: issues.filter((i) => i.level === "error").length === 0,
|
|
16
|
+
issues
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
function validateMeta(spec, issues) {
|
|
20
|
+
const { meta } = spec;
|
|
21
|
+
if (!meta.key?.trim()) issues.push({
|
|
22
|
+
level: "error",
|
|
23
|
+
message: "Translation spec must have a non-empty key",
|
|
24
|
+
path: "meta.key"
|
|
25
|
+
});
|
|
26
|
+
if (!meta.version?.trim()) issues.push({
|
|
27
|
+
level: "error",
|
|
28
|
+
message: "Translation spec must have a non-empty version",
|
|
29
|
+
path: "meta.version"
|
|
30
|
+
});
|
|
31
|
+
if (!meta.domain?.trim()) issues.push({
|
|
32
|
+
level: "error",
|
|
33
|
+
message: "Translation spec must have a non-empty domain",
|
|
34
|
+
path: "meta.domain"
|
|
35
|
+
});
|
|
36
|
+
if (!meta.owners?.length) issues.push({
|
|
37
|
+
level: "warning",
|
|
38
|
+
message: "Translation spec should specify owners",
|
|
39
|
+
path: "meta.owners"
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
function validateLocale(spec, issues) {
|
|
43
|
+
if (!spec.locale?.trim()) issues.push({
|
|
44
|
+
level: "error",
|
|
45
|
+
message: "Translation spec must specify a locale",
|
|
46
|
+
path: "locale"
|
|
47
|
+
});
|
|
48
|
+
else if (!isValidLocaleFormat(spec.locale)) issues.push({
|
|
49
|
+
level: "warning",
|
|
50
|
+
message: `Locale "${spec.locale}" may not be a valid ISO 639-1 code`,
|
|
51
|
+
path: "locale"
|
|
52
|
+
});
|
|
53
|
+
if (spec.fallback && !isValidLocaleFormat(spec.fallback)) issues.push({
|
|
54
|
+
level: "warning",
|
|
55
|
+
message: `Fallback locale "${spec.fallback}" may not be a valid ISO 639-1 code`,
|
|
56
|
+
path: "fallback"
|
|
57
|
+
});
|
|
58
|
+
if (spec.fallback === spec.locale) issues.push({
|
|
59
|
+
level: "warning",
|
|
60
|
+
message: "Fallback locale is the same as primary locale",
|
|
61
|
+
path: "fallback"
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
function validateMessages(spec, issues) {
|
|
65
|
+
const { messages } = spec;
|
|
66
|
+
if (!messages || Object.keys(messages).length === 0) {
|
|
67
|
+
issues.push({
|
|
68
|
+
level: "warning",
|
|
69
|
+
message: "Translation spec has no messages",
|
|
70
|
+
path: "messages"
|
|
71
|
+
});
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
for (const [key, message] of Object.entries(messages)) {
|
|
75
|
+
const path = `messages.${key}`;
|
|
76
|
+
if (!message.value?.trim()) issues.push({
|
|
77
|
+
level: "error",
|
|
78
|
+
message: `Message "${key}" has an empty value`,
|
|
79
|
+
path: `${path}.value`
|
|
80
|
+
});
|
|
81
|
+
const valueParams = extractPlaceholders(message.value);
|
|
82
|
+
const declaredPlaceholders = message.placeholders ?? [];
|
|
83
|
+
for (const placeholder of declaredPlaceholders) if (!valueParams.includes(placeholder.name)) issues.push({
|
|
84
|
+
level: "warning",
|
|
85
|
+
message: `Placeholder "${placeholder.name}" is defined but not used in message "${key}"`,
|
|
86
|
+
path: `${path}.placeholders`
|
|
87
|
+
});
|
|
88
|
+
if (message.placeholders !== void 0) {
|
|
89
|
+
for (const param of valueParams) if (!declaredPlaceholders.find((p) => p.name === param)) issues.push({
|
|
90
|
+
level: "info",
|
|
91
|
+
message: `Placeholder "{${param}}" used in message "${key}" but not declared`,
|
|
92
|
+
path: `${path}.placeholders`
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
const icuResult = validateICUFormat(message.value);
|
|
96
|
+
if (!icuResult.valid) issues.push({
|
|
97
|
+
level: "error",
|
|
98
|
+
message: `Message "${key}" has invalid ICU format: ${icuResult.error}`,
|
|
99
|
+
path: `${path}.value`
|
|
100
|
+
});
|
|
101
|
+
if (message.variants?.length) {
|
|
102
|
+
const seenVariants = /* @__PURE__ */ new Set();
|
|
103
|
+
for (const variant of message.variants) {
|
|
104
|
+
const variantKey = `${variant.type}:${variant.key}`;
|
|
105
|
+
if (seenVariants.has(variantKey)) issues.push({
|
|
106
|
+
level: "warning",
|
|
107
|
+
message: `Duplicate variant "${variantKey}" in message "${key}"`,
|
|
108
|
+
path: `${path}.variants`
|
|
109
|
+
});
|
|
110
|
+
seenVariants.add(variantKey);
|
|
111
|
+
if (!variant.value?.trim()) issues.push({
|
|
112
|
+
level: "error",
|
|
113
|
+
message: `Variant "${variantKey}" has an empty value in message "${key}"`,
|
|
114
|
+
path: `${path}.variants`
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (message.maxLength !== void 0) {
|
|
119
|
+
if (message.maxLength <= 0) issues.push({
|
|
120
|
+
level: "error",
|
|
121
|
+
message: `Message "${key}" has invalid maxLength (must be positive)`,
|
|
122
|
+
path: `${path}.maxLength`
|
|
123
|
+
});
|
|
124
|
+
else if (message.value.length > message.maxLength) issues.push({
|
|
125
|
+
level: "warning",
|
|
126
|
+
message: `Message "${key}" value exceeds maxLength (${message.value.length} > ${message.maxLength})`,
|
|
127
|
+
path: `${path}.value`
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
function validatePluralRules(spec, issues) {
|
|
133
|
+
if (!spec.pluralRules?.length) return;
|
|
134
|
+
for (let i = 0; i < spec.pluralRules.length; i++) {
|
|
135
|
+
const rule = spec.pluralRules[i];
|
|
136
|
+
if (!rule) continue;
|
|
137
|
+
const path = `pluralRules[${i}]`;
|
|
138
|
+
if (!rule.variable?.trim()) issues.push({
|
|
139
|
+
level: "error",
|
|
140
|
+
message: "Plural rule must specify a variable name",
|
|
141
|
+
path: `${path}.variable`
|
|
142
|
+
});
|
|
143
|
+
if (!rule.rules?.length) issues.push({
|
|
144
|
+
level: "error",
|
|
145
|
+
message: "Plural rule must have at least one category",
|
|
146
|
+
path: `${path}.rules`
|
|
147
|
+
});
|
|
148
|
+
else if (!rule.rules.some((r) => r.category === "other")) issues.push({
|
|
149
|
+
level: "error",
|
|
150
|
+
message: `Plural rule "${rule.variable}" must have an "other" category`,
|
|
151
|
+
path: `${path}.rules`
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Validate ICU message format syntax.
|
|
157
|
+
*
|
|
158
|
+
* This is a simplified validator that checks for balanced braces
|
|
159
|
+
* and extracts placeholders, plurals, and selects.
|
|
160
|
+
*
|
|
161
|
+
* @param message - Message string to validate
|
|
162
|
+
* @returns Validation result with extracted components
|
|
163
|
+
*/
|
|
164
|
+
function validateICUFormat(message) {
|
|
165
|
+
const placeholders = [];
|
|
166
|
+
const plurals = [];
|
|
167
|
+
const selects = [];
|
|
168
|
+
let braceDepth = 0;
|
|
169
|
+
let currentPlaceholder = "";
|
|
170
|
+
let inPlaceholder = false;
|
|
171
|
+
try {
|
|
172
|
+
for (let i = 0; i < message.length; i++) {
|
|
173
|
+
const char = message[i];
|
|
174
|
+
const prevChar = i > 0 ? message[i - 1] : "";
|
|
175
|
+
if (char === "'" && i + 1 < message.length) {
|
|
176
|
+
const nextChar = message[i + 1];
|
|
177
|
+
if (nextChar === "{" || nextChar === "}") {
|
|
178
|
+
i++;
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if (char === "{") {
|
|
183
|
+
if (prevChar !== "'") {
|
|
184
|
+
braceDepth++;
|
|
185
|
+
if (braceDepth === 1) {
|
|
186
|
+
inPlaceholder = true;
|
|
187
|
+
currentPlaceholder = "";
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
} else if (char === "}") {
|
|
191
|
+
if (prevChar !== "'") {
|
|
192
|
+
braceDepth--;
|
|
193
|
+
if (braceDepth === 0 && inPlaceholder) {
|
|
194
|
+
processPlaceholder(currentPlaceholder.trim(), placeholders, plurals, selects);
|
|
195
|
+
inPlaceholder = false;
|
|
196
|
+
}
|
|
197
|
+
if (braceDepth < 0) return {
|
|
198
|
+
valid: false,
|
|
199
|
+
error: "Unbalanced closing brace",
|
|
200
|
+
placeholders: [],
|
|
201
|
+
plurals: [],
|
|
202
|
+
selects: []
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
} else if (inPlaceholder) currentPlaceholder += char;
|
|
206
|
+
}
|
|
207
|
+
if (braceDepth !== 0) return {
|
|
208
|
+
valid: false,
|
|
209
|
+
error: "Unbalanced opening brace",
|
|
210
|
+
placeholders: [],
|
|
211
|
+
plurals: [],
|
|
212
|
+
selects: []
|
|
213
|
+
};
|
|
214
|
+
return {
|
|
215
|
+
valid: true,
|
|
216
|
+
placeholders,
|
|
217
|
+
plurals,
|
|
218
|
+
selects
|
|
219
|
+
};
|
|
220
|
+
} catch (error) {
|
|
221
|
+
return {
|
|
222
|
+
valid: false,
|
|
223
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
224
|
+
placeholders: [],
|
|
225
|
+
plurals: [],
|
|
226
|
+
selects: []
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
function processPlaceholder(placeholder, placeholders, plurals, selects) {
|
|
231
|
+
const parts = placeholder.split(",").map((p) => p.trim());
|
|
232
|
+
const name = parts[0];
|
|
233
|
+
const type = parts[1]?.toLowerCase();
|
|
234
|
+
if (name && !placeholders.includes(name)) placeholders.push(name);
|
|
235
|
+
if (type === "plural" || type === "selectordinal") {
|
|
236
|
+
if (name && !plurals.includes(name)) plurals.push(name);
|
|
237
|
+
} else if (type === "select") {
|
|
238
|
+
if (name && !selects.includes(name)) selects.push(name);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Find messages that exist in source but not in target.
|
|
243
|
+
*
|
|
244
|
+
* @param sourceSpec - Source translation spec (usually base locale like 'en')
|
|
245
|
+
* @param targetSpec - Target translation spec to check
|
|
246
|
+
* @returns Array of missing translations
|
|
247
|
+
*/
|
|
248
|
+
function findMissingTranslations(sourceSpec, targetSpec) {
|
|
249
|
+
const missing = [];
|
|
250
|
+
for (const [key, message] of Object.entries(sourceSpec.messages)) if (!targetSpec.messages[key]) missing.push({
|
|
251
|
+
messageKey: key,
|
|
252
|
+
sourceLocale: sourceSpec.locale,
|
|
253
|
+
targetLocale: targetSpec.locale,
|
|
254
|
+
sourceMessage: message
|
|
255
|
+
});
|
|
256
|
+
return missing;
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Find all missing translations across a registry.
|
|
260
|
+
*
|
|
261
|
+
* Compares each locale against a base locale to find missing keys.
|
|
262
|
+
*
|
|
263
|
+
* @param registry - Translation registry
|
|
264
|
+
* @param specKey - Translation spec key to check
|
|
265
|
+
* @param baseLocale - Base locale to compare against
|
|
266
|
+
* @returns Map of locale to missing translations
|
|
267
|
+
*/
|
|
268
|
+
function findAllMissingTranslations(registry, specKey, baseLocale) {
|
|
269
|
+
const result = /* @__PURE__ */ new Map();
|
|
270
|
+
const baseSpec = registry.getLatest(specKey, baseLocale);
|
|
271
|
+
if (!baseSpec) return result;
|
|
272
|
+
const locales = registry.listLocales(specKey);
|
|
273
|
+
for (const locale of locales) {
|
|
274
|
+
if (locale === baseLocale) continue;
|
|
275
|
+
const targetSpec = registry.getLatest(specKey, locale);
|
|
276
|
+
if (targetSpec) {
|
|
277
|
+
const missing = findMissingTranslations(baseSpec, targetSpec);
|
|
278
|
+
if (missing.length > 0) result.set(locale, missing);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return result;
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Validate all translation specs in a registry.
|
|
285
|
+
*
|
|
286
|
+
* @param registry - Translation registry to validate
|
|
287
|
+
* @returns Validation result
|
|
288
|
+
*/
|
|
289
|
+
function validateTranslationRegistry(registry) {
|
|
290
|
+
const issues = [];
|
|
291
|
+
for (const spec of registry.list()) {
|
|
292
|
+
const specResult = validateTranslationSpec(spec);
|
|
293
|
+
issues.push(...specResult.issues.map((i) => ({
|
|
294
|
+
...i,
|
|
295
|
+
path: `${spec.meta.key}.v${spec.meta.version}:${spec.locale}${i.path ? `.${i.path}` : ""}`
|
|
296
|
+
})));
|
|
297
|
+
}
|
|
298
|
+
return {
|
|
299
|
+
valid: issues.filter((i) => i.level === "error").length === 0,
|
|
300
|
+
issues
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
var TranslationValidationError = class extends Error {
|
|
304
|
+
constructor(message, issues) {
|
|
305
|
+
super(message);
|
|
306
|
+
this.issues = issues;
|
|
307
|
+
this.name = "TranslationValidationError";
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
/**
|
|
311
|
+
* Assert that a translation spec is valid, throwing if not.
|
|
312
|
+
*
|
|
313
|
+
* @param spec - Translation spec to validate
|
|
314
|
+
* @throws {TranslationValidationError} If validation fails
|
|
315
|
+
*/
|
|
316
|
+
function assertTranslationSpecValid(spec) {
|
|
317
|
+
const result = validateTranslationSpec(spec);
|
|
318
|
+
if (!result.valid) throw new TranslationValidationError(`Translation ${spec.meta.key}.v${spec.meta.version}:${spec.locale} is invalid`, result.issues);
|
|
319
|
+
}
|
|
320
|
+
function isValidLocaleFormat(locale) {
|
|
321
|
+
return /^[a-z]{2}(-[A-Z]{2})?$/.test(locale);
|
|
322
|
+
}
|
|
323
|
+
function extractPlaceholders(value) {
|
|
324
|
+
return validateICUFormat(value).placeholders;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
//#endregion
|
|
328
|
+
export { TranslationValidationError, assertTranslationSpecValid, findAllMissingTranslations, findMissingTranslations, validateICUFormat, validateTranslationRegistry, validateTranslationSpec };
|
package/dist/types.d.ts
CHANGED
|
@@ -9,83 +9,209 @@ import { SpecVariantResolver } from "./experiments/spec-resolver.js";
|
|
|
9
9
|
import { EventRegistry } from "./events.js";
|
|
10
10
|
|
|
11
11
|
//#region src/types.d.ts
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Common runtime types for ContractSpec execution.
|
|
15
|
+
*
|
|
16
|
+
* Provides types for execution context, policy decisions, event emission,
|
|
17
|
+
* and handler context passed through the contracts runtime.
|
|
18
|
+
*
|
|
19
|
+
* @module types
|
|
20
|
+
*/
|
|
12
21
|
/**
|
|
13
|
-
*
|
|
22
|
+
* Actor type representing the entity making a request.
|
|
23
|
+
*
|
|
24
|
+
* - `anonymous`: Unauthenticated request
|
|
25
|
+
* - `user`: Authenticated end-user
|
|
26
|
+
* - `admin`: Administrative/system user with elevated privileges
|
|
14
27
|
*/
|
|
15
28
|
type Actor = 'anonymous' | 'user' | 'admin';
|
|
29
|
+
/**
|
|
30
|
+
* Channel through which a request originates.
|
|
31
|
+
*
|
|
32
|
+
* - `web`: Browser/web application
|
|
33
|
+
* - `mobile`: Native mobile application
|
|
34
|
+
* - `job`: Background job/scheduled task
|
|
35
|
+
* - `agent`: AI agent or automated system
|
|
36
|
+
*/
|
|
16
37
|
type Channel = 'web' | 'mobile' | 'job' | 'agent';
|
|
38
|
+
/**
|
|
39
|
+
* Discriminator for all ContractSpec specification types.
|
|
40
|
+
*
|
|
41
|
+
* Used to identify the kind of spec in registries and runtime operations.
|
|
42
|
+
*/
|
|
17
43
|
type ContractSpecType = 'app-config' | 'agent' | 'operation' | 'example' | 'event' | 'presentation' | 'capability' | 'integration' | 'data-view' | 'feature' | 'workflow' | 'policy' | 'theme' | 'telemetry' | 'experiment' | 'knowledge-space';
|
|
44
|
+
/**
|
|
45
|
+
* Decision for a specific field access.
|
|
46
|
+
* Used for fine-grained field-level authorization.
|
|
47
|
+
*/
|
|
18
48
|
interface FieldLevelDecision {
|
|
49
|
+
/** The field path being evaluated. */
|
|
19
50
|
field: string;
|
|
51
|
+
/** Whether access is allowed or denied. */
|
|
20
52
|
effect: 'allow' | 'deny';
|
|
53
|
+
/** Human-readable reason for the decision. */
|
|
21
54
|
reason?: string;
|
|
22
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* Result of a policy evaluation.
|
|
58
|
+
*
|
|
59
|
+
* Contains the access decision and any applicable constraints
|
|
60
|
+
* like rate limits, escalation requirements, or consent needs.
|
|
61
|
+
*/
|
|
23
62
|
interface PolicyDecision {
|
|
63
|
+
/** Overall access decision: allow or deny. */
|
|
24
64
|
effect: 'allow' | 'deny';
|
|
65
|
+
/** Human-readable reason for the decision. */
|
|
25
66
|
reason?: string;
|
|
67
|
+
/** Rate limit constraints to apply if allowed. */
|
|
26
68
|
rateLimit?: Pick<RateLimitDefinition, 'rpm' | 'key' | 'windowSeconds' | 'burst'>;
|
|
69
|
+
/** Escalation requirement for manual review. */
|
|
27
70
|
escalate?: 'human_review' | null;
|
|
71
|
+
/** Per-field access decisions. */
|
|
28
72
|
fieldDecisions?: FieldLevelDecision[];
|
|
73
|
+
/** PII handling policy. */
|
|
29
74
|
pii?: PolicySpec['pii'];
|
|
75
|
+
/** Consents required before proceeding. */
|
|
30
76
|
requiredConsents?: ConsentDefinition[];
|
|
77
|
+
/** Which engine produced this decision. */
|
|
31
78
|
evaluatedBy?: 'engine' | 'opa';
|
|
32
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* Input for policy evaluation.
|
|
82
|
+
* Contains context about the request being authorized.
|
|
83
|
+
*/
|
|
33
84
|
interface PolicyDeciderInput {
|
|
85
|
+
/** Service name (e.g., "sigil"). */
|
|
34
86
|
service: string;
|
|
87
|
+
/** Command/operation name (e.g., "beginSignup"). */
|
|
35
88
|
command: string;
|
|
89
|
+
/** Operation version. */
|
|
36
90
|
version: string;
|
|
91
|
+
/** Actor type making the request. */
|
|
37
92
|
actor: Actor;
|
|
93
|
+
/** Channel the request came from. */
|
|
38
94
|
channel?: Channel;
|
|
95
|
+
/** Roles assigned to the actor. */
|
|
39
96
|
roles?: string[];
|
|
97
|
+
/** Organization context if applicable. */
|
|
40
98
|
organizationId?: string | null;
|
|
99
|
+
/** User context if authenticated. */
|
|
41
100
|
userId?: string | null;
|
|
101
|
+
/** Active feature flags. */
|
|
42
102
|
flags?: string[];
|
|
43
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* Function that evaluates policy rules and returns a decision.
|
|
106
|
+
*/
|
|
44
107
|
type PolicyDecider = (input: PolicyDeciderInput) => Promise<PolicyDecision>;
|
|
108
|
+
/**
|
|
109
|
+
* Function that enforces rate limits.
|
|
110
|
+
*
|
|
111
|
+
* @param key - The rate limit key (e.g., user ID, IP)
|
|
112
|
+
* @param cost - Cost of this request (usually 1)
|
|
113
|
+
* @param rpm - Requests per minute limit
|
|
114
|
+
* @throws When rate limit is exceeded
|
|
115
|
+
*/
|
|
45
116
|
type RateLimiter = (key: string, cost: number, rpm: number) => Promise<void>;
|
|
117
|
+
/**
|
|
118
|
+
* Function that resolves translation keys to localized strings.
|
|
119
|
+
*/
|
|
46
120
|
type TranslationResolver = (key: MessageKey, locale?: Locale) => Promise<string | null> | string | null;
|
|
47
|
-
/**
|
|
121
|
+
/**
|
|
122
|
+
* Function that publishes events to the outbox/message bus.
|
|
123
|
+
* Called after validation and guard checks pass.
|
|
124
|
+
*/
|
|
48
125
|
type EventPublisher = (envelope: {
|
|
126
|
+
/** Event key (e.g., "user.created"). */
|
|
49
127
|
key: string;
|
|
128
|
+
/** Event version. */
|
|
50
129
|
version: string;
|
|
130
|
+
/** Validated event payload. */
|
|
51
131
|
payload: unknown;
|
|
132
|
+
/** Trace ID for correlation. */
|
|
52
133
|
traceId?: string;
|
|
53
134
|
}) => Promise<void>;
|
|
135
|
+
/**
|
|
136
|
+
* Execution context passed to operation handlers.
|
|
137
|
+
*
|
|
138
|
+
* Contains all contextual information and service hooks needed
|
|
139
|
+
* during operation execution, including auth context, policy evaluation,
|
|
140
|
+
* telemetry, event publishing, and resolved configurations.
|
|
141
|
+
*
|
|
142
|
+
* @example
|
|
143
|
+
* ```typescript
|
|
144
|
+
* async function loginHandler(input: LoginInput, ctx: HandlerCtx) {
|
|
145
|
+
* // Check policy
|
|
146
|
+
* const decision = await ctx.decide?.({
|
|
147
|
+
* service: 'auth',
|
|
148
|
+
* command: 'login',
|
|
149
|
+
* version: '1.0.0',
|
|
150
|
+
* actor: ctx.actor ?? 'anonymous',
|
|
151
|
+
* });
|
|
152
|
+
*
|
|
153
|
+
* // Track telemetry
|
|
154
|
+
* ctx.telemetry?.track('login_attempt', { userId: ctx.userId });
|
|
155
|
+
*
|
|
156
|
+
* // Publish event
|
|
157
|
+
* await ctx.eventPublisher?.({
|
|
158
|
+
* key: 'user.loggedIn',
|
|
159
|
+
* version: '1.0.0',
|
|
160
|
+
* payload: { userId: input.email },
|
|
161
|
+
* traceId: ctx.traceId,
|
|
162
|
+
* });
|
|
163
|
+
* }
|
|
164
|
+
* ```
|
|
165
|
+
*/
|
|
54
166
|
interface HandlerCtx {
|
|
167
|
+
/** Distributed trace identifier for request correlation. */
|
|
55
168
|
traceId?: string;
|
|
169
|
+
/** Idempotency key for deduplication. */
|
|
56
170
|
idemKey?: string;
|
|
171
|
+
/** Organization context for multi-tenant operations. */
|
|
57
172
|
organizationId?: string | null;
|
|
173
|
+
/** Authenticated user ID. */
|
|
58
174
|
userId?: string | null;
|
|
175
|
+
/** Actor type making the request. */
|
|
59
176
|
actor?: Actor;
|
|
177
|
+
/** Channel the request originated from. */
|
|
60
178
|
channel?: Channel;
|
|
179
|
+
/** Roles assigned to the authenticated user. */
|
|
61
180
|
roles?: string[];
|
|
62
|
-
/** Policy engine
|
|
181
|
+
/** Policy engine for authorization decisions. */
|
|
63
182
|
decide?: PolicyDecider;
|
|
64
|
-
/** Rate limiter (e.g., Redis) */
|
|
183
|
+
/** Rate limiter service (e.g., Redis-backed). */
|
|
65
184
|
rateLimit?: RateLimiter;
|
|
66
|
-
/** Telemetry tracker */
|
|
185
|
+
/** Telemetry tracker for metrics and events. */
|
|
67
186
|
telemetry?: TelemetryTracker;
|
|
68
|
-
/** Event publisher (outbox
|
|
187
|
+
/** Event publisher for domain events (outbox/bus). */
|
|
69
188
|
eventPublisher?: EventPublisher;
|
|
70
|
-
/** Secret provider for secure
|
|
189
|
+
/** Secret provider for secure credential access. */
|
|
71
190
|
secretProvider?: SecretProvider;
|
|
72
|
-
/**
|
|
191
|
+
/**
|
|
192
|
+
* Internal emit guard for enforcing declared event emissions.
|
|
193
|
+
* Populated by the executor runtime.
|
|
194
|
+
* @internal
|
|
195
|
+
*/
|
|
73
196
|
__emitGuard__?: (key: string, version: string, payload: unknown) => Promise<void>;
|
|
74
|
-
/** Resolved application configuration for
|
|
197
|
+
/** Resolved application configuration for this execution. */
|
|
75
198
|
appConfig?: ResolvedAppConfig;
|
|
76
|
-
/** Resolved integration connections available to this execution */
|
|
199
|
+
/** Resolved integration connections available to this execution. */
|
|
77
200
|
integrations?: ResolvedIntegration[];
|
|
78
|
-
/** Resolved knowledge spaces available to this execution */
|
|
201
|
+
/** Resolved knowledge spaces available to this execution. */
|
|
79
202
|
knowledge?: ResolvedKnowledge[];
|
|
80
|
-
/** Resolved branding context */
|
|
203
|
+
/** Resolved branding context (logos, colors, etc.). */
|
|
81
204
|
branding?: ResolvedBranding;
|
|
82
|
-
/** Translation context */
|
|
205
|
+
/** Translation context with config and resolver. */
|
|
83
206
|
translation?: {
|
|
207
|
+
/** Resolved translation configuration. */
|
|
84
208
|
config: ResolvedTranslation;
|
|
209
|
+
/** Function to resolve translation keys. */
|
|
85
210
|
resolve?: TranslationResolver;
|
|
86
211
|
};
|
|
87
|
-
/**
|
|
212
|
+
/** Spec variant resolver for A/B testing and experiments. */
|
|
88
213
|
specVariantResolver?: SpecVariantResolver;
|
|
214
|
+
/** Event registry for runtime event spec lookup. */
|
|
89
215
|
eventSpecResolver?: EventRegistry;
|
|
90
216
|
}
|
|
91
217
|
//#endregion
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { OptionalVersionedSpecRef, SpecKeyRef, VersionedSpecRef, createKeyRef, createOptionalRef, createVersionedRef, formatVersionedRefKey, isOptionalVersionedSpecRef, isSpecKeyRef, isVersionedSpecRef, parseVersionedRefKey } from "./refs.js";
|
|
1
2
|
import { ChangeEntry, ChangeType, ChangelogDocBlock, ChangelogEntry, ChangelogJsonExport, ChangelogResult, SemanticVersion, VersionAnalysis, VersionAnalysisResult, VersionBumpType, isChangeType, isChangelogDocBlock, isVersionBumpType } from "./types.js";
|
|
2
3
|
import { bumpVersion, compareVersions, determineBumpType, formatVersion, getBumpTypePriority, getMaxBumpType, isValidVersion, isVersionEqual, isVersionGreater, isVersionLess, parseVersion, parseVersionStrict, validateVersion } from "./utils.js";
|
|
3
|
-
export { ChangeEntry, ChangeType, ChangelogDocBlock, ChangelogEntry, ChangelogJsonExport, ChangelogResult, SemanticVersion, VersionAnalysis, VersionAnalysisResult, VersionBumpType, bumpVersion, compareVersions, determineBumpType, formatVersion, getBumpTypePriority, getMaxBumpType, isChangeType, isChangelogDocBlock, isValidVersion, isVersionBumpType, isVersionEqual, isVersionGreater, isVersionLess, parseVersion, parseVersionStrict, validateVersion };
|
|
4
|
+
export { ChangeEntry, ChangeType, ChangelogDocBlock, ChangelogEntry, ChangelogJsonExport, ChangelogResult, OptionalVersionedSpecRef, SemanticVersion, SpecKeyRef, VersionAnalysis, VersionAnalysisResult, VersionBumpType, VersionedSpecRef, bumpVersion, compareVersions, createKeyRef, createOptionalRef, createVersionedRef, determineBumpType, formatVersion, formatVersionedRefKey, getBumpTypePriority, getMaxBumpType, isChangeType, isChangelogDocBlock, isOptionalVersionedSpecRef, isSpecKeyRef, isValidVersion, isVersionBumpType, isVersionEqual, isVersionGreater, isVersionLess, isVersionedSpecRef, parseVersion, parseVersionStrict, parseVersionedRefKey, validateVersion };
|
package/dist/versioning/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
+
import { createKeyRef, createOptionalRef, createVersionedRef, formatVersionedRefKey, isOptionalVersionedSpecRef, isSpecKeyRef, isVersionedSpecRef, parseVersionedRefKey } from "./refs.js";
|
|
1
2
|
import { isChangeType, isChangelogDocBlock, isVersionBumpType } from "./types.js";
|
|
2
3
|
import { bumpVersion, compareVersions, determineBumpType, formatVersion, getBumpTypePriority, getMaxBumpType, isValidVersion, isVersionEqual, isVersionGreater, isVersionLess, parseVersion, parseVersionStrict, validateVersion } from "./utils.js";
|
|
3
4
|
|
|
4
|
-
export { bumpVersion, compareVersions, determineBumpType, formatVersion, getBumpTypePriority, getMaxBumpType, isChangeType, isChangelogDocBlock, isValidVersion, isVersionBumpType, isVersionEqual, isVersionGreater, isVersionLess, parseVersion, parseVersionStrict, validateVersion };
|
|
5
|
+
export { bumpVersion, compareVersions, createKeyRef, createOptionalRef, createVersionedRef, determineBumpType, formatVersion, formatVersionedRefKey, getBumpTypePriority, getMaxBumpType, isChangeType, isChangelogDocBlock, isOptionalVersionedSpecRef, isSpecKeyRef, isValidVersion, isVersionBumpType, isVersionEqual, isVersionGreater, isVersionLess, isVersionedSpecRef, parseVersion, parseVersionStrict, parseVersionedRefKey, validateVersion };
|