@actuarial-ts/core 0.6.1 → 0.7.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 +11 -2
- package/dist/canonical.d.ts +11 -0
- package/dist/canonical.d.ts.map +1 -1
- package/dist/canonical.js +136 -26
- package/dist/canonical.js.map +1 -1
- package/dist/diagnosticEvidenceIntern.d.ts +16 -0
- package/dist/diagnosticEvidenceIntern.d.ts.map +1 -0
- package/dist/diagnosticEvidenceIntern.js +139 -0
- package/dist/diagnosticEvidenceIntern.js.map +1 -0
- package/dist/diagnosticExposure.d.ts.map +1 -1
- package/dist/diagnosticExposure.js +97 -7
- package/dist/diagnosticExposure.js.map +1 -1
- package/dist/diagnosticIdentity.d.ts.map +1 -1
- package/dist/diagnosticIdentity.js +6 -3
- package/dist/diagnosticIdentity.js.map +1 -1
- package/dist/diagnosticIdentityStream.d.ts +56 -0
- package/dist/diagnosticIdentityStream.d.ts.map +1 -0
- package/dist/diagnosticIdentityStream.js +472 -0
- package/dist/diagnosticIdentityStream.js.map +1 -0
- package/dist/diagnosticPreparation.d.ts +28 -0
- package/dist/diagnosticPreparation.d.ts.map +1 -1
- package/dist/diagnosticPreparation.js +160 -36
- package/dist/diagnosticPreparation.js.map +1 -1
- package/dist/diagnosticReview.d.ts +4 -1
- package/dist/diagnosticReview.d.ts.map +1 -1
- package/dist/diagnosticReview.js +203 -51
- package/dist/diagnosticReview.js.map +1 -1
- package/dist/diagnosticReviewSources.d.ts +18 -0
- package/dist/diagnosticReviewSources.d.ts.map +1 -0
- package/dist/diagnosticReviewSources.js +76 -0
- package/dist/diagnosticReviewSources.js.map +1 -0
- package/dist/diagnosticReviewStore.d.ts +88 -0
- package/dist/diagnosticReviewStore.d.ts.map +1 -0
- package/dist/diagnosticReviewStore.js +455 -0
- package/dist/diagnosticReviewStore.js.map +1 -0
- package/dist/diagnosticRunner.d.ts +25 -1
- package/dist/diagnosticRunner.d.ts.map +1 -1
- package/dist/diagnosticRunner.js +115 -7
- package/dist/diagnosticRunner.js.map +1 -1
- package/dist/fnvAccumulator.d.ts +6 -0
- package/dist/fnvAccumulator.d.ts.map +1 -0
- package/dist/fnvAccumulator.js +21 -0
- package/dist/fnvAccumulator.js.map +1 -0
- package/dist/index.d.ts +8 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -3
- package/dist/index.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/src/canonical.ts +144 -26
- package/src/diagnosticEvidenceIntern.ts +151 -0
- package/src/diagnosticExposure.ts +126 -17
- package/src/diagnosticIdentity.ts +15 -6
- package/src/diagnosticIdentityStream.ts +553 -0
- package/src/diagnosticPreparation.ts +268 -84
- package/src/diagnosticReview.ts +293 -85
- package/src/diagnosticReviewSources.ts +85 -0
- package/src/diagnosticReviewStore.ts +692 -0
- package/src/diagnosticRunner.ts +206 -9
- package/src/fnvAccumulator.ts +20 -0
- package/src/index.ts +38 -1
- package/src/version.ts +1 -1
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ The package is designed to support the actuary's compliance with the ASOPs; it d
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
npm install @actuarial-ts/core@0.
|
|
10
|
+
npm install @actuarial-ts/core@0.7.0
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
ESM, TypeScript-first, zero runtime dependencies, Node 20+.
|
|
@@ -77,9 +77,18 @@ The six built-in formulas are basis-independent. The factory creates ten count i
|
|
|
77
77
|
|
|
78
78
|
All metrics are ratio-of-sums: measures are aggregated at source-group/origin/valuation, groups are mapped and merged, then division happens once. Measure-local `missing: "unknown" | "zero"` is explicit. Exposure timing is either `origin-static` or `valuation-specific`. Calendar and ordered axes derive normalized origins, valuations, development ages, and units; input rows cannot assert a trusted age.
|
|
79
79
|
|
|
80
|
+
Exposure reconciliation accepts at most 250,000 observations per call. Collection
|
|
81
|
+
capacity is independent of optional source-file/sheet/cell metadata: each record
|
|
82
|
+
retains the JSON depth (256 including the outer array) and one-million-node
|
|
83
|
+
resource guards, while the owned result is frozen by observation/cohort rather
|
|
84
|
+
than rechecked against a single collection-wide JSON node budget. Malformed
|
|
85
|
+
arrays, accessors, non-JSON metadata, and cycles still produce typed validation
|
|
86
|
+
errors. Nonfinite exposure values remain supported audited inputs, not usable
|
|
87
|
+
numeric exposure values. This is a bounded capacity, not an unlimited scale claim.
|
|
88
|
+
|
|
80
89
|
Compilation validates the whole graph atomically: IDs, sources, role types, compatibility groups, development semantics, derivation acyclicity, expression limits, rule operands, basis/population references, and period coordinates. Authentic compiled/prepared objects are owner-branded and frozen. Formula, calculation, definition, preparation, and result identities are deterministic FNV-1a/JCS integrity aids—not cryptographic signatures.
|
|
81
90
|
|
|
82
|
-
See the generated [formula and instance catalog](https://github.com/yerromnitsuj/actng/blob/v0.
|
|
91
|
+
See the generated [formula and instance catalog](https://github.com/yerromnitsuj/actng/blob/v0.7.0/docs/reference/diagnostic-formulas.md) and [0.6 migration guide](https://github.com/yerromnitsuj/actng/blob/v0.7.0/docs/migrations/0.6-generalized-diagnostics.md).
|
|
83
92
|
|
|
84
93
|
## Main method families
|
|
85
94
|
|
package/dist/canonical.d.ts
CHANGED
|
@@ -16,4 +16,15 @@ export declare function canonicalJson(value: unknown): string;
|
|
|
16
16
|
* needing tamper evidence must sign or cryptographically hash the payload.
|
|
17
17
|
*/
|
|
18
18
|
export declare function fnv1a64(text: string): string;
|
|
19
|
+
/**
|
|
20
|
+
* The exact integrity tag `fnv1a64(canonicalJson(value))`, without constructing
|
|
21
|
+
* the complete canonical string or UTF-8 byte array. Validation, key ordering,
|
|
22
|
+
* escaping and unsupported-value error paths match `canonicalJson`.
|
|
23
|
+
*
|
|
24
|
+
* Text and byte buffers are bounded, including for a single large string/key;
|
|
25
|
+
* object-key sorting, ancestor tracking and paths still scale with input shape.
|
|
26
|
+
* The caller's input remains in memory. This is not a constant-memory dataset
|
|
27
|
+
* pipeline, a collision-resistant hash, or a substitute for exact equality.
|
|
28
|
+
*/
|
|
29
|
+
export declare function canonicalFnv1a64(value: unknown): string;
|
|
19
30
|
//# sourceMappingURL=canonical.d.ts.map
|
package/dist/canonical.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"canonical.d.ts","sourceRoot":"","sources":["../src/canonical.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"canonical.d.ts","sourceRoot":"","sources":["../src/canonical.ts"],"names":[],"mappings":"AA8GA;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAsBpD;AAiCD;;;;;;;;GAQG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAI5C;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CA0CvD"}
|
package/dist/canonical.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ReservingError } from "./types.js";
|
|
2
|
+
import { createFnvAccumulator } from "./fnvAccumulator.js";
|
|
2
3
|
/**
|
|
3
4
|
* Canonical JSON serialization and the FNV-1a integrity hash — the SDK's
|
|
4
5
|
* single equality oracle, relocated here from @actuarial-ts/compliance so
|
|
@@ -32,19 +33,25 @@ function isPlainObject(value) {
|
|
|
32
33
|
const proto = Object.getPrototypeOf(value);
|
|
33
34
|
return proto === Object.prototype || proto === null;
|
|
34
35
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
/** One traversal owns validation and property access order for both outputs. */
|
|
37
|
+
function canonicalize(value, path, seen, sink) {
|
|
38
|
+
if (value === null) {
|
|
39
|
+
sink.write("null");
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
38
42
|
switch (typeof value) {
|
|
39
43
|
case "string":
|
|
40
|
-
|
|
44
|
+
sink.quote(value);
|
|
45
|
+
return;
|
|
41
46
|
case "boolean":
|
|
42
|
-
|
|
47
|
+
sink.write(value ? "true" : "false");
|
|
48
|
+
return;
|
|
43
49
|
case "number": {
|
|
44
50
|
if (!Number.isFinite(value)) {
|
|
45
51
|
throw new ReservingError("UNSUPPORTED_VALUE", `non-finite number (${String(value)}) at ${path}`);
|
|
46
52
|
}
|
|
47
|
-
|
|
53
|
+
sink.write(Object.is(value, -0) ? "0" : String(value));
|
|
54
|
+
return;
|
|
48
55
|
}
|
|
49
56
|
case "undefined":
|
|
50
57
|
throw new ReservingError("UNSUPPORTED_VALUE", `undefined at ${path}`);
|
|
@@ -61,28 +68,33 @@ function canonicalize(value, path, seen) {
|
|
|
61
68
|
throw new ReservingError("UNSUPPORTED_VALUE", `circular reference at ${path}`);
|
|
62
69
|
}
|
|
63
70
|
seen.add(obj);
|
|
64
|
-
let out;
|
|
65
71
|
if (Array.isArray(obj)) {
|
|
66
|
-
|
|
72
|
+
sink.startContainer("[");
|
|
67
73
|
for (let i = 0; i < obj.length; i++) {
|
|
68
|
-
|
|
74
|
+
if (i > 0)
|
|
75
|
+
sink.write(",");
|
|
76
|
+
canonicalize(obj[i], `${path}[${i}]`, seen, sink);
|
|
69
77
|
}
|
|
70
|
-
|
|
78
|
+
sink.endContainer("]");
|
|
71
79
|
}
|
|
72
80
|
else if (isPlainObject(obj)) {
|
|
73
81
|
const keys = Object.keys(obj).sort();
|
|
74
|
-
|
|
75
|
-
for (
|
|
76
|
-
|
|
82
|
+
sink.startContainer("{");
|
|
83
|
+
for (let i = 0; i < keys.length; i++) {
|
|
84
|
+
if (i > 0)
|
|
85
|
+
sink.write(",");
|
|
86
|
+
const key = keys[i];
|
|
87
|
+
sink.quote(key);
|
|
88
|
+
sink.write(":");
|
|
89
|
+
canonicalize(obj[key], `${path}.${key}`, seen, sink);
|
|
77
90
|
}
|
|
78
|
-
|
|
91
|
+
sink.endContainer("}");
|
|
79
92
|
}
|
|
80
93
|
else {
|
|
81
94
|
const name = obj.constructor?.name ?? "unknown";
|
|
82
95
|
throw new ReservingError("UNSUPPORTED_VALUE", `non-plain object (${name}) at ${path}; only plain objects, arrays, and JSON primitives are canonicalizable`);
|
|
83
96
|
}
|
|
84
97
|
seen.delete(obj);
|
|
85
|
-
return out;
|
|
86
98
|
}
|
|
87
99
|
/**
|
|
88
100
|
* Deterministic JSON serialization: sorted object keys (recursively), arrays
|
|
@@ -92,11 +104,57 @@ function canonicalize(value, path, seen) {
|
|
|
92
104
|
* "$.rows[2].ultimate" — for any value JSON cannot faithfully represent.
|
|
93
105
|
*/
|
|
94
106
|
export function canonicalJson(value) {
|
|
95
|
-
|
|
107
|
+
// Join finished subtrees as before, rather than retaining a flat token array
|
|
108
|
+
// for the entire input graph alongside the final serialized string.
|
|
109
|
+
const frames = [[]];
|
|
110
|
+
const write = (text) => {
|
|
111
|
+
frames[frames.length - 1].push(text);
|
|
112
|
+
};
|
|
113
|
+
canonicalize(value, "$", new Set(), {
|
|
114
|
+
write,
|
|
115
|
+
quote(text) {
|
|
116
|
+
write(JSON.stringify(text));
|
|
117
|
+
},
|
|
118
|
+
startContainer(token) {
|
|
119
|
+
frames.push([token]);
|
|
120
|
+
},
|
|
121
|
+
endContainer(token) {
|
|
122
|
+
const parts = frames.pop();
|
|
123
|
+
parts.push(token);
|
|
124
|
+
write(parts.join(""));
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
return frames[0].join("");
|
|
128
|
+
}
|
|
129
|
+
const UTF8_CHUNK_CODE_UNITS = 16_384;
|
|
130
|
+
const QUOTED_CHUNK_CODE_UNITS = 4_096;
|
|
131
|
+
/** A valid surrogate pair must reach native escaping/encoding together. */
|
|
132
|
+
function chunkEnd(text, start, limit) {
|
|
133
|
+
let end = Math.min(start + limit, text.length);
|
|
134
|
+
const before = text.charCodeAt(end - 1);
|
|
135
|
+
const after = text.charCodeAt(end);
|
|
136
|
+
if (before >= 0xd800 && before <= 0xdbff && after >= 0xdc00 && after <= 0xdfff)
|
|
137
|
+
end -= 1;
|
|
138
|
+
return end;
|
|
139
|
+
}
|
|
140
|
+
/** Reuse at most 48 KiB for long strings instead of duplicating all UTF-8 bytes. */
|
|
141
|
+
function* utf8Chunks(text) {
|
|
142
|
+
const encoder = new TextEncoder();
|
|
143
|
+
// Retain TextEncoder's runtime coercion for untyped JavaScript callers.
|
|
144
|
+
if (typeof text !== "string" || text.length <= UTF8_CHUNK_CODE_UNITS) {
|
|
145
|
+
yield encoder.encode(text);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
// A UTF-16 code unit needs at most three UTF-8 bytes (a surrogate pair
|
|
149
|
+
// needs four bytes for two code units), so encodeInto always consumes a chunk.
|
|
150
|
+
const buffer = new Uint8Array(UTF8_CHUNK_CODE_UNITS * 3);
|
|
151
|
+
for (let start = 0; start < text.length;) {
|
|
152
|
+
const end = chunkEnd(text, start, UTF8_CHUNK_CODE_UNITS);
|
|
153
|
+
const { written } = encoder.encodeInto(text.slice(start, end), buffer);
|
|
154
|
+
yield buffer.subarray(0, written);
|
|
155
|
+
start = end;
|
|
156
|
+
}
|
|
96
157
|
}
|
|
97
|
-
const FNV_OFFSET_BASIS = 0xcbf29ce484222325n;
|
|
98
|
-
const FNV_PRIME = 0x100000001b3n;
|
|
99
|
-
const MASK_64 = 0xffffffffffffffffn;
|
|
100
158
|
/**
|
|
101
159
|
* FNV-1a 64-bit hash over the UTF-8 bytes of `text`, returned as a 16-hex-char
|
|
102
160
|
* string.
|
|
@@ -107,12 +165,64 @@ const MASK_64 = 0xffffffffffffffffn;
|
|
|
107
165
|
* needing tamper evidence must sign or cryptographically hash the payload.
|
|
108
166
|
*/
|
|
109
167
|
export function fnv1a64(text) {
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
168
|
+
const hash = createFnvAccumulator();
|
|
169
|
+
for (const bytes of utf8Chunks(text))
|
|
170
|
+
hash.update(bytes);
|
|
171
|
+
return hash.digest();
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* The exact integrity tag `fnv1a64(canonicalJson(value))`, without constructing
|
|
175
|
+
* the complete canonical string or UTF-8 byte array. Validation, key ordering,
|
|
176
|
+
* escaping and unsupported-value error paths match `canonicalJson`.
|
|
177
|
+
*
|
|
178
|
+
* Text and byte buffers are bounded, including for a single large string/key;
|
|
179
|
+
* object-key sorting, ancestor tracking and paths still scale with input shape.
|
|
180
|
+
* The caller's input remains in memory. This is not a constant-memory dataset
|
|
181
|
+
* pipeline, a collision-resistant hash, or a substitute for exact equality.
|
|
182
|
+
*/
|
|
183
|
+
export function canonicalFnv1a64(value) {
|
|
184
|
+
const hash = createFnvAccumulator();
|
|
185
|
+
const encoder = new TextEncoder();
|
|
186
|
+
const bytes = new Uint8Array(UTF8_CHUNK_CODE_UNITS * 3);
|
|
187
|
+
let buffered = "";
|
|
188
|
+
const flush = () => {
|
|
189
|
+
if (!buffered.length)
|
|
190
|
+
return;
|
|
191
|
+
// Three bytes per UTF-16 code unit always fit; pairs need only four for two.
|
|
192
|
+
const { written } = encoder.encodeInto(buffered, bytes);
|
|
193
|
+
hash.update(bytes.subarray(0, written));
|
|
194
|
+
buffered = "";
|
|
195
|
+
};
|
|
196
|
+
const write = (text) => {
|
|
197
|
+
for (let start = 0; start < text.length;) {
|
|
198
|
+
const end = chunkEnd(text, start, UTF8_CHUNK_CODE_UNITS - buffered.length);
|
|
199
|
+
if (end === start) {
|
|
200
|
+
flush();
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
buffered += text.slice(start, end);
|
|
204
|
+
start = end;
|
|
205
|
+
if (buffered.length >= UTF8_CHUNK_CODE_UNITS - 1)
|
|
206
|
+
flush();
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
canonicalize(value, "$", new Set(), {
|
|
210
|
+
write,
|
|
211
|
+
startContainer: write,
|
|
212
|
+
endContainer: write,
|
|
213
|
+
quote(text) {
|
|
214
|
+
write('"');
|
|
215
|
+
for (let start = 0; start < text.length;) {
|
|
216
|
+
const end = chunkEnd(text, start, QUOTED_CHUNK_CODE_UNITS);
|
|
217
|
+
// Bounded native escaping retains minimal JSON escape spelling and lone
|
|
218
|
+
// surrogate escapes without allocating one full escaped string atom.
|
|
219
|
+
write(JSON.stringify(text.slice(start, end)).slice(1, -1));
|
|
220
|
+
start = end;
|
|
221
|
+
}
|
|
222
|
+
write('"');
|
|
223
|
+
},
|
|
224
|
+
});
|
|
225
|
+
flush();
|
|
226
|
+
return hash.digest();
|
|
117
227
|
}
|
|
118
228
|
//# sourceMappingURL=canonical.js.map
|
package/dist/canonical.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"canonical.js","sourceRoot":"","sources":["../src/canonical.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"canonical.js","sourceRoot":"","sources":["../src/canonical.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Private, invocation-scoped sharing of small, newly owned evidence nodes.
|
|
3
|
+
* This is not a validation boundary: callers must finish normalizing/cloning
|
|
4
|
+
* every child first, and must never submit caller-owned objects or SDK brands.
|
|
5
|
+
*/
|
|
6
|
+
export interface DiagnosticEvidenceInternOptions {
|
|
7
|
+
readonly maxEntries?: number;
|
|
8
|
+
readonly maxSignatureCharacters?: number;
|
|
9
|
+
readonly maxCandidateProperties?: number;
|
|
10
|
+
}
|
|
11
|
+
type EvidenceMode = "plain" | "source" | "free";
|
|
12
|
+
export declare function createDiagnosticEvidenceInterner(options?: DiagnosticEvidenceInternOptions): {
|
|
13
|
+
internOwned<T extends object>(candidate: T, mode: EvidenceMode): T;
|
|
14
|
+
};
|
|
15
|
+
export {};
|
|
16
|
+
//# sourceMappingURL=diagnosticEvidenceIntern.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diagnosticEvidenceIntern.d.ts","sourceRoot":"","sources":["../src/diagnosticEvidenceIntern.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,WAAW,+BAA+B;IAC9C,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,sBAAsB,CAAC,EAAE,MAAM,CAAC;IACzC,QAAQ,CAAC,sBAAsB,CAAC,EAAE,MAAM,CAAC;CAC1C;AAED,KAAK,YAAY,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC;AA8BhD,wBAAgB,gCAAgC,CAC9C,OAAO,GAAE,+BAAoC,GAC5C;IACD,WAAW,CAAC,CAAC,SAAS,MAAM,EAAE,SAAS,EAAE,CAAC,EAAE,IAAI,EAAE,YAAY,GAAG,CAAC,CAAC;CACpE,CAyGA"}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
const DEFAULT_MAX_ENTRIES = 100_000;
|
|
2
|
+
const DEFAULT_MAX_SIGNATURE_CHARACTERS = 16 * 1024 * 1024;
|
|
3
|
+
// Wide result envelopes are usually unique; focus the budget on their small
|
|
4
|
+
// repeated coordinates, sources, scopes, states, and short child lists.
|
|
5
|
+
const DEFAULT_MAX_CANDIDATE_PROPERTIES = 10;
|
|
6
|
+
const MAX_CANDIDATE_STRING_CHARACTERS = 4096;
|
|
7
|
+
function sameOwnedNode(left, right) {
|
|
8
|
+
if (Object.getPrototypeOf(left) !== Object.getPrototypeOf(right))
|
|
9
|
+
return false;
|
|
10
|
+
const leftKeys = Reflect.ownKeys(left);
|
|
11
|
+
const rightKeys = Reflect.ownKeys(right);
|
|
12
|
+
if (leftKeys.length !== rightKeys.length)
|
|
13
|
+
return false;
|
|
14
|
+
return leftKeys.every((key, index) => {
|
|
15
|
+
if (key !== rightKeys[index])
|
|
16
|
+
return false;
|
|
17
|
+
const a = Object.getOwnPropertyDescriptor(left, key);
|
|
18
|
+
const b = Object.getOwnPropertyDescriptor(right, key);
|
|
19
|
+
return ("value" in a &&
|
|
20
|
+
"value" in b &&
|
|
21
|
+
a.enumerable === b.enumerable &&
|
|
22
|
+
a.configurable === b.configurable &&
|
|
23
|
+
a.writable === b.writable &&
|
|
24
|
+
Object.is(a.value, b.value));
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
export function createDiagnosticEvidenceInterner(options = {}) {
|
|
28
|
+
const maxEntries = options.maxEntries ?? DEFAULT_MAX_ENTRIES;
|
|
29
|
+
const maxSignatureCharacters = options.maxSignatureCharacters ?? DEFAULT_MAX_SIGNATURE_CHARACTERS;
|
|
30
|
+
const maxCandidateProperties = options.maxCandidateProperties ?? DEFAULT_MAX_CANDIDATE_PROPERTIES;
|
|
31
|
+
const pool = new Map();
|
|
32
|
+
const childIds = new WeakMap();
|
|
33
|
+
let nextChildId = 1;
|
|
34
|
+
let signatureCharacters = 0;
|
|
35
|
+
return {
|
|
36
|
+
internOwned(candidate, mode) {
|
|
37
|
+
if (!Object.isFrozen(candidate))
|
|
38
|
+
return candidate;
|
|
39
|
+
const prototype = Object.getPrototypeOf(candidate);
|
|
40
|
+
const array = Array.isArray(candidate);
|
|
41
|
+
if (array
|
|
42
|
+
? prototype !== Array.prototype
|
|
43
|
+
: prototype !== null && prototype !== Object.prototype)
|
|
44
|
+
return candidate;
|
|
45
|
+
if (array && candidate.length >= maxCandidateProperties)
|
|
46
|
+
return candidate;
|
|
47
|
+
const keys = Reflect.ownKeys(candidate);
|
|
48
|
+
if (keys.length > maxCandidateProperties)
|
|
49
|
+
return candidate;
|
|
50
|
+
const signatureParts = [
|
|
51
|
+
mode,
|
|
52
|
+
array ? "array" : prototype === null ? "null" : "object",
|
|
53
|
+
];
|
|
54
|
+
// A declined candidate must not grow persistent bookkeeping. Stage at
|
|
55
|
+
// most this small candidate's children, including repeated references,
|
|
56
|
+
// and commit their IDs only if the candidate enters the bounded pool.
|
|
57
|
+
let pendingChildIds;
|
|
58
|
+
let stringCharacters = 0;
|
|
59
|
+
for (const key of keys) {
|
|
60
|
+
// Diagnostic JSON does not contain symbols. Declining unexpected
|
|
61
|
+
// shapes is harmless and never invokes an accessor or caller method.
|
|
62
|
+
if (typeof key !== "string")
|
|
63
|
+
return candidate;
|
|
64
|
+
const descriptor = Object.getOwnPropertyDescriptor(candidate, key);
|
|
65
|
+
if (!("value" in descriptor))
|
|
66
|
+
return candidate;
|
|
67
|
+
const value = descriptor.value;
|
|
68
|
+
let tokenType;
|
|
69
|
+
let tokenValue;
|
|
70
|
+
if (value === null) {
|
|
71
|
+
tokenType = "null";
|
|
72
|
+
tokenValue = null;
|
|
73
|
+
}
|
|
74
|
+
else if (typeof value === "object") {
|
|
75
|
+
if (!Object.isFrozen(value))
|
|
76
|
+
return candidate;
|
|
77
|
+
tokenType = "object";
|
|
78
|
+
const existingId = childIds.get(value);
|
|
79
|
+
if (existingId !== undefined)
|
|
80
|
+
tokenValue = existingId;
|
|
81
|
+
else {
|
|
82
|
+
pendingChildIds ??= new Map();
|
|
83
|
+
let pendingId = pendingChildIds.get(value);
|
|
84
|
+
if (pendingId === undefined) {
|
|
85
|
+
pendingId = nextChildId + pendingChildIds.size;
|
|
86
|
+
pendingChildIds.set(value, pendingId);
|
|
87
|
+
}
|
|
88
|
+
tokenValue = pendingId;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
else if (typeof value === "number") {
|
|
92
|
+
if (!Number.isFinite(value))
|
|
93
|
+
return candidate;
|
|
94
|
+
tokenType = "number";
|
|
95
|
+
tokenValue = Object.is(value, -0) ? "-0" : value;
|
|
96
|
+
}
|
|
97
|
+
else if (typeof value === "string" ||
|
|
98
|
+
typeof value === "boolean" ||
|
|
99
|
+
value === undefined) {
|
|
100
|
+
tokenType = typeof value;
|
|
101
|
+
tokenValue = value;
|
|
102
|
+
if (typeof value === "string")
|
|
103
|
+
stringCharacters += value.length;
|
|
104
|
+
}
|
|
105
|
+
else
|
|
106
|
+
return candidate;
|
|
107
|
+
stringCharacters += key.length;
|
|
108
|
+
if (stringCharacters > MAX_CANDIDATE_STRING_CHARACTERS)
|
|
109
|
+
return candidate;
|
|
110
|
+
// Frozen data properties are necessarily non-configurable and
|
|
111
|
+
// non-writable. Keep the varying enumerability bit (including array
|
|
112
|
+
// length) and a fixed four-slot typed tuple, without temporary nested
|
|
113
|
+
// arrays. The independent equality check still verifies every flag.
|
|
114
|
+
signatureParts.push(key, descriptor.enumerable, tokenType, tokenValue);
|
|
115
|
+
}
|
|
116
|
+
const signature = JSON.stringify(signatureParts);
|
|
117
|
+
// An unseen child cannot occur in an existing entry: every admitted
|
|
118
|
+
// entry committed all its child IDs. No equality hit is skipped here.
|
|
119
|
+
const existing = pendingChildIds ? undefined : pool.get(signature);
|
|
120
|
+
// Signatures encode exact children and descriptor/order information;
|
|
121
|
+
// independently check equality before sharing rather than treating a
|
|
122
|
+
// fingerprint as proof of equality.
|
|
123
|
+
if (existing !== undefined && sameOwnedNode(existing, candidate))
|
|
124
|
+
return existing;
|
|
125
|
+
if (pool.size < maxEntries &&
|
|
126
|
+
signatureCharacters + signature.length <= maxSignatureCharacters) {
|
|
127
|
+
if (pendingChildIds) {
|
|
128
|
+
for (const [child, id] of pendingChildIds)
|
|
129
|
+
childIds.set(child, id);
|
|
130
|
+
nextChildId += pendingChildIds.size;
|
|
131
|
+
}
|
|
132
|
+
pool.set(signature, candidate);
|
|
133
|
+
signatureCharacters += signature.length;
|
|
134
|
+
}
|
|
135
|
+
return candidate;
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=diagnosticEvidenceIntern.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diagnosticEvidenceIntern.js","sourceRoot":"","sources":["../src/diagnosticEvidenceIntern.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"diagnosticExposure.d.ts","sourceRoot":"","sources":["../src/diagnosticExposure.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,oBAAoB,EACpB,wBAAwB,EACzB,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"diagnosticExposure.d.ts","sourceRoot":"","sources":["../src/diagnosticExposure.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,oBAAoB,EACpB,wBAAwB,EACzB,MAAM,4BAA4B,CAAC;AA8HpC,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,MAAM,CAAC,EAAE,wBAAwB,CAAC;CAC5C;AAED,MAAM,MAAM,6BAA6B,GACrC;IAAE,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACvD;IAAE,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAA;CAAE,GACpD;IACE,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAC9B,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAC;IACrB,QAAQ,CAAC,aAAa,EAAE,KAAK,GAAG,mBAAmB,GAAG,mBAAmB,CAAC;CAC3E,CAAC;AAEN,MAAM,WAAW,kCAAkC;IACjD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,6BAA6B,CAAC;IAC9C,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,MAAM,CAAC,EAAE,wBAAwB,CAAC;CAC5C;AAED,MAAM,MAAM,4BAA4B,GACpC;IACE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,OAAO,EAAE,SAAS,wBAAwB,EAAE,CAAC;CACvD,GACD;IACE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,SAAS,CACxB,SAAS,GAAG,YAAY,GAAG,YAAY,GAAG,WAAW,GAAG,UAAU,CACnE,EAAE,CAAC;IACJ,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAC;IACrB,QAAQ,CAAC,YAAY,EAAE,SAAS,kCAAkC,EAAE,CAAC;CACtE,CAAC;AAEN,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,MAAM,GAAG,IAAI,GACnB,6BAA6B,CAyB/B;AA0OD,wBAAgB,4BAA4B,CAC1C,YAAY,EAAE,SAAS,6BAA6B,EAAE,EACtD,eAAe,EAAE,QAAQ,CACvB,MAAM,CAAC,MAAM,EAAE,eAAe,GAAG,oBAAoB,CAAC,CACvD,EACD,UAAU,CAAC,EAAE,oBAAoB,GAChC,SAAS,4BAA4B,EAAE,CA8IzC"}
|
|
@@ -5,7 +5,97 @@ import { compareDiagnosticIdentityValues } from "./diagnosticOrdering.js";
|
|
|
5
5
|
import { normalizeDiagnosticPeriodWithAxis } from "./diagnosticPeriodAxis.js";
|
|
6
6
|
import { validateDiagnosticPeriodAxisInput } from "./diagnosticDefinitions.js";
|
|
7
7
|
import { DiagnosticValidationError, } from "./types.js";
|
|
8
|
-
import { diagnosticJsonPreflight, hasDiagnosticOwn, isDiagnosticPlainRecord, isDiagnosticToken, snapshotDiagnosticJson, } from "./diagnosticRuntime.js";
|
|
8
|
+
import { diagnosticJsonPreflight, diagnosticRecord, hasDiagnosticOwn, isDiagnosticPlainRecord, isDiagnosticToken, snapshotDiagnosticJson, MAX_DIAGNOSTIC_JSON_DEPTH, } from "./diagnosticRuntime.js";
|
|
9
|
+
// Bound the collection separately from each untrusted record. The generic
|
|
10
|
+
// million-node JSON cap otherwise rejects legitimate files at ~71k–100k rows,
|
|
11
|
+
// depending only on how much source provenance each observation carries.
|
|
12
|
+
const MAX_EXPOSURE_OBSERVATIONS = 250_000;
|
|
13
|
+
// JSON enumeration skips hidden properties, but the strict exposure schema
|
|
14
|
+
// reads recognized own fields. Refuse hidden accessors before those reads.
|
|
15
|
+
function hiddenExposureAccessorIssues(value, path) {
|
|
16
|
+
if (!isDiagnosticPlainRecord(value))
|
|
17
|
+
return [];
|
|
18
|
+
const issues = [];
|
|
19
|
+
for (const key of Object.getOwnPropertyNames(value)) {
|
|
20
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
21
|
+
if (!descriptor.enumerable && !("value" in descriptor))
|
|
22
|
+
issues.push({
|
|
23
|
+
domain: "input",
|
|
24
|
+
code: "invalid-json-value",
|
|
25
|
+
path: propertyPath(path, key),
|
|
26
|
+
message: "JSON objects may contain only data properties",
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
return issues;
|
|
30
|
+
}
|
|
31
|
+
function exposureCollectionPreflight(value) {
|
|
32
|
+
if (!Array.isArray(value))
|
|
33
|
+
return diagnosticJsonPreflight(value, "input");
|
|
34
|
+
if (Object.getPrototypeOf(value) !== Array.prototype)
|
|
35
|
+
return [
|
|
36
|
+
{
|
|
37
|
+
domain: "input",
|
|
38
|
+
code: "invalid-json-value",
|
|
39
|
+
path: "$",
|
|
40
|
+
message: "Value must use a plain object or array prototype",
|
|
41
|
+
},
|
|
42
|
+
];
|
|
43
|
+
if (value.length > MAX_EXPOSURE_OBSERVATIONS)
|
|
44
|
+
return [
|
|
45
|
+
{
|
|
46
|
+
domain: "input",
|
|
47
|
+
code: "expression-limit",
|
|
48
|
+
path: "$",
|
|
49
|
+
message: `Exposure observation count exceeds ${MAX_EXPOSURE_OBSERVATIONS}`,
|
|
50
|
+
},
|
|
51
|
+
];
|
|
52
|
+
const issues = [];
|
|
53
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
54
|
+
if (key === "length")
|
|
55
|
+
continue;
|
|
56
|
+
const index = typeof key === "string" ? Number(key) : Number.NaN;
|
|
57
|
+
if (Number.isInteger(index) &&
|
|
58
|
+
index >= 0 &&
|
|
59
|
+
index < value.length &&
|
|
60
|
+
String(index) === key)
|
|
61
|
+
continue;
|
|
62
|
+
issues.push({
|
|
63
|
+
domain: "input",
|
|
64
|
+
code: "invalid-json-value",
|
|
65
|
+
path: typeof key === "symbol" ? "$" : propertyPath("$", key),
|
|
66
|
+
message: "JSON arrays may contain only indexed data properties",
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
for (let index = 0; index < value.length; index++) {
|
|
70
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
|
|
71
|
+
if (!descriptor || !("value" in descriptor)) {
|
|
72
|
+
issues.push({
|
|
73
|
+
domain: "input",
|
|
74
|
+
code: "invalid-json-value",
|
|
75
|
+
path: `$[${index}]`,
|
|
76
|
+
message: "JSON arrays may contain only indexed data properties",
|
|
77
|
+
});
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
issues.push(...hiddenExposureAccessorIssues(descriptor.value, `$[${index}]`));
|
|
81
|
+
if (isDiagnosticPlainRecord(descriptor.value)) {
|
|
82
|
+
const source = Object.getOwnPropertyDescriptor(descriptor.value, "source");
|
|
83
|
+
if (source && "value" in source)
|
|
84
|
+
issues.push(...hiddenExposureAccessorIssues(source.value, `$[${index}].source`));
|
|
85
|
+
}
|
|
86
|
+
// The outer collection used to occupy depth 1: keep the exact depth
|
|
87
|
+
// contract, and retain the default million-node guard for each record.
|
|
88
|
+
for (const issue of diagnosticJsonPreflight(descriptor.value, "input", {
|
|
89
|
+
maxDepth: MAX_DIAGNOSTIC_JSON_DEPTH - 1,
|
|
90
|
+
}))
|
|
91
|
+
issues.push({ ...issue, path: `$[${index}]${issue.path.slice(1)}` });
|
|
92
|
+
}
|
|
93
|
+
return issues;
|
|
94
|
+
}
|
|
95
|
+
/** Only for new SDK-owned objects whose leaves have already been validated. */
|
|
96
|
+
function freezeExposureRecord(value) {
|
|
97
|
+
return Object.freeze(Object.assign(diagnosticRecord(), value));
|
|
98
|
+
}
|
|
9
99
|
export function auditDiagnosticNumber(value) {
|
|
10
100
|
if (value !== null && typeof value !== "number")
|
|
11
101
|
throw new DiagnosticValidationError([
|
|
@@ -42,7 +132,7 @@ function validateExposureArguments(observations, timingByMeasure, periodAxis) {
|
|
|
42
132
|
// Nonfinite exposure amounts are supported audited inputs. They are the
|
|
43
133
|
// only non-JSON numbers permitted here; metadata still must be finite.
|
|
44
134
|
const issues = [
|
|
45
|
-
...
|
|
135
|
+
...exposureCollectionPreflight(observations)
|
|
46
136
|
.filter((issue) => !(issue.code === "invalid-json-value" &&
|
|
47
137
|
issue.message === "JSON numeric value must be finite" &&
|
|
48
138
|
/^\$\[\d+\]\.value$/.test(issue.path)))
|
|
@@ -197,14 +287,14 @@ export function reconcileDiagnosticExposures(observations, timingByMeasure, peri
|
|
|
197
287
|
cohort.push(observation);
|
|
198
288
|
cohorts.set(identity, cohort);
|
|
199
289
|
}
|
|
200
|
-
return
|
|
290
|
+
return Object.freeze([...cohorts.values()]
|
|
201
291
|
.map((cohort) => {
|
|
202
292
|
const first = cohort[0];
|
|
203
293
|
const timing = hasDiagnosticOwn(timingByMeasure, first.measureId)
|
|
204
294
|
? timingByMeasure[first.measureId]
|
|
205
295
|
: undefined;
|
|
206
296
|
const audited = cohort
|
|
207
|
-
.map((item) => ({
|
|
297
|
+
.map((item) => snapshotDiagnosticJson({
|
|
208
298
|
sourceGroup: item.sourceGroup,
|
|
209
299
|
origin: item.origin,
|
|
210
300
|
...(item.valuation === undefined
|
|
@@ -230,7 +320,7 @@ export function reconcileDiagnosticExposures(observations, timingByMeasure, peri
|
|
|
230
320
|
issues.push("conflict");
|
|
231
321
|
const validStaticCopies = timing === "origin-static" && issues.length === 0;
|
|
232
322
|
if (issues.length > 0)
|
|
233
|
-
return
|
|
323
|
+
return freezeExposureRecord({
|
|
234
324
|
measureId: first.measureId,
|
|
235
325
|
key: first.key,
|
|
236
326
|
status: "invalid",
|
|
@@ -241,8 +331,8 @@ export function reconcileDiagnosticExposures(observations, timingByMeasure, peri
|
|
|
241
331
|
const value = audited[0].value;
|
|
242
332
|
if (value.status !== "observed")
|
|
243
333
|
throw new Error("unreachable invalid exposure state");
|
|
244
|
-
const sources = normalizeDiagnosticSourceLocations(audited.map((item) => item.source));
|
|
245
|
-
return
|
|
334
|
+
const sources = normalizeDiagnosticSourceLocations(audited.map((item) => item.source)).map((source) => snapshotDiagnosticJson(source));
|
|
335
|
+
return freezeExposureRecord({
|
|
246
336
|
measureId: first.measureId,
|
|
247
337
|
key: first.key,
|
|
248
338
|
status: "valid",
|