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.
115 lines
5.4 KiB
JavaScript
115 lines
5.4 KiB
JavaScript
#!/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);
|
|
}
|