pi-mega-compact 0.16.2 → 0.17.0
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 +81 -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 +10 -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 +81 -0
- package/dist/src/config.js +6 -0
- package/dist/src/httpEmbedder.js +136 -1
- 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/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/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/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-CAnWomhw.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{AreaChart-Y_X0Bbjd.js.map → AreaChart-CAnWomhw.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{BarChart-B9XDb-Ob.js → BarChart-lZ2-s71k.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{BarChart-B9XDb-Ob.js.map → BarChart-lZ2-s71k.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{CacheTab-BUGVb1G1.js → CacheTab-BL2Thh66.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{CacheTab-BUGVb1G1.js.map → CacheTab-BL2Thh66.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{EventsTab-dOqLYTsy.js → EventsTab-DEh2Z9fp.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{EventsTab-dOqLYTsy.js.map → EventsTab-DEh2Z9fp.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{HealthTab-c2vezKg1.js → HealthTab-DTwKclxk.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{HealthTab-c2vezKg1.js.map → HealthTab-DTwKclxk.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MaintenanceTab-bQYHIcdx.js → MaintenanceTab-DDfBEdbX.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MaintenanceTab-bQYHIcdx.js.map → MaintenanceTab-DDfBEdbX.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MemoryMapTab-2L8pH6y2.js → MemoryMapTab-Dgfmg-0Q.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MemoryMapTab-2L8pH6y2.js.map → MemoryMapTab-Dgfmg-0Q.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MetricsTab-CzUx1jpo.js → MetricsTab-DOcMm0Tx.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MetricsTab-CzUx1jpo.js.map → MetricsTab-DOcMm0Tx.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{OverviewTab-eCNoaclv.js → OverviewTab-DHT5aoHg.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{OverviewTab-eCNoaclv.js.map → OverviewTab-DHT5aoHg.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{ReposTab-tb0IW3NF.js → ReposTab-CGAmtR_c.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{ReposTab-tb0IW3NF.js.map → ReposTab-CGAmtR_c.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{SessionsTab-C-IoLw5h.js → SessionsTab-BIgx6iKT.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{SessionsTab-C-IoLw5h.js.map → SessionsTab-BIgx6iKT.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{SetupTab-BvmFi8gf.js → SetupTab-ADghKuEI.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{SetupTab-BvmFi8gf.js.map → SetupTab-ADghKuEI.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-DBH-cOlA.js → TimeSavedCard-C33UiiOM.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-DBH-cOlA.js.map → TimeSavedCard-C33UiiOM.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TurnsTab-DBMgmvnX.js → TurnsTab-DdbL9wPN.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TurnsTab-DBMgmvnX.js.map → TurnsTab-DdbL9wPN.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/VectorCortexTab-Cl1pbEdR.js +2 -0
- package/extensions/dashboard-client/dist/assets/VectorCortexTab-Cl1pbEdR.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{WikiTab-1LcQrXk3.js → WikiTab-Ch00OFKs.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{WikiTab-1LcQrXk3.js.map → WikiTab-Ch00OFKs.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{button-DtEsZTUE.js → button-ByeEdo2g.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{button-DtEsZTUE.js.map → button-ByeEdo2g.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{card-DcfTmw-4.js → card-DesoLXvc.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{card-DcfTmw-4.js.map → card-DesoLXvc.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-BsIxUDyw.js → generateCategoricalChart-CCAB7Iz4.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-BsIxUDyw.js.map → generateCategoricalChart-CCAB7Iz4.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{index-B93cewiX.js → index-B6cgyAPr.js} +34 -29
- package/extensions/dashboard-client/dist/assets/index-B6cgyAPr.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-DIZC-oe-.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{switch-Bowd7jCD.js.map → switch-DIZC-oe-.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{toggle-Bo7kJZ4u.js → toggle-CIoWaH2e.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{toggle-Bo7kJZ4u.js.map → toggle-CIoWaH2e.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{useSSE-zNhb0c3u.js → useSSE-Bv4943l1.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{useSSE-zNhb0c3u.js.map → useSSE-Bv4943l1.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-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 +35 -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 +87 -0
- package/src/config.ts +27 -0
- package/src/httpEmbedder.ts +134 -1
- 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/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,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vector-cortex/migrations/occurrence-v2.ts — M2 copy/validate/switch migration
|
|
3
|
+
* for the occurrence-v2 ledger + compat journal (VC1B).
|
|
4
|
+
*
|
|
5
|
+
* M2 converts the neutral v2 ledger into a lossless legacy export so an OLD
|
|
6
|
+
* binary may keep reading after a downgrade. It follows the compat-journal
|
|
7
|
+
* copy/validate/switch contract: COPY the v2 rows + journal records into the
|
|
8
|
+
* staged legacy export, VALIDATE the staged export (seq monotonicity + digest
|
|
9
|
+
* parity + unrepresentable rows listed), and ONLY THEN atomically SWITCH
|
|
10
|
+
* authority to the export. Each phase is exposed separately so a crash between
|
|
11
|
+
* validate and switch retains the OLD authority and a restart resumes
|
|
12
|
+
* idempotently (the unique failure-injection contract — terminate after
|
|
13
|
+
* `validated` and before `switched`, restart switches once without duplicate
|
|
14
|
+
* rows).
|
|
15
|
+
*
|
|
16
|
+
* Runs against an injected M2Host so the migration is deterministic and
|
|
17
|
+
* testable. Never mutates v2; the export is a NEW copy (silent direct downgrade
|
|
18
|
+
* is rejected). Registered conformance rows: M2-001..015 + MIG-DOWN-001.
|
|
19
|
+
* PREVENT-002/011/PI-004 honored; no console.log.
|
|
20
|
+
*/
|
|
21
|
+
import { MIG_DOWN_FAIL, verifyLegacyDigest } from "../ledger/compat-journal.js";
|
|
22
|
+
/** M2 failure codes (downgrade export). */
|
|
23
|
+
export const M2_FAIL = {
|
|
24
|
+
...MIG_DOWN_FAIL,
|
|
25
|
+
COPY_MISSING: "M2_COPY_MISSING",
|
|
26
|
+
UNREPRESENTABLE_UNLISTED: "M2_UNREPRESENTABLE_UNLISTED",
|
|
27
|
+
};
|
|
28
|
+
/** The freshly computed legacy export from the v2 journal (expected bytes). */
|
|
29
|
+
export function computeLegacyExport(host) {
|
|
30
|
+
return host.journalRows();
|
|
31
|
+
}
|
|
32
|
+
/** Phase 1 — copy: stage the legacy export without activating. Returns rows. */
|
|
33
|
+
export function m2Copy(host) {
|
|
34
|
+
const rows = computeLegacyExport(host);
|
|
35
|
+
host.writeStagedLegacy(rows);
|
|
36
|
+
return rows;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Phase 2 — validate: the staged export must equal the freshly computed one
|
|
40
|
+
* (copy-match), the journal must be active and have reached the `copied`
|
|
41
|
+
* phase, seq must be monotonic per session, digests must be well-formed, and
|
|
42
|
+
* every unrepresentable row must be listed. Advances to `validated` only when
|
|
43
|
+
* every check passes (so a crash here leaves authority unchanged).
|
|
44
|
+
*/
|
|
45
|
+
export function m2Validate(host) {
|
|
46
|
+
const codes = [];
|
|
47
|
+
const staged = host.stagedLegacy();
|
|
48
|
+
const expected = computeLegacyExport(host);
|
|
49
|
+
if (staged === null) {
|
|
50
|
+
codes.push(M2_FAIL.COPY_MISSING);
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
const sameRows = staged.length === expected.length &&
|
|
54
|
+
staged.every((r, i) => r.session === expected[i].session &&
|
|
55
|
+
r.seq === expected[i].seq &&
|
|
56
|
+
r.eventId === expected[i].eventId &&
|
|
57
|
+
r.unrepresentable === expected[i].unrepresentable);
|
|
58
|
+
if (!sameRows)
|
|
59
|
+
codes.push(M2_FAIL.COPY_MISSING);
|
|
60
|
+
// Seq monotonic per session (no regression).
|
|
61
|
+
const prev = new Map();
|
|
62
|
+
for (const r of staged) {
|
|
63
|
+
const last = prev.get(r.session);
|
|
64
|
+
if (last !== undefined && r.seq <= last)
|
|
65
|
+
codes.push(M2_FAIL.SEQ_REGRESSION);
|
|
66
|
+
prev.set(r.session, r.seq);
|
|
67
|
+
}
|
|
68
|
+
// Digests on representable rows must verify: recompute sha256 over the stored
|
|
69
|
+
// source and compare to the recorded digest (Q03 — real parity).
|
|
70
|
+
for (const r of staged) {
|
|
71
|
+
if (!r.unrepresentable && !verifyLegacyDigest(r.digest, r.legacyProjection)) {
|
|
72
|
+
codes.push(M2_FAIL.DIGEST_MISMATCH);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// Every unrepresentable row is explicitly flagged (MIG-DOWN-003).
|
|
76
|
+
for (const r of expected) {
|
|
77
|
+
if (r.unrepresentable) {
|
|
78
|
+
const flag = staged.find((s) => s.session === r.session && s.seq === r.seq && s.unrepresentable);
|
|
79
|
+
if (!flag)
|
|
80
|
+
codes.push(M2_FAIL.UNREPRESENTABLE_UNLISTED);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (!host.journalActive())
|
|
85
|
+
codes.push(M2_FAIL.NOT_ACTIVE);
|
|
86
|
+
if (host.phase() !== "copied")
|
|
87
|
+
codes.push(M2_FAIL.PHASE_UNREACHED);
|
|
88
|
+
if (codes.length === 0) {
|
|
89
|
+
const v = host.validateStaged();
|
|
90
|
+
if (!v.ok) {
|
|
91
|
+
// Q05: narrow the host's string codes through a runtime type guard instead
|
|
92
|
+
// of an unchecked cast — unknown codes are dropped, never mis-typed.
|
|
93
|
+
for (const c of v.codes)
|
|
94
|
+
if (isM2MigrationCode(c))
|
|
95
|
+
codes.push(c);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
const ok = codes.length === 0;
|
|
99
|
+
return { ok, codes: dedupe(codes) };
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Phase 3 — switch: atomic activation of the legacy export as the openable
|
|
103
|
+
* authority. Only call after m2Validate reports ok; the journal advances to
|
|
104
|
+
* `switched` and v2 becomes read-only-archival. Idempotent on resume.
|
|
105
|
+
*/
|
|
106
|
+
export function m2Switch(host) {
|
|
107
|
+
host.switchLegacy();
|
|
108
|
+
}
|
|
109
|
+
/** Registered M2 conformance ID range (M2-001..015). */
|
|
110
|
+
export const M2_IDS = [
|
|
111
|
+
"M2-001",
|
|
112
|
+
"M2-002",
|
|
113
|
+
"M2-003",
|
|
114
|
+
"M2-004",
|
|
115
|
+
"M2-005",
|
|
116
|
+
"M2-006",
|
|
117
|
+
"M2-007",
|
|
118
|
+
"M2-008",
|
|
119
|
+
"M2-009",
|
|
120
|
+
"M2-010",
|
|
121
|
+
"M2-011",
|
|
122
|
+
"M2-012",
|
|
123
|
+
"M2-013",
|
|
124
|
+
"M2-014",
|
|
125
|
+
"M2-015",
|
|
126
|
+
];
|
|
127
|
+
/** Registered MIG-DOWN conformance ID (MIG-DOWN-001). */
|
|
128
|
+
export const MIG_DOWN_IDS = ["MIG-DOWN-001"];
|
|
129
|
+
/** Runtime narrowing: is `code` a known M2 migration failure code? */
|
|
130
|
+
function isM2MigrationCode(code) {
|
|
131
|
+
return Object.values(M2_FAIL).includes(code);
|
|
132
|
+
}
|
|
133
|
+
function dedupe(codes) {
|
|
134
|
+
const out = [];
|
|
135
|
+
for (const c of codes)
|
|
136
|
+
if (!out.includes(c))
|
|
137
|
+
out.push(c);
|
|
138
|
+
return out;
|
|
139
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vector-cortex/replay/cut.ts — ReplayCutV2 effective-cut calculator (VC0B).
|
|
3
|
+
*
|
|
4
|
+
* M3 effective-cut rule: effective = min(boundarySafeSeq, committedSeq,
|
|
5
|
+
* capturedHighWater), capped by requestedSeq, then retreated to the call
|
|
6
|
+
* boundary whenever the candidate intersects a tool call/result pair, then
|
|
7
|
+
* clamped to the recent-anchor floor. On a tie among the min sources we choose
|
|
8
|
+
* the LOWER source order and record CUT_LOWEST_SOURCE_ORDER. This is the
|
|
9
|
+
* successor to the legacy capped-replay effective cut — the defect at
|
|
10
|
+
* docs/vector-cortex/SPRINT_PLAN.md M3 ("capped replay effective cut must
|
|
11
|
+
* respect boundary/commit/capture minima") is fixed here.
|
|
12
|
+
*
|
|
13
|
+
* Pure/deterministic — no network, no side effects (PREVENT-PI-004).
|
|
14
|
+
*/
|
|
15
|
+
const MIN_SOURCES = ["boundary", "committed", "captured"];
|
|
16
|
+
/**
|
|
17
|
+
* min-of-three, capped by requestedSeq, with deterministic lower-source
|
|
18
|
+
* tie-breaking. Returns the min value and every source that tied for it.
|
|
19
|
+
* On a tie the LOWER source order wins; `sources[0]` is the winning source.
|
|
20
|
+
*/
|
|
21
|
+
function minOfCapped(requestedSeq, boundarySafeSeq, committedSeq, capturedHighWater) {
|
|
22
|
+
const values = [
|
|
23
|
+
["boundary", boundarySafeSeq],
|
|
24
|
+
["committed", committedSeq],
|
|
25
|
+
["captured", capturedHighWater],
|
|
26
|
+
];
|
|
27
|
+
const minVal = values.reduce((a, [, v]) => (v < a ? v : a), requestedSeq);
|
|
28
|
+
const floors = values.filter(([, v]) => v === minVal).map(([s]) => s);
|
|
29
|
+
// If a source value exceeds requestedSeq it is capped away; the min is
|
|
30
|
+
// positioned at requestedSeq only when every source is above the request.
|
|
31
|
+
const value = floors.length > 0 ? minVal : requestedSeq;
|
|
32
|
+
return { value, sources: floors.length > 0 ? floors : ["boundary"] };
|
|
33
|
+
}
|
|
34
|
+
/** True when a cut at seq `e` does NOT split any tool call/result pair. */
|
|
35
|
+
export function cutIsPairSafe(e, pairs) {
|
|
36
|
+
for (const p of pairs) {
|
|
37
|
+
// Cutting at e keeps events with seq <= e. A pair is split when the call
|
|
38
|
+
// (callSeq) is kept but the result (resultSeq) is dropped.
|
|
39
|
+
if (p.callSeq <= e && e < p.resultSeq)
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Retreat `e` upward-locked toward the call boundary of any pair it splits.
|
|
46
|
+
* Returns the largest pair-safe e <= start, never below `floor`.
|
|
47
|
+
*
|
|
48
|
+
* `splitResolved` is true when a CUT_TOOL_PAIR_SPLIT retreat occurred; when a
|
|
49
|
+
* retreat would cross the floor the cut clamps AT the floor and the reason
|
|
50
|
+
* becomes CUT_ANCHOR_FLOOR (the floor is assumed pair-safe for legal inputs).
|
|
51
|
+
*/
|
|
52
|
+
function retreatAgainstPairs(start, pairs, floor) {
|
|
53
|
+
let e = start;
|
|
54
|
+
let split = false;
|
|
55
|
+
let floorClamped = false;
|
|
56
|
+
for (;;) {
|
|
57
|
+
let hit = false;
|
|
58
|
+
for (const p of pairs) {
|
|
59
|
+
if (p.callSeq <= e && e < p.resultSeq) {
|
|
60
|
+
hit = true;
|
|
61
|
+
const target = p.callSeq - 1n;
|
|
62
|
+
if (target < floor) {
|
|
63
|
+
// No-progress guard (VC0B-I12): the retreat target lies below the
|
|
64
|
+
// floor. If the floor itself still splits a pair we must not loop
|
|
65
|
+
// back onto it (that hangs the synchronous agent loop) — clamp AT
|
|
66
|
+
// the floor once and stop. The floor is taken as the legal cut.
|
|
67
|
+
e = floor;
|
|
68
|
+
floorClamped = true;
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
e = target;
|
|
72
|
+
}
|
|
73
|
+
split = true;
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// Terminate when no pair splits e, OR when we have already clamped to the
|
|
78
|
+
// floor (the floor is an absolute lower bound; we never retreat past it
|
|
79
|
+
// even if it is not itself pair-safe).
|
|
80
|
+
if (!hit || floorClamped)
|
|
81
|
+
return { effective: e, split, floorClamped };
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Compute the v2 effective cut. Pure with respect to the provided inputs.
|
|
86
|
+
*/
|
|
87
|
+
export function computeEffectiveCutV2(input) {
|
|
88
|
+
const { requestedSeq, boundarySafeSeq, committedSeq, capturedHighWater, anchorFloor, pairs } = input;
|
|
89
|
+
const retreats = [];
|
|
90
|
+
// Step 1 — min-of-three (capped by requestedSeq) with lower-source tie-break.
|
|
91
|
+
const min = minOfCapped(requestedSeq, boundarySafeSeq, committedSeq, capturedHighWater);
|
|
92
|
+
let effective = min.value;
|
|
93
|
+
// Tie among the min sources: resolved to the LOWER source order — record it.
|
|
94
|
+
if (min.sources.length > 1) {
|
|
95
|
+
retreats.push({
|
|
96
|
+
code: "CUT_LOWEST_SOURCE_ORDER",
|
|
97
|
+
fromSeq: effective,
|
|
98
|
+
toSeq: effective,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
// Step 2 — pair retreat (never below the anchor floor).
|
|
102
|
+
const retreat = retreatAgainstPairs(effective, pairs, anchorFloor);
|
|
103
|
+
if (retreat.split && retreat.floorClamped) {
|
|
104
|
+
retreats.push({ code: "CUT_ANCHOR_FLOOR", fromSeq: effective, toSeq: retreat.effective });
|
|
105
|
+
}
|
|
106
|
+
else if (retreat.split) {
|
|
107
|
+
retreats.push({ code: "CUT_TOOL_PAIR_SPLIT", fromSeq: effective, toSeq: retreat.effective });
|
|
108
|
+
}
|
|
109
|
+
effective = retreat.effective;
|
|
110
|
+
// Step 3 — final anchor-floor clamp (safety; legal floors are already >=).
|
|
111
|
+
if (effective < anchorFloor) {
|
|
112
|
+
retreats.push({ code: "CUT_ANCHOR_FLOOR", fromSeq: effective, toSeq: anchorFloor });
|
|
113
|
+
effective = anchorFloor;
|
|
114
|
+
}
|
|
115
|
+
const cut = {
|
|
116
|
+
requestedSeq,
|
|
117
|
+
boundarySafeSeq,
|
|
118
|
+
committedSeq,
|
|
119
|
+
capturedHighWater,
|
|
120
|
+
effectiveSeq: effective,
|
|
121
|
+
};
|
|
122
|
+
return { cut, retreats };
|
|
123
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vector-cortex/replay/emit.ts — VC0B replay emit seam (VC0B).
|
|
3
|
+
*
|
|
4
|
+
* The single structured-event surface for the replay/effective-cut path. It is
|
|
5
|
+
* the replay analogue of the VC0A eval observer (`src/vector-cortex/eval/
|
|
6
|
+
* observer.ts`): the same non-fatal, structured-JSON `ts`+`event` contract
|
|
7
|
+
* (see `src/log.ts` LogEntry), delivered through an injected emit callback so it
|
|
8
|
+
* stays pi-agnostic, deterministically testable, and network-free
|
|
9
|
+
* (PREVENT-PI-004). A future VC0C breaker/dashboard consumes these lines.
|
|
10
|
+
*
|
|
11
|
+
* This is deliberately MINIMAL — it is NOT a second metrics pipeline. It carries
|
|
12
|
+
* only the two events the replay/effective-cut path emits:
|
|
13
|
+
* - vector_cortex_replay_cut_retreat (a pair/anchor/lowest-source retreat)
|
|
14
|
+
* - vector_cortex_replay_highwater_frozen (mode C: host transcript unchanged)
|
|
15
|
+
*
|
|
16
|
+
* Flag gating: this is VC0B's single real consumer of `VC0B_ENABLED`. When the
|
|
17
|
+
* flag is OFF, the reporter emits NOTHING (zero observability writes) — mirroring
|
|
18
|
+
* the VC0A mode-C pattern where flag-off produces zero evaluation writes. The
|
|
19
|
+
* ReplayReportV2 data (cut/effectiveSeq/capturedHighWater/mode) is returned by
|
|
20
|
+
* `runReplayV2` regardless; only the emitted observability is gated. The flag is
|
|
21
|
+
* read per-call so a live toggle takes effect immediately.
|
|
22
|
+
*/
|
|
23
|
+
import { VC0B_ENABLED } from "../../config/vector-cortex.js";
|
|
24
|
+
function safe(fn) {
|
|
25
|
+
return (event, fields) => {
|
|
26
|
+
try {
|
|
27
|
+
fn(event, fields);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
/* non-fatal observability — never break the agent loop */
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Build a typed reporter over an injected emit callback. Observability emission
|
|
36
|
+
* is gated on `VC0B_ENABLED()` — flag OFF means zero replay observability writes
|
|
37
|
+
* (VC0A mode-C parity). `emit` is optional: absent (or flag off) degrades to a
|
|
38
|
+
* no-op. The flag is read per-call so a live toggle takes effect immediately.
|
|
39
|
+
*/
|
|
40
|
+
export function createReplayReporter(emit) {
|
|
41
|
+
const fire = safe((event, fields) => {
|
|
42
|
+
if (VC0B_ENABLED())
|
|
43
|
+
emit?.(event, fields);
|
|
44
|
+
});
|
|
45
|
+
return {
|
|
46
|
+
cutRetreat(fields) {
|
|
47
|
+
fire("vector_cortex_replay_cut_retreat", fields);
|
|
48
|
+
},
|
|
49
|
+
highWaterFrozen(fields) {
|
|
50
|
+
fire("vector_cortex_replay_highwater_frozen", fields);
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/** Export the event names for consumers that match on them. */
|
|
55
|
+
export const REPLAY_EVENTS = [
|
|
56
|
+
"vector_cortex_replay_cut_retreat",
|
|
57
|
+
"vector_cortex_replay_highwater_frozen",
|
|
58
|
+
];
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vector-cortex/replay/replay.ts — ReplayCutV2 replay scan (VC0B).
|
|
3
|
+
*
|
|
4
|
+
* Scans EventV2 occurrences in ascending `(seq, eventId bytewise UTF-8)`,
|
|
5
|
+
* computes the v2 effective cut (M3 min-of-three + pair retreat + anchor floor),
|
|
6
|
+
* and returns the replayed prefix bytes joined in strict source order. Before
|
|
7
|
+
* returning bytes it reports any CUT_TOOL_PAIR_SPLIT / CUT_ANCHOR_FLOOR retreat
|
|
8
|
+
* via the injected emit callback. Hard invariants are enforced zero-tolerance:
|
|
9
|
+
* the output is a source-order prefix with no orphan tool event and no split
|
|
10
|
+
* pair (reordered === 0, orphanToolEvents === 0, splitPairs-after-cut === 0).
|
|
11
|
+
*
|
|
12
|
+
* Mode A uses the v2 effective-cut calculator; mode B is a plain sequential
|
|
13
|
+
* boundary scan with no precomputed index; mode C leaves the host transcript
|
|
14
|
+
* unchanged and returns a zero-byte report with the derived high-water frozen.
|
|
15
|
+
*
|
|
16
|
+
* Pure wrt inputs, emits structured events via the replay emit seam
|
|
17
|
+
* (`src/vector-cortex/replay/emit.ts`) — no console, no network (PREVENT-PI-004).
|
|
18
|
+
*/
|
|
19
|
+
import { computeEffectiveCutV2 } from "./cut.js";
|
|
20
|
+
import { computeEffectiveCutV2B } from "./replayB.js";
|
|
21
|
+
import { createReplayReporter } from "./emit.js";
|
|
22
|
+
/** The pair map is per-session; a tool result references one earlier call by id. */
|
|
23
|
+
export function extractToolPairs(occurrences) {
|
|
24
|
+
const calls = new Map();
|
|
25
|
+
const pairs = [];
|
|
26
|
+
let maxSeq = -1n;
|
|
27
|
+
for (const o of occurrences) {
|
|
28
|
+
if (o.seq > maxSeq)
|
|
29
|
+
maxSeq = o.seq;
|
|
30
|
+
if (o.role === "tool" && o.toolCallId !== undefined) {
|
|
31
|
+
const callSeq = calls.get(o.toolCallId);
|
|
32
|
+
if (callSeq !== undefined && callSeq < o.seq) {
|
|
33
|
+
pairs.push({ callSeq, resultSeq: o.seq });
|
|
34
|
+
// Matched: remove so only genuinely-unclosed calls remain for the
|
|
35
|
+
// synthetic dangling-call pairs below (otherwise a matched call would
|
|
36
|
+
// also get a synthetic pair and force an over-retreat).
|
|
37
|
+
calls.delete(o.toolCallId);
|
|
38
|
+
}
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
// A call is an assistant event carrying a tool call id in EventV2; here we
|
|
42
|
+
// treat an assistant occurrence with a toolCallId as the CALL side.
|
|
43
|
+
if (o.toolCallId !== undefined) {
|
|
44
|
+
calls.set(o.toolCallId, o.seq);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// VC0B-I11: a call with NO observed result (dangling / unclosed) must still
|
|
48
|
+
// bound the cut. Mode B's open-call tracker retreats below such a call; to
|
|
49
|
+
// keep A and B in lockstep we surface each dangling call as a synthetic pair
|
|
50
|
+
// whose "result" lies one past the last observed seq. Any cut at/after the
|
|
51
|
+
// call then splits it, so A retreats to the call boundary exactly like B —
|
|
52
|
+
// and the synthetic unmatched marker also makes finalizeReplay count the
|
|
53
|
+
// dangling call as an orphan instead of reporting a false 0.
|
|
54
|
+
const pastEnd = maxSeq + 1n;
|
|
55
|
+
for (const callSeq of calls.values()) {
|
|
56
|
+
pairs.push({ callSeq, resultSeq: pastEnd });
|
|
57
|
+
}
|
|
58
|
+
return pairs;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Largest pair-safe seq <= `ceiling` (a cut at e keeps seq <= e and must not
|
|
62
|
+
* split any pair where callSeq <= e < resultSeq). Sequential boundary scan
|
|
63
|
+
* (mode B); no aggregate index required.
|
|
64
|
+
*/
|
|
65
|
+
export function largestPairSafeSeq(pairs, ceiling) {
|
|
66
|
+
let e = ceiling;
|
|
67
|
+
for (;;) {
|
|
68
|
+
let blocked = false;
|
|
69
|
+
for (const p of pairs) {
|
|
70
|
+
if (p.callSeq <= e && e < p.resultSeq) {
|
|
71
|
+
e = p.callSeq - 1n;
|
|
72
|
+
blocked = true;
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (!blocked)
|
|
77
|
+
return e;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/** Ascending (seq, eventId) comparator over two occurrences. */
|
|
81
|
+
export function compareOccurrences(a, b) {
|
|
82
|
+
if (a.seq !== b.seq)
|
|
83
|
+
return a.seq < b.seq ? -1 : 1;
|
|
84
|
+
const ea = a.eventId;
|
|
85
|
+
const eb = b.eventId;
|
|
86
|
+
if (ea < eb)
|
|
87
|
+
return -1;
|
|
88
|
+
if (ea > eb)
|
|
89
|
+
return 1;
|
|
90
|
+
return 0;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Run the v2 replay scan and produce a ReplayReportV2 plus the replayed bytes.
|
|
94
|
+
*/
|
|
95
|
+
export function runReplayV2(options) {
|
|
96
|
+
const { sessionId, occurrences, requestedSeq, committedSeq, capturedHighWater, anchorFloor } = options;
|
|
97
|
+
const mode = options.mode ?? "A";
|
|
98
|
+
const reporter = options.reporter ?? createReplayReporter();
|
|
99
|
+
// Mode C: unchanged host transcript — derived high-water frozen, zero bytes.
|
|
100
|
+
if (mode === "C") {
|
|
101
|
+
const cut = {
|
|
102
|
+
requestedSeq,
|
|
103
|
+
boundarySafeSeq: requestedSeq,
|
|
104
|
+
committedSeq,
|
|
105
|
+
capturedHighWater,
|
|
106
|
+
effectiveSeq: anchorFloor,
|
|
107
|
+
};
|
|
108
|
+
reporter.highWaterFrozen({
|
|
109
|
+
session: sessionId,
|
|
110
|
+
committedSeq: committedSeq.toString(),
|
|
111
|
+
frozenHighWater: capturedHighWater.toString(),
|
|
112
|
+
});
|
|
113
|
+
return {
|
|
114
|
+
report: zeroReport(cut, "C", []),
|
|
115
|
+
bytes: new Uint8Array(0),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
const pairs = extractToolPairs(occurrences);
|
|
119
|
+
// Mode B: genuinely independent deterministic local cut (streams the bytes,
|
|
120
|
+
// shares no subroutine with A). Mode A: optimized pair-list + min-of-three.
|
|
121
|
+
if (mode === "B") {
|
|
122
|
+
const b = computeEffectiveCutV2B({
|
|
123
|
+
occurrences,
|
|
124
|
+
requestedSeq,
|
|
125
|
+
committedSeq,
|
|
126
|
+
capturedHighWater,
|
|
127
|
+
anchorFloor,
|
|
128
|
+
});
|
|
129
|
+
const bCut = {
|
|
130
|
+
requestedSeq,
|
|
131
|
+
boundarySafeSeq: b.boundarySafeSeq,
|
|
132
|
+
committedSeq,
|
|
133
|
+
capturedHighWater,
|
|
134
|
+
effectiveSeq: b.effectiveSeq,
|
|
135
|
+
};
|
|
136
|
+
return finalizeReplay({
|
|
137
|
+
occurrences,
|
|
138
|
+
cut: bCut,
|
|
139
|
+
mode: "B",
|
|
140
|
+
retreats: [],
|
|
141
|
+
pairs,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
// Boundary-safe cap: largest pair-safe seq (sequential scan; mode A).
|
|
145
|
+
const boundarySafeSeq = largestPairSafeSeq(pairs, requestedSeq);
|
|
146
|
+
// effective cut: min-of-three + pair retreat + anchor floor.
|
|
147
|
+
const effectiveResult = computeEffectiveCutV2({
|
|
148
|
+
requestedSeq,
|
|
149
|
+
boundarySafeSeq,
|
|
150
|
+
committedSeq,
|
|
151
|
+
capturedHighWater,
|
|
152
|
+
anchorFloor,
|
|
153
|
+
pairs,
|
|
154
|
+
});
|
|
155
|
+
const { cut, retreats } = effectiveResult;
|
|
156
|
+
for (const r of retreats) {
|
|
157
|
+
reporter.cutRetreat({
|
|
158
|
+
session: sessionId,
|
|
159
|
+
code: r.code,
|
|
160
|
+
fromSeq: r.fromSeq.toString(),
|
|
161
|
+
toSeq: r.toSeq.toString(),
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
return finalizeReplay({
|
|
165
|
+
occurrences,
|
|
166
|
+
cut,
|
|
167
|
+
mode: "A",
|
|
168
|
+
retreats,
|
|
169
|
+
pairs,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
/** Shared post-cut scan: strict (seq,eventId) prefix + invariant counting. */
|
|
173
|
+
function finalizeReplay(opts) {
|
|
174
|
+
const { occurrences, cut, mode, retreats, pairs } = opts;
|
|
175
|
+
const effective = cut.effectiveSeq;
|
|
176
|
+
// Strict ascending source order scan up to the effective cut.
|
|
177
|
+
const kept = [];
|
|
178
|
+
let reordered = 0;
|
|
179
|
+
let scanned = 0;
|
|
180
|
+
let bytesTotal = 0;
|
|
181
|
+
const keptBytes = [];
|
|
182
|
+
let prev = null;
|
|
183
|
+
const effectiveBig = effective;
|
|
184
|
+
for (const o of occurrences) {
|
|
185
|
+
scanned += 1;
|
|
186
|
+
if (compareOccurrences(prev ?? o, o) > 0)
|
|
187
|
+
reordered += 1;
|
|
188
|
+
prev = o;
|
|
189
|
+
if (o.seq > effectiveBig)
|
|
190
|
+
break;
|
|
191
|
+
kept.push(o);
|
|
192
|
+
if (o.originalBytes) {
|
|
193
|
+
keptBytes.push(o.originalBytes);
|
|
194
|
+
bytesTotal += o.originalBytes.length;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
// No orphan tool event: every kept tool RESULT must have its CALL kept too.
|
|
198
|
+
const keptCallIds = new Set();
|
|
199
|
+
for (const o of kept) {
|
|
200
|
+
if (o.toolCallId !== undefined && o.role !== "tool") {
|
|
201
|
+
keptCallIds.add(o.toolCallId);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
let orphanToolEvents = 0;
|
|
205
|
+
for (const o of kept) {
|
|
206
|
+
if (o.role === "tool" && o.toolCallId !== undefined && !keptCallIds.has(o.toolCallId)) {
|
|
207
|
+
orphanToolEvents += 1;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
// Pair-splits that survived retreat (must be 0 for a correct boundary-safe cut).
|
|
211
|
+
let splitPairs = 0;
|
|
212
|
+
for (const p of pairs) {
|
|
213
|
+
if (p.callSeq <= effectiveBig && effectiveBig < p.resultSeq)
|
|
214
|
+
splitPairs += 1;
|
|
215
|
+
}
|
|
216
|
+
const bytes = concatBytes(keptBytes);
|
|
217
|
+
const report = {
|
|
218
|
+
cut,
|
|
219
|
+
mode,
|
|
220
|
+
counts: {
|
|
221
|
+
scanned,
|
|
222
|
+
bytes: bytesTotal,
|
|
223
|
+
replayed: kept.length,
|
|
224
|
+
splitPairs,
|
|
225
|
+
anchorFloorCuts: retreats.filter((r) => r.code === "CUT_ANCHOR_FLOOR").length,
|
|
226
|
+
lowestSourceCuts: retreats.filter((r) => r.code === "CUT_LOWEST_SOURCE_ORDER").length,
|
|
227
|
+
reordered,
|
|
228
|
+
orphanToolEvents,
|
|
229
|
+
},
|
|
230
|
+
retreats,
|
|
231
|
+
};
|
|
232
|
+
return { report, bytes };
|
|
233
|
+
}
|
|
234
|
+
function concatBytes(chunks) {
|
|
235
|
+
const total = chunks.reduce((a, c) => a + c.length, 0);
|
|
236
|
+
const out = new Uint8Array(total);
|
|
237
|
+
let off = 0;
|
|
238
|
+
for (const c of chunks) {
|
|
239
|
+
out.set(c, off);
|
|
240
|
+
off += c.length;
|
|
241
|
+
}
|
|
242
|
+
return out;
|
|
243
|
+
}
|
|
244
|
+
function zeroReport(cut, mode, retreats) {
|
|
245
|
+
return {
|
|
246
|
+
cut,
|
|
247
|
+
mode,
|
|
248
|
+
counts: {
|
|
249
|
+
scanned: 0,
|
|
250
|
+
bytes: 0,
|
|
251
|
+
replayed: 0,
|
|
252
|
+
splitPairs: 0,
|
|
253
|
+
anchorFloorCuts: 0,
|
|
254
|
+
lowestSourceCuts: 0,
|
|
255
|
+
reordered: 0,
|
|
256
|
+
orphanToolEvents: 0,
|
|
257
|
+
},
|
|
258
|
+
retreats,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vector-cortex/replay/replayB.ts — Mode B: independent deterministic replay cut
|
|
3
|
+
* (VC0B, TRIAD_RESILIENCE).
|
|
4
|
+
*
|
|
5
|
+
* Mode B is the deliberately SLOW-but-simple local algorithm. It is a genuine
|
|
6
|
+
* counterpart to mode A (`./cut.ts`): it shares NO subroutine with A — no
|
|
7
|
+
* `computeEffectiveCutV2`, no `largestPairSafeSeq`, no `cutIsPairSafe`, no
|
|
8
|
+
* min-of-three shortcut. Instead it walks the authoritative observed occurrence
|
|
9
|
+
* bytes in ascending `(seq, eventId)` order from seq 0 and, at EVERY step,
|
|
10
|
+
* checks tool-pair completeness (via its own open-call tracker built from the
|
|
11
|
+
* bytes) plus the authority high-water caps and the anchor floor.
|
|
12
|
+
*
|
|
13
|
+
* Contract parity: the v2 effective cut is a well-defined pure function of the
|
|
14
|
+
* stream and the authority inputs, so two independent implementations of the
|
|
15
|
+
* same rule MUST agree. A optimizes via a precomputed pair list + min-of-three +
|
|
16
|
+
* retreat; B brute-forces the LARGEST seq that satisfies every constraint by
|
|
17
|
+
* scanning — slower (O(n·open)), which is exactly its purpose: when the breaker
|
|
18
|
+
* flips to B it catches bugs in A because the two share no code.
|
|
19
|
+
*
|
|
20
|
+
* Pure/deterministic, no network, no side effects (PREVENT-PI-004).
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* Independent mode-B effective cut. Walks the occurrence bytes once, tracking
|
|
24
|
+
* open tool calls from the bytes themselves (no extracted pair list). The
|
|
25
|
+
* boundary-safe seq is the largest pair-complete seq <= requestedSeq; the
|
|
26
|
+
* effective seq is the largest pair-complete seq that also lies within the
|
|
27
|
+
* authority ceiling (requested/committed/captured, applied as independent
|
|
28
|
+
* sequential caps — no shared min-of-three) and at/above the anchor floor.
|
|
29
|
+
*/
|
|
30
|
+
export function computeEffectiveCutV2B(input) {
|
|
31
|
+
const { occurrences, requestedSeq, committedSeq, capturedHighWater, anchorFloor } = input;
|
|
32
|
+
// Independent sequential authority ceiling (NOT A's minOfCapped).
|
|
33
|
+
let ceiling = requestedSeq;
|
|
34
|
+
if (ceiling > committedSeq)
|
|
35
|
+
ceiling = committedSeq;
|
|
36
|
+
if (ceiling > capturedHighWater)
|
|
37
|
+
ceiling = capturedHighWater;
|
|
38
|
+
// Open tool calls awaiting a result (toolCallId -> callSeq), built from bytes.
|
|
39
|
+
const open = new Map();
|
|
40
|
+
let boundarySafeSeq = 0n; // largest pair-complete seq <= requestedSeq
|
|
41
|
+
let effectiveSeq = 0n; // largest pair-complete seq <= ceiling (>= floor)
|
|
42
|
+
for (const o of occurrences) {
|
|
43
|
+
if (o.seq > requestedSeq)
|
|
44
|
+
break;
|
|
45
|
+
const s = o.seq;
|
|
46
|
+
if (o.toolCallId !== undefined && o.role !== "tool") {
|
|
47
|
+
// A CALL: it stays open until its result is observed.
|
|
48
|
+
open.set(o.toolCallId, s);
|
|
49
|
+
}
|
|
50
|
+
else if (o.toolCallId !== undefined && o.role === "tool") {
|
|
51
|
+
// Its RESULT: the pair closes.
|
|
52
|
+
open.delete(o.toolCallId);
|
|
53
|
+
}
|
|
54
|
+
// A cut at `s` is pair-complete iff no still-open call lies at or below `s`
|
|
55
|
+
// (such a call's result is beyond the cut, so keeping the call orphans it).
|
|
56
|
+
let complete = true;
|
|
57
|
+
for (const cseq of open.values()) {
|
|
58
|
+
if (cseq <= s) {
|
|
59
|
+
complete = false;
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (!complete)
|
|
64
|
+
continue;
|
|
65
|
+
boundarySafeSeq = s;
|
|
66
|
+
if (s <= ceiling && s >= anchorFloor)
|
|
67
|
+
effectiveSeq = s;
|
|
68
|
+
}
|
|
69
|
+
// Anchor floor is an absolute lower bound (parity with A's final clamp).
|
|
70
|
+
if (effectiveSeq < anchorFloor)
|
|
71
|
+
effectiveSeq = anchorFloor;
|
|
72
|
+
return { boundarySafeSeq, effectiveSeq };
|
|
73
|
+
}
|