@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,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* S3-Compatible Cold Storage Adapter
|
|
3
|
+
*
|
|
4
|
+
* Implements IColdStorageAdapter for any S3-compatible object store:
|
|
5
|
+
* AWS S3, Cloudflare R2, Google Cloud Storage (S3-compat), MinIO, etc.
|
|
6
|
+
*
|
|
7
|
+
* DESIGN:
|
|
8
|
+
* - Zero AWS SDK dependency. Client is injected via S3LikeClient interface.
|
|
9
|
+
* - Binary envelope format: self-contained, no sidecar files needed.
|
|
10
|
+
* - Async encode/decode via Phase 4 Async Codec.
|
|
11
|
+
*
|
|
12
|
+
* RFC-0000 INVARIANTS:
|
|
13
|
+
* - write() is fire-and-forget (caller does not wait for confirmation)
|
|
14
|
+
* - Adapter errors are caught and returned, never thrown
|
|
15
|
+
* - Payload integrity is preserved via binary envelope with embedded hash
|
|
16
|
+
*/
|
|
17
|
+
import type { EncodedPayload } from '../utils/binary-codec.js';
|
|
18
|
+
import type { ColdStorageReadResult, ColdStorageWriteResult, IColdStorageAdapter } from './cold-storage.js';
|
|
19
|
+
/**
|
|
20
|
+
* Minimal S3-compatible client interface.
|
|
21
|
+
*
|
|
22
|
+
* Users provide their own implementation using @aws-sdk/client-s3,
|
|
23
|
+
* Cloudflare R2 bindings, MinIO client, or any S3-compatible SDK.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* // AWS S3
|
|
27
|
+
* import { S3Client, PutObjectCommand, GetObjectCommand, DeleteObjectCommand, HeadBucketCommand } from '@aws-sdk/client-s3'
|
|
28
|
+
* const s3 = new S3Client({ region: 'us-east-1' })
|
|
29
|
+
* const client: S3LikeClient = {
|
|
30
|
+
* putObject: (p) => s3.send(new PutObjectCommand(p)).then(() => {}),
|
|
31
|
+
* getObject: (p) => s3.send(new GetObjectCommand(p)).then(r => ({
|
|
32
|
+
* Body: new Uint8Array(await r.Body!.transformToByteArray())
|
|
33
|
+
* })),
|
|
34
|
+
* deleteObject: (p) => s3.send(new DeleteObjectCommand(p)).then(() => {}),
|
|
35
|
+
* headBucket: (p) => s3.send(new HeadBucketCommand(p)).then(() => {}),
|
|
36
|
+
* }
|
|
37
|
+
*/
|
|
38
|
+
export interface S3LikeClient {
|
|
39
|
+
putObject(params: {
|
|
40
|
+
Bucket: string;
|
|
41
|
+
Key: string;
|
|
42
|
+
Body: Uint8Array;
|
|
43
|
+
ContentType?: string;
|
|
44
|
+
}): Promise<void>;
|
|
45
|
+
getObject(params: {
|
|
46
|
+
Bucket: string;
|
|
47
|
+
Key: string;
|
|
48
|
+
}): Promise<{
|
|
49
|
+
Body: Uint8Array;
|
|
50
|
+
}>;
|
|
51
|
+
deleteObject(params: {
|
|
52
|
+
Bucket: string;
|
|
53
|
+
Key: string;
|
|
54
|
+
}): Promise<void>;
|
|
55
|
+
headBucket(params: {
|
|
56
|
+
Bucket: string;
|
|
57
|
+
}): Promise<void>;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* S3 cold storage configuration.
|
|
61
|
+
*/
|
|
62
|
+
export interface S3ColdStorageConfig {
|
|
63
|
+
/** Pre-configured S3-compatible client (injected, not owned) */
|
|
64
|
+
client: S3LikeClient;
|
|
65
|
+
/** Target bucket name */
|
|
66
|
+
bucket: string;
|
|
67
|
+
/** Key prefix for all evidence objects (e.g., 'tracehound/evidence/') */
|
|
68
|
+
prefix?: string;
|
|
69
|
+
}
|
|
70
|
+
declare const HEADER_SIZE = 78;
|
|
71
|
+
/**
|
|
72
|
+
* Pack EncodedPayload into a self-contained binary envelope.
|
|
73
|
+
* Used for storage — all metadata is embedded, no sidecar needed.
|
|
74
|
+
*/
|
|
75
|
+
declare function packEnvelope(payload: EncodedPayload): Uint8Array;
|
|
76
|
+
/**
|
|
77
|
+
* Unpack binary envelope back to EncodedPayload.
|
|
78
|
+
* Returns null if envelope is invalid (wrong magic, version, or size mismatch).
|
|
79
|
+
*/
|
|
80
|
+
declare function unpackEnvelope(data: Uint8Array): EncodedPayload | null;
|
|
81
|
+
/**
|
|
82
|
+
* S3-compatible cold storage adapter.
|
|
83
|
+
*
|
|
84
|
+
* Stores evidence as binary envelopes in any S3-compatible object store.
|
|
85
|
+
* Client is injected — no AWS SDK dependency in core.
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* const adapter = new S3ColdStorage({
|
|
89
|
+
* client: myS3Client,
|
|
90
|
+
* bucket: 'tracehound-evidence',
|
|
91
|
+
* prefix: 'prod/evidence/',
|
|
92
|
+
* })
|
|
93
|
+
*/
|
|
94
|
+
export declare class S3ColdStorage implements IColdStorageAdapter {
|
|
95
|
+
private readonly client;
|
|
96
|
+
private readonly bucket;
|
|
97
|
+
private readonly prefix;
|
|
98
|
+
constructor(config: S3ColdStorageConfig);
|
|
99
|
+
/**
|
|
100
|
+
* Resolve storage key for an evidence ID.
|
|
101
|
+
*/
|
|
102
|
+
private key;
|
|
103
|
+
write(id: string, payload: EncodedPayload): Promise<ColdStorageWriteResult>;
|
|
104
|
+
read(id: string): Promise<ColdStorageReadResult>;
|
|
105
|
+
delete(id: string): Promise<boolean>;
|
|
106
|
+
isAvailable(): Promise<boolean>;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Create an S3-compatible cold storage adapter.
|
|
110
|
+
*
|
|
111
|
+
* @param config - S3 client, bucket, and optional key prefix
|
|
112
|
+
* @returns IColdStorageAdapter backed by S3-compatible storage
|
|
113
|
+
*/
|
|
114
|
+
export declare function createS3ColdStorage(config: S3ColdStorageConfig): IColdStorageAdapter;
|
|
115
|
+
export { HEADER_SIZE, packEnvelope, unpackEnvelope };
|
|
116
|
+
//# sourceMappingURL=s3-cold-storage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"s3-cold-storage.d.ts","sourceRoot":"","sources":["../../src/core/s3-cold-storage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAA;AAC9D,OAAO,KAAK,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAA;AAM3G;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,WAAW,YAAY;IAC3B,SAAS,CAAC,MAAM,EAAE;QAChB,MAAM,EAAE,MAAM,CAAA;QACd,GAAG,EAAE,MAAM,CAAA;QACX,IAAI,EAAE,UAAU,CAAA;QAChB,WAAW,CAAC,EAAE,MAAM,CAAA;KACrB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAEjB,SAAS,CAAC,MAAM,EAAE;QAChB,MAAM,EAAE,MAAM,CAAA;QACd,GAAG,EAAE,MAAM,CAAA;KACZ,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,UAAU,CAAA;KAAE,CAAC,CAAA;IAEjC,YAAY,CAAC,MAAM,EAAE;QACnB,MAAM,EAAE,MAAM,CAAA;QACd,GAAG,EAAE,MAAM,CAAA;KACZ,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAEjB,UAAU,CAAC,MAAM,EAAE;QACjB,MAAM,EAAE,MAAM,CAAA;KACf,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAClB;AAMD;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,gEAAgE;IAChE,MAAM,EAAE,YAAY,CAAA;IACpB,yBAAyB;IACzB,MAAM,EAAE,MAAM,CAAA;IACd,yEAAyE;IACzE,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAoBD,QAAA,MAAM,WAAW,KAAK,CAAA;AAEtB;;;GAGG;AACH,iBAAS,YAAY,CAAC,OAAO,EAAE,cAAc,GAAG,UAAU,CAwBzD;AAED;;;GAGG;AACH,iBAAS,cAAc,CAAC,IAAI,EAAE,UAAU,GAAG,cAAc,GAAG,IAAI,CAyC/D;AAMD;;;;;;;;;;;;GAYG;AACH,qBAAa,aAAc,YAAW,mBAAmB;IACvD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAc;IACrC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAQ;IAC/B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAQ;gBAEnB,MAAM,EAAE,mBAAmB;IAMvC;;OAEG;IACH,OAAO,CAAC,GAAG;IAIL,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,sBAAsB,CAAC;IAmB3E,IAAI,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC;IAqBhD,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAepC,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;CAQtC;AAMD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,mBAAmB,GAAG,mBAAmB,CAEpF;AAGD,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,cAAc,EAAE,CAAA"}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* S3-Compatible Cold Storage Adapter
|
|
3
|
+
*
|
|
4
|
+
* Implements IColdStorageAdapter for any S3-compatible object store:
|
|
5
|
+
* AWS S3, Cloudflare R2, Google Cloud Storage (S3-compat), MinIO, etc.
|
|
6
|
+
*
|
|
7
|
+
* DESIGN:
|
|
8
|
+
* - Zero AWS SDK dependency. Client is injected via S3LikeClient interface.
|
|
9
|
+
* - Binary envelope format: self-contained, no sidecar files needed.
|
|
10
|
+
* - Async encode/decode via Phase 4 Async Codec.
|
|
11
|
+
*
|
|
12
|
+
* RFC-0000 INVARIANTS:
|
|
13
|
+
* - write() is fire-and-forget (caller does not wait for confirmation)
|
|
14
|
+
* - Adapter errors are caught and returned, never thrown
|
|
15
|
+
* - Payload integrity is preserved via binary envelope with embedded hash
|
|
16
|
+
*/
|
|
17
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
18
|
+
// Binary Envelope Format
|
|
19
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
20
|
+
//
|
|
21
|
+
// Offset Size Description
|
|
22
|
+
// ────── ──── ────────────────────────
|
|
23
|
+
// 0 4 Magic: 0x54 0x48 0x43 0x53 ("THCS")
|
|
24
|
+
// 4 2 Version: 0x00 0x01
|
|
25
|
+
// 6 4 originalSize (uint32 BE)
|
|
26
|
+
// 10 4 compressedSize (uint32 BE)
|
|
27
|
+
// 14 64 SHA-256 hash (hex ASCII, 64 bytes)
|
|
28
|
+
// 78 N compressed gzip data
|
|
29
|
+
//
|
|
30
|
+
// Total header: 78 bytes
|
|
31
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
32
|
+
const ENVELOPE_MAGIC = new Uint8Array([0x54, 0x48, 0x43, 0x53]); // "THCS"
|
|
33
|
+
const ENVELOPE_VERSION = 1;
|
|
34
|
+
const HEADER_SIZE = 78;
|
|
35
|
+
/**
|
|
36
|
+
* Pack EncodedPayload into a self-contained binary envelope.
|
|
37
|
+
* Used for storage — all metadata is embedded, no sidecar needed.
|
|
38
|
+
*/
|
|
39
|
+
function packEnvelope(payload) {
|
|
40
|
+
const envelope = new Uint8Array(HEADER_SIZE + payload.compressedSize);
|
|
41
|
+
const view = new DataView(envelope.buffer);
|
|
42
|
+
// Magic
|
|
43
|
+
envelope.set(ENVELOPE_MAGIC, 0);
|
|
44
|
+
// Version
|
|
45
|
+
view.setUint16(4, ENVELOPE_VERSION, false); // big-endian
|
|
46
|
+
// originalSize
|
|
47
|
+
view.setUint32(6, payload.originalSize, false);
|
|
48
|
+
// compressedSize
|
|
49
|
+
view.setUint32(10, payload.compressedSize, false);
|
|
50
|
+
// Hash (64 ASCII hex chars)
|
|
51
|
+
const hashBytes = new TextEncoder().encode(payload.hash);
|
|
52
|
+
envelope.set(hashBytes, 14);
|
|
53
|
+
// Compressed data
|
|
54
|
+
envelope.set(payload.compressed, HEADER_SIZE);
|
|
55
|
+
return envelope;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Unpack binary envelope back to EncodedPayload.
|
|
59
|
+
* Returns null if envelope is invalid (wrong magic, version, or size mismatch).
|
|
60
|
+
*/
|
|
61
|
+
function unpackEnvelope(data) {
|
|
62
|
+
if (data.length < HEADER_SIZE) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
// Verify magic
|
|
66
|
+
for (let i = 0; i < 4; i++) {
|
|
67
|
+
if (data[i] !== ENVELOPE_MAGIC[i]) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
72
|
+
// Verify version
|
|
73
|
+
const version = view.getUint16(4, false);
|
|
74
|
+
if (version !== ENVELOPE_VERSION) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
const originalSize = view.getUint32(6, false);
|
|
78
|
+
const compressedSize = view.getUint32(10, false);
|
|
79
|
+
// Verify data length matches
|
|
80
|
+
if (data.length !== HEADER_SIZE + compressedSize) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
// Extract hash
|
|
84
|
+
const hashBytes = data.slice(14, 78);
|
|
85
|
+
const hash = new TextDecoder().decode(hashBytes);
|
|
86
|
+
// Extract compressed data
|
|
87
|
+
const compressed = data.slice(HEADER_SIZE, HEADER_SIZE + compressedSize);
|
|
88
|
+
return {
|
|
89
|
+
compressed,
|
|
90
|
+
hash,
|
|
91
|
+
originalSize,
|
|
92
|
+
compressedSize,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
96
|
+
// S3 Cold Storage Adapter
|
|
97
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
98
|
+
/**
|
|
99
|
+
* S3-compatible cold storage adapter.
|
|
100
|
+
*
|
|
101
|
+
* Stores evidence as binary envelopes in any S3-compatible object store.
|
|
102
|
+
* Client is injected — no AWS SDK dependency in core.
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* const adapter = new S3ColdStorage({
|
|
106
|
+
* client: myS3Client,
|
|
107
|
+
* bucket: 'tracehound-evidence',
|
|
108
|
+
* prefix: 'prod/evidence/',
|
|
109
|
+
* })
|
|
110
|
+
*/
|
|
111
|
+
export class S3ColdStorage {
|
|
112
|
+
client;
|
|
113
|
+
bucket;
|
|
114
|
+
prefix;
|
|
115
|
+
constructor(config) {
|
|
116
|
+
this.client = config.client;
|
|
117
|
+
this.bucket = config.bucket;
|
|
118
|
+
this.prefix = config.prefix ?? 'tracehound/evidence/';
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Resolve storage key for an evidence ID.
|
|
122
|
+
*/
|
|
123
|
+
key(id) {
|
|
124
|
+
return `${this.prefix}${id}.thcs`;
|
|
125
|
+
}
|
|
126
|
+
async write(id, payload) {
|
|
127
|
+
try {
|
|
128
|
+
const envelope = packEnvelope(payload);
|
|
129
|
+
const key = this.key(id);
|
|
130
|
+
await this.client.putObject({
|
|
131
|
+
Bucket: this.bucket,
|
|
132
|
+
Key: key,
|
|
133
|
+
Body: envelope,
|
|
134
|
+
ContentType: 'application/octet-stream',
|
|
135
|
+
});
|
|
136
|
+
return { success: true, id: key };
|
|
137
|
+
}
|
|
138
|
+
catch (err) {
|
|
139
|
+
const message = err instanceof Error ? err.message : 'Unknown S3 write error';
|
|
140
|
+
return { success: false, error: message };
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
async read(id) {
|
|
144
|
+
try {
|
|
145
|
+
const key = this.key(id);
|
|
146
|
+
const response = await this.client.getObject({
|
|
147
|
+
Bucket: this.bucket,
|
|
148
|
+
Key: key,
|
|
149
|
+
});
|
|
150
|
+
const payload = unpackEnvelope(response.Body);
|
|
151
|
+
if (!payload) {
|
|
152
|
+
return { success: false, error: 'Invalid envelope format' };
|
|
153
|
+
}
|
|
154
|
+
return { success: true, payload };
|
|
155
|
+
}
|
|
156
|
+
catch (err) {
|
|
157
|
+
const message = err instanceof Error ? err.message : 'Unknown S3 read error';
|
|
158
|
+
return { success: false, error: message };
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
async delete(id) {
|
|
162
|
+
try {
|
|
163
|
+
const key = this.key(id);
|
|
164
|
+
await this.client.deleteObject({
|
|
165
|
+
Bucket: this.bucket,
|
|
166
|
+
Key: key,
|
|
167
|
+
});
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
async isAvailable() {
|
|
175
|
+
try {
|
|
176
|
+
await this.client.headBucket({ Bucket: this.bucket });
|
|
177
|
+
return true;
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
185
|
+
// Factory + Exports
|
|
186
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
187
|
+
/**
|
|
188
|
+
* Create an S3-compatible cold storage adapter.
|
|
189
|
+
*
|
|
190
|
+
* @param config - S3 client, bucket, and optional key prefix
|
|
191
|
+
* @returns IColdStorageAdapter backed by S3-compatible storage
|
|
192
|
+
*/
|
|
193
|
+
export function createS3ColdStorage(config) {
|
|
194
|
+
return new S3ColdStorage(config);
|
|
195
|
+
}
|
|
196
|
+
// Re-export envelope utilities for advanced use cases (custom adapters)
|
|
197
|
+
export { HEADER_SIZE, packEnvelope, unpackEnvelope };
|
|
198
|
+
//# sourceMappingURL=s3-cold-storage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"s3-cold-storage.js","sourceRoot":"","sources":["../../src/core/s3-cold-storage.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tick Scheduler - jittered task scheduling for background operations.
|
|
3
|
+
*
|
|
4
|
+
* RFC-0000 CRITICAL INVARIANTS:
|
|
5
|
+
* - skipIfBusy: true by default (prevents timing attacks)
|
|
6
|
+
* - Jitter applied to all ticks (prevents predictable patterns)
|
|
7
|
+
* - No blocking of hot-path
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Scheduled task definition.
|
|
11
|
+
*/
|
|
12
|
+
export interface ScheduledTask {
|
|
13
|
+
/** Unique task identifier */
|
|
14
|
+
id: string;
|
|
15
|
+
/** Task execution function */
|
|
16
|
+
execute: () => void | Promise<void>;
|
|
17
|
+
/** Interval between executions in ms */
|
|
18
|
+
intervalMs: number;
|
|
19
|
+
/** Priority (lower = higher priority) */
|
|
20
|
+
priority?: number;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Tick Scheduler configuration.
|
|
24
|
+
*/
|
|
25
|
+
export interface TickSchedulerConfig {
|
|
26
|
+
/** Base tick interval in ms */
|
|
27
|
+
tickInterval: number;
|
|
28
|
+
/** Maximum jitter range in ms (added to tick interval) */
|
|
29
|
+
jitterMs: number;
|
|
30
|
+
/**
|
|
31
|
+
* Skip tick if system is busy.
|
|
32
|
+
* DEFAULT: true (RFC requirement for timing attack prevention)
|
|
33
|
+
*/
|
|
34
|
+
skipIfBusy?: boolean;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Scheduler statistics (immutable snapshot).
|
|
38
|
+
*/
|
|
39
|
+
export interface SchedulerStats {
|
|
40
|
+
/** Total ticks executed */
|
|
41
|
+
totalTicks: number;
|
|
42
|
+
/** Total tasks executed */
|
|
43
|
+
totalTasksExecuted: number;
|
|
44
|
+
/** Total ticks skipped (due to busy) */
|
|
45
|
+
skippedTicks: number;
|
|
46
|
+
/** Number of scheduled tasks */
|
|
47
|
+
scheduledTasks: number;
|
|
48
|
+
/** Whether scheduler is running */
|
|
49
|
+
running: boolean;
|
|
50
|
+
/** Whether system is currently busy */
|
|
51
|
+
busy: boolean;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Busy check function type.
|
|
55
|
+
* Returns true if system is busy.
|
|
56
|
+
*/
|
|
57
|
+
export type BusyChecker = () => boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Scheduler interface.
|
|
60
|
+
*/
|
|
61
|
+
export interface IScheduler {
|
|
62
|
+
/**
|
|
63
|
+
* Start the scheduler.
|
|
64
|
+
*/
|
|
65
|
+
start(): void;
|
|
66
|
+
/**
|
|
67
|
+
* Stop the scheduler.
|
|
68
|
+
*/
|
|
69
|
+
stop(): void;
|
|
70
|
+
/**
|
|
71
|
+
* Schedule a task.
|
|
72
|
+
*
|
|
73
|
+
* @param task - Task definition
|
|
74
|
+
*/
|
|
75
|
+
schedule(task: ScheduledTask): void;
|
|
76
|
+
/**
|
|
77
|
+
* Unschedule a task by ID.
|
|
78
|
+
*
|
|
79
|
+
* @param taskId - Task ID to remove
|
|
80
|
+
*/
|
|
81
|
+
unschedule(taskId: string): void;
|
|
82
|
+
/**
|
|
83
|
+
* Set busy checker function.
|
|
84
|
+
* Used to determine if ticks should be skipped.
|
|
85
|
+
*
|
|
86
|
+
* @param checker - Function that returns true if busy
|
|
87
|
+
*/
|
|
88
|
+
setBusyChecker(checker: BusyChecker): void;
|
|
89
|
+
/**
|
|
90
|
+
* Get scheduler statistics (immutable snapshot).
|
|
91
|
+
*/
|
|
92
|
+
readonly stats: Readonly<SchedulerStats>;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Tick Scheduler implementation.
|
|
96
|
+
*/
|
|
97
|
+
export declare class Scheduler implements IScheduler {
|
|
98
|
+
private readonly config;
|
|
99
|
+
private readonly tasks;
|
|
100
|
+
private tickTimeoutId;
|
|
101
|
+
private busyChecker;
|
|
102
|
+
private _running;
|
|
103
|
+
private _busy;
|
|
104
|
+
private _totalTicks;
|
|
105
|
+
private _totalTasksExecuted;
|
|
106
|
+
private _skippedTicks;
|
|
107
|
+
private readonly skipIfBusy;
|
|
108
|
+
constructor(config: TickSchedulerConfig);
|
|
109
|
+
start(): void;
|
|
110
|
+
stop(): void;
|
|
111
|
+
schedule(task: ScheduledTask): void;
|
|
112
|
+
unschedule(taskId: string): void;
|
|
113
|
+
setBusyChecker(checker: BusyChecker): void;
|
|
114
|
+
get stats(): Readonly<SchedulerStats>;
|
|
115
|
+
private scheduleTick;
|
|
116
|
+
private executeTick;
|
|
117
|
+
private getDueTasks;
|
|
118
|
+
private executeTask;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Create a Scheduler instance.
|
|
122
|
+
*
|
|
123
|
+
* @param config - Scheduler configuration
|
|
124
|
+
*/
|
|
125
|
+
export declare function createScheduler(config: TickSchedulerConfig): IScheduler;
|
|
126
|
+
//# sourceMappingURL=scheduler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scheduler.d.ts","sourceRoot":"","sources":["../../src/core/scheduler.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,6BAA6B;IAC7B,EAAE,EAAE,MAAM,CAAA;IACV,8BAA8B;IAC9B,OAAO,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACnC,wCAAwC;IACxC,UAAU,EAAE,MAAM,CAAA;IAClB,yCAAyC;IACzC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,+BAA+B;IAC/B,YAAY,EAAE,MAAM,CAAA;IACpB,0DAA0D;IAC1D,QAAQ,EAAE,MAAM,CAAA;IAChB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,2BAA2B;IAC3B,UAAU,EAAE,MAAM,CAAA;IAClB,2BAA2B;IAC3B,kBAAkB,EAAE,MAAM,CAAA;IAC1B,wCAAwC;IACxC,YAAY,EAAE,MAAM,CAAA;IACpB,gCAAgC;IAChC,cAAc,EAAE,MAAM,CAAA;IACtB,mCAAmC;IACnC,OAAO,EAAE,OAAO,CAAA;IAChB,uCAAuC;IACvC,IAAI,EAAE,OAAO,CAAA;CACd;AAED;;;GAGG;AACH,MAAM,MAAM,WAAW,GAAG,MAAM,OAAO,CAAA;AAEvC;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB;;OAEG;IACH,KAAK,IAAI,IAAI,CAAA;IAEb;;OAEG;IACH,IAAI,IAAI,IAAI,CAAA;IAEZ;;;;OAIG;IACH,QAAQ,CAAC,IAAI,EAAE,aAAa,GAAG,IAAI,CAAA;IAEnC;;;;OAIG;IACH,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IAEhC;;;;;OAKG;IACH,cAAc,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,CAAA;IAE1C;;OAEG;IACH,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,cAAc,CAAC,CAAA;CACzC;AAcD;;GAEG;AACH,qBAAa,SAAU,YAAW,UAAU;IAe9B,OAAO,CAAC,QAAQ,CAAC,MAAM;IAdnC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAoC;IAC1D,OAAO,CAAC,aAAa,CAA6C;IAClE,OAAO,CAAC,WAAW,CAA2B;IAC9C,OAAO,CAAC,QAAQ,CAAQ;IACxB,OAAO,CAAC,KAAK,CAAQ;IAGrB,OAAO,CAAC,WAAW,CAAI;IACvB,OAAO,CAAC,mBAAmB,CAAI;IAC/B,OAAO,CAAC,aAAa,CAAI;IAGzB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;gBAEP,MAAM,EAAE,mBAAmB;IAKxD,KAAK,IAAI,IAAI;IAMb,IAAI,IAAI,IAAI;IAUZ,QAAQ,CAAC,IAAI,EAAE,aAAa,GAAG,IAAI;IAOnC,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAIhC,cAAc,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI;IAI1C,IAAI,KAAK,IAAI,QAAQ,CAAC,cAAc,CAAC,CASpC;IAID,OAAO,CAAC,YAAY;IAYpB,OAAO,CAAC,WAAW;IA6BnB,OAAO,CAAC,WAAW;IAanB,OAAO,CAAC,WAAW;CAiBpB;AAMD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,mBAAmB,GAAG,UAAU,CAEvE"}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tick Scheduler - jittered task scheduling for background operations.
|
|
3
|
+
*
|
|
4
|
+
* RFC-0000 CRITICAL INVARIANTS:
|
|
5
|
+
* - skipIfBusy: true by default (prevents timing attacks)
|
|
6
|
+
* - Jitter applied to all ticks (prevents predictable patterns)
|
|
7
|
+
* - No blocking of hot-path
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Tick Scheduler implementation.
|
|
11
|
+
*/
|
|
12
|
+
export class Scheduler {
|
|
13
|
+
config;
|
|
14
|
+
tasks = new Map();
|
|
15
|
+
tickTimeoutId = null;
|
|
16
|
+
busyChecker = () => false;
|
|
17
|
+
_running = false;
|
|
18
|
+
_busy = false;
|
|
19
|
+
// Statistics
|
|
20
|
+
_totalTicks = 0;
|
|
21
|
+
_totalTasksExecuted = 0;
|
|
22
|
+
_skippedTicks = 0;
|
|
23
|
+
// Config with defaults
|
|
24
|
+
skipIfBusy;
|
|
25
|
+
constructor(config) {
|
|
26
|
+
this.config = config;
|
|
27
|
+
// skipIfBusy defaults to true (RFC requirement)
|
|
28
|
+
this.skipIfBusy = config.skipIfBusy ?? true;
|
|
29
|
+
}
|
|
30
|
+
start() {
|
|
31
|
+
if (this._running)
|
|
32
|
+
return;
|
|
33
|
+
this._running = true;
|
|
34
|
+
this.scheduleTick();
|
|
35
|
+
}
|
|
36
|
+
stop() {
|
|
37
|
+
if (!this._running)
|
|
38
|
+
return;
|
|
39
|
+
this._running = false;
|
|
40
|
+
if (this.tickTimeoutId) {
|
|
41
|
+
clearTimeout(this.tickTimeoutId);
|
|
42
|
+
this.tickTimeoutId = null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
schedule(task) {
|
|
46
|
+
this.tasks.set(task.id, {
|
|
47
|
+
task,
|
|
48
|
+
lastExecuted: 0,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
unschedule(taskId) {
|
|
52
|
+
this.tasks.delete(taskId);
|
|
53
|
+
}
|
|
54
|
+
setBusyChecker(checker) {
|
|
55
|
+
this.busyChecker = checker;
|
|
56
|
+
}
|
|
57
|
+
get stats() {
|
|
58
|
+
return Object.freeze({
|
|
59
|
+
totalTicks: this._totalTicks,
|
|
60
|
+
totalTasksExecuted: this._totalTasksExecuted,
|
|
61
|
+
skippedTicks: this._skippedTicks,
|
|
62
|
+
scheduledTasks: this.tasks.size,
|
|
63
|
+
running: this._running,
|
|
64
|
+
busy: this._busy,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
// ─── Private Methods ───────────────────────────────────────────────────────
|
|
68
|
+
scheduleTick() {
|
|
69
|
+
if (!this._running)
|
|
70
|
+
return;
|
|
71
|
+
// Apply jitter to interval
|
|
72
|
+
const jitter = Math.random() * this.config.jitterMs;
|
|
73
|
+
const delay = this.config.tickInterval + jitter;
|
|
74
|
+
this.tickTimeoutId = setTimeout(() => {
|
|
75
|
+
this.executeTick();
|
|
76
|
+
}, delay);
|
|
77
|
+
}
|
|
78
|
+
executeTick() {
|
|
79
|
+
if (!this._running)
|
|
80
|
+
return;
|
|
81
|
+
this._totalTicks++;
|
|
82
|
+
// Check if system is busy
|
|
83
|
+
this._busy = this.busyChecker();
|
|
84
|
+
if (this.skipIfBusy && this._busy) {
|
|
85
|
+
this._skippedTicks++;
|
|
86
|
+
this.scheduleTick();
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
// Execute due tasks
|
|
90
|
+
const now = Date.now();
|
|
91
|
+
const dueTasks = this.getDueTasks(now);
|
|
92
|
+
// Sort by priority
|
|
93
|
+
dueTasks.sort((a, b) => (a.task.priority ?? 100) - (b.task.priority ?? 100));
|
|
94
|
+
for (const taskState of dueTasks) {
|
|
95
|
+
this.executeTask(taskState, now);
|
|
96
|
+
}
|
|
97
|
+
// Schedule next tick
|
|
98
|
+
this.scheduleTick();
|
|
99
|
+
}
|
|
100
|
+
getDueTasks(now) {
|
|
101
|
+
const dueTasks = [];
|
|
102
|
+
for (const [, taskState] of this.tasks) {
|
|
103
|
+
const elapsed = now - taskState.lastExecuted;
|
|
104
|
+
if (elapsed >= taskState.task.intervalMs) {
|
|
105
|
+
dueTasks.push(taskState);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return dueTasks;
|
|
109
|
+
}
|
|
110
|
+
executeTask(taskState, now) {
|
|
111
|
+
taskState.lastExecuted = now;
|
|
112
|
+
this._totalTasksExecuted++;
|
|
113
|
+
try {
|
|
114
|
+
const result = taskState.task.execute();
|
|
115
|
+
// Handle async tasks (fire-and-forget)
|
|
116
|
+
if (result instanceof Promise) {
|
|
117
|
+
result.catch(() => {
|
|
118
|
+
// Silently ignore task errors
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
// Silently ignore task errors
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
128
|
+
// Factory
|
|
129
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
130
|
+
/**
|
|
131
|
+
* Create a Scheduler instance.
|
|
132
|
+
*
|
|
133
|
+
* @param config - Scheduler configuration
|
|
134
|
+
*/
|
|
135
|
+
export function createScheduler(config) {
|
|
136
|
+
return new Scheduler(config);
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=scheduler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scheduler.js","sourceRoot":"","sources":["../../src/core/scheduler.ts"],"names":[],"mappings":"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"}
|