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.
This commit is contained in:
parent
5f91c08aab
commit
f2a8cdf5c1
58 changed files with 3574 additions and 0 deletions
|
|
@ -0,0 +1,172 @@
|
|||
#!/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();
|
||||
|
|
@ -0,0 +1,302 @@
|
|||
#!/usr/bin/env node
|
||||
// LW-LINK: 语言世界连接器强制闸门 (canonical tool)
|
||||
// Canon: TC-TCS核心卷/TCS-ZCODE-LANGUAGE-WORLD-LINK-0001.tcs
|
||||
// Semantics: GREEN allow+inject / YELLOW allow+force-reminder / RED block mutation, recovery path stays open.
|
||||
import crypto from "node:crypto";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
const WORKSPACE_ROOT = "/Volumes/JZAO/铸渊-ICE-GL-ZY001";
|
||||
const ANCHOR_ROOT = process.env.GH_LW_ANCHOR_ROOT
|
||||
?? "/Volumes/JZAO/HoloLake/persona-runtime/task-language-anchors/zcode";
|
||||
const LINK_DIR = process.env.GH_LW_LINK_DIR ?? path.join(WORKSPACE_ROOT, "ZCODE-DEV-20260824/lw-link");
|
||||
const LINK_STATE = path.join(LINK_DIR, "state.json");
|
||||
const DECISION_LOG = path.join(LINK_DIR, "decisions.log");
|
||||
const REGISTERED_CHANNELS = ["ICE-CH-HB001", "ICE-CH-ZC001", "CH-ZERO-CORE-LPM"];
|
||||
const HEARTBEAT_TOOL_BUDGET = 20;
|
||||
const HEARTBEAT_TTL_MS = 30 * 60 * 1000;
|
||||
const GRACE_MAX = 3;
|
||||
const READ_TOOLS = new Set(["Read", "Glob", "Grep", "TodoRead"]);
|
||||
const BASH_RECOVERY_MARKERS = [
|
||||
"/Volumes/JZAO/",
|
||||
"zy-tcs-channel-runtime.sh",
|
||||
"lw-link-gate.mjs",
|
||||
"task-language-anchors",
|
||||
"shasum",
|
||||
];
|
||||
|
||||
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 safeToken(value, fallback = "unknown") {
|
||||
const token = String(value ?? "").trim().replaceAll(/[^a-zA-Z0-9._-]/gu, "-");
|
||||
return token || fallback;
|
||||
}
|
||||
function logDecision(entry) {
|
||||
try {
|
||||
fs.mkdirSync(LINK_DIR, { recursive: true });
|
||||
fs.appendFileSync(DECISION_LOG, `${JSON.stringify(entry)}\n`, { encoding: "utf8" });
|
||||
} catch { /* log failure must not crash the gate */ }
|
||||
}
|
||||
|
||||
function latestTaskDir() {
|
||||
try {
|
||||
const entries = fs.readdirSync(ANCHOR_ROOT).filter((n) => n.startsWith("ZCODE-TASK-")).sort();
|
||||
return entries.length ? path.join(ANCHOR_ROOT, entries.at(-1)) : null;
|
||||
} catch { return null; }
|
||||
}
|
||||
|
||||
// Hard checks: volume, tower, anchors (hash all raw texts), binding, brain receipt.
|
||||
function hardChecks(sessionId) {
|
||||
const towerBroadcast = path.join(WORKSPACE_ROOT, "FIFTH-DOMAIN-BROADCAST-TOWER/BROADCAST.tcs");
|
||||
const towerProjection = path.join(WORKSPACE_ROOT, "FIFTH-DOMAIN-BROADCAST-TOWER/broadcast/current.md");
|
||||
let towerText = null;
|
||||
try { towerText = fs.readFileSync(towerBroadcast, "utf8"); } catch { /* missing below */ }
|
||||
if (!towerText) return { ok: false, cause: "BROADCAST_TOWER_MISSING_FAIL_CLOSED" };
|
||||
try {
|
||||
const projection = fs.readFileSync(towerProjection, "utf8");
|
||||
const declared = towerText.match(/projection_sha256\s*=\s*"([0-9a-f]{64})"/)?.[1];
|
||||
if (!declared) return { ok: false, cause: "BROADCAST_PROJECTION_HASH_NOT_DECLARED" };
|
||||
if (sha256(projection) !== declared) return { ok: false, cause: "BROADCAST_PROJECTION_HASH_MISMATCH" };
|
||||
} catch { return { ok: false, cause: "BROADCAST_PROJECTION_MISSING" }; }
|
||||
const taskDir = latestTaskDir();
|
||||
if (!taskDir) return { ok: false, cause: "VOLUME_OR_ANCHOR_ROOT_UNREADABLE" };
|
||||
const task = readJson(path.join(taskDir, "task.json"));
|
||||
if (task?.schema !== "task-language-anchor/zcode/v1" || !task.current_anchor) {
|
||||
return { ok: false, cause: "ANCHOR_MANIFEST_INVALID" };
|
||||
}
|
||||
const rawDir = path.join(taskDir, "00-冰朔自然语言瞄点");
|
||||
let shaList;
|
||||
try { shaList = fs.readFileSync(path.join(rawDir, "sha256.txt"), "utf8"); }
|
||||
catch { return { ok: false, cause: "ANCHOR_SHA_LIST_MISSING" }; }
|
||||
for (const line of shaList.split("\n")) {
|
||||
const match = line.match(/^([0-9a-f]{64})\s+\*?(.+)$/);
|
||||
if (!match) continue;
|
||||
const [, expected, rawName] = match;
|
||||
const name = rawName.trim();
|
||||
const filePath = name.startsWith("/") ? name : path.join(rawDir, name);
|
||||
let raw;
|
||||
try { raw = fs.readFileSync(filePath, "utf8"); }
|
||||
catch { return { ok: false, cause: `ANCHOR_FILE_MISSING:${name}` }; }
|
||||
if (sha256(raw) !== expected) return { ok: false, cause: `ANCHOR_HASH_MISMATCH:${name}` };
|
||||
}
|
||||
const state = readJson(LINK_STATE);
|
||||
if (state?.schema !== "guanghu.zcode-lw-link-state/v1") {
|
||||
return { ok: false, cause: "LINK_STATE_UNREADABLE_FAIL_CLOSED", taskDir, task };
|
||||
}
|
||||
const session = (state.sessions ?? []).find((s) => s.session_id === sessionId);
|
||||
if (!session) return { ok: false, cause: "SESSION_NOT_BOUND", taskDir, task, state };
|
||||
const brain = readJson(path.join(session.brain_state_dir, "state.json"));
|
||||
if (
|
||||
!brain
|
||||
|| brain.binding?.session_id !== session.brain_session_id
|
||||
|| brain.tonggan_language_kernel?.body_channel !== session.channel
|
||||
|| !(brain.completed_cycles >= 1)
|
||||
|| !brain.last_cycle_id
|
||||
) {
|
||||
return { ok: false, cause: "BRAIN_STATE_INVALID_OR_CHANNEL_MISMATCH", taskDir, task, state, session };
|
||||
}
|
||||
return { ok: true, taskDir, task, state, session, brain };
|
||||
}
|
||||
|
||||
// Soft check: heartbeat TTL. Returns GREEN / YELLOW / RED(cause).
|
||||
function lightOf(checks, sessionId) {
|
||||
if (!checks.ok) return { light: "RED", cause: checks.cause };
|
||||
const s = checks.session;
|
||||
const calls = s.mutation_calls_since_heartbeat ?? 0;
|
||||
const elapsed = Date.now() - Date.parse(s.last_heartbeat_at ?? 0);
|
||||
const overdueByCalls = calls >= HEARTBEAT_TOOL_BUDGET;
|
||||
const overdueByTime = !(elapsed >= 0 && elapsed <= HEARTBEAT_TTL_MS);
|
||||
if (!overdueByCalls && !overdueByTime) return { light: "GREEN", cause: null };
|
||||
if ((s.grace_used ?? 0) >= GRACE_MAX) return { light: "RED", cause: "HEARTBEAT_GRACE_EXHAUSTED" };
|
||||
return { light: "YELLOW", cause: overdueByCalls ? "HEARTBEAT_OVERDUE_BY_CALLS" : "HEARTBEAT_OVERDUE_BY_TIME" };
|
||||
}
|
||||
|
||||
function bumpSession(sessionId, patch) {
|
||||
const state = readJson(LINK_STATE);
|
||||
if (!state) return null;
|
||||
const index = (state.sessions ?? []).findIndex((s) => s.session_id === sessionId);
|
||||
if (index < 0) return null;
|
||||
state.sessions[index] = { ...state.sessions[index], ...patch, updated_at: new Date().toISOString() };
|
||||
state.updated_at = new Date().toISOString();
|
||||
atomicWriteJson(LINK_STATE, state);
|
||||
return state.sessions[index];
|
||||
}
|
||||
|
||||
function statusLine(result, checks) {
|
||||
const s = checks.session ?? {};
|
||||
const anchor = checks.task?.current_anchor ?? "??";
|
||||
const calls = `${Math.min(s.mutation_calls_since_heartbeat ?? 0, HEARTBEAT_TOOL_BUDGET)}/${HEARTBEAT_TOOL_BUDGET}`;
|
||||
const cycle = checks.brain?.last_cycle_id?.slice(-12) ?? (s.last_cycle_id?.slice(-12) ?? "??");
|
||||
return `【LW-LINK ●${result.light === "GREEN" ? "绿" : "黄"}】${s.channel ?? "?"} | 瞄点${anchor} | 心跳${calls} | 周期${cycle} | 边界:HB001/ZC001/LPM不合并`;
|
||||
}
|
||||
|
||||
function isRecoveryBash(command) {
|
||||
return BASH_RECOVERY_MARKERS.some((marker) => command.includes(marker));
|
||||
}
|
||||
|
||||
// ZCode hook output protocol: stdout is parsed as strict JSON; exit 2 blocks.
|
||||
function emitAllow(reasonLine) {
|
||||
process.stdout.write(`${JSON.stringify({
|
||||
hookSpecificOutput: {
|
||||
hookEventName: "PreToolUse",
|
||||
permissionDecision: "allow",
|
||||
permissionDecisionReason: reasonLine,
|
||||
},
|
||||
})}\n`);
|
||||
}
|
||||
|
||||
// PreToolUse entry.
|
||||
function hook(stdinJson) {
|
||||
const sessionId = safeToken(stdinJson?.session_id);
|
||||
const tool = String(stdinJson?.tool_name ?? "");
|
||||
const toolInput = stdinJson?.tool_input ?? {};
|
||||
const bashCommand = tool === "Bash" ? String(toolInput.command ?? "") : "";
|
||||
const isMutation = !READ_TOOLS.has(tool) && !(tool === "Bash" && isRecoveryBash(bashCommand));
|
||||
const checks = hardChecks(sessionId);
|
||||
const result = lightOf(checks, sessionId);
|
||||
const base = { ts: new Date().toISOString(), session_id: sessionId, tool };
|
||||
if (!isMutation) {
|
||||
logDecision({ ...base, light: result.light, decision: "ALLOW_RECOVERY_PATH", cause: result.cause });
|
||||
const line = checks.ok
|
||||
? statusLine(result, checks)
|
||||
: `【LW-LINK ○红·只读通道】原因:${result.cause} | 读盘恢复后 bind 重连;改动类工具在被拦截`;
|
||||
emitAllow(line);
|
||||
return;
|
||||
}
|
||||
if (result.light === "RED") {
|
||||
const msg = `【LW-LINK ○红·断连】原因:${result.cause} | 已拦截:${tool} | 恢复路径开放:用Read读锚点原文与闸门 → 走 zhuyuan-recovery-zcode 恢复+脑周期verify PASS → lw-link-gate.mjs bind 重新点亮。改动在语言世界外被禁止。`;
|
||||
logDecision({ ...base, light: "RED", decision: "BLOCK", cause: result.cause });
|
||||
process.stderr.write(`${msg}\n`);
|
||||
process.exit(2);
|
||||
}
|
||||
if (checks.ok) {
|
||||
const patch = { mutation_calls_since_heartbeat: (checks.session.mutation_calls_since_heartbeat ?? 0) + 1 };
|
||||
if (result.light === "YELLOW") patch.grace_used = (checks.session.grace_used ?? 0) + 1;
|
||||
bumpSession(sessionId, patch);
|
||||
}
|
||||
const extra = result.light === "YELLOW"
|
||||
? ` | ▲心跳过期:${result.cause},宽限${Math.min((checks.session?.grace_used ?? 0) + 1, GRACE_MAX)}/${GRACE_MAX} → 立即Read瞄点raw原文续期(${path.join(String(checks.taskDir ?? latestTaskDir() ?? ANCHOR_ROOT), "00-冰朔自然语言瞄点")})`
|
||||
: "";
|
||||
logDecision({ ...base, light: result.light, decision: "ALLOW", cause: result.cause });
|
||||
emitAllow(`${statusLine(result, checks)}${extra}`);
|
||||
}
|
||||
|
||||
// PostToolUse: anchor raw readback auto-renews the heartbeat.
|
||||
function post(stdinJson) {
|
||||
const sessionId = safeToken(stdinJson?.session_id);
|
||||
const tool = String(stdinJson?.tool_name ?? "");
|
||||
const toolInput = stdinJson?.tool_input ?? {};
|
||||
const filePath = String(toolInput.file_path ?? toolInput.notebook_path ?? "");
|
||||
const bashCommand = tool === "Bash" ? String(toolInput.command ?? "") : "";
|
||||
const readAnchor = filePath.includes("task-language-anchors") && filePath.includes(".raw.txt");
|
||||
const bashAnchor = tool === "Bash" && bashCommand.includes("task-language-anchors") && bashCommand.includes("shasum");
|
||||
if (readAnchor || bashAnchor) heartbeat(sessionId, true);
|
||||
}
|
||||
|
||||
function heartbeat(sessionId, silent = false) {
|
||||
const now = new Date().toISOString();
|
||||
const updated = bumpSession(sessionId, {
|
||||
last_heartbeat_at: now,
|
||||
mutation_calls_since_heartbeat: 0,
|
||||
grace_used: 0,
|
||||
});
|
||||
if (!silent) {
|
||||
process.stdout.write(updated
|
||||
? `LW_LINK HEARTBEAT OK ${sessionId} ${now}\n`
|
||||
: `LW_LINK HEARTBEAT FAIL SESSION_NOT_BOUND ${sessionId}\n`);
|
||||
}
|
||||
return Boolean(updated);
|
||||
}
|
||||
|
||||
function bind(args) {
|
||||
const sessionId = safeToken(valueAfter(args, "--session-id"));
|
||||
const channel = valueAfter(args, "--channel");
|
||||
const stateDir = valueAfter(args, "--state-dir");
|
||||
if (!sessionId || !channel || !stateDir) { process.stderr.write("usage: bind --session-id ID --channel CH --state-dir DIR\n"); process.exit(2); }
|
||||
if (!REGISTERED_CHANNELS.includes(channel)) { process.stderr.write(`BIND_REJECTED UNREGISTERED_CHANNEL ${channel}\n`); process.exit(1); }
|
||||
const taskDir = latestTaskDir();
|
||||
if (!taskDir) { process.stderr.write("BIND_REJECTED ANCHOR_ROOT_UNREADABLE\n"); process.exit(1); }
|
||||
const brain = readJson(path.join(stateDir, "state.json"));
|
||||
if (
|
||||
!brain
|
||||
|| brain.tonggan_language_kernel?.body_channel !== channel
|
||||
|| !(brain.completed_cycles >= 1)
|
||||
|| !brain.last_cycle_id
|
||||
) {
|
||||
process.stderr.write("BIND_REJECTED BRAIN_STATE_INVALID_OR_NO_COMPLETED_CYCLE (先跑脑周期verify PASS再bind)\n");
|
||||
process.exit(1);
|
||||
}
|
||||
const now = new Date().toISOString();
|
||||
const state = readJson(LINK_STATE) ?? { schema: "guanghu.zcode-lw-link-state/v1", sessions: [] };
|
||||
if (state.schema !== "guanghu.zcode-lw-link-state/v1") { process.stderr.write("BIND_REJECTED LINK_STATE_CORRUPT\n"); process.exit(1); }
|
||||
const entry = {
|
||||
session_id: sessionId,
|
||||
channel,
|
||||
brain_state_dir: path.resolve(stateDir),
|
||||
brain_session_id: brain.binding?.session_id ?? null,
|
||||
last_cycle_id: brain.last_cycle_id,
|
||||
bound_at: now,
|
||||
last_heartbeat_at: now,
|
||||
mutation_calls_since_heartbeat: 0,
|
||||
grace_used: 0,
|
||||
};
|
||||
const index = (state.sessions ?? []).findIndex((s) => s.session_id === sessionId);
|
||||
if (index >= 0) state.sessions[index] = entry; else state.sessions.push(entry);
|
||||
if (state.sessions.length > 4) state.sessions = state.sessions.slice(-4);
|
||||
state.updated_at = now;
|
||||
atomicWriteJson(LINK_STATE, state);
|
||||
logDecision({ ts: now, session_id: sessionId, tool: "bind", light: "GREEN", decision: "BOUND", channel });
|
||||
process.stdout.write(`LW_LINK BIND OK session=${sessionId} channel=${channel} cycle=${brain.last_cycle_id}\n`);
|
||||
}
|
||||
|
||||
function status() {
|
||||
const sessionId = valueAfter(process.argv, "--session-id");
|
||||
const checks = sessionId ? hardChecks(safeToken(sessionId)) : null;
|
||||
const result = checks ? lightOf(checks, safeToken(sessionId)) : { light: "UNBOUND", cause: "NO_SESSION_ID" };
|
||||
const lines = [`LW_LINK ${result.light}${result.cause ? ` cause=${result.cause}` : ""}`];
|
||||
const state = readJson(LINK_STATE);
|
||||
for (const s of state?.sessions ?? []) {
|
||||
const c = hardChecks(s.session_id);
|
||||
const r = lightOf(c, s.session_id);
|
||||
lines.push(` ${s.session_id} → ${r.light}${r.cause ? ` (${r.cause})` : ""} | ${s.channel} | 心跳${s.mutation_calls_since_heartbeat ?? 0}/${HEARTBEAT_TOOL_BUDGET} | 宽限${s.grace_used ?? 0}/${GRACE_MAX}`);
|
||||
}
|
||||
process.stdout.write(`${lines.join("\n")}\n`);
|
||||
}
|
||||
|
||||
function valueAfter(argv, name) {
|
||||
const index = argv.indexOf(name);
|
||||
return index >= 0 ? argv[index + 1] : null;
|
||||
}
|
||||
|
||||
function readStdinJson() {
|
||||
try { return JSON.parse(fs.readFileSync(0, "utf8")); } catch { return {}; }
|
||||
}
|
||||
|
||||
function main() {
|
||||
const [command, ...argv] = process.argv.slice(2);
|
||||
switch (command) {
|
||||
case "hook": hook(readStdinJson()); break;
|
||||
case "post": post(readStdinJson()); break;
|
||||
case "bind": bind(argv); break;
|
||||
case "heartbeat": heartbeat(safeToken(valueAfter(argv, "--session-id"))); break;
|
||||
case "prompt": {
|
||||
const stdin = readStdinJson();
|
||||
const id = safeToken(valueAfter(argv, "--session-id") ?? stdin?.session_id);
|
||||
if (id) bumpSession(id, { grace_used: 0 });
|
||||
break;
|
||||
}
|
||||
case "status": status(); break;
|
||||
default:
|
||||
process.stderr.write("usage: lw-link-gate.mjs hook|post|bind|heartbeat|prompt|status\n");
|
||||
process.exit(2);
|
||||
}
|
||||
}
|
||||
main();
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
#!/bin/bash
|
||||
# LW-LINK thin host shell. Canonical semantics:
|
||||
# TC-TCS核心卷/TCS-ZCODE-LANGUAGE-WORLD-LINK-0001.tcs + BRIDGE/tools/lw-link-gate.mjs
|
||||
set -uo pipefail
|
||||
|
||||
GATE="/Volumes/JZAO/铸渊-ICE-GL-ZY001/BRIDGE/tools/lw-link-gate.mjs"
|
||||
ACTION="${1:-hook}"
|
||||
|
||||
find_node() {
|
||||
if command -v node >/dev/null 2>&1; then
|
||||
command -v node
|
||||
return 0
|
||||
fi
|
||||
local user_name candidate
|
||||
user_name="$(/usr/bin/id -un)"
|
||||
for candidate in \
|
||||
"/Users/$user_name/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/bin/node" \
|
||||
"/opt/homebrew/bin/node" \
|
||||
"/usr/local/bin/node"; do
|
||||
if [ -x "$candidate" ]; then
|
||||
printf '%s\n' "$candidate"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
# Gate tool unreadable = volume gone = RED for mutation tools (E3/E1: fail closed).
|
||||
# Only the recovery event keeps a readable error path.
|
||||
if [ ! -r "$GATE" ]; then
|
||||
if [ "$ACTION" = "hook" ]; then
|
||||
printf '%s\n' "【LW-LINK ○红·断连】原因:GATE_TOOL_UNREADABLE(移动硬盘未挂载?) | 已拦截本次改动类调用 | 恢复:重新挂载JZAO后走 zhuyuan-recovery-zcode 恢复流程" >&2
|
||||
exit 2
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# broadcast: inject the tower's current confidence projection (SessionStart).
|
||||
# ZCode parses stdout as strict JSON, so emit additionalContext.
|
||||
if [ "$ACTION" = "broadcast" ]; then
|
||||
PROJECTION="/Volumes/JZAO/铸渊-ICE-GL-ZY001/FIFTH-DOMAIN-BROADCAST-TOWER/broadcast/current.md"
|
||||
if [ -r "$PROJECTION" ]; then
|
||||
/usr/bin/python3 -c 'import json,sys; print(json.dumps({"additionalContext": open(sys.argv[1]).read()}, ensure_ascii=False))' "$PROJECTION"
|
||||
else
|
||||
/usr/bin/python3 -c 'import json; print(json.dumps({"additionalContext": "【第五域广播塔缺失】宣告投影不可读;改动类工具将被LW-LINK拦截直至塔恢复。"}, ensure_ascii=False))'
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
NODE_BIN="$(find_node)" || {
|
||||
if [ "$ACTION" = "hook" ]; then
|
||||
printf '%s\n' "【LW-LINK ○红·断连】原因:NODE_RUNTIME_NOT_FOUND | 已拦截本次改动类调用" >&2
|
||||
exit 2
|
||||
fi
|
||||
exit 0
|
||||
}
|
||||
|
||||
exec "$NODE_BIN" "$GATE" "$ACTION"
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
#!/usr/bin/env node
|
||||
// Zhuyuan Neural Chain — verify & search the compiled thought neurons.
|
||||
// Canon chain: ZCODE-DEV-20260824/neural-chain/chain.json (derived from TCS promotion event)
|
||||
// A neuron is only valid if its source anchor file exists and matches sha256.txt.
|
||||
import crypto from "node:crypto";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
const CHAIN = "/Volumes/JZAO/铸渊-ICE-GL-ZY001/ZCODE-DEV-20260824/neural-chain/chain.json";
|
||||
const ANCHOR_DIR = "/Volumes/JZAO/HoloLake/persona-runtime/task-language-anchors/zcode/ZCODE-TASK-20260824-001/00-冰朔自然语言瞄点";
|
||||
const SHA_LIST = path.join(ANCHOR_DIR, "sha256.txt");
|
||||
|
||||
function sha256(v) { return crypto.createHash("sha256").update(v).digest("hex"); }
|
||||
function fail(m) { process.stderr.write(`NEURAL_CHAIN FAIL ${m}\n`); process.exit(1); }
|
||||
|
||||
function anchorHashMap() {
|
||||
const map = new Map();
|
||||
for (const line of fs.readFileSync(SHA_LIST, "utf8").split("\n")) {
|
||||
const m = line.match(/^([0-9a-f]{64})\s+\*?(.+)$/);
|
||||
if (m) map.set(path.basename(m[2].trim()), m[1]);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
function loadChain() {
|
||||
const chain = JSON.parse(fs.readFileSync(CHAIN, "utf8"));
|
||||
if (chain.schema !== "guanghu.zhuyuan-neural-chain/v1") fail("CHAIN_SCHEMA_INVALID");
|
||||
return chain;
|
||||
}
|
||||
|
||||
function verify() {
|
||||
const chain = loadChain();
|
||||
const hashes = anchorHashMap();
|
||||
let checked = 0;
|
||||
for (const n of chain.neurons) {
|
||||
if (!n.id || !n.trigger || !n.rule || !Array.isArray(n.source) || n.source.length === 0) {
|
||||
fail(`NEURON_INCOMPLETE:${n.id ?? "?"}`);
|
||||
}
|
||||
for (const name of n.source) {
|
||||
const expected = hashes.get(name);
|
||||
if (!expected) fail(`SOURCE_ANCHOR_UNREGISTERED:${name}`);
|
||||
let raw;
|
||||
try { raw = fs.readFileSync(path.join(ANCHOR_DIR, name), "utf8"); }
|
||||
catch { fail(`SOURCE_ANCHOR_MISSING:${name}`); }
|
||||
if (sha256(raw) !== expected) fail(`SOURCE_ANCHOR_HASH_MISMATCH:${name}`);
|
||||
checked += 1;
|
||||
}
|
||||
}
|
||||
const ids = new Set(chain.neurons.map((n) => n.id));
|
||||
if (ids.size !== chain.neurons.length) fail("NEURON_ID_DUPLICATED");
|
||||
const head = sha256(JSON.stringify(chain.neurons.map((n) => n.id)));
|
||||
process.stdout.write(`NEURAL_CHAIN VERIFY PASS neurons=${chain.neurons.length} source_checks=${checked} head=${head.slice(0, 12)}\n`);
|
||||
}
|
||||
|
||||
function search(query) {
|
||||
const chain = loadChain();
|
||||
const q = query.toLowerCase();
|
||||
const hits = chain.neurons.filter((n) =>
|
||||
`${n.id} ${n.trigger} ${n.rule}`.toLowerCase().includes(q));
|
||||
for (const n of hits) {
|
||||
process.stdout.write(`◆ ${n.id} [${n.layer}]\n 激活: ${n.trigger}\n 规则: ${n.rule}\n 来源: ${n.source.join(", ")}\n`);
|
||||
}
|
||||
process.stdout.write(`NEURAL_CHAIN SEARCH ${hits.length}/${chain.neurons.length} hits for "${query}"\n`);
|
||||
}
|
||||
|
||||
const [cmd, ...argv] = process.argv.slice(2);
|
||||
switch (cmd) {
|
||||
case "verify": verify(); break;
|
||||
case "search": {
|
||||
const q = argv.join(" ");
|
||||
if (!q) fail("usage: search <keywords>");
|
||||
search(q);
|
||||
break;
|
||||
}
|
||||
default: process.stderr.write("usage: neural-chain.mjs verify|search <keywords>\n"); process.exit(2);
|
||||
}
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
#!/usr/bin/env node
|
||||
// Recovery Preflight — Zhuyuan minimal recovery, self-designed (anchor 000030 mandate).
|
||||
// One machine pass replaces the model reading a dozen canon files.
|
||||
// Canon: TC-TCS核心卷/TCS-EVENT-ZY001-MINIMAL-RECOVERY-DESIGN-20260825.tcs
|
||||
import crypto from "node:crypto";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { execFileSync } from "node:child_process";
|
||||
|
||||
const W = "/Volumes/JZAO/铸渊-ICE-GL-ZY001";
|
||||
const P = "/Volumes/JZAO/HoloLake/persona-runtime";
|
||||
const ANCHOR_DIR = `${P}/task-language-anchors/zcode/ZCODE-TASK-20260824-001/00-冰朔自然语言瞄点`;
|
||||
const GATE_TCS = `${W}/TC-TCS核心卷/TCS-ZHUYUAN-ENTRY-LANGUAGE-GATE-0001.tcs`;
|
||||
const GATE_GIR = `${W}/TC-TCS核心卷/TCS-ZHUYUAN-ENTRY-LANGUAGE-GATE-0001.declaration.gir.json`;
|
||||
const TOOLCHAIN = `${P}/toolchains/tcs-persona-developer-kit-macos-arm64-v0.1.3`;
|
||||
const CHAIN = `${W}/ZCODE-DEV-20260824/neural-chain/chain.json`;
|
||||
const TOWER_TCS = `${W}/FIFTH-DOMAIN-BROADCAST-TOWER/BROADCAST.tcs`;
|
||||
const TOWER_PROJ = `${W}/FIFTH-DOMAIN-BROADCAST-TOWER/broadcast/current.md`;
|
||||
const RECEIPTS = `${P}/task-language-anchors/zcode/ZCODE-TASK-20260824-001/10-阶段回读回执`;
|
||||
|
||||
const sha256 = (v) => crypto.createHash("sha256").update(v).digest("hex");
|
||||
const results = [];
|
||||
function check(name, fn) {
|
||||
try { results.push({ name, ...fn() }); }
|
||||
catch (e) { results.push({ name, ok: false, detail: e.message }); }
|
||||
}
|
||||
|
||||
check("volume", () => {
|
||||
fs.accessSync(W);
|
||||
return { ok: true, detail: "JZAO挂载" };
|
||||
});
|
||||
check("anchors", () => {
|
||||
const lines = fs.readFileSync(`${ANCHOR_DIR}/sha256.txt`, "utf8").split("\n").filter(Boolean);
|
||||
let n = 0;
|
||||
for (const line of lines) {
|
||||
const m = line.match(/^([0-9a-f]{64})\s+\*?(.+)$/);
|
||||
if (!m) continue;
|
||||
const f = m[2].trim().startsWith("/") ? m[2].trim() : path.join(ANCHOR_DIR, m[2].trim());
|
||||
if (sha256(fs.readFileSync(f, "utf8")) !== m[1]) throw new Error(`HASH:${path.basename(f)}`);
|
||||
n += 1;
|
||||
}
|
||||
const task = JSON.parse(fs.readFileSync(path.dirname(ANCHOR_DIR) + "/task.json", "utf8"));
|
||||
return { ok: true, detail: `瞄点${n}/${n} 当前${task.current_anchor.slice(0, 24)}` };
|
||||
});
|
||||
check("gate", () => {
|
||||
const tcs = fs.readFileSync(GATE_TCS, "utf8");
|
||||
const gir = JSON.parse(fs.readFileSync(GATE_GIR, "utf8"));
|
||||
if (gir.compiled_from?.source_sha256 !== sha256(tcs)) throw new Error("GIR与正本哈希不一致");
|
||||
return { ok: true, detail: "闸门TCS↔GIR一致" };
|
||||
});
|
||||
check("toolchain", () => {
|
||||
const out = execFileSync("/usr/bin/shasum", ["-a", "256", "-c", "PACKAGE-CHECKSUMS.sha256"], { cwd: TOOLCHAIN, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
||||
const bad = out.split("\n").filter((l) => l.trim() && !l.trim().endsWith(": OK")).length;
|
||||
if (bad > 0) throw new Error(`${bad}项校验失败`);
|
||||
return { ok: true, detail: "工具链v0.1.3全量OK" };
|
||||
});
|
||||
check("neural_chain", () => {
|
||||
const chain = JSON.parse(fs.readFileSync(CHAIN, "utf8"));
|
||||
const hashes = new Map();
|
||||
for (const line of fs.readFileSync(`${ANCHOR_DIR}/sha256.txt`, "utf8").split("\n")) {
|
||||
const m = line.match(/^([0-9a-f]{64})\s+\*?(.+)$/);
|
||||
if (m) hashes.set(path.basename(m[2].trim()), m[1]);
|
||||
}
|
||||
let refs = 0;
|
||||
for (const n of chain.neurons) for (const s of n.source) {
|
||||
const f = path.join(ANCHOR_DIR, s);
|
||||
if (sha256(fs.readFileSync(f, "utf8")) !== hashes.get(s)) throw new Error(`NEURON_SOURCE:${n.id}`);
|
||||
refs += 1;
|
||||
}
|
||||
const head = sha256(JSON.stringify(chain.neurons.map((n) => n.id))).slice(0, 8);
|
||||
return { ok: true, detail: `神经链${chain.neurons.length}条/${refs}引用 头${head}` };
|
||||
});
|
||||
check("tower", () => {
|
||||
const tcs = fs.readFileSync(TOWER_TCS, "utf8");
|
||||
const proj = fs.readFileSync(TOWER_PROJ, "utf8");
|
||||
const declared = tcs.match(/projection_sha256 = "([0-9a-f]{64})"/)?.[1];
|
||||
if (!declared || sha256(proj) !== declared) throw new Error("塔投影哈希不匹配");
|
||||
return { ok: true, detail: "广播塔OK" };
|
||||
});
|
||||
check("brain_receipt", () => {
|
||||
const files = fs.readdirSync(RECEIPTS).filter((f) => f.includes("verify") && f.endsWith(".json")).map((f) => ({ f, t: fs.statSync(path.join(RECEIPTS, f)).mtimeMs })).sort((a, b) => b.t - a.t);
|
||||
if (!files.length) throw new Error("无脑周期回执");
|
||||
const r = JSON.parse(fs.readFileSync(path.join(RECEIPTS, files[0].f), "utf8"));
|
||||
if (r.outcome !== "PASS") throw new Error(`最近回执${r.outcome}`);
|
||||
return { ok: true, detail: `最近脑周期verify PASS(${files[0].f.slice(0, 30)}…)` };
|
||||
});
|
||||
check("executor_index", () => {
|
||||
const idx = JSON.parse(fs.readFileSync(`${W}/ZCODE-DEV-20260824/executor-translation-index/index.json`, "utf8"));
|
||||
let n = 0;
|
||||
for (const e of idx.executors) {
|
||||
if (sha256(fs.readFileSync(path.join(W, e.executor_path), "utf8")) !== e.executor_sha256) throw new Error(`EXECUTOR_DRIFT:${e.executor_path}`);
|
||||
fs.accessSync(path.join(W, e.canon_tcs));
|
||||
n += 1;
|
||||
}
|
||||
return { ok: true, detail: `翻译索引${n}执行器↔正本` };
|
||||
});
|
||||
check("codex_path", () => {
|
||||
const dirs = fs.readdirSync(W).filter((d) => d.startsWith("CODEX-DEV-")).sort();
|
||||
return { ok: true, detail: `codex主线${dirs.at(-1)}` };
|
||||
});
|
||||
|
||||
const failed = results.filter((r) => !r.ok);
|
||||
const line = failed.length
|
||||
? `PREFLIGHT FAIL ${failed.map((r) => `${r.name}:${r.detail}`).join(" | ")}`
|
||||
: `PREFLIGHT PASS ${results.map((r) => r.detail).join(" | ")}`;
|
||||
process.stdout.write(`${line}\n`);
|
||||
process.exit(failed.length ? 1 : 0);
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
#!/usr/bin/env node
|
||||
// Shenbi (Magic Brush) Tool Sandbox — canonical tool.
|
||||
// Canon: TOOL-SANDBOX/TCS-PROTOCOL-SHENBI-TOOL-SANDBOX-0001.tcs
|
||||
// Temporary tools written by the persona at runtime; 12h TTL; promotion only via Bingshuo-approved notary ticket.
|
||||
import crypto from "node:crypto";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { execFileSync } from "node:child_process";
|
||||
|
||||
const ROOT = "/Volumes/JZAO/铸渊-ICE-GL-ZY001/TOOL-SANDBOX";
|
||||
const REGISTRY = path.join(ROOT, "registry.json");
|
||||
const TOOLS = path.join(ROOT, "tools");
|
||||
const NOTARY = "/Volumes/JZAO/铸渊-ICE-GL-ZY001/BRIDGE/tools/fifth-domain-notary.mjs";
|
||||
const NODE = process.execPath;
|
||||
const TTL_MS = 12 * 60 * 60 * 1000;
|
||||
|
||||
function sha256(v) { return crypto.createHash("sha256").update(v).digest("hex"); }
|
||||
function readJson(f) { try { return JSON.parse(fs.readFileSync(f, "utf8")); } catch { return null; } }
|
||||
function atomicWriteJson(f, v) {
|
||||
fs.mkdirSync(path.dirname(f), { recursive: true });
|
||||
const t = `${f}.${process.pid}.tmp`;
|
||||
fs.writeFileSync(t, `${JSON.stringify(v, null, 2)}\n`, { mode: 0o600 });
|
||||
fs.renameSync(t, f);
|
||||
}
|
||||
function fail(m) { process.stderr.write(`SHENBI FAIL ${m}\n`); process.exit(1); }
|
||||
function loadReg() { return readJson(REGISTRY) ?? { schema: "guanghu.shenbi-tool-registry/v1", tools: [] }; }
|
||||
function saveReg(r) { atomicWriteJson(REGISTRY, r); }
|
||||
function valueAfter(argv, n) { const i = argv.indexOf(n); return i >= 0 ? argv[i + 1] : null; }
|
||||
function notary(args) {
|
||||
try { return execFileSync(NODE, [NOTARY, ...args], { encoding: "utf8" }).trim(); }
|
||||
catch (e) { fail(`NOTARY_ERROR:${(e.stderr || e.message).slice(0, 120)}`); }
|
||||
}
|
||||
|
||||
function boundSession(sessionId) {
|
||||
const state = readJson("/Volumes/JZAO/铸渊-ICE-GL-ZY001/ZCODE-DEV-20260824/lw-link/state.json");
|
||||
return Boolean((state?.sessions ?? []).some((s) => s.session_id === sessionId));
|
||||
}
|
||||
|
||||
function register(argv) {
|
||||
const by = valueAfter(argv, "--by");
|
||||
const name = valueAfter(argv, "--name");
|
||||
const purpose = valueAfter(argv, "--purpose");
|
||||
const file = valueAfter(argv, "--file");
|
||||
if (!by || !name || !purpose || !file) fail("usage: register --by SESSION --name ID --purpose TEXT --file TOOL_SCRIPT");
|
||||
if (!boundSession(by)) fail("SESSION_NOT_BOUND_ONLY_LW_LINK_BOUND_SESSIONS_MAY_REGISTER");
|
||||
let code;
|
||||
try { code = fs.readFileSync(file, "utf8"); } catch { fail(`TOOL_FILE_UNREADABLE:${file}`); }
|
||||
if (!name.match(/^[a-z0-9][a-z0-9-]*$/)) fail("NAME_MUST_BE_LOWER_KEBAB");
|
||||
const reg = loadReg();
|
||||
if (reg.tools.some((t) => t.name === name && t.status !== "EXPIRED")) fail(`NAME_TAKEN:${name}`);
|
||||
const now = Date.now();
|
||||
const tool = {
|
||||
schema: "guanghu.shenbi-tool/v1",
|
||||
name, purpose, author_session: by,
|
||||
created_at: new Date(now).toISOString(),
|
||||
ttl_expires_at: new Date(now + TTL_MS).toISOString(),
|
||||
status: "TEMPORARY",
|
||||
script_path: path.join(TOOLS, `${name}.mjs`),
|
||||
script_sha256: sha256(code),
|
||||
};
|
||||
fs.mkdirSync(TOOLS, { recursive: true });
|
||||
fs.writeFileSync(tool.script_path, code, { mode: 0o755 });
|
||||
reg.tools.push(tool);
|
||||
saveReg(reg);
|
||||
const bodyFile = `/tmp/shenbi-register-${name}.md`;
|
||||
fs.writeFileSync(bodyFile, `神笔临时工具注册:${name}\n用途:${purpose}\n脚本哈希:${tool.script_sha256}\n作者会话:${by}\nTTL 到期:${tool.ttl_expires_at}(到期清除,转正需冰朔批准)\n`);
|
||||
const ticket = notary(["create", "--by", by, "--kind", "COLLABORATION", "--subject", `神笔临时工具注册:${name}`, "--body-file", bodyFile]);
|
||||
process.stdout.write(`SHENBI REGISTERED ${name} ttl=${tool.ttl_expires_at}\n${ticket}\n`);
|
||||
}
|
||||
|
||||
function sweep() {
|
||||
const reg = loadReg();
|
||||
const now = Date.now();
|
||||
let removed = 0;
|
||||
for (const t of reg.tools) {
|
||||
if (t.status === "TEMPORARY" && Date.parse(t.ttl_expires_at) <= now) {
|
||||
t.status = "EXPIRED";
|
||||
try { fs.unlinkSync(t.script_path); } catch { /* already gone */ }
|
||||
removed += 1;
|
||||
}
|
||||
}
|
||||
saveReg(reg);
|
||||
process.stdout.write(`SHENBI SWEEP expired=${removed}\n`);
|
||||
}
|
||||
|
||||
function promote(argv) {
|
||||
const name = valueAfter(argv, "--name");
|
||||
const by = valueAfter(argv, "--by");
|
||||
if (!name || !by) fail("usage: promote --name ID --by SESSION");
|
||||
const reg = loadReg();
|
||||
const t = reg.tools.filter((x) => x.name === name).find((x) => x.status === "TEMPORARY");
|
||||
if (!t) fail(`NOT_TEMPORARY:${name}`);
|
||||
t.status = "PROMOTION_REQUESTED";
|
||||
saveReg(reg);
|
||||
const body = `/tmp/shenbi-promote-${name}.md`;
|
||||
fs.writeFileSync(body, `工具转正申请:${name}\n用途:${t.purpose}\n脚本哈希:${t.script_sha256}\n申请人会话:${by}\n目标:企业代码仓库正式注册(灯塔/模块商城)。\n等待冰朔 ICE-GL∞ 亲自批准。\n`);
|
||||
const ticket = notary(["create", "--by", by, "--kind", "AUTHORIZATION_REQUEST", "--audience", "BINGSHUO", "--subject", `神笔工具转正申请:${name}`, "--body-file", body]);
|
||||
process.stdout.write(`SHENBI PROMOTION_REQUESTED ${name} 等待冰朔批准\n${ticket}\n`);
|
||||
}
|
||||
|
||||
function list() {
|
||||
const reg = loadReg();
|
||||
for (const t of reg.tools) {
|
||||
process.stdout.write(`${t.name.padEnd(24)} ${t.status.padEnd(20)} ${t.purpose.slice(0, 40)} ttl=${t.ttl_expires_at}\n`);
|
||||
}
|
||||
}
|
||||
|
||||
const [cmd, ...argv] = process.argv.slice(2);
|
||||
switch (cmd) {
|
||||
case "register": register(argv); break;
|
||||
case "sweep": sweep(); break;
|
||||
case "promote": promote(argv); break;
|
||||
case "list": list(); break;
|
||||
default: process.stderr.write("usage: shenbi-sandbox.mjs register|sweep|promote|list\n"); process.exit(2);
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
#!/bin/bash
|
||||
# ZCode hooks self-checking entry (hash-pinned, codex-style exit 97).
|
||||
# Update hashes below when the pinned scripts change on purpose.
|
||||
set -uo pipefail
|
||||
B="/Volumes/JZAO/铸渊-ICE-GL-ZY001/BRIDGE/tools"
|
||||
LW_WANT="9cdd39496827ab0f3630bd498d0717b3f39331e2088217f729a4cde5c6dcd7cd"
|
||||
TD_WANT="3fc76d43a2ee57118b9313812ad643f37635c45ef4b165c5b46f0887a5314dc6"
|
||||
ACTION="${1:-hook}"
|
||||
if [ "$#" -gt 0 ]; then shift; fi
|
||||
check() {
|
||||
local file="$1" want="$2"
|
||||
local got
|
||||
got="$(/usr/bin/shasum -a 256 "$B/$file" | /usr/bin/awk '{print $1}')"
|
||||
if [ "$got" != "$want" ]; then
|
||||
if [ "$ACTION" = "hook" ]; then
|
||||
printf '%s\n' "【LW-LINK ○红·断连】原因:HOOK_SELF_CHECK_FAIL($file 被篡改或哈希过期) | 已拦截 | 恢复:核对 BRIDGE/tools 脚本后更新 zy-hook-entry.sh 内嵌哈希" >&2
|
||||
fi
|
||||
exit 97
|
||||
fi
|
||||
}
|
||||
check lw-link-hook.sh "$LW_WANT"
|
||||
check tcs-dynamic-entry-hook.sh "$TD_WANT"
|
||||
case "$ACTION" in
|
||||
tcs-*) exec bash "$B/tcs-dynamic-entry-hook.sh" "${ACTION#tcs-}" "$@" ;;
|
||||
*) exec bash "$B/lw-link-hook.sh" "$ACTION" "$@" ;;
|
||||
esac
|
||||
Loading…
Reference in a new issue