@actuarial-ts/compliance 0.6.0 → 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.
Files changed (83) hide show
  1. package/README.md +19 -9
  2. package/dist/bundle.d.ts +1 -1
  3. package/dist/bundle.d.ts.map +1 -1
  4. package/dist/bundle.js +163 -21
  5. package/dist/bundle.js.map +1 -1
  6. package/dist/diagnosticArtifactStream.d.ts +30 -0
  7. package/dist/diagnosticArtifactStream.d.ts.map +1 -0
  8. package/dist/diagnosticArtifactStream.js +128 -0
  9. package/dist/diagnosticArtifactStream.js.map +1 -0
  10. package/dist/diagnosticByteSource.d.ts +10 -0
  11. package/dist/diagnosticByteSource.d.ts.map +1 -0
  12. package/dist/diagnosticByteSource.js +70 -0
  13. package/dist/diagnosticByteSource.js.map +1 -0
  14. package/dist/diagnosticByteView.d.ts +3 -0
  15. package/dist/diagnosticByteView.d.ts.map +1 -0
  16. package/dist/diagnosticByteView.js +27 -0
  17. package/dist/diagnosticByteView.js.map +1 -0
  18. package/dist/diagnosticCompactRun.d.ts +43 -0
  19. package/dist/diagnosticCompactRun.d.ts.map +1 -0
  20. package/dist/diagnosticCompactRun.js +253 -0
  21. package/dist/diagnosticCompactRun.js.map +1 -0
  22. package/dist/diagnosticReplayFrames.d.ts +10 -0
  23. package/dist/diagnosticReplayFrames.d.ts.map +1 -0
  24. package/dist/diagnosticReplayFrames.js +99 -0
  25. package/dist/diagnosticReplayFrames.js.map +1 -0
  26. package/dist/diagnosticReplayProtocol.d.ts +17 -0
  27. package/dist/diagnosticReplayProtocol.d.ts.map +1 -0
  28. package/dist/diagnosticReplayProtocol.js +98 -0
  29. package/dist/diagnosticReplayProtocol.js.map +1 -0
  30. package/dist/diagnosticReplayReader.d.ts +41 -0
  31. package/dist/diagnosticReplayReader.d.ts.map +1 -0
  32. package/dist/diagnosticReplayReader.js +346 -0
  33. package/dist/diagnosticReplayReader.js.map +1 -0
  34. package/dist/diagnosticReplayValue.d.ts +34 -0
  35. package/dist/diagnosticReplayValue.d.ts.map +1 -0
  36. package/dist/diagnosticReplayValue.js +269 -0
  37. package/dist/diagnosticReplayValue.js.map +1 -0
  38. package/dist/diagnosticReplayWriter.d.ts +25 -0
  39. package/dist/diagnosticReplayWriter.d.ts.map +1 -0
  40. package/dist/diagnosticReplayWriter.js +148 -0
  41. package/dist/diagnosticReplayWriter.js.map +1 -0
  42. package/dist/diagnosticRun.d.ts +52 -29
  43. package/dist/diagnosticRun.d.ts.map +1 -1
  44. package/dist/diagnosticRun.js +564 -174
  45. package/dist/diagnosticRun.js.map +1 -1
  46. package/dist/diagnosticRunEvidence.d.ts +18 -0
  47. package/dist/diagnosticRunEvidence.d.ts.map +1 -0
  48. package/dist/diagnosticRunEvidence.js +195 -0
  49. package/dist/diagnosticRunEvidence.js.map +1 -0
  50. package/dist/index.d.ts +4 -0
  51. package/dist/index.d.ts.map +1 -1
  52. package/dist/index.js +4 -0
  53. package/dist/index.js.map +1 -1
  54. package/dist/ledger.d.ts.map +1 -1
  55. package/dist/ledger.js +47 -6
  56. package/dist/ledger.js.map +1 -1
  57. package/dist/metadata.d.ts.map +1 -1
  58. package/dist/metadata.js +27 -3
  59. package/dist/metadata.js.map +1 -1
  60. package/dist/sha256Stream.d.ts +6 -0
  61. package/dist/sha256Stream.d.ts.map +1 -0
  62. package/dist/sha256Stream.js +103 -0
  63. package/dist/sha256Stream.js.map +1 -0
  64. package/dist/version.d.ts +1 -1
  65. package/dist/version.js +1 -1
  66. package/package.json +6 -5
  67. package/src/bundle.ts +272 -32
  68. package/src/diagnosticArtifactStream.ts +148 -0
  69. package/src/diagnosticByteSource.ts +82 -0
  70. package/src/diagnosticByteView.ts +27 -0
  71. package/src/diagnosticCompactRun.ts +462 -0
  72. package/src/diagnosticReplayFrames.ts +99 -0
  73. package/src/diagnosticReplayProtocol.ts +99 -0
  74. package/src/diagnosticReplayReader.ts +428 -0
  75. package/src/diagnosticReplayValue.ts +269 -0
  76. package/src/diagnosticReplayWriter.ts +190 -0
  77. package/src/diagnosticRun.ts +1030 -93
  78. package/src/diagnosticRunEvidence.ts +391 -0
  79. package/src/index.ts +10 -0
  80. package/src/ledger.ts +59 -8
  81. package/src/metadata.ts +48 -7
  82. package/src/sha256Stream.ts +108 -0
  83. package/src/version.ts +1 -1
