mirror of
https://github.com/Youzini-afk/ST-Bionic-Memory-Ecology.git
synced 2026-06-13 18:31:16 +08:00
chore(rebirth): establish v3 cutover gates
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -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
|
||||
|
||||
22
README.md
22
README.md
@@ -16,6 +16,7 @@ ST-BME(Bionic Memory Ecology)是一个 **SillyTavern 第三方前端扩展**
|
||||
- [主要配置](#主要配置)
|
||||
- [记忆模型](#记忆模型)
|
||||
- [长聊天与历史安全](#长聊天与历史安全)
|
||||
- [v3 重生计划与数据格式硬切](#v3-重生计划与数据格式硬切)
|
||||
- [常用操作速查](#常用操作速查)
|
||||
- [数据存储与同步](#数据存储与同步)
|
||||
- [排障指南](#排障指南)
|
||||
@@ -25,6 +26,27 @@ ST-BME(Bionic 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
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 核心能力
|
||||
|
||||
- **自动记忆提取**
|
||||
|
||||
@@ -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
128
runtime/rebirth-policy.mjs
Normal 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";
|
||||
}
|
||||
38
scripts/rebirth-phase0-inventory.mjs
Normal file
38
scripts/rebirth-phase0-inventory.mjs
Normal 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
67
tests/rebirth-phase0.mjs
Normal 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");
|
||||
Reference in New Issue
Block a user