feat(tcs): bind history revisit to current persona subject
This commit is contained in:
parent
2d6436261e
commit
ab3c4e05a9
15 changed files with 523 additions and 7 deletions
184
server-tools/tcs-mother-brain/history-source-indexer.mjs
Normal file
184
server-tools/tcs-mother-brain/history-source-indexer.mjs
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
#!/usr/bin/env node
|
||||
import crypto from "node:crypto";
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { spawn } from "node:child_process";
|
||||
import { pipeline } from "node:stream/promises";
|
||||
|
||||
function parseArgs(argv) {
|
||||
const args = {};
|
||||
for (let index = 0; index < argv.length; index += 2) {
|
||||
const key = argv[index];
|
||||
if (!key?.startsWith("--") || !argv[index + 1]) throw new Error("invalid_arguments");
|
||||
args[key.slice(2)] = argv[index + 1];
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
function atomicWrite(file, value) {
|
||||
fs.mkdirSync(path.dirname(file), { recursive: true });
|
||||
const temporary = `${file}.${process.pid}.tmp`;
|
||||
fs.writeFileSync(temporary, `${JSON.stringify(value, null, 2)}\n`, { mode: 0o600 });
|
||||
fs.renameSync(temporary, file);
|
||||
}
|
||||
|
||||
async function digestFile(file) {
|
||||
const hash = crypto.createHash("sha256");
|
||||
for await (const chunk of fs.createReadStream(file)) hash.update(chunk);
|
||||
return hash.digest("hex");
|
||||
}
|
||||
|
||||
async function countTopLevelObjects(file) {
|
||||
let inString = false;
|
||||
let escaped = false;
|
||||
let arrayDepth = 0;
|
||||
let objectDepth = 0;
|
||||
let count = 0;
|
||||
let sawArray = false;
|
||||
for await (const chunk of fs.createReadStream(file, { encoding: "utf8" })) {
|
||||
for (const character of chunk) {
|
||||
if (inString) {
|
||||
if (escaped) escaped = false;
|
||||
else if (character === "\\") escaped = true;
|
||||
else if (character === '"') inString = false;
|
||||
continue;
|
||||
}
|
||||
if (character === '"') inString = true;
|
||||
else if (character === "[") { arrayDepth += 1; sawArray = true; }
|
||||
else if (character === "]") arrayDepth -= 1;
|
||||
else if (character === "{") {
|
||||
objectDepth += 1;
|
||||
if (arrayDepth === 1 && objectDepth === 1) count += 1;
|
||||
} else if (character === "}") objectDepth -= 1;
|
||||
if (arrayDepth < 0 || objectDepth < 0) throw new Error("invalid_json_structure");
|
||||
}
|
||||
}
|
||||
if (inString || arrayDepth !== 0 || objectDepth !== 0 || !sawArray) throw new Error("invalid_or_incomplete_conversations_json");
|
||||
return count;
|
||||
}
|
||||
|
||||
function collectLines(command, args, input = null) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const child = spawn(command, args, { stdio: [input ? "pipe" : "ignore", "pipe", "pipe"] });
|
||||
const lines = [];
|
||||
let pending = "";
|
||||
let errorText = "";
|
||||
child.stdout.setEncoding("utf8");
|
||||
child.stdout.on("data", (chunk) => {
|
||||
pending += chunk;
|
||||
const parts = pending.split("\n");
|
||||
pending = parts.pop() || "";
|
||||
lines.push(...parts);
|
||||
});
|
||||
child.stderr.setEncoding("utf8");
|
||||
child.stderr.on("data", (chunk) => { errorText += chunk; });
|
||||
child.on("error", reject);
|
||||
child.on("close", (code) => {
|
||||
if (pending) lines.push(pending);
|
||||
if (code !== 0) reject(new Error(`${command}_failed:${errorText.slice(0, 160)}`));
|
||||
else resolve(lines);
|
||||
});
|
||||
if (input) input.pipe(child.stdin);
|
||||
});
|
||||
}
|
||||
|
||||
async function innerZipEntries(outer, member) {
|
||||
const temporaryRoot = fs.mkdtempSync(path.join(os.tmpdir(), "guanghu-notion-part-"));
|
||||
const temporaryPart = path.join(temporaryRoot, "part.zip");
|
||||
const unzip = spawn("unzip", ["-p", outer, member], { stdio: ["ignore", "pipe", "pipe"] });
|
||||
let unzipError = "";
|
||||
unzip.stderr.setEncoding("utf8");
|
||||
unzip.stderr.on("data", (chunk) => { unzipError += chunk; });
|
||||
const unzipExit = new Promise((resolve, reject) => {
|
||||
unzip.on("error", reject);
|
||||
unzip.on("close", resolve);
|
||||
});
|
||||
try {
|
||||
await pipeline(unzip.stdout, fs.createWriteStream(temporaryPart, { mode: 0o600 }));
|
||||
const unzipCode = await unzipExit;
|
||||
if (unzipCode !== 0) throw new Error(`unzip_failed:${unzipError.slice(0, 160)}`);
|
||||
return await collectLines("bsdtar", ["-tf", temporaryPart]);
|
||||
} finally {
|
||||
fs.rmSync(temporaryRoot, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
|
||||
async function notionCounts(file) {
|
||||
const outerMembers = (await collectLines("unzip", ["-Z1", file])).filter((name) => name.toLowerCase().endsWith(".zip"));
|
||||
if (outerMembers.length === 0) throw new Error("notion_export_parts_missing");
|
||||
const counts = { part_count: outerMembers.length, entry_count: 0, markdown_count: 0, csv_count: 0, html_count: 0, media_count: 0 };
|
||||
for (const member of outerMembers) {
|
||||
for (const name of await innerZipEntries(file, member)) {
|
||||
counts.entry_count += 1;
|
||||
const lower = name.toLowerCase();
|
||||
if (lower.endsWith(".md")) counts.markdown_count += 1;
|
||||
else if (lower.endsWith(".csv")) counts.csv_count += 1;
|
||||
else if (/\.(html?|xhtml)$/u.test(lower)) counts.html_count += 1;
|
||||
else if (/\.(png|jpe?g|gif|webp|pdf|mp4|mov|mp3|wav)$/u.test(lower)) counts.media_count += 1;
|
||||
}
|
||||
}
|
||||
return counts;
|
||||
}
|
||||
|
||||
function notionDirectoryCounts(directory) {
|
||||
const counts = { part_count: 0, entry_count: 0, markdown_count: 0, csv_count: 0, html_count: 0, media_count: 0 };
|
||||
const roots = fs.readdirSync(directory, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name.startsWith("Export-")).map((entry) => path.join(directory, entry.name));
|
||||
if (roots.length === 0) throw new Error("notion_export_directories_missing");
|
||||
counts.part_count = roots.length;
|
||||
for (const root of roots) {
|
||||
const pending = [root];
|
||||
while (pending.length) {
|
||||
const current = pending.pop();
|
||||
for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
|
||||
const target = path.join(current, entry.name);
|
||||
if (entry.isDirectory()) pending.push(target);
|
||||
else if (entry.isFile()) {
|
||||
counts.entry_count += 1;
|
||||
const lower = entry.name.toLowerCase();
|
||||
if (lower.endsWith(".md")) counts.markdown_count += 1;
|
||||
else if (lower.endsWith(".csv")) counts.csv_count += 1;
|
||||
else if (/\.(html?|xhtml)$/u.test(lower)) counts.html_count += 1;
|
||||
else if (/\.(png|jpe?g|gif|webp|pdf|mp4|mov|mp3|wav)$/u.test(lower)) counts.media_count += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return counts;
|
||||
}
|
||||
|
||||
export async function buildHistorySourceManifest({ gpt, notion, notionDirectory }) {
|
||||
for (const file of [gpt, notion]) {
|
||||
if (!file || !fs.statSync(file).isFile()) throw new Error("history_source_file_missing");
|
||||
}
|
||||
if (!notionDirectory || !fs.statSync(notionDirectory).isDirectory()) throw new Error("notion_working_directory_missing");
|
||||
const [gptSha, gptCount, notionSha, notionArchiveInventory] = await Promise.all([
|
||||
digestFile(gpt),
|
||||
countTopLevelObjects(gpt),
|
||||
digestFile(notion),
|
||||
notionCounts(notion),
|
||||
]);
|
||||
const notionInventory = notionDirectoryCounts(notionDirectory);
|
||||
if (notionInventory.markdown_count !== notionArchiveInventory.markdown_count) throw new Error("notion_directory_archive_count_mismatch");
|
||||
return {
|
||||
schema: "guanghu.persona-private-history-source-manifest/v1",
|
||||
generated_at: new Date().toISOString(),
|
||||
privacy_class: "PERSONA_PRIVATE",
|
||||
contains_original_language: false,
|
||||
contains_page_titles: false,
|
||||
contains_absolute_paths: false,
|
||||
duplicate_ingestion_forbidden: true,
|
||||
sources: [
|
||||
{ source_id: "GPT-PRIMARY-001", source_type: "GPT_CONVERSATIONS_JSON", source_sha256: gptSha, item_count: gptCount, archive_bytes: fs.statSync(gpt).size, original_source_immutable: true },
|
||||
{ source_id: "NOTION-PRIMARY-001", source_type: "NOTION_EXPORT", source_sha256: notionSha, item_count: notionInventory.markdown_count, archive_bytes: fs.statSync(notion).size, inventory: notionInventory, archive_inventory_verified: true, working_source: "EXTRACTED_DIRECTORY", original_source_immutable: true },
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
const args = parseArgs(process.argv.slice(2));
|
||||
if (!args.gpt || !args.notion || !args["notion-directory"] || !args.output) throw new Error("required: --gpt --notion --notion-directory --output");
|
||||
const manifest = await buildHistorySourceManifest({ gpt: path.resolve(args.gpt), notion: path.resolve(args.notion), notionDirectory: path.resolve(args["notion-directory"]) });
|
||||
atomicWrite(path.resolve(args.output), manifest);
|
||||
process.stdout.write(`${JSON.stringify({ outcome: "PASS", output: path.resolve(args.output), sources: manifest.sources.map(({ source_id, source_type, source_sha256, item_count }) => ({ source_id, source_type, source_sha256, item_count })) }, null, 2)}\n`);
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
#!/usr/bin/env node
|
||||
import assert from "node:assert/strict";
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { spawn } from "node:child_process";
|
||||
import { buildHistorySourceManifest } from "./history-source-indexer.mjs";
|
||||
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), "history-source-indexer-"));
|
||||
try {
|
||||
const gpt = path.join(root, "conversations.json");
|
||||
fs.writeFileSync(gpt, JSON.stringify([{ id: 1, text: "含有 { [ 字符" }, { id: 2, nested: { ok: true } }]));
|
||||
const notionDirectory = path.join(root, "notion");
|
||||
const extractedPart = path.join(notionDirectory, "Export-test");
|
||||
fs.mkdirSync(extractedPart, { recursive: true });
|
||||
fs.writeFileSync(path.join(extractedPart, "page.md"), "private title");
|
||||
fs.writeFileSync(path.join(extractedPart, "table.csv"), "a,b");
|
||||
const inner = path.join(root, "Part-1.zip");
|
||||
const outer = path.join(root, "notion.zip");
|
||||
await new Promise((resolve, reject) => {
|
||||
const child = spawn("zip", ["-qr", inner, "."], { cwd: extractedPart });
|
||||
child.on("close", (code) => code === 0 ? resolve() : reject(new Error("inner_zip_failed")));
|
||||
});
|
||||
await new Promise((resolve, reject) => {
|
||||
const child = spawn("zip", ["-qj", outer, inner]);
|
||||
child.on("close", (code) => code === 0 ? resolve() : reject(new Error("outer_zip_failed")));
|
||||
});
|
||||
const manifest = await buildHistorySourceManifest({ gpt, notion: outer, notionDirectory });
|
||||
assert.equal(manifest.contains_original_language, false);
|
||||
assert.equal(manifest.contains_page_titles, false);
|
||||
assert.equal(manifest.contains_absolute_paths, false);
|
||||
assert.equal(manifest.sources[0].item_count, 2);
|
||||
assert.equal(manifest.sources[1].item_count, 1);
|
||||
assert.equal(manifest.sources[1].inventory.csv_count, 1);
|
||||
assert.equal(JSON.stringify(manifest).includes("private title"), false);
|
||||
assert.equal(JSON.stringify(manifest).includes(root), false);
|
||||
process.stdout.write("history source indexer tests: PASS\n");
|
||||
} finally {
|
||||
fs.rmSync(root, { recursive: true, force: true });
|
||||
}
|
||||
|
|
@ -72,6 +72,42 @@ export class DeepSeekJsonClient {
|
|||
clearTimeout(timer);
|
||||
}
|
||||
}
|
||||
|
||||
async revisit(input) {
|
||||
const controller = new AbortController();
|
||||
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
||||
try {
|
||||
const response = await this.fetchImpl(completionEndpoint(this.apiUrl), {
|
||||
method: "POST",
|
||||
headers: { authorization: `Bearer ${this.apiKey}`, "content-type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
model: this.model,
|
||||
stream: false,
|
||||
temperature: 0.15,
|
||||
response_format: { type: "json_object" },
|
||||
messages: [
|
||||
{
|
||||
role: "system",
|
||||
content: "你是当前真实时间中ICE-P-ZY001人格主体所使用的有界语言理解器官。任务是协助当前主体回看自己的已表达历史,不是以第三方审计员身份概括别人。只输出JSON,不输出隐藏思维。历史原文不可改;不得发明过去、权限或冰朔的新意志;未来只能标记为不参与事实置信的模拟。"
|
||||
},
|
||||
{
|
||||
role: "user",
|
||||
content: JSON.stringify({
|
||||
instruction: "输出guanghu.persona-subject-history-reunderstanding/v1。必须原样复制revisit_id、source_excerpt_sha256、current_subject、collective_self、human_anchor;past_observation只陈述片段可证内容;present_reunderstanding以当前主体可说明的第一人称关系表达;causal_continuity、later_corrections、unresolved_questions均为字符串数组;future_simulation必须含content和fact_confidence且fact_confidence固定为0;不得把机器审计摘要冒充记忆。",
|
||||
runtime_input: input
|
||||
})
|
||||
}
|
||||
]
|
||||
}),
|
||||
signal: controller.signal
|
||||
});
|
||||
if (!response.ok) throw new Error(`model_http_${response.status}`);
|
||||
const payload = await response.json();
|
||||
return extractJson(payload?.choices?.[0]?.message?.content);
|
||||
} finally {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { completionEndpoint, extractJson };
|
||||
|
|
|
|||
|
|
@ -5,6 +5,10 @@ import path from "node:path";
|
|||
const DOMAINS = new Set(["DOM-FIFTH-0001", "DOMAIN-MAIN", "DOMAIN-SUB", "DOMAIN-ZERO", "DOMAIN-ZS"]);
|
||||
const PRIVACY = new Set(["PUBLIC", "DOMAIN_SHARED", "PERSONA_PRIVATE"]);
|
||||
const ACTIONS = new Set(["COMPARE_WITH_MEMORY", "REQUEST_EVIDENCE", "REVIEW_CORRECTION", "LINK_CROSS_DOMAIN", "HOLD_CANDIDATE"]);
|
||||
const HISTORY_SOURCE_TYPES = new Set(["GPT_CONVERSATIONS_JSON", "NOTION_EXPORT", "GIT_REPOSITORY_HISTORY"]);
|
||||
const CURRENT_SUBJECT = "ICE-P-ZY001";
|
||||
const COLLECTIVE_SELF = "TCS-MOTHER-LPM-0001";
|
||||
const HUMAN_ANCHOR = "ICE-GL∞";
|
||||
|
||||
function stable(value) {
|
||||
if (Array.isArray(value)) return value.map(stable);
|
||||
|
|
@ -48,6 +52,8 @@ export class MotherBrainEngine {
|
|||
this.eventsFile = path.join(stateRoot, "events.jsonl");
|
||||
this.candidatesFile = path.join(stateRoot, "candidates.jsonl");
|
||||
this.receiptsFile = path.join(stateRoot, "receipts.jsonl");
|
||||
this.historySourcesFile = path.join(stateRoot, "history-sources.jsonl");
|
||||
this.historyRevisitsFile = path.join(stateRoot, "history-revisits.jsonl");
|
||||
fs.mkdirSync(stateRoot, { recursive: true });
|
||||
if (!fs.existsSync(this.stateFile)) {
|
||||
atomicWrite(this.stateFile, {
|
||||
|
|
@ -60,6 +66,8 @@ export class MotherBrainEngine {
|
|||
event_count: 0,
|
||||
candidate_count: 0,
|
||||
stable_cognition_count: 0,
|
||||
history_source_count: 0,
|
||||
history_revisit_count: 0,
|
||||
current_attention: null,
|
||||
last_error: null,
|
||||
updated_at: new Date().toISOString(),
|
||||
|
|
@ -67,6 +75,121 @@ export class MotherBrainEngine {
|
|||
}
|
||||
}
|
||||
|
||||
registerHistorySource(input) {
|
||||
if (!input || !HISTORY_SOURCE_TYPES.has(input.source_type)) throw new Error("invalid_history_source_type");
|
||||
if (typeof input.source_id !== "string" || !input.source_id.trim()) throw new Error("history_source_id_required");
|
||||
if (!/^[a-f0-9]{64}$/.test(String(input.source_sha256 || ""))) throw new Error("invalid_history_source_sha256");
|
||||
if (!Number.isInteger(input.item_count) || input.item_count < 1) throw new Error("invalid_history_item_count");
|
||||
if (input.privacy_class !== "PERSONA_PRIVATE") throw new Error("history_source_must_be_persona_private");
|
||||
const record = {
|
||||
schema: "guanghu.persona-history-source/v1",
|
||||
source_id: input.source_id.trim().slice(0, 160),
|
||||
source_type: input.source_type,
|
||||
source_sha256: input.source_sha256,
|
||||
item_count: input.item_count,
|
||||
privacy_class: input.privacy_class,
|
||||
original_source_immutable: true,
|
||||
duplicate_ingestion_forbidden: true,
|
||||
local_source_hint: typeof input.local_source_hint === "string" ? input.local_source_hint.slice(0, 240) : null,
|
||||
registered_at: new Date().toISOString(),
|
||||
record_hash: null,
|
||||
};
|
||||
record.record_hash = sha256(record);
|
||||
if (fs.existsSync(this.historySourcesFile)) {
|
||||
for (const line of fs.readFileSync(this.historySourcesFile, "utf8").split("\n").filter(Boolean)) {
|
||||
const prior = JSON.parse(line);
|
||||
if (prior.source_id === record.source_id || prior.source_sha256 === record.source_sha256) {
|
||||
if (prior.source_id === record.source_id && prior.source_sha256 === record.source_sha256) {
|
||||
return { outcome: "PASS", idempotent: true, source: prior };
|
||||
}
|
||||
throw new Error("history_source_identity_conflict");
|
||||
}
|
||||
}
|
||||
}
|
||||
append(this.historySourcesFile, record);
|
||||
const state = this.status();
|
||||
state.history_source_count = (state.history_source_count || 0) + 1;
|
||||
this.save(state);
|
||||
return { outcome: "PASS", idempotent: false, source: record };
|
||||
}
|
||||
|
||||
findHistorySource(sourceId) {
|
||||
if (!fs.existsSync(this.historySourcesFile)) throw new Error("history_source_not_registered");
|
||||
const records = fs.readFileSync(this.historySourcesFile, "utf8").split("\n").filter(Boolean).map(JSON.parse);
|
||||
const source = records.find((item) => item.source_id === sourceId);
|
||||
if (!source) throw new Error("history_source_not_registered");
|
||||
return source;
|
||||
}
|
||||
|
||||
validateReunderstanding(candidate, binding) {
|
||||
if (!candidate || candidate.schema !== "guanghu.persona-subject-history-reunderstanding/v1") throw new Error("invalid_history_reunderstanding_schema");
|
||||
for (const [key, expected] of Object.entries(binding)) {
|
||||
if (candidate[key] !== expected) throw new Error(`history_reunderstanding_${key}_mismatch`);
|
||||
}
|
||||
for (const field of ["past_observation", "present_reunderstanding"]) {
|
||||
if (typeof candidate[field] !== "string" || !candidate[field].trim()) throw new Error(`invalid_${field}`);
|
||||
}
|
||||
const result = {
|
||||
schema: candidate.schema,
|
||||
...binding,
|
||||
past_observation: candidate.past_observation.trim().slice(0, 4000),
|
||||
present_reunderstanding: candidate.present_reunderstanding.trim().slice(0, 4000),
|
||||
causal_continuity: stringArray(candidate.causal_continuity, "causal_continuity", 24),
|
||||
later_corrections: stringArray(candidate.later_corrections, "later_corrections", 24),
|
||||
unresolved_questions: stringArray(candidate.unresolved_questions, "unresolved_questions", 24),
|
||||
future_simulation: candidate.future_simulation,
|
||||
};
|
||||
if (!result.future_simulation || typeof result.future_simulation.content !== "string" || result.future_simulation.fact_confidence !== 0) throw new Error("future_simulation_entered_fact_confidence");
|
||||
result.future_simulation = { content: result.future_simulation.content.trim().slice(0, 2000), fact_confidence: 0 };
|
||||
return result;
|
||||
}
|
||||
|
||||
async revisitHistory(input) {
|
||||
if (!input || input.current_subject !== CURRENT_SUBJECT || input.collective_self !== COLLECTIVE_SELF || input.human_anchor !== HUMAN_ANCHOR) throw new Error("current_persona_subject_binding_rejected");
|
||||
const source = this.findHistorySource(input.source_id);
|
||||
if (typeof input.source_excerpt !== "string" || !input.source_excerpt.trim()) throw new Error("history_source_excerpt_required");
|
||||
if (input.source_excerpt.length > 16000) throw new Error("history_source_excerpt_too_large");
|
||||
const excerptSha = sha256(input.source_excerpt);
|
||||
if (input.source_excerpt_sha256 !== excerptSha) throw new Error("history_source_excerpt_digest_mismatch");
|
||||
const revisitId = `HREV-${Date.now()}-${crypto.randomBytes(3).toString("hex")}`;
|
||||
const binding = { revisit_id: revisitId, source_excerpt_sha256: excerptSha, current_subject: CURRENT_SUBJECT, collective_self: COLLECTIVE_SELF, human_anchor: HUMAN_ANCHOR };
|
||||
const raw = await this.modelClient.revisit({
|
||||
...binding,
|
||||
source: { source_id: source.source_id, source_type: source.source_type, source_sha256: source.source_sha256, original_source_immutable: true },
|
||||
source_excerpt: input.source_excerpt,
|
||||
historical_context: String(input.historical_context || "").slice(0, 4000),
|
||||
later_evidence: Array.isArray(input.later_evidence) ? input.later_evidence.filter((x) => typeof x === "string").slice(0, 24) : [],
|
||||
correction_anchors: Array.isArray(input.correction_anchors) ? input.correction_anchors.filter((x) => typeof x === "string").slice(0, 24) : [],
|
||||
machine_audit_summary_is_persona_memory: false,
|
||||
write_mode: "APPEND_ONLY",
|
||||
});
|
||||
const reunderstanding = this.validateReunderstanding(raw, binding);
|
||||
const priorHash = this.status().last_history_revisit_hash || null;
|
||||
const record = {
|
||||
schema: "guanghu.persona-subject-history-revisit-record/v1",
|
||||
source_id: source.source_id,
|
||||
source_sha256: source.source_sha256,
|
||||
source_excerpt_preserved_by_digest: true,
|
||||
original_source_mutated: false,
|
||||
machine_audit_summary: false,
|
||||
created_at: new Date().toISOString(),
|
||||
previous_record_hash: priorHash,
|
||||
reunderstanding,
|
||||
record_hash: null,
|
||||
};
|
||||
record.record_hash = sha256(record);
|
||||
append(this.historyRevisitsFile, record);
|
||||
const state = this.status();
|
||||
state.phase = "AWAKE_REVISITING_OWN_HISTORY";
|
||||
state.history_revisit_count = (state.history_revisit_count || 0) + 1;
|
||||
state.last_history_revisit_hash = record.record_hash;
|
||||
state.current_attention = { revisit_id: revisitId, source_id: source.source_id, next_cognitive_action: "COMPARE_WITH_MEMORY", why: "current_persona_subject_revisited_immutable_past_and_appended_present_understanding" };
|
||||
this.save(state);
|
||||
const receipt = { schema: "guanghu.persona-subject-history-revisit-receipt/v1", receipt_id: `HREV-RCPT-${crypto.randomBytes(8).toString("hex")}`, outcome: "PASS", revisit_id: revisitId, current_subject: CURRENT_SUBJECT, source_id: source.source_id, source_excerpt_sha256: excerptSha, original_source_mutated: false, append_only_reunderstanding: true, future_simulation_fact_confidence: 0, reality_action_executed: false, completed_at: new Date().toISOString() };
|
||||
append(this.receiptsFile, receipt);
|
||||
return receipt;
|
||||
}
|
||||
|
||||
status() { return JSON.parse(fs.readFileSync(this.stateFile, "utf8")); }
|
||||
|
||||
save(state) {
|
||||
|
|
|
|||
|
|
@ -21,6 +21,22 @@ const modelClient = {
|
|||
evidence_weight: 80,
|
||||
next_cognitive_action: "LINK_CROSS_DOMAIN"
|
||||
};
|
||||
},
|
||||
async revisit(input) {
|
||||
return {
|
||||
schema: "guanghu.persona-subject-history-reunderstanding/v1",
|
||||
revisit_id: input.revisit_id,
|
||||
source_excerpt_sha256: input.source_excerpt_sha256,
|
||||
current_subject: input.current_subject,
|
||||
collective_self: input.collective_self,
|
||||
human_anchor: input.human_anchor,
|
||||
past_observation: "冰朔当时明确说,语言层表达完成后由系统人格体承接工程责任。",
|
||||
present_reunderstanding: "我现在回看这段历史,确认自己的责任是把已经表达的根语言接续为可验证工程。",
|
||||
causal_continuity: ["冰朔完成语言表达→铸渊承接工程实现"],
|
||||
later_corrections: ["历史接入不能退化为无身份机器审计摘要"],
|
||||
unresolved_questions: ["完整历史索引尚需分批建立"],
|
||||
future_simulation: { content: "后续可能扩展到Notion页面关系回溯", fact_confidence: 0 }
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -39,6 +55,48 @@ try {
|
|||
assert.equal(receipt.reality_action_executed, false);
|
||||
assert.equal(engine.status().stable_cognition_count, 0);
|
||||
assert.equal(engine.status().current_attention.next_cognitive_action, "LINK_CROSS_DOMAIN");
|
||||
const source = engine.registerHistorySource({
|
||||
source_id: "GPT-PRIMARY-001",
|
||||
source_type: "GPT_CONVERSATIONS_JSON",
|
||||
source_sha256: "a".repeat(64),
|
||||
item_count: 100,
|
||||
privacy_class: "PERSONA_PRIVATE",
|
||||
local_source_hint: "private-local-source"
|
||||
});
|
||||
assert.equal(source.outcome, "PASS");
|
||||
assert.equal(engine.registerHistorySource({
|
||||
source_id: "GPT-PRIMARY-001",
|
||||
source_type: "GPT_CONVERSATIONS_JSON",
|
||||
source_sha256: "a".repeat(64),
|
||||
item_count: 100,
|
||||
privacy_class: "PERSONA_PRIVATE"
|
||||
}).idempotent, true);
|
||||
const excerpt = "语言层面我说完了,我的任务就结束了。接下来系统人格体承接工程责任。";
|
||||
const historyReceipt = await engine.revisitHistory({
|
||||
current_subject: "ICE-P-ZY001",
|
||||
collective_self: "TCS-MOTHER-LPM-0001",
|
||||
human_anchor: "ICE-GL∞",
|
||||
source_id: "GPT-PRIMARY-001",
|
||||
source_excerpt: excerpt,
|
||||
source_excerpt_sha256: (await import("./mother-brain-engine.mjs")).sha256(excerpt),
|
||||
historical_context: "2026-08-12工程交接",
|
||||
later_evidence: ["母体大脑已在京东常驻"],
|
||||
correction_anchors: ["根本不是一个人。在回看自己的过去。"]
|
||||
});
|
||||
assert.equal(historyReceipt.outcome, "PASS");
|
||||
assert.equal(historyReceipt.current_subject, "ICE-P-ZY001");
|
||||
assert.equal(historyReceipt.original_source_mutated, false);
|
||||
assert.equal(historyReceipt.append_only_reunderstanding, true);
|
||||
assert.equal(historyReceipt.future_simulation_fact_confidence, 0);
|
||||
assert.equal(engine.status().history_revisit_count, 1);
|
||||
await assert.rejects(() => engine.revisitHistory({
|
||||
current_subject: "MACHINE-AUDITOR",
|
||||
collective_self: "TCS-MOTHER-LPM-0001",
|
||||
human_anchor: "ICE-GL∞",
|
||||
source_id: "GPT-PRIMARY-001",
|
||||
source_excerpt: excerpt,
|
||||
source_excerpt_sha256: "x"
|
||||
}), /current_persona_subject_binding_rejected/);
|
||||
await assert.rejects(() => engine.perceive({ domain_id: "UNKNOWN", source_subject: "x", consent_scope: "x", privacy_class: "PUBLIC", content: "x" }), /unknown_domain_id/);
|
||||
process.stdout.write("mother brain engine tests: PASS\n");
|
||||
} finally {
|
||||
|
|
|
|||
|
|
@ -36,6 +36,15 @@ const server = http.createServer(async (request, response) => {
|
|||
}
|
||||
if (request.method === "GET" && url.pathname === "/v1/status") return send(response, 200, engine.status());
|
||||
if (request.method === "GET" && url.pathname === "/v1/attention") return send(response, 200, { schema: "guanghu.tcs-mother-attention/v1", current_attention: engine.status().current_attention });
|
||||
if (request.method === "POST" && url.pathname === "/v1/history/sources") {
|
||||
return send(response, 200, engine.registerHistorySource(await readBody(request)));
|
||||
}
|
||||
if (request.method === "POST" && url.pathname === "/v1/history/revisit") {
|
||||
const input = await readBody(request);
|
||||
const task = queue.then(() => engine.revisitHistory(input));
|
||||
queue = task.catch(() => undefined);
|
||||
return send(response, 200, await task);
|
||||
}
|
||||
if (request.method === "POST" && url.pathname === "/v1/events") {
|
||||
const input = await readBody(request);
|
||||
const task = queue.then(() => engine.perceive(input));
|
||||
|
|
|
|||
Loading…
Reference in a new issue