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,158 @@
1
+ /**
2
+ * vector-cortex/residual/dct.ts — orthonormal DCT-II basis V1 (VC4B).
3
+ *
4
+ * Basis V1 is the orthonormal DCT-II matrix generated ANALYTICALLY and never
5
+ * learned or stored (RESIDUAL_CODEC §byte scope):
6
+ *
7
+ * alpha(0) = sqrt(1/n), alpha(k>0) = sqrt(2/n)
8
+ * C[k][i] = alpha(k) * cos(pi * (2i + 1) * k / (2n))
9
+ *
10
+ * Forward coefficients are the dot product with each basis row (the orthonormal
11
+ * least-squares solve); the inverse is the transpose applied to the coefficient
12
+ * vector. Byte mapping is `x = (byte - 127.5) / 127.5`; the inverse maps back
13
+ * with round-to-nearest-even and clamps to 0..255.
14
+ *
15
+ * The full 4096x4096 matrix is ~134 MB of float64, so it is NEVER materialized.
16
+ * Instead the cosine argument `pi*(2i+1)*k/(2n)` is reduced modulo `2n` against
17
+ * a precomputed half-period table of length `2n` — this is EXACTLY the same set
18
+ * of cosine values the matrix would hold (cos is sampled only at the `2n`
19
+ * distinct arguments `pi*j/(2n)`, j = 0..2n-1), so the transform is bit-stable
20
+ * and table-generation is O(n) rather than O(n^2).
21
+ *
22
+ * Pure numeric transform: no storage, no console, no network (PREVENT-PI-004 /
23
+ * PREVENT-011).
24
+ */
25
+ import { RESIDUAL_BLOCK_SIZE } from "./types.js";
26
+ /** Byte-to-signal mapping midpoint / half-range (RESIDUAL_CODEC §transform). */
27
+ const BYTE_MIDPOINT = 127.5;
28
+ /**
29
+ * Cosine table cache keyed by block length. `table[j] = cos(pi * j / (2n))` for
30
+ * j = 0..2n-1; every DCT argument `pi*(2i+1)*k/(2n)` reduces to one of these
31
+ * entries (with a sign) because cos has period `2*pi` = index period `4n`.
32
+ */
33
+ const cosTables = new Map();
34
+ /** Build (or fetch) the `4n`-entry cosine table for block length `n`. */
35
+ function cosTable(n) {
36
+ const cached = cosTables.get(n);
37
+ if (cached)
38
+ return cached;
39
+ const period = 4 * n;
40
+ const table = new Float64Array(period);
41
+ for (let j = 0; j < period; j++) {
42
+ table[j] = Math.cos((Math.PI * j) / (2 * n));
43
+ }
44
+ cosTables.set(n, table);
45
+ return table;
46
+ }
47
+ /** Orthonormal DCT-II row scale: alpha(0)=sqrt(1/n), else sqrt(2/n). */
48
+ export function alpha(k, n) {
49
+ return k === 0 ? Math.sqrt(1 / n) : Math.sqrt(2 / n);
50
+ }
51
+ /**
52
+ * Map a padded byte block to the signal domain: `x = (byte - 127.5) / 127.5`.
53
+ * The input must be exactly `n` bytes (the caller zero-pads the final block).
54
+ */
55
+ export function bytesToSignal(block) {
56
+ const out = new Float64Array(block.length);
57
+ for (let i = 0; i < block.length; i++) {
58
+ out[i] = (block[i] - BYTE_MIDPOINT) / BYTE_MIDPOINT;
59
+ }
60
+ return out;
61
+ }
62
+ /**
63
+ * Map a reconstructed signal back to bytes: invert the affine map, round to
64
+ * nearest with ties-to-even, and clamp to 0..255 (RESIDUAL_CODEC §transform).
65
+ */
66
+ export function signalToBytes(signal) {
67
+ const out = new Uint8Array(signal.length);
68
+ for (let i = 0; i < signal.length; i++) {
69
+ const v = signal[i] * BYTE_MIDPOINT + BYTE_MIDPOINT;
70
+ const r = roundHalfToEven(v);
71
+ out[i] = r < 0 ? 0 : r > 255 ? 255 : r;
72
+ }
73
+ return out;
74
+ }
75
+ /**
76
+ * Round to nearest, ties to even (banker's rounding). `Math.round` rounds ties
77
+ * toward +Infinity, which is NOT the rule RESIDUAL_CODEC mandates.
78
+ */
79
+ export function roundHalfToEven(v) {
80
+ const floor = Math.floor(v);
81
+ const diff = v - floor;
82
+ if (diff > 0.5)
83
+ return floor + 1;
84
+ if (diff < 0.5)
85
+ return floor;
86
+ // Exact tie: pick the even neighbour.
87
+ return floor % 2 === 0 ? floor : floor + 1;
88
+ }
89
+ /**
90
+ * Forward orthonormal DCT-II over one block of length `n` (default 4096).
91
+ * `coefficients[k] = alpha(k) * sum_i x[i] * cos(pi*(2i+1)*k/(2n))`, emitted in
92
+ * ascending frequency order `k = 0..n-1` (the exact RESIDUAL_CODEC coefficient
93
+ * order).
94
+ */
95
+ export function forwardDct(signal) {
96
+ const n = signal.length;
97
+ const table = cosTable(n);
98
+ const period = 4 * n;
99
+ const out = new Float64Array(n);
100
+ for (let k = 0; k < n; k++) {
101
+ let sum = 0;
102
+ // Argument index advances by 2k each step, starting at k (i=0 gives
103
+ // (2*0+1)*k = k). Reduce modulo the 4n-entry period.
104
+ let idx = k % period;
105
+ const step = (2 * k) % period;
106
+ for (let i = 0; i < n; i++) {
107
+ sum += signal[i] * table[idx];
108
+ idx += step;
109
+ if (idx >= period)
110
+ idx -= period;
111
+ }
112
+ out[k] = alpha(k, n) * sum;
113
+ }
114
+ return out;
115
+ }
116
+ /**
117
+ * Inverse orthonormal DCT (the transpose of the forward matrix):
118
+ * `x[i] = sum_k alpha(k) * c[k] * cos(pi*(2i+1)*k/(2n))`.
119
+ */
120
+ export function inverseDct(coefficients) {
121
+ const n = coefficients.length;
122
+ const table = cosTable(n);
123
+ const period = 4 * n;
124
+ // Pre-scale each coefficient by its row alpha so the inner loop is a plain
125
+ // dot product (identical arithmetic to scaling inside the loop for k, and it
126
+ // keeps the per-i accumulation order stable).
127
+ const scaled = new Float64Array(n);
128
+ for (let k = 0; k < n; k++)
129
+ scaled[k] = alpha(k, n) * coefficients[k];
130
+ const out = new Float64Array(n);
131
+ for (let i = 0; i < n; i++) {
132
+ let sum = 0;
133
+ const step = (2 * i + 1) % period;
134
+ let idx = 0;
135
+ for (let k = 0; k < n; k++) {
136
+ sum += scaled[k] * table[idx];
137
+ idx += step;
138
+ if (idx >= period)
139
+ idx -= period;
140
+ }
141
+ out[i] = sum;
142
+ }
143
+ return out;
144
+ }
145
+ /**
146
+ * Split a payload into fixed 4096-byte blocks, zero-padding ONLY the final
147
+ * block. The original length is retained by the caller (the header) so the
148
+ * padding is dropped on decode.
149
+ */
150
+ export function splitBlocks(payload, blockSize = RESIDUAL_BLOCK_SIZE) {
151
+ const blocks = [];
152
+ for (let off = 0; off < payload.length; off += blockSize) {
153
+ const block = new Uint8Array(blockSize);
154
+ block.set(payload.subarray(off, Math.min(off + blockSize, payload.length)));
155
+ blocks.push(block);
156
+ }
157
+ return blocks;
158
+ }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * vector-cortex/residual/fixture-payload.ts — deterministic payload
3
+ * materialization for the VC4B conformance corpus.
4
+ *
5
+ * The residual fixtures describe their payloads GENERATIVELY (`kind` + `length`
6
+ * + `seed`) rather than embedding megabytes of base64, so this module is the
7
+ * single normative generator both the fixture producer and the acceptance test
8
+ * agree on. Every generator is pure and deterministic — the same descriptor
9
+ * always yields byte-identical output, which is what makes the committed
10
+ * fixtures meaningful.
11
+ *
12
+ * Pure byte generation: no storage, no console, no network (PREVENT-PI-004 /
13
+ * PREVENT-011).
14
+ */
15
+ /** Linear congruential generator (numerical-recipes constants), deterministic. */
16
+ function lcgBytes(length, seed) {
17
+ const out = new Uint8Array(length);
18
+ let state = seed >>> 0;
19
+ for (let i = 0; i < length; i++) {
20
+ state = (Math.imul(state, 1664525) + 1013904223) >>> 0;
21
+ out[i] = (state >>> 24) & 0xff;
22
+ }
23
+ return out;
24
+ }
25
+ /**
26
+ * Materialize a fixture payload descriptor into exact bytes.
27
+ *
28
+ * empty zero bytes
29
+ * zeros `length` 0x00 bytes
30
+ * constant `length` copies of `value`
31
+ * sequence `i % 256`
32
+ * lcg deterministic pseudorandom stream from `seed`
33
+ * text repeating printable ASCII (valid UTF-8)
34
+ * invalid-utf8 a stream containing lone continuation/overlong bytes
35
+ * dc-outlier all 0xff except one 0x00 at `outlierOffset` (forces the exact
36
+ * correction stream: a DC-dominant block whose coarse scale
37
+ * cannot reproduce the outlier's neighbourhood)
38
+ * alternating 0x00/0xff alternation (maximum Nyquist energy)
39
+ */
40
+ export function materializePayload(d) {
41
+ const length = d.length ?? 0;
42
+ switch (d.kind) {
43
+ case "empty":
44
+ return new Uint8Array(0);
45
+ case "zeros":
46
+ return new Uint8Array(length);
47
+ case "constant":
48
+ return new Uint8Array(length).fill((d.value ?? 0) & 0xff);
49
+ case "sequence":
50
+ return Uint8Array.from({ length }, (_v, i) => i % 256);
51
+ case "lcg":
52
+ return lcgBytes(length, d.seed ?? 1);
53
+ case "text": {
54
+ const alphabet = "abcdefghijklmnopqrstuvwxyz0123456789 ";
55
+ return Uint8Array.from({ length }, (_v, i) => alphabet.charCodeAt(i % alphabet.length));
56
+ }
57
+ case "invalid-utf8": {
58
+ // Deliberately invalid: lone 0x80 continuation, 0xff (never valid), and
59
+ // the overlong 0xc0 0xaf encoding of "/". Never normalized by the codec.
60
+ const pattern = [0xff, 0x80, 0xc0, 0xaf, 0x00, 0xfe];
61
+ return Uint8Array.from({ length }, (_v, i) => pattern[i % pattern.length]);
62
+ }
63
+ case "dc-outlier": {
64
+ const out = new Uint8Array(length).fill(255);
65
+ const at = d.outlierOffset ?? 0;
66
+ if (at < length)
67
+ out[at] = 0;
68
+ return out;
69
+ }
70
+ case "alternating":
71
+ return Uint8Array.from({ length }, (_v, i) => (i % 2 === 0 ? 0 : 255));
72
+ case "literal":
73
+ return new Uint8Array(Buffer.from(d.bytesBase64 ?? "", "base64"));
74
+ default:
75
+ throw new Error(`unknown residual payload kind: ${d.kind}`);
76
+ }
77
+ }
@@ -0,0 +1,182 @@
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
+ import { GF_PRIMITIVE_POLYNOMIAL } from "./types.js";
17
+ const FIELD_SIZE = 256;
18
+ /** `EXP[i] = 2^i` in GF(2^8) (doubled length so multiply needs no modulo). */
19
+ const EXP = new Uint8Array(FIELD_SIZE * 2);
20
+ /** `LOG[x] = i` such that `2^i = x`; `LOG[0]` is unused (0 has no logarithm). */
21
+ const LOG = new Uint8Array(FIELD_SIZE);
22
+ (function buildTables() {
23
+ let x = 1;
24
+ for (let i = 0; i < FIELD_SIZE - 1; i++) {
25
+ EXP[i] = x;
26
+ LOG[x] = i;
27
+ x <<= 1;
28
+ if (x & 0x100)
29
+ x ^= GF_PRIMITIVE_POLYNOMIAL;
30
+ }
31
+ // Mirror the cycle so `EXP[a + b]` is valid for a,b <= 254 without a modulo.
32
+ for (let i = FIELD_SIZE - 1; i < EXP.length; i++) {
33
+ EXP[i] = EXP[i - (FIELD_SIZE - 1)];
34
+ }
35
+ })();
36
+ /** GF(2^8) addition (and subtraction) is XOR. */
37
+ export function gfAdd(a, b) {
38
+ return (a ^ b) & 0xff;
39
+ }
40
+ /** GF(2^8) multiplication via log/antilog tables. */
41
+ export function gfMul(a, b) {
42
+ if (a === 0 || b === 0)
43
+ return 0;
44
+ return EXP[LOG[a] + LOG[b]];
45
+ }
46
+ /** GF(2^8) division; dividing by zero is a programming error and throws. */
47
+ export function gfDiv(a, b) {
48
+ if (b === 0)
49
+ throw new RangeError("gf256: division by zero");
50
+ if (a === 0)
51
+ return 0;
52
+ return EXP[LOG[a] + (FIELD_SIZE - 1) - LOG[b]];
53
+ }
54
+ /** Multiplicative inverse of a non-zero field element. */
55
+ export function gfInv(a) {
56
+ if (a === 0)
57
+ throw new RangeError("gf256: zero has no inverse");
58
+ return EXP[FIELD_SIZE - 1 - LOG[a]];
59
+ }
60
+ /** `base^exponent` in GF(2^8) (exponent is a non-negative integer). */
61
+ export function gfPow(base, exponent) {
62
+ if (exponent === 0)
63
+ return 1;
64
+ if (base === 0)
65
+ return 0;
66
+ return EXP[(LOG[base] * exponent) % (FIELD_SIZE - 1)];
67
+ }
68
+ /** Allocate a zero matrix. */
69
+ export function gfMatrix(rows, cols) {
70
+ return { rows, cols, data: new Uint8Array(rows * cols) };
71
+ }
72
+ /** Read `m[r][c]`. */
73
+ export function gfAt(m, r, c) {
74
+ return m.data[r * m.cols + c];
75
+ }
76
+ /** Write `m[r][c] = v`. */
77
+ export function gfSet(m, r, c, v) {
78
+ m.data[r * m.cols + c] = v;
79
+ }
80
+ /**
81
+ * Build the `rows x cols` Vandermonde matrix `V[r][c] = alpha_r^c` with the
82
+ * distinct evaluation points `alpha_r = r + 1` (RESIDUAL_CODEC: rows r=0..8,
83
+ * columns c=0..5).
84
+ */
85
+ export function vandermonde(rows, cols) {
86
+ const m = gfMatrix(rows, cols);
87
+ for (let r = 0; r < rows; r++) {
88
+ for (let c = 0; c < cols; c++) {
89
+ gfSet(m, r, c, gfPow((r + 1) & 0xff, c));
90
+ }
91
+ }
92
+ return m;
93
+ }
94
+ /** Matrix product `a x b` over GF(2^8). */
95
+ export function gfMatMul(a, b) {
96
+ const out = gfMatrix(a.rows, b.cols);
97
+ for (let r = 0; r < a.rows; r++) {
98
+ for (let c = 0; c < b.cols; c++) {
99
+ let acc = 0;
100
+ for (let i = 0; i < a.cols; i++) {
101
+ acc ^= gfMul(gfAt(a, r, i), gfAt(b, i, c));
102
+ }
103
+ gfSet(out, r, c, acc);
104
+ }
105
+ }
106
+ return out;
107
+ }
108
+ /** Extract the contiguous row range `[start, start+count)` of a matrix. */
109
+ export function gfSubRows(m, start, count) {
110
+ const out = gfMatrix(count, m.cols);
111
+ out.data.set(m.data.subarray(start * m.cols, (start + count) * m.cols));
112
+ return out;
113
+ }
114
+ /** Gather the given row indices (in order) into a new matrix. */
115
+ export function gfPickRows(m, indices) {
116
+ const out = gfMatrix(indices.length, m.cols);
117
+ indices.forEach((src, dest) => {
118
+ out.data.set(m.data.subarray(src * m.cols, (src + 1) * m.cols), dest * m.cols);
119
+ });
120
+ return out;
121
+ }
122
+ /**
123
+ * Invert a square GF(2^8) matrix by Gauss–Jordan elimination with a
124
+ * DETERMINISTIC left-to-right pivot search (the first row at or below the
125
+ * current column with a non-zero entry). Returns null when the matrix is
126
+ * singular.
127
+ */
128
+ export function gfInvert(m) {
129
+ if (m.rows !== m.cols)
130
+ return null;
131
+ const n = m.rows;
132
+ const work = gfMatrix(n, n);
133
+ work.data.set(m.data);
134
+ const inv = gfMatrix(n, n);
135
+ for (let i = 0; i < n; i++)
136
+ gfSet(inv, i, i, 1);
137
+ const swapRows = (mat, a, b) => {
138
+ if (a === b)
139
+ return;
140
+ for (let c = 0; c < mat.cols; c++) {
141
+ const t = gfAt(mat, a, c);
142
+ gfSet(mat, a, c, gfAt(mat, b, c));
143
+ gfSet(mat, b, c, t);
144
+ }
145
+ };
146
+ for (let col = 0; col < n; col++) {
147
+ // Deterministic pivot: the lowest-index row >= col with a non-zero entry.
148
+ let pivot = -1;
149
+ for (let r = col; r < n; r++) {
150
+ if (gfAt(work, r, col) !== 0) {
151
+ pivot = r;
152
+ break;
153
+ }
154
+ }
155
+ if (pivot === -1)
156
+ return null; // singular
157
+ swapRows(work, col, pivot);
158
+ swapRows(inv, col, pivot);
159
+ // Normalize the pivot row.
160
+ const pivotValue = gfAt(work, col, col);
161
+ if (pivotValue !== 1) {
162
+ const scale = gfInv(pivotValue);
163
+ for (let c = 0; c < n; c++) {
164
+ gfSet(work, col, c, gfMul(gfAt(work, col, c), scale));
165
+ gfSet(inv, col, c, gfMul(gfAt(inv, col, c), scale));
166
+ }
167
+ }
168
+ // Eliminate the column from every other row.
169
+ for (let r = 0; r < n; r++) {
170
+ if (r === col)
171
+ continue;
172
+ const factor = gfAt(work, r, col);
173
+ if (factor === 0)
174
+ continue;
175
+ for (let c = 0; c < n; c++) {
176
+ gfSet(work, r, c, gfAdd(gfAt(work, r, c), gfMul(factor, gfAt(work, col, c))));
177
+ gfSet(inv, r, c, gfAdd(gfAt(inv, r, c), gfMul(factor, gfAt(inv, col, c))));
178
+ }
179
+ }
180
+ }
181
+ return inv;
182
+ }
@@ -0,0 +1,245 @@
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
+ import { createHash } from "node:crypto";
22
+ import { gfAdd, gfAt, gfInvert, gfMatMul, gfMatrix, gfMul, gfPickRows, gfSet, gfSubRows, vandermonde, } from "./gf256.js";
23
+ import { RS_DATA_SHARDS, RS_PARITY_SHARDS, RS_TOTAL_SHARDS, } from "./types.js";
24
+ /** SHA-256 of a byte slice, lowercase hex. */
25
+ export function sha256Hex(bytes) {
26
+ return createHash("sha256").update(bytes).digest("hex");
27
+ }
28
+ /**
29
+ * The systematic `9x6` generator `G = V x inverse(V[0..5,0..5])`. Built once:
30
+ * the matrix depends only on the fixed geometry and the fixed field.
31
+ */
32
+ let cachedGenerator = null;
33
+ export function systematicGenerator() {
34
+ if (cachedGenerator)
35
+ return cachedGenerator;
36
+ const v = vandermonde(RS_TOTAL_SHARDS, RS_DATA_SHARDS);
37
+ const top = gfSubRows(v, 0, RS_DATA_SHARDS);
38
+ const topInv = gfInvert(top);
39
+ if (!topInv) {
40
+ // The fixed Vandermonde top square over GF(2^8) with distinct points 1..6
41
+ // is always invertible; this guard exists so a future geometry change fails
42
+ // loudly rather than silently producing a broken generator.
43
+ throw new Error("residual parity: singular Vandermonde top square");
44
+ }
45
+ cachedGenerator = gfMatMul(v, topInv);
46
+ return cachedGenerator;
47
+ }
48
+ /** Per-shard payload length for a stream of `streamLength` bytes. */
49
+ export function shardLength(streamLength) {
50
+ return Math.ceil(streamLength / RS_DATA_SHARDS);
51
+ }
52
+ /**
53
+ * Split the protected stream into 9 shards: 6 systematic data shards (equal
54
+ * length, final one zero-padded) followed by 3 parity shards.
55
+ */
56
+ export function encodeShards(stream) {
57
+ const len = shardLength(stream.length);
58
+ const g = systematicGenerator();
59
+ const data = [];
60
+ for (let i = 0; i < RS_DATA_SHARDS; i++) {
61
+ const shard = new Uint8Array(len);
62
+ shard.set(stream.subarray(i * len, Math.min((i + 1) * len, stream.length)));
63
+ data.push(shard);
64
+ }
65
+ const shards = data.map((bytes, index) => ({
66
+ schema: "parity-shard-v1",
67
+ index,
68
+ kind: "data",
69
+ bytes,
70
+ digest: sha256Hex(bytes),
71
+ streamLength: stream.length,
72
+ }));
73
+ for (let p = 0; p < RS_PARITY_SHARDS; p++) {
74
+ const row = RS_DATA_SHARDS + p;
75
+ const bytes = new Uint8Array(len);
76
+ for (let c = 0; c < RS_DATA_SHARDS; c++) {
77
+ const coefficient = gfAt(g, row, c);
78
+ if (coefficient === 0)
79
+ continue;
80
+ const src = data[c];
81
+ for (let b = 0; b < len; b++) {
82
+ bytes[b] = gfAdd(bytes[b], gfMul(coefficient, src[b]));
83
+ }
84
+ }
85
+ shards.push({
86
+ schema: "parity-shard-v1",
87
+ index: row,
88
+ kind: "parity",
89
+ bytes,
90
+ digest: sha256Hex(bytes),
91
+ streamLength: stream.length,
92
+ });
93
+ }
94
+ return shards;
95
+ }
96
+ /**
97
+ * Verify every supplied shard's SHA-256 and return the indices whose digest
98
+ * does NOT match. A corrupt shard is DETECTED here; the caller promotes it to a
99
+ * known erasure rather than attempting unknown-error correction.
100
+ */
101
+ export function detectCorruptShards(shards) {
102
+ const corrupt = [];
103
+ for (const s of shards) {
104
+ if (sha256Hex(s.bytes) !== s.digest)
105
+ corrupt.push(s.index);
106
+ }
107
+ return corrupt.sort((a, b) => a - b);
108
+ }
109
+ /** Structural validation shared by recovery paths. */
110
+ function validateShards(shards) {
111
+ if (shards.length === 0)
112
+ return { ok: false, code: "RES_TOO_MANY_ERASURES" };
113
+ const seen = new Set();
114
+ const length = shards[0].bytes.length;
115
+ const streamLength = shards[0].streamLength;
116
+ for (const s of shards) {
117
+ if (s.index < 0 || s.index >= RS_TOTAL_SHARDS || !Number.isInteger(s.index)) {
118
+ return { ok: false, code: "RES_DUPLICATE_SHARD_INDEX" };
119
+ }
120
+ if (seen.has(s.index))
121
+ return { ok: false, code: "RES_DUPLICATE_SHARD_INDEX" };
122
+ seen.add(s.index);
123
+ if (s.bytes.length !== length) {
124
+ return { ok: false, code: "RES_SHARD_LENGTH_MISMATCH" };
125
+ }
126
+ if (s.streamLength !== streamLength) {
127
+ return { ok: false, code: "RES_SHARD_LENGTH_MISMATCH" };
128
+ }
129
+ }
130
+ if (shardLength(streamLength) !== length) {
131
+ return { ok: false, code: "RES_SHARD_LENGTH_MISMATCH" };
132
+ }
133
+ return { ok: true, length, streamLength };
134
+ }
135
+ /**
136
+ * Reconstruct the protected stream from AT LEAST 6 surviving shards.
137
+ *
138
+ * Every supplied shard is digest-checked first: a shard whose SHA-256 does not
139
+ * match its recorded digest is treated as a KNOWN erasure (it is dropped from
140
+ * the surviving set), which is exactly how a corrupt parity shard becomes the
141
+ * third erasure in the sprint's failure-injection case. If fewer than 6 shards
142
+ * survive that check, recovery fails closed with `RES_TOO_MANY_ERASURES` and
143
+ * never attempts unknown-error correction.
144
+ */
145
+ export function recoverStream(shards) {
146
+ const structural = validateShards(shards);
147
+ if (!structural.ok)
148
+ return structural;
149
+ const { length, streamLength } = structural;
150
+ // Digest check: promote every detected corruption to a known erasure.
151
+ const corrupt = new Set(detectCorruptShards(shards));
152
+ const survivors = shards
153
+ .filter((s) => !corrupt.has(s.index))
154
+ .slice()
155
+ .sort((a, b) => a.index - b.index);
156
+ if (survivors.length < RS_DATA_SHARDS) {
157
+ return { ok: false, code: "RES_TOO_MANY_ERASURES" };
158
+ }
159
+ // Deterministic selection: the six lowest surviving indices.
160
+ const chosen = survivors.slice(0, RS_DATA_SHARDS);
161
+ const g = systematicGenerator();
162
+ const sub = gfPickRows(g, chosen.map((s) => s.index));
163
+ const subInv = gfInvert(sub);
164
+ if (!subInv)
165
+ return { ok: false, code: "RES_SINGULAR_MATRIX" };
166
+ // Recover the six data shards: D = inverse(G_chosen) x S_chosen.
167
+ const recoveredData = [];
168
+ for (let r = 0; r < RS_DATA_SHARDS; r++) {
169
+ const out = new Uint8Array(length);
170
+ for (let c = 0; c < RS_DATA_SHARDS; c++) {
171
+ const coefficient = gfAt(subInv, r, c);
172
+ if (coefficient === 0)
173
+ continue;
174
+ const src = chosen[c].bytes;
175
+ for (let b = 0; b < length; b++) {
176
+ out[b] = gfAdd(out[b], gfMul(coefficient, src[b]));
177
+ }
178
+ }
179
+ recoveredData.push(out);
180
+ }
181
+ const stream = new Uint8Array(streamLength);
182
+ for (let i = 0; i < RS_DATA_SHARDS; i++) {
183
+ const start = i * length;
184
+ if (start >= streamLength)
185
+ break;
186
+ const take = Math.min(length, streamLength - start);
187
+ stream.set(recoveredData[i].subarray(0, take), start);
188
+ }
189
+ const present = new Set(chosen.map((s) => s.index));
190
+ const recoveredIndices = [];
191
+ for (let i = 0; i < RS_TOTAL_SHARDS; i++)
192
+ if (!present.has(i))
193
+ recoveredIndices.push(i);
194
+ return { ok: true, stream, recoveredIndices };
195
+ }
196
+ /**
197
+ * Recover with an EXPLICIT erasure set: the named indices are treated as lost
198
+ * regardless of their digest. More than `m=3` marked erasures fails closed with
199
+ * `RES_TOO_MANY_ERASURES` before any matrix work — including the case where
200
+ * three data shards are marked AND a fourth (parity) shard is corrupt.
201
+ */
202
+ export function recoverWithErasures(shards, erasedIndices) {
203
+ const erased = new Set(erasedIndices);
204
+ if (erased.size > RS_PARITY_SHARDS) {
205
+ return { ok: false, code: "RES_TOO_MANY_ERASURES" };
206
+ }
207
+ const kept = shards.filter((s) => !erased.has(s.index));
208
+ // A corruption among the KEPT shards is an additional known erasure; if that
209
+ // pushes the total past m=3 the recovery fails closed rather than attempting
210
+ // unknown-error correction.
211
+ const corrupt = detectCorruptShards(kept);
212
+ if (erased.size + corrupt.length > RS_PARITY_SHARDS) {
213
+ return { ok: false, code: "RES_TOO_MANY_ERASURES" };
214
+ }
215
+ return recoverStream(kept);
216
+ }
217
+ /** Rebuild the full 9-shard set from a recovered stream (repair path). */
218
+ export function repairShards(stream) {
219
+ return encodeShards(stream);
220
+ }
221
+ /** Test/diagnostic helper: the systematic generator's parity rows. */
222
+ export function parityRows() {
223
+ const g = systematicGenerator();
224
+ const rows = [];
225
+ for (let r = RS_DATA_SHARDS; r < RS_TOTAL_SHARDS; r++) {
226
+ const row = [];
227
+ for (let c = 0; c < RS_DATA_SHARDS; c++)
228
+ row.push(gfAt(g, r, c));
229
+ rows.push(row);
230
+ }
231
+ return rows;
232
+ }
233
+ /** Test helper: assert the generator's top square really is the identity. */
234
+ export function generatorIsSystematic() {
235
+ const g = systematicGenerator();
236
+ for (let r = 0; r < RS_DATA_SHARDS; r++) {
237
+ for (let c = 0; c < RS_DATA_SHARDS; c++) {
238
+ const expected = r === c ? 1 : 0;
239
+ if (gfAt(g, r, c) !== expected)
240
+ return false;
241
+ }
242
+ }
243
+ return true;
244
+ }
245
+ export { gfMatrix, gfSet };