chore(builds): 重編 tier2 成品——把 #105 放進執行檔(出貨路徑 A 的第 0 步)
leo 選 A(把出貨線推到能出一版含 #105 的 bundle)。查證後發現最前面還有一層: cypher 源碼最後動 10d150a(#105) / 成品最後動 a24f291(更早) mcp 源碼最後動 10d150a(#105) / 成品最後動 8e10f1d(更早) ⇒ #105 改了 cypher 與 mcp 兩邊源碼,但沒有重編成品。 這正是 Arcrun#93 那道「源碼比執行檔新就停下來」的閘要擋的狀態。 用官方唯一編譯點 scripts/build-worker-artifacts.mjs(Arcrun#80 的機制,已存在) 重編五顆,全部 5/5: arcrun-cypher-executor 571KB source=10d150ac arcrun-kbdb 146KB source=10d150ac arcrun-mcp 1152KB source=10d150ac arcrun-http-request 78KB source=1e85dfb4(未變) arcrun-code 150KB source=621cb8d9(未變) 交叉驗證(不只信它自記的 commit):/portal/data/ 這條 #105 才有的路徑 在 arcrun-mcp 成品裡出現 8 次。 下一步(等 leo 解閘):把修好的引擎部署到 geek6688 當出貨機 (ARCRUN_SHIP_BASE 可覆寫,預設是 leo21c——arcrun-rag#79 要搬離的正是這個), 再從那台跑出貨線,第 17 站 purge 的 wait 節點才有帶修法的引擎可跑。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -13285,7 +13285,12 @@ portalRouter.post(
|
||||
session_token: token,
|
||||
display_name: rec.values.display_name ?? "",
|
||||
role: rec.values.role ?? "user",
|
||||
libraries: parseLibraries(rec.values.libraries)
|
||||
libraries: parseLibraries(rec.values.libraries),
|
||||
// session 還能活多久(秒)。**非機密**(是這台實例的 TTL 設定,不是任何人的憑據),
|
||||
// 但呼叫端需要它才能把自己發的憑證對齊這個上限——arcrun-mcp 用它把 OAuth
|
||||
// access_token 的 TTL 夾到 min(自己的 TTL, 這個值):否則 MCP token 活 30 天、
|
||||
// 底下的 portal session 7 天就死,使用者會在第 8 天遇到「連著卻查不到」的鬼打牆。
|
||||
session_expires_in: sessionTtl(c.env)
|
||||
// 絕不回租戶字串(design §3.3:portal_user 拿到租戶字串就能繞過庫 filter 直打 /kbdb/*)
|
||||
});
|
||||
})
|
||||
@@ -15363,6 +15368,151 @@ portalDataRouter.get(
|
||||
return c.json({ success: true, workflows, total: workflows.length, read_only: true });
|
||||
})
|
||||
);
|
||||
function recordLibrary(values) {
|
||||
const lib = values?.library;
|
||||
return typeof lib === "string" && lib.trim() ? lib.trim() : null;
|
||||
}
|
||||
function canReadRecord(rec, tenant2, libraries) {
|
||||
if ((rec.owner_id ?? "") !== tenant2) return false;
|
||||
const lib = recordLibrary(rec.values);
|
||||
return lib === null || canReadLibrary(libraries, lib);
|
||||
}
|
||||
portalDataRouter.get(
|
||||
"/portal/data/map",
|
||||
(c) => run(c, async () => {
|
||||
const auth = await requirePortalUser(c);
|
||||
if (!auth.ok) return auth.res;
|
||||
const libraries = parseLibraries(auth.user.values.libraries);
|
||||
if (libraries.length === 0) {
|
||||
return c.json({ success: true, libraries: [], count: 0, note: "\u6B64\u5E33\u865F\u5C1A\u672A\u88AB\u6388\u6B0A\u4EFB\u4F55\u77E5\u8B58\u5EAB\uFF0C\u8ACB\u806F\u7D61\u7BA1\u7406\u54E1\u3002" });
|
||||
}
|
||||
const res = await kbdbFetch(c.env, `/map?owner_id=${encodeURIComponent(portalTenant(c.env))}`);
|
||||
if (!res.ok) {
|
||||
return new Response(res.body, { status: res.status, headers: { "Content-Type": "application/json" } });
|
||||
}
|
||||
const body = await res.json().catch(() => null);
|
||||
if (!body || !Array.isArray(body.libraries)) {
|
||||
return c.json({ error: "\u85CF\u66F8\u5730\u5716\u8B80\u53D6\u5931\u6557\uFF1AKBDB \u56DE\u61C9\u4E0D\u662F\u9810\u671F\u7684 libraries \u6E05\u55AE" }, 502);
|
||||
}
|
||||
const allowed = body.libraries.filter(
|
||||
(l) => typeof l?.library === "string" && canReadLibrary(libraries, l.library)
|
||||
);
|
||||
return c.json({ success: true, libraries: allowed, count: allowed.length });
|
||||
})
|
||||
);
|
||||
portalDataRouter.get(
|
||||
"/portal/data/map/:library",
|
||||
(c) => run(c, async () => {
|
||||
const auth = await requirePortalUser(c);
|
||||
if (!auth.ok) return auth.res;
|
||||
const libraries = parseLibraries(auth.user.values.libraries);
|
||||
const library = c.req.param("library");
|
||||
if (!canReadLibrary(libraries, library)) return notFound(c);
|
||||
const res = await kbdbFetch(
|
||||
c.env,
|
||||
`/map/${encodeURIComponent(library)}?owner_id=${encodeURIComponent(portalTenant(c.env))}`
|
||||
);
|
||||
if (res.status === 404) return notFound(c);
|
||||
if (!res.ok) return c.json({ error: `KBDB \u56DE\u932F\uFF08HTTP ${res.status}\uFF09` }, 502);
|
||||
return new Response(res.body, { status: 200, headers: { "Content-Type": "application/json" } });
|
||||
})
|
||||
);
|
||||
portalDataRouter.get(
|
||||
"/portal/data/templates",
|
||||
(c) => run(c, async () => {
|
||||
const auth = await requirePortalUser(c);
|
||||
if (!auth.ok) return auth.res;
|
||||
const res = await kbdbFetch(c.env, "/templates");
|
||||
if (!res.ok) return c.json({ error: `KBDB \u56DE\u932F\uFF08HTTP ${res.status}\uFF09` }, 502);
|
||||
return new Response(res.body, { status: 200, headers: { "Content-Type": "application/json" } });
|
||||
})
|
||||
);
|
||||
portalDataRouter.post(
|
||||
"/portal/data/templates",
|
||||
(c) => run(c, async () => {
|
||||
const auth = await requirePortalUser(c);
|
||||
if (!auth.ok) return auth.res;
|
||||
const body = await c.req.json().catch(() => null);
|
||||
if (!body || typeof body.name !== "string" || !body.name.trim() || !Array.isArray(body.slots)) {
|
||||
return c.json({ error: "name \u8207 slots[] \u5FC5\u586B" }, 400);
|
||||
}
|
||||
const res = await kbdbFetch(c.env, "/templates", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
name: body.name,
|
||||
slots: body.slots,
|
||||
description: typeof body.description === "string" ? body.description : void 0,
|
||||
created_by: portalTenant(c.env)
|
||||
})
|
||||
});
|
||||
return new Response(res.body, { status: res.status, headers: { "Content-Type": "application/json" } });
|
||||
})
|
||||
);
|
||||
portalDataRouter.get(
|
||||
"/portal/data/records/by-template/:template",
|
||||
(c) => run(c, async () => {
|
||||
const auth = await requirePortalUser(c);
|
||||
if (!auth.ok) return auth.res;
|
||||
const libraries = parseLibraries(auth.user.values.libraries);
|
||||
if (libraries.length === 0) return c.json({ success: true, records: [], count: 0 });
|
||||
const tenant2 = portalTenant(c.env);
|
||||
const res = await kbdbFetch(
|
||||
c.env,
|
||||
`/records/by-template/${encodeURIComponent(c.req.param("template"))}?owner_id=${encodeURIComponent(tenant2)}`
|
||||
);
|
||||
if (!res.ok) return c.json({ error: `KBDB \u56DE\u932F\uFF08HTTP ${res.status}\uFF09` }, 502);
|
||||
const body = await res.json().catch(() => null);
|
||||
if (!body || !Array.isArray(body.records)) {
|
||||
return c.json({ error: "record \u8B80\u53D6\u5931\u6557\uFF1AKBDB \u56DE\u61C9\u4E0D\u662F\u9810\u671F\u7684 records \u6E05\u55AE" }, 502);
|
||||
}
|
||||
const records = body.records.filter((r) => canReadRecord(r, tenant2, libraries));
|
||||
return c.json({ success: true, records, count: records.length });
|
||||
})
|
||||
);
|
||||
portalDataRouter.get(
|
||||
"/portal/data/records/:recordId",
|
||||
(c) => run(c, async () => {
|
||||
const auth = await requirePortalUser(c);
|
||||
if (!auth.ok) return auth.res;
|
||||
const libraries = parseLibraries(auth.user.values.libraries);
|
||||
if (libraries.length === 0) return notFound(c);
|
||||
const res = await kbdbFetch(c.env, `/records/${encodeURIComponent(c.req.param("recordId"))}`);
|
||||
if (res.status === 404) return notFound(c);
|
||||
if (!res.ok) return c.json({ error: `KBDB \u56DE\u932F\uFF08HTTP ${res.status}\uFF09` }, 502);
|
||||
const body = await res.json().catch(() => null);
|
||||
const record = body?.record;
|
||||
if (!record) return notFound(c);
|
||||
if (!canReadRecord(record, portalTenant(c.env), libraries)) return notFound(c);
|
||||
return c.json({ success: true, record });
|
||||
})
|
||||
);
|
||||
portalDataRouter.post(
|
||||
"/portal/data/records",
|
||||
(c) => run(c, async () => {
|
||||
const auth = await requirePortalUser(c);
|
||||
if (!auth.ok) return auth.res;
|
||||
const libraries = parseLibraries(auth.user.values.libraries);
|
||||
if (libraries.length === 0) {
|
||||
return c.json({ error: "\u6B64\u5E33\u865F\u5C1A\u672A\u88AB\u6388\u6B0A\u4EFB\u4F55\u77E5\u8B58\u5EAB\uFF0C\u7121\u6CD5\u5BEB\u5165" }, 403);
|
||||
}
|
||||
const body = await c.req.json().catch(() => null);
|
||||
if (!body || typeof body.template !== "string" || !body.template.trim() || !body.values || typeof body.values !== "object") {
|
||||
return c.json({ error: "template \u8207 values \u5FC5\u586B" }, 400);
|
||||
}
|
||||
const values = body.values;
|
||||
const targetLib = recordLibrary(values);
|
||||
if (targetLib !== null && !canReadLibrary(libraries, targetLib)) {
|
||||
return c.json({ error: `\u7121\u300C${targetLib}\u300D\u5EAB\u7684\u6B0A\u9650\uFF0C\u4E0D\u80FD\u5BEB\u5165\u8A72\u5EAB` }, 403);
|
||||
}
|
||||
const res = await kbdbFetch(c.env, "/records", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ template: body.template, values, owner_id: portalTenant(c.env) })
|
||||
});
|
||||
return new Response(res.body, { status: res.status, headers: { "Content-Type": "application/json" } });
|
||||
})
|
||||
);
|
||||
portalDataRouter.get(
|
||||
"/portal/data/diagnostics",
|
||||
(c) => run(c, async () => {
|
||||
|
||||
@@ -3281,7 +3281,7 @@ async function createRecord(db, input) {
|
||||
});
|
||||
await db.prepare(`INSERT INTO entry_values (id, record_id, template_id, slot_name, entry_id) VALUES (?, ?, ?, ?, ?)`).bind(uid2("ev"), recordId, tpl.id, slot, entry.id).run();
|
||||
}
|
||||
return { record_id: recordId, template_id: tpl.id, values: input.values };
|
||||
return { record_id: recordId, template_id: tpl.id, values: input.values, owner_id: input.owner_id ?? null };
|
||||
}
|
||||
async function updateRecord(db, recordId, values) {
|
||||
const evRes = await db.prepare(
|
||||
@@ -3312,7 +3312,7 @@ async function updateRecord(db, recordId, values) {
|
||||
}
|
||||
async function getRecord(db, recordId) {
|
||||
const res = await db.prepare(
|
||||
`SELECT ev.slot_name as slot, e.content as content, ev.template_id as template_id
|
||||
`SELECT ev.slot_name as slot, e.content as content, ev.template_id as template_id, e.owner_id as owner_id
|
||||
FROM entry_values ev JOIN entries e ON ev.entry_id = e.id
|
||||
WHERE ev.record_id = ?`
|
||||
).bind(recordId).all();
|
||||
@@ -3320,7 +3320,8 @@ async function getRecord(db, recordId) {
|
||||
if (rows.length === 0) return null;
|
||||
const values = {};
|
||||
for (const r of rows) values[r.slot] = r.content;
|
||||
return { record_id: recordId, template_id: rows[0].template_id, values };
|
||||
const owner_id = rows.find((r) => r.owner_id != null)?.owner_id ?? null;
|
||||
return { record_id: recordId, template_id: rows[0].template_id, values, owner_id };
|
||||
}
|
||||
async function searchByTemplate(db, template, owner_id, limit = 100) {
|
||||
const tpl = await getTemplate(db, template);
|
||||
@@ -3339,17 +3340,18 @@ async function searchByTemplate(db, template, owner_id, limit = 100) {
|
||||
const chunk = ids.slice(i, i + 90);
|
||||
const placeholders = chunk.map(() => "?").join(",");
|
||||
const evRes = await db.prepare(
|
||||
`SELECT ev.record_id as record_id, ev.slot_name as slot, e.content as content, ev.template_id as template_id
|
||||
`SELECT ev.record_id as record_id, ev.slot_name as slot, e.content as content, ev.template_id as template_id, e.owner_id as owner_id
|
||||
FROM entry_values ev JOIN entries e ON ev.entry_id = e.id
|
||||
WHERE ev.record_id IN (${placeholders})`
|
||||
).bind(...chunk).all();
|
||||
for (const r of evRes.results ?? []) {
|
||||
let rec = byId.get(r.record_id);
|
||||
if (!rec) {
|
||||
rec = { record_id: r.record_id, template_id: r.template_id, values: {} };
|
||||
rec = { record_id: r.record_id, template_id: r.template_id, values: {}, owner_id: null };
|
||||
byId.set(r.record_id, rec);
|
||||
}
|
||||
rec.values[r.slot] = r.content;
|
||||
if (rec.owner_id == null && r.owner_id != null) rec.owner_id = r.owner_id;
|
||||
}
|
||||
}
|
||||
return ids.map((id) => byId.get(id)).filter((r) => !!r);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,18 +1,18 @@
|
||||
{
|
||||
"schema": 1,
|
||||
"built_for": "arcrun-tier2-worker-artifacts",
|
||||
"generated_at": "2026-08-12T07:29:58.626Z",
|
||||
"repo_head": "1791ffa4972b4135dacd4208e805f67b747479c4",
|
||||
"generated_at": "2026-08-12T12:02:15.698Z",
|
||||
"repo_head": "89b80ff90e95f07b5d44978ef58090ac783f53bd",
|
||||
"repo_dirty": false,
|
||||
"workers": [
|
||||
{
|
||||
"name": "arcrun-cypher-executor",
|
||||
"source_dir": "cypher-executor",
|
||||
"source_commit": "f1370e2275eea62b64a88821a096f2c2cfe76fb0",
|
||||
"source_commit": "10d150ac2b4385af95a457f3c411430c4a146cf9",
|
||||
"main_module": "worker.mjs",
|
||||
"main_file": "arcrun-cypher-executor/worker.mjs",
|
||||
"js_bytes": 577374,
|
||||
"content_sha256": "8411ed59b7ad9e1a74ac0d8e3b620d7166e7d0178ac5939e6cc736f2e8d1d2be",
|
||||
"js_bytes": 584610,
|
||||
"content_sha256": "6a424274ecf7beb28b747672296c4c33f949c6d7b134e8054e640e92198910ba",
|
||||
"modules": [],
|
||||
"compat_date": "2025-02-19",
|
||||
"compat_flags": [
|
||||
@@ -58,11 +58,11 @@
|
||||
{
|
||||
"name": "arcrun-kbdb",
|
||||
"source_dir": "kbdb",
|
||||
"source_commit": "c497ec418eba6cd94b1d5872671c51fd5812c11c",
|
||||
"source_commit": "10d150ac2b4385af95a457f3c411430c4a146cf9",
|
||||
"main_module": "worker.mjs",
|
||||
"main_file": "arcrun-kbdb/worker.mjs",
|
||||
"js_bytes": 149533,
|
||||
"content_sha256": "ffb8d43467d0cefbd7545fdc0d347f2b965e3c3de20b3315eed7613f20266891",
|
||||
"js_bytes": 149797,
|
||||
"content_sha256": "8b23853cbc88aee0ca15ef20ca46e92bd8e75064cd311af2847f4d51811960b1",
|
||||
"modules": [],
|
||||
"compat_date": "2025-02-19",
|
||||
"compat_flags": [
|
||||
@@ -148,11 +148,11 @@
|
||||
{
|
||||
"name": "arcrun-mcp",
|
||||
"source_dir": "mcp",
|
||||
"source_commit": "035e8b255b0dcbd4238707f7d2ac8ccf9ee1ba72",
|
||||
"source_commit": "10d150ac2b4385af95a457f3c411430c4a146cf9",
|
||||
"main_module": "worker.mjs",
|
||||
"main_file": "arcrun-mcp/worker.mjs",
|
||||
"js_bytes": 1165388,
|
||||
"content_sha256": "c5ff10f9b9d5a77217be343af12d2be3ee8f9792d3e1e091e48e5e6c8d24ca9d",
|
||||
"js_bytes": 1179487,
|
||||
"content_sha256": "1cd4c4d079d72bf7cba7c490ba6a88476f70b3ea51af7e5c93f9a184ae3c0ce6",
|
||||
"modules": [],
|
||||
"compat_date": "2024-11-27",
|
||||
"compat_flags": [
|
||||
|
||||
Reference in New Issue
Block a user