pi-mega-compact 0.20.0 → 0.20.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.
Files changed (111) hide show
  1. package/dist/config/vector-cortex.js +25 -0
  2. package/dist/config.js +1 -1
  3. package/dist/extensions/dashboard-server/routes-rag-settings-helpers.js +2 -0
  4. package/dist/extensions/dashboard-server/routes-vector-cortex-reconstruct.js +33 -0
  5. package/dist/extensions/dashboard-server/routes-vector-cortex-residual.js +51 -0
  6. package/dist/extensions/dashboard-server/routes-vector-cortex.js +2 -0
  7. package/dist/extensions/dashboard-server/routes.js +1 -1
  8. package/dist/extensions/dashboard-server/server.js +5 -1
  9. package/dist/src/config/vector-cortex.js +25 -0
  10. package/dist/src/config.js +1 -1
  11. package/dist/src/vector-cortex/reconstruct/_acceptance-helpers.js +365 -0
  12. package/dist/src/vector-cortex/reconstruct/assemble.js +137 -0
  13. package/dist/src/vector-cortex/reconstruct/closure.js +268 -0
  14. package/dist/src/vector-cortex/reconstruct/types.js +46 -0
  15. package/dist/src/vector-cortex/reconstruct/validate.js +152 -0
  16. package/dist/src/vector-cortex/residual/codec.js +224 -0
  17. package/dist/src/vector-cortex/residual/dct.js +158 -0
  18. package/dist/src/vector-cortex/residual/fixture-payload.js +77 -0
  19. package/dist/src/vector-cortex/residual/gf256.js +182 -0
  20. package/dist/src/vector-cortex/residual/parity.js +245 -0
  21. package/dist/src/vector-cortex/residual/quantize.js +200 -0
  22. package/dist/src/vector-cortex/residual/stream.js +124 -0
  23. package/dist/src/vector-cortex/residual/types.js +55 -0
  24. package/dist/vector-cortex/reconstruct/_acceptance-helpers.js +365 -0
  25. package/dist/vector-cortex/reconstruct/assemble.js +137 -0
  26. package/dist/vector-cortex/reconstruct/closure.js +268 -0
  27. package/dist/vector-cortex/reconstruct/types.js +46 -0
  28. package/dist/vector-cortex/reconstruct/validate.js +152 -0
  29. package/dist/vector-cortex/residual/codec.js +224 -0
  30. package/dist/vector-cortex/residual/dct.js +158 -0
  31. package/dist/vector-cortex/residual/fixture-payload.js +77 -0
  32. package/dist/vector-cortex/residual/gf256.js +182 -0
  33. package/dist/vector-cortex/residual/parity.js +245 -0
  34. package/dist/vector-cortex/residual/quantize.js +200 -0
  35. package/dist/vector-cortex/residual/stream.js +124 -0
  36. package/dist/vector-cortex/residual/types.js +55 -0
  37. package/extensions/dashboard-client/dist/assets/{AreaChart-n4z5w9Ox.js → AreaChart-XF3jPpvn.js} +2 -2
  38. package/extensions/dashboard-client/dist/assets/{AreaChart-n4z5w9Ox.js.map → AreaChart-XF3jPpvn.js.map} +1 -1
  39. package/extensions/dashboard-client/dist/assets/{BarChart-BLGjWWBr.js → BarChart-DnYYn0GF.js} +2 -2
  40. package/extensions/dashboard-client/dist/assets/{BarChart-BLGjWWBr.js.map → BarChart-DnYYn0GF.js.map} +1 -1
  41. package/extensions/dashboard-client/dist/assets/{CacheTab-CAnrKKVK.js → CacheTab-D3bVskCL.js} +2 -2
  42. package/extensions/dashboard-client/dist/assets/{CacheTab-CAnrKKVK.js.map → CacheTab-D3bVskCL.js.map} +1 -1
  43. package/extensions/dashboard-client/dist/assets/{EventsTab-D-GL7Ngq.js → EventsTab-CX_8myfc.js} +2 -2
  44. package/extensions/dashboard-client/dist/assets/{EventsTab-D-GL7Ngq.js.map → EventsTab-CX_8myfc.js.map} +1 -1
  45. package/extensions/dashboard-client/dist/assets/{HealthTab-C4x5qQ8s.js → HealthTab-CnVzBQvN.js} +2 -2
  46. package/extensions/dashboard-client/dist/assets/{HealthTab-C4x5qQ8s.js.map → HealthTab-CnVzBQvN.js.map} +1 -1
  47. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-BSPs0eq5.js → MaintenanceTab-Cao--dtV.js} +2 -2
  48. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-BSPs0eq5.js.map → MaintenanceTab-Cao--dtV.js.map} +1 -1
  49. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-BWEUtTSu.js → MemoryMapTab-DZdI-9ig.js} +2 -2
  50. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-BWEUtTSu.js.map → MemoryMapTab-DZdI-9ig.js.map} +1 -1
  51. package/extensions/dashboard-client/dist/assets/{MetricsTab-Dhm1c3Cx.js → MetricsTab-SlpSmNU4.js} +2 -2
  52. package/extensions/dashboard-client/dist/assets/{MetricsTab-Dhm1c3Cx.js.map → MetricsTab-SlpSmNU4.js.map} +1 -1
  53. package/extensions/dashboard-client/dist/assets/{OverviewTab-Cm6cySiQ.js → OverviewTab-C_-_4au9.js} +2 -2
  54. package/extensions/dashboard-client/dist/assets/{OverviewTab-Cm6cySiQ.js.map → OverviewTab-C_-_4au9.js.map} +1 -1
  55. package/extensions/dashboard-client/dist/assets/{ReposTab-DxLN1N4K.js → ReposTab-CyWedWZC.js} +2 -2
  56. package/extensions/dashboard-client/dist/assets/{ReposTab-DxLN1N4K.js.map → ReposTab-CyWedWZC.js.map} +1 -1
  57. package/extensions/dashboard-client/dist/assets/{SessionsTab-CZuI4ISW.js → SessionsTab-wqPBMIJZ.js} +2 -2
  58. package/extensions/dashboard-client/dist/assets/{SessionsTab-CZuI4ISW.js.map → SessionsTab-wqPBMIJZ.js.map} +1 -1
  59. package/extensions/dashboard-client/dist/assets/{SetupTab-C_0VOomm.js → SetupTab-MojXmuUf.js} +2 -2
  60. package/extensions/dashboard-client/dist/assets/{SetupTab-C_0VOomm.js.map → SetupTab-MojXmuUf.js.map} +1 -1
  61. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-BhbaRLmi.js → TimeSavedCard-BxcplEOJ.js} +2 -2
  62. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-BhbaRLmi.js.map → TimeSavedCard-BxcplEOJ.js.map} +1 -1
  63. package/extensions/dashboard-client/dist/assets/{TurnsTab-CXJmbjje.js → TurnsTab-D8_qWbjY.js} +2 -2
  64. package/extensions/dashboard-client/dist/assets/{TurnsTab-CXJmbjje.js.map → TurnsTab-D8_qWbjY.js.map} +1 -1
  65. package/extensions/dashboard-client/dist/assets/VectorCortexTab-B3yisAyB.js +2 -0
  66. package/extensions/dashboard-client/dist/assets/VectorCortexTab-B3yisAyB.js.map +1 -0
  67. package/extensions/dashboard-client/dist/assets/{WikiTab-C5uvMCzp.js → WikiTab-Cfd-QZYz.js} +2 -2
  68. package/extensions/dashboard-client/dist/assets/{WikiTab-C5uvMCzp.js.map → WikiTab-Cfd-QZYz.js.map} +1 -1
  69. package/extensions/dashboard-client/dist/assets/{button-Cl_fxJcg.js → button-tX5ht0bb.js} +2 -2
  70. package/extensions/dashboard-client/dist/assets/{button-Cl_fxJcg.js.map → button-tX5ht0bb.js.map} +1 -1
  71. package/extensions/dashboard-client/dist/assets/{card-BH8Le5YM.js → card-BT3Z5dDg.js} +2 -2
  72. package/extensions/dashboard-client/dist/assets/{card-BH8Le5YM.js.map → card-BT3Z5dDg.js.map} +1 -1
  73. package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-sZ6ZNtBi.js → generateCategoricalChart-B6p2WnkY.js} +2 -2
  74. package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-sZ6ZNtBi.js.map → generateCategoricalChart-B6p2WnkY.js.map} +1 -1
  75. package/extensions/dashboard-client/dist/assets/{index-CX9zv8cJ.js → index-CFLekP12.js} +3 -3
  76. package/extensions/dashboard-client/dist/assets/{index-CX9zv8cJ.js.map → index-CFLekP12.js.map} +1 -1
  77. package/extensions/dashboard-client/dist/assets/{switch-CXJhcjhM.js → switch-B52oAMSw.js} +2 -2
  78. package/extensions/dashboard-client/dist/assets/{switch-CXJhcjhM.js.map → switch-B52oAMSw.js.map} +1 -1
  79. package/extensions/dashboard-client/dist/assets/{toggle-CoLtIc6y.js → toggle-VxxVc5Tp.js} +2 -2
  80. package/extensions/dashboard-client/dist/assets/{toggle-CoLtIc6y.js.map → toggle-VxxVc5Tp.js.map} +1 -1
  81. package/extensions/dashboard-client/dist/assets/{useSSE-fSXho606.js → useSSE-DAVdZnG7.js} +2 -2
  82. package/extensions/dashboard-client/dist/assets/{useSSE-fSXho606.js.map → useSSE-DAVdZnG7.js.map} +1 -1
  83. package/extensions/dashboard-client/dist/index.html +1 -1
  84. package/extensions/dashboard-client/src/api/vector-cortex.ts +9 -0
  85. package/extensions/dashboard-client/src/tabs/VectorCortexTab.tsx +37 -0
  86. package/extensions/dashboard-client/src/types/vector-cortex.ts +16 -0
  87. package/extensions/dashboard-server/api-contracts/vector-cortex.ts +57 -0
  88. package/extensions/dashboard-server/routes-rag-settings-helpers.ts +12 -0
  89. package/extensions/dashboard-server/routes-vector-cortex-reconstruct.ts +41 -0
  90. package/extensions/dashboard-server/routes-vector-cortex-residual.ts +60 -0
  91. package/extensions/dashboard-server/routes-vector-cortex.ts +2 -0
  92. package/extensions/dashboard-server/routes.ts +2 -0
  93. package/extensions/dashboard-server/server.ts +4 -0
  94. package/package.json +1 -1
  95. package/src/config/vector-cortex.ts +27 -0
  96. package/src/config.ts +2 -0
  97. package/src/vector-cortex/reconstruct/_acceptance-helpers.ts +455 -0
  98. package/src/vector-cortex/reconstruct/assemble.ts +170 -0
  99. package/src/vector-cortex/reconstruct/closure.ts +299 -0
  100. package/src/vector-cortex/reconstruct/types.ts +330 -0
  101. package/src/vector-cortex/reconstruct/validate.ts +184 -0
  102. package/src/vector-cortex/residual/codec.ts +292 -0
  103. package/src/vector-cortex/residual/dct.ts +166 -0
  104. package/src/vector-cortex/residual/fixture-payload.ts +90 -0
  105. package/src/vector-cortex/residual/gf256.ts +196 -0
  106. package/src/vector-cortex/residual/parity.ts +283 -0
  107. package/src/vector-cortex/residual/quantize.ts +230 -0
  108. package/src/vector-cortex/residual/stream.ts +135 -0
  109. package/src/vector-cortex/residual/types.ts +236 -0
  110. package/extensions/dashboard-client/dist/assets/VectorCortexTab-BGe9hu3r.js +0 -2
  111. package/extensions/dashboard-client/dist/assets/VectorCortexTab-BGe9hu3r.js.map +0 -1
