qlm-measure 0.2.0 → 0.2.2
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 +159 -12
- package/README.md +24 -139
- package/dist/checks.d.ts +21 -0
- package/dist/checks.d.ts.map +1 -0
- package/dist/checks.js +25 -0
- package/dist/checks.js.map +1 -0
- package/dist/cli.d.ts +7 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +314 -0
- package/dist/cli.js.map +1 -0
- package/dist/clients/engine-client.d.ts +1 -1
- package/dist/clients/engine-client.d.ts.map +1 -1
- package/dist/clients/engine-client.js +1 -1
- package/dist/clients/engine-client.js.map +1 -1
- package/dist/clients/index.d.ts +4 -4
- package/dist/clients/index.d.ts.map +1 -1
- package/dist/clients/index.js +2 -2
- package/dist/clients/index.js.map +1 -1
- package/dist/clients/ontology-client.d.ts +10 -0
- package/dist/clients/ontology-client.d.ts.map +1 -1
- package/dist/clients/ontology-client.js +4 -0
- package/dist/clients/ontology-client.js.map +1 -1
- package/dist/emitter/index.d.ts +2 -2
- package/dist/emitter/index.d.ts.map +1 -1
- package/dist/emitter/index.js +1 -1
- package/dist/emitter/index.js.map +1 -1
- package/dist/emitter/measurement-emitter.d.ts +1 -1
- package/dist/emitter/measurement-emitter.d.ts.map +1 -1
- package/dist/index.d.ts +19 -16
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -8
- package/dist/index.js.map +1 -1
- package/dist/rules.d.ts +10 -0
- package/dist/rules.d.ts.map +1 -0
- package/dist/rules.js +46 -0
- package/dist/rules.js.map +1 -0
- package/dist/schema/evidence-record.d.ts +2 -2
- package/dist/schema/evidence-record.d.ts.map +1 -1
- package/dist/schema/index.d.ts +6 -6
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema/index.js +2 -2
- package/dist/schema/index.js.map +1 -1
- package/dist/schema/measurement-event.d.ts +1 -1
- package/dist/schema/measurement-event.d.ts.map +1 -1
- package/dist/schema/observation-event.d.ts +1 -1
- package/dist/schema/observation-event.d.ts.map +1 -1
- package/dist/verifier/hash.d.ts +8 -7
- package/dist/verifier/hash.d.ts.map +1 -1
- package/dist/verifier/hash.js +17 -31
- package/dist/verifier/hash.js.map +1 -1
- package/dist/verifier/index.d.ts +4 -4
- package/dist/verifier/index.d.ts.map +1 -1
- package/dist/verifier/index.js +3 -3
- package/dist/verifier/index.js.map +1 -1
- package/dist/verifier/replay.d.ts +1 -1
- package/dist/verifier/replay.d.ts.map +1 -1
- package/dist/verifier/verify-record.d.ts +4 -30
- package/dist/verifier/verify-record.d.ts.map +1 -1
- package/dist/verifier/verify-record.js +44 -83
- package/dist/verifier/verify-record.js.map +1 -1
- package/package.json +4 -1
package/dist/rules.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public update rule registry.
|
|
3
|
+
* Each rule is a pure function: given inputs, produce a posterior.
|
|
4
|
+
* This module ships with the SDK and has no server dependency.
|
|
5
|
+
*/
|
|
6
|
+
const registry = new Map();
|
|
7
|
+
export function registerRule(ruleId, fn) {
|
|
8
|
+
registry.set(ruleId, fn);
|
|
9
|
+
}
|
|
10
|
+
export function getRule(ruleId) {
|
|
11
|
+
return registry.get(ruleId);
|
|
12
|
+
}
|
|
13
|
+
export function reproduce(ruleId, params, prior, correct, weight) {
|
|
14
|
+
const fn = getRule(ruleId);
|
|
15
|
+
if (!fn)
|
|
16
|
+
return null;
|
|
17
|
+
return fn(prior, correct, weight, params);
|
|
18
|
+
}
|
|
19
|
+
// ── tempered-bkt-1 ─────────────────────────────────────────
|
|
20
|
+
// Written from depth_engine/update.py. Spec: rules/tempered-bkt-1.md
|
|
21
|
+
function temperedBkt1(prior, correct, weight, params) {
|
|
22
|
+
const slip = params.slip;
|
|
23
|
+
const guess = params.guess;
|
|
24
|
+
if (prior <= 0 || prior >= 1)
|
|
25
|
+
throw new Error(`prior must be in (0, 1), got ${prior}`);
|
|
26
|
+
if (weight < 0 || weight > 1)
|
|
27
|
+
throw new Error(`weight must be in [0, 1], got ${weight}`);
|
|
28
|
+
if (weight === 0)
|
|
29
|
+
return prior;
|
|
30
|
+
let pObsMastered;
|
|
31
|
+
let pObsNotMastered;
|
|
32
|
+
if (correct) {
|
|
33
|
+
pObsMastered = Math.pow(1 - slip, weight);
|
|
34
|
+
pObsNotMastered = Math.pow(guess, weight);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
pObsMastered = Math.pow(slip, weight);
|
|
38
|
+
pObsNotMastered = Math.pow(1 - guess, weight);
|
|
39
|
+
}
|
|
40
|
+
const numerator = prior * pObsMastered;
|
|
41
|
+
const denominator = numerator + (1 - prior) * pObsNotMastered;
|
|
42
|
+
const posterior = numerator / denominator;
|
|
43
|
+
return Math.max(1e-15, Math.min(1 - 1e-15, posterior));
|
|
44
|
+
}
|
|
45
|
+
registerRule("tempered-bkt-1", temperedBkt1);
|
|
46
|
+
//# sourceMappingURL=rules.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rules.js","sourceRoot":"","sources":["../src/rules.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAkB,CAAC;AAE3C,MAAM,UAAU,YAAY,CAAC,MAAc,EAAE,EAAU;IACrD,QAAQ,CAAC,GAAG,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AAC3B,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,MAAc;IACpC,OAAO,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;AAC9B,CAAC;AAED,MAAM,UAAU,SAAS,CACvB,MAAc,EAAE,MAA8B,EAC9C,KAAa,EAAE,OAAgB,EAAE,MAAc;IAE/C,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC3B,IAAI,CAAC,EAAE;QAAE,OAAO,IAAI,CAAC;IACrB,OAAO,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;AAC5C,CAAC;AAED,8DAA8D;AAC9D,qEAAqE;AAErE,SAAS,YAAY,CAAC,KAAa,EAAE,OAAgB,EAAE,MAAc,EAAE,MAA8B;IACnG,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC;IACzB,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC;IAE3B,IAAI,KAAK,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,gCAAgC,KAAK,EAAE,CAAC,CAAC;IACvF,IAAI,MAAM,GAAG,CAAC,IAAI,MAAM,GAAG,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,iCAAiC,MAAM,EAAE,CAAC,CAAC;IACzF,IAAI,MAAM,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAE/B,IAAI,YAAoB,CAAC;IACzB,IAAI,eAAuB,CAAC;IAE5B,IAAI,OAAO,EAAE,CAAC;QACZ,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,EAAE,MAAM,CAAC,CAAC;QAC1C,eAAe,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IAC5C,CAAC;SAAM,CAAC;QACN,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACtC,eAAe,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,KAAK,EAAE,MAAM,CAAC,CAAC;IAChD,CAAC;IAED,MAAM,SAAS,GAAG,KAAK,GAAG,YAAY,CAAC;IACvC,MAAM,WAAW,GAAG,SAAS,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC,GAAG,eAAe,CAAC;IAC9D,MAAM,SAAS,GAAG,SAAS,GAAG,WAAW,CAAC;IAE1C,OAAO,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC;AACzD,CAAC;AAED,YAAY,CAAC,gBAAgB,EAAE,YAAY,CAAC,CAAC"}
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
* and what it decided — enabling external verification of
|
|
7
7
|
* bookkeeping integrity without any estimation mathematics.
|
|
8
8
|
*/
|
|
9
|
-
import type { ObservationEvent } from "./observation-event";
|
|
10
|
-
import type { ScaffoldType, DepthLevel, EpistemicMode, TriageResult } from "./enums";
|
|
9
|
+
import type { ObservationEvent } from "./observation-event.js";
|
|
10
|
+
import type { ScaffoldType, DepthLevel, EpistemicMode, TriageResult } from "./enums.js";
|
|
11
11
|
/**
|
|
12
12
|
* A single evidence entry in the record.
|
|
13
13
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"evidence-record.d.ts","sourceRoot":"","sources":["../../src/schema/evidence-record.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"evidence-record.d.ts","sourceRoot":"","sources":["../../src/schema/evidence-record.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAExF;;;;;;GAMG;AACH,MAAM,WAAW,aAAa;IAC5B,sEAAsE;IACtE,OAAO,EAAE,MAAM,CAAC;IAEhB,0BAA0B;IAC1B,SAAS,EAAE,MAAM,CAAC;IAElB,qEAAqE;IACrE,KAAK,EAAE,gBAAgB,GAAG;QAAE,QAAQ,EAAE,IAAI,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IAEhE,2DAA2D;IAC3D,YAAY,EAAE,YAAY,CAAC;IAE3B,iDAAiD;IACjD,UAAU,EAAE,UAAU,CAAC;IAEvB,qCAAqC;IACrC,aAAa,EAAE,aAAa,GAAG,IAAI,CAAC;IAEpC,6BAA6B;IAC7B,YAAY,EAAE,YAAY,CAAC;IAE3B,oEAAoE;IACpE,gBAAgB,EAAE,MAAM,CAAC;IAEzB,oDAAoD;IACpD,cAAc,EAAE,MAAM,CAAC;IAEvB,mDAAmD;IACnD,gBAAgB,EAAE,MAAM,CAAC;IAEzB,sDAAsD;IACtD,uBAAuB,CAAC,EAAE;QACxB,UAAU,EAAE,OAAO,CAAC;QACpB,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC;IAEF,4DAA4D;IAC5D,SAAS,EAAE,MAAM,CAAC;IAElB,oDAAoD;IACpD,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,qCAAqC;IACrC,cAAc,EAAE,MAAM,CAAC;IAEvB,uCAAuC;IACvC,OAAO,EAAE,aAAa,EAAE,CAAC;IAEzB,4EAA4E;IAC5E,eAAe,CAAC,EAAE;QAChB,kDAAkD;QAClD,OAAO,EAAE,MAAM,CAAC;QAChB,8CAA8C;QAC9C,WAAW,EAAE,MAAM,CAAC;KACrB,CAAC;CACH;AAED,gGAAgG;AAChG,eAAO,MAAM,oBAAoB,MAAM,CAAC"}
|
package/dist/schema/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
export type { ScaffoldType, EpistemicMode, DepthLevel, TriageResult, EventCategory, } from "./enums";
|
|
2
|
-
export { toPublicEpistemicMode } from "./enums";
|
|
3
|
-
export type { ObservationEvent } from "./observation-event";
|
|
4
|
-
export type { MeasurementEvent } from "./measurement-event";
|
|
5
|
-
export type { EvidenceEntry, EvidenceRecord } from "./evidence-record";
|
|
6
|
-
export { COMPACTION_RETENTION } from "./evidence-record";
|
|
1
|
+
export type { ScaffoldType, EpistemicMode, DepthLevel, TriageResult, EventCategory, } from "./enums.js";
|
|
2
|
+
export { toPublicEpistemicMode } from "./enums.js";
|
|
3
|
+
export type { ObservationEvent } from "./observation-event.js";
|
|
4
|
+
export type { MeasurementEvent } from "./measurement-event.js";
|
|
5
|
+
export type { EvidenceEntry, EvidenceRecord } from "./evidence-record.js";
|
|
6
|
+
export { COMPACTION_RETENTION } from "./evidence-record.js";
|
|
7
7
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/schema/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,YAAY,EACZ,aAAa,EACb,UAAU,EACV,YAAY,EACZ,aAAa,GACd,MAAM,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/schema/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,YAAY,EACZ,aAAa,EACb,UAAU,EACV,YAAY,EACZ,aAAa,GACd,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAEnD,YAAY,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,YAAY,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC1E,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC"}
|
package/dist/schema/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { toPublicEpistemicMode } from "./enums";
|
|
2
|
-
export { COMPACTION_RETENTION } from "./evidence-record";
|
|
1
|
+
export { toPublicEpistemicMode } from "./enums.js";
|
|
2
|
+
export { COMPACTION_RETENTION } from "./evidence-record.js";
|
|
3
3
|
//# sourceMappingURL=index.js.map
|
package/dist/schema/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/schema/index.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,qBAAqB,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/schema/index.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAKnD,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"measurement-event.d.ts","sourceRoot":"","sources":["../../src/schema/measurement-event.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"measurement-event.d.ts","sourceRoot":"","sources":["../../src/schema/measurement-event.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,MAAM,WAAW,gBAAgB;IAC/B,6BAA6B;IAC7B,SAAS,EAAE,MAAM,CAAC;IAElB,sBAAsB;IACtB,aAAa,EAAE,aAAa,CAAC;IAE7B,0BAA0B;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,4BAA4B;IAC5B,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,wCAAwC;IACxC,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB,+BAA+B;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf,wCAAwC;IACxC,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd,kCAAkC;IAClC,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB,iDAAiD;IACjD,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAElC,2CAA2C;IAC3C,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB,4CAA4C;IAC5C,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB,yDAAyD;IACzD,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB,+CAA+C;IAC/C,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB"}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Integrators needing excluded fields use the `ext` namespace.
|
|
10
10
|
*/
|
|
11
|
-
import type { ScaffoldType, EpistemicMode } from "./enums";
|
|
11
|
+
import type { ScaffoldType, EpistemicMode } from "./enums.js";
|
|
12
12
|
export interface ObservationEvent {
|
|
13
13
|
/** Pseudonymous student identifier. MUST NOT contain PII. */
|
|
14
14
|
studentId: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"observation-event.d.ts","sourceRoot":"","sources":["../../src/schema/observation-event.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"observation-event.d.ts","sourceRoot":"","sources":["../../src/schema/observation-event.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE9D,MAAM,WAAW,gBAAgB;IAC/B,6DAA6D;IAC7D,SAAS,EAAE,MAAM,CAAC;IAElB,0BAA0B;IAC1B,SAAS,EAAE,MAAM,CAAC;IAElB,8CAA8C;IAC9C,MAAM,EAAE,MAAM,CAAC;IAEf,0BAA0B;IAC1B,SAAS,EAAE,MAAM,CAAC;IAElB,gDAAgD;IAChD,OAAO,EAAE,OAAO,CAAC;IAEjB,qCAAqC;IACrC,cAAc,EAAE,MAAM,CAAC;IAEvB,mDAAmD;IACnD,MAAM,EAAE,MAAM,CAAC;IAEf,iDAAiD;IACjD,YAAY,CAAC,EAAE,YAAY,GAAG,IAAI,CAAC;IAEnC,+DAA+D;IAC/D,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAEhC,mEAAmE;IACnE,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAE9B,2DAA2D;IAC3D,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAEhC,+BAA+B;IAC/B,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAExB,qCAAqC;IACrC,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf,8CAA8C;IAC9C,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB,iDAAiD;IACjD,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAE3B,gDAAgD;IAChD,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B,8EAA8E;IAC9E,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAE5B,iEAAiE;IACjE,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAE5B,0DAA0D;IAC1D,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB,iEAAiE;IACjE,aAAa,CAAC,EAAE,aAAa,CAAC;IAE9B,kEAAkE;IAClE,aAAa,CAAC,EAAE,OAAO,CAAC;IAExB,mEAAmE;IACnE,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAM9B,qEAAqE;IACrE,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB,gDAAgD;IAChD,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAE/B,qEAAqE;IACrE,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAE9B,gDAAgD;IAChD,gBAAgB,CAAC,EAAE,eAAe,GAAG,iBAAiB,GAAG,mBAAmB,CAAC;IAE7E,oEAAoE;IACpE,wBAAwB,CAAC,EAAE,MAAM,CAAC;CACnC"}
|
package/dist/verifier/hash.d.ts
CHANGED
|
@@ -1,28 +1,29 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Hash utilities for evidence record integrity.
|
|
3
3
|
*
|
|
4
|
-
* Uses SHA-256 via
|
|
4
|
+
* Uses SHA-256 via Node's crypto module (ESM import).
|
|
5
5
|
* Canonical serialization ensures deterministic hashing.
|
|
6
6
|
*/
|
|
7
|
-
import type { EvidenceEntry } from "../schema/evidence-record";
|
|
7
|
+
import type { EvidenceEntry } from "../schema/evidence-record.js";
|
|
8
8
|
/**
|
|
9
9
|
* Canonical JSON serialization — sorted keys, no whitespace.
|
|
10
|
-
*
|
|
11
|
-
*
|
|
10
|
+
* Matches Python's json.dumps(sort_keys=True, separators=(",",":"), default=str).
|
|
11
|
+
*
|
|
12
|
+
* Key difference from bare JSON.stringify: Python writes 1.0 for integer-valued
|
|
13
|
+
* floats while JS writes 1. We match Python by post-processing the known numeric fields.
|
|
12
14
|
*/
|
|
13
15
|
export declare function canonicalize(obj: unknown): string;
|
|
14
16
|
/**
|
|
15
17
|
* Compute SHA-256 hash of a string.
|
|
16
|
-
* Works in both browser (Web Crypto) and Node (crypto module).
|
|
17
18
|
*/
|
|
18
19
|
export declare function sha256(input: string): string;
|
|
19
20
|
/**
|
|
20
21
|
* Compute the canonical hash of an evidence entry.
|
|
21
|
-
*
|
|
22
|
+
* Every field must be explicitly null (not undefined) to match Python.
|
|
22
23
|
*/
|
|
23
24
|
export declare function hashEntry(entry: EvidenceEntry): string;
|
|
24
25
|
/**
|
|
25
|
-
* Compute a hash over a canonical string
|
|
26
|
+
* Compute a hash over a canonical string.
|
|
26
27
|
*/
|
|
27
28
|
export declare function hashCanonical(input: string): string;
|
|
28
29
|
//# sourceMappingURL=hash.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hash.d.ts","sourceRoot":"","sources":["../../src/verifier/hash.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;
|
|
1
|
+
{"version":3,"file":"hash.d.ts","sourceRoot":"","sources":["../../src/verifier/hash.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAElE;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAcjD;AAED;;GAEG;AACH,wBAAgB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE5C;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,CAetD;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEnD"}
|
package/dist/verifier/hash.js
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Hash utilities for evidence record integrity.
|
|
3
3
|
*
|
|
4
|
-
* Uses SHA-256 via
|
|
4
|
+
* Uses SHA-256 via Node's crypto module (ESM import).
|
|
5
5
|
* Canonical serialization ensures deterministic hashing.
|
|
6
6
|
*/
|
|
7
|
+
import { createHash } from "crypto";
|
|
7
8
|
/**
|
|
8
9
|
* Canonical JSON serialization — sorted keys, no whitespace.
|
|
9
|
-
*
|
|
10
|
-
*
|
|
10
|
+
* Matches Python's json.dumps(sort_keys=True, separators=(",",":"), default=str).
|
|
11
|
+
*
|
|
12
|
+
* Key difference from bare JSON.stringify: Python writes 1.0 for integer-valued
|
|
13
|
+
* floats while JS writes 1. We match Python by post-processing the known numeric fields.
|
|
11
14
|
*/
|
|
12
15
|
export function canonicalize(obj) {
|
|
13
|
-
|
|
16
|
+
const raw = JSON.stringify(obj, (_, value) => {
|
|
14
17
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
15
18
|
return Object.keys(value).sort().reduce((sorted, key) => {
|
|
16
19
|
sorted[key] = value[key];
|
|
@@ -19,46 +22,29 @@ export function canonicalize(obj) {
|
|
|
19
22
|
}
|
|
20
23
|
return value;
|
|
21
24
|
});
|
|
25
|
+
// Match Python's number formatting: integer-valued floats get .0
|
|
26
|
+
// Python: json.dumps(1.0) -> "1.0", JSON.stringify(1.0) -> "1"
|
|
27
|
+
return raw.replace(/"(evidentialWeight|priorPosterior|updatedPosterior|score|difficulty|classifierConfidence)":([-]?\d+)(?=[,}\]])/g, (_, key, num) => `"${key}":${num}.0`);
|
|
22
28
|
}
|
|
23
29
|
/**
|
|
24
30
|
* Compute SHA-256 hash of a string.
|
|
25
|
-
* Works in both browser (Web Crypto) and Node (crypto module).
|
|
26
31
|
*/
|
|
27
32
|
export function sha256(input) {
|
|
28
|
-
|
|
29
|
-
try {
|
|
30
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
31
|
-
const crypto = require("crypto");
|
|
32
|
-
return crypto.createHash("sha256").update(input).digest("hex");
|
|
33
|
-
}
|
|
34
|
-
catch {
|
|
35
|
-
// Browser path — synchronous fallback using a simple hash
|
|
36
|
-
// Note: in production, use Web Crypto API (async) for true SHA-256
|
|
37
|
-
return simpleHash(input);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
/** Simple non-cryptographic hash for browser environments without crypto. */
|
|
41
|
-
function simpleHash(str) {
|
|
42
|
-
let hash = 0;
|
|
43
|
-
for (let i = 0; i < str.length; i++) {
|
|
44
|
-
const char = str.charCodeAt(i);
|
|
45
|
-
hash = ((hash << 5) - hash + char) | 0;
|
|
46
|
-
}
|
|
47
|
-
return Math.abs(hash).toString(16).padStart(8, "0");
|
|
33
|
+
return createHash("sha256").update(input).digest("hex");
|
|
48
34
|
}
|
|
49
35
|
/**
|
|
50
36
|
* Compute the canonical hash of an evidence entry.
|
|
51
|
-
*
|
|
37
|
+
* Every field must be explicitly null (not undefined) to match Python.
|
|
52
38
|
*/
|
|
53
39
|
export function hashEntry(entry) {
|
|
54
40
|
const hashable = {
|
|
55
41
|
version: entry.version,
|
|
56
42
|
timestamp: entry.timestamp,
|
|
57
43
|
event: entry.event,
|
|
58
|
-
scaffoldType: entry.scaffoldType,
|
|
59
|
-
depthLevel: entry.depthLevel,
|
|
60
|
-
epistemicMode: entry.epistemicMode,
|
|
61
|
-
triageResult: entry.triageResult,
|
|
44
|
+
scaffoldType: entry.scaffoldType ?? null,
|
|
45
|
+
depthLevel: entry.depthLevel ?? null,
|
|
46
|
+
epistemicMode: entry.epistemicMode ?? null,
|
|
47
|
+
triageResult: entry.triageResult ?? null,
|
|
62
48
|
evidentialWeight: entry.evidentialWeight,
|
|
63
49
|
priorPosterior: entry.priorPosterior,
|
|
64
50
|
updatedPosterior: entry.updatedPosterior,
|
|
@@ -67,7 +53,7 @@ export function hashEntry(entry) {
|
|
|
67
53
|
return sha256(canonicalize(hashable));
|
|
68
54
|
}
|
|
69
55
|
/**
|
|
70
|
-
* Compute a hash over a canonical string
|
|
56
|
+
* Compute a hash over a canonical string.
|
|
71
57
|
*/
|
|
72
58
|
export function hashCanonical(input) {
|
|
73
59
|
return sha256(input);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hash.js","sourceRoot":"","sources":["../../src/verifier/hash.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;
|
|
1
|
+
{"version":3,"file":"hash.js","sourceRoot":"","sources":["../../src/verifier/hash.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAGpC;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAC,GAAY;IACvC,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE;QAC3C,IAAI,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YAChE,OAAO,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC,MAAM,CAA0B,CAAC,MAAM,EAAE,GAAG,EAAE,EAAE;gBAC/E,MAAM,CAAC,GAAG,CAAC,GAAI,KAAiC,CAAC,GAAG,CAAC,CAAC;gBACtD,OAAO,MAAM,CAAC;YAChB,CAAC,EAAE,EAAE,CAAC,CAAC;QACT,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC,CAAC,CAAC;IACH,iEAAiE;IACjE,+DAA+D;IAC/D,OAAO,GAAG,CAAC,OAAO,CAAC,iHAAiH,EAClI,CAAC,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE,CAAC,IAAI,GAAG,KAAK,GAAG,IAAI,CAAC,CAAC;AAC1C,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,MAAM,CAAC,KAAa;IAClC,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC1D,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,SAAS,CAAC,KAAoB;IAC5C,MAAM,QAAQ,GAAG;QACf,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,YAAY,EAAE,KAAK,CAAC,YAAY,IAAI,IAAI;QACxC,UAAU,EAAE,KAAK,CAAC,UAAU,IAAI,IAAI;QACpC,aAAa,EAAE,KAAK,CAAC,aAAa,IAAI,IAAI;QAC1C,YAAY,EAAE,KAAK,CAAC,YAAY,IAAI,IAAI;QACxC,gBAAgB,EAAE,KAAK,CAAC,gBAAgB;QACxC,cAAc,EAAE,KAAK,CAAC,cAAc;QACpC,gBAAgB,EAAE,KAAK,CAAC,gBAAgB;QACxC,uBAAuB,EAAE,KAAK,CAAC,uBAAuB,IAAI,IAAI;KAC/D,CAAC;IACF,OAAO,MAAM,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC;AACxC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,aAAa,CAAC,KAAa;IACzC,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;AACvB,CAAC"}
|
package/dist/verifier/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { verifyRecord } from "./verify-record";
|
|
2
|
-
export type { VerificationResult, Violation } from "./verify-record";
|
|
3
|
-
export { replayToVersion, posteriorAtVersion, summarizeToVersion } from "./replay";
|
|
4
|
-
export { hashEntry, hashCanonical, canonicalize, sha256 } from "./hash";
|
|
1
|
+
export { verifyRecord } from "./verify-record.js";
|
|
2
|
+
export type { VerificationResult, Violation } from "./verify-record.js";
|
|
3
|
+
export { replayToVersion, posteriorAtVersion, summarizeToVersion } from "./replay.js";
|
|
4
|
+
export { hashEntry, hashCanonical, canonicalize, sha256 } from "./hash.js";
|
|
5
5
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/verifier/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/verifier/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,YAAY,EAAE,kBAAkB,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AACxE,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACtF,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC"}
|
package/dist/verifier/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { verifyRecord } from "./verify-record";
|
|
2
|
-
export { replayToVersion, posteriorAtVersion, summarizeToVersion } from "./replay";
|
|
3
|
-
export { hashEntry, hashCanonical, canonicalize, sha256 } from "./hash";
|
|
1
|
+
export { verifyRecord } from "./verify-record.js";
|
|
2
|
+
export { replayToVersion, posteriorAtVersion, summarizeToVersion } from "./replay.js";
|
|
3
|
+
export { hashEntry, hashCanonical, canonicalize, sha256 } from "./hash.js";
|
|
4
4
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/verifier/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/verifier/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACtF,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC"}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* This is pure bookkeeping: it slices the entry array to version N
|
|
5
5
|
* and returns the logged values. It does NOT recompute any posteriors.
|
|
6
6
|
*/
|
|
7
|
-
import type { EvidenceRecord, EvidenceEntry } from "../schema/evidence-record";
|
|
7
|
+
import type { EvidenceRecord, EvidenceEntry } from "../schema/evidence-record.js";
|
|
8
8
|
/**
|
|
9
9
|
* Replay an evidence record to a specific version.
|
|
10
10
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"replay.d.ts","sourceRoot":"","sources":["../../src/verifier/replay.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"replay.d.ts","sourceRoot":"","sources":["../../src/verifier/replay.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAElF;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,cAAc,EACtB,aAAa,EAAE,MAAM,GACpB,aAAa,EAAE,CAEjB;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,cAAc,EACtB,aAAa,EAAE,MAAM,GACpB,MAAM,GAAG,IAAI,CAIf;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,cAAc,EACtB,aAAa,EAAE,MAAM,GACpB;IACD,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,oBAAoB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7C,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC5C,CAkBA"}
|
|
@@ -2,47 +2,21 @@
|
|
|
2
2
|
* Evidence Record Verifier
|
|
3
3
|
*
|
|
4
4
|
* Verifies the bookkeeping integrity of an evidence record WITHOUT
|
|
5
|
-
* containing any estimation mathematics.
|
|
6
|
-
* "verify instead of trust."
|
|
5
|
+
* containing any estimation mathematics.
|
|
7
6
|
*
|
|
8
|
-
*
|
|
9
|
-
* - Schema validity (required fields present, correct types)
|
|
10
|
-
* - Strictly monotonic version numbers
|
|
11
|
-
* - Timestamp monotonicity (with configurable tolerance)
|
|
12
|
-
* - Hash-chain integrity (prevHash linkage, entryHash recompute)
|
|
13
|
-
* - Posterior chain consistency (each priorPosterior = previous updatedPosterior)
|
|
14
|
-
* - Compaction-boundary integrity
|
|
15
|
-
* - Enum validity (all enum values are from the public vocabulary)
|
|
16
|
-
*
|
|
17
|
-
* What it MUST NOT do (enforced by test):
|
|
18
|
-
* - Never recompute a posterior
|
|
19
|
-
* - Never contain an update function
|
|
20
|
-
* - Never embed a weight or threshold
|
|
21
|
-
* - Never import from any estimation module
|
|
7
|
+
* verify makes no network calls. Records never leave your machine.
|
|
22
8
|
*/
|
|
23
|
-
import type { EvidenceRecord } from "../schema/evidence-record";
|
|
9
|
+
import type { EvidenceRecord } from "../schema/evidence-record.js";
|
|
24
10
|
export interface Violation {
|
|
25
|
-
/** Entry version where the violation was found, or 0 for record-level. */
|
|
26
11
|
version: number;
|
|
27
|
-
/** Category of the violation. */
|
|
28
12
|
category: "schema" | "version" | "timestamp" | "hash" | "posterior" | "compaction" | "enum";
|
|
29
|
-
/** Human-readable description. */
|
|
30
13
|
message: string;
|
|
14
|
+
checkId: string;
|
|
31
15
|
}
|
|
32
16
|
export interface VerificationResult {
|
|
33
|
-
/** Whether the record passed all checks. */
|
|
34
17
|
valid: boolean;
|
|
35
|
-
/** List of violations found. */
|
|
36
18
|
violations: Violation[];
|
|
37
|
-
/** Number of entries checked. */
|
|
38
19
|
entriesChecked: number;
|
|
39
20
|
}
|
|
40
|
-
/**
|
|
41
|
-
* Verify the integrity of an evidence record.
|
|
42
|
-
*
|
|
43
|
-
* @param record The evidence record to verify.
|
|
44
|
-
* @param timestampToleranceMs Maximum allowed backward timestamp drift (default: 1000ms).
|
|
45
|
-
* @returns Verification result with any violations found.
|
|
46
|
-
*/
|
|
47
21
|
export declare function verifyRecord(record: EvidenceRecord, timestampToleranceMs?: number): VerificationResult;
|
|
48
22
|
//# sourceMappingURL=verify-record.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"verify-record.d.ts","sourceRoot":"","sources":["../../src/verifier/verify-record.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"verify-record.d.ts","sourceRoot":"","sources":["../../src/verifier/verify-record.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAiB,MAAM,8BAA8B,CAAC;AAIlF,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,QAAQ,GAAG,SAAS,GAAG,WAAW,GAAG,MAAM,GAAG,WAAW,GAAG,YAAY,GAAG,MAAM,CAAC;IAC5F,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,OAAO,CAAC;IACf,UAAU,EAAE,SAAS,EAAE,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;CACxB;AAYD,wBAAgB,YAAY,CAC1B,MAAM,EAAE,cAAc,EACtB,oBAAoB,GAAE,MAAa,GAClC,kBAAkB,CAwHpB"}
|
|
@@ -2,148 +2,113 @@
|
|
|
2
2
|
* Evidence Record Verifier
|
|
3
3
|
*
|
|
4
4
|
* Verifies the bookkeeping integrity of an evidence record WITHOUT
|
|
5
|
-
* containing any estimation mathematics.
|
|
6
|
-
* "verify instead of trust."
|
|
5
|
+
* containing any estimation mathematics.
|
|
7
6
|
*
|
|
8
|
-
*
|
|
9
|
-
* - Schema validity (required fields present, correct types)
|
|
10
|
-
* - Strictly monotonic version numbers
|
|
11
|
-
* - Timestamp monotonicity (with configurable tolerance)
|
|
12
|
-
* - Hash-chain integrity (prevHash linkage, entryHash recompute)
|
|
13
|
-
* - Posterior chain consistency (each priorPosterior = previous updatedPosterior)
|
|
14
|
-
* - Compaction-boundary integrity
|
|
15
|
-
* - Enum validity (all enum values are from the public vocabulary)
|
|
16
|
-
*
|
|
17
|
-
* What it MUST NOT do (enforced by test):
|
|
18
|
-
* - Never recompute a posterior
|
|
19
|
-
* - Never contain an update function
|
|
20
|
-
* - Never embed a weight or threshold
|
|
21
|
-
* - Never import from any estimation module
|
|
7
|
+
* verify makes no network calls. Records never leave your machine.
|
|
22
8
|
*/
|
|
23
|
-
import { hashEntry } from "./hash";
|
|
9
|
+
import { hashEntry } from "./hash.js";
|
|
24
10
|
const VALID_SCAFFOLD_TYPES = ["none", "socratic", "probing", "metacognitive", "scaffolding", "explain", "hint", "demonstrate"];
|
|
25
11
|
const VALID_DEPTH_LEVELS = ["surface", "conceptual", "transfer", "integration"];
|
|
26
12
|
const VALID_EPISTEMIC_MODES = ["experience", "inference", "analogy", "testimony"];
|
|
27
13
|
const VALID_TRIAGE_RESULTS = ["correct", "slip", "misconception", "disengagement", "ambiguous"];
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
* @param timestampToleranceMs Maximum allowed backward timestamp drift (default: 1000ms).
|
|
33
|
-
* @returns Verification result with any violations found.
|
|
34
|
-
*/
|
|
14
|
+
function isValidISO8601(ts) {
|
|
15
|
+
const d = new Date(ts);
|
|
16
|
+
return !isNaN(d.getTime()) && ts.length > 0;
|
|
17
|
+
}
|
|
35
18
|
export function verifyRecord(record, timestampToleranceMs = 1000) {
|
|
36
19
|
const violations = [];
|
|
37
|
-
// Record-level schema checks
|
|
38
20
|
if (!record.studentScopeId || typeof record.studentScopeId !== "string") {
|
|
39
|
-
violations.push({ version: 0, category: "schema", message: "Missing or invalid studentScopeId" });
|
|
21
|
+
violations.push({ version: 0, category: "schema", message: "Missing or invalid studentScopeId", checkId: "SCHEMA.REQUIRED" });
|
|
40
22
|
}
|
|
41
23
|
if (!Array.isArray(record.entries)) {
|
|
42
|
-
violations.push({ version: 0, category: "schema", message: "entries must be an array" });
|
|
24
|
+
violations.push({ version: 0, category: "schema", message: "entries must be an array", checkId: "SCHEMA.REQUIRED" });
|
|
43
25
|
return { valid: false, violations, entriesChecked: 0 };
|
|
44
26
|
}
|
|
45
|
-
// Compaction boundary
|
|
27
|
+
// Compaction boundary
|
|
46
28
|
if (record.compactedBefore) {
|
|
47
29
|
if (typeof record.compactedBefore.version !== "number") {
|
|
48
|
-
violations.push({ version: 0, category: "compaction", message: "compactedBefore.version must be a number" });
|
|
30
|
+
violations.push({ version: 0, category: "compaction", message: "compactedBefore.version must be a number", checkId: "COMPACTION.BOUNDARY" });
|
|
49
31
|
}
|
|
50
32
|
if (typeof record.compactedBefore.summaryHash !== "string" || !record.compactedBefore.summaryHash) {
|
|
51
|
-
violations.push({ version: 0, category: "compaction", message: "compactedBefore.summaryHash must be a non-empty string" });
|
|
33
|
+
violations.push({ version: 0, category: "compaction", message: "compactedBefore.summaryHash must be a non-empty string", checkId: "COMPACTION.BOUNDARY" });
|
|
52
34
|
}
|
|
53
|
-
// First entry version must be > compaction boundary
|
|
54
35
|
if (record.entries.length > 0 && record.entries[0].version <= record.compactedBefore.version) {
|
|
55
36
|
violations.push({
|
|
56
|
-
version: record.entries[0].version,
|
|
57
|
-
category: "compaction",
|
|
37
|
+
version: record.entries[0].version, category: "compaction",
|
|
58
38
|
message: `First entry version (${record.entries[0].version}) must be > compaction boundary (${record.compactedBefore.version})`,
|
|
39
|
+
checkId: "COMPACTION.BOUNDARY",
|
|
59
40
|
});
|
|
60
41
|
}
|
|
61
42
|
}
|
|
62
43
|
let prevVersion = record.compactedBefore?.version ?? 0;
|
|
63
44
|
let prevTimestamp = "";
|
|
64
45
|
let prevHash = record.compactedBefore?.summaryHash ?? "";
|
|
65
|
-
// Track last updatedPosterior per scope for posterior chain check
|
|
66
46
|
let lastUpdatedPosterior = null;
|
|
67
|
-
for (
|
|
68
|
-
const entry = record.entries[i];
|
|
47
|
+
for (const entry of record.entries) {
|
|
69
48
|
const v = entry.version ?? -1;
|
|
70
|
-
//
|
|
49
|
+
// SCHEMA.REQUIRED
|
|
71
50
|
if (typeof v !== "number" || v < 1) {
|
|
72
|
-
violations.push({ version: v, category: "schema", message: "version must be a positive integer" });
|
|
51
|
+
violations.push({ version: v, category: "schema", message: "version must be a positive integer", checkId: "SCHEMA.REQUIRED" });
|
|
73
52
|
}
|
|
74
53
|
if (typeof entry.timestamp !== "string" || !entry.timestamp) {
|
|
75
|
-
violations.push({ version: v, category: "schema", message: "timestamp is required" });
|
|
76
|
-
}
|
|
77
|
-
if (entry.event === undefined || entry.event === null) {
|
|
78
|
-
violations.push({ version: v, category: "schema", message: "event is required" });
|
|
54
|
+
violations.push({ version: v, category: "schema", message: "timestamp is required", checkId: "SCHEMA.REQUIRED" });
|
|
79
55
|
}
|
|
56
|
+
// SCHEMA.TYPES
|
|
80
57
|
if (typeof entry.evidentialWeight !== "number") {
|
|
81
|
-
violations.push({ version: v, category: "schema", message: "evidentialWeight must be a number" });
|
|
58
|
+
violations.push({ version: v, category: "schema", message: "evidentialWeight must be a number", checkId: "SCHEMA.TYPES" });
|
|
82
59
|
}
|
|
83
60
|
if (typeof entry.priorPosterior !== "number") {
|
|
84
|
-
violations.push({ version: v, category: "schema", message: "priorPosterior must be a number" });
|
|
61
|
+
violations.push({ version: v, category: "schema", message: "priorPosterior must be a number", checkId: "SCHEMA.TYPES" });
|
|
85
62
|
}
|
|
86
63
|
if (typeof entry.updatedPosterior !== "number") {
|
|
87
|
-
violations.push({ version: v, category: "schema", message: "updatedPosterior must be a number" });
|
|
64
|
+
violations.push({ version: v, category: "schema", message: "updatedPosterior must be a number", checkId: "SCHEMA.TYPES" });
|
|
88
65
|
}
|
|
89
|
-
//
|
|
66
|
+
// SCHEMA.TIMESTAMP_FORMAT
|
|
67
|
+
if (typeof entry.timestamp === "string" && entry.timestamp && !isValidISO8601(entry.timestamp)) {
|
|
68
|
+
violations.push({ version: v, category: "timestamp", message: `Timestamp is not valid ISO 8601: "${entry.timestamp}"`, checkId: "SCHEMA.TIMESTAMP_FORMAT" });
|
|
69
|
+
}
|
|
70
|
+
// VERSION.MONOTONIC
|
|
90
71
|
if (v <= prevVersion) {
|
|
91
|
-
violations.push({
|
|
92
|
-
version: v,
|
|
93
|
-
category: "version",
|
|
94
|
-
message: `Version ${v} is not strictly greater than previous ${prevVersion}`,
|
|
95
|
-
});
|
|
72
|
+
violations.push({ version: v, category: "version", message: `Version ${v} <= previous ${prevVersion}`, checkId: "VERSION.MONOTONIC" });
|
|
96
73
|
}
|
|
97
|
-
//
|
|
74
|
+
// TIMESTAMP.MONOTONIC
|
|
98
75
|
if (prevTimestamp && entry.timestamp) {
|
|
99
76
|
const prevMs = new Date(prevTimestamp).getTime();
|
|
100
77
|
const currMs = new Date(entry.timestamp).getTime();
|
|
101
78
|
if (!isNaN(prevMs) && !isNaN(currMs) && currMs < prevMs - timestampToleranceMs) {
|
|
102
|
-
violations.push({
|
|
103
|
-
version: v,
|
|
104
|
-
category: "timestamp",
|
|
105
|
-
message: `Timestamp goes backward: ${entry.timestamp} < ${prevTimestamp} (tolerance: ${timestampToleranceMs}ms)`,
|
|
106
|
-
});
|
|
79
|
+
violations.push({ version: v, category: "timestamp", message: "Timestamp goes backward", checkId: "TIMESTAMP.MONOTONIC" });
|
|
107
80
|
}
|
|
108
81
|
}
|
|
109
|
-
//
|
|
82
|
+
// ENUM.VALID
|
|
110
83
|
if (entry.scaffoldType && !VALID_SCAFFOLD_TYPES.includes(entry.scaffoldType)) {
|
|
111
|
-
violations.push({ version: v, category: "enum", message: `Invalid scaffoldType: ${entry.scaffoldType}
|
|
84
|
+
violations.push({ version: v, category: "enum", message: `Invalid scaffoldType: ${entry.scaffoldType}`, checkId: "ENUM.VALID" });
|
|
112
85
|
}
|
|
113
86
|
if (entry.depthLevel && !VALID_DEPTH_LEVELS.includes(entry.depthLevel)) {
|
|
114
|
-
violations.push({ version: v, category: "enum", message: `Invalid depthLevel: ${entry.depthLevel}
|
|
87
|
+
violations.push({ version: v, category: "enum", message: `Invalid depthLevel: ${entry.depthLevel}`, checkId: "ENUM.VALID" });
|
|
115
88
|
}
|
|
116
89
|
if (entry.epistemicMode && !VALID_EPISTEMIC_MODES.includes(entry.epistemicMode)) {
|
|
117
|
-
violations.push({ version: v, category: "enum", message: `Invalid epistemicMode: ${entry.epistemicMode}
|
|
90
|
+
violations.push({ version: v, category: "enum", message: `Invalid epistemicMode: ${entry.epistemicMode}`, checkId: "ENUM.VALID" });
|
|
118
91
|
}
|
|
119
92
|
if (entry.triageResult && !VALID_TRIAGE_RESULTS.includes(entry.triageResult)) {
|
|
120
|
-
violations.push({ version: v, category: "enum", message: `Invalid triageResult: ${entry.triageResult}
|
|
93
|
+
violations.push({ version: v, category: "enum", message: `Invalid triageResult: ${entry.triageResult}`, checkId: "ENUM.VALID" });
|
|
121
94
|
}
|
|
122
|
-
//
|
|
95
|
+
// HASH.PREV_LINK
|
|
123
96
|
if (entry.prevHash !== prevHash) {
|
|
124
|
-
violations.push({
|
|
125
|
-
version: v,
|
|
126
|
-
category: "hash",
|
|
127
|
-
message: `prevHash mismatch: expected "${prevHash}", got "${entry.prevHash}"`,
|
|
128
|
-
});
|
|
97
|
+
violations.push({ version: v, category: "hash", message: "prevHash mismatch", checkId: "HASH.PREV_LINK" });
|
|
129
98
|
}
|
|
99
|
+
// HASH.ENTRY_RECOMPUTE
|
|
130
100
|
const computedHash = hashEntry(entry);
|
|
131
101
|
if (entry.entryHash !== computedHash) {
|
|
132
|
-
violations.push({
|
|
133
|
-
version: v,
|
|
134
|
-
category: "hash",
|
|
135
|
-
message: `entryHash mismatch: expected "${computedHash}", got "${entry.entryHash}"`,
|
|
136
|
-
});
|
|
102
|
+
violations.push({ version: v, category: "hash", message: "entryHash mismatch", checkId: "HASH.ENTRY_RECOMPUTE" });
|
|
137
103
|
}
|
|
138
|
-
//
|
|
139
|
-
// Each entry's priorPosterior should equal the previous entry's updatedPosterior
|
|
104
|
+
// POSTERIOR.CHAIN
|
|
140
105
|
if (lastUpdatedPosterior !== null && typeof entry.priorPosterior === "number") {
|
|
141
106
|
const diff = Math.abs(entry.priorPosterior - lastUpdatedPosterior);
|
|
142
107
|
if (diff > 1e-9) {
|
|
143
108
|
violations.push({
|
|
144
|
-
version: v,
|
|
145
|
-
|
|
146
|
-
|
|
109
|
+
version: v, category: "posterior",
|
|
110
|
+
message: `priorPosterior (${entry.priorPosterior}) != previous updatedPosterior (${lastUpdatedPosterior})`,
|
|
111
|
+
checkId: "POSTERIOR.CHAIN",
|
|
147
112
|
});
|
|
148
113
|
}
|
|
149
114
|
}
|
|
@@ -154,10 +119,6 @@ export function verifyRecord(record, timestampToleranceMs = 1000) {
|
|
|
154
119
|
lastUpdatedPosterior = entry.updatedPosterior;
|
|
155
120
|
}
|
|
156
121
|
}
|
|
157
|
-
return {
|
|
158
|
-
valid: violations.length === 0,
|
|
159
|
-
violations,
|
|
160
|
-
entriesChecked: record.entries.length,
|
|
161
|
-
};
|
|
122
|
+
return { valid: violations.length === 0, violations, entriesChecked: record.entries.length };
|
|
162
123
|
}
|
|
163
124
|
//# sourceMappingURL=verify-record.js.map
|