chore(rebirth): establish v3 cutover gates

This commit is contained in:
youzini
2026-05-30 13:26:19 +00:00
parent 152913811f
commit a7b974b748
6 changed files with 259 additions and 1 deletions

1
.gitignore vendored
View File

@@ -17,3 +17,4 @@ docs/BME六大功能全景解析.xlsx
ST-BME_backup_6f78abcb-9aea-45b1-a8ad-fbbd8e4075f0-cx4dad.json
plans/mvu-extra-analysis-guard.md
tests/.tmp-*/
plans/deep-optimization-design.md

View File

@@ -16,6 +16,7 @@ ST-BMEBionic Memory Ecology是一个 **SillyTavern 第三方前端扩展**
- [主要配置](#主要配置)
- [记忆模型](#记忆模型)
- [长聊天与历史安全](#长聊天与历史安全)
- [v3 重生计划与数据格式硬切](#v3-重生计划与数据格式硬切)
- [常用操作速查](#常用操作速查)
- [数据存储与同步](#数据存储与同步)
- [排障指南](#排障指南)
@@ -25,6 +26,27 @@ ST-BMEBionic Memory Ecology是一个 **SillyTavern 第三方前端扩展**
---
## v3 重生计划与数据格式硬切
ST-BME 正在推进一次“克制版重生”身份解析、持久化确认、GraphHead 数据模型和持久化路由外壳会被重建为更干净的 v3 核心Luker、OPFS、IndexedDB、Authority、向量等仍在使用的适配能力会平移到新接口。
这次重生会采用 **v3 新命名空间 + 新数据格式硬切**
- v3 运行时不会把旧 IndexedDB / OPFS / Luker sidecar / metadata-full / 旧 commit marker 当作热路径数据源;
- 不会保留永久的“旧格式读取层”或 dark-read/dual-write 兼容层;
- 如需保留旧图谱,请在切换前通过面板“导出图谱”或服务器备份功能先导出/备份;
- 后续最多提供一次性导入/重置工具,旧数据导入后工具可移除;
- 如果不导入旧图谱v3 会从聊天历史重新提取/重建,但这会消耗 LLM 调用,且不保证完全复现旧图谱;
- v3 数据可能无法被旧版本读取,切换或回滚前请保留导出/备份。
当前阶段的策略盘点 / 切换清单命令(不会扫描浏览器或服务器实际数据):
```bash
npm run rebirth:inventory
```
---
## 核心能力
- **自动记忆提取**

View File

@@ -6,6 +6,7 @@
"test:triviumdb-poc": "node tests/triviumdb-poc.mjs",
"test:runtime-history": "node tests/runtime-history.mjs",
"test:graph-persistence": "node tests/graph-persistence.mjs",
"test:rebirth-phase0": "node tests/rebirth-phase0.mjs",
"test:hide-engine": "node tests/hide-engine.mjs",
"test:maintenance-journal": "node tests/maintenance-journal.mjs",
"test:indexeddb-persistence": "node tests/indexeddb-persistence.mjs",
@@ -29,7 +30,8 @@
"test:authority:e2e:restore": "node tests/e2e/authority-checkpoint-restore.mjs",
"test:authority:e2e:all": "npm run test:authority:e2e && npm run test:authority:e2e:diagnostics && npm run test:authority:e2e:restore",
"test:all": "npm run test:stable",
"check": "node scripts/check-syntax.mjs"
"check": "node scripts/check-syntax.mjs",
"rebirth:inventory": "node scripts/rebirth-phase0-inventory.mjs"
},
"dependencies": {
"triviumdb": "0.7.1"

128
runtime/rebirth-policy.mjs Normal file
View File

@@ -0,0 +1,128 @@
// ST-BME restrained rebirth policy.
//
// Phase 0 deliberately keeps this module side-effect free. It records the
// project-level cutover contract so later phases cannot quietly reintroduce a
// permanent legacy data-format compatibility layer.
export const REBIRTH_FORMAT_VERSION = 3;
export const V3_STORAGE_NAMESPACES = Object.freeze({
root: "st-bme-v3",
graph: "graph-v3",
commitMarker: "commit-marker-v3",
vectorManifest: "vector-manifest-v3",
authorityGraph: "authority-graph-v3",
lukerSidecar: "luker-graph-v3",
});
export const LEGACY_DATA_RUNTIME_POLICY = Object.freeze({
permanentRuntimeLegacyRead: false,
darkReadDualWriteMigration: false,
allowedLegacyAccess: Object.freeze(["one-shot-importer", "explicit-export", "manual-reset"]),
fallbackWhenNoImporter: "rebuild-from-chat-history",
});
export const LIVE_ADAPTER_TARGETS = Object.freeze([
"indexeddb",
"opfs",
"authority-sql",
"luker-chat-state",
"vector-manifest",
]);
export const LEGACY_DATA_SOURCES = Object.freeze([
Object.freeze({
id: "indexeddb-legacy",
kind: "graph-store",
runtimeAction: "ignore",
phase0Action: "inventory-or-export",
notes: "Old IndexedDB snapshots/migration stores must not be auto-read by v3 runtime.",
}),
Object.freeze({
id: "opfs-legacy",
kind: "graph-store",
runtimeAction: "ignore",
phase0Action: "inventory-or-export",
notes: "Old OPFS v1/v2 graph layouts require explicit import or reset.",
}),
Object.freeze({
id: "authority-sql-legacy",
kind: "server-graph-store",
runtimeAction: "ignore",
phase0Action: "inventory-or-export",
notes: "Authority v3 must use a graphId/schema-version namespace and reject old rows by default.",
}),
Object.freeze({
id: "luker-sidecar-legacy",
kind: "host-chat-state",
runtimeAction: "ignore",
phase0Action: "inventory-or-export",
notes: "Legacy Luker manifest/journal/checkpoint keys remain inert unless an importer reads them.",
}),
Object.freeze({
id: "metadata-full-legacy",
kind: "chat-metadata",
runtimeAction: "ignore",
phase0Action: "inventory-or-export",
notes: "Old full graph blobs in chat metadata are not a v3 runtime source.",
}),
Object.freeze({
id: "commit-marker-legacy",
kind: "chat-metadata",
runtimeAction: "ignore",
phase0Action: "inventory-or-export",
notes: "Old commit markers are evidence only for a one-shot importer, not v3 acceptance state.",
}),
Object.freeze({
id: "vector-manifest-legacy",
kind: "vector-state",
runtimeAction: "ignore",
phase0Action: "reset-or-rebuild",
notes: "Vectors are rebuildable; legacy vector manifests must not contaminate v3 graphId/vectorSpaceId.",
}),
]);
export const PHASE0_BACKUP_CHECKLIST = Object.freeze([
Object.freeze({
id: "manual-graph-export",
label: "Export current graph JSON from the ST-BME panel before enabling v3.",
source: "ui-actions-controller:onExportGraphController",
}),
Object.freeze({
id: "server-backup",
label: "If Authority/server backup is used, create a server backup envelope first.",
source: "sync/bme-sync:backupToServer",
}),
Object.freeze({
id: "authority-reset-plan",
label: "Plan an explicit Authority v3 namespace/reset so old SQL/blob/vector rows cannot be selected.",
source: "runtime/rebirth-policy:V3_STORAGE_NAMESPACES.authorityGraph",
}),
Object.freeze({
id: "legacy-import-decision",
label: "Decide per legacy source: one-shot import, export-only backup, rebuild from chat history, or discard.",
source: "runtime/rebirth-policy:LEGACY_DATA_SOURCES",
}),
]);
export function getRebirthPhase0Inventory() {
return {
formatVersion: REBIRTH_FORMAT_VERSION,
namespaces: { ...V3_STORAGE_NAMESPACES },
policy: {
permanentRuntimeLegacyRead: LEGACY_DATA_RUNTIME_POLICY.permanentRuntimeLegacyRead,
darkReadDualWriteMigration: LEGACY_DATA_RUNTIME_POLICY.darkReadDualWriteMigration,
allowedLegacyAccess: [...LEGACY_DATA_RUNTIME_POLICY.allowedLegacyAccess],
fallbackWhenNoImporter: LEGACY_DATA_RUNTIME_POLICY.fallbackWhenNoImporter,
},
liveAdapterTargets: [...LIVE_ADAPTER_TARGETS],
legacyDataSources: LEGACY_DATA_SOURCES.map((source) => ({ ...source })),
backupChecklist: PHASE0_BACKUP_CHECKLIST.map((item) => ({ ...item })),
};
}
export function shouldV3RuntimeReadLegacySource(sourceId) {
const source = LEGACY_DATA_SOURCES.find((entry) => entry.id === sourceId);
if (!source) return false;
return source.runtimeAction === "read";
}

View File

@@ -0,0 +1,38 @@
#!/usr/bin/env node
import { getRebirthPhase0Inventory } from "../runtime/rebirth-policy.mjs";
const inventory = getRebirthPhase0Inventory();
if (process.argv.includes("--json")) {
console.log(JSON.stringify(inventory, null, 2));
process.exit(0);
}
console.log("ST-BME v3 restrained rebirth — Phase 0 policy inventory / cutover checklist");
console.log(`formatVersion: ${inventory.formatVersion}`);
console.log("\nV3 namespaces:");
for (const [key, value] of Object.entries(inventory.namespaces)) {
console.log(` - ${key}: ${value}`);
}
console.log("\nLegacy runtime policy:");
console.log(` permanentRuntimeLegacyRead: ${inventory.policy.permanentRuntimeLegacyRead}`);
console.log(` darkReadDualWriteMigration: ${inventory.policy.darkReadDualWriteMigration}`);
console.log(` allowedLegacyAccess: ${inventory.policy.allowedLegacyAccess.join(", ")}`);
console.log(` fallbackWhenNoImporter: ${inventory.policy.fallbackWhenNoImporter}`);
console.log("\nLive adapter targets to port (not rewrite):");
for (const target of inventory.liveAdapterTargets) {
console.log(` - ${target}`);
}
console.log("\nLegacy data sources:");
for (const source of inventory.legacyDataSources) {
console.log(` - ${source.id}: runtime=${source.runtimeAction}, phase0=${source.phase0Action}`);
}
console.log("\nBackup / cutover checklist:");
for (const item of inventory.backupChecklist) {
console.log(` - [${item.id}] ${item.label}`);
}

67
tests/rebirth-phase0.mjs Normal file
View File

@@ -0,0 +1,67 @@
// ST-BME restrained rebirth — Phase 0 policy characterization.
import assert from "node:assert/strict";
import {
LEGACY_DATA_RUNTIME_POLICY,
LEGACY_DATA_SOURCES,
PHASE0_BACKUP_CHECKLIST,
REBIRTH_FORMAT_VERSION,
V3_STORAGE_NAMESPACES,
getRebirthPhase0Inventory,
shouldV3RuntimeReadLegacySource,
} from "../runtime/rebirth-policy.mjs";
assert.equal(REBIRTH_FORMAT_VERSION, 3);
for (const [key, namespace] of Object.entries(V3_STORAGE_NAMESPACES)) {
assert.match(namespace, /v3/, `${key} namespace must be versioned as v3`);
}
assert.equal(new Set(Object.values(V3_STORAGE_NAMESPACES)).size, Object.keys(V3_STORAGE_NAMESPACES).length);
console.log(" ✓ v3 namespaces are explicit and collision-resistant");
assert.equal(LEGACY_DATA_RUNTIME_POLICY.permanentRuntimeLegacyRead, false);
assert.equal(LEGACY_DATA_RUNTIME_POLICY.darkReadDualWriteMigration, false);
assert.deepEqual(LEGACY_DATA_RUNTIME_POLICY.allowedLegacyAccess, [
"one-shot-importer",
"explicit-export",
"manual-reset",
]);
assert.equal(LEGACY_DATA_RUNTIME_POLICY.fallbackWhenNoImporter, "rebuild-from-chat-history");
for (const source of LEGACY_DATA_SOURCES) {
assert.equal(source.runtimeAction, "ignore", `${source.id} must remain inert for v3 runtime`);
assert.equal(
shouldV3RuntimeReadLegacySource(source.id),
false,
`${source.id} must not be read by the v3 runtime`,
);
assert.notEqual(source.phase0Action, "runtime-read", `${source.id} must not plan a runtime read`);
}
for (const requiredSource of [
"metadata-full-legacy",
"commit-marker-legacy",
"vector-manifest-legacy",
"authority-sql-legacy",
]) {
assert.ok(
LEGACY_DATA_SOURCES.some((source) => source.id === requiredSource),
`${requiredSource} must be represented in Phase 0 policy`,
);
}
console.log(" ✓ legacy sources are inert for the v3 runtime");
const inventory = getRebirthPhase0Inventory();
assert.equal(inventory.formatVersion, 3);
assert.equal(inventory.policy.permanentRuntimeLegacyRead, false);
assert.equal(inventory.policy.fallbackWhenNoImporter, "rebuild-from-chat-history");
assert.equal(inventory.namespaces.authorityGraph, "authority-graph-v3");
assert.equal(inventory.namespaces.lukerSidecar, "luker-graph-v3");
assert.ok(inventory.legacyDataSources.length >= 6);
assert.ok(PHASE0_BACKUP_CHECKLIST.some((item) => item.id === "manual-graph-export"));
assert.ok(PHASE0_BACKUP_CHECKLIST.some((item) => item.id === "authority-reset-plan"));
console.log(" ✓ Phase 0 inventory exposes backup and cutover gates");
console.log("rebirth-phase0 tests passed");