pi-mega-compact 0.16.3 → 0.17.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config/vector-cortex.js +91 -0
- package/dist/dedup/digest.js +30 -0
- package/dist/dedup/l1-lsh-v2.js +60 -0
- package/dist/dedup/l1-lsh.js +52 -0
- package/dist/dedup/l1-minhash-v2.js +193 -0
- package/dist/dedup/l1-minhash.js +91 -0
- package/dist/dedup/l1-verify.js +54 -0
- package/dist/dedup/mmr.js +45 -0
- package/dist/dedup/normalize.js +39 -0
- package/dist/dedup/raptor/buildHistory.js +164 -0
- package/dist/dedup/raptor/guardrails.js +83 -0
- package/dist/dedup/raptor/incremental.js +304 -0
- package/dist/dedup/raptor/index.js +190 -0
- package/dist/dedup/raptor/kmeans.js +152 -0
- package/dist/dedup/raptor/multilevel.js +184 -0
- package/dist/dedup/raptor/retrieval.js +93 -0
- package/dist/dedup/raptor/summarizer.js +86 -0
- package/dist/dedup/raptor/tree.js +202 -0
- package/dist/dedup/topk.js +60 -0
- package/dist/extensions/dashboard-server/api-contracts/endpoints/registry-ext.js +30 -0
- package/dist/extensions/dashboard-server/api-contracts/endpoints/registry.js +4 -14
- package/dist/extensions/dashboard-server/api-contracts/vector-cortex.js +12 -0
- package/dist/extensions/dashboard-server/routes-rag-settings-helpers.js +11 -0
- package/dist/extensions/dashboard-server/routes-vector-cortex.js +292 -0
- package/dist/extensions/dashboard-server/routes.js +1 -0
- package/dist/extensions/dashboard-server/server.js +9 -1
- package/dist/extensions/mega-events/context-handler.js +11 -0
- package/dist/extensions/mega-runtime/vector-cortex-ledger.js +131 -0
- package/dist/extensions/mega-runtime/vector-cortex-safety.js +100 -0
- package/dist/src/config/vector-cortex.js +91 -0
- package/dist/src/config.js +6 -0
- package/dist/src/dedup/l1-lsh-v2.js +60 -0
- package/dist/src/dedup/l1-minhash-v2.js +193 -0
- package/dist/src/dedup/normalize.js +1 -1
- package/dist/src/vector-cortex/conformance/emit.js +46 -0
- package/dist/src/vector-cortex/conformance/manifest.js +235 -0
- package/dist/src/vector-cortex/conformance/runner.js +111 -0
- package/dist/src/vector-cortex/conformance/triadB-reader.js +95 -0
- package/dist/src/vector-cortex/eval/annotations.js +41 -0
- package/dist/src/vector-cortex/eval/metrics.js +86 -0
- package/dist/src/vector-cortex/eval/observer.js +61 -0
- package/dist/src/vector-cortex/eval/persist.js +67 -0
- package/dist/src/vector-cortex/eval/reader.js +29 -0
- package/dist/src/vector-cortex/eval/types.js +33 -0
- package/dist/src/vector-cortex/ledger/adapter.js +55 -0
- package/dist/src/vector-cortex/ledger/compat-journal.js +206 -0
- package/dist/src/vector-cortex/ledger/emit.js +53 -0
- package/dist/src/vector-cortex/ledger/event-codec.js +72 -0
- package/dist/src/vector-cortex/ledger/event-codecB.js +76 -0
- package/dist/src/vector-cortex/ledger/sqlite.js +200 -0
- package/dist/src/vector-cortex/ledger/store.js +184 -0
- package/dist/src/vector-cortex/ledger/types.js +52 -0
- package/dist/src/vector-cortex/ledger/validator.js +117 -0
- package/dist/src/vector-cortex/migrations/effective-cut-v2.js +103 -0
- package/dist/src/vector-cortex/migrations/minhash-v2.js +209 -0
- package/dist/src/vector-cortex/migrations/occurrence-v2.js +139 -0
- package/dist/src/vector-cortex/replay/cut.js +123 -0
- package/dist/src/vector-cortex/replay/emit.js +58 -0
- package/dist/src/vector-cortex/replay/replay.js +260 -0
- package/dist/src/vector-cortex/replay/replayB.js +73 -0
- package/dist/src/vector-cortex/replay/types.js +52 -0
- package/dist/src/vector-cortex/resilience/breaker-core.js +359 -0
- package/dist/src/vector-cortex/resilience/breaker.js +12 -0
- package/dist/src/vector-cortex/resilience/emit.js +56 -0
- package/dist/src/vector-cortex/resilience/spool-core.js +347 -0
- package/dist/src/vector-cortex/resilience/spool.js +12 -0
- package/dist/src/vector-cortex/resilience/types.js +32 -0
- package/dist/vector-cortex/conformance/emit.js +46 -0
- package/dist/vector-cortex/conformance/manifest.js +235 -0
- package/dist/vector-cortex/conformance/runner.js +111 -0
- package/dist/vector-cortex/conformance/triadB-reader.js +95 -0
- package/dist/vector-cortex/eval/annotations.js +41 -0
- package/dist/vector-cortex/eval/metrics.js +86 -0
- package/dist/vector-cortex/eval/observer.js +61 -0
- package/dist/vector-cortex/eval/persist.js +67 -0
- package/dist/vector-cortex/eval/reader.js +29 -0
- package/dist/vector-cortex/eval/types.js +33 -0
- package/dist/vector-cortex/ledger/adapter.js +55 -0
- package/dist/vector-cortex/ledger/compat-journal.js +206 -0
- package/dist/vector-cortex/ledger/emit.js +53 -0
- package/dist/vector-cortex/ledger/event-codec.js +72 -0
- package/dist/vector-cortex/ledger/event-codecB.js +76 -0
- package/dist/vector-cortex/ledger/sqlite.js +200 -0
- package/dist/vector-cortex/ledger/store.js +184 -0
- package/dist/vector-cortex/ledger/types.js +52 -0
- package/dist/vector-cortex/ledger/validator.js +117 -0
- package/dist/vector-cortex/migrations/effective-cut-v2.js +103 -0
- package/dist/vector-cortex/migrations/minhash-v2.js +209 -0
- package/dist/vector-cortex/migrations/occurrence-v2.js +139 -0
- package/dist/vector-cortex/replay/cut.js +123 -0
- package/dist/vector-cortex/replay/emit.js +58 -0
- package/dist/vector-cortex/replay/replay.js +260 -0
- package/dist/vector-cortex/replay/replayB.js +73 -0
- package/dist/vector-cortex/replay/types.js +52 -0
- package/dist/vector-cortex/resilience/breaker-core.js +359 -0
- package/dist/vector-cortex/resilience/breaker.js +12 -0
- package/dist/vector-cortex/resilience/emit.js +56 -0
- package/dist/vector-cortex/resilience/spool-core.js +347 -0
- package/dist/vector-cortex/resilience/spool.js +12 -0
- package/dist/vector-cortex/resilience/types.js +32 -0
- package/extensions/dashboard-client/dist/assets/{AreaChart-Y_X0Bbjd.js → AreaChart-BQXFF7UI.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{AreaChart-Y_X0Bbjd.js.map → AreaChart-BQXFF7UI.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{BarChart-B9XDb-Ob.js → BarChart-BVtoz9Eg.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{BarChart-B9XDb-Ob.js.map → BarChart-BVtoz9Eg.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{CacheTab-BUGVb1G1.js → CacheTab-trI2TIGy.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{CacheTab-BUGVb1G1.js.map → CacheTab-trI2TIGy.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{EventsTab-dOqLYTsy.js → EventsTab-CNUbk9__.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{EventsTab-dOqLYTsy.js.map → EventsTab-CNUbk9__.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{HealthTab-c2vezKg1.js → HealthTab-K-bd_CM-.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{HealthTab-c2vezKg1.js.map → HealthTab-K-bd_CM-.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MaintenanceTab-bQYHIcdx.js → MaintenanceTab-CoEioKzx.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MaintenanceTab-bQYHIcdx.js.map → MaintenanceTab-CoEioKzx.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MemoryMapTab-2L8pH6y2.js → MemoryMapTab-CJo24ulv.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MemoryMapTab-2L8pH6y2.js.map → MemoryMapTab-CJo24ulv.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MetricsTab-CzUx1jpo.js → MetricsTab-DCNlQWWP.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MetricsTab-CzUx1jpo.js.map → MetricsTab-DCNlQWWP.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{OverviewTab-eCNoaclv.js → OverviewTab-CQQlyIQ6.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{OverviewTab-eCNoaclv.js.map → OverviewTab-CQQlyIQ6.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{ReposTab-tb0IW3NF.js → ReposTab-Cqm82_yn.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{ReposTab-tb0IW3NF.js.map → ReposTab-Cqm82_yn.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{SessionsTab-C-IoLw5h.js → SessionsTab-A8xTAdET.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{SessionsTab-C-IoLw5h.js.map → SessionsTab-A8xTAdET.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{SetupTab-BvmFi8gf.js → SetupTab-CqaSgaZl.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{SetupTab-BvmFi8gf.js.map → SetupTab-CqaSgaZl.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-DBH-cOlA.js → TimeSavedCard-C_f3q8GR.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-DBH-cOlA.js.map → TimeSavedCard-C_f3q8GR.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TurnsTab-DBMgmvnX.js → TurnsTab-BEsafACo.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TurnsTab-DBMgmvnX.js.map → TurnsTab-BEsafACo.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/VectorCortexTab-DD1CMK80.js +2 -0
- package/extensions/dashboard-client/dist/assets/VectorCortexTab-DD1CMK80.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{WikiTab-1LcQrXk3.js → WikiTab-Bf01M2_Z.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{WikiTab-1LcQrXk3.js.map → WikiTab-Bf01M2_Z.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{button-DtEsZTUE.js → button-BEJPTk_E.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{button-DtEsZTUE.js.map → button-BEJPTk_E.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{card-DcfTmw-4.js → card-DK1V382s.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{card-DcfTmw-4.js.map → card-DK1V382s.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-BsIxUDyw.js → generateCategoricalChart-CRtaPphL.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-BsIxUDyw.js.map → generateCategoricalChart-CRtaPphL.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{index-B93cewiX.js → index-CB6BslE4.js} +34 -29
- package/extensions/dashboard-client/dist/assets/index-CB6BslE4.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/index-CfvziyBi.css +1 -0
- package/extensions/dashboard-client/dist/assets/{switch-Bowd7jCD.js → switch-C4FV6B9k.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{switch-Bowd7jCD.js.map → switch-C4FV6B9k.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{toggle-Bo7kJZ4u.js → toggle-GBmeENMd.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{toggle-Bo7kJZ4u.js.map → toggle-GBmeENMd.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{useSSE-zNhb0c3u.js → useSSE-BOKZGSn1.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{useSSE-zNhb0c3u.js.map → useSSE-BOKZGSn1.js.map} +1 -1
- package/extensions/dashboard-client/dist/index.html +2 -2
- package/extensions/dashboard-client/src/App.tsx +2 -0
- package/extensions/dashboard-client/src/api/vector-cortex.ts +54 -0
- package/extensions/dashboard-client/src/tabs/VectorCortexTab.tsx +272 -0
- package/extensions/dashboard-client/src/tabs/registry.ts +4 -1
- package/extensions/dashboard-client/src/types/vector-cortex.ts +84 -0
- package/extensions/dashboard-client/tsconfig.json +1 -1
- package/extensions/dashboard-server/api-contracts/endpoints/registry-ext.ts +67 -0
- package/extensions/dashboard-server/api-contracts/endpoints/registry.ts +4 -21
- package/extensions/dashboard-server/api-contracts/index.ts +3 -0
- package/extensions/dashboard-server/api-contracts/vector-cortex.ts +136 -0
- package/extensions/dashboard-server/routes-rag-settings-helpers.ts +41 -0
- package/extensions/dashboard-server/routes-vector-cortex.ts +332 -0
- package/extensions/dashboard-server/routes.ts +6 -0
- package/extensions/dashboard-server/server.ts +8 -0
- package/extensions/mega-events/context-handler.ts +16 -0
- package/extensions/mega-runtime/vector-cortex-ledger.ts +165 -0
- package/extensions/mega-runtime/vector-cortex-safety.ts +181 -0
- package/package.json +2 -1
- package/src/config/vector-cortex.ts +98 -0
- package/src/config.ts +28 -0
- package/src/dedup/l1-lsh-v2.ts +78 -0
- package/src/dedup/l1-minhash-v2.ts +221 -0
- package/src/dedup/normalize.ts +1 -1
- package/src/store/sqlite/game-scores.ts +2 -1
- package/src/vector-cortex/conformance/emit.ts +56 -0
- package/src/vector-cortex/conformance/manifest.ts +298 -0
- package/src/vector-cortex/conformance/runner.ts +165 -0
- package/src/vector-cortex/conformance/triadB-reader.ts +110 -0
- package/src/vector-cortex/eval/annotations.ts +70 -0
- package/src/vector-cortex/eval/metrics.ts +113 -0
- package/src/vector-cortex/eval/observer.ts +86 -0
- package/src/vector-cortex/eval/persist.ts +73 -0
- package/src/vector-cortex/eval/reader.ts +45 -0
- package/src/vector-cortex/eval/types.ts +89 -0
- package/src/vector-cortex/ledger/adapter.ts +71 -0
- package/src/vector-cortex/ledger/compat-journal.ts +234 -0
- package/src/vector-cortex/ledger/emit.ts +86 -0
- package/src/vector-cortex/ledger/event-codec.ts +79 -0
- package/src/vector-cortex/ledger/event-codecB.ts +87 -0
- package/src/vector-cortex/ledger/sqlite.ts +265 -0
- package/src/vector-cortex/ledger/store.ts +380 -0
- package/src/vector-cortex/ledger/types.ts +141 -0
- package/src/vector-cortex/ledger/validator.ts +124 -0
- package/src/vector-cortex/migrations/effective-cut-v2.ts +149 -0
- package/src/vector-cortex/migrations/minhash-v2.ts +263 -0
- package/src/vector-cortex/migrations/occurrence-v2.ts +192 -0
- package/src/vector-cortex/replay/cut.ts +169 -0
- package/src/vector-cortex/replay/emit.ts +89 -0
- package/src/vector-cortex/replay/replay.ts +308 -0
- package/src/vector-cortex/replay/replayB.ts +97 -0
- package/src/vector-cortex/replay/types.ts +145 -0
- package/src/vector-cortex/resilience/breaker-core.ts +444 -0
- package/src/vector-cortex/resilience/breaker.ts +22 -0
- package/src/vector-cortex/resilience/emit.ts +99 -0
- package/src/vector-cortex/resilience/spool-core.ts +411 -0
- package/src/vector-cortex/resilience/spool.ts +22 -0
- package/src/vector-cortex/resilience/types.ts +158 -0
- package/extensions/dashboard-client/dist/assets/index-B93cewiX.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/index-C_tCbOI0.css +0 -1
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vector-cortex/ledger/emit.ts — VC1A ledger emit seam.
|
|
3
|
+
*
|
|
4
|
+
* The single structured-event surface for the EventV2 ledger path. It mirrors
|
|
5
|
+
* the VC0B replay emit seam (`src/vector-cortex/replay/emit.ts`): the same
|
|
6
|
+
* non-fatal, structured-JSON `ts`+`event` contract (`src/log.ts` LogEntry),
|
|
7
|
+
* delivered through an injected emit callback so it stays pi-agnostic,
|
|
8
|
+
* deterministically testable, and network-free (PREVENT-PI-004).
|
|
9
|
+
*
|
|
10
|
+
* Carries only the two EventV2 events:
|
|
11
|
+
* - vector_cortex_event_decoded (a V2 occurrence was byte-decoded)
|
|
12
|
+
* - vector_cortex_event_validation_failed (a V2 conformity check failed)
|
|
13
|
+
*
|
|
14
|
+
* Flag gating: this is VC1A's single real consumer of `VC1A_ENABLED()`. When the
|
|
15
|
+
* flag is OFF the reporter emits NOTHING (zero observability writes) — the
|
|
16
|
+
* VC1A mode-C byte-identical behavior — while the codec/validator still operate.
|
|
17
|
+
* The flag is read per-call so a live toggle takes effect immediately.
|
|
18
|
+
*/
|
|
19
|
+
import { VC1A_ENABLED } from "../../config/vector-cortex.js";
|
|
20
|
+
function safe(fn) {
|
|
21
|
+
return (event, fields) => {
|
|
22
|
+
try {
|
|
23
|
+
fn(event, fields);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
/* non-fatal observability — never break the agent loop */
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Build a typed reporter over an injected emit callback. Emission is gated on
|
|
32
|
+
* `VC1A_ENABLED()` — flag OFF means zero ledger observability writes (mode-C
|
|
33
|
+
* parity). `emit` is optional: absent (or flag off) degrades to a no-op.
|
|
34
|
+
*/
|
|
35
|
+
export function createLedgerReporter(emit) {
|
|
36
|
+
const fire = safe((event, fields) => {
|
|
37
|
+
if (VC1A_ENABLED())
|
|
38
|
+
emit?.(event, fields);
|
|
39
|
+
});
|
|
40
|
+
return {
|
|
41
|
+
eventDecoded(fields) {
|
|
42
|
+
fire("vector_cortex_event_decoded", fields);
|
|
43
|
+
},
|
|
44
|
+
validationFailed(fields) {
|
|
45
|
+
fire("vector_cortex_event_validation_failed", fields);
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/** Export the event names for consumers that match on them. */
|
|
50
|
+
export const LEDGER_EVENTS = [
|
|
51
|
+
"vector_cortex_event_decoded",
|
|
52
|
+
"vector_cortex_event_validation_failed",
|
|
53
|
+
];
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vector-cortex/ledger/event-codec.ts — EventV2 byte-authority codec, Mode A
|
|
3
|
+
* (VC1A, TRIAD_RESILIENCE).
|
|
4
|
+
*
|
|
5
|
+
* A retains `originalBytes` + a SHA-256 `bytesDigest` and classifies strict
|
|
6
|
+
* UTF-8 (success) vs invalid-byte content WITHOUT replacement decoding:
|
|
7
|
+
* `TextDecoder(..., {fatal:true})` either yields text or throws — invalid input
|
|
8
|
+
* is represented only as `{valid:false, base64}` and is NEVER lossy-replaced.
|
|
9
|
+
* `canonicalNfc` is computed ONLY as a derived field for valid UTF-8 and is
|
|
10
|
+
* NEVER used for identity, digest, or byte reconstruction — `decode` returns the
|
|
11
|
+
* authoritative `originalBytes` exactly.
|
|
12
|
+
*
|
|
13
|
+
* Mode B (`./event-codecB.ts`) is a genuinely independent implementation of the
|
|
14
|
+
* same byte rule; A and B share no subroutine and must agree byte-for-byte.
|
|
15
|
+
*
|
|
16
|
+
* Pure/deterministic — no console, no network, no side effects (PREVENT-PI-004).
|
|
17
|
+
*/
|
|
18
|
+
import nodeCrypto from "node:crypto";
|
|
19
|
+
/** Mode-A internal SHA-256 helper (NOT shared with mode B). */
|
|
20
|
+
function digestSha256A(bytes) {
|
|
21
|
+
const hex = nodeCrypto.createHash("sha256").update(bytes).digest("hex");
|
|
22
|
+
return `sha256:${hex}`;
|
|
23
|
+
}
|
|
24
|
+
/** Mode-A internal base64 (NOT shared with mode B). */
|
|
25
|
+
function base64EncodeA(bytes) {
|
|
26
|
+
return Buffer.from(bytes).toString("base64");
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Strict UTF-8 classification with FATAL decoding (never replaces invalid bytes
|
|
30
|
+
* with U+FFFD). Returns the decoded text, or `{valid:false, base64}`.
|
|
31
|
+
*/
|
|
32
|
+
export function classifyUtf8(bytes) {
|
|
33
|
+
try {
|
|
34
|
+
const text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
35
|
+
return { valid: true, text };
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return { valid: false, base64: base64EncodeA(bytes) };
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/** Build the Mode-A byte-authority codec. */
|
|
42
|
+
export function createEventCodec() {
|
|
43
|
+
return {
|
|
44
|
+
encode(input) {
|
|
45
|
+
const bytes = input.bytes;
|
|
46
|
+
const bytesDigest = digestSha256A(bytes);
|
|
47
|
+
const utf8 = classifyUtf8(bytes);
|
|
48
|
+
const event = {
|
|
49
|
+
schema: "event-v2",
|
|
50
|
+
sessionId: input.sessionId,
|
|
51
|
+
seq: input.seq,
|
|
52
|
+
eventId: input.eventId,
|
|
53
|
+
role: input.role,
|
|
54
|
+
kind: input.kind,
|
|
55
|
+
originalBytes: bytes,
|
|
56
|
+
bytesDigest,
|
|
57
|
+
utf8,
|
|
58
|
+
occurredAtMs: input.occurredAtMs,
|
|
59
|
+
};
|
|
60
|
+
if (input.toolCallId !== undefined)
|
|
61
|
+
event.toolCallId = input.toolCallId;
|
|
62
|
+
// canonicalNfc is a DERIVED search key — valid UTF-8 only, never identity.
|
|
63
|
+
if (utf8.valid)
|
|
64
|
+
event.canonicalNfc = utf8.text.normalize("NFC");
|
|
65
|
+
return event;
|
|
66
|
+
},
|
|
67
|
+
decode(event) {
|
|
68
|
+
return event.originalBytes;
|
|
69
|
+
},
|
|
70
|
+
classifyUtf8,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vector-cortex/ledger/event-codecB.ts — EventV2 byte-authority codec, Mode B
|
|
3
|
+
* (VC1A, TRIAD_RESILIENCE).
|
|
4
|
+
*
|
|
5
|
+
* B is a GENUINELY independent raw-byte record implementation of the SAME byte
|
|
6
|
+
* rule as Mode A (`./event-codec.ts`). It shares NO subroutine with A: its own
|
|
7
|
+
* SHA-256 helper, its own base64 helper, its own strict-UTF-8 classifier, and
|
|
8
|
+
* its own raw record — the lesson from VC0B-I09 (a mode-B tested against code
|
|
9
|
+
* identical to A is a tautology). Because the digest of given bytes is a pure
|
|
10
|
+
* function, two independent implementations MUST agree byte-for-byte; the
|
|
11
|
+
* acceptance test asserts `bytesDigest` parity across the fixture corpus.
|
|
12
|
+
*
|
|
13
|
+
* B retains `originalBytes` + `bytesDigest` and classifies strict UTF-8 with no
|
|
14
|
+
* lossy replacement; `canonicalNfc` is derived for valid UTF-8 but never used
|
|
15
|
+
* for identity, digest, or reconstruction.
|
|
16
|
+
*
|
|
17
|
+
* Pure/deterministic — no console, no network, no side effects (PREVENT-PI-004).
|
|
18
|
+
*/
|
|
19
|
+
import nodeCrypto from "node:crypto";
|
|
20
|
+
/** Mode-B internal SHA-256 helper (independent of A's — no shared subroutine). */
|
|
21
|
+
function digestSha256B(bytes) {
|
|
22
|
+
const d = nodeCrypto.createHash("sha256");
|
|
23
|
+
d.update(bytes);
|
|
24
|
+
const hex = d.digest("hex");
|
|
25
|
+
return `sha256:${hex}`;
|
|
26
|
+
}
|
|
27
|
+
/** Mode-B internal base64 (independent of A's). */
|
|
28
|
+
function base64EncodeB(bytes) {
|
|
29
|
+
let out = "";
|
|
30
|
+
const buf = Buffer.from(bytes);
|
|
31
|
+
for (let i = 0; i < buf.length; i += 3) {
|
|
32
|
+
const b0 = buf[i];
|
|
33
|
+
const b1 = i + 1 < buf.length ? buf[i + 1] : -1;
|
|
34
|
+
const b2 = i + 2 < buf.length ? buf[i + 2] : -1;
|
|
35
|
+
out += CHARS[(b0 >> 2) & 63];
|
|
36
|
+
out += CHARS[((b0 & 3) << 4) | (b1 < 0 ? 0 : (b1 >> 4) & 15)];
|
|
37
|
+
if (b1 >= 0)
|
|
38
|
+
out += CHARS[((b1 & 15) << 2) | (b2 < 0 ? 0 : (b2 >> 6) & 3)];
|
|
39
|
+
if (b2 >= 0)
|
|
40
|
+
out += CHARS[b2 & 63];
|
|
41
|
+
}
|
|
42
|
+
// Padding (independent reconstruction of standard base64).
|
|
43
|
+
const pad = (3 - (buf.length % 3)) % 3;
|
|
44
|
+
for (let i = 0; i < pad; i++)
|
|
45
|
+
out += "=";
|
|
46
|
+
return out;
|
|
47
|
+
}
|
|
48
|
+
const CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
49
|
+
/** Mode-B strict UTF-8 classifier (fatal decode; no replacement). */
|
|
50
|
+
function classifyUtf8B(bytes) {
|
|
51
|
+
try {
|
|
52
|
+
const text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
53
|
+
return { valid: true, text };
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return { valid: false, base64: base64EncodeB(bytes) };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/** Independently digest + classify raw bytes into a B record. */
|
|
60
|
+
export function recordRawBytesB(bytes) {
|
|
61
|
+
const bytesDigest = digestSha256B(bytes);
|
|
62
|
+
const utf8 = classifyUtf8B(bytes);
|
|
63
|
+
const record = { originalBytes: bytes, bytesDigest, utf8 };
|
|
64
|
+
if (utf8.valid) {
|
|
65
|
+
return { originalBytes: bytes, bytesDigest, utf8, canonicalNfc: utf8.text.normalize("NFC") };
|
|
66
|
+
}
|
|
67
|
+
return record;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Independent digest check: recompute the SHA-256 over an EventV2's
|
|
71
|
+
* `originalBytes` via B's OWN algorithm and compare to the stored digest.
|
|
72
|
+
* Returns the recomputed digest string (parity-checked by the caller).
|
|
73
|
+
*/
|
|
74
|
+
export function digestCheckB(event) {
|
|
75
|
+
return digestSha256B(event.originalBytes);
|
|
76
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vector-cortex/ledger/sqlite.ts — occurrence-v2 SQLite store, Mode A (VC1B).
|
|
3
|
+
*
|
|
4
|
+
* A self-contained, isolated SQLite store (`node:sqlite` `DatabaseSync`) over its
|
|
5
|
+
* OWN database file — NOT the host `sqlite.db` — so the neutral v2 ledger is a
|
|
6
|
+
* true independent authority, not a host table. Holds the canonical occurrence
|
|
7
|
+
* rows plus per-session monotonic seq and the durable contiguous high-water.
|
|
8
|
+
*
|
|
9
|
+
* The defect M2 fixes: the host `raw_transcript` table keys on
|
|
10
|
+
* `(content_hash, session_id)`, so repeated equal bytes at a later turn are
|
|
11
|
+
* silently dropped. Here the ledger keys occurrences by `(event_id, digest)`
|
|
12
|
+
* ONLY — equal bytes at a distinct seq/event-id are preserved as separate
|
|
13
|
+
* occurrences (M2-DUP-001).
|
|
14
|
+
*
|
|
15
|
+
* Append enforces, per occurrence: (1) strictly monotonic per-session seq
|
|
16
|
+
* (EVT_SEQ_REGRESSION otherwise); (2) a tool RESULT names exactly one earlier
|
|
17
|
+
* call (EVT_TOOL_CALL_MISSING otherwise); (3) exact `(event_id,digest)`
|
|
18
|
+
* re-appends are acknowledged idempotently, never duplicated.
|
|
19
|
+
*
|
|
20
|
+
* Mode A of the VC1B triad: transactional SQLite append. Mode B (`./spool.ts`)
|
|
21
|
+
* is an independent fsync spool; mode C leaves the host transcript unchanged.
|
|
22
|
+
*
|
|
23
|
+
* PREVENT-002: every query is parameterized. PREVENT-011: no `any`.
|
|
24
|
+
* PREVENT-PI-004: local filesystem only, no network. No console.log.
|
|
25
|
+
*/
|
|
26
|
+
import { DatabaseSync } from "node:sqlite";
|
|
27
|
+
import { existsSync, mkdirSync } from "node:fs";
|
|
28
|
+
import { dirname } from "node:path";
|
|
29
|
+
import { createHash } from "node:crypto";
|
|
30
|
+
const SCHEMA = `
|
|
31
|
+
CREATE TABLE IF NOT EXISTS occurrence_v2 (
|
|
32
|
+
session TEXT NOT NULL,
|
|
33
|
+
seq INTEGER NOT NULL,
|
|
34
|
+
event_id TEXT NOT NULL,
|
|
35
|
+
digest TEXT NOT NULL,
|
|
36
|
+
kind TEXT NOT NULL,
|
|
37
|
+
tool_call_id TEXT,
|
|
38
|
+
source_bytes BLOB,
|
|
39
|
+
PRIMARY KEY (session, seq),
|
|
40
|
+
UNIQUE (session, event_id, digest)
|
|
41
|
+
) STRICT;
|
|
42
|
+
|
|
43
|
+
CREATE TABLE IF NOT EXISTS ledger_high_water (
|
|
44
|
+
session TEXT PRIMARY KEY,
|
|
45
|
+
seq INTEGER NOT NULL
|
|
46
|
+
) STRICT;
|
|
47
|
+
`;
|
|
48
|
+
/** Default digest for a row when the caller omits one (sha256 over bytes). */
|
|
49
|
+
export function ledgerDigest(bytes) {
|
|
50
|
+
const hex = createHash("sha256").update(bytes).digest("hex");
|
|
51
|
+
return `sha256:${hex}`;
|
|
52
|
+
}
|
|
53
|
+
/** Open (or reuse) the isolated occurrence-v2 ledger DB handle. */
|
|
54
|
+
export function openOccurrenceStore(dbPath) {
|
|
55
|
+
const dir = dirname(dbPath);
|
|
56
|
+
if (!existsSync(dir))
|
|
57
|
+
mkdirSync(dir, { recursive: true });
|
|
58
|
+
const db = new DatabaseSync(dbPath);
|
|
59
|
+
db.exec("PRAGMA journal_mode = WAL");
|
|
60
|
+
db.exec("PRAGMA foreign_keys = ON");
|
|
61
|
+
db.exec(SCHEMA);
|
|
62
|
+
return db;
|
|
63
|
+
}
|
|
64
|
+
function rowToOccurrence(row) {
|
|
65
|
+
return {
|
|
66
|
+
session: row.session,
|
|
67
|
+
seq: BigInt(row.seq),
|
|
68
|
+
eventId: row.event_id,
|
|
69
|
+
digest: row.digest,
|
|
70
|
+
kind: row.kind,
|
|
71
|
+
toolCallId: row.tool_call_id ?? undefined,
|
|
72
|
+
sourceBytes: row.source_bytes ? Buffer.from(row.source_bytes) : new Uint8Array(0),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/** Current durable contiguous high-water for a session (0 when none). */
|
|
76
|
+
export function ledgerHighWater(db, session) {
|
|
77
|
+
const row = db
|
|
78
|
+
.prepare(`SELECT seq FROM ledger_high_water WHERE session = @session`)
|
|
79
|
+
.get({ "@session": session });
|
|
80
|
+
return row ? BigInt(row.seq) : 0n;
|
|
81
|
+
}
|
|
82
|
+
/** Read all accepted occurrences for a session in ascending (seq,eventId). */
|
|
83
|
+
export function readSessionOccurrences(db, session) {
|
|
84
|
+
const rows = db
|
|
85
|
+
.prepare(`SELECT session, seq, event_id, digest, kind, tool_call_id, source_bytes
|
|
86
|
+
FROM occurrence_v2 WHERE session = @session
|
|
87
|
+
ORDER BY seq ASC, event_id ASC`)
|
|
88
|
+
.all({ "@session": session });
|
|
89
|
+
return rows.map(rowToOccurrence);
|
|
90
|
+
}
|
|
91
|
+
/** Occurrences at or above `fromSeq` (inclusive), ascending. */
|
|
92
|
+
export function readFromSeq(db, session, fromSeq) {
|
|
93
|
+
const rows = db
|
|
94
|
+
.prepare(`SELECT session, seq, event_id, digest, kind, tool_call_id, source_bytes
|
|
95
|
+
FROM occurrence_v2 WHERE session = @session AND seq >= @from
|
|
96
|
+
ORDER BY seq ASC, event_id ASC`)
|
|
97
|
+
.all({ "@session": session, "@from": Number(fromSeq) });
|
|
98
|
+
return rows.map(rowToOccurrence);
|
|
99
|
+
}
|
|
100
|
+
/** Count accepted occurrences for a session. */
|
|
101
|
+
export function countOccurrences(db, session) {
|
|
102
|
+
const row = db
|
|
103
|
+
.prepare(`SELECT COUNT(*) AS cnt FROM occurrence_v2 WHERE session = @session`)
|
|
104
|
+
.get({ "@session": session });
|
|
105
|
+
return row.cnt;
|
|
106
|
+
}
|
|
107
|
+
/** Whether a `(event_id, digest)` pair is already accepted in a session. */
|
|
108
|
+
export function hasOccurrence(db, session, eventId, digest) {
|
|
109
|
+
const row = db
|
|
110
|
+
.prepare(`SELECT 1 AS hit FROM occurrence_v2
|
|
111
|
+
WHERE session = @session AND event_id = @event_id AND digest = @digest LIMIT 1`)
|
|
112
|
+
.get({ "@session": session, "@event_id": eventId, "@digest": digest });
|
|
113
|
+
return row !== undefined;
|
|
114
|
+
}
|
|
115
|
+
/** The stored occurrence matching a `(event_id, digest)` pair, or undefined. */
|
|
116
|
+
function findOccurrence(db, session, eventId, digest) {
|
|
117
|
+
const row = db
|
|
118
|
+
.prepare(`SELECT session, seq, event_id, digest, kind, tool_call_id, source_bytes
|
|
119
|
+
FROM occurrence_v2
|
|
120
|
+
WHERE session = @session AND event_id = @event_id AND digest = @digest LIMIT 1`)
|
|
121
|
+
.get({ "@session": session, "@event_id": eventId, "@digest": digest });
|
|
122
|
+
return row ? rowToOccurrence(row) : undefined;
|
|
123
|
+
}
|
|
124
|
+
/** Whether a prior call row with the given eventId exists in the session. */
|
|
125
|
+
function hasCallRef(db, session, eventId) {
|
|
126
|
+
const row = db
|
|
127
|
+
.prepare(`SELECT 1 AS hit FROM occurrence_v2
|
|
128
|
+
WHERE session = @session AND event_id = @event_id LIMIT 1`)
|
|
129
|
+
.get({ "@session": session, "@event_id": eventId });
|
|
130
|
+
return row !== undefined;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Append one occurrence within a transaction. Enforces monotonic seq, tool-call
|
|
134
|
+
* reference completeness, and `(event_id,digest)` idempotent ack. Returns the
|
|
135
|
+
* accepted row or a deterministic failure code.
|
|
136
|
+
*/
|
|
137
|
+
export function appendOccurrence(db, input) {
|
|
138
|
+
const digest = input.digest ?? ledgerDigest(input.sourceBytes);
|
|
139
|
+
const occurrence = {
|
|
140
|
+
session: input.session,
|
|
141
|
+
seq: input.seq,
|
|
142
|
+
eventId: input.eventId,
|
|
143
|
+
digest,
|
|
144
|
+
kind: input.kind,
|
|
145
|
+
toolCallId: input.toolCallId,
|
|
146
|
+
sourceBytes: input.sourceBytes,
|
|
147
|
+
};
|
|
148
|
+
// (3) exact (event_id,digest) re-append is acknowledged idempotently, WITHOUT
|
|
149
|
+
// inserting a new row. Return the EXISTING stored occurrence (its real seq) —
|
|
150
|
+
// not the caller's input — so a journal record written from this result carries
|
|
151
|
+
// the existing seq, which always corresponds to a real occurrence_v2 row (Q01:
|
|
152
|
+
// never a phantom journal row at a seq with no backing occurrence).
|
|
153
|
+
const existing = findOccurrence(db, input.session, input.eventId, digest);
|
|
154
|
+
if (existing) {
|
|
155
|
+
return { ok: true, occurrence: existing };
|
|
156
|
+
}
|
|
157
|
+
// (1) monotonic seq: must be exactly the next expected seq (highWater+1).
|
|
158
|
+
const hw = ledgerHighWater(db, input.session);
|
|
159
|
+
if (input.seq !== hw + 1n) {
|
|
160
|
+
return { ok: false, code: "EVT_SEQ_REGRESSION", rejected: occurrence };
|
|
161
|
+
}
|
|
162
|
+
// (2) a tool RESULT names exactly one earlier call in the same session.
|
|
163
|
+
if (input.toolCallId !== undefined && input.toolCallId !== "") {
|
|
164
|
+
if (!hasCallRef(db, input.session, input.toolCallId)) {
|
|
165
|
+
return { ok: false, code: "EVT_TOOL_CALL_MISSING", rejected: occurrence };
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
// Accept: insert the row and advance the per-session high-water atomically.
|
|
169
|
+
db.exec("SAVEPOINT mc_ledger");
|
|
170
|
+
try {
|
|
171
|
+
db.prepare(`INSERT INTO occurrence_v2 (session, seq, event_id, digest, kind, tool_call_id, source_bytes)
|
|
172
|
+
VALUES (@session, @seq, @event_id, @digest, @kind, @tool_call_id, @source_bytes)`).run({
|
|
173
|
+
"@session": input.session,
|
|
174
|
+
"@seq": Number(input.seq),
|
|
175
|
+
"@event_id": input.eventId,
|
|
176
|
+
"@digest": digest,
|
|
177
|
+
"@kind": input.kind,
|
|
178
|
+
"@tool_call_id": input.toolCallId ?? null,
|
|
179
|
+
"@source_bytes": Buffer.from(input.sourceBytes),
|
|
180
|
+
});
|
|
181
|
+
db.prepare(`INSERT INTO ledger_high_water (session, seq) VALUES (@session, @seq)
|
|
182
|
+
ON CONFLICT(session) DO UPDATE SET seq = excluded.seq`).run({ "@session": input.session, "@seq": Number(input.seq) });
|
|
183
|
+
db.exec("RELEASE mc_ledger");
|
|
184
|
+
}
|
|
185
|
+
catch (e) {
|
|
186
|
+
db.exec("ROLLBACK TO mc_ledger");
|
|
187
|
+
db.exec("RELEASE mc_ledger");
|
|
188
|
+
throw e;
|
|
189
|
+
}
|
|
190
|
+
return { ok: true, occurrence };
|
|
191
|
+
}
|
|
192
|
+
/** Append a batch; each occurrence reports its own result. */
|
|
193
|
+
export function appendOccurrenceBatch(db, inputs) {
|
|
194
|
+
return inputs.map((input) => appendOccurrence(db, input));
|
|
195
|
+
}
|
|
196
|
+
/** Diagnostic: total accepted occurrences across all sessions. */
|
|
197
|
+
export function countAllOccurrences(db) {
|
|
198
|
+
const row = db.prepare(`SELECT COUNT(*) AS cnt FROM occurrence_v2`).get();
|
|
199
|
+
return row.cnt;
|
|
200
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vector-cortex/ledger/store.ts — VC1B capability-separated ledger contracts.
|
|
3
|
+
*
|
|
4
|
+
* Owns `LedgerReader` / `LedgerWriter` / `LedgerAdmin` and `CompatJournalV1`.
|
|
5
|
+
* The ledger is append-only, seq is monotonic per session, a tool RESULT names
|
|
6
|
+
* exactly one earlier call in the same session, and duplicates are identified by
|
|
7
|
+
* `(eventId, digest)` only (two occurrences may share bytes at distinct seq).
|
|
8
|
+
*
|
|
9
|
+
* Capability gating mirrors the host `asReader/asWriter/asAdmin` pattern: a
|
|
10
|
+
* consumer receives ONLY what it needs. Dashboard GET receives `LedgerReader`;
|
|
11
|
+
* only ingestion receives `LedgerWriter`; only the migration coordinator
|
|
12
|
+
* receives `LedgerAdmin`. This keeps the neutral byte-authority ledger closed to
|
|
13
|
+
* anything but the sanctioned channels (CONTRACTS §Store and migration
|
|
14
|
+
* contracts).
|
|
15
|
+
*
|
|
16
|
+
* Pure type/schema definitions + small pure predicates. No storage, no console,
|
|
17
|
+
* no network, no side effects (PREVENT-PI-004 / PREVENT-011).
|
|
18
|
+
*/
|
|
19
|
+
import { VC1B_ENABLED } from "../../config/vector-cortex.js";
|
|
20
|
+
import { openOccurrenceStore, appendOccurrence, ledgerHighWater, readSessionOccurrences, readFromSeq, countOccurrences, hasOccurrence, } from "./sqlite.js";
|
|
21
|
+
import { initCompatJournal, createCompatJournal, journalPhase, } from "./compat-journal.js";
|
|
22
|
+
import { m2Copy, m2Validate, m2Switch, } from "../migrations/occurrence-v2.js";
|
|
23
|
+
/** The token that gates closures to a single capability (PREVENT-011-free). */
|
|
24
|
+
const _capability = Symbol("mc-ledger-capability");
|
|
25
|
+
/**
|
|
26
|
+
* Create the capability-separated occurrence-v2 ledger over its OWN isolated
|
|
27
|
+
* SQLite DB. `emit` is optional; occurrence-appended and compat-switch-committed
|
|
28
|
+
* events are only emitted when VC1B_ENABLED() and an emitter is supplied (mode-C
|
|
29
|
+
* parity: flag OFF / no emitter => zero observability writes).
|
|
30
|
+
*
|
|
31
|
+
* Normalization: `stateDir` gives the standard daemon location
|
|
32
|
+
* `<stateDir>/vector-cortex/occurrence-v2.db`; a bare `dbPath` overrides it
|
|
33
|
+
* (tests/rehearsal isolate the ledger). The compat journal shares the same DB
|
|
34
|
+
* so a v2 append + its journal record commit atomically.
|
|
35
|
+
*/
|
|
36
|
+
export function createLedgerStore(opts, emit) {
|
|
37
|
+
const dbPath = "dbPath" in opts
|
|
38
|
+
? opts.dbPath
|
|
39
|
+
: `${opts.stateDir}/vector-cortex/occurrence-v2.db`;
|
|
40
|
+
const db = openOccurrenceStore(dbPath);
|
|
41
|
+
initCompatJournal(db);
|
|
42
|
+
const compat = createCompatJournal(db);
|
|
43
|
+
const fire = (event, fields) => {
|
|
44
|
+
if (!VC1B_ENABLED())
|
|
45
|
+
return;
|
|
46
|
+
try {
|
|
47
|
+
emit?.(event, fields);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
/* non-fatal observability — never break the agent loop */
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
const asReader = () => ({
|
|
54
|
+
kind: "LedgerReader",
|
|
55
|
+
[_capability]: "reader",
|
|
56
|
+
highWater: (session) => ledgerHighWater(db, session),
|
|
57
|
+
readSession: (session) => readSessionOccurrences(db, session),
|
|
58
|
+
readFrom: (session, fromSeq) => readFromSeq(db, session, fromSeq),
|
|
59
|
+
count: (session) => countOccurrences(db, session),
|
|
60
|
+
hasOccurrence: (session, eventId, digest) => hasOccurrence(db, session, eventId, digest),
|
|
61
|
+
});
|
|
62
|
+
const asWriter = () => ({
|
|
63
|
+
kind: "LedgerWriter",
|
|
64
|
+
[_capability]: "writer",
|
|
65
|
+
append(input) {
|
|
66
|
+
// The occurrence insert and its compat-journal record commit atomically
|
|
67
|
+
// inside one nested savepoint (CONTRACTS §Store: atomically appends).
|
|
68
|
+
db.exec("SAVEPOINT mc_ledger_app");
|
|
69
|
+
try {
|
|
70
|
+
const result = appendOccurrence(db, input);
|
|
71
|
+
if (result.ok) {
|
|
72
|
+
compat.record({
|
|
73
|
+
occurrence: result.occurrence,
|
|
74
|
+
legacyProjection: legacyProjectionOf(result.occurrence),
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
if (result.ok) {
|
|
78
|
+
db.exec("RELEASE mc_ledger_app");
|
|
79
|
+
fire("vector_cortex_occurrence_appended", {
|
|
80
|
+
session: result.occurrence.session,
|
|
81
|
+
seq: result.occurrence.seq.toString(),
|
|
82
|
+
eventId: result.occurrence.eventId,
|
|
83
|
+
digest: result.occurrence.digest,
|
|
84
|
+
kind: result.occurrence.kind,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
db.exec("ROLLBACK TO mc_ledger_app");
|
|
89
|
+
db.exec("RELEASE mc_ledger_app");
|
|
90
|
+
}
|
|
91
|
+
return result;
|
|
92
|
+
}
|
|
93
|
+
catch (e) {
|
|
94
|
+
db.exec("ROLLBACK TO mc_ledger_app");
|
|
95
|
+
db.exec("RELEASE mc_ledger_app");
|
|
96
|
+
throw e;
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
appendBatch(inputs) {
|
|
100
|
+
const out = [];
|
|
101
|
+
for (const input of inputs)
|
|
102
|
+
out.push(this.append(input));
|
|
103
|
+
return out;
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
const asAdmin = () => ({
|
|
107
|
+
kind: "LedgerAdmin",
|
|
108
|
+
[_capability]: "admin",
|
|
109
|
+
compat,
|
|
110
|
+
migrateOccurrenceV2() {
|
|
111
|
+
// copy => validate => switch; resumable & idempotent. The M2 host wraps
|
|
112
|
+
// the live DB + journal so every phase transition advances the SAME
|
|
113
|
+
// singleton journal state machine (prepared→copied→validated→switched).
|
|
114
|
+
const unrepresentable = compat.prepare().length;
|
|
115
|
+
const mig = migrateHost(db, compat);
|
|
116
|
+
m2Copy(mig);
|
|
117
|
+
const v = m2Validate(mig);
|
|
118
|
+
if (v.ok) {
|
|
119
|
+
m2Switch(mig);
|
|
120
|
+
fire("vector_cortex_compat_switch_committed", {
|
|
121
|
+
session: "all",
|
|
122
|
+
unrepresentable,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
return { ok: v.ok, codes: v.codes };
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
return { reader: asReader, writer: asWriter, admin: asAdmin, close: () => db.close() };
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Lossless legacy projection for a v2 occurrence: its identity envelope plus
|
|
132
|
+
* base64 source bytes. Returns null (→ journal `unrepresentable` marker) when the
|
|
133
|
+
* source bytes do not form valid UTF-8, which the legacy pipeline cannot round-trip
|
|
134
|
+
* losslessly — those rows are listed rather than silently coerced (MIG-DOWN-003).
|
|
135
|
+
*/
|
|
136
|
+
function legacyProjectionOf(occ) {
|
|
137
|
+
const source = Buffer.from(occ.sourceBytes);
|
|
138
|
+
try {
|
|
139
|
+
new TextDecoder("utf-8", { fatal: true }).decode(source);
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
return JSON.stringify({
|
|
145
|
+
session: occ.session,
|
|
146
|
+
eventId: occ.eventId,
|
|
147
|
+
kind: occ.kind,
|
|
148
|
+
digest: occ.digest,
|
|
149
|
+
source: source.toString("base64"),
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
/** Build an M2Host over the live ledger DB + journal (for migrateOccurrenceV2). */
|
|
153
|
+
function migrateHost(db, journal) {
|
|
154
|
+
let staged = null;
|
|
155
|
+
return {
|
|
156
|
+
db,
|
|
157
|
+
phase: () => journalPhase(db),
|
|
158
|
+
journalActive: () => journal.active(),
|
|
159
|
+
journalRows: () => journalRowsOf(db),
|
|
160
|
+
writeStagedLegacy: (rows) => {
|
|
161
|
+
staged = rows;
|
|
162
|
+
journal.copied();
|
|
163
|
+
},
|
|
164
|
+
stagedLegacy: () => staged,
|
|
165
|
+
validateStaged: () => journal.validate(),
|
|
166
|
+
switchLegacy: () => journal.switched(),
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
/** Read the journaled occurrences as legacy-export rows. */
|
|
170
|
+
function journalRowsOf(db) {
|
|
171
|
+
const rows = db
|
|
172
|
+
.prepare(`SELECT session, seq, event_id, digest, kind, legacy_projection, unrepresentable
|
|
173
|
+
FROM compat_journal_v1 ORDER BY session ASC, seq ASC`)
|
|
174
|
+
.all();
|
|
175
|
+
return rows.map((r) => ({
|
|
176
|
+
session: r.session,
|
|
177
|
+
seq: BigInt(r.seq),
|
|
178
|
+
eventId: r.event_id,
|
|
179
|
+
digest: r.digest,
|
|
180
|
+
kind: r.kind,
|
|
181
|
+
legacyProjection: r.legacy_projection,
|
|
182
|
+
unrepresentable: r.unrepresentable === 1,
|
|
183
|
+
}));
|
|
184
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vector-cortex/ledger/types.ts — EventV2 / EventCodec byte-authority contract
|
|
3
|
+
* types, validation result codes, and the registered EVT conformance ID range.
|
|
4
|
+
*
|
|
5
|
+
* Owned by VC1A (canonical byte events). Consumes only reviewer-accepted
|
|
6
|
+
* predecessor contracts and [common contracts](../../CONTRACTS.md §EventV2),
|
|
7
|
+
* which are NORMATIVE here. `originalBytes` and its SHA-256 digest are the byte
|
|
8
|
+
* authority; strict UTF-8 classification is never lossy; `canonicalNfc` is a
|
|
9
|
+
* DERIVED comparison/search key only (never identity, digest, or reconstruction).
|
|
10
|
+
*
|
|
11
|
+
* Pure type/schema definitions + a short pure digest/classify predicate runtime
|
|
12
|
+
* (validator codes). No network, no side effects (PREVENT-PI-004 / PREVENT-011).
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Registered EVT conformance ID range (EVT-001..030). The acceptance test reads
|
|
16
|
+
* these rows from the v2 manifest and asserts each returns its manifest bytes or
|
|
17
|
+
* exactly its listed failure code. Mirrors CUT_IDS / M3_IDS in replay/types.ts.
|
|
18
|
+
* EVT-001..015 are owned by VC1A (codec/validator); EVT-016..030 are owned by
|
|
19
|
+
* VC1B (occurrence ledger, see ledger/store.ts).
|
|
20
|
+
*/
|
|
21
|
+
export const EVT_IDS = [
|
|
22
|
+
"EVT-001",
|
|
23
|
+
"EVT-002",
|
|
24
|
+
"EVT-003",
|
|
25
|
+
"EVT-004",
|
|
26
|
+
"EVT-005",
|
|
27
|
+
"EVT-006",
|
|
28
|
+
"EVT-007",
|
|
29
|
+
"EVT-008",
|
|
30
|
+
"EVT-009",
|
|
31
|
+
"EVT-010",
|
|
32
|
+
"EVT-011",
|
|
33
|
+
"EVT-012",
|
|
34
|
+
"EVT-013",
|
|
35
|
+
"EVT-014",
|
|
36
|
+
"EVT-015",
|
|
37
|
+
"EVT-016",
|
|
38
|
+
"EVT-017",
|
|
39
|
+
"EVT-018",
|
|
40
|
+
"EVT-019",
|
|
41
|
+
"EVT-020",
|
|
42
|
+
"EVT-021",
|
|
43
|
+
"EVT-022",
|
|
44
|
+
"EVT-023",
|
|
45
|
+
"EVT-024",
|
|
46
|
+
"EVT-025",
|
|
47
|
+
"EVT-026",
|
|
48
|
+
"EVT-027",
|
|
49
|
+
"EVT-028",
|
|
50
|
+
"EVT-029",
|
|
51
|
+
"EVT-030",
|
|
52
|
+
];
|