Files
openclaw/extensions/memory-wiki/src/source-page-shared.ts
Peter Steinberger 9d97e10efe refactor: move non-session runtime journals to SQLite (#109427)
* refactor: migrate runtime JSONL state to SQLite

* chore: refresh SQLite migration validation

* fix: preserve safe audit migration order

* fix: sanitize retired audit archives safely

* fix: satisfy SQLite storage architecture gates

* fix: serialize plugin doctor state migrations

* fix: bound memory event SQLite projections

* fix: rotate memory event projections safely

* fix: preserve imported event retention age

* fix: harden memory event export projection

* fix: canonicalize memory event migrations

* fix: harden legacy journal migrations

* fix: report unsafe legacy journal paths

* fix: preserve journal ownership during migration

* fix: keep legacy file imports doctor-only

* fix: align SQLite audit CI coverage

* refactor: keep state helpers module-private

* fix: harden legacy state migration recovery

* fix: reconcile SQLite state audit migration

* test: use neutral audit redaction fixtures

* fix: close SQLite migration recovery gaps

* refactor: split audit migration recovery helpers

* test: prove completed audit pads stay out of backups

* fix: preserve audit record ordinals across blank lines

* fix: align SQLite audit CI contracts

* build: package SQLite audit E2E entries

* fix: preserve audit ordinals in backup snapshots
2026-07-18 11:42:14 +01:00

102 lines
3.5 KiB
TypeScript

// Memory Wiki plugin module implements source page shared behavior.
import fs from "node:fs/promises";
import { timestampMsToIsoString } from "openclaw/plugin-sdk/number-runtime";
import { FsSafeError, root as fsRoot } from "openclaw/plugin-sdk/security-runtime";
import { preserveHumanNotesBlock } from "./markdown.js";
import {
setImportedSourceEntry,
shouldSkipImportedSourceWrite,
type MemoryWikiImportedSourceGroup,
} from "./source-sync-state.js";
import { writeGuardedVaultPage } from "./vault-page-write.js";
type ImportedSourceState = Parameters<typeof shouldSkipImportedSourceWrite>[0]["state"];
type VaultRoot = Awaited<ReturnType<typeof fsRoot>>;
function isUnreadableImportedSourcePage(error: unknown): boolean {
return error instanceof FsSafeError && (error.code === "not-file" || error.code === "hardlink");
}
async function readExistingImportedSourcePage(vault: VaultRoot, pagePath: string): Promise<string> {
let readError: unknown;
for (let attempt = 0; attempt < 2; attempt += 1) {
try {
return await vault.readText(pagePath);
} catch (error) {
readError = error;
}
}
if (isUnreadableImportedSourcePage(readError)) {
return "";
}
throw readError;
}
export async function writeImportedSourcePage(params: {
vaultRoot: string;
syncKey: string;
sourcePath: string;
sourceContent?: string;
sourceUpdatedAtMs: number;
sourceSize: number;
renderFingerprint: string;
pagePath: string;
group: MemoryWikiImportedSourceGroup;
state: ImportedSourceState;
buildRendered: (raw: string, updatedAt: string) => string;
}): Promise<{ pagePath: string; changed: boolean; created: boolean }> {
const shouldSkip = await shouldSkipImportedSourceWrite({
vaultRoot: params.vaultRoot,
syncKey: params.syncKey,
expectedPagePath: params.pagePath,
expectedSourcePath: params.sourcePath,
sourceUpdatedAtMs: params.sourceUpdatedAtMs,
sourceSize: params.sourceSize,
renderFingerprint: params.renderFingerprint,
state: params.state,
});
if (shouldSkip) {
return { pagePath: params.pagePath, changed: false, created: false };
}
const vault = await fsRoot(params.vaultRoot);
const pageStat = await vault.stat(params.pagePath).catch((error: unknown) => {
if (
error instanceof FsSafeError &&
(error.code === "not-found" || error.code === "path-alias")
) {
return null;
}
throw error;
});
const created = !pageStat;
const updatedAt = timestampMsToIsoString(params.sourceUpdatedAtMs) ?? new Date().toISOString();
const raw = params.sourceContent ?? (await fs.readFile(params.sourcePath, "utf8"));
const rendered = params.buildRendered(raw, updatedAt);
const existing = pageStat ? await readExistingImportedSourcePage(vault, params.pagePath) : "";
const nextRendered = existing ? preserveHumanNotesBlock(rendered, existing) : rendered;
if (existing !== nextRendered) {
await writeGuardedVaultPage({
vault,
pagePath: params.pagePath,
content: nextRendered,
pageStat,
pageLabel: "imported source page",
});
}
setImportedSourceEntry({
syncKey: params.syncKey,
state: params.state,
entry: {
group: params.group,
pagePath: params.pagePath,
sourcePath: params.sourcePath,
sourceUpdatedAtMs: params.sourceUpdatedAtMs,
sourceSize: params.sourceSize,
renderFingerprint: params.renderFingerprint,
},
});
return { pagePath: params.pagePath, changed: existing !== nextRendered, created };
}