pi-mega-compact 0.20.8 → 0.20.10
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 +29 -0
- package/dist/config.js +1 -1
- package/dist/extensions/dashboard-server/route-dispatch.js +8 -1
- package/dist/extensions/dashboard-server/routes-rag-settings-vector-cortex.js +2 -0
- package/dist/extensions/dashboard-server/routes-vector-cortex-heal.js +42 -3
- package/dist/extensions/dashboard-server/routes-vector-cortex-repair.js +63 -0
- 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 +29 -0
- package/dist/src/config.js +1 -1
- package/dist/src/vector-cortex/heal/_repair-fixture.js +70 -0
- 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/controller.js +165 -0
- package/dist/src/vector-cortex/heal/rebuild.js +136 -0
- package/dist/src/vector-cortex/heal/repair-emit.js +83 -0
- package/dist/src/vector-cortex/heal/repair-types.js +74 -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 +2 -0
- package/dist/src/vector-cortex/heal/verify.js +75 -0
- package/dist/vector-cortex/heal/_repair-fixture.js +70 -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/controller.js +165 -0
- package/dist/vector-cortex/heal/rebuild.js +136 -0
- package/dist/vector-cortex/heal/repair-emit.js +83 -0
- package/dist/vector-cortex/heal/repair-types.js +74 -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 +2 -0
- package/dist/vector-cortex/heal/verify.js +75 -0
- package/extensions/dashboard-client/dist/assets/{AreaChart-5yxaI8IO.js → AreaChart-BDMjyRQp.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{AreaChart-5yxaI8IO.js.map → AreaChart-BDMjyRQp.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{BarChart-BoHhCqH-.js → BarChart-CqzPzOkj.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{BarChart-BoHhCqH-.js.map → BarChart-CqzPzOkj.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{CacheTab-Di6L3CsF.js → CacheTab-Bo8SH9q8.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{CacheTab-Di6L3CsF.js.map → CacheTab-Bo8SH9q8.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{EventsTab-Cdff3h6s.js → EventsTab-DIEs6zC-.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{EventsTab-Cdff3h6s.js.map → EventsTab-DIEs6zC-.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{HealthTab-MthYYVLw.js → HealthTab-3a8IjwIK.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{HealthTab-MthYYVLw.js.map → HealthTab-3a8IjwIK.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MaintenanceTab-D9G4ntsG.js → MaintenanceTab-CdKwAcXo.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MaintenanceTab-D9G4ntsG.js.map → MaintenanceTab-CdKwAcXo.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MemoryMapTab-CN8Lr5Qf.js → MemoryMapTab-D2hHuNj-.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MemoryMapTab-CN8Lr5Qf.js.map → MemoryMapTab-D2hHuNj-.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MetricsTab-CgO32kSo.js → MetricsTab-CFhIhqrr.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MetricsTab-CgO32kSo.js.map → MetricsTab-CFhIhqrr.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{OverviewTab-Cb4kR0h1.js → OverviewTab-Dzp5ZoiA.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{OverviewTab-Cb4kR0h1.js.map → OverviewTab-Dzp5ZoiA.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{ReposTab-Dt1FGnMR.js → ReposTab-CFJT9mDR.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{ReposTab-Dt1FGnMR.js.map → ReposTab-CFJT9mDR.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{SessionsTab-D7D2RW6U.js → SessionsTab-BhP1z6_y.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{SessionsTab-D7D2RW6U.js.map → SessionsTab-BhP1z6_y.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{SetupTab-CgV5xCzz.js → SetupTab-2Glh7MKk.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{SetupTab-CgV5xCzz.js.map → SetupTab-2Glh7MKk.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-CCNuJjyO.js → TimeSavedCard-DmwBDhkw.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-CCNuJjyO.js.map → TimeSavedCard-DmwBDhkw.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TurnsTab-Cv1OCttJ.js → TurnsTab-CYIaOUUZ.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TurnsTab-Cv1OCttJ.js.map → TurnsTab-CYIaOUUZ.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/VectorCortexTab-DD7vGaRS.js +2 -0
- package/extensions/dashboard-client/dist/assets/VectorCortexTab-DD7vGaRS.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{WikiTab-B48QnQrK.js → WikiTab-CP-JEd17.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{WikiTab-B48QnQrK.js.map → WikiTab-CP-JEd17.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{button-BBQl2MiF.js → button-B1RhLsGs.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{button-BBQl2MiF.js.map → button-B1RhLsGs.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{card-BYbefUur.js → card-BLPT2-8G.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{card-BYbefUur.js.map → card-BLPT2-8G.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-swm0YZe0.js → generateCategoricalChart-DVOMJzL2.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-swm0YZe0.js.map → generateCategoricalChart-DVOMJzL2.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{index-DeIky7xm.js → index-CuLdiHRl.js} +3 -3
- package/extensions/dashboard-client/dist/assets/{index-DeIky7xm.js.map → index-CuLdiHRl.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{switch-CctU78ww.js → switch-C5tqzhcl.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{switch-CctU78ww.js.map → switch-C5tqzhcl.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{toggle-BRCNCoiI.js → toggle-HcM6W2Yl.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{toggle-BRCNCoiI.js.map → toggle-HcM6W2Yl.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{useSSE-WfOiWDH_.js → useSSE-BtWUs2kL.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{useSSE-WfOiWDH_.js.map → useSSE-BtWUs2kL.js.map} +1 -1
- package/extensions/dashboard-client/dist/index.html +1 -1
- package/extensions/dashboard-client/src/api/vector-cortex.ts +18 -0
- package/extensions/dashboard-client/src/tabs/VectorCortexRepairCard.tsx +43 -0
- package/extensions/dashboard-client/src/tabs/VectorCortexRestoreCard.tsx +42 -0
- package/extensions/dashboard-client/src/tabs/VectorCortexShardsCard.tsx +89 -0
- package/extensions/dashboard-client/src/tabs/VectorCortexTab.tsx +22 -60
- package/extensions/dashboard-client/src/types/vector-cortex.ts +33 -0
- package/extensions/dashboard-server/api-contracts/vector-cortex-heal.ts +68 -0
- package/extensions/dashboard-server/api-contracts/vector-cortex.ts +5 -1
- package/extensions/dashboard-server/route-dispatch.ts +6 -0
- package/extensions/dashboard-server/routes-rag-settings-vector-cortex.ts +12 -0
- package/extensions/dashboard-server/routes-vector-cortex-heal.ts +51 -4
- package/extensions/dashboard-server/routes-vector-cortex-repair.ts +72 -0
- 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 +31 -0
- package/src/config.ts +2 -0
- package/src/vector-cortex/heal/_repair-fixture.ts +138 -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/controller.ts +189 -0
- package/src/vector-cortex/heal/rebuild.ts +200 -0
- package/src/vector-cortex/heal/repair-emit.ts +118 -0
- package/src/vector-cortex/heal/repair-types.ts +227 -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 +50 -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,35 @@ 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");
|
|
224
|
+
/**
|
|
225
|
+
* VC6C — self-healing derived controller. Default ON. `MEGACOMPACT_VC6C=0`
|
|
226
|
+
* disables and is byte-identical to the predecessor (VC6B): the `detectGaps` /
|
|
227
|
+
* `planRebuild` / `rebuildGeneration` / `switchPointer` arithmetic STILL RUNS (it
|
|
228
|
+
* is PURE — a high-water comparison plus a SHA-256 root-digest check, with an
|
|
229
|
+
* INJECTED clock and no storage or network), but the
|
|
230
|
+
* `vector_cortex_repair_planned` / `vector_cortex_repair_pointer_switched` /
|
|
231
|
+
* `vector_cortex_repair_backoff` events are never emitted and the dashboard
|
|
232
|
+
* repair diagnostics seam is suppressed. Flag OFF gates the reporter + dashboard
|
|
233
|
+
* seam, never the gap-detection/rebuild arithmetic (in particular, an unverified
|
|
234
|
+
* pointer switch stays refused with the flag off), so flag-off
|
|
235
|
+
* outbound/predecessor golden bytes match exactly. This flag MUST also be a
|
|
236
|
+
* dashboard SETTINGS toggle (visible in config UI, never in EXCLUDED_SETTINGS),
|
|
237
|
+
* mirroring VC4A/VC4B/VC4C/VC5A/VC5B/VC5C/VC6A/VC6B.
|
|
238
|
+
*/
|
|
239
|
+
export const VC6C_ENABLED = () => sprintFlag("MEGACOMPACT_VC6C");
|
|
211
240
|
// ---------------------------------------------------------------------------
|
|
212
241
|
// Breaker state machine constants (TRIAD_RESILIENCE.md §breaker).
|
|
213
242
|
// 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, VC6C_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,10 @@
|
|
|
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
|
+
// VC6C repair lives in its own module (routes-vector-cortex-repair.ts) so the
|
|
13
|
+
// heal route file stays well under the 400-line extension soft limit.
|
|
14
|
+
import { handleVectorCortexRepair } from "./routes-vector-cortex-repair.js";
|
|
12
15
|
/**
|
|
13
16
|
* Dispatch a request through every registered route handler.
|
|
14
17
|
* Returns true if a handler claimed the request (ended the response).
|
|
@@ -98,5 +101,9 @@ export function dispatchRoutes(req, res, ctx) {
|
|
|
98
101
|
return true;
|
|
99
102
|
if (handleVectorCortexClosureProof(req, res, ctx))
|
|
100
103
|
return true;
|
|
104
|
+
if (handleVectorCortexRestore(req, res, ctx))
|
|
105
|
+
return true;
|
|
106
|
+
if (handleVectorCortexRepair(req, res, ctx))
|
|
107
|
+
return true;
|
|
101
108
|
return false;
|
|
102
109
|
}
|
|
@@ -40,5 +40,7 @@ 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),
|
|
44
|
+
boolDirect("MEGACOMPACT_VC6C", "VC6C Self-Healing Derived State", "Self-healing derived-state controller: detects gaps between each derived subsystem's high-water mark and the durable authority high-water, then rebuilds derived state by copy -> root-digest verification -> atomic pointer switch, so a partially rebuilt subsystem is never made visible. A targeted single-subsystem rebuild is mode A; an ambiguous gap escalates to a full deterministic rebuild (mode B); if both rebuild paths fail the derived state is disabled rather than served stale (mode C). Rebuilds are rate-limited to one per subsystem per 5 minutes with deterministic exponential backoff, so a persistently failing subsystem cannot spin. OFF = byte-identical predecessor (VC6B); no controller runs, so the dashboard reports mode C.", true),
|
|
43
45
|
],
|
|
44
46
|
};
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dashboard-server/routes-vector-cortex-repair.ts — VC6C self-healing
|
|
3
|
+
* derived-state dashboard route.
|
|
4
|
+
*
|
|
5
|
+
* Reader-only GET /api/vector-cortex/repair returning the self-healing
|
|
6
|
+
* controller's aggregate diagnostics: whether the flag is enabled, the runtime
|
|
7
|
+
* triad mode, cumulative rebuild/pointer-switch/backoff counters, the last
|
|
8
|
+
* deterministic backoff delay, and the last HEAL_REPAIR_* failure code.
|
|
9
|
+
*
|
|
10
|
+
* COUNTS + ERROR CODES ONLY. NEVER exposes subsystem source bytes, rebuilt
|
|
11
|
+
* derived rows, per-subsystem gap ranges, high-water marks, root digests, or
|
|
12
|
+
* ledger text (reader-only, SECURITY_PRIVACY — the per-subsystem gap detail
|
|
13
|
+
* lives in the structured event log, not the dashboard). There is no mutation
|
|
14
|
+
* seam here either: rebuilds are driven by the controller's own gap detection,
|
|
15
|
+
* never by a dashboard request, so non-GET is rejected outright.
|
|
16
|
+
*
|
|
17
|
+
* Split into its own file (rather than grown into routes-vector-cortex-heal.ts)
|
|
18
|
+
* to keep every extensions/ file well under the 400-line soft-as-hard limit.
|
|
19
|
+
*
|
|
20
|
+
* Guardrails: PREVENT-PI-004 (local in-process state only), PREVENT-011 (no
|
|
21
|
+
* `any`), reader-only aggregate (counts + codes only).
|
|
22
|
+
*/
|
|
23
|
+
import { VC6C_ENABLED } from "../../src/config.js";
|
|
24
|
+
import { sendJson } from "./routes-vector-cortex-shared.js";
|
|
25
|
+
/**
|
|
26
|
+
* Reader-only GET /api/vector-cortex/repair (VC6C).
|
|
27
|
+
*
|
|
28
|
+
* Counts and HEAL_REPAIR_* codes only — this is a static reader-only aggregate
|
|
29
|
+
* seam (same shape as the VC6A/VC6B handlers): there is no payload endpoint and
|
|
30
|
+
* never will be, so no subsystem bytes, gap ranges, high-water marks, or root
|
|
31
|
+
* digests can leak here.
|
|
32
|
+
*/
|
|
33
|
+
export function handleVectorCortexRepair(req, res, _ctx) {
|
|
34
|
+
const url = req.url ?? "";
|
|
35
|
+
const path = url.split("?")[0] ?? url;
|
|
36
|
+
if (path !== "/api/vector-cortex/repair")
|
|
37
|
+
return false;
|
|
38
|
+
if (req.method !== "GET") {
|
|
39
|
+
sendJson(res, 405, { error: "method_not_allowed" });
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
const enabled = VC6C_ENABLED();
|
|
43
|
+
// Flag-off routes to mode C: with VC6C off no self-healing controller runs at
|
|
44
|
+
// all, so neither the targeted rebuild (mode A) nor the full deterministic
|
|
45
|
+
// rebuild (mode B) path is available. Mode C is exactly the spec's "derived
|
|
46
|
+
// state disabled" outcome — the honest OFF view is that derived state is not
|
|
47
|
+
// being healed, rather than implying a rebuild path that is not wired.
|
|
48
|
+
// Mirrors how VC6B's OFF view reports the disclose-loss mode it actually takes.
|
|
49
|
+
const mode = enabled ? "A" : "C";
|
|
50
|
+
const body = {
|
|
51
|
+
enabled,
|
|
52
|
+
mode,
|
|
53
|
+
repairAttempts: 0,
|
|
54
|
+
repairsPlanned: 0,
|
|
55
|
+
pointersSwitched: 0,
|
|
56
|
+
backoffs: 0,
|
|
57
|
+
lastBackoffMs: null,
|
|
58
|
+
lastFailure: null,
|
|
59
|
+
updatedAt: new Date().toISOString(),
|
|
60
|
+
};
|
|
61
|
+
sendJson(res, 200, body);
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
@@ -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,35 @@ 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");
|
|
224
|
+
/**
|
|
225
|
+
* VC6C — self-healing derived controller. Default ON. `MEGACOMPACT_VC6C=0`
|
|
226
|
+
* disables and is byte-identical to the predecessor (VC6B): the `detectGaps` /
|
|
227
|
+
* `planRebuild` / `rebuildGeneration` / `switchPointer` arithmetic STILL RUNS (it
|
|
228
|
+
* is PURE — a high-water comparison plus a SHA-256 root-digest check, with an
|
|
229
|
+
* INJECTED clock and no storage or network), but the
|
|
230
|
+
* `vector_cortex_repair_planned` / `vector_cortex_repair_pointer_switched` /
|
|
231
|
+
* `vector_cortex_repair_backoff` events are never emitted and the dashboard
|
|
232
|
+
* repair diagnostics seam is suppressed. Flag OFF gates the reporter + dashboard
|
|
233
|
+
* seam, never the gap-detection/rebuild arithmetic (in particular, an unverified
|
|
234
|
+
* pointer switch stays refused with the flag off), so flag-off
|
|
235
|
+
* outbound/predecessor golden bytes match exactly. This flag MUST also be a
|
|
236
|
+
* dashboard SETTINGS toggle (visible in config UI, never in EXCLUDED_SETTINGS),
|
|
237
|
+
* mirroring VC4A/VC4B/VC4C/VC5A/VC5B/VC5C/VC6A/VC6B.
|
|
238
|
+
*/
|
|
239
|
+
export const VC6C_ENABLED = () => sprintFlag("MEGACOMPACT_VC6C");
|
|
211
240
|
// ---------------------------------------------------------------------------
|
|
212
241
|
// Breaker state machine constants (TRIAD_RESILIENCE.md §breaker).
|
|
213
242
|
// 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, VC6C_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,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* heal/_repair-fixture.ts — conformance fixture I/O for VC6C healing-controller rows.
|
|
3
|
+
*
|
|
4
|
+
* Sibling of `_restore-fixture.ts` (VC6B) and `_acceptance-fixture.ts` (VC6A);
|
|
5
|
+
* split out so no loader approaches the 300-line soft limit and so the
|
|
6
|
+
* BigInt/base64 decoding lives next to the contract it reconstitutes.
|
|
7
|
+
*
|
|
8
|
+
* DECODING IS THE POINT. Fixtures are canonical JSON, which cannot express
|
|
9
|
+
* bigints or bytes, so the corpus stores numeric `nowMs` / high-waters and
|
|
10
|
+
* base64 source bytes. These loaders turn those back into the REAL `RepairState`
|
|
11
|
+
* / `RebuildInput` objects the production controller consumes — no mocks, no
|
|
12
|
+
* stubs, no parallel "test shape". A lossy decode would make the digests or the
|
|
13
|
+
* planned ranges disagree and the acceptance test would fail loudly.
|
|
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 healing-controller fixture (asserting it IS registered). */
|
|
20
|
+
export function repairFixture(id) {
|
|
21
|
+
const m = readManifest();
|
|
22
|
+
const row = m.fixtures.find((f) => f.id === id && f.path.startsWith("healing-controller/"));
|
|
23
|
+
assert.ok(row, `fixture ${id} registered under healing-controller/ in manifest`);
|
|
24
|
+
return JSON.parse(readFileSync(join(V2, row.path), "utf8"));
|
|
25
|
+
}
|
|
26
|
+
/** JSON numbers -> the bigint fields `RepairState` declares. */
|
|
27
|
+
export function decodeState(s) {
|
|
28
|
+
return {
|
|
29
|
+
subsystem: s.subsystem,
|
|
30
|
+
derivedHighWater: BigInt(s.derivedHighWater),
|
|
31
|
+
authorityHighWater: BigInt(s.authorityHighWater),
|
|
32
|
+
lastRebuildAt: s.lastRebuildAt === null ? null : BigInt(s.lastRebuildAt),
|
|
33
|
+
generation: s.generation,
|
|
34
|
+
mode: s.mode,
|
|
35
|
+
...(s.failedAttempts !== undefined ? { failedAttempts: s.failedAttempts } : {}),
|
|
36
|
+
...(s.authorityFrozen !== undefined ? { authorityFrozen: s.authorityFrozen } : {}),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/** Reconstitute a real `RebuildInput` from its fixture row. */
|
|
40
|
+
export function decodeRebuild(r) {
|
|
41
|
+
return {
|
|
42
|
+
subsystem: r.subsystem,
|
|
43
|
+
range: {
|
|
44
|
+
sessionId: r.subsystem,
|
|
45
|
+
seqStart: 0n,
|
|
46
|
+
seqEnd: 0n,
|
|
47
|
+
byteStart: 0,
|
|
48
|
+
byteEnd: 0,
|
|
49
|
+
},
|
|
50
|
+
generation: r.generation,
|
|
51
|
+
sourceBytes: new Uint8Array(Buffer.from(r.sourceBytesBase64, "base64")),
|
|
52
|
+
expectedDigest: r.expectedDigest,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/** Flag-pinned wrapper: VC6C gated by MEGACOMPACT_VC6C (defaults ON). */
|
|
56
|
+
export function withVc6cFlagsOn(fn) {
|
|
57
|
+
return () => {
|
|
58
|
+
const saved = process.env.MEGACOMPACT_VC6C;
|
|
59
|
+
process.env.MEGACOMPACT_VC6C = "1";
|
|
60
|
+
try {
|
|
61
|
+
fn();
|
|
62
|
+
}
|
|
63
|
+
finally {
|
|
64
|
+
if (saved === undefined)
|
|
65
|
+
delete process.env.MEGACOMPACT_VC6C;
|
|
66
|
+
else
|
|
67
|
+
process.env.MEGACOMPACT_VC6C = saved;
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
}
|
|
@@ -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,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vector-cortex/heal/controller.ts — VC6C gap detection + rebuild planning.
|
|
3
|
+
*
|
|
4
|
+
* Compares each derived subsystem's high-water to the durable authority
|
|
5
|
+
* high-water and emits a `RepairPlanV1` for every gap that is real, actionable,
|
|
6
|
+
* and not rate-limited. The controller DECIDES; `rebuild.ts` executes.
|
|
7
|
+
*
|
|
8
|
+
* THE AUTHORITY IS NEVER WRITTEN. Every function here takes `RepairState` (whose
|
|
9
|
+
* `authorityHighWater` is readonly) and returns plans. There is no code path,
|
|
10
|
+
* guarded or otherwise, that mutates authority — derived state is rebuildable,
|
|
11
|
+
* authority is not, so repair is strictly one-directional.
|
|
12
|
+
*
|
|
13
|
+
* FOUR REASONS NOT TO PLAN, in priority order. The order is the contract: a
|
|
14
|
+
* frozen authority outranks a rate limit, because "the frontier is not real yet"
|
|
15
|
+
* is a statement about CORRECTNESS while "you rebuilt recently" is only about
|
|
16
|
+
* pacing. Reporting RATE_LIMITED during an outage would tell an operator to wait
|
|
17
|
+
* five minutes for a rebuild that must never happen at all.
|
|
18
|
+
*
|
|
19
|
+
* 1. AUTHORITY FROZEN. During an outage the durable high-water freezes while
|
|
20
|
+
* the spool keeps accepting frames (TRIAD_RESILIENCE §frontier). A derived
|
|
21
|
+
* subsystem behind a frozen frontier is CORRECT, so we refuse to plan rather
|
|
22
|
+
* than chase the spool tail and materialize non-durable frames.
|
|
23
|
+
* 2. NO GAP. `derived >= authority` — nothing to do. Note `>`: a derived source
|
|
24
|
+
* AHEAD of authority is not repaired by rebuilding a backwards range (that
|
|
25
|
+
* would produce an inverted range); it is left alone for the breaker.
|
|
26
|
+
* 3. MODE C. Derived state is disabled for this subsystem. Re-planning a
|
|
27
|
+
* subsystem whose derived tier is intentionally off is a rebuild loop.
|
|
28
|
+
* 4. RATE LIMITED. One rebuild per subsystem per 5 minutes.
|
|
29
|
+
*
|
|
30
|
+
* DETERMINISTIC JITTER. The ±10% spread comes from a SHA-256 of the subsystem
|
|
31
|
+
* name plus the attempt, never `Math.random`. Two subsystems desynchronize (no
|
|
32
|
+
* thundering herd) while a single subsystem's schedule is byte-reproducible — a
|
|
33
|
+
* fixture can pin `backoffMs` exactly, which a PRNG would make untestable.
|
|
34
|
+
*
|
|
35
|
+
* PURE. `node:crypto` is the only dependency beyond types: no storage, no
|
|
36
|
+
* console, no network, and no clock — `nowMs` is always injected, which is what
|
|
37
|
+
* makes the fake-clock restart fixtures possible (PREVENT-PI-004 / PREVENT-011).
|
|
38
|
+
*/
|
|
39
|
+
import { createHash } from "node:crypto";
|
|
40
|
+
import { REPAIR_BACKOFF_BASE_MS, REPAIR_BACKOFF_CAP_MS, REPAIR_BACKOFF_JITTER, REPAIR_RATE_LIMIT_MS, } from "./repair-types.js";
|
|
41
|
+
/**
|
|
42
|
+
* A stable fraction in [0,1) derived from the subsystem name + attempt.
|
|
43
|
+
*
|
|
44
|
+
* Uses the first 6 hex digits (24 bits) of SHA-256 — plenty of spread for a ±10%
|
|
45
|
+
* window, and small enough to stay exact in a float64 division.
|
|
46
|
+
*/
|
|
47
|
+
function digestFraction(subsystem, attempt) {
|
|
48
|
+
const hex = createHash("sha256")
|
|
49
|
+
.update(`${subsystem}:${attempt}`)
|
|
50
|
+
.digest("hex")
|
|
51
|
+
.slice(0, 6);
|
|
52
|
+
return parseInt(hex, 16) / 0x1000000;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Deterministic exponential backoff: `30s * 2^attempt`, capped at 15 minutes,
|
|
56
|
+
* then spread by ±10% seeded from the subsystem digest.
|
|
57
|
+
*
|
|
58
|
+
* The cap is applied BEFORE the jitter so the jitter is a spread around the cap
|
|
59
|
+
* rather than a way to exceed it; the result is floored to an integer ms so the
|
|
60
|
+
* value is exactly representable in JSON (a fixture pins it verbatim).
|
|
61
|
+
*
|
|
62
|
+
* `attempt` is clamped at 0 and at 30: `2^attempt` for an unclamped large attempt
|
|
63
|
+
* would reach Infinity, and `Infinity * jitter` is NaN — a NaN backoff would
|
|
64
|
+
* schedule a plan that is never eligible and silently wedge the subsystem.
|
|
65
|
+
*/
|
|
66
|
+
export function computeBackoff(subsystem, attempt) {
|
|
67
|
+
const safe = Math.min(Math.max(Math.floor(attempt), 0), 30);
|
|
68
|
+
const raw = REPAIR_BACKOFF_BASE_MS * 2 ** safe;
|
|
69
|
+
const capped = Math.min(raw, REPAIR_BACKOFF_CAP_MS);
|
|
70
|
+
// Map [0,1) onto [-1,+1) then scale to the ±10% window.
|
|
71
|
+
const spread = (digestFraction(subsystem, safe) * 2 - 1) * REPAIR_BACKOFF_JITTER;
|
|
72
|
+
return Math.max(0, Math.floor(capped * (1 + spread)));
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* True when this subsystem rebuilt less than 5 minutes ago.
|
|
76
|
+
*
|
|
77
|
+
* A subsystem that has NEVER rebuilt (`lastRebuildAt === null`) is never rate
|
|
78
|
+
* limited — the first repair after a restart must not be delayed by the absence
|
|
79
|
+
* of history.
|
|
80
|
+
*/
|
|
81
|
+
export function isRateLimited(lastRebuildAt, nowMs) {
|
|
82
|
+
if (lastRebuildAt === null)
|
|
83
|
+
return false;
|
|
84
|
+
return lastRebuildAt + BigInt(REPAIR_RATE_LIMIT_MS) > nowMs;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* The seq window a rebuild must cover: the first UNBUILT seq through the
|
|
88
|
+
* authority frontier, inclusive.
|
|
89
|
+
*
|
|
90
|
+
* Byte bounds are intentionally 0..0. VC6C plans in SEQ space — the controller
|
|
91
|
+
* knows how far each derived tier has been built, but byte offsets belong to the
|
|
92
|
+
* shard/ledger layer that executes the rebuild. Inventing byte bounds here would
|
|
93
|
+
* be fabricating a fact the controller does not have.
|
|
94
|
+
*/
|
|
95
|
+
function gapRange(state) {
|
|
96
|
+
return {
|
|
97
|
+
sessionId: state.subsystem,
|
|
98
|
+
seqStart: state.derivedHighWater + 1n,
|
|
99
|
+
seqEnd: state.authorityHighWater,
|
|
100
|
+
byteStart: 0,
|
|
101
|
+
byteEnd: 0,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Build the plan for one subsystem's gap.
|
|
106
|
+
*
|
|
107
|
+
* Always targets `generation + 1`: a rebuild NEVER writes into the live
|
|
108
|
+
* generation, so a failed or half-written rebuild cannot corrupt what is
|
|
109
|
+
* currently being served (see `rebuild.ts` copy-then-switch).
|
|
110
|
+
*
|
|
111
|
+
* Exported for direct unit testing and for callers that have already decided a
|
|
112
|
+
* plan is warranted; `detectGaps` is the guarded entry point that applies the
|
|
113
|
+
* four refusal rules first.
|
|
114
|
+
*/
|
|
115
|
+
export function planRebuild(state, nowMs) {
|
|
116
|
+
const backoffMs = computeBackoff(state.subsystem, state.failedAttempts ?? 0);
|
|
117
|
+
return {
|
|
118
|
+
schema: "repair-plan-v1",
|
|
119
|
+
subsystem: state.subsystem,
|
|
120
|
+
range: gapRange(state),
|
|
121
|
+
generation: state.generation + 1,
|
|
122
|
+
backoffMs,
|
|
123
|
+
scheduledAt: nowMs + BigInt(backoffMs),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* True when a subsystem is eligible for a rebuild plan (the four refusal rules).
|
|
128
|
+
* Ordering matters — see the file header.
|
|
129
|
+
*/
|
|
130
|
+
export function isPlannable(state, nowMs) {
|
|
131
|
+
if (state.authorityFrozen === true)
|
|
132
|
+
return false;
|
|
133
|
+
if (state.derivedHighWater >= state.authorityHighWater)
|
|
134
|
+
return false;
|
|
135
|
+
if (state.mode === "C")
|
|
136
|
+
return false;
|
|
137
|
+
if (isRateLimited(state.lastRebuildAt, nowMs))
|
|
138
|
+
return false;
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Detect derived gaps and plan a rebuild for each eligible subsystem.
|
|
143
|
+
*
|
|
144
|
+
* Output order follows INPUT order, not subsystem name: the caller controls
|
|
145
|
+
* priority (a topology gap may matter more than a closure gap), and re-sorting
|
|
146
|
+
* here would silently override that. Never throws — an unplannable subsystem is
|
|
147
|
+
* simply absent from the result, so a single wedged tier cannot stop the others
|
|
148
|
+
* from healing (PRACTICES: non-fatal).
|
|
149
|
+
*/
|
|
150
|
+
export function detectGaps(states, nowMs) {
|
|
151
|
+
const plans = [];
|
|
152
|
+
for (const state of states) {
|
|
153
|
+
if (!isPlannable(state, nowMs))
|
|
154
|
+
continue;
|
|
155
|
+
plans.push(planRebuild(state, nowMs));
|
|
156
|
+
}
|
|
157
|
+
return plans;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* The bound controller surface. A plain object (not a class): there is no
|
|
161
|
+
* instance state to hold — every input, including the clock, is injected.
|
|
162
|
+
*/
|
|
163
|
+
export function createRepairController() {
|
|
164
|
+
return { detectGaps, planRebuild };
|
|
165
|
+
}
|