From 7c2c1e68f3496e99158b044a52adb05878151fbd Mon Sep 17 00:00:00 2001 From: youzini Date: Sat, 30 May 2026 18:32:48 +0000 Subject: [PATCH] chore: remove abandoned hard-cut v3 namespace cluster --- README.md | 22 +-- graph/graph-head.js | 261 --------------------------- graph/graph-v3-namespace.js | 99 ---------- package.json | 7 +- runtime/rebirth-policy.mjs | 128 ------------- scripts/rebirth-phase0-inventory.mjs | 38 ---- sync/graph-store-contract.js | 140 -------------- sync/graph-store-v3-adapter.js | 172 ------------------ tests/graph-head.mjs | 180 ------------------ tests/graph-store-contract.mjs | 132 -------------- tests/graph-store-v3-adapter.mjs | 190 ------------------- tests/rebirth-phase0.mjs | 67 ------- 12 files changed, 7 insertions(+), 1429 deletions(-) delete mode 100644 graph/graph-head.js delete mode 100644 graph/graph-v3-namespace.js delete mode 100644 runtime/rebirth-policy.mjs delete mode 100644 scripts/rebirth-phase0-inventory.mjs delete mode 100644 sync/graph-store-contract.js delete mode 100644 sync/graph-store-v3-adapter.js delete mode 100644 tests/graph-head.mjs delete mode 100644 tests/graph-store-contract.mjs delete mode 100644 tests/graph-store-v3-adapter.mjs delete mode 100644 tests/rebirth-phase0.mjs diff --git a/README.md b/README.md index 89f5d61..6550d9e 100644 --- a/README.md +++ b/README.md @@ -43,8 +43,7 @@ ST-BME 的记忆图谱以「耐久快照」形式存储在各存储层(Indexed - `sync/graph-snapshot-schema.js`:冻结顶层键集合、`schemaVersion`、宽容解析(保留未知嵌套字段、丢弃未知顶层键); - `sync/graph-snapshot-upgrade.js`:`upgradeGraphSnapshotOnRead` 就地升级链(单调、幂等、不降级、不抛错),已接入 `buildGraphFromSnapshot` 加载路径; - `runtime/identity-resolver.js`:活动身份、图谱身份、排队身份和 marker 身份分离; -- `sync/persistence-reducer.js`:accepted / queued / pending 持久化状态机和事件 reducer; -- `graph/graph-head.js`:GraphHead、ReplicaPointer 和 commit marker 纯模型。 +- `sync/persistence-reducer.js`:accepted / queued / pending 持久化状态机和事件 reducer。 > 贡献提示:新增图谱数据时,请加进 `meta` / `state` / 记录对象,**不要新增耐久快照顶层键**;只有在加入一个 `upgrade-on-read` 步骤时才提升 `GRAPH_SNAPSHOT_SCHEMA_VERSION`。`tests/graph-snapshot-schema.mjs`、`tests/snapshot-forward-compat.mjs` 是该契约的长期回归保护,请勿删除。 @@ -803,8 +802,6 @@ ST-BME/ ├── graph/ # 图数据模型与领域状态 │ ├── graph.js # 节点/边 CRUD、序列化、迁移 │ ├── graph-persistence.js # 持久化常量、加载状态、身份别名 -│ ├── graph-head.js # GraphHead / ReplicaPointer / commit marker 纯模型 -│ ├── graph-v3-namespace.js # 控制面命名空间常量 │ ├── schema.js # 节点和关系 Schema │ ├── memory-scope.js # 主客观作用域与空间区域 │ ├── knowledge-state.js # 认知归属、可见性、区域状态 @@ -857,7 +854,6 @@ ST-BME/ │ ├── identity-resolver.js # 身份解析核心 │ ├── runtime-state.js │ ├── reroll-transaction-boundary.js # reroll 召回复用事务边界 -│ ├── rebirth-policy.mjs # v3 重生策略/门禁盘点 │ ├── settings-defaults.js │ ├── generation-options.js │ ├── planner-tag-utils.js @@ -874,9 +870,7 @@ ST-BME/ │ ├── persistence-reducer.js # 持久化 accepted/queued/pending reducer │ ├── legacy-persistence-repair.js # 旧状态安全修复策略 │ ├── graph-snapshot-schema.js # 耐久快照契约:冻结顶层键 + 宽容解析 -│ ├── graph-snapshot-upgrade.js # 快照 upgrade-on-read 就地升级链 -│ ├── graph-store-contract.js # GraphStore 契约和路由计划 -│ └── graph-store-v3-adapter.js # GraphStore head/marker 适配包装层 +│ └── graph-snapshot-upgrade.js # 快照 upgrade-on-read 就地升级链 │ ├── host/ # SillyTavern 宿主适配 │ ├── event-binding.js @@ -936,14 +930,10 @@ npm run test:p0 控制面与数据格式专项: ```bash -npm run test:rebirth-phase0 npm run test:identity-resolver npm run test:persistence-reducer -npm run test:graph-head npm run test:vector-gate npm run test:reroll-transaction-boundary -npm run test:graph-store-contract -npm run test:graph-store-v3-adapter npm run test:graph-snapshot-schema npm run test:graph-snapshot-upgrade npm run test:snapshot-forward-compat @@ -998,11 +988,11 @@ npm run version:bump-manifest - **`tests/graph-persistence.mjs`** - 图谱持久化基础行为。 -- **`tests/identity-resolver.mjs` / `tests/persistence-reducer.mjs` / `tests/graph-head.mjs`** - - v3 身份、持久化状态机和 GraphHead 控制面。 +- **`tests/identity-resolver.mjs` / `tests/persistence-reducer.mjs`** + - 身份解析核心、持久化 accepted/queued/pending 状态机。 -- **`tests/graph-store-contract.mjs` / `tests/graph-store-v3-adapter.mjs`** - - v3 GraphStore 契约、命名空间隔离和适配器包装层。 +- **`tests/graph-snapshot-schema.mjs` / `tests/snapshot-forward-compat.mjs`** + - 耐久快照契约、宽容解析和真实存储向前兼容往返。 - **`tests/indexeddb-persistence.mjs`** - IndexedDB 快照、增量提交、hydrate。 diff --git a/graph/graph-head.js b/graph/graph-head.js deleted file mode 100644 index cff60b6..0000000 --- a/graph/graph-head.js +++ /dev/null @@ -1,261 +0,0 @@ -// ST-BME v3 GraphHead model. -// -// Pure helpers only. Phase 3 introduces the v3 data shape without switching -// storage routes. A GraphHead owns graph identity/revision/counts; replicas and -// commit markers are pointers to that head instead of competing authorities. - -import { isAcceptedLegacyPersistenceTier } from "../sync/legacy-persistence-repair.js"; -import { normalizeIdentityValue } from "../runtime/identity-resolver.js"; -import { getGraphStats } from "./graph.js"; - -export const GRAPH_HEAD_FORMAT_VERSION = 3; -export const GRAPH_REPLICA_POINTER_FORMAT_VERSION = 3; -export const GRAPH_COMMIT_MARKER_V3_FORMAT_VERSION = 3; - -function normalizeNonNegativeInteger(value = 0) { - const numeric = Number(value || 0); - if (!Number.isFinite(numeric) || numeric <= 0) return 0; - return Math.floor(numeric); -} - -function normalizeFloor(value = -1) { - const numeric = Number(value); - if (!Number.isFinite(numeric)) return -1; - return Math.floor(numeric); -} - -function normalizeUpdatedAt(value = "") { - return String(value || new Date().toISOString()); -} - -function normalizeCounts(value = {}) { - return { - nodeCount: normalizeNonNegativeInteger(value.nodeCount ?? value.nodes), - edgeCount: normalizeNonNegativeInteger(value.edgeCount ?? value.edges), - archivedCount: normalizeNonNegativeInteger(value.archivedCount ?? value.archivedNodes), - tombstoneCount: normalizeNonNegativeInteger(value.tombstoneCount ?? value.tombstones), - }; -} - -function firstIdentity(...values) { - for (const value of values) { - const normalized = normalizeIdentityValue(value); - if (normalized) return normalized; - } - return ""; -} - -export function normalizeGraphHead(input = null, fallback = {}) { - const source = input && typeof input === "object" && !Array.isArray(input) ? input : {}; - const fallbackSource = - fallback && typeof fallback === "object" && !Array.isArray(fallback) ? fallback : {}; - const counts = normalizeCounts({ - ...(fallbackSource.counts || fallbackSource), - ...(source.counts || source), - }); - const integrity = firstIdentity(source.integrity, fallbackSource.integrity); - const chatId = firstIdentity(source.chatId, fallbackSource.chatId); - const graphId = firstIdentity(source.graphId, fallbackSource.graphId, integrity, chatId); - - return { - formatVersion: GRAPH_HEAD_FORMAT_VERSION, - graphId, - chatId, - hostChatId: firstIdentity(source.hostChatId, fallbackSource.hostChatId), - integrity, - revision: normalizeNonNegativeInteger(source.revision ?? fallbackSource.revision), - schemaVersion: normalizeNonNegativeInteger( - source.schemaVersion ?? fallbackSource.schemaVersion, - ), - lastProcessedAssistantFloor: normalizeFloor( - source.lastProcessedAssistantFloor ?? fallbackSource.lastProcessedAssistantFloor, - ), - extractionCount: normalizeNonNegativeInteger( - source.extractionCount ?? fallbackSource.extractionCount, - ), - counts, - updatedAt: normalizeUpdatedAt(source.updatedAt || fallbackSource.updatedAt), - reason: String(source.reason || fallbackSource.reason || ""), - }; -} - -export function buildGraphHeadFromGraph( - graph = null, - { - graphId = "", - chatId = "", - hostChatId = "", - integrity = "", - revision = 0, - reason = "", - updatedAt = "", - } = {}, -) { - const stats = graph ? getGraphStats(graph) : null; - const historyState = graph?.historyState || {}; - return normalizeGraphHead({ - graphId, - chatId: firstIdentity(chatId, historyState.chatId), - hostChatId, - integrity, - revision, - schemaVersion: graph?.version, - lastProcessedAssistantFloor: Number.isFinite(Number(historyState.lastProcessedAssistantFloor)) - ? Number(historyState.lastProcessedAssistantFloor) - : Number.isFinite(Number(stats?.lastProcessedSeq)) - ? Number(stats.lastProcessedSeq) - : -1, - extractionCount: historyState.extractionCount, - counts: { - nodeCount: stats?.activeNodes, - edgeCount: stats?.totalEdges, - archivedCount: stats?.archivedNodes, - tombstoneCount: stats?.tombstones, - }, - updatedAt, - reason, - }); -} - -export function normalizeReplicaPointer(input = null, fallback = {}) { - const source = input && typeof input === "object" && !Array.isArray(input) ? input : {}; - const fallbackSource = - fallback && typeof fallback === "object" && !Array.isArray(fallback) ? fallback : {}; - const storageTier = String(source.storageTier || fallbackSource.storageTier || "none") - .trim() - .toLowerCase() || "none"; - const revision = normalizeNonNegativeInteger(source.revision ?? fallbackSource.revision); - const graphId = firstIdentity(source.graphId, fallbackSource.graphId); - const chatId = firstIdentity(source.chatId, fallbackSource.chatId); - const integrity = firstIdentity(source.integrity, fallbackSource.integrity); - const accepted = - source.accepted === true && - revision > 0 && - Boolean(graphId) && - isAcceptedLegacyPersistenceTier(storageTier); - - return { - formatVersion: GRAPH_REPLICA_POINTER_FORMAT_VERSION, - graphId, - revision, - storageTier, - accepted, - chatId, - integrity, - persistedAt: String(source.persistedAt || source.updatedAt || fallbackSource.persistedAt || ""), - source: String(source.source || fallbackSource.source || ""), - reason: String(source.reason || fallbackSource.reason || ""), - }; -} - -export function isReplicaAccepted(pointer = null) { - return normalizeReplicaPointer(pointer).accepted === true; -} - -export function buildCommitMarkerV3({ head = null, replica = null, reason = "", persistedAt = "" } = {}) { - const normalizedHead = normalizeGraphHead(head); - const normalizedReplica = normalizeReplicaPointer(replica, { - graphId: normalizedHead.graphId, - revision: normalizedHead.revision, - chatId: normalizedHead.chatId, - integrity: normalizedHead.integrity, - reason, - persistedAt, - }); - const replicaMatchesHead = - normalizedReplica.accepted === true && - normalizedReplica.graphId === normalizedHead.graphId && - normalizedReplica.revision === normalizedHead.revision; - return { - formatVersion: GRAPH_COMMIT_MARKER_V3_FORMAT_VERSION, - graphId: normalizedHead.graphId, - revision: normalizedHead.revision, - accepted: replicaMatchesHead, - storageTier: normalizedReplica.storageTier, - chatId: normalizedHead.chatId || normalizedReplica.chatId, - hostChatId: normalizedHead.hostChatId, - integrity: normalizedHead.integrity || normalizedReplica.integrity, - nodeCount: normalizedHead.counts.nodeCount, - edgeCount: normalizedHead.counts.edgeCount, - archivedCount: normalizedHead.counts.archivedCount, - tombstoneCount: normalizedHead.counts.tombstoneCount, - lastProcessedAssistantFloor: normalizedHead.lastProcessedAssistantFloor, - extractionCount: normalizedHead.extractionCount, - persistedAt: normalizedReplica.persistedAt || persistedAt || normalizedHead.updatedAt, - reason: String(reason || normalizedReplica.reason || normalizedHead.reason || ""), - }; -} - -export function normalizeCommitMarkerV3(marker = null) { - if (!marker || typeof marker !== "object" || Array.isArray(marker)) return null; - const head = normalizeGraphHead({ - graphId: marker.graphId, - chatId: marker.chatId, - hostChatId: marker.hostChatId, - integrity: marker.integrity, - revision: marker.revision, - lastProcessedAssistantFloor: marker.lastProcessedAssistantFloor, - extractionCount: marker.extractionCount, - counts: marker, - updatedAt: marker.persistedAt, - reason: marker.reason, - }); - const replica = normalizeReplicaPointer({ - graphId: head.graphId, - revision: head.revision, - storageTier: marker.storageTier, - accepted: marker.accepted, - chatId: head.chatId, - integrity: head.integrity, - persistedAt: marker.persistedAt, - reason: marker.reason, - }); - return buildCommitMarkerV3({ head, replica, reason: marker.reason, persistedAt: marker.persistedAt }); -} - -export function graphHeadFromLegacyPersistenceMeta({ meta = null, graph = null } = {}) { - const legacyMeta = meta && typeof meta === "object" && !Array.isArray(meta) ? meta : {}; - return buildGraphHeadFromGraph(graph, { - graphId: legacyMeta.graphId, - chatId: legacyMeta.chatId, - integrity: legacyMeta.integrity, - revision: legacyMeta.revision, - reason: legacyMeta.reason, - updatedAt: legacyMeta.updatedAt, - }); -} - -export function graphHeadFromLegacyCommitMarker(marker = null) { - return normalizeGraphHead({ - graphId: marker?.graphId, - chatId: marker?.chatId, - integrity: marker?.integrity, - revision: marker?.revision, - lastProcessedAssistantFloor: marker?.lastProcessedAssistantFloor, - extractionCount: marker?.extractionCount, - counts: marker, - updatedAt: marker?.persistedAt, - reason: marker?.reason, - }); -} - -// Test/importer/diagnostic bridge only. Do not use this in v3 runtime hot paths; -// v3 storage routes should write v3 GraphHead/ReplicaPointer directly. -export function commitMarkerV3ToLegacyMarker(marker = null) { - const normalized = normalizeCommitMarkerV3(marker); - if (!normalized) return null; - return { - revision: normalized.revision, - lastProcessedAssistantFloor: normalized.lastProcessedAssistantFloor, - extractionCount: normalized.extractionCount, - nodeCount: normalized.nodeCount, - edgeCount: normalized.edgeCount, - archivedCount: normalized.archivedCount, - persistedAt: normalized.persistedAt, - storageTier: normalized.storageTier, - accepted: normalized.accepted, - reason: normalized.reason, - chatId: normalized.chatId, - integrity: normalized.integrity, - }; -} diff --git a/graph/graph-v3-namespace.js b/graph/graph-v3-namespace.js deleted file mode 100644 index 3ec52d8..0000000 --- a/graph/graph-v3-namespace.js +++ /dev/null @@ -1,99 +0,0 @@ -// ST-BME v3 hard-cut namespace constants. -// -// These constants intentionally do not alias legacy st_bme/st-bme/STBME keys. -// Phase 6 introduces the namespace contract only; live routes are ported later. - -export const GRAPH_V3_NAMESPACE_VERSION = 3; -export const GRAPH_V3_MODULE_NAME = "st_bme_v3"; - -export const GRAPH_V3_METADATA_KEY = `${GRAPH_V3_MODULE_NAME}_graph`; -export const GRAPH_V3_HEAD_KEY = `${GRAPH_V3_MODULE_NAME}_graph_head`; -export const GRAPH_V3_COMMIT_MARKER_KEY = `${GRAPH_V3_MODULE_NAME}_commit_marker`; -export const GRAPH_V3_CHAT_STATE_NAMESPACE = `${GRAPH_V3_MODULE_NAME}_graph_state`; -export const GRAPH_V3_LUKER_MANIFEST_NAMESPACE = `${GRAPH_V3_MODULE_NAME}_graph_manifest`; -export const GRAPH_V3_LUKER_JOURNAL_NAMESPACE = `${GRAPH_V3_MODULE_NAME}_graph_journal`; -export const GRAPH_V3_LUKER_CHECKPOINT_NAMESPACE = `${GRAPH_V3_MODULE_NAME}_graph_checkpoint`; -export const GRAPH_V3_SHADOW_SNAPSHOT_STORAGE_PREFIX = `${GRAPH_V3_MODULE_NAME}:graph-shadow:`; -export const GRAPH_V3_IDENTITY_ALIAS_STORAGE_KEY = `${GRAPH_V3_MODULE_NAME}:chat-identity-aliases`; - -export const GRAPH_V3_INDEXEDDB_NAME_PREFIX = "ST_BME_V3"; -export const GRAPH_V3_OPFS_ROOT_DIRECTORY_NAME = "stbme-v3"; -export const GRAPH_V3_AUTHORITY_TABLES = Object.freeze({ - meta: `${GRAPH_V3_MODULE_NAME}_graph_meta`, - nodes: `${GRAPH_V3_MODULE_NAME}_graph_nodes`, - edges: `${GRAPH_V3_MODULE_NAME}_graph_edges`, - tombstones: `${GRAPH_V3_MODULE_NAME}_graph_tombstones`, -}); - -export const GRAPH_LEGACY_NAMESPACE_VALUES = Object.freeze([ - "st_bme", - "st_bme_graph", - "st_bme_commit_marker", - "st_bme_graph_state", - "st_bme_graph_manifest", - "st_bme_graph_journal", - "st_bme_graph_checkpoint", - "st_bme:graph-shadow:", - "st_bme:chat-identity-aliases", - "STBME_", - "st-bme", - "st_bme_graph_meta", - "st_bme_graph_nodes", - "st_bme_graph_edges", - "st_bme_graph_tombstones", -]); - -function normalizeNamespaceSegment(value = "") { - return String(value ?? "") - .trim() - .replace(/[^a-zA-Z0-9_-]+/g, "_") - .replace(/^_+|_+$/g, "") || "default"; -} - -export function buildGraphV3IndexedDbName(chatId = "") { - return `${GRAPH_V3_INDEXEDDB_NAME_PREFIX}_${normalizeNamespaceSegment(chatId)}`; -} - -export function buildGraphV3OpfsChatPath(chatId = "") { - return `${GRAPH_V3_OPFS_ROOT_DIRECTORY_NAME}/chats/${normalizeNamespaceSegment(chatId)}`; -} - -export function buildGraphV3AuthorityPartition(graphId = "") { - return `${GRAPH_V3_MODULE_NAME}:${normalizeNamespaceSegment(graphId)}`; -} - -export function listGraphV3NamespaceValues() { - return Object.freeze([ - GRAPH_V3_MODULE_NAME, - GRAPH_V3_METADATA_KEY, - GRAPH_V3_HEAD_KEY, - GRAPH_V3_COMMIT_MARKER_KEY, - GRAPH_V3_CHAT_STATE_NAMESPACE, - GRAPH_V3_LUKER_MANIFEST_NAMESPACE, - GRAPH_V3_LUKER_JOURNAL_NAMESPACE, - GRAPH_V3_LUKER_CHECKPOINT_NAMESPACE, - GRAPH_V3_SHADOW_SNAPSHOT_STORAGE_PREFIX, - GRAPH_V3_IDENTITY_ALIAS_STORAGE_KEY, - GRAPH_V3_INDEXEDDB_NAME_PREFIX, - GRAPH_V3_OPFS_ROOT_DIRECTORY_NAME, - ...Object.values(GRAPH_V3_AUTHORITY_TABLES), - ]); -} - -export function validateGraphV3NamespaceIsolation(legacyValues = GRAPH_LEGACY_NAMESPACE_VALUES) { - const legacy = new Set((Array.isArray(legacyValues) ? legacyValues : []).map((value) => String(value))); - const conflicts = listGraphV3NamespaceValues().filter((value) => legacy.has(String(value))); - const unsafePrefixConflicts = []; - if (GRAPH_V3_INDEXEDDB_NAME_PREFIX.startsWith("STBME_")) { - unsafePrefixConflicts.push({ surface: "indexeddb", legacyPrefix: "STBME_" }); - } - if (GRAPH_V3_OPFS_ROOT_DIRECTORY_NAME.startsWith("st-bme")) { - unsafePrefixConflicts.push({ surface: "opfs", legacyPrefix: "st-bme" }); - } - return { - isolated: conflicts.length === 0 && unsafePrefixConflicts.length === 0, - conflicts, - unsafePrefixConflicts, - namespaceVersion: GRAPH_V3_NAMESPACE_VERSION, - }; -} diff --git a/package.json b/package.json index 2db4c22..d04cfb6 100644 --- a/package.json +++ b/package.json @@ -6,12 +6,8 @@ "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:identity-resolver": "node tests/identity-resolver.mjs", "test:persistence-reducer": "node tests/persistence-reducer.mjs", - "test:graph-head": "node tests/graph-head.mjs", - "test:graph-store-contract": "node tests/graph-store-contract.mjs", - "test:graph-store-v3-adapter": "node tests/graph-store-v3-adapter.mjs", "test:graph-snapshot-schema": "node tests/graph-snapshot-schema.mjs", "test:graph-snapshot-upgrade": "node tests/graph-snapshot-upgrade.mjs", "test:snapshot-forward-compat": "node tests/snapshot-forward-compat.mjs", @@ -41,8 +37,7 @@ "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", - "rebirth:inventory": "node scripts/rebirth-phase0-inventory.mjs" + "check": "node scripts/check-syntax.mjs" }, "dependencies": { "triviumdb": "0.7.1" diff --git a/runtime/rebirth-policy.mjs b/runtime/rebirth-policy.mjs deleted file mode 100644 index 92dd6fe..0000000 --- a/runtime/rebirth-policy.mjs +++ /dev/null @@ -1,128 +0,0 @@ -// 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"; -} diff --git a/scripts/rebirth-phase0-inventory.mjs b/scripts/rebirth-phase0-inventory.mjs deleted file mode 100644 index b7ea967..0000000 --- a/scripts/rebirth-phase0-inventory.mjs +++ /dev/null @@ -1,38 +0,0 @@ -#!/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}`); -} diff --git a/sync/graph-store-contract.js b/sync/graph-store-contract.js deleted file mode 100644 index 0c554f7..0000000 --- a/sync/graph-store-contract.js +++ /dev/null @@ -1,140 +0,0 @@ -// ST-BME v3 GraphStore contract and pure router shell. -// -// Phase 6 only defines/validates the contract and route plans. Live adapters are -// ported in Phase 7 so durable routing is not switched accidentally. - -export const GRAPH_STORE_CONTRACT_VERSION = 3; - -export const GRAPH_STORE_KINDS = Object.freeze({ - AUTHORITY: "authority", - OPFS: "opfs", - INDEXEDDB: "indexeddb", - LUKER_CHAT_STATE: "luker-chat-state", - NONE: "none", -}); - -export const GRAPH_STORE_REQUIRED_METHODS = Object.freeze([ - "open", - "close", - "getMeta", - "patchMeta", - "commitDelta", - "exportSnapshot", - "exportSnapshotProbe", - "importSnapshot", -]); - -export const GRAPH_STORE_OPTIONAL_METHODS = Object.freeze([ - "readHead", - "writeHead", - "readCommitMarker", - "writeCommitMarker", - "isEmpty", - "deleteAll", -]); - -function normalizeStoreKind(value = "") { - const kind = String(value || "").trim().toLowerCase(); - if (Object.values(GRAPH_STORE_KINDS).includes(kind)) return kind; - return GRAPH_STORE_KINDS.NONE; -} - -function methodExists(store = null, method = "") { - return store && typeof store[method] === "function"; -} - -export function inspectGraphStoreContract(store = null, options = {}) { - const requiredMethods = Array.isArray(options.requiredMethods) - ? options.requiredMethods - : GRAPH_STORE_REQUIRED_METHODS; - const optionalMethods = Array.isArray(options.optionalMethods) - ? options.optionalMethods - : GRAPH_STORE_OPTIONAL_METHODS; - const missingMethods = requiredMethods.filter((method) => !methodExists(store, method)); - const supportedOptionalMethods = optionalMethods.filter((method) => methodExists(store, method)); - return { - contractVersion: GRAPH_STORE_CONTRACT_VERSION, - valid: missingMethods.length === 0, - storeKind: normalizeStoreKind(store?.storeKind || store?.kind), - storeMode: String(store?.storeMode || store?.mode || ""), - missingMethods, - supportedOptionalMethods, - }; -} - -export function assertGraphStoreContract(store = null, options = {}) { - const inspection = inspectGraphStoreContract(store, options); - if (!inspection.valid) { - const error = new Error(`graph-store-contract-invalid:${inspection.missingMethods.join(",")}`); - error.code = "graph_store_contract_invalid"; - error.contract = inspection; - throw error; - } - return inspection; -} - -function normalizeBoolean(value) { - return value === true; -} - -function normalizePreference(value = "") { - const normalized = String(value || "").trim().toLowerCase(); - if (normalized === "authority-sql") return GRAPH_STORE_KINDS.AUTHORITY; - if (normalized === "opfs-primary" || normalized === "opfs-shadow") return GRAPH_STORE_KINDS.OPFS; - if (normalized === "indexeddb") return GRAPH_STORE_KINDS.INDEXEDDB; - if (normalized === "luker-chat-state") return GRAPH_STORE_KINDS.LUKER_CHAT_STATE; - return "auto"; -} - -function pushUniqueRoute(routes, kind, reason = "") { - const normalizedKind = normalizeStoreKind(kind); - if (!normalizedKind || normalizedKind === GRAPH_STORE_KINDS.NONE) return; - if (routes.some((route) => route.kind === normalizedKind)) return; - routes.push({ kind: normalizedKind, reason: String(reason || normalizedKind) }); -} - -export function planGraphStoreRoute(input = {}) { - const preference = normalizePreference(input.preference || input.primaryStorageTier || input.localStoreMode); - const capabilities = input.capabilities && typeof input.capabilities === "object" ? input.capabilities : {}; - const environment = input.environment && typeof input.environment === "object" ? input.environment : {}; - const hardCutNamespace = input.hardCutNamespace && typeof input.hardCutNamespace === "object" - ? input.hardCutNamespace - : null; - const routes = []; - - const authorityReady = normalizeBoolean(capabilities.authoritySqlReady || capabilities.storagePrimaryReady); - const opfsReady = normalizeBoolean(capabilities.opfsReady || capabilities.opfsAvailable); - const indexedDbReady = - normalizeBoolean(capabilities.indexedDbReady) || normalizeBoolean(capabilities.indexedDbAvailable); - const lukerReady = normalizeBoolean(environment.lukerChatStateReady || capabilities.lukerChatStateReady); - - if (preference === GRAPH_STORE_KINDS.AUTHORITY && authorityReady) { - pushUniqueRoute(routes, GRAPH_STORE_KINDS.AUTHORITY, "preferred-authority-sql"); - } - if (preference === GRAPH_STORE_KINDS.OPFS && opfsReady) { - pushUniqueRoute(routes, GRAPH_STORE_KINDS.OPFS, "preferred-opfs"); - } - if (preference === GRAPH_STORE_KINDS.INDEXEDDB && indexedDbReady) { - pushUniqueRoute(routes, GRAPH_STORE_KINDS.INDEXEDDB, "preferred-indexeddb"); - } - if (preference === GRAPH_STORE_KINDS.LUKER_CHAT_STATE && lukerReady) { - pushUniqueRoute(routes, GRAPH_STORE_KINDS.LUKER_CHAT_STATE, "preferred-luker-chat-state"); - } - - if (authorityReady) pushUniqueRoute(routes, GRAPH_STORE_KINDS.AUTHORITY, "authority-sql-ready"); - if (opfsReady) pushUniqueRoute(routes, GRAPH_STORE_KINDS.OPFS, "opfs-ready"); - if (indexedDbReady) pushUniqueRoute(routes, GRAPH_STORE_KINDS.INDEXEDDB, "indexeddb-ready"); - if (lukerReady) pushUniqueRoute(routes, GRAPH_STORE_KINDS.LUKER_CHAT_STATE, "luker-chat-state-ready"); - - return { - contractVersion: GRAPH_STORE_CONTRACT_VERSION, - hardCut: true, - hotPathReadsLegacy: false, - namespace: hardCutNamespace, - primary: routes[0]?.kind || GRAPH_STORE_KINDS.NONE, - fallback: routes.slice(1).map((route) => route.kind), - routes, - blocked: routes.length === 0, - reason: routes.length ? routes[0].reason : "no-graph-store-route-ready", - }; -} diff --git a/sync/graph-store-v3-adapter.js b/sync/graph-store-v3-adapter.js deleted file mode 100644 index 6a0da1c..0000000 --- a/sync/graph-store-v3-adapter.js +++ /dev/null @@ -1,172 +0,0 @@ -// ST-BME v3 GraphStore adapter wrappers. -// -// These wrappers add v3 head/marker sidecar methods to existing stores without -// changing their legacy load/save behavior. Physical namespace cutover is handled -// by dedicated constructors/routes later. - -import { - GRAPH_V3_COMMIT_MARKER_KEY, - GRAPH_V3_HEAD_KEY, -} from "../graph/graph-v3-namespace.js"; -import { - normalizeCommitMarkerV3, - normalizeGraphHead, -} from "../graph/graph-head.js"; -import { - readGraphChatStateNamespaces, - writeGraphChatStatePayload, -} from "../graph/graph-persistence.js"; -import { assertGraphStoreContract } from "./graph-store-contract.js"; - -const GRAPH_STORE_V3_WRAPPED = Symbol.for("st-bme.graph-store-v3-wrapped"); - -function bindStoreMethod(store = null, method = "") { - const value = store?.[method]; - return typeof value === "function" ? value.bind(store) : value; -} - -export function isGraphStoreV3Wrapped(store = null) { - return Boolean(store?.[GRAPH_STORE_V3_WRAPPED]); -} - -export function wrapDbLikeGraphStoreV3(store = null) { - assertGraphStoreContract(store); - if (isGraphStoreV3Wrapped(store)) return store; - - const wrapper = Object.create(store); - Object.defineProperty(wrapper, GRAPH_STORE_V3_WRAPPED, { - value: true, - enumerable: false, - }); - - for (const key of Reflect.ownKeys(store)) { - if (key === GRAPH_STORE_V3_WRAPPED) continue; - const descriptor = Object.getOwnPropertyDescriptor(store, key); - if (descriptor) Object.defineProperty(wrapper, key, descriptor); - } - - for (const method of [ - "open", - "close", - "getMeta", - "setMeta", - "patchMeta", - "commitDelta", - "exportSnapshot", - "exportSnapshotProbe", - "importSnapshot", - "isEmpty", - "clearAll", - ]) { - if (typeof store[method] === "function") { - wrapper[method] = bindStoreMethod(store, method); - } - } - - wrapper.readHead = async ({ fallback = null } = {}) => { - const raw = await store.getMeta(GRAPH_V3_HEAD_KEY, null); - return raw == null ? fallback : normalizeGraphHead(raw, fallback || {}); - }; - - wrapper.writeHead = async (head = null, { fallback = null } = {}) => { - const normalized = normalizeGraphHead(head, fallback || {}); - await store.patchMeta({ [GRAPH_V3_HEAD_KEY]: normalized }); - return normalized; - }; - - wrapper.readCommitMarker = async ({ fallback = null } = {}) => { - const raw = await store.getMeta(GRAPH_V3_COMMIT_MARKER_KEY, null); - return normalizeCommitMarkerV3(raw) || fallback; - }; - - wrapper.writeCommitMarker = async (marker = null) => { - const normalized = normalizeCommitMarkerV3(marker); - if (!normalized) { - const error = new Error("graph-store-v3-commit-marker-invalid"); - error.code = "graph_store_v3_commit_marker_invalid"; - throw error; - } - await store.patchMeta({ [GRAPH_V3_COMMIT_MARKER_KEY]: normalized }); - return normalized; - }; - - wrapper.deleteAll = async (...args) => { - if (typeof store.clearAll !== "function") { - const error = new Error("graph-store-v3-delete-all-unavailable"); - error.code = "graph_store_v3_delete_all_unavailable"; - throw error; - } - return store.clearAll(...args); - }; - - return wrapper; -} - -export function createLukerChatStateGraphStoreV3({ - context = null, - chatStateTarget = null, - storeKind = "luker-chat-state", - storeMode = "luker-chat-state-v3", -} = {}) { - async function readNamespace(namespace = "", fallback = null) { - const payloads = await readGraphChatStateNamespaces(context, [namespace], { - target: chatStateTarget, - }); - return payloads.get(namespace) ?? fallback; - } - - async function writeNamespace(namespace = "", payload = null) { - const result = await writeGraphChatStatePayload(context, namespace, payload, { - target: chatStateTarget, - }); - if (result?.ok !== true) { - const error = new Error(result?.reason || "luker-graph-store-v3-write-failed"); - error.code = "luker_graph_store_v3_write_failed"; - error.result = result; - throw error; - } - return payload; - } - - return { - storeKind, - storeMode, - async open() { - return this; - }, - async close() {}, - async getMeta(key = "", fallbackValue = null) { - return readNamespace(String(key || ""), fallbackValue); - }, - async patchMeta(record = {}) { - const entries = Object.entries(record && typeof record === "object" ? record : {}); - for (const [key, value] of entries) { - await writeNamespace(key, value); - } - return record; - }, - async readHead({ fallback = null } = {}) { - const raw = await readNamespace(GRAPH_V3_HEAD_KEY, null); - return raw == null ? fallback : normalizeGraphHead(raw, fallback || {}); - }, - async writeHead(head = null, { fallback = null } = {}) { - const normalized = normalizeGraphHead(head, fallback || {}); - await writeNamespace(GRAPH_V3_HEAD_KEY, normalized); - return normalized; - }, - async readCommitMarker({ fallback = null } = {}) { - const raw = await readNamespace(GRAPH_V3_COMMIT_MARKER_KEY, null); - return normalizeCommitMarkerV3(raw) || fallback; - }, - async writeCommitMarker(marker = null) { - const normalized = normalizeCommitMarkerV3(marker); - if (!normalized) { - const error = new Error("luker-graph-store-v3-commit-marker-invalid"); - error.code = "luker_graph_store_v3_commit_marker_invalid"; - throw error; - } - await writeNamespace(GRAPH_V3_COMMIT_MARKER_KEY, normalized); - return normalized; - }, - }; -} diff --git a/tests/graph-head.mjs b/tests/graph-head.mjs deleted file mode 100644 index cf08e77..0000000 --- a/tests/graph-head.mjs +++ /dev/null @@ -1,180 +0,0 @@ -// ST-BME restrained rebirth — Phase 3 GraphHead model tests. - -import assert from "node:assert/strict"; -import { createEmptyGraph } from "../graph/graph.js"; -import { - GRAPH_COMMIT_MARKER_V3_FORMAT_VERSION, - GRAPH_HEAD_FORMAT_VERSION, - buildCommitMarkerV3, - buildGraphHeadFromGraph, - commitMarkerV3ToLegacyMarker, - graphHeadFromLegacyCommitMarker, - graphHeadFromLegacyPersistenceMeta, - isReplicaAccepted, - normalizeCommitMarkerV3, - normalizeGraphHead, - normalizeReplicaPointer, -} from "../graph/graph-head.js"; - -const graph = createEmptyGraph(); -graph.version = 9; -graph.historyState.chatId = "chat-a"; -graph.historyState.lastProcessedAssistantFloor = 8.9; -graph.historyState.extractionCount = 3.2; -graph.lastProcessedSeq = 7; -graph.nodes.push( - { id: "n1", type: "event", archived: false }, - { id: "n2", type: "event", archived: true }, -); -graph.edges.push({ id: "e1", from: "n1", to: "n2" }); - -const head = buildGraphHeadFromGraph(graph, { - graphId: "graph-a", - chatId: "chat-a", - hostChatId: "host-chat-a", - integrity: "integrity-a", - revision: 12.7, - reason: "unit-test", - updatedAt: "2026-01-01T00:00:00.000Z", -}); - -assert.equal(head.formatVersion, GRAPH_HEAD_FORMAT_VERSION); -assert.equal(head.graphId, "graph-a"); -assert.equal(head.chatId, "chat-a"); -assert.equal(head.hostChatId, "host-chat-a"); -assert.equal(head.integrity, "integrity-a"); -assert.equal(head.revision, 12); -assert.equal(head.schemaVersion, 9); -assert.equal(head.lastProcessedAssistantFloor, 8); -assert.equal(head.extractionCount, 3); -assert.deepEqual(head.counts, { - nodeCount: 1, - edgeCount: 1, - archivedCount: 1, - tombstoneCount: 0, -}); - -console.log(" ✓ GraphHead owns normalized graph identity, revision, and counts"); - -const acceptedPointer = normalizeReplicaPointer({ - graphId: head.graphId, - revision: head.revision, - storageTier: "authority-sql", - accepted: true, - chatId: head.chatId, - integrity: head.integrity, - persistedAt: "2026-01-01T00:00:01.000Z", -}); -assert.equal(isReplicaAccepted(acceptedPointer), true); - -const unsafePointer = normalizeReplicaPointer({ - graphId: head.graphId, - revision: head.revision, - storageTier: "metadata-full", - accepted: true, -}); -assert.equal(unsafePointer.accepted, false); -assert.equal(isReplicaAccepted(unsafePointer), false); - -const missingGraphIdPointer = normalizeReplicaPointer({ - revision: head.revision, - storageTier: "authority-sql", - accepted: true, -}); -assert.equal( - missingGraphIdPointer.accepted, - false, - "accepted replica pointers must carry graphId evidence", -); - -console.log(" ✓ ReplicaPointer accepts only canonical storage tiers"); - -const marker = buildCommitMarkerV3({ - head, - replica: acceptedPointer, - reason: "accepted-save", -}); -assert.equal(marker.formatVersion, GRAPH_COMMIT_MARKER_V3_FORMAT_VERSION); -assert.equal(marker.graphId, "graph-a"); -assert.equal(marker.revision, 12); -assert.equal(marker.accepted, true); -assert.equal(marker.storageTier, "authority-sql"); -assert.equal(marker.nodeCount, 1); -assert.equal(marker.edgeCount, 1); -assert.equal(marker.archivedCount, 1); -assert.equal(marker.lastProcessedAssistantFloor, 8); -assert.equal(marker.extractionCount, 3); - -assert.deepEqual(normalizeCommitMarkerV3(marker), marker); - -const mismatchedReplicaMarker = buildCommitMarkerV3({ - head, - replica: { - ...acceptedPointer, - revision: head.revision - 1, - }, -}); -assert.equal( - mismatchedReplicaMarker.accepted, - false, - "v3 marker must not accept head revision from a mismatched replica pointer", -); - -console.log(" ✓ v3 commit marker is a small accepted replica pointer plus head diagnostics"); - -const legacyMarker = commitMarkerV3ToLegacyMarker(marker); -assert.deepEqual(legacyMarker, { - revision: 12, - lastProcessedAssistantFloor: 8, - extractionCount: 3, - nodeCount: 1, - edgeCount: 1, - archivedCount: 1, - persistedAt: acceptedPointer.persistedAt, - storageTier: "authority-sql", - accepted: true, - reason: "accepted-save", - chatId: "chat-a", - integrity: "integrity-a", -}); - -const headFromLegacyMarker = graphHeadFromLegacyCommitMarker(legacyMarker); -assert.equal(headFromLegacyMarker.revision, 12); -assert.equal(headFromLegacyMarker.counts.nodeCount, 1); -assert.equal(headFromLegacyMarker.counts.edgeCount, 1); -assert.equal(headFromLegacyMarker.graphId, "integrity-a"); - -const headFromLegacyMeta = graphHeadFromLegacyPersistenceMeta({ - graph, - meta: { - revision: 9, - chatId: "meta-chat", - integrity: "meta-integrity", - updatedAt: "2026-01-02T00:00:00.000Z", - reason: "legacy-meta", - }, -}); -assert.equal(headFromLegacyMeta.revision, 9); -assert.equal(headFromLegacyMeta.chatId, "meta-chat"); -assert.equal(headFromLegacyMeta.graphId, "meta-integrity"); -assert.equal(headFromLegacyMeta.counts.archivedCount, 1); - -console.log(" ✓ legacy marker/meta can be converted without becoming runtime compatibility paths"); - -const normalizedHead = normalizeGraphHead({ - revision: -5, - lastProcessedAssistantFloor: "bad", - counts: { nodeCount: -1, edgeCount: 2.9 }, -}); -assert.equal(normalizedHead.revision, 0); -assert.equal(normalizedHead.lastProcessedAssistantFloor, -1); -assert.equal(normalizedHead.counts.nodeCount, 0); -assert.equal(normalizedHead.counts.edgeCount, 2); -assert.equal( - head.counts.tombstoneCount, - 0, - "tombstoneCount is reserved until a canonical tombstone collection is introduced", -); - -console.log(" ✓ GraphHead normalization is safe for malformed inputs"); -console.log("graph-head tests passed"); diff --git a/tests/graph-store-contract.mjs b/tests/graph-store-contract.mjs deleted file mode 100644 index 07b9482..0000000 --- a/tests/graph-store-contract.mjs +++ /dev/null @@ -1,132 +0,0 @@ -// ST-BME restrained rebirth — Phase 6 GraphStore contract/router shell tests. - -import assert from "node:assert/strict"; -import { - GRAPH_LEGACY_NAMESPACE_VALUES, - GRAPH_V3_AUTHORITY_TABLES, - GRAPH_V3_CHAT_STATE_NAMESPACE, - GRAPH_V3_COMMIT_MARKER_KEY, - GRAPH_V3_INDEXEDDB_NAME_PREFIX, - GRAPH_V3_LUKER_CHECKPOINT_NAMESPACE, - GRAPH_V3_LUKER_JOURNAL_NAMESPACE, - GRAPH_V3_LUKER_MANIFEST_NAMESPACE, - GRAPH_V3_METADATA_KEY, - GRAPH_V3_MODULE_NAME, - GRAPH_V3_OPFS_ROOT_DIRECTORY_NAME, - buildGraphV3AuthorityPartition, - buildGraphV3IndexedDbName, - buildGraphV3OpfsChatPath, - listGraphV3NamespaceValues, - validateGraphV3NamespaceIsolation, -} from "../graph/graph-v3-namespace.js"; -import { - GRAPH_STORE_CONTRACT_VERSION, - GRAPH_STORE_KINDS, - assertGraphStoreContract, - inspectGraphStoreContract, - planGraphStoreRoute, -} from "../sync/graph-store-contract.js"; - -const v3Values = listGraphV3NamespaceValues(); -assert.ok(v3Values.includes(GRAPH_V3_MODULE_NAME)); -assert.ok(v3Values.includes(GRAPH_V3_METADATA_KEY)); -assert.ok(v3Values.includes(GRAPH_V3_COMMIT_MARKER_KEY)); -assert.ok(v3Values.includes(GRAPH_V3_CHAT_STATE_NAMESPACE)); -assert.ok(v3Values.includes(GRAPH_V3_LUKER_MANIFEST_NAMESPACE)); -assert.ok(v3Values.includes(GRAPH_V3_LUKER_JOURNAL_NAMESPACE)); -assert.ok(v3Values.includes(GRAPH_V3_LUKER_CHECKPOINT_NAMESPACE)); -assert.ok(v3Values.includes(GRAPH_V3_INDEXEDDB_NAME_PREFIX)); -assert.ok(v3Values.includes(GRAPH_V3_OPFS_ROOT_DIRECTORY_NAME)); -assert.ok(v3Values.includes(GRAPH_V3_AUTHORITY_TABLES.meta)); - -const isolation = validateGraphV3NamespaceIsolation(); -assert.equal(isolation.isolated, true); -assert.deepEqual(isolation.conflicts, []); -assert.deepEqual(isolation.unsafePrefixConflicts, []); - -for (const value of v3Values) { - assert.equal( - GRAPH_LEGACY_NAMESPACE_VALUES.includes(value), - false, - `v3 namespace must not reuse legacy value: ${value}`, - ); -} - -assert.equal(buildGraphV3IndexedDbName("chat/a b"), "ST_BME_V3_chat_a_b"); -assert.equal(buildGraphV3OpfsChatPath("chat/a b"), "stbme-v3/chats/chat_a_b"); -assert.equal(buildGraphV3IndexedDbName("chat").startsWith("STBME_"), false); -assert.equal(buildGraphV3OpfsChatPath("chat").startsWith("st-bme"), false); -assert.equal(buildGraphV3AuthorityPartition("graph/a b"), "st_bme_v3:graph_a_b"); - -console.log(" ✓ v3 hard-cut namespaces are isolated from legacy keys"); - -function createMockStore(extra = {}) { - return { - storeKind: "authority", - storeMode: "authority-sql-primary", - async open() {}, - async close() {}, - async getMeta() {}, - async patchMeta() {}, - async commitDelta() {}, - async exportSnapshot() {}, - async exportSnapshotProbe() {}, - async importSnapshot() {}, - ...extra, - }; -} - -const contract = inspectGraphStoreContract(createMockStore({ async readHead() {} })); -assert.equal(contract.contractVersion, GRAPH_STORE_CONTRACT_VERSION); -assert.equal(contract.valid, true); -assert.equal(contract.storeKind, GRAPH_STORE_KINDS.AUTHORITY); -assert.deepEqual(contract.missingMethods, []); -assert.ok(contract.supportedOptionalMethods.includes("readHead")); -assert.doesNotThrow(() => assertGraphStoreContract(createMockStore())); - -assert.throws( - () => assertGraphStoreContract(createMockStore({ commitDelta: undefined })), - /graph-store-contract-invalid:commitDelta/, -); - -console.log(" ✓ GraphStore contract validates existing adapter-shaped stores"); - -const authorityPlan = planGraphStoreRoute({ - preference: "authority-sql", - capabilities: { authoritySqlReady: true, opfsReady: true, indexedDbReady: true }, - environment: { lukerChatStateReady: true }, - hardCutNamespace: { moduleName: GRAPH_V3_MODULE_NAME }, -}); -assert.equal(authorityPlan.hardCut, true); -assert.equal(authorityPlan.hotPathReadsLegacy, false); -assert.equal(authorityPlan.primary, GRAPH_STORE_KINDS.AUTHORITY); -assert.deepEqual(authorityPlan.fallback, [ - GRAPH_STORE_KINDS.OPFS, - GRAPH_STORE_KINDS.INDEXEDDB, - GRAPH_STORE_KINDS.LUKER_CHAT_STATE, -]); -assert.equal(authorityPlan.namespace.moduleName, GRAPH_V3_MODULE_NAME); - -const lukerPlan = planGraphStoreRoute({ - primaryStorageTier: "luker-chat-state", - capabilities: { authoritySqlReady: false, opfsReady: false, indexedDbReady: false }, - environment: { lukerChatStateReady: true }, -}); -assert.equal(lukerPlan.primary, GRAPH_STORE_KINDS.LUKER_CHAT_STATE); - -const blockedPlan = planGraphStoreRoute({ - capabilities: { authoritySqlReady: false, opfsReady: false, indexedDbReady: false }, - environment: { lukerChatStateReady: false }, -}); -assert.equal(blockedPlan.blocked, true); -assert.equal(blockedPlan.reason, "no-graph-store-route-ready"); - -const emptyCapabilityPlan = planGraphStoreRoute({}); -assert.equal( - emptyCapabilityPlan.blocked, - true, - "Phase 6 shell must not assume IndexedDB readiness when callers omit capabilities", -); - -console.log(" ✓ v3 router shell plans routes without switching live persistence"); -console.log("graph-store-contract tests passed"); diff --git a/tests/graph-store-v3-adapter.mjs b/tests/graph-store-v3-adapter.mjs deleted file mode 100644 index b3fb402..0000000 --- a/tests/graph-store-v3-adapter.mjs +++ /dev/null @@ -1,190 +0,0 @@ -// ST-BME restrained rebirth — Phase 7 v3 GraphStore adapter tests. - -import assert from "node:assert/strict"; -import { - GRAPH_V3_COMMIT_MARKER_KEY, - GRAPH_V3_HEAD_KEY, - GRAPH_V3_METADATA_KEY, -} from "../graph/graph-v3-namespace.js"; -import { - buildCommitMarkerV3, - normalizeGraphHead, - normalizeReplicaPointer, -} from "../graph/graph-head.js"; -import { GRAPH_STORE_REQUIRED_METHODS, inspectGraphStoreContract } from "../sync/graph-store-contract.js"; -import { - createLukerChatStateGraphStoreV3, - isGraphStoreV3Wrapped, - wrapDbLikeGraphStoreV3, -} from "../sync/graph-store-v3-adapter.js"; - -function createMockDbLikeStore() { - const meta = new Map(); - const calls = []; - return { - storeKind: "indexeddb", - storeMode: "indexeddb-v3-test", - meta, - calls, - async open() { - calls.push(["open"]); - return this; - }, - async close() { - calls.push(["close"]); - }, - async getMeta(key, fallbackValue = null) { - calls.push(["getMeta", key]); - return meta.has(key) ? meta.get(key) : fallbackValue; - }, - async patchMeta(record = {}) { - calls.push(["patchMeta", Object.keys(record).sort()]); - for (const [key, value] of Object.entries(record)) { - meta.set(key, value); - } - return record; - }, - async commitDelta() {}, - async exportSnapshot() {}, - async exportSnapshotProbe() {}, - async importSnapshot() {}, - async clearAll() { - calls.push(["clearAll"]); - meta.clear(); - return { ok: true }; - }, - }; -} - -const rawStore = createMockDbLikeStore(); -const wrapped = wrapDbLikeGraphStoreV3(rawStore); -assert.equal(isGraphStoreV3Wrapped(wrapped), true); -assert.equal(wrapDbLikeGraphStoreV3(wrapped), wrapped); - -const wrappedContract = inspectGraphStoreContract(wrapped); -assert.equal(wrappedContract.valid, true); -assert.ok(wrappedContract.supportedOptionalMethods.includes("readHead")); -assert.ok(wrappedContract.supportedOptionalMethods.includes("writeCommitMarker")); -assert.ok(wrappedContract.supportedOptionalMethods.includes("deleteAll")); - -const head = normalizeGraphHead({ - graphId: "graph-a", - chatId: "chat-a", - integrity: "integrity-a", - revision: 9, - counts: { nodeCount: 2, edgeCount: 1 }, -}); -const writtenHead = await wrapped.writeHead(head); -assert.equal(writtenHead.graphId, "graph-a"); -assert.deepEqual(await wrapped.readHead(), writtenHead); -assert.deepEqual(rawStore.meta.get(GRAPH_V3_HEAD_KEY), writtenHead); -assert.equal(rawStore.meta.has(GRAPH_V3_METADATA_KEY), false, "head must use dedicated v3 head key"); - -const marker = buildCommitMarkerV3({ - head, - replica: normalizeReplicaPointer({ - graphId: head.graphId, - revision: head.revision, - storageTier: "indexeddb", - accepted: true, - }), -}); -const writtenMarker = await wrapped.writeCommitMarker(marker); -assert.equal(writtenMarker.accepted, true); -assert.deepEqual(await wrapped.readCommitMarker(), writtenMarker); -assert.deepEqual(rawStore.meta.get(GRAPH_V3_COMMIT_MARKER_KEY), writtenMarker); -assert.equal(rawStore.meta.has("st_bme_commit_marker"), false, "legacy marker key must stay untouched"); - -await wrapped.deleteAll(); -assert.equal(rawStore.meta.size, 0); - -console.log(" ✓ DB-like v3 wrapper adds head/marker methods without legacy key writes"); - -class ClassBackedStore { - constructor() { - this.storeKind = "opfs"; - this.storeMode = "class-backed-test"; - this.meta = new Map(); - this.clearCount = 0; - } - - async open() { - return this; - } - - async close() {} - - async getMeta(key, fallbackValue = null) { - assert.equal(this instanceof ClassBackedStore, true, "wrapped methods must keep class instance this"); - return this.meta.has(key) ? this.meta.get(key) : fallbackValue; - } - - async patchMeta(record = {}) { - assert.equal(this instanceof ClassBackedStore, true, "patchMeta must run on the original class instance"); - for (const [key, value] of Object.entries(record)) { - this.meta.set(key, value); - } - return record; - } - - async commitDelta() {} - async exportSnapshot() {} - async exportSnapshotProbe() {} - async importSnapshot() {} - - async clearAll() { - assert.equal(this instanceof ClassBackedStore, true, "deleteAll must delegate to class-backed clearAll"); - this.clearCount += 1; - } -} - -const classBackedRaw = new ClassBackedStore(); -const classBackedWrapped = wrapDbLikeGraphStoreV3(classBackedRaw); -await classBackedWrapped.writeHead(head); -assert.equal((await classBackedWrapped.readHead()).graphId, "graph-a"); -await classBackedWrapped.deleteAll(); -assert.equal(classBackedRaw.clearCount, 1); - -console.log(" ✓ DB-like wrapper preserves class-instance method binding"); - -const chatState = new Map(); -const updatedNamespaces = []; -const lukerContext = { - getChatState(namespace) { - return chatState.get(namespace) || null; - }, - getChatStateBatch(namespaces) { - return new Map(namespaces.map((namespace) => [namespace, chatState.get(namespace) || null])); - }, - updateChatState(namespace, updater) { - updatedNamespaces.push(namespace); - const next = updater(chatState.get(namespace) || null); - chatState.set(namespace, next); - return { ok: true, updated: true }; - }, -}; - -const lukerStore = createLukerChatStateGraphStoreV3({ context: lukerContext }); -const lukerContract = inspectGraphStoreContract(lukerStore, { - requiredMethods: ["open", "close", "getMeta", "patchMeta", "readHead", "writeHead", "readCommitMarker", "writeCommitMarker"], -}); -assert.equal(lukerContract.valid, true); -for (const requiredMethod of GRAPH_STORE_REQUIRED_METHODS) { - if (["commitDelta", "exportSnapshot", "exportSnapshotProbe", "importSnapshot"].includes(requiredMethod)) { - assert.equal( - typeof lukerStore[requiredMethod], - "undefined", - `Luker thin wrapper must not claim unsupported DB-like method ${requiredMethod}`, - ); - } -} - -await lukerStore.writeHead(head); -await lukerStore.writeCommitMarker(marker); -assert.deepEqual(await lukerStore.readHead(), head); -assert.deepEqual(await lukerStore.readCommitMarker(), marker); -assert.deepEqual(updatedNamespaces, [GRAPH_V3_HEAD_KEY, GRAPH_V3_COMMIT_MARKER_KEY]); -assert.equal(chatState.has("st_bme_graph_state"), false, "Luker wrapper must not write legacy chat-state namespace"); - -console.log(" ✓ Luker v3 wrapper writes only v3 head/marker namespaces"); -console.log("graph-store-v3-adapter tests passed"); diff --git a/tests/rebirth-phase0.mjs b/tests/rebirth-phase0.mjs deleted file mode 100644 index 7df7f38..0000000 --- a/tests/rebirth-phase0.mjs +++ /dev/null @@ -1,67 +0,0 @@ -// 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");