pi-mega-compact 0.19.3 → 0.20.1

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 (114) hide show
  1. package/dist/config/vector-cortex.js +24 -0
  2. package/dist/config.js +1 -1
  3. package/dist/extensions/dashboard-server/routes-cache.test/_helpers.js +2 -0
  4. package/dist/extensions/dashboard-server/routes-rag-settings-helpers.js +2 -0
  5. package/dist/extensions/dashboard-server/routes-vector-cortex-helpers.js +100 -0
  6. package/dist/extensions/dashboard-server/routes-vector-cortex-residual.js +51 -0
  7. package/dist/extensions/dashboard-server/routes-vector-cortex-shards.js +48 -0
  8. package/dist/extensions/dashboard-server/routes-vector-cortex.js +2 -0
  9. package/dist/extensions/dashboard-server/routes.js +1 -1
  10. package/dist/extensions/dashboard-server/server.js +41 -16
  11. package/dist/extensions/dashboard-server/tailscale.js +32 -0
  12. package/dist/src/config/vector-cortex.js +24 -0
  13. package/dist/src/config.js +1 -1
  14. package/dist/src/vector-cortex/residual/codec.js +224 -0
  15. package/dist/src/vector-cortex/residual/dct.js +158 -0
  16. package/dist/src/vector-cortex/residual/fixture-payload.js +77 -0
  17. package/dist/src/vector-cortex/residual/gf256.js +182 -0
  18. package/dist/src/vector-cortex/residual/parity.js +245 -0
  19. package/dist/src/vector-cortex/residual/quantize.js +200 -0
  20. package/dist/src/vector-cortex/residual/stream.js +124 -0
  21. package/dist/src/vector-cortex/residual/types.js +55 -0
  22. package/dist/src/vector-cortex/shards/exact.js +134 -0
  23. package/dist/src/vector-cortex/shards/manifest.js +237 -0
  24. package/dist/src/vector-cortex/shards/semantic.js +137 -0
  25. package/dist/src/vector-cortex/shards/types.js +51 -0
  26. package/dist/vector-cortex/residual/codec.js +224 -0
  27. package/dist/vector-cortex/residual/dct.js +158 -0
  28. package/dist/vector-cortex/residual/fixture-payload.js +77 -0
  29. package/dist/vector-cortex/residual/gf256.js +182 -0
  30. package/dist/vector-cortex/residual/parity.js +245 -0
  31. package/dist/vector-cortex/residual/quantize.js +200 -0
  32. package/dist/vector-cortex/residual/stream.js +124 -0
  33. package/dist/vector-cortex/residual/types.js +55 -0
  34. package/dist/vector-cortex/shards/exact.js +134 -0
  35. package/dist/vector-cortex/shards/manifest.js +237 -0
  36. package/dist/vector-cortex/shards/semantic.js +137 -0
  37. package/dist/vector-cortex/shards/types.js +51 -0
  38. package/extensions/dashboard-client/dist/assets/{AreaChart-DqiFSnDx.js → AreaChart-n4z5w9Ox.js} +2 -2
  39. package/extensions/dashboard-client/dist/assets/{AreaChart-DqiFSnDx.js.map → AreaChart-n4z5w9Ox.js.map} +1 -1
  40. package/extensions/dashboard-client/dist/assets/{BarChart-Cqg-PQMK.js → BarChart-BLGjWWBr.js} +2 -2
  41. package/extensions/dashboard-client/dist/assets/{BarChart-Cqg-PQMK.js.map → BarChart-BLGjWWBr.js.map} +1 -1
  42. package/extensions/dashboard-client/dist/assets/{CacheTab-BPNs0xIF.js → CacheTab-CAnrKKVK.js} +2 -2
  43. package/extensions/dashboard-client/dist/assets/{CacheTab-BPNs0xIF.js.map → CacheTab-CAnrKKVK.js.map} +1 -1
  44. package/extensions/dashboard-client/dist/assets/{EventsTab-BQf7OGkf.js → EventsTab-D-GL7Ngq.js} +2 -2
  45. package/extensions/dashboard-client/dist/assets/{EventsTab-BQf7OGkf.js.map → EventsTab-D-GL7Ngq.js.map} +1 -1
  46. package/extensions/dashboard-client/dist/assets/{HealthTab-B14TaSPN.js → HealthTab-C4x5qQ8s.js} +2 -2
  47. package/extensions/dashboard-client/dist/assets/{HealthTab-B14TaSPN.js.map → HealthTab-C4x5qQ8s.js.map} +1 -1
  48. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-D8lkRoa1.js → MaintenanceTab-BSPs0eq5.js} +2 -2
  49. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-D8lkRoa1.js.map → MaintenanceTab-BSPs0eq5.js.map} +1 -1
  50. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-BOWtGfpx.js → MemoryMapTab-BWEUtTSu.js} +2 -2
  51. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-BOWtGfpx.js.map → MemoryMapTab-BWEUtTSu.js.map} +1 -1
  52. package/extensions/dashboard-client/dist/assets/{MetricsTab-7XI9cWaX.js → MetricsTab-Dhm1c3Cx.js} +2 -2
  53. package/extensions/dashboard-client/dist/assets/{MetricsTab-7XI9cWaX.js.map → MetricsTab-Dhm1c3Cx.js.map} +1 -1
  54. package/extensions/dashboard-client/dist/assets/{OverviewTab-C9KaGYvh.js → OverviewTab-Cm6cySiQ.js} +2 -2
  55. package/extensions/dashboard-client/dist/assets/{OverviewTab-C9KaGYvh.js.map → OverviewTab-Cm6cySiQ.js.map} +1 -1
  56. package/extensions/dashboard-client/dist/assets/{ReposTab-BwESNhox.js → ReposTab-DxLN1N4K.js} +2 -2
  57. package/extensions/dashboard-client/dist/assets/{ReposTab-BwESNhox.js.map → ReposTab-DxLN1N4K.js.map} +1 -1
  58. package/extensions/dashboard-client/dist/assets/{SessionsTab-DKH55Rda.js → SessionsTab-CZuI4ISW.js} +2 -2
  59. package/extensions/dashboard-client/dist/assets/{SessionsTab-DKH55Rda.js.map → SessionsTab-CZuI4ISW.js.map} +1 -1
  60. package/extensions/dashboard-client/dist/assets/{SetupTab-zDxf-nJd.js → SetupTab-C_0VOomm.js} +2 -2
  61. package/extensions/dashboard-client/dist/assets/{SetupTab-zDxf-nJd.js.map → SetupTab-C_0VOomm.js.map} +1 -1
  62. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-C9isog4L.js → TimeSavedCard-BhbaRLmi.js} +2 -2
  63. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-C9isog4L.js.map → TimeSavedCard-BhbaRLmi.js.map} +1 -1
  64. package/extensions/dashboard-client/dist/assets/{TurnsTab-ciI6ydvj.js → TurnsTab-CXJmbjje.js} +2 -2
  65. package/extensions/dashboard-client/dist/assets/{TurnsTab-ciI6ydvj.js.map → TurnsTab-CXJmbjje.js.map} +1 -1
  66. package/extensions/dashboard-client/dist/assets/VectorCortexTab-BGe9hu3r.js +2 -0
  67. package/extensions/dashboard-client/dist/assets/VectorCortexTab-BGe9hu3r.js.map +1 -0
  68. package/extensions/dashboard-client/dist/assets/{WikiTab-DcYHc734.js → WikiTab-C5uvMCzp.js} +2 -2
  69. package/extensions/dashboard-client/dist/assets/{WikiTab-DcYHc734.js.map → WikiTab-C5uvMCzp.js.map} +1 -1
  70. package/extensions/dashboard-client/dist/assets/{button-DF8lx4bt.js → button-Cl_fxJcg.js} +2 -2
  71. package/extensions/dashboard-client/dist/assets/{button-DF8lx4bt.js.map → button-Cl_fxJcg.js.map} +1 -1
  72. package/extensions/dashboard-client/dist/assets/{card-DIoDheSZ.js → card-BH8Le5YM.js} +2 -2
  73. package/extensions/dashboard-client/dist/assets/{card-DIoDheSZ.js.map → card-BH8Le5YM.js.map} +1 -1
  74. package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-BFRzeYX3.js → generateCategoricalChart-sZ6ZNtBi.js} +2 -2
  75. package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-BFRzeYX3.js.map → generateCategoricalChart-sZ6ZNtBi.js.map} +1 -1
  76. package/extensions/dashboard-client/dist/assets/{index-DSZON0Yi.js → index-CX9zv8cJ.js} +3 -3
  77. package/extensions/dashboard-client/dist/assets/{index-DSZON0Yi.js.map → index-CX9zv8cJ.js.map} +1 -1
  78. package/extensions/dashboard-client/dist/assets/{switch-l4qo22mU.js → switch-CXJhcjhM.js} +2 -2
  79. package/extensions/dashboard-client/dist/assets/{switch-l4qo22mU.js.map → switch-CXJhcjhM.js.map} +1 -1
  80. package/extensions/dashboard-client/dist/assets/{toggle-DPn6td1_.js → toggle-CoLtIc6y.js} +2 -2
  81. package/extensions/dashboard-client/dist/assets/{toggle-DPn6td1_.js.map → toggle-CoLtIc6y.js.map} +1 -1
  82. package/extensions/dashboard-client/dist/assets/{useSSE-BLmolqTJ.js → useSSE-fSXho606.js} +2 -2
  83. package/extensions/dashboard-client/dist/assets/{useSSE-BLmolqTJ.js.map → useSSE-fSXho606.js.map} +1 -1
  84. package/extensions/dashboard-client/dist/index.html +1 -1
  85. package/extensions/dashboard-client/src/api/vector-cortex.ts +9 -0
  86. package/extensions/dashboard-client/src/tabs/VectorCortexTab.tsx +35 -0
  87. package/extensions/dashboard-client/src/types/vector-cortex.ts +13 -0
  88. package/extensions/dashboard-server/api-contracts/vector-cortex.ts +53 -0
  89. package/extensions/dashboard-server/routes-cache.test/_helpers.ts +2 -0
  90. package/extensions/dashboard-server/routes-rag-settings-helpers.ts +12 -0
  91. package/extensions/dashboard-server/routes-vector-cortex-helpers.ts +110 -0
  92. package/extensions/dashboard-server/routes-vector-cortex-residual.ts +60 -0
  93. package/extensions/dashboard-server/routes-vector-cortex-shards.ts +57 -0
  94. package/extensions/dashboard-server/routes-vector-cortex.ts +2 -0
  95. package/extensions/dashboard-server/routes.ts +2 -0
  96. package/extensions/dashboard-server/server.ts +46 -17
  97. package/extensions/dashboard-server/tailscale.ts +32 -0
  98. package/package.json +1 -1
  99. package/src/config/vector-cortex.ts +26 -0
  100. package/src/config.ts +2 -0
  101. package/src/vector-cortex/residual/codec.ts +292 -0
  102. package/src/vector-cortex/residual/dct.ts +166 -0
  103. package/src/vector-cortex/residual/fixture-payload.ts +90 -0
  104. package/src/vector-cortex/residual/gf256.ts +196 -0
  105. package/src/vector-cortex/residual/parity.ts +283 -0
  106. package/src/vector-cortex/residual/quantize.ts +230 -0
  107. package/src/vector-cortex/residual/stream.ts +135 -0
  108. package/src/vector-cortex/residual/types.ts +236 -0
  109. package/src/vector-cortex/shards/exact.ts +141 -0
  110. package/src/vector-cortex/shards/manifest.ts +276 -0
  111. package/src/vector-cortex/shards/semantic.ts +156 -0
  112. package/src/vector-cortex/shards/types.ts +212 -0
  113. package/extensions/dashboard-client/dist/assets/VectorCortexTab-qTZ7NWng.js +0 -2
  114. package/extensions/dashboard-client/dist/assets/VectorCortexTab-qTZ7NWng.js.map +0 -1
