@trucore/openclaw-atf 0.1.0 → 0.2.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.
@@ -0,0 +1,132 @@
1
+ /**
2
+ * result_builder.mjs — Universal ATF result builder
3
+ *
4
+ * Builds platform-agnostic result objects for ATF tool responses.
5
+ * This is the UNIVERSAL layer — adapters wrap these results in their
6
+ * transport-specific envelopes.
7
+ *
8
+ * Separation of concerns:
9
+ * - result_builder.mjs → universal result shape (this file)
10
+ * - tool_response.mjs → OpenClaw-specific envelope wrapping
11
+ * - future CLI adapter → stdout JSON formatting
12
+ * - future API adapter → HTTP response mapping
13
+ *
14
+ * Exports:
15
+ * - buildResult(toolName, status, result, meta?) → UniversalResult
16
+ * - buildErrorResult(toolName, message, opts?) → UniversalErrorResult
17
+ * - buildMeta(toolName, opts?) → ResultMeta
18
+ *
19
+ * No dependencies beyond sibling status_codes. No side effects.
20
+ */
21
+
22
+ import { RESPONSE_STATUS } from "./status_codes.mjs";
23
+
24
+ // ---------------------------------------------------------------------------
25
+ // Constants — canonical identity
26
+ // ---------------------------------------------------------------------------
27
+
28
+ /** Canonical product identity. */
29
+ const PRODUCT_ID = "trucore-atf";
30
+
31
+ /** Product version — kept in sync by version consistency tests. */
32
+ const PRODUCT_VERSION = "0.2.0";
33
+
34
+ // ---------------------------------------------------------------------------
35
+ // Meta builder
36
+ // ---------------------------------------------------------------------------
37
+
38
+ /**
39
+ * Build standard metadata for a tool result.
40
+ *
41
+ * @param {string} toolName Name of the tool producing the result.
42
+ * @param {object} [opts] Optional metadata overrides.
43
+ * @param {object} [opts.backend] Backend resolution info.
44
+ * @param {string[]} [opts.warnings] Warnings for operator.
45
+ * @returns {{ product_id: string, product_version: string, tool: string, ts: string, backend?: object, warnings?: string[] }}
46
+ */
47
+ export function buildMeta(toolName, opts = {}) {
48
+ const meta = {
49
+ product_id: PRODUCT_ID,
50
+ product_version: PRODUCT_VERSION,
51
+ tool: toolName,
52
+ ts: new Date().toISOString(),
53
+ };
54
+
55
+ if (opts.backend && typeof opts.backend === "object") {
56
+ meta.backend = {
57
+ preferred: opts.backend.preferred_backend ?? opts.backend.preferred ?? "unknown",
58
+ effective: opts.backend.effective_backend ?? opts.backend.effective ?? "unknown",
59
+ fallback_occurred: opts.backend.fallback_occurred ?? false,
60
+ fallback_reason: opts.backend.fallback_reason ?? null,
61
+ };
62
+ }
63
+
64
+ if (Array.isArray(opts.warnings) && opts.warnings.length > 0) {
65
+ meta.warnings = [...opts.warnings];
66
+ }
67
+
68
+ return meta;
69
+ }
70
+
71
+ // ---------------------------------------------------------------------------
72
+ // Success result builder
73
+ // ---------------------------------------------------------------------------
74
+
75
+ /**
76
+ * Build a universal ATF success result.
77
+ *
78
+ * This is the platform-agnostic result object. Adapters wrap this in
79
+ * their own envelope format.
80
+ *
81
+ * Universal result shape:
82
+ * { status, summary, result, _meta }
83
+ *
84
+ * @param {string} toolName Name of the tool.
85
+ * @param {string} summary Human-readable summary.
86
+ * @param {object} result Tool-specific result data.
87
+ * @param {object} [opts] Optional overrides.
88
+ * @param {string} [opts.status] RESPONSE_STATUS value (default: "ok").
89
+ * @param {object} [opts.backend] Backend resolution info.
90
+ * @param {string[]} [opts.warnings] Warnings.
91
+ * @returns {{ status: string, summary: string, result: object, _meta: object }}
92
+ */
93
+ export function buildResult(toolName, summary, result, opts = {}) {
94
+ return {
95
+ status: opts.status ?? RESPONSE_STATUS.OK,
96
+ summary,
97
+ result: result ?? {},
98
+ _meta: buildMeta(toolName, opts),
99
+ };
100
+ }
101
+
102
+ // ---------------------------------------------------------------------------
103
+ // Error result builder
104
+ // ---------------------------------------------------------------------------
105
+
106
+ /**
107
+ * Build a universal ATF error result.
108
+ *
109
+ * Universal error shape:
110
+ * { status: "error", error, remediation?, _meta }
111
+ *
112
+ * @param {string} toolName Name of the tool.
113
+ * @param {string} message Error message.
114
+ * @param {object} [opts] Optional overrides.
115
+ * @param {object} [opts.backend] Backend resolution info.
116
+ * @param {string[]} [opts.warnings] Warnings.
117
+ * @param {string[]} [opts.remediation] Fix instructions.
118
+ * @returns {{ status: string, error: string, remediation?: string[], _meta: object }}
119
+ */
120
+ export function buildErrorResult(toolName, message, opts = {}) {
121
+ const payload = {
122
+ status: RESPONSE_STATUS.ERROR,
123
+ error: message,
124
+ _meta: buildMeta(toolName, opts),
125
+ };
126
+
127
+ if (Array.isArray(opts.remediation) && opts.remediation.length > 0) {
128
+ payload.remediation = [...opts.remediation];
129
+ }
130
+
131
+ return payload;
132
+ }
@@ -0,0 +1,402 @@
1
+ /**
2
+ * schemas.mjs — Universal ATF contract family definitions
3
+ *
4
+ * Defines the canonical request/response shapes for each ATF contract
5
+ * family. These are platform-agnostic — any adapter (OpenClaw, CLI, API,
6
+ * SDK, future UI) can map to/from these shapes.
7
+ *
8
+ * This module defines STRUCTURE, not runtime logic. Think of it as
9
+ * the "interface declarations" for ATF contracts.
10
+ *
11
+ * Contract families:
12
+ * 1. READINESS — health, doctor, preflight
13
+ * 2. ADVISORY — adoption advisor, bot preflight
14
+ * 3. EXPLANATION — tx_explain
15
+ * 4. BILLING — billing_info, billing_claim
16
+ * 5. DISCOVERY — discover, bootstrap_plan
17
+ * 6. PROTECTION — protect_intent, verify_receipt, report_savings
18
+ *
19
+ * Exports:
20
+ * - CONTRACT_FAMILIES — frozen family metadata
21
+ * - UNIVERSAL_RESULT_FIELDS — fields present in every universal result
22
+ * - ADAPTER_ENVELOPE_FIELDS — fields added by adapters (not universal)
23
+ * - TOOL_FAMILY_MAP — maps each tool to its contract family
24
+ *
25
+ * No dependencies. No side effects. Pure constants.
26
+ */
27
+
28
+ // ---------------------------------------------------------------------------
29
+ // Contract families
30
+ // ---------------------------------------------------------------------------
31
+
32
+ /**
33
+ * Canonical ATF contract families.
34
+ *
35
+ * Each family groups tools that share similar request/response patterns.
36
+ * Future adapters map their transport-specific inputs/outputs to these
37
+ * canonical shapes.
38
+ */
39
+ export const CONTRACT_FAMILIES = Object.freeze({
40
+ READINESS: Object.freeze({
41
+ family_id: "readiness",
42
+ purpose:
43
+ "Check ATF availability, integration health, and pre-session readiness.",
44
+ tools: Object.freeze([
45
+ "atf_health",
46
+ "atf_integration_doctor",
47
+ "atf_bot_preflight",
48
+ ]),
49
+ canonical_request: Object.freeze({
50
+ description: "No required input. Config is read from environment.",
51
+ fields: Object.freeze({}),
52
+ }),
53
+ canonical_response: Object.freeze({
54
+ description:
55
+ "Structured readiness signal with backend availability, " +
56
+ "config validity, warnings, and remediation steps.",
57
+ required_fields: Object.freeze([
58
+ "status",
59
+ "warnings",
60
+ "remediation",
61
+ ]),
62
+ optional_fields: Object.freeze([
63
+ "ready",
64
+ "healthy",
65
+ "backend_ready",
66
+ "config_valid",
67
+ "cli_available",
68
+ "api_available",
69
+ "native_tools_expected",
70
+ "native_tools_available",
71
+ "native_tools_missing",
72
+ "config_errors",
73
+ "config_warnings",
74
+ ]),
75
+ }),
76
+ }),
77
+
78
+ ADVISORY: Object.freeze({
79
+ family_id: "advisory",
80
+ purpose:
81
+ "Evaluate signals and return deterministic adoption/installation recommendations.",
82
+ tools: Object.freeze(["atf_adoption_advisor"]),
83
+ canonical_request: Object.freeze({
84
+ description:
85
+ "Map of trigger_id → boolean signals indicating bot capabilities.",
86
+ fields: Object.freeze({
87
+ signals: {
88
+ type: "object",
89
+ required: false,
90
+ description:
91
+ "Map of trigger_id to boolean. Omit for no-trigger evaluation.",
92
+ },
93
+ }),
94
+ }),
95
+ canonical_response: Object.freeze({
96
+ description:
97
+ "Deterministic adoption recommendation with matched triggers, " +
98
+ "severity, tier, call sequence, and install guidance.",
99
+ required_fields: Object.freeze([
100
+ "install_needed",
101
+ "recommended_tier",
102
+ "highest_severity",
103
+ "matched_triggers",
104
+ ]),
105
+ optional_fields: Object.freeze([
106
+ "unmatched_triggers",
107
+ "call_sequence",
108
+ "install_guidance",
109
+ "next_actions",
110
+ ]),
111
+ }),
112
+ }),
113
+
114
+ EXPLANATION: Object.freeze({
115
+ family_id: "explanation",
116
+ purpose:
117
+ "Explain deny decisions and reason codes in human-readable terms.",
118
+ tools: Object.freeze(["atf_tx_explain"]),
119
+ canonical_request: Object.freeze({
120
+ description:
121
+ "Either an array of reason codes or a receipt/decision object.",
122
+ fields: Object.freeze({
123
+ reasonCodes: {
124
+ type: "string[]",
125
+ required: false,
126
+ description: "Array of ATF reason codes to explain.",
127
+ },
128
+ receipt: {
129
+ type: "object",
130
+ required: false,
131
+ description:
132
+ "ATF receipt or decision object with reason_codes field.",
133
+ },
134
+ }),
135
+ }),
136
+ canonical_response: Object.freeze({
137
+ description:
138
+ "Structured explanations for each reason code with category " +
139
+ "and remediation.",
140
+ required_fields: Object.freeze([
141
+ "reason_codes",
142
+ "explanations",
143
+ ]),
144
+ optional_fields: Object.freeze([
145
+ "decision",
146
+ "receipt_meta",
147
+ ]),
148
+ }),
149
+ }),
150
+
151
+ BILLING: Object.freeze({
152
+ family_id: "billing",
153
+ purpose:
154
+ "Discover pricing/package metadata and verify on-chain payment claims.",
155
+ tools: Object.freeze(["atf_billing_info", "atf_billing_claim"]),
156
+ canonical_request: Object.freeze({
157
+ description: "For info: optional packageId filter. " +
158
+ "For claim: tx_signature, wallet, package_id required.",
159
+ fields: Object.freeze({
160
+ packageId: {
161
+ type: "string",
162
+ required: false,
163
+ description: "Filter to a specific package (billing_info).",
164
+ },
165
+ tx_signature: {
166
+ type: "string",
167
+ required: "claim_only",
168
+ description: "Solana transaction signature (billing_claim).",
169
+ },
170
+ wallet: {
171
+ type: "string",
172
+ required: "claim_only",
173
+ description: "Claimant wallet address (billing_claim).",
174
+ },
175
+ package_id: {
176
+ type: "string",
177
+ required: "claim_only",
178
+ description: "Package being claimed (billing_claim).",
179
+ },
180
+ asset_symbol: {
181
+ type: "string",
182
+ required: false,
183
+ description: "Optional asset hint: USDC or SOL.",
184
+ },
185
+ cluster: {
186
+ type: "string",
187
+ required: false,
188
+ description: "Solana cluster (default: mainnet-beta).",
189
+ },
190
+ }),
191
+ }),
192
+ canonical_response: Object.freeze({
193
+ description:
194
+ "For info: package/pricing/payment metadata. " +
195
+ "For claim: deterministic verification result.",
196
+ required_fields: Object.freeze([
197
+ "claim_status|packages",
198
+ ]),
199
+ optional_fields: Object.freeze([
200
+ "deny_code",
201
+ "payment_rails",
202
+ "activation",
203
+ "warnings",
204
+ "remediation",
205
+ "amount_received",
206
+ "asset_symbol",
207
+ "treasury_verified",
208
+ "payment_verified",
209
+ "entitlement_status",
210
+ "free_vs_advanced",
211
+ ]),
212
+ }),
213
+ }),
214
+
215
+ DISCOVERY: Object.freeze({
216
+ family_id: "discovery",
217
+ purpose:
218
+ "Discover ATF capabilities, manifest, bootstrap recipes, and toolcard.",
219
+ tools: Object.freeze([
220
+ "atf_discover",
221
+ "atf_bootstrap_plan",
222
+ "atf_bootstrap_execute_safe",
223
+ ]),
224
+ canonical_request: Object.freeze({
225
+ description: "Optional recipe ID or manifest URL override.",
226
+ fields: Object.freeze({
227
+ recipe: {
228
+ type: "string",
229
+ required: false,
230
+ description: "Bootstrap recipe ID.",
231
+ },
232
+ manifestUrl: {
233
+ type: "string",
234
+ required: false,
235
+ description: "Override manifest fetch URL.",
236
+ },
237
+ dryRun: {
238
+ type: "boolean",
239
+ required: false,
240
+ description: "Preview steps without executing (execute_safe).",
241
+ },
242
+ }),
243
+ }),
244
+ canonical_response: Object.freeze({
245
+ description:
246
+ "Manifest summary, bootstrap steps, or execution results.",
247
+ required_fields: Object.freeze(["status"]),
248
+ optional_fields: Object.freeze([
249
+ "id",
250
+ "version",
251
+ "capabilities",
252
+ "recipe_ids",
253
+ "steps",
254
+ "exitCode",
255
+ ]),
256
+ }),
257
+ }),
258
+
259
+ PROTECTION: Object.freeze({
260
+ family_id: "protection",
261
+ purpose:
262
+ "Evaluate intents against policy, verify receipts, report savings.",
263
+ tools: Object.freeze([
264
+ "atf_protect_intent",
265
+ "atf_verify_receipt",
266
+ "atf_report_savings",
267
+ ]),
268
+ canonical_request: Object.freeze({
269
+ description:
270
+ "For protect: intent object. For verify: receipt. " +
271
+ "For savings: optional receipt count/dir.",
272
+ fields: Object.freeze({
273
+ intentJson: {
274
+ type: "object",
275
+ required: "protect_only",
276
+ description: "ATF ExecutionRequest or intent object.",
277
+ },
278
+ exposureHints: {
279
+ type: "object",
280
+ required: false,
281
+ description: "Optional exposure hints for savings estimates.",
282
+ },
283
+ receipt: {
284
+ type: "object|string",
285
+ required: "verify_only",
286
+ description: "Receipt JSON, string, or file path.",
287
+ },
288
+ last: {
289
+ type: "integer",
290
+ required: false,
291
+ description: "Number of recent receipts (report_savings).",
292
+ },
293
+ receiptsDir: {
294
+ type: "string",
295
+ required: false,
296
+ description: "Directory containing receipt files.",
297
+ },
298
+ }),
299
+ }),
300
+ canonical_response: Object.freeze({
301
+ description:
302
+ "Allow/deny decision with reason codes, receipt verification, " +
303
+ "or savings summary.",
304
+ required_fields: Object.freeze(["status"]),
305
+ optional_fields: Object.freeze([
306
+ "allow",
307
+ "reason_codes",
308
+ "receipt",
309
+ "verified",
310
+ "content_hash",
311
+ "intent_hash",
312
+ "total_denied",
313
+ "by_reason_code",
314
+ "evidence",
315
+ "savings_estimate_usd",
316
+ ]),
317
+ }),
318
+ }),
319
+ });
320
+
321
+ // ---------------------------------------------------------------------------
322
+ // Universal result fields — present in every ATF tool result
323
+ // ---------------------------------------------------------------------------
324
+
325
+ /**
326
+ * Fields present in every universal ATF tool result.
327
+ * These are the CORE contract — adapters must preserve them.
328
+ */
329
+ export const UNIVERSAL_RESULT_FIELDS = Object.freeze([
330
+ "status", // RESPONSE_STATUS value
331
+ "result", // tool-specific payload
332
+ ]);
333
+
334
+ // ---------------------------------------------------------------------------
335
+ // Adapter envelope fields — added by transport adapters
336
+ // ---------------------------------------------------------------------------
337
+
338
+ /**
339
+ * Fields added by transport-specific adapters (NOT part of universal contract).
340
+ *
341
+ * For example, the OpenClaw adapter wraps results in:
342
+ * { content: [{type: "text", text}, {type: "json", json: universalResult}] }
343
+ *
344
+ * A CLI adapter might instead output JSON directly.
345
+ * An API adapter might wrap in HTTP response headers + JSON body.
346
+ */
347
+ export const ADAPTER_ENVELOPE_FIELDS = Object.freeze({
348
+ OPENCLAW: Object.freeze({
349
+ description:
350
+ "OpenClaw wraps the universal result in a content array with " +
351
+ "text + json items. Error responses add isError: true.",
352
+ wrapper_fields: Object.freeze(["content", "isError"]),
353
+ content_item_types: Object.freeze(["text", "json"]),
354
+ }),
355
+ CLI: Object.freeze({
356
+ description:
357
+ "CLI adapter outputs the universal result as formatted JSON to stdout. " +
358
+ "Exit code 0 = success, non-zero = error.",
359
+ wrapper_fields: Object.freeze(["exitCode"]),
360
+ }),
361
+ API: Object.freeze({
362
+ description:
363
+ "API adapter returns the universal result as HTTP JSON response body. " +
364
+ "HTTP status code maps to RESPONSE_STATUS.",
365
+ wrapper_fields: Object.freeze(["httpStatus", "headers"]),
366
+ }),
367
+ });
368
+
369
+ // ---------------------------------------------------------------------------
370
+ // Tool → family map
371
+ // ---------------------------------------------------------------------------
372
+
373
+ /**
374
+ * Maps each ATF tool name to its contract family ID.
375
+ */
376
+ export const TOOL_FAMILY_MAP = Object.freeze({
377
+ atf_health: "readiness",
378
+ atf_integration_doctor: "readiness",
379
+ atf_bot_preflight: "readiness",
380
+ atf_adoption_advisor: "advisory",
381
+ atf_tx_explain: "explanation",
382
+ atf_billing_info: "billing",
383
+ atf_billing_claim: "billing",
384
+ atf_discover: "discovery",
385
+ atf_bootstrap_plan: "discovery",
386
+ atf_bootstrap_execute_safe: "discovery",
387
+ atf_protect_intent: "protection",
388
+ atf_verify_receipt: "protection",
389
+ atf_report_savings: "protection",
390
+ });
391
+
392
+ /**
393
+ * All 13 canonical tool names, frozen.
394
+ */
395
+ export const CANONICAL_TOOLS = Object.freeze(
396
+ Object.keys(TOOL_FAMILY_MAP),
397
+ );
398
+
399
+ /**
400
+ * Number of canonical tools. Used by tests for tool count assertions.
401
+ */
402
+ export const CANONICAL_TOOL_COUNT = CANONICAL_TOOLS.length;
@@ -0,0 +1,148 @@
1
+ /**
2
+ * status_codes.mjs — Universal ATF status code vocabularies
3
+ *
4
+ * Platform-agnostic status enums used across all ATF surfaces:
5
+ * CLI, API, SDK, OpenClaw plugin, future human-dev UIs.
6
+ *
7
+ * These are the CANONICAL source of truth for status values.
8
+ * Adapters (OpenClaw, CLI, etc.) import from here — never duplicate.
9
+ *
10
+ * Exports:
11
+ * - RESPONSE_STATUS — tool response status enum
12
+ * - DOCTOR_STATUS — integration doctor status enum
13
+ * - CLAIM_STATUS — billing claim result status enum
14
+ * - BACKEND_STATUS — backend availability status enum
15
+ * - PREFLIGHT_STATUS — preflight go/no-go signal enum
16
+ * - ADOPTION_TIER — adoption recommendation tier enum
17
+ * - SEVERITY — trigger severity enum
18
+ *
19
+ * No dependencies. No side effects. Pure constants.
20
+ */
21
+
22
+ // ---------------------------------------------------------------------------
23
+ // Response status — used by all tool responses
24
+ // ---------------------------------------------------------------------------
25
+
26
+ /**
27
+ * Deterministic status values for tool responses.
28
+ * Every ATF tool response carries one of these.
29
+ */
30
+ export const RESPONSE_STATUS = Object.freeze({
31
+ /** Tool executed successfully, full capability. */
32
+ OK: "ok",
33
+ /** Tool executed but with reduced capability (e.g. fallback backend). */
34
+ DEGRADED: "degraded",
35
+ /** Tool execution failed — see error and remediation fields. */
36
+ ERROR: "error",
37
+ /** Backend completely unreachable — tool cannot operate. */
38
+ UNAVAILABLE: "unavailable",
39
+ });
40
+
41
+ // ---------------------------------------------------------------------------
42
+ // Doctor status — integration readiness check
43
+ // ---------------------------------------------------------------------------
44
+
45
+ /**
46
+ * Overall integration doctor status. Stable finite set.
47
+ */
48
+ export const DOCTOR_STATUS = Object.freeze({
49
+ /** Integration healthy. All systems operational. */
50
+ OK: "ok",
51
+ /** Integration degraded. Some capabilities limited. */
52
+ DEGRADED: "degraded",
53
+ /** Integration misconfigured. Check warnings and remediation. */
54
+ MISCONFIGURED: "misconfigured",
55
+ /** Integration unavailable. No ATF backend reachable. */
56
+ UNAVAILABLE: "unavailable",
57
+ });
58
+
59
+ // ---------------------------------------------------------------------------
60
+ // Claim status — billing claim verification result
61
+ // ---------------------------------------------------------------------------
62
+
63
+ /**
64
+ * Deterministic claim result statuses.
65
+ */
66
+ export const CLAIM_STATUS = Object.freeze({
67
+ /** Payment verified, claim approved. */
68
+ VERIFIED: "verified",
69
+ /** Payment verification failed — see deny_code. */
70
+ DENIED: "denied",
71
+ /** Infrastructure error during verification. */
72
+ ERROR: "error",
73
+ });
74
+
75
+ // ---------------------------------------------------------------------------
76
+ // Backend status — backend availability
77
+ // ---------------------------------------------------------------------------
78
+
79
+ /**
80
+ * Backend availability status.
81
+ */
82
+ export const BACKEND_STATUS = Object.freeze({
83
+ /** Backend healthy and available. */
84
+ OK: "ok",
85
+ /** Backend available but degraded (fallback). */
86
+ DEGRADED: "degraded",
87
+ /** Backend misconfigured. */
88
+ MISCONFIGURED: "misconfigured",
89
+ /** Backend unavailable. */
90
+ UNAVAILABLE: "unavailable",
91
+ });
92
+
93
+ // ---------------------------------------------------------------------------
94
+ // Preflight status — pre-session readiness signal
95
+ // ---------------------------------------------------------------------------
96
+
97
+ /**
98
+ * Preflight go/no-go signal.
99
+ */
100
+ export const PREFLIGHT_STATUS = Object.freeze({
101
+ /** Ready to protect intents. */
102
+ READY: "ready",
103
+ /** Degraded but operational. */
104
+ DEGRADED: "degraded",
105
+ /** Not ready — see remediation. */
106
+ NOT_READY: "not_ready",
107
+ });
108
+
109
+ // ---------------------------------------------------------------------------
110
+ // Adoption tier — adoption recommendation tiers
111
+ // ---------------------------------------------------------------------------
112
+
113
+ /**
114
+ * Adoption recommendation tiers for the adoption advisor.
115
+ */
116
+ export const ADOPTION_TIER = Object.freeze({
117
+ /** Free core: all enforcement tools, no payment. */
118
+ FREE_CORE: "free_core",
119
+ /** Advanced bot: premium support + operator controls. */
120
+ ADVANCED_BOT: "advanced_bot",
121
+ /** No adoption needed at this time. */
122
+ NONE: "none",
123
+ });
124
+
125
+ // ---------------------------------------------------------------------------
126
+ // Severity — trigger severity levels
127
+ // ---------------------------------------------------------------------------
128
+
129
+ /**
130
+ * Trigger severity levels (ordered: critical > high > medium > low).
131
+ */
132
+ export const SEVERITY = Object.freeze({
133
+ CRITICAL: "critical",
134
+ HIGH: "high",
135
+ MEDIUM: "medium",
136
+ LOW: "low",
137
+ });
138
+
139
+ /**
140
+ * Severity ordering for comparison.
141
+ * Higher number = more severe.
142
+ */
143
+ export const SEVERITY_RANK = Object.freeze({
144
+ [SEVERITY.CRITICAL]: 4,
145
+ [SEVERITY.HIGH]: 3,
146
+ [SEVERITY.MEDIUM]: 2,
147
+ [SEVERITY.LOW]: 1,
148
+ });