@sentropic/track 0.1.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/LICENSE +21 -0
- package/README.md +84 -0
- package/dist/accept/index.d.ts +3 -0
- package/dist/accept/index.d.ts.map +1 -0
- package/dist/accept/index.js +5 -0
- package/dist/accept/index.js.map +1 -0
- package/dist/accept/ingest.d.ts +16 -0
- package/dist/accept/ingest.d.ts.map +1 -0
- package/dist/accept/ingest.js +60 -0
- package/dist/accept/ingest.js.map +1 -0
- package/dist/accept/status.d.ts +15 -0
- package/dist/accept/status.d.ts.map +1 -0
- package/dist/accept/status.js +44 -0
- package/dist/accept/status.js.map +1 -0
- package/dist/branch/index.d.ts +2 -0
- package/dist/branch/index.d.ts.map +1 -0
- package/dist/branch/index.js +3 -0
- package/dist/branch/index.js.map +1 -0
- package/dist/branch/parse.d.ts +31 -0
- package/dist/branch/parse.d.ts.map +1 -0
- package/dist/branch/parse.js +116 -0
- package/dist/branch/parse.js.map +1 -0
- package/dist/cli/desync.d.ts +14 -0
- package/dist/cli/desync.d.ts.map +1 -0
- package/dist/cli/desync.js +35 -0
- package/dist/cli/desync.js.map +1 -0
- package/dist/cli/index.d.ts +8 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +402 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/events/canonical.d.ts +5 -0
- package/dist/events/canonical.d.ts.map +1 -0
- package/dist/events/canonical.js +134 -0
- package/dist/events/canonical.js.map +1 -0
- package/dist/events/frame.d.ts +11 -0
- package/dist/events/frame.d.ts.map +1 -0
- package/dist/events/frame.js +19 -0
- package/dist/events/frame.js.map +1 -0
- package/dist/events/head.d.ts +21 -0
- package/dist/events/head.d.ts.map +1 -0
- package/dist/events/head.js +54 -0
- package/dist/events/head.js.map +1 -0
- package/dist/events/index.d.ts +7 -0
- package/dist/events/index.d.ts.map +1 -0
- package/dist/events/index.js +9 -0
- package/dist/events/index.js.map +1 -0
- package/dist/events/store.d.ts +33 -0
- package/dist/events/store.d.ts.map +1 -0
- package/dist/events/store.js +121 -0
- package/dist/events/store.js.map +1 -0
- package/dist/events/types.d.ts +42 -0
- package/dist/events/types.d.ts.map +1 -0
- package/dist/events/types.js +22 -0
- package/dist/events/types.js.map +1 -0
- package/dist/events/validate.d.ts +72 -0
- package/dist/events/validate.d.ts.map +1 -0
- package/dist/events/validate.js +210 -0
- package/dist/events/validate.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/model/acceptance.d.ts +60 -0
- package/dist/model/acceptance.d.ts.map +1 -0
- package/dist/model/acceptance.js +2 -0
- package/dist/model/acceptance.js.map +1 -0
- package/dist/model/blocker.d.ts +33 -0
- package/dist/model/blocker.d.ts.map +1 -0
- package/dist/model/blocker.js +15 -0
- package/dist/model/blocker.js.map +1 -0
- package/dist/model/decision.d.ts +59 -0
- package/dist/model/decision.d.ts.map +1 -0
- package/dist/model/decision.js +17 -0
- package/dist/model/decision.js.map +1 -0
- package/dist/model/index.d.ts +6 -0
- package/dist/model/index.d.ts.map +1 -0
- package/dist/model/index.js +7 -0
- package/dist/model/index.js.map +1 -0
- package/dist/model/item.d.ts +51 -0
- package/dist/model/item.d.ts.map +1 -0
- package/dist/model/item.js +42 -0
- package/dist/model/item.js.map +1 -0
- package/dist/model/priority.d.ts +22 -0
- package/dist/model/priority.d.ts.map +1 -0
- package/dist/model/priority.js +13 -0
- package/dist/model/priority.js.map +1 -0
- package/dist/priority/index.d.ts +2 -0
- package/dist/priority/index.d.ts.map +1 -0
- package/dist/priority/index.js +4 -0
- package/dist/priority/index.js.map +1 -0
- package/dist/report/buckets.d.ts +18 -0
- package/dist/report/buckets.d.ts.map +1 -0
- package/dist/report/buckets.js +24 -0
- package/dist/report/buckets.js.map +1 -0
- package/dist/report/build.d.ts +44 -0
- package/dist/report/build.d.ts.map +1 -0
- package/dist/report/build.js +62 -0
- package/dist/report/build.js.map +1 -0
- package/dist/report/format.d.ts +5 -0
- package/dist/report/format.d.ts.map +1 -0
- package/dist/report/format.js +81 -0
- package/dist/report/format.js.map +1 -0
- package/dist/report/index.d.ts +4 -0
- package/dist/report/index.d.ts.map +1 -0
- package/dist/report/index.js +5 -0
- package/dist/report/index.js.map +1 -0
- package/dist/state/fold.d.ts +25 -0
- package/dist/state/fold.d.ts.map +1 -0
- package/dist/state/fold.js +208 -0
- package/dist/state/fold.js.map +1 -0
- package/dist/state/index.d.ts +3 -0
- package/dist/state/index.d.ts.map +1 -0
- package/dist/state/index.js +4 -0
- package/dist/state/index.js.map +1 -0
- package/dist/state/snapshot.d.ts +27 -0
- package/dist/state/snapshot.d.ts.map +1 -0
- package/dist/state/snapshot.js +49 -0
- package/dist/state/snapshot.js.map +1 -0
- package/dist/track.d.ts +118 -0
- package/dist/track.d.ts.map +1 -0
- package/dist/track.js +450 -0
- package/dist/track.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +2 -0
- package/dist/version.js.map +1 -0
- package/package.json +58 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { closeSync, existsSync, fsyncSync, mkdirSync, openSync, readFileSync, renameSync, writeSync, } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { canonicalize } from './canonical.js';
|
|
4
|
+
export function headPath(eventsPath) {
|
|
5
|
+
return join(dirname(eventsPath), 'head.json');
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Read the head, or `null` if it is missing, unparseable, OR malformed in shape. The head is
|
|
9
|
+
* rebuildable, so any problem must degrade to "no head" (never throw, never a bogus anchor that
|
|
10
|
+
* would silently disable detection or raise a false truncation/head-mismatch).
|
|
11
|
+
*/
|
|
12
|
+
export function readHead(eventsPath) {
|
|
13
|
+
const p = headPath(eventsPath);
|
|
14
|
+
if (!existsSync(p))
|
|
15
|
+
return null;
|
|
16
|
+
let parsed;
|
|
17
|
+
try {
|
|
18
|
+
parsed = JSON.parse(readFileSync(p, 'utf8'));
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
if (typeof parsed !== 'object' || parsed === null)
|
|
24
|
+
return null;
|
|
25
|
+
const record = parsed;
|
|
26
|
+
const len = record['streamLength'];
|
|
27
|
+
const last = record['lastContentHash'];
|
|
28
|
+
if (typeof len !== 'number' || !Number.isInteger(len) || len < 0)
|
|
29
|
+
return null;
|
|
30
|
+
if (len === 0) {
|
|
31
|
+
if (last !== null)
|
|
32
|
+
return null;
|
|
33
|
+
return { streamLength: 0, lastContentHash: null };
|
|
34
|
+
}
|
|
35
|
+
if (typeof last !== 'string' || !last.startsWith('sha256:'))
|
|
36
|
+
return null;
|
|
37
|
+
return { streamLength: len, lastContentHash: last };
|
|
38
|
+
}
|
|
39
|
+
/** Write the head atomically (temp file + fsync + rename) so a crash never leaves a torn head. */
|
|
40
|
+
export function writeHead(eventsPath, head) {
|
|
41
|
+
const p = headPath(eventsPath);
|
|
42
|
+
mkdirSync(dirname(p), { recursive: true });
|
|
43
|
+
const tmp = `${p}.tmp`;
|
|
44
|
+
const fd = openSync(tmp, 'w');
|
|
45
|
+
try {
|
|
46
|
+
writeSync(fd, canonicalize(head) + '\n');
|
|
47
|
+
fsyncSync(fd);
|
|
48
|
+
}
|
|
49
|
+
finally {
|
|
50
|
+
closeSync(fd);
|
|
51
|
+
}
|
|
52
|
+
renameSync(tmp, p);
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=head.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"head.js","sourceRoot":"","sources":["../../src/events/head.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,EACT,UAAU,EACV,SAAS,EACT,SAAS,EACT,QAAQ,EACR,YAAY,EACZ,UAAU,EACV,SAAS,GACV,MAAM,SAAS,CAAA;AAChB,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAEzC,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAc7C,MAAM,UAAU,QAAQ,CAAC,UAAkB;IACzC,OAAO,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,WAAW,CAAC,CAAA;AAC/C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,QAAQ,CAAC,UAAkB;IACzC,MAAM,CAAC,GAAG,QAAQ,CAAC,UAAU,CAAC,CAAA;IAC9B,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;QAAE,OAAO,IAAI,CAAA;IAC/B,IAAI,MAAe,CAAA;IACnB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAA;IAC9C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAA;IACb,CAAC;IACD,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,IAAI,CAAA;IAC9D,MAAM,MAAM,GAAG,MAAiC,CAAA;IAChD,MAAM,GAAG,GAAG,MAAM,CAAC,cAAc,CAAC,CAAA;IAClC,MAAM,IAAI,GAAG,MAAM,CAAC,iBAAiB,CAAC,CAAA;IACtC,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,GAAG,GAAG,CAAC;QAAE,OAAO,IAAI,CAAA;IAC7E,IAAI,GAAG,KAAK,CAAC,EAAE,CAAC;QACd,IAAI,IAAI,KAAK,IAAI;YAAE,OAAO,IAAI,CAAA;QAC9B,OAAO,EAAE,YAAY,EAAE,CAAC,EAAE,eAAe,EAAE,IAAI,EAAE,CAAA;IACnD,CAAC;IACD,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC;QAAE,OAAO,IAAI,CAAA;IACxE,OAAO,EAAE,YAAY,EAAE,GAAG,EAAE,eAAe,EAAE,IAAc,EAAE,CAAA;AAC/D,CAAC;AAED,kGAAkG;AAClG,MAAM,UAAU,SAAS,CAAC,UAAkB,EAAE,IAAU;IACtD,MAAM,CAAC,GAAG,QAAQ,CAAC,UAAU,CAAC,CAAA;IAC9B,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IAC1C,MAAM,GAAG,GAAG,GAAG,CAAC,MAAM,CAAA;IACtB,MAAM,EAAE,GAAG,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;IAC7B,IAAI,CAAC;QACH,SAAS,CAAC,EAAE,EAAE,YAAY,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,CAAA;QACxC,SAAS,CAAC,EAAE,CAAC,CAAA;IACf,CAAC;YAAS,CAAC;QACT,SAAS,CAAC,EAAE,CAAC,CAAA;IACf,CAAC;IACD,UAAU,CAAC,GAAG,EAAE,CAAC,CAAC,CAAA;AACpB,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export * from './types.js';
|
|
2
|
+
export { canonicalize, computeHash, materialize } from './canonical.js';
|
|
3
|
+
export { contentHashOf, stripFrame } from './frame.js';
|
|
4
|
+
export { headPath, readHead, writeHead, type Head } from './head.js';
|
|
5
|
+
export { EventStore } from './store.js';
|
|
6
|
+
export { validate, type IntegrityFinding, type IntegrityResult } from './validate.js';
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/events/index.ts"],"names":[],"mappings":"AAEA,cAAc,YAAY,CAAA;AAC1B,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AACvE,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AACtD,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,IAAI,EAAE,MAAM,WAAW,CAAA;AACpE,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AACvC,OAAO,EAAE,QAAQ,EAAE,KAAK,gBAAgB,EAAE,KAAK,eAAe,EAAE,MAAM,eAAe,CAAA"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Event contract: append-only frame, contentHash, positional chain, atomic cmdId batch (SPEC §3).
|
|
2
|
+
// The frozen contract everything folds over.
|
|
3
|
+
export * from './types.js';
|
|
4
|
+
export { canonicalize, computeHash, materialize } from './canonical.js';
|
|
5
|
+
export { contentHashOf, stripFrame } from './frame.js';
|
|
6
|
+
export { headPath, readHead, writeHead } from './head.js';
|
|
7
|
+
export { EventStore } from './store.js';
|
|
8
|
+
export { validate } from './validate.js';
|
|
9
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/events/index.ts"],"names":[],"mappings":"AAAA,kGAAkG;AAClG,6CAA6C;AAC7C,cAAc,YAAY,CAAA;AAC1B,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AACvE,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AACtD,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAa,MAAM,WAAW,CAAA;AACpE,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AACvC,OAAO,EAAE,QAAQ,EAA+C,MAAM,eAAe,CAAA"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { CommandEvent, TrackEvent, Ulid } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Append-only single-writer event store over `.track/events.jsonl` (SPEC §3, §4).
|
|
4
|
+
* The store owns the integrity frame: it assigns per-aggregate `seq`, the positional
|
|
5
|
+
* `prevHash`, and `contentHash`, and writes a command's events as one atomic batch.
|
|
6
|
+
*
|
|
7
|
+
* Fail-closed: `appendCommand` `validate`s the existing log before extending it, so a
|
|
8
|
+
* tampered/reordered/truncated log cannot be silently grown.
|
|
9
|
+
*/
|
|
10
|
+
export declare class EventStore {
|
|
11
|
+
private readonly filePath;
|
|
12
|
+
constructor(filePath: string);
|
|
13
|
+
/** All events in stream (append) order. Throws on a malformed (e.g. torn) line. */
|
|
14
|
+
readAll(): TrackEvent[];
|
|
15
|
+
/**
|
|
16
|
+
* Append one command's events atomically. A multi-event command (`inputs.length > 1`)
|
|
17
|
+
* requires `opts.cmdId` and is tagged with `cmd:{i,n}`; a single-event command is
|
|
18
|
+
* standalone (no `cmdId`). Refuses to extend a log that does not pass `validate`.
|
|
19
|
+
*/
|
|
20
|
+
appendCommand(inputs: ReadonlyArray<CommandEvent>, opts?: {
|
|
21
|
+
cmdId?: Ulid;
|
|
22
|
+
}): TrackEvent[];
|
|
23
|
+
/**
|
|
24
|
+
* Append all of a command's lines in one fsync'd write. Each line is the event's `canonicalize`
|
|
25
|
+
* form — the SAME serialization used for `contentHash`, so persisted bytes can never diverge
|
|
26
|
+
* from the hashed bytes (immune to prototype pollution / live values). `writeSync` may
|
|
27
|
+
* short-write, so we loop until every byte is flushed. This makes a normal append durable; it
|
|
28
|
+
* does not make a mid-write *crash* atomic — a torn trailing line is reported by
|
|
29
|
+
* `validate`/`readAll` (fail-closed), not silently skipped.
|
|
30
|
+
*/
|
|
31
|
+
private appendAtomic;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/events/store.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,YAAY,EAAqB,UAAU,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AAGnF;;;;;;;GAOG;AACH,qBAAa,UAAU;IACT,OAAO,CAAC,QAAQ,CAAC,QAAQ;gBAAR,QAAQ,EAAE,MAAM;IAE7C,mFAAmF;IACnF,OAAO,IAAI,UAAU,EAAE;IAiBvB;;;;OAIG;IACH,aAAa,CACX,MAAM,EAAE,aAAa,CAAC,YAAY,CAAC,EACnC,IAAI,GAAE;QAAE,KAAK,CAAC,EAAE,IAAI,CAAA;KAAO,GAC1B,UAAU,EAAE;IAgEf;;;;;;;OAOG;IACH,OAAO,CAAC,YAAY;CAcrB"}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { closeSync, existsSync, fsyncSync, mkdirSync, openSync, readFileSync, writeSync, } from 'node:fs';
|
|
2
|
+
import { dirname } from 'node:path';
|
|
3
|
+
import { canonicalize, materialize } from './canonical.js';
|
|
4
|
+
import { contentHashOf } from './frame.js';
|
|
5
|
+
import { readHead, writeHead } from './head.js';
|
|
6
|
+
import { validate } from './validate.js';
|
|
7
|
+
/**
|
|
8
|
+
* Append-only single-writer event store over `.track/events.jsonl` (SPEC §3, §4).
|
|
9
|
+
* The store owns the integrity frame: it assigns per-aggregate `seq`, the positional
|
|
10
|
+
* `prevHash`, and `contentHash`, and writes a command's events as one atomic batch.
|
|
11
|
+
*
|
|
12
|
+
* Fail-closed: `appendCommand` `validate`s the existing log before extending it, so a
|
|
13
|
+
* tampered/reordered/truncated log cannot be silently grown.
|
|
14
|
+
*/
|
|
15
|
+
export class EventStore {
|
|
16
|
+
filePath;
|
|
17
|
+
constructor(filePath) {
|
|
18
|
+
this.filePath = filePath;
|
|
19
|
+
}
|
|
20
|
+
/** All events in stream (append) order. Throws on a malformed (e.g. torn) line. */
|
|
21
|
+
readAll() {
|
|
22
|
+
if (!existsSync(this.filePath))
|
|
23
|
+
return [];
|
|
24
|
+
const raw = readFileSync(this.filePath, 'utf8');
|
|
25
|
+
const events = [];
|
|
26
|
+
const lines = raw.split('\n');
|
|
27
|
+
for (let i = 0; i < lines.length; i++) {
|
|
28
|
+
const line = lines[i];
|
|
29
|
+
if (line === undefined || line.trim().length === 0)
|
|
30
|
+
continue;
|
|
31
|
+
try {
|
|
32
|
+
events.push(JSON.parse(line));
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
throw new Error(`EventStore: malformed event line ${i + 1} in ${this.filePath}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return events;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Append one command's events atomically. A multi-event command (`inputs.length > 1`)
|
|
42
|
+
* requires `opts.cmdId` and is tagged with `cmd:{i,n}`; a single-event command is
|
|
43
|
+
* standalone (no `cmdId`). Refuses to extend a log that does not pass `validate`.
|
|
44
|
+
*/
|
|
45
|
+
appendCommand(inputs, opts = {}) {
|
|
46
|
+
if (inputs.length === 0) {
|
|
47
|
+
throw new Error('EventStore.appendCommand: empty command (no events)');
|
|
48
|
+
}
|
|
49
|
+
const n = inputs.length;
|
|
50
|
+
const isBatch = n > 1;
|
|
51
|
+
if (isBatch && opts.cmdId === undefined) {
|
|
52
|
+
throw new Error('EventStore.appendCommand: a multi-event command requires a cmdId');
|
|
53
|
+
}
|
|
54
|
+
const existing = this.readAll();
|
|
55
|
+
const integrity = validate(existing, readHead(this.filePath));
|
|
56
|
+
if (!integrity.ok) {
|
|
57
|
+
const kinds = integrity.findings.map((f) => f.kind).join(', ');
|
|
58
|
+
throw new Error(`EventStore.appendCommand: refusing to extend an invalid log ` +
|
|
59
|
+
`(${integrity.findings.length} finding(s): ${kinds})`);
|
|
60
|
+
}
|
|
61
|
+
let prevHash = existing.length > 0 ? existing[existing.length - 1].contentHash : null;
|
|
62
|
+
// Next per-aggregate seq = max(seq) + 1, aligned with validate's authority.
|
|
63
|
+
const lastSeqByAggregate = new Map();
|
|
64
|
+
for (const e of existing) {
|
|
65
|
+
lastSeqByAggregate.set(e.aggregateId, e.seq);
|
|
66
|
+
}
|
|
67
|
+
const events = [];
|
|
68
|
+
inputs.forEach((input, i) => {
|
|
69
|
+
const seq = (lastSeqByAggregate.get(input.aggregateId) ?? 0) + 1;
|
|
70
|
+
lastSeqByAggregate.set(input.aggregateId, seq);
|
|
71
|
+
const core = isBatch
|
|
72
|
+
? { ...input, cmdId: opts.cmdId, cmd: { i, n } }
|
|
73
|
+
: { ...input };
|
|
74
|
+
// Materialize ONCE to an inert plain-data snapshot, then hash AND persist that same
|
|
75
|
+
// snapshot — so a live payload (Proxy / getter / toJSON) cannot diverge between the two.
|
|
76
|
+
const materialized = materialize(core);
|
|
77
|
+
const contentHash = contentHashOf(materialized);
|
|
78
|
+
const event = { ...materialized, seq, prevHash, contentHash };
|
|
79
|
+
events.push(event);
|
|
80
|
+
prevHash = contentHash;
|
|
81
|
+
});
|
|
82
|
+
// Validate the FULL candidate stream (not just the existing prefix): the command itself
|
|
83
|
+
// could introduce a cross-event violation (e.g. an aggregate-mismatch, a malformed batch).
|
|
84
|
+
const candidate = validate([...existing, ...events]);
|
|
85
|
+
if (!candidate.ok) {
|
|
86
|
+
const kinds = candidate.findings.map((f) => f.kind).join(', ');
|
|
87
|
+
throw new Error(`EventStore.appendCommand: command would produce an invalid log ` +
|
|
88
|
+
`(${candidate.findings.length} finding(s): ${kinds})`);
|
|
89
|
+
}
|
|
90
|
+
this.appendAtomic(events);
|
|
91
|
+
writeHead(this.filePath, {
|
|
92
|
+
streamLength: existing.length + events.length,
|
|
93
|
+
lastContentHash: events[events.length - 1].contentHash,
|
|
94
|
+
});
|
|
95
|
+
return events;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Append all of a command's lines in one fsync'd write. Each line is the event's `canonicalize`
|
|
99
|
+
* form — the SAME serialization used for `contentHash`, so persisted bytes can never diverge
|
|
100
|
+
* from the hashed bytes (immune to prototype pollution / live values). `writeSync` may
|
|
101
|
+
* short-write, so we loop until every byte is flushed. This makes a normal append durable; it
|
|
102
|
+
* does not make a mid-write *crash* atomic — a torn trailing line is reported by
|
|
103
|
+
* `validate`/`readAll` (fail-closed), not silently skipped.
|
|
104
|
+
*/
|
|
105
|
+
appendAtomic(events) {
|
|
106
|
+
mkdirSync(dirname(this.filePath), { recursive: true });
|
|
107
|
+
const buffer = Buffer.from(events.map((e) => canonicalize(e)).join('\n') + '\n', 'utf8');
|
|
108
|
+
const fd = openSync(this.filePath, 'a');
|
|
109
|
+
try {
|
|
110
|
+
let offset = 0;
|
|
111
|
+
while (offset < buffer.length) {
|
|
112
|
+
offset += writeSync(fd, buffer, offset, buffer.length - offset);
|
|
113
|
+
}
|
|
114
|
+
fsyncSync(fd);
|
|
115
|
+
}
|
|
116
|
+
finally {
|
|
117
|
+
closeSync(fd);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/events/store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export type Ulid = string;
|
|
2
|
+
export type ActorId = string;
|
|
3
|
+
export type Sha256 = `sha256:${string}`;
|
|
4
|
+
export declare const AGGREGATES: readonly ["item", "decision", "blocker"];
|
|
5
|
+
export type Aggregate = (typeof AGGREGATES)[number];
|
|
6
|
+
export declare const EVENT_TYPES: readonly ["item.created", "spec.transition", "realization.transition", "acceptance.criterion.added", "acceptance.evidence.linked", "acceptance.run", "acceptance.waived", "blocker.opened", "blocker.resolved", "decision.created", "decision.disposition", "dossier.revised", "decision.outcome", "priority.assessed", "branch.imported"];
|
|
7
|
+
export type EventType = (typeof EVENT_TYPES)[number];
|
|
8
|
+
/**
|
|
9
|
+
* Position of an event within an atomic command batch.
|
|
10
|
+
* Lot-1 refinement of SPEC §3: the spec frame carries `cmdId?` to correlate a batch but
|
|
11
|
+
* no size, so `validate` cannot detect a *dropped trailing member*. `cmd` makes a batch
|
|
12
|
+
* self-describing (and is covered by `contentHash`). Present iff `cmdId` is present.
|
|
13
|
+
*/
|
|
14
|
+
export interface CmdPosition {
|
|
15
|
+
i: number;
|
|
16
|
+
n: number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The command-supplied core of an event: everything except the integrity frame
|
|
20
|
+
* (`seq`, `prevHash`, `contentHash`). This is exactly the domain hashed into `contentHash`
|
|
21
|
+
* (cf. h2a `stripFrame`).
|
|
22
|
+
*/
|
|
23
|
+
export interface EventCore {
|
|
24
|
+
id: Ulid;
|
|
25
|
+
type: EventType;
|
|
26
|
+
aggregate: Aggregate;
|
|
27
|
+
aggregateId: Ulid;
|
|
28
|
+
at: string;
|
|
29
|
+
by: ActorId;
|
|
30
|
+
payload: Readonly<Record<string, unknown>>;
|
|
31
|
+
cmdId?: Ulid;
|
|
32
|
+
cmd?: CmdPosition;
|
|
33
|
+
}
|
|
34
|
+
/** A command's contribution before the store assigns the integrity frame. */
|
|
35
|
+
export type CommandEvent = Omit<EventCore, 'cmdId' | 'cmd'>;
|
|
36
|
+
/** A fully persisted event = core + positional/integrity frame. */
|
|
37
|
+
export interface TrackEvent extends EventCore {
|
|
38
|
+
seq: number;
|
|
39
|
+
prevHash: Sha256 | null;
|
|
40
|
+
contentHash: Sha256;
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/events/types.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,IAAI,GAAG,MAAM,CAAA;AACzB,MAAM,MAAM,OAAO,GAAG,MAAM,CAAA;AAC5B,MAAM,MAAM,MAAM,GAAG,UAAU,MAAM,EAAE,CAAA;AAEvC,eAAO,MAAM,UAAU,0CAA2C,CAAA;AAClE,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,CAAC,CAAA;AAEnD,eAAO,MAAM,WAAW,4UAgBd,CAAA;AACV,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,CAAC,CAAA;AAEpD;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC1B,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;CACV;AAED;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,IAAI,CAAA;IACR,IAAI,EAAE,SAAS,CAAA;IACf,SAAS,EAAE,SAAS,CAAA;IACpB,WAAW,EAAE,IAAI,CAAA;IACjB,EAAE,EAAE,MAAM,CAAA;IACV,EAAE,EAAE,OAAO,CAAA;IACX,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;IAC1C,KAAK,CAAC,EAAE,IAAI,CAAA;IACZ,GAAG,CAAC,EAAE,WAAW,CAAA;CAClB;AAED,6EAA6E;AAC7E,MAAM,MAAM,YAAY,GAAG,IAAI,CAAC,SAAS,EAAE,OAAO,GAAG,KAAK,CAAC,CAAA;AAE3D,mEAAmE;AACnE,MAAM,WAAW,UAAW,SAAQ,SAAS;IAC3C,GAAG,EAAE,MAAM,CAAA;IACX,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,WAAW,EAAE,MAAM,CAAA;CACpB"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Event contract — the frozen core of @sentropic/track (SPEC §3).
|
|
2
|
+
// Faithful to the h2a journal model (positional prevHash chain + per-aggregate seq),
|
|
3
|
+
// re-expressed for a typed product backlog.
|
|
4
|
+
export const AGGREGATES = ['item', 'decision', 'blocker'];
|
|
5
|
+
export const EVENT_TYPES = [
|
|
6
|
+
'item.created',
|
|
7
|
+
'spec.transition',
|
|
8
|
+
'realization.transition',
|
|
9
|
+
'acceptance.criterion.added',
|
|
10
|
+
'acceptance.evidence.linked',
|
|
11
|
+
'acceptance.run',
|
|
12
|
+
'acceptance.waived',
|
|
13
|
+
'blocker.opened',
|
|
14
|
+
'blocker.resolved',
|
|
15
|
+
'decision.created',
|
|
16
|
+
'decision.disposition',
|
|
17
|
+
'dossier.revised',
|
|
18
|
+
'decision.outcome',
|
|
19
|
+
'priority.assessed',
|
|
20
|
+
'branch.imported',
|
|
21
|
+
];
|
|
22
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/events/types.ts"],"names":[],"mappings":"AAAA,kEAAkE;AAClE,qFAAqF;AACrF,4CAA4C;AAM5C,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,MAAM,EAAE,UAAU,EAAE,SAAS,CAAU,CAAA;AAGlE,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,cAAc;IACd,iBAAiB;IACjB,wBAAwB;IACxB,4BAA4B;IAC5B,4BAA4B;IAC5B,gBAAgB;IAChB,mBAAmB;IACnB,gBAAgB;IAChB,kBAAkB;IAClB,kBAAkB;IAClB,sBAAsB;IACtB,iBAAiB;IACjB,kBAAkB;IAClB,mBAAmB;IACnB,iBAAiB;CACT,CAAA"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { Head } from './head.js';
|
|
2
|
+
import type { Sha256, TrackEvent } from './types.js';
|
|
3
|
+
export type IntegrityFinding = {
|
|
4
|
+
kind: 'content-hash';
|
|
5
|
+
index: number;
|
|
6
|
+
eventId: string;
|
|
7
|
+
expected: Sha256;
|
|
8
|
+
actual: Sha256;
|
|
9
|
+
} | {
|
|
10
|
+
kind: 'prev-hash';
|
|
11
|
+
index: number;
|
|
12
|
+
eventId: string;
|
|
13
|
+
expected: Sha256 | null;
|
|
14
|
+
actual: Sha256 | null;
|
|
15
|
+
} | {
|
|
16
|
+
kind: 'aggregate-seq';
|
|
17
|
+
index: number;
|
|
18
|
+
eventId: string;
|
|
19
|
+
aggregateId: string;
|
|
20
|
+
expected: number;
|
|
21
|
+
actual: number;
|
|
22
|
+
} | {
|
|
23
|
+
kind: 'aggregate-mismatch';
|
|
24
|
+
index: number;
|
|
25
|
+
eventId: string;
|
|
26
|
+
aggregateId: string;
|
|
27
|
+
expected: string;
|
|
28
|
+
actual: string;
|
|
29
|
+
} | {
|
|
30
|
+
kind: 'batch-frame';
|
|
31
|
+
index: number;
|
|
32
|
+
eventId: string;
|
|
33
|
+
cmdId: string | null;
|
|
34
|
+
reason: string;
|
|
35
|
+
} | {
|
|
36
|
+
kind: 'partial-batch';
|
|
37
|
+
cmdId: string;
|
|
38
|
+
expected: number;
|
|
39
|
+
actual: number;
|
|
40
|
+
reason: string;
|
|
41
|
+
} | {
|
|
42
|
+
kind: 'batch-noncontiguous';
|
|
43
|
+
cmdId: string;
|
|
44
|
+
index: number;
|
|
45
|
+
} | {
|
|
46
|
+
kind: 'truncation';
|
|
47
|
+
expected: number;
|
|
48
|
+
actual: number;
|
|
49
|
+
} | {
|
|
50
|
+
kind: 'head-mismatch';
|
|
51
|
+
index: number;
|
|
52
|
+
expected: Sha256 | null;
|
|
53
|
+
actual: Sha256;
|
|
54
|
+
};
|
|
55
|
+
export interface IntegrityResult {
|
|
56
|
+
ok: boolean;
|
|
57
|
+
findings: IntegrityFinding[];
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Integrity check over a stream (SPEC §3, A4 + A5). For each event in stream order:
|
|
61
|
+
* (i) recompute `contentHash` from the core (content tamper), (ii) `prevHash` equals the
|
|
62
|
+
* previous event's stored `contentHash` (insertion/reorder), (iii) per-aggregate `seq` is
|
|
63
|
+
* 1-based and strictly contiguous (drop/dup), (iv) an `aggregateId` keeps one `aggregate`
|
|
64
|
+
* type. Then per `cmdId`: `cmd` iff `cmdId`, a consistent positive integer `n`, every index
|
|
65
|
+
* unique in `[0,n)`, count `== n`, contiguous. With a `head`, suffix **truncation** and
|
|
66
|
+
* head-position tamper are detected.
|
|
67
|
+
*
|
|
68
|
+
* Frozen threat model — NOT detected from the array alone: suffix truncation without a head,
|
|
69
|
+
* a full rewrite-with-rechain, SHA-256 collisions. Durable anchoring is the docs-git layer.
|
|
70
|
+
*/
|
|
71
|
+
export declare function validate(events: ReadonlyArray<TrackEvent>, head?: Head | null): IntegrityResult;
|
|
72
|
+
//# sourceMappingURL=validate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/events/validate.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AACrC,OAAO,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAEpD,MAAM,MAAM,gBAAgB,GACxB;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC1F;IACE,IAAI,EAAE,WAAW,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;CACtB,GACD;IACE,IAAI,EAAE,eAAe,CAAA;IACrB,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;IACf,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;CACf,GACD;IACE,IAAI,EAAE,oBAAoB,CAAA;IAC1B,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;IACf,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;CACf,GACD;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC7F;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC1F;IAAE,IAAI,EAAE,qBAAqB,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAC7D;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACxD;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAA;AAErF,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,OAAO,CAAA;IACX,QAAQ,EAAE,gBAAgB,EAAE,CAAA;CAC7B;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,QAAQ,CACtB,MAAM,EAAE,aAAa,CAAC,UAAU,CAAC,EACjC,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GACjB,eAAe,CAyFjB"}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { contentHashOf, stripFrame } from './frame.js';
|
|
2
|
+
/**
|
|
3
|
+
* Integrity check over a stream (SPEC §3, A4 + A5). For each event in stream order:
|
|
4
|
+
* (i) recompute `contentHash` from the core (content tamper), (ii) `prevHash` equals the
|
|
5
|
+
* previous event's stored `contentHash` (insertion/reorder), (iii) per-aggregate `seq` is
|
|
6
|
+
* 1-based and strictly contiguous (drop/dup), (iv) an `aggregateId` keeps one `aggregate`
|
|
7
|
+
* type. Then per `cmdId`: `cmd` iff `cmdId`, a consistent positive integer `n`, every index
|
|
8
|
+
* unique in `[0,n)`, count `== n`, contiguous. With a `head`, suffix **truncation** and
|
|
9
|
+
* head-position tamper are detected.
|
|
10
|
+
*
|
|
11
|
+
* Frozen threat model — NOT detected from the array alone: suffix truncation without a head,
|
|
12
|
+
* a full rewrite-with-rechain, SHA-256 collisions. Durable anchoring is the docs-git layer.
|
|
13
|
+
*/
|
|
14
|
+
export function validate(events, head) {
|
|
15
|
+
const findings = [];
|
|
16
|
+
let prevHash = null;
|
|
17
|
+
const lastSeqByAggregate = new Map();
|
|
18
|
+
const aggregateKind = new Map();
|
|
19
|
+
for (let i = 0; i < events.length; i++) {
|
|
20
|
+
const e = events[i];
|
|
21
|
+
// (i) content tamper
|
|
22
|
+
const expectedHash = contentHashOf(stripFrame(e));
|
|
23
|
+
if (e.contentHash !== expectedHash) {
|
|
24
|
+
findings.push({
|
|
25
|
+
kind: 'content-hash',
|
|
26
|
+
index: i,
|
|
27
|
+
eventId: e.id,
|
|
28
|
+
expected: expectedHash,
|
|
29
|
+
actual: e.contentHash,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
// (ii) positional chain (insertion / reorder)
|
|
33
|
+
if (e.prevHash !== prevHash) {
|
|
34
|
+
findings.push({
|
|
35
|
+
kind: 'prev-hash',
|
|
36
|
+
index: i,
|
|
37
|
+
eventId: e.id,
|
|
38
|
+
expected: prevHash,
|
|
39
|
+
actual: e.prevHash,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
// (iii) per-aggregate seq: exact +1, 1-based
|
|
43
|
+
const expectedSeq = (lastSeqByAggregate.get(e.aggregateId) ?? 0) + 1;
|
|
44
|
+
if (e.seq !== expectedSeq) {
|
|
45
|
+
findings.push({
|
|
46
|
+
kind: 'aggregate-seq',
|
|
47
|
+
index: i,
|
|
48
|
+
eventId: e.id,
|
|
49
|
+
aggregateId: e.aggregateId,
|
|
50
|
+
expected: expectedSeq,
|
|
51
|
+
actual: e.seq,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
lastSeqByAggregate.set(e.aggregateId, e.seq);
|
|
55
|
+
// (iv) one aggregateId keeps one aggregate type (ULID global uniqueness invariant)
|
|
56
|
+
const knownKind = aggregateKind.get(e.aggregateId);
|
|
57
|
+
if (knownKind === undefined) {
|
|
58
|
+
aggregateKind.set(e.aggregateId, e.aggregate);
|
|
59
|
+
}
|
|
60
|
+
else if (knownKind !== e.aggregate) {
|
|
61
|
+
findings.push({
|
|
62
|
+
kind: 'aggregate-mismatch',
|
|
63
|
+
index: i,
|
|
64
|
+
eventId: e.id,
|
|
65
|
+
aggregateId: e.aggregateId,
|
|
66
|
+
expected: knownKind,
|
|
67
|
+
actual: e.aggregate,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
// batch frame: cmd iff cmdId
|
|
71
|
+
if (e.cmdId !== undefined && e.cmd === undefined) {
|
|
72
|
+
findings.push({
|
|
73
|
+
kind: 'batch-frame',
|
|
74
|
+
index: i,
|
|
75
|
+
eventId: e.id,
|
|
76
|
+
cmdId: e.cmdId,
|
|
77
|
+
reason: 'cmdId without cmd:{i,n}',
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
if (e.cmd !== undefined && e.cmdId === undefined) {
|
|
81
|
+
findings.push({
|
|
82
|
+
kind: 'batch-frame',
|
|
83
|
+
index: i,
|
|
84
|
+
eventId: e.id,
|
|
85
|
+
cmdId: null,
|
|
86
|
+
reason: 'cmd:{i,n} without cmdId',
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
prevHash = e.contentHash;
|
|
90
|
+
}
|
|
91
|
+
findings.push(...validateBatches(events));
|
|
92
|
+
findings.push(...validateHead(events, head));
|
|
93
|
+
return { ok: findings.length === 0, findings };
|
|
94
|
+
}
|
|
95
|
+
function validateBatches(events) {
|
|
96
|
+
const findings = [];
|
|
97
|
+
const batches = new Map();
|
|
98
|
+
events.forEach((e, index) => {
|
|
99
|
+
if (e.cmdId === undefined)
|
|
100
|
+
return;
|
|
101
|
+
const members = batches.get(e.cmdId) ?? [];
|
|
102
|
+
members.push({ index, i: e.cmd?.i, n: e.cmd?.n });
|
|
103
|
+
batches.set(e.cmdId, members);
|
|
104
|
+
});
|
|
105
|
+
for (const [cmdId, members] of batches) {
|
|
106
|
+
// Determine a consistent, valid n across members.
|
|
107
|
+
const declaredNs = [...new Set(members.map((m) => m.n).filter((n) => n !== undefined))];
|
|
108
|
+
if (declaredNs.length > 1) {
|
|
109
|
+
findings.push({
|
|
110
|
+
kind: 'partial-batch',
|
|
111
|
+
cmdId,
|
|
112
|
+
expected: -1,
|
|
113
|
+
actual: members.length,
|
|
114
|
+
reason: `inconsistent cmd.n across members: ${declaredNs.join(',')}`,
|
|
115
|
+
});
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
const n = declaredNs[0];
|
|
119
|
+
if (n === undefined || !Number.isInteger(n) || n <= 0) {
|
|
120
|
+
findings.push({
|
|
121
|
+
kind: 'partial-batch',
|
|
122
|
+
cmdId,
|
|
123
|
+
expected: -1,
|
|
124
|
+
actual: members.length,
|
|
125
|
+
reason: `invalid or missing cmd.n (${String(n)})`,
|
|
126
|
+
});
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
// count
|
|
130
|
+
if (members.length !== n) {
|
|
131
|
+
findings.push({
|
|
132
|
+
kind: 'partial-batch',
|
|
133
|
+
cmdId,
|
|
134
|
+
expected: n,
|
|
135
|
+
actual: members.length,
|
|
136
|
+
reason: `expected ${n} members, found ${members.length}`,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
// contiguity in stream
|
|
140
|
+
const indices = members.map((m) => m.index);
|
|
141
|
+
const min = Math.min(...indices);
|
|
142
|
+
const max = Math.max(...indices);
|
|
143
|
+
if (max - min + 1 !== members.length) {
|
|
144
|
+
findings.push({ kind: 'batch-noncontiguous', cmdId, index: min });
|
|
145
|
+
}
|
|
146
|
+
// positions: each i an integer in [0,n), unique, covering 0..n-1
|
|
147
|
+
const seen = new Set();
|
|
148
|
+
for (const m of members) {
|
|
149
|
+
if (m.i === undefined || !Number.isInteger(m.i) || m.i < 0 || m.i >= n) {
|
|
150
|
+
findings.push({
|
|
151
|
+
kind: 'partial-batch',
|
|
152
|
+
cmdId,
|
|
153
|
+
expected: n,
|
|
154
|
+
actual: members.length,
|
|
155
|
+
reason: `invalid cmd.i (${String(m.i)}) for n=${n}`,
|
|
156
|
+
});
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
if (seen.has(m.i)) {
|
|
160
|
+
findings.push({
|
|
161
|
+
kind: 'partial-batch',
|
|
162
|
+
cmdId,
|
|
163
|
+
expected: n,
|
|
164
|
+
actual: members.length,
|
|
165
|
+
reason: `duplicate cmd.i (${m.i})`,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
seen.add(m.i);
|
|
169
|
+
}
|
|
170
|
+
const missing = [];
|
|
171
|
+
for (let p = 0; p < n; p++) {
|
|
172
|
+
if (!seen.has(p))
|
|
173
|
+
missing.push(p);
|
|
174
|
+
}
|
|
175
|
+
if (missing.length > 0) {
|
|
176
|
+
findings.push({
|
|
177
|
+
kind: 'partial-batch',
|
|
178
|
+
cmdId,
|
|
179
|
+
expected: n,
|
|
180
|
+
actual: seen.size,
|
|
181
|
+
reason: `missing batch positions ${missing.join(',')}`,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return findings;
|
|
186
|
+
}
|
|
187
|
+
function validateHead(events, head) {
|
|
188
|
+
if (head === null || head === undefined)
|
|
189
|
+
return [];
|
|
190
|
+
// Suffix truncation: fewer events than the head recorded.
|
|
191
|
+
if (head.streamLength > events.length) {
|
|
192
|
+
return [{ kind: 'truncation', expected: head.streamLength, actual: events.length }];
|
|
193
|
+
}
|
|
194
|
+
// Tamper at the head position. (events.length > head.streamLength is a stale head — rebuildable, not an error.)
|
|
195
|
+
if (head.streamLength >= 1) {
|
|
196
|
+
const anchor = events[head.streamLength - 1];
|
|
197
|
+
if (anchor.contentHash !== head.lastContentHash) {
|
|
198
|
+
return [
|
|
199
|
+
{
|
|
200
|
+
kind: 'head-mismatch',
|
|
201
|
+
index: head.streamLength - 1,
|
|
202
|
+
expected: head.lastContentHash,
|
|
203
|
+
actual: anchor.contentHash,
|
|
204
|
+
},
|
|
205
|
+
];
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return [];
|
|
209
|
+
}
|
|
210
|
+
//# sourceMappingURL=validate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.js","sourceRoot":"","sources":["../../src/events/validate.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { VERSION } from './version.js';
|
|
2
|
+
export * from './events/index.js';
|
|
3
|
+
export * from './model/index.js';
|
|
4
|
+
export * from './state/index.js';
|
|
5
|
+
export * from './accept/index.js';
|
|
6
|
+
export * from './report/index.js';
|
|
7
|
+
export * from './branch/index.js';
|
|
8
|
+
export { Track, type ImportResult, type OpenBlockerInput, type TrackOptions } from './track.js';
|
|
9
|
+
export { runCli, type CliIO } from './cli/index.js';
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AACtC,cAAc,mBAAmB,CAAA;AACjC,cAAc,kBAAkB,CAAA;AAChC,cAAc,kBAAkB,CAAA;AAChC,cAAc,mBAAmB,CAAA;AACjC,cAAc,mBAAmB,CAAA;AACjC,cAAc,mBAAmB,CAAA;AACjC,OAAO,EAAE,KAAK,EAAE,KAAK,YAAY,EAAE,KAAK,gBAAgB,EAAE,KAAK,YAAY,EAAE,MAAM,YAAY,CAAA;AAC/F,OAAO,EAAE,MAAM,EAAE,KAAK,KAAK,EAAE,MAAM,gBAAgB,CAAA"}
|