@@ -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 };
@@ -0,0 +1,200 @@
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
+ import { roundHalfToEven } from "./dct.js";
23
+ import { RESIDUAL_BLOCK_SIZE, } from "./types.js";
24
+ /** Int16 quantization limit (symmetric; -32768 is never produced). */
25
+ export const INT16_LIMIT = 32767;
26
+ /**
27
+ * Quantize one block's coefficients to int16 with a per-block float32 scale.
28
+ * Rejects non-finite inputs and any value that would saturate int16 with
29
+ * `RES_QUANTIZE_RANGE` (RESIDUAL_CODEC: saturation rejects encoding).
30
+ */
31
+ export function quantizeBlock(coefficients) {
32
+ let peak = 0;
33
+ for (let i = 0; i < coefficients.length; i++) {
34
+ const c = coefficients[i];
35
+ if (!Number.isFinite(c))
36
+ return { ok: false, code: "RES_QUANTIZE_RANGE" };
37
+ const a = Math.abs(c);
38
+ if (a > peak)
39
+ peak = a;
40
+ }
41
+ const out = new Int16Array(coefficients.length);
42
+ if (peak === 0) {
43
+ // All-zero block: scale is exactly 0 and every coefficient stays 0.
44
+ return { ok: true, block: { scale: 0, coefficients: out } };
45
+ }
46
+ // The persisted scale is a float32 — quantize/dequantize with the SAME
47
+ // float32 value the artifact stores so encode and decode agree bit-for-bit.
48
+ const scale = Math.fround(peak / INT16_LIMIT);
49
+ if (!Number.isFinite(scale) || scale <= 0) {
50
+ return { ok: false, code: "RES_QUANTIZE_RANGE" };
51
+ }
52
+ for (let i = 0; i < coefficients.length; i++) {
53
+ const q = roundHalfToEven(coefficients[i] / scale);
54
+ if (q > INT16_LIMIT || q < -INT16_LIMIT) {
55
+ return { ok: false, code: "RES_QUANTIZE_RANGE" };
56
+ }
57
+ out[i] = q;
58
+ }
59
+ return { ok: true, block: { scale, coefficients: out } };
60
+ }
61
+ /** Dequantize a block back to float coefficients (`coefficient * scale`). */
62
+ export function dequantizeBlock(block) {
63
+ const out = new Float64Array(block.coefficients.length);
64
+ for (let i = 0; i < block.coefficients.length; i++) {
65
+ out[i] = block.coefficients[i] * block.scale;
66
+ }
67
+ return out;
68
+ }
69
+ /**
70
+ * Diff a reconstructed block against the original, producing the sorted
71
+ * correction entries for that block (empty when the reconstruction is exact).
72
+ */
73
+ export function diffBlock(original, reconstructed) {
74
+ const out = [];
75
+ for (let i = 0; i < original.length; i++) {
76
+ if (original[i] !== reconstructed[i]) {
77
+ out.push({ offset: i, original: original[i] });
78
+ }
79
+ }
80
+ return out;
81
+ }
82
+ /** Apply a block's corrections in place over a reconstructed block. */
83
+ export function applyCorrections(reconstructed, corrections) {
84
+ let previous = -1;
85
+ for (const c of corrections) {
86
+ if (!Number.isInteger(c.offset) || c.offset < 0 || c.offset >= reconstructed.length) {
87
+ return { ok: false, code: "RES_CORRECTION_RANGE" };
88
+ }
89
+ if (!Number.isInteger(c.original) || c.original < 0 || c.original > 255) {
90
+ return { ok: false, code: "RES_CORRECTION_RANGE" };
91
+ }
92
+ // Entries are sorted ascending with no duplicate offset.
93
+ if (c.offset === previous) {
94
+ return { ok: false, code: "RES_CORRECTION_DUPLICATE_OFFSET" };
95
+ }
96
+ if (c.offset < previous)
97
+ return { ok: false, code: "RES_CORRECTION_RANGE" };
98
+ previous = c.offset;
99
+ reconstructed[c.offset] = c.original;
100
+ }
101
+ return { ok: true };
102
+ }
103
+ // ── varint (LEB128, unsigned) ───────────────────────────────────────────────
104
+ /** Encode an unsigned integer as LEB128 varint bytes. */
105
+ export function encodeVarint(value) {
106
+ if (!Number.isInteger(value) || value < 0) {
107
+ throw new RangeError("varint requires a non-negative integer");
108
+ }
109
+ const out = [];
110
+ let v = value;
111
+ do {
112
+ let byte = v & 0x7f;
113
+ v = Math.floor(v / 128);
114
+ if (v > 0)
115
+ byte |= 0x80;
116
+ out.push(byte);
117
+ } while (v > 0);
118
+ return out;
119
+ }
120
+ /** Decode an LEB128 varint at `offset`; returns the value and the next offset. */
121
+ export function decodeVarint(bytes, offset) {
122
+ let value = 0;
123
+ let shift = 1;
124
+ let i = offset;
125
+ for (; i < bytes.length; i++) {
126
+ const byte = bytes[i];
127
+ value += (byte & 0x7f) * shift;
128
+ if ((byte & 0x80) === 0)
129
+ return { value, next: i + 1 };
130
+ shift *= 128;
131
+ if (shift > Number.MAX_SAFE_INTEGER)
132
+ return null;
133
+ }
134
+ return null;
135
+ }
136
+ // ── correction stream serialization ─────────────────────────────────────────
137
+ /**
138
+ * Serialize the block-scoped correction stream: a varint block count, then for
139
+ * each NON-EMPTY block in ascending order a u32 LE blockIndex, a varint
140
+ * correction count, and the sorted `(u16 LE offset, u8 original)` entries.
141
+ * Omitted blocks have an implicit count of zero.
142
+ */
143
+ export function serializeCorrections(blocks) {
144
+ const present = blocks
145
+ .filter((b) => b.corrections.length > 0)
146
+ .slice()
147
+ .sort((a, b) => a.blockIndex - b.blockIndex);
148
+ const out = [];
149
+ out.push(...encodeVarint(present.length));
150
+ for (const b of present) {
151
+ out.push(b.blockIndex & 0xff, (b.blockIndex >>> 8) & 0xff, (b.blockIndex >>> 16) & 0xff, (b.blockIndex >>> 24) & 0xff);
152
+ out.push(...encodeVarint(b.corrections.length));
153
+ for (const c of b.corrections) {
154
+ out.push(c.offset & 0xff, (c.offset >>> 8) & 0xff, c.original & 0xff);
155
+ }
156
+ }
157
+ return Uint8Array.from(out);
158
+ }
159
+ /** Parse a serialized correction stream. Returns null on any malformed input. */
160
+ export function parseCorrections(bytes, offset) {
161
+ const head = decodeVarint(bytes, offset);
162
+ if (!head)
163
+ return null;
164
+ let pos = head.next;
165
+ const blocks = [];
166
+ let previousIndex = -1;
167
+ for (let b = 0; b < head.value; b++) {
168
+ if (pos + 4 > bytes.length)
169
+ return null;
170
+ const blockIndex = bytes[pos] |
171
+ (bytes[pos + 1] << 8) |
172
+ (bytes[pos + 2] << 16) |
173
+ (bytes[pos + 3] << 24);
174
+ pos += 4;
175
+ if (blockIndex < 0 || blockIndex <= previousIndex)
176
+ return null;
177
+ previousIndex = blockIndex;
178
+ const count = decodeVarint(bytes, pos);
179
+ if (!count)
180
+ return null;
181
+ pos = count.next;
182
+ const corrections = [];
183
+ let previousOffset = -1;
184
+ for (let i = 0; i < count.value; i++) {
185
+ if (pos + 3 > bytes.length)
186
+ return null;
187
+ const off = bytes[pos] | (bytes[pos + 1] << 8);
188
+ const original = bytes[pos + 2];
189
+ pos += 3;
190
+ if (off >= RESIDUAL_BLOCK_SIZE)
191
+ return null;
192
+ if (off <= previousOffset)
193
+ return null; // duplicate or unsorted
194
+ previousOffset = off;
195
+ corrections.push({ offset: off, original });
196
+ }
197
+ blocks.push({ blockIndex, corrections });
198
+ }
199
+ return { blocks, next: pos };
200
+ }
@@ -0,0 +1,124 @@
1
+ /**
2
+ * vector-cortex/residual/stream.ts — canonical PROTECTED STREAM serialization
3
+ * for the VC4B residual codec.
4
+ *
5
+ * The protected stream is `header + all block scales/coefficient arrays +
6
+ * corrections` (RESIDUAL_CODEC §erasure parity), serialized in exactly this
7
+ * canonical order so encode and decode are byte-symmetric:
8
+ *
9
+ * header (46 bytes) magic `VCR1` | u32 LE originalLength | 32-byte SHA-256
10
+ * | u16 LE blockSize | u16 LE k | u16 LE m
11
+ * blocks for each block in ascending index:
12
+ * float32 LE scale | 4096 * int16 LE coefficient
13
+ * corrections varint blockCount, then per non-empty block
14
+ * u32 LE blockIndex | varint count
15
+ * | count * (u16 LE offset, u8 original)
16
+ *
17
+ * Pure serialization: no storage, no console, no network (PREVENT-PI-004 /
18
+ * PREVENT-011).
19
+ */
20
+ import { parseCorrections, serializeCorrections, } from "./quantize.js";
21
+ import { RESIDUAL_BLOCK_SIZE, RESIDUAL_HEADER_BYTES, RESIDUAL_MAGIC, RS_DATA_SHARDS, RS_PARITY_SHARDS, } from "./types.js";
22
+ /** Per-block serialized size: float32 scale + n int16 coefficients. */
23
+ export function blockBytes(blockSize = RESIDUAL_BLOCK_SIZE) {
24
+ return 4 + blockSize * 2;
25
+ }
26
+ /** Serialize the canonical 46-byte header. */
27
+ export function serializeHeader(header) {
28
+ const out = new Uint8Array(RESIDUAL_HEADER_BYTES);
29
+ const view = new DataView(out.buffer);
30
+ for (let i = 0; i < 4; i++)
31
+ out[i] = RESIDUAL_MAGIC.charCodeAt(i);
32
+ view.setUint32(4, header.originalLength, true);
33
+ const digest = Buffer.from(header.payloadDigest, "hex");
34
+ out.set(digest.subarray(0, 32), 8);
35
+ view.setUint16(40, header.blockSize, true);
36
+ view.setUint16(42, header.dataShards, true);
37
+ view.setUint16(44, header.parityShards, true);
38
+ return out;
39
+ }
40
+ /** Parse the canonical header. Returns null on magic/geometry mismatch. */
41
+ export function parseHeader(bytes) {
42
+ if (bytes.length < RESIDUAL_HEADER_BYTES)
43
+ return null;
44
+ for (let i = 0; i < 4; i++) {
45
+ if (bytes[i] !== RESIDUAL_MAGIC.charCodeAt(i))
46
+ return null;
47
+ }
48
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
49
+ const originalLength = view.getUint32(4, true);
50
+ const payloadDigest = Buffer.from(bytes.subarray(8, 40)).toString("hex");
51
+ const blockSize = view.getUint16(40, true);
52
+ const dataShards = view.getUint16(42, true);
53
+ const parityShards = view.getUint16(44, true);
54
+ if (blockSize !== RESIDUAL_BLOCK_SIZE)
55
+ return null;
56
+ if (dataShards !== RS_DATA_SHARDS || parityShards !== RS_PARITY_SHARDS)
57
+ return null;
58
+ return {
59
+ magic: RESIDUAL_MAGIC,
60
+ originalLength,
61
+ payloadDigest,
62
+ blockSize: RESIDUAL_BLOCK_SIZE,
63
+ dataShards: RS_DATA_SHARDS,
64
+ parityShards: RS_PARITY_SHARDS,
65
+ };
66
+ }
67
+ /** Serialize the full protected stream for one encoded artifact. */
68
+ export function serializeStream(codec) {
69
+ const head = serializeHeader(codec.header);
70
+ const perBlock = blockBytes(codec.header.blockSize);
71
+ const corrections = serializeCorrections(codec.corrections);
72
+ const out = new Uint8Array(head.length + codec.blocks.length * perBlock + corrections.length);
73
+ out.set(head, 0);
74
+ const view = new DataView(out.buffer);
75
+ let pos = head.length;
76
+ for (const block of codec.blocks) {
77
+ view.setFloat32(pos, block.scale, true);
78
+ pos += 4;
79
+ for (let i = 0; i < block.coefficients.length; i++) {
80
+ view.setInt16(pos, block.coefficients[i], true);
81
+ pos += 2;
82
+ }
83
+ }
84
+ out.set(corrections, pos);
85
+ return out;
86
+ }
87
+ /** Parse a protected stream back into the codec artifact. Null on malformed. */
88
+ export function parseStream(stream) {
89
+ const header = parseHeader(stream);
90
+ if (!header)
91
+ return null;
92
+ const perBlock = blockBytes(header.blockSize);
93
+ const blockCount = Math.ceil(header.originalLength / header.blockSize);
94
+ const bodyEnd = RESIDUAL_HEADER_BYTES + blockCount * perBlock;
95
+ if (stream.length < bodyEnd)
96
+ return null;
97
+ const view = new DataView(stream.buffer, stream.byteOffset, stream.byteLength);
98
+ const blocks = [];
99
+ let pos = RESIDUAL_HEADER_BYTES;
100
+ for (let b = 0; b < blockCount; b++) {
101
+ const scale = view.getFloat32(pos, true);
102
+ pos += 4;
103
+ const coefficients = new Int16Array(header.blockSize);
104
+ for (let i = 0; i < header.blockSize; i++) {
105
+ coefficients[i] = view.getInt16(pos, true);
106
+ pos += 2;
107
+ }
108
+ blocks.push({ scale, coefficients });
109
+ }
110
+ const parsed = parseCorrections(stream, pos);
111
+ if (!parsed)
112
+ return null;
113
+ // Every correction must name a block that exists.
114
+ for (const block of parsed.blocks) {
115
+ if (block.blockIndex >= blockCount)
116
+ return null;
117
+ }
118
+ return {
119
+ schema: "residual-codec-v1",
120
+ header,
121
+ blocks,
122
+ corrections: parsed.blocks,
123
+ };
124
+ }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * vector-cortex/residual/types.ts — reversible residual payload codec (VC4B).
3
+ *
4
+ * Owns `ResidualCodecV1` / `ParityShardV1` — the contract of the sprint failure
5
+ * triad:
6
+ *
7
+ * A = admitted residual (DCT + int16 + exact corrections) + RS(9,6) parity;
8
+ * B = exact compressed bytes (forced when the >95% accounting rejects A);
9
+ * C = ledger bytes (forced when A/B decode fails).
10
+ *
11
+ * Semantic vectors NEVER claim to recover exact text. Exact bytes come only from
12
+ * exact payload shards (VC4A `ExactShardV1`) or this REVERSIBLE codec: a block
13
+ * DCT-II analysis, int16 quantization, and a block-scoped exact correction
14
+ * stream that makes post-decode byte error exactly zero for admitted artifacts.
15
+ * Numeric erasure parity protects the codec bytes; it is not a substitute for
16
+ * the exact payload (RESIDUAL_CODEC.md).
17
+ *
18
+ * Consumes only reviewer-accepted predecessor contracts (VC1A EventV2 bytes via
19
+ * VC4A shard ranges) and the common contracts. Pure types + registered
20
+ * conformance IDs: no storage, no console, no network (PREVENT-PI-004 /
21
+ * PREVENT-011).
22
+ */
23
+ /** Fixed transform block length (RESIDUAL_CODEC §byte scope: 4096). */
24
+ export const RESIDUAL_BLOCK_SIZE = 4096;
25
+ /** Reed–Solomon shard geometry: k=6 data shards, m=3 parity shards. */
26
+ export const RS_DATA_SHARDS = 6;
27
+ export const RS_PARITY_SHARDS = 3;
28
+ export const RS_TOTAL_SHARDS = RS_DATA_SHARDS + RS_PARITY_SHARDS;
29
+ /** GF(2^8) primitive polynomial for the parity field (0x11d). */
30
+ export const GF_PRIMITIVE_POLYNOMIAL = 0x11d;
31
+ /** Header magic bytes `VCR1`. */
32
+ export const RESIDUAL_MAGIC = "VCR1";
33
+ /**
34
+ * Admission ratio: residual is admitted only when its FULL encoded size is at
35
+ * most `floor(0.95 * exactCompressedSize)` (RESIDUAL_CODEC §admission). The
36
+ * accounting counts every persisted byte — header, scales, coefficients,
37
+ * corrections, shard metadata, all 9 shards, and digests.
38
+ */
39
+ export const ADMISSION_NUMERATOR = 95;
40
+ export const ADMISSION_DENOMINATOR = 100;
41
+ /** Serialized header byte length: 4 + 4 + 32 + 2 + 2 + 2. */
42
+ export const RESIDUAL_HEADER_BYTES = 46;
43
+ /**
44
+ * Registered RES conformance ID range (RES-001..050). The acceptance test reads
45
+ * these rows from the v2 manifest and asserts each returns its manifest
46
+ * `ok`/`code`. The three named assertions (RES-DCT-001 / RES-RS-002 /
47
+ * RES-ADMIT-003) live alongside them.
48
+ */
49
+ export const RES_IDS = Array.from({ length: 50 }, (_v, i) => `RES-${String(i + 1).padStart(3, "0")}`);
50
+ /** Named RES conformance assertions (the sprint's headline rows). */
51
+ export const RES_NAMED_IDS = [
52
+ "RES-DCT-001",
53
+ "RES-RS-002",
54
+ "RES-ADMIT-003",
55
+ ];