@@ -0,0 +1,190 @@
1
+ import { iterateDiagnosticIdentityJson } from "@actuarial-ts/core";
2
+ import { getCompletedCompactDiagnosticRunInput } from "@actuarial-ts/data";
3
+ import {
4
+ assertVerifiedCompactDiagnosticRunProvenance,
5
+ getCompactDiagnosticRunOwner,
6
+ type VerifiedCompactDiagnosticRunProvenance,
7
+ } from "./diagnosticCompactRun.js";
8
+ import type { DiagnosticArtifactDigest } from "./diagnosticRun.js";
9
+ import { diagnosticByteView } from "./diagnosticByteView.js";
10
+ import { consumeDiagnosticByteSource } from "./diagnosticByteSource.js";
11
+ import {
12
+ encodeReplayFrames,
13
+ REPLAY_TEXT_UNITS,
14
+ type ReplayFrame,
15
+ } from "./diagnosticReplayFrames.js";
16
+ import { replayValueFrames } from "./diagnosticReplayValue.js";
17
+ import { createSha256 } from "./sha256Stream.js";
18
+ import {
19
+ ARTIFACT_CHUNK_BYTES,
20
+ REPLAY_FORMAT,
21
+ REPLAY_VERSION,
22
+ badReplay,
23
+ mismatch,
24
+ replayArray,
25
+ replayCancellation,
26
+ replayFrameBytes,
27
+ replayRecord,
28
+ replayToken,
29
+ encodeArtifactChunk,
30
+ } from "./diagnosticReplayProtocol.js";
31
+
32
+ export interface DiagnosticReplayStreamRun {
33
+ readonly id: string;
34
+ readonly provenance: VerifiedCompactDiagnosticRunProvenance;
35
+ }
36
+ export type DiagnosticReplayByteSource = Iterable<Uint8Array> | AsyncIterable<Uint8Array>;
37
+ export interface WriteDiagnosticReplayStreamInput {
38
+ readonly runs: readonly DiagnosticReplayStreamRun[];
39
+ /** Fresh stream of at most 64 KiB per chunk, for each unique computed artifact. */
40
+ readonly openArtifact: (
41
+ artifact: Extract<DiagnosticArtifactDigest, { assurance: "sdk-computed" }>,
42
+ ) => DiagnosticReplayByteSource | Promise<DiagnosticReplayByteSource>;
43
+ readonly signal?: AbortSignal;
44
+ }
45
+
46
+ function replayInput(provenance: VerifiedCompactDiagnosticRunProvenance) {
47
+ const input = getCompletedCompactDiagnosticRunInput(getCompactDiagnosticRunOwner(provenance));
48
+ // Only a small envelope is new. All dataset/evidence arrays are the immutable
49
+ // originals owned by the validated gateway, including filtered-out inputs.
50
+ // Definition uses the normalized wire shape accepted by the core compiler,
51
+ // not a falsely typed authored definition with its optional-null defaults.
52
+ return {
53
+ definition: input.definition.definition,
54
+ losses: input.losses,
55
+ exposures: input.exposures,
56
+ ...(input.filter === null ? {} : { filter: input.filter }),
57
+ completePeriodCutoffs: input.completePeriodCutoffs,
58
+ ...(input.expectedCells === null ? {} : { expectedCells: input.expectedCells }),
59
+ reviewEvidence: input.reviewEvidence,
60
+ ...(input.runPresetId === null ? {} : { runPresetId: input.runPresetId }),
61
+ ...(input.datasetArtifactId === null ? {} : { datasetArtifactId: input.datasetArtifactId }),
62
+ groupMap: input.groupMap,
63
+ groupDimensions: input.groupDimensions,
64
+ policy: {
65
+ allowedReviewStatuses: input.policy.allowedReviewStatuses,
66
+ allowedMetricFindingSeverities: input.policy.allowedMetricFindingSeverities,
67
+ ...(input.policy.rationaleRef === null ? {} : { rationaleRef: input.policy.rationaleRef }),
68
+ },
69
+ };
70
+ }
71
+
72
+ /**
73
+ * Versioned large-data replay, NOT an interchange BundleDoc or the old JSON
74
+ * provenance DTO. Writes only from genuine immutable provenance owners. The
75
+ * host owns I/O and must publish a file only after the generator completes.
76
+ * All artifact bytes are rechecked against their authenticated digests while
77
+ * writing. Consumer cancellation closes producers; pending host I/O should use
78
+ * the same signal. No full candidate identity or whole archive string exists.
79
+ */
80
+ export function writeDiagnosticReplayStream(
81
+ input: WriteDiagnosticReplayStreamInput,
82
+ ): AsyncGenerator<Uint8Array> {
83
+ // Snapshot at call time, before the first await/yield (not at first next()).
84
+ const options = replayRecord(input, ["runs", "openArtifact", "signal"]);
85
+ const runs = replayArray(options.runs).map((value) => {
86
+ const item = replayRecord(value, ["id", "provenance"]);
87
+ const id = replayToken(item.id);
88
+ assertVerifiedCompactDiagnosticRunProvenance(item.provenance);
89
+ return Object.freeze({ id, provenance: item.provenance });
90
+ });
91
+ if (!runs.length || new Set(runs.map((run) => run.id)).size !== runs.length)
92
+ badReplay("Replay must have at least one run and unique run IDs");
93
+ if (typeof options.openArtifact !== "function")
94
+ badReplay("Replay openArtifact must be a function");
95
+ const openArtifact = options.openArtifact as WriteDiagnosticReplayStreamInput["openArtifact"];
96
+ const checkCancellation = replayCancellation(options.signal);
97
+ checkCancellation();
98
+ const artifacts = new Map<string, DiagnosticArtifactDigest>();
99
+ for (const { provenance } of runs)
100
+ for (const artifact of [...provenance.inputArtifacts, ...provenance.preparationArtifacts]) {
101
+ const prior = artifacts.get(artifact.id);
102
+ if (
103
+ prior &&
104
+ (prior.scope !== artifact.scope ||
105
+ prior.assurance !== artifact.assurance ||
106
+ prior.algorithm !== artifact.algorithm ||
107
+ prior.value !== artifact.value ||
108
+ (prior.assurance === "sdk-computed" &&
109
+ artifact.assurance === "sdk-computed" &&
110
+ prior.byteLength !== artifact.byteLength))
111
+ )
112
+ badReplay("Shared replay artifact IDs have conflicting evidence");
113
+ artifacts.set(artifact.id, artifact);
114
+ }
115
+
116
+ async function* body(): AsyncGenerator<ReplayFrame> {
117
+ yield [REPLAY_FORMAT, REPLAY_VERSION];
118
+ for (const artifact of artifacts.values()) {
119
+ checkCancellation();
120
+ yield ["artifact"];
121
+ yield* replayValueFrames(artifact);
122
+ yield ["artifact-data"];
123
+ if (artifact.assurance === "sdk-computed") {
124
+ const hash = createSha256();
125
+ let length = 0;
126
+ for await (const raw of consumeDiagnosticByteSource(
127
+ await openArtifact(artifact),
128
+ checkCancellation,
129
+ )) {
130
+ checkCancellation();
131
+ const view = diagnosticByteView(raw);
132
+ if (view.byteLength > ARTIFACT_CHUNK_BYTES)
133
+ badReplay("Artifact read chunks must be at most 64 KiB");
134
+ // Snapshot the entire bounded delivered chunk before yielding.
135
+ const owned = new Uint8Array(view);
136
+ length += owned.length;
137
+ if (!Number.isSafeInteger(length) || length > artifact.byteLength)
138
+ mismatch("Artifact grew after its verified digest");
139
+ hash.update(owned);
140
+ if (owned.length) yield ["bytes", encodeArtifactChunk(owned)];
141
+ }
142
+ checkCancellation();
143
+ if (length !== artifact.byteLength || hash.digest() !== artifact.value)
144
+ mismatch("Artifact bytes changed after their verified digest");
145
+ }
146
+ yield ["artifact-end"];
147
+ }
148
+ for (const { id, provenance } of runs) {
149
+ checkCancellation();
150
+ yield ["run"];
151
+ yield* replayValueFrames({
152
+ id,
153
+ input: replayInput(provenance),
154
+ inputArtifactIds: provenance.inputArtifacts.map((item) => item.id),
155
+ preparationArtifactIds: provenance.preparationArtifacts.map((item) => item.id),
156
+ preparationLineage: provenance.preparationLineage,
157
+ });
158
+ yield ["input-end"];
159
+ for (const [channel, document] of [
160
+ ["manifest", provenance.manifestIdentityDocument],
161
+ ["result", provenance.resultIdentityDocument],
162
+ ] as const) {
163
+ for (const text of iterateDiagnosticIdentityJson(document))
164
+ for (let start = 0; start < text.length; start += REPLAY_TEXT_UNITS)
165
+ yield [channel, text.slice(start, start + REPLAY_TEXT_UNITS)];
166
+ yield [`${channel}-end`];
167
+ }
168
+ yield [
169
+ "run-end",
170
+ provenance.runFingerprint,
171
+ provenance.resultFingerprint,
172
+ provenance.runResultFingerprint,
173
+ ];
174
+ }
175
+ }
176
+ async function* sequenced(): AsyncGenerator<ReplayFrame> {
177
+ let sequence = 0;
178
+ const hash = createSha256();
179
+ for await (const event of body()) {
180
+ checkCancellation();
181
+ if (!Number.isSafeInteger(sequence + 1)) badReplay("Replay sequence exceeded safe integers");
182
+ const frame = [sequence++, ...event];
183
+ hash.update(replayFrameBytes(frame));
184
+ yield frame;
185
+ }
186
+ checkCancellation();
187
+ yield [sequence, "end", artifacts.size, runs.length, hash.digest()];
188
+ }
189
+ return encodeReplayFrames(sequenced());
190
+ }