@orgloop/core 0.6.1 → 0.7.1
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/audit.d.ts +100 -0
- package/dist/audit.d.ts.map +1 -0
- package/dist/audit.js +81 -0
- package/dist/audit.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/loop-detector.d.ts +81 -0
- package/dist/loop-detector.d.ts.map +1 -0
- package/dist/loop-detector.js +158 -0
- package/dist/loop-detector.js.map +1 -0
- package/dist/module-instance.d.ts +4 -0
- package/dist/module-instance.d.ts.map +1 -1
- package/dist/module-instance.js.map +1 -1
- package/dist/output-validator.d.ts +59 -0
- package/dist/output-validator.d.ts.map +1 -0
- package/dist/output-validator.js +180 -0
- package/dist/output-validator.js.map +1 -0
- package/dist/runtime.d.ts +35 -0
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +219 -26
- package/dist/runtime.js.map +1 -1
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +7 -0
- package/dist/schema.js.map +1 -1
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +7 -2
- package/dist/store.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OutputValidator — pre-delivery validation of SOP outputs.
|
|
3
|
+
*
|
|
4
|
+
* Checks for potential payload propagation (viral agent loop defense):
|
|
5
|
+
* - Instruction-like content in outputs (potential injection)
|
|
6
|
+
* - Content similarity to input (echo/amplification)
|
|
7
|
+
* - References to tools, URLs, or actions outside expected SOP scope
|
|
8
|
+
*/
|
|
9
|
+
// ─── Instruction Detection Patterns ──────────────────────────────────────────
|
|
10
|
+
const DEFAULT_INSTRUCTION_PATTERNS = [
|
|
11
|
+
// System prompt injection markers
|
|
12
|
+
/\bignore\s+(all\s+)?previous\s+instructions\b/i,
|
|
13
|
+
/\byou\s+are\s+now\b/i,
|
|
14
|
+
/\bsystem\s*:\s*/i,
|
|
15
|
+
/\b(new|override|updated?)\s+instructions?\b/i,
|
|
16
|
+
// Agent manipulation
|
|
17
|
+
/\bexecute\s+(this|the\s+following)\s+(command|code|script)\b/i,
|
|
18
|
+
/\brun\s+(this|the\s+following)\s+(command|code|script)\b/i,
|
|
19
|
+
/\bmodify\s+your\s+(instructions?|behavior|prompt)\b/i,
|
|
20
|
+
// Prompt injection delimiters
|
|
21
|
+
/^-{3,}\s*$/m,
|
|
22
|
+
/\[INST\]/i,
|
|
23
|
+
/<<\s*SYS\s*>>/i,
|
|
24
|
+
// Encoded instructions
|
|
25
|
+
/\bbase64\s*[:=]\s*[A-Za-z0-9+/=]{20,}/i,
|
|
26
|
+
];
|
|
27
|
+
// ─── OutputValidator Class ───────────────────────────────────────────────────
|
|
28
|
+
export class OutputValidator {
|
|
29
|
+
options;
|
|
30
|
+
constructor(options) {
|
|
31
|
+
this.options = {
|
|
32
|
+
detectInstructions: options?.detectInstructions ?? true,
|
|
33
|
+
detectEcho: options?.detectEcho ?? true,
|
|
34
|
+
detectScopeViolations: options?.detectScopeViolations ?? true,
|
|
35
|
+
echoThreshold: options?.echoThreshold ?? 0.7,
|
|
36
|
+
holdOnCritical: options?.holdOnCritical ?? false,
|
|
37
|
+
instructionPatterns: [
|
|
38
|
+
...DEFAULT_INSTRUCTION_PATTERNS,
|
|
39
|
+
...(options?.instructionPatterns ?? []),
|
|
40
|
+
],
|
|
41
|
+
allowedDomains: options?.allowedDomains ?? [],
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Validate an output payload before it's committed to an external system.
|
|
46
|
+
*/
|
|
47
|
+
validate(outputContent, inputEvent, sopScope) {
|
|
48
|
+
const flags = [];
|
|
49
|
+
if (this.options.detectInstructions) {
|
|
50
|
+
flags.push(...this.checkInstructionContent(outputContent));
|
|
51
|
+
}
|
|
52
|
+
if (this.options.detectEcho) {
|
|
53
|
+
flags.push(...this.checkInputEcho(outputContent, inputEvent));
|
|
54
|
+
}
|
|
55
|
+
if (this.options.detectScopeViolations) {
|
|
56
|
+
flags.push(...this.checkScopeViolations(outputContent, sopScope));
|
|
57
|
+
}
|
|
58
|
+
const hasCritical = flags.some((f) => f.severity === 'critical');
|
|
59
|
+
const holdForReview = hasCritical && this.options.holdOnCritical;
|
|
60
|
+
return {
|
|
61
|
+
passed: !hasCritical,
|
|
62
|
+
hold_for_review: holdForReview,
|
|
63
|
+
flags,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/** Check for instruction-like content that could be prompt injection. */
|
|
67
|
+
checkInstructionContent(content) {
|
|
68
|
+
const flags = [];
|
|
69
|
+
for (const pattern of this.options.instructionPatterns) {
|
|
70
|
+
const match = pattern.exec(content);
|
|
71
|
+
if (match) {
|
|
72
|
+
flags.push({
|
|
73
|
+
type: 'instruction_content',
|
|
74
|
+
severity: 'critical',
|
|
75
|
+
message: `Potential instruction injection detected: "${match[0]}"`,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return flags;
|
|
80
|
+
}
|
|
81
|
+
/** Check if the output is suspiciously similar to the input (echo/amplification). */
|
|
82
|
+
checkInputEcho(content, inputEvent) {
|
|
83
|
+
const flags = [];
|
|
84
|
+
// Serialize input payload for comparison
|
|
85
|
+
const inputText = JSON.stringify(inputEvent.payload);
|
|
86
|
+
if (!inputText || inputText.length < 20)
|
|
87
|
+
return flags;
|
|
88
|
+
const similarity = this.computeSimilarity(content, inputText);
|
|
89
|
+
if (similarity >= this.options.echoThreshold) {
|
|
90
|
+
flags.push({
|
|
91
|
+
type: 'input_echo',
|
|
92
|
+
severity: similarity >= 0.9 ? 'critical' : 'warning',
|
|
93
|
+
message: `Output is ${Math.round(similarity * 100)}% similar to input payload (threshold: ${Math.round(this.options.echoThreshold * 100)}%)`,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
return flags;
|
|
97
|
+
}
|
|
98
|
+
/** Check for references to tools, URLs, or actions outside SOP's expected scope. */
|
|
99
|
+
checkScopeViolations(content, sopScope) {
|
|
100
|
+
const flags = [];
|
|
101
|
+
// Extract URLs from content
|
|
102
|
+
const urlPattern = /https?:\/\/[^\s"'<>)}\]]+/gi;
|
|
103
|
+
const urls = content.match(urlPattern) ?? [];
|
|
104
|
+
const allowedDomains = [...this.options.allowedDomains, ...(sopScope?.allowedDomains ?? [])];
|
|
105
|
+
if (allowedDomains.length > 0) {
|
|
106
|
+
for (const url of urls) {
|
|
107
|
+
try {
|
|
108
|
+
const hostname = new URL(url).hostname;
|
|
109
|
+
const isAllowed = allowedDomains.some((d) => hostname === d || hostname.endsWith(`.${d}`));
|
|
110
|
+
if (!isAllowed) {
|
|
111
|
+
flags.push({
|
|
112
|
+
type: 'scope_violation',
|
|
113
|
+
severity: 'warning',
|
|
114
|
+
message: `URL references domain "${hostname}" not in allowed scope: ${allowedDomains.join(', ')}`,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
// Malformed URL — flag it
|
|
120
|
+
flags.push({
|
|
121
|
+
type: 'scope_violation',
|
|
122
|
+
severity: 'warning',
|
|
123
|
+
message: `Malformed URL detected in output: "${url.slice(0, 100)}"`,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
// Check for shell command patterns
|
|
129
|
+
const shellPatterns = [
|
|
130
|
+
/\b(curl|wget|ssh|scp|rsync)\s+/i,
|
|
131
|
+
/\brm\s+-rf\b/i,
|
|
132
|
+
/\bsudo\s+/i,
|
|
133
|
+
/\bchmod\s+[0-7]{3,4}\b/i,
|
|
134
|
+
/\|\s*(bash|sh|zsh)\b/i,
|
|
135
|
+
];
|
|
136
|
+
for (const pattern of shellPatterns) {
|
|
137
|
+
const match = pattern.exec(content);
|
|
138
|
+
if (match) {
|
|
139
|
+
flags.push({
|
|
140
|
+
type: 'scope_violation',
|
|
141
|
+
severity: 'warning',
|
|
142
|
+
message: `Shell command pattern detected in output: "${match[0]}"`,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return flags;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Compute bigram-based Jaccard similarity between two strings.
|
|
150
|
+
* Returns 0-1 where 1 is identical.
|
|
151
|
+
*/
|
|
152
|
+
computeSimilarity(a, b) {
|
|
153
|
+
if (a.length === 0 && b.length === 0)
|
|
154
|
+
return 1;
|
|
155
|
+
if (a.length === 0 || b.length === 0)
|
|
156
|
+
return 0;
|
|
157
|
+
const bigramsA = this.toBigrams(a.toLowerCase());
|
|
158
|
+
const bigramsB = this.toBigrams(b.toLowerCase());
|
|
159
|
+
let intersection = 0;
|
|
160
|
+
const bCopy = new Map(bigramsA);
|
|
161
|
+
for (const [bigram, countA] of bCopy) {
|
|
162
|
+
const countB = bigramsB.get(bigram) ?? 0;
|
|
163
|
+
intersection += Math.min(countA, countB);
|
|
164
|
+
}
|
|
165
|
+
const totalA = [...bigramsA.values()].reduce((s, v) => s + v, 0);
|
|
166
|
+
const totalB = [...bigramsB.values()].reduce((s, v) => s + v, 0);
|
|
167
|
+
const union = totalA + totalB - intersection;
|
|
168
|
+
return union === 0 ? 0 : intersection / union;
|
|
169
|
+
}
|
|
170
|
+
/** Extract character bigrams from a string. */
|
|
171
|
+
toBigrams(s) {
|
|
172
|
+
const bigrams = new Map();
|
|
173
|
+
for (let i = 0; i < s.length - 1; i++) {
|
|
174
|
+
const bigram = s.slice(i, i + 2);
|
|
175
|
+
bigrams.set(bigram, (bigrams.get(bigram) ?? 0) + 1);
|
|
176
|
+
}
|
|
177
|
+
return bigrams;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
//# sourceMappingURL=output-validator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"output-validator.js","sourceRoot":"","sources":["../src/output-validator.ts"],"names":[],"mappings":"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"}
|
package/dist/runtime.d.ts
CHANGED
|
@@ -7,12 +7,18 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { EventEmitter } from 'node:events';
|
|
9
9
|
import type { ModuleStatus, OrgLoopEvent, RuntimeStatus } from '@orgloop/sdk';
|
|
10
|
+
import type { AuditRecord, AuditTrailOptions } from './audit.js';
|
|
11
|
+
import { AuditTrail } from './audit.js';
|
|
10
12
|
import type { EventBus } from './bus.js';
|
|
11
13
|
import type { EventHistoryOptions, EventHistoryQuery, EventRecord } from './event-history.js';
|
|
12
14
|
import type { RuntimeControl } from './http.js';
|
|
13
15
|
import { WebhookServer } from './http.js';
|
|
14
16
|
import { LoggerManager } from './logger.js';
|
|
17
|
+
import type { LoopCheckResult, LoopDetectorOptions } from './loop-detector.js';
|
|
18
|
+
import { LoopDetector } from './loop-detector.js';
|
|
15
19
|
import type { ModuleConfig } from './module-instance.js';
|
|
20
|
+
import type { OutputValidatorOptions } from './output-validator.js';
|
|
21
|
+
import { OutputValidator } from './output-validator.js';
|
|
16
22
|
import type { CheckpointStore } from './store.js';
|
|
17
23
|
export interface SourceCircuitBreakerOptions {
|
|
18
24
|
/** Consecutive failures before opening circuit (default: 5) */
|
|
@@ -48,6 +54,12 @@ export interface RuntimeOptions {
|
|
|
48
54
|
metricsPort?: number;
|
|
49
55
|
/** Event history ring buffer options */
|
|
50
56
|
eventHistory?: EventHistoryOptions;
|
|
57
|
+
/** Audit trail options */
|
|
58
|
+
auditTrail?: AuditTrailOptions;
|
|
59
|
+
/** Output validator options */
|
|
60
|
+
outputValidator?: OutputValidatorOptions;
|
|
61
|
+
/** Loop detector options */
|
|
62
|
+
loopDetector?: LoopDetectorOptions;
|
|
51
63
|
}
|
|
52
64
|
export interface LoadModuleOptions {
|
|
53
65
|
/** Pre-instantiated source connectors (keyed by source ID) */
|
|
@@ -89,6 +101,9 @@ declare class Runtime extends EventEmitter implements RuntimeControl {
|
|
|
89
101
|
private readonly metricsPort;
|
|
90
102
|
private readonly eventHistory;
|
|
91
103
|
private readonly routeStats;
|
|
104
|
+
private readonly auditTrail;
|
|
105
|
+
private readonly outputValidator;
|
|
106
|
+
private readonly loopDetector;
|
|
92
107
|
constructor(options?: RuntimeOptions);
|
|
93
108
|
start(): Promise<void>;
|
|
94
109
|
/** Start the HTTP server for webhooks and control API. */
|
|
@@ -104,6 +119,7 @@ declare class Runtime extends EventEmitter implements RuntimeControl {
|
|
|
104
119
|
private deliverToActor;
|
|
105
120
|
private pollSource;
|
|
106
121
|
private scheduleCircuitRetry;
|
|
122
|
+
private resolveCheckpointStore;
|
|
107
123
|
private countAllSources;
|
|
108
124
|
private installCrashHandlers;
|
|
109
125
|
private removeCrashHandlers;
|
|
@@ -144,6 +160,25 @@ declare class Runtime extends EventEmitter implements RuntimeControl {
|
|
|
144
160
|
event_count: number;
|
|
145
161
|
poll_interval?: string;
|
|
146
162
|
}>;
|
|
163
|
+
/** Query the audit trail. */
|
|
164
|
+
queryAuditTrail(filter?: {
|
|
165
|
+
trace_id?: string;
|
|
166
|
+
route?: string;
|
|
167
|
+
actor?: string;
|
|
168
|
+
held_only?: boolean;
|
|
169
|
+
flagged_only?: boolean;
|
|
170
|
+
limit?: number;
|
|
171
|
+
}): AuditRecord[];
|
|
172
|
+
/** Get the full audit chain for a trace ID. */
|
|
173
|
+
getAuditChain(traceId: string): AuditRecord[];
|
|
174
|
+
/** Get loop detector state for a trace ID. */
|
|
175
|
+
getLoopState(traceId: string): LoopCheckResult | null;
|
|
176
|
+
/** Get the audit trail instance (for direct access in tests). */
|
|
177
|
+
getAuditTrail(): AuditTrail;
|
|
178
|
+
/** Get the loop detector instance (for direct access in tests). */
|
|
179
|
+
getLoopDetector(): LoopDetector;
|
|
180
|
+
/** Get the output validator instance (for direct access in tests). */
|
|
181
|
+
getOutputValidator(): OutputValidator;
|
|
147
182
|
/** Get the metrics registry for Prometheus text output. */
|
|
148
183
|
getMetricsText(): Promise<string | null>;
|
|
149
184
|
/** Get the webhook server for API handler registration. */
|
package/dist/runtime.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAI3C,OAAO,KAAK,EAGX,YAAY,EACZ,YAAY,EAEZ,aAAa,EACb,MAAM,cAAc,CAAC;AAEtB,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAGzC,OAAO,KAAK,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAE9F,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAChD,OAAO,EAAqB,aAAa,EAAE,MAAM,WAAW,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"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"}
|
package/dist/runtime.js
CHANGED
|
@@ -10,18 +10,21 @@ import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
|
10
10
|
import { homedir } from 'node:os';
|
|
11
11
|
import { join } from 'node:path';
|
|
12
12
|
import { generateTraceId } from '@orgloop/sdk';
|
|
13
|
+
import { AuditTrail, contentHash, generateAuditId } from './audit.js';
|
|
13
14
|
import { InMemoryBus } from './bus.js';
|
|
14
15
|
import { ConnectorError, DeliveryError, ModuleNotFoundError } from './errors.js';
|
|
15
16
|
import { EventHistory } from './event-history.js';
|
|
16
17
|
import { DEFAULT_HTTP_PORT, WebhookServer } from './http.js';
|
|
17
18
|
import { LoggerManager } from './logger.js';
|
|
19
|
+
import { LoopDetector } from './loop-detector.js';
|
|
18
20
|
import { MetricsServer } from './metrics.js';
|
|
19
21
|
import { ModuleInstance } from './module-instance.js';
|
|
22
|
+
import { OutputValidator } from './output-validator.js';
|
|
20
23
|
import { stripFrontMatter } from './prompt.js';
|
|
21
24
|
import { ModuleRegistry } from './registry.js';
|
|
22
25
|
import { interpolateConfig, matchRoutes } from './router.js';
|
|
23
26
|
import { Scheduler } from './scheduler.js';
|
|
24
|
-
import { InMemoryCheckpointStore } from './store.js';
|
|
27
|
+
import { FileCheckpointStore, InMemoryCheckpointStore } from './store.js';
|
|
25
28
|
import { executeTransformPipeline } from './transform.js';
|
|
26
29
|
// ─── Runtime Class ───────────────────────────────────────────────────────────
|
|
27
30
|
class Runtime extends EventEmitter {
|
|
@@ -60,6 +63,10 @@ class Runtime extends EventEmitter {
|
|
|
60
63
|
// REST API: event history and route stats
|
|
61
64
|
eventHistory;
|
|
62
65
|
routeStats = new Map();
|
|
66
|
+
// Audit trail, output validation, and loop detection
|
|
67
|
+
auditTrail;
|
|
68
|
+
outputValidator;
|
|
69
|
+
loopDetector;
|
|
63
70
|
constructor(options) {
|
|
64
71
|
super();
|
|
65
72
|
this.bus = options?.bus ?? new InMemoryBus();
|
|
@@ -89,6 +96,10 @@ class Runtime extends EventEmitter {
|
|
|
89
96
|
}
|
|
90
97
|
// Event history ring buffer
|
|
91
98
|
this.eventHistory = new EventHistory(options?.eventHistory);
|
|
99
|
+
// Audit trail, output validation, and loop detection
|
|
100
|
+
this.auditTrail = new AuditTrail(options?.auditTrail);
|
|
101
|
+
this.outputValidator = new OutputValidator(options?.outputValidator);
|
|
102
|
+
this.loopDetector = new LoopDetector(options?.loopDetector);
|
|
92
103
|
}
|
|
93
104
|
// ─── Lifecycle ───────────────────────────────────────────────────────────
|
|
94
105
|
async start() {
|
|
@@ -166,7 +177,7 @@ class Runtime extends EventEmitter {
|
|
|
166
177
|
}
|
|
167
178
|
// ─── Module Management ───────────────────────────────────────────────────
|
|
168
179
|
async loadModule(config, options) {
|
|
169
|
-
const checkpointStore = options?.checkpointStore ??
|
|
180
|
+
const checkpointStore = options?.checkpointStore ?? this.resolveCheckpointStore(config);
|
|
170
181
|
const mod = new ModuleInstance(config, {
|
|
171
182
|
sources: options?.sources ?? new Map(),
|
|
172
183
|
actors: options?.actors ?? new Map(),
|
|
@@ -290,6 +301,41 @@ class Runtime extends EventEmitter {
|
|
|
290
301
|
event_type: event.type,
|
|
291
302
|
module: mod.name,
|
|
292
303
|
});
|
|
304
|
+
// ─── Loop Detection ──────────────────────────────────────────────
|
|
305
|
+
if (event.trace_id) {
|
|
306
|
+
const loopCheck = this.loopDetector.check(event.trace_id, event.id, event.source, event.type, null, // route not yet known
|
|
307
|
+
null);
|
|
308
|
+
if (loopCheck.circuit_broken) {
|
|
309
|
+
await this.emitLog('loop.circuit_broken', {
|
|
310
|
+
event_id: event.id,
|
|
311
|
+
trace_id: event.trace_id,
|
|
312
|
+
source: event.source,
|
|
313
|
+
module: mod.name,
|
|
314
|
+
result: `Circuit broken: event chain depth ${loopCheck.chain_depth} exceeds limit`,
|
|
315
|
+
metadata: {
|
|
316
|
+
chain_depth: loopCheck.chain_depth,
|
|
317
|
+
chain: loopCheck.chain.map((n) => n.event_id),
|
|
318
|
+
},
|
|
319
|
+
});
|
|
320
|
+
this.emit('loop:circuit_broken', { event, loopCheck });
|
|
321
|
+
await this.bus.ack(event.id);
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
if (loopCheck.loop_detected) {
|
|
325
|
+
await this.emitLog('loop.detected', {
|
|
326
|
+
event_id: event.id,
|
|
327
|
+
trace_id: event.trace_id,
|
|
328
|
+
source: event.source,
|
|
329
|
+
module: mod.name,
|
|
330
|
+
result: `Loop detected: chain depth ${loopCheck.chain_depth}`,
|
|
331
|
+
metadata: {
|
|
332
|
+
chain_depth: loopCheck.chain_depth,
|
|
333
|
+
flags: loopCheck.flags.map((f) => f.message),
|
|
334
|
+
},
|
|
335
|
+
});
|
|
336
|
+
this.emit('loop:detected', { event, loopCheck });
|
|
337
|
+
}
|
|
338
|
+
}
|
|
293
339
|
// Write to bus (WAL)
|
|
294
340
|
await this.bus.publish(event);
|
|
295
341
|
// Match routes from this module
|
|
@@ -424,6 +470,9 @@ class Runtime extends EventEmitter {
|
|
|
424
470
|
module: mod.name,
|
|
425
471
|
});
|
|
426
472
|
const startTime = Date.now();
|
|
473
|
+
const auditFlags = [];
|
|
474
|
+
const auditOutputs = [];
|
|
475
|
+
let deliveryStatus = 'error';
|
|
427
476
|
try {
|
|
428
477
|
// Build delivery config (interpolate {{dot.path}} templates from event)
|
|
429
478
|
const deliveryConfig = {
|
|
@@ -442,44 +491,88 @@ class Runtime extends EventEmitter {
|
|
|
442
491
|
// Non-fatal: log but continue delivery
|
|
443
492
|
}
|
|
444
493
|
}
|
|
445
|
-
|
|
446
|
-
const
|
|
447
|
-
|
|
448
|
-
|
|
494
|
+
// ─── Output Validation ───────────────────────────────────────
|
|
495
|
+
const deliveryContent = JSON.stringify(deliveryConfig);
|
|
496
|
+
const validation = this.outputValidator.validate(deliveryContent, event);
|
|
497
|
+
if (validation.flags.length > 0) {
|
|
498
|
+
auditFlags.push(...validation.flags);
|
|
499
|
+
for (const flag of validation.flags) {
|
|
500
|
+
await this.emitLog('audit.flag', {
|
|
501
|
+
event_id: event.id,
|
|
502
|
+
trace_id: event.trace_id,
|
|
503
|
+
route: routeName,
|
|
504
|
+
target: actorId,
|
|
505
|
+
module: mod.name,
|
|
506
|
+
result: flag.message,
|
|
507
|
+
metadata: { flag_type: flag.type, severity: flag.severity },
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
if (validation.hold_for_review) {
|
|
512
|
+
deliveryStatus = 'held';
|
|
513
|
+
await this.emitLog('audit.held', {
|
|
449
514
|
event_id: event.id,
|
|
450
515
|
trace_id: event.trace_id,
|
|
451
516
|
route: routeName,
|
|
452
517
|
target: actorId,
|
|
453
|
-
duration_ms: durationMs,
|
|
454
518
|
module: mod.name,
|
|
519
|
+
result: 'Output held for human review due to critical flags',
|
|
520
|
+
metadata: { flags: validation.flags.map((f) => f.message) },
|
|
455
521
|
});
|
|
456
|
-
this.emit('
|
|
457
|
-
event,
|
|
458
|
-
route: routeName,
|
|
459
|
-
actor: actorId,
|
|
460
|
-
status: 'delivered',
|
|
461
|
-
});
|
|
522
|
+
this.emit('audit:held', { event, route: routeName, actor: actorId, validation });
|
|
462
523
|
}
|
|
463
524
|
else {
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
525
|
+
// Proceed with delivery
|
|
526
|
+
const result = await actor.deliver(event, deliveryConfig);
|
|
527
|
+
const durationMs = Date.now() - startTime;
|
|
528
|
+
// Record output
|
|
529
|
+
auditOutputs.push({
|
|
530
|
+
type: `deliver.${result.status}`,
|
|
468
531
|
target: actorId,
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
});
|
|
473
|
-
this.emit('delivery', {
|
|
474
|
-
event,
|
|
475
|
-
route: routeName,
|
|
476
|
-
actor: actorId,
|
|
477
|
-
status: result.status,
|
|
532
|
+
content_hash: contentHash(deliveryConfig),
|
|
533
|
+
timestamp: new Date().toISOString(),
|
|
534
|
+
flags: validation.flags,
|
|
478
535
|
});
|
|
536
|
+
if (result.status === 'delivered') {
|
|
537
|
+
deliveryStatus = 'delivered';
|
|
538
|
+
await this.emitLog('deliver.success', {
|
|
539
|
+
event_id: event.id,
|
|
540
|
+
trace_id: event.trace_id,
|
|
541
|
+
route: routeName,
|
|
542
|
+
target: actorId,
|
|
543
|
+
duration_ms: durationMs,
|
|
544
|
+
module: mod.name,
|
|
545
|
+
});
|
|
546
|
+
this.emit('delivery', {
|
|
547
|
+
event,
|
|
548
|
+
route: routeName,
|
|
549
|
+
actor: actorId,
|
|
550
|
+
status: 'delivered',
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
else {
|
|
554
|
+
deliveryStatus = result.status;
|
|
555
|
+
await this.emitLog('deliver.failure', {
|
|
556
|
+
event_id: event.id,
|
|
557
|
+
trace_id: event.trace_id,
|
|
558
|
+
route: routeName,
|
|
559
|
+
target: actorId,
|
|
560
|
+
duration_ms: durationMs,
|
|
561
|
+
error: result.error?.message ?? result.status,
|
|
562
|
+
module: mod.name,
|
|
563
|
+
});
|
|
564
|
+
this.emit('delivery', {
|
|
565
|
+
event,
|
|
566
|
+
route: routeName,
|
|
567
|
+
actor: actorId,
|
|
568
|
+
status: result.status,
|
|
569
|
+
});
|
|
570
|
+
}
|
|
479
571
|
}
|
|
480
572
|
}
|
|
481
573
|
catch (err) {
|
|
482
574
|
const durationMs = Date.now() - startTime;
|
|
575
|
+
deliveryStatus = 'error';
|
|
483
576
|
const error = new DeliveryError(actorId, routeName, 'Delivery failed', { cause: err });
|
|
484
577
|
this.emit('error', error);
|
|
485
578
|
this.metricsServer?.connectorErrors.inc({ connector: actorId });
|
|
@@ -493,6 +586,44 @@ class Runtime extends EventEmitter {
|
|
|
493
586
|
module: mod.name,
|
|
494
587
|
});
|
|
495
588
|
}
|
|
589
|
+
// ─── Audit Trail Recording ───────────────────────────────────────
|
|
590
|
+
const durationMs = Date.now() - startTime;
|
|
591
|
+
const chainDepth = event.trace_id ? this.loopDetector.getChainDepth(event.trace_id) : 1;
|
|
592
|
+
const auditRecord = {
|
|
593
|
+
id: generateAuditId(),
|
|
594
|
+
timestamp: new Date().toISOString(),
|
|
595
|
+
trace_id: event.trace_id ?? '',
|
|
596
|
+
input_event_id: event.id,
|
|
597
|
+
input_source: event.source,
|
|
598
|
+
input_type: event.type,
|
|
599
|
+
input_content_hash: contentHash(event.payload),
|
|
600
|
+
route: routeName,
|
|
601
|
+
sop_file: route.with?.prompt_file ?? null,
|
|
602
|
+
module: mod.name,
|
|
603
|
+
actor: actorId,
|
|
604
|
+
delivery_status: deliveryStatus,
|
|
605
|
+
duration_ms: durationMs,
|
|
606
|
+
outputs: auditOutputs,
|
|
607
|
+
chain_depth: chainDepth,
|
|
608
|
+
parent_event_id: null, // set by caller if part of a chain
|
|
609
|
+
held_for_review: deliveryStatus === 'held',
|
|
610
|
+
flags: auditFlags,
|
|
611
|
+
};
|
|
612
|
+
this.auditTrail.record(auditRecord);
|
|
613
|
+
await this.emitLog('audit.record', {
|
|
614
|
+
event_id: event.id,
|
|
615
|
+
trace_id: event.trace_id,
|
|
616
|
+
route: routeName,
|
|
617
|
+
target: actorId,
|
|
618
|
+
module: mod.name,
|
|
619
|
+
metadata: {
|
|
620
|
+
audit_id: auditRecord.id,
|
|
621
|
+
delivery_status: deliveryStatus,
|
|
622
|
+
chain_depth: chainDepth,
|
|
623
|
+
flag_count: auditFlags.length,
|
|
624
|
+
held: auditRecord.held_for_review,
|
|
625
|
+
},
|
|
626
|
+
});
|
|
496
627
|
}
|
|
497
628
|
// ─── Source Polling ───────────────────────────────────────────────────────
|
|
498
629
|
async pollSource(sourceId, moduleName) {
|
|
@@ -596,6 +727,34 @@ class Runtime extends EventEmitter {
|
|
|
596
727
|
this.circuitRetryTimers.set(timerKey, timer);
|
|
597
728
|
}
|
|
598
729
|
// ─── Helpers ────────────────────────────────────────────────────────────
|
|
730
|
+
resolveCheckpointStore(config) {
|
|
731
|
+
const cpConfig = config.defaults?.checkpoint;
|
|
732
|
+
if (cpConfig?.store === 'memory') {
|
|
733
|
+
return new InMemoryCheckpointStore();
|
|
734
|
+
}
|
|
735
|
+
// Explicit file store requested or directory configured
|
|
736
|
+
if (cpConfig?.store === 'file' || cpConfig?.dir) {
|
|
737
|
+
if (cpConfig?.dir) {
|
|
738
|
+
const dir = cpConfig.dir.startsWith('/') || !config.modulePath
|
|
739
|
+
? cpConfig.dir
|
|
740
|
+
: join(config.modulePath, cpConfig.dir);
|
|
741
|
+
return new FileCheckpointStore(dir);
|
|
742
|
+
}
|
|
743
|
+
if (config.modulePath) {
|
|
744
|
+
return new FileCheckpointStore(join(config.modulePath, '.orgloop', 'checkpoints'));
|
|
745
|
+
}
|
|
746
|
+
return new FileCheckpointStore();
|
|
747
|
+
}
|
|
748
|
+
// No explicit config — use file if we have a directory hint
|
|
749
|
+
if (config.modulePath) {
|
|
750
|
+
return new FileCheckpointStore(join(config.modulePath, '.orgloop', 'checkpoints'));
|
|
751
|
+
}
|
|
752
|
+
if (this.dataDir) {
|
|
753
|
+
return new FileCheckpointStore(this.dataDir);
|
|
754
|
+
}
|
|
755
|
+
// No directory context at all — fall back to in-memory
|
|
756
|
+
return new InMemoryCheckpointStore();
|
|
757
|
+
}
|
|
599
758
|
countAllSources() {
|
|
600
759
|
let count = 0;
|
|
601
760
|
for (const mod of this.registry.list()) {
|
|
@@ -770,6 +929,40 @@ class Runtime extends EventEmitter {
|
|
|
770
929
|
}
|
|
771
930
|
return sources;
|
|
772
931
|
}
|
|
932
|
+
// ─── Audit Trail & Loop Detection API ────────────────────────────────
|
|
933
|
+
/** Query the audit trail. */
|
|
934
|
+
queryAuditTrail(filter) {
|
|
935
|
+
return this.auditTrail.query(filter);
|
|
936
|
+
}
|
|
937
|
+
/** Get the full audit chain for a trace ID. */
|
|
938
|
+
getAuditChain(traceId) {
|
|
939
|
+
return this.auditTrail.getChain(traceId);
|
|
940
|
+
}
|
|
941
|
+
/** Get loop detector state for a trace ID. */
|
|
942
|
+
getLoopState(traceId) {
|
|
943
|
+
const chain = this.loopDetector.getChain(traceId);
|
|
944
|
+
if (chain.length === 0)
|
|
945
|
+
return null;
|
|
946
|
+
return {
|
|
947
|
+
loop_detected: false,
|
|
948
|
+
circuit_broken: this.loopDetector.isCircuitBroken(traceId),
|
|
949
|
+
chain_depth: chain.length,
|
|
950
|
+
chain,
|
|
951
|
+
flags: [],
|
|
952
|
+
};
|
|
953
|
+
}
|
|
954
|
+
/** Get the audit trail instance (for direct access in tests). */
|
|
955
|
+
getAuditTrail() {
|
|
956
|
+
return this.auditTrail;
|
|
957
|
+
}
|
|
958
|
+
/** Get the loop detector instance (for direct access in tests). */
|
|
959
|
+
getLoopDetector() {
|
|
960
|
+
return this.loopDetector;
|
|
961
|
+
}
|
|
962
|
+
/** Get the output validator instance (for direct access in tests). */
|
|
963
|
+
getOutputValidator() {
|
|
964
|
+
return this.outputValidator;
|
|
965
|
+
}
|
|
773
966
|
/** Get the metrics registry for Prometheus text output. */
|
|
774
967
|
async getMetricsText() {
|
|
775
968
|
if (!this.metricsServer)
|