@tagma/sdk 0.2.9 → 0.3.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/package.json +1 -1
- package/src/engine.ts +32 -4
- package/src/sdk.ts +1 -1
- package/src/triggers/file.ts +2 -1
- package/src/triggers/manual.ts +5 -3
package/package.json
CHANGED
package/src/engine.ts
CHANGED
|
@@ -19,6 +19,26 @@ import {
|
|
|
19
19
|
import { Logger, tailLines, clip, type LogLevel } from './logger';
|
|
20
20
|
import { InMemoryApprovalGateway, type ApprovalGateway } from './approval';
|
|
21
21
|
|
|
22
|
+
// ═══ A7: Typed trigger errors ═══
|
|
23
|
+
// Replace string-matching on error messages with structured error types so
|
|
24
|
+
// coincidental substrings don't cause misclassification.
|
|
25
|
+
|
|
26
|
+
export class TriggerBlockedError extends Error {
|
|
27
|
+
readonly code = 'TRIGGER_BLOCKED' as const;
|
|
28
|
+
constructor(message: string) {
|
|
29
|
+
super(message);
|
|
30
|
+
this.name = 'TriggerBlockedError';
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export class TriggerTimeoutError extends Error {
|
|
35
|
+
readonly code = 'TRIGGER_TIMEOUT' as const;
|
|
36
|
+
constructor(message: string) {
|
|
37
|
+
super(message);
|
|
38
|
+
this.name = 'TriggerTimeoutError';
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
22
42
|
// ═══ Preflight Validation ═══
|
|
23
43
|
|
|
24
44
|
function preflight(config: PipelineConfig, dag: Dag): void {
|
|
@@ -420,18 +440,26 @@ export async function runPipeline(
|
|
|
420
440
|
});
|
|
421
441
|
log.debug(`[task:${taskId}]`, `trigger fired`);
|
|
422
442
|
} catch (err: unknown) {
|
|
423
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
424
443
|
// If pipeline was aborted while we were still waiting for the trigger,
|
|
425
444
|
// this task never entered running state → skipped, not timeout.
|
|
426
445
|
state.finishedAt = nowISO();
|
|
427
446
|
if (pipelineAborted) {
|
|
428
447
|
setTaskStatus(taskId, 'skipped');
|
|
429
|
-
} else if (
|
|
448
|
+
} else if (err instanceof TriggerBlockedError) {
|
|
430
449
|
setTaskStatus(taskId, 'blocked'); // user/policy rejection
|
|
431
|
-
} else if (
|
|
450
|
+
} else if (err instanceof TriggerTimeoutError) {
|
|
432
451
|
setTaskStatus(taskId, 'timeout'); // genuine trigger wait timeout
|
|
433
452
|
} else {
|
|
434
|
-
|
|
453
|
+
// A7 fallback: also check message strings for backward-compat with
|
|
454
|
+
// third-party trigger plugins that don't throw typed errors yet.
|
|
455
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
456
|
+
if (msg.includes('rejected') || msg.includes('denied')) {
|
|
457
|
+
setTaskStatus(taskId, 'blocked');
|
|
458
|
+
} else if (msg.includes('timeout')) {
|
|
459
|
+
setTaskStatus(taskId, 'timeout');
|
|
460
|
+
} else {
|
|
461
|
+
setTaskStatus(taskId, 'failed'); // plugin error, watcher crash, etc.
|
|
462
|
+
}
|
|
435
463
|
}
|
|
436
464
|
try {
|
|
437
465
|
await fireHook(taskId, 'task_failure');
|
package/src/sdk.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// The CLI (src/index.ts in the CLI project) also imports from here.
|
|
5
5
|
|
|
6
6
|
// ── Core engine ──
|
|
7
|
-
export { runPipeline } from './engine';
|
|
7
|
+
export { runPipeline, TriggerBlockedError, TriggerTimeoutError } from './engine';
|
|
8
8
|
export type { EngineResult, RunPipelineOptions, PipelineEvent } from './engine';
|
|
9
9
|
|
|
10
10
|
// ── Pipeline runner (multi-pipeline lifecycle management) ──
|
package/src/triggers/file.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { resolve, dirname } from 'path';
|
|
|
3
3
|
import { mkdir } from 'fs/promises';
|
|
4
4
|
import type { TriggerPlugin, TriggerContext } from '../types';
|
|
5
5
|
import { parseDuration, validatePath } from '../utils';
|
|
6
|
+
import { TriggerTimeoutError } from '../engine';
|
|
6
7
|
|
|
7
8
|
const IS_WINDOWS = process.platform === 'win32';
|
|
8
9
|
|
|
@@ -118,7 +119,7 @@ export const FileTrigger: TriggerPlugin = {
|
|
|
118
119
|
timer = setTimeout(() => {
|
|
119
120
|
if (settled) return;
|
|
120
121
|
cleanup();
|
|
121
|
-
reject(new
|
|
122
|
+
reject(new TriggerTimeoutError(`file trigger timeout: ${filePath} did not appear within ${config.timeout}`));
|
|
122
123
|
}, timeoutMs);
|
|
123
124
|
}
|
|
124
125
|
|
package/src/triggers/manual.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { TriggerPlugin, TriggerContext } from '../types';
|
|
2
2
|
import { parseDuration } from '../utils';
|
|
3
|
+
import { TriggerBlockedError, TriggerTimeoutError } from '../engine';
|
|
3
4
|
|
|
4
5
|
export const ManualTrigger: TriggerPlugin = {
|
|
5
6
|
name: 'manual',
|
|
@@ -66,14 +67,15 @@ export const ManualTrigger: TriggerPlugin = {
|
|
|
66
67
|
case 'approved':
|
|
67
68
|
return { confirmed: true, approvalId: decision.approvalId, actor: decision.actor };
|
|
68
69
|
case 'rejected':
|
|
69
|
-
|
|
70
|
+
// A7: Use typed error for proper classification in the engine.
|
|
71
|
+
throw new TriggerBlockedError(
|
|
70
72
|
`Manual trigger rejected by ${decision.actor ?? 'user'}` +
|
|
71
73
|
(decision.reason ? `: ${decision.reason}` : ''),
|
|
72
74
|
);
|
|
73
75
|
case 'timeout':
|
|
74
|
-
throw new
|
|
76
|
+
throw new TriggerTimeoutError(`Manual trigger timeout: ${decision.reason ?? 'no decision made'}`);
|
|
75
77
|
case 'aborted':
|
|
76
|
-
throw new
|
|
78
|
+
throw new TriggerBlockedError(`Manual trigger aborted: ${decision.reason ?? 'pipeline aborted'}`);
|
|
77
79
|
}
|
|
78
80
|
},
|
|
79
81
|
};
|