@tracehound/core 1.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.
- package/README.md +125 -0
- package/dist/core/agent.d.ts +89 -0
- package/dist/core/agent.d.ts.map +1 -0
- package/dist/core/agent.js +141 -0
- package/dist/core/agent.js.map +1 -0
- package/dist/core/audit-chain.d.ts +39 -0
- package/dist/core/audit-chain.d.ts.map +1 -0
- package/dist/core/audit-chain.js +87 -0
- package/dist/core/audit-chain.js.map +1 -0
- package/dist/core/cold-storage.d.ts +87 -0
- package/dist/core/cold-storage.d.ts.map +1 -0
- package/dist/core/cold-storage.js +53 -0
- package/dist/core/cold-storage.js.map +1 -0
- package/dist/core/evidence-factory.d.ts +85 -0
- package/dist/core/evidence-factory.d.ts.map +1 -0
- package/dist/core/evidence-factory.js +96 -0
- package/dist/core/evidence-factory.js.map +1 -0
- package/dist/core/evidence.d.ts +48 -0
- package/dist/core/evidence.d.ts.map +1 -0
- package/dist/core/evidence.js +135 -0
- package/dist/core/evidence.js.map +1 -0
- package/dist/core/fail-safe.d.ts +149 -0
- package/dist/core/fail-safe.d.ts.map +1 -0
- package/dist/core/fail-safe.js +217 -0
- package/dist/core/fail-safe.js.map +1 -0
- package/dist/core/hound-ipc.d.ts +91 -0
- package/dist/core/hound-ipc.d.ts.map +1 -0
- package/dist/core/hound-ipc.js +196 -0
- package/dist/core/hound-ipc.js.map +1 -0
- package/dist/core/hound-pool.d.ts +157 -0
- package/dist/core/hound-pool.d.ts.map +1 -0
- package/dist/core/hound-pool.js +337 -0
- package/dist/core/hound-pool.js.map +1 -0
- package/dist/core/hound-process.d.ts +14 -0
- package/dist/core/hound-process.d.ts.map +1 -0
- package/dist/core/hound-process.js +112 -0
- package/dist/core/hound-process.js.map +1 -0
- package/dist/core/hound-worker.d.ts +14 -0
- package/dist/core/hound-worker.d.ts.map +1 -0
- package/dist/core/hound-worker.js +112 -0
- package/dist/core/hound-worker.js.map +1 -0
- package/dist/core/lane-queue.d.ts +121 -0
- package/dist/core/lane-queue.d.ts.map +1 -0
- package/dist/core/lane-queue.js +181 -0
- package/dist/core/lane-queue.js.map +1 -0
- package/dist/core/license-manager.d.ts +128 -0
- package/dist/core/license-manager.d.ts.map +1 -0
- package/dist/core/license-manager.js +219 -0
- package/dist/core/license-manager.js.map +1 -0
- package/dist/core/notification-emitter.d.ts +140 -0
- package/dist/core/notification-emitter.d.ts.map +1 -0
- package/dist/core/notification-emitter.js +197 -0
- package/dist/core/notification-emitter.js.map +1 -0
- package/dist/core/process-adapter.d.ts +146 -0
- package/dist/core/process-adapter.d.ts.map +1 -0
- package/dist/core/process-adapter.js +174 -0
- package/dist/core/process-adapter.js.map +1 -0
- package/dist/core/quarantine.d.ts +95 -0
- package/dist/core/quarantine.d.ts.map +1 -0
- package/dist/core/quarantine.js +221 -0
- package/dist/core/quarantine.js.map +1 -0
- package/dist/core/rate-limiter.d.ts +94 -0
- package/dist/core/rate-limiter.d.ts.map +1 -0
- package/dist/core/rate-limiter.js +156 -0
- package/dist/core/rate-limiter.js.map +1 -0
- package/dist/core/s3-cold-storage.d.ts +116 -0
- package/dist/core/s3-cold-storage.d.ts.map +1 -0
- package/dist/core/s3-cold-storage.js +198 -0
- package/dist/core/s3-cold-storage.js.map +1 -0
- package/dist/core/scheduler.d.ts +126 -0
- package/dist/core/scheduler.d.ts.map +1 -0
- package/dist/core/scheduler.js +138 -0
- package/dist/core/scheduler.js.map +1 -0
- package/dist/core/security-state.d.ts +170 -0
- package/dist/core/security-state.d.ts.map +1 -0
- package/dist/core/security-state.js +156 -0
- package/dist/core/security-state.js.map +1 -0
- package/dist/core/tier-capacity.d.ts +58 -0
- package/dist/core/tier-capacity.d.ts.map +1 -0
- package/dist/core/tier-capacity.js +89 -0
- package/dist/core/tier-capacity.js.map +1 -0
- package/dist/core/tracehound.d.ts +85 -0
- package/dist/core/tracehound.d.ts.map +1 -0
- package/dist/core/tracehound.js +90 -0
- package/dist/core/tracehound.js.map +1 -0
- package/dist/core/trust-boundary.d.ts +85 -0
- package/dist/core/trust-boundary.d.ts.map +1 -0
- package/dist/core/trust-boundary.js +71 -0
- package/dist/core/trust-boundary.js.map +1 -0
- package/dist/core/watcher.d.ts +153 -0
- package/dist/core/watcher.d.ts.map +1 -0
- package/dist/core/watcher.js +141 -0
- package/dist/core/watcher.js.map +1 -0
- package/dist/index.d.ts +53 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +112 -0
- package/dist/index.js.map +1 -0
- package/dist/types/audit.d.ts +45 -0
- package/dist/types/audit.d.ts.map +1 -0
- package/dist/types/audit.js +5 -0
- package/dist/types/audit.js.map +1 -0
- package/dist/types/common.d.ts +12 -0
- package/dist/types/common.d.ts.map +1 -0
- package/dist/types/common.js +5 -0
- package/dist/types/common.js.map +1 -0
- package/dist/types/config.d.ts +98 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/config.js +58 -0
- package/dist/types/config.js.map +1 -0
- package/dist/types/errors.d.ts +118 -0
- package/dist/types/errors.d.ts.map +1 -0
- package/dist/types/errors.js +266 -0
- package/dist/types/errors.js.map +1 -0
- package/dist/types/evidence.d.ts +102 -0
- package/dist/types/evidence.d.ts.map +1 -0
- package/dist/types/evidence.js +5 -0
- package/dist/types/evidence.js.map +1 -0
- package/dist/types/index.d.ts +18 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +9 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/result.d.ts +62 -0
- package/dist/types/result.d.ts.map +1 -0
- package/dist/types/result.js +34 -0
- package/dist/types/result.js.map +1 -0
- package/dist/types/scent.d.ts +55 -0
- package/dist/types/scent.d.ts.map +1 -0
- package/dist/types/scent.js +5 -0
- package/dist/types/scent.js.map +1 -0
- package/dist/types/signature.d.ts +47 -0
- package/dist/types/signature.d.ts.map +1 -0
- package/dist/types/signature.js +68 -0
- package/dist/types/signature.js.map +1 -0
- package/dist/types/threat.d.ts +38 -0
- package/dist/types/threat.d.ts.map +1 -0
- package/dist/types/threat.js +18 -0
- package/dist/types/threat.js.map +1 -0
- package/dist/utils/binary-codec.d.ts +225 -0
- package/dist/utils/binary-codec.d.ts.map +1 -0
- package/dist/utils/binary-codec.js +266 -0
- package/dist/utils/binary-codec.js.map +1 -0
- package/dist/utils/compare.d.ts +26 -0
- package/dist/utils/compare.d.ts.map +1 -0
- package/dist/utils/compare.js +44 -0
- package/dist/utils/compare.js.map +1 -0
- package/dist/utils/encode.d.ts +39 -0
- package/dist/utils/encode.d.ts.map +1 -0
- package/dist/utils/encode.js +124 -0
- package/dist/utils/encode.js.map +1 -0
- package/dist/utils/hash.d.ts +19 -0
- package/dist/utils/hash.d.ts.map +1 -0
- package/dist/utils/hash.js +25 -0
- package/dist/utils/hash.js.map +1 -0
- package/dist/utils/id.d.ts +20 -0
- package/dist/utils/id.d.ts.map +1 -0
- package/dist/utils/id.js +47 -0
- package/dist/utils/id.js.map +1 -0
- package/dist/utils/runtime.d.ts +24 -0
- package/dist/utils/runtime.d.ts.map +1 -0
- package/dist/utils/runtime.js +68 -0
- package/dist/utils/runtime.js.map +1 -0
- package/dist/utils/serialize.d.ts +14 -0
- package/dist/utils/serialize.d.ts.map +1 -0
- package/dist/utils/serialize.js +27 -0
- package/dist/utils/serialize.js.map +1 -0
- package/package.json +54 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Universal Notification API - Event emission for all consumers.
|
|
3
|
+
*
|
|
4
|
+
* RFC-0000 COMPLIANCE:
|
|
5
|
+
* - Read-only event emission (no backpressure)
|
|
6
|
+
* - Fire-and-forget semantics
|
|
7
|
+
* - No blocking on consumer processing
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* All possible event types emitted by Tracehound.
|
|
11
|
+
*/
|
|
12
|
+
export type EventType = 'threat.detected' | 'evidence.quarantined' | 'evidence.evicted' | 'rate_limit.exceeded' | 'system.panic' | 'license.validated' | 'license.expired';
|
|
13
|
+
/**
|
|
14
|
+
* Base event structure.
|
|
15
|
+
*/
|
|
16
|
+
export interface TracehoundEvent<T = unknown> {
|
|
17
|
+
/** Event type */
|
|
18
|
+
type: EventType;
|
|
19
|
+
/** Unix timestamp (ms) */
|
|
20
|
+
timestamp: number;
|
|
21
|
+
/** Event payload */
|
|
22
|
+
payload: T;
|
|
23
|
+
/** Unique event ID */
|
|
24
|
+
id: string;
|
|
25
|
+
}
|
|
26
|
+
export interface ThreatDetectedPayload {
|
|
27
|
+
scentId: string;
|
|
28
|
+
category: string;
|
|
29
|
+
severity: 'low' | 'medium' | 'high' | 'critical';
|
|
30
|
+
source: string;
|
|
31
|
+
}
|
|
32
|
+
export interface EvidenceQuarantinedPayload {
|
|
33
|
+
signature: string;
|
|
34
|
+
severity: 'low' | 'medium' | 'high' | 'critical';
|
|
35
|
+
sizeBytes: number;
|
|
36
|
+
}
|
|
37
|
+
export interface EvidenceEvictedPayload {
|
|
38
|
+
signature: string;
|
|
39
|
+
reason: 'capacity' | 'policy' | 'manual';
|
|
40
|
+
}
|
|
41
|
+
export interface RateLimitExceededPayload {
|
|
42
|
+
source: string;
|
|
43
|
+
retryAfterMs: number;
|
|
44
|
+
}
|
|
45
|
+
export interface SystemPanicPayload {
|
|
46
|
+
level: 'warning' | 'critical' | 'fatal';
|
|
47
|
+
reason: string;
|
|
48
|
+
context?: Record<string, unknown>;
|
|
49
|
+
}
|
|
50
|
+
export interface LicenseValidatedPayload {
|
|
51
|
+
tier: 'starter' | 'pro' | 'enterprise';
|
|
52
|
+
daysRemaining?: number;
|
|
53
|
+
}
|
|
54
|
+
export interface LicenseExpiredPayload {
|
|
55
|
+
tier: 'starter' | 'pro' | 'enterprise';
|
|
56
|
+
gracePeriod: boolean;
|
|
57
|
+
}
|
|
58
|
+
export interface WebhookConfig {
|
|
59
|
+
/** Webhook URL */
|
|
60
|
+
url: string;
|
|
61
|
+
/** Events to subscribe to (empty = all) */
|
|
62
|
+
events?: EventType[];
|
|
63
|
+
/** Custom headers */
|
|
64
|
+
headers?: Record<string, string>;
|
|
65
|
+
/** Secret for HMAC signature */
|
|
66
|
+
secret?: string;
|
|
67
|
+
/** Retry configuration */
|
|
68
|
+
retry?: {
|
|
69
|
+
maxAttempts: number;
|
|
70
|
+
delayMs: number;
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
export type EventCallback<T = unknown> = (event: TracehoundEvent<T>) => void;
|
|
74
|
+
/**
|
|
75
|
+
* Notification Emitter interface.
|
|
76
|
+
*/
|
|
77
|
+
export interface INotificationEmitter {
|
|
78
|
+
/**
|
|
79
|
+
* Register a callback for an event type.
|
|
80
|
+
*/
|
|
81
|
+
on<T = unknown>(event: EventType, callback: EventCallback<T>): void;
|
|
82
|
+
/**
|
|
83
|
+
* Unregister a callback for an event type.
|
|
84
|
+
*/
|
|
85
|
+
off<T = unknown>(event: EventType, callback: EventCallback<T>): void;
|
|
86
|
+
/**
|
|
87
|
+
* Subscribe to events as an async iterable.
|
|
88
|
+
* @param events - Event types to subscribe to (empty = all)
|
|
89
|
+
*/
|
|
90
|
+
subscribe(events?: EventType[]): AsyncIterable<TracehoundEvent>;
|
|
91
|
+
/**
|
|
92
|
+
* Register a webhook for event delivery.
|
|
93
|
+
* @returns Webhook ID
|
|
94
|
+
*/
|
|
95
|
+
registerWebhook(config: WebhookConfig): string;
|
|
96
|
+
/**
|
|
97
|
+
* Unregister a webhook.
|
|
98
|
+
*/
|
|
99
|
+
unregisterWebhook(id: string): void;
|
|
100
|
+
/**
|
|
101
|
+
* Emit an event to all consumers.
|
|
102
|
+
*/
|
|
103
|
+
emit<T>(type: EventType, payload: T): void;
|
|
104
|
+
/**
|
|
105
|
+
* Get emitter statistics.
|
|
106
|
+
*/
|
|
107
|
+
readonly stats: NotificationEmitterStats;
|
|
108
|
+
}
|
|
109
|
+
export interface NotificationEmitterStats {
|
|
110
|
+
totalEmitted: number;
|
|
111
|
+
byType: Record<EventType, number>;
|
|
112
|
+
activeCallbacks: number;
|
|
113
|
+
activeSubscribers: number;
|
|
114
|
+
activeWebhooks: number;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Notification Emitter implementation.
|
|
118
|
+
*/
|
|
119
|
+
export declare class NotificationEmitter implements INotificationEmitter {
|
|
120
|
+
private callbacks;
|
|
121
|
+
private subscribers;
|
|
122
|
+
private webhooks;
|
|
123
|
+
private _totalEmitted;
|
|
124
|
+
private _byType;
|
|
125
|
+
private eventCounter;
|
|
126
|
+
on<T = unknown>(event: EventType, callback: EventCallback<T>): void;
|
|
127
|
+
off<T = unknown>(event: EventType, callback: EventCallback<T>): void;
|
|
128
|
+
subscribe(events?: EventType[]): AsyncIterable<TracehoundEvent>;
|
|
129
|
+
registerWebhook(config: WebhookConfig): string;
|
|
130
|
+
unregisterWebhook(id: string): void;
|
|
131
|
+
emit<T>(type: EventType, payload: T): void;
|
|
132
|
+
get stats(): NotificationEmitterStats;
|
|
133
|
+
private dispatchWebhook;
|
|
134
|
+
private sleep;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Create a Notification Emitter instance.
|
|
138
|
+
*/
|
|
139
|
+
export declare function createNotificationEmitter(): INotificationEmitter;
|
|
140
|
+
//# sourceMappingURL=notification-emitter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notification-emitter.d.ts","sourceRoot":"","sources":["../../src/core/notification-emitter.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH;;GAEG;AACH,MAAM,MAAM,SAAS,GACjB,iBAAiB,GACjB,sBAAsB,GACtB,kBAAkB,GAClB,qBAAqB,GACrB,cAAc,GACd,mBAAmB,GACnB,iBAAiB,CAAA;AAErB;;GAEG;AACH,MAAM,WAAW,eAAe,CAAC,CAAC,GAAG,OAAO;IAC1C,iBAAiB;IACjB,IAAI,EAAE,SAAS,CAAA;IACf,0BAA0B;IAC1B,SAAS,EAAE,MAAM,CAAA;IACjB,oBAAoB;IACpB,OAAO,EAAE,CAAC,CAAA;IACV,sBAAsB;IACtB,EAAE,EAAE,MAAM,CAAA;CACX;AAMD,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,UAAU,CAAA;IAChD,MAAM,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,0BAA0B;IACzC,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,UAAU,CAAA;IAChD,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,sBAAsB;IACrC,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,UAAU,GAAG,QAAQ,GAAG,QAAQ,CAAA;CACzC;AAED,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,MAAM,CAAA;IACd,YAAY,EAAE,MAAM,CAAA;CACrB;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,SAAS,GAAG,UAAU,GAAG,OAAO,CAAA;IACvC,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAClC;AAED,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,SAAS,GAAG,KAAK,GAAG,YAAY,CAAA;IACtC,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,SAAS,GAAG,KAAK,GAAG,YAAY,CAAA;IACtC,WAAW,EAAE,OAAO,CAAA;CACrB;AAMD,MAAM,WAAW,aAAa;IAC5B,kBAAkB;IAClB,GAAG,EAAE,MAAM,CAAA;IACX,2CAA2C;IAC3C,MAAM,CAAC,EAAE,SAAS,EAAE,CAAA;IACpB,qBAAqB;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAChC,gCAAgC;IAChC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,0BAA0B;IAC1B,KAAK,CAAC,EAAE;QACN,WAAW,EAAE,MAAM,CAAA;QACnB,OAAO,EAAE,MAAM,CAAA;KAChB,CAAA;CACF;AAUD,MAAM,MAAM,aAAa,CAAC,CAAC,GAAG,OAAO,IAAI,CAAC,KAAK,EAAE,eAAe,CAAC,CAAC,CAAC,KAAK,IAAI,CAAA;AAE5E;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC;;OAEG;IACH,EAAE,CAAC,CAAC,GAAG,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,aAAa,CAAC,CAAC,CAAC,GAAG,IAAI,CAAA;IAEnE;;OAEG;IACH,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,aAAa,CAAC,CAAC,CAAC,GAAG,IAAI,CAAA;IAEpE;;;OAGG;IACH,SAAS,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,GAAG,aAAa,CAAC,eAAe,CAAC,CAAA;IAE/D;;;OAGG;IACH,eAAe,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM,CAAA;IAE9C;;OAEG;IACH,iBAAiB,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAA;IAEnC;;OAEG;IACH,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,GAAG,IAAI,CAAA;IAE1C;;OAEG;IACH,QAAQ,CAAC,KAAK,EAAE,wBAAwB,CAAA;CACzC;AAED,MAAM,WAAW,wBAAwB;IACvC,YAAY,EAAE,MAAM,CAAA;IACpB,MAAM,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAAA;IACjC,eAAe,EAAE,MAAM,CAAA;IACvB,iBAAiB,EAAE,MAAM,CAAA;IACzB,cAAc,EAAE,MAAM,CAAA;CACvB;AAMD;;GAEG;AACH,qBAAa,mBAAoB,YAAW,oBAAoB;IAC9D,OAAO,CAAC,SAAS,CAA2C;IAC5D,OAAO,CAAC,WAAW,CAGZ;IACP,OAAO,CAAC,QAAQ,CAAuC;IAEvD,OAAO,CAAC,aAAa,CAAI;IACzB,OAAO,CAAC,OAAO,CAA+B;IAC9C,OAAO,CAAC,YAAY,CAAI;IAExB,EAAE,CAAC,CAAC,GAAG,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,aAAa,CAAC,CAAC,CAAC,GAAG,IAAI;IASnE,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,aAAa,CAAC,CAAC,CAAC,GAAG,IAAI;IAOpE,SAAS,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,GAAG,aAAa,CAAC,eAAe,CAAC;IAoD/D,eAAe,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM;IAM9C,iBAAiB,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAInC,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,GAAG,IAAI;IAyC1C,IAAI,KAAK,IAAI,wBAAwB,CAapC;YAIa,eAAe;IA+C7B,OAAO,CAAC,KAAK;CAGd;AAMD;;GAEG;AACH,wBAAgB,yBAAyB,IAAI,oBAAoB,CAEhE"}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Universal Notification API - Event emission for all consumers.
|
|
3
|
+
*
|
|
4
|
+
* RFC-0000 COMPLIANCE:
|
|
5
|
+
* - Read-only event emission (no backpressure)
|
|
6
|
+
* - Fire-and-forget semantics
|
|
7
|
+
* - No blocking on consumer processing
|
|
8
|
+
*/
|
|
9
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
10
|
+
// Implementation
|
|
11
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
12
|
+
/**
|
|
13
|
+
* Notification Emitter implementation.
|
|
14
|
+
*/
|
|
15
|
+
export class NotificationEmitter {
|
|
16
|
+
callbacks = new Map();
|
|
17
|
+
subscribers = [];
|
|
18
|
+
webhooks = new Map();
|
|
19
|
+
_totalEmitted = 0;
|
|
20
|
+
_byType = new Map();
|
|
21
|
+
eventCounter = 0;
|
|
22
|
+
on(event, callback) {
|
|
23
|
+
let set = this.callbacks.get(event);
|
|
24
|
+
if (!set) {
|
|
25
|
+
set = new Set();
|
|
26
|
+
this.callbacks.set(event, set);
|
|
27
|
+
}
|
|
28
|
+
set.add(callback);
|
|
29
|
+
}
|
|
30
|
+
off(event, callback) {
|
|
31
|
+
const set = this.callbacks.get(event);
|
|
32
|
+
if (set) {
|
|
33
|
+
set.delete(callback);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
subscribe(events) {
|
|
37
|
+
const self = this;
|
|
38
|
+
const queue = [];
|
|
39
|
+
let resolve = null;
|
|
40
|
+
let closed = false;
|
|
41
|
+
const subscriber = {
|
|
42
|
+
events: events ?? null,
|
|
43
|
+
push: (event) => {
|
|
44
|
+
if (closed)
|
|
45
|
+
return;
|
|
46
|
+
if (resolve) {
|
|
47
|
+
const r = resolve;
|
|
48
|
+
resolve = null;
|
|
49
|
+
r({ value: event, done: false });
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
queue.push(event);
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
this.subscribers.push(subscriber);
|
|
57
|
+
return {
|
|
58
|
+
[Symbol.asyncIterator]() {
|
|
59
|
+
return {
|
|
60
|
+
next() {
|
|
61
|
+
if (closed) {
|
|
62
|
+
return Promise.resolve({ value: undefined, done: true });
|
|
63
|
+
}
|
|
64
|
+
const queued = queue.shift();
|
|
65
|
+
if (queued) {
|
|
66
|
+
return Promise.resolve({ value: queued, done: false });
|
|
67
|
+
}
|
|
68
|
+
return new Promise((r) => {
|
|
69
|
+
resolve = r;
|
|
70
|
+
});
|
|
71
|
+
},
|
|
72
|
+
return() {
|
|
73
|
+
closed = true;
|
|
74
|
+
const idx = self.subscribers.indexOf(subscriber);
|
|
75
|
+
if (idx !== -1) {
|
|
76
|
+
self.subscribers.splice(idx, 1);
|
|
77
|
+
}
|
|
78
|
+
return Promise.resolve({ value: undefined, done: true });
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
registerWebhook(config) {
|
|
85
|
+
const id = `webhook-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
86
|
+
this.webhooks.set(id, { ...config, id });
|
|
87
|
+
return id;
|
|
88
|
+
}
|
|
89
|
+
unregisterWebhook(id) {
|
|
90
|
+
this.webhooks.delete(id);
|
|
91
|
+
}
|
|
92
|
+
emit(type, payload) {
|
|
93
|
+
const event = {
|
|
94
|
+
type,
|
|
95
|
+
timestamp: Date.now(),
|
|
96
|
+
payload,
|
|
97
|
+
id: `evt-${++this.eventCounter}`,
|
|
98
|
+
};
|
|
99
|
+
// Update stats
|
|
100
|
+
this._totalEmitted++;
|
|
101
|
+
this._byType.set(type, (this._byType.get(type) ?? 0) + 1);
|
|
102
|
+
// Notify callbacks (fire-and-forget)
|
|
103
|
+
const callbacks = this.callbacks.get(type);
|
|
104
|
+
if (callbacks) {
|
|
105
|
+
for (const cb of callbacks) {
|
|
106
|
+
try {
|
|
107
|
+
cb(event);
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
// Silently ignore callback errors
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
// Notify subscribers
|
|
115
|
+
for (const sub of this.subscribers) {
|
|
116
|
+
if (sub.events === null || sub.events.includes(type)) {
|
|
117
|
+
sub.push(event);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// Dispatch webhooks (async, fire-and-forget)
|
|
121
|
+
for (const [, webhook] of this.webhooks) {
|
|
122
|
+
if (!webhook.events || webhook.events.length === 0 || webhook.events.includes(type)) {
|
|
123
|
+
this.dispatchWebhook(webhook, event).catch(() => {
|
|
124
|
+
// Silently ignore webhook errors
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
get stats() {
|
|
130
|
+
const byType = {};
|
|
131
|
+
for (const [type, count] of this._byType) {
|
|
132
|
+
byType[type] = count;
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
totalEmitted: this._totalEmitted,
|
|
136
|
+
byType: byType,
|
|
137
|
+
activeCallbacks: Array.from(this.callbacks.values()).reduce((sum, set) => sum + set.size, 0),
|
|
138
|
+
activeSubscribers: this.subscribers.length,
|
|
139
|
+
activeWebhooks: this.webhooks.size,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
// ─── Private Methods ─────────────────────────────────────────────────────────
|
|
143
|
+
async dispatchWebhook(webhook, event) {
|
|
144
|
+
const body = JSON.stringify(event);
|
|
145
|
+
const headers = {
|
|
146
|
+
'Content-Type': 'application/json',
|
|
147
|
+
'User-Agent': 'Tracehound/1.0',
|
|
148
|
+
...webhook.headers,
|
|
149
|
+
};
|
|
150
|
+
// Add HMAC signature if secret provided
|
|
151
|
+
if (webhook.secret) {
|
|
152
|
+
const { createHmac } = await import('node:crypto');
|
|
153
|
+
const signature = createHmac('sha256', webhook.secret).update(body).digest('hex');
|
|
154
|
+
headers['X-Tracehound-Signature'] = `sha256=${signature}`;
|
|
155
|
+
}
|
|
156
|
+
const maxAttempts = webhook.retry?.maxAttempts ?? 3;
|
|
157
|
+
const delayMs = webhook.retry?.delayMs ?? 1000;
|
|
158
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
159
|
+
try {
|
|
160
|
+
const response = await fetch(webhook.url, {
|
|
161
|
+
method: 'POST',
|
|
162
|
+
headers,
|
|
163
|
+
body,
|
|
164
|
+
});
|
|
165
|
+
if (response.ok) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
// Retry on 5xx errors
|
|
169
|
+
if (response.status >= 500 && attempt < maxAttempts) {
|
|
170
|
+
await this.sleep(delayMs * attempt);
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
return; // Non-retryable error, give up silently
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
if (attempt < maxAttempts) {
|
|
177
|
+
await this.sleep(delayMs * attempt);
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
// Max attempts reached, give up silently
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
sleep(ms) {
|
|
185
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
189
|
+
// Factory
|
|
190
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
191
|
+
/**
|
|
192
|
+
* Create a Notification Emitter instance.
|
|
193
|
+
*/
|
|
194
|
+
export function createNotificationEmitter() {
|
|
195
|
+
return new NotificationEmitter();
|
|
196
|
+
}
|
|
197
|
+
//# sourceMappingURL=notification-emitter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notification-emitter.js","sourceRoot":"","sources":["../../src/core/notification-emitter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hound Process Adapter - Platform-agnostic child process management.
|
|
3
|
+
*
|
|
4
|
+
* RFC-0000 REQUIREMENTS:
|
|
5
|
+
* - OS-level memory isolation
|
|
6
|
+
* - Independent crash domain
|
|
7
|
+
* - SIGKILL on kill()
|
|
8
|
+
* - No fork-specific semantics (spawn + pipe baseline)
|
|
9
|
+
*
|
|
10
|
+
* PLATFORM SUPPORT:
|
|
11
|
+
*
|
|
12
|
+
* | Feature | Linux/macOS | Windows |
|
|
13
|
+
* |----------------------|-------------|------------------|
|
|
14
|
+
* | Memory limit (V8) | ✓ Enforced | ✓ Enforced |
|
|
15
|
+
* | SIGKILL | ✓ Native | ✓ TerminateProcess |
|
|
16
|
+
* | CPU limit | ✗ N/A | ✗ N/A |
|
|
17
|
+
* | Network isolation | Declared | Declared |
|
|
18
|
+
* | Filesystem isolation | Declared | Declared |
|
|
19
|
+
*
|
|
20
|
+
* WINDOWS LIMITATIONS:
|
|
21
|
+
* - Process constraints (network, filesystem, child spawn) are DECLARATIVE ONLY
|
|
22
|
+
* - No OS-level enforcement without external tools (Job Objects, containers)
|
|
23
|
+
* - Production recommendation: Use WSL2 or Docker for full isolation
|
|
24
|
+
*
|
|
25
|
+
* SECURITY MODEL:
|
|
26
|
+
* - Constraints are defense-in-depth, not security boundaries
|
|
27
|
+
* - Core security relies on: process separation, SIGKILL, timeout
|
|
28
|
+
* - Do NOT rely on constraint enforcement for security-critical isolation
|
|
29
|
+
*/
|
|
30
|
+
import { ChildProcess } from 'node:child_process';
|
|
31
|
+
import { type MessageParser } from './hound-ipc.js';
|
|
32
|
+
/**
|
|
33
|
+
* Handle to a spawned Hound process.
|
|
34
|
+
*/
|
|
35
|
+
export interface HoundHandle {
|
|
36
|
+
/** OS process ID */
|
|
37
|
+
readonly pid: number;
|
|
38
|
+
/** Internal process reference (not exposed) */
|
|
39
|
+
readonly _process: ChildProcess;
|
|
40
|
+
/** Message parser for this process */
|
|
41
|
+
readonly _parser: MessageParser;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Process exit callback.
|
|
45
|
+
*/
|
|
46
|
+
export type ExitCallback = (code: number | null, signal: string | null) => void;
|
|
47
|
+
/**
|
|
48
|
+
* Message callback.
|
|
49
|
+
*/
|
|
50
|
+
export type MessageCallback = (payload: ArrayBuffer) => void;
|
|
51
|
+
/**
|
|
52
|
+
* Process constraints (declarative, best-effort).
|
|
53
|
+
*
|
|
54
|
+
* NOTE: These are DECLARATIVE INTENT, not enforced guarantees.
|
|
55
|
+
* Platform-dependent. No security correctness depends on enforcement.
|
|
56
|
+
*/
|
|
57
|
+
export interface HoundProcessConstraints {
|
|
58
|
+
/** Max memory in MB (Linux: ulimit -v) */
|
|
59
|
+
maxMemoryMB?: number;
|
|
60
|
+
/** Network access (declared, not enforced in JS) */
|
|
61
|
+
networkAccess: false;
|
|
62
|
+
/** File system write (declared, not enforced in JS) */
|
|
63
|
+
fileSystemWrite: false;
|
|
64
|
+
/** Child process spawn (declared, not enforced in JS) */
|
|
65
|
+
childSpawn: false;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Default constraints (read-only).
|
|
69
|
+
*/
|
|
70
|
+
export declare const DEFAULT_CONSTRAINTS: Readonly<HoundProcessConstraints>;
|
|
71
|
+
/**
|
|
72
|
+
* Hound Process Adapter interface.
|
|
73
|
+
*
|
|
74
|
+
* Abstracts child process management for:
|
|
75
|
+
* - Current: Node.js child_process
|
|
76
|
+
* - Future: WASM sandbox (same interface)
|
|
77
|
+
*/
|
|
78
|
+
export interface IHoundProcessAdapter {
|
|
79
|
+
/**
|
|
80
|
+
* Spawn a new Hound process.
|
|
81
|
+
*
|
|
82
|
+
* @param scriptPath - Path to Hound process script
|
|
83
|
+
* @param constraints - Process constraints (declarative)
|
|
84
|
+
* @returns Handle to spawned process
|
|
85
|
+
*/
|
|
86
|
+
spawn(scriptPath: string, constraints?: Partial<HoundProcessConstraints>): HoundHandle;
|
|
87
|
+
/**
|
|
88
|
+
* Send binary payload to process.
|
|
89
|
+
*
|
|
90
|
+
* @param handle - Process handle
|
|
91
|
+
* @param payload - Binary payload
|
|
92
|
+
*/
|
|
93
|
+
send(handle: HoundHandle, payload: ArrayBuffer): void;
|
|
94
|
+
/**
|
|
95
|
+
* Kill process immediately (SIGKILL).
|
|
96
|
+
*
|
|
97
|
+
* @param handle - Process handle
|
|
98
|
+
*/
|
|
99
|
+
kill(handle: HoundHandle): void;
|
|
100
|
+
/**
|
|
101
|
+
* Register exit callback.
|
|
102
|
+
*
|
|
103
|
+
* @param handle - Process handle
|
|
104
|
+
* @param callback - Exit callback
|
|
105
|
+
*/
|
|
106
|
+
onExit(handle: HoundHandle, callback: ExitCallback): void;
|
|
107
|
+
/**
|
|
108
|
+
* Register message callback.
|
|
109
|
+
*
|
|
110
|
+
* @param handle - Process handle
|
|
111
|
+
* @param callback - Message callback
|
|
112
|
+
*/
|
|
113
|
+
onMessage(handle: HoundHandle, callback: MessageCallback): void;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Create a Hound Process Adapter.
|
|
117
|
+
*
|
|
118
|
+
* @returns Process adapter instance
|
|
119
|
+
*/
|
|
120
|
+
export declare function createProcessAdapter(): IHoundProcessAdapter;
|
|
121
|
+
/**
|
|
122
|
+
* Mock process state for testing.
|
|
123
|
+
*/
|
|
124
|
+
interface MockProcessState {
|
|
125
|
+
pid: number;
|
|
126
|
+
alive: boolean;
|
|
127
|
+
exitCallbacks: ExitCallback[];
|
|
128
|
+
messageCallbacks: MessageCallback[];
|
|
129
|
+
receivedPayloads: ArrayBuffer[];
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Create a mock adapter for testing.
|
|
133
|
+
* Does not spawn real processes.
|
|
134
|
+
*
|
|
135
|
+
* @returns Mock adapter with test utilities
|
|
136
|
+
*/
|
|
137
|
+
export declare function createMockAdapter(): IHoundProcessAdapter & {
|
|
138
|
+
/** Get all mock processes */
|
|
139
|
+
getMockProcesses(): Map<number, MockProcessState>;
|
|
140
|
+
/** Simulate process exit */
|
|
141
|
+
simulateExit(pid: number, code: number | null): void;
|
|
142
|
+
/** Simulate message from process */
|
|
143
|
+
simulateMessage(pid: number, payload: ArrayBuffer): void;
|
|
144
|
+
};
|
|
145
|
+
export {};
|
|
146
|
+
//# sourceMappingURL=process-adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"process-adapter.d.ts","sourceRoot":"","sources":["../../src/core/process-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,EAAE,YAAY,EAAS,MAAM,oBAAoB,CAAA;AACxD,OAAO,EAAsC,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAMvF;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,oBAAoB;IACpB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,+CAA+C;IAC/C,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAA;IAC/B,sCAAsC;IACtC,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAA;CAChC;AAED;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAA;AAE/E;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAA;AAE5D;;;;;GAKG;AACH,MAAM,WAAW,uBAAuB;IACtC,0CAA0C;IAC1C,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,oDAAoD;IACpD,aAAa,EAAE,KAAK,CAAA;IACpB,uDAAuD;IACvD,eAAe,EAAE,KAAK,CAAA;IACtB,yDAAyD;IACzD,UAAU,EAAE,KAAK,CAAA;CAClB;AAED;;GAEG;AACH,eAAO,MAAM,mBAAmB,EAAE,QAAQ,CAAC,uBAAuB,CAKhE,CAAA;AAEF;;;;;;GAMG;AACH,MAAM,WAAW,oBAAoB;IACnC;;;;;;OAMG;IACH,KAAK,CAAC,UAAU,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,OAAO,CAAC,uBAAuB,CAAC,GAAG,WAAW,CAAA;IAEtF;;;;;OAKG;IACH,IAAI,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,WAAW,GAAG,IAAI,CAAA;IAErD;;;;OAIG;IACH,IAAI,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,CAAA;IAE/B;;;;;OAKG;IACH,MAAM,CAAC,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,YAAY,GAAG,IAAI,CAAA;IAEzD;;;;;OAKG;IACH,SAAS,CAAC,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,eAAe,GAAG,IAAI,CAAA;CAChE;AAMD;;;;GAIG;AACH,wBAAgB,oBAAoB,IAAI,oBAAoB,CA+D3D;AAMD;;GAEG;AACH,UAAU,gBAAgB;IACxB,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,OAAO,CAAA;IACd,aAAa,EAAE,YAAY,EAAE,CAAA;IAC7B,gBAAgB,EAAE,eAAe,EAAE,CAAA;IACnC,gBAAgB,EAAE,WAAW,EAAE,CAAA;CAChC;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,IAAI,oBAAoB,GAAG;IAC1D,6BAA6B;IAC7B,gBAAgB,IAAI,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAA;IACjD,4BAA4B;IAC5B,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAAA;IACpD,oCAAoC;IACpC,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,GAAG,IAAI,CAAA;CACzD,CA+EA"}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hound Process Adapter - Platform-agnostic child process management.
|
|
3
|
+
*
|
|
4
|
+
* RFC-0000 REQUIREMENTS:
|
|
5
|
+
* - OS-level memory isolation
|
|
6
|
+
* - Independent crash domain
|
|
7
|
+
* - SIGKILL on kill()
|
|
8
|
+
* - No fork-specific semantics (spawn + pipe baseline)
|
|
9
|
+
*
|
|
10
|
+
* PLATFORM SUPPORT:
|
|
11
|
+
*
|
|
12
|
+
* | Feature | Linux/macOS | Windows |
|
|
13
|
+
* |----------------------|-------------|------------------|
|
|
14
|
+
* | Memory limit (V8) | ✓ Enforced | ✓ Enforced |
|
|
15
|
+
* | SIGKILL | ✓ Native | ✓ TerminateProcess |
|
|
16
|
+
* | CPU limit | ✗ N/A | ✗ N/A |
|
|
17
|
+
* | Network isolation | Declared | Declared |
|
|
18
|
+
* | Filesystem isolation | Declared | Declared |
|
|
19
|
+
*
|
|
20
|
+
* WINDOWS LIMITATIONS:
|
|
21
|
+
* - Process constraints (network, filesystem, child spawn) are DECLARATIVE ONLY
|
|
22
|
+
* - No OS-level enforcement without external tools (Job Objects, containers)
|
|
23
|
+
* - Production recommendation: Use WSL2 or Docker for full isolation
|
|
24
|
+
*
|
|
25
|
+
* SECURITY MODEL:
|
|
26
|
+
* - Constraints are defense-in-depth, not security boundaries
|
|
27
|
+
* - Core security relies on: process separation, SIGKILL, timeout
|
|
28
|
+
* - Do NOT rely on constraint enforcement for security-critical isolation
|
|
29
|
+
*/
|
|
30
|
+
import { ChildProcess, spawn } from 'node:child_process';
|
|
31
|
+
import { createMessageParser, encodeMessage } from './hound-ipc.js';
|
|
32
|
+
/**
|
|
33
|
+
* Default constraints (read-only).
|
|
34
|
+
*/
|
|
35
|
+
export const DEFAULT_CONSTRAINTS = Object.freeze({
|
|
36
|
+
maxMemoryMB: 128,
|
|
37
|
+
networkAccess: false,
|
|
38
|
+
fileSystemWrite: false,
|
|
39
|
+
childSpawn: false,
|
|
40
|
+
});
|
|
41
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
42
|
+
// Implementation
|
|
43
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
44
|
+
/**
|
|
45
|
+
* Create a Hound Process Adapter.
|
|
46
|
+
*
|
|
47
|
+
* @returns Process adapter instance
|
|
48
|
+
*/
|
|
49
|
+
export function createProcessAdapter() {
|
|
50
|
+
return {
|
|
51
|
+
spawn(scriptPath, constraints) {
|
|
52
|
+
const mergedConstraints = { ...DEFAULT_CONSTRAINTS, ...constraints };
|
|
53
|
+
// Build spawn options
|
|
54
|
+
const execArgv = [];
|
|
55
|
+
// Memory limit (V8 heap)
|
|
56
|
+
if (mergedConstraints.maxMemoryMB) {
|
|
57
|
+
execArgv.push(`--max-old-space-size=${mergedConstraints.maxMemoryMB}`);
|
|
58
|
+
}
|
|
59
|
+
// Security flags
|
|
60
|
+
execArgv.push('--disable-proto=throw');
|
|
61
|
+
execArgv.push('--disallow-code-generation-from-strings');
|
|
62
|
+
// Spawn child process
|
|
63
|
+
// NOTE: Using spawn (not fork) for Windows compatibility
|
|
64
|
+
const child = spawn(process.execPath, [...execArgv, scriptPath], {
|
|
65
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
66
|
+
detached: false,
|
|
67
|
+
windowsHide: true,
|
|
68
|
+
});
|
|
69
|
+
if (!child.pid) {
|
|
70
|
+
throw new Error('Failed to spawn Hound process');
|
|
71
|
+
}
|
|
72
|
+
const parser = createMessageParser();
|
|
73
|
+
return {
|
|
74
|
+
pid: child.pid,
|
|
75
|
+
_process: child,
|
|
76
|
+
_parser: parser,
|
|
77
|
+
};
|
|
78
|
+
},
|
|
79
|
+
send(handle, payload) {
|
|
80
|
+
const message = encodeMessage(payload);
|
|
81
|
+
handle._process.stdin?.write(message);
|
|
82
|
+
},
|
|
83
|
+
kill(handle) {
|
|
84
|
+
// SIGKILL for immediate termination
|
|
85
|
+
handle._process.kill('SIGKILL');
|
|
86
|
+
},
|
|
87
|
+
onExit(handle, callback) {
|
|
88
|
+
handle._process.on('exit', (code, signal) => {
|
|
89
|
+
callback(code, signal);
|
|
90
|
+
});
|
|
91
|
+
},
|
|
92
|
+
onMessage(handle, callback) {
|
|
93
|
+
handle._process.stdout?.on('data', (chunk) => {
|
|
94
|
+
const messages = handle._parser.feed(chunk);
|
|
95
|
+
for (const payload of messages) {
|
|
96
|
+
callback(payload);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Create a mock adapter for testing.
|
|
104
|
+
* Does not spawn real processes.
|
|
105
|
+
*
|
|
106
|
+
* @returns Mock adapter with test utilities
|
|
107
|
+
*/
|
|
108
|
+
export function createMockAdapter() {
|
|
109
|
+
const processes = new Map();
|
|
110
|
+
let nextPid = 1000;
|
|
111
|
+
const mockAdapter = {
|
|
112
|
+
spawn() {
|
|
113
|
+
const pid = nextPid++;
|
|
114
|
+
const state = {
|
|
115
|
+
pid,
|
|
116
|
+
alive: true,
|
|
117
|
+
exitCallbacks: [],
|
|
118
|
+
messageCallbacks: [],
|
|
119
|
+
receivedPayloads: [],
|
|
120
|
+
};
|
|
121
|
+
processes.set(pid, state);
|
|
122
|
+
return {
|
|
123
|
+
pid,
|
|
124
|
+
_process: { pid },
|
|
125
|
+
_parser: createMessageParser(),
|
|
126
|
+
};
|
|
127
|
+
},
|
|
128
|
+
send(handle, payload) {
|
|
129
|
+
const state = processes.get(handle.pid);
|
|
130
|
+
if (state?.alive) {
|
|
131
|
+
state.receivedPayloads.push(payload);
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
kill(handle) {
|
|
135
|
+
const state = processes.get(handle.pid);
|
|
136
|
+
if (state?.alive) {
|
|
137
|
+
state.alive = false;
|
|
138
|
+
for (const cb of state.exitCallbacks) {
|
|
139
|
+
cb(null, 'SIGKILL');
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
onExit(handle, callback) {
|
|
144
|
+
const state = processes.get(handle.pid);
|
|
145
|
+
state?.exitCallbacks.push(callback);
|
|
146
|
+
},
|
|
147
|
+
onMessage(handle, callback) {
|
|
148
|
+
const state = processes.get(handle.pid);
|
|
149
|
+
state?.messageCallbacks.push(callback);
|
|
150
|
+
},
|
|
151
|
+
getMockProcesses() {
|
|
152
|
+
return processes;
|
|
153
|
+
},
|
|
154
|
+
simulateExit(pid, code) {
|
|
155
|
+
const state = processes.get(pid);
|
|
156
|
+
if (state?.alive) {
|
|
157
|
+
state.alive = false;
|
|
158
|
+
for (const cb of state.exitCallbacks) {
|
|
159
|
+
cb(code, null);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
simulateMessage(pid, payload) {
|
|
164
|
+
const state = processes.get(pid);
|
|
165
|
+
if (state?.alive) {
|
|
166
|
+
for (const cb of state.messageCallbacks) {
|
|
167
|
+
cb(payload);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
};
|
|
172
|
+
return mockAdapter;
|
|
173
|
+
}
|
|
174
|
+
//# sourceMappingURL=process-adapter.js.map
|