@@ -0,0 +1,196 @@
1
+ /**
2
+ * vector-cortex/residual/gf256.ts — GF(2^8) field arithmetic and matrix algebra
3
+ * for the VC4B Reed–Solomon erasure parity.
4
+ *
5
+ * Field: GF(2^8) with primitive polynomial `0x11d`, elements represented as
6
+ * polynomial-basis bytes (RESIDUAL_CODEC §erasure parity). Log/antilog tables
7
+ * are built once from generator 2 and drive constant-time multiply/divide.
8
+ *
9
+ * Matrix inversion and recovery use DETERMINISTIC left-to-right pivot search and
10
+ * GF Gaussian elimination — no randomness, no iteration-order dependence, so
11
+ * every implementation reaches byte-identical results.
12
+ *
13
+ * Pure arithmetic: no storage, no console, no network (PREVENT-PI-004 /
14
+ * PREVENT-011).
15
+ */
16
+
17
+ import { GF_PRIMITIVE_POLYNOMIAL } from "./types.js";
18
+
19
+ const FIELD_SIZE = 256;
20
+
21
+ /** `EXP[i] = 2^i` in GF(2^8) (doubled length so multiply needs no modulo). */
22
+ const EXP = new Uint8Array(FIELD_SIZE * 2);
23
+ /** `LOG[x] = i` such that `2^i = x`; `LOG[0]` is unused (0 has no logarithm). */
24
+ const LOG = new Uint8Array(FIELD_SIZE);
25
+
26
+ (function buildTables(): void {
27
+ let x = 1;
28
+ for (let i = 0; i < FIELD_SIZE - 1; i++) {
29
+ EXP[i] = x;
30
+ LOG[x] = i;
31
+ x <<= 1;
32
+ if (x & 0x100) x ^= GF_PRIMITIVE_POLYNOMIAL;
33
+ }
34
+ // Mirror the cycle so `EXP[a + b]` is valid for a,b <= 254 without a modulo.
35
+ for (let i = FIELD_SIZE - 1; i < EXP.length; i++) {
36
+ EXP[i] = EXP[i - (FIELD_SIZE - 1)]!;
37
+ }
38
+ })();
39
+
40
+ /** GF(2^8) addition (and subtraction) is XOR. */
41
+ export function gfAdd(a: number, b: number): number {
42
+ return (a ^ b) & 0xff;
43
+ }
44
+
45
+ /** GF(2^8) multiplication via log/antilog tables. */
46
+ export function gfMul(a: number, b: number): number {
47
+ if (a === 0 || b === 0) return 0;
48
+ return EXP[LOG[a]! + LOG[b]!]!;
49
+ }
50
+
51
+ /** GF(2^8) division; dividing by zero is a programming error and throws. */
52
+ export function gfDiv(a: number, b: number): number {
53
+ if (b === 0) throw new RangeError("gf256: division by zero");
54
+ if (a === 0) return 0;
55
+ return EXP[LOG[a]! + (FIELD_SIZE - 1) - LOG[b]!]!;
56
+ }
57
+
58
+ /** Multiplicative inverse of a non-zero field element. */
59
+ export function gfInv(a: number): number {
60
+ if (a === 0) throw new RangeError("gf256: zero has no inverse");
61
+ return EXP[FIELD_SIZE - 1 - LOG[a]!]!;
62
+ }
63
+
64
+ /** `base^exponent` in GF(2^8) (exponent is a non-negative integer). */
65
+ export function gfPow(base: number, exponent: number): number {
66
+ if (exponent === 0) return 1;
67
+ if (base === 0) return 0;
68
+ return EXP[(LOG[base]! * exponent) % (FIELD_SIZE - 1)]!;
69
+ }
70
+
71
+ /** A dense row-major GF(2^8) matrix. */
72
+ export interface GfMatrix {
73
+ readonly rows: number;
74
+ readonly cols: number;
75
+ readonly data: Uint8Array;
76
+ }
77
+
78
+ /** Allocate a zero matrix. */
79
+ export function gfMatrix(rows: number, cols: number): GfMatrix {
80
+ return { rows, cols, data: new Uint8Array(rows * cols) };
81
+ }
82
+
83
+ /** Read `m[r][c]`. */
84
+ export function gfAt(m: GfMatrix, r: number, c: number): number {
85
+ return m.data[r * m.cols + c]!;
86
+ }
87
+
88
+ /** Write `m[r][c] = v`. */
89
+ export function gfSet(m: GfMatrix, r: number, c: number, v: number): void {
90
+ m.data[r * m.cols + c] = v;
91
+ }
92
+
93
+ /**
94
+ * Build the `rows x cols` Vandermonde matrix `V[r][c] = alpha_r^c` with the
95
+ * distinct evaluation points `alpha_r = r + 1` (RESIDUAL_CODEC: rows r=0..8,
96
+ * columns c=0..5).
97
+ */
98
+ export function vandermonde(rows: number, cols: number): GfMatrix {
99
+ const m = gfMatrix(rows, cols);
100
+ for (let r = 0; r < rows; r++) {
101
+ for (let c = 0; c < cols; c++) {
102
+ gfSet(m, r, c, gfPow((r + 1) & 0xff, c));
103
+ }
104
+ }
105
+ return m;
106
+ }
107
+
108
+ /** Matrix product `a x b` over GF(2^8). */
109
+ export function gfMatMul(a: GfMatrix, b: GfMatrix): GfMatrix {
110
+ const out = gfMatrix(a.rows, b.cols);
111
+ for (let r = 0; r < a.rows; r++) {
112
+ for (let c = 0; c < b.cols; c++) {
113
+ let acc = 0;
114
+ for (let i = 0; i < a.cols; i++) {
115
+ acc ^= gfMul(gfAt(a, r, i), gfAt(b, i, c));
116
+ }
117
+ gfSet(out, r, c, acc);
118
+ }
119
+ }
120
+ return out;
121
+ }
122
+
123
+ /** Extract the contiguous row range `[start, start+count)` of a matrix. */
124
+ export function gfSubRows(m: GfMatrix, start: number, count: number): GfMatrix {
125
+ const out = gfMatrix(count, m.cols);
126
+ out.data.set(m.data.subarray(start * m.cols, (start + count) * m.cols));
127
+ return out;
128
+ }
129
+
130
+ /** Gather the given row indices (in order) into a new matrix. */
131
+ export function gfPickRows(m: GfMatrix, indices: readonly number[]): GfMatrix {
132
+ const out = gfMatrix(indices.length, m.cols);
133
+ indices.forEach((src, dest) => {
134
+ out.data.set(m.data.subarray(src * m.cols, (src + 1) * m.cols), dest * m.cols);
135
+ });
136
+ return out;
137
+ }
138
+
139
+ /**
140
+ * Invert a square GF(2^8) matrix by Gauss–Jordan elimination with a
141
+ * DETERMINISTIC left-to-right pivot search (the first row at or below the
142
+ * current column with a non-zero entry). Returns null when the matrix is
143
+ * singular.
144
+ */
145
+ export function gfInvert(m: GfMatrix): GfMatrix | null {
146
+ if (m.rows !== m.cols) return null;
147
+ const n = m.rows;
148
+ const work = gfMatrix(n, n);
149
+ work.data.set(m.data);
150
+ const inv = gfMatrix(n, n);
151
+ for (let i = 0; i < n; i++) gfSet(inv, i, i, 1);
152
+
153
+ const swapRows = (mat: GfMatrix, a: number, b: number): void => {
154
+ if (a === b) return;
155
+ for (let c = 0; c < mat.cols; c++) {
156
+ const t = gfAt(mat, a, c);
157
+ gfSet(mat, a, c, gfAt(mat, b, c));
158
+ gfSet(mat, b, c, t);
159
+ }
160
+ };
161
+
162
+ for (let col = 0; col < n; col++) {
163
+ // Deterministic pivot: the lowest-index row >= col with a non-zero entry.
164
+ let pivot = -1;
165
+ for (let r = col; r < n; r++) {
166
+ if (gfAt(work, r, col) !== 0) {
167
+ pivot = r;
168
+ break;
169
+ }
170
+ }
171
+ if (pivot === -1) return null; // singular
172
+ swapRows(work, col, pivot);
173
+ swapRows(inv, col, pivot);
174
+
175
+ // Normalize the pivot row.
176
+ const pivotValue = gfAt(work, col, col);
177
+ if (pivotValue !== 1) {
178
+ const scale = gfInv(pivotValue);
179
+ for (let c = 0; c < n; c++) {
180
+ gfSet(work, col, c, gfMul(gfAt(work, col, c), scale));
181
+ gfSet(inv, col, c, gfMul(gfAt(inv, col, c), scale));
182
+ }
183
+ }
184
+ // Eliminate the column from every other row.
185
+ for (let r = 0; r < n; r++) {
186
+ if (r === col) continue;
187
+ const factor = gfAt(work, r, col);
188
+ if (factor === 0) continue;
189
+ for (let c = 0; c < n; c++) {
190
+ gfSet(work, r, c, gfAdd(gfAt(work, r, c), gfMul(factor, gfAt(work, col, c))));
191
+ gfSet(inv, r, c, gfAdd(gfAt(inv, r, c), gfMul(factor, gfAt(inv, col, c))));
192
+ }
193
+ }
194
+ }
195
+ return inv;
196
+ }
@@ -0,0 +1,283 @@
1
+ /**
2
+ * vector-cortex/residual/parity.ts — Reed–Solomon (9,6) erasure parity over the
3
+ * protected stream (VC4B).
4
+ *
5
+ * Construction (RESIDUAL_CODEC §erasure parity): the protected stream is split
6
+ * into `k=6` equal, zero-padded data shards (the unpadded stream length is
7
+ * stored on every shard). A `9x6` Vandermonde matrix `V[r][c] = alpha_r^c` with
8
+ * evaluation points `alpha_r = r+1` is converted to a SYSTEMATIC generator
9
+ * `G = V x inverse(V[0..5, 0..5])`; rows 0..5 are then the identity (data shards
10
+ * pass through unchanged) and rows 6..8 produce the three parity shards in that
11
+ * order.
12
+ *
13
+ * Recovery: any 6 of the 9 shards reconstruct the stream. Every shard carries
14
+ * its own SHA-256, so an UNKNOWN corruption is DETECTED and can be promoted to a
15
+ * known erasure — it is never blindly error-corrected. More than 3 erasures
16
+ * fails closed (`RES_TOO_MANY_ERASURES`) to the exact source / mode C.
17
+ *
18
+ * Guardrails: local hashing only, no storage, no console, no network
19
+ * (PREVENT-PI-004 / PREVENT-011).
20
+ */
21
+
22
+ import { createHash } from "node:crypto";
23
+ import {
24
+ gfAdd,
25
+ gfAt,
26
+ gfInvert,
27
+ gfMatMul,
28
+ gfMatrix,
29
+ gfMul,
30
+ gfPickRows,
31
+ gfSet,
32
+ gfSubRows,
33
+ vandermonde,
34
+ type GfMatrix,
35
+ } from "./gf256.js";
36
+ import {
37
+ RS_DATA_SHARDS,
38
+ RS_PARITY_SHARDS,
39
+ RS_TOTAL_SHARDS,
40
+ type ParityRecoveryResult,
41
+ type ParityShardV1,
42
+ type ResidualFailureCode,
43
+ } from "./types.js";
44
+
45
+ /** SHA-256 of a byte slice, lowercase hex. */
46
+ export function sha256Hex(bytes: Uint8Array): string {
47
+ return createHash("sha256").update(bytes).digest("hex");
48
+ }
49
+
50
+ /**
51
+ * The systematic `9x6` generator `G = V x inverse(V[0..5,0..5])`. Built once:
52
+ * the matrix depends only on the fixed geometry and the fixed field.
53
+ */
54
+ let cachedGenerator: GfMatrix | null = null;
55
+
56
+ export function systematicGenerator(): GfMatrix {
57
+ if (cachedGenerator) return cachedGenerator;
58
+ const v = vandermonde(RS_TOTAL_SHARDS, RS_DATA_SHARDS);
59
+ const top = gfSubRows(v, 0, RS_DATA_SHARDS);
60
+ const topInv = gfInvert(top);
61
+ if (!topInv) {
62
+ // The fixed Vandermonde top square over GF(2^8) with distinct points 1..6
63
+ // is always invertible; this guard exists so a future geometry change fails
64
+ // loudly rather than silently producing a broken generator.
65
+ throw new Error("residual parity: singular Vandermonde top square");
66
+ }
67
+ cachedGenerator = gfMatMul(v, topInv);
68
+ return cachedGenerator;
69
+ }
70
+
71
+ /** Per-shard payload length for a stream of `streamLength` bytes. */
72
+ export function shardLength(streamLength: number): number {
73
+ return Math.ceil(streamLength / RS_DATA_SHARDS);
74
+ }
75
+
76
+ /**
77
+ * Split the protected stream into 9 shards: 6 systematic data shards (equal
78
+ * length, final one zero-padded) followed by 3 parity shards.
79
+ */
80
+ export function encodeShards(stream: Uint8Array): ParityShardV1[] {
81
+ const len = shardLength(stream.length);
82
+ const g = systematicGenerator();
83
+
84
+ const data: Uint8Array[] = [];
85
+ for (let i = 0; i < RS_DATA_SHARDS; i++) {
86
+ const shard = new Uint8Array(len);
87
+ shard.set(stream.subarray(i * len, Math.min((i + 1) * len, stream.length)));
88
+ data.push(shard);
89
+ }
90
+
91
+ const shards: ParityShardV1[] = data.map((bytes, index) => ({
92
+ schema: "parity-shard-v1",
93
+ index,
94
+ kind: "data",
95
+ bytes,
96
+ digest: sha256Hex(bytes),
97
+ streamLength: stream.length,
98
+ }));
99
+
100
+ for (let p = 0; p < RS_PARITY_SHARDS; p++) {
101
+ const row = RS_DATA_SHARDS + p;
102
+ const bytes = new Uint8Array(len);
103
+ for (let c = 0; c < RS_DATA_SHARDS; c++) {
104
+ const coefficient = gfAt(g, row, c);
105
+ if (coefficient === 0) continue;
106
+ const src = data[c]!;
107
+ for (let b = 0; b < len; b++) {
108
+ bytes[b] = gfAdd(bytes[b]!, gfMul(coefficient, src[b]!));
109
+ }
110
+ }
111
+ shards.push({
112
+ schema: "parity-shard-v1",
113
+ index: row,
114
+ kind: "parity",
115
+ bytes,
116
+ digest: sha256Hex(bytes),
117
+ streamLength: stream.length,
118
+ });
119
+ }
120
+ return shards;
121
+ }
122
+
123
+ /**
124
+ * Verify every supplied shard's SHA-256 and return the indices whose digest
125
+ * does NOT match. A corrupt shard is DETECTED here; the caller promotes it to a
126
+ * known erasure rather than attempting unknown-error correction.
127
+ */
128
+ export function detectCorruptShards(
129
+ shards: readonly ParityShardV1[],
130
+ ): number[] {
131
+ const corrupt: number[] = [];
132
+ for (const s of shards) {
133
+ if (sha256Hex(s.bytes) !== s.digest) corrupt.push(s.index);
134
+ }
135
+ return corrupt.sort((a, b) => a - b);
136
+ }
137
+
138
+ /** Structural validation shared by recovery paths. */
139
+ function validateShards(
140
+ shards: readonly ParityShardV1[],
141
+ ): { ok: true; length: number; streamLength: number } | { ok: false; code: ResidualFailureCode } {
142
+ if (shards.length === 0) return { ok: false, code: "RES_TOO_MANY_ERASURES" };
143
+ const seen = new Set<number>();
144
+ const length = shards[0]!.bytes.length;
145
+ const streamLength = shards[0]!.streamLength;
146
+ for (const s of shards) {
147
+ if (s.index < 0 || s.index >= RS_TOTAL_SHARDS || !Number.isInteger(s.index)) {
148
+ return { ok: false, code: "RES_DUPLICATE_SHARD_INDEX" };
149
+ }
150
+ if (seen.has(s.index)) return { ok: false, code: "RES_DUPLICATE_SHARD_INDEX" };
151
+ seen.add(s.index);
152
+ if (s.bytes.length !== length) {
153
+ return { ok: false, code: "RES_SHARD_LENGTH_MISMATCH" };
154
+ }
155
+ if (s.streamLength !== streamLength) {
156
+ return { ok: false, code: "RES_SHARD_LENGTH_MISMATCH" };
157
+ }
158
+ }
159
+ if (shardLength(streamLength) !== length) {
160
+ return { ok: false, code: "RES_SHARD_LENGTH_MISMATCH" };
161
+ }
162
+ return { ok: true, length, streamLength };
163
+ }
164
+
165
+ /**
166
+ * Reconstruct the protected stream from AT LEAST 6 surviving shards.
167
+ *
168
+ * Every supplied shard is digest-checked first: a shard whose SHA-256 does not
169
+ * match its recorded digest is treated as a KNOWN erasure (it is dropped from
170
+ * the surviving set), which is exactly how a corrupt parity shard becomes the
171
+ * third erasure in the sprint's failure-injection case. If fewer than 6 shards
172
+ * survive that check, recovery fails closed with `RES_TOO_MANY_ERASURES` and
173
+ * never attempts unknown-error correction.
174
+ */
175
+ export function recoverStream(
176
+ shards: readonly ParityShardV1[],
177
+ ): ParityRecoveryResult {
178
+ const structural = validateShards(shards);
179
+ if (!structural.ok) return structural;
180
+ const { length, streamLength } = structural;
181
+
182
+ // Digest check: promote every detected corruption to a known erasure.
183
+ const corrupt = new Set(detectCorruptShards(shards));
184
+ const survivors = shards
185
+ .filter((s) => !corrupt.has(s.index))
186
+ .slice()
187
+ .sort((a, b) => a.index - b.index);
188
+
189
+ if (survivors.length < RS_DATA_SHARDS) {
190
+ return { ok: false, code: "RES_TOO_MANY_ERASURES" };
191
+ }
192
+
193
+ // Deterministic selection: the six lowest surviving indices.
194
+ const chosen = survivors.slice(0, RS_DATA_SHARDS);
195
+ const g = systematicGenerator();
196
+ const sub = gfPickRows(g, chosen.map((s) => s.index));
197
+ const subInv = gfInvert(sub);
198
+ if (!subInv) return { ok: false, code: "RES_SINGULAR_MATRIX" };
199
+
200
+ // Recover the six data shards: D = inverse(G_chosen) x S_chosen.
201
+ const recoveredData: Uint8Array[] = [];
202
+ for (let r = 0; r < RS_DATA_SHARDS; r++) {
203
+ const out = new Uint8Array(length);
204
+ for (let c = 0; c < RS_DATA_SHARDS; c++) {
205
+ const coefficient = gfAt(subInv, r, c);
206
+ if (coefficient === 0) continue;
207
+ const src = chosen[c]!.bytes;
208
+ for (let b = 0; b < length; b++) {
209
+ out[b] = gfAdd(out[b]!, gfMul(coefficient, src[b]!));
210
+ }
211
+ }
212
+ recoveredData.push(out);
213
+ }
214
+
215
+ const stream = new Uint8Array(streamLength);
216
+ for (let i = 0; i < RS_DATA_SHARDS; i++) {
217
+ const start = i * length;
218
+ if (start >= streamLength) break;
219
+ const take = Math.min(length, streamLength - start);
220
+ stream.set(recoveredData[i]!.subarray(0, take), start);
221
+ }
222
+
223
+ const present = new Set(chosen.map((s) => s.index));
224
+ const recoveredIndices: number[] = [];
225
+ for (let i = 0; i < RS_TOTAL_SHARDS; i++) if (!present.has(i)) recoveredIndices.push(i);
226
+ return { ok: true, stream, recoveredIndices };
227
+ }
228
+
229
+ /**
230
+ * Recover with an EXPLICIT erasure set: the named indices are treated as lost
231
+ * regardless of their digest. More than `m=3` marked erasures fails closed with
232
+ * `RES_TOO_MANY_ERASURES` before any matrix work — including the case where
233
+ * three data shards are marked AND a fourth (parity) shard is corrupt.
234
+ */
235
+ export function recoverWithErasures(
236
+ shards: readonly ParityShardV1[],
237
+ erasedIndices: readonly number[],
238
+ ): ParityRecoveryResult {
239
+ const erased = new Set(erasedIndices);
240
+ if (erased.size > RS_PARITY_SHARDS) {
241
+ return { ok: false, code: "RES_TOO_MANY_ERASURES" };
242
+ }
243
+ const kept = shards.filter((s) => !erased.has(s.index));
244
+ // A corruption among the KEPT shards is an additional known erasure; if that
245
+ // pushes the total past m=3 the recovery fails closed rather than attempting
246
+ // unknown-error correction.
247
+ const corrupt = detectCorruptShards(kept);
248
+ if (erased.size + corrupt.length > RS_PARITY_SHARDS) {
249
+ return { ok: false, code: "RES_TOO_MANY_ERASURES" };
250
+ }
251
+ return recoverStream(kept);
252
+ }
253
+
254
+ /** Rebuild the full 9-shard set from a recovered stream (repair path). */
255
+ export function repairShards(stream: Uint8Array): ParityShardV1[] {
256
+ return encodeShards(stream);
257
+ }
258
+
259
+ /** Test/diagnostic helper: the systematic generator's parity rows. */
260
+ export function parityRows(): number[][] {
261
+ const g = systematicGenerator();
262
+ const rows: number[][] = [];
263
+ for (let r = RS_DATA_SHARDS; r < RS_TOTAL_SHARDS; r++) {
264
+ const row: number[] = [];
265
+ for (let c = 0; c < RS_DATA_SHARDS; c++) row.push(gfAt(g, r, c));
266
+ rows.push(row);
267
+ }
268
+ return rows;
269
+ }
270
+
271
+ /** Test helper: assert the generator's top square really is the identity. */
272
+ export function generatorIsSystematic(): boolean {
273
+ const g = systematicGenerator();
274
+ for (let r = 0; r < RS_DATA_SHARDS; r++) {
275
+ for (let c = 0; c < RS_DATA_SHARDS; c++) {
276
+ const expected = r === c ? 1 : 0;
277
+ if (gfAt(g, r, c) !== expected) return false;
278
+ }
279
+ }
280
+ return true;
281
+ }
282
+
283
+ export { gfMatrix, gfSet };
@@ -0,0 +1,230 @@
1
+ /**
2
+ * vector-cortex/residual/quantize.ts — int16 coefficient quantization + the
3
+ * block-scoped exact correction stream (VC4B).
4
+ *
5
+ * Quantization (RESIDUAL_CODEC §byte scope): signed coefficients are quantized
6
+ * to int16 with a per-block float32 LE scale `max(abs(c))/32767` (an all-zero
7
+ * block has scale 0). Ties round to nearest-even; SATURATION REJECTS the
8
+ * encoding rather than clipping — a value that would land outside
9
+ * [-32767, 32767] returns `RES_QUANTIZE_RANGE`, as does any non-finite input.
10
+ * Dequantization multiplies back by the (float32-rounded) scale.
11
+ *
12
+ * Because quantization may not reproduce the bytes exactly, the encoder appends
13
+ * a block-scoped EXACT correction stream: for each block in ascending u32 LE
14
+ * `blockIndex`, a varint correction count followed by sorted
15
+ * `(u16 offsetWithinBlock, u8 original)` entries. Offsets are 0..4095, duplicate
16
+ * offsets are rejected, and omitted blocks have count zero. Applying the
17
+ * corrections makes post-quantization byte error exactly ZERO.
18
+ *
19
+ * Pure numeric/serialization logic: no storage, no console, no network
20
+ * (PREVENT-PI-004 / PREVENT-011).
21
+ */
22
+
23
+ import { roundHalfToEven } from "./dct.js";
24
+ import {
25
+ RESIDUAL_BLOCK_SIZE,
26
+ type BlockCorrectionsV1,
27
+ type CorrectionV1,
28
+ type QuantizedBlockV1,
29
+ type ResidualFailureCode,
30
+ } from "./types.js";
31
+
32
+ /** Int16 quantization limit (symmetric; -32768 is never produced). */
33
+ export const INT16_LIMIT = 32767;
34
+
35
+ /** Quantization result: the block, or the exact rejection code. */
36
+ export type QuantizeResult =
37
+ | { ok: true; block: QuantizedBlockV1 }
38
+ | { ok: false; code: ResidualFailureCode };
39
+
40
+ /**
41
+ * Quantize one block's coefficients to int16 with a per-block float32 scale.
42
+ * Rejects non-finite inputs and any value that would saturate int16 with
43
+ * `RES_QUANTIZE_RANGE` (RESIDUAL_CODEC: saturation rejects encoding).
44
+ */
45
+ export function quantizeBlock(coefficients: Float64Array): QuantizeResult {
46
+ let peak = 0;
47
+ for (let i = 0; i < coefficients.length; i++) {
48
+ const c = coefficients[i]!;
49
+ if (!Number.isFinite(c)) return { ok: false, code: "RES_QUANTIZE_RANGE" };
50
+ const a = Math.abs(c);
51
+ if (a > peak) peak = a;
52
+ }
53
+ const out = new Int16Array(coefficients.length);
54
+ if (peak === 0) {
55
+ // All-zero block: scale is exactly 0 and every coefficient stays 0.
56
+ return { ok: true, block: { scale: 0, coefficients: out } };
57
+ }
58
+ // The persisted scale is a float32 — quantize/dequantize with the SAME
59
+ // float32 value the artifact stores so encode and decode agree bit-for-bit.
60
+ const scale = Math.fround(peak / INT16_LIMIT);
61
+ if (!Number.isFinite(scale) || scale <= 0) {
62
+ return { ok: false, code: "RES_QUANTIZE_RANGE" };
63
+ }
64
+ for (let i = 0; i < coefficients.length; i++) {
65
+ const q = roundHalfToEven(coefficients[i]! / scale);
66
+ if (q > INT16_LIMIT || q < -INT16_LIMIT) {
67
+ return { ok: false, code: "RES_QUANTIZE_RANGE" };
68
+ }
69
+ out[i] = q;
70
+ }
71
+ return { ok: true, block: { scale, coefficients: out } };
72
+ }
73
+
74
+ /** Dequantize a block back to float coefficients (`coefficient * scale`). */
75
+ export function dequantizeBlock(block: QuantizedBlockV1): Float64Array {
76
+ const out = new Float64Array(block.coefficients.length);
77
+ for (let i = 0; i < block.coefficients.length; i++) {
78
+ out[i] = block.coefficients[i]! * block.scale;
79
+ }
80
+ return out;
81
+ }
82
+
83
+ /**
84
+ * Diff a reconstructed block against the original, producing the sorted
85
+ * correction entries for that block (empty when the reconstruction is exact).
86
+ */
87
+ export function diffBlock(
88
+ original: Uint8Array,
89
+ reconstructed: Uint8Array,
90
+ ): CorrectionV1[] {
91
+ const out: CorrectionV1[] = [];
92
+ for (let i = 0; i < original.length; i++) {
93
+ if (original[i] !== reconstructed[i]) {
94
+ out.push({ offset: i, original: original[i]! });
95
+ }
96
+ }
97
+ return out;
98
+ }
99
+
100
+ /** Apply a block's corrections in place over a reconstructed block. */
101
+ export function applyCorrections(
102
+ reconstructed: Uint8Array,
103
+ corrections: readonly CorrectionV1[],
104
+ ): { ok: true } | { ok: false; code: ResidualFailureCode } {
105
+ let previous = -1;
106
+ for (const c of corrections) {
107
+ if (!Number.isInteger(c.offset) || c.offset < 0 || c.offset >= reconstructed.length) {
108
+ return { ok: false, code: "RES_CORRECTION_RANGE" };
109
+ }
110
+ if (!Number.isInteger(c.original) || c.original < 0 || c.original > 255) {
111
+ return { ok: false, code: "RES_CORRECTION_RANGE" };
112
+ }
113
+ // Entries are sorted ascending with no duplicate offset.
114
+ if (c.offset === previous) {
115
+ return { ok: false, code: "RES_CORRECTION_DUPLICATE_OFFSET" };
116
+ }
117
+ if (c.offset < previous) return { ok: false, code: "RES_CORRECTION_RANGE" };
118
+ previous = c.offset;
119
+ reconstructed[c.offset] = c.original;
120
+ }
121
+ return { ok: true };
122
+ }
123
+
124
+ // ── varint (LEB128, unsigned) ───────────────────────────────────────────────
125
+
126
+ /** Encode an unsigned integer as LEB128 varint bytes. */
127
+ export function encodeVarint(value: number): number[] {
128
+ if (!Number.isInteger(value) || value < 0) {
129
+ throw new RangeError("varint requires a non-negative integer");
130
+ }
131
+ const out: number[] = [];
132
+ let v = value;
133
+ do {
134
+ let byte = v & 0x7f;
135
+ v = Math.floor(v / 128);
136
+ if (v > 0) byte |= 0x80;
137
+ out.push(byte);
138
+ } while (v > 0);
139
+ return out;
140
+ }
141
+
142
+ /** Decode an LEB128 varint at `offset`; returns the value and the next offset. */
143
+ export function decodeVarint(
144
+ bytes: Uint8Array,
145
+ offset: number,
146
+ ): { value: number; next: number } | null {
147
+ let value = 0;
148
+ let shift = 1;
149
+ let i = offset;
150
+ for (; i < bytes.length; i++) {
151
+ const byte = bytes[i]!;
152
+ value += (byte & 0x7f) * shift;
153
+ if ((byte & 0x80) === 0) return { value, next: i + 1 };
154
+ shift *= 128;
155
+ if (shift > Number.MAX_SAFE_INTEGER) return null;
156
+ }
157
+ return null;
158
+ }
159
+
160
+ // ── correction stream serialization ─────────────────────────────────────────
161
+
162
+ /**
163
+ * Serialize the block-scoped correction stream: a varint block count, then for
164
+ * each NON-EMPTY block in ascending order a u32 LE blockIndex, a varint
165
+ * correction count, and the sorted `(u16 LE offset, u8 original)` entries.
166
+ * Omitted blocks have an implicit count of zero.
167
+ */
168
+ export function serializeCorrections(
169
+ blocks: readonly BlockCorrectionsV1[],
170
+ ): Uint8Array {
171
+ const present = blocks
172
+ .filter((b) => b.corrections.length > 0)
173
+ .slice()
174
+ .sort((a, b) => a.blockIndex - b.blockIndex);
175
+ const out: number[] = [];
176
+ out.push(...encodeVarint(present.length));
177
+ for (const b of present) {
178
+ out.push(
179
+ b.blockIndex & 0xff,
180
+ (b.blockIndex >>> 8) & 0xff,
181
+ (b.blockIndex >>> 16) & 0xff,
182
+ (b.blockIndex >>> 24) & 0xff,
183
+ );
184
+ out.push(...encodeVarint(b.corrections.length));
185
+ for (const c of b.corrections) {
186
+ out.push(c.offset & 0xff, (c.offset >>> 8) & 0xff, c.original & 0xff);
187
+ }
188
+ }
189
+ return Uint8Array.from(out);
190
+ }
191
+
192
+ /** Parse a serialized correction stream. Returns null on any malformed input. */
193
+ export function parseCorrections(
194
+ bytes: Uint8Array,
195
+ offset: number,
196
+ ): { blocks: BlockCorrectionsV1[]; next: number } | null {
197
+ const head = decodeVarint(bytes, offset);
198
+ if (!head) return null;
199
+ let pos = head.next;
200
+ const blocks: BlockCorrectionsV1[] = [];
201
+ let previousIndex = -1;
202
+ for (let b = 0; b < head.value; b++) {
203
+ if (pos + 4 > bytes.length) return null;
204
+ const blockIndex =
205
+ bytes[pos]! |
206
+ (bytes[pos + 1]! << 8) |
207
+ (bytes[pos + 2]! << 16) |
208
+ (bytes[pos + 3]! << 24);
209
+ pos += 4;
210
+ if (blockIndex < 0 || blockIndex <= previousIndex) return null;
211
+ previousIndex = blockIndex;
212
+ const count = decodeVarint(bytes, pos);
213
+ if (!count) return null;
214
+ pos = count.next;
215
+ const corrections: CorrectionV1[] = [];
216
+ let previousOffset = -1;
217
+ for (let i = 0; i < count.value; i++) {
218
+ if (pos + 3 > bytes.length) return null;
219
+ const off = bytes[pos]! | (bytes[pos + 1]! << 8);
220
+ const original = bytes[pos + 2]!;
221
+ pos += 3;
222
+ if (off >= RESIDUAL_BLOCK_SIZE) return null;
223
+ if (off <= previousOffset) return null; // duplicate or unsorted
224
+ previousOffset = off;
225
+ corrections.push({ offset: off, original });
226
+ }
227
+ blocks.push({ blockIndex, corrections });
228
+ }
229
+ return { blocks, next: pos };
230
+ }