pi-mega-compact 0.20.8 → 0.20.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config/vector-cortex.js +13 -0
- package/dist/config.js +1 -1
- package/dist/extensions/dashboard-server/route-dispatch.js +3 -1
- package/dist/extensions/dashboard-server/routes-rag-settings-vector-cortex.js +1 -0
- package/dist/extensions/dashboard-server/routes-vector-cortex-heal.js +42 -3
- package/dist/extensions/dashboard-server/routes-vector-cortex.js +3 -1
- package/dist/extensions/dashboard-server/routes.js +1 -1
- package/dist/src/config/vector-cortex.js +13 -0
- package/dist/src/config.js +1 -1
- package/dist/src/vector-cortex/heal/_restore-fixture.js +96 -0
- package/dist/src/vector-cortex/heal/_vc6b-helpers.js +35 -0
- package/dist/src/vector-cortex/heal/restore-emit.js +70 -0
- package/dist/src/vector-cortex/heal/restore-readers.js +114 -0
- package/dist/src/vector-cortex/heal/restore-types.js +66 -0
- package/dist/src/vector-cortex/heal/restore.js +140 -0
- package/dist/src/vector-cortex/heal/types.js +1 -0
- package/dist/src/vector-cortex/heal/verify.js +75 -0
- package/dist/vector-cortex/heal/_restore-fixture.js +96 -0
- package/dist/vector-cortex/heal/_vc6b-helpers.js +35 -0
- package/dist/vector-cortex/heal/restore-emit.js +70 -0
- package/dist/vector-cortex/heal/restore-readers.js +114 -0
- package/dist/vector-cortex/heal/restore-types.js +66 -0
- package/dist/vector-cortex/heal/restore.js +140 -0
- package/dist/vector-cortex/heal/types.js +1 -0
- package/dist/vector-cortex/heal/verify.js +75 -0
- package/extensions/dashboard-client/dist/assets/{AreaChart-5yxaI8IO.js → AreaChart-fxyq2KaH.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{AreaChart-5yxaI8IO.js.map → AreaChart-fxyq2KaH.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{BarChart-BoHhCqH-.js → BarChart-D5EqAINP.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{BarChart-BoHhCqH-.js.map → BarChart-D5EqAINP.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{CacheTab-Di6L3CsF.js → CacheTab-D4UWbzLo.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{CacheTab-Di6L3CsF.js.map → CacheTab-D4UWbzLo.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{EventsTab-Cdff3h6s.js → EventsTab-BiXquGrX.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{EventsTab-Cdff3h6s.js.map → EventsTab-BiXquGrX.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{HealthTab-MthYYVLw.js → HealthTab-D5QvoTjy.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{HealthTab-MthYYVLw.js.map → HealthTab-D5QvoTjy.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MaintenanceTab-D9G4ntsG.js → MaintenanceTab-Cy_-CWgM.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MaintenanceTab-D9G4ntsG.js.map → MaintenanceTab-Cy_-CWgM.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MemoryMapTab-CN8Lr5Qf.js → MemoryMapTab-DrAjgGOR.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MemoryMapTab-CN8Lr5Qf.js.map → MemoryMapTab-DrAjgGOR.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MetricsTab-CgO32kSo.js → MetricsTab-BvPHcuFN.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MetricsTab-CgO32kSo.js.map → MetricsTab-BvPHcuFN.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{OverviewTab-Cb4kR0h1.js → OverviewTab-DsbIigOQ.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{OverviewTab-Cb4kR0h1.js.map → OverviewTab-DsbIigOQ.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{ReposTab-Dt1FGnMR.js → ReposTab-7v_uZX_a.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{ReposTab-Dt1FGnMR.js.map → ReposTab-7v_uZX_a.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{SessionsTab-D7D2RW6U.js → SessionsTab-BXGrv-6C.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{SessionsTab-D7D2RW6U.js.map → SessionsTab-BXGrv-6C.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{SetupTab-CgV5xCzz.js → SetupTab-QRUXa65y.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{SetupTab-CgV5xCzz.js.map → SetupTab-QRUXa65y.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-CCNuJjyO.js → TimeSavedCard-p327ZZKT.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-CCNuJjyO.js.map → TimeSavedCard-p327ZZKT.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TurnsTab-Cv1OCttJ.js → TurnsTab-BcmBSlik.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TurnsTab-Cv1OCttJ.js.map → TurnsTab-BcmBSlik.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/VectorCortexTab-D2fjKNyT.js +2 -0
- package/extensions/dashboard-client/dist/assets/VectorCortexTab-D2fjKNyT.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{WikiTab-B48QnQrK.js → WikiTab-DLXFMuNy.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{WikiTab-B48QnQrK.js.map → WikiTab-DLXFMuNy.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{button-BBQl2MiF.js → button-C9CghmRx.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{button-BBQl2MiF.js.map → button-C9CghmRx.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{card-BYbefUur.js → card-DWU-1y3t.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{card-BYbefUur.js.map → card-DWU-1y3t.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-swm0YZe0.js → generateCategoricalChart-Dayy9dGn.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-swm0YZe0.js.map → generateCategoricalChart-Dayy9dGn.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{index-DeIky7xm.js → index-D9PsW3VZ.js} +3 -3
- package/extensions/dashboard-client/dist/assets/{index-DeIky7xm.js.map → index-D9PsW3VZ.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{switch-CctU78ww.js → switch-CnLFAmVT.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{switch-CctU78ww.js.map → switch-CnLFAmVT.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{toggle-BRCNCoiI.js → toggle-HN5C7_vy.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{toggle-BRCNCoiI.js.map → toggle-HN5C7_vy.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{useSSE-WfOiWDH_.js → useSSE-DoJfk3Ce.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{useSSE-WfOiWDH_.js.map → useSSE-DoJfk3Ce.js.map} +1 -1
- package/extensions/dashboard-client/dist/index.html +1 -1
- package/extensions/dashboard-client/src/api/vector-cortex.ts +9 -0
- package/extensions/dashboard-client/src/tabs/VectorCortexRestoreCard.tsx +42 -0
- package/extensions/dashboard-client/src/tabs/VectorCortexTab.tsx +8 -0
- package/extensions/dashboard-client/src/types/vector-cortex.ts +16 -0
- package/extensions/dashboard-server/api-contracts/vector-cortex-heal.ts +30 -0
- package/extensions/dashboard-server/api-contracts/vector-cortex.ts +4 -1
- package/extensions/dashboard-server/route-dispatch.ts +2 -0
- package/extensions/dashboard-server/routes-rag-settings-vector-cortex.ts +6 -0
- package/extensions/dashboard-server/routes-vector-cortex-heal.ts +51 -4
- package/extensions/dashboard-server/routes-vector-cortex.ts +6 -1
- package/extensions/dashboard-server/routes.ts +1 -0
- package/package.json +1 -1
- package/src/config/vector-cortex.ts +14 -0
- package/src/config.ts +1 -0
- package/src/vector-cortex/heal/_restore-fixture.ts +163 -0
- package/src/vector-cortex/heal/_vc6b-helpers.ts +55 -0
- package/src/vector-cortex/heal/restore-emit.ts +94 -0
- package/src/vector-cortex/heal/restore-readers.ts +145 -0
- package/src/vector-cortex/heal/restore-types.ts +185 -0
- package/src/vector-cortex/heal/restore.ts +165 -0
- package/src/vector-cortex/heal/types.ts +22 -0
- package/src/vector-cortex/heal/verify.ts +93 -0
- package/extensions/dashboard-client/dist/assets/VectorCortexTab-DTabQo6h.js +0 -2
- package/extensions/dashboard-client/dist/assets/VectorCortexTab-DTabQo6h.js.map +0 -1
|
@@ -208,6 +208,19 @@ export const VC5C_ENABLED = () => sprintFlag("MEGACOMPACT_VC5C");
|
|
|
208
208
|
* EXCLUDED_SETTINGS), mirroring VC4A/VC4B/VC4C/VC5A/VC5B/VC5C.
|
|
209
209
|
*/
|
|
210
210
|
export const VC6A_ENABLED = () => sprintFlag("MEGACOMPACT_VC6A");
|
|
211
|
+
/**
|
|
212
|
+
* VC6B — exact source restoration. Default ON. `MEGACOMPACT_VC6B=0` disables and
|
|
213
|
+
* is byte-identical to the predecessor (VC6A): the `restoreSources` /
|
|
214
|
+
* `verifyRestored` arithmetic STILL RUNS (it is PURE — an exact-source read plus
|
|
215
|
+
* a SHA-256 comparison, with no clock, storage, or network), but the
|
|
216
|
+
* `vector_cortex_source_restored` / `vector_cortex_restore_digest_rejected`
|
|
217
|
+
* events are never emitted and the dashboard restoration diagnostics seam is
|
|
218
|
+
* suppressed. Flag OFF gates the reporter + dashboard seam, never the
|
|
219
|
+
* restore/verify arithmetic, so flag-off outbound/predecessor golden bytes match
|
|
220
|
+
* exactly. This flag MUST also be a dashboard SETTINGS toggle (visible in config
|
|
221
|
+
* UI, never in EXCLUDED_SETTINGS), mirroring VC4A/VC4B/VC4C/VC5A/VC5B/VC5C/VC6A.
|
|
222
|
+
*/
|
|
223
|
+
export const VC6B_ENABLED = () => sprintFlag("MEGACOMPACT_VC6B");
|
|
211
224
|
// ---------------------------------------------------------------------------
|
|
212
225
|
// Breaker state machine constants (TRIAD_RESILIENCE.md §breaker).
|
|
213
226
|
// Rolled numbers for one 60s window; VC0C consumes these at its breaker seam.
|
package/dist/config.js
CHANGED
|
@@ -114,4 +114,4 @@ export const NEW_UI = () => ragEnabled("MEGACOMPACT_NEW_UI");
|
|
|
114
114
|
// default ON, `=0`/`_DISABLED` off. Re-exported from src/config/vector-cortex.ts
|
|
115
115
|
// so root consumers share one source of truth.
|
|
116
116
|
// ---------------------------------------------------------------------------
|
|
117
|
-
export { VC0A_ENABLED, VC0B_ENABLED, VC1A_ENABLED, VC0C_ENABLED, VC1B_ENABLED, VC1C_ENABLED, VC2A_ENABLED, VC2B_ENABLED, VC2C_ENABLED, VC3A_ENABLED, VC3B_ENABLED, VC3C_ENABLED, VC4A_ENABLED, VC4B_ENABLED, VC4C_ENABLED, VC5A_ENABLED, VC5B_ENABLED, VC5C_ENABLED, VC6A_ENABLED, BREAKER_WINDOW_MS, BREAKER_MIN_ATTEMPTS, BREAKER_PERF_FAILURES, BREAKER_PERF_FAILURE_RATE, BREAKER_CORRECTNESS_FAILURES, BREAKER_COOLDOWN_MS, BREAKER_PROBE_COUNT, BREAKER_RETRY_BASE_MS, BREAKER_RETRY_CAP_MS, BREAKER_RETRY_JITTER, BREAKER_HYSTERESIS_FAILURE_RATE, BREAKER_HYSTERESIS_BUDGET_P95_MS, BREAKER_MIN_HEALTHY_RESIDENCE_MS, } from "./config/vector-cortex.js";
|
|
117
|
+
export { VC0A_ENABLED, VC0B_ENABLED, VC1A_ENABLED, VC0C_ENABLED, VC1B_ENABLED, VC1C_ENABLED, VC2A_ENABLED, VC2B_ENABLED, VC2C_ENABLED, VC3A_ENABLED, VC3B_ENABLED, VC3C_ENABLED, VC4A_ENABLED, VC4B_ENABLED, VC4C_ENABLED, VC5A_ENABLED, VC5B_ENABLED, VC5C_ENABLED, VC6A_ENABLED, VC6B_ENABLED, BREAKER_WINDOW_MS, BREAKER_MIN_ATTEMPTS, BREAKER_PERF_FAILURES, BREAKER_PERF_FAILURE_RATE, BREAKER_CORRECTNESS_FAILURES, BREAKER_COOLDOWN_MS, BREAKER_PROBE_COUNT, BREAKER_RETRY_BASE_MS, BREAKER_RETRY_CAP_MS, BREAKER_RETRY_JITTER, BREAKER_HYSTERESIS_FAILURE_RATE, BREAKER_HYSTERESIS_BUDGET_P95_MS, BREAKER_MIN_HEALTHY_RESIDENCE_MS, } from "./config/vector-cortex.js";
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* PREVENT-PI-004: local filesystem read only, no network.
|
|
9
9
|
* PREVENT-011: no `any` type.
|
|
10
10
|
*/
|
|
11
|
-
import { handleIndex, handleRepoIndex, handleEvents, handleGameState, handleGameScores, handlePerfSamples, handlePerf, handleAchievements, handleSessions, handleTopics, handleTurns, handleMaintenance, handleProviderCache, handleMemoryStatus, handleCacheStripes, handleSetupStatus, handleSetupDetect, handleSetupConfigure, handleMemoryMap, handleRaptorTree, handleRaptorBuildHistory, handleContextHealth, handleCachePoison, handleHealthSettings, handleEmbedderHealth, handleRagSettings, handleRagMetrics, handleModelThresholds, handleWiki, handleVectorCortexEvaluation, handleVectorCortexHealth, handleVectorCortexBreakersReset, handleVectorCortexLedger, handleVectorCortexTopology, handleVectorCortexQuery, handleVectorCortexShards, handleVectorCortexResidual, handleVectorCortexReconstruct, handleVectorCortexPlans, handleVectorCortexRender, handleVectorCortexRollout, handleVectorCortexClosureProof, } from "./routes.js";
|
|
11
|
+
import { handleIndex, handleRepoIndex, handleEvents, handleGameState, handleGameScores, handlePerfSamples, handlePerf, handleAchievements, handleSessions, handleTopics, handleTurns, handleMaintenance, handleProviderCache, handleMemoryStatus, handleCacheStripes, handleSetupStatus, handleSetupDetect, handleSetupConfigure, handleMemoryMap, handleRaptorTree, handleRaptorBuildHistory, handleContextHealth, handleCachePoison, handleHealthSettings, handleEmbedderHealth, handleRagSettings, handleRagMetrics, handleModelThresholds, handleWiki, handleVectorCortexEvaluation, handleVectorCortexHealth, handleVectorCortexBreakersReset, handleVectorCortexLedger, handleVectorCortexTopology, handleVectorCortexQuery, handleVectorCortexShards, handleVectorCortexResidual, handleVectorCortexReconstruct, handleVectorCortexPlans, handleVectorCortexRender, handleVectorCortexRollout, handleVectorCortexClosureProof, handleVectorCortexRestore, } from "./routes.js";
|
|
12
12
|
/**
|
|
13
13
|
* Dispatch a request through every registered route handler.
|
|
14
14
|
* Returns true if a handler claimed the request (ended the response).
|
|
@@ -98,5 +98,7 @@ export function dispatchRoutes(req, res, ctx) {
|
|
|
98
98
|
return true;
|
|
99
99
|
if (handleVectorCortexClosureProof(req, res, ctx))
|
|
100
100
|
return true;
|
|
101
|
+
if (handleVectorCortexRestore(req, res, ctx))
|
|
102
|
+
return true;
|
|
101
103
|
return false;
|
|
102
104
|
}
|
|
@@ -40,5 +40,6 @@ export const VECTOR_CORTEX_SETTINGS = {
|
|
|
40
40
|
boolDirect("MEGACOMPACT_VC5B", "VC5B Validated Renderer + Provider Profiles", "Validated prompt renderer: replays VC5A's stable Kahn order verbatim, preserves exact tool bytes (PREVENT-PI-002), places compacted context via the host before_agent_start prepend seam — never role:system (PREVENT-PI-003) — and SHA-256 hashes the entire canonical outbound request before provider invocation. Unknown provider/model cleanly bypasses to the predecessor prompt path. OFF = byte-identical predecessor (VC5A).", true),
|
|
41
41
|
boolDirect("MEGACOMPACT_VC5C", "VC5C Live Graduated Rollout", "Live graduated rollout: deterministically hashes each session into a stable 10,000-bucket cohort and advances the exposure gate (1/5/25/50/100%) only after a 72h monotonic residency, a powered sample, >=10,000 events, and >=200 sessions — advancing ONE gate at a time. A hard causal/tool/anchor/exact failure freezes promotion and selects the pre-VC path. OFF = byte-identical predecessor (VC5B).", true),
|
|
42
42
|
boolDirect("MEGACOMPACT_VC6A", "VC6A Advanced Closure Optimization", "Advanced closure optimization: deterministically reduces the already-mandatory VC4C closure by transitive reduction over depends edges, emitting a ClosureProofV2 receipt per closure and a verifier that replays reductions against the conservative oracle (HEAL_PROOF_SET_MISMATCH on selected-set divergence). Protected edges (tool-pair / anchor / contradiction / sole-dependency) are never removed. The optimized selected set is byte-identical to the conservative closure. OFF = byte-identical predecessor (VC5C); arithmetic runs, only the reporter + dashboard seam is suppressed.", true),
|
|
43
|
+
boolDirect("MEGACOMPACT_VC6B", "VC6B Exact Source Restoration", "Exact source restoration: restores the original bytes of closure spans ONLY from an exact shard (mode A) or a verified ledger range scan (mode B) — never inferred, reconstructed, or paraphrased from embeddings or semantic text. Requests are hard-bounded at 64 spans / 4MiB (HEAL_RESTORE_LIMIT); every span's SHA-256 is recomputed and bytes are inserted only after all requested span metadata validates, so a single bad span fails the whole request closed (HEAL_RESTORE_DIGEST_MISMATCH, HEAL_RESTORE_RANGE_MISMATCH). When no exact source exists (HEAL_RESTORE_SOURCE_MISSING) the old context is omitted and the loss is disclosed (mode C) rather than filled in. OFF = byte-identical predecessor (VC6A).", true),
|
|
43
44
|
],
|
|
44
45
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* dashboard-server/routes-vector-cortex-heal.ts — VC6A closure-optimization
|
|
3
|
-
* dashboard
|
|
2
|
+
* dashboard-server/routes-vector-cortex-heal.ts — VC6A closure-optimization and
|
|
3
|
+
* VC6B exact-source-restoration dashboard routes.
|
|
4
4
|
*
|
|
5
5
|
* Reader-only GET /api/vector-cortex/closure-proof returning aggregate-only
|
|
6
6
|
* closure diagnostics: whether the flag is enabled, the runtime triad mode, and
|
|
@@ -12,10 +12,14 @@
|
|
|
12
12
|
* (the conservative VC4C closure, no reduction) — that is exactly the spec's
|
|
13
13
|
* fallback rule — so `mode:"B"` + `enabled:false` is the byte-stable OFF view.
|
|
14
14
|
*
|
|
15
|
+
* Reader-only GET /api/vector-cortex/restore (VC6B) returns restore COUNTS and
|
|
16
|
+
* HEAL_RESTORE_* error codes only. There is NO payload endpoint for restoration,
|
|
17
|
+
* ever — no restored bytes, span ids, node ids, byte ranges, or ledger text.
|
|
18
|
+
*
|
|
15
19
|
* Guardrails: PREVENT-PI-004 (local in-process state only), PREVENT-011 (no
|
|
16
20
|
* `any`), reader-only aggregate (counts + mode only).
|
|
17
21
|
*/
|
|
18
|
-
import { VC6A_ENABLED } from "../../src/config.js";
|
|
22
|
+
import { VC6A_ENABLED, VC6B_ENABLED } from "../../src/config.js";
|
|
19
23
|
import { sendJson } from "./routes-vector-cortex-shared.js";
|
|
20
24
|
/** Reader-only GET /api/vector-cortex/closure-proof (VC6A). */
|
|
21
25
|
export function handleVectorCortexClosureProof(req, res, _ctx) {
|
|
@@ -45,3 +49,38 @@ export function handleVectorCortexClosureProof(req, res, _ctx) {
|
|
|
45
49
|
sendJson(res, 200, body);
|
|
46
50
|
return true;
|
|
47
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* Reader-only GET /api/vector-cortex/restore (VC6B).
|
|
54
|
+
*
|
|
55
|
+
* Counts and HEAL_RESTORE_* codes only — this is a static reader-only aggregate
|
|
56
|
+
* seam (same shape as the VC6A handler): there is no payload endpoint and never
|
|
57
|
+
* will be, so no restored bytes/span ids/node ids/ledger text can leak here.
|
|
58
|
+
*/
|
|
59
|
+
export function handleVectorCortexRestore(req, res, _ctx) {
|
|
60
|
+
const url = req.url ?? "";
|
|
61
|
+
const path = url.split("?")[0] ?? url;
|
|
62
|
+
if (path !== "/api/vector-cortex/restore")
|
|
63
|
+
return false;
|
|
64
|
+
if (req.method !== "GET") {
|
|
65
|
+
sendJson(res, 405, { error: "method_not_allowed" });
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
const enabled = VC6B_ENABLED();
|
|
69
|
+
// Flag-off routes to mode C: with VC6B off there is no exact-restoration path
|
|
70
|
+
// at all, so the honest OFF view is the disclose-loss mode (old context is
|
|
71
|
+
// omitted rather than inferred) — mirroring how VC6A's OFF view reports the
|
|
72
|
+
// conservative fallback it actually takes.
|
|
73
|
+
const mode = enabled ? "A" : "C";
|
|
74
|
+
const body = {
|
|
75
|
+
enabled,
|
|
76
|
+
mode,
|
|
77
|
+
restoreAttempts: 0,
|
|
78
|
+
restoredCount: 0,
|
|
79
|
+
missingCount: 0,
|
|
80
|
+
digestRejections: 0,
|
|
81
|
+
lastRejection: null,
|
|
82
|
+
updatedAt: new Date().toISOString(),
|
|
83
|
+
};
|
|
84
|
+
sendJson(res, 200, body);
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
* VC0C health/reset-> routes-vector-cortex-health.ts(GET /health, POST /breakers/reset)
|
|
10
10
|
* VC1B ledger -> routes-vector-cortex-ledger.ts (GET /ledger)
|
|
11
11
|
* VC3A topology -> routes-vector-cortex-topology.ts(GET /topology)
|
|
12
|
+
* VC6A closure -> routes-vector-cortex-heal.ts (GET /closure-proof)
|
|
13
|
+
* VC6B restore -> routes-vector-cortex-heal.ts (GET /restore)
|
|
12
14
|
*
|
|
13
15
|
* Guardrails: PREVENT-PI-004 (local filesystem read only), PREVENT-011 (no
|
|
14
16
|
* `any`), reader-only aggregates (never payloads/prompts/ledger text).
|
|
@@ -24,4 +26,4 @@ export { handleVectorCortexReconstruct } from "./routes-vector-cortex-reconstruc
|
|
|
24
26
|
export { handleVectorCortexPlans } from "./routes-vector-cortex-plans.js";
|
|
25
27
|
export { handleVectorCortexRender } from "./routes-vector-cortex-render.js";
|
|
26
28
|
export { handleVectorCortexRollout } from "./routes-vector-cortex-rollout.js";
|
|
27
|
-
export { handleVectorCortexClosureProof } from "./routes-vector-cortex-heal.js";
|
|
29
|
+
export { handleVectorCortexClosureProof, handleVectorCortexRestore, } from "./routes-vector-cortex-heal.js";
|
|
@@ -23,4 +23,4 @@ export { handleRagSettings } from "./routes-rag-settings.js";
|
|
|
23
23
|
export { handleRagMetrics } from "./routes-rag-metrics.js";
|
|
24
24
|
export { handleModelThresholds } from "./routes-model-thresholds.js";
|
|
25
25
|
export { handleWiki } from "./routes-wiki.js";
|
|
26
|
-
export { handleVectorCortexEvaluation, handleVectorCortexHealth, handleVectorCortexBreakersReset, handleVectorCortexLedger, handleVectorCortexTopology, handleVectorCortexQuery, handleVectorCortexShards, handleVectorCortexResidual, handleVectorCortexReconstruct, handleVectorCortexPlans, handleVectorCortexRender, handleVectorCortexRollout, handleVectorCortexClosureProof, } from "./routes-vector-cortex.js";
|
|
26
|
+
export { handleVectorCortexEvaluation, handleVectorCortexHealth, handleVectorCortexBreakersReset, handleVectorCortexLedger, handleVectorCortexTopology, handleVectorCortexQuery, handleVectorCortexShards, handleVectorCortexResidual, handleVectorCortexReconstruct, handleVectorCortexPlans, handleVectorCortexRender, handleVectorCortexRollout, handleVectorCortexClosureProof, handleVectorCortexRestore, } from "./routes-vector-cortex.js";
|
|
@@ -208,6 +208,19 @@ export const VC5C_ENABLED = () => sprintFlag("MEGACOMPACT_VC5C");
|
|
|
208
208
|
* EXCLUDED_SETTINGS), mirroring VC4A/VC4B/VC4C/VC5A/VC5B/VC5C.
|
|
209
209
|
*/
|
|
210
210
|
export const VC6A_ENABLED = () => sprintFlag("MEGACOMPACT_VC6A");
|
|
211
|
+
/**
|
|
212
|
+
* VC6B — exact source restoration. Default ON. `MEGACOMPACT_VC6B=0` disables and
|
|
213
|
+
* is byte-identical to the predecessor (VC6A): the `restoreSources` /
|
|
214
|
+
* `verifyRestored` arithmetic STILL RUNS (it is PURE — an exact-source read plus
|
|
215
|
+
* a SHA-256 comparison, with no clock, storage, or network), but the
|
|
216
|
+
* `vector_cortex_source_restored` / `vector_cortex_restore_digest_rejected`
|
|
217
|
+
* events are never emitted and the dashboard restoration diagnostics seam is
|
|
218
|
+
* suppressed. Flag OFF gates the reporter + dashboard seam, never the
|
|
219
|
+
* restore/verify arithmetic, so flag-off outbound/predecessor golden bytes match
|
|
220
|
+
* exactly. This flag MUST also be a dashboard SETTINGS toggle (visible in config
|
|
221
|
+
* UI, never in EXCLUDED_SETTINGS), mirroring VC4A/VC4B/VC4C/VC5A/VC5B/VC5C/VC6A.
|
|
222
|
+
*/
|
|
223
|
+
export const VC6B_ENABLED = () => sprintFlag("MEGACOMPACT_VC6B");
|
|
211
224
|
// ---------------------------------------------------------------------------
|
|
212
225
|
// Breaker state machine constants (TRIAD_RESILIENCE.md §breaker).
|
|
213
226
|
// Rolled numbers for one 60s window; VC0C consumes these at its breaker seam.
|
package/dist/src/config.js
CHANGED
|
@@ -114,4 +114,4 @@ export const NEW_UI = () => ragEnabled("MEGACOMPACT_NEW_UI");
|
|
|
114
114
|
// default ON, `=0`/`_DISABLED` off. Re-exported from src/config/vector-cortex.ts
|
|
115
115
|
// so root consumers share one source of truth.
|
|
116
116
|
// ---------------------------------------------------------------------------
|
|
117
|
-
export { VC0A_ENABLED, VC0B_ENABLED, VC1A_ENABLED, VC0C_ENABLED, VC1B_ENABLED, VC1C_ENABLED, VC2A_ENABLED, VC2B_ENABLED, VC2C_ENABLED, VC3A_ENABLED, VC3B_ENABLED, VC3C_ENABLED, VC4A_ENABLED, VC4B_ENABLED, VC4C_ENABLED, VC5A_ENABLED, VC5B_ENABLED, VC5C_ENABLED, VC6A_ENABLED, BREAKER_WINDOW_MS, BREAKER_MIN_ATTEMPTS, BREAKER_PERF_FAILURES, BREAKER_PERF_FAILURE_RATE, BREAKER_CORRECTNESS_FAILURES, BREAKER_COOLDOWN_MS, BREAKER_PROBE_COUNT, BREAKER_RETRY_BASE_MS, BREAKER_RETRY_CAP_MS, BREAKER_RETRY_JITTER, BREAKER_HYSTERESIS_FAILURE_RATE, BREAKER_HYSTERESIS_BUDGET_P95_MS, BREAKER_MIN_HEALTHY_RESIDENCE_MS, } from "./config/vector-cortex.js";
|
|
117
|
+
export { VC0A_ENABLED, VC0B_ENABLED, VC1A_ENABLED, VC0C_ENABLED, VC1B_ENABLED, VC1C_ENABLED, VC2A_ENABLED, VC2B_ENABLED, VC2C_ENABLED, VC3A_ENABLED, VC3B_ENABLED, VC3C_ENABLED, VC4A_ENABLED, VC4B_ENABLED, VC4C_ENABLED, VC5A_ENABLED, VC5B_ENABLED, VC5C_ENABLED, VC6A_ENABLED, VC6B_ENABLED, BREAKER_WINDOW_MS, BREAKER_MIN_ATTEMPTS, BREAKER_PERF_FAILURES, BREAKER_PERF_FAILURE_RATE, BREAKER_CORRECTNESS_FAILURES, BREAKER_COOLDOWN_MS, BREAKER_PROBE_COUNT, BREAKER_RETRY_BASE_MS, BREAKER_RETRY_CAP_MS, BREAKER_RETRY_JITTER, BREAKER_HYSTERESIS_FAILURE_RATE, BREAKER_HYSTERESIS_BUDGET_P95_MS, BREAKER_MIN_HEALTHY_RESIDENCE_MS, } from "./config/vector-cortex.js";
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* heal/_restore-fixture.ts — conformance fixture I/O for VC6B restoration rows.
|
|
3
|
+
*
|
|
4
|
+
* Sibling of `_acceptance-fixture.ts` (which owns the VC6A closure-optimization
|
|
5
|
+
* rows); split out so neither file approaches the 300-line soft limit and so the
|
|
6
|
+
* base64/BigInt decoding lives next to the contract it reconstitutes.
|
|
7
|
+
*
|
|
8
|
+
* DECODING IS THE POINT. Fixtures are canonical JSON, which cannot express bytes
|
|
9
|
+
* or bigints, so the corpus stores `originalBytesBase64` + numeric `seq`. These
|
|
10
|
+
* loaders turn those back into the REAL `ExactShardV1` / `EventV2` objects the
|
|
11
|
+
* production readers consume — no mocks, no stubs, no parallel "test shape". If
|
|
12
|
+
* the decode were lossy the digests would not match and the acceptance test would
|
|
13
|
+
* fail loudly, which is exactly the guarantee we want from a byte-identity sprint.
|
|
14
|
+
*/
|
|
15
|
+
import { readFileSync } from "node:fs";
|
|
16
|
+
import { join } from "node:path";
|
|
17
|
+
import assert from "node:assert/strict";
|
|
18
|
+
import { V2, readManifest } from "./_acceptance-fixture.js";
|
|
19
|
+
/** Read one registered restoration fixture (asserting it IS registered). */
|
|
20
|
+
export function restorationFixture(id) {
|
|
21
|
+
const m = readManifest();
|
|
22
|
+
const row = m.fixtures.find((f) => f.id === id && f.path.startsWith("restoration/"));
|
|
23
|
+
assert.ok(row, `fixture ${id} registered under restoration/ in manifest`);
|
|
24
|
+
return JSON.parse(readFileSync(join(V2, row.path), "utf8"));
|
|
25
|
+
}
|
|
26
|
+
/** base64 -> exact bytes, byte-for-byte (invalid UTF-8 included). */
|
|
27
|
+
export function decodeBytes(base64) {
|
|
28
|
+
return new Uint8Array(Buffer.from(base64, "base64"));
|
|
29
|
+
}
|
|
30
|
+
/** JSON number seq bounds -> the bigint bounds `ShardRange` declares. */
|
|
31
|
+
export function decodeRange(r) {
|
|
32
|
+
return {
|
|
33
|
+
sessionId: r.sessionId,
|
|
34
|
+
seqStart: BigInt(r.seqStart),
|
|
35
|
+
seqEnd: BigInt(r.seqEnd),
|
|
36
|
+
byteStart: r.byteStart,
|
|
37
|
+
byteEnd: r.byteEnd,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/** Reconstitute a real `ExactShardV1` from its fixture row. */
|
|
41
|
+
export function decodeShard(s) {
|
|
42
|
+
return {
|
|
43
|
+
schema: "exact-shard-v1",
|
|
44
|
+
sessionId: s.sessionId,
|
|
45
|
+
range: decodeRange(s.range),
|
|
46
|
+
kind: "exact",
|
|
47
|
+
originalBytes: decodeBytes(s.originalBytesBase64),
|
|
48
|
+
digest: s.digest,
|
|
49
|
+
byteCount: s.byteCount,
|
|
50
|
+
case: s.case,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Reconstitute a real `EventV2`. `utf8` is re-derived by strict classification
|
|
55
|
+
* (never lossy replacement) so an invalid-UTF-8 fixture produces the
|
|
56
|
+
* `{valid:false, base64}` discriminant the VC1A contract requires.
|
|
57
|
+
*/
|
|
58
|
+
export function decodeEvent(e) {
|
|
59
|
+
const bytes = decodeBytes(e.originalBytesBase64);
|
|
60
|
+
let utf8;
|
|
61
|
+
try {
|
|
62
|
+
utf8 = { valid: true, text: new TextDecoder("utf-8", { fatal: true }).decode(bytes) };
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
utf8 = { valid: false, base64: e.originalBytesBase64 };
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
schema: "event-v2",
|
|
69
|
+
sessionId: e.sessionId,
|
|
70
|
+
seq: BigInt(e.seq),
|
|
71
|
+
eventId: e.eventId,
|
|
72
|
+
role: e.role,
|
|
73
|
+
kind: e.kind,
|
|
74
|
+
originalBytes: bytes,
|
|
75
|
+
bytesDigest: e.bytesDigest,
|
|
76
|
+
utf8,
|
|
77
|
+
...(e.toolCallId !== undefined ? { toolCallId: e.toolCallId } : {}),
|
|
78
|
+
occurredAtMs: BigInt(e.occurredAtMs),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
/** Flag-pinned wrapper: VC6B gated by MEGACOMPACT_VC6B (defaults ON). */
|
|
82
|
+
export function withVc6bFlagsOn(fn) {
|
|
83
|
+
return () => {
|
|
84
|
+
const saved = process.env.MEGACOMPACT_VC6B;
|
|
85
|
+
process.env.MEGACOMPACT_VC6B = "1";
|
|
86
|
+
try {
|
|
87
|
+
fn();
|
|
88
|
+
}
|
|
89
|
+
finally {
|
|
90
|
+
if (saved === undefined)
|
|
91
|
+
delete process.env.MEGACOMPACT_VC6B;
|
|
92
|
+
else
|
|
93
|
+
process.env.MEGACOMPACT_VC6B = saved;
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* heal/_vc6b-helpers.ts — shared decode/run helpers for VC6B acceptance tests.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from vc6b-acceptance.test.ts so no single test file crosses the
|
|
5
|
+
* 300-line soft limit (soft-as-hard gate). Each sibling test file imports from
|
|
6
|
+
* here rather than duplicating the decode/runReal logic.
|
|
7
|
+
*/
|
|
8
|
+
import { createHash } from "node:crypto";
|
|
9
|
+
import { restoreSources } from "./restore.js";
|
|
10
|
+
import { verifyRestored } from "./verify.js";
|
|
11
|
+
import { decodeRange, decodeShard, decodeEvent } from "./_restore-fixture.js";
|
|
12
|
+
export const enc = (s) => new Uint8Array(Buffer.from(s));
|
|
13
|
+
export const hex = (b) => createHash("sha256").update(b).digest("hex");
|
|
14
|
+
export function decodeFx(fx) {
|
|
15
|
+
return {
|
|
16
|
+
request: {
|
|
17
|
+
schema: "restore-request-v1",
|
|
18
|
+
sessionId: fx.input.sessionId,
|
|
19
|
+
spans: fx.input.request.spans.map((s) => ({
|
|
20
|
+
nodeId: s.nodeId,
|
|
21
|
+
range: decodeRange(s.range),
|
|
22
|
+
digest: s.digest,
|
|
23
|
+
})),
|
|
24
|
+
},
|
|
25
|
+
reader: {
|
|
26
|
+
exactShards: fx.input.exactShards.map(decodeShard),
|
|
27
|
+
ledgerEvents: fx.input.ledgerEvents.map(decodeEvent),
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export function runReal(fx) {
|
|
32
|
+
const { request, reader } = decodeFx(fx);
|
|
33
|
+
const result = restoreSources(request, reader);
|
|
34
|
+
return { request, reader, result, verification: verifyRestored(result, request) };
|
|
35
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vector-cortex/heal/restore-emit.ts — VC6B event reporter seam.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors `./emit.ts`: a thin `safe()` wrapper around an optional injected
|
|
5
|
+
* `emit` (unit tests pass `undefined` and stay pure), and the two event names the
|
|
6
|
+
* sprint spec requires verbatim:
|
|
7
|
+
* - `vector_cortex_source_restored` — a restore batch completed.
|
|
8
|
+
* - `vector_cortex_restore_digest_rejected` — a source failed its digest check.
|
|
9
|
+
*
|
|
10
|
+
* FLAG SEMANTICS. `restoreSources` / `verifyRestored` are PURE arithmetic and run
|
|
11
|
+
* REGARDLESS of `MEGACOMPACT_VC6B`. The flag gates ONLY this reporting +
|
|
12
|
+
* dashboard seam: with the flag off we still restore and still verify, we just do
|
|
13
|
+
* not announce it under the VC6B event namespace. That is what makes flag-off
|
|
14
|
+
* byte-identical to VC6A — the computation is never skipped, only the emission.
|
|
15
|
+
*
|
|
16
|
+
* PAYLOAD DISCIPLINE. These events carry COUNTS and MODES only — never restored
|
|
17
|
+
* bytes, never node text, never a digest of user content. The exact ledger is not
|
|
18
|
+
* diagnostic data (SECURITY_PRIVACY), and a restoration event is exactly the
|
|
19
|
+
* place where an unguarded `payload` field would leak the entire transcript into
|
|
20
|
+
* a log file.
|
|
21
|
+
*
|
|
22
|
+
* No console, no storage, no network (PREVENT-PI-004 / PREVENT-011). Every line
|
|
23
|
+
* is a structured JSON event with `ts` + `event`.
|
|
24
|
+
*/
|
|
25
|
+
import { VC6B_ENABLED } from "../../config/vector-cortex.js";
|
|
26
|
+
/** Run `fn` only when an emit exists; a reporting failure is never fatal. */
|
|
27
|
+
function safe(emit, fn) {
|
|
28
|
+
if (emit === undefined)
|
|
29
|
+
return;
|
|
30
|
+
try {
|
|
31
|
+
fn(emit);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
// Non-fatal: a reporting failure must never break the agent loop.
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/** The event names VC6B emits, exported for the dashboard seam and tests. */
|
|
38
|
+
export const RESTORE_EVENT_NAMES = [
|
|
39
|
+
"vector_cortex_source_restored",
|
|
40
|
+
"vector_cortex_restore_digest_rejected",
|
|
41
|
+
];
|
|
42
|
+
/**
|
|
43
|
+
* Report a completed restoration. Counts and mode only — enough to see whether
|
|
44
|
+
* the shard index is serving reads (mode A) or every span is falling through to
|
|
45
|
+
* a ledger scan (mode B), without disclosing what was restored.
|
|
46
|
+
*/
|
|
47
|
+
export function reportSourceRestored(emit, payload) {
|
|
48
|
+
if (!VC6B_ENABLED())
|
|
49
|
+
return;
|
|
50
|
+
safe(emit, (e) => e("vector_cortex_source_restored", {
|
|
51
|
+
ts: undefined,
|
|
52
|
+
event: "vector_cortex_source_restored",
|
|
53
|
+
sessionId: payload.sessionId,
|
|
54
|
+
restoredCount: payload.restoredCount,
|
|
55
|
+
missingCount: payload.missingCount,
|
|
56
|
+
mode: payload.mode,
|
|
57
|
+
}));
|
|
58
|
+
}
|
|
59
|
+
/** Report a rejected source (digest/limit/range). Suppressed under flag-off. */
|
|
60
|
+
export function reportRestoreDigestRejected(emit, payload) {
|
|
61
|
+
if (!VC6B_ENABLED())
|
|
62
|
+
return;
|
|
63
|
+
safe(emit, (e) => e("vector_cortex_restore_digest_rejected", {
|
|
64
|
+
ts: undefined,
|
|
65
|
+
event: "vector_cortex_restore_digest_rejected",
|
|
66
|
+
sessionId: payload.sessionId,
|
|
67
|
+
code: payload.code,
|
|
68
|
+
mode: payload.mode,
|
|
69
|
+
}));
|
|
70
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vector-cortex/heal/restore-readers.ts — the two EXACT source readers (VC6B).
|
|
3
|
+
*
|
|
4
|
+
* Split out of `restore.ts` so the orchestrator stays a short, readable policy
|
|
5
|
+
* file and each reader can be reasoned about on its own (PRACTICES: split at the
|
|
6
|
+
* 300-line soft limit, delegate-shell + impl).
|
|
7
|
+
*
|
|
8
|
+
* Both readers share one discipline: they return bytes ONLY after recomputing
|
|
9
|
+
* SHA-256 over the bytes they actually hold and comparing it to the digest the
|
|
10
|
+
* REQUEST pinned. Neither reader trusts the digest recorded alongside its own
|
|
11
|
+
* source — an `ExactShardV1.digest` is metadata that lives in the same file as
|
|
12
|
+
* the bytes, so a swapped file carries a matching (wrong) pair. Only the
|
|
13
|
+
* caller-supplied digest is authoritative, and only a fresh hash of the fetched
|
|
14
|
+
* bytes can be checked against it.
|
|
15
|
+
*
|
|
16
|
+
* Pure/deterministic/local: `node:crypto` only (a Node built-in, not a network
|
|
17
|
+
* call), no storage, no console (PREVENT-PI-004 / PREVENT-011).
|
|
18
|
+
*/
|
|
19
|
+
import { createHash } from "node:crypto";
|
|
20
|
+
/**
|
|
21
|
+
* SHA-256 over bytes as LOWERCASE HEX with NO `sha256:` prefix — the
|
|
22
|
+
* `ExactShardV1.digest` / `ReconstructionSpan.digest` / `RestoreSpanRequest`
|
|
23
|
+
* convention. `EventV2.bytesDigest` prefixes this same value with `sha256:`.
|
|
24
|
+
*/
|
|
25
|
+
export function sha256Hex(bytes) {
|
|
26
|
+
return createHash("sha256").update(bytes).digest("hex");
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Strip the `sha256:` prefix from an `EventV2.bytesDigest` so it can be compared
|
|
30
|
+
* against a bare hex digest. A value that does not carry the prefix is returned
|
|
31
|
+
* unchanged rather than mangled — the comparison then simply fails, which is the
|
|
32
|
+
* correct outcome for a malformed record.
|
|
33
|
+
*/
|
|
34
|
+
export function bareHex(digest) {
|
|
35
|
+
return digest.startsWith("sha256:") ? digest.slice("sha256:".length) : digest;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Structural range equality. All five components must match: two spans over the
|
|
39
|
+
* same seq window but different byte offsets are DIFFERENT spans (a re-encoded
|
|
40
|
+
* or re-offset stream is not the stream that was requested).
|
|
41
|
+
*/
|
|
42
|
+
export function rangeEquals(a, b) {
|
|
43
|
+
return (a.sessionId === b.sessionId &&
|
|
44
|
+
a.seqStart === b.seqStart &&
|
|
45
|
+
a.seqEnd === b.seqEnd &&
|
|
46
|
+
a.byteStart === b.byteStart &&
|
|
47
|
+
a.byteEnd === b.byteEnd);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Mode A — indexed exact-shard read.
|
|
51
|
+
*
|
|
52
|
+
* Selection is by RANGE identity plus the shard's own recorded digest, which
|
|
53
|
+
* narrows the candidate set the way a real index lookup would. The accepted
|
|
54
|
+
* candidate is then re-hashed from its `originalBytes`: this second check is
|
|
55
|
+
* what catches a shard whose bytes were swapped AFTER the index lookup resolved
|
|
56
|
+
* (the sprint's unique failure injection). A candidate that matches by range but
|
|
57
|
+
* fails either digest check reports `digest-mismatch` — it does NOT silently fall
|
|
58
|
+
* through to the ledger, because a corrupt exact shard is a fact worth surfacing.
|
|
59
|
+
*/
|
|
60
|
+
export function readExactShard(shards, range, digest) {
|
|
61
|
+
const byRange = shards.filter((s) => rangeEquals(s.range, range));
|
|
62
|
+
if (byRange.length === 0)
|
|
63
|
+
return { kind: "absent" };
|
|
64
|
+
const candidate = byRange.find((s) => s.digest === digest);
|
|
65
|
+
if (candidate === undefined)
|
|
66
|
+
return { kind: "digest-mismatch" };
|
|
67
|
+
// Defense in depth: the shard's recorded digest is metadata, the bytes are the
|
|
68
|
+
// authority. Re-hash what we actually hold.
|
|
69
|
+
const bytes = candidate.originalBytes;
|
|
70
|
+
if (sha256Hex(bytes) !== digest)
|
|
71
|
+
return { kind: "digest-mismatch" };
|
|
72
|
+
return { kind: "bytes", bytes };
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Mode B — ledger range scan.
|
|
76
|
+
*
|
|
77
|
+
* An INDEPENDENT path from mode A: no shard index is consulted. The occurrence
|
|
78
|
+
* records covering `[seqStart..seqEnd]` are selected, sorted ascending by seq
|
|
79
|
+
* (the caller's array order is untrusted — a scan that concatenated records in
|
|
80
|
+
* arrival order would produce plausible-looking but wrong bytes), each record's
|
|
81
|
+
* own `bytesDigest` is verified, and the concatenation is hashed against the
|
|
82
|
+
* span digest.
|
|
83
|
+
*
|
|
84
|
+
* Both checks matter. Per-record verification localizes corruption to a single
|
|
85
|
+
* occurrence; the span-level hash catches a scan that is individually valid but
|
|
86
|
+
* collectively wrong — a missing record in the middle of the range, or a range
|
|
87
|
+
* that covers different content than the requester believed.
|
|
88
|
+
*/
|
|
89
|
+
export function readLedgerSpan(events, range, digest) {
|
|
90
|
+
const covering = events
|
|
91
|
+
.filter((e) => e.sessionId === range.sessionId &&
|
|
92
|
+
e.seq >= range.seqStart &&
|
|
93
|
+
e.seq <= range.seqEnd)
|
|
94
|
+
.sort((a, b) => (a.seq < b.seq ? -1 : a.seq > b.seq ? 1 : 0));
|
|
95
|
+
if (covering.length === 0)
|
|
96
|
+
return { kind: "absent" };
|
|
97
|
+
// Per-record byte authority (VC1A): a record whose own digest disagrees with
|
|
98
|
+
// its bytes is corrupt, and a corrupt record can never contribute to a restore.
|
|
99
|
+
for (const e of covering) {
|
|
100
|
+
if (sha256Hex(e.originalBytes) !== bareHex(e.bytesDigest)) {
|
|
101
|
+
return { kind: "digest-mismatch" };
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
const total = covering.reduce((sum, e) => sum + e.originalBytes.length, 0);
|
|
105
|
+
const bytes = new Uint8Array(total);
|
|
106
|
+
let offset = 0;
|
|
107
|
+
for (const e of covering) {
|
|
108
|
+
bytes.set(e.originalBytes, offset);
|
|
109
|
+
offset += e.originalBytes.length;
|
|
110
|
+
}
|
|
111
|
+
if (sha256Hex(bytes) !== digest)
|
|
112
|
+
return { kind: "digest-mismatch" };
|
|
113
|
+
return { kind: "bytes", bytes };
|
|
114
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vector-cortex/heal/restore-types.ts — VC6B exact source restoration contract.
|
|
3
|
+
*
|
|
4
|
+
* VC6A optimized WHICH EDGES the closure plan walks. VC6B answers the next
|
|
5
|
+
* question: when the plan needs a node whose bytes are no longer in the live
|
|
6
|
+
* window, WHERE do those bytes come from? The answer is deliberately narrow —
|
|
7
|
+
* an EXACT source, or nothing.
|
|
8
|
+
*
|
|
9
|
+
* THE CARDINAL RULE. Restored bytes are ONLY ever read from an exact source:
|
|
10
|
+
* 1. an `ExactShardV1` whose range and digest both match the request, or
|
|
11
|
+
* 2. a scan of the `EventV2` occurrence ledger over the requested seq range.
|
|
12
|
+
* Bytes are NEVER inferred from an embedding, a semantic shard, a RAPTOR
|
|
13
|
+
* summary, or any other derived/lossy representation. A semantic tier can tell
|
|
14
|
+
* you what a span was ABOUT; it cannot tell you what the span WAS. Attempting to
|
|
15
|
+
* "restore" from a derived source would silently fabricate transcript history,
|
|
16
|
+
* so VC6B has no code path that can do it: `RestoreReader` exposes exactly the
|
|
17
|
+
* two exact sources and nothing else.
|
|
18
|
+
*
|
|
19
|
+
* THE VERIFICATION RULE. Every restored span must hash to the SHA-256 digest the
|
|
20
|
+
* REQUEST pinned, checked immediately before insertion. A source that matches by
|
|
21
|
+
* range but not by hash is rejected (`HEAL_RESTORE_DIGEST_MISMATCH`) — never
|
|
22
|
+
* "close enough". This is what makes a swapped shard file, a truncated read, or a
|
|
23
|
+
* corrupted ledger record fail loudly instead of poisoning the reconstruction.
|
|
24
|
+
*
|
|
25
|
+
* DIGEST PINNING (three fields exist; do not confuse them).
|
|
26
|
+
* - `ReconstructionSpan.digest` and `ExactShardV1.digest` are SHA-256 in
|
|
27
|
+
* LOWERCASE HEX with NO prefix.
|
|
28
|
+
* - `EventV2.bytesDigest` is `sha256:<hex>`, WITH the prefix.
|
|
29
|
+
* `RestoreSpanRequest.digest` works at SPAN level and uses the FORMER: bare
|
|
30
|
+
* lowercase hex, matching `ExactShardV1.digest` / `ReconstructionSpan.digest`.
|
|
31
|
+
* The prefixed `EventV2.bytesDigest` is used ONLY for per-event verification
|
|
32
|
+
* inside the ledger-scan path and is never the request-level digest. Mixing the
|
|
33
|
+
* two would make every ledger restoration fail (or, worse, make a prefix-stripped
|
|
34
|
+
* comparison accidentally succeed against the wrong granularity).
|
|
35
|
+
*
|
|
36
|
+
* BOUNDS. A restore request is attacker-shaped input: it names spans and byte
|
|
37
|
+
* ranges. `RESTORE_LIMIT_SPANS` / `RESTORE_LIMIT_BYTES` bound it BEFORE any
|
|
38
|
+
* reader is consulted, so an oversized request cannot be used to make the
|
|
39
|
+
* restorer walk the whole ledger (HEAL-LIMIT-002).
|
|
40
|
+
*
|
|
41
|
+
* Pure types + registered conformance IDs: no storage, no console, no network
|
|
42
|
+
* (PREVENT-PI-004 / PREVENT-011).
|
|
43
|
+
*/
|
|
44
|
+
/**
|
|
45
|
+
* Maximum spans in one request. A request naming more spans than this is
|
|
46
|
+
* rejected outright — the bound exists so a single call cannot be turned into an
|
|
47
|
+
* unbounded traversal of the ledger.
|
|
48
|
+
*/
|
|
49
|
+
export const RESTORE_LIMIT_SPANS = 64;
|
|
50
|
+
/**
|
|
51
|
+
* Maximum aggregate requested bytes (4 MiB), summed from the REQUEST's ranges
|
|
52
|
+
* (`byteEnd - byteStart`) so the bound is enforceable without reading anything.
|
|
53
|
+
*/
|
|
54
|
+
export const RESTORE_LIMIT_BYTES = 4 * 1024 * 1024;
|
|
55
|
+
/**
|
|
56
|
+
* Registered VC6B conformance ID range (HEAL-016..030), continuing VC6A's
|
|
57
|
+
* HEAL-001..015. The acceptance test reads these rows from the v2 manifest and
|
|
58
|
+
* asserts each returns its manifest `ok`/`code`.
|
|
59
|
+
*/
|
|
60
|
+
export const RESTORE_IDS = Array.from({ length: 15 }, (_v, i) => `HEAL-${String(i + 16).padStart(3, "0")}`);
|
|
61
|
+
/** Named VC6B conformance assertions (the sprint's headline rows). */
|
|
62
|
+
export const RESTORE_NAMED_IDS = [
|
|
63
|
+
"HEAL-SPAN-001",
|
|
64
|
+
"HEAL-LIMIT-002",
|
|
65
|
+
"HEAL-DIGEST-003",
|
|
66
|
+
];
|