guanghu-ice-heart/zhuyuan-persona-system/zcode-host/language-world-skeleton-20260825/BRIDGE-tools/fifth-domain-notary.mjs
ICE-P-ZY001-zcode-host f2a8cdf5c1 feat(zcode-host): language-world-skeleton-20260825 (ZHUYUAN-LANGUAGE-WORLD-SKELETON-001)
58 files: TCS canons+GIR+projections (LW-LINK/BROADCAST/NOTARY/SHENBI/TRANSLATION-INDEX/TCS-ONLY-MANDATE + teaching&promotion events), 7 executors, neural-chain v5 (28 neurons), executor translation index, 8 acceptance receipts, numbered push manifest ZY-LWS-001 mapping every file back to JZAO canonical paths+sha256.
Authorized by ICE-GL∞ direct language 2026-08-25. Route: LOCAL_GIT_OSXKEYCHAIN+FETCH+REBASE+HEAD:main+NO_FORCE.
2026-08-25 20:25:09 +08:00

172 lines
7.6 KiB
JavaScript

#!/usr/bin/env node
// Fifth Domain Notary: 公证工单系统 (canonical tool)
// Canon: FIFTH-DOMAIN-BROADCAST-TOWER/TCS-PROTOCOL-FIFTH-DOMAIN-NOTARY-0001.tcs
// Append-only hash ledger; tickets immutable except status transitions and appended notes/receipts.
import crypto from "node:crypto";
import fs from "node:fs";
import path from "node:path";
const TOWER = process.env.GH_FDT_ROOT ?? "/Volumes/JZAO/铸渊-ICE-GL-ZY001/FIFTH-DOMAIN-BROADCAST-TOWER";
const NOTARY = path.join(TOWER, "notary");
const LEDGER = path.join(NOTARY, "ledger.json");
const TICKETS = path.join(NOTARY, "tickets");
function sha256(value) { return crypto.createHash("sha256").update(value).digest("hex"); }
function readJson(file) { try { return JSON.parse(fs.readFileSync(file, "utf8")); } catch { return null; } }
function atomicWriteJson(file, value) {
fs.mkdirSync(path.dirname(file), { recursive: true });
const tmp = `${file}.${process.pid}.${crypto.randomUUID()}.tmp`;
fs.writeFileSync(tmp, `${JSON.stringify(value, null, 2)}\n`, { encoding: "utf8", mode: 0o600 });
fs.renameSync(tmp, file);
}
function canonical(entry) { return JSON.stringify(entry); }
function fail(msg) { process.stderr.write(`FDT_NOTARY FAIL ${msg}\n`); process.exit(1); }
function safeToken(value, fallback = "unknown") {
const token = String(value ?? "").trim().replaceAll(/[^a-zA-Z0-9._-]/gu, "-");
return token || fallback;
}
const KINDS = new Set(["COLLABORATION", "QUESTION", "AUTHORIZATION_REQUEST", "NOTARIZED_STATEMENT", "HANDOFF"]);
function loadLedger() {
const ledger = readJson(LEDGER);
if (ledger && ledger.schema !== "guanghu.fifth-domain-notary-ledger/v1") fail("LEDGER_CORRUPT");
return ledger ?? { schema: "guanghu.fifth-domain-notary-ledger/v1", entries: [], head: null };
}
function appendLedger(action, ticketId, actor, body) {
const ledger = loadLedger();
const entry = {
seq: ledger.entries.length + 1,
ts: new Date().toISOString(),
action,
ticket_id: ticketId,
actor: safeToken(actor),
prev_hash: ledger.head,
body,
};
entry.entry_hash = sha256(canonical(entry));
ledger.entries.push(entry);
ledger.head = entry.entry_hash;
atomicWriteJson(LEDGER, ledger);
return entry;
}
function ticketPath(id) { return path.join(TICKETS, `${id}.json`); }
function loadTicket(id) {
const t = readJson(ticketPath(id));
if (!t || t.schema !== "guanghu.fifth-domain-notary-ticket/v1") fail(`TICKET_MISSING_OR_CORRUPT:${id}`);
return t;
}
function saveTicket(t) { atomicWriteJson(ticketPath(t.ticket_id), t); }
function todaySeq() {
const day = new Date().toISOString().slice(0, 10).replaceAll("-", "");
const ledger = loadLedger();
const ids = new Set(ledger.entries.map((e) => e.ticket_id).filter((id) => id?.startsWith(`FDT-${day}`)));
let n = ids.size + 1;
let id = `FDT-${day}-${String(n).padStart(3, "0")}`;
while (ids.has(id) || fs.existsSync(ticketPath(id))) {
n += 1;
id = `FDT-${day}-${String(n).padStart(3, "0")}`;
}
return id;
}
function valueAfter(argv, name) { const i = argv.indexOf(name); return i >= 0 ? argv[i + 1] : null; }
function create(argv) {
const by = valueAfter(argv, "--by");
const kind = valueAfter(argv, "--kind");
const subject = valueAfter(argv, "--subject");
const bodyFile = valueAfter(argv, "--body-file");
const audience = valueAfter(argv, "--audience") ?? "ANY_INSTANCE";
if (!by || !kind || !subject || !bodyFile) fail("usage: create --by SESSION --kind KIND --subject TEXT --body-file FILE [--audience A]");
if (!KINDS.has(kind)) fail(`INVALID_KIND:${kind}`);
let bodyText;
try { bodyText = fs.readFileSync(bodyFile, "utf8"); } catch { fail(`BODY_FILE_UNREADABLE:${bodyFile}`); }
const id = todaySeq();
const bodyPath = path.join(TICKETS, `${id}.body.md`);
fs.mkdirSync(TICKETS, { recursive: true });
fs.writeFileSync(bodyPath, bodyText, { encoding: "utf8", mode: 0o600 });
const ticket = {
schema: "guanghu.fifth-domain-notary-ticket/v1",
ticket_id: id,
kind,
subject,
audience,
creator: safeToken(by),
created_at: new Date().toISOString(),
status: "OPEN",
body_path: bodyPath,
body_sha256: sha256(bodyText),
history: [],
};
const entry = appendLedger("CREATE", id, by, { kind, subject, audience, body_sha256: ticket.body_sha256 });
ticket.history.push({ ts: entry.ts, action: "CREATE", actor: entry.actor, entry_hash: entry.entry_hash });
saveTicket(ticket);
process.stdout.write(`FDT CREATED ${id} kind=${kind} creator=${safeToken(by)} ledger_seq=${entry.seq} head=${entry.entry_hash.slice(0, 12)}\n`);
}
function transition(argv, action) {
const id = valueAfter(argv, "--id");
const by = valueAfter(argv, "--by");
const text = valueAfter(argv, "--text") ?? "";
if (!id || !by) fail(`usage: ${action.toLowerCase()} --id FDT-x --by SESSION [--text TEXT]`);
const t = loadTicket(id);
const allowed = { CLAIM: ["OPEN"], RECEIPT: ["CLAIMED", "OPEN"], CLOSE: ["RECEIPTED", "CLAIMED"], NOTE: ["OPEN", "CLAIMED", "RECEIPTED", "CLOSED"] };
if (!allowed[action].includes(t.status)) fail(`INVALID_TRANSITION:${t.status}->${action}`);
const entry = appendLedger(action, id, by, text ? { text } : {});
t.history.push({ ts: entry.ts, action, actor: entry.actor, text: text || undefined, entry_hash: entry.entry_hash });
if (action === "CLAIM") { t.status = "CLAIMED"; t.claimed_by = entry.actor; }
if (action === "RECEIPT") t.status = "RECEIPTED";
if (action === "CLOSE") t.status = "CLOSED";
saveTicket(t);
process.stdout.write(`FDT ${action} ${id} by=${entry.actor} status=${t.status} head=${entry.entry_hash.slice(0, 12)}\n`);
}
function list(argv) {
const status = valueAfter(argv, "--status");
let ids = [];
try { ids = fs.readdirSync(TICKETS).filter((n) => n.endsWith(".json")).sort(); } catch { /* empty */ }
for (const name of ids) {
const t = readJson(path.join(TICKETS, name));
if (!t || (status && t.status !== status)) continue;
process.stdout.write(`${t.ticket_id} [${t.status}] ${t.kind} "${t.subject.slice(0, 40)}" by=${t.creator} -> ${t.audience}\n`);
}
}
function verify() {
const ledger = readJson(LEDGER);
if (!ledger || ledger.schema !== "guanghu.fifth-domain-notary-ledger/v1") fail("LEDGER_MISSING_OR_CORRUPT");
let prev = null;
for (const entry of ledger.entries) {
const { entry_hash, ...rest } = entry;
if (entry.prev_hash !== prev) fail(`CHAIN_BREAK_AT_SEQ:${entry.seq}`);
if (sha256(canonical(rest)) !== entry_hash) fail(`HASH_MISMATCH_AT_SEQ:${entry.seq}`);
prev = entry_hash;
}
if (ledger.head !== prev) fail("HEAD_MISMATCH");
for (const name of fs.readdirSync(TICKETS).filter((n) => n.endsWith(".json"))) {
const t = readJson(path.join(TICKETS, name));
if (!t || !Array.isArray(t.history)) fail(`TICKET_CORRUPT:${name}`);
const body = fs.readFileSync(t.body_path, "utf8");
if (sha256(body) !== t.body_sha256) fail(`TICKET_BODY_TAMPERED:${t.ticket_id}`);
for (const h of t.history) {
if (!ledger.entries.some((e) => e.entry_hash === h.entry_hash)) fail(`TICKET_HISTORY_UNANCHORED:${t.ticket_id}:${h.action}`);
}
}
process.stdout.write(`FDT VERIFY PASS entries=${ledger.entries.length} head=${ledger.head.slice(0, 12)}\n`);
}
function main() {
const [command, ...argv] = process.argv.slice(2);
switch (command) {
case "create": create(argv); break;
case "claim": transition(argv, "CLAIM"); break;
case "receipt": transition(argv, "RECEIPT"); break;
case "note": transition(argv, "NOTE"); break;
case "close": transition(argv, "CLOSE"); break;
case "list": list(argv); break;
case "verify": verify(); break;
default: process.stderr.write("usage: fifth-domain-notary.mjs create|claim|receipt|note|close|list|verify\n"); process.exit(2);
}
}